|
| 1 | +--- |
| 2 | +sidebar_position: 5 |
| 3 | +--- |
| 4 | + |
| 5 | +# Interact with a Deployed Contract |
| 6 | + |
| 7 | +## Overview |
| 8 | +In this tutorial, we will interact with a deployed smart contract by calling its public method, in a separate process or by a different party. |
| 9 | + |
| 10 | +To do this, we need to create a smart contract instance that corresponds to the deployed contract on chain. |
| 11 | + |
| 12 | +## The Smart Contract |
| 13 | + |
| 14 | +We will reuse the stateful `Counter` contract [from a previous step](../how-to-write-a-contract/stateful-contract#create-a-stateful-contract). |
| 15 | + |
| 16 | +```ts |
| 17 | + |
| 18 | +export interface CounterState extends StructObject { |
| 19 | + count: Int32; |
| 20 | +} |
| 21 | + |
| 22 | +export class Counter extends SmartContract<CounterState> { |
| 23 | + @method() |
| 24 | + public increase() { |
| 25 | + this.state.count++; |
| 26 | + |
| 27 | + this.appendStateOutput( |
| 28 | + // new output of the contract |
| 29 | + TxUtils.buildOutput(this.ctx.spentScript, this.ctx.spentAmount), |
| 30 | + // new state hash of the contract |
| 31 | + Counter.stateHash(this.state), |
| 32 | + ); |
| 33 | + |
| 34 | + const outputs = this.buildStateOutputs() + this.buildChangeOutput(); |
| 35 | + |
| 36 | + assert(this.checkOutputs(outputs), 'Outputs mismatch with the transaction context') |
| 37 | + } |
| 38 | +} |
| 39 | +``` |
| 40 | + |
| 41 | +## Deploy |
| 42 | + |
| 43 | +To deploy the smart contract by calling the `deplly()` function: |
| 44 | + |
| 45 | +```ts |
| 46 | +const counter = new Counter(); |
| 47 | +counter.state = { |
| 48 | + count: 0n |
| 49 | +} |
| 50 | +const covenant = StatefulCovenant.createCovenant(counter) |
| 51 | +const provider = getDefaultProvider() |
| 52 | +const signer = getDefaultSigner() |
| 53 | +const deployTx = await deploy(signer, provider, covenant) |
| 54 | +console.log(`Counter deployed: ${deployTx.id}, the count is: ${counter.state.count}`) |
| 55 | +``` |
| 56 | + |
| 57 | +The function deploys the contract with a initial state `0n` and returns the transaction of the deployed contract. |
| 58 | + |
| 59 | +## Interact with call feature |
| 60 | + |
| 61 | +Next, we update our deployed smart contract by calling the `call()` function: |
| 62 | + |
| 63 | +```ts |
| 64 | +const newCovenant = covenant.next({ count: covenant.state.count + 1n }); |
| 65 | + |
| 66 | +const callTx = await call(signer, provider, covenant, { |
| 67 | + invokeMethod: (contract: Counter) => { |
| 68 | + contract.increase() |
| 69 | + }, |
| 70 | +}, { |
| 71 | + covenant: newCovenant, |
| 72 | + satoshis: 330 |
| 73 | +}) |
| 74 | + |
| 75 | + |
| 76 | +console.log(`Counter contract called: ${callTx.getId()}`) |
| 77 | +covenant = newCovenant; |
| 78 | +``` |
| 79 | + |
| 80 | +This function will help you construct transactions to call the covenant. It takes *5* parameters: |
| 81 | + |
| 82 | + |
| 83 | +1. a [signer](../how-to-deploy-and-call-a-contract#signer) - *required* |
| 84 | +2. a [provider](../how-to-deploy-and-call-a-contract#provider) - *required* |
| 85 | +3. a covenant to be called - *required* |
| 86 | +4. a subContractCall - used to specify which contract to use for unlocking - *required* |
| 87 | +5. a new covenant which can be get by `covenant.next(new state)` and its locked satoshis, - *optional* |
| 88 | + |
| 89 | + |
| 90 | +Let's encapsulate the entire process within a main function, designed to deploy the contract and increment its value five times: |
| 91 | + |
| 92 | +```ts |
| 93 | +async function main() { |
| 94 | + |
| 95 | + const counter = new Counter(); |
| 96 | + counter.state = { |
| 97 | + count: 0n |
| 98 | + } |
| 99 | + |
| 100 | + let covenant = StatefulCovenant.createCovenant(counter) |
| 101 | + |
| 102 | + const provider = getDefaultProvider() |
| 103 | + const signer = getDefaultSigner() |
| 104 | + |
| 105 | + const deployTx = await deploy(signer, provider, covenant) |
| 106 | + |
| 107 | + console.log(`Counter contract deployed: ${deployTx.getId()}`) |
| 108 | + |
| 109 | + |
| 110 | + for (let i = 0; i < 10; i++) { |
| 111 | + const newCovenant = covenant.next({ count: covenant.state.count + 1n }); |
| 112 | + |
| 113 | + await sleep(5) |
| 114 | + |
| 115 | + const callTx = await call(signer, provider, covenant, { |
| 116 | + invokeMethod: (contract: Counter) => { |
| 117 | + contract.increase() |
| 118 | + }, |
| 119 | + }, { |
| 120 | + covenant: newCovenant, |
| 121 | + satoshis: 330 |
| 122 | + }) |
| 123 | + |
| 124 | + |
| 125 | + console.log(`Counter contract called: ${callTx.getId()}`) |
| 126 | + covenant = newCovenant; |
| 127 | + } |
| 128 | +} |
| 129 | + |
| 130 | +(async () => { |
| 131 | + await main() |
| 132 | +})() |
| 133 | +``` |
| 134 | + |
| 135 | +If we execute the code, we should get an output similar to the following: |
| 136 | + |
| 137 | +```ts |
| 138 | +Counter deployed: 1b1c09383f6a483dabf138111cf80e6921cc2050ffdbb6c7493f47a2c3759180, the count is: 0 |
| 139 | +Counter covenant called: ec07e45c9114ce2b4f439414f0c79e13e90e3157f6dae6c1e66510d7f2cecc6c, the count now is: 1 |
| 140 | +Counter covenant called: f65967b0cfe84e7e8c7b54bda2ce6f216177f87bbc95561044470321f435c07c, the count now is: 2 |
| 141 | +Counter covenant called: e4f0862c62a6c42e10097c0e1b975710f3a4ef0f768a694e5edc7c4bd20997eb, the count now is: 3 |
| 142 | +Counter covenant called: 68da33af2c64657de41dfcd9c525f3f8c37cffd28be8a4a5374bc8ea31e8f7b5, the count now is: 4 |
| 143 | +Counter covenant called: 24033524f0bceb18172f9bbb4c3baecdcf8f04e233bd13ed27c9061e0f224d4d, the count now is: 5 |
| 144 | +``` |
| 145 | + |
| 146 | +## Interact with customize transaction |
| 147 | + |
| 148 | +For more complex covenants, you may need to build your own transaction to complete the call to covenant. The following code shows how to construct a transaction to call the `Counter` covenant: |
| 149 | + |
| 150 | +```ts |
| 151 | +const contract = new Counter() |
| 152 | +contract.state = { count: 0n }; |
| 153 | +const covenant = StatefulCovenant.createCovenant(contract) |
| 154 | +const provider = getDefaultProvider() |
| 155 | +const signer = getDefaultSigner() |
| 156 | +const deployTx = await deploy(signer, provider, covenant) |
| 157 | +console.log(`Counter deployed: ${deployTx.id}, the count is: ${contract.state.count}`) |
| 158 | + |
| 159 | + |
| 160 | +// create a new covenant containing new states |
| 161 | +const newCovenant = covenant.next({ count: covenant.state.count + 1n }); |
| 162 | + |
| 163 | +const address = await signer.getAddress(); |
| 164 | + |
| 165 | +const feeRate = await provider.getFeeRate(); |
| 166 | + |
| 167 | +// fetch utxos to pay fee |
| 168 | +const utxos = await provider.getUtxos(address); |
| 169 | + |
| 170 | +// build transaction |
| 171 | +const psbt = new ExtPsbt().addCovenantInput(covenant).spendUTXO(utxos); |
| 172 | + |
| 173 | +// add Counter covenant output |
| 174 | +psbt.addCovenantOutput(newCovenant, 330); |
| 175 | + |
| 176 | +// update unlocking script |
| 177 | +psbt.updateCovenantInput(0, covenant, { |
| 178 | + invokeMethod: (contract: Counter) => { |
| 179 | + contract.increase() |
| 180 | + }, |
| 181 | +}); |
| 182 | + |
| 183 | +// add change output |
| 184 | +psbt.change(address, feeRate).seal(); |
| 185 | + |
| 186 | +// get sign options |
| 187 | +const options = psbt.psbtOptions() || { |
| 188 | + autoFinalized: false, |
| 189 | + toSignInputs: [], |
| 190 | +}; |
| 191 | + |
| 192 | +utxos.forEach((_, index) => { |
| 193 | + options.toSignInputs.push({ |
| 194 | + index: index + 1, |
| 195 | + address: address, |
| 196 | + }); |
| 197 | +}); |
| 198 | + |
| 199 | +// request sign |
| 200 | +const signedPsbtHex = await signer.signPsbt(psbt.toHex(), options); |
| 201 | +// combine signature |
| 202 | +const signedPsbt = psbt.combine(ExtPsbt.fromHex(signedPsbtHex)).finalizeAllInputs(); |
| 203 | +// extract psbt to Transaction |
| 204 | +const callTx = signedPsbt.extractTransaction(); |
| 205 | +// broadcast transaction to finish the call |
| 206 | +await provider.broadcast(callTx.toHex()); |
| 207 | + |
| 208 | +console.log(`Counter covenant called: ${callTx.getId()}`) |
| 209 | +``` |
| 210 | + |
| 211 | +The above code uses `ExtPsbt` to build transactions. `ExtPsbt` is an extension class of [Psbt](../references/bitcoinjs-lib/classes/Psbt). |
| 212 | + |
| 213 | +`Psbt` class can parse and generate a PSBT binary based off of the BIP174. |
| 214 | + |
| 215 | +## Conclusion |
| 216 | + |
| 217 | +Congratulations! You've now deployed AND interacted with a Bitcoin smart contract. |
0 commit comments