Skip to content

How to Deploy and Call a Contract


This section covers the complete workflow from compiling a contract to deploying and calling it on the Bitcoin network. UTXO_Compiler compiles .ct contracts into locking script bytecode; on-chain deployment and calls require a wallet or transaction-building tool.


Compilation Output

After running the compiler, the output is a JSON-formatted bytecode description:

bash
./utxo_compiler my_contract.ct

Example output (simplified):

json
{
  "metadata": {
    "...": "..."
  },
  "abi": [
    {
      "type": "function",
      "name": "verify",
      "index": 0,
      "params": [
        {
          "name": "sig",
          "type": "hex"
        },
        {
          "name": "pubKey",
          "type": "hex"
        }
      ]
    }
  ],
  "lock": {
    "asm": "OP_DUP OP_HASH160 <self.pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG",
    "hex": "76a9<self.pubKeyHash>88ac"
  },
  "unlock": {
    "verify": "<sig><pubKey>"
  },
  "functions": [
    {
      "name": "verify",
      "type": "public",
      "params": [
        {
          "name": "sig",
          "type": "hex"
        },
        {
          "name": "pubKey",
          "type": "hex"
        }
      ]
    }
  ]
}

Key fields:

  • lock.hex: Hex-encoded bytecode sequence, used directly as the locking script for a transaction output
  • lock.asm: Human-readable opcode disassembly for inspection
  • abi / functions: Public function list and parameter types, describing what data must be provided when unlocking
  • structs: Struct layouts used by parameters such as PreTX and CurrentTX
  • unlock: Per-function placeholder template, useful for checking the field order inside each serialized parameter group

Deploying a Contract

"Deploying a contract" in the UTXO model means creating a transaction whose output uses the compiled contract locking script.

Deployment transaction
├── Input: some existing UTXO (provides funds)
└── Output:
    ├── value:          number of satoshis to lock in the contract
    └── locking script: bytecode compiled by utxo_compiler

Locking Script and Instance Data

The compiled lock.hex usually contains two kinds of data:

  • Contract logic: script logic compiled from .ct code; the same contract template usually keeps this part unchanged.
  • Instance data: data written for a specific deployed instance, such as self.pubKeyHash, a deadline, or an initial counter value.

Before deployment, replace placeholders such as <self.xxx> with real byte data. For P2PKH, self.pubKeyHash should be replaced with the 20-byte Hash160 of the recipient public key.

For stateful contracts, the split point matters. A common layout stores mutable instance data after the last OP_RETURN and any 0xff padding bytes that follow it. In the Counter example the mutable state is encoded after that offset as 08 + uint64LE(count), so changing the count means replacing exactly those 9 bytes while leaving the logic prefix unchanged.

UTXO_Compiler does not require a specific wallet or transaction library. As long as your tool can construct transaction outputs and set the locking script, it can use the compiled lock.hex.

Deployment Flow

  1. Compile the contract and read lock.hex.
  2. Replace instance placeholders such as <self.pubKeyHash>.
  3. If the contract is stateful, write the initial state bytes into the instance-data area.
  4. Build a transaction output whose locking script is the final contract script.
  5. Sign and broadcast the transaction with your wallet or transaction library.

Once broadcast, the deployment transaction's contract output, for example txid:0, is the deployed contract instance. Any later transaction that wants to spend it must satisfy the unlocking conditions encoded in the contract.

For chained local tests, a transaction library may let you convert the newly created output directly into an input for the next unconfirmed transaction.

For constructor-style placeholders such as <self.pubKeyHash>, it is usually worth centralizing placeholder replacement in a small helper that reads constructorParams, encodes integer values in little-endian form, and replaces every <self.name> placeholder before the script is used in an output.

Deployment Example

Assume tbc, readFile, and broadcastRawTx come from your transaction stack:

ts
const compiled = JSON.parse(await readFile("p2pkh.json", "utf8"));
const lockHex = compiled.lock.hex.replace("<self.pubKeyHash>", pubKeyHashHex);

const deployTx = new tbc.Transaction();
deployTx.from([fundingUtxo]);
deployTx.addOutput(new tbc.Transaction.Output({
  script: tbc.Script.fromHex(lockHex),
  satoshis: 10_000,
}));
deployTx.change(changeAddress);
deployTx.feePerKb(80);
deployTx.sign(privateKey);

await broadcastRawTx(deployTx.serialize());

Calling a Contract

"Calling a contract" means constructing a transaction that spends the contract UTXO. The caller provides the required data in the input's unlocking script.

Only public functions declared by the host Contract contribute ABI call parameters. Imported library helpers may be called internally by those functions, but they do not add separate unlocking parameters or selectable call targets.

Unlocking Script Structure

The unlocking script is constructed by the caller. Its content is a sequence of push operations; the first item written sits lower on the stack, and the last item written is closest to the top when the locking script starts.

# Example: P2PKH contract's verify(sig: hex, pubKey: hex)
Unlocking script = <sig> <pubKey>

For a single public function, this usually means writing the parameters in ABI order. During BVM execution, the contents of the unlocking script are first pushed onto the main stack (sig at the bottom, pubKey at the top), then the locking script consumes those values.

Public Function Execution Order

For contracts containing multiple public functions, the public functions execute in sequence in the order they are declared in the source code. The order is determined by the definition position; there is no need to specify "which function to select" at call time.

Because later pushes are nearer the stack top, concatenate each function's argument group in reverse execution order. Inside each function group, follow the compiler's unlock[functionName] template.

