Skip to content

fix(statics): skip AMS tokens that reuse a static contract address#8959

Closed
zahin-mohammad wants to merge 1 commit into
masterfrom
worktree-cshld-976-token-address-dedup
Closed

fix(statics): skip AMS tokens that reuse a static contract address#8959
zahin-mohammad wants to merge 1 commit into
masterfrom
worktree-cshld-976-token-address-dedup

Conversation

@zahin-mohammad
Copy link
Copy Markdown
Contributor

Summary

  • Fixes an uncaught DuplicateContractAddressDefinitionError that crashes the BitGo UI when an AMS-served token reuses a contract address already claimed by a static token under a different name.
  • Root cause: the AMS↔static merge in createTokenMapUsingConfigDetails dedups via isCoinPresentInCoinMap, which only matches on name/id/alias — never the contract address. The colliding token slipped through and CoinMap.fromCoinsaddCoin then threw.
  • Adds CoinMap.hasTokenAddressConflict(coin) (reusing the map's own contractAddressKey / nftCollectionIdKey derivation) and uses it in the merge loop to skip a colliding token instead of crashing. Checking the built coin means the key uses the same address normalization as static tokens, so it matches reliably. Covers both contract-address and NFT-collection-id collisions.

Surfaced by the eth:at bot token (0x0581ccdf…aa, eth testnet) added in #8885; the live AMS config serves a token at the same address under a different name. Observed crash path: syncAmsCoinsToPresenter → createTokenMapUsingTrimmedConfigDetails → createTokenMapUsingConfigDetails → CoinMap.fromCoins → addCoin.

Ships forward (no statics pin, no lost coins) — unlike the bitgo-retail revert (BitGo/bitgo-retail#6932), which stepped statics back only 3 beta builds and still shipped eth:at, so it would not have fixed the crash.

Test plan

  • New TDD test in modules/statics/test/unit/coins.ts — fails before the fix (throws DuplicateContractAddressDefinitionError), passes after (token skipped, static token preserved).
  • Full statics unit suite: 28,769 passing, 0 failing.
  • tsc --noEmit clean; ESLint 0 errors on changed files.

Scope note

Targets the static-vs-AMS collision (the incident). A hypothetical AMS-vs-AMS duplicate (two server tokens at the same address, neither in statics) would still throw at fromCoins — a separate, pre-existing AMS data-integrity concern, out of scope here.

Ticket: CSHLD-976

🤖 Generated with Claude Code

The UI builds a combined coin map by merging the static coin map with the
live AMS token config (createTokenMapUsingConfigDetails). The dedup guard
isCoinPresentInCoinMap only matches on name/id/alias, so an AMS token that
reuses a contract address already claimed by a static token under a
different name slipped through, and CoinMap.fromCoins then threw
DuplicateContractAddressDefinitionError — crashing the UI.

Add CoinMap.hasTokenAddressConflict, which reuses the map's own
contract-address and NFT-collection key derivation, and use it in the merge
loop to skip a colliding token instead of crashing.

Ticket: CSHLD-976

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@linear-code
Copy link
Copy Markdown

linear-code Bot commented Jun 5, 2026

CSHLD-976

@zahin-mohammad
Copy link
Copy Markdown
Contributor Author

Will let the coins team take this forward.

mmcshinsky-bitgo added a commit that referenced this pull request Jun 5, 2026
… nft collection id

The ams<->static merge in createTokenMapUsingConfigDetails dedups via
isCoinPresentInCoinMap, which only matches on name/id/alias -- never the contract
address. An AMS-served token that reuses a contract address (or NFT collection id)
already claimed by a static token under a different name slips through, and
CoinMap.fromCoins -> addCoin then throws DuplicateContractAddressDefinitionError,
aborting the entire token-map build for every consumer.

Add CoinMap.hasTokenAddressConflict(coin) -- reusing the map's own contractAddressKey /
nftCollectionIdKey derivation against its populated address/NFT indexes -- and use it in
the merge loop to skip a colliding token instead of crashing. Statics coins are seeded
first, so statics stays the source of truth and the colliding AMS token is the one
dropped. Covers both contract-address and NFT-collection-id collisions.

Surfaced by the eth:at bot token (0x0581ccdf...aa, eth testnet) added in #8885; the
live AMS config serves a token at the same address under a different name.

Supersedes the handoff reference in #8959 (closed).

CSHLD-976
mmcshinsky-bitgo added a commit that referenced this pull request Jun 5, 2026
… nft collection id

The ams<->static merge in createTokenMapUsingConfigDetails dedups via
isCoinPresentInCoinMap, which only matches on name/id/alias -- never the contract
address. An AMS-served token that reuses a contract address (or NFT collection id)
already claimed by a static token under a different name slips through, and
CoinMap.fromCoins -> addCoin then throws DuplicateContractAddressDefinitionError,
aborting the entire token-map build for every consumer.

Add CoinMap.hasTokenAddressConflict(coin) -- reusing the map's own contractAddressKey /
nftCollectionIdKey derivation against its populated address/NFT indexes -- and use it in
the merge loop to skip a colliding token instead of crashing. Statics coins are seeded
first, so statics stays the source of truth and the colliding AMS token is the one
dropped. Covers both contract-address and NFT-collection-id collisions.

Surfaced by the eth:at bot token (0x0581ccdf...aa, eth testnet) added in #8885; the
live AMS config serves a token at the same address under a different name.

Supersedes the handoff reference in #8959 (closed).

CSHLD-976
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.

1 participant