Skip to content

Commit 9b1bbe2

Browse files
authored
chore: add ic env + build-image.sh to motoko/basic_bitcoin (#1429)
1 parent 1fc2bbe commit 9b1bbe2

7 files changed

Lines changed: 100 additions & 87 deletions

File tree

.github/workflows/basic_bitcoin.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,4 @@ jobs:
4040
run: |
4141
icp network start -d
4242
icp deploy --cycles 30t
43-
make test
43+
bash test.sh

motoko/basic_bitcoin/Makefile

Lines changed: 0 additions & 62 deletions
This file was deleted.

motoko/basic_bitcoin/README.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ The example covers three Bitcoin address types, each backed by a different signi
2424

2525
### Prerequisites
2626

27-
- Node.js
28-
- icp-cli: `npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm`
29-
- ic-mops: `npm install -g ic-mops`
30-
- Docker (required for local testing — bundles the IC network launcher + `bitcoind`)
27+
- [Node.js](https://nodejs.org/) v18+
28+
- [icp-cli](https://cli.internetcomputer.org/): `npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm`
29+
- [ic-mops](https://mops.one/): `npm install -g ic-mops`
30+
- [Docker](https://docs.docker.com/get-docker/) (required for local testing — bundles the IC network launcher + `bitcoind`)
3131

3232
### Install
3333

@@ -41,7 +41,7 @@ cd examples/motoko/basic_bitcoin
4141
The local environment uses a self-contained Docker image (`icp-cli-network-launcher-bitcoin`) that runs `bitcoind` in regtest mode alongside the IC network. Build it once:
4242

4343
```bash
44-
make build-image
44+
./build-image.sh
4545
```
4646

4747
Then deploy and run tests:
@@ -53,27 +53,27 @@ make test
5353
icp network stop
5454
```
5555

56-
> If tests fail with an out-of-cycles error, run `make topup` and retry.
56+
> If tests fail with an out-of-cycles error, top up the canister and retry:
57+
> ```bash
58+
> icp canister top-up --amount 30t backend
59+
> ```
5760
58-
### Staging (IC mainnet, Bitcoin testnet4)
61+
### Deploy to the IC network
5962
60-
```bash
61-
icp deploy -e staging
62-
```
63-
64-
### Production (IC mainnet, Bitcoin mainnet)
63+
The `ic` environment deploys to IC mainnet connected to Bitcoin testnet4, using `test_key_1`:
6564
6665
```bash
67-
icp deploy -e production
66+
icp deploy -e ic --cycles 30t
6867
```
6968
69+
> To deploy to Bitcoin mainnet, change the `init_args` for the `ic` environment in `icp.yaml` from `testnet` to `mainnet`. The canister automatically selects `key_1` (the production threshold signing key) when initialized with the `mainnet` variant.
70+
7071
## Environments
7172

7273
| Environment | IC network | Bitcoin network | Key |
7374
|-------------|-----------|----------------|-----|
7475
| `local` | local (PocketIC) | regtest | `test_key_1` |
75-
| `staging` | IC mainnet | testnet4 | `test_key_1` |
76-
| `production` | IC mainnet | mainnet | `key_1` |
76+
| `ic` | IC mainnet | testnet4 | `test_key_1` |
7777

7878
## Available functions
7979

motoko/basic_bitcoin/backend/app.mo

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,16 @@ actor class BasicBitcoin(network : Types.Network) {
1212

1313
/// The Bitcoin network to connect to.
1414
/// Passed as an init arg and determined by the environment:
15-
/// `regtest` (local), `testnet` (staging), or `mainnet` (production).
15+
/// `regtest` (local), `testnet` (ic — default), or `mainnet` (ic — production).
1616
let NETWORK : Types.Network = network;
1717

1818
/// The derivation path to use for ECDSA secp256k1 or Schnorr BIP340/BIP341 key
1919
/// derivation.
2020
transient let DERIVATION_PATH : [[Nat8]] = [];
2121

2222
// The ECDSA/Schnorr key name depends on which Bitcoin network this canister targets:
23-
// - "key_1" — Bitcoin mainnet on ICP mainnet
24-
// - "test_key_1" — Bitcoin testnet4 on ICP mainnet (staging) OR local regtest
23+
// - "key_1" — Bitcoin mainnet on ICP mainnet (production)
24+
// - "test_key_1" — Bitcoin testnet4 on ICP mainnet OR local regtest
2525
transient let KEY_NAME : Text = switch NETWORK {
2626
case (#mainnet) "key_1";
2727
case (#testnet or #regtest) "test_key_1";
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env bash
2+
set -e
3+
# Uses --pull to always fetch the latest base image.
4+
# Remove --pull if the Dockerfile is updated to pin the base image version.
5+
docker build --pull -t icp-cli-network-launcher-bitcoin .

motoko/basic_bitcoin/icp.yaml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,10 @@ environments:
1616
init_args:
1717
backend: "(variant { regtest })"
1818

19-
- name: staging
19+
- name: ic
2020
network: ic
21+
# Deploys to Bitcoin testnet4 using test_key_1 by default.
22+
# For Bitcoin mainnet, change to "(variant { mainnet })" — the canister
23+
# automatically switches to key_1 when initialized with the mainnet variant.
2124
init_args:
2225
backend: "(variant { testnet })"
23-
24-
- name: production
25-
network: ic
26-
init_args:
27-
backend: "(variant { mainnet })"

motoko/basic_bitcoin/test.sh

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#!/usr/bin/env bash
2+
set -e
3+
4+
IMAGE_NAME=icp-cli-network-launcher-bitcoin
5+
# Find the running container built from our custom image
6+
BITCOIN_CONTAINER=$(docker ps --filter "ancestor=${IMAGE_NAME}" --format "{{.ID}}" | head -1)
7+
8+
echo "=== Test 1: get_p2pkh_address returns a valid Bitcoin address ==="
9+
result=$(icp canister call backend get_p2pkh_address '()') && \
10+
echo "$result" && \
11+
echo "$result" | grep -q '"' && \
12+
echo "PASS" || (echo "FAIL" && exit 1)
13+
14+
echo "=== Test 2: get_p2tr_key_only_address returns a valid Bitcoin address ==="
15+
result=$(icp canister call backend get_p2tr_key_only_address '()') && \
16+
echo "$result" && \
17+
echo "$result" | grep -q '"' && \
18+
echo "PASS" || (echo "FAIL" && exit 1)
19+
20+
echo "=== Test 3: get_p2tr_address returns a valid Bitcoin address ==="
21+
result=$(icp canister call backend get_p2tr_address '()') && \
22+
echo "$result" && \
23+
echo "$result" | grep -q '"' && \
24+
echo "PASS" || (echo "FAIL" && exit 1)
25+
26+
echo "=== Test 4: get_current_fee_percentiles returns a vec ==="
27+
result=$(icp canister call backend get_current_fee_percentiles '()') && \
28+
echo "$result" && \
29+
echo "PASS" || (echo "FAIL" && exit 1)
30+
31+
echo "=== Mining 101 blocks to fund test address ==="
32+
[ -n "$BITCOIN_CONTAINER" ] || (echo "ERROR: network launcher container not running — run 'icp network start -d' first" && exit 1)
33+
addr=$(icp canister call backend get_p2pkh_address '()' | grep -o '"[^"]*"' | tr -d '"') && \
34+
docker exec "$BITCOIN_CONTAINER" bitcoin-cli -regtest \
35+
-rpcuser=ic-btc-integration -rpcpassword=ic-btc-integration \
36+
generatetoaddress 101 "$addr" > /dev/null && \
37+
echo "mined 101 blocks to $addr"
38+
39+
echo "=== Waiting for IC to sync Bitcoin blocks ==="
40+
_sync_addr=$(icp canister call backend get_p2pkh_address '()' | grep -o '"[^"]*"' | tr -d '"')
41+
# Poll get_utxos until it succeeds AND reports a non-zero tip_height.
42+
# Using get_utxos (not get_balance) because it reflects the definitive sync state:
43+
# get_balance can return stale non-zero values from previous runs while the bitcoin
44+
# integration canister is still syncing new blocks and rejecting all fresh calls.
45+
# The || { sleep; continue; } pattern retries on both rejection errors and zero height.
46+
_synced=false
47+
for _i in $(seq 1 60); do
48+
_result=$(icp canister call backend get_utxos "(\"$_sync_addr\")" 2>/dev/null) \
49+
|| { sleep 1; continue; }
50+
echo "$_result" | grep -qE 'tip_height = [1-9][0-9]*' \
51+
|| { sleep 1; continue; }
52+
echo "IC synced after ${_i}s"
53+
_synced=true
54+
break
55+
done
56+
[ "$_synced" = true ] || { echo "FAIL: IC did not sync within 60s"; exit 1; }
57+
58+
echo "=== Test 5: get_balance returns non-zero after mining ==="
59+
addr=$(icp canister call backend get_p2pkh_address '()' | grep -o '"[^"]*"' | tr -d '"') && \
60+
result=$(icp canister call backend get_balance "(\"$addr\")") && \
61+
echo "$result" && \
62+
echo "$result" | grep -qE '^\([1-9]' && \
63+
echo "PASS" || (echo "FAIL" && exit 1)
64+
65+
echo "=== Test 6: get_utxos returns synced chain state after mining ==="
66+
addr=$(icp canister call backend get_p2pkh_address '()' | grep -o '"[^"]*"' | tr -d '"') && \
67+
result=$(icp canister call backend get_utxos "(\"$addr\")") && \
68+
echo "$result" && \
69+
echo "$result" | grep -qE 'tip_height = [1-9][0-9]*' && \
70+
echo "PASS" || (echo "FAIL" && exit 1)
71+
72+
echo "=== All tests passed ==="

0 commit comments

Comments
 (0)