For example, if Counter runs getCountFromPreTX(pretx) before verifyCurrentTX(ctx), the first function consumes pretx, so that group must be pushed last: Unlocking script = <ctx fields> <pretx fields>.

Imported library helpers are skipped in this sequence. They run only when a public function calls them.

At execution time, the BVM pushes the unlocking script data onto the main stack, then runs the contract bytecode. If the checks pass, the top of the stack should be 1, and the network accepts the transaction.

Call Example

Assume buildUnlockingScript() serializes the function arguments as push-data items:

ts
const unlockingScript = buildUnlockingScript([sigHex, pubKeyHex]);

const callTx = new tbc.Transaction();
callTx.from([contractUtxo, feeUtxo]);
callTx.addOutput(new tbc.Transaction.Output({
  script: recipientScript,
  satoshis: contractUtxo.satoshis - fee,
}));
callTx.change(changeAddress);
callTx.sign(privateKey); // signs ordinary wallet inputs such as feeUtxo
callTx.setInputScript({ inputIndex: 0 }, () => unlockingScript);

await broadcastRawTx(callTx.serialize());

Stateful Calls

Simple signature contracts only need values such as signatures and public keys. Stateful contracts also need transaction-context data proving that:

  1. The current input spends the correct old contract UTXO.
  2. The current transaction creates the expected new contract output.

These contracts usually describe parent transactions and current transaction outputs with structs such as PreTX and CurrentTX. When calling the contract, the unlocking script must provide serialized data for these structs in addition to ordinary parameters.

A typical Counter-style call flow is:

  • Read public function parameters and struct array lengths from the compiled JSON.
  • Build the next locking script first, including the updated state bytes for the output being created.
  • Serialize the spent output, input list, output list, and other data from the parent transaction.
  • Serialize the current transaction outputs that the contract needs to commit to.
  • Concatenate the unlocking script according to the public function execution order.
  • Ensure the current transaction outputs match checks such as BVM.outputsHash inside the contract.

Pay special attention to stack order in multi-public-function scenarios: data written later to the unlocking script is closer to the stack top, so the first function's parameter group usually belongs in the latter half of the unlocking script.

For each output, serialize:

<value> <LockingScript.SuffixData> <LockingScript.PartialHash> <LockingScript.Size>

Long scripts are represented as PartialHash + SuffixData + Size. The contract can call PartialHash(...) to reconstruct the script commitment without pushing the entire logic prefix every time. Short scripts can use PartialHash = 00 and keep the full script in SuffixData.

For the contract output itself, use the real contract data offset. For ordinary outputs, split at the largest 64-byte boundary below the script length, or leave scripts shorter than 64 bytes unsplit.

The serializer must write every value in the same shape that the compiled structs expect: fixed-width numbers are little-endian, byte strings are prefixed with the proper push-data length, and missing fixed-array entries are padded with empty pushes. Constants such as 08, 10, 20, and 28 are push lengths for 8-byte amounts, 16-byte transaction headers, 32-byte hashes, and 40-byte input records.

For stateful contracts, build the next output script first, then serialize the parent transaction data and current transaction outputs according to the ABI structs. Concatenate the resulting hex groups according to stack order. In the Counter pattern, verifyCurrentTX(ctx) executes after getCountFromPreTX(pretx), so the unlocking script pushes ctx data first and pretx data last.

If a contract declares current-input data or two-level ancestry, serialize those structs in the same way: derive lengths from the ABI, serialize exactly the fields declared by the struct, and concatenate the resulting groups according to stack order.


Beginner Pitfalls

  • Instance data must match exactly. If self.X or SuffixData has even one byte too many or too few, contract verification can fail.
  • Parameter order must match the ABI. Unlocking script push order affects parameter positions on the stack; with multiple public functions, function parameter groups are usually concatenated in reverse execution order.
  • Multiple public functions execute serially in declaration order. The caller must supply parameters for all public functions, not pick one.
  • Library helpers are internal. Imported functions such as verifyP2PKH are not ABI entries and do not receive standalone unlock arguments.
  • BVM.outputsHash / BVM.unlockingInput etc. are injected by the network at execution time. When debugging locally, feed simulated transaction data via settxfile (see How to Debug a Contract).
  • Fixed array lengths are part of the ABI. If PreTX.Outputs is Output[3], serialize at most 3 outputs and pad missing entries exactly as the contract expects.
  • partialOffset must come from the actual deployed script. Do not hard-code it unless the script layout is fixed and tested; derive it from the script bytes.
  • Update state before hashing outputs. For stateful contracts, build the next output script first, then serialize CurrentTX; otherwise BVM.outputsHash will commit to the wrong bytes.
  • Fees are decided by the caller. The contract itself does not enforce fees, but miners require a minimum fee based on transaction size before accepting a broadcast.
  • Signature preimage construction. CheckSig validates a preimage computed per the SIGHASH rules — sign with the same SIGHASH type that node validation uses.
  • Verify locally before broadcasting. Run your transaction library's local verification before sending raw transactions to the network.

Quick Review

  • Deploying a contract means creating a UTXO whose locking script is the compiled lock.hex.
  • Calling a contract means spending that contract UTXO and supplying arguments in the unlocking script.
  • A single public function usually follows ABI parameter order; multiple public functions require careful argument-group stack ordering.
  • Stateful contracts also need parent-transaction and current-output data so the contract can verify state progression.
  • Before building a real transaction, verify scripts, signatures, and output commitments locally.

Next Steps


Chinese version