Skip to content

feat: add Stacks (Bitcoin L2) chain support#190

Open
Sertug17 wants to merge 1 commit intoopen-wallet-standard:mainfrom
Sertug17:feat/stacks-signer
Open

feat: add Stacks (Bitcoin L2) chain support#190
Sertug17 wants to merge 1 commit intoopen-wallet-standard:mainfrom
Sertug17:feat/stacks-signer

Conversation

@Sertug17
Copy link
Copy Markdown
Contributor

@Sertug17 Sertug17 commented Apr 4, 2026

Closes #99

Adds Stacks as a supported chain in OWS, implementing the full ChainSigner interface.

What's included

StacksSigner (ows-signer/src/chains/stacks.rs)

  • secp256k1 curve, coin type 5757 (SLIP-44)
  • c32check address encoding — SP... mainnet, ST... testnet
  • SHA-512/256 transaction hashing (Stacks-native)
  • SIP-018 message signing via SHA-512/256
  • Derivation path: m/44'/5757'/0'/0/{index}

Chain registry (ows-core/src/chain.rs)

  • ChainType::Stacks variant
  • CAIP-2: stacks:1 (mainnet), stacks:2147483648 (testnet)
  • CAIP-10 namespace: "stacks"
  • Coin type: 5757

Tests

7 unit tests:

  • mainnet/testnet address prefix validation
  • mainnet vs testnet address differ
  • deterministic address derivation
  • SHA-512/256 transaction signing
  • derivation path format
  • chain properties (type, curve, coin type)

Notes


Note

Medium Risk
Adds a new chain family (Stacks) into wallet derivation/signing flows and introduces new crypto/encoding code paths (Stacks c32check + SHA-512/256 signing, plus EVM EIP-7702 authorization hashing), which could affect key/address derivation and signing correctness.

Overview
Adds Stacks (Bitcoin L2) as a first-class chain family: new ChainType::Stacks, CAIP-2 entries for mainnet/testnet, namespace/coin-type mappings, and inclusion in ALL_CHAIN_TYPES so universal wallet derivation now generates Stacks accounts.

Introduces StacksSigner implementing ChainSigner (secp256k1, SLIP-44 5757, c32check address derivation, SHA-512/256-based tx/message hashing) and wires it into signer_for_chain; broadcasting is explicitly stubbed as unsupported for Stacks.

Extends EvmSigner with EIP-7702 authorization helpers (authorization_payload/authorization_hash) including quantity/address parsing with an early-rejection guard for oversized decimal inputs, and adds focused unit tests for these new behaviors.

Reviewed by Cursor Bugbot for commit 2d84e08. Bugbot is set up for automated code reviews on this repo. Configure here.

Closes open-wallet-standard#99

- StacksSigner implementing ChainSigner — secp256k1 curve, coin type 5757
- c32check address encoding (SP... mainnet, ST... testnet)
- SHA-512/256 transaction hashing (Stacks-native, distinct from Bitcoin)
- SIP-018 message signing via SHA-512/256
- CAIP-2/10 integration: stacks:1 (mainnet), stacks:2147483648 (testnet)
- Derivation path: m/44'/5757'/0'/0/{index}
- 7 unit tests covering address derivation, signing, determinism
- CLI policy display and broadcast stub
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 4, 2026

@Sertug17 is attempting to deploy a commit to the MoonPay Team on Vercel.

A member of the Team first needs to authorize it.

@Sertug17 Sertug17 requested a review from njdawn as a code owner April 4, 2026 19:04
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 2d84e08. Configure here.


let mut payload = vec![version];
payload.extend_from_slice(data);
payload.extend_from_slice(checksum);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

c32check encodes version byte twice, producing wrong addresses

High Severity

The c32check_encode function includes the version byte in the payload that gets c32-encoded (line 44: vec![version]), and then also prepends the version character separately on line 68. Per the Stacks c32check spec, only data + checksum (without the version byte) gets c32-encoded; the version appears solely as the second character prefix (after S). This double-encoding produces addresses that are longer and entirely incompatible with real Stacks addresses — any derived address will be invalid on-chain.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2d84e08. Configure here.

ChainType::Filecoin => "filecoin",
ChainType::Sui => "sui",
ChainType::Xrpl => "xrpl",
ChainType::Stacks => "stacks",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FromStr for ChainType missing Stacks variant

Medium Severity

The FromStr implementation for ChainType was not updated to include "stacks". All other match-based methods (namespace(), default_coin_type(), from_namespace(), Display) were updated with the new variant, but from_str still falls through to the _ wildcard, causing "stacks".parse::<ChainType>() to return an error instead of Ok(ChainType::Stacks).

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 2d84e08. Configure here.

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.

Proposal: Enhanced Bitcoin L1 support (Taproot, PSBT) + Stacks as Bitcoin L2

1 participant