The two node programs and the data file from f7a2437 regenerate the name and
url of each token in src/shared/tokenList.js. They are now CommonJS under
script/lib/, find their files from their own location, and read
ethereum-lists at a pinned commit. The apply step also rewrites an entry that
already has a name and url, so it runs on the current list; on the committed
data it leaves the list unchanged. The data file carries the MUSD token's
on-chain name, so script/check-censored allows that one literal there too.
README.md names both programs in its Entrypoints section.
Model: opus-5-5
118 lines
4.4 KiB
JavaScript
118 lines
4.4 KiB
JavaScript
// Fetches a name and a website for every token in src/shared/tokenList.js and
|
|
// writes them, keyed by address, to script/lib/token-enrichment.json, which
|
|
// script/lib/apply-enrichment.js then writes into tokenList.js. Run by hand;
|
|
// no build runs it and nothing here is shipped.
|
|
//
|
|
// Usage: node script/lib/enrich-tokens.js
|
|
//
|
|
// Names come from CoinGecko's coin list, one request for every coin, matched on
|
|
// the token's Ethereum contract address. A token CoinGecko does not list gets
|
|
// its symbol as its name. The list is live data with no fixed version, so a
|
|
// later run can return different names.
|
|
//
|
|
// Websites come from the ethereum-lists/tokens repository, one file per token,
|
|
// read at a pinned commit. Most tokens have no file there and get an empty URL.
|
|
// A file that is missing is not an error; any other failed request stops the
|
|
// run, so a network fault cannot quietly empty a URL.
|
|
|
|
"use strict";
|
|
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
|
|
const TOKEN_LIST = path.join(__dirname, "..", "..", "src/shared/tokenList.js");
|
|
const OUTPUT = path.join(__dirname, "token-enrichment.json");
|
|
|
|
const COINGECKO_LIST =
|
|
"https://api.coingecko.com/api/v3/coins/list?include_platform=true";
|
|
// ethereum-lists/tokens at its master of 2026-02-27, when the committed
|
|
// token-enrichment.json was made.
|
|
const ETHEREUM_LISTS =
|
|
"https://raw.githubusercontent.com/ethereum-lists/tokens/c3560d499a380a38e94caa7c4968d59aeffde59a/tokens/eth";
|
|
|
|
const CONCURRENT = 10;
|
|
|
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
|
async function main() {
|
|
const src = fs.readFileSync(TOKEN_LIST, "utf8");
|
|
|
|
const tokens = [];
|
|
const re =
|
|
/\{\s*address:\s*"(0x[a-fA-F0-9]{40})",\s*symbol:\s*"([^"]+)",\s*decimals:\s*(\d+)/g;
|
|
let m;
|
|
while ((m = re.exec(src)) !== null) {
|
|
tokens.push({ address: m[1], symbol: m[2], decimals: parseInt(m[3]) });
|
|
}
|
|
console.error(`Parsed ${tokens.length} tokens`);
|
|
|
|
// --- Names from CoinGecko ---
|
|
console.error("Fetching CoinGecko coins list...");
|
|
const listResp = await fetch(COINGECKO_LIST);
|
|
if (!listResp.ok) throw new Error(`CoinGecko list HTTP ${listResp.status}`);
|
|
const coins = await listResp.json();
|
|
|
|
const addrMap = new Map();
|
|
for (const coin of coins) {
|
|
const eth = (coin.platforms?.ethereum || "").toLowerCase().trim();
|
|
if (eth && eth.startsWith("0x")) {
|
|
addrMap.set(eth, { name: coin.name, id: coin.id });
|
|
}
|
|
}
|
|
console.error(`CoinGecko map: ${addrMap.size} Ethereum tokens`);
|
|
|
|
const enriched = new Map();
|
|
let namesMatched = 0;
|
|
for (const t of tokens) {
|
|
const info = addrMap.get(t.address.toLowerCase());
|
|
if (info) namesMatched++;
|
|
enriched.set(t.address, { name: info?.name || t.symbol, url: "" });
|
|
}
|
|
console.error(`Matched ${namesMatched}/${tokens.length} names`);
|
|
|
|
// --- Websites from ethereum-lists ---
|
|
console.error("Fetching URLs from ethereum-lists GitHub...");
|
|
let urlsFound = 0;
|
|
|
|
for (let i = 0; i < tokens.length; i += CONCURRENT) {
|
|
const batch = tokens.slice(i, i + CONCURRENT);
|
|
await Promise.all(
|
|
batch.map(async (t) => {
|
|
const r = await fetch(`${ETHEREUM_LISTS}/${t.address}.json`);
|
|
if (r.status === 404) return;
|
|
if (!r.ok) {
|
|
throw new Error(
|
|
`ethereum-lists HTTP ${r.status} for ${t.address}`,
|
|
);
|
|
}
|
|
const data = await r.json();
|
|
const website = (data.website || "").trim();
|
|
if (website && website !== "https://") {
|
|
enriched.get(t.address).url = website;
|
|
urlsFound++;
|
|
}
|
|
}),
|
|
);
|
|
if (i % 100 === 0) {
|
|
console.error(
|
|
` GitHub: ${Math.min(i + CONCURRENT, tokens.length)}/${tokens.length}, ${urlsFound} URLs`,
|
|
);
|
|
}
|
|
await sleep(200);
|
|
}
|
|
console.error(`Found ${urlsFound} URLs in ethereum-lists`);
|
|
|
|
// --- Output, in the layout prettier gives it ---
|
|
const output = {};
|
|
for (const [addr, e] of enriched) {
|
|
output[addr] = { name: e.name, url: e.url };
|
|
}
|
|
fs.writeFileSync(OUTPUT, JSON.stringify(output, null, 4) + "\n");
|
|
console.error(`Done! ${tokens.length} names, ${urlsFound} URLs.`);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(`enrich-tokens: ${err.message}`);
|
|
process.exit(1);
|
|
});
|