Getting started (Chipnet)
This guide walks through a simple Chipnet workflow using the transfer_with_timeout example contract:
Fund the contract.
Spend the contract output using either:
Transfer path (send funds to the recipient), or
Timeout path (return funds to the sender after the timeout).
If you prefer a runnable script instead of copy/pasting code from this guide, see:
examples/contract_transfer_with_timeout.py
Examples should run directly from the project root, e.g.:
uv run python examples/contract_transfer_with_timeout.py --mode transfer
Install
Install from PyPI:
pip install cashscript-py
If you use uv:
uv add cashscript-py
1) Prepare Chipnet funds (FAUCET_WIF)
You’ll need a Chipnet (tBCH) private key with some coins. You’ll also need this for Chipnet E2E tests.
If you’re using Electron Cash:
Start Electron Cash in Chipnet mode:
electron-cash --chipnetGo to the Addresses tab.
Pick an unused receiving address, label it
Faucet, then Freeze it.Right-click it again → Private key → copy the WIF.
Get some Chipnet coins (mine them, use a faucet, or ask in the BCH developer community). For this guide, aim for at
least 0.01 tBCH, and send them to your frozen Faucet address.
Export the WIF so examples/tests and cashscript_py_support.utils can use it:
Linux/macOS:
export FAUCET_WIF="your_faucet_wif_here"
Windows (PowerShell):
$env:FAUCET_WIF = "your_faucet_wif_here"
Notes:
Treat
FAUCET_WIFlike a password: do not commit it to git or paste it into logs/issues.E2E tests may consolidate funds back into a single UTXO after the test session to keep your faucet wallet tidy.
2) Run the example (choose Transfer or Timeout)
This single script supports both spending paths. Set MODE to either "transfer" or "timeout".
Notes:
The contract’s timeout is set to a very low block height (
2) so the timeout path is always available.The timeout path uses
builder.set_locktime(...)to satisfy the contract’sOP_CHECKLOCKTIMEVERIFYrequirement.
import asyncio
import json
from typing import Any
from cashscript_py import Contract, ElectrumNetworkProvider, Network, Output, SignatureTemplate, TransactionBuilder
from cashscript_py_support.utils import STANDARD_FEE, Keypair, add_utxo, faucet, return_all
MODE = "transfer" # "transfer" or "timeout"
async def main() -> None:
with open("./examples/transfer_with_timeout.json", "r", encoding="utf-8") as f:
artifact: dict[str, Any] = json.load(f)
# Establish a connection to an Electrum Cash / Fulcrum server.
provider = ElectrumNetworkProvider("chipnet")
# Create a key to receive payouts from the contract.
receiver = Keypair()
# Constructor args: [sender public key, recipient public key, timeout (unix timestamp or block height)]
contract_arguments = [faucet.public_key, receiver.public_key, 2]
contract = Contract(artifact, contract_arguments, provider)
# Calculate amounts
net_amount = 10_000 # Amount the recipient should receive (satoshis)
gross_amount = net_amount + STANDARD_FEE # Extra to cover the spend-from-contract fee
# Fund the contract (sends from the Faucet key provided via FAUCET_WIF)
contract_utxo = await add_utxo(contract.address, gross_amount, provider)
print("Fund txid:", contract_utxo.txid)
builder = TransactionBuilder(provider)
if MODE == "transfer":
# Spend using the contract's transfer() path, sending the coins to the recipient.
receiver_sig = SignatureTemplate(receiver.private_key)
builder.add_input(contract_utxo, contract.unlock["transfer"](receiver_sig))
builder.add_output(Output(receiver.address, net_amount))
details: dict[str, str] = await builder.send()
print("Transfer txid:", details["txid"])
print("Transfer hex:", details["hex"])
# Optional: return the coins back to the Faucet address to tidy up.
await return_all(receiver, provider)
elif MODE == "timeout":
# Spend using the contract's timeout() path, returning the coins to the sender.
builder.set_locktime(100) # Must be >= the contract's timeout (2)
faucet_sig = SignatureTemplate(faucet.private_key)
builder.add_input(contract_utxo, contract.unlock["timeout"](faucet_sig))
builder.add_output(Output(faucet.address, net_amount))
details: dict[str, str] = await builder.send()
print("Timeout txid:", details["txid"])
print("Timeout hex:", details["hex"])
else:
raise ValueError(f"Unknown MODE: {MODE!r}")
if __name__ == "__main__":
asyncio.run(main())