From aa0202b76ff06008f28654a0429484f67e01c111 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Tue, 29 Sep 2026 08:58:26 +1300 Subject: [PATCH 1/2] feat: add rebasing token extension with on-chain sync tool Adds extensions.rebasing for tokens whose balances change without a transfer (Aave aTokens, stETH, AMPL, OUSD), populated by tools/sync-rebasing.ts via on-chain selector probes. Flags 285 tokens. --- .gitignore | 1 + README.md | 44 ++- index/arbitrum/erc20.json | 60 ++-- index/avalanche/erc20.json | 75 +++-- index/base-sepolia/erc20.json | 18 +- index/base/erc20.json | 45 ++- index/bnb/erc20.json | 24 +- index/gnosis/erc20.json | 27 +- index/index.json | 18 +- index/mainnet/erc20.json | 462 ++++++++++++++++++---------- index/optimism/erc20.json | 42 ++- index/polygon/erc20.json | 102 ++++--- package.json | 1 + tools/sync-rebasing.ts | 554 ++++++++++++++++++++++++++++++++++ 14 files changed, 1178 insertions(+), 295 deletions(-) create mode 100644 tools/sync-rebasing.ts diff --git a/.gitignore b/.gitignore index e444980..1274a69 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ node_modules tools/node_modules tools/config/creds.env tools/.fee-on-transfer-cache.* +tools/.rebasing-cache.* gcp-sa-key.json yarn-error.log diff --git a/README.md b/README.md index 79e96f7..cc87fed 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,41 @@ pnpm update-featured -- --write --count 20 Supported chains: mainnet, arbitrum, polygon, optimism, base, avalanche, bnb, gnosis, arbitrum-nova. +## Sync Rebasing Tokens + +Flags tokens whose balances change without a transfer — Aave aTokens, Lido stETH, Ampleforth, +Origin OUSD/OETH — with `extensions.rebasing: true`, so consumers can refuse to quote a fixed +amount against a balance that moves underneath them. + +Detection is on-chain and evidence-based: each token is probed over public RPCs for read-only +selector pairs that only a rebasing implementation exposes (`UNDERLYING_ASSET_ADDRESS` + +`scaledBalanceOf` for Aave v2/v3, `underlyingAssetAddress` + `principalBalanceOf` for Aave v1, +`getPooledEthByShares` + `sharesOf` for stETH, `rebasingCreditsPerToken` + `nonRebasingSupply` +for Origin). ERC-4626 vaults are excluded deliberately: `sUSDe` and `wstETH` move share +_price_, not balances, so fixed-amount math against them stays correct. + +Every probe carries a `totalSupply` sentinel. If the sentinel fails the result is discarded and +retried in smaller batches, then as individual `eth_call`s — a missing selector and an RPC that +silently dropped the call are otherwise indistinguishable, and the latter would unflag real +rebasing tokens. Tokens that never answer are reported as `unreachable` rather than assumed +clean. Results are cached in `tools/.rebasing-cache.json` so an interrupted scan resumes. + +By default the script performs a **dry run**. Pass `--write` to apply changes. + +```bash +# Preview all supported chains +pnpm sync-rebasing + +# Preview a single chain +pnpm sync-rebasing -- --chain base + +# Apply changes +pnpm sync-rebasing -- --write +``` + +Run `pnpm format` afterwards: the script writes with `JSON.stringify`, which expands short +arrays that Prettier keeps inline. + ## Token List Formats The ERC-20 token lists present in this repository follow the [Uniswap Token List Schema](https://github.com/Uniswap/token-lists). The original list was populated using [Coingecko](https://www.coingecko.com/en)'s erc20 token list [CoinGecko](https://tokens.coingecko.com/uniswap/all.json). Token description and links are taken from Coingecko's API. @@ -94,10 +129,17 @@ See [here](https://github.com/0xsequence/token-directory/blob/master/index/mainn extensions: { link: string | null, // URL of token's website description: string | null, // Short description of token (1000 chars max) - ogImage: string | null // URL of Open Graph image of token website + ogImage: string | null, // URL of Open Graph image of token website + feeOnTransfer?: true, // Set when the token taxes transfers; omitted otherwise + rebasing?: true // Set when balances change without a transfer + // (Aave aTokens, stETH, AMPL); omitted otherwise } ``` +`feeOnTransfer` and `rebasing` are populated by tooling rather than by hand — see +[Sync Rebasing Tokens](#sync-rebasing-tokens). Both are present only when true, so absence +means "no evidence gathered", not "verified false". + ### ERC721 and ERC1155 See [here](https://github.com/0xsequence/token-directory/blob/master/index/mainnet/erc721.json) for erc721 and [here](https://github.com/0xsequence/token-directory/blob/master/index/mainnet/erc1155.json) for erc1155 examples. diff --git a/index/arbitrum/erc20.json b/index/arbitrum/erc20.json index 75572a5..019f612 100644 --- a/index/arbitrum/erc20.json +++ b/index/arbitrum/erc20.json @@ -199,7 +199,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -218,7 +219,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -237,7 +239,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -256,7 +259,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -275,7 +279,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -294,7 +299,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -313,7 +319,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -332,7 +339,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -351,7 +359,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -370,7 +379,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -389,7 +399,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -408,7 +419,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -427,7 +439,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -446,7 +459,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -465,7 +479,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -484,7 +499,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -503,7 +519,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -522,7 +539,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -541,7 +559,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -560,7 +579,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { diff --git a/index/avalanche/erc20.json b/index/avalanche/erc20.json index 7eb1022..f6a6c23 100644 --- a/index/avalanche/erc20.json +++ b/index/avalanche/erc20.json @@ -218,7 +218,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -237,7 +238,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -256,7 +258,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -275,7 +278,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -294,7 +298,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -313,7 +318,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -332,7 +338,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -351,7 +358,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -370,7 +378,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -389,7 +398,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -408,7 +418,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -427,7 +438,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -446,7 +458,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -465,7 +478,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -484,7 +498,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -503,7 +518,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -522,7 +538,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -541,7 +558,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -560,7 +578,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -579,7 +598,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -598,7 +618,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -617,7 +638,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -636,7 +658,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -655,7 +678,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -674,7 +698,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } } ] diff --git a/index/base-sepolia/erc20.json b/index/base-sepolia/erc20.json index 9b796ae..8f72cb4 100644 --- a/index/base-sepolia/erc20.json +++ b/index/base-sepolia/erc20.json @@ -54,7 +54,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -73,7 +74,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -92,7 +94,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -111,7 +114,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -130,7 +134,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -149,7 +154,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } } ], diff --git a/index/base/erc20.json b/index/base/erc20.json index 07a46a5..cbc22ef 100644 --- a/index/base/erc20.json +++ b/index/base/erc20.json @@ -143,7 +143,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -162,7 +163,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -181,7 +183,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -200,7 +203,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -219,7 +223,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -238,7 +243,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -257,7 +263,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -276,7 +283,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -295,7 +303,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -314,7 +323,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -333,7 +343,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -352,7 +363,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -371,7 +383,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -390,7 +403,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -409,7 +423,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { diff --git a/index/bnb/erc20.json b/index/bnb/erc20.json index 4c4ce7b..e312dc2 100644 --- a/index/bnb/erc20.json +++ b/index/bnb/erc20.json @@ -502,7 +502,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -521,7 +522,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -540,7 +542,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -559,7 +562,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -578,7 +582,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -597,7 +602,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -616,7 +622,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -635,7 +642,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { diff --git a/index/gnosis/erc20.json b/index/gnosis/erc20.json index 9701474..c23d5ce 100644 --- a/index/gnosis/erc20.json +++ b/index/gnosis/erc20.json @@ -85,7 +85,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -104,7 +105,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -123,7 +125,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -142,7 +145,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -161,7 +165,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -180,7 +185,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -199,7 +205,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -218,7 +225,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -237,7 +245,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { diff --git a/index/index.json b/index/index.json index 760fc32..76aed76 100644 --- a/index/index.json +++ b/index/index.json @@ -28,7 +28,7 @@ "chainId": 42161, "tokenLists": { "erc1155.json": "efdf23b5c46d712ebba7b3fd5226518b4dd190badda613b3a5718d178a67f3e9", - "erc20.json": "1fd91cf3fa66bf9442854214f4652edaa9268dbccdd7d3e33f3cdbe2383df35f", + "erc20.json": "b934ad685bbbbae19d8a8e149def9d7e495e610353b1f98c273b8044633ed0fe", "erc721.json": "e8a31ae32437b84c1bfdc91d51a14c0d401ddd703ebe233aa4cc30ed2c236dc7", "misc.json": "7fdcd6fbf97166c6c38d0b7c59d5f34beeb611cdaea6fe0c7bf37ed9b4b3ad83" } @@ -87,7 +87,7 @@ "chainId": 43114, "tokenLists": { "erc1155.json": "87e84f05ad461615cca7a29b579fc5b20694a12b7b4a729088dc23eb01c6b41c", - "erc20.json": "17d62612fac5347b54719dfa4ad85da102792a93b2bb4fe9816e48ca0155d33d", + "erc20.json": "429e0986ae3fb16a7c288a60136306665d07dda8cd7ce9d9611975e7e06ac22c", "erc721.json": "0649e6b67af5860e10c6211d3e097bfce25b9cf25eaee216e29a68304f4f3a74", "misc.json": "92e1d2eb863ba815365f798d5ffef9185971eae5e4eb58d412a4dababcab960b" } @@ -105,7 +105,7 @@ "chainId": 8453, "tokenLists": { "erc1155.json": "1939b05371600ce96c9584299659f67230a939735f607db71169a3d6af439b8f", - "erc20.json": "6eb214ba85d39cb68b15dbc4b4258217865a2ffb2626ec46cceab4b143a81e92", + "erc20.json": "92b4185642315ad6f45d861bb27db9280268e88028bce99361851e60081af0f1", "erc721.json": "a2420c3efcbff0f0be19ce1c1f9ecafb510098a5f4aa802cc72e75cf06d141ee", "misc.json": "5d196e02d0d93403a221925aa68194f8946e506c3d3a38f528a3f310bdc21f6c" } @@ -123,7 +123,7 @@ "chainId": 84532, "tokenLists": { "erc1155.json": "90b87f976209d848d87f802b00e2f4d6f0931f7ddeb5ee2b9f4d7799e24e629c", - "erc20.json": "70cb38f70856af3d5969ad22d62ca05ae7d8204ecd74e420468d43d94cfbf8b3", + "erc20.json": "5f117820cf0939f4b481ea2e00a0f4dae006a9f18797f9c6b35e887349040614", "erc721.json": "6f98afc458aeedebb4a5e6766770564d1b13b7329f235e4aec06fee0aba61b3d", "misc.json": "012d3ed4c4aca85a7a8a7cb0496734713302a4dd9c67a0be68df0112b67b20c8" } @@ -138,7 +138,7 @@ "chainId": 56, "tokenLists": { "erc1155.json": "ae7818922dbfa44cc008d2a1efc4bf0a6545f92c66fe2ee2596d75be78892bc6", - "erc20.json": "ecd4410d45eb6ab2c3a1fd52df0de9581d8b9d39d8752db226b34900e1eb624a", + "erc20.json": "b308f566693d30cdf686b58dbc68d0f144c8aa95284b98a0410e3c2111233d70", "erc721.json": "e5471df6794131d1eb8a2a03a5984052a87015b5c1b204eb98762b492ecac91e", "misc.json": "bf2fb3b03bd2fdda14b8b13075d0e4bbe3eef0daf4bf83f1a6125d9939cf4e86" } @@ -168,7 +168,7 @@ "chainId": 100, "tokenLists": { "erc1155.json": "8932ecfce6b8951d24a850e08de16105fe8d394764f8beefc016b571d894c46f", - "erc20.json": "a7e720784c1a6223e3fdc19dd98ede7f4c4b899ec4dfc4bde6403128dcf62d45", + "erc20.json": "f067786808d83bb3db15db1890f029fa10c6d9b32d74ce4107447fd48928bcfe", "erc721.json": "ce8e5cd39632d71337faa7e4678fad3d0f215a648bea78bed3fbba3c9a032a0f", "misc.json": "5d5dbda639f269f54df67d8a844353522df6224fb0c4a898a2494d3677ab3921" } @@ -213,7 +213,7 @@ "chainId": 1, "tokenLists": { "erc1155.json": "05a4b8873fcb7ae59997e4381a1b7470dd9bcc6120ed4f7d331f751cc1af519a", - "erc20.json": "d5c0c6716a860b07052ada8c2115df0c6894285f7b7cc701fef081ce91c9081c", + "erc20.json": "2760de168de52e31db949eeb084f5745d8ff9f0413694cb47bf0cfcb403d0e8f", "erc721.json": "8e6349df6152d116602a6626909083d8a97892b981a42ca8470db6670d3da66d", "misc.json": "f98a5ccf5fa208e0861c020ad16b44adfafff42d8f359fa94e6701a7397b1e92" } @@ -238,7 +238,7 @@ "chainId": 10, "tokenLists": { "erc1155.json": "d3b8cd8af641eb038f784d1ad753c51ec8262a3c93f6a6d56ba1620a7d88e0e0", - "erc20.json": "9f33579559ede3850dcb42758bec5e0f3ca41c405b4874de74c4ade0156b7844", + "erc20.json": "7b326e59545e96124eef3d0e4d1000508acd5e251327480aecc882317a0ab5b2", "erc721.json": "b31e44ac1f7d75445c07eb621f4c84aed399a228bc00e8c97d322aac6af81452", "misc.json": "125585e3bdfbabdba8c61efbe6344dc4213b47d22013779adf728e9a0d6dd92c" } @@ -256,7 +256,7 @@ "chainId": 137, "tokenLists": { "erc1155.json": "f6a710897facb2b4caa28a78694166302811cbd31139d04cccdc4a3e4a18a36c", - "erc20.json": "b771139705041ecab22d1af557d3e52ab4c2c30d0782db7011506c2103116e45", + "erc20.json": "9774ad1dc9fe0068cd92f5e2b8072a38173b4773d0a68b89a06a0715e30f8b53", "erc721.json": "e32b38f33246eb5dc67bacbb7d72ac0612060cc20aed7d628c30320708fff62f", "misc.json": "80ed5add20b07abafcd9ced20d8cdc0ba6a588b724320c66739e1db3d264d503" } diff --git a/index/mainnet/erc20.json b/index/mainnet/erc20.json index 26a0f9b..f5dfe36 100644 --- a/index/mainnet/erc20.json +++ b/index/mainnet/erc20.json @@ -4301,7 +4301,8 @@ "extensions": { "link": "https://www.ampleforth.org/", "description": "Ampleforth is a digital-asset-protocol for smart commodity-money.\r\n\r\nThe smart commodity money with a unique elastic supply protocol. AMPL supply expands and contracts in response to it’s price deviating from a 1 USD target. Deviations result in a supply change of AMPLs once every 24 hours, increasing or decreasing the number of tokens in each holder’s wallet pro-rata. Ampleforth is the only asset in the world with this elastic supply property, and therefore has counter-cyclical trading pressure and is uncorrelated with other digital assets such as Bitcoin.", - "ogImage": "https://assets.fragments.org/images/ampl_default_image_1.jpg" + "ogImage": "https://assets.fragments.org/images/ampl_default_image_1.jpg", + "rebasing": true } }, { @@ -8031,7 +8032,8 @@ "extensions": { "link": "https://aave.com/atokens", "description": "Aave DAI is an interest bearing token minted upon deposit and burned when redeemed. The aDAI is pegged 1:1 to the value of the underlying DAI that is deposited in Aave protocol. aDAI, can be freely stored, transferred, and traded. While the underlying asset is loaned out to borrowers, aDAI accrue interest in real time, directly in your wallet!", - "ogImage": "https://aave.com/favicon64.png" + "ogImage": "https://aave.com/favicon64.png", + "rebasing": true } }, { @@ -9590,7 +9592,8 @@ "extensions": { "link": "https://aave.com/atokens", "description": "Aave SUSD is an interest bearing token minted upon deposit and burned when redeemed. The aSUSD is pegged 1:1 to the value of the underlying SUSD that is deposited in Aave protocol. aSUSD, can be freely stored, transferred, and traded. While the underlying asset is loaned out to borrowers, aSUSD accrue interest in real time, directly in your wallet!", - "ogImage": "https://aave.com/favicon64.png" + "ogImage": "https://aave.com/favicon64.png", + "rebasing": true } }, { @@ -9603,7 +9606,8 @@ "extensions": { "link": "https://aave.com/atokens", "description": "Aave LINK is an interest bearing token minted upon deposit and burned when redeemed. The aLINK is pegged 1:1 to the value of the underlying LINK that is deposited in Aave protocol. aLINK, can be freely stored, transferred, and traded. While the underlying asset is loaned out to borrowers, aLINK accrue interest in real time, directly in your wallet!", - "ogImage": "https://aave.com/favicon64.png" + "ogImage": "https://aave.com/favicon64.png", + "rebasing": true } }, { @@ -9616,7 +9620,8 @@ "extensions": { "link": "https://aave.com/atokens", "description": "Aave SNX is an interest bearing token minted upon deposit and burned when redeemed. The aSNX is pegged 1:1 to the value of the underlying SNX that is deposited in Aave protocol. aSNX, can be freely stored, transferred, and traded. While the underlying asset is loaned out to borrowers, aSNX accrue interest in real time, directly in your wallet!", - "ogImage": "https://aave.com/favicon64.png" + "ogImage": "https://aave.com/favicon64.png", + "rebasing": true } }, { @@ -9629,7 +9634,8 @@ "extensions": { "link": "https://aave.com/atokens", "description": "Aave BUSD is an interest bearing token pegged 1:1 to underlying BUSD deposited in Aave. aBUSD accrue interest in real time in your wallet.", - "ogImage": "https://aave.com/favicon64.png" + "ogImage": "https://aave.com/favicon64.png", + "rebasing": true } }, { @@ -14519,7 +14525,8 @@ "extensions": { "link": "https://www.ousd.com", "description": "Origin Dollar (OUSD) is the first stablecoin that earns a yield while it's still in your wallet. Built by the team at Origin Protocol (OGN).", - "ogImage": "https://ousd.com/images/share-facebook.png" + "ogImage": "https://ousd.com/images/share-facebook.png", + "rebasing": true } }, { @@ -20770,7 +20777,8 @@ "link": "https://www.lido.fi", "description": "Lido Staked Ether (stETH) is a token that represents your staked ether in Lido, combining the value of initial deposit and staking rewards. stETH tokens are minted upon deposit and burned when redeemed. stETH token balances are pegged 1:1 to the ethers that are staked by Lido and the token’s balances are updated daily to reflect earnings and rewards. stETH tokens can be used as one would use ether, allowing you to earn ETH 2.0 staking rewards whilst benefiting from e.g. yields across decentralised finance products.", "ogImage": "/static/images/meta/lido.png", - "featureIndex": 19 + "featureIndex": 19, + "rebasing": true } }, { @@ -22960,7 +22968,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -22979,7 +22988,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -22998,7 +23008,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23017,7 +23028,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23036,7 +23048,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23055,7 +23068,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23074,7 +23088,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23093,7 +23108,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23112,7 +23128,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23131,7 +23148,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23150,7 +23168,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23169,7 +23188,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23188,7 +23208,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23207,7 +23228,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23226,7 +23248,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23245,7 +23268,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23264,7 +23288,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23283,7 +23308,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23302,7 +23328,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23321,7 +23348,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23340,7 +23368,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23359,7 +23388,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23378,7 +23408,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23397,7 +23428,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23416,7 +23448,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23435,7 +23468,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23454,7 +23488,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23473,7 +23508,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23492,7 +23528,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23511,7 +23548,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23530,7 +23568,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23549,7 +23588,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23568,7 +23608,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23587,7 +23628,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23606,7 +23648,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23625,7 +23668,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23644,7 +23688,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23663,7 +23708,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23682,7 +23728,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23701,7 +23748,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23720,7 +23768,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23739,7 +23788,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23758,7 +23808,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23777,7 +23828,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23796,7 +23848,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23815,7 +23868,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23834,7 +23888,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23853,7 +23908,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23872,7 +23928,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23891,7 +23948,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23910,7 +23968,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23929,7 +23988,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23948,7 +24008,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23967,7 +24028,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -23986,7 +24048,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24005,7 +24068,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24024,7 +24088,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24043,7 +24108,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24062,7 +24128,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24081,7 +24148,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24100,7 +24168,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24119,7 +24188,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24138,7 +24208,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24157,7 +24228,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24176,7 +24248,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24195,7 +24268,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24214,7 +24288,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24233,7 +24308,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24252,7 +24328,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24271,7 +24348,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24290,7 +24368,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24309,7 +24388,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24328,7 +24408,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24347,7 +24428,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24366,7 +24448,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24385,7 +24468,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24404,7 +24488,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24423,7 +24508,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24442,7 +24528,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24461,7 +24548,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24480,7 +24568,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24499,7 +24588,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24518,7 +24608,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24537,7 +24628,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24556,7 +24648,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24575,7 +24668,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24594,7 +24688,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24613,7 +24708,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24632,7 +24728,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24651,7 +24748,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24670,7 +24768,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24689,7 +24788,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24708,7 +24808,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24727,7 +24828,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24746,7 +24848,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24765,7 +24868,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24784,7 +24888,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24803,7 +24908,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24822,7 +24928,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24841,7 +24948,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24860,7 +24968,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24879,7 +24988,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24898,7 +25008,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24917,7 +25028,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24936,7 +25048,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24955,7 +25068,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24974,7 +25088,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -24993,7 +25108,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25012,7 +25128,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25031,7 +25148,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25050,7 +25168,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25069,7 +25188,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25088,7 +25208,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25107,7 +25228,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25126,7 +25248,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25145,7 +25268,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25164,7 +25288,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25183,7 +25308,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25202,7 +25328,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25221,7 +25348,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25240,7 +25368,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25259,7 +25388,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25278,7 +25408,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25297,7 +25428,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25316,7 +25448,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25335,7 +25468,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25354,7 +25488,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25373,7 +25508,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25392,7 +25528,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25411,7 +25548,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25430,7 +25568,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25449,7 +25588,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25468,7 +25608,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25487,7 +25628,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25506,7 +25648,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25525,7 +25668,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25544,7 +25688,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25563,7 +25708,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25582,7 +25728,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25601,7 +25748,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25620,7 +25768,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25639,7 +25788,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -25686,7 +25836,8 @@ "underlyingTokenDecimals": 18, "indexingInfo": { "useOnChainBalance": true - } + }, + "rebasing": true } }, { @@ -25705,7 +25856,8 @@ "underlyingTokenDecimals": 18, "indexingInfo": { "useOnChainBalance": true - } + }, + "rebasing": true } }, { @@ -25724,7 +25876,8 @@ "underlyingTokenDecimals": 18, "indexingInfo": { "useOnChainBalance": true - } + }, + "rebasing": true } }, { @@ -25743,7 +25896,8 @@ "underlyingTokenDecimals": 6, "indexingInfo": { "useOnChainBalance": true - } + }, + "rebasing": true } }, { diff --git a/index/optimism/erc20.json b/index/optimism/erc20.json index 8423a1a..7e3e7fe 100644 --- a/index/optimism/erc20.json +++ b/index/optimism/erc20.json @@ -997,7 +997,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -1016,7 +1017,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -1035,7 +1037,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -1054,7 +1057,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -1073,7 +1077,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -1092,7 +1097,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -1111,7 +1117,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -1130,7 +1137,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -1149,7 +1157,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -1168,7 +1177,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -1187,7 +1197,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -1206,7 +1217,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -1225,7 +1237,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -1244,7 +1257,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { diff --git a/index/polygon/erc20.json b/index/polygon/erc20.json index 59887ac..8ba63ef 100644 --- a/index/polygon/erc20.json +++ b/index/polygon/erc20.json @@ -6134,7 +6134,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6153,7 +6154,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6172,7 +6174,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6191,7 +6194,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6210,7 +6214,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6229,7 +6234,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6248,7 +6254,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6267,7 +6274,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6286,7 +6294,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6305,7 +6314,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6324,7 +6334,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6343,7 +6354,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6362,7 +6374,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6381,7 +6394,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6400,7 +6414,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6419,7 +6434,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6438,7 +6454,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6457,7 +6474,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6476,7 +6494,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6495,7 +6514,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6514,7 +6534,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6533,7 +6554,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6552,7 +6574,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6571,7 +6594,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6590,7 +6614,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6609,7 +6634,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6628,7 +6654,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6647,7 +6674,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6666,7 +6694,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6685,7 +6714,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6704,7 +6734,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6723,7 +6754,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6742,7 +6774,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { @@ -6761,7 +6794,8 @@ "indexingInfo": { "useOnChainBalance": true }, - "protocol": "aave" + "protocol": "aave", + "rebasing": true } }, { diff --git a/package.json b/package.json index 2d335d9..5732c69 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "update-featured": "tsx tools/update-featured.ts", "sync-coingecko": "tsx tools/sync-coingecko.ts", "sync-fee-on-transfer": "tsx tools/sync-fee-on-transfer.ts", + "sync-rebasing": "tsx tools/sync-rebasing.ts", "format": "prettier --write .", "prepare": "husky" }, diff --git a/tools/sync-rebasing.ts b/tools/sync-rebasing.ts new file mode 100644 index 0000000..b4e38e4 --- /dev/null +++ b/tools/sync-rebasing.ts @@ -0,0 +1,554 @@ +import path from 'node:path' +import { promises as fs } from 'node:fs' +import { parseArgs } from 'node:util' +import { createPublicClient, http, parseAbi } from 'viem' +import type { PublicClient } from 'viem' + +const TOKEN_DIRECTORY_ROOT = path.resolve('./index') + +// Cached probe results keyed `chainId:address`, so an interrupted scan resumes +// without re-querying. A token's balance mechanism does not change once deployed. +const CACHE_PATH = path.resolve('./tools/.rebasing-cache.json') + +// Deployed on every chain below at the same address. +const MULTICALL3 = '0xcA11bde05977b3631167028862bE2a173976CA11' as const + +const PROBE_TARGET = '0x0000000000000000000000000000000000000000' as const + +// One multicall carries CHUNK_SIZE * PROBES.length staticcalls; keep the batch +// under public-RPC response limits. +const CHUNK_SIZE = 10 + +// Public RPCs drop calls from an oversized batch and report them as reverts, +// which would read as "not rebasing". Each token therefore carries a sentinel +// call every ERC-20 answers; if it fails the whole result is discarded and the +// token is retried alone before being reported as unreachable. +const MAX_RETRIES = 3 + +// Public RPCs throttle sustained batch traffic; pacing requests keeps a large +// chain (mainnet carries ~2000 tokens) from tripping the limiter mid-scan. +const BATCH_DELAY_MS = 250 + +// Later retry rounds wait longer, giving a tripped rate limiter time to reset. +const RETRY_BACKOFF_MS = 5000 + +const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) + +// Chain folder name -> public RPC. Chains without an entry are skipped, so a +// rebasing token on an unlisted chain stays unflagged rather than mis-flagged. +const CHAIN_RPCS: Record = { + mainnet: 'https://ethereum-rpc.publicnode.com', + polygon: 'https://polygon-bor-rpc.publicnode.com', + base: 'https://base-rpc.publicnode.com', + arbitrum: 'https://arbitrum-one-rpc.publicnode.com', + optimism: 'https://optimism-rpc.publicnode.com', + avalanche: 'https://avalanche-c-chain-rpc.publicnode.com', + bnb: 'https://bsc-rpc.publicnode.com', + gnosis: 'https://gnosis-rpc.publicnode.com', + berachain: 'https://rpc.berachain.com', + sonic: 'https://sonic-rpc.publicnode.com', + soneium: 'https://rpc.soneium.org', + katana: 'https://rpc.katana.network', + hyperevm: 'https://rpc.hyperliquid.xyz/evm', + 'polygon-zkevm': 'https://zkevm-rpc.com', + 'arbitrum-nova': 'https://arbitrum-nova-rpc.publicnode.com', + 'base-sepolia': 'https://base-sepolia-rpc.publicnode.com', + 'arbitrum-sepolia': 'https://arbitrum-sepolia-rpc.publicnode.com', + 'optimism-sepolia': 'https://optimism-sepolia-rpc.publicnode.com', + sepolia: 'https://ethereum-sepolia-rpc.publicnode.com', + amoy: 'https://polygon-amoy-bor-rpc.publicnode.com', + 'avalanche-testnet': 'https://avalanche-fuji-c-chain-rpc.publicnode.com', + 'bnb-testnet': 'https://bsc-testnet-rpc.publicnode.com', +} + +const NATIVE_ADDRESSES = new Set([ + '0x0000000000000000000000000000000000000000', + '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', +]) + +const PROBE_ABI = parseAbi([ + // Sentinel: proves the staticcall reached the contract at all + 'function totalSupply() view returns (uint256)', + // Aave v2/v3 aToken + 'function UNDERLYING_ASSET_ADDRESS() view returns (address)', + 'function scaledBalanceOf(address) view returns (uint256)', + 'function scaledTotalSupply() view returns (uint256)', + // Aave v1 aToken + 'function underlyingAssetAddress() view returns (address)', + 'function principalBalanceOf(address) view returns (uint256)', + // Lido stETH + 'function getPooledEthByShares(uint256) view returns (uint256)', + 'function sharesOf(address) view returns (uint256)', + // Origin OUSD / OETH / superOETHb + 'function rebasingCreditsPerToken() view returns (uint256)', + 'function nonRebasingSupply() view returns (uint256)', + // Mountain USDM and other reward-multiplier rebasers + 'function rewardMultiplier() view returns (uint256)', + 'function convertToShares(uint256) view returns (uint256)', + // ERC4626 marker, used only to exclude share-price vaults + 'function asset() view returns (address)', +]) + +type ProbeName = + | 'totalSupply' + | 'underlyingAssetAddressUpper' + | 'scaledBalanceOf' + | 'scaledTotalSupply' + | 'underlyingAssetAddressLower' + | 'principalBalanceOf' + | 'getPooledEthByShares' + | 'sharesOf' + | 'rebasingCreditsPerToken' + | 'nonRebasingSupply' + | 'rewardMultiplier' + | 'convertToShares' + | 'asset' + +type Probe = { + name: ProbeName + functionName: string + args: readonly unknown[] +} + +const PROBES: readonly Probe[] = [ + { name: 'totalSupply', functionName: 'totalSupply', args: [] }, + { + name: 'underlyingAssetAddressUpper', + functionName: 'UNDERLYING_ASSET_ADDRESS', + args: [], + }, + { + name: 'scaledBalanceOf', + functionName: 'scaledBalanceOf', + args: [PROBE_TARGET], + }, + { name: 'scaledTotalSupply', functionName: 'scaledTotalSupply', args: [] }, + { + name: 'underlyingAssetAddressLower', + functionName: 'underlyingAssetAddress', + args: [], + }, + { + name: 'principalBalanceOf', + functionName: 'principalBalanceOf', + args: [PROBE_TARGET], + }, + { + name: 'getPooledEthByShares', + functionName: 'getPooledEthByShares', + args: [1000000000000000000n], + }, + { name: 'sharesOf', functionName: 'sharesOf', args: [PROBE_TARGET] }, + { + name: 'rebasingCreditsPerToken', + functionName: 'rebasingCreditsPerToken', + args: [], + }, + { name: 'nonRebasingSupply', functionName: 'nonRebasingSupply', args: [] }, + { name: 'rewardMultiplier', functionName: 'rewardMultiplier', args: [] }, + { + name: 'convertToShares', + functionName: 'convertToShares', + args: [1000000000000000000n], + }, + { name: 'asset', functionName: 'asset', args: [] }, +] + +type ProbeResult = Record + +type TokenListEntry = { + chainId: number + address: string + name: string + symbol?: string + decimals?: number + logoURI?: string + extensions?: Record +} + +type TokenList = { + tokens: TokenListEntry[] +} + +type CacheEntry = { mechanism: string | null } + +type ChainStats = { + flagged: number + cleared: number + scanned: number + unreachable: number +} + +/** + * Classify a token from which read-only selectors it answers. Each pair is a + * signature no plain ERC-20 implements, so a hit means balances move without a + * Transfer event. Returns null for tokens with fixed balances. + */ +function classify(probe: ProbeResult): string | null { + // Aave v2/v3 aTokens: balance = scaledBalance * liquidity index. + if (probe.underlyingAssetAddressUpper && probe.scaledBalanceOf) { + return 'aave-atoken' + } + // Aave v1 aTokens predate the scaled-balance interface. + if (probe.underlyingAssetAddressLower && probe.principalBalanceOf) { + return 'aave-v1-atoken' + } + // Lido stETH: balance = shares * pooled ETH / total shares. + if (probe.getPooledEthByShares && probe.sharesOf) { + return 'lido-steth' + } + // Origin OUSD/OETH: balance = credits / creditsPerToken. + if (probe.rebasingCreditsPerToken && probe.nonRebasingSupply) { + return 'origin-rebasing' + } + // Ampleforth UFragments: balance = gons / gonsPerFragment. The Aave check runs + // first, so an aToken never lands here. + if (probe.scaledBalanceOf && probe.scaledTotalSupply) { + return 'ampleforth' + } + // Mountain USDM: balance = shares * rewardMultiplier, scaled to 1e18. Meme + // tokens expose an unscaled rewardMultiplier that means something else, so + // require the shares accessor too. ERC-4626 vaults move share price instead + // of balances, so exclude anything exposing asset(). + if (probe.rewardMultiplier && probe.convertToShares && !probe.asset) { + return 'reward-multiplier' + } + return null +} + +async function loadCache(): Promise> { + let raw: string + try { + raw = await fs.readFile(CACHE_PATH, 'utf-8') + } catch { + return {} + } + + // A corrupt cache means silently rescanning everything, so surface it instead. + try { + return JSON.parse(raw) as Record + } catch { + throw new Error( + `Cache at ${CACHE_PATH} is unreadable; delete it to rescan from scratch.` + ) + } +} + +async function saveCache(cache: Record): Promise { + // Written after every chunk, so a kill mid-write must not truncate the file. + const tmp = `${CACHE_PATH}.tmp` + await fs.writeFile(tmp, `${JSON.stringify(cache, null, 2)}\n`) + await fs.rename(tmp, CACHE_PATH) +} + +/** + * Probe a chunk of tokens in one multicall. Returns null for a token whose + * sentinel call failed, so an RPC fault never looks like "not rebasing". + */ +async function probeChunk( + client: PublicClient, + addresses: readonly string[] +): Promise<(ProbeResult | null)[]> { + const contracts = addresses.flatMap(address => + PROBES.map(probe => ({ + address: address as `0x${string}`, + abi: PROBE_ABI, + functionName: probe.functionName, + args: probe.args, + })) + ) + + const results = await client.multicall({ + contracts, + allowFailure: true, + multicallAddress: MULTICALL3, + }) + + return addresses.map((_, tokenIndex) => { + const slice = results.slice( + tokenIndex * PROBES.length, + (tokenIndex + 1) * PROBES.length + ) + + // Multicall3 returns a failure both for a reverted staticcall and for a + // selector the contract does not implement; either way the probe is absent. + const probe = {} as ProbeResult + PROBES.forEach((definition, index) => { + probe[definition.name] = slice[index].status === 'success' + }) + + // A token that cannot answer totalSupply did not really get called: public + // RPCs silently drop calls from a large batch and report them as reverts. + // Treating that as "no rebase selectors" would unflag real rebasing tokens. + return probe.totalSupply ? probe : null + }) +} + +/** + * Probe one token with individual eth_calls. Pre-EIP-140 contracts answer an + * unknown selector with `throw`, which consumes the entire gas allowance and + * fails every later call in the same multicall; a per-call request bounds that + * blast radius to the one probe. + */ +async function probeSingle( + client: PublicClient, + address: string +): Promise { + const probe = {} as ProbeResult + + for (const definition of PROBES) { + try { + await client.readContract({ + address: address as `0x${string}`, + abi: PROBE_ABI, + functionName: definition.functionName, + args: definition.args, + }) + probe[definition.name] = true + } catch { + probe[definition.name] = false + } + } + + return probe.totalSupply ? probe : null +} + +/** + * Probe tokens, retrying anything whose sentinel failed in progressively + * smaller batches, then one eth_call at a time. Tokens still failing are + * returned as null. + */ +async function probeWithRetry( + client: PublicClient, + tokens: readonly TokenListEntry[] +): Promise> { + const resolved = new Map() + let pending = [...tokens] + + for ( + let attempt = 0; + attempt < MAX_RETRIES && pending.length > 0; + attempt++ + ) { + const stillPending: TokenListEntry[] = [] + + // Let a rate limiter that tripped during the previous round reset. + if (attempt > 0) await sleep(RETRY_BACKOFF_MS * attempt) + + // The last round abandons multicall entirely; a gas-guzzling revert in one + // token can no longer take its neighbours down with it. + if (attempt === MAX_RETRIES - 1) { + for (const token of pending) { + try { + const probe = await probeSingle(client, token.address) + if (probe === null) { + stillPending.push(token) + continue + } + resolved.set(token.address.toLowerCase(), probe) + } catch { + stillPending.push(token) + } + } + pending = stillPending + break + } + + const size = + attempt === 0 ? CHUNK_SIZE : Math.max(1, CHUNK_SIZE >> (attempt * 2)) + + for (let start = 0; start < pending.length; start += size) { + const batch = pending.slice(start, start + size) + if (start > 0) await sleep(BATCH_DELAY_MS) + let probes: (ProbeResult | null)[] + try { + probes = await probeChunk( + client, + batch.map(token => token.address) + ) + } catch { + // A transport-level failure is retryable in the same way a dropped + // call is; fall through to the next, smaller round. + stillPending.push(...batch) + continue + } + + batch.forEach((token, index) => { + const probe = probes[index] + if (probe === null) { + stillPending.push(token) + return + } + resolved.set(token.address.toLowerCase(), probe) + }) + } + + pending = stillPending + } + + // Anything unresolved is reported rather than silently treated as clean. + for (const token of pending) { + resolved.set(token.address.toLowerCase(), null) + } + + return resolved +} + +async function processChain( + chain: string, + write: boolean, + cache: Record +): Promise { + const rpcUrl = CHAIN_RPCS[chain] + const tokenListPath = path.join(TOKEN_DIRECTORY_ROOT, chain, 'erc20.json') + + let tokenList: TokenList + try { + tokenList = JSON.parse( + await fs.readFile(tokenListPath, 'utf-8') + ) as TokenList + } catch { + console.warn(`[${chain}] No erc20.json found, skipping.`) + return { flagged: 0, cleared: 0, scanned: 0, unreachable: 0 } + } + + const candidates = tokenList.tokens.filter( + token => !NATIVE_ADDRESSES.has(token.address.toLowerCase()) + ) + + const client: PublicClient = createPublicClient({ transport: http(rpcUrl) }) + + console.log(`[${chain}] Probing ${candidates.length} tokens...`) + + let flagged = 0 + let cleared = 0 + let unreachable = 0 + let dirty = false + + const uncached = candidates.filter( + token => !(`${token.chainId}:${token.address.toLowerCase()}` in cache) + ) + + if (uncached.length > 0) { + const probes = await probeWithRetry(client, uncached) + for (const token of uncached) { + const probe = probes.get(token.address.toLowerCase()) + // An unresolved probe is left out of the cache so a rerun retries it. + if (probe === null || probe === undefined) continue + cache[`${token.chainId}:${token.address.toLowerCase()}`] = { + mechanism: classify(probe), + } + } + await saveCache(cache) + } + + for (const token of candidates) { + const entry = cache[`${token.chainId}:${token.address.toLowerCase()}`] + if (!entry) { + unreachable++ + console.warn( + ` [${chain}] ${token.symbol ?? token.name} (${token.address}) unreachable` + ) + continue + } + + const isRebasing = entry.mechanism !== null + const wasFlagged = token.extensions?.rebasing === true + + if (isRebasing && !wasFlagged) { + flagged++ + dirty = true + console.log( + ` [${chain}] ${token.symbol ?? token.name} (${token.address}) ${entry.mechanism}` + ) + token.extensions = { ...token.extensions, rebasing: true } + } else if (!isRebasing && wasFlagged) { + // A probe that came back clean contradicts the stored flag; drop it so + // the field never outlives the evidence for it. + cleared++ + dirty = true + console.log( + ` [${chain}] ${token.symbol ?? token.name} (${token.address}) no longer rebasing` + ) + delete token.extensions?.rebasing + } + } + + console.log( + `[${chain}] ${write ? 'Flagged' : 'Would flag'} ${flagged} tokens` + + `${cleared > 0 ? `, cleared ${cleared}` : ''}` + + `${unreachable > 0 ? `, ${unreachable} unreachable` : ''}.` + ) + + if (dirty && write) { + await fs.writeFile(tokenListPath, `${JSON.stringify(tokenList, null, 2)}\n`) + console.log(`[${chain}] Wrote ${tokenListPath}`) + } + + return { flagged, cleared, scanned: candidates.length, unreachable } +} + +const main = async () => { + const args = process.argv.slice(2).filter(a => a !== '--') + const { values } = parseArgs({ + args, + options: { + write: { type: 'boolean', default: false }, + chain: { type: 'string' }, + }, + strict: true, + }) + + const write = values.write ?? false + const chainFilter = values.chain + + if (chainFilter && !CHAIN_RPCS[chainFilter]) { + console.error( + `Unknown chain "${chainFilter}". Supported: ${Object.keys(CHAIN_RPCS).join(', ')}` + ) + process.exitCode = 1 + return + } + + const chains = chainFilter ? [chainFilter] : Object.keys(CHAIN_RPCS) + + const cache = await loadCache() + + let flagged = 0 + let cleared = 0 + let scanned = 0 + let unreachable = 0 + const failed: string[] = [] + + for (const chain of chains) { + try { + const result = await processChain(chain, write, cache) + flagged += result.flagged + cleared += result.cleared + scanned += result.scanned + unreachable += result.unreachable + } catch (error) { + // One chain failing must not discard the chains after it; the cache keeps + // completed probes so a rerun resumes cheaply. + failed.push(chain) + console.error(`[${chain}] Failed: ${(error as Error).message}`) + } + } + + console.log( + `\nProbed ${scanned} tokens, flagged ${flagged}, cleared ${cleared}` + + `, ${unreachable} unreachable.` + ) + if (failed.length > 0) { + console.error(`Incomplete for: ${failed.join(', ')} — rerun to finish.`) + process.exitCode = 1 + } + if (!write) { + console.log('Dry run — pass --write to apply changes.') + } +} + +main().catch(error => { + console.error('Failed to sync rebasing flags:', error) + process.exitCode = 1 +}) From 23b0c90794d5ad553b9f94326671da1c74e31033 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Tue, 29 Sep 2026 08:58:51 +1300 Subject: [PATCH 2/2] docs: document sync-fee-on-transfer tool --- README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.md b/README.md index cc87fed..fd8c73c 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,37 @@ pnpm update-featured -- --write --count 20 Supported chains: mainnet, arbitrum, polygon, optimism, base, avalanche, bnb, gnosis, arbitrum-nova. +## Sync Fee-on-Transfer Tokens + +Flags tokens that tax transfers with `extensions.feeOnTransfer: true`, so consumers can refuse to +quote a fixed amount the recipient will never receive in full. + +Detection uses the [GoPlus token security API](https://gopluslabs.io/token-security-api): any +token with a non-zero `transfer_tax` is flagged. An empty `transfer_tax` means GoPlus has no +result for the token, which is counted as unknown rather than treated as zero. GoPlus only computes +fresh reports for single-address requests, so tokens are queried one at a time with a 4s delay and +rate-limit responses are retried with backoff — a full scan takes hours. Results are cached in +`tools/.fee-on-transfer-cache.json` so an interrupted scan resumes. + +By default the script performs a **dry run**. Pass `--write` to apply changes. + +```bash +# Preview all supported chains +pnpm sync-fee-on-transfer + +# Preview a single chain +pnpm sync-fee-on-transfer -- --chain bnb + +# Apply changes +pnpm sync-fee-on-transfer -- --write +``` + +Supported chains (those GoPlus covers): mainnet, polygon, base, optimism, arbitrum, avalanche, bnb, +gnosis, berachain, soneium, sonic, monad. + +Run `pnpm format` afterwards: the script writes with `JSON.stringify`, which expands short +arrays that Prettier keeps inline. + ## Sync Rebasing Tokens Flags tokens whose balances change without a transfer — Aave aTokens, Lido stETH, Ampleforth, @@ -137,6 +168,7 @@ See [here](https://github.com/0xsequence/token-directory/blob/master/index/mainn ``` `feeOnTransfer` and `rebasing` are populated by tooling rather than by hand — see +[Sync Fee-on-Transfer Tokens](#sync-fee-on-transfer-tokens) and [Sync Rebasing Tokens](#sync-rebasing-tokens). Both are present only when true, so absence means "no evidence gathered", not "verified false".