Fee-on-Transfer tokens are a class of ERC-20 tokens that automatically deduct a certain fee during transfers. JAMM DEX supports trading these tokens through specialized functions, ensuring accuracy and reliability in the swap process.
Characteristics of Fee-on-Transfer Tokens
How They Work
Fee-on-transfer tokens deduct a certain percentage of tokens as fees during each transfer or transferFrom call:
// Regular ERC-20 transferfunctiontransfer(addressto,uint256amount)externalreturns(bool){_transfer(msg.sender, to, amount);returntrue;}// Fee-on-transfer token transferfunctiontransfer(addressto,uint256amount)externalreturns(bool){uint256 fee = amount * feeRate /10000;// Calculate feeuint256 actualAmount = amount - fee;// Actual transfer amount_transfer(msg.sender, to, actualAmount);_transfer(msg.sender, feeRecipient, fee);// Transfer feereturntrue;}
Common Types
Deflationary tokens: Burn a certain percentage of tokens on each transfer
Reflection tokens: Distribute transfer fees to all holders
Tax tokens: Send transfer fees to a specified address
Dynamic fee tokens: Dynamically adjust fee rates based on conditions
JAMM DEX Support Mechanism
Standard Swaps vs Fee-on-Transfer Support Swaps
Standard swap functions:
Pre-calculate exact input/output amounts
Assume transfer amount equals actual received amount
Not suitable for fee-on-transfer tokens
Fee-on-transfer support functions:
Calculate based on actual received amounts
Dynamically adjust swap logic
Specifically handle fee-on-transfer tokens
Supported Functions
JAMM DEX provides the following fee-on-transfer support functions:
Implementation Principles
Internal Swap Logic
Key Differences:
Don't pre-calculate input amounts
Calculate based on actual balance changes in trading pairs
Recalculate input amount for each hop
Usage Guide
Token to Token Swaps
JU to Fee-on-Transfer Token
Fee-on-Transfer Token to JU
Fee-on-Transfer Token Detection
Automatic Detection Methods
Known Fee-on-Transfer Token List
Best Practices
1. Slippage Management
2. Pre-Swap Validation
3. Swap Result Analysis
4. Multi-Hop Fee-on-Transfer Token Swaps
Monitoring and Debugging
Swap Monitoring
Debugging Tools
Summary
Fee-on-transfer token support is an important feature of JAMM DEX. This guide covers:
Fee-on-Transfer Token Principles: Working mechanisms and common types
JAMM DEX Support: Specialized support functions and implementation principles
Usage Methods: Swap implementations in various scenarios
Token Detection: Methods to automatically identify fee-on-transfer tokens
Best Practices: Slippage management, validation, analysis, etc.
Monitoring and Debugging: Swap monitoring and troubleshooting tools
By correctly using fee-on-transfer token support features, developers can ensure normal trading of these special tokens on JAMM DEX.
async function checkIfFeeOnTransfer(tokenAddress, testAmount = null) {
try {
const token = new ethers.Contract(tokenAddress, erc20ABI, provider);
// Method 1: Check contract code for fee-related keywords
const code = await provider.getCode(tokenAddress);
const feeKeywords = ['fee', 'tax', 'burn', 'reflect', 'deflationary'];
const hasFeekeywords = feeKeywords.some(keyword =>
code.toLowerCase().includes(ethers.utils.id(keyword).slice(2, 10))
);
if (hasFeekeywords) {
return { isFeeOnTransfer: true, method: 'code_analysis', confidence: 'medium' };
}
// Method 2: Simulate transfer test (requires test tokens)
if (testAmount) {
const result = await simulateTransfer(tokenAddress, testAmount);
return result;
}
// Method 3: Check known fee-on-transfer token list
const knownFeeTokens = await getKnownFeeOnTransferTokens();
if (knownFeeTokens.includes(tokenAddress.toLowerCase())) {
return { isFeeOnTransfer: true, method: 'known_list', confidence: 'high' };
}
return { isFeeOnTransfer: false, method: 'default', confidence: 'low' };
} catch (error) {
console.error("Fee-on-transfer token detection failed:", error);
return { isFeeOnTransfer: false, method: 'error', confidence: 'unknown' };
}
}
async function simulateTransfer(tokenAddress, testAmount) {
// This needs to be done in a test environment or using static calls
// Actual implementation requires more complex logic
try {
const token = new ethers.Contract(tokenAddress, erc20ABI, provider);
// Use callStatic to simulate transfer
const balanceBefore = await token.balanceOf(testAddress);
await token.callStatic.transfer(testAddress, testAmount);
const balanceAfter = await token.balanceOf(testAddress);
const actualReceived = balanceAfter.sub(balanceBefore);
const feeRate = testAmount.sub(actualReceived).mul(10000).div(testAmount);
return {
isFeeOnTransfer: feeRate.gt(0),
feeRate: feeRate.toNumber(), // Basis points
method: 'simulation',
confidence: 'high'
};
} catch (error) {
return { isFeeOnTransfer: false, method: 'simulation_failed', confidence: 'unknown' };
}
}
// Maintain a list of known fee-on-transfer tokens
const KNOWN_FEE_ON_TRANSFER_TOKENS = {
// Mainnet addresses example (actual addresses need to be determined based on JuChain network)
'UNSURE': { name: 'Example Fee Token', feeRate: 200 }, // 2% fee rate
// Add more known fee-on-transfer tokens
};
async function getKnownFeeOnTransferTokens() {
return Object.keys(KNOWN_FEE_ON_TRANSFER_TOKENS).map(addr => addr.toLowerCase());
}
function getTokenFeeInfo(tokenAddress) {
return KNOWN_FEE_ON_TRANSFER_TOKENS[tokenAddress.toLowerCase()] || null;
}
function calculateFeeTokenSlippage(baseslippage, tokenInfo) {
let adjustedSlippage = baseslippage;
// Adjust slippage based on token type
if (tokenInfo.isFeeOnTransfer) {
// Base adjustment: add 2x the transfer fee rate as additional slippage
adjustedSlippage += (tokenInfo.feeRate || 500) * 2 / 100; // Convert to percentage
// Minimum slippage protection
adjustedSlippage = Math.max(adjustedSlippage, 5); // At least 5%
// Maximum slippage limit
adjustedSlippage = Math.min(adjustedSlippage, 20); // At most 20%
}
return adjustedSlippage;
}
async function validateFeeTokenSwap(tokenIn, tokenOut, amountIn) {
const validations = [];
// Check input token
const tokenInInfo = await checkIfFeeOnTransfer(tokenIn);
if (tokenInInfo.isFeeOnTransfer) {
validations.push({
type: 'warning',
message: `Input token ${tokenIn} is fee-on-transfer, actual input may be less than expected`
});
}
// Check output token
const tokenOutInfo = await checkIfFeeOnTransfer(tokenOut);
if (tokenOutInfo.isFeeOnTransfer) {
validations.push({
type: 'warning',
message: `Output token ${tokenOut} is fee-on-transfer, actual output may be less than expected`
});
}
// Check liquidity
const factory = new ethers.Contract(FACTORY_ADDRESS, factoryABI, provider);
const pairAddress = await factory.getPair(tokenIn, tokenOut, 100);
if (pairAddress === ethers.constants.AddressZero) {
validations.push({
type: 'error',
message: 'Trading pair does not exist'
});
} else {
const pair = new ethers.Contract(pairAddress, pairABI, provider);
const reserves = await pair.getReserves();
if (reserves.reserve0.eq(0) || reserves.reserve1.eq(0)) {
validations.push({
type: 'error',
message: 'Insufficient liquidity in trading pair'
});
}
}
return validations;
}