Hi everyone,
I’m currently testing a BTC to ckBTC and ckBTC to BTC conversion process using a canister on the Internet Computer (IC). While the BTC to ckBTC conversion works perfectly, I’m encountering an issue with the ckBTC to BTC conversion status.
Here are the details:
- Environment/Setup: I’m using the
CkBtcConverter
canister, interacting with it viadfx
commands, and running a local Bitcoin node withdocker-compose
for testing. My Bitcoin node is fully synced, and I’ve generated over 10,000 blocks (block height 10000+). - What I’ve done:
- Successfully converted BTC to ckBTC using
update_ckbtc_balance
. - Initiated ckBTC to BTC conversion using
convert_ckbtc_to_btc
(which internally callsretrieve_btc_with_approval
). The function returns “withdrawal successful with block index: 2”. - Generated additional blocks (101 blocks each time) to ensure transaction confirmation and packaging.
- Checked the status using
retrieve_btc_status
orretrieve_btc_status_v2
withblock_index = 2
.
- Successfully converted BTC to ckBTC using
- Expected result: I expected the status to update to “Confirmed” or a similar completed state after generating sufficient blocks.
- Actual result: The status remains
(variant { Pending })
or occasionally(variant { Signing })
, regardless of how many blocks I generate.
Below is my full test script and output for reference:
Deploy Script:
#!/bin/bash
CKBTC_MINTER_ID_TEST="ml52i-qqaaa-aaaar-qaaba-cai"
CKBTC_LEDGER_ID_TEST="mc6ru-gyaaa-aaaar-qaaaq-cai"
CKBTC_KYT_ID_TEST="pvm5g-xaaaa-aaaar-qaaia-cai"
CKBTC_INDEX_ID_TEST="mm444-5iaaa-aaaar-qaabq-cai"
PRINCIPAL="$(dfx identity get-principal)"
dfx deploy --specified-id $CKBTC_MINTER_ID_TEST ckbtc_minter --argument '(variant{
Init = record {
btc_network = variant { Regtest };
ledger_id = principal "'$CKBTC_LEDGER_ID_TEST'";
ecdsa_key_name = "dfx_test_key";
retrieve_btc_min_amount = 10_000;
max_time_in_queue_nanos = 10_000_000_000;
min_confirmations = opt 1;
mode = variant { GeneralAvailability };
kyt_fee = opt 100;
kyt_principal = opt principal "'$CKBTC_KYT_ID_TEST'"
}
})'
dfx deploy --specified-id $CKBTC_LEDGER_ID_TEST ckbtc_ledger --argument '(variant{
Init = record{
token_symbol = "ckBTC";
token_name = "Chain Ley Local Bitcoin";
minting_account = record { owner = principal "'$CKBTC_MINTER_ID_TEST'" };
transfer_fee = 10;
metadata = vec {};
max_memo_length = opt 80;
initial_balances = vec {};
archive_options = record {
num_blocks_to_archive = 10_000;
trigger_threshold = 20_000;
controller_id = principal "'$PRINCIPAL'";
cycles_for_archive_creation = opt 1_000_000_000_000;
max_message_size_bytes = null;
node_max_memory_size_bytes = opt 3_221_225_472;
};
feature_flags = opt record { icrc2 = true };
}
})'
dfx deploy --specified-id pvm5g-xaaaa-aaaar-qaaia-cai ckbtc_kyt --argument '(variant{
InitArg = record {
api_key = "";
minter_id = principal "'$CKBTC_MINTER_ID_TEST'";
maintainers = vec {principal "'$PRINCIPAL'" };
mode = variant { AcceptAll };
}
})'
dfx canister call ckbtc_kyt set_api_key '(record { api_key = "" })'
dfx deploy --specified-id $CKBTC_INDEX_ID_TEST ckbtc_index --argument '(opt variant{
Init = record{
ledger_id = principal "'$CKBTC_LEDGER_ID_TEST'";
}
})'
dfx deploy CkBtcConverter --argument '(principal "'$PRINCIPAL'", principal "'$CKBTC_MINTER_ID_TEST'", principal "'$CKBTC_LEDGER_ID_TEST'")'
Test Script:
#!/bin/bash
# Set colors
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[0;33m'
NC='\033[0m' # No Color
# Default wait time
WAIT_TIME=3
# Parse command line arguments
while getopts ":w:" opt; do
case $opt in
w)
WAIT_TIME=$OPTARG
echo -e "${YELLOW}Set wait time to: $WAIT_TIME seconds${NC}"
;;
\?)
echo -e "${RED}Invalid option: -$OPTARG${NC}" >&2
exit 1
;;
:)
echo -e "${RED}Option -$OPTARG requires an argument.${NC}" >&2
exit 1
;;
esac
done
echo -e "${YELLOW}=== Starting BTC and ckBTC Conversion Service Test ===${NC}"
# Get current user's Principal ID
PRINCIPAL=$(dfx identity get-principal)
echo -e "${YELLOW}Current user Principal: ${PRINCIPAL}${NC}"
# Check identity using whoami
echo -e "\n${YELLOW}Check identity using whoami${NC}"
dfx canister call CkBtcConverter whoami
# 1. Test getting BTC deposit address
echo -e "\n${YELLOW}1. Test getting BTC deposit address${NC}"
BTC_ADDRESS=$(dfx canister call CkBtcConverter get_btc_deposit_address | grep -o '"[^"]*"' | sed 's/"//g')
if [ -z "$BTC_ADDRESS" ]; then
echo -e "${RED}❌ Failed to get BTC address${NC}"
exit 1
else
echo -e "${GREEN}✅ Successfully got BTC address: ${BTC_ADDRESS}${NC}"
fi
# 2. Test checking initial BTC balance (should be 0)
echo -e "\n${YELLOW}2. Test checking initial BTC balance${NC}"
INITIAL_BALANCE=$(dfx canister call CkBtcConverter check_btc_balance)
echo "$INITIAL_BALANCE"
# Helper function to check if canister needs more time to initialize
check_canister_ready() {
local result="$1"
if echo "$result" | grep -q "is not ready"; then
echo -e "${YELLOW}Canister is not ready yet, waiting 10 seconds...${NC}"
sleep 10
return 1
elif echo "$result" | grep -q "Canister state is not fully synced"; then
echo -e "${YELLOW}Bitcoin node sync not complete, waiting 20 seconds...${NC}"
sleep 20
return 1
fi
return 0
}
# Check if initial result needs to wait for canister preparation
if ! check_canister_ready "$INITIAL_BALANCE"; then
echo -e "${YELLOW}Trying to check balance again...${NC}"
INITIAL_BALANCE=$(dfx canister call CkBtcConverter check_btc_balance)
echo "$INITIAL_BALANCE"
fi
# 3. Send bitcoin to BTC address
echo -e "\n${YELLOW}3. Send bitcoin to BTC address${NC}"
echo -e "${YELLOW}Generate one block and send mining reward to test address${NC}"
docker compose exec bitcoind bitcoin-cli generatetoaddress 1 "$BTC_ADDRESS"
echo -e "${YELLOW}Generate additional 101 blocks to ensure transaction confirmation${NC}"
docker compose exec bitcoind bitcoin-cli -generate 101
echo -e "${YELLOW}Wait ${WAIT_TIME} seconds to ensure transaction confirmation${NC}"
sleep $WAIT_TIME
# 4. Check BTC balance again (should have balance now)
echo -e "\n${YELLOW}4. Check BTC balance again${NC}"
BTC_BALANCE_RESULT=$(dfx canister call CkBtcConverter check_btc_balance)
echo "$BTC_BALANCE_RESULT"
# If sync error occurs, wait and retry
if echo "$BTC_BALANCE_RESULT" | grep -q "Canister state is not fully synced"; then
echo -e "${YELLOW}Bitcoin node sync not complete, waiting 10 seconds and retry...${NC}"
sleep 10
echo -e "${YELLOW}Trying to check balance again...${NC}"
BTC_BALANCE_RESULT=$(dfx canister call CkBtcConverter check_btc_balance)
echo "$BTC_BALANCE_RESULT"
fi
# 5. Execute BTC to ckBTC conversion
echo -e "\n${YELLOW}5. Execute BTC to ckBTC conversion${NC}"
# Get deposit fee
echo -e "${YELLOW}Check deposit fee (KYT fee)${NC}"
DEPOSIT_FEE=$(dfx canister call CkBtcConverter get_deposit_fee | grep -o '[0-9]\+' | head -1)
echo -e "${GREEN}KYT fee: ${DEPOSIT_FEE} satoshis${NC}"
# Call update_ckbtc_balance to update balance
CONVERSION_RESULT=$(dfx canister call CkBtcConverter update_ckbtc_balance)
# Check if contains "ok" variant
if echo "$CONVERSION_RESULT" | grep -q "variant { ok"; then
echo -e "${GREEN}✅ BTC to ckBTC conversion request successful${NC}"
RESULT_DETAIL=$(echo "$CONVERSION_RESULT" | grep -o '"[^"]*"' | sed 's/"//g')
if [ -n "$RESULT_DETAIL" ]; then
echo -e "${GREEN}Details: ${RESULT_DETAIL}${NC}"
fi
else
echo -e "${RED}❌ BTC to ckBTC conversion failed${NC}"
echo -e "${RED}Error details: ${CONVERSION_RESULT}${NC}"
# If sync state error, wait longer and retry
if echo "$CONVERSION_RESULT" | grep -q "Canister state is not fully synced"; then
echo -e "${YELLOW}Bitcoin node sync not complete, waiting 10 seconds and retry...${NC}"
sleep 10
echo -e "${YELLOW}Trying to update balance again...${NC}"
CONVERSION_RESULT=$(dfx canister call CkBtcConverter update_ckbtc_balance)
echo -e "${YELLOW}Retry result: ${CONVERSION_RESULT}${NC}"
fi
fi
# Generate 10 blocks to ensure transaction packaging
echo -e "${YELLOW}Generate 10 blocks to ensure transaction packaging${NC}"
docker compose exec bitcoind bitcoin-cli -generate 10
# 6. Check ckBTC balance
echo -e "\n${YELLOW}6. Check ckBTC balance${NC}"
dfx canister call CkBtcConverter check_ckbtc_balance
# --- Test ckBTC to BTC conversion ---
# 7. Prepare to execute ckBTC to BTC conversion (using test BTC address)
echo -e "\n${YELLOW}7. Prepare ckBTC to BTC conversion${NC}"
echo -e "${YELLOW}Generate a bitcoin address using bitcoin-cli for receiving BTC${NC}"
BTC_ADDRESS_RECEIVE=$(docker compose exec bitcoind bitcoin-cli getnewaddress | tr -d '\r\n')
echo -e "${GREEN}Receiving address: ${BTC_ADDRESS_RECEIVE}${NC}"
# Get ckBTC balance for withdrawal
CKBTC_BALANCE=$(dfx canister call CkBtcConverter check_ckbtc_balance | grep -o 'ok = [0-9_]\+ : nat' | sed 's/[^0-9]//g')
if [ -z "$CKBTC_BALANCE" ] || [ "$CKBTC_BALANCE" -eq 0 ]; then
echo -e "${RED}❌ No ckBTC balance available for conversion, skipping ckBTC to BTC conversion test${NC}"
echo -e "\n${GREEN}==== Test Completed ====${NC}"
exit 0
else
echo -e "${GREEN}Found ckBTC balance: ${CKBTC_BALANCE}${NC}"
fi
# Check conversion fee
echo -e "${YELLOW}Query conversion fee for ${CKBTC_BALANCE} satoshi${NC}"
FEE=$(dfx canister call CkBtcConverter get_conversion_fee "($CKBTC_BALANCE)" | grep -o '[0-9]\+' | head -1)
if [ -z "$FEE" ]; then
echo -e "${RED}❌ Unable to get conversion fee, using default value 30000 satoshis${NC}"
FEE=30000
else
echo -e "${GREEN}Fee: ${FEE} satoshis${NC}"
# Add 20000 satoshi safety margin, consistent with code check logic
FEE=$((FEE + 20000))
fi
# Calculate actual amount available for conversion (deducting fees and safety margin)
ACTUAL_AMOUNT=$((CKBTC_BALANCE - FEE))
echo -e "${YELLOW}Actual conversion amount after deducting fees: ${ACTUAL_AMOUNT} satoshis${NC}"
# Get minter info, check minimum withdrawal amount
MINTER_INFO=$(dfx canister call ckbtc_minter get_minter_info)
MIN_AMOUNT=$(echo "$MINTER_INFO" | grep -o 'retrieve_btc_min_amount = [0-9]\+' | grep -o '[0-9]\+')
echo -e "${YELLOW}Minimum conversion amount: ${MIN_AMOUNT} satoshis${NC}"
if [ "$ACTUAL_AMOUNT" -lt "$MIN_AMOUNT" ]; then
echo -e "${RED}❌ Conversion amount ${ACTUAL_AMOUNT} is less than minimum conversion amount ${MIN_AMOUNT}${NC}"
echo -e "${YELLOW}Using original amount ${CKBTC_BALANCE} to attempt conversion${NC}"
ACTUAL_AMOUNT=$CKBTC_BALANCE
else
echo -e "${GREEN}✅ Conversion amount meets minimum requirement${NC}"
fi
# Call correct method parameters (amount, btcAddress)
echo -e "${YELLOW}Execute conversion, amount: ${ACTUAL_AMOUNT} satoshi${NC}"
WITHDRAWAL_RESULT=$(dfx canister call CkBtcConverter convert_ckbtc_to_btc "($ACTUAL_AMOUNT, \"$BTC_ADDRESS_RECEIVE\")")
# Check if contains "ok" variant
if echo "$WITHDRAWAL_RESULT" | grep -q "variant { ok"; then
RESULT_DETAIL=$(echo "$WITHDRAWAL_RESULT" | grep -o '"[^"]*"' | sed 's/"//g')
echo -e "${GREEN}✅ ckBTC to BTC conversion request successful${NC}"
if [ -n "$RESULT_DETAIL" ]; then
echo -e "${GREEN}Details: ${RESULT_DETAIL}${NC}"
fi
# Immediately generate 10 blocks to ensure transaction packaging
echo -e "${YELLOW}Generate 10 blocks to ensure conversion transaction packaging${NC}"
docker compose exec bitcoind bitcoin-cli -generate 10
else
echo -e "${RED}❌ ckBTC to BTC conversion failed${NC}"
echo -e "${RED}Error details: ${WITHDRAWAL_RESULT}${NC}"
fi
# 8. Wait a few seconds for conversion to complete
echo -e "\n${YELLOW}8. Wait $((WAIT_TIME*2)) seconds for conversion process to complete${NC}"
sleep $((WAIT_TIME*2))
# 9. Check BTC and ckBTC balances again
echo -e "\n${YELLOW}9.1 Check BTC balance again${NC}"
dfx canister call CkBtcConverter check_btc_balance
# Check BTC balance by address
echo -e "\n${YELLOW}9.2 Check BTC balance by address${NC}"
dfx canister call CkBtcConverter check_btc_balance_by_address "(\"$BTC_ADDRESS_RECEIVE\")"
echo -e "\n${YELLOW}9.3 Check ckBTC balance again${NC}"
dfx canister call CkBtcConverter check_ckbtc_balance
echo -e "\n${GREEN}==== Test Completed ====${NC}"
Test Output:
./test/test_converter_en.sh
=== Starting BTC and ckBTC Conversion Service Test ===
Current user Principal: ab3e7-qglzn-ag67t-wvpya-flrwq-g2ljv-soxgq-igjge-m3bxe-g5mlg-lqe
Check identity using whoami
(principal "ab3e7-qglzn-ag67t-wvpya-flrwq-g2ljv-soxgq-igjge-m3bxe-g5mlg-lqe")
1. Test getting BTC deposit address
✅ Successfully got BTC address: bcrt1q094qml0p8cghz9t2zsdj37cqvq3tvenzxv5d5k
2. Test checking initial BTC balance
(variant { ok = 0 : nat64 })
3. Send bitcoin to BTC address
Generate one block and send mining reward to test address
[
"7646f2154ec23c0d881a4d558550e61f1202c43b9a27b9669d7cd46800bb292e"
]
Generate additional 101 blocks to ensure transaction confirmation
{
"address": "bcrt1q9p0pc0zfhme95hur3kgfz9jg842l6nn9nk93hx",
"blocks": [
"253cb8b5cb186f062e6b1332290c9fdb526d70621e5e177b35465ea81f17bec1",
"5a2e286e255a4446e0243c807fb95e5ce4ad6284a9a6b8184a667f11bbee80d1",
....
"3ccd9fec12471457a65fb9eff6a2678aa8fd1c5869307ef1e4d6fed3a40e9177"
]
}
Wait 3 seconds to ensure transaction confirmation
4. Check BTC balance again
(
variant {
err = variant {
Other = "IC0503: Error from Canister g4xu7-jiaaa-aaaan-aaaaq-cai: Canister called `ic0.trap` with message: \'Panicked at \'Canister state is not fully synced.\', canister/src/lib.rs:252:13\'.\nConsider gracefully handling failures from this canister or altering the canister to handle exceptions. See documentation: https://internetcomputer.org/docs/current/references/execution-errors#trapped-explicitly"
}
},
)
Bitcoin node sync not complete, waiting 10 seconds and retry...
Trying to check balance again...
(variant { ok = 5_000_000_000 : nat64 })
5. Execute BTC to ckBTC conversion
Check deposit fee (KYT fee)
KYT fee: 100 satoshis
✅ BTC to ckBTC conversion request successful
Details: Minting successful: 4999999900
Generate 10 blocks to ensure transaction packaging
{
"address": "bcrt1qufc3fjzsxk6z3kyessq7p7z35qssfxafqh3hkm",
"blocks": [
"3f97410f94a5ff36a51c3ea34978ef0a15531238657a754e59a132128c525fd3",
...
"7f9070a85427e5dfe102f5f9f00339b9c4e755065c4e5980eef91cb8a14690c9",
"21f491f84044525f16cd63b1812bb767f500d9eb12f3a6f18d97774561958632"
]
}
6. Check ckBTC balance
(variant { ok = 4_999_999_900 : nat })
7. Prepare ckBTC to BTC conversion
Generate a bitcoin address using bitcoin-cli for receiving BTC
Receiving address: bcrt1q36ep66wrllengklw99t7uaw4swcrac3tad0yqu
Found ckBTC balance: 4999999900
Query conversion fee for 4999999900 satoshi
Fee: 412 satoshis
Actual conversion amount after deducting fees: 4999979488 satoshis
Minimum conversion amount: 10 satoshis
✅ Conversion amount meets minimum requirement
Execute conversion, amount: 4999979488 satoshi
✅ ckBTC to BTC conversion request successful
Details: withdrawal successful with block index: 2
Generate 10 blocks to ensure conversion transaction packaging
{
"address": "bcrt1qz6wwec4uffez8g2a4tsclgwdy34er258k6tw3k",
"blocks": [
"2d32eb6f6285fa85f92943b08d359378efa99a0e1467ef2e7e563cc5675e2d8b",
....
....
"347b44381d99dab7218b7f36c42bda9669d06527b5d09db5e6731888d7456d22"
]
}
8. Wait 6 seconds for conversion process to complete
9.1 Check BTC balance again
(variant { ok = 5_000_000_000 : nat64 })
9.2 Check BTC balance by address
(variant { ok = 0 : nat64 })
9.3 Check ckBTC balance again
(variant { ok = 20_402 : nat })
==== Test Completed ====
❯ dfx canister call ckbtc_minter retrieve_btc_status_v2
This method requires arguments.
Enter field block_index : nat64
✔ Enter a nat64 · 2
Sending the following argument:
(record { block_index = 2 : nat64 })
Do you want to send this message? [y/N]
y
(variant { Pending })
❯ docker compose exec bitcoind bitcoin-cli -generate 101
{
"address": "bcrt1q5eg977afunpl3g8h52pu90kdkm4qx04dxgzjv4",
"blocks": [
"688dcf0f5b8f71db1962aaa51c7db9417e8b412a1fc9d7f2f17dbbd680a763da",
...
...
"314b568919587e1082f782850054dc8768be68bd52f4899d99ee658a9450ea65",
"6eef827ea6293912dd511bb7b759e423e8c52eb3ec6bf400eb8d839acf4feefb"
]
}
❯ dfx canister call ckbtc_minter retrieve_btc_status_v2
This method requires arguments.
Enter field block_index : nat64
✔ Enter a nat64 · 2
Sending the following argument:
(record { block_index = 2 : nat64 })
Do you want to send this message? [y/N]
y
(variant { Pending })
Additional Observations:
- My Bitcoin balance at the receiving address (
bcrt1q36ep66wrllengklw99t7uaw4swcrac3tad0yqu
) remains 0 when checked viacheck_btc_balance_by_address
. - The ckBTC balance has been deducted correctly (
20,402 satoshis
remaining after conversion). - I’ve waited several seconds (and even longer) between status checks, but the status does not update.
Has anyone encountered a similar issue with retrieve_btc_status
or retrieve_btc_status_v2
staying in “Pending” or “Signing” state during ckBTC to BTC conversion? Could this be related to the local Bitcoin node setup, canister state, or something else on the IC side? Any guidance on how to debug or resolve this would be greatly appreciated!