SwapChan
Never holds your keys Β· your wallet signs
blocktools CLI

Chain table

Source of truth: crates/core/src/chains.rs. Known-answer vectors come from scripts/kat-chains.py, an independent base58check that uses only hashlib,…

Source of truth: crates/core/src/chains.rs. Known-answer vectors come from scripts/kat-chains.py, an independent base58check that uses only hashlib, and are checked in the kat_long_tail test. tx-build builds chains whose Sign is Standard or ForkId (bch/bsv/btg; tx-sign signs those with SIGHASH_ALL|FORKID, see below); it refuses PosTime only for pot (ppc/nvc/clam/rdd have a dedicated pos-ntime builder/signer, crates/tx/src/pos.rs; blk is Standard) and Unknown (qrk, plus the non-UTXO chains, which have their own signers). No coin other than BTC has been signed and accepted on a real network.

Supported (verified)

name ticker SLIP-44 p2pkh p2sh hrp wif checksum decimals forkid tx format sweep build sources
btc BTC 0 00 05 bc 128 sha256d 8 β€” legacy yes esplora blockstream.info
ltc LTC 2 30 32 ltc 176 sha256d 8 β€” legacy yes esplora litecoinspace.org
doge DOGE 3 1e 16 β€” 158 sha256d 8 β€” legacy yes none
eth ETH 60 β€” β€” β€” β€” β€” 18 β€” evm no none
tron TRX 195 β€” β€” β€” β€” β€” 6 β€” account no none
xrp XRP 144 β€” β€” β€” β€” β€” 6 β€” account no none
sol SOL 501 β€” β€” β€” β€” β€” 9 β€” account no none
atom ATOM 118 β€” β€” cosmos β€” β€” 6 β€” account no none
algo ALGO 283 β€” β€” β€” β€” β€” 6 β€” account no none
ada ADA 1815 β€” β€” β€” β€” β€” 6 β€” account no none
ton TON 607 β€” β€” β€” β€” β€” 9 β€” account no none
dot DOT 354 β€” β€” β€” β€” β€” 10 β€” account no none
xlm XLM 148 β€” β€” β€” β€” β€” 7 β€” account no none
aptos APT 637 β€” β€” β€” β€” β€” 8 β€” account no none
sui SUI 784 β€” β€” β€” β€” β€” 9 β€” account no none
near NEAR 397 β€” β€” β€” β€” β€” 24 β€” account no none
xtz XTZ 1729 β€” β€” β€” β€” β€” 6 β€” account no none
egld EGLD 508 β€” β€” β€” β€” β€” 18 β€” account no none
qtum QTUM 2301 3a 32 qc 128 sha256d 8 β€” legacy yes qtuminfo qtum.info
ftc FTC 8 0e 05 fc 142 sha256d 8 β€” legacy yes none
nmc NMC 7 34 0d nc 180 sha256d 8 β€” legacy yes none
dash DASH 5 4c 10 β€” 204 sha256d 8 β€” legacy yes blockbook blockbook.dash.zelcore.io; insight insight.dash.org
vtc VTC 28 47 05 vtc 128 sha256d 8 β€” legacy yes blockbook blockbook.vtc.zelcore.io
dgb DGB 20 1e 3f dgb 128 sha256d 8 β€” legacy yes blockbook blockbook.dgb.zelcore.io
via VIA 14 47 21 via 199 sha256d 8 β€” legacy yes blockbook blockbook.viacoin.org
mona MONA 22 32 37 mona 176 sha256d 8 β€” legacy yes insight mona.insight.monaco-ex.org
rvn RVN 175 3c 7a β€” 128 sha256d 8 β€” legacy yes blockbook blockbook.rvn.zelcore.io; blockbook blockbook.ravencoin.org
xpm XPM 24 17 53 β€” 151 sha256d 8 β€” legacy yes cryptoid chainz.cryptoid.info
trc TRC 83 00 05 β€” 128 sha256d 8 β€” legacy yes none
mec MEC 217 32 05 mec 178 sha256d 8 β€” legacy yes none
aur AUR 85 17 05 β€” 176 sha256d 8 β€” legacy yes cryptoid chainz.cryptoid.info
bch BCH 145 00 05 β€” 128 sha256d 8 0 forkid yes none
bsv BSV 236 00 05 β€” 128 sha256d 8 0 forkid yes woc whatsonchain.com
btg BTG 156 26 17 btg 128 sha256d 8 79 forkid yes none
ppc PPC 6 37 75 pc 183 sha256d 6 β€” pos-ntime yes blockbook blockbook.peercoin.net
nvc NVC 50 08 14 β€” 136 sha256d 6 β€” pos-ntime yes cryptoid chainz.cryptoid.info
clam CLAM 23 89 0d β€” 133 sha256d 8 β€” pos-ntime yes cryptoid chainz.cryptoid.info
blk BLK 10 19 55 β€” 153 sha256d 8 β€” legacy yes cryptoid chainz.cryptoid.info
rdd RDD 4 3d 05 rdd 189 sha256d 8 β€” pos-ntime yes blockbook blockbook.reddcoin.com
pot POT 81 37 6e β€” 183 sha256d 8 β€” pos-ntime no cryptoid chainz.cryptoid.info
qrk QRK 82 3a 09 β€” 186 sha256d 5 β€” legacy yes cryptoid chainz.cryptoid.info
zec ZEC 133 1cb8 1cbd β€” 128 sha256d 8 β€” zcash-v5 yes (transparent P2PKH) none
grs GRS 17 24 05 grs 128 groestl512d 8 β€” legacy-sha256 yes none listed
strd STRD 118 β€” β€” stride β€” β€” 6 β€” account no none
axl AXL 118 β€” β€” axelar β€” β€” 6 β€” account no none
cre CRE 118 β€” β€” cre β€” β€” 6 β€” account no none
kuji KUJI 118 β€” β€” kujira β€” β€” 6 β€” account no none
cmdx CMDX 118 β€” β€” comdex β€” β€” 6 β€” account no none
ntrn NTRN 118 β€” β€” neutron β€” β€” 6 β€” account no none
somm SOMM 118 β€” β€” somm β€” β€” 6 β€” account no none
fet FET 118 β€” β€” fetch β€” β€” 6 β€” account no none
mars MARS 118 β€” β€” mars β€” β€” 6 β€” account no none
umee UMEE 118 β€” β€” umee β€” β€” 6 β€” account no none
noble USDC 118 β€” β€” noble β€” β€” 6 β€” account no none
sei SEI 118 β€” β€” sei β€” β€” 6 β€” account no none
tia TIA 118 β€” β€” celestia β€” β€” 6 β€” account no none
coreum CORE 990 β€” β€” core β€” β€” 6 β€” account no none
qsr QSR 118 β€” β€” quasar β€” β€” 6 β€” account no none
xprt XPRT 118 β€” β€” persistence β€” β€” 6 β€” account no none
akt AKT 118 β€” β€” akash β€” β€” 6 β€” account no none
kava KAVA 459 β€” β€” kava β€” β€” 6 β€” account no none
blz BLZ 483 β€” β€” bluzelle β€” β€” 6 β€” account no none
band BAND 494 β€” β€” band β€” β€” 6 β€” account no none
cro CRO 394 β€” β€” cro β€” β€” 8 β€” account no none
bld BLD 564 β€” β€” agoric β€” β€” 6 β€” account no none
dydx DYDX 118 β€” β€” dydx β€” β€” 18 β€” account no none
rune RUNE 931 β€” β€” thor β€” β€” 8 β€” account no none
inj INJ 60 β€” β€” inj β€” β€” 18 β€” account no none
evmos EVMOS 60 β€” β€” evmos β€” β€” 18 β€” account no none
canto CANTO 60 β€” β€” canto β€” β€” 18 β€” account no none
zeta ZETA 60 β€” β€” zeta β€” β€” 18 β€” account no none

