Skip to content

Remove builtin tokens#710

Open
peachbits wants to merge 14 commits intomasterfrom
matthew/builtin-tokens
Open

Remove builtin tokens#710
peachbits wants to merge 14 commits intomasterfrom
matthew/builtin-tokens

Conversation

@peachbits
Copy link
Contributor

@peachbits peachbits commented Feb 26, 2026

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

none

Description

none

Note

Medium Risk
Token plumbing and persistence paths are reworked (builtins now loaded from bundled JSON and merged into customTokens), which could affect token visibility/enabling and legacy migrations if edge cases are missed.

Overview
Removes the separate builtinTokens state and login-time builtin loading; allTokens is now served directly from customTokens.

Adds bundled builtinTokens.json loading (via Rollup JSON support) and a batch addCustomTokens/ACCOUNT_CUSTOM_TOKENS_ADDED path to merge tokens efficiently, enabling migration of builtin/legacy-enabled tokens into customTokens. Also extends EdgeToken parsing with an optional isUserCreated flag.

Written by Cursor Bugbot for commit 8fbb8f0. This will update automatically on new commits. Configure here.


…ctions

Certain legacy functionality needs to rely on the tokens being present. They can use the local json if necessary
@peachbits peachbits force-pushed the matthew/builtin-tokens branch from 0225b98 to c5a4453 Compare February 26, 2026 08:15
@peachbits peachbits force-pushed the matthew/builtin-tokens branch from c5a4453 to 7613aff Compare February 26, 2026 08:16
canReplaceByFee?: boolean // Defaults to false
customFeeTemplate?: EdgeObjectTemplate // Indicates custom fee support
customTokenTemplate?: EdgeObjectTemplate // Indicates custom token support
customTokenTemplate?: EdgeObjectTemplate // An empty array indicates token support. A filled array indicates custom token support.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this. It's too subtle, and makes the code hard to understand. Let's add an explicit hasTokens?: boolean instead, and leave customTokenTemplate just for custom token support.

const enabledTokensByPlugin = new Map<string, Set<string>>()

// Get all wallet IDs for this account
const walletIds = accountState.currencyWalletIds
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am deeply concerned about when this migration happens. The problem is that the list of wallets may be out of date on the device, and then once the data sync takes place, the newly-discovered wallets will not receive the migration. Instead, I would expect this migration to happen as part of the loadTokensFile function, which would use your giant JSON to upgrade currencyCodes. Then, once loadTokensFile returns, the engine pixie can do a diff with the accountState.customTokens[pluginId] and insert any enabled tokens that we find in your big JSON but not in the custom token list.

Moving the migration to the engine-start logic would also eliminate Cursor's worry about uninitialized storage.

This revised plan might be a little slower on first-migrate, but should be faster and more stable long-term.

const LEGACY_TOKENS_FILE = 'EnabledTokens.json'
const SEEN_TX_CHECKPOINT_FILE = 'seenTxCheckpoint.json'
const TOKENS_FILE = 'Tokens.json'
export const TOKENS_FILE = 'Tokens.json'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we move the migration logic to engine-start, as described above, this can go back to being file-private as it should be. We really do want to keep all disk IO in this one source file so it's easy to audit & understand.

continue
}
currencyCode = allTokens[tokenId].currencyCode
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The GUI no longer uses the old .balances table, which is currency-code based. Could we just remove it as part of the breaking change?

engine.disableTokens != null &&
engine.enableTokens != null
) {
const builtinTokens = loadBuiltinTokensJson()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The engines no longer implement disableTokens or enableTokens, which are currency-code based. Can we just remove those options as part of the breaking change?


allTokens(state = {}, action, next, prev): EdgePluginMap<EdgeTokenMap> {
const { builtinTokens, customTokens } = next.self
const { customTokens } = next.self
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can gut this whole reducer, now that allTokens is just an alias for customTokens. It could become simply:

allTokens(state = {}, action, next, prev): EdgePluginMap<EdgeTokenMap> {
  return next.self.customTokens
}

const { state } = this._ai.props
const { _accountId: accountId, _pluginId: pluginId } = this
return state.accounts[accountId].builtinTokens[pluginId]
return state.accounts[accountId].allTokens[pluginId] ?? emptyTokens
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ?? emptyTokens is not needed, as the updated reducer can never return undefined.

const accountState = input.props.state.accounts[accountId]
const allTokens = accountState.allTokens[pluginId] ?? {}

const allTokens =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ?? {} is not needed, as the updated reducer can never return undefined.

input.props.state.accounts[accountId].allTokens[pluginId] ?? {}
const enabledTokenIds = uniqueStrings(tokenIds).filter(
tokenId => allTokens[tokenId] != null
tokenId => tokenId in allTokens
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the tokenId => allTokens[tokenId] != null test, which is more robust.

expect(config.allTokens).deep.equals({
...config.customTokens,
...config.builtinTokens
...config.customTokens
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need the {...} anymore, but can simply use customTokens directly now.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

),

allTokens(state = {}, action, next, prev): EdgePluginMap<EdgeTokenMap> {
const { builtinTokens, customTokens } = next.self
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allTokens[pluginId] now undefined causing runtime crashes

High Severity

The allTokens reducer now returns next.self.customTokens directly, but customTokens only contains entries for plugins that have explicitly stored tokens. The old reducer iterated over all currency plugins and created entries for each (even empty {}), guaranteeing allTokens[pluginId] was always an object. Now allTokens[pluginId] returns undefined for any plugin without custom tokens. Multiple consumers access allTokens[pluginId] without null guards — e.g., changeEnabledTokenIds does allTokens[tokenId] on the result, and getCurrencyMultiplier/upgradeCurrencyCode call Object.keys(allTokens) — all of which throw TypeError when allTokens is undefined. This affects non-token chains like Bitcoin and any wallet whose plugin has no migrated or user-created tokens yet.

Additional Locations (1)

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants