@pancakeswap/token-lists 0.0.17 → 0.1.1

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.
@@ -0,0 +1,111 @@
1
+ import { getVersionUpgrade, VersionUpgrade } from '../src/getVersionUpgrade'
2
+ import { TokenList } from '../src/types'
3
+
4
+ // Mutable state type for reducer operations
5
+ type MutableListsState = {
6
+ byUrl: {
7
+ [url: string]: {
8
+ current: TokenList | null
9
+ pendingUpdate: TokenList | null
10
+ loadingRequestId: string | null
11
+ error: string | null
12
+ }
13
+ }
14
+ lastInitializedDefaultListOfLists?: string[]
15
+ activeListUrls: string[] | undefined
16
+ }
17
+
18
+ /**
19
+ * Core function to set a single list to pending state
20
+ */
21
+ export const setPendingTokenList = (state: MutableListsState, url: string, requestId: string): void => {
22
+ const current = state.byUrl[url]?.current ?? null
23
+ const pendingUpdate = state.byUrl[url]?.pendingUpdate ?? null
24
+
25
+ state.byUrl[url] = {
26
+ current,
27
+ pendingUpdate,
28
+ loadingRequestId: requestId,
29
+ error: null,
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Core function to set a single list to fulfilled state
35
+ */
36
+ export const setFulfilledTokenList = (
37
+ state: MutableListsState,
38
+ url: string,
39
+ tokenList: TokenList,
40
+ requestId: string,
41
+ DEFAULT_ACTIVE_LIST_URLS: string[],
42
+ ): boolean => {
43
+ const current = state.byUrl[url]?.current
44
+ const loadingRequestId = state.byUrl[url]?.loadingRequestId
45
+
46
+ // no-op if update does nothing
47
+ if (current) {
48
+ const upgradeType = getVersionUpgrade(current.version, tokenList.version)
49
+
50
+ if (upgradeType === VersionUpgrade.NONE) return false
51
+ if (loadingRequestId === null || loadingRequestId === requestId) {
52
+ state.byUrl[url] = {
53
+ ...state.byUrl[url],
54
+ loadingRequestId: null,
55
+ error: null,
56
+ current,
57
+ pendingUpdate: tokenList,
58
+ }
59
+ }
60
+ return false // not a new list, so no activation needed
61
+ }
62
+ // activate if on default active
63
+ if (DEFAULT_ACTIVE_LIST_URLS.includes(url) && state.activeListUrls && !state.activeListUrls.includes(url)) {
64
+ state.activeListUrls.push(url)
65
+ }
66
+
67
+ state.byUrl[url] = {
68
+ ...state.byUrl[url],
69
+ loadingRequestId: null,
70
+ error: null,
71
+ current: tokenList,
72
+ pendingUpdate: null,
73
+ }
74
+ return true // new list, might need activation
75
+ }
76
+
77
+ /**
78
+ * Core function to set a single list to rejected state
79
+ */
80
+ export const setRejectedTokenList = (
81
+ state: MutableListsState,
82
+ url: string,
83
+ requestId: string,
84
+ errorMessage: string,
85
+ ): void => {
86
+ if (state.byUrl[url]?.loadingRequestId !== requestId) {
87
+ // no-op since it's not the latest request
88
+ return
89
+ }
90
+
91
+ state.byUrl[url] = {
92
+ ...state.byUrl[url],
93
+ loadingRequestId: null,
94
+ error: errorMessage,
95
+ current: null,
96
+ pendingUpdate: null,
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Batch activate URLs if needed
102
+ */
103
+ export const batchActivateUrls = (state: MutableListsState, urls: string[]): void => {
104
+ if (urls.length > 0 && state.activeListUrls) {
105
+ urls.forEach((url) => {
106
+ if (!state.activeListUrls!.includes(url)) {
107
+ state.activeListUrls!.push(url)
108
+ }
109
+ })
110
+ }
111
+ }
@@ -26,7 +26,7 @@ function useFetchListCallback(
26
26
  return tokenList
27
27
  })
28
28
  .catch((error) => {
29
- console.error(`Failed to get list at url ${listUrl}`, error)
29
+ console.warn(`Failed to get list at url ${listUrl}`, error)
30
30
  if (sendDispatch) {
31
31
  dispatch(fetchTokenList.rejected({ url: listUrl, requestId, errorMessage: error.message }))
32
32
  }
@@ -11,7 +11,7 @@
11
11
  {
12
12
  "major": 1,
13
13
  "minor": 0,
14
- "patch": 0
14
+ "patch": 1
15
15
  }
16
16
  ],
17
17
  "additionalProperties": false,
@@ -196,17 +196,17 @@
196
196
  "type": "string",
197
197
  "description": "The name of the token",
198
198
  "minLength": 1,
199
- "maxLength": 40,
200
- "pattern": "^[ \\w.'+\\-%/À-ÖØ-öø-ÿ:&\\[\\]\\(\\)]+$",
201
- "examples": ["USD Coin"]
199
+ "maxLength": 60,
200
+ "pattern": "^[ \\w.'+\\-%/,\\$À-ÖØ-öø-ÿ:&\\[\\]\\(\\)\\u4e00-\\u9fa5]+$",
201
+ "examples": ["USD Coin", "币安币"]
202
202
  },
203
203
  "symbol": {
204
204
  "type": "string",
205
205
  "description": "The symbol for the token; must be alphanumeric",
206
- "pattern": "^[a-zA-Z0-9+\\-%/$.\\s]+$",
206
+ "pattern": "^[a-zA-Z0-9+\\-%/$.\\s_\\u4e00-\\u9fa5]+$",
207
207
  "minLength": 1,
208
208
  "maxLength": 20,
209
- "examples": ["USDC"]
209
+ "examples": ["USDC", "币安"]
210
210
  },
211
211
  "logoURI": {
212
212
  "type": "string",
@@ -256,14 +256,14 @@
256
256
  "type": "string",
257
257
  "description": "The name of the token",
258
258
  "minLength": 1,
259
- "maxLength": 40,
260
- "pattern": "^[ \\w.'+\\-%/À-ÖØ-öø-ÿ:&\\[\\]\\(\\)]+$",
259
+ "maxLength": 60,
260
+ "pattern": "^[ \\w.'+\\-%/,\\$À-ÖØ-öø-ÿ:&\\[\\]\\(\\)]+$",
261
261
  "examples": ["USD Coin"]
262
262
  },
263
263
  "symbol": {
264
264
  "type": "string",
265
265
  "description": "The symbol for the token; must be alphanumeric",
266
- "pattern": "^[a-zA-Z0-9+\\-%/$.]+$",
266
+ "pattern": "^[a-zA-Z0-9+\\-%/$._]+$",
267
267
  "minLength": 1,
268
268
  "maxLength": 20,
269
269
  "examples": ["USDC"]
@@ -356,6 +356,15 @@
356
356
  "description": "A URI for the logo of the token list; prefer SVG or PNG of size 256x256",
357
357
  "format": "uri",
358
358
  "examples": ["ipfs://QmXfzKRvjZz3u5JRgC4v5mGVbm9ahrUiB4DgzHBsnWbTMM"]
359
+ },
360
+ "extensions": {
361
+ "type": "object",
362
+ "description": "An object containing arbitrary or vendor-specific token list metadata",
363
+ "propertyNames": {
364
+ "$ref": "#/definitions/ExtensionIdentifier"
365
+ },
366
+ "additionalProperties": true,
367
+ "maxProperties": 10
359
368
  }
360
369
  },
361
370
  "if": {
package/src/index.test.ts CHANGED
@@ -9,6 +9,7 @@ test('exports', () => {
9
9
  "VersionUpgrade",
10
10
  "getVersionUpgrade",
11
11
  "createFilterToken",
12
+ "isScaledUIAmountToken",
12
13
  ]
13
14
  `)
14
15
  })
package/src/index.ts CHANGED
@@ -2,3 +2,4 @@ export * from './wrappedTokenInfo'
2
2
  export * from './types'
3
3
  export * from './getVersionUpgrade'
4
4
  export * from './filtering'
5
+ export * from './scaledUIAmount'
@@ -0,0 +1,41 @@
1
+ // Token-list `extensions.scaledUIAmount` metadata for ERC-8056 (Scaled UI Amount) tokens.
2
+ //
3
+ // Curated by the indexer/CMS at ingestion time. The boolean `enabled` flag is the
4
+ // load-bearing FE-side check; other fields are snapshots / hints (the live multiplier
5
+ // is always re-read from chain via the `useScaledUIAmountMetadata` hook).
6
+ //
7
+ // See BNB Chain partner integration doc v1.0 (8 Apr 2026) §8.2.4 for the upstream
8
+ // shape this is modeled after.
9
+
10
+ import type { TokenInfo } from './types'
11
+
12
+ /**
13
+ * Static metadata identifying an ERC-8056 (Scaled UI Amount) token in a token list.
14
+ * `enabled: true` is the only field FE consumes for routing decisions; everything
15
+ * else is hint/snapshot data refreshed live from chain.
16
+ */
17
+ export interface ScaledUIAmountExtension {
18
+ /** Set to `true` for tokens that implement ERC-8056. The load-bearing FE check. */
19
+ enabled: boolean
20
+ /** Optional snapshot of the live multiplier at the time of indexing (18-decimal fixed). */
21
+ multiplier?: string
22
+ /** Optional human-readable multiplier (e.g. `"2.0"`). Display hint only. */
23
+ multiplierFormatted?: string
24
+ /** ERC-165 interface IDs the contract claims to support. */
25
+ interfaceIds?: string[]
26
+ }
27
+
28
+ /**
29
+ * Returns true if a token-list entry is flagged as ERC-8056 enabled by the indexer.
30
+ * Tokens lacking the extension or with `enabled: false` are treated as standard ERC-20s
31
+ * (identity multiplier `1.0×`); the FE never falls into ScaledUI math for these.
32
+ *
33
+ * Note: the load-bearing decision lives here, not on the `Token` class. Token
34
+ * instances stay pure identity objects per the architectural decision in the
35
+ * implementation doc.
36
+ */
37
+ export function isScaledUIAmountToken(token: Pick<TokenInfo, 'extensions'>): boolean {
38
+ const ext = token.extensions?.scaledUIAmount
39
+ if (!ext || typeof ext !== 'object' || Array.isArray(ext)) return false
40
+ return (ext as { enabled?: unknown }).enabled === true
41
+ }
package/src/types.ts CHANGED
@@ -43,4 +43,5 @@ export interface TokenList {
43
43
  readonly keywords?: string[]
44
44
  readonly tags?: Tags
45
45
  readonly logoURI?: string
46
+ readonly extensions?: Record<string, unknown>
46
47
  }
@@ -17,9 +17,17 @@ export interface SerializedWrappedToken extends SerializedToken {
17
17
  export class WrappedTokenInfo extends Token {
18
18
  public readonly logoURI: string | undefined
19
19
 
20
+ /**
21
+ * Preserves the token-list `extensions` bag (curator-set metadata like
22
+ * `bridgeInfo`, `scaledUIAmount.enabled`, etc.). Stored as a readonly reference;
23
+ * not serialised back out of the class (kept in-memory only).
24
+ */
25
+ public readonly extensions: TokenInfo['extensions']
26
+
20
27
  constructor(tokenInfo: TokenInfo) {
21
28
  super(tokenInfo.chainId, tokenInfo.address, tokenInfo.decimals, tokenInfo.symbol, tokenInfo.name)
22
29
  this.logoURI = tokenInfo.logoURI
30
+ this.extensions = tokenInfo.extensions
23
31
  }
24
32
 
25
33
  public get serialize(): SerializedWrappedToken {