Skip to content

fix(statics): never swap known coins for a different coin class - #9971

Draft
ralph-bitgo[bot] wants to merge 1 commit into
masterfrom
CSHLD-1957-statics-protect-known-base-coins
Draft

ralph-bitgo[bot] wants to merge 1 commit into
masterfrom
CSHLD-1957-statics-protect-known-base-coins

Conversation

@ralph-bitgo

@ralph-bitgo ralph-bitgo Bot commented Oct 14, 2026

Copy link
Copy Markdown

Description

Fixes CSHLD-1957. createToken() in @bitgo/statics refreshed any coin matched by an AMS entry — including known base chains — rebuilding it as a generic DynamicCoin. A LightningCoin such as tlnbtc was swapped out, so downstream @bitgo/abstract-lightning threw coin is not a lightning coin and getCoinSingletons() failed in every wallet-platform process on staging (regression from #9922, shipped via @bitgo-beta/statics@15.1.1-beta.2138).

What changed

  • modules/statics/src/coins.ts — createToken() now refreshes a known coin only when both the existing record and the AMS entry are tokens; known base coins keep their static record.
  • modules/statics/src/map.ts — CoinMap.replace() rejects a candidate whose coin class differs from the record it would replace (coin '<name>' cannot be replaced: coin class changed from <Class> to <Class>), checked after the existing immutable-field checks so identity mismatches keep their specific errors.
  • modules/statics/test/unit/coins.ts — regression tests: valid-UUID AMS entries for tlnbtc (LightningCoin) and btc (UtxoCoin) preserve identity and class through createTokenUsingTrimmedConfigDetails and createTokenMapUsingTrimmedConfigDetails; a DynamicCoin can no longer replace btc; the same-class base-coin refresh fixture now keeps the candidate's prototype. This replaces the previous test that passed only because it used the invalid id 'btc'.

Why

Staging is broken for every wallet-platform process that builds coin singletons (/startupready client-constants check, market-data worker, ERC-7984 unwrap decrypt worker), and an ERC-7984 unshield journey is stuck in DECRYPT_PENDING, retrying every ~15 minutes until coin lookups work again. The commit that introduced CoinMap.replace documented that replacement "only swaps when both records are tokens", but the shipped code never enforced it, so an AMS entry for a known base chain could swap LightningCoin for DynamicCoin.

Issue Number

CSHLD-1957 (related: CSHLD-1936, CHALO-1809)

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

  • Full @bitgo/statics unit suite (npx mocha in modules/statics): 39702 passing, 0 failing.
  • Throwaway repro of the ticket's minimal case (AMS entry with a valid UUID for tlnbtc via createTokenMapUsingTrimmedConfigDetails, and for btc via createTokenUsingTrimmedConfigDetails): before the fix both became DynamicCoin; after the fix LightningCoin / UtxoCoin and object identity are preserved. Repro script deleted after verification.
  • yarn build (tsc) in modules/statics, plus eslint and prettier --check on the three changed files: 0 errors.

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • My code compiles correctly for both Node and Browser environments
  • I have commented my code, particularly in hard-to-understand areas
  • My commits follow Conventional Commits and I have properly described any BREAKING CHANGES
  • The ticket or github issue was included in the commit message as a reference
  • I have made corresponding changes to the documentation and on any new/updated functions and/or methods - jsdoc
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Ticket: CSHLD-1957

What: createToken() refreshes a known coin only when both the existing
record and the AMS entry are tokens, and CoinMap.replace now rejects a
candidate whose constructor differs from the record it would replace.
Tests cover valid-UUID AMS entries for tlnbtc and btc and a DynamicCoin
attempting to replace btc.

Why: statics 15.1.1-beta.2138 rebuilt known base chains from AMS
entries, so a LightningCoin such as tlnbtc became a generic DynamicCoin.
Downstream abstract-lightning threw "coin is not a lightning coin",
getCoinSingletons() failed in every wallet-platform process on staging,
and ERC-7984 unwrap journeys cannot finish until coin singletons work.

Ticket: CSHLD-1957
Session-Id: 0d9fb72f-b6e4-4906-8aad-9b1608911df4
Task-Id: 5c117f14-0e09-4396-b8fb-58501604623a
@linear-code

linear-code Bot commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

CSHLD-1957

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Unit tests are failing on Node 26.x (Current release line, non-blocking). This is not an LTS version yet, so it does not block merge, but it signals an incompatibility to fix before Node 26.x becomes LTS.

View run

@prajwalu142
prajwalu142 marked this pull request as ready for review October 14, 2026 14:45
@prajwalu142
prajwalu142 requested a review from a team as a code owner October 14, 2026 14:45
@prajwalu142
prajwalu142 marked this pull request as draft October 14, 2026 14:45

This branch has not been deployed

No deployments
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