Context
dashpay/platform#3981 adds a one-shot transaction-decode FFI (platform_wallet_decode_transaction) to rs-platform-wallet-ffi: consensus-decode raw tx bytes into per-input/per-output C structs with derived addresses, so host apps (Swift today) can reconstruct (address, amount) pairs from stored raw bytes — e.g. to match arbitrary transactions against the CrowdNode on-chain API ("did this tx pay amount X to address Y?").
That function is pure Core-chain functionality: its entire vocabulary is consensus::deserialize, Address::from_script, scriptPubKey classification, and txid byte order. It takes no wallet handle and touches no Platform state. It only lives in the platform repo because that was the crate the feature shipped from. The right home is rust-dashcore, next to the library that owns the types.
Current gap in key-wallet-ffi
key-wallet-ffi already exposes transaction_deserialize(bytes) -> FFITransaction*, but FFITransaction is an opaque handle whose full API is builder/signing-oriented (transaction_create, add_input, add_output, get_txid, serialize, sighash, sign_input, destroy). There are no read accessors and no script→address helper, so an FFI consumer can deserialize a transaction and then cannot look inside it.
Proposal
Port the API from dashpay/platform#3981 (see packages/rs-platform-wallet-ffi/src/tx_decode.rs) into key-wallet-ffi, essentially verbatim:
DecodedTransactionFFI { txid[32], inputs*, inputs_count, outputs*, outputs_count }
DecodedTxInputFFI { prev_txid[32], prev_vout, address* }
DecodedTxOutputFFI { address*, value_duffs, script_pubkey*, script_pubkey_len }
tx_decode(tx_bytes, len, network, out_decoded) -> result — one-shot struct-out call; rejects trailing bytes
decoded_transaction_free(...)
Deliberately not piecemeal accessors on FFITransaction (get_output_count, get_output_at, …): the one-shot struct-out shape is a less chatty, less error-prone FFI surface, and it is what the platform PR already validated.
Notes carried over from the platform implementation:
- Output
address comes from Address::from_script; null for non-standard scripts (OP_RETURN, bare multisig, …).
- Input
address is a best-effort recovery from P2PKH-shaped scriptSigs (exactly two pushes: DER-shaped sig then 33/65-byte pubkey) and is unauthenticated — display/matching hint only, never authorization. Keep the aggressive doc warnings; centralizing this heuristic with its warning label beats having every host app reimplement it.
txid / prev_txid in consensus (internal) byte order; hosts reverse for explorer display.
Migration plan
Once this lands and dashpay/platform bumps its rust-dashcore pin (routine), delete tx_decode.rs from rs-platform-wallet-ffi and re-point the thin Swift wrapper — everything ships in the same xcframework header namespace, so the host-side change is minimal.
The layering rule this establishes: FFI functions live next to the library that owns their types — wallet-less Core-chain utilities in rust-dashcore FFI crates; anything touching a platform-wallet handle or Platform state in rs-platform-wallet-ffi.
Context
dashpay/platform#3981 adds a one-shot transaction-decode FFI (
platform_wallet_decode_transaction) tors-platform-wallet-ffi: consensus-decode raw tx bytes into per-input/per-output C structs with derived addresses, so host apps (Swift today) can reconstruct(address, amount)pairs from stored raw bytes — e.g. to match arbitrary transactions against the CrowdNode on-chain API ("did this tx pay amount X to address Y?").That function is pure Core-chain functionality: its entire vocabulary is
consensus::deserialize,Address::from_script, scriptPubKey classification, and txid byte order. It takes no wallet handle and touches no Platform state. It only lives in the platform repo because that was the crate the feature shipped from. The right home is rust-dashcore, next to the library that owns the types.Current gap in key-wallet-ffi
key-wallet-ffialready exposestransaction_deserialize(bytes) -> FFITransaction*, butFFITransactionis an opaque handle whose full API is builder/signing-oriented (transaction_create,add_input,add_output,get_txid,serialize,sighash,sign_input,destroy). There are no read accessors and no script→address helper, so an FFI consumer can deserialize a transaction and then cannot look inside it.Proposal
Port the API from dashpay/platform#3981 (see
packages/rs-platform-wallet-ffi/src/tx_decode.rs) intokey-wallet-ffi, essentially verbatim:DecodedTransactionFFI { txid[32], inputs*, inputs_count, outputs*, outputs_count }DecodedTxInputFFI { prev_txid[32], prev_vout, address* }DecodedTxOutputFFI { address*, value_duffs, script_pubkey*, script_pubkey_len }tx_decode(tx_bytes, len, network, out_decoded) -> result— one-shot struct-out call; rejects trailing bytesdecoded_transaction_free(...)Deliberately not piecemeal accessors on
FFITransaction(get_output_count,get_output_at, …): the one-shot struct-out shape is a less chatty, less error-prone FFI surface, and it is what the platform PR already validated.Notes carried over from the platform implementation:
addresscomes fromAddress::from_script; null for non-standard scripts (OP_RETURN, bare multisig, …).addressis a best-effort recovery from P2PKH-shaped scriptSigs (exactly two pushes: DER-shaped sig then 33/65-byte pubkey) and is unauthenticated — display/matching hint only, never authorization. Keep the aggressive doc warnings; centralizing this heuristic with its warning label beats having every host app reimplement it.txid/prev_txidin consensus (internal) byte order; hosts reverse for explorer display.Migration plan
Once this lands and dashpay/platform bumps its rust-dashcore pin (routine), delete
tx_decode.rsfromrs-platform-wallet-ffiand re-point the thin Swift wrapper — everything ships in the same xcframework header namespace, so the host-side change is minimal.The layering rule this establishes: FFI functions live next to the library that owns their types — wallet-less Core-chain utilities in rust-dashcore FFI crates; anything touching a platform-wallet handle or Platform state in
rs-platform-wallet-ffi.