Skip to content

fix: use standard Substrate V4 format for unsigned extrinsics#218

Merged
lcovar merged 1 commit intomasterfrom
BTC-3161/fix-unsigned-extrinsic-format
Mar 19, 2026
Merged

fix: use standard Substrate V4 format for unsigned extrinsics#218
lcovar merged 1 commit intomasterfrom
BTC-3161/fix-unsigned-extrinsic-format

Conversation

@lcovar
Copy link
Contributor

@lcovar lcovar commented Mar 18, 2026

Summary

wasm-dot's unsigned extrinsic builder was encoding signed extensions (era, nonce, tip, etc.) in the extrinsic body before the call data:

compact(length) | 0x04 | era | nonce | tip | ... | call_data

Standard Substrate V4 unsigned format is:

compact(length) | 0x04 | call_data

Signed extensions belong only in the signing payload (computed by subxt-core's signer_payload()), not in the extrinsic body. The non-standard format caused any tool decoding the unsigned extrinsic to misread the extension bytes as the start of call data, producing invalid pallet/method indices (e.g. [43, 127] instead of [10, 3] for balances.transferKeepAlive).

This didn't affect signed transactions (subxt rebuilds the full extrinsic correctly during signing) or broadcast, but broke offline verification and any external decoder.

Changes

  • builder/mod.rs: simplified build_unsigned_extrinsic to just 0x04 | call_data, removed signed extension encoding logic (era/nonce/tip/CheckMetadataHash/ChargeAssetTxPayment). Era/nonce/tip are now set on the Transaction struct from the build context instead.
  • transaction.rs: updated unsigned extrinsic parser to read call data immediately after version byte (no extension parsing for unsigned).
  • Removed is_empty_type and encode_zero_value helpers from builder (only needed for extension encoding, still in transaction.rs for signed parsing).

Testing

All 26 existing tests pass. The fix is backwards-compatible for consumers: signablePayload(), addSignature(), and toBroadcastFormat() continue to work correctly since they use subxt-core APIs that handle extensions internally.

@lcovar lcovar requested a review from a team as a code owner March 18, 2026 23:50
@lcovar lcovar force-pushed the BTC-3161/fix-unsigned-extrinsic-format branch from 2e7feb2 to 51c61ec Compare March 18, 2026 23:50
The unsigned extrinsic builder was encoding signed extensions (era,
nonce, tip, CheckMetadataHash, ChargeAssetTxPayment) in the extrinsic
body before the call data. Standard Substrate V4 unsigned format is:

  compact(length) | 0x04 | call_data

Signed extensions belong only in the signing payload (computed by
subxt-core's signer_payload()), not in the broadcast-format extrinsic.

The non-standard format caused any tool decoding the unsigned extrinsic
(polkadot-js createType, txwrapper decode, offline verification tools)
to misread the extension bytes as the start of call data, producing
invalid pallet/method indices.

Also update the unsigned extrinsic parser to match: call data starts
immediately after the version byte for unsigned transactions.

BTC-3161
@lcovar lcovar force-pushed the BTC-3161/fix-unsigned-extrinsic-format branch from 51c61ec to 1111b4c Compare March 19, 2026 03:57
@lcovar lcovar enabled auto-merge March 19, 2026 13:24
@lcovar lcovar merged commit 3a2ad71 into master Mar 19, 2026
13 checks passed
@lcovar lcovar deleted the BTC-3161/fix-unsigned-extrinsic-format branch March 19, 2026 14:23
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