Documentation
¶
Overview ¶
Package btc implements the Bitcoin custody vault adapter. The vault is a native SegWit P2WSH (BIP 141) address whose redeem script is an m-of-n OP_CHECKMULTISIG over the providers' secp256k1 public keys — no smart contract. See custody docs/btc_spec.md.
This file holds the chain primitives that are a pure function of their inputs: redeem-script construction, vault address derivation, the deterministic unsigned-transaction builder, BIP-143 sighash computation, and witness assembly. They carry no daemon state and no network access, so every provider that observes the same authorized withdrawal and the same UTXO set builds a byte-identical transaction — the precondition for non-interactive multisig signing.
Index ¶
- Variables
- func AccountTag(accountURI string) []byte
- func AssembleWitness(redeemScript []byte, orderedSigs [][]byte) wire.TxWitness
- func BuildUnsignedTx(utxos []UTXO, recipient btcutil.Address, amount int64, vault btcutil.Address, ...) (*wire.MsgTx, error)
- func DepositAddress(accountURI string, threshold int, pubkeys [][]byte, net *chaincfg.Params) (btcutil.Address, []byte, error)
- func EstimateFeeSats(numInputs, numOutputs int, satPerVByte int64) int64
- func PkScript(addr btcutil.Address) ([]byte, error)
- func RedeemScript(threshold int, pubkeys [][]byte) ([]byte, error)
- func SighashAll(tx *wire.MsgTx, inputIdx int, redeemScript []byte, amount int64, ...) ([]byte, error)
- func TaggedRedeemScript(tag []byte, threshold int, pubkeys [][]byte) ([]byte, error)
- func VaultAddress(redeemScript []byte, net *chaincfg.Params) (btcutil.Address, error)
- type AssetResolver
- type Client
- func (c *Client) EstimateSmartFeeSatPerVByte(ctx context.Context, confTarget int, fallbackRate int64) (int64, error)
- func (c *Client) GetBlockCount(ctx context.Context) (int64, error)
- func (c *Client) GetBlockHash(ctx context.Context, height int64) (string, error)
- func (c *Client) GetBlockTxids(ctx context.Context, blockHash string) ([]string, error)
- func (c *Client) GetRawTransaction(ctx context.Context, txid string) (*RawTx, error)
- func (c *Client) GetTxOut(ctx context.Context, txid string, vout uint32, includeMempool bool) (*TxOut, error)
- func (c *Client) ListUnspent(ctx context.Context, minConf int, addrs []string) ([]Unspent, error)
- func (c *Client) SendRawTransaction(ctx context.Context, hexTx string) (string, error)
- type Config
- type ConsolidationFinalizer
- func (f *ConsolidationFinalizer) Due(ctx context.Context, target int, feeCeilingSatVb int64) (bool, error)
- func (f *ConsolidationFinalizer) OwnedUTXOCount(ctx context.Context) (int, error)
- func (f *ConsolidationFinalizer) Pack(ctx context.Context, consolidationID [32]byte) ([]byte, error)
- func (f *ConsolidationFinalizer) Sign(ctx context.Context, packed []byte) ([]byte, error)
- func (f *ConsolidationFinalizer) Submit(ctx context.Context, packed []byte, shares [][]byte) (string, error)
- func (f *ConsolidationFinalizer) Validate(ctx context.Context, consolidationID [32]byte, packed []byte) error
- type Depositor
- type Option
- type RPC
- type RPCError
- type RawTx
- type RawVout
- type RotationFinalizer
- func (f *RotationFinalizer) Pack(ctx context.Context, opID [32]byte, newSigners []string, newThreshold int) ([]byte, error)
- func (f *RotationFinalizer) Sign(ctx context.Context, packed []byte) ([]byte, error)
- func (f *RotationFinalizer) Submit(ctx context.Context, packed []byte, shares [][]byte) (string, error)
- func (f *RotationFinalizer) Validate(ctx context.Context, opID [32]byte, packed []byte, newSigners []string, ...) error
- func (f *RotationFinalizer) VerifyRotation(ctx context.Context, newSigners []string, newThreshold int) (string, bool, error)
- type SigShare
- type TxOut
- type UTXO
- type Unspent
- type VaultStore
- type WithdrawalFinalizer
- func (f *WithdrawalFinalizer) Pack(ctx context.Context, op *core.WithdrawalOp, withdrawalID [32]byte, ...) ([]byte, error)
- func (f *WithdrawalFinalizer) RegisterDepositAccounts(accountURIs ...string) error
- func (f *WithdrawalFinalizer) Sign(ctx context.Context, packed []byte) ([]byte, error)
- func (f *WithdrawalFinalizer) Submit(ctx context.Context, packed []byte, shares [][]byte) (string, error)
- func (f *WithdrawalFinalizer) Validate(ctx context.Context, packed []byte, op *core.WithdrawalOp, ...) error
- func (f *WithdrawalFinalizer) VerifyExecution(ctx context.Context, withdrawalID [32]byte) (string, bool, error)
Constants ¶
This section is empty.
Variables ¶
var ErrTooFragmented = errors.New("btc: utxo set too fragmented for a standard-size tx")
ErrTooFragmented reports that covering a withdrawal would require more inputs than the configured maxInputs bound — i.e. the vault's UTXO set is too fragmented to build a standard-size transaction. Callers detect it (via errors.Is) to trigger a consolidation fold and retry the withdrawal.
Functions ¶
func AccountTag ¶
AccountTag derives the per-account script tag from a clearnet account URI: the 32-byte SHA256 of the URI.
func AssembleWitness ¶
AssembleWitness builds the witness stack for a P2WSH multisig input:
[ <empty>, sig_1, ..., sig_m, redeemScript ]
The leading empty element is the OP_CHECKMULTISIG off-by-one workaround. Each signature is DER-encoded with the SIGHASH_ALL type byte already appended, and the slice MUST already be ordered to match the position of the corresponding public key in the redeem script.
func BuildUnsignedTx ¶
func BuildUnsignedTx( utxos []UTXO, recipient btcutil.Address, amount int64, vault btcutil.Address, withdrawalID [32]byte, feeSats int64, ) (*wire.MsgTx, error)
BuildUnsignedTx constructs the canonical unsigned withdrawal transaction. Inputs are the selected vault UTXOs (sorted BIP-69) so construction is order-independent. Outputs are emitted in a fixed canonical order: recipient, then change back to the vault (omitted if below dust), then a zero-value OP_RETURN carrying the 32-byte clearnet WithdrawalID.
func DepositAddress ¶
func DepositAddress(accountURI string, threshold int, pubkeys [][]byte, net *chaincfg.Params) (btcutil.Address, []byte, error)
DepositAddress derives the per-account deposit P2WSH address and its witness script. Watch-only: a pure function of accountURI plus the base pubkeys, no private keys involved.
func EstimateFeeSats ¶
EstimateFeeSats returns the fee for a transaction with numInputs P2WSH inputs and numOutputs outputs at the given rate.
func RedeemScript ¶
RedeemScript builds the m-of-n OP_CHECKMULTISIG redeem script over the given compressed secp256k1 public keys. Keys are sorted lexicographically (BIP 67) before assembly so the script — and therefore the vault address — is independent of the order in which keys were supplied.
func SighashAll ¶
func SighashAll( tx *wire.MsgTx, inputIdx int, redeemScript []byte, amount int64, prevFetcher txscript.PrevOutputFetcher, ) ([]byte, error)
SighashAll computes the BIP-143 SIGHASH_ALL digest for one input.
func TaggedRedeemScript ¶
TaggedRedeemScript prefixes the m-of-n redeem script with `<tag> OP_DROP`, yielding a distinct witness script — and therefore a distinct P2WSH address — per tag while leaving the signing semantics identical: OP_DROP discards the tag before OP_CHECKMULTISIG, so the same keys sign the same way with no key derivation. This is how per-account deposit addresses are derived (tag = AccountTag(accountURI)); every deposit address is full m-of-n custody.
Types ¶
type AssetResolver ¶ added in v0.5.0
type AssetResolver struct{}
func NewAssetResolver ¶ added in v0.5.0
func NewAssetResolver() AssetResolver
func (AssetResolver) AssetDecimals ¶ added in v0.5.0
func (AssetResolver) ValidateAssetAddress ¶ added in v0.5.0
func (AssetResolver) ValidateAssetAddress(_ context.Context, assetAddress string) error
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a concrete bitcoind JSON-RPC client implementing RPC. Wallet-scoped calls (ListUnspent) route to /wallet/<wallet>; the rest hit the node root. It carries only the read + broadcast surface the adapters need — wallet provisioning (createwallet, mining, funding) is deliberately out of scope.
func NewClient ¶
NewClient builds a bitcoind RPC client at url with basic-auth user/pass. wallet is the wallet name wallet-scoped RPCs route to (may be empty if the node has a single default wallet loaded).
func (*Client) EstimateSmartFeeSatPerVByte ¶
func (c *Client) EstimateSmartFeeSatPerVByte(ctx context.Context, confTarget int, fallbackRate int64) (int64, error)
EstimateSmartFeeSatPerVByte returns the node's fee estimate for confTarget blocks, falling back to fallbackRate when the node cannot estimate (e.g. on regtest).
func (*Client) GetBlockCount ¶
GetBlockCount returns the height of the most-work fully-validated chain.
func (*Client) GetBlockHash ¶
GetBlockHash returns the block hash at the given height.
func (*Client) GetBlockTxids ¶
GetBlockTxids returns the txids in the block (verbosity 1).
func (*Client) GetRawTransaction ¶
GetRawTransaction returns the decoded transaction (verbose=true). Requires the node to find the tx: txindex=1, or the tx unspent/in mempool.
func (*Client) GetTxOut ¶
func (c *Client) GetTxOut(ctx context.Context, txid string, vout uint32, includeMempool bool) (*TxOut, error)
GetTxOut returns the unspent output txid:vout, or nil if it is spent/unknown.
func (*Client) ListUnspent ¶
ListUnspent returns the vault UTXOs at addrs with at least minConf confirmations.
type Config ¶
type Config struct {
ConfirmationDepth uint64 // min confirmations for a vault UTXO to be spendable
FeeConfTarget int // estimatesmartfee confirmation target (blocks)
FallbackFeeRate int64 // sat/vByte used when the node can't estimate
FeeCapSatPerVByte int64 // ceiling Validate accepts on a canonical tx
// MaxInputsPerWithdrawal bounds how many inputs a withdrawal may select
// before SelectUTXOs returns ErrTooFragmented (0 = unbounded). A fragmented
// vault that hits this should consolidate (see ConsolidationFinalizer) and
// retry.
MaxInputsPerWithdrawal int
// ConsolidationBatchMax bounds how many of the vault's smallest UTXOs one
// consolidation fold spends (0 => a sane default well under the standard-tx
// input ceiling).
ConsolidationBatchMax int
}
Config carries the per-chain tunables the finalizer needs.
type ConsolidationFinalizer ¶ added in v0.2.0
type ConsolidationFinalizer struct {
// contains filtered or unexported fields
}
ConsolidationFinalizer folds a bounded batch of the vault's smallest UTXOs back into a single base-vault output, shrinking the UTXO count so withdrawals keep fitting a standard-size tx (the counterpart to SelectUTXOs' maxInputs bound / ErrTooFragmented).
It is mechanically a withdrawal-to-self: same vault, current keys, no pivot. Unlike the rotation sweep it is partial by design (a bounded batch, not the full set), so it carries no completeness rule. Pack/Validate are the fold-specific mechanics; Sign/Submit reuse the current-vault withdrawal machinery, exactly as RotationFinalizer does.
func NewConsolidationFinalizer ¶ added in v0.2.0
func NewConsolidationFinalizer(net *chaincfg.Params, rpc RPC, signer sign.Signer, store VaultStore, cfg Config, assets blockchain.AssetResolver, accountURIs ...string) (*ConsolidationFinalizer, error)
NewConsolidationFinalizer builds the BTC consolidation finalizer. signer is this node's vault key; store supplies the current vault (consolidation never pivots, so Pivot is unused). accountURIs are the per-account deposit accounts whose tagged-address UTXOs are eligible to fold alongside the base vault.
func (*ConsolidationFinalizer) Due ¶ added in v0.2.0
func (f *ConsolidationFinalizer) Due(ctx context.Context, target int, feeCeilingSatVb int64) (bool, error)
Due reports whether a periodic fold should run now: the owned UTXO count exceeds target AND the current fee rate is at or below feeCeilingSatVb (a low-fee window). feeCeilingSatVb <= 0 disables the fee gate.
func (*ConsolidationFinalizer) OwnedUTXOCount ¶ added in v0.2.0
func (f *ConsolidationFinalizer) OwnedUTXOCount(ctx context.Context) (int, error)
OwnedUTXOCount reports the number of currently-spendable owned UTXOs. The consolidation trigger uses it to decide whether a fold is warranted.
func (*ConsolidationFinalizer) Pack ¶ added in v0.2.0
func (f *ConsolidationFinalizer) Pack(ctx context.Context, consolidationID [32]byte) ([]byte, error)
Pack selects the vault's smallest spendable UTXOs (up to the batch max) and folds them into a single base-vault output minus fee, with consolidationID in an OP_RETURN. Smallest-first keeps the large coins intact for largest-first withdrawal selection and shrinks the count fastest. The change computes to zero (recipient == vault), so the result is the two-output form [baseVault(total-fee), OP_RETURN(consolidationID)].
func (*ConsolidationFinalizer) Sign ¶ added in v0.2.0
Sign produces this node's per-input signatures over the fold, delegating to the current-vault signing machinery (the fold spends base-vault and deposit inputs under the current redeem scripts, exactly as a withdrawal).
func (*ConsolidationFinalizer) Submit ¶ added in v0.2.0
func (f *ConsolidationFinalizer) Submit(ctx context.Context, packed []byte, shares [][]byte) (string, error)
Submit assembles the witnesses from the collected shares and broadcasts the fold, returning its hash. Idempotent on an already-known/spent reply (the UTXO-model analogue of a re-submit guard).
func (*ConsolidationFinalizer) Validate ¶ added in v0.2.0
func (f *ConsolidationFinalizer) Validate(ctx context.Context, consolidationID [32]byte, packed []byte) error
Validate is the follower-side trust boundary for a fold. A vault→vault fold cannot move funds out of custody, so validation is deliberately lenient (no completeness rule, no byte-identical build): exactly two outputs, output 0 paying the base vault, output 1 an OP_RETURN(consolidationID); every input a confirmed owned UTXO (nothing foreign dragged in); the batch within the size bound; and the implied fee within the griefing ceiling.
type Depositor ¶
type Depositor struct {
// contains filtered or unexported fields
}
Depositor funds a per-account deposit address from the depositor's own P2WPKH wallet (the key the supplied sign.Signer holds). It implements core.VaultDepositor. The deposit address is derived from the vault's pubkeys + threshold (the same address the withdrawal finalizer can later spend).
func NewDepositor ¶
func NewDepositor(net *chaincfg.Params, rpc RPC, signer sign.Signer, vaultPubkeys [][]byte, threshold int, cfg Config, assets blockchain.AssetResolver) (*Depositor, error)
NewDepositor builds the BTC depositor. signer is the depositor's secp256k1 key; vaultPubkeys + threshold define the vault whose per-account deposit addresses funds are sent to.
func (*Depositor) DepositorAddress ¶
DepositorAddress returns the depositor's own P2WPKH funding address.
func (*Depositor) SubmitDeposit ¶
func (d *Depositor) SubmitDeposit(ctx context.Context, assetAddress string, amount decimal.Decimal, dest core.DepositDestination) (string, error)
SubmitDeposit sends amount from the depositor's wallet to the per-account deposit address for dest.Account. assetAddress must be "" for native BTC. Builds, signs (P2WPKH), and broadcasts the funding tx. A non-zero dest.Ref is rejected: the account is encoded in the deposit address and a plain BTC send has no side-data channel for a sub-account (ADR-015 has no BTC reference).
func (*Depositor) VerifyDeposit ¶
func (d *Depositor) VerifyDeposit(ctx context.Context, txID string, minConf uint64) (core.DepositStatus, error)
VerifyDeposit reports the on-chain status of the deposit txID. Requires the node to resolve the tx (txindex=1, or the tx unspent / in the mempool). A tx the node has never seen — or one reorged out and dropped — reads as DepositAbsent; a mempool tx (0 confs) is DepositPending until it is mined with at least max(1, minConf) confirmations (a deposit is only Confirmed once on chain, consistent with the other chains).
type Option ¶
type Option func(*Client)
Option configures a Client.
func WithHTTPClient ¶
WithHTTPClient overrides the default *http.Client (30s timeout).
type RPC ¶
type RPC interface {
ListUnspent(ctx context.Context, minConf int, addrs []string) ([]Unspent, error)
GetTxOut(ctx context.Context, txid string, vout uint32, includeMempool bool) (*TxOut, error)
SendRawTransaction(ctx context.Context, hexTx string) (string, error)
EstimateSmartFeeSatPerVByte(ctx context.Context, confTarget int, fallbackRate int64) (int64, error)
// For VerifyExecution: scan recent blocks for the OP_RETURN <withdrawalID>.
GetBlockCount(ctx context.Context) (int64, error)
GetBlockHash(ctx context.Context, height int64) (string, error)
GetBlockTxids(ctx context.Context, blockHash string) ([]string, error)
GetRawTransaction(ctx context.Context, txid string) (*RawTx, error)
}
RPC is the bitcoind RPC surface the adapters depend on. It is supplied by the caller (mirroring how the EVM adapters take a caller-supplied *ethclient.Client), so the SDK carries no JSON-RPC client of its own. The block/raw-tx methods back the withdrawal-execution scan.
type RPCError ¶
RPCError is a typed bitcoind JSON-RPC error response. The Code lets callers branch on outcome (e.g. already-in-chain) without string matching.
type RotationFinalizer ¶
type RotationFinalizer struct {
// contains filtered or unexported fields
}
RotationFinalizer rotates a BTC P2WSH vault by sweeping every old-vault UTXO into the vault derived from the new signer set. It implements core.SignerRotationFinalizer. The signing/merge/UTXO machinery is the withdrawal path (it is mechanically a withdrawal whose inputs are all old-vault UTXOs and whose single output is the new vault); the extra pieces are the sweep build and the post-confirmation pivot via the VaultStore.
func NewRotationFinalizer ¶
func NewRotationFinalizer(net *chaincfg.Params, rpc RPC, signer sign.Signer, store VaultStore, cfg Config, assets blockchain.AssetResolver, accountURIs ...string) (*RotationFinalizer, error)
NewRotationFinalizer builds the BTC rotation finalizer. signer is this node's vault key; store supplies the current vault and receives the pivot. accountURIs are the per-account deposit accounts whose tagged-address UTXOs must be included in the sweep (the base vault is always swept) — undeclared accounts' UTXOs would be stranded under the old vault.
func (*RotationFinalizer) Pack ¶
func (f *RotationFinalizer) Pack(ctx context.Context, opID [32]byte, newSigners []string, newThreshold int) ([]byte, error)
Pack lists every current-vault UTXO and builds the unsigned sweep: all of them as inputs, output 0 paying the new vault the total minus fee, and a final zero-value OP_RETURN carrying opID so an external watcher can attribute the landed sweep to this rotation (and pivot the vault).
func (*RotationFinalizer) Sign ¶
Sign produces this node's per-input signatures over the sweep, delegating to the current-vault signing machinery.
func (*RotationFinalizer) Submit ¶
func (f *RotationFinalizer) Submit(ctx context.Context, packed []byte, shares [][]byte) (string, error)
Submit assembles the witnesses from the collected shares and broadcasts the sweep. Idempotent by the UTXO model: if the sweep already landed, its inputs are spent and the rebroadcast is rejected as already-known/missing-inputs, in which case the original txID is returned.
func (*RotationFinalizer) Validate ¶
func (f *RotationFinalizer) Validate(ctx context.Context, opID [32]byte, packed []byte, newSigners []string, newThreshold int) error
Validate re-derives the new vault and asserts the packed sweep pays exactly it from output 0, carries the OP_RETURN(opID) marker as its final output, consumes only current-vault UTXOs, and keeps the implied fee within the ceiling.
func (*RotationFinalizer) VerifyRotation ¶
func (f *RotationFinalizer) VerifyRotation(ctx context.Context, newSigners []string, newThreshold int) (string, bool, error)
VerifyRotation reports whether the sweep landed — the new vault holds at least one confirmed UTXO — and, when so, pivots the store to the new vault. Binary; the sweep txID is not recovered here, so an empty txID is returned with done=true. Note: a vault with nothing to sweep cannot be observed as rotated.
type SigShare ¶
type SigShare struct {
}
SigShare is one signer's contribution: a DER+sighash signature per input, in input order, plus the signer's 33-byte compressed pubkey (hex).
type UTXO ¶
UTXO is a spendable vault output.
func SelectUTXOs ¶
func SelectUTXOs(available []UTXO, amount int64, satPerVByte int64, numFixedOutputs, maxInputs int) (selected []UTXO, feeSats int64, err error)
SelectUTXOs deterministically chooses inputs to cover amount plus the fee the resulting transaction will pay. UTXOs are sorted by (amount desc, txid, vout) and accumulated greedily until they cover amount + fee, where the fee grows with each added input. numFixedOutputs is the count of always-present outputs (recipient + OP_RETURN = 2); a change output is assumed for fee sizing.
maxInputs bounds the input count so the resulting tx stays within Bitcoin's standard-size relay limit. When covering the amount would need more than maxInputs inputs, it returns ErrTooFragmented (the caller then consolidates and retries). maxInputs <= 0 means unbounded.
type Unspent ¶
type Unspent struct {
TxID string
Vout uint32
AmountSats int64
Confirmations int64
ScriptPubKey string
}
Unspent is a vault UTXO as reported by ListUnspent.
type VaultStore ¶
type VaultStore interface {
Current(ctx context.Context) (pubkeys [][]byte, threshold int, err error)
Pivot(ctx context.Context, pubkeys [][]byte, threshold int) error
}
VaultStore is the seam that lets BTC rotation fit the in-place SignerRotationFinalizer interface. A P2WSH vault's address is a function of its signer set, so rotation is a sweep into a newly-derived vault, after which the daemon must pivot to that vault. Current supplies the active vault (the source of UTXOs to sweep and the set that authorizes the sweep); Pivot adopts the new vault once the sweep confirms.
Pivot must be idempotent: it is called by every node from VerifyRotation when the sweep is observed, and re-adopting the already-current vault is a no-op.
type WithdrawalFinalizer ¶
type WithdrawalFinalizer struct {
// contains filtered or unexported fields
}
WithdrawalFinalizer is the Bitcoin m-of-n P2WSH vault withdrawal path. It owns this node's signer (one of the vault keys); the quorum's shares are merged off-mesh by the caller. It implements core.VaultWithdrawalFinalizer.
func NewWithdrawalFinalizer ¶
func NewWithdrawalFinalizer(net *chaincfg.Params, rpc RPC, signer sign.Signer, pubkeys [][]byte, threshold int, cfg Config, assets blockchain.AssetResolver) (*WithdrawalFinalizer, error)
NewWithdrawalFinalizer builds the vault finalizer. pubkeys are the providers' 33-byte compressed keys; signer is this node's identity and its public key must be one of pubkeys.
func (*WithdrawalFinalizer) Pack ¶
func (f *WithdrawalFinalizer) Pack(ctx context.Context, op *core.WithdrawalOp, withdrawalID [32]byte, deadline int64) ([]byte, error)
Pack selects vault UTXOs, sizes the fee, and builds the canonical unsigned transaction (recipient, optional change, OP_RETURN <withdrawalID>).
deadline is accepted for interface symmetry but deliberately ignored: a Bitcoin transaction has no consensus-level expiry, so the digest carries no time bound. BTC's authorization lifetime is instead governed by the UTXO set (the inputs stop existing once spent) and, for the re-credit path, by a receipt-gated Expired ceremony that requires no local signature share ever existed — see the adapter's AuthorizationDead.
func (*WithdrawalFinalizer) RegisterDepositAccounts ¶
func (f *WithdrawalFinalizer) RegisterDepositAccounts(accountURIs ...string) error
RegisterDepositAccounts adds the tagged deposit addresses for the given account URIs to the spendable set, so withdrawals can select and sign UTXOs that landed at per-account deposit addresses.
func (*WithdrawalFinalizer) Sign ¶
Sign produces this node's signature over every input of the packed tx, each under its own witness script. Returns a JSON SigShare.
func (*WithdrawalFinalizer) Submit ¶
func (f *WithdrawalFinalizer) Submit(ctx context.Context, packed []byte, shares [][]byte) (string, error)
Submit assembles the witnesses from the collected shares and broadcasts the signed tx, returning its hash. Idempotent on an already-known/spent reply.
func (*WithdrawalFinalizer) Validate ¶
func (f *WithdrawalFinalizer) Validate(ctx context.Context, packed []byte, op *core.WithdrawalOp, withdrawalID [32]byte, deadline int64) error
Validate re-derives the trust-bound shape from the op and asserts the packed tx matches: output 0 pays the exact recipient/amount, the final output is OP_RETURN <withdrawalID>, any middle output is change to the vault, every input is a confirmed vault UTXO, and the implied fee is within the ceiling.
func (*WithdrawalFinalizer) VerifyExecution ¶
func (f *WithdrawalFinalizer) VerifyExecution(ctx context.Context, withdrawalID [32]byte) (string, bool, error)
VerifyExecution scans the most recent blocks for a tx carrying OP_RETURN <withdrawalID>. Returns the txID + true on a hit. Bounded by executionScanBlocks; a withdrawal older than that window reads as not-found.