diff --git a/src/api/utils/tradeQuoter.ts b/src/api/utils/tradeQuoter.ts index 20e5beb..e62a4e1 100644 --- a/src/api/utils/tradeQuoter.ts +++ b/src/api/utils/tradeQuoter.ts @@ -354,7 +354,10 @@ export class TradeQuoter { decimals: number, coinPrices: CoinGeckoCoinPrices ): string { - const coinPrice = coinPrices[address][USD_CURRENCY_CODE]; + const coinPrice = (coinPrices[address]) + ? coinPrices[address][USD_CURRENCY_CODE] + : 0; + const normalizedAmount = this.normalizeTokenAmount(amount, decimals) * coinPrice; return new Intl.NumberFormat('en-US', {style: 'currency', currency: 'USD'}).format(normalizedAmount); } @@ -375,7 +378,11 @@ export class TradeQuoter { ): string { const totalGasCost = this.totalGasCost(gasPrice, gas); const chainCurrencyAddress = this.chainCurrencyAddress(chainId); - const coinPrice = coinPrices[chainCurrencyAddress][USD_CURRENCY_CODE]; + + const coinPrice = (coinPrices[chainCurrencyAddress]) + ? coinPrices[chainCurrencyAddress][USD_CURRENCY_CODE] + : 0; + const cost = totalGasCost * coinPrice; // Polygon prices are low - using 4 significant digits here so something besides zero appears @@ -439,13 +446,23 @@ export class TradeQuoter { toTokenDecimals: number, coinPrices: CoinGeckoCoinPrices ): string { - const fromTokenPriceUsd = coinPrices[fromTokenAddress][USD_CURRENCY_CODE]; - const toTokenPriceUsd = coinPrices[toTokenAddress][USD_CURRENCY_CODE]; + const fromTokenPriceUsd = (coinPrices[fromTokenAddress]) + ? (coinPrices[fromTokenAddress])[USD_CURRENCY_CODE] + : 0; + + const toTokenPriceUsd = (coinPrices[toTokenAddress]) + ? coinPrices[toTokenAddress][USD_CURRENCY_CODE] + : 0; const fromTokenTotalUsd = this.normalizeTokenAmount(fromTokenAmount, fromTokenDecimals) * fromTokenPriceUsd; const toTokenTotalUsd = this.normalizeTokenAmount(toTokenAmount, toTokenDecimals) * toTokenPriceUsd; - const slippageRaw = (fromTokenTotalUsd - toTokenTotalUsd) / fromTokenTotalUsd; + let slippageRaw = (fromTokenTotalUsd - toTokenTotalUsd) / fromTokenTotalUsd; + + if (isNaN(slippageRaw)) { + slippageRaw = 0; + } + return this.formatAsPercentage(slippageRaw * 100); } diff --git a/test/api/TradeQuoter.spec.ts b/test/api/TradeQuoter.spec.ts index 7700827..6b58704 100644 --- a/test/api/TradeQuoter.spec.ts +++ b/test/api/TradeQuoter.spec.ts @@ -56,6 +56,7 @@ axios.get.mockImplementation(val => { case fixture.coinGeckoTokenRequestEth: return fixture.coinGeckoTokenResponseEth; case fixture.coinGeckoTokenRequestPoly: return fixture.coinGeckoTokenResponsePoly; case fixture.coinGeckoPricesRequestEth: return fixture.coinGeckoPricesResponseEth; + case fixture.coinGeckoPricesRequestEthEmpty: return fixture.coinGeckoPricesResponseEmpty; case fixture.coinGeckoPricesRequestPoly: return fixture.coinGeckoPricesResponsePoly; case fixture.quickswapRequestPoly: return fixture.quickswapResponsePoly; } @@ -151,6 +152,19 @@ describe('TradeQuoter', () => { const quote = await subject(); expect(quote).to.be.deep.equal(fixture.setTradeQuoteEth); }); + + describe('when coingecko does not return a price', () => { + // Empty quote expects these duplicate tokens (they must be components on Set) + beforeEach(() => { + subjectFromToken = '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2'; + subjectToToken = '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2'; + }); + + it('should generate a trade quote correctly', async () => { + const quote = await subject(); + expect(quote).to.be.deep.equal(fixture.setTradeQuoteEmptyPrice); + }); + }); }); }); diff --git a/test/fixtures/tradeQuote.ts b/test/fixtures/tradeQuote.ts index b09f8c9..a44d423 100644 --- a/test/fixtures/tradeQuote.ts +++ b/test/fixtures/tradeQuote.ts @@ -162,6 +162,11 @@ export const tradeQuoteFixtures = { }, }, + coinGeckoPricesRequestEthEmpty: 'https://api.coingecko.com/api/v3/simple/token_price/ethereum?contract_addresses=0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2,0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2,0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2&vs_currencies=usd,usd,usd', + coinGeckoPricesResponseEmpty: { + data: {}, + }, + coinGeckoPricesRequestPoly: 'https://api.coingecko.com/api/v3/simple/token_price/polygon-pos?contract_addresses=0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270,0x2791bca1f2de4661ed88a30c99a7a9449aa84174,0x1bfd67037b42cf73acf2047067bd4f2c47d9bfd6&vs_currencies=usd,usd,usd', coinGeckoPricesResponsePoly: { data: { @@ -308,4 +313,31 @@ export const tradeQuoteFixtures = { feePercentage: '0.00%', slippage: '1.11%' }, }, + + setTradeQuoteEmptyPrice: { + from: '0x1494ca1f11d487c2bbe4543e90080aeba4ba3c2b', + fromTokenAddress: '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2', + toTokenAddress: '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2', + exchangeAdapterName: 'ZeroExApiAdapterV4', + calldata: '0x415565b00000000000000000000000009f8f72aa9304c8b593d555f12ef6589cc3a579a2', + gas: '315000', + gasPrice: '61', + slippagePercentage: '2.00%', + fromTokenAmount: '1126868991563', + toTokenAmount: '90314741816', + display: { + inputAmountRaw: '.5', + inputAmount: '500000000000000000', + quoteAmount: '499999999999793729', + fromTokenDisplayAmount: '0.4999999999997937', + toTokenDisplayAmount: '0.04131269116050703', + fromTokenPriceUsd: '$0.00', + toTokenPriceUsd: '$0.00', + gasCostsUsd: '$0.00', + gasCostsChainCurrency: '0.0192150 ETH', + feePercentage: '1.00%', + slippage: '0.00%', + }, + }, + };