Files
lez-programs/apps/amm/qml/components/shared/TokenList.js
T
r4bbit 4651f28a05 fix(apps/amm): one token list and one token picker for both views
The two views listed different tokens: swap rendered tokenList()
(TOKENS_CONFIG verbatim, no chain check) while liquidity rendered
resolveTokens(), which reads each id on-chain and silently drops any whose
definition isn't a readable fungible token owned by the configured
TokenProgram.
2026-08-20 15:21:47 +02:00

103 lines
4.2 KiB
JavaScript

.pragma library
// The app's one token list, shared by the swap and liquidity views so a token
// never appears in one picker and not the other.
//
// Two sources have to be reconciled:
// * tokenList() — TOKENS_CONFIG verbatim. Carries the display `symbol`,
// which is the only place a symbol exists. No chain check.
// * resolveTokens() — the same configured ids PLUS the user's persisted custom
// ids, each read on-chain. Drops any id whose definition
// isn't a readable fungible token owned by the configured
// TokenProgram, and carries holdingId/balance. Ids come
// back as canonical base58.
//
// Merging them keeps every configured token listed (a dropped one is marked
// unselectable rather than vanishing), adds the custom tokens, and puts the
// configured symbol back on rows that resolveTokens returned without one.
// resolveTokens() echoes ids as base58, so only a base58-configured id can be
// compared against a resolved row here. See merge().
function isBase58Id(tokenId) {
return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(String(tokenId || ""))
}
function configTokenFor(configTokens, definitionId) {
for (var i = 0; i < configTokens.length; ++i) {
if (String(configTokens[i].definitionId || "") === definitionId)
return configTokens[i]
}
return null
}
// Rows are `{ definitionId, symbol, name, totalSupply, holdingId, balance }`,
// plus `selectable: false` and a `code` on a configured token that did not
// resolve. `unresolvedCode` is the reason code such a row carries.
function merge(configTokens, resolvedTokens, unresolvedCode) {
var config = configTokens || []
var resolved = resolvedTokens || []
// No resolution data at all (module unavailable, AMM not initialized, or the
// call simply hasn't landed) is "unknown", not "everything is broken" —
// greying out the whole list would make the view unusable. Fall back to the
// configured list as-is, which is what the swap view always did.
if (resolved.length === 0)
return config.map(function(token) {
return {
"definitionId": String(token.definitionId || ""),
"symbol": String(token.symbol || ""),
"name": String(token.name || ""),
"totalSupply": "0",
"holdingId": "",
"balance": "0"
}
})
var rows = []
var resolvedIds = {}
for (var i = 0; i < resolved.length; ++i) {
var row = resolved[i]
var id = String(row.definitionId || "")
var configured = configTokenFor(config, id)
resolvedIds[id] = true
rows.push({
"definitionId": id,
"symbol": configured ? String(configured.symbol || "") : "",
// Prefer the configured name so both pickers agree; fall back to the
// on-chain name for a custom token the config doesn't carry.
"name": configured && configured.name
? String(configured.name) : String(row.name || ""),
"totalSupply": row.totalSupply,
"holdingId": row.holdingId,
"balance": row.balance
})
}
for (var j = 0; j < config.length; ++j) {
var token = config[j]
var configuredId = String(token.definitionId || "")
if (configuredId.length === 0 || resolvedIds[configuredId])
continue
// A hex-configured id can't be compared against the base58 rows above, so
// synthesizing a row risks duplicating a token that did resolve. Skip it
// and fall back to the pre-merge behaviour for that entry.
if (!isBase58Id(configuredId))
continue
rows.push({
"definitionId": configuredId,
"symbol": String(token.symbol || ""),
"name": String(token.name || ""),
"totalSupply": "0",
"holdingId": "",
"balance": "0",
// Listed but greyed out, with the reason on hover, instead of
// vanishing with no explanation.
"selectable": false,
"code": unresolvedCode || "token_unresolved"
})
}
return rows
}