UNPKG

caravan-x

Version:

A terminal-based utility for managing Caravan multisig wallets in regtest mode. This tool simplifies development and testing with Caravan by providing an easy-to-use interface

151 lines (150 loc) 5.92 kB
{ "name": "Timelock Transaction Demo", "description": "This script demonstrates absolute and relative timelocks in Bitcoin transactions, showing how funds can be locked until a specific block height or time.", "version": "1.0.0", "variables": { "senderWallet": "timelock_sender", "receiverWallet": "timelock_receiver", "lockBlocks": 5, "amount": 1.0 }, "actions": [ { "type": "CREATE_WALLET", "description": "Create a sender wallet", "params": { "name": "${senderWallet}", "options": { "disablePrivateKeys": false, "descriptorWallet": true }, "variableName": "sender" } }, { "type": "CREATE_WALLET", "description": "Create a receiver wallet", "params": { "name": "${receiverWallet}", "options": { "disablePrivateKeys": false, "descriptorWallet": true }, "variableName": "receiver" } }, { "type": "MINE_BLOCKS", "description": "Mine blocks to fund sender wallet", "params": { "toWallet": "${senderWallet}", "count": 5, "variableName": "fundingBlocks" } }, { "type": "CUSTOM", "description": "Get current block height", "params": { "code": "const info = await context.rpcClient.callRpc('getblockchaininfo'); context.variables.currentHeight = info.blocks;", "variableName": "currentHeight" } }, { "type": "CUSTOM", "description": "Calculate lock height", "params": { "code": "context.variables.lockHeight = context.variables.currentHeight + context.variables.lockBlocks;", "variableName": "lockHeight" } }, { "type": "CUSTOM", "description": "Create an absolute timelock transaction (nLockTime)", "params": { "code": "const receiverAddress = await context.bitcoinService.getNewAddress(context.variables.receiverWallet); const txResult = await context.rpcClient.callRpc('createrawtransaction', [[], { [receiverAddress]: context.variables.amount }]); const fundResult = await context.rpcClient.callRpc('fundrawtransaction', [txResult, { locktime: context.variables.lockHeight }], context.variables.senderWallet); const signResult = await context.rpcClient.callRpc('signrawtransactionwithwallet', [fundResult.hex], context.variables.senderWallet); context.variables.timelockTx = signResult.hex; context.variables.receiverAddress = receiverAddress; return { hex: signResult.hex, lockHeight: context.variables.lockHeight, receiverAddress };", "variableName": "timelockTxResult" } }, { "type": "CUSTOM", "description": "Broadcast timelock transaction", "params": { "code": "try { const txid = await context.rpcClient.callRpc('sendrawtransaction', [context.variables.timelockTx]); context.transactions[txid] = { hex: context.variables.timelockTx, status: 'broadcasted', lockHeight: context.variables.lockHeight }; return txid; } catch (error) { console.log('Cannot broadcast timelock transaction yet: ' + error.message); return null; }", "variableName": "timelockTxid" } }, { "type": "WAIT", "description": "Wait briefly", "params": { "seconds": 1 } }, { "type": "CUSTOM", "description": "Attempt to verify transaction is in mempool (should fail due to timelock)", "params": { "code": "try { if (!context.variables.timelockTxid) throw new Error('Transaction was not broadcast'); const mempoolTxs = await context.rpcClient.callRpc('getrawmempool'); return mempoolTxs.includes(context.variables.timelockTxid); } catch (error) { console.log('Transaction not in mempool as expected: ' + error.message); return false; }", "variableName": "inMempool" } }, { "type": "ASSERT", "description": "Verify transaction is not in mempool due to timelock", "params": { "condition": "!context.variables.inMempool", "message": "Transaction should not be in mempool due to timelock" } }, { "type": "MINE_BLOCKS", "description": "Mine blocks until timelock expires", "params": { "toWallet": "${senderWallet}", "count": "${lockBlocks + 1}", "variableName": "maturingBlocks" } }, { "type": "CUSTOM", "description": "Broadcast timelock transaction now that locktime is reached", "params": { "code": "try { const txid = await context.rpcClient.callRpc('sendrawtransaction', [context.variables.timelockTx]); context.transactions[txid] = { hex: context.variables.timelockTx, status: 'broadcasted', lockHeight: context.variables.lockHeight }; return txid; } catch (error) { console.log('Error broadcasting: ' + error.message); return null; }", "variableName": "finalTxid" } }, { "type": "WAIT", "description": "Wait briefly for transaction to propagate", "params": { "seconds": 1 } }, { "type": "MINE_BLOCKS", "description": "Mine a block to confirm the transaction", "params": { "toWallet": "${receiverWallet}", "count": 1, "variableName": "confirmationBlock" } }, { "type": "CUSTOM", "description": "Verify receiver wallet received the funds", "params": { "code": "const walletInfo = await context.bitcoinService.getWalletInfo(context.variables.receiverWallet); return walletInfo.balance > 0;", "variableName": "receiverHasFunds" } }, { "type": "ASSERT", "description": "Confirm receiver got the funds after timelock expired", "params": { "condition": "context.variables.receiverHasFunds", "message": "Receiver wallet should have received funds after timelock expired" } } ] }