Skip to content

Execution

Fee-on-transfer and rebasing tokens: bot accounting risks

A taxed swap delivers fewer units than its quote, then a rebase changes the holding again. Follow the checked ledger and verify the exact router boundary.

An ivory paper ribbon passes through a ceramic ring, forming an accordion fan beside a small lime-lined dish.
Illustration for execution; examples in this article are educational.

Fee-on-transfer tokens can credit a recipient with fewer units than the sender spends; rebasing tokens can change a wallet's balance without a trade. A bot must separate the quote, the actual swap receipt and later balance adjustments, then check the exact router's support. Size the next action from reconciled holdings, not a quoted amount or transfer log alone.

Identify which amount changed

A swap involves several quantities: the amount debited from the sender, the amount reaching the pool, the pool's output and the amount credited to the final recipient. An integration that stores all four as “trade amount” loses the evidence needed to explain a shortfall.

With an inclusive fee-on-transfer token, part of a transfer is burned or diverted, leaving the recipient with less than the sender transferred. An exclusive fee adds a separate trailing transfer. These are distinct integration problems in the Uniswap v2 troubleshooting guide; a method handling the first does not establish compatibility with the second.

A rebase changes token balances independently of a transfer. For a documented implementation, Lido's stETH can update a holder's balance without emitting a holder-level ERC-20 transfer event. Its integration guide explains the share accounting behind that behavior. An event-only inventory can therefore become stale even when no swap was missed.

Pool liquidity also affects output. The concentrated-liquidity execution guide follows that cause through a swap path. Here, the additional question is whether the token's own mechanics change the amounts crossing each boundary.

Check the exact router and function

“The DEX supports this token” is too broad for a bot configuration. Record the chain, token contract, router address, contract version, called function and full route. An aggregator's name does not reveal which underlying contracts execute its quote.

In canonical Uniswap v2 Router02, the exact-input function swapExactTokensForTokensSupportingFeeOnTransferTokens calculates each hop's input from the pair's current balance minus its recorded reserve. It checks the final recipient's output balance increase against amountOutMin. The function does not return the ordinary swap method's amounts array. These details come from the Router02 source, rather than a promise about every router bearing a similar name.

Documented integrationWhat the evidence establishesWhat still needs checking
Uniswap v2 Router02 supporting exact-input token swapInput is derived from pair balances; final token receipt has a minimum checkToken behavior, deployed code, route and recipient
Uniswap v3 router contractsThe v3 documentation says fee-on-transfer tokens do not function with those routersA different integration needs its own evidence
Custom router or wrapperIts own implementation defines handlingDebit, receipt, conversion rate and every relevant failure path

The Uniswap v3 token integration page also makes a narrower distinction for rebasing tokens: pool creation and swaps can succeed, while liquidity providers bear negative-rebase losses when positions become active. A successful swap does not establish suitable accounting for holding the asset or providing liquidity. These are v3 statements; they do not describe every Uniswap version.

Increasing slippage tolerance cannot add a missing token-handling implementation. It changes the acceptable execution boundary. Investigate an unsupported transfer path before changing that boundary.

A hypothetical taxed swap, followed by a rebase

This original example uses fictitious tokens X and Y. Both have six decimal places. The pool starts with 100,000 X and 50,000 Y, with balances equal to recorded reserves. There are no competing transactions, donations or rebases during the swap. The recipient starts with zero Y.

Assume the sender spends 1,000 X. X deducts a 2% inclusive fee on the transfer into the pool. Y deducts a 3% inclusive fee on the pool's transfer to the recipient. These invented token fees are separate from the pool fee.

For the pool calculation, use the canonical Uniswap v2 library's output formula:

output = floor(input × 997 × reserveY / (reserveX × 1000 + input × 997))

All values in that expression are integer base units. The factor 997/1000 represents this formula's 0.30% pool fee. For the invented transfer fees, floor the fee in base units and credit the remainder. This states the rounding rule explicitly instead of rounding each displayed amount arbitrarily.

A reserve-based estimate that treats the full 1,000 X as pool input gives 493.579017 Y before any output transfer fee. The actual input reaching the pool is 1,000 − 20 = 980 X, producing a smaller gross output.

Quantity or confirmed stepX amountY amountAccounting meaning
Estimate ignoring token transfer fees1,000.000000 input493.579017 estimated outputA comparison quote, not inventory
Sender's actual debit1,000.000000—Total X spent by the wallet
Inclusive X fee20.000000—X diverted or burned
Actual pool input980.000000—Input used in the pool formula
Gross pool output—483.802954Y leaving the pool before its fee
Inclusive Y fee—14.514088Y diverted or burned
Recipient's swap receipt—469.288866Actual acquired wallet units
Later 4% negative rebase—−18.771555Balance adjustment under the hypothetical rule
Holding after that rebase—450.517311Current Y inventory

The output fee is floor(483.802954 × 3%) at six decimals, or 14.514088 Y. Subtracting it leaves 469.288866 Y. Relative to the quote ignoring transfer fees, the receipt is 24.290151 Y lower, about 4.9212%. That difference combines the reduced pool input and the output transfer fee; it is not all a change in market price.

For this fixture, a recipient minimum of 470 Y would fail the Router02 balance-delta check. A minimum of 465 Y would pass that check, subject to the example's other assumptions. Those are comparisons for explaining the check, not suggested slippage settings.

After the swap, assume Y undergoes a separate 4% negative rebase. The hypothetical token computes the new wallet balance by flooring 469.288866 × 0.96 to six decimals, yielding 450.517311 Y. No further trades or transfers occur. This is an invented stress case, not a statement about stETH's rebase size, frequency or history.

Keep the 18.771555 Y reduction as a balance adjustment. Rewriting the original fill as 450.517311 Y would erase the distinction between execution and subsequent ownership changes. The example stops at inventory: valuing that holding requires a separate price observation, and gas is excluded from these token amounts.

Reconcile holdings without inventing trades

Use a token-unit identity for a defined wallet and period:

closing balance = opening balance + net receipts − actual debits + rebase adjustments + other explained changes

Each term needs a timestamp or block reference. For the example's swap-only period, zero opening Y and no other activity make the wallet increase equal to the receipt. In a shared wallet, a deposit or another bot's transfer between observations would break that shortcut. Block-end balances also combine all relevant activity in the block; they do not automatically isolate one transaction.

Retain the transaction hash, recipient, token address, transfer records and balance observations. Use transaction-level execution evidence where necessary to isolate the swap. If the evidence cannot separate concurrent changes, leave an unresolved difference and suspend dependent sizing instead of assigning it to the nearest trade.

Preserve event granularity when preparing reports. The aggregate versus raw trades guide explains why aggregation changes what a record represents. On-chain records add another boundary: a pool swap event, a token transfer and a rebase adjustment answer different questions. None should silently become an extra fill in the journal.

For stETH specifically, Lido recommends storing and operating shares, then converting to token amounts for interaction. Its guide also documents small integer-rounding residues in token/share conversion. Rebasing tokens need an adapter for their actual accounting model; shares are not a universal standard for all of them.

A wrapper changes the unit being held. In Lido's wstETH implementation, the holder's wrapper balance stays constant through a rebase while its stETH conversion rate changes. Keep the wrapper contract, quantity and rate observation together, using the same Lido integration guidance. Treating one wrapper unit as one underlying token would introduce a fresh accounting error.

Validate the boundary before enabling automation

Use this worksheet for each token and route. Save the evidence alongside the configuration so a later change can be compared with the original assumptions.

CheckEvidence to retainDecision it supports
Asset identityChain, token contract, decimals and wrapper relationshipWhich unit the bot owns
Transfer semanticsSender debit and recipient credit for each relevant boundaryWhether fees or extra transfers alter amounts
Router handlingDeployed address, version, source and exact functionWhether that execution path handles those semantics
Quote assumptionsBlock reference, route, input and included feesWhether estimated output means final receipt
Balance modelRebase event/index or share conversion, with observation timeHow holdings can change between trades
Reconciliation scopeWallet, transaction identities and concurrent activityWhether a balance delta can be attributed
Failure policyRevert, unknown transaction status and unresolved balance actionsWhen the next instruction must wait

In a controlled simulation, exercise the actual contract path in both directions: wallet to pool and pool to wallet. Check the chosen minimum against final recipient receipt, then run a rebase between an acquisition and its follow-up action. Include existing inventory, rounding residue and another wallet movement. A simulation at one state supports only those tested conditions.

Rebases can also affect pool balances. Uniswap's v2 troubleshooting documentation describes reserve drift: negative rebases can shift a deficit to a later transacting account, while positive rebases can leave a surplus outside recorded reserves. Re-reading the wallet alone cannot certify the next pool quote.

An exclusive trailing fee or a changed transfer rule requires a compatibility investigation. A provider timeout needs a different response: reconcile the original transaction's status before another spending attempt. The API retry and backoff guide helps define transient-error handling; repeated submissions cannot resolve an unsupported token mechanism.

For centralized exchange balances, retain the venue's separate available-versus-total reconciliation. An on-chain wallet receipt does not define an exchange's credited or spendable balance. These on-chain examples are educational and do not establish KLINEO support for these token types or router integrations.

Before the next action, require three recorded values: what the quote expected, what the swap actually delivered, and what the wallet holds at the new decision time. If those values differ, explain the difference before the bot spends the balance.

Keep the context with the example.

Each worked example is illustrative, with its sources and assumptions in the article. Read our editorial standards for authorship, verification and corrections.