STAS3 transaction factories.
Pure functions that build complete, signed transactions for STAS 3 token operations: two-tx issuance, base spend, freeze, unfreeze, and swap.
Summary
Functions
Build the txType=1 atomic-swap trailing-parameter block per spec §9.5.
Build the txType=2..7 merge trailing-parameter block per spec §8.1.
Build a generic STAS3 spend transaction.
Build a STAS3 confiscation transaction.
Build a STAS3 freeze transaction.
Build the two-transaction STAS3 issuance flow.
Build a STAS3 merge transaction.
Build a STAS3 redeem transaction.
Build a STAS3 split transaction.
Build a STAS3 swap-cancellation transaction (spec §9.4).
Build a STAS3 swap flow transaction with auto-detected mode.
Build a STAS3 swap-swap transaction.
Build a STAS3 swap-swap transaction with the spec §9.5 trailing
<counterparty_script> <piece_count> <piece_array> block auto-wired
into BOTH inputs' unlocking scripts.
Build a STAS3 transfer-swap transaction.
Build a STAS3 unfreeze transaction.
Parse a previously-encoded trailing parameter block.
Detect whether a swap is transfer-swap or swap-swap based on input locking scripts.
Types
@type base_config() :: %{ token_inputs: [BSV.Tokens.TokenInput.t()], fee_txid: binary(), fee_vout: non_neg_integer(), fee_satoshis: non_neg_integer(), fee_locking_script: BSV.Script.t(), fee_private_key: BSV.PrivateKey.t(), destinations: [BSV.Tokens.Stas3OutputParams.t()], spend_type: BSV.Tokens.Stas3SpendType.t(), fee_rate: non_neg_integer() }
@type issue_config() :: %{ scheme: BSV.Tokens.Scheme.t(), funding_txid: binary(), funding_vout: non_neg_integer(), funding_satoshis: non_neg_integer(), funding_locking_script: BSV.Script.t(), funding_private_key: BSV.PrivateKey.t() | nil, funding_key: BSV.Tokens.SigningKey.t() | nil, outputs: [ %{satoshis: non_neg_integer(), owner_pkh: <<_::160>>, freezable: boolean()} ], fee_rate: non_neg_integer() }
Functions
@spec build_atomic_swap_trailing(binary(), binary(), [non_neg_integer()]) :: {:ok, binary()} | {:error, term()}
Build the txType=1 atomic-swap trailing-parameter block per spec §9.5.
Wraps BSV.Tokens.Script.Stas3Pieces.encode_atomic_swap_pieces/3.
Returns the raw bytes:
counterparty_script_push ‖ piece_count_byte ‖ piece_arrayAppend this binary to a STAS 3.0 swap unlocking script (after the authz block) when constructing a txType=1 transaction.
@spec build_merge_trailing(2..7, binary(), [non_neg_integer()]) :: {:ok, binary()} | {:error, term()}
Build the txType=2..7 merge trailing-parameter block per spec §8.1.
Wraps BSV.Tokens.Script.Stas3Pieces.encode_merge_pieces/3.
piece_count MUST be in 2..7 and equal length(asset_output_indices).
@spec build_stas3_base_tx(base_config()) :: {:ok, BSV.Transaction.t()} | {:error, term()}
Build a generic STAS3 spend transaction.
@spec build_stas3_confiscate_tx(base_config()) :: {:ok, BSV.Transaction.t()} | {:error, term()}
Build a STAS3 confiscation transaction.
Per spec §9.3 (enforced before signing via
BSV.Tokens.Stas3.Validate.confiscation/1):
- the input's
flagsfield has the CONFISCATABLE bit set, - frozen inputs CAN be confiscated (spec §9.6: confiscation has the highest precedence and is permitted to override freeze).
Returns {:error, :confiscate_flag_not_set} if the flag is missing.
Spec §9.6 precedence (informative — independent factories choose intent): Confiscation > Freeze > Swap > Regular spend.
@spec build_stas3_freeze_tx(base_config()) :: {:ok, BSV.Transaction.t()} | {:error, term()}
Build a STAS3 freeze transaction.
Per spec §9.2 (enforced before signing via BSV.Tokens.Stas3.Validate.freeze/2):
- exactly one destination (a single STAS output),
owner_pkhandredemption_pkhbyte-identical to the input,- the input's
flagsfield has the FREEZABLE bit set.
Returns {:error, :freeze_output_count}, {:error, :freeze_field_drift},
or {:error, :freeze_flag_not_set} on validation failure.
@spec build_stas3_issue_txs(issue_config()) :: {:ok, %{contract_tx: BSV.Transaction.t(), issue_tx: BSV.Transaction.t()}} | {:error, term()}
Build the two-transaction STAS3 issuance flow.
@spec build_stas3_merge_tx(base_config()) :: {:ok, BSV.Transaction.t()} | {:error, term()}
Build a STAS3 merge transaction.
Merges exactly 2 STAS inputs into 1-2 STAS outputs. This is a semantic wrapper
around build_stas3_base_tx/1 that enforces merge-specific constraints.
Parameters
config- Abase_config()map with::token_inputs- Exactly 2 token inputs:destinations- 1-2 STAS3 output destinations- Other fields as per
base_config()
Returns
{:ok, transaction}on success{:error, reason}on validation failure
@spec build_stas3_redeem_tx(map()) :: {:ok, BSV.Transaction.t()} | {:error, term()}
Build a STAS3 redeem transaction.
Redeems STAS tokens back to regular P2PKH satoshis. Only the issuer can redeem.
This is NOT a wrapper around build_stas3_base_tx/1 because the primary output
is P2PKH rather than STAS3.
Parameters
config- A map with::token_input- A singleTokenInput(the STAS UTXO to redeem):fee_txid,:fee_vout,:fee_satoshis,:fee_locking_script,:fee_private_key- Funding input for fees:redeem_satoshis- Amount to redeem as P2PKH output:redeem_pkh- The 20-byte pubkey hash for the P2PKH redeem output:remaining_destinations- Optional list ofStas3OutputParamsfor remaining STAS outputs (default[]):fee_rate- Fee rate in sat/KB
Rules
- Token input owner must be the issuer (owner_pkh == redemption_pkh from script)
- Frozen inputs cannot be redeemed
- Conservation: stas_in == redeem_satoshis + sum(remaining STAS outputs)
- Uses spending type 1 (regular)
Returns
{:ok, transaction}on success{:error, reason}on validation failure
@spec build_stas3_split_tx(base_config()) :: {:ok, BSV.Transaction.t()} | {:error, term()}
Build a STAS3 split transaction.
Splits a single STAS input into 1-4 STAS outputs. This is a semantic wrapper
around build_stas3_base_tx/1 that enforces split-specific constraints:
exactly 1 STAS input and 1-4 destinations.
Parameters
config- Abase_config()map with::token_inputs- Exactly 1 token input:destinations- 1-4 STAS3 output destinations- Other fields as per
base_config()
Returns
{:ok, transaction}on success{:error, reason}on validation failure
@spec build_stas3_swap_cancel_tx(base_config()) :: {:ok, BSV.Transaction.t()} | {:error, term()}
Build a STAS3 swap-cancellation transaction (spec §9.4).
Cancels a standing swap offer by spending the maker's swap-descriptor
UTXO back to its var2.receiveAddr owner. Constraints (enforced via
BSV.Tokens.Stas3.Validate.swap_cancel/2):
- exactly 1 token input whose
var2is a swap descriptor (action 0x01), - exactly 1 destination,
- destination's
owner_pkhequals the input'svar2.receiveAddr.
The unlocking script uses spendType = 4. Authorisation against
receiveAddr is enforced by the engine at spend time.
Returns {:error, :swap_cancel_missing_descriptor},
{:error, :swap_cancel_output_count}, or
{:error, :swap_cancel_owner_mismatch} on validation failure.
@spec build_stas3_swap_flow_tx(base_config()) :: {:ok, BSV.Transaction.t()} | {:error, term()}
Build a STAS3 swap flow transaction with auto-detected mode.
Reads each input's locking script to detect swap action data:
- Both inputs have swap action data → swap-swap (spending type 4)
- Otherwise → transfer-swap (spending type 1)
Parameters
config- Abase_config()map with exactly 2 token inputs.
Returns
{:ok, transaction}on success{:error, reason}on validation failure
@spec build_stas3_swap_swap_tx(base_config()) :: {:ok, BSV.Transaction.t()} | {:error, term()}
Build a STAS3 swap-swap transaction.
Both sides are swap requests (spending type 4). Requires exactly 2 STAS inputs, both carrying swap action data. Rejects frozen inputs.
Outputs can be 2-4 STAS outputs:
- Outputs 0-1: principal swap legs (neutral action data)
- Output 2: optional remainder for leg 1
- Output 3: optional remainder for leg 2
Parameters
config- Abase_config()map with exactly 2 token inputs and 2-4 destinations.
Returns
{:ok, transaction}on success{:error, reason}on validation failure (wrong input count, frozen inputs)
@spec build_stas3_swap_swap_tx_with_pieces( base_config(), [%{preceding_tx: binary(), asset_output_index: non_neg_integer()}] ) :: {:ok, BSV.Transaction.t()} | {:error, term()}
Build a STAS3 swap-swap transaction with the spec §9.5 trailing
<counterparty_script> <piece_count> <piece_array> block auto-wired
into BOTH inputs' unlocking scripts.
Use this entry point when the resulting tx must satisfy engine-level
verification — i.e. an actual on-chain spend rather than a witness
shape test. For each input i, the appended trailing block contains:
counterparty_script— the OTHER input's locking script (raw bytes).piece_count— derived frompieces[i](number of pieces produced frompreceding_txafter excising the asset script atasset_output_index).piece_array— reverse-ordered, length-prefixed (1-byte length, then body) — matching the engine ASM'sOP_1 OP_SPLIT OP_IFDUP OP_IF OP_SWAP OP_SPLIT OP_ENDIFconsumption pattern.
pieces MUST be a list of EXACTLY 2 maps, each with :preceding_tx
(binary) and :asset_output_index (non-negative integer) keys.
Mirrors bsv-sdk-rust::build_stas3_swap_swap_tx_with_pieces/2.
Returns {:error, reason} for the same input-shape failures as
build_stas3_swap_swap_tx/1 (frozen inputs, wrong count) plus piece
encoding errors propagated from Stas3Pieces.
@spec build_stas3_transfer_swap_tx(base_config()) :: {:ok, BSV.Transaction.t()} | {:error, term()}
Build a STAS3 transfer-swap transaction.
One side transfers (spending type 1), the other side's swap request is consumed. Requires exactly 2 STAS inputs. Rejects frozen inputs.
Outputs can be 2-4 STAS outputs:
- Outputs 0-1: principal swap legs (neutral action data)
- Output 2: optional remainder for leg 1
- Output 3: optional remainder for leg 2
Parameters
config- Abase_config()map with exactly 2 token inputs and 2-4 destinations.
Returns
{:ok, transaction}on success{:error, reason}on validation failure (wrong input count, frozen inputs)
@spec build_stas3_unfreeze_tx(base_config()) :: {:ok, BSV.Transaction.t()} | {:error, term()}
Build a STAS3 unfreeze transaction.
Same §9.2 constraints as build_stas3_freeze_tx/1.
Parse a previously-encoded trailing parameter block.
@spec resolve_stas3_swap_mode([BSV.Tokens.TokenInput.t()]) :: :swap_swap | :transfer_swap
Detect whether a swap is transfer-swap or swap-swap based on input locking scripts.
Reads each input's locking script and checks for swap action data:
- Both inputs have swap action data →
:swap_swap - Otherwise →
:transfer_swap
Parameters
token_inputs- List of exactly 2TokenInputstructs
Returns
:swap_swapor:transfer_swap