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

Replacing CoingeckoTokenPriceService by ApiTokenPriceService to fetch… #564

25 changes: 25 additions & 0 deletions balancer-js/examples/data/api-token-price-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Display APRs for pool ids hardcoded under `const ids`
* Run command: yarn example ./examples/data/token-prices.ts
*/
import { ApiTokenPriceService } from '@/modules/sor/token-price/apiTokenPriceService';

const dai = '0x6b175474e89094c44da98b954eedeac495271d0f';
const weth = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
const ohm = '0X64AA3364F17A4D01C6F1751FD97C2BD3D7E7F1D5';

(async () => {
const apiTokenPriceService = new ApiTokenPriceService(1);
const daiPriceInEth = await apiTokenPriceService.getNativeAssetPriceInToken(
dai
);
console.log('Dai Price In ETH: ' + daiPriceInEth);
const wethPriceInEth = await apiTokenPriceService.getNativeAssetPriceInToken(
weth
);
console.log('WETH Price In ETH: ' + wethPriceInEth);
const ohmPriceInEth = await apiTokenPriceService.getNativeAssetPriceInToken(
ohm
);
console.log('OHM Price In ETH: ' + ohmPriceInEth);
})();
18 changes: 12 additions & 6 deletions balancer-js/src/modules/data/token-prices/coingecko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class CoingeckoPriceRepository implements Findable<Price> {
prices: { [key: string]: Promise<Price> } = {};
nativePrice?: Promise<Price>;
urlBase: string;
urlBaseNative: string;
baseTokenAddresses: string[];
debouncer: Debouncer<TokenPrices, string>;
apiKey?: string;
Expand All @@ -27,9 +28,14 @@ export class CoingeckoPriceRepository implements Findable<Price> {
coingecko?: CoingeckoConfig
) {
this.baseTokenAddresses = tokenAddresses.map(tokenAddressForPricing);
this.urlBase = `https://api.coingecko.com/api/v3/simple/token_price/${this.platform(
this.urlBase = `https://${
coingecko?.coingeckoApiKey && !coingecko.isDemoApiKey ? 'pro-' : ''
}api.coingecko.com/api/v3/simple/token_price/${this.platform(
chainId
)}?vs_currencies=usd,eth`;
this.urlBaseNative = `https://${
coingecko?.coingeckoApiKey && !coingecko.isDemoApiKey ? 'pro-' : ''
}api.coingecko.com/api/v3/simple/price/?vs_currencies=eth,usd&ids=`;
this.apiKey = coingecko?.coingeckoApiKey;
this.debouncer = new Debouncer<TokenPrices, string>(
this.fetch.bind(this),
Expand All @@ -45,7 +51,7 @@ export class CoingeckoPriceRepository implements Findable<Price> {
try {
const { data } = await axios.get<TokenPrices>(this.url(addresses), {
signal,
headers: { ApiKey: this.apiKey ?? '' },
headers: { 'x-cg-pro-api-key': this.apiKey ?? '' },
});
return data;
} catch (error) {
Expand Down Expand Up @@ -74,10 +80,10 @@ export class CoingeckoPriceRepository implements Findable<Price> {
if (this.chainId === 137) assetId = Assets.MATIC;
if (this.chainId === 100) assetId = Assets.XDAI;
return axios
.get<{ [key in Assets]: Price }>(
`https://api.coingecko.com/api/v3/simple/price/?vs_currencies=eth,usd&ids=${assetId}`,
{ signal }
)
.get<{ [key in Assets]: Price }>(`${this.urlBaseNative}${assetId}`, {
signal,
headers: { 'x-cg-pro-api-key': this.apiKey ?? '' },
})
.then(({ data }) => {
return data[assetId];
})
Expand Down
20 changes: 13 additions & 7 deletions balancer-js/src/modules/sor/sor.module.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { SOR, SorConfig, TokenPriceService } from '@balancer-labs/sor';
import { Provider, JsonRpcProvider } from '@ethersproject/providers';
import { SubgraphPoolDataService } from './pool-data/subgraphPoolDataService';
import { CoingeckoTokenPriceService } from './token-price/coingeckoTokenPriceService';
import {
SubgraphClient,
createSubgraphClient,
Expand All @@ -10,10 +9,13 @@ import {
BalancerNetworkConfig,
BalancerSdkConfig,
BalancerSdkSorConfig,
CoingeckoConfig,
} from '@/types';
import { SubgraphTokenPriceService } from './token-price/subgraphTokenPriceService';
import { getNetworkConfig } from '@/modules/sdk.helpers';
import { POOLS_TO_IGNORE } from '@/lib/constants/poolsToIgnore';
import { ApiTokenPriceService } from '@/modules/sor/token-price/apiTokenPriceService';
import { CoingeckoTokenPriceService } from '@/modules/sor/token-price/coingeckoTokenPriceService';

export class Sor extends SOR {
constructor(sdkConfig: BalancerSdkConfig) {
Expand All @@ -36,15 +38,16 @@ export class Sor extends SOR {
const tokenPriceService = Sor.getTokenPriceService(
network,
sorConfig,
subgraphClient
subgraphClient,
sdkConfig.coingecko
);

super(provider, sorNetworkConfig, poolDataService, tokenPriceService);
}

private static getSorConfig(config: BalancerSdkConfig): BalancerSdkSorConfig {
return {
tokenPriceService: 'coingecko',
tokenPriceService: 'api',
poolDataService: 'subgraph',
fetchOnChainBalances: true,
...config.sor,
Expand Down Expand Up @@ -89,17 +92,20 @@ export class Sor extends SOR {
private static getTokenPriceService(
network: BalancerNetworkConfig,
sorConfig: BalancerSdkSorConfig,
subgraphClient: SubgraphClient
subgraphClient: SubgraphClient,
coingeckoConfig?: CoingeckoConfig
): TokenPriceService {
if (sorConfig.tokenPriceService === 'coingecko' && coingeckoConfig) {
return new CoingeckoTokenPriceService(network.chainId, coingeckoConfig);
}
if (typeof sorConfig.tokenPriceService === 'object') {
return sorConfig.tokenPriceService;
} else if (sorConfig.tokenPriceService === 'subgraph') {
new SubgraphTokenPriceService(
return new SubgraphTokenPriceService(
subgraphClient,
network.addresses.tokens.wrappedNativeAsset
);
}

return new CoingeckoTokenPriceService(network.chainId);
return new ApiTokenPriceService(network.chainId);
brunoguerios marked this conversation as resolved.
Show resolved Hide resolved
}
}
64 changes: 64 additions & 0 deletions balancer-js/src/modules/sor/token-price/apiTokenPriceService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { TokenPriceService } from '@balancer-labs/sor';
import { gql, request } from 'graphql-request';
import { Network } from '@/types';

export class ApiTokenPriceService implements TokenPriceService {
private chainKey: string;

private balancerApiUrl = 'https://api-v3.balancer.fi/';

private tokenPriceQuery = gql`
query queryTokenPrices($chainKey: GqlChain!) {
tokenGetCurrentPrices(chains: [$chainKey]) {
address
price
}
}
`;

constructor(private readonly chainId: number) {
this.chainKey = Network[chainId];
}
async getNativeAssetPriceInToken(tokenAddress: string): Promise<string> {
const { tokenGetCurrentPrices: tokenPrices } = await request(
this.balancerApiUrl,
this.tokenPriceQuery,
{
chainKey: this.chainKey,
}
);
const tokenPriceUsd = (
tokenPrices as { address: string; price: number }[]
).find(
({ address }) => address.toLowerCase() === tokenAddress.toLowerCase()
);
if (!tokenPriceUsd) {
throw new Error('Token Price not found in the API');
}
const nativeAssetPriceUsd = (
tokenPrices as { address: string; price: number }[]
).find(
({ address }) =>
address.toLowerCase() ===
NativeAssetAddress[this.chainKey as keyof typeof NativeAssetAddress]
);
if (!nativeAssetPriceUsd) {
throw new Error('Native Token Price not found in the API');
}
const tokenPriceInNativeAsset =
tokenPriceUsd.price / nativeAssetPriceUsd.price;
return String(tokenPriceInNativeAsset);
}
}

enum NativeAssetAddress {
MAINNET = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
POLYGON = '0x0000000000000000000000000000000000001010',
ARBITRUM = '0x912ce59144191c1204e64559fe8253a0e49e6548',
AVALANCHE = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
BASE = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
FANTOM = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
GNOSIS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
OPTIMISM = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
ZKEVM = '0xa2036f0538221a77a3937f1379699f44945018d0',
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import { TokenPriceService } from '@balancer-labs/sor';
import axios from 'axios';
import { BALANCER_NETWORK_CONFIG } from '@/lib/constants/config';
import { Network, BalancerNetworkConfig } from '@/types';
import { Network, BalancerNetworkConfig, CoingeckoConfig } from '@/types';

export class CoingeckoTokenPriceService implements TokenPriceService {
constructor(private readonly chainId: number) {}
private urlBase: string;
private apiKey: string;
constructor(private readonly chainId: number, coingecko: CoingeckoConfig) {
this.urlBase = `https://${
coingecko?.coingeckoApiKey && !coingecko.isDemoApiKey ? 'pro-' : ''
}api.coingecko.com/api/v3/simple/token_price/${
this.platformId
}?vs_currencies=${this.nativeAssetId}`;
this.apiKey = coingecko.coingeckoApiKey;
}

public async getNativeAssetPriceInToken(
tokenAddress: string
Expand All @@ -22,12 +31,13 @@ export class CoingeckoTokenPriceService implements TokenPriceService {
* @returns the price of 1 ETH in terms of the token base units
*/
async getTokenPriceInNativeAsset(tokenAddress: string): Promise<string> {
const endpoint = `https://api.coingecko.com/api/v3/simple/token_price/${this.platformId}?contract_addresses=${tokenAddress}&vs_currencies=${this.nativeAssetId}`;
const endpoint = `${this.urlBase}&contract_addresses=${tokenAddress}`;

const { data } = await axios.get(endpoint, {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'x-cg-pro-api-key': this.apiKey ?? '',
brunoguerios marked this conversation as resolved.
Show resolved Hide resolved
},
});

Expand Down
2 changes: 1 addition & 1 deletion balancer-js/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export interface BalancerTenderlyConfig {
export interface BalancerSdkSorConfig {
//use a built-in service or provide a custom implementation of a TokenPriceService
//defaults to coingecko
tokenPriceService: 'coingecko' | 'subgraph' | TokenPriceService;
tokenPriceService: 'api' | 'coingecko' | 'subgraph' | TokenPriceService;
//use a built-in service or provide a custom implementation of a PoolDataService
//defaults to subgraph
poolDataService: 'subgraph' | PoolDataService;
Expand Down
Loading