Skip to content

Commit

Permalink
Feat/support arb tokens on coingecko endpoint (#1278)
Browse files Browse the repository at this point in the history
* add utility for parsing query params with coercion for non string values

* extend api to accept tokenAdress & chainId

* bump sdk version

* bump sdk, fix build error

* small refactor

* update sdk
  • Loading branch information
gsteenkamp89 committed Nov 29, 2024
1 parent 3e72a3d commit 44108c2
Show file tree
Hide file tree
Showing 4 changed files with 451 additions and 50 deletions.
4 changes: 2 additions & 2 deletions api/_constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ defaultRelayerFeeCapitalCostConfig["USDB"] = {
...defaultRelayerFeeCapitalCostConfig["DAI"],
};

export const coinGeckoAssetPlatformLookup: Record<string, string> = {
"0x4200000000000000000000000000000000000042": "optimistic-ethereum",
export const coinGeckoAssetPlatformLookup: Record<string, number> = {
"0x4200000000000000000000000000000000000042": CHAIN_IDs.OPTIMISM,
};

export const defaultRelayerAddressOverride: {
Expand Down
35 changes: 32 additions & 3 deletions api/_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,17 @@ import {
utils,
Signer,
} from "ethers";
import { define } from "superstruct";
import {
assert,
coerce,
create,
define,
Infer,
integer,
min,
string,
Struct,
} from "superstruct";

import enabledMainnetRoutesAsJson from "../src/data/routes_1_0xc186fA914353c44b2E33eBE05f21846F1048bEda.json";
import enabledSepoliaRoutesAsJson from "../src/data/routes_11155111_0x14224e63716afAcE30C9a417E0542281869f7d9e.json";
Expand All @@ -36,7 +46,7 @@ import {
} from "./_abis";
import { BatchAccountBalanceResponse } from "./batch-account-balance";
import { StaticJsonRpcProvider } from "@ethersproject/providers";
import { VercelResponse } from "@vercel/node";
import { VercelRequestQuery, VercelResponse } from "@vercel/node";
import {
BLOCK_TAG_LAG,
CHAIN_IDs,
Expand Down Expand Up @@ -1182,6 +1192,22 @@ export function applyMapFilter<InputType, MapType>(
}, []);
}

// superstruct first coerces, then validates
export const positiveInt = coerce(min(integer(), 0), string(), (value) =>
Number(value)
);

// parses, coerces and validates query params
// first coerce any fields that can be coerced, then validate
export function parseQuery<
Q extends VercelRequestQuery,
S extends Struct<any, any>,
>(query: Q, schema: S): Infer<S> {
const coerced = create(query, schema);
assert(coerced, schema);
return coerced;
}

/* ------------------------- superstruct validators ------------------------- */

export function parsableBigNumberString() {
Expand Down Expand Up @@ -1907,7 +1933,10 @@ export function getCachedFillGasUsage(
);
const { nativeGasCost } = await relayerFeeCalculatorQueries.getGasCosts(
buildDepositForSimulation(deposit),
overrides?.relayerAddress
overrides?.relayerAddress,
{
omitMarkup: true,
}
);
return nativeGasCost;
};
Expand Down
51 changes: 34 additions & 17 deletions api/coingecko.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { VercelResponse } from "@vercel/node";
import { ethers } from "ethers";
import { object, assert, Infer, optional, string, pattern } from "superstruct";
import { object, Infer, optional, string, pattern } from "superstruct";
import { TypedVercelRequest } from "./_types";
import {
getLogger,
handleErrorCondition,
validAddress,
getBalancerV2TokenPrice,
getCachedTokenPrice,
parseQuery,
positiveInt,
} from "./_utils";
import {
CG_CONTRACTS_DEFERRED_TO_ID,
Expand All @@ -29,7 +31,9 @@ const {
} = process.env;

const CoingeckoQueryParamsSchema = object({
l1Token: validAddress(),
l1Token: optional(validAddress()),
tokenAddress: optional(validAddress()),
chainId: optional(positiveInt),
baseCurrency: optional(string()),
date: optional(pattern(string(), /\d{2}-\d{2}-\d{4}/)),
});
Expand All @@ -47,13 +51,25 @@ const handler = async (
query,
});
try {
assert(query, CoingeckoQueryParamsSchema);

let { l1Token, baseCurrency, date: dateStr } = query;
let {
l1Token,
tokenAddress,
chainId,
baseCurrency,
date: dateStr,
} = parseQuery(query, CoingeckoQueryParamsSchema);

let address = l1Token ?? tokenAddress;
if (!address) {
throw new InvalidParamError({
message: `Token Address is undefined. You must define either "l1Token", or "tokenAddress"`,
param: "l1Token, tokenAddress",
});
}

// Format the params for consistency
baseCurrency = (baseCurrency ?? "eth").toLowerCase();
l1Token = ethers.utils.getAddress(l1Token);
address = ethers.utils.getAddress(address);

// Confirm that the base Currency is supported by Coingecko
const isDerivedCurrency = SUPPORTED_CG_DERIVED_CURRENCIES.has(baseCurrency);
Expand All @@ -75,8 +91,8 @@ const handler = async (

// Perform a 1-deep lookup to see if the provided l1Token is
// to be "redirected" to another provided token contract address
if (redirectLookupAddresses[l1Token]) {
l1Token = redirectLookupAddresses[l1Token];
if (redirectLookupAddresses[address]) {
address = redirectLookupAddresses[address];
}

const coingeckoClient = Coingecko.get(
Expand All @@ -91,17 +107,17 @@ const handler = async (
BALANCER_V2_TOKENS ?? "[]"
).map(ethers.utils.getAddress);

const platformId = coinGeckoAssetPlatformLookup[l1Token] ?? "ethereum";
chainId = coinGeckoAssetPlatformLookup[address] ?? chainId ?? 1;

if (balancerV2PoolTokens.includes(ethers.utils.getAddress(l1Token))) {
if (balancerV2PoolTokens.includes(ethers.utils.getAddress(address))) {
if (dateStr) {
throw new InvalidParamError({
message: "Historical price not supported for BalancerV2 tokens",
param: "date",
});
}
if (baseCurrency === "usd") {
price = await getBalancerV2TokenPrice(l1Token);
price = await getBalancerV2TokenPrice(address);
} else {
throw new InvalidParamError({
message: "Only CG base currency allowed for BalancerV2 tokens is usd",
Expand All @@ -117,20 +133,21 @@ const handler = async (
const modifiedBaseCurrency = isDerivedCurrency ? "usd" : baseCurrency;
if (dateStr) {
price = await coingeckoClient.getContractHistoricDayPrice(
l1Token,
address,
dateStr,
modifiedBaseCurrency
modifiedBaseCurrency,
chainId
);
} else {
[, price] = CG_CONTRACTS_DEFERRED_TO_ID.has(l1Token)
[, price] = CG_CONTRACTS_DEFERRED_TO_ID.has(address)
? await coingeckoClient.getCurrentPriceById(
l1Token,
address,
modifiedBaseCurrency
)
: await coingeckoClient.getCurrentPriceByContract(
l1Token,
address,
modifiedBaseCurrency,
platformId
chainId
);
}
}
Expand Down
Loading

0 comments on commit 44108c2

Please sign in to comment.