BSV.Tokens.Script.Stas3Builder (bsv_sdk v2.0.0)

Copy Markdown View Source

Builder for STAS 3.0 (stas3-freeze-multisig) locking scripts.

Summary

Functions

Build flags byte from boolean options.

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

build_stas3_flags(flags)

@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.

build_stas3_locking_script(arg1, arg2, action_data, frozen, freezable_or_flags, service_fields, optional_data)

@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 hash
  • redemption_pkh - 20-byte redemption public key hash
  • action_data - optional action data ({:swap, hash} or {:custom, bytes})
  • frozen - whether the token is frozen
  • freezable - whether the token supports freeze operations
  • service_fields - list of additional service field binaries
  • optional_data - list of additional optional data binaries

compute_stas3_requested_script_hash(locking_script)

@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.

decode_swap_action_data(arg1)

@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}

encode_swap_action_data(map)

@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.

encode_unlock_amount(amount)

@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_stas3_script_tail(arg)

@spec extract_stas3_script_tail(binary()) :: binary()

Extract the locking script tail (everything after owner + action_data fields).

The STAS3 locking script layout is:

  1. Owner field: OP_DATA_20 (0x14) + 20-byte PKH
  2. Action data field: push_data or OP_FALSE(0x00) or OP_2(0x52)
  3. 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_var2(arg)

@spec freeze_var2(binary()) :: binary()

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 formFrozen var2 form
empty push (OP_0, 0x00)OP_2 (0x52)
pushdata bytelength / OP_PUSHDATA1/2/4 pushprepend 0x02 to the pushed bytes, repush
OP_1, OP_3..OP_16, OP_1NEGATEconvert 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(data)

@spec push_data(binary()) :: binary()

Push data with appropriate length prefix.

unfreeze_var2(arg)

@spec unfreeze_var2(binary()) :: binary()

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)