Skip to main content

Use Chainhook with Bitcoin

The following guide helps you define predicates to use chainhook with Bitcoin.

Guide to if_this / then_that predicate design

To get started with Bitcoin predicates, we can use the chainhook to generate a template:

$ chainhook predicates new hello-ordinals.json --bitcoin

The above command generates a hello-ordinals.json file that looks like this:

{
"chain": "bitcoin",
"uuid": "a618b9ab-b836-43c9-954d-e31e8940322e",
"name": "Hello world",
"version": 1,
"networks": {
"mainnet": {
"start_block": 0,
"end_block": 100,
"if_this": {
"scope": "ordinals_protocol",
"operation": "inscription_feed"
},
"then_that": {
"file_append": {
"path": "ordinals.txt"
}
}
}
}
}

if_this and then_that specifications

The current bitcoin predicates support the following if_this constructs. Get any transaction matching a given txid. The txid mandatory argument admits: - 32 bytes hex encoded type.


{
"if_this": {
"scope": "txid",
"equals": "0xfaaac1833dc4883e7ec28f61e35b41f896c395f8d288b1a177155de2abd6052f"
}
}

Get any transaction, including an OP_RETURN output starting with a set of characters. The starts_with mandatory argument admits:

  • ASCII string type. example: X2[
  • hex encoded bytes. example: 0x589403
{
"if_this": {
"scope": "outputs",
"op_return": {
"starts_with": "X2["
}
}
}

Get any transaction, including an OP_RETURN output matching the sequence of bytes specified equals mandatory argument admits:

  • hex encoded bytes. example: 0x589403
{
"if_this": {
"scope": "outputs",
"op_return": {
"equals": "0x69bd04208265aca9424d0337dac7d9e84371a2c91ece1891d67d3554bd9fdbe60afc6924d4b0773d90000006700010000006600012"
}
}
}

Get any transaction, including an OP_RETURN output ending with a set of characters ends_with mandatory argument admits:

  • ASCII string type. example: X2[
  • hex encoded bytes. example: 0x589403
{
"if_this": {
"scope": "outputs",
"op_return": {
"ends_with": "0x76a914000000000000000000000000000000000000000088ac"
}
}
}

Get any transaction including a p2pkh output paying a given recipient p2pkh construct admits:

  • string type. example: "mr1iPkD9N3RJZZxXRk7xF9d36gffa6exNC"
  • hex encoded bytes type. example: "0x76a914ee9369fb719c0ba43ddf4d94638a970b84775f4788ac"
{
"if_this": {
"scope": "outputs",
"p2pkh": "mr1iPkD9N3RJZZxXRk7xF9d36gffa6exNC"
}
}

Get any transaction including a p2sh output paying a given recipient p2sh construct admits:

  • string type. example: "2MxDJ723HBJtEMa2a9vcsns4qztxBuC8Zb2"
  • hex encoded bytes type. example: "0x76a914ee9369fb719c0ba43ddf4d94638a970b84775f4788ac"
{
"if_this": {
"scope": "outputs",
"p2sh": "2MxDJ723HBJtEMa2a9vcsns4qztxBuC8Zb2"
}
}

Get any transaction including a p2wpkh output paying a given recipient p2wpkh construct admits:

  • string type. example: "bcrt1qnxknq3wqtphv7sfwy07m7e4sr6ut9yt6ed99jg"
{
"if_this": {
"scope": "outputs",
"p2wpkh": "bcrt1qnxknq3wqtphv7sfwy07m7e4sr6ut9yt6ed99jg"
}
}

Get any transaction including a p2wsh output paying a given recipient p2wsh construct admits:

  • string type. example: "bc1qklpmx03a8qkv263gy8te36w0z9yafxplc5kwzc"
{
"if_this": {
"scope": "outputs",
"p2wsh": "bc1qklpmx03a8qkv263gy8te36w0z9yafxplc5kwzc"
}
}

Get any Bitcoin transaction, including a Block commitment. Broadcasted payloads include Proof of Transfer reward information.

{
"if_this": {
"scope": "stacks_protocol",
"operation": "block_committed"
}
}

Get any transaction, including a key registration operation

{
"if_this": {
"scope": "stacks_protocol",
"operation": "leader_key_registered"
}
}

Get any transaction, including an STX transfer operation // Coming soon

{
"if_this": {
"scope": "stacks_protocol",
"operation": "stx_transferred"
}
}

Get any transaction, including an STX lock operation // Coming soon

{
"if_this": {
"scope": "stacks_protocol",
"operation": "stx_locked"
}
}

Get any transaction including a new Ordinal inscription (inscription revealed and transferred)

{
"if_this": {
"scope": "ordinals_protocol",
"operation": "inscription_feed"
}
}

In terms of actions available, the following then_that constructs are supported:

HTTP Post block/transaction payload to a given endpoint. The http_post construct admits:

  • url (string type). Example: http://localhost:3000/api/v1/wrapBtc
  • authorization_header (string type). Secret to add to the request authorization header when posting payloads

{
"then_that": {
"http_post": {
"url": "http://localhost:3000/api/v1/wrapBtc",
"authorization_header": "Bearer cn389ncoiwuencr"
}
}
}

Append events to a file through the filesystem. Convenient for local tests. The `file_append` construct admits:
- path (string type). Path to the file on disk.

```jsonc
{
"then_that": {
"file_append": {
"path": "/tmp/events.json",
}
}
}

Additional configuration knobs available

// Ignore any block before the given block:
"start_block": 101

// Ignore any block after the given block:
"end_block": 201

// Stop evaluating chainhook after a given number of occurrences found:
"expire_after_occurrence": 1

// Include proof:
"include_proof": false

// Include Bitcoin transaction inputs in the payload:
"include_inputs": false

// Include Bitcoin transaction outputs in the payload:
"include_outputs": false

// Include Bitcoin transaction witness in the payload:
"include_witness": false

Putting all the above configurations together

Retrieve and HTTP Post to http://localhost:3000/api/v1/wrapBtc the five first transfers to the p2wpkh bcrt1qnxk...yt6ed99jg address of any amount, occurring after block height 10200.

{
"chain": "bitcoin",
"uuid": "1",
"name": "Wrap BTC",
"version": 1,
"networks": {
"testnet": {
"if_this": {
"scope": "outputs",
"p2wpkh": {
"equals": "bcrt1qnxknq3wqtphv7sfwy07m7e4sr6ut9yt6ed99jg"
}
},
"then_that": {
"http_post": {
"url": "http://localhost:3000/api/v1/transfers",
"authorization_header": "Bearer cn389ncoiwuencr"
}
},
"start_block": 10200,
"expire_after_occurrence": 5,
}
}
}

Another example

A specification file can also include different networks. In this case, the chainhook will select the predicate corresponding to the network it was launched against.

{
"chain": "bitcoin",
"uuid": "1",
"name": "Wrap BTC",
"version": 1,
"networks": {
"testnet": {
"if_this": {
"scope": "ordinals_protocol",
"operation": "inscription_feed"
},
"then_that": {
"http_post": {
"url": "http://localhost:3000/api/v1/ordinals",
"authorization_header": "Bearer cn389ncoiwuencr"
}
},
"start_block": 10200,
},
"mainnet": {
"if_this": {
"scope": "ordinals_protocol",
"operation": "inscription_feed"
},
"then_that": {
"http_post": {
"url": "http://my-protocol.xyz/api/v1/ordinals",
"authorization_header": "Bearer cn389ncoiwuencr"
}
},
"start_block": 90232,
}

}
}

Guide to local Bitcoin testnet / mainnet predicate scanning

To scan the Bitcoin chain with a given predicate, a bitcoind instance with access to the RPC methods getblockhash and getblock must be accessible. The RPC calls latency will directly impact the speed of the scans.

note

Configuring a bitcoind instance is out of the scope of this guide.

Assuming a bitcoind node is correctly configured, you can perform scans using the following command:

$ chainhook predicates scan ./path/to/predicate.json --testnet

When using the flag --testnet, the scan operation will generate a configuration file in memory using the following settings:

[storage]
driver = "memory"

[chainhooks]
max_stacks_registrations = 500
max_bitcoin_registrations = 500

[network]
mode = "testnet"
bitcoind_rpc_url = "http://0.0.0.0:18332"
bitcoind_rpc_username = "testnet"
bitcoind_rpc_password = "testnet"
stacks_node_rpc_url = "http://0.0.0.0:20443"

When using the flag --mainnet, the scan operation will generate a configuration file in memory using the following settings:

[storage]
driver = "memory"

[chainhooks]
max_stacks_registrations = 500
max_bitcoin_registrations = 500

[network]
mode = "mainnet"
bitcoind_rpc_url = "http://0.0.0.0:8332"
bitcoind_rpc_username = "mainnet"
bitcoind_rpc_password = "mainnet"
stacks_node_rpc_url = "http://0.0.0.0:20443"

Developers can customize their Bitcoin node's credentials and network address by adding the flag -config=/path/to/config.toml.

$ chainhook config new --testnet
✔ Generated config file Testnet.toml

$ chainhook predicates scan ./path/predicate.json --config=./Testnet.toml

Tips and tricks

To optimize your experience with scanning, the following are a few knobs you can play with:

  • Use of adequate values for start_block and end_block in predicates will drastically improve the speed.
  • Networking: reducing the number of network hops between the chainhook and the bitcoind processes can also help a lot.