Fee-warning limits (verify-tx / wasm verifyTx, chainParams().fee_warn)

high_fee fires above fee base units, high_fee_rate above rate base units per vbyte. Default 1,000,000 / 500 (Bitcoin-tuned). A per-coin row is 20 x the measured median fee rate x a 1,000 vB sweep (rate limit 20 x median), never below the default. Measured 2026-10-24 from recent mainnet blocks (non-coinbase txs, fee = inputs - outputs); scripts in the commit message basis strings.

Since 2026-10-25: (1) the fee limit is per started 1,000 vB of the tx (FeeWarn::fee_for(vsize) = fee x ceil(vsize/1000)), so a 147-input VTC sweep (~21,800 vB) is checked against 22 x 2,000,000, not 2,000,000; (2) the hard fee-rate cap in every sweep builder (buildUnsignedSweep*, legacy, pos-ntime and zec) is per coin, FeeWarn::rate_cap() = max(2,000, 4 x rate limit): btc/default 2,000, xpm 8,000,000 (measured median 100,312 sat/B is now buildable). Break: the pos-ntime and zec builders previously allowed up to 100,000 sat/B; they now get their row's cap (2,000 unless the row has a measured limit). (unmeasured)

Default fee cap (signing, no --max-fee; 3ec1d1a). Every signing route (tx-sign, tx-verify, envelope, wasm verifyTx) refuses fee_above_cap above max(100,000 sat, the chain's rate limit from this table x the tx's vsize), even with warnings waived; an explicit max_fee replaces it. The verdict reports fee_cap {chain, cap, source}. Measured by crates/tx/tests/fee_cap_chains.rs: SwapChan's corpus of 35 legitimate sweeps all pass (the old flat 100,000-sat cap refused 9 of 35); on each of the 27 buildable chains a fee 1 unit over the cap is refused and 10x the cap passes only with an explicit max_fee. BTC: a 1-input tx paying 240,000 sat is refused.

