@uniswap/smart-order-router
Version:
Uniswap Smart Order Router
161 lines • 14.7 kB
JavaScript
import { BigNumber } from '@ethersproject/bignumber';
import { ChainId } from '@uniswap/sdk-core';
import { TokenFeeDetector__factory } from '../types/other/factories/TokenFeeDetector__factory';
import { log, metric, MetricLoggerUnit, WRAPPED_NATIVE_CURRENCY, } from '../util';
import { USDC_ON, USDT_ON } from './token-provider';
const DEFAULT_TOKEN_BUY_FEE_BPS = BigNumber.from(0);
const DEFAULT_TOKEN_SELL_FEE_BPS = BigNumber.from(0);
// on detector failure, assume no fee
export const DEFAULT_TOKEN_FEE_RESULT = {
buyFeeBps: DEFAULT_TOKEN_BUY_FEE_BPS,
sellFeeBps: DEFAULT_TOKEN_SELL_FEE_BPS,
};
// address at which the FeeDetector lens is deployed
const FEE_DETECTOR_ADDRESS = (chainId) => {
switch (chainId) {
case ChainId.MAINNET:
return '0xbc708B192552e19A088b4C4B8772aEeA83bCf760';
case ChainId.OPTIMISM:
return '0x95aDC98A949dCD94645A8cD56830D86e4Cf34Eff';
case ChainId.BNB:
return '0xCF6220e4496B091a6b391D48e770f1FbaC63E740';
case ChainId.POLYGON:
return '0xC988e19819a63C0e487c6Ad8d6668Ac773923BF2';
case ChainId.BASE:
return '0xCF6220e4496B091a6b391D48e770f1FbaC63E740';
case ChainId.ARBITRUM_ONE:
return '0x37324D81e318260DC4f0fCb68035028eFdE6F50e';
case ChainId.CELO:
return '0x8eEa35913DdeD795001562f9bA5b282d3ac04B60';
case ChainId.AVALANCHE:
return '0x8269d47c4910B8c87789aA0eC128C11A8614dfC8';
case ChainId.WORLDCHAIN:
return '0xbc708B192552e19A088b4C4B8772aEeA83bCf760';
case ChainId.UNICHAIN_SEPOLIA:
return '0xbc708B192552e19A088b4C4B8772aEeA83bCf760';
case ChainId.UNICHAIN:
return '0xbc708B192552e19A088b4C4B8772aEeA83bCf760';
case ChainId.SONEIUM:
return '0x7A5299822b2cD9aC9A9f67756Aa2d62140e9A66f';
default:
// just default to mainnet contract
return '0xbc708B192552e19A088b4C4B8772aEeA83bCf760';
}
};
// Amount has to be big enough to avoid rounding errors, but small enough that
// most v2 pools will have at least this many token units
// 100000 is the smallest number that avoids rounding errors in bps terms
// 10000 was not sufficient due to rounding errors for rebase token (e.g. stETH)
const AMOUNT_TO_FLASH_BORROW = '100000';
// 1M gas limit per validate call, should cover most swap cases
const GAS_LIMIT_PER_VALIDATE = 1000000;
export class OnChainTokenFeeFetcher {
constructor(chainId, rpcProvider, tokenFeeAddress = FEE_DETECTOR_ADDRESS(chainId), gasLimitPerCall = GAS_LIMIT_PER_VALIDATE, amountToFlashBorrow = AMOUNT_TO_FLASH_BORROW) {
this.tokenFeeAddress = tokenFeeAddress;
this.gasLimitPerCall = gasLimitPerCall;
this.amountToFlashBorrow = amountToFlashBorrow;
this.BASE_TOKENS = this.getBaseTokensByChain(chainId);
this.contract = TokenFeeDetector__factory.connect(this.tokenFeeAddress, rpcProvider);
}
getBaseTokensByChain(chainId) {
var _a, _b, _c;
const baseTokens = [];
// Priority order: WETH -> USDT -> USDC
const weth = (_a = WRAPPED_NATIVE_CURRENCY[chainId]) === null || _a === void 0 ? void 0 : _a.address;
if (weth) {
baseTokens.push(weth);
}
try {
const usdt = (_b = USDT_ON(chainId)) === null || _b === void 0 ? void 0 : _b.address;
if (usdt) {
baseTokens.push(usdt);
}
}
catch (e) {
// USDT not available on this chain
}
try {
const usdc = (_c = USDC_ON(chainId)) === null || _c === void 0 ? void 0 : _c.address;
if (usdc) {
baseTokens.push(usdc);
}
}
catch (e) {
// USDC not available on this chain
}
return baseTokens;
}
async fetchFees(addresses, providerConfig) {
const tokenToResult = {};
// Filter out all base tokens from the addresses to check
const baseTokensLower = this.BASE_TOKENS.map((addr) => addr.toLowerCase());
const addressesWithoutBaseTokens = addresses.filter((address) => !baseTokensLower.includes(address.toLowerCase()));
// Create function params for all token x base token combinations
const functionParams = [];
for (const address of addressesWithoutBaseTokens) {
for (const baseToken of this.BASE_TOKENS) {
functionParams.push([address, baseToken, this.amountToFlashBorrow]);
}
}
// Execute all validation calls in parallel
const results = await Promise.all(functionParams.map(async ([address, baseToken, amountToBorrow]) => {
try {
// We use the validate function instead of batchValidate to avoid poison pill problem.
// One token that consumes too much gas could cause the entire batch to fail.
const feeResult = await this.contract.callStatic.validate(address, baseToken, amountToBorrow, {
gasLimit: this.gasLimitPerCall,
blockTag: providerConfig === null || providerConfig === void 0 ? void 0 : providerConfig.blockNumber,
});
metric.putMetric('TokenFeeFetcherFetchFeesSuccess', 1, MetricLoggerUnit.Count);
return { address, baseToken, ...feeResult };
}
catch (err) {
log.error({ err }, `Error calling validate on-chain for token ${address} with base token ${baseToken}`);
metric.putMetric('TokenFeeFetcherFetchFeesFailure', 1, MetricLoggerUnit.Count);
// in case of FOT token fee fetch failure, we return null
// so that they won't get returned from the token-fee-fetcher
// and thus no fee will be applied, and the cache won't cache on FOT tokens with failed fee fetching
return {
address,
baseToken,
buyFeeBps: undefined,
sellFeeBps: undefined,
feeTakenOnTransfer: false,
externalTransferFailed: false,
sellReverted: false,
};
}
}));
// Group results by token address and pick the first successful result
// (prioritizing by base token order: WETH -> USDT -> USDC)
const resultsByToken = new Map();
for (const result of results) {
const existing = resultsByToken.get(result.address) || [];
existing.push(result);
resultsByToken.set(result.address, existing);
}
// For each token, find the first successful result (by base token priority)
for (const [address, tokenResults] of resultsByToken) {
// Sort by base token priority order
const sortedResults = tokenResults.sort((a, b) => {
const aIndex = this.BASE_TOKENS.indexOf(a.baseToken);
const bIndex = this.BASE_TOKENS.indexOf(b.baseToken);
return aIndex - bIndex;
});
// Find first result with valid fee data (buyFeeBps or sellFeeBps > 0)
const validResult = sortedResults.find((result) => (result.buyFeeBps && !result.buyFeeBps.isZero()) ||
(result.sellFeeBps && !result.sellFeeBps.isZero()));
if (validResult) {
tokenToResult[address] = {
buyFeeBps: validResult.buyFeeBps,
sellFeeBps: validResult.sellFeeBps,
feeTakenOnTransfer: validResult.feeTakenOnTransfer,
externalTransferFailed: validResult.externalTransferFailed,
sellReverted: validResult.sellReverted,
};
}
}
return tokenToResult;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9rZW4tZmVlLWZldGNoZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvcHJvdmlkZXJzL3Rva2VuLWZlZS1mZXRjaGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUVyRCxPQUFPLEVBQUUsT0FBTyxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFFNUMsT0FBTyxFQUFFLHlCQUF5QixFQUFFLE1BQU0sb0RBQW9ELENBQUM7QUFFL0YsT0FBTyxFQUNMLEdBQUcsRUFDSCxNQUFNLEVBQ04sZ0JBQWdCLEVBQ2hCLHVCQUF1QixHQUN4QixNQUFNLFNBQVMsQ0FBQztBQUdqQixPQUFPLEVBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBRXBELE1BQU0seUJBQXlCLEdBQUcsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNwRCxNQUFNLDBCQUEwQixHQUFHLFNBQVMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFFckQscUNBQXFDO0FBQ3JDLE1BQU0sQ0FBQyxNQUFNLHdCQUF3QixHQUFHO0lBQ3RDLFNBQVMsRUFBRSx5QkFBeUI7SUFDcEMsVUFBVSxFQUFFLDBCQUEwQjtDQUN2QyxDQUFDO0FBYUYsb0RBQW9EO0FBQ3BELE1BQU0sb0JBQW9CLEdBQUcsQ0FBQyxPQUFnQixFQUFFLEVBQUU7SUFDaEQsUUFBUSxPQUFPLEVBQUU7UUFDZixLQUFLLE9BQU8sQ0FBQyxPQUFPO1lBQ2xCLE9BQU8sNENBQTRDLENBQUM7UUFDdEQsS0FBSyxPQUFPLENBQUMsUUFBUTtZQUNuQixPQUFPLDRDQUE0QyxDQUFDO1FBQ3RELEtBQUssT0FBTyxDQUFDLEdBQUc7WUFDZCxPQUFPLDRDQUE0QyxDQUFDO1FBQ3RELEtBQUssT0FBTyxDQUFDLE9BQU87WUFDbEIsT0FBTyw0Q0FBNEMsQ0FBQztRQUN0RCxLQUFL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