Transaction Builder

The CashScript-Py TransactionBuilder generalizes transaction building to allow complex transactions combining multiple different smart contracts within a single transaction, or to create basic P2PKH transactions. The builder works by adding inputs and outputs to fully specify the transaction shape.

Info: defining the inputs and outputs requires careful consideration because the difference in BCH value between inputs and outputs is paid in transaction fees to miners.

Instantiating a transaction builder

from cashscript_py import ElectrumNetworkProvider, TransactionBuilder

provider = ElectrumNetworkProvider("mainnet")
transaction_builder = TransactionBuilder(provider)

To start, instantiate a transaction builder and pass in a NetworkProvider instance and other options.

Constructor options

provider

The provider option specifies the network provider to use when broadcasting the transaction and querying for details.

maximum_fee_satoshis

The maximum_fee_satoshis option specifies the maximum fee for the transaction in satoshis. If this fee is exceeded, an error is raised when building the transaction.

maximum_fee_sats_per_byte

The maximum_fee_sats_per_byte option specifies the maximum fee per byte for the transaction. If this fee is exceeded, an error is raised when building the transaction.

allow_implicit_fungible_token_burn

The allow_implicit_fungible_token_burn option specifies whether implicit burning of fungible tokens is allowed (default: False). If set to True, the transaction builder will not raise an error when burning fungible tokens.

Example

from cashscript_py import ElectrumNetworkProvider, TransactionBuilder

provider = ElectrumNetworkProvider("mainnet")
transaction_builder = TransactionBuilder(provider)

Transaction building

add_input()

transaction_builder.add_input(utxo, unlocker, sequence=None) -> TransactionBuilder

Adds a single input UTXO to the transaction that can be unlocked using the provided unlocker. The unlocker can be derived from a SignatureTemplate or a Contract instance’s spending functions.

The sequence parameter can be used to specify the sequence number of the input (advanced usage).

Note: you can create custom unlockers by implementing the Unlocker interface, but most use cases are covered by SignatureTemplate and Contract.

Example

contract_utxos = await contract.get_utxos()
alice_utxos = await provider.get_utxos(alice_address)

transaction_builder.add_input(contract_utxos[0], contract.unlock["spend"]())
transaction_builder.add_input(alice_utxos[0], alice_template.unlock_p2pkh())

add_inputs()

Overloads:

  • transaction_builder.add_inputs(utxos: list[Utxo], unlocker: Unlocker, sequence: int | None = None) -> TransactionBuilder

  • transaction_builder.add_inputs(utxos: list[UnlockableUtxo]) -> TransactionBuilder

Adds a list of input UTXOs, either with a single shared unlocker or with individual unlockers per UTXO.

Example

contract_utxos = await contract.get_utxos()
alice_utxos = await provider.get_utxos(alice_address)

# Use a single unlocker for all inputs you're adding at a time
transaction_builder.add_inputs(contract_utxos, contract.unlock["spend"]())
transaction_builder.add_inputs(alice_utxos, alice_template.unlock_p2pkh())

# Or add UnlockableUtxo items (each UTXO already has an unlocker attached)
# unlockable_utxos = [UnlockableUtxo.from_utxo(...), ...]
# transaction_builder.add_inputs(unlockable_utxos)

add_output() & add_outputs()

transaction_builder.add_output(output: Output) -> TransactionBuilder transaction_builder.add_outputs(outputs: list[Output]) -> TransactionBuilder

Adds a single output or a list of outputs to the transaction.

Output.to can be a CashAddress (str) or raw locking bytecode (bytes). Output.token can optionally describe CashTokens.

Example

from cashscript_py import Output

transaction_builder.add_output(Output(to=alice_address, amount=100_000))
transaction_builder.add_outputs(
    [
        Output(to=alice_address, amount=50_000),
        Output(to=bob_address, amount=50_000),
    ]
)

add_op_return_output()

transaction_builder.add_op_return_output(chunks: list[str]) -> TransactionBuilder

Adds an OP_RETURN output to the transaction with the provided data chunks in string format. If a string is 0x-prefixed, it is treated as hex; otherwise it is treated as UTF-8.

Example

# Post "Hello World!" to memo.cash
transaction_builder.add_op_return_output(["0x6d02", "Hello World!"])

set_locktime()

transaction_builder.set_locktime(locktime: int) -> TransactionBuilder

Sets the locktime for the transaction to set a transaction-level absolute timelock. The locktime can be set to a block height or a unix timestamp (per consensus rules).

Example

# Use current block height as locktime (common for CLTV contracts)
current_block_height = await provider.get_block_height()
transaction_builder.set_locktime(current_block_height)

Completing the transaction

send()

await transaction_builder.send(raw: bool = False) -> dict[str, str]

After completing a transaction, send() broadcasts it to the network and then polls the provider until the transaction can be retrieved.

CashScript-Py currently returns:

  • {"txid": "...", "hex": "..."} when raw=False (default)

  • {"hex": "..."} when raw=True

Example

from cashscript_py import Output, TransactionBuilder

contract_utxos = await contract.get_utxos()
alice_utxos = await provider.get_utxos(alice_address)
maximum_fee_satoshis = 1000

tx_details: dict[str, str] = await (
    TransactionBuilder(provider, maximum_fee_satoshis=maximum_fee_satoshis)
    .add_input(contract_utxos[0], contract.unlock["spend"](alice_template, 1000))
    .add_input(alice_utxos[0], alice_template.unlock_p2pkh())
    .add_output(Output(to=bob_address, amount=100_000))
    .add_op_return_output(["0x6d02", "Hello World!"])
    .send()
)
print(tx_details)

build()

transaction_builder.build() -> str

After completing a transaction, build() constructs and signs the transaction and returns the signed transaction hex string. This can then be imported into other tools or libraries as necessary.

Example

from cashscript_py import Output, TransactionBuilder

contract_utxos = await contract.get_utxos()
alice_utxos = await provider.get_utxos(alice_address)
maximum_fee_satoshis = 1000

tx_hex: str = (
    TransactionBuilder(provider, maximum_fee_satoshis=maximum_fee_satoshis)
    .add_input(contract_utxos[0], contract.unlock["spend"](alice_template, 1000))
    .add_input(alice_utxos[0], alice_template.unlock_p2pkh())
    .add_output(Output(to=bob_address, amount=100_000))
    .add_op_return_output(["0x6d02", "Hello World!"])
    .build()
)
print(tx_hex)

debug()

The upstream TypeScript SDK can locally evaluate and debug transactions via a debug() API. CashScript-Py does not currently provide an equivalent.

For debugging in Python, use:

  • CASHSCRIPT_PY_DEBUG=1 to print preimage/sighash/signature/script details

  • pytest + chipnet E2E tests for integration coverage

get_bitauth_uri()

The upstream TypeScript SDK can generate a BitAuth IDE URI (getBitauthUri()). CashScript-Py does not currently provide this helper.

Caution: in general, it is unsafe to debug mainnet transactions in web tooling, as private keys can be exposed.

generate_wc_transaction_object()

The upstream TypeScript SDK can generate a WalletConnect signing payload. CashScript-Py does not currently provide this feature.

Transaction errors

Transactions can fail for a number of reasons, including:

  • insufficient or excessive fees (fee caps exceeded)

  • invalid unlocking bytecode (bad signature, wrong script path, missing locktime/sequence constraints)

  • provider failures (broadcast rejected, server unavailable)

  • token-policy violations (implicit fungible token burn not allowed)

In CashScript-Py, these failures are raised as Python exceptions (e.g., ValueError for local validation failures, or provider exceptions like BroadcastFailed for network rejection).