A complete regtest walkthrough demonstrating an L2 → L1 swap from start to finish.
End Goal: Alice ends up with parentchain coins (received from Bob). Bob ends up with sidechain coins (claimed from Alice's swap offer).
Paste these once per terminal session:
export BITCOIN_DIR="/path/to/bitcoin-patched/build/bin"
export BITCOIND="$BITCOIN_DIR/bitcoind"
export BITCOIN_CLI="$BITCOIN_DIR/bitcoin-cli"
export ENFORCER="/path/to/bip300301_enforcer/target/debug/bip300301_enforcer"
export RPC_USER="user"
export RPC_PASSWORD="passwordDC"
# Mainchain regtest (sidechain activation)
export MAINCHAIN_RPC_PORT="18443"
export MAINCHAIN_P2P_PORT="38333"
export MAINCHAIN_DATADIR="$HOME/coinshift-mainchain-data"
export MAINCHAIN_WALLET="mainchainwallet"
# Parentchain regtest (swap transactions)
export PARENTCHAIN_RPC_PORT="18444"
export PARENTCHAIN_P2P_PORT="38334"
export PARENTCHAIN_DATADIR="$HOME/coinshift-parentchain-data"
export PARENTCHAIN_WALLET="parentchainwallet"
# Enforcer
export ENFORCER_GRPC_PORT="50051"
export ENFORCER_GRPC_ADDR="127.0.0.1:$ENFORCER_GRPC_PORT"
# ZMQ
export ZMQ_SEQUENCE="tcp://127.0.0.1:29000"
export ZMQ_HASHBLOCK="tcp://127.0.0.1:29001"
export ZMQ_HASHTX="tcp://127.0.0.1:29002"
export ZMQ_RAWBLOCK="tcp://127.0.0.1:29003"
export ZMQ_RAWTX="tcp://127.0.0.1:29004"
# Coinshift RPC
export COINSHIFT_RPC_URL="http://127.0.0.1:6255"
mkdir -p "$MAINCHAIN_DATADIR" "$PARENTCHAIN_DATADIR"
"$BITCOIND" -regtest \
-fallbackfee=0.0002 \
-rpcuser="$RPC_USER" -rpcpassword="$RPC_PASSWORD" \
-rpcport="$MAINCHAIN_RPC_PORT" \
-server -txindex -rest \
-zmqpubsequence="$ZMQ_SEQUENCE" \
-zmqpubhashblock="$ZMQ_HASHBLOCK" \
-zmqpubhashtx="$ZMQ_HASHTX" \
-zmqpubrawblock="$ZMQ_RAWBLOCK" \
-zmqpubrawtx="$ZMQ_RAWTX" \
-listen -port="$MAINCHAIN_P2P_PORT" \
-datadir="$MAINCHAIN_DATADIR" -daemon
Wait for RPC, then create wallet and mine 101 blocks:
"$BITCOIN_CLI" -regtest -rpcwait \
-rpcuser="$RPC_USER" -rpcpassword="$RPC_PASSWORD" \
-rpcport="$MAINCHAIN_RPC_PORT" -datadir="$MAINCHAIN_DATADIR" \
createwallet "$MAINCHAIN_WALLET" || true
ADDR=$("$BITCOIN_CLI" -regtest -rpcwait \
-rpcuser="$RPC_USER" -rpcpassword="$RPC_PASSWORD" \
-rpcport="$MAINCHAIN_RPC_PORT" -datadir="$MAINCHAIN_DATADIR" \
-rpcwallet="$MAINCHAIN_WALLET" getnewaddress)
"$BITCOIN_CLI" -regtest -rpcwait \
-rpcuser="$RPC_USER" -rpcpassword="$RPC_PASSWORD" \
-rpcport="$MAINCHAIN_RPC_PORT" -datadir="$MAINCHAIN_DATADIR" \
generatetoaddress 101 "$ADDR"
"$BITCOIND" -regtest \
-fallbackfee=0.0002 \
-rpcuser="$RPC_USER" -rpcpassword="$RPC_PASSWORD" \
-rpcport="$PARENTCHAIN_RPC_PORT" \
-server -txindex -rest \
-listen -port="$PARENTCHAIN_P2P_PORT" \
-datadir="$PARENTCHAIN_DATADIR" -daemon
Create wallet and mine 101 blocks on the parentchain too.
"$ENFORCER" \
--node-rpc-addr=127.0.0.1:"$MAINCHAIN_RPC_PORT" \
--node-rpc-user="$RPC_USER" \
--node-rpc-pass="$RPC_PASSWORD" \
--node-zmq-addr-sequence="$ZMQ_SEQUENCE" \
--serve-grpc-addr "$ENFORCER_GRPC_ADDR" \
--enable-wallet \
--wallet-sync-source=disabled
grpcurl -plaintext -d @ "$ENFORCER_GRPC_ADDR" \
cusf.mainchain.v1.WalletService/CreateSidechainProposal \
< docs/create_sidechain_proposal.json
Mine additional blocks to activate the sidechain.
The coinshift app needs to connect to the parentchain to monitor swap transactions. Configure via the GUI (L1 Config) or config file:
http://127.0.0.1:18444userpasswordDCThen start the coinshift sidechain node:
./target/release/coinshift --network regtest
Alice needs L2 coins before she can create a swap. Get her deposit address and fund it:
# Get deposit address from coinshift
DEPOSIT_ADDRESS=$(curl -s -X POST "$COINSHIFT_RPC_URL" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":10,"method":"format_deposit_address","params":[]}' \
| jq -r '.result')
# Send coins to deposit address on mainchain
"$BITCOIN_CLI" -regtest -rpcwait \
-rpcuser="$RPC_USER" -rpcpassword="$RPC_PASSWORD" \
-rpcport="$MAINCHAIN_RPC_PORT" -datadir="$MAINCHAIN_DATADIR" \
-rpcwallet="$MAINCHAIN_WALLET" \
sendtoaddress "$DEPOSIT_ADDRESS" 1.0
# Mine blocks to process the deposit (BIP-300)
"$BITCOIN_CLI" -regtest -rpcwait \
-rpcuser="$RPC_USER" -rpcpassword="$RPC_PASSWORD" \
-rpcport="$MAINCHAIN_RPC_PORT" -datadir="$MAINCHAIN_DATADIR" \
generatetoaddress 6 "$DEPOSIT_ADDRESS"
Check Alice's L2 balance:
curl -s -X POST "$COINSHIFT_RPC_URL" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":12,"method":"get_balance","params":[]}'
Alice offers her L2 coins in exchange for parentchain coins:
# Get Alice's parentchain address
ALICE_PARENTCHAIN_ADDR=$("$BITCOIN_CLI" -regtest -rpcwait \
-rpcuser="$RPC_USER" -rpcpassword="$RPC_PASSWORD" \
-rpcport="$PARENTCHAIN_RPC_PORT" -datadir="$PARENTCHAIN_DATADIR" \
-rpcwallet="$PARENTCHAIN_WALLET" getnewaddress)
# Create the swap
curl -s -X POST "$COINSHIFT_RPC_URL" \
-H "Content-Type: application/json" \
-d "{
\"jsonrpc\":\"2.0\",\"id\":1,
\"method\":\"create_swap\",
\"params\":{
\"parent_chain\":\"Regtest\",
\"l1_recipient_address\":\"$ALICE_PARENTCHAIN_ADDR\",
\"l1_amount_sats\":5000000,
\"l2_recipient\":null,
\"l2_amount_sats\":10000000,
\"required_confirmations\":1,
\"fee_sats\":1000
}
}"
Save the swap_id from the response. Mine a mainchain block to include it:
SWAP_ID="swap_id_from_response" # Replace with actual value
Bob sends the exact l1_amount to Alice's parentchain address:
"$BITCOIN_CLI" -regtest -rpcwait \
-rpcuser="$RPC_USER" -rpcpassword="$RPC_PASSWORD" \
-rpcport="$PARENTCHAIN_RPC_PORT" -datadir="$PARENTCHAIN_DATADIR" \
-rpcwallet="$PARENTCHAIN_WALLET" \
sendtoaddress "$ALICE_PARENTCHAIN_ADDR" 0.05
# Mine a block to confirm Bob's payment
"$BITCOIN_CLI" -regtest -rpcwait \
-rpcuser="$RPC_USER" -rpcpassword="$RPC_PASSWORD" \
-rpcport="$PARENTCHAIN_RPC_PORT" -datadir="$PARENTCHAIN_DATADIR" \
generatetoaddress 1 "$BOB_PARENTCHAIN_ADDR"
Mine a mainchain block to trigger 2WPD processing, which detects Bob's parentchain transaction:
# Trigger 2WPD
"$BITCOIN_CLI" -regtest -rpcwait \
-rpcuser="$RPC_USER" -rpcpassword="$RPC_PASSWORD" \
-rpcport="$MAINCHAIN_RPC_PORT" -datadir="$MAINCHAIN_DATADIR" \
generatetoaddress 1 "$ADDR"
sleep 2
# Check swap status (should be ReadyToClaim)
curl -s -X POST "$COINSHIFT_RPC_URL" \
-H "Content-Type: application/json" \
-d "{\"jsonrpc\":\"2.0\",\"id\":4,\"method\":\"get_swap_status\",\"params\":{\"swap_id\":\"$SWAP_ID\"}}"
curl -s -X POST "$COINSHIFT_RPC_URL" \
-H "Content-Type: application/json" \
-d "{
\"jsonrpc\":\"2.0\",\"id\":5,
\"method\":\"claim_swap\",
\"params\":{
\"swap_id\":\"$SWAP_ID\",
\"l2_claimer_address\":\"$BOB_L2_ADDRESS\"
}
}"
Mine another mainchain block and verify the swap is Completed.
# Alice's parentchain balance (should have received Bob's payment)
"$BITCOIN_CLI" -regtest -rpcwait \
-rpcuser="$RPC_USER" -rpcpassword="$RPC_PASSWORD" \
-rpcport="$PARENTCHAIN_RPC_PORT" -datadir="$PARENTCHAIN_DATADIR" \
-rpcwallet="$PARENTCHAIN_WALLET" \
getreceivedbyaddress "$ALICE_PARENTCHAIN_ADDR"
# List all swaps
curl -s -X POST "$COINSHIFT_RPC_URL" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":9,"method":"list_swaps","params":[]}'
Expected final state: Alice has parentchain coins (from Bob). Bob has sidechain coins (claimed from swap). Swap is marked
Completed. Locked L2 outputs are released.
# Stop mainchain
"$BITCOIN_CLI" -regtest -rpcwait \
-rpcuser="$RPC_USER" -rpcpassword="$RPC_PASSWORD" \
-rpcport="$MAINCHAIN_RPC_PORT" -datadir="$MAINCHAIN_DATADIR" stop || true
# Stop parentchain
"$BITCOIN_CLI" -regtest -rpcwait \
-rpcuser="$RPC_USER" -rpcpassword="$RPC_PASSWORD" \
-rpcport="$PARENTCHAIN_RPC_PORT" -datadir="$PARENTCHAIN_DATADIR" stop || true
# Stop enforcer
pkill -f "bip300301_enforcer" || true