Skip to content

fix(statics): skip AMS tokens that reuse a static contract address or NFT collection id#8957

Closed
mmcshinsky-bitgo wants to merge 1 commit into
masterfrom
fix/statics-ams-contract-dedup
Closed

fix(statics): skip AMS tokens that reuse a static contract address or NFT collection id#8957
mmcshinsky-bitgo wants to merge 1 commit into
masterfrom
fix/statics-ams-contract-dedup

Conversation

@mmcshinsky-bitgo
Copy link
Copy Markdown
Contributor

@mmcshinsky-bitgo mmcshinsky-bitgo commented Jun 5, 2026

Fixes CSHLD-976. Supersedes the handoff reference #8959 (closed by @zahin-mohammad — "will let the coins team take this forward"); this adopts that PR's hasTokenAddressConflict design.

Problem

The AMS↔static merge in createTokenMapUsingConfigDetails (and the …Trimmed… variant that calls it) dedups via isCoinPresentInCoinMap (coins.ts:434), which only matches on name / id / alias — never the contract address:

export function isCoinPresentInCoinMap({ name, id, alias }) {
  return Boolean(coins.has(name) || (id && coins.has(id)) || (alias && coins.has(alias)));
}

But CoinMap.fromCoinsaddCoin (map.ts:62-68) also dedups by contract address (family:network.type:contractAddress) and NFT collection id. An AMS-served token reusing a contract a static token already claims under a different name slips the guard and throws DuplicateContractAddressDefinitionError, aborting the entire token-map build for every consumer (bitgo-retail hydration, bitgo-admin, bitgo-microservices, indexerdb, prime, client-admin, bitgo.ts, …).

Surfaced by the static eth:at bot token (0x0581ccdf…, eth testnet, added in #8885 / @bitgo/statics@58.43.0) colliding with the live AMS token at the same address under a different name.

Fix

Add CoinMap.hasTokenAddressConflict(coin) — reusing the map's own private contractAddressKey / nftCollectionIdKey derivation against its populated _coinByContractAddress / _coinByNftCollectionID indexes — and use it in the merge loop:

const token = createToken(tokenConfig);
if (token && !coins.hasTokenAddressConflict(token)) {
  BaseCoins.set(token.name, token);
}

Statics coins are seeded first, so statics stays the source of truth and the colliding AMS token is the one skipped. Checking the built coin against the map's own indexes means the key derivation/normalization always matches addCoin (no parallel bookkeeping, no public key-builder surface). Covers contract-address and NFT-collection-id collisions. Strict statics self-construction is unchanged (still throws on genuine duplicates at build/test time).

Acceptance criteria (CSHLD-976)

  • AMS token sharing a contract address with a static token is skipped (no throw)
  • Same protection for NFT collection id collisions (nftCollectionIdKey)
  • Unit test reproducing the duplicate-contract / different-name case asserting no throw
  • UI no longer crashes (companion bitgo-retail chore: update mainnet api url for plasma and kavaevm #6934 + this root-cause fix)

Scope note

Targets the static↔AMS collision (the incident). A hypothetical AMS↔AMS duplicate (two server tokens at the same address, neither in statics) is a separate, pre-existing AMS data-integrity concern — left to surface rather than be silently masked here; the bitgo-retail guard (#6934) keeps it from crashing the UI.

Verification

  • npx mocha test/unit/coins.ts28768 passing (incl. new test)
  • tsc --build clean, prettier --check clean

Related

🤖 Generated with Claude Code

@mmcshinsky-bitgo mmcshinsky-bitgo force-pushed the fix/statics-ams-contract-dedup branch from 9a29706 to d9db10b Compare June 5, 2026 18:32
@mmcshinsky-bitgo mmcshinsky-bitgo changed the title fix(statics): skip AMS tokens that duplicate an existing contract address fix(statics): skip AMS tokens that duplicate an existing contract address or NFT collection id Jun 5, 2026
@mmcshinsky-bitgo
Copy link
Copy Markdown
Contributor Author

Like #8959, will let coins team move forward with this.

@mmcshinsky-bitgo mmcshinsky-bitgo force-pushed the fix/statics-ams-contract-dedup branch from d9db10b to 7db3b4e Compare June 5, 2026 18:47
@mmcshinsky-bitgo mmcshinsky-bitgo changed the title fix(statics): skip AMS tokens that duplicate an existing contract address or NFT collection id fix(statics): skip AMS tokens that reuse a static contract address or NFT collection id 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 mmcshinsky-bitgo force-pushed the fix/statics-ams-contract-dedup branch from 7db3b4e to 8e16a1c Compare June 5, 2026 20:07
@mmcshinsky-bitgo mmcshinsky-bitgo self-assigned this Jun 5, 2026
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