diff --git a/test/lib/apiTokenPriceService.ts b/test/lib/apiTokenPriceService.ts new file mode 100644 index 00000000..6346cda9 --- /dev/null +++ b/test/lib/apiTokenPriceService.ts @@ -0,0 +1,67 @@ +import { gql, request } from 'graphql-request'; +import { TokenPriceService } from '../../src'; +import { Network } from '../testScripts/constants'; + +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 { + 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', +} diff --git a/test/lib/constants.ts b/test/lib/constants.ts index cc958ff0..10ae8d82 100644 --- a/test/lib/constants.ts +++ b/test/lib/constants.ts @@ -56,11 +56,11 @@ export const sorConfigEth: SorConfig = { weth: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', connectingTokens: [ { - symbol: 'wstEth', + symbol: 'wETH', address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', }, { - symbol: 'wEth', + symbol: 'wstETH', address: '0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0', }, ], diff --git a/test/testScripts/constants.ts b/test/testScripts/constants.ts index 1905c381..24d6c333 100644 --- a/test/testScripts/constants.ts +++ b/test/testScripts/constants.ts @@ -31,6 +31,17 @@ export const SOR_CONFIG: Record = { symbol: 'wstEth', address: '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', }, + { + symbol: 'rEth', + address: '0xae78736cd615f374d3085123a210448e74fc6393', + }, + { + symbol: 'ethX', + address: '0xa35b1b31ce002fbf2058d22f30f95d405200a15b', + }, + ], + triPathMidPoolIds: [ + '0x1e19cf2d73a72ef1332c882f20534b6519be0276000200000000000000000112', // rETH/WETH ], wETHwstETH: { id: '0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080', @@ -369,6 +380,31 @@ export const ADDRESSES = { decimals: 18, symbol: 'swETH', }, + BAL80WETH20: { + address: '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56', + decimals: 18, + symbol: 'BAL80WETH20', + }, + vETH: { + address: '0x4bc3263eb5bb2ef7ad9ab6fb68be80e43b43801f', + decimals: 18, + symbol: 'vETH', + }, + vETH_vector: { + address: '0x38d64ce1bdf1a9f24e0ec469c9cade61236fb4a0', + decimals: 18, + symbol: 'vETH_vector', + }, + ezETH: { + address: '0xbf5495efe5db9ce00f80364c8b423567e58d2110', + decimals: 18, + symbol: 'ezETH', + }, + weETH: { + address: '0xcd5fe23c85820f7b72d0926fc9b05b43e359b7ee', + decimals: 18, + symbol: 'weETH', + }, }, [Network.POLYGON]: { MATIC: { @@ -498,11 +534,21 @@ export const ADDRESSES = { decimals: 6, symbol: 'USDC', }, + USDT: { + address: '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9', + decimals: 6, + symbol: 'USDT', + }, STETH: { address: 'N/A', decimals: 18, symbol: 'STETH', }, + sfrxETH: { + address: '0x95ab45875cffdba1e5f451b950bc2e42c0053f39', + decimals: 18, + symbol: 'sfrxETH', + }, }, [Network.GNOSIS]: { WETH: { diff --git a/test/testScripts/swapExample.ts b/test/testScripts/swapExample.ts index 7518eb7a..886df90e 100644 --- a/test/testScripts/swapExample.ts +++ b/test/testScripts/swapExample.ts @@ -1,6 +1,6 @@ // Example using SOR to find the best swap for a given pair and simulate using batchSwap. // Requires TRADER_KEY in .env. -// Run using: $ TS_NODE_PROJECT='tsconfig.testing.json' ts-node ./test/testScripts/swapExample.ts +// Run using: yarn example ./test/testScripts/swapExample.ts // NOTE: This is for test/debug purposes, the Balancer SDK Swaps module has a more user friendly interface for interacting with SOR: // https://github.com/balancer-labs/balancer-sdk/tree/develop/balancer-js#swaps-module import dotenv from 'dotenv'; @@ -10,7 +10,6 @@ import { JsonRpcProvider } from '@ethersproject/providers'; import { Wallet } from '@ethersproject/wallet'; import { Contract } from '@ethersproject/contracts'; import { SOR, SwapInfo, SwapTypes } from '../../src'; -import { CoingeckoTokenPriceService } from '../lib/coingeckoTokenPriceService'; import { SubgraphPoolDataService } from '../lib/subgraphPoolDataService'; import { Network, @@ -24,6 +23,7 @@ import { import { buildTx, printOutput } from './utils'; import vaultArtifact from '../../src/abi/Vault.json'; +import { ApiTokenPriceService } from '../lib/apiTokenPriceService'; // Setup SOR with data services function setUp(networkId: Network, provider: JsonRpcProvider): SOR { @@ -42,9 +42,10 @@ function setUp(networkId: Network, provider: JsonRpcProvider): SOR { // mockPoolDataService.setPools(poolsSource); // Use coingecko to fetch token price information. Used to calculate cost of additonal swaps/hops. - const coingeckoTokenPriceService = new CoingeckoTokenPriceService( - networkId - ); + // const coingeckoTokenPriceService = new CoingeckoTokenPriceService( + // networkId + // ); + const apiTokenPriceService = new ApiTokenPriceService(networkId); // Use the mock token price service if you want to manually set the token price in native asset // import { mockPoolDataService } from '../lib/mockPoolDataService'; // mockTokenPriceService.setTokenPrice('0.001'); @@ -53,22 +54,22 @@ function setUp(networkId: Network, provider: JsonRpcProvider): SOR { provider, SOR_CONFIG[networkId], subgraphPoolDataService, - coingeckoTokenPriceService + apiTokenPriceService ); } export async function swap(): Promise { - const networkId = Network.GNOSIS; + const networkId = Network.MAINNET; const provider = new JsonRpcProvider(PROVIDER_URLS[networkId]); // gasPrice is used by SOR as a factor to determine how many pools to swap against. // i.e. higher cost means more costly to trade against lots of different pools. const gasPrice = BigNumber.from('14000000000'); // This determines the max no of pools the SOR will use to swap. const maxPools = 4; - const tokenIn = ADDRESSES[networkId].WXDAI; - const tokenOut = ADDRESSES[networkId].crvUSD; + const tokenIn = ADDRESSES[networkId].BAL80WETH20; + const tokenOut = ADDRESSES[networkId].USDC; const swapType: SwapTypes = SwapTypes.SwapExactIn; - const swapAmount = parseFixed('200', 18); + const swapAmount = parseFixed('17', tokenIn.decimals); const sor = setUp(networkId, provider); @@ -81,8 +82,8 @@ export async function swap(): Promise { tokenOut.address, swapType, swapAmount, - { gasPrice, maxPools }, - false + undefined, + true ); // Simulate the swap transaction @@ -103,41 +104,41 @@ export async function swap(): Promise { tx.limits ); - if (![tokenIn, tokenOut].includes(ADDRESSES[networkId].STETH)) { - console.log('VAULT SWAP'); - const vaultContract = new Contract( - vaultAddr, - vaultArtifact, - provider - ); - // Simulates a call to `batchSwap`, returning an array of Vault asset deltas. - // Each element in the array corresponds to the asset at the same index, and indicates the number of tokens(or ETH) - // the Vault would take from the sender(if positive) or send to the recipient(if negative). - const deltas = await vaultContract.queryBatchSwap( - swapType, - swapInfo.swaps, - swapInfo.tokenAddresses, - tx.funds - ); - console.log(deltas.toString()); - // To actually make the trade: - // vaultContract.connect(wallet); - // const tx = await vaultContract - // .connect(wallet) - // .batchSwap( - // swapType, - // swapInfo.swaps, - // swapInfo.tokenAddresses, - // tx.funds, - // tx.limits, - // tx.deadline, - // tx.overRides - // ); + // if (![tokenIn, tokenOut].includes(ADDRESSES[networkId].STETH)) { + // console.log('VAULT SWAP'); + // const vaultContract = new Contract( + // vaultAddr, + // vaultArtifact, + // provider + // ); + // // Simulates a call to `batchSwap`, returning an array of Vault asset deltas. + // // Each element in the array corresponds to the asset at the same index, and indicates the number of tokens(or ETH) + // // the Vault would take from the sender(if positive) or send to the recipient(if negative). + // const deltas = await vaultContract.queryBatchSwap( + // swapType, + // swapInfo.swaps, + // swapInfo.tokenAddresses, + // tx.funds + // ); + // console.log(deltas.toString()); + // // To actually make the trade: + // // vaultContract.connect(wallet); + // // const tx = await vaultContract + // // .connect(wallet) + // // .batchSwap( + // // swapType, + // // swapInfo.swaps, + // // swapInfo.tokenAddresses, + // // tx.funds, + // // tx.limits, + // // tx.deadline, + // // tx.overRides + // // ); - // console.log(`tx: ${tx}`); - } else { - console.log('RELAYER SWAP - Execute via batchRelayer.'); - } + // // console.log(`tx: ${tx}`); + // } else { + // console.log('RELAYER SWAP - Execute via batchRelayer.'); + // } } else { console.log('No Valid Swap'); await printOutput(