diff --git a/lib/graphql/graphql-queries.ts b/lib/graphql/graphql-queries.ts index 8166aa041..e24d94328 100644 --- a/lib/graphql/graphql-queries.ts +++ b/lib/graphql/graphql-queries.ts @@ -11,6 +11,9 @@ query Token($chain: Chain!, $address: String!) { feeData { buyFeeBps sellFeeBps + feeTakenOnTransfer + externalTransferFailed + sellReverted } } } @@ -29,6 +32,9 @@ query Tokens($contracts: [ContractInput!]!) { feeData { buyFeeBps sellFeeBps + feeTakenOnTransfer + externalTransferFailed + sellReverted } } } diff --git a/lib/graphql/graphql-schemas.ts b/lib/graphql/graphql-schemas.ts index 6dbbc8e65..c432dd4b1 100644 --- a/lib/graphql/graphql-schemas.ts +++ b/lib/graphql/graphql-schemas.ts @@ -21,5 +21,8 @@ export interface TokenInfo { feeData?: { buyFeeBps?: string sellFeeBps?: string + feeTakenOnTransfer?: boolean + externalTransferFailed?: boolean + sellReverted?: boolean } } diff --git a/lib/graphql/graphql-token-fee-fetcher.ts b/lib/graphql/graphql-token-fee-fetcher.ts index 7dd4d6d50..8bbd03598 100644 --- a/lib/graphql/graphql-token-fee-fetcher.ts +++ b/lib/graphql/graphql-token-fee-fetcher.ts @@ -46,9 +46,24 @@ export class GraphQLTokenFeeFetcher implements ITokenFeeFetcher { if (token.feeData?.buyFeeBps || token.feeData?.sellFeeBps) { const buyFeeBps = token.feeData.buyFeeBps ? BigNumber.from(token.feeData.buyFeeBps) : undefined const sellFeeBps = token.feeData.sellFeeBps ? BigNumber.from(token.feeData.sellFeeBps) : undefined - tokenFeeMap[token.address] = { buyFeeBps, sellFeeBps } + const feeTakenOnTransfer = token.feeData.feeTakenOnTransfer + const externalTransferFailed = token.feeData.externalTransferFailed + const sellReverted = token.feeData.sellReverted + tokenFeeMap[token.address] = { + buyFeeBps, + sellFeeBps, + feeTakenOnTransfer, + externalTransferFailed, + sellReverted, + } } else { - tokenFeeMap[token.address] = { buyFeeBps: undefined, sellFeeBps: undefined } + tokenFeeMap[token.address] = { + buyFeeBps: undefined, + sellFeeBps: undefined, + feeTakenOnTransfer: false, + externalTransferFailed: false, + sellReverted: false, + } } }) metric.putMetric('GraphQLTokenFeeFetcherFetchFeesSuccess', 1, MetricLoggerUnit.Count) diff --git a/package-lock.json b/package-lock.json index d5db31032..ca9249f89 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "@uniswap/permit2-sdk": "^1.3.0", "@uniswap/router-sdk": "^1.9.2", "@uniswap/sdk-core": "^5.3.0", - "@uniswap/smart-order-router": "3.36.2", + "@uniswap/smart-order-router": "3.37.0", "@uniswap/token-lists": "^1.0.0-beta.33", "@uniswap/universal-router-sdk": "^2.2.0", "@uniswap/v2-sdk": "^4.3.2", @@ -4748,9 +4748,9 @@ } }, "node_modules/@uniswap/smart-order-router": { - "version": "3.36.2", - "resolved": "https://registry.npmjs.org/@uniswap/smart-order-router/-/smart-order-router-3.36.2.tgz", - "integrity": "sha512-IJG36HLyI5h2vHKmIFxsMIHh4vS3TmgBF07xzM3uaWHzKN6yEUJqD5B/7x2WmC6C/t84o7NkyR6Rgr1tP5m6FA==", + "version": "3.37.0", + "resolved": "https://registry.npmjs.org/@uniswap/smart-order-router/-/smart-order-router-3.37.0.tgz", + "integrity": "sha512-yxS21l4UqX5B1fM2y3pEsw8HxYQVJzPdRsplriLiwrqnyt21CsX0VdkxTHKVF8HeOa1/XFMao9DgCzmcM0soxw==", "dependencies": { "@eth-optimism/sdk": "^3.2.2", "@types/brotli": "^1.3.4", @@ -28385,9 +28385,9 @@ } }, "@uniswap/smart-order-router": { - "version": "3.36.2", - "resolved": "https://registry.npmjs.org/@uniswap/smart-order-router/-/smart-order-router-3.36.2.tgz", - "integrity": "sha512-IJG36HLyI5h2vHKmIFxsMIHh4vS3TmgBF07xzM3uaWHzKN6yEUJqD5B/7x2WmC6C/t84o7NkyR6Rgr1tP5m6FA==", + "version": "3.37.0", + "resolved": "https://registry.npmjs.org/@uniswap/smart-order-router/-/smart-order-router-3.37.0.tgz", + "integrity": "sha512-yxS21l4UqX5B1fM2y3pEsw8HxYQVJzPdRsplriLiwrqnyt21CsX0VdkxTHKVF8HeOa1/XFMao9DgCzmcM0soxw==", "requires": { "@eth-optimism/sdk": "^3.2.2", "@types/brotli": "^1.3.4", diff --git a/package.json b/package.json index c50c20413..c45768c35 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "@uniswap/router-sdk": "^1.9.2", "@uniswap/sdk-core": "^5.3.0", "@types/semver": "^7.5.8", - "@uniswap/smart-order-router": "3.36.2", + "@uniswap/smart-order-router": "3.37.0", "@uniswap/token-lists": "^1.0.0-beta.33", "@uniswap/universal-router-sdk": "^2.2.0", "@uniswap/v2-sdk": "^4.3.2", diff --git a/test/mocha/e2e/quote.test.ts b/test/mocha/e2e/quote.test.ts index 527ad4a65..0fed155df 100644 --- a/test/mocha/e2e/quote.test.ts +++ b/test/mocha/e2e/quote.test.ts @@ -80,6 +80,24 @@ const BULLET_WHT_TAX = new Token( BigNumber.from(500), BigNumber.from(500) ) +export const DFNDR = new Token( + ChainId.MAINNET, + '0x3f57c35633cb29834bb7577ba8052eab90f52a02', + 18, + 'DFNDR', + 'Defender Bot', + false +) +export const DFNDR_WITH_TAX = new Token( + ChainId.MAINNET, + '0x3f57c35633cb29834bb7577ba8052eab90f52a02', + 18, + 'DFNDR', + 'Defender Bot', + false, + BigNumber.from(500), + BigNumber.from(500) +) const V2_SUPPORTED_PAIRS = [ [WETH9[ChainId.ARBITRUM_ONE], USDC_NATIVE_ARBITRUM], @@ -1114,6 +1132,7 @@ describe('quote', function () { const tokenInAndTokenOut = [ [BULLET, WETH9[ChainId.MAINNET]!], [WETH9[ChainId.MAINNET]!, BULLET], + [WETH9[ChainId.MAINNET]!, DFNDR], ] tokenInAndTokenOut.forEach(([tokenIn, tokenOut]) => { @@ -1156,9 +1175,12 @@ describe('quote', function () { enableUniversalRouter: true, // if fee-on-transfer flag is not enabled, most likely the simulation will fail due to quote not subtracting the tax simulateFromAddress: enableFeeOnTransferFeeFetching ? simulateFromAddress : undefined, + portionBips: FLAT_PORTION.bips, + portionRecipient: FLAT_PORTION.recipient, } const queryParams = qs.stringify(quoteReq) + console.log(`${API}?${queryParams}`) const response: AxiosResponse = await axios.get( `${API}?${queryParams}` @@ -1170,6 +1192,18 @@ describe('quote', function () { const quoteWithFlagOn = responses.find((r) => r.enableFeeOnTransferFeeFetching === true) expect(quoteWithFlagOn).not.to.be.undefined + + // in case of FOT token that should not take a portion/fee, we assert that all portion fields are undefined + if (!tokenOut?.equals(WETH9[ChainId.MAINNET])) { + expect(quoteWithFlagOn!.data.portionAmount).to.be.undefined + expect(quoteWithFlagOn!.data.portionBips).to.be.undefined + expect(quoteWithFlagOn!.data.portionRecipient).to.be.undefined + } else { + expect(quoteWithFlagOn!.data.portionAmount).to.be.not.undefined + expect(quoteWithFlagOn!.data.portionBips).to.be.not.undefined + expect(quoteWithFlagOn!.data.portionRecipient).to.be.not.undefined + } + responses .filter((r) => r.enableFeeOnTransferFeeFetching !== true) .forEach((r) => { diff --git a/test/mocha/integ/graphql/graphql-token-fee-fetcher.test.ts b/test/mocha/integ/graphql/graphql-token-fee-fetcher.test.ts index 04e41c2d9..245313e78 100644 --- a/test/mocha/integ/graphql/graphql-token-fee-fetcher.test.ts +++ b/test/mocha/integ/graphql/graphql-token-fee-fetcher.test.ts @@ -64,9 +64,15 @@ describe('integration test for GraphQLTokenFeeFetcher', () => { expect(tokenFeeMap[WETH9[ChainId.MAINNET]!.address]).to.not.be.undefined expect(tokenFeeMap[WETH9[ChainId.MAINNET]!.address]?.buyFeeBps).to.be.undefined expect(tokenFeeMap[WETH9[ChainId.MAINNET]!.address]?.sellFeeBps).to.be.undefined + expect(tokenFeeMap[WETH9[ChainId.MAINNET]!.address]?.feeTakenOnTransfer).to.not.be.undefined + expect(tokenFeeMap[WETH9[ChainId.MAINNET]!.address]?.externalTransferFailed).to.not.be.undefined + expect(tokenFeeMap[WETH9[ChainId.MAINNET]!.address]?.sellReverted).to.not.be.undefined expect(tokenFeeMap[BITBOY.address]).to.not.be.undefined expect(tokenFeeMap[BITBOY.address]?.buyFeeBps?._hex).equals(BITBOY.buyFeeBps?._hex) expect(tokenFeeMap[BITBOY.address]?.sellFeeBps?._hex).equals(BITBOY.sellFeeBps?._hex) + expect(tokenFeeMap[BITBOY.address]?.feeTakenOnTransfer).equals(false) + expect(tokenFeeMap[BITBOY.address]?.externalTransferFailed).equals(true) + expect(tokenFeeMap[BITBOY.address]?.sellReverted).equals(false) }) it('Fetch BULLET and BITBOY, should return BOTH', async () => { @@ -77,9 +83,16 @@ describe('integration test for GraphQLTokenFeeFetcher', () => { expect(tokenFeeMap[BULLET.address]).to.not.be.undefined expect(tokenFeeMap[BULLET.address]?.buyFeeBps?._hex).equals(BULLET.buyFeeBps?._hex) expect(tokenFeeMap[BULLET.address]?.sellFeeBps?._hex).equals(BULLET.sellFeeBps?._hex) + expect(tokenFeeMap[BULLET.address]?.feeTakenOnTransfer).equals(false) + expect(tokenFeeMap[BULLET.address]?.externalTransferFailed).equals(true) + expect(tokenFeeMap[BULLET.address]?.sellReverted).equals(true) + expect(tokenFeeMap[BITBOY.address]).to.not.be.undefined expect(tokenFeeMap[BITBOY.address]?.buyFeeBps?._hex).equals(BITBOY.buyFeeBps?._hex) expect(tokenFeeMap[BITBOY.address]?.sellFeeBps?._hex).equals(BITBOY.sellFeeBps?._hex) + expect(tokenFeeMap[BITBOY.address]?.feeTakenOnTransfer).equals(false) + expect(tokenFeeMap[BITBOY.address]?.externalTransferFailed).equals(true) + expect(tokenFeeMap[BITBOY.address]?.sellReverted).equals(false) expect(spyGraphQLFetcher.calledOnce).to.be.true expect(spyOnChainFetcher.calledOnce).to.be.false @@ -97,6 +110,9 @@ describe('integration test for GraphQLTokenFeeFetcher', () => { expect(tokenFeeMap[BITBOY.address]).to.not.be.undefined expect(tokenFeeMap[BITBOY.address]?.buyFeeBps?._hex).equals(BITBOY.buyFeeBps?._hex) expect(tokenFeeMap[BITBOY.address]?.sellFeeBps?._hex).equals(BITBOY.sellFeeBps?._hex) + expect(tokenFeeMap[BITBOY.address]?.feeTakenOnTransfer).equals(false) + expect(tokenFeeMap[BITBOY.address]?.externalTransferFailed).equals(true) + expect(tokenFeeMap[BITBOY.address]?.sellReverted).equals(false) expect(spyGraphQLFetcher.calledOnce).to.be.true expect(spyOnChainFetcher.calledOnce).to.be.true