Builder for STAS 3.0 (stas3-freeze-multisig) locking scripts.
Summary
Functions
Build flags byte from boolean options.
Build a STAS3 locking script.
Compute the requestedScriptHash for a STAS3 locking script.
Decode a swap action data binary into structured fields.
Encode swap action data fields into a binary for embedding in a locking script.
Encode a STAS 3.0 unlocking-script amount as a minimal little-endian push.
Extract the locking script tail (everything after owner + action_data fields).
Freeze a STAS 3.0 var2 field (action 0x02) per spec §6.2.
Push data with appropriate length prefix.
Unfreeze a STAS 3.0 var2 field per spec §6.2.
Functions
@spec build_stas3_flags(boolean() | BSV.Tokens.ScriptFlags.t()) :: binary()
Build flags byte from boolean options.
Accepts either a single boolean (legacy: freezable only) or a
ScriptFlags struct for full flag support.
@spec build_stas3_locking_script( <<_::160>>, <<_::160>>, BSV.Tokens.ActionData.t() | nil, boolean(), boolean() | BSV.Tokens.ScriptFlags.t(), [binary()], [binary()] ) :: {:ok, BSV.Script.t()} | {:error, term()}
Build a STAS3 locking script.
Parameters
owner_pkh- 20-byte owner public key hashredemption_pkh- 20-byte redemption public key hashaction_data- optional action data ({:swap, hash}or{:custom, bytes})frozen- whether the token is frozenfreezable- whether the token supports freeze operationsservice_fields- list of additional service field binariesoptional_data- list of additional optional data binaries
@spec compute_stas3_requested_script_hash(binary()) :: <<_::256>>
Compute the requestedScriptHash for a STAS3 locking script.
Extracts the "tail" of a locking script (everything after the owner and action_data fields), then returns SHA256(tail). This hash is used in swap action data to identify the counterparty's expected locking script structure.
Parameters
locking_script- Full STAS3 locking script binary
Returns
A 32-byte SHA256 hash of the locking script tail.
@spec decode_swap_action_data(binary()) :: {:ok, BSV.Tokens.ActionData.swap_fields()} | {:error, term()}
Decode a swap action data binary into structured fields.
Parses one or more 61-byte swap legs from the binary. Each leg starts with kind byte 0x01 followed by 32-byte hash, 20-byte PKH, and two uint32 LE values.
Parameters
data- Binary starting with kind byte 0x01
Returns
{:ok, swap_fields} or {:error, reason}
@spec encode_swap_action_data(BSV.Tokens.ActionData.swap_fields()) :: binary()
Encode swap action data fields into a binary for embedding in a locking script.
Each swap leg is 61 bytes: 1 (kind 0x01) + 32 (hash) + 20 (pkh) + 4 (numerator LE) + 4 (denominator LE).
Parameters
fields- Map with:requested_script_hash(32 bytes),:requested_pkh(20 bytes),:rate_numerator(uint32),:rate_denominator(uint32)
Returns
A 61-byte binary encoding the swap action data.
@spec encode_unlock_amount(non_neg_integer()) :: binary()
Encode a STAS 3.0 unlocking-script amount as a minimal little-endian push.
Per STAS 3.0 v0.1 §7, the out1_amount, out2_amount, out3_amount,
out4_amount and change_amount fields are "Unsigned LE (up to 8 B) or
empty" — i.e. minimal-LE encoded, not fixed 8 bytes. Zero is encoded as the
empty push.
Sign-bit safety
The engine treats the pushed bytes as a Bitcoin script number when it
later splices them into the BIP-143-style outputs blob via
OP_BIN2NUM / OP_NUM2BIN. Bitcoin script numbers are sign-magnitude
little-endian: when the high bit of the most-significant byte is set,
the value is interpreted as negative. For unsigned-LE token amounts
whose top byte happens to have its high bit set (any value with a top
byte ≥ 0x80, e.g. 0xBD0E = 48398), we MUST append a 0x00 sentinel
byte to keep the value non-negative when read as a script number.
Without it the engine reconstructs a wildly different change amount in
its outputs blob and the BIP-143 hashOutputs check fails (root cause
of :eval_false for any STAS 3.0 spend whose change amount has a top
byte ≥ 0x80).
Returns the wire bytes of the push instruction (length prefix + LE payload) suitable for direct concatenation into an unlocking script binary.
Examples
iex> Stas3Builder.encode_unlock_amount(0)
<<0x00>>
iex> Stas3Builder.encode_unlock_amount(1)
<<0x01, 0x01>>
iex> Stas3Builder.encode_unlock_amount(0x7F)
<<0x01, 0x7F>>
# 0xFF requires a sign-bit-disambiguation sentinel.
iex> Stas3Builder.encode_unlock_amount(0xFF)
<<0x02, 0xFF, 0x00>>
iex> Stas3Builder.encode_unlock_amount(0x100)
<<0x02, 0x00, 0x01>>
Extract the locking script tail (everything after owner + action_data fields).
The STAS3 locking script layout is:
- Owner field: OP_DATA_20 (0x14) + 20-byte PKH
- Action data field: push_data or OP_FALSE(0x00) or OP_2(0x52)
- Tail: everything from base template to end of script
Parameters
script- Full STAS3 locking script binary
Returns
Binary containing everything after the action_data field.
Freeze a STAS 3.0 var2 field (action 0x02) per spec §6.2.
Takes the wire bytes of the original var2 push (the on-script encoding
of the var2 push instruction) and returns the wire bytes of the frozen
marker push.
Mapping (spec §6.2 table):
| Original var2 form | Frozen var2 form |
|---|---|
empty push (OP_0, 0x00) | OP_2 (0x52) |
| pushdata bytelength / OP_PUSHDATA1/2/4 push | prepend 0x02 to the pushed bytes, repush |
OP_1, OP_3..OP_16, OP_1NEGATE | convert to pushdata, then prepend 0x02 |
Returns the wire bytes of the new push instruction (a single push opcode +
payload). unfreeze_var2/1 is the strict inverse.
Push data with appropriate length prefix.
Unfreeze a STAS 3.0 var2 field per spec §6.2.
Strict inverse of freeze_var2/1. Takes the wire bytes of the frozen push
and returns the wire bytes of the original push.
Recognised inputs:
- single byte
OP_2(0x52) → empty push (OP_0, 0x00) - any pushdata whose first payload byte is
0x02→ reverse-mapped payload (single-byte values 0x01, 0x03..0x10, and 0x81 are remapped back to their bare-opcode form; otherwise re-emitted as pushdata)