chain fee limit rate limit measured median sample
dgb 10,000,000 10,000 512 sat/vB 60 txs, blocks 24444779-24445073, blockbook.dgb.zelcore.io (unmeasured)
rvn 20,000,000 20,000 1004 sat/vB 60 txs, blocks 4596227-4596275, blockbook.rvn.zelcore.io (unmeasured)
qtum 14,000,000 14,000 700 sat/vB 100 txs, blocks 6253698-6255768, qtum.info (unmeasured)
xpm 2,000,000,000 2,000,000 100,312 sat/B 30 txs, blocks 7064002-7064118, chainz.cryptoid.info (raw rebuilt from explorer JSON, sha256d = txid, exact size) (unmeasured)
vtc 2,000,000 2,000 101 sat/vB 60 txs, blocks 2703728-2703858, blockbook.vtc.zelcore.io (unmeasured)
doge 1,200,000,000 1,200,000 60,463 sat/B (p10 2,418, p90 523,560) 100 txs, blocks 6429664-6429671, blockbook.doge.zelcore.io, sha256d-checked. Policy minimum 1,000 sat/B (RECOMMENDED_MIN_TX_FEE 0.01 DOGE/kB) is far below what wallets pay (unmeasured)
ftc 40,000,000 40,000 2,000 sat/vB (p10 600) 100 txs (21 segwit), blocks 6407004-6408299, blockbook.feathercoin.com (unmeasured)
mona 2,000,000 2,000 2.54 sat/vB; p75 and p90 = 100 100 txs, blocks 4142748-4143093, mona.insight.monaco-ex.org, exact vsize (witness stripped), 100/100 txids checked. Bimodal: ~2.5 (relay-minimum wallets) and 100 (Monacoin Core DEFAULT_TRANSACTION_MINFEE 100,000/kB); the limit is 20 x the 100 mode (unmeasured)
aur 2,000,000 2,000 102.8 sat/B 30 txs, blocks 5913204-5913347, chainz.cryptoid.info (raw rebuilt, sha256d = txid, exact size) (unmeasured)

MONA rate (SwapChan uses 220): the chain data says 2.5 sat/vB is the median and 100 sat/vB the common wallet default; no sampled tx paid 220 (max 349, one outlier). 2.5 is right as "what the chain accepts" (node relayfee 0.001 MONA/kB = 1 sat/B); 100 matches Monacoin Core's default minimum tx fee. 220 is not supported by the data: it overpays ~2x the wallet default and ~90x the median. Suggest 100 (wallet-compatible) or a floor of 3. (unmeasured)

Measured and left at the default: dash (3.9 sat/B, 60 txs, blockbook.dash.zelcore.io), trc (re-measured 2026-10-25: median 20.0 sat/B, p10 1.32, p90 20.1, 100 txs in 2,743 blocks 3325168-3327910, insight.terracoin.io, txid-checked 100/100; replaces the earlier 14-tx 14.8 figure), ppc (blockbook reports fee 0 for most txs; fees are burned), via (4 txs in 400 blocks, all fee 0). Also measured: wdc 5.4 sat/B (4 txs, block 11881187; default kept). Not measured (default applies): every other row, including nmc, mec, nvc, clam, blk, pot, qrk, grs, bch, bsv, btg, ltc. (unmeasured)

RDD (not measured): source found, rddblockexplorer.com (blockbook). Nearly every tx is a PoSV stake (outputs > inputs); a scan of ~9,000 blocks (6002715-6011409) found only 32 fee-paying txs, and the run was stopped before fees were computed. RDD keeps the default limits (now size-scaled); its own row still needs measuring.

ZEC vs ZIP-317 (2026-10-25): 200 txs, blocks 3529835-3529915, blockbook.zec.zelcore.io. Ratio fee / ZIP-317 conventional fee (5,000 x max(2, transparent inputs, transparent outputs)): median 1.48, p10 1.00, 73/199 exactly 1.00, 1 below 1. The upper tail (p90 ~125,000x) is not real overpayment: blockbook cannot see shielded value, so for shielding/unshielding txs inputs - outputs is not the fee; only transparent-only txs are meaningful there, and the ZIP-317 figure is a lower bound when shielded actions exist. Rate median 49.7 sat/B (ZIP-317 minimum on a 1-in-1-out tx). Default limits kept. Immature coinbase: the ZEC explorer SwapChan uses (blockbook.zec.zelcore.io) lists immature coinbase outputs as spendable UTXOs (reported by SwapChan; not re-measured here); consensus requires 100 confirmations (COINBASE_MATURITY). Consumers must drop UTXOs with coinbase: true and confirmations < 100 before buildUnsignedSweep; blocktools cannot tell from {txid,vout,amount,address} alone. (unmeasured)

Unverified (never returned as supported)

name ticker SLIP-44 p2pkh p2sh hrp wif checksum decimals forkid tx format sweep build sources
wdc WDC 0 49 05 β€” 201 sha256d 8 β€” legacy no cryptoid chainz.cryptoid.info
anc ANC 0 17 0f β€” 151 sha256d 8 β€” legacy no none
bcc BCC 0 12 55 β€” 146 sha256d 8 β€” pos-ntime no none

Zcash Sapling: zec-sapling CLI (ZIP-32, m/32'/133'/i', zs1 default address, optional 0600 viewing-key file; --unified adds the ZIP-316 u1 orchard+sapling+p2pkh address and t1). No spend/scan.

Zcash transparent is listed by wasm listChains/supportedChains (scope transparent) since 2026-10-25. Evidence: ZIP-243/244 official vectors, 7 mainnet txs (txid + signature), 21 mainnet script->address pairs from api.blockchair.com (crates/tx/tests/data/zec_addresses.txt). Untested: BIP44 m/44'/133' derivation, xpubs, broadcast, v6.

WDC: tx format confirmed Bitcoin-legacy on 4 mainnet txs (block 11881187): txid = sha256d, 357 P2PKH signatures verify under the legacy sighash, and every input address re-derives under p2pkh 73 (crates/tx/tests/wdc.rs). Still verified: false: no SLIP-44 coin type, so no derivation path can be checked. QRK (2026-10-25): tx format Bitcoin-legacy, found via chainz api.dws?q=lasttxs (block-by-block scanning saw only coinbase): 10/10 recent non-coinbase txs rebuild to their txid under sha256d, but only with values x 1e5, i.e. QRK has 5 decimals (COIN = 100000; the table said 8, now fixed: decimals 5). crates/tx/tests/qrk.rs: 2 txs (blocks 13906691, 13907052; 8 and 170 inputs), 10 P2PKH signatures verify under the legacy sighash and re-derive to the chainz input addresses under p2pkh 58; 168 P2PK inputs are not checked (pubkey lives in the prevout). Row now Standard, so buildUnsignedSweep supports qrk. Untested: a built QRK sweep broadcast on mainnet; QRK fee rates (default limits apply).

Account payments (2026-10-25)

xlm, algo, atom, ton, ada and dot have payment/sweep builders in acct-tx (see recipes/chain-polkadot.md, chain-cardano.md, chain-ton.md, chain-stellar.md, chain-algorand.md, chain-cosmos.md). The table's sweep build column is the wasm/UTXO builder only and stays no for them.

Groestlcoin extended keys and taproot (2026-10-25)

GRS xpub/tpub strings use the Groestl-512 checksum and are accepted only with --chain grs (vectors: Groestlcoin/eclair commit f52fbf9, 4 tpub pairs); classify reports them as xpub. GRS rows include p2tr (grs1p…, BIP341 vector); spending GRS taproot inputs is refused. Untested: real Electrum-GRS mainnet xpub, any mainnet grs1p.

Monero (2026-10-25)

Scan and sweep: host tool xmr-wallet over a loopback monero-wallet-rpc. 2026-10-25 stagenet run (Mac, official monero v0.18.5.4 binaries, SHA-256 checked against the published hashes.txt): faucet 0.5 XMR seen by xmr-wallet scan (unlocked 500000000000 at height 2236134); xmr-wallet sweep --relay planned amount 499969620000 + fee 30380000 = unlocked, relayed tx bb300227e8e6c418fe7d825ac432d79abc2d4b943d99e053eabfba3c8197a96d (mined in stagenet block 2236135, checked on a public stagenet node); a view-only scan of the destination wallet shows that amount incoming (pool). The TTY confirm was answered by an expect script standing in for a human. Untested: mainnet, Polyseed/13-word/non-English seeds, subaddresses.

Solana public data (2026-10-25)

wasm/lib sol: PDA/ATA, on-curve, Anchor discriminator, legacy+v0 tx decode, ed25519 signature check, solVerifyTx policy. No Solana builder yet (planned: acct-tx style, solders vectors).

  • 2026-10-26: BCH CashAddr destinations in the CLIs: tx-build --to, tx-verify --expect-to and tx-sign --expect-to accept bitcoincash:q…/p…, bare and upper-case forms (mainnet prefix, 20-byte hash). Measured (Linux only): crates/core/tests/cashaddr_script.rs (2 tests; CashAddr spec table, the CashAddr maps to the same script as its legacy address; bad checksum, mixed case, bchtest refused) and scripts/e2e.d/cashaddr-tx.sh (tx-build bytes identical to the legacy --to, verify refuses a different destination, tx-sign passes). Untested: other CashAddr hash sizes (refused), eCash prefix on xec.
  • 2026-10-25: BCH CashAddr accepted by addressToScript('bch', …) and converted by cashAddrToLegacy / legacyToCashAddr.
  • 2026-10-25: EVM transfer builder for any chain id (buildEvmTransfer); UTXO selection (selectUtxos).
  • 2026-10-25: EVM recovery/sweep verifier (SwapChan parity, raw tx entry points).
  • 2026-10-25: Solana v1 transactions (SIMD-0385) decode + verify; XRP X-address kind xaddress in detectAddressInfo.
  • 2026-10-25: buildPsbtWithMemo output order [to, change, OP_RETURN] (THORChain), optional memo, selected-input fee fix.
  • 2026-10-25: Tron txID + raw_data decode (tronDecodeTx); BTC-family PSBT with OP_RETURN memo (buildPsbtWithMemo).

Fork-id signing (BCH, BSV, BTG)

tx-sign signs bch/bsv/btg PSBTs from tx-build with the replay-protected fork-id sighash: the BIP143 digest layout for every input type (P2PKH included), committed with the 32-bit type 0x41 | forkid << 8 (BCH/BSV forkid 0 -> 0x41; BTG forkid 79 -> 0x4f41), signature byte 0x41. Never the BTC sighash. Signed: P2PKH (compressed and uncompressed) and P2PK on all three; P2WPKH on BTG only (the only one with an hrp). Refused: any other script (forkid_script_unsupported), any input without a proving prev_tx (legacy_input_amount_unproven), P2WPKH on bch/bsv.

Measured (Linux only): forkid_sighash matches all 495 SIGHASH_FORKID rows of Bitcoin ABC's src/test/data/sighash.json (crates/tx/tests/forkid.rs; vendored verbatim as crates/tx/tests/vectors/abc-sighash.json); the signatures in BCH mainnet tx 96ee2000…bbb4, BSV mainnet tx 8f95d11f…508c (block 972,739) and Trust Wallet Core's BTG P2WPKH vector verify under it and do not verify under the BTC sighash (or, for BTG, fork id 0); tx-sign's signer reproduces Trust Wallet Core's BCH and BTG signed txs byte for byte (forkid_bch_btg_byte_exact); scripts/e2e.d/forkid.sh builds, tty-signs, decodes and re-verifies a sweep on each chain and checks each signature with an independent pure-Python ECDSA verifier. Untested: broadcast or acceptance on any of the three networks, BCH CashAddr destinations in tx-build, BCH Schnorr signatures, multi-input fork-id txs against a real node.

Every non-segwit input needs prev_tx: P2PKH on every chain including the fork-id chains BCH/BSV/BTG (their sighash commits the amount, but every check keys on the script type and the fork-id signer requires the proof too), GRS and PoS-nTime. Only zec is exempt (ZIP-243/244 amounts via zec_prevouts).

  • 2026-10-25: buildPsbtWithMemo defaults to non-RBF (0xffffffff); optional trailing rbf arg.

EVM: Trust Wallet Core signer vectors

crates/tx/tests/vectors/twc-evm.json vendors the 13 signed transactions of Trust Wallet Core's rust/tw_evm/tests/signer.rs (commit adf80f8, Apache-2.0; public test keys): 7 legacy EIP-155 (native, ERC-20 transfer and approve, ERC-20 transfer as a generic contract call, ERC-721 and ERC-1155 transfer, a contract creation on chain id 1) and 6 EIP-1559 (native on chain id 3, ERC-20 transfer, transfer with an access list, approve, ERC-721, ERC-1155).

Measured (Linux only): every one parses, recovers to the address of its key, round-trips, and re-signs byte for byte from its unsigned form with evm::Tx::sign (crates/tx/tests/twc_evm.rs, 7 legacy + 6 EIP-1559); a changed nonce stops it recovering to the signer. scripts/e2e.d/twc-evm.sh runs the legacy native and the EIP-1559 ERC-20 vectors through the real tx-sign CLI (code typed on a pty) and gets Trust Wallet Core's bytes; ERC-1155 safeTransferFrom is refused as unknown_contract_call and ERC-721 transferFrom as token_call_not_transfer, before any key is read.

Refused (no vector here): EIP-2930 type 1 (eip2930_unsupported), EIP-4844 type 3 (eip4844_unsupported), EIP-7702 type 4 (eip7702_unsupported), and Trust Wallet Core's ERC-4337 / Barz user operations. Untested: broadcast on any network, NFT-aware decoding (ERC-721/1155 are shown only as raw or transferFrom calldata), chain ids outside the vectors.

Message signing: Trust Wallet Core message-signer vectors

msg-sign and msg-verify port Trust Wallet Core's EthMessageSigner (rust/tw_evm/tests/message_signer.rs) and BitcoinMessageSigner (tests/chains/Bitcoin/MessageSignerTests.cpp), commit adf80f8, public test keys. Code: crates/tx/src/message.rs. The chain is detected from the address: 0x + 40 hex is Ethereum, a base58 P2PKH address starting with 1 is Bitcoin; --chain must agree.

  • Ethereum: EIP-191 personal_sign, keccak256("\x19Ethereum Signed Message:\n" + decimal length + message). The signature is r||s||v, with v = 27/28 (legacy, the default), 0/1 (raw, Immutable X) or 35 + 2N + rec (eip155:N).
  • Bitcoin: Bitcoin Core signmessage, sha256d(varstr("Bitcoin Signed Message:\n") + varstr(message)), base64 of header||r||s with header 27 + rec (+4 for a compressed key). msg-sign picks compressed or uncompressed from whichever P2PKH address the key matches.

Measured (Linux only): crates/tx/tests/twc_message.rs (2 tests). It signs TWC's 4 Ethereum vectors byte for byte (legacy "Foo", legacy_hex as bytes, eip155 chain 0, Immutable X) and recovers each to its key's address. It signs TWC's 3 Bitcoin vectors byte for byte (compressed "test signature" and "another text", uncompressed "test signature"), recovers all 6 of TWC's Bitcoin verify vectors (one of them, 1B8Qea.., is high-s and is accepted on verify as Bitcoin Core does), matches both messageToHash values, and covers TWC's negatives: wrong address, signature too short, segwit address. scripts/e2e.d/twc-message.sh runs the real CLIs (code typed on a pty), gets TWC's signatures for 2 Bitcoin and 3 Ethereum cases, and checks every refusal below plus the wrong-code abort; a mismatched verify exits 1 with signer_mismatch.

Refused by msg-sign, before any key is read: a bare 32-byte hash (blind_hash_message; TWC signs its legacy_hex vector this way, and msg-verify checks it with --hex, but blocktools will not sign a hash it cannot show); hex or typed-data input (only_text_messages_signed); a message that decodes as a transaction or PSBT (message_looks_like_transaction); non-UTF-8, empty or oversized messages; segwit and taproot addresses (address_not_p2pkh; BIP137 segwit headers 35-42 and BIP322 are unsupported); other chains' message prefixes (message_prefix_unproven_for_chain, e.g. ltc, doge); EIP-712 (blocktools only verifies it, with typed-data-verify). A key that does not match --address gives address_does_not_match_key. Untested: testnet addresses, wallets other than TWC (no vectors from MetaMask, Electrum or Bitcoin Core were run here), and long messages (the multi-byte varint length prefix has no vector).

Cosmos SDK: Trust Wallet Core MsgSend vectors

acct-tx --chain atom builds, signs and decodes Cosmos SDK MsgSend (/cosmos.bank.v1beta1.MsgSend, SIGN_MODE_DIRECT, secp256k1 pubkey). Code: crates/acct/src/atom.rs. The chain is detected from the bech32 hrp of --from (and of the TxRaw on decode); --to must use the same hrp. The hrp picks the base denom and the default --chain-id; a different chain id or denom is a warning, not an error, so testnets still decode.

hrp chain default chain id denom TWC vector (adf80f8)
cosmos Cosmos Hub cosmoshub-4 uatom tests/chains/Cosmos/SignerTests.cpp SignTxProtobuf (gaia-13003)
osmo Osmosis osmosis-1 uosmo Osmosis/SignerTests.cpp SignTransfer_81B4
juno Juno juno-1 ujuno Juno/TWAnySignerTests.cpp Sign
stars Stargaze stargaze-1 ustars Stargaze/TWAnySignerTests.cpp Sign
secret Secret secret-4 uscrt Secret/SignerTests.cpp Sign
terra Terra 2 phoenix-1 uluna TerraV2/SignerTests.cpp SignSendTx
cro Crypto.org crypto-org-chain-mainnet-1 basecro CryptoOrg/TWAnySignerTests.cpp SignTx_Proto_BCB213

Measured (Linux only): crates/acct/tests/twc_cosmos.rs (33 tests; 20 address-only hrps signed against vectors from tools/twc-vectors/gen_cosmos23.rs). Evmos has no published TWC signer vector: its vector was generated by running wallet-core adf80f8's own Rust signer on fixed inputs with a TWC public test key (tools/twc-vectors/, manual workflow twc-vectors.yml on Battleaxe; inputs and run id in the test). Injective (2 vectors: NativeInjective/SignerTests.cpp, TransactionCompilerTests.cpp) and ZetaChain (rust/tw_tests/tests/chains/zetachain/zetachain_sign.rs, athens testnet) sign with ethsecp256k1: keccak256(SignDoc), Injective's AuthInfo carries the 65-byte uncompressed key under /injective.crypto.v1beta1.ethsecp256k1.PubKey, ZetaChain the 33-byte key under /ethermint.crypto.v1.ethsecp256k1.PubKey; a fee with no coin (ZetaChain's vector) round-trips. For each chain it decodes TWC's tx_bytes, verifies the signature, checks the detected chain, re-signs with TWC's public test key and requires the TxRaw to match byte for byte. One test feeds TWC's IBC transfer, MsgVote, Terra CosmWasm and Stargaze CW721 vectors to the decoder and requires each to be refused. scripts/e2e.d/twc-cosmos.sh rebuilds the Osmosis vector from its inputs with the real CLI (keys --hrp osmo, build, sign with the code typed on a pty) and gets TWC's tx_bytes exactly, then checks the refusals below.

Refused: any other hrp (cosmos_hrp_without_vector), which includes Canto (TWC adf80f8 signs Canto as plain Cosmos with a /cosmos.crypto.secp256k1.PubKey and sha256, which does not match its ethsecp256k1 address); a --to on another hrp (wrong network); every message other than MsgSend (IBC, staking, governance, CosmWasm, NFTs); amino-JSON signing (TWC's Crypto.org amino and SignTxJson vectors are not ported); fee and amount in different denoms. Untested: TWC's SignDirect_0a90010a vector (a pre-built body passed through; its auth info carries no pubkey, which the decoder refuses), broadcast, multi-coin amounts, Terra Classic (same terra hrp; columbus-5 gets the chain-id warning but has no vector here), and signing on the 23 address-only Cosmos rows below.

Cosmos SDK address rows (TWC address vectors, no signing)

strd, axl, cre, kuji, cmdx, ntrn, somm, fet, mars, umee, noble, sei, tia, coreum, qsr, xprt, akt, kava, blz, band, bld, dydx and rune (THORChain), plus cro: derive (m/44'/coin'/0'/0/i), validate and auto-detect from the bech32 hrp. Hrp and coin type from TWC adf80f8 registry.json. Measured (Linux only): crates/blocktools/tests/twc_cosmos_addr.rs (4 tests) derives TWC's tests/common/CoinAddressDerivationTests.cpp address for the public 0x46Γ—32 dummy key on all 24, checks the coin type and decimals and that detect returns exactly that chain, and replays 19 pubkey-to-address vectors from tests/chains/Cosmos/<Chain>/TWAnyAddressTests.cpp. acct-tx refuses to sign on these hrps (cosmos_hrp_without_vector) except cro, because TWC has no MsgSend vector for them. Untested: balance lookup, any transaction on these chains.

Ethermint (ethsecp256k1) address rows: inj, evmos, canto, zeta

Injective, Evmos, Canto and ZetaChain native addresses: path m/44'/60'/0'/0/i (the same key as the EVM address), address = bech32(hrp, keccak256(uncompressed pubkey)[12..]), i.e. the EVM address bytes under the chain's hrp. Derive, validate and auto-detect from the hrp; address only. Hrp, path and 18 decimals from TWC adf80f8 registry.json. Measured (Linux only): twc_ethermint_address in crates/blocktools/tests/twc_cosmos_addr.rs derives TWC's CoinAddressDerivationTests.cpp address for the 0x46Γ—32 key on all 4, replays the pubkey-to-address vector from tests/chains/Cosmos/Native{Injective,Evmos,Canto}/TWAnyAddressTests.cpp and rust/tw_tests/tests/chains/zetachain/zetachain_address.rs, and checks detect returns exactly that chain. acct-tx signs MsgSend on inj, zeta and evmos (TWC signer vectors; Evmos TWC-signer-generated, see above) and refuses canto (cosmos_hrp_without_vector). Untested: broadcast on any of them (no Evmos tx has been accepted by a node), balance lookup, any Canto transaction.

Chain quirks and their known-answer vectors (2026-10-25)

Every way a chain departs from plain Bitcoin is listed here with at least one independent known-answer test (KAT): a vector from another implementation, a spec, or real chain data, never only our own output. crates/tx/tests/quirks.rs parses this table and fails if a row has no KAT, names a test that does not exist, or lacks a source, and if a chain with a quirk in code (pos::layout, the zec path, single-SHA-256 GRS digests) has no row. Add the vector with the quirk.

quirk chains KAT (tests file::fn) vector source
groestl-single-sha256-bip143 grs grs.rs::bip143_single_sha256_vectors groestlcoinjs-lib test/fixtures/transaction.json hashForWitnessV0 (data/grs_witness_v0.txt)
groestl-single-sha256-legacy grs grs.rs::mainnet_signatures_verify_under_single_sha256 Groestlcoin mainnet txs, api.blockchair.com raw/transaction (data/grs_mainnet.txt)
pos-ntime-after-version ppc nvc clam pos.rs::mainnet_txids_and_signatures PPC raw hex from Blockbook; NVC/CLAM from chainz.cryptoid.info JSON, txid-checked (data/pos_mainnet.txt)
pos-ntime-after-locktime rdd pos.rs::mainnet_txids_and_signatures RDD mainnet raw hex from Blockbook (data/pos_mainnet.txt)
pos-nvc-json-serialize nvc nvc_json.rs::mainnet_json_reserializes_to_txid api.novaco.in gettransaction JSON, coinstake + regular + PoS coinbase, txid-checked (data/nvc_*.json)
blk-bitcoin-layout blk pos.rs::blackcoin_is_bitcoin_layout Blackcoin mainnet txs (data/pos_mainnet.txt blk rows)
zcash-v5-zip244 zec zcash.rs::zip244_vectors zcash-test-vectors zip_0244.json (data/zec_zip244.txt)
zcash-v4-zip243 zec zcash.rs::zip243_vectors zcash-test-vectors zip_0243.json (data/zec_zip243.txt)
zcash-envelope-mainnet zec zcash.rs::mainnet_txs zec mainnet txs, api.blockchair.com raw/transaction (data/zec_mainnet.txt)
zcash-blake2b-personal zec zcash.rs::blake2b_matches_hashlib Python hashlib.blake2b with person=
p2sh-wrapped-segwit btc ltc sign.rs::bip143_p2sh_p2wpkh_byte_exact BIP143 P2SH-P2WPKH example
p2sh-bare-amount-uncommitted btc ltc sign.rs::bare_p2sh_is_not_amount_committed BIP143 vs legacy sighash definitions (amount in BIP143 preimage only)
evm-eip155-chain-id eth bsc polygon avax kat.rs::eip155_example EIP-155 spec example
qrk-5-decimals-legacy qrk qrk.rs::qrk_mainnet_txs_are_bitcoin_legacy chainz.cryptoid.info tx.raw.dws, txid-checked (data/qrk_mainnet.txt)
qtum-legacy qtum qtum.rs::qtum_mainnet_txs_are_bitcoin_legacy qtum.info api raw-tx (data/qtum_mainnet.txt)
wdc-legacy wdc wdc.rs::wdc_mainnet_txs_are_bitcoin_legacy chainz.cryptoid.info tx.raw.dws (data/wdc_mainnet.txt)

The registry as data

chain-table prints the CHAINS table above as JSON (one object per chain; --array for a single array; chain-table btc for one row): name, ticker, SLIP-44 coin_type, curve, encoding with its version bytes / hrp / SS58 prefix, native_seed, sign (UTXO sighash scheme; account_family means the chain is signed by its own tool, e.g. acct-tx / xrp-tx), buildable (tx-build signs it), verified and the parameter source. The wasm build exports the same array as chainTable().

Trust Wallet Core UTXO forks: PIVX, Syscoin, Firo (address + WIF only)

Rows pivx, sys, firo carry each coin's own chainparams prefixes (PIVX 30/13/WIF 212; Syscoin 63/5/WIF 128, bech32 hrp sys; Firo 82/7/WIF 210). Signing is Unknown: build/sign refuse them; no transaction has been tested on any of these chains.

Measured (Linux only): scripts/e2e.d/twc-utxo.sh gives Trust Wallet Core's published addresses (tests/chains, commit adf80f8) for Syscoin p2pkh and p2wpkh and Firo p2pkh from TWC's public test keys; the KAT table covers all three. Untested: PIVX against an outside vector from a private key (TWC only has a pubkey/mnemonic case), Syscoin's other address types.

Exchange and staking addresses (validate and detect only; no output script is built, so tx-build/verify-tx cannot pay them): Firo EXX... (version 01 b9 bb), PIVX EXM... (01 b9 a2) and PIVX cold-staking S... (version 63), from each coin's src/chainparams.cpp. A PIVX S... address is also a valid Syscoin p2pkh (both use version 63), so detect lists both. Measured (Linux only): crates/blocktools/tests/exchange_addr.rs (3 tests) accepts the Firo wiki's published transparent/EXX pair ("Information for Exchanges", same key), rejects a one-character change and Firo testnet EXT, and accepts PIVX EXM/S addresses built from that hash160 by an independent hashlib base58check. Untested: the PIVX addresses against an address PIVX itself published (none found), sending to any of these address types.

Kusama and Polymesh (SS58 prefixes 2 and 12, address only)

Rows ksm (SLIP-44 434) and polyx (595) use the same substrate key as dot (subkey mini-secret, sr25519) and only change the SS58 prefix. Signing is Unknown; no transaction is built or signed. Trust Wallet Core derives these chains with ed25519 SLIP-10 keys (m/44'/434'/0'/0'/0'), so a TWC wallet's Kusama/Polymesh address is not what these rows give from the same phrase; that is the ed25519 port, not done here.

Measured (Linux only): crates/secrets/tests/dot.rs encodes TWC's published Kusama and Polymesh public keys to TWC's addresses (tests/chains/Kusama, Polymesh, commit adf80f8) and validates them; scripts/e2e.d/twc-substrate.sh checks the CLI rows (its expected Kusama address was computed independently in Python from subkey's published key), and that redact drops a Kusama address on a line labeled another chain. Untested: Polkadot.js/Talisman wallets on these prefixes, balances (no RPC row).

Aptos, Sui, NEAR, Tezos, MultiversX (ed25519 SLIP-0010, address only, 2026-10-26)

Rows aptos (SLIP-44 637, m/44'/637'/i'/0'/0', address 0x+sha3-256(pubβ€–0x00)), sui (784, m/44'/784'/i'/0'/0', 0x+blake2b-256(0x00β€–pub)) near (397, m/44'/397'/i', implicit account = hex(pub)), xtz (1729, m/44'/1729'/i'/0', tz1 = base58check(06a19fβ€–blake2b-160(pub)); tz2/tz3/KT1 validate) and egld (508, m/44'/508'/0'/0'/i', bech32 erd). The secret is the 32-byte ed25519 seed as hex. Signing is Unknown; no transaction is built or signed. A 64-hex address is shape-ambiguous, so detect lists every chain it fits (0x+64 hex: aptos and sui; bare 64 hex: sui and near).

Measured (Linux only): crates/blocktools/tests/twc_ed25519.rs (5 tests) replays Trust Wallet Core adf80f8 public vectors: CoinAddressDerivationTests.cpp dummy key 0x46Γ—32 β†’ TWC's Aptos/Sui/NEAR/Tezos/MultiversX addresses; Tezos and MultiversX AddressTests.cpp key β†’ address and valid/invalid lists; TWHDWalletTests.cpp DeriveTezos public key and DeriveMultiversX key and address at the registry paths; Aptos AddressTests.cpp private/public key β†’ address and its valid/invalid lists; Sui sui_address.rs valid/invalid lists; HDWalletTests.cpp AptosKey and NearKey (public test phrases) β†’ TWC's private keys and NEAR address at the registry paths. Untested: NEAR named accounts (alice.near, refused), Tezos tz2/tz3 key derivation (secp256k1/P-256; validate only), Aptos multi-ed25519 and Sui secp256k1/secp256r1 schemes, any wallet app other than TWC, balances (no RPC row).