@anywalletkit/tokens 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -37,10 +37,10 @@ await swaps.quoteSwap2Routes({ ...swapSource(token), toToken: 'USDC', fromTokenA
37
37
 
38
38
  `inspectToken` calls the account's `getTokenInfo(address)` (`wallet-evm`, `wallet-solana`): decimals, symbol, name, supply and holder-relevant flags. A wallet, an NFT or a plain contract throws `NotATokenError`. `assessToken(info, chain)` then lists every warning, worst first:
39
39
 
40
- - **danger**: `impersonation` (the symbol reads as a catalog token of the same chain, or as a native coin or major stablecoin anywhere, after folding Cyrillic / Greek look-alikes, `$` → S, `₮` → T, fullwidth and invisible characters), `permanent-delegate`, `transfer-hook`, `non-transferable`;
41
- - **caution**: `unlisted` (always), `upgradeable`, `mint-authority`, `freeze-authority`, `transfer-fee`, `default-frozen`, `pausable`, `odd-characters`, `no-symbol`, `zero-supply`, `unusual-decimals`.
40
+ - **danger**: `impersonation` (the symbol reads as a catalog token of the same chain, or as a native coin or major stablecoin anywhere, after folding Cyrillic / Greek look-alikes, `$` → S, `₮` → T, ASCII `l` / `1` / `|` → I and `0` → O, `rn` → m, `cl` → d, `vv` → w, accents, fullwidth and invisible characters), `odd-characters` when the symbol has letters that pass for Latin ones (Cyrillic, Greek, Armenian, Cherokee, Canadian syllabics, small capitals, letterlike, enclosed and math letters) or characters that hide in text (zero-width, bidi), `permanent-delegate`, `transfer-hook`, `non-transferable`;
41
+ - **caution**: `unlisted` (always), `upgradeable`, `mint-authority`, `freeze-authority`, `transfer-fee`, `default-frozen`, `pausable`, `odd-characters` (emoji, CJK, accents, a space inside the symbol, odd characters in the name), `no-symbol`, `zero-supply`, `unusual-decimals`.
42
42
 
43
- A user token's symbol and name are what the token says about itself: display them next to the address, never identify the token by them.
43
+ A user token's symbol and name are what the token says about itself: display them next to the address, never identify the token by them. Both are trimmed and cut (`tokenText`: symbol `TOKEN_SYMBOL_MAX` = 32, name `TOKEN_NAME_MAX` = 128 code points) before they are stored or assessed, so what is shown is what was checked.
44
44
 
45
45
  ## TokensTable
46
46
 
package/dist/index.cjs CHANGED
@@ -20,13 +20,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
+ TOKEN_NAME_MAX: () => TOKEN_NAME_MAX,
24
+ TOKEN_SYMBOL_MAX: () => TOKEN_SYMBOL_MAX,
23
25
  TokensError: () => TokensError,
24
26
  TokensTable: () => TokensTable,
25
27
  assessToken: () => assessToken,
26
28
  foldSymbol: () => foldSymbol,
27
29
  inspectToken: () => inspectToken,
28
30
  memoryJsonStorage: () => memoryJsonStorage,
29
- swapSource: () => swapSource
31
+ swapSource: () => swapSource,
32
+ tokenText: () => tokenText
30
33
  });
31
34
  module.exports = __toCommonJS(src_exports);
32
35
 
@@ -43,6 +46,17 @@ var FLAG_RISKS = {
43
46
  "default-frozen": { level: "caution", message: "New holders start frozen until the issuer lets them move it." },
44
47
  "pausable": { level: "caution", message: "The issuer can pause every transfer." }
45
48
  };
49
+ var TOKEN_SYMBOL_MAX = 32;
50
+ var TOKEN_NAME_MAX = 128;
51
+ function tokenText(text, max) {
52
+ let out = "";
53
+ let n = 0;
54
+ for (const ch of text.trim()) {
55
+ if (n++ >= max) break;
56
+ out += ch;
57
+ }
58
+ return out.trim();
59
+ }
46
60
  var CONFUSABLES = {
47
61
  "\u0410": "A",
48
62
  "\u0412": "B",
@@ -105,13 +119,94 @@ var CONFUSABLES = {
105
119
  "\u20AC": "E",
106
120
  "\xA2": "C",
107
121
  "\u20BF": "B",
108
- "\u039E": "E"
122
+ "\u039E": "E",
123
+ // ASCII and Latin look-alikes of I and O (UTS #39 confusables): "DAl" reads as DAI, "USDl" as USD1.
124
+ "l": "I",
125
+ "1": "I",
126
+ "|": "I",
127
+ "\u0131": "I",
128
+ "\u0142": "I",
129
+ "\u01C0": "I",
130
+ "0": "O",
131
+ "\xF8": "O",
132
+ "\xD8": "O",
133
+ // Latin letters without a decomposition, and Latin-1 accented letters for runtimes without String.prototype.normalize.
134
+ "\xD0": "D",
135
+ "\xF0": "D",
136
+ "\u0110": "D",
137
+ "\u0111": "D",
138
+ "\u0126": "H",
139
+ "\u0127": "H",
140
+ "\u0166": "T",
141
+ "\u0167": "T",
142
+ "\xDF": "B",
143
+ "\xDE": "P",
144
+ "\xFE": "P",
145
+ "\u014A": "N",
146
+ "\u014B": "N",
147
+ "\xC0": "A",
148
+ "\xC1": "A",
149
+ "\xC2": "A",
150
+ "\xC3": "A",
151
+ "\xC4": "A",
152
+ "\xC5": "A",
153
+ "\xE0": "A",
154
+ "\xE1": "A",
155
+ "\xE2": "A",
156
+ "\xE3": "A",
157
+ "\xE4": "A",
158
+ "\xE5": "A",
159
+ "\xC7": "C",
160
+ "\xE7": "C",
161
+ "\xC8": "E",
162
+ "\xC9": "E",
163
+ "\xCA": "E",
164
+ "\xCB": "E",
165
+ "\xE8": "E",
166
+ "\xE9": "E",
167
+ "\xEA": "E",
168
+ "\xEB": "E",
169
+ "\xCC": "I",
170
+ "\xCD": "I",
171
+ "\xCE": "I",
172
+ "\xCF": "I",
173
+ "\xEC": "I",
174
+ "\xED": "I",
175
+ "\xEE": "I",
176
+ "\xEF": "I",
177
+ "\xD1": "N",
178
+ "\xF1": "N",
179
+ "\xD2": "O",
180
+ "\xD3": "O",
181
+ "\xD4": "O",
182
+ "\xD5": "O",
183
+ "\xD6": "O",
184
+ "\xF2": "O",
185
+ "\xF3": "O",
186
+ "\xF4": "O",
187
+ "\xF5": "O",
188
+ "\xF6": "O",
189
+ "\xD9": "U",
190
+ "\xDA": "U",
191
+ "\xDB": "U",
192
+ "\xDC": "U",
193
+ "\xF9": "U",
194
+ "\xFA": "U",
195
+ "\xFB": "U",
196
+ "\xFC": "U",
197
+ "\xDD": "Y",
198
+ "\xFD": "Y",
199
+ "\xFF": "Y"
109
200
  };
110
- var INVISIBLE = /[­͏؜ᅟᅠ឴឵᠋-᠏​-‏‪-‮⁠-ㅤ︀-️ᅠ]/g;
201
+ var PAIRS = [[/rn/g, "m"], [/cl/g, "d"], [/vv|VV/g, "W"]];
202
+ var MARKS = /[\u0300-\u036f\u1ab0-\u1aff\u1dc0-\u1dff\u20d0-\u20ff\ufe20-\ufe2f]/g;
203
+ var INVISIBLE = /[\x00-\x08\x0E-\x1F\x7F-\x9F\u00AD\u034F\u061C\u115F\u1160\u17B4\u17B5\u180B-\u180F\u200B-\u200F\u202A-\u202E\u2060-\u206F\u2800\u3164\uFE00-\uFE0F\uFEFF\uFFA0\uFFF9-\uFFFB]|\uDB40[\uDC00-\uDC7F\uDD00-\uDDEF]|\uD834[\uDD59\uDD73-\uDD7A]|\uD82F[\uDCA0-\uDCA3]/g;
111
204
  function foldSymbol(text) {
112
- let s = text.replace(INVISIBLE, "").replace(/\s+/g, "");
113
- if (typeof s.normalize === "function") s = s.normalize("NFKC");
205
+ let s = tokenText(text, TOKEN_SYMBOL_MAX).replace(INVISIBLE, "");
206
+ if (typeof s.normalize === "function") s = s.normalize("NFKD").replace(MARKS, "");
207
+ s = s.replace(/\s+/g, "");
114
208
  s = s.replace(/[\uFF01-\uFF5E]/g, (c) => String.fromCharCode(c.charCodeAt(0) - 65248));
209
+ for (const [re, to] of PAIRS) s = s.replace(re, to);
115
210
  let out = "";
116
211
  for (const ch of s) out += CONFUSABLES[ch] ?? ch;
117
212
  return out.toUpperCase();
@@ -135,17 +230,28 @@ function lookalikeOf(folded, chain) {
135
230
  return majorSymbols().get(folded);
136
231
  }
137
232
  var PLAIN = /^[\x20-\x7E]*$/;
233
+ var LOOKALIKE_LETTERS = /[\u0250-\u02FF\u0370-\u03FF\u0400-\u052F\u0530-\u058F\u10B9\u10BD\u10C7\u10CD\u10E7\u10FD\u10FF\u13A0-\u13FF\u1400-\u167F\u16A0-\u16FF\u1C80-\u1C8F\u1C95\u1CB7\u1CBD\u1CBF\u1D00-\u1DBF\u1F00-\u1FFF\u2100-\u2116\u2118-\u211F\u2121\u2123-\u214F\u2160-\u2188\u2460-\u24FF\u2C80-\u2CFF\u2D2D\u2D30-\u2D7F\u2DE0-\u2DFF\uA4D0-\uA4FF\uA500-\uA63F\uA640-\uA69F\uA720-\uA7FF\uAB30-\uABBF]|\uD835[\uDC00-\uDFFF]|\uD83C[\uDD10-\uDD69]|\uD801[\uDC00-\uDC4F\uDCB0-\uDCFF]|\uD800[\uDE80-\uDEDF\uDF00-\uDF2F]|\uD806[\uDCA0-\uDCFF]/;
234
+ var HIDING = /[\x00-\x08\x0E-\x1F\x7F-\x9F\u00AD\u034F\u061C\u115F\u1160\u17B4\u17B5\u180B-\u180F\u200B\u200C\u200E\u200F\u202A-\u202E\u2060-\u206F\u2800\u3164\uFEFF\uFFA0\uFFF9-\uFFFB]|\uDB40[\uDC00-\uDC7F\uDD00-\uDDEF]|\uD834[\uDD59\uDD73-\uDD7A]|\uD82F[\uDCA0-\uDCA3]/;
235
+ var LINE_BREAK = /[\n\v\f\r\u0085\u2028\u2029]/;
236
+ var EMOJI_TAG_SEQUENCE = /\uD83C\uDFF4(?:\uDB40[\uDC20-\uDC7E])+\uDB40\uDC7F/g;
237
+ var SPELLING_ZWNJ = /([^\x00-\x7F])\u200C(?=[^\x00-\x7F])/g;
238
+ var passesOrHides = (text) => LOOKALIKE_LETTERS.test(text) || HIDING.test(text.replace(EMOJI_TAG_SEQUENCE, "").replace(SPELLING_ZWNJ, "$1"));
138
239
  function assessToken(info, chain) {
139
240
  const risks = [{ code: "unlisted", level: "caution", message: "Not in the wallet's token list. Anyone can create a token with any name and logo: check the address with the project itself." }];
140
- const folded = foldSymbol(info.symbol);
141
- const lookalike = lookalikeOf(folded, chain);
241
+ const symbol = tokenText(info.symbol, TOKEN_SYMBOL_MAX);
242
+ const name = tokenText(info.name, TOKEN_NAME_MAX);
243
+ const whole = info.symbol.slice(0, 4 * TOKEN_SYMBOL_MAX).replace(/\s+/g, "");
244
+ const lookalike = lookalikeOf(foldSymbol(symbol), chain) ?? lookalikeOf(foldSymbol(whole), chain);
142
245
  if (lookalike) {
143
- risks.push({ code: "impersonation", level: "danger", message: `It calls itself ${JSON.stringify(info.symbol)}, like ${lookalike}, but it is a different token at a different address.` });
246
+ risks.push({ code: "impersonation", level: "danger", message: `It calls itself ${JSON.stringify(symbol)}, like ${lookalike}, but it is a different token at a different address.` });
144
247
  }
145
- if (!PLAIN.test(info.symbol) || !PLAIN.test(info.name) || info.symbol !== info.symbol.replace(INVISIBLE, "") || info.name !== info.name.replace(INVISIBLE, "")) {
146
- risks.push({ code: "odd-characters", level: "caution", message: "Its symbol or name uses characters that can look like other letters, or invisible ones." });
248
+ const head = info.symbol.slice(0, 4 * TOKEN_SYMBOL_MAX);
249
+ if (passesOrHides(head) || passesOrHides(symbol) || LINE_BREAK.test(symbol)) {
250
+ risks.push({ code: "odd-characters", level: "danger", message: "Its symbol uses letters of another alphabet that pass for Latin ones (Cyrillic, Greek\u2026) or characters that hide in text." });
251
+ } else if (!PLAIN.test(symbol) || /\s/.test(symbol) || !PLAIN.test(name) || name !== name.replace(INVISIBLE, "")) {
252
+ risks.push({ code: "odd-characters", level: "caution", message: "Its symbol or name uses unusual characters (emoji, other scripts, accents), spaces inside the symbol, or invisible ones." });
147
253
  }
148
- if (!info.symbol.trim()) risks.push({ code: "no-symbol", level: "caution", message: "The token reports no symbol." });
254
+ if (!symbol) risks.push({ code: "no-symbol", level: "caution", message: "The token reports no symbol." });
149
255
  if (info.totalSupply === 0n) risks.push({ code: "zero-supply", level: "caution", message: "Nothing has been minted." });
150
256
  if (info.decimals > 24) risks.push({ code: "unusual-decimals", level: "caution", message: `It uses ${info.decimals} decimals; amounts may display oddly.` });
151
257
  for (const flag of info.flags) {
@@ -191,8 +297,8 @@ function validEntry(t) {
191
297
  return {
192
298
  chain: e.chain,
193
299
  address: e.address,
194
- symbol: e.symbol.slice(0, 128),
195
- name: e.name.slice(0, 128),
300
+ symbol: tokenText(e.symbol, TOKEN_SYMBOL_MAX),
301
+ name: tokenText(e.name, TOKEN_NAME_MAX),
196
302
  decimals: e.decimals,
197
303
  standard: e.standard,
198
304
  flags: [...e.flags],
@@ -213,17 +319,19 @@ async function inspectToken(account, address, now = Date.now) {
213
319
  if (listed) return { listed };
214
320
  if (typeof account.getTokenInfo !== "function") throw new TokensError("unsupported-chain", `This ${account.chain} account cannot read token info.`);
215
321
  const info = await account.getTokenInfo(ref);
322
+ const symbol = tokenText(info.symbol, TOKEN_SYMBOL_MAX);
323
+ const name = tokenText(info.name, TOKEN_NAME_MAX);
216
324
  const token = {
217
325
  chain,
218
326
  address: info.address,
219
- symbol: info.symbol.slice(0, 128),
220
- name: info.name.slice(0, 128),
327
+ symbol,
328
+ name,
221
329
  decimals: info.decimals,
222
330
  standard: info.standard,
223
331
  flags: [...info.flags],
224
332
  addedAt: now()
225
333
  };
226
- return { info, assessment: assessToken(info, chain), token };
334
+ return { info, assessment: assessToken({ ...info, symbol, name }, chain), token };
227
335
  }
228
336
  function swapSource(token) {
229
337
  return { fromToken: token.address, fromTokenDecimals: token.decimals };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/assess.ts","../src/table.ts"],"sourcesContent":["export { assessToken, foldSymbol, type TokenAssessment, type TokenRisk, type TokenRiskCode, type TokenRiskLevel } from './assess.js'\nexport {\n TokensTable, TokensError, inspectToken, swapSource, memoryJsonStorage,\n type JsonStorage, type TokensState, type UserToken, type TokenInspection, type TokensErrorCode,\n} from './table.js'\n","import type { TokenFlag, TokenInfo } from '@anywalletkit/core'\nimport { CC_SOURCE_CHAINS, catalogChainId, ccChain } from '@anywalletkit/swap2-assets'\n\n/** `danger`: the token can take or trap the user's money, or pretends to be another one. `caution`: worth knowing. */\nexport type TokenRiskLevel = 'danger' | 'caution'\n\n/**\n * Why a token deserves a warning:\n * - `unlisted`: always — the wallet's list does not include it, and anyone can deploy a token with any name and logo.\n * - `impersonation`: its symbol reads as a known token's (USDC, ETH, …), look-alike letters included.\n * - `odd-characters`: its symbol or name uses characters outside plain ASCII, or invisible ones.\n * - `no-symbol`: it reports no symbol.\n * - `zero-supply`: nothing has been minted.\n * - `unusual-decimals`: more than 24 decimals.\n * - a {@link TokenFlag} read from the chain (issuer powers, Token-2022 extensions).\n */\nexport type TokenRiskCode = 'unlisted' | 'impersonation' | 'odd-characters' | 'no-symbol' | 'zero-supply' | 'unusual-decimals' | TokenFlag\n\n/** One warning, worded for the person adding the token. */\nexport interface TokenRisk {\n code: TokenRiskCode\n level: TokenRiskLevel\n message: string\n}\n\n/** Every warning that applies, worst first, and the worst level. A user token is never \"safe\": `caution` is the floor. */\nexport interface TokenAssessment {\n level: TokenRiskLevel\n risks: TokenRisk[]\n}\n\nconst FLAG_RISKS: Readonly<Record<TokenFlag, { level: TokenRiskLevel; message: string }>> = {\n 'upgradeable': { level: 'caution', message: \"The token's owner can replace its code.\" },\n 'mint-authority': { level: 'caution', message: 'The issuer can mint more at any time.' },\n 'freeze-authority': { level: 'caution', message: 'The issuer can freeze your balance.' },\n 'permanent-delegate': { level: 'danger', message: 'The issuer can move or burn the tokens in your wallet.' },\n 'transfer-fee': { level: 'caution', message: 'Every transfer pays a fee to the issuer.' },\n 'transfer-hook': { level: 'danger', message: \"A program of the issuer's choosing runs on every transfer: it can block selling.\" },\n 'non-transferable': { level: 'danger', message: 'It cannot be moved: not sent, not swapped.' },\n 'default-frozen': { level: 'caution', message: 'New holders start frozen until the issuer lets them move it.' },\n 'pausable': { level: 'caution', message: 'The issuer can pause every transfer.' },\n}\n\n/** Letters that render like Latin capitals (Cyrillic, Greek) and currency signs used as letters. */\nconst CONFUSABLES: Readonly<Record<string, string>> = {\n 'А': 'A', 'В': 'B', 'Е': 'E', 'К': 'K', 'М': 'M', 'Н': 'H', 'О': 'O', 'Р': 'P', 'С': 'C', 'Т': 'T', 'Х': 'X', 'У': 'Y', 'І': 'I', 'Ј': 'J', 'Ѕ': 'S', 'Ԁ': 'D',\n 'а': 'A', 'в': 'B', 'е': 'E', 'к': 'K', 'м': 'M', 'н': 'H', 'о': 'O', 'р': 'P', 'с': 'C', 'т': 'T', 'х': 'X', 'у': 'Y', 'і': 'I', 'ј': 'J', 'ѕ': 'S', 'ԁ': 'D',\n 'Α': 'A', 'Β': 'B', 'Ε': 'E', 'Ζ': 'Z', 'Η': 'H', 'Ι': 'I', 'Κ': 'K', 'Μ': 'M', 'Ν': 'N', 'Ο': 'O', 'Ρ': 'P', 'Τ': 'T', 'Υ': 'Y', 'Χ': 'X',\n 'α': 'A', 'ε': 'E', 'ι': 'I', 'κ': 'K', 'ν': 'V', 'ο': 'O', 'ρ': 'P', 'τ': 'T', 'υ': 'U', 'χ': 'X',\n '$': 'S', '₮': 'T', '€': 'E', '¢': 'C', '₿': 'B', 'Ξ': 'E',\n}\n/** Zero-width, bidi and other invisible format characters. */\nconst INVISIBLE = /[­͏؜ᅟᅠ឴឵᠋-᠏​-‏‪-‮⁠-ㅤ︀-️ᅠ]/g\n\n/** A symbol folded the way a reader sees it: invisible characters gone, compatibility forms unified, look-alikes mapped, upper case. */\nexport function foldSymbol(text: string): string {\n let s = text.replace(INVISIBLE, '').replace(/\\s+/g, '')\n if (typeof s.normalize === 'function') s = s.normalize('NFKC')\n // Fullwidth ASCII by hand too: some Hermes builds ship without String.prototype.normalize.\n s = s.replace(/[\\uFF01-\\uFF5E]/g, (c) => String.fromCharCode(c.charCodeAt(0) - 0xFEE0))\n let out = ''\n for (const ch of s) out += CONFUSABLES[ch] ?? ch\n return out.toUpperCase()\n}\n\n/** Symbols nobody should reuse on any chain: the big stablecoins and wrapped coins (natives are added from the catalog). */\nconst MAJOR_TOKENS = ['USDT', 'USDC', 'DAI', 'USDe', 'USDS', 'PYUSD', 'FDUSD', 'USD1', 'USDG', 'EURC', 'WETH', 'WBTC', 'cbBTC', 'stETH', 'wstETH', 'WSOL']\n\nlet majors: ReadonlyMap<string, string> | undefined\n/** Folded → display: every chain's native coin from the catalog, plus {@link MAJOR_TOKENS}. */\nfunction majorSymbols(): ReadonlyMap<string, string> {\n if (majors) return majors\n const m = new Map<string, string>()\n for (const chain of CC_SOURCE_CHAINS) for (const a of chain.assets) if (!a.contract && !m.has(foldSymbol(a.symbol))) m.set(foldSymbol(a.symbol), a.symbol)\n for (const sym of MAJOR_TOKENS) if (!m.has(foldSymbol(sym))) m.set(foldSymbol(sym), sym)\n majors = m\n return m\n}\n\n/** The known token a folded symbol reads as: any catalog token on the same chain, or a major one anywhere. */\nfunction lookalikeOf(folded: string, chain: string | undefined): string | undefined {\n if (!folded) return undefined\n if (chain) {\n const same = ccChain(catalogChainId(chain))?.assets.find((a) => foldSymbol(a.symbol) === folded)\n if (same) return same.symbol\n }\n return majorSymbols().get(folded)\n}\n\nconst PLAIN = /^[\\x20-\\x7E]*$/\n\n/**\n * Warnings for a token read with `getTokenInfo`, for a token the wallet's list does not include.\n * Pure: nothing is fetched. `impersonation` when its symbol (look-alike letters folded) reads as a\n * catalog token on the same `chain` — the real one would be in the list, this is another contract —\n * or as a native coin or major stablecoin anywhere. A bridged token of another chain's list (JUP on\n * Solana, listed on Unichain) is not flagged.\n */\nexport function assessToken(info: TokenInfo, chain?: string): TokenAssessment {\n const risks: TokenRisk[] = [{ code: 'unlisted', level: 'caution', message: \"Not in the wallet's token list. Anyone can create a token with any name and logo: check the address with the project itself.\" }]\n const folded = foldSymbol(info.symbol)\n const lookalike = lookalikeOf(folded, chain)\n if (lookalike) {\n risks.push({ code: 'impersonation', level: 'danger', message: `It calls itself ${JSON.stringify(info.symbol)}, like ${lookalike}, but it is a different token at a different address.` })\n }\n if (!PLAIN.test(info.symbol) || !PLAIN.test(info.name) || info.symbol !== info.symbol.replace(INVISIBLE, '') || info.name !== info.name.replace(INVISIBLE, '')) {\n risks.push({ code: 'odd-characters', level: 'caution', message: 'Its symbol or name uses characters that can look like other letters, or invisible ones.' })\n }\n if (!info.symbol.trim()) risks.push({ code: 'no-symbol', level: 'caution', message: 'The token reports no symbol.' })\n if (info.totalSupply === 0n) risks.push({ code: 'zero-supply', level: 'caution', message: 'Nothing has been minted.' })\n if (info.decimals > 24) risks.push({ code: 'unusual-decimals', level: 'caution', message: `It uses ${info.decimals} decimals; amounts may display oddly.` })\n for (const flag of info.flags) {\n const r = FLAG_RISKS[flag]\n if (r) risks.push({ code: flag, ...r })\n }\n risks.sort((a, b) => (a.level === b.level ? 0 : a.level === 'danger' ? -1 : 1))\n return { level: risks.some((r) => r.level === 'danger') ? 'danger' : 'caution', risks }\n}\n","import type { IWalletAccountReadOnly, TokenFlag, TokenInfo } from '@anywalletkit/core'\nimport { catalogChainId, ccAssetByToken, isTokenAddress, type CcAsset } from '@anywalletkit/swap2-assets'\nimport { assessToken, type TokenAssessment } from './assess.js'\n\n/** One JSON value under one key — the shape every small store needs (AsyncStorage, chrome.storage, memory). */\nexport interface JsonStorage<T> {\n /** The stored value, or `null` on first run. */\n get(): Promise<T | null>\n /** Persist the value. */\n set(value: T): Promise<void>\n}\n\n/** In-memory storage for tests and smoke runs. */\nexport function memoryJsonStorage<T>(initial: T | null = null): JsonStorage<T> & { value: T | null } {\n const clone = (v: T): T => JSON.parse(JSON.stringify(v)) as T\n const s = {\n value: initial,\n async get() { return s.value === null ? null : clone(s.value) },\n async set(value: T) { s.value = clone(value) },\n }\n return s\n}\n\n/** A token the user added. `symbol` and `name` are what the token reports: display them, never match on them. */\nexport interface UserToken {\n /** CAIP-2 chain id, the catalog's key (`solana:mainnet`, `eip155:1`). */\n chain: string\n /** Contract (EVM, checksummed as read) or mint (Solana). The token's identity. */\n address: string\n symbol: string\n name: string\n decimals: number\n /** `erc20`, `spl` or `spl-token-2022`. */\n standard: string\n /** Facts read from the chain when it was added. */\n flags: TokenFlag[]\n /** When the user added it (ms since epoch). */\n addedAt: number\n}\n\n/** What {@link TokensTable} stores. */\nexport interface TokensState {\n v: 1\n tokens: UserToken[]\n}\n\n/** Codes of {@link TokensError}. */\nexport type TokensErrorCode = 'not-loaded' | 'unsupported-chain' | 'bad-address' | 'listed' | 'bad-token' | 'full'\n\n/** A refusal from the tokens table or {@link inspectToken}, with a machine-readable `code`. */\nexport class TokensError extends Error {\n readonly code: TokensErrorCode\n constructor(code: TokensErrorCode, message: string) {\n super(message)\n this.name = 'TokensError'\n this.code = code\n }\n}\n\nconst sameAddress = (chain: string, a: string, b: string) => (chain.startsWith('eip155:') ? a.toLowerCase() === b.toLowerCase() : a === b)\nconst FLAGS: ReadonlySet<string> = new Set<TokenFlag>(['upgradeable', 'mint-authority', 'freeze-authority', 'permanent-delegate', 'transfer-fee', 'transfer-hook', 'non-transferable', 'default-frozen', 'pausable'])\n\n/** A stored entry as it must be, or null (a hand-edited or corrupted entry is dropped, never trusted). */\nfunction validEntry(t: unknown): UserToken | null {\n if (!t || typeof t !== 'object') return null\n const e = t as Record<string, unknown>\n if (typeof e.chain !== 'string' || typeof e.address !== 'string' || !isTokenAddress(e.chain, e.address)) return null\n if (typeof e.decimals !== 'number' || !Number.isInteger(e.decimals) || e.decimals < 0 || e.decimals > 77) return null\n if (typeof e.symbol !== 'string' || typeof e.name !== 'string' || typeof e.standard !== 'string' || typeof e.addedAt !== 'number') return null\n if (!Array.isArray(e.flags) || !e.flags.every((f) => typeof f === 'string' && FLAGS.has(f))) return null\n return {\n chain: e.chain, address: e.address, symbol: e.symbol.slice(0, 128), name: e.name.slice(0, 128), decimals: e.decimals,\n standard: e.standard, flags: [...e.flags] as TokenFlag[], addedAt: e.addedAt,\n }\n}\n\n/** The result of looking a token up before adding it. */\nexport type TokenInspection =\n | { listed: CcAsset }\n | { listed?: undefined; info: TokenInfo; assessment: TokenAssessment; token: UserToken }\n\n/**\n * Read a token from the chain through `account` (its chain decides where) and assess it. A token the\n * catalog already lists comes back as `{ listed }`: nothing to add. Otherwise the token, ready for\n * {@link TokensTable.add}, and the warnings to show first. Throws {@link TokensError}\n * (`unsupported-chain`, `bad-address`) or the chain package's `NotATokenError`.\n */\nexport async function inspectToken(\n account: Pick<IWalletAccountReadOnly, 'chain' | 'getTokenInfo'> & { getChainId?: () => Promise<number | bigint> },\n address: string,\n now: () => number = Date.now,\n): Promise<TokenInspection> {\n let reported = account.chain\n // An EVM account created without a chain id reports 'eip155:unknown' until it has asked its RPC:\n // ask now, or the catalog check (already listed? a look-alike of this chain's tokens?) would miss.\n if (reported === 'eip155:unknown') {\n if (typeof account.getChainId !== 'function') throw new TokensError('unsupported-chain', 'The account does not know its chain id; create it with chainId.')\n reported = `eip155:${String(await account.getChainId())}`\n }\n const chain = catalogChainId(reported)\n if (!chain.startsWith('eip155:') && !chain.startsWith('solana:')) throw new TokensError('unsupported-chain', `Adding tokens is supported on EVM chains and Solana, not ${account.chain}.`)\n const ref = address.trim()\n if (!isTokenAddress(chain, ref)) throw new TokensError('bad-address', `${JSON.stringify(address)} is not a token address on ${chain}.`)\n const listed = ccAssetByToken(chain, ref)\n if (listed) return { listed }\n if (typeof account.getTokenInfo !== 'function') throw new TokensError('unsupported-chain', `This ${account.chain} account cannot read token info.`)\n const info = await account.getTokenInfo(ref)\n const token: UserToken = {\n chain, address: info.address, symbol: info.symbol.slice(0, 128), name: info.name.slice(0, 128), decimals: info.decimals,\n standard: info.standard, flags: [...info.flags], addedAt: now(),\n }\n return { info, assessment: assessToken(info, chain), token }\n}\n\n/**\n * The swap options that name a user token as the source: its address and decimals. Pass them to an\n * engine created with `allowUnlistedTokens: true`.\n */\nexport function swapSource(token: Pick<UserToken, 'address' | 'decimals'>): { fromToken: string; fromTokenDecimals: number } {\n return { fromToken: token.address, fromTokenDecimals: token.decimals }\n}\n\n/**\n * The user's own tokens, over any JSON storage: load once at cold start, then read synchronously.\n * Writes queue behind each other; a failed save leaves memory as stored. Tokens the catalog lists are\n * refused (they are already there), and so are addresses that are not EVM contracts or Solana mints.\n */\nexport class TokensTable {\n private _state: TokensState | null = null\n private _loading: Promise<TokensState> | null = null\n private _writes: Promise<unknown> = Promise.resolve()\n private readonly listeners = new Set<() => void>()\n readonly maxTokens: number\n\n constructor(private readonly storage: JsonStorage<TokensState>, options: { maxTokens?: number } = {}) {\n this.maxTokens = options.maxTokens ?? 200\n }\n\n /** Read the stored list. Call once before the sync reads; a failed read throws and leaves the table unloaded. */\n async load(): Promise<void> {\n await this.state()\n }\n\n /** Called after every successful write. Returns the unsubscribe function. */\n subscribe(fn: () => void): () => void {\n this.listeners.add(fn)\n return () => { this.listeners.delete(fn) }\n }\n\n /** The user's tokens, oldest first; only those on `chain` when given. */\n list(chain?: string): UserToken[] {\n const s = this.loaded()\n const want = chain === undefined ? undefined : catalogChainId(chain)\n return s.tokens.filter((t) => want === undefined || t.chain === want).map((t) => ({ ...t, flags: [...t.flags] }))\n }\n\n /** The user token at `address` on `chain`, if added. */\n get(chain: string, address: string): UserToken | undefined {\n const c = catalogChainId(chain)\n const t = this.loaded().tokens.find((x) => x.chain === c && sameAddress(c, x.address, address))\n return t ? { ...t, flags: [...t.flags] } : undefined\n }\n\n /** Add (or refresh) a token — usually `inspection.token` after the user accepted the warnings. */\n async add(token: UserToken): Promise<UserToken> {\n const entry = validEntry({ ...token, chain: catalogChainId(token.chain) })\n if (!entry) throw new TokensError('bad-token', 'Not a valid token entry (address, decimals 0–77, symbol, name, standard, flags).')\n if (ccAssetByToken(entry.chain, entry.address)) throw new TokensError('listed', `${entry.address} is already in the wallet's token list.`)\n await this.update((s) => {\n const rest = s.tokens.filter((t) => !(t.chain === entry.chain && sameAddress(entry.chain, t.address, entry.address)))\n if (rest.length >= this.maxTokens) throw new TokensError('full', `At most ${this.maxTokens} tokens can be added.`)\n return { v: 1, tokens: [...rest, entry] }\n })\n return { ...entry, flags: [...entry.flags] }\n }\n\n /** Remove a token. Returns whether it was there. */\n async remove(chain: string, address: string): Promise<boolean> {\n const c = catalogChainId(chain)\n let removed = false\n await this.update((s) => {\n const rest = s.tokens.filter((t) => !(t.chain === c && sameAddress(c, t.address, address)))\n removed = rest.length !== s.tokens.length\n return { v: 1, tokens: rest }\n })\n return removed\n }\n\n private loaded(): TokensState {\n if (!this._state) throw new TokensError('not-loaded', 'Call load() before reading the tokens table.')\n return this._state\n }\n\n private async state(): Promise<TokensState> {\n if (this._state) return this._state\n this._loading ??= (async () => {\n let stored: TokensState | null\n try {\n stored = await this.storage.get()\n } catch (e) {\n // A failed read is not an empty list: the next write would erase the user's tokens.\n this._loading = null\n throw e\n }\n const tokens = stored && stored.v === 1 && Array.isArray(stored.tokens)\n ? stored.tokens.map(validEntry).filter((t): t is UserToken => t !== null)\n : []\n this._state = { v: 1, tokens }\n return this._state\n })()\n return this._loading\n }\n\n private update(change: (s: TokensState) => TokensState): Promise<void> {\n const run = this._writes.then(async () => {\n const next = change(await this.state())\n await this.storage.set(next)\n this._state = next\n for (const fn of this.listeners) {\n try { fn() } catch { /* a listener's failure is its own */ }\n }\n })\n this._writes = run.catch(() => undefined)\n return run\n }\n}\n"],"mappings":"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","names":["import_swap2_assets"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/assess.ts","../src/table.ts"],"sourcesContent":["export { TOKEN_NAME_MAX, TOKEN_SYMBOL_MAX, assessToken, foldSymbol, tokenText, type TokenAssessment, type TokenRisk, type TokenRiskCode, type TokenRiskLevel } from './assess.js'\nexport {\n TokensTable, TokensError, inspectToken, swapSource, memoryJsonStorage,\n type JsonStorage, type TokensState, type UserToken, type TokenInspection, type TokensErrorCode,\n} from './table.js'\n","import type { TokenFlag, TokenInfo } from '@anywalletkit/core'\nimport { CC_SOURCE_CHAINS, catalogChainId, ccChain } from '@anywalletkit/swap2-assets'\n\n/** `danger`: the token can take or trap the user's money, or pretends to be another one. `caution`: worth knowing. */\nexport type TokenRiskLevel = 'danger' | 'caution'\n\n/**\n * Why a token deserves a warning:\n * - `unlisted`: always — the wallet's list does not include it, and anyone can deploy a token with any name and logo.\n * - `impersonation`: its symbol reads as a known token's (USDC, ETH, …), look-alike letters included.\n * - `odd-characters`: its symbol or name uses characters outside plain ASCII, or invisible ones.\n * - `no-symbol`: it reports no symbol.\n * - `zero-supply`: nothing has been minted.\n * - `unusual-decimals`: more than 24 decimals.\n * - a {@link TokenFlag} read from the chain (issuer powers, Token-2022 extensions).\n */\nexport type TokenRiskCode = 'unlisted' | 'impersonation' | 'odd-characters' | 'no-symbol' | 'zero-supply' | 'unusual-decimals' | TokenFlag\n\n/** One warning, worded for the person adding the token. */\nexport interface TokenRisk {\n code: TokenRiskCode\n level: TokenRiskLevel\n message: string\n}\n\n/** Every warning that applies, worst first, and the worst level. A user token is never \"safe\": `caution` is the floor. */\nexport interface TokenAssessment {\n level: TokenRiskLevel\n risks: TokenRisk[]\n}\n\nconst FLAG_RISKS: Readonly<Record<TokenFlag, { level: TokenRiskLevel; message: string }>> = {\n 'upgradeable': { level: 'caution', message: \"The token's owner can replace its code.\" },\n 'mint-authority': { level: 'caution', message: 'The issuer can mint more at any time.' },\n 'freeze-authority': { level: 'caution', message: 'The issuer can freeze your balance.' },\n 'permanent-delegate': { level: 'danger', message: 'The issuer can move or burn the tokens in your wallet.' },\n 'transfer-fee': { level: 'caution', message: 'Every transfer pays a fee to the issuer.' },\n 'transfer-hook': { level: 'danger', message: \"A program of the issuer's choosing runs on every transfer: it can block selling.\" },\n 'non-transferable': { level: 'danger', message: 'It cannot be moved: not sent, not swapped.' },\n 'default-frozen': { level: 'caution', message: 'New holders start frozen until the issuer lets them move it.' },\n 'pausable': { level: 'caution', message: 'The issuer can pause every transfer.' },\n}\n\n/** Longest symbol the wallet stores and assesses (code points); a longer one is cut, after trimming, before anything reads it. */\nexport const TOKEN_SYMBOL_MAX = 32\n/** Longest name the wallet stores (code points). */\nexport const TOKEN_NAME_MAX = 128\n\n/**\n * A symbol or name as the wallet keeps and shows it: trimmed, cut to `max` code points, trimmed again —\n * so \"USDC\" + 124 spaces + \"X\" is stored, shown and assessed as \"USDC\" (scan 2026-10-27 modules, F28).\n */\nexport function tokenText(text: string, max: number): string {\n let out = ''\n let n = 0\n for (const ch of text.trim()) {\n if (n++ >= max) break\n out += ch\n }\n return out.trim()\n}\n\n/** Letters that render like Latin capitals (Cyrillic, Greek) and currency signs used as letters. */\nconst CONFUSABLES: Readonly<Record<string, string>> = {\n 'А': 'A', 'В': 'B', 'Е': 'E', 'К': 'K', 'М': 'M', 'Н': 'H', 'О': 'O', 'Р': 'P', 'С': 'C', 'Т': 'T', 'Х': 'X', 'У': 'Y', 'І': 'I', 'Ј': 'J', 'Ѕ': 'S', 'Ԁ': 'D',\n 'а': 'A', 'в': 'B', 'е': 'E', 'к': 'K', 'м': 'M', 'н': 'H', 'о': 'O', 'р': 'P', 'с': 'C', 'т': 'T', 'х': 'X', 'у': 'Y', 'і': 'I', 'ј': 'J', 'ѕ': 'S', 'ԁ': 'D',\n 'Α': 'A', 'Β': 'B', 'Ε': 'E', 'Ζ': 'Z', 'Η': 'H', 'Ι': 'I', 'Κ': 'K', 'Μ': 'M', 'Ν': 'N', 'Ο': 'O', 'Ρ': 'P', 'Τ': 'T', 'Υ': 'Y', 'Χ': 'X',\n 'α': 'A', 'ε': 'E', 'ι': 'I', 'κ': 'K', 'ν': 'V', 'ο': 'O', 'ρ': 'P', 'τ': 'T', 'υ': 'U', 'χ': 'X',\n '$': 'S', '₮': 'T', '€': 'E', '¢': 'C', '₿': 'B', 'Ξ': 'E',\n // ASCII and Latin look-alikes of I and O (UTS #39 confusables): \"DAl\" reads as DAI, \"USDl\" as USD1.\n 'l': 'I', '1': 'I', '|': 'I', 'ı': 'I', 'ł': 'I', 'ǀ': 'I', '0': 'O', 'ø': 'O', 'Ø': 'O',\n // Latin letters without a decomposition, and Latin-1 accented letters for runtimes without String.prototype.normalize.\n 'Ð': 'D', 'ð': 'D', 'Đ': 'D', 'đ': 'D', 'Ħ': 'H', 'ħ': 'H', 'Ŧ': 'T', 'ŧ': 'T', 'ß': 'B', 'Þ': 'P', 'þ': 'P', 'Ŋ': 'N', 'ŋ': 'N',\n 'À': 'A', 'Á': 'A', 'Â': 'A', 'Ã': 'A', 'Ä': 'A', 'Å': 'A', 'à': 'A', 'á': 'A', 'â': 'A', 'ã': 'A', 'ä': 'A', 'å': 'A',\n 'Ç': 'C', 'ç': 'C', 'È': 'E', 'É': 'E', 'Ê': 'E', 'Ë': 'E', 'è': 'E', 'é': 'E', 'ê': 'E', 'ë': 'E',\n 'Ì': 'I', 'Í': 'I', 'Î': 'I', 'Ï': 'I', 'ì': 'I', 'í': 'I', 'î': 'I', 'ï': 'I', 'Ñ': 'N', 'ñ': 'N',\n 'Ò': 'O', 'Ó': 'O', 'Ô': 'O', 'Õ': 'O', 'Ö': 'O', 'ò': 'O', 'ó': 'O', 'ô': 'O', 'õ': 'O', 'ö': 'O',\n 'Ù': 'U', 'Ú': 'U', 'Û': 'U', 'Ü': 'U', 'ù': 'U', 'ú': 'U', 'û': 'U', 'ü': 'U', 'Ý': 'Y', 'ý': 'Y', 'ÿ': 'Y',\n}\n/** Letter pairs that read as one letter (UTS #39): rn → m, cl → d, vv → w. */\nconst PAIRS: ReadonlyArray<[RegExp, string]> = [[/rn/g, 'm'], [/cl/g, 'd'], [/vv|VV/g, 'W']]\n/** Combining marks: an accent over a Latin letter reads as the letter. */\nconst MARKS = /[\\u0300-\\u036f\\u1ab0-\\u1aff\\u1dc0-\\u1dff\\u20d0-\\u20ff\\ufe20-\\ufe2f]/g\n/**\n * Characters a reader does not see: zero-width, bidi and other format characters, C0 / C1 controls,\n * fillers and blank glyphs (Hangul fillers, U+2800 braille blank), interlinear annotation, and the\n * astral ones — tag characters (ASCII smuggling), variation selectors 17–256, musical and shorthand\n * format controls. Explicit ranges: Hermes has no Unicode property escapes.\n */\nconst INVISIBLE = /[\\x00-\\x08\\x0E-\\x1F\\x7F-\\x9F\\u00AD\\u034F\\u061C\\u115F\\u1160\\u17B4\\u17B5\\u180B-\\u180F\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u206F\\u2800\\u3164\\uFE00-\\uFE0F\\uFEFF\\uFFA0\\uFFF9-\\uFFFB]|\\uDB40[\\uDC00-\\uDC7F\\uDD00-\\uDDEF]|\\uD834[\\uDD59\\uDD73-\\uDD7A]|\\uD82F[\\uDCA0-\\uDCA3]/g\n\n/**\n * A symbol folded the way a reader sees it — a confusables skeleton in the spirit of UTS #39: invisible\n * characters and spaces gone, compatibility forms unified and accents dropped (NFKD), look-alike letters\n * mapped (Cyrillic, Greek, currency signs, and ASCII l / 1 / | → I, 0 → O; rn → m, cl → d, vv → w), upper\n * case. Only the first {@link TOKEN_SYMBOL_MAX} code points are read (scan 2026-10-27 modules, F36).\n */\nexport function foldSymbol(text: string): string {\n let s = tokenText(text, TOKEN_SYMBOL_MAX).replace(INVISIBLE, '')\n if (typeof s.normalize === 'function') s = s.normalize('NFKD').replace(MARKS, '')\n // After NFKD: a spacing accent (´ ¨ ¸ ‾) decomposes into a space and a mark.\n s = s.replace(/\\s+/g, '')\n // Fullwidth ASCII by hand too: some Hermes builds ship without String.prototype.normalize.\n s = s.replace(/[\\uFF01-\\uFF5E]/g, (c) => String.fromCharCode(c.charCodeAt(0) - 0xFEE0))\n for (const [re, to] of PAIRS) s = s.replace(re, to)\n let out = ''\n for (const ch of s) out += CONFUSABLES[ch] ?? ch\n return out.toUpperCase()\n}\n\n/** Symbols nobody should reuse on any chain: the big stablecoins and wrapped coins (natives are added from the catalog). */\nconst MAJOR_TOKENS = ['USDT', 'USDC', 'DAI', 'USDe', 'USDS', 'PYUSD', 'FDUSD', 'USD1', 'USDG', 'EURC', 'WETH', 'WBTC', 'cbBTC', 'stETH', 'wstETH', 'WSOL']\n\nlet majors: ReadonlyMap<string, string> | undefined\n/** Folded → display: every chain's native coin from the catalog, plus {@link MAJOR_TOKENS}. */\nfunction majorSymbols(): ReadonlyMap<string, string> {\n if (majors) return majors\n const m = new Map<string, string>()\n for (const chain of CC_SOURCE_CHAINS) for (const a of chain.assets) if (!a.contract && !m.has(foldSymbol(a.symbol))) m.set(foldSymbol(a.symbol), a.symbol)\n for (const sym of MAJOR_TOKENS) if (!m.has(foldSymbol(sym))) m.set(foldSymbol(sym), sym)\n majors = m\n return m\n}\n\n/** The known token a folded symbol reads as: any catalog token on the same chain, or a major one anywhere. */\nfunction lookalikeOf(folded: string, chain: string | undefined): string | undefined {\n if (!folded) return undefined\n if (chain) {\n const same = ccChain(catalogChainId(chain))?.assets.find((a) => foldSymbol(a.symbol) === folded)\n if (same) return same.symbol\n }\n return majorSymbols().get(folded)\n}\n\nconst PLAIN = /^[\\x20-\\x7E]*$/\n/**\n * Letters that pass for Latin ones, by block (the whole block, as for Cyrillic): IPA and spacing\n * modifier letters, Greek, Cyrillic, Armenian, Cherokee, Canadian syllabics, runic, phonetic letters\n * and small capitals (also Latin Extended-D / E), letterlike symbols (ℂ ℍ ℕ — not ™ ℠ ℗), Roman\n * numerals, enclosed letters and digits (Ⓤ ①, 🅤), Coptic, Tifinagh, Lisu, Vai, mathematical letters\n * (𝐔) and the astral Deseret, Osage, Lycian, Carian, Old Italic and Warang Citi; for Georgian, a living\n * script, only its look-alike letters (Ⴝ ყ ჿ Ჽ ⴭ…). Explicit ranges: Hermes has no Unicode property\n * escapes. Emoji, CJK, Arabic, Hebrew, Thai and accented Latin are not here: a caution, not a danger.\n */\nconst LOOKALIKE_LETTERS = /[\\u0250-\\u02FF\\u0370-\\u03FF\\u0400-\\u052F\\u0530-\\u058F\\u10B9\\u10BD\\u10C7\\u10CD\\u10E7\\u10FD\\u10FF\\u13A0-\\u13FF\\u1400-\\u167F\\u16A0-\\u16FF\\u1C80-\\u1C8F\\u1C95\\u1CB7\\u1CBD\\u1CBF\\u1D00-\\u1DBF\\u1F00-\\u1FFF\\u2100-\\u2116\\u2118-\\u211F\\u2121\\u2123-\\u214F\\u2160-\\u2188\\u2460-\\u24FF\\u2C80-\\u2CFF\\u2D2D\\u2D30-\\u2D7F\\u2DE0-\\u2DFF\\uA4D0-\\uA4FF\\uA500-\\uA63F\\uA640-\\uA69F\\uA720-\\uA7FF\\uAB30-\\uABBF]|\\uD835[\\uDC00-\\uDFFF]|\\uD83C[\\uDD10-\\uDD69]|\\uD801[\\uDC00-\\uDC4F\\uDCB0-\\uDCFF]|\\uD800[\\uDE80-\\uDEDF\\uDF00-\\uDF2F]|\\uD806[\\uDCA0-\\uDCFF]/\n/**\n * What hides inside text: {@link INVISIBLE} without the joiner and the variation selectors 1–16\n * emoji are built with.\n */\nconst HIDING = /[\\x00-\\x08\\x0E-\\x1F\\x7F-\\x9F\\u00AD\\u034F\\u061C\\u115F\\u1160\\u17B4\\u17B5\\u180B-\\u180F\\u200B\\u200C\\u200E\\u200F\\u202A-\\u202E\\u2060-\\u206F\\u2800\\u3164\\uFEFF\\uFFA0\\uFFF9-\\uFFFB]|\\uDB40[\\uDC00-\\uDC7F\\uDD00-\\uDDEF]|\\uD834[\\uDD59\\uDD73-\\uDD7A]|\\uD82F[\\uDCA0-\\uDCA3]/\n/** A line break inside a symbol: what follows it is on a line a one-line label does not show. */\nconst LINE_BREAK = /[\\n\\v\\f\\r\\u0085\\u2028\\u2029]/\n/** Emoji built from tag characters (the England, Scotland and Wales flags) are emoji, not hidden text. */\nconst EMOJI_TAG_SEQUENCE = /\\uD83C\\uDFF4(?:\\uDB40[\\uDC20-\\uDC7E])+\\uDB40\\uDC7F/g\n/** Zero-width non-joiner between two non-ASCII characters is spelling (Persian, Indic scripts), not hiding. */\nconst SPELLING_ZWNJ = /([^\\x00-\\x7F])\\u200C(?=[^\\x00-\\x7F])/g\n/** Letters that pass for Latin ones, or characters that hide, in `text`. */\nconst passesOrHides = (text: string): boolean => LOOKALIKE_LETTERS.test(text) || HIDING.test(text.replace(EMOJI_TAG_SEQUENCE, '').replace(SPELLING_ZWNJ, '$1'))\n\n\n/**\n * Warnings for a token read with `getTokenInfo`, for a token the wallet's list does not include.\n * Pure: nothing is fetched. `impersonation` when its symbol (look-alike letters folded) reads as a\n * catalog token on the same `chain` — the real one would be in the list, this is another contract —\n * or as a native coin or major stablecoin anywhere. A bridged token of another chain's list (JUP on\n * Solana, listed on Unichain) is not flagged.\n */\nexport function assessToken(info: TokenInfo, chain?: string): TokenAssessment {\n const risks: TokenRisk[] = [{ code: 'unlisted', level: 'caution', message: \"Not in the wallet's token list. Anyone can create a token with any name and logo: check the address with the project itself.\" }]\n // What the wallet stores and shows (trimmed, cut to TOKEN_SYMBOL_MAX), and the whole text a UI that does\n // not cut it would show: a look-alike in either is one (scan 2026-10-27 modules, F28).\n const symbol = tokenText(info.symbol, TOKEN_SYMBOL_MAX)\n const name = tokenText(info.name, TOKEN_NAME_MAX)\n const whole = info.symbol.slice(0, 4 * TOKEN_SYMBOL_MAX).replace(/\\s+/g, '')\n const lookalike = lookalikeOf(foldSymbol(symbol), chain) ?? lookalikeOf(foldSymbol(whole), chain)\n if (lookalike) {\n risks.push({ code: 'impersonation', level: 'danger', message: `It calls itself ${JSON.stringify(symbol)}, like ${lookalike}, but it is a different token at a different address.` })\n }\n // The raw head (what a host showing the raw symbol shows) and the stored symbol (what this wallet shows):\n // leading spaces must not push the look-alike out of the part that is checked.\n const head = info.symbol.slice(0, 4 * TOKEN_SYMBOL_MAX)\n if (passesOrHides(head) || passesOrHides(symbol) || LINE_BREAK.test(symbol)) {\n // Letters of another alphabet that pass for Latin ones, or characters that hide, are how a token passes for a\n // known one (scan 2026-10-27 modules, F36). Emoji, CJK and accented Latin stay a caution (owner decision 2026-10-28).\n risks.push({ code: 'odd-characters', level: 'danger', message: 'Its symbol uses letters of another alphabet that pass for Latin ones (Cyrillic, Greek…) or characters that hide in text.' })\n } else if (!PLAIN.test(symbol) || /\\s/.test(symbol) || !PLAIN.test(name) || name !== name.replace(INVISIBLE, '')) {\n risks.push({ code: 'odd-characters', level: 'caution', message: 'Its symbol or name uses unusual characters (emoji, other scripts, accents), spaces inside the symbol, or invisible ones.' })\n }\n if (!symbol) risks.push({ code: 'no-symbol', level: 'caution', message: 'The token reports no symbol.' })\n if (info.totalSupply === 0n) risks.push({ code: 'zero-supply', level: 'caution', message: 'Nothing has been minted.' })\n if (info.decimals > 24) risks.push({ code: 'unusual-decimals', level: 'caution', message: `It uses ${info.decimals} decimals; amounts may display oddly.` })\n for (const flag of info.flags) {\n const r = FLAG_RISKS[flag]\n if (r) risks.push({ code: flag, ...r })\n }\n risks.sort((a, b) => (a.level === b.level ? 0 : a.level === 'danger' ? -1 : 1))\n return { level: risks.some((r) => r.level === 'danger') ? 'danger' : 'caution', risks }\n}\n","import type { IWalletAccountReadOnly, TokenFlag, TokenInfo } from '@anywalletkit/core'\nimport { catalogChainId, ccAssetByToken, isTokenAddress, type CcAsset } from '@anywalletkit/swap2-assets'\nimport { TOKEN_NAME_MAX, TOKEN_SYMBOL_MAX, assessToken, tokenText, type TokenAssessment } from './assess.js'\n\n/** One JSON value under one key — the shape every small store needs (AsyncStorage, chrome.storage, memory). */\nexport interface JsonStorage<T> {\n /** The stored value, or `null` on first run. */\n get(): Promise<T | null>\n /** Persist the value. */\n set(value: T): Promise<void>\n}\n\n/** In-memory storage for tests and smoke runs. */\nexport function memoryJsonStorage<T>(initial: T | null = null): JsonStorage<T> & { value: T | null } {\n const clone = (v: T): T => JSON.parse(JSON.stringify(v)) as T\n const s = {\n value: initial,\n async get() { return s.value === null ? null : clone(s.value) },\n async set(value: T) { s.value = clone(value) },\n }\n return s\n}\n\n/** A token the user added. `symbol` and `name` are what the token reports: display them, never match on them. */\nexport interface UserToken {\n /** CAIP-2 chain id, the catalog's key (`solana:mainnet`, `eip155:1`). */\n chain: string\n /** Contract (EVM, checksummed as read) or mint (Solana). The token's identity. */\n address: string\n symbol: string\n name: string\n decimals: number\n /** `erc20`, `spl` or `spl-token-2022`. */\n standard: string\n /** Facts read from the chain when it was added. */\n flags: TokenFlag[]\n /** When the user added it (ms since epoch). */\n addedAt: number\n}\n\n/** What {@link TokensTable} stores. */\nexport interface TokensState {\n v: 1\n tokens: UserToken[]\n}\n\n/** Codes of {@link TokensError}. */\nexport type TokensErrorCode = 'not-loaded' | 'unsupported-chain' | 'bad-address' | 'listed' | 'bad-token' | 'full'\n\n/** A refusal from the tokens table or {@link inspectToken}, with a machine-readable `code`. */\nexport class TokensError extends Error {\n readonly code: TokensErrorCode\n constructor(code: TokensErrorCode, message: string) {\n super(message)\n this.name = 'TokensError'\n this.code = code\n }\n}\n\nconst sameAddress = (chain: string, a: string, b: string) => (chain.startsWith('eip155:') ? a.toLowerCase() === b.toLowerCase() : a === b)\nconst FLAGS: ReadonlySet<string> = new Set<TokenFlag>(['upgradeable', 'mint-authority', 'freeze-authority', 'permanent-delegate', 'transfer-fee', 'transfer-hook', 'non-transferable', 'default-frozen', 'pausable'])\n\n/** A stored entry as it must be, or null (a hand-edited or corrupted entry is dropped, never trusted). */\nfunction validEntry(t: unknown): UserToken | null {\n if (!t || typeof t !== 'object') return null\n const e = t as Record<string, unknown>\n if (typeof e.chain !== 'string' || typeof e.address !== 'string' || !isTokenAddress(e.chain, e.address)) return null\n if (typeof e.decimals !== 'number' || !Number.isInteger(e.decimals) || e.decimals < 0 || e.decimals > 77) return null\n if (typeof e.symbol !== 'string' || typeof e.name !== 'string' || typeof e.standard !== 'string' || typeof e.addedAt !== 'number') return null\n if (!Array.isArray(e.flags) || !e.flags.every((f) => typeof f === 'string' && FLAGS.has(f))) return null\n return {\n chain: e.chain, address: e.address, symbol: tokenText(e.symbol, TOKEN_SYMBOL_MAX), name: tokenText(e.name, TOKEN_NAME_MAX), decimals: e.decimals,\n standard: e.standard, flags: [...e.flags] as TokenFlag[], addedAt: e.addedAt,\n }\n}\n\n/** The result of looking a token up before adding it. */\nexport type TokenInspection =\n | { listed: CcAsset }\n | { listed?: undefined; info: TokenInfo; assessment: TokenAssessment; token: UserToken }\n\n/**\n * Read a token from the chain through `account` (its chain decides where) and assess it. A token the\n * catalog already lists comes back as `{ listed }`: nothing to add. Otherwise the token, ready for\n * {@link TokensTable.add}, and the warnings to show first. Throws {@link TokensError}\n * (`unsupported-chain`, `bad-address`) or the chain package's `NotATokenError`.\n */\nexport async function inspectToken(\n account: Pick<IWalletAccountReadOnly, 'chain' | 'getTokenInfo'> & { getChainId?: () => Promise<number | bigint> },\n address: string,\n now: () => number = Date.now,\n): Promise<TokenInspection> {\n let reported = account.chain\n // An EVM account created without a chain id reports 'eip155:unknown' until it has asked its RPC:\n // ask now, or the catalog check (already listed? a look-alike of this chain's tokens?) would miss.\n if (reported === 'eip155:unknown') {\n if (typeof account.getChainId !== 'function') throw new TokensError('unsupported-chain', 'The account does not know its chain id; create it with chainId.')\n reported = `eip155:${String(await account.getChainId())}`\n }\n const chain = catalogChainId(reported)\n if (!chain.startsWith('eip155:') && !chain.startsWith('solana:')) throw new TokensError('unsupported-chain', `Adding tokens is supported on EVM chains and Solana, not ${account.chain}.`)\n const ref = address.trim()\n if (!isTokenAddress(chain, ref)) throw new TokensError('bad-address', `${JSON.stringify(address)} is not a token address on ${chain}.`)\n const listed = ccAssetByToken(chain, ref)\n if (listed) return { listed }\n if (typeof account.getTokenInfo !== 'function') throw new TokensError('unsupported-chain', `This ${account.chain} account cannot read token info.`)\n const info = await account.getTokenInfo(ref)\n // What is stored is what is assessed: trimmed and cut first (scan 2026-10-27 modules, F28).\n const symbol = tokenText(info.symbol, TOKEN_SYMBOL_MAX)\n const name = tokenText(info.name, TOKEN_NAME_MAX)\n const token: UserToken = {\n chain, address: info.address, symbol, name, decimals: info.decimals,\n standard: info.standard, flags: [...info.flags], addedAt: now(),\n }\n return { info, assessment: assessToken({ ...info, symbol, name }, chain), token }\n}\n\n/**\n * The swap options that name a user token as the source: its address and decimals. Pass them to an\n * engine created with `allowUnlistedTokens: true`.\n */\nexport function swapSource(token: Pick<UserToken, 'address' | 'decimals'>): { fromToken: string; fromTokenDecimals: number } {\n return { fromToken: token.address, fromTokenDecimals: token.decimals }\n}\n\n/**\n * The user's own tokens, over any JSON storage: load once at cold start, then read synchronously.\n * Writes queue behind each other; a failed save leaves memory as stored. Tokens the catalog lists are\n * refused (they are already there), and so are addresses that are not EVM contracts or Solana mints.\n */\nexport class TokensTable {\n private _state: TokensState | null = null\n private _loading: Promise<TokensState> | null = null\n private _writes: Promise<unknown> = Promise.resolve()\n private readonly listeners = new Set<() => void>()\n readonly maxTokens: number\n\n constructor(private readonly storage: JsonStorage<TokensState>, options: { maxTokens?: number } = {}) {\n this.maxTokens = options.maxTokens ?? 200\n }\n\n /** Read the stored list. Call once before the sync reads; a failed read throws and leaves the table unloaded. */\n async load(): Promise<void> {\n await this.state()\n }\n\n /** Called after every successful write. Returns the unsubscribe function. */\n subscribe(fn: () => void): () => void {\n this.listeners.add(fn)\n return () => { this.listeners.delete(fn) }\n }\n\n /** The user's tokens, oldest first; only those on `chain` when given. */\n list(chain?: string): UserToken[] {\n const s = this.loaded()\n const want = chain === undefined ? undefined : catalogChainId(chain)\n return s.tokens.filter((t) => want === undefined || t.chain === want).map((t) => ({ ...t, flags: [...t.flags] }))\n }\n\n /** The user token at `address` on `chain`, if added. */\n get(chain: string, address: string): UserToken | undefined {\n const c = catalogChainId(chain)\n const t = this.loaded().tokens.find((x) => x.chain === c && sameAddress(c, x.address, address))\n return t ? { ...t, flags: [...t.flags] } : undefined\n }\n\n /** Add (or refresh) a token — usually `inspection.token` after the user accepted the warnings. */\n async add(token: UserToken): Promise<UserToken> {\n const entry = validEntry({ ...token, chain: catalogChainId(token.chain) })\n if (!entry) throw new TokensError('bad-token', 'Not a valid token entry (address, decimals 0–77, symbol, name, standard, flags).')\n if (ccAssetByToken(entry.chain, entry.address)) throw new TokensError('listed', `${entry.address} is already in the wallet's token list.`)\n await this.update((s) => {\n const rest = s.tokens.filter((t) => !(t.chain === entry.chain && sameAddress(entry.chain, t.address, entry.address)))\n if (rest.length >= this.maxTokens) throw new TokensError('full', `At most ${this.maxTokens} tokens can be added.`)\n return { v: 1, tokens: [...rest, entry] }\n })\n return { ...entry, flags: [...entry.flags] }\n }\n\n /** Remove a token. Returns whether it was there. */\n async remove(chain: string, address: string): Promise<boolean> {\n const c = catalogChainId(chain)\n let removed = false\n await this.update((s) => {\n const rest = s.tokens.filter((t) => !(t.chain === c && sameAddress(c, t.address, address)))\n removed = rest.length !== s.tokens.length\n return { v: 1, tokens: rest }\n })\n return removed\n }\n\n private loaded(): TokensState {\n if (!this._state) throw new TokensError('not-loaded', 'Call load() before reading the tokens table.')\n return this._state\n }\n\n private async state(): Promise<TokensState> {\n if (this._state) return this._state\n this._loading ??= (async () => {\n let stored: TokensState | null\n try {\n stored = await this.storage.get()\n } catch (e) {\n // A failed read is not an empty list: the next write would erase the user's tokens.\n this._loading = null\n throw e\n }\n const tokens = stored && stored.v === 1 && Array.isArray(stored.tokens)\n ? stored.tokens.map(validEntry).filter((t): t is UserToken => t !== null)\n : []\n this._state = { v: 1, tokens }\n return this._state\n })()\n return this._loading\n }\n\n private update(change: (s: TokensState) => TokensState): Promise<void> {\n const run = this._writes.then(async () => {\n const next = change(await this.state())\n await this.storage.set(next)\n this._state = next\n for (const fn of this.listeners) {\n try { fn() } catch { /* a listener's failure is its own */ }\n }\n })\n this._writes = run.catch(() => undefined)\n return run\n }\n}\n"],"mappings":"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","names":["import_swap2_assets"]}
package/dist/index.d.cts CHANGED
@@ -25,7 +25,21 @@ interface TokenAssessment {
25
25
  level: TokenRiskLevel;
26
26
  risks: TokenRisk[];
27
27
  }
28
- /** A symbol folded the way a reader sees it: invisible characters gone, compatibility forms unified, look-alikes mapped, upper case. */
28
+ /** Longest symbol the wallet stores and assesses (code points); a longer one is cut, after trimming, before anything reads it. */
29
+ declare const TOKEN_SYMBOL_MAX = 32;
30
+ /** Longest name the wallet stores (code points). */
31
+ declare const TOKEN_NAME_MAX = 128;
32
+ /**
33
+ * A symbol or name as the wallet keeps and shows it: trimmed, cut to `max` code points, trimmed again —
34
+ * so "USDC" + 124 spaces + "X" is stored, shown and assessed as "USDC" (scan 2026-10-27 modules, F28).
35
+ */
36
+ declare function tokenText(text: string, max: number): string;
37
+ /**
38
+ * A symbol folded the way a reader sees it — a confusables skeleton in the spirit of UTS #39: invisible
39
+ * characters and spaces gone, compatibility forms unified and accents dropped (NFKD), look-alike letters
40
+ * mapped (Cyrillic, Greek, currency signs, and ASCII l / 1 / | → I, 0 → O; rn → m, cl → d, vv → w), upper
41
+ * case. Only the first {@link TOKEN_SYMBOL_MAX} code points are read (scan 2026-10-27 modules, F36).
42
+ */
29
43
  declare function foldSymbol(text: string): string;
30
44
  /**
31
45
  * Warnings for a token read with `getTokenInfo`, for a token the wallet's list does not include.
@@ -133,4 +147,4 @@ declare class TokensTable {
133
147
  private update;
134
148
  }
135
149
 
136
- export { type JsonStorage, type TokenAssessment, type TokenInspection, type TokenRisk, type TokenRiskCode, type TokenRiskLevel, TokensError, type TokensErrorCode, type TokensState, TokensTable, type UserToken, assessToken, foldSymbol, inspectToken, memoryJsonStorage, swapSource };
150
+ export { type JsonStorage, TOKEN_NAME_MAX, TOKEN_SYMBOL_MAX, type TokenAssessment, type TokenInspection, type TokenRisk, type TokenRiskCode, type TokenRiskLevel, TokensError, type TokensErrorCode, type TokensState, TokensTable, type UserToken, assessToken, foldSymbol, inspectToken, memoryJsonStorage, swapSource, tokenText };
package/dist/index.d.ts CHANGED
@@ -25,7 +25,21 @@ interface TokenAssessment {
25
25
  level: TokenRiskLevel;
26
26
  risks: TokenRisk[];
27
27
  }
28
- /** A symbol folded the way a reader sees it: invisible characters gone, compatibility forms unified, look-alikes mapped, upper case. */
28
+ /** Longest symbol the wallet stores and assesses (code points); a longer one is cut, after trimming, before anything reads it. */
29
+ declare const TOKEN_SYMBOL_MAX = 32;
30
+ /** Longest name the wallet stores (code points). */
31
+ declare const TOKEN_NAME_MAX = 128;
32
+ /**
33
+ * A symbol or name as the wallet keeps and shows it: trimmed, cut to `max` code points, trimmed again —
34
+ * so "USDC" + 124 spaces + "X" is stored, shown and assessed as "USDC" (scan 2026-10-27 modules, F28).
35
+ */
36
+ declare function tokenText(text: string, max: number): string;
37
+ /**
38
+ * A symbol folded the way a reader sees it — a confusables skeleton in the spirit of UTS #39: invisible
39
+ * characters and spaces gone, compatibility forms unified and accents dropped (NFKD), look-alike letters
40
+ * mapped (Cyrillic, Greek, currency signs, and ASCII l / 1 / | → I, 0 → O; rn → m, cl → d, vv → w), upper
41
+ * case. Only the first {@link TOKEN_SYMBOL_MAX} code points are read (scan 2026-10-27 modules, F36).
42
+ */
29
43
  declare function foldSymbol(text: string): string;
30
44
  /**
31
45
  * Warnings for a token read with `getTokenInfo`, for a token the wallet's list does not include.
@@ -133,4 +147,4 @@ declare class TokensTable {
133
147
  private update;
134
148
  }
135
149
 
136
- export { type JsonStorage, type TokenAssessment, type TokenInspection, type TokenRisk, type TokenRiskCode, type TokenRiskLevel, TokensError, type TokensErrorCode, type TokensState, TokensTable, type UserToken, assessToken, foldSymbol, inspectToken, memoryJsonStorage, swapSource };
150
+ export { type JsonStorage, TOKEN_NAME_MAX, TOKEN_SYMBOL_MAX, type TokenAssessment, type TokenInspection, type TokenRisk, type TokenRiskCode, type TokenRiskLevel, TokensError, type TokensErrorCode, type TokensState, TokensTable, type UserToken, assessToken, foldSymbol, inspectToken, memoryJsonStorage, swapSource, tokenText };
package/dist/index.js CHANGED
@@ -11,6 +11,17 @@ var FLAG_RISKS = {
11
11
  "default-frozen": { level: "caution", message: "New holders start frozen until the issuer lets them move it." },
12
12
  "pausable": { level: "caution", message: "The issuer can pause every transfer." }
13
13
  };
14
+ var TOKEN_SYMBOL_MAX = 32;
15
+ var TOKEN_NAME_MAX = 128;
16
+ function tokenText(text, max) {
17
+ let out = "";
18
+ let n = 0;
19
+ for (const ch of text.trim()) {
20
+ if (n++ >= max) break;
21
+ out += ch;
22
+ }
23
+ return out.trim();
24
+ }
14
25
  var CONFUSABLES = {
15
26
  "\u0410": "A",
16
27
  "\u0412": "B",
@@ -73,13 +84,94 @@ var CONFUSABLES = {
73
84
  "\u20AC": "E",
74
85
  "\xA2": "C",
75
86
  "\u20BF": "B",
76
- "\u039E": "E"
87
+ "\u039E": "E",
88
+ // ASCII and Latin look-alikes of I and O (UTS #39 confusables): "DAl" reads as DAI, "USDl" as USD1.
89
+ "l": "I",
90
+ "1": "I",
91
+ "|": "I",
92
+ "\u0131": "I",
93
+ "\u0142": "I",
94
+ "\u01C0": "I",
95
+ "0": "O",
96
+ "\xF8": "O",
97
+ "\xD8": "O",
98
+ // Latin letters without a decomposition, and Latin-1 accented letters for runtimes without String.prototype.normalize.
99
+ "\xD0": "D",
100
+ "\xF0": "D",
101
+ "\u0110": "D",
102
+ "\u0111": "D",
103
+ "\u0126": "H",
104
+ "\u0127": "H",
105
+ "\u0166": "T",
106
+ "\u0167": "T",
107
+ "\xDF": "B",
108
+ "\xDE": "P",
109
+ "\xFE": "P",
110
+ "\u014A": "N",
111
+ "\u014B": "N",
112
+ "\xC0": "A",
113
+ "\xC1": "A",
114
+ "\xC2": "A",
115
+ "\xC3": "A",
116
+ "\xC4": "A",
117
+ "\xC5": "A",
118
+ "\xE0": "A",
119
+ "\xE1": "A",
120
+ "\xE2": "A",
121
+ "\xE3": "A",
122
+ "\xE4": "A",
123
+ "\xE5": "A",
124
+ "\xC7": "C",
125
+ "\xE7": "C",
126
+ "\xC8": "E",
127
+ "\xC9": "E",
128
+ "\xCA": "E",
129
+ "\xCB": "E",
130
+ "\xE8": "E",
131
+ "\xE9": "E",
132
+ "\xEA": "E",
133
+ "\xEB": "E",
134
+ "\xCC": "I",
135
+ "\xCD": "I",
136
+ "\xCE": "I",
137
+ "\xCF": "I",
138
+ "\xEC": "I",
139
+ "\xED": "I",
140
+ "\xEE": "I",
141
+ "\xEF": "I",
142
+ "\xD1": "N",
143
+ "\xF1": "N",
144
+ "\xD2": "O",
145
+ "\xD3": "O",
146
+ "\xD4": "O",
147
+ "\xD5": "O",
148
+ "\xD6": "O",
149
+ "\xF2": "O",
150
+ "\xF3": "O",
151
+ "\xF4": "O",
152
+ "\xF5": "O",
153
+ "\xF6": "O",
154
+ "\xD9": "U",
155
+ "\xDA": "U",
156
+ "\xDB": "U",
157
+ "\xDC": "U",
158
+ "\xF9": "U",
159
+ "\xFA": "U",
160
+ "\xFB": "U",
161
+ "\xFC": "U",
162
+ "\xDD": "Y",
163
+ "\xFD": "Y",
164
+ "\xFF": "Y"
77
165
  };
78
- var INVISIBLE = /[­͏؜ᅟᅠ឴឵᠋-᠏​-‏‪-‮⁠-ㅤ︀-️ᅠ]/g;
166
+ var PAIRS = [[/rn/g, "m"], [/cl/g, "d"], [/vv|VV/g, "W"]];
167
+ var MARKS = /[\u0300-\u036f\u1ab0-\u1aff\u1dc0-\u1dff\u20d0-\u20ff\ufe20-\ufe2f]/g;
168
+ var INVISIBLE = /[\x00-\x08\x0E-\x1F\x7F-\x9F\u00AD\u034F\u061C\u115F\u1160\u17B4\u17B5\u180B-\u180F\u200B-\u200F\u202A-\u202E\u2060-\u206F\u2800\u3164\uFE00-\uFE0F\uFEFF\uFFA0\uFFF9-\uFFFB]|\uDB40[\uDC00-\uDC7F\uDD00-\uDDEF]|\uD834[\uDD59\uDD73-\uDD7A]|\uD82F[\uDCA0-\uDCA3]/g;
79
169
  function foldSymbol(text) {
80
- let s = text.replace(INVISIBLE, "").replace(/\s+/g, "");
81
- if (typeof s.normalize === "function") s = s.normalize("NFKC");
170
+ let s = tokenText(text, TOKEN_SYMBOL_MAX).replace(INVISIBLE, "");
171
+ if (typeof s.normalize === "function") s = s.normalize("NFKD").replace(MARKS, "");
172
+ s = s.replace(/\s+/g, "");
82
173
  s = s.replace(/[\uFF01-\uFF5E]/g, (c) => String.fromCharCode(c.charCodeAt(0) - 65248));
174
+ for (const [re, to] of PAIRS) s = s.replace(re, to);
83
175
  let out = "";
84
176
  for (const ch of s) out += CONFUSABLES[ch] ?? ch;
85
177
  return out.toUpperCase();
@@ -103,17 +195,28 @@ function lookalikeOf(folded, chain) {
103
195
  return majorSymbols().get(folded);
104
196
  }
105
197
  var PLAIN = /^[\x20-\x7E]*$/;
198
+ var LOOKALIKE_LETTERS = /[\u0250-\u02FF\u0370-\u03FF\u0400-\u052F\u0530-\u058F\u10B9\u10BD\u10C7\u10CD\u10E7\u10FD\u10FF\u13A0-\u13FF\u1400-\u167F\u16A0-\u16FF\u1C80-\u1C8F\u1C95\u1CB7\u1CBD\u1CBF\u1D00-\u1DBF\u1F00-\u1FFF\u2100-\u2116\u2118-\u211F\u2121\u2123-\u214F\u2160-\u2188\u2460-\u24FF\u2C80-\u2CFF\u2D2D\u2D30-\u2D7F\u2DE0-\u2DFF\uA4D0-\uA4FF\uA500-\uA63F\uA640-\uA69F\uA720-\uA7FF\uAB30-\uABBF]|\uD835[\uDC00-\uDFFF]|\uD83C[\uDD10-\uDD69]|\uD801[\uDC00-\uDC4F\uDCB0-\uDCFF]|\uD800[\uDE80-\uDEDF\uDF00-\uDF2F]|\uD806[\uDCA0-\uDCFF]/;
199
+ var HIDING = /[\x00-\x08\x0E-\x1F\x7F-\x9F\u00AD\u034F\u061C\u115F\u1160\u17B4\u17B5\u180B-\u180F\u200B\u200C\u200E\u200F\u202A-\u202E\u2060-\u206F\u2800\u3164\uFEFF\uFFA0\uFFF9-\uFFFB]|\uDB40[\uDC00-\uDC7F\uDD00-\uDDEF]|\uD834[\uDD59\uDD73-\uDD7A]|\uD82F[\uDCA0-\uDCA3]/;
200
+ var LINE_BREAK = /[\n\v\f\r\u0085\u2028\u2029]/;
201
+ var EMOJI_TAG_SEQUENCE = /\uD83C\uDFF4(?:\uDB40[\uDC20-\uDC7E])+\uDB40\uDC7F/g;
202
+ var SPELLING_ZWNJ = /([^\x00-\x7F])\u200C(?=[^\x00-\x7F])/g;
203
+ var passesOrHides = (text) => LOOKALIKE_LETTERS.test(text) || HIDING.test(text.replace(EMOJI_TAG_SEQUENCE, "").replace(SPELLING_ZWNJ, "$1"));
106
204
  function assessToken(info, chain) {
107
205
  const risks = [{ code: "unlisted", level: "caution", message: "Not in the wallet's token list. Anyone can create a token with any name and logo: check the address with the project itself." }];
108
- const folded = foldSymbol(info.symbol);
109
- const lookalike = lookalikeOf(folded, chain);
206
+ const symbol = tokenText(info.symbol, TOKEN_SYMBOL_MAX);
207
+ const name = tokenText(info.name, TOKEN_NAME_MAX);
208
+ const whole = info.symbol.slice(0, 4 * TOKEN_SYMBOL_MAX).replace(/\s+/g, "");
209
+ const lookalike = lookalikeOf(foldSymbol(symbol), chain) ?? lookalikeOf(foldSymbol(whole), chain);
110
210
  if (lookalike) {
111
- risks.push({ code: "impersonation", level: "danger", message: `It calls itself ${JSON.stringify(info.symbol)}, like ${lookalike}, but it is a different token at a different address.` });
211
+ risks.push({ code: "impersonation", level: "danger", message: `It calls itself ${JSON.stringify(symbol)}, like ${lookalike}, but it is a different token at a different address.` });
112
212
  }
113
- if (!PLAIN.test(info.symbol) || !PLAIN.test(info.name) || info.symbol !== info.symbol.replace(INVISIBLE, "") || info.name !== info.name.replace(INVISIBLE, "")) {
114
- risks.push({ code: "odd-characters", level: "caution", message: "Its symbol or name uses characters that can look like other letters, or invisible ones." });
213
+ const head = info.symbol.slice(0, 4 * TOKEN_SYMBOL_MAX);
214
+ if (passesOrHides(head) || passesOrHides(symbol) || LINE_BREAK.test(symbol)) {
215
+ risks.push({ code: "odd-characters", level: "danger", message: "Its symbol uses letters of another alphabet that pass for Latin ones (Cyrillic, Greek\u2026) or characters that hide in text." });
216
+ } else if (!PLAIN.test(symbol) || /\s/.test(symbol) || !PLAIN.test(name) || name !== name.replace(INVISIBLE, "")) {
217
+ risks.push({ code: "odd-characters", level: "caution", message: "Its symbol or name uses unusual characters (emoji, other scripts, accents), spaces inside the symbol, or invisible ones." });
115
218
  }
116
- if (!info.symbol.trim()) risks.push({ code: "no-symbol", level: "caution", message: "The token reports no symbol." });
219
+ if (!symbol) risks.push({ code: "no-symbol", level: "caution", message: "The token reports no symbol." });
117
220
  if (info.totalSupply === 0n) risks.push({ code: "zero-supply", level: "caution", message: "Nothing has been minted." });
118
221
  if (info.decimals > 24) risks.push({ code: "unusual-decimals", level: "caution", message: `It uses ${info.decimals} decimals; amounts may display oddly.` });
119
222
  for (const flag of info.flags) {
@@ -159,8 +262,8 @@ function validEntry(t) {
159
262
  return {
160
263
  chain: e.chain,
161
264
  address: e.address,
162
- symbol: e.symbol.slice(0, 128),
163
- name: e.name.slice(0, 128),
265
+ symbol: tokenText(e.symbol, TOKEN_SYMBOL_MAX),
266
+ name: tokenText(e.name, TOKEN_NAME_MAX),
164
267
  decimals: e.decimals,
165
268
  standard: e.standard,
166
269
  flags: [...e.flags],
@@ -181,17 +284,19 @@ async function inspectToken(account, address, now = Date.now) {
181
284
  if (listed) return { listed };
182
285
  if (typeof account.getTokenInfo !== "function") throw new TokensError("unsupported-chain", `This ${account.chain} account cannot read token info.`);
183
286
  const info = await account.getTokenInfo(ref);
287
+ const symbol = tokenText(info.symbol, TOKEN_SYMBOL_MAX);
288
+ const name = tokenText(info.name, TOKEN_NAME_MAX);
184
289
  const token = {
185
290
  chain,
186
291
  address: info.address,
187
- symbol: info.symbol.slice(0, 128),
188
- name: info.name.slice(0, 128),
292
+ symbol,
293
+ name,
189
294
  decimals: info.decimals,
190
295
  standard: info.standard,
191
296
  flags: [...info.flags],
192
297
  addedAt: now()
193
298
  };
194
- return { info, assessment: assessToken(info, chain), token };
299
+ return { info, assessment: assessToken({ ...info, symbol, name }, chain), token };
195
300
  }
196
301
  function swapSource(token) {
197
302
  return { fromToken: token.address, fromTokenDecimals: token.decimals };
@@ -290,12 +395,15 @@ var TokensTable = class {
290
395
  }
291
396
  };
292
397
  export {
398
+ TOKEN_NAME_MAX,
399
+ TOKEN_SYMBOL_MAX,
293
400
  TokensError,
294
401
  TokensTable,
295
402
  assessToken,
296
403
  foldSymbol,
297
404
  inspectToken,
298
405
  memoryJsonStorage,
299
- swapSource
406
+ swapSource,
407
+ tokenText
300
408
  };
301
409
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/assess.ts","../src/table.ts"],"sourcesContent":["import type { TokenFlag, TokenInfo } from '@anywalletkit/core'\nimport { CC_SOURCE_CHAINS, catalogChainId, ccChain } from '@anywalletkit/swap2-assets'\n\n/** `danger`: the token can take or trap the user's money, or pretends to be another one. `caution`: worth knowing. */\nexport type TokenRiskLevel = 'danger' | 'caution'\n\n/**\n * Why a token deserves a warning:\n * - `unlisted`: always — the wallet's list does not include it, and anyone can deploy a token with any name and logo.\n * - `impersonation`: its symbol reads as a known token's (USDC, ETH, …), look-alike letters included.\n * - `odd-characters`: its symbol or name uses characters outside plain ASCII, or invisible ones.\n * - `no-symbol`: it reports no symbol.\n * - `zero-supply`: nothing has been minted.\n * - `unusual-decimals`: more than 24 decimals.\n * - a {@link TokenFlag} read from the chain (issuer powers, Token-2022 extensions).\n */\nexport type TokenRiskCode = 'unlisted' | 'impersonation' | 'odd-characters' | 'no-symbol' | 'zero-supply' | 'unusual-decimals' | TokenFlag\n\n/** One warning, worded for the person adding the token. */\nexport interface TokenRisk {\n code: TokenRiskCode\n level: TokenRiskLevel\n message: string\n}\n\n/** Every warning that applies, worst first, and the worst level. A user token is never \"safe\": `caution` is the floor. */\nexport interface TokenAssessment {\n level: TokenRiskLevel\n risks: TokenRisk[]\n}\n\nconst FLAG_RISKS: Readonly<Record<TokenFlag, { level: TokenRiskLevel; message: string }>> = {\n 'upgradeable': { level: 'caution', message: \"The token's owner can replace its code.\" },\n 'mint-authority': { level: 'caution', message: 'The issuer can mint more at any time.' },\n 'freeze-authority': { level: 'caution', message: 'The issuer can freeze your balance.' },\n 'permanent-delegate': { level: 'danger', message: 'The issuer can move or burn the tokens in your wallet.' },\n 'transfer-fee': { level: 'caution', message: 'Every transfer pays a fee to the issuer.' },\n 'transfer-hook': { level: 'danger', message: \"A program of the issuer's choosing runs on every transfer: it can block selling.\" },\n 'non-transferable': { level: 'danger', message: 'It cannot be moved: not sent, not swapped.' },\n 'default-frozen': { level: 'caution', message: 'New holders start frozen until the issuer lets them move it.' },\n 'pausable': { level: 'caution', message: 'The issuer can pause every transfer.' },\n}\n\n/** Letters that render like Latin capitals (Cyrillic, Greek) and currency signs used as letters. */\nconst CONFUSABLES: Readonly<Record<string, string>> = {\n 'А': 'A', 'В': 'B', 'Е': 'E', 'К': 'K', 'М': 'M', 'Н': 'H', 'О': 'O', 'Р': 'P', 'С': 'C', 'Т': 'T', 'Х': 'X', 'У': 'Y', 'І': 'I', 'Ј': 'J', 'Ѕ': 'S', 'Ԁ': 'D',\n 'а': 'A', 'в': 'B', 'е': 'E', 'к': 'K', 'м': 'M', 'н': 'H', 'о': 'O', 'р': 'P', 'с': 'C', 'т': 'T', 'х': 'X', 'у': 'Y', 'і': 'I', 'ј': 'J', 'ѕ': 'S', 'ԁ': 'D',\n 'Α': 'A', 'Β': 'B', 'Ε': 'E', 'Ζ': 'Z', 'Η': 'H', 'Ι': 'I', 'Κ': 'K', 'Μ': 'M', 'Ν': 'N', 'Ο': 'O', 'Ρ': 'P', 'Τ': 'T', 'Υ': 'Y', 'Χ': 'X',\n 'α': 'A', 'ε': 'E', 'ι': 'I', 'κ': 'K', 'ν': 'V', 'ο': 'O', 'ρ': 'P', 'τ': 'T', 'υ': 'U', 'χ': 'X',\n '$': 'S', '₮': 'T', '€': 'E', '¢': 'C', '₿': 'B', 'Ξ': 'E',\n}\n/** Zero-width, bidi and other invisible format characters. */\nconst INVISIBLE = /[­͏؜ᅟᅠ឴឵᠋-᠏​-‏‪-‮⁠-ㅤ︀-️ᅠ]/g\n\n/** A symbol folded the way a reader sees it: invisible characters gone, compatibility forms unified, look-alikes mapped, upper case. */\nexport function foldSymbol(text: string): string {\n let s = text.replace(INVISIBLE, '').replace(/\\s+/g, '')\n if (typeof s.normalize === 'function') s = s.normalize('NFKC')\n // Fullwidth ASCII by hand too: some Hermes builds ship without String.prototype.normalize.\n s = s.replace(/[\\uFF01-\\uFF5E]/g, (c) => String.fromCharCode(c.charCodeAt(0) - 0xFEE0))\n let out = ''\n for (const ch of s) out += CONFUSABLES[ch] ?? ch\n return out.toUpperCase()\n}\n\n/** Symbols nobody should reuse on any chain: the big stablecoins and wrapped coins (natives are added from the catalog). */\nconst MAJOR_TOKENS = ['USDT', 'USDC', 'DAI', 'USDe', 'USDS', 'PYUSD', 'FDUSD', 'USD1', 'USDG', 'EURC', 'WETH', 'WBTC', 'cbBTC', 'stETH', 'wstETH', 'WSOL']\n\nlet majors: ReadonlyMap<string, string> | undefined\n/** Folded → display: every chain's native coin from the catalog, plus {@link MAJOR_TOKENS}. */\nfunction majorSymbols(): ReadonlyMap<string, string> {\n if (majors) return majors\n const m = new Map<string, string>()\n for (const chain of CC_SOURCE_CHAINS) for (const a of chain.assets) if (!a.contract && !m.has(foldSymbol(a.symbol))) m.set(foldSymbol(a.symbol), a.symbol)\n for (const sym of MAJOR_TOKENS) if (!m.has(foldSymbol(sym))) m.set(foldSymbol(sym), sym)\n majors = m\n return m\n}\n\n/** The known token a folded symbol reads as: any catalog token on the same chain, or a major one anywhere. */\nfunction lookalikeOf(folded: string, chain: string | undefined): string | undefined {\n if (!folded) return undefined\n if (chain) {\n const same = ccChain(catalogChainId(chain))?.assets.find((a) => foldSymbol(a.symbol) === folded)\n if (same) return same.symbol\n }\n return majorSymbols().get(folded)\n}\n\nconst PLAIN = /^[\\x20-\\x7E]*$/\n\n/**\n * Warnings for a token read with `getTokenInfo`, for a token the wallet's list does not include.\n * Pure: nothing is fetched. `impersonation` when its symbol (look-alike letters folded) reads as a\n * catalog token on the same `chain` — the real one would be in the list, this is another contract —\n * or as a native coin or major stablecoin anywhere. A bridged token of another chain's list (JUP on\n * Solana, listed on Unichain) is not flagged.\n */\nexport function assessToken(info: TokenInfo, chain?: string): TokenAssessment {\n const risks: TokenRisk[] = [{ code: 'unlisted', level: 'caution', message: \"Not in the wallet's token list. Anyone can create a token with any name and logo: check the address with the project itself.\" }]\n const folded = foldSymbol(info.symbol)\n const lookalike = lookalikeOf(folded, chain)\n if (lookalike) {\n risks.push({ code: 'impersonation', level: 'danger', message: `It calls itself ${JSON.stringify(info.symbol)}, like ${lookalike}, but it is a different token at a different address.` })\n }\n if (!PLAIN.test(info.symbol) || !PLAIN.test(info.name) || info.symbol !== info.symbol.replace(INVISIBLE, '') || info.name !== info.name.replace(INVISIBLE, '')) {\n risks.push({ code: 'odd-characters', level: 'caution', message: 'Its symbol or name uses characters that can look like other letters, or invisible ones.' })\n }\n if (!info.symbol.trim()) risks.push({ code: 'no-symbol', level: 'caution', message: 'The token reports no symbol.' })\n if (info.totalSupply === 0n) risks.push({ code: 'zero-supply', level: 'caution', message: 'Nothing has been minted.' })\n if (info.decimals > 24) risks.push({ code: 'unusual-decimals', level: 'caution', message: `It uses ${info.decimals} decimals; amounts may display oddly.` })\n for (const flag of info.flags) {\n const r = FLAG_RISKS[flag]\n if (r) risks.push({ code: flag, ...r })\n }\n risks.sort((a, b) => (a.level === b.level ? 0 : a.level === 'danger' ? -1 : 1))\n return { level: risks.some((r) => r.level === 'danger') ? 'danger' : 'caution', risks }\n}\n","import type { IWalletAccountReadOnly, TokenFlag, TokenInfo } from '@anywalletkit/core'\nimport { catalogChainId, ccAssetByToken, isTokenAddress, type CcAsset } from '@anywalletkit/swap2-assets'\nimport { assessToken, type TokenAssessment } from './assess.js'\n\n/** One JSON value under one key — the shape every small store needs (AsyncStorage, chrome.storage, memory). */\nexport interface JsonStorage<T> {\n /** The stored value, or `null` on first run. */\n get(): Promise<T | null>\n /** Persist the value. */\n set(value: T): Promise<void>\n}\n\n/** In-memory storage for tests and smoke runs. */\nexport function memoryJsonStorage<T>(initial: T | null = null): JsonStorage<T> & { value: T | null } {\n const clone = (v: T): T => JSON.parse(JSON.stringify(v)) as T\n const s = {\n value: initial,\n async get() { return s.value === null ? null : clone(s.value) },\n async set(value: T) { s.value = clone(value) },\n }\n return s\n}\n\n/** A token the user added. `symbol` and `name` are what the token reports: display them, never match on them. */\nexport interface UserToken {\n /** CAIP-2 chain id, the catalog's key (`solana:mainnet`, `eip155:1`). */\n chain: string\n /** Contract (EVM, checksummed as read) or mint (Solana). The token's identity. */\n address: string\n symbol: string\n name: string\n decimals: number\n /** `erc20`, `spl` or `spl-token-2022`. */\n standard: string\n /** Facts read from the chain when it was added. */\n flags: TokenFlag[]\n /** When the user added it (ms since epoch). */\n addedAt: number\n}\n\n/** What {@link TokensTable} stores. */\nexport interface TokensState {\n v: 1\n tokens: UserToken[]\n}\n\n/** Codes of {@link TokensError}. */\nexport type TokensErrorCode = 'not-loaded' | 'unsupported-chain' | 'bad-address' | 'listed' | 'bad-token' | 'full'\n\n/** A refusal from the tokens table or {@link inspectToken}, with a machine-readable `code`. */\nexport class TokensError extends Error {\n readonly code: TokensErrorCode\n constructor(code: TokensErrorCode, message: string) {\n super(message)\n this.name = 'TokensError'\n this.code = code\n }\n}\n\nconst sameAddress = (chain: string, a: string, b: string) => (chain.startsWith('eip155:') ? a.toLowerCase() === b.toLowerCase() : a === b)\nconst FLAGS: ReadonlySet<string> = new Set<TokenFlag>(['upgradeable', 'mint-authority', 'freeze-authority', 'permanent-delegate', 'transfer-fee', 'transfer-hook', 'non-transferable', 'default-frozen', 'pausable'])\n\n/** A stored entry as it must be, or null (a hand-edited or corrupted entry is dropped, never trusted). */\nfunction validEntry(t: unknown): UserToken | null {\n if (!t || typeof t !== 'object') return null\n const e = t as Record<string, unknown>\n if (typeof e.chain !== 'string' || typeof e.address !== 'string' || !isTokenAddress(e.chain, e.address)) return null\n if (typeof e.decimals !== 'number' || !Number.isInteger(e.decimals) || e.decimals < 0 || e.decimals > 77) return null\n if (typeof e.symbol !== 'string' || typeof e.name !== 'string' || typeof e.standard !== 'string' || typeof e.addedAt !== 'number') return null\n if (!Array.isArray(e.flags) || !e.flags.every((f) => typeof f === 'string' && FLAGS.has(f))) return null\n return {\n chain: e.chain, address: e.address, symbol: e.symbol.slice(0, 128), name: e.name.slice(0, 128), decimals: e.decimals,\n standard: e.standard, flags: [...e.flags] as TokenFlag[], addedAt: e.addedAt,\n }\n}\n\n/** The result of looking a token up before adding it. */\nexport type TokenInspection =\n | { listed: CcAsset }\n | { listed?: undefined; info: TokenInfo; assessment: TokenAssessment; token: UserToken }\n\n/**\n * Read a token from the chain through `account` (its chain decides where) and assess it. A token the\n * catalog already lists comes back as `{ listed }`: nothing to add. Otherwise the token, ready for\n * {@link TokensTable.add}, and the warnings to show first. Throws {@link TokensError}\n * (`unsupported-chain`, `bad-address`) or the chain package's `NotATokenError`.\n */\nexport async function inspectToken(\n account: Pick<IWalletAccountReadOnly, 'chain' | 'getTokenInfo'> & { getChainId?: () => Promise<number | bigint> },\n address: string,\n now: () => number = Date.now,\n): Promise<TokenInspection> {\n let reported = account.chain\n // An EVM account created without a chain id reports 'eip155:unknown' until it has asked its RPC:\n // ask now, or the catalog check (already listed? a look-alike of this chain's tokens?) would miss.\n if (reported === 'eip155:unknown') {\n if (typeof account.getChainId !== 'function') throw new TokensError('unsupported-chain', 'The account does not know its chain id; create it with chainId.')\n reported = `eip155:${String(await account.getChainId())}`\n }\n const chain = catalogChainId(reported)\n if (!chain.startsWith('eip155:') && !chain.startsWith('solana:')) throw new TokensError('unsupported-chain', `Adding tokens is supported on EVM chains and Solana, not ${account.chain}.`)\n const ref = address.trim()\n if (!isTokenAddress(chain, ref)) throw new TokensError('bad-address', `${JSON.stringify(address)} is not a token address on ${chain}.`)\n const listed = ccAssetByToken(chain, ref)\n if (listed) return { listed }\n if (typeof account.getTokenInfo !== 'function') throw new TokensError('unsupported-chain', `This ${account.chain} account cannot read token info.`)\n const info = await account.getTokenInfo(ref)\n const token: UserToken = {\n chain, address: info.address, symbol: info.symbol.slice(0, 128), name: info.name.slice(0, 128), decimals: info.decimals,\n standard: info.standard, flags: [...info.flags], addedAt: now(),\n }\n return { info, assessment: assessToken(info, chain), token }\n}\n\n/**\n * The swap options that name a user token as the source: its address and decimals. Pass them to an\n * engine created with `allowUnlistedTokens: true`.\n */\nexport function swapSource(token: Pick<UserToken, 'address' | 'decimals'>): { fromToken: string; fromTokenDecimals: number } {\n return { fromToken: token.address, fromTokenDecimals: token.decimals }\n}\n\n/**\n * The user's own tokens, over any JSON storage: load once at cold start, then read synchronously.\n * Writes queue behind each other; a failed save leaves memory as stored. Tokens the catalog lists are\n * refused (they are already there), and so are addresses that are not EVM contracts or Solana mints.\n */\nexport class TokensTable {\n private _state: TokensState | null = null\n private _loading: Promise<TokensState> | null = null\n private _writes: Promise<unknown> = Promise.resolve()\n private readonly listeners = new Set<() => void>()\n readonly maxTokens: number\n\n constructor(private readonly storage: JsonStorage<TokensState>, options: { maxTokens?: number } = {}) {\n this.maxTokens = options.maxTokens ?? 200\n }\n\n /** Read the stored list. Call once before the sync reads; a failed read throws and leaves the table unloaded. */\n async load(): Promise<void> {\n await this.state()\n }\n\n /** Called after every successful write. Returns the unsubscribe function. */\n subscribe(fn: () => void): () => void {\n this.listeners.add(fn)\n return () => { this.listeners.delete(fn) }\n }\n\n /** The user's tokens, oldest first; only those on `chain` when given. */\n list(chain?: string): UserToken[] {\n const s = this.loaded()\n const want = chain === undefined ? undefined : catalogChainId(chain)\n return s.tokens.filter((t) => want === undefined || t.chain === want).map((t) => ({ ...t, flags: [...t.flags] }))\n }\n\n /** The user token at `address` on `chain`, if added. */\n get(chain: string, address: string): UserToken | undefined {\n const c = catalogChainId(chain)\n const t = this.loaded().tokens.find((x) => x.chain === c && sameAddress(c, x.address, address))\n return t ? { ...t, flags: [...t.flags] } : undefined\n }\n\n /** Add (or refresh) a token — usually `inspection.token` after the user accepted the warnings. */\n async add(token: UserToken): Promise<UserToken> {\n const entry = validEntry({ ...token, chain: catalogChainId(token.chain) })\n if (!entry) throw new TokensError('bad-token', 'Not a valid token entry (address, decimals 0–77, symbol, name, standard, flags).')\n if (ccAssetByToken(entry.chain, entry.address)) throw new TokensError('listed', `${entry.address} is already in the wallet's token list.`)\n await this.update((s) => {\n const rest = s.tokens.filter((t) => !(t.chain === entry.chain && sameAddress(entry.chain, t.address, entry.address)))\n if (rest.length >= this.maxTokens) throw new TokensError('full', `At most ${this.maxTokens} tokens can be added.`)\n return { v: 1, tokens: [...rest, entry] }\n })\n return { ...entry, flags: [...entry.flags] }\n }\n\n /** Remove a token. Returns whether it was there. */\n async remove(chain: string, address: string): Promise<boolean> {\n const c = catalogChainId(chain)\n let removed = false\n await this.update((s) => {\n const rest = s.tokens.filter((t) => !(t.chain === c && sameAddress(c, t.address, address)))\n removed = rest.length !== s.tokens.length\n return { v: 1, tokens: rest }\n })\n return removed\n }\n\n private loaded(): TokensState {\n if (!this._state) throw new TokensError('not-loaded', 'Call load() before reading the tokens table.')\n return this._state\n }\n\n private async state(): Promise<TokensState> {\n if (this._state) return this._state\n this._loading ??= (async () => {\n let stored: TokensState | null\n try {\n stored = await this.storage.get()\n } catch (e) {\n // A failed read is not an empty list: the next write would erase the user's tokens.\n this._loading = null\n throw e\n }\n const tokens = stored && stored.v === 1 && Array.isArray(stored.tokens)\n ? stored.tokens.map(validEntry).filter((t): t is UserToken => t !== null)\n : []\n this._state = { v: 1, tokens }\n return this._state\n })()\n return this._loading\n }\n\n private update(change: (s: TokensState) => TokensState): Promise<void> {\n const run = this._writes.then(async () => {\n const next = change(await this.state())\n await this.storage.set(next)\n this._state = next\n for (const fn of this.listeners) {\n try { fn() } catch { /* a listener's failure is its own */ }\n }\n })\n this._writes = run.catch(() => undefined)\n return run\n }\n}\n"],"mappings":"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","names":["catalogChainId","catalogChainId"]}
1
+ {"version":3,"sources":["../src/assess.ts","../src/table.ts"],"sourcesContent":["import type { TokenFlag, TokenInfo } from '@anywalletkit/core'\nimport { CC_SOURCE_CHAINS, catalogChainId, ccChain } from '@anywalletkit/swap2-assets'\n\n/** `danger`: the token can take or trap the user's money, or pretends to be another one. `caution`: worth knowing. */\nexport type TokenRiskLevel = 'danger' | 'caution'\n\n/**\n * Why a token deserves a warning:\n * - `unlisted`: always — the wallet's list does not include it, and anyone can deploy a token with any name and logo.\n * - `impersonation`: its symbol reads as a known token's (USDC, ETH, …), look-alike letters included.\n * - `odd-characters`: its symbol or name uses characters outside plain ASCII, or invisible ones.\n * - `no-symbol`: it reports no symbol.\n * - `zero-supply`: nothing has been minted.\n * - `unusual-decimals`: more than 24 decimals.\n * - a {@link TokenFlag} read from the chain (issuer powers, Token-2022 extensions).\n */\nexport type TokenRiskCode = 'unlisted' | 'impersonation' | 'odd-characters' | 'no-symbol' | 'zero-supply' | 'unusual-decimals' | TokenFlag\n\n/** One warning, worded for the person adding the token. */\nexport interface TokenRisk {\n code: TokenRiskCode\n level: TokenRiskLevel\n message: string\n}\n\n/** Every warning that applies, worst first, and the worst level. A user token is never \"safe\": `caution` is the floor. */\nexport interface TokenAssessment {\n level: TokenRiskLevel\n risks: TokenRisk[]\n}\n\nconst FLAG_RISKS: Readonly<Record<TokenFlag, { level: TokenRiskLevel; message: string }>> = {\n 'upgradeable': { level: 'caution', message: \"The token's owner can replace its code.\" },\n 'mint-authority': { level: 'caution', message: 'The issuer can mint more at any time.' },\n 'freeze-authority': { level: 'caution', message: 'The issuer can freeze your balance.' },\n 'permanent-delegate': { level: 'danger', message: 'The issuer can move or burn the tokens in your wallet.' },\n 'transfer-fee': { level: 'caution', message: 'Every transfer pays a fee to the issuer.' },\n 'transfer-hook': { level: 'danger', message: \"A program of the issuer's choosing runs on every transfer: it can block selling.\" },\n 'non-transferable': { level: 'danger', message: 'It cannot be moved: not sent, not swapped.' },\n 'default-frozen': { level: 'caution', message: 'New holders start frozen until the issuer lets them move it.' },\n 'pausable': { level: 'caution', message: 'The issuer can pause every transfer.' },\n}\n\n/** Longest symbol the wallet stores and assesses (code points); a longer one is cut, after trimming, before anything reads it. */\nexport const TOKEN_SYMBOL_MAX = 32\n/** Longest name the wallet stores (code points). */\nexport const TOKEN_NAME_MAX = 128\n\n/**\n * A symbol or name as the wallet keeps and shows it: trimmed, cut to `max` code points, trimmed again —\n * so \"USDC\" + 124 spaces + \"X\" is stored, shown and assessed as \"USDC\" (scan 2026-10-27 modules, F28).\n */\nexport function tokenText(text: string, max: number): string {\n let out = ''\n let n = 0\n for (const ch of text.trim()) {\n if (n++ >= max) break\n out += ch\n }\n return out.trim()\n}\n\n/** Letters that render like Latin capitals (Cyrillic, Greek) and currency signs used as letters. */\nconst CONFUSABLES: Readonly<Record<string, string>> = {\n 'А': 'A', 'В': 'B', 'Е': 'E', 'К': 'K', 'М': 'M', 'Н': 'H', 'О': 'O', 'Р': 'P', 'С': 'C', 'Т': 'T', 'Х': 'X', 'У': 'Y', 'І': 'I', 'Ј': 'J', 'Ѕ': 'S', 'Ԁ': 'D',\n 'а': 'A', 'в': 'B', 'е': 'E', 'к': 'K', 'м': 'M', 'н': 'H', 'о': 'O', 'р': 'P', 'с': 'C', 'т': 'T', 'х': 'X', 'у': 'Y', 'і': 'I', 'ј': 'J', 'ѕ': 'S', 'ԁ': 'D',\n 'Α': 'A', 'Β': 'B', 'Ε': 'E', 'Ζ': 'Z', 'Η': 'H', 'Ι': 'I', 'Κ': 'K', 'Μ': 'M', 'Ν': 'N', 'Ο': 'O', 'Ρ': 'P', 'Τ': 'T', 'Υ': 'Y', 'Χ': 'X',\n 'α': 'A', 'ε': 'E', 'ι': 'I', 'κ': 'K', 'ν': 'V', 'ο': 'O', 'ρ': 'P', 'τ': 'T', 'υ': 'U', 'χ': 'X',\n '$': 'S', '₮': 'T', '€': 'E', '¢': 'C', '₿': 'B', 'Ξ': 'E',\n // ASCII and Latin look-alikes of I and O (UTS #39 confusables): \"DAl\" reads as DAI, \"USDl\" as USD1.\n 'l': 'I', '1': 'I', '|': 'I', 'ı': 'I', 'ł': 'I', 'ǀ': 'I', '0': 'O', 'ø': 'O', 'Ø': 'O',\n // Latin letters without a decomposition, and Latin-1 accented letters for runtimes without String.prototype.normalize.\n 'Ð': 'D', 'ð': 'D', 'Đ': 'D', 'đ': 'D', 'Ħ': 'H', 'ħ': 'H', 'Ŧ': 'T', 'ŧ': 'T', 'ß': 'B', 'Þ': 'P', 'þ': 'P', 'Ŋ': 'N', 'ŋ': 'N',\n 'À': 'A', 'Á': 'A', 'Â': 'A', 'Ã': 'A', 'Ä': 'A', 'Å': 'A', 'à': 'A', 'á': 'A', 'â': 'A', 'ã': 'A', 'ä': 'A', 'å': 'A',\n 'Ç': 'C', 'ç': 'C', 'È': 'E', 'É': 'E', 'Ê': 'E', 'Ë': 'E', 'è': 'E', 'é': 'E', 'ê': 'E', 'ë': 'E',\n 'Ì': 'I', 'Í': 'I', 'Î': 'I', 'Ï': 'I', 'ì': 'I', 'í': 'I', 'î': 'I', 'ï': 'I', 'Ñ': 'N', 'ñ': 'N',\n 'Ò': 'O', 'Ó': 'O', 'Ô': 'O', 'Õ': 'O', 'Ö': 'O', 'ò': 'O', 'ó': 'O', 'ô': 'O', 'õ': 'O', 'ö': 'O',\n 'Ù': 'U', 'Ú': 'U', 'Û': 'U', 'Ü': 'U', 'ù': 'U', 'ú': 'U', 'û': 'U', 'ü': 'U', 'Ý': 'Y', 'ý': 'Y', 'ÿ': 'Y',\n}\n/** Letter pairs that read as one letter (UTS #39): rn → m, cl → d, vv → w. */\nconst PAIRS: ReadonlyArray<[RegExp, string]> = [[/rn/g, 'm'], [/cl/g, 'd'], [/vv|VV/g, 'W']]\n/** Combining marks: an accent over a Latin letter reads as the letter. */\nconst MARKS = /[\\u0300-\\u036f\\u1ab0-\\u1aff\\u1dc0-\\u1dff\\u20d0-\\u20ff\\ufe20-\\ufe2f]/g\n/**\n * Characters a reader does not see: zero-width, bidi and other format characters, C0 / C1 controls,\n * fillers and blank glyphs (Hangul fillers, U+2800 braille blank), interlinear annotation, and the\n * astral ones — tag characters (ASCII smuggling), variation selectors 17–256, musical and shorthand\n * format controls. Explicit ranges: Hermes has no Unicode property escapes.\n */\nconst INVISIBLE = /[\\x00-\\x08\\x0E-\\x1F\\x7F-\\x9F\\u00AD\\u034F\\u061C\\u115F\\u1160\\u17B4\\u17B5\\u180B-\\u180F\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u206F\\u2800\\u3164\\uFE00-\\uFE0F\\uFEFF\\uFFA0\\uFFF9-\\uFFFB]|\\uDB40[\\uDC00-\\uDC7F\\uDD00-\\uDDEF]|\\uD834[\\uDD59\\uDD73-\\uDD7A]|\\uD82F[\\uDCA0-\\uDCA3]/g\n\n/**\n * A symbol folded the way a reader sees it — a confusables skeleton in the spirit of UTS #39: invisible\n * characters and spaces gone, compatibility forms unified and accents dropped (NFKD), look-alike letters\n * mapped (Cyrillic, Greek, currency signs, and ASCII l / 1 / | → I, 0 → O; rn → m, cl → d, vv → w), upper\n * case. Only the first {@link TOKEN_SYMBOL_MAX} code points are read (scan 2026-10-27 modules, F36).\n */\nexport function foldSymbol(text: string): string {\n let s = tokenText(text, TOKEN_SYMBOL_MAX).replace(INVISIBLE, '')\n if (typeof s.normalize === 'function') s = s.normalize('NFKD').replace(MARKS, '')\n // After NFKD: a spacing accent (´ ¨ ¸ ‾) decomposes into a space and a mark.\n s = s.replace(/\\s+/g, '')\n // Fullwidth ASCII by hand too: some Hermes builds ship without String.prototype.normalize.\n s = s.replace(/[\\uFF01-\\uFF5E]/g, (c) => String.fromCharCode(c.charCodeAt(0) - 0xFEE0))\n for (const [re, to] of PAIRS) s = s.replace(re, to)\n let out = ''\n for (const ch of s) out += CONFUSABLES[ch] ?? ch\n return out.toUpperCase()\n}\n\n/** Symbols nobody should reuse on any chain: the big stablecoins and wrapped coins (natives are added from the catalog). */\nconst MAJOR_TOKENS = ['USDT', 'USDC', 'DAI', 'USDe', 'USDS', 'PYUSD', 'FDUSD', 'USD1', 'USDG', 'EURC', 'WETH', 'WBTC', 'cbBTC', 'stETH', 'wstETH', 'WSOL']\n\nlet majors: ReadonlyMap<string, string> | undefined\n/** Folded → display: every chain's native coin from the catalog, plus {@link MAJOR_TOKENS}. */\nfunction majorSymbols(): ReadonlyMap<string, string> {\n if (majors) return majors\n const m = new Map<string, string>()\n for (const chain of CC_SOURCE_CHAINS) for (const a of chain.assets) if (!a.contract && !m.has(foldSymbol(a.symbol))) m.set(foldSymbol(a.symbol), a.symbol)\n for (const sym of MAJOR_TOKENS) if (!m.has(foldSymbol(sym))) m.set(foldSymbol(sym), sym)\n majors = m\n return m\n}\n\n/** The known token a folded symbol reads as: any catalog token on the same chain, or a major one anywhere. */\nfunction lookalikeOf(folded: string, chain: string | undefined): string | undefined {\n if (!folded) return undefined\n if (chain) {\n const same = ccChain(catalogChainId(chain))?.assets.find((a) => foldSymbol(a.symbol) === folded)\n if (same) return same.symbol\n }\n return majorSymbols().get(folded)\n}\n\nconst PLAIN = /^[\\x20-\\x7E]*$/\n/**\n * Letters that pass for Latin ones, by block (the whole block, as for Cyrillic): IPA and spacing\n * modifier letters, Greek, Cyrillic, Armenian, Cherokee, Canadian syllabics, runic, phonetic letters\n * and small capitals (also Latin Extended-D / E), letterlike symbols (ℂ ℍ ℕ — not ™ ℠ ℗), Roman\n * numerals, enclosed letters and digits (Ⓤ ①, 🅤), Coptic, Tifinagh, Lisu, Vai, mathematical letters\n * (𝐔) and the astral Deseret, Osage, Lycian, Carian, Old Italic and Warang Citi; for Georgian, a living\n * script, only its look-alike letters (Ⴝ ყ ჿ Ჽ ⴭ…). Explicit ranges: Hermes has no Unicode property\n * escapes. Emoji, CJK, Arabic, Hebrew, Thai and accented Latin are not here: a caution, not a danger.\n */\nconst LOOKALIKE_LETTERS = /[\\u0250-\\u02FF\\u0370-\\u03FF\\u0400-\\u052F\\u0530-\\u058F\\u10B9\\u10BD\\u10C7\\u10CD\\u10E7\\u10FD\\u10FF\\u13A0-\\u13FF\\u1400-\\u167F\\u16A0-\\u16FF\\u1C80-\\u1C8F\\u1C95\\u1CB7\\u1CBD\\u1CBF\\u1D00-\\u1DBF\\u1F00-\\u1FFF\\u2100-\\u2116\\u2118-\\u211F\\u2121\\u2123-\\u214F\\u2160-\\u2188\\u2460-\\u24FF\\u2C80-\\u2CFF\\u2D2D\\u2D30-\\u2D7F\\u2DE0-\\u2DFF\\uA4D0-\\uA4FF\\uA500-\\uA63F\\uA640-\\uA69F\\uA720-\\uA7FF\\uAB30-\\uABBF]|\\uD835[\\uDC00-\\uDFFF]|\\uD83C[\\uDD10-\\uDD69]|\\uD801[\\uDC00-\\uDC4F\\uDCB0-\\uDCFF]|\\uD800[\\uDE80-\\uDEDF\\uDF00-\\uDF2F]|\\uD806[\\uDCA0-\\uDCFF]/\n/**\n * What hides inside text: {@link INVISIBLE} without the joiner and the variation selectors 1–16\n * emoji are built with.\n */\nconst HIDING = /[\\x00-\\x08\\x0E-\\x1F\\x7F-\\x9F\\u00AD\\u034F\\u061C\\u115F\\u1160\\u17B4\\u17B5\\u180B-\\u180F\\u200B\\u200C\\u200E\\u200F\\u202A-\\u202E\\u2060-\\u206F\\u2800\\u3164\\uFEFF\\uFFA0\\uFFF9-\\uFFFB]|\\uDB40[\\uDC00-\\uDC7F\\uDD00-\\uDDEF]|\\uD834[\\uDD59\\uDD73-\\uDD7A]|\\uD82F[\\uDCA0-\\uDCA3]/\n/** A line break inside a symbol: what follows it is on a line a one-line label does not show. */\nconst LINE_BREAK = /[\\n\\v\\f\\r\\u0085\\u2028\\u2029]/\n/** Emoji built from tag characters (the England, Scotland and Wales flags) are emoji, not hidden text. */\nconst EMOJI_TAG_SEQUENCE = /\\uD83C\\uDFF4(?:\\uDB40[\\uDC20-\\uDC7E])+\\uDB40\\uDC7F/g\n/** Zero-width non-joiner between two non-ASCII characters is spelling (Persian, Indic scripts), not hiding. */\nconst SPELLING_ZWNJ = /([^\\x00-\\x7F])\\u200C(?=[^\\x00-\\x7F])/g\n/** Letters that pass for Latin ones, or characters that hide, in `text`. */\nconst passesOrHides = (text: string): boolean => LOOKALIKE_LETTERS.test(text) || HIDING.test(text.replace(EMOJI_TAG_SEQUENCE, '').replace(SPELLING_ZWNJ, '$1'))\n\n\n/**\n * Warnings for a token read with `getTokenInfo`, for a token the wallet's list does not include.\n * Pure: nothing is fetched. `impersonation` when its symbol (look-alike letters folded) reads as a\n * catalog token on the same `chain` — the real one would be in the list, this is another contract —\n * or as a native coin or major stablecoin anywhere. A bridged token of another chain's list (JUP on\n * Solana, listed on Unichain) is not flagged.\n */\nexport function assessToken(info: TokenInfo, chain?: string): TokenAssessment {\n const risks: TokenRisk[] = [{ code: 'unlisted', level: 'caution', message: \"Not in the wallet's token list. Anyone can create a token with any name and logo: check the address with the project itself.\" }]\n // What the wallet stores and shows (trimmed, cut to TOKEN_SYMBOL_MAX), and the whole text a UI that does\n // not cut it would show: a look-alike in either is one (scan 2026-10-27 modules, F28).\n const symbol = tokenText(info.symbol, TOKEN_SYMBOL_MAX)\n const name = tokenText(info.name, TOKEN_NAME_MAX)\n const whole = info.symbol.slice(0, 4 * TOKEN_SYMBOL_MAX).replace(/\\s+/g, '')\n const lookalike = lookalikeOf(foldSymbol(symbol), chain) ?? lookalikeOf(foldSymbol(whole), chain)\n if (lookalike) {\n risks.push({ code: 'impersonation', level: 'danger', message: `It calls itself ${JSON.stringify(symbol)}, like ${lookalike}, but it is a different token at a different address.` })\n }\n // The raw head (what a host showing the raw symbol shows) and the stored symbol (what this wallet shows):\n // leading spaces must not push the look-alike out of the part that is checked.\n const head = info.symbol.slice(0, 4 * TOKEN_SYMBOL_MAX)\n if (passesOrHides(head) || passesOrHides(symbol) || LINE_BREAK.test(symbol)) {\n // Letters of another alphabet that pass for Latin ones, or characters that hide, are how a token passes for a\n // known one (scan 2026-10-27 modules, F36). Emoji, CJK and accented Latin stay a caution (owner decision 2026-10-28).\n risks.push({ code: 'odd-characters', level: 'danger', message: 'Its symbol uses letters of another alphabet that pass for Latin ones (Cyrillic, Greek…) or characters that hide in text.' })\n } else if (!PLAIN.test(symbol) || /\\s/.test(symbol) || !PLAIN.test(name) || name !== name.replace(INVISIBLE, '')) {\n risks.push({ code: 'odd-characters', level: 'caution', message: 'Its symbol or name uses unusual characters (emoji, other scripts, accents), spaces inside the symbol, or invisible ones.' })\n }\n if (!symbol) risks.push({ code: 'no-symbol', level: 'caution', message: 'The token reports no symbol.' })\n if (info.totalSupply === 0n) risks.push({ code: 'zero-supply', level: 'caution', message: 'Nothing has been minted.' })\n if (info.decimals > 24) risks.push({ code: 'unusual-decimals', level: 'caution', message: `It uses ${info.decimals} decimals; amounts may display oddly.` })\n for (const flag of info.flags) {\n const r = FLAG_RISKS[flag]\n if (r) risks.push({ code: flag, ...r })\n }\n risks.sort((a, b) => (a.level === b.level ? 0 : a.level === 'danger' ? -1 : 1))\n return { level: risks.some((r) => r.level === 'danger') ? 'danger' : 'caution', risks }\n}\n","import type { IWalletAccountReadOnly, TokenFlag, TokenInfo } from '@anywalletkit/core'\nimport { catalogChainId, ccAssetByToken, isTokenAddress, type CcAsset } from '@anywalletkit/swap2-assets'\nimport { TOKEN_NAME_MAX, TOKEN_SYMBOL_MAX, assessToken, tokenText, type TokenAssessment } from './assess.js'\n\n/** One JSON value under one key — the shape every small store needs (AsyncStorage, chrome.storage, memory). */\nexport interface JsonStorage<T> {\n /** The stored value, or `null` on first run. */\n get(): Promise<T | null>\n /** Persist the value. */\n set(value: T): Promise<void>\n}\n\n/** In-memory storage for tests and smoke runs. */\nexport function memoryJsonStorage<T>(initial: T | null = null): JsonStorage<T> & { value: T | null } {\n const clone = (v: T): T => JSON.parse(JSON.stringify(v)) as T\n const s = {\n value: initial,\n async get() { return s.value === null ? null : clone(s.value) },\n async set(value: T) { s.value = clone(value) },\n }\n return s\n}\n\n/** A token the user added. `symbol` and `name` are what the token reports: display them, never match on them. */\nexport interface UserToken {\n /** CAIP-2 chain id, the catalog's key (`solana:mainnet`, `eip155:1`). */\n chain: string\n /** Contract (EVM, checksummed as read) or mint (Solana). The token's identity. */\n address: string\n symbol: string\n name: string\n decimals: number\n /** `erc20`, `spl` or `spl-token-2022`. */\n standard: string\n /** Facts read from the chain when it was added. */\n flags: TokenFlag[]\n /** When the user added it (ms since epoch). */\n addedAt: number\n}\n\n/** What {@link TokensTable} stores. */\nexport interface TokensState {\n v: 1\n tokens: UserToken[]\n}\n\n/** Codes of {@link TokensError}. */\nexport type TokensErrorCode = 'not-loaded' | 'unsupported-chain' | 'bad-address' | 'listed' | 'bad-token' | 'full'\n\n/** A refusal from the tokens table or {@link inspectToken}, with a machine-readable `code`. */\nexport class TokensError extends Error {\n readonly code: TokensErrorCode\n constructor(code: TokensErrorCode, message: string) {\n super(message)\n this.name = 'TokensError'\n this.code = code\n }\n}\n\nconst sameAddress = (chain: string, a: string, b: string) => (chain.startsWith('eip155:') ? a.toLowerCase() === b.toLowerCase() : a === b)\nconst FLAGS: ReadonlySet<string> = new Set<TokenFlag>(['upgradeable', 'mint-authority', 'freeze-authority', 'permanent-delegate', 'transfer-fee', 'transfer-hook', 'non-transferable', 'default-frozen', 'pausable'])\n\n/** A stored entry as it must be, or null (a hand-edited or corrupted entry is dropped, never trusted). */\nfunction validEntry(t: unknown): UserToken | null {\n if (!t || typeof t !== 'object') return null\n const e = t as Record<string, unknown>\n if (typeof e.chain !== 'string' || typeof e.address !== 'string' || !isTokenAddress(e.chain, e.address)) return null\n if (typeof e.decimals !== 'number' || !Number.isInteger(e.decimals) || e.decimals < 0 || e.decimals > 77) return null\n if (typeof e.symbol !== 'string' || typeof e.name !== 'string' || typeof e.standard !== 'string' || typeof e.addedAt !== 'number') return null\n if (!Array.isArray(e.flags) || !e.flags.every((f) => typeof f === 'string' && FLAGS.has(f))) return null\n return {\n chain: e.chain, address: e.address, symbol: tokenText(e.symbol, TOKEN_SYMBOL_MAX), name: tokenText(e.name, TOKEN_NAME_MAX), decimals: e.decimals,\n standard: e.standard, flags: [...e.flags] as TokenFlag[], addedAt: e.addedAt,\n }\n}\n\n/** The result of looking a token up before adding it. */\nexport type TokenInspection =\n | { listed: CcAsset }\n | { listed?: undefined; info: TokenInfo; assessment: TokenAssessment; token: UserToken }\n\n/**\n * Read a token from the chain through `account` (its chain decides where) and assess it. A token the\n * catalog already lists comes back as `{ listed }`: nothing to add. Otherwise the token, ready for\n * {@link TokensTable.add}, and the warnings to show first. Throws {@link TokensError}\n * (`unsupported-chain`, `bad-address`) or the chain package's `NotATokenError`.\n */\nexport async function inspectToken(\n account: Pick<IWalletAccountReadOnly, 'chain' | 'getTokenInfo'> & { getChainId?: () => Promise<number | bigint> },\n address: string,\n now: () => number = Date.now,\n): Promise<TokenInspection> {\n let reported = account.chain\n // An EVM account created without a chain id reports 'eip155:unknown' until it has asked its RPC:\n // ask now, or the catalog check (already listed? a look-alike of this chain's tokens?) would miss.\n if (reported === 'eip155:unknown') {\n if (typeof account.getChainId !== 'function') throw new TokensError('unsupported-chain', 'The account does not know its chain id; create it with chainId.')\n reported = `eip155:${String(await account.getChainId())}`\n }\n const chain = catalogChainId(reported)\n if (!chain.startsWith('eip155:') && !chain.startsWith('solana:')) throw new TokensError('unsupported-chain', `Adding tokens is supported on EVM chains and Solana, not ${account.chain}.`)\n const ref = address.trim()\n if (!isTokenAddress(chain, ref)) throw new TokensError('bad-address', `${JSON.stringify(address)} is not a token address on ${chain}.`)\n const listed = ccAssetByToken(chain, ref)\n if (listed) return { listed }\n if (typeof account.getTokenInfo !== 'function') throw new TokensError('unsupported-chain', `This ${account.chain} account cannot read token info.`)\n const info = await account.getTokenInfo(ref)\n // What is stored is what is assessed: trimmed and cut first (scan 2026-10-27 modules, F28).\n const symbol = tokenText(info.symbol, TOKEN_SYMBOL_MAX)\n const name = tokenText(info.name, TOKEN_NAME_MAX)\n const token: UserToken = {\n chain, address: info.address, symbol, name, decimals: info.decimals,\n standard: info.standard, flags: [...info.flags], addedAt: now(),\n }\n return { info, assessment: assessToken({ ...info, symbol, name }, chain), token }\n}\n\n/**\n * The swap options that name a user token as the source: its address and decimals. Pass them to an\n * engine created with `allowUnlistedTokens: true`.\n */\nexport function swapSource(token: Pick<UserToken, 'address' | 'decimals'>): { fromToken: string; fromTokenDecimals: number } {\n return { fromToken: token.address, fromTokenDecimals: token.decimals }\n}\n\n/**\n * The user's own tokens, over any JSON storage: load once at cold start, then read synchronously.\n * Writes queue behind each other; a failed save leaves memory as stored. Tokens the catalog lists are\n * refused (they are already there), and so are addresses that are not EVM contracts or Solana mints.\n */\nexport class TokensTable {\n private _state: TokensState | null = null\n private _loading: Promise<TokensState> | null = null\n private _writes: Promise<unknown> = Promise.resolve()\n private readonly listeners = new Set<() => void>()\n readonly maxTokens: number\n\n constructor(private readonly storage: JsonStorage<TokensState>, options: { maxTokens?: number } = {}) {\n this.maxTokens = options.maxTokens ?? 200\n }\n\n /** Read the stored list. Call once before the sync reads; a failed read throws and leaves the table unloaded. */\n async load(): Promise<void> {\n await this.state()\n }\n\n /** Called after every successful write. Returns the unsubscribe function. */\n subscribe(fn: () => void): () => void {\n this.listeners.add(fn)\n return () => { this.listeners.delete(fn) }\n }\n\n /** The user's tokens, oldest first; only those on `chain` when given. */\n list(chain?: string): UserToken[] {\n const s = this.loaded()\n const want = chain === undefined ? undefined : catalogChainId(chain)\n return s.tokens.filter((t) => want === undefined || t.chain === want).map((t) => ({ ...t, flags: [...t.flags] }))\n }\n\n /** The user token at `address` on `chain`, if added. */\n get(chain: string, address: string): UserToken | undefined {\n const c = catalogChainId(chain)\n const t = this.loaded().tokens.find((x) => x.chain === c && sameAddress(c, x.address, address))\n return t ? { ...t, flags: [...t.flags] } : undefined\n }\n\n /** Add (or refresh) a token — usually `inspection.token` after the user accepted the warnings. */\n async add(token: UserToken): Promise<UserToken> {\n const entry = validEntry({ ...token, chain: catalogChainId(token.chain) })\n if (!entry) throw new TokensError('bad-token', 'Not a valid token entry (address, decimals 0–77, symbol, name, standard, flags).')\n if (ccAssetByToken(entry.chain, entry.address)) throw new TokensError('listed', `${entry.address} is already in the wallet's token list.`)\n await this.update((s) => {\n const rest = s.tokens.filter((t) => !(t.chain === entry.chain && sameAddress(entry.chain, t.address, entry.address)))\n if (rest.length >= this.maxTokens) throw new TokensError('full', `At most ${this.maxTokens} tokens can be added.`)\n return { v: 1, tokens: [...rest, entry] }\n })\n return { ...entry, flags: [...entry.flags] }\n }\n\n /** Remove a token. Returns whether it was there. */\n async remove(chain: string, address: string): Promise<boolean> {\n const c = catalogChainId(chain)\n let removed = false\n await this.update((s) => {\n const rest = s.tokens.filter((t) => !(t.chain === c && sameAddress(c, t.address, address)))\n removed = rest.length !== s.tokens.length\n return { v: 1, tokens: rest }\n })\n return removed\n }\n\n private loaded(): TokensState {\n if (!this._state) throw new TokensError('not-loaded', 'Call load() before reading the tokens table.')\n return this._state\n }\n\n private async state(): Promise<TokensState> {\n if (this._state) return this._state\n this._loading ??= (async () => {\n let stored: TokensState | null\n try {\n stored = await this.storage.get()\n } catch (e) {\n // A failed read is not an empty list: the next write would erase the user's tokens.\n this._loading = null\n throw e\n }\n const tokens = stored && stored.v === 1 && Array.isArray(stored.tokens)\n ? stored.tokens.map(validEntry).filter((t): t is UserToken => t !== null)\n : []\n this._state = { v: 1, tokens }\n return this._state\n })()\n return this._loading\n }\n\n private update(change: (s: TokensState) => TokensState): Promise<void> {\n const run = this._writes.then(async () => {\n const next = change(await this.state())\n await this.storage.set(next)\n this._state = next\n for (const fn of this.listeners) {\n try { fn() } catch { /* a listener's failure is its own */ }\n }\n })\n this._writes = run.catch(() => undefined)\n return run\n }\n}\n"],"mappings":"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","names":["catalogChainId","catalogChainId"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anywalletkit/tokens",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Tokens users add themselves, at their own risk: read a token's decimals, symbol, name and holder-relevant facts from the chain (EVM contracts, Solana mints incl. Token-2022 extensions), a risk assessment that flags look-alikes of known tokens and issuer powers, and a persistent list (TokensTable over any JSON storage) that apps show beside the swap catalog and can swap through the engines that accept unlisted tokens.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://docs.anywallet.tech",
@@ -42,8 +42,8 @@
42
42
  "@types/node": "^22",
43
43
  "vitest": "^5.0.3",
44
44
  "@anywalletkit/core": "0.6.1",
45
- "@anywalletkit/wallet-evm": "0.3.1",
46
- "@anywalletkit/wallet-solana": "0.3.1"
45
+ "@anywalletkit/wallet-solana": "0.4.0",
46
+ "@anywalletkit/wallet-evm": "0.4.1"
47
47
  },
48
48
  "keywords": [
49
49
  "tokens",