Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/support arb tokens on coingecko endpoint #1278

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
36 changes: 31 additions & 5 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 @@ -1395,6 +1405,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 @@ -2120,9 +2146,9 @@ export function getCachedFillGasUsage(
const { nativeGasCost } = await relayerFeeCalculatorQueries.getGasCosts(
buildDepositForSimulation(deposit),
overrides?.relayerAddress,
undefined,
undefined,
true
{
omitMarkup: true,
}
);
return nativeGasCost;
};
Expand Down
47 changes: 32 additions & 15 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 {
CHAIN_IDs,
Expand All @@ -28,7 +30,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 @@ -46,13 +50,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 @@ -74,8 +90,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 @@ -90,17 +106,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 @@ -116,15 +132,16 @@ const handler = async (
const modifiedBaseCurrency = isDerivedCurrency ? "usd" : baseCurrency;
if (dateStr) {
price = await coingeckoClient.getContractHistoricDayPrice(
l1Token,
address,
dateStr,
modifiedBaseCurrency
modifiedBaseCurrency,
chainId
);
} else {
[, price] = await coingeckoClient.getCurrentPriceByContract(
l1Token,
address,
modifiedBaseCurrency,
platformId
chainId
);
}
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"@across-protocol/constants": "^3.1.16",
"@across-protocol/contracts": "^3.0.11",
"@across-protocol/contracts-v3.0.6": "npm:@across-protocol/[email protected]",
"@across-protocol/sdk": "^3.2.9",
"@across-protocol/sdk": "^3.3.18",
"@amplitude/analytics-browser": "^2.3.5",
"@balancer-labs/sdk": "1.1.6-beta.16",
"@emotion/react": "^11.13.0",
Expand Down
Loading
Loading