STAS 3.0 v0.2.3 §8 / §9.5 atomic-swap and merge "piece array" trailing parameters for STAS 3.0 unlocking scripts.
Background
For atomic-swap (txType = 1) and merge transactions (txType = 2..7),
the STAS unlocking script appends a trailing block whose layout depends
on txType. Per spec v0.2.3, each piece is its own
OP_PUSHDATA operation in the unlocking script, and piece_count is
a minimal Bitcoin numeric push (DXS convention from
dxs-bsv-token-sdk's ScriptBuilder.addNumber: for n ∈ 1..=16 emit
OP_<n> = single byte 0x50 + n; for n ∈ 17..=127 emit 0x01 <n>;
for larger emit script-num-encoded with sign-bit sentinel):
# txType = 1 (atomic swap)
pushdata(counterparty_locking_script)
minimal_numeric_push(piece_count)
pushdata(piece_1) ... pushdata(piece_N)
# txType = 2..7 (merge)
minimal_numeric_push(piece_count) # must equal txType
pushdata(piece_1) ... pushdata(piece_N)What "pieces" are (spec §9.5)
The canonical engine
(github.com/stassso/STAS-3-script-templates) consumes them via an
unrolled, counter-driven block:
OP_OVER OP_IF OP_SWAP OP_1SUB OP_SWAP OP_3 OP_PICK OP_CAT OP_10 OP_ROLL OP_CAT OP_ENDIFrepeated 5×, driven by a decrementing piece_count counter. There is
no concatenated length-prefixed blob and no per-piece size limit —
the earlier 127-byte limit was a phantom of the obsolete encoding.
Given the preceding transaction (the tx that produced the input UTXO):
- For each named asset output (
asset_output_indices), locate the locking script. Within that script, identify the "asset script" — the bytes from the engine prefix (0x6D 0x82 0x73 0x63) to the end of the script. Everything BEFORE that prefix belongs to the two var fields (ownerpush +var2push) and is NOT excised. - The remaining tx bytes (parts that AREN'T excised) are the pieces: one before the first excised region, one between each pair, and one after the last.
- Reverse the piece order (head on top of stack).
- Concatenate independent pushdata pushes — no length prefix inside.
This module exposes:
encode_atomic_swap_pieces/3— build the trailing block for txType=1encode_merge_pieces/3— build the trailing block for txType=2..7parse/2— decode a previously-encoded trailing block
Strict boundaries: at least one asset output must be named, the merge
piece count must be in 2..7, and parsed piece_count must match the
number of pieces read back.
Summary
Functions
Build the trailing-parameters block for a txType = 1 atomic-swap
unlocking script.
Build the trailing-parameters block for a merge unlocking script
(txType = 2..7).
Parse a previously-encoded trailing parameter block.
Types
Functions
@spec encode_atomic_swap_pieces(binary(), binary(), [non_neg_integer()]) :: {:ok, binary()} | {:error, term()}
Build the trailing-parameters block for a txType = 1 atomic-swap
unlocking script.
Returns the byte sequence formed by appending independent Bitcoin pushdata operations:
pushdata(counterparty_script) ‖ minimal_numeric_push(piece_count) ‖
pushdata(piece_1) ‖ … ‖ pushdata(piece_N)ready to splice verbatim into an unlocking script.
asset_output_indices MUST list at least one valid output index in
preceding_tx.
@spec encode_merge_pieces(2..7, binary(), [non_neg_integer()]) :: {:ok, binary()} | {:error, term()}
Build the trailing-parameters block for a merge unlocking script
(txType = 2..7).
Returns the byte sequence:
minimal_numeric_push(piece_count) ‖
pushdata(piece_1) ‖ … ‖ pushdata(piece_N)Per spec §8, piece_count MUST equal the merge txType (2..7) and MUST
equal the resulting number of pieces. With K excised asset-script
regions in preceding_tx, the resulting array has K + 1 pieces, so
length(asset_output_indices) MUST equal piece_count - 1.
@spec parse(binary(), 1..7) :: {:ok, parsed_swap() | parsed_merge()} | {:error, term()}
Parse a previously-encoded trailing parameter block.
tx_type selects the layout:
1— atomic swap: leading pushdata-framed counterparty script,then a minimal-numeric-push piece_count, then `piece_count` independent pushdata piece operations. Returns `{:ok, %{counterparty_script: _, piece_count: _, pieces: _}}`.2..7— merge: leading minimal-numeric-push piece_count (mustequal `tx_type`), then `piece_count` independent pushdata piece operations. Returns `{:ok, %{piece_count: _, pieces: _}}`.
On malformed input — bad framing, count mismatch with array length,
unsupported tx_type — returns {:error, reason}.