chore: flag fee-on-transfer tokens on mainnet - #153
Open
ScreamingHawk wants to merge 2 commits into
Open
Conversation
Tokens that tax transfers credit the recipient less than the sender sends, which breaks any integration that assumes transfer(x) delivers x. Add extensions.feeOnTransfer to the 132 mainnet ERC-20s GoPlus reports a non-zero transfer_tax for, and a tool to refresh the flag.
Member
|
Great call, LGTM |
miguelmota
approved these changes
Aug 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds
extensions.feeOnTransfer: trueto 132 mainnet ERC-20s, plustools/sync-fee-on-transfer.tsto refresh the flag.Fee-on-transfer tokens (transfer taxes, reflection, burn-on-transfer) credit the recipient less than the sender sends. Any integration that assumes
transfer(x)deliversxbreaks on them — the recipient balance can never reach the expected amount, so balance-based preconditions are unsatisfiable by construction and no amount the user sends will fix it.Example: SHUF (
0x3a9fff45…6b9e) burns 1% and redistributes 1% on every transfer. A send of 1237.674738 credits 1212.921243240.Data source
GoPlus token security API (
transfer_tax != 0), free unauthenticated tier. New third-party dependency for this repo — previously only CoinGecko was used. CoinGecko was checked first and publishes nothing equivalent: no tax, honeypot or transfer-behaviour field on any endpoint. The only signals in this repo today are free-textdescriptionprose, which is not machine-actionable.Coverage is mainnet-only in practice:
717 of 2738 scanned (26%) returned no tax data. Outside mainnet, "0 flagged" mostly means "GoPlus has no data", not "clean" — optimism is 77/77 unknown, avalanche 34/34, gnosis 12/12. An empty
transfer_taxis treated as unknown, not as zero, and such tokens are left unflagged.Verification
The GoPlus results were independently double-checked against on-chain state for all 132 flagged tokens, using a separate script (deliberately not committed — it is a one-off audit, not something to maintain). Method:
eth_simulateV1, transfer from a token's Uniswap V2 pair to a fresh address, diff recipient balance before/after in the same simulated block.Outcome:
DGX(V2 pair holds 3 base units, too small to probe) andTNI(non-standard ERC-20,transferreturns no data). Neither is contradicted; both keep the flag. Unverifiable is not treated as clean.Net: 130/132 confirmed on-chain, 0 genuine contradictions.
One correction to GoPlus surfaced:
XVIXreports 0.5% but measures 0.6987%. The flag is still correct. Since exact rates can be wrong,feeOnTransferis stored as a boolean only — no rate is persisted.Tool
pnpm sync-fee-on-transfer— dry-run by default,--writeapplies,--chain <name>scopes. Follows the existingsync-coingecko.tsconventions.Notes for anyone re-running it:
Scope
Data + tooling only. Consumers do not read this field yet.
go-tokendirectory'sContractInfoExtensionneeds a matchingFeeOnTransfer boolbefore anything downstream can act on it.