Skip to content

fix(sdk-coin-polyx): replace stale mainnet v8 material with live metadata#9354

Draft
nvrakesh06 wants to merge 1 commit into
masterfrom
nvrakeshreddy/polyx-v8-mainnet-material-fix
Draft

fix(sdk-coin-polyx): replace stale mainnet v8 material with live metadata#9354
nvrakesh06 wants to merge 1 commit into
masterfrom
nvrakeshreddy/polyx-v8-mainnet-material-fix

Conversation

@nvrakesh06

Copy link
Copy Markdown
Contributor

Summary

mainnetV8Material (modules/sdk-coin-polyx/src/resources/mainnetV8.ts) was a placeholder created before the Polymesh mainnet v8 fork (July 22, 2026). It mirrored v7 metadata with only specVersion/txVersion bumped to v8 values — the actual pallet/call-index layout was never updated.

Every v8 builder (V8TransferBuilder, V8HexTransferBuilder, V8TokenTransferBuilder, V8RegisterDidBuilder, V8RegisterDidWithCDDBuilder, V8PreApproveAssetBuilder, V8BatchStakingBuilder, V8NominateBuilder, V8BondExtraBuilder, V8UnbondBuilder, V8WithdrawUnbondedBuilder, V8BatchUnstakingBuilder) defaults to this same bundled material in its constructor. This is also the material TransactionBuilderFactory.tryGetV8Builder() falls back to internally when decoding. So any caller — direct SDK consumer, sandbox script, or future code — that builds/decodes a real mainnet v8 transaction without first overriding .material() with live data hits corrupted call-index resolution.

This is a companion fix to #CECHO-1781 (the getDummyTransferHex() v7→v8 re-encode in bitgo-microservices) and the related SI-1034 specVersion-forwarding fix already on this repo — this PR fixes the underlying metadata shape, not just the specVersion number.

Proof

Decoding a real, correctly-v8-encoded mainnet transferWithMemo extrinsic using only the old bundled mainnetV8Material (no live override) — the exact fallback path tryGetV8Builder() uses:

Call: failed decoding system.killStorage:: ...
Bytes: required length less than remainder, expected at least 14191, found 69

It misresolves the call as system.killStorage and throws, instead of routing to V8TransferBuilder.

After regenerating mainnetV8Material with live metadata fetched directly from wss://mainnet-rpc.polymesh.network/ (bypassing substrate-api-sidecar, whose /runtime/metadata 500s on v8 metadata for sidecar versions < 20.10):

  • genesisHash is unchanged (0x6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063) — confirms this is the correct chain.
  • A fresh V8TransferBuilder build using only the default bundled material now correctly encodes call index 0x0528.
  • Decoding that built tx via TransactionBuilderFactory.from(), again using only the default material (no live override), now correctly round-trips to a V8TransferBuilder instead of throwing.
  • Live chain is at specVersion 8000020; this snapshot was captured at that same spec version, at block height 24795900.

Why this didn't break wallet-platform's production flows

I audited every real (non-dummy) build/decode call site in wallet-platform's polyx.ts — fresh builds (PolyxFactory), getSignedTx, transactionToBuilder, the TSS-combine flow, extractMemoFromRawTx, and the sendQueue nonce-rebuild path. All of them fetch live metadata via getOrFetchTransactionMetadata() (RPC-backed, not the SDK's bundled material) and call .material(liveMaterial) before building/decoding — enforced by an explicit @obligation comment in the factory. So wallet-platform never actually exercised the bug in this PR. This PR closes the gap for anyone who calls the SDK directly without doing the same.

Test plan

  • mocha unit suite in sdk-coin-polyx — 252 passing, 2 pending (unchanged from before this change)
  • Verified genesisHash in new material matches the old placeholder's mainnet genesis hash
  • Verified build-then-decode round trip using only default (bundled) material succeeds post-fix
  • Reviewer: confirm no other consumers depend on the exact byte content of the old placeholder metadata (e.g. golden-file tests elsewhere in the monorepo)

Made with Cursor

…data

mainnetV8Material was a placeholder created before the mainnet v8 fork:
it mirrored v7 metadata with only specVersion/txVersion bumped, so its
call-index layout never matched the live v8 chain. Decoding a real,
correctly-encoded mainnet transferWithMemo extrinsic against this
placeholder misresolved the call as system.killStorage and threw,
instead of routing to V8TransferBuilder. Every v8 builder
(V8TransferBuilder, V8TokenTransferBuilder, V8RegisterDidBuilder, etc.)
defaults to this same material in its constructor, so any caller that
built or decoded a mainnet v8 transaction without first overriding
.material() with live data hit the same corruption.

Regenerated mainnetV8Material by fetching live runtime metadata directly
from wss://mainnet-rpc.polymesh.network/ via @polkadot/api (bypassing
substrate-api-sidecar, whose /runtime/metadata endpoint 500s on v8
metadata on sidecar versions < 20.10). Verified genesisHash is unchanged
and that a fresh V8TransferBuilder build now round-trips through
factory.from() using only the default bundled material, with no live
material override required.

Refs: CECHO-1781

Co-authored-by: Cursor <cursoragent@cursor.com>
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