Spaces:
Runtime error
Runtime error
| sidebar_position: 3 | |
| # Call Multiple Contracts in a Single Tx | |
| Up to now, we have only shown how to call one smart contract in a transaction. That is, only one input of the tx spends a smart contract UTXO, and the other inputs, if any, spend Pay-to-Public-Key-Hash ([P2PKH](https://learnmeabitcoin.com/guide/p2pkh)) UTXOs, which are generally NOT regarded as smart contracts. | |
| There are cases where it is desirable to spend multiple smart contract UTXOs in different inputs of a tx. | |
| The main differences from [calling a single contract](../how-to-deploy-and-call-a-contract/how-to-deploy-and-call-a-contract.md#contract-call) are: | |
| 1. Set `multiContractCall = true` in `MethodCallOptions` | |
| 2. Each call may only return a partial/incomplete transaction, instead of a complete transaction | |
| 3. A partial tx has to be passed as `ContractTransaction` in `MethodCallOptions` in subsequent calls | |
| 4. Finally invoke `SmartContract.multiContractCall(partialContractTx: ContractTransaction, signer: Signer)` to sign and broadcast the complete transaction | |
| The following is an [example code](https://github.com/sCrypt-Inc/boilerplate/blob/master/tests/testnet/multi_contracts_call.ts) of calling two contracts at the same time: | |
| ```ts | |
| import { Counter } from '../../src/contracts/counter' | |
| import { getDefaultSigner } from '../utils/helper' | |
| import { HashPuzzle } from '../../src/contracts/hashPuzzle' | |
| async function main() { | |
| await Counter.compile() | |
| await HashPuzzle.compile() | |
| const signer = getDefaultSigner() | |
| let counter = new Counter(1n) | |
| // connect to a signer | |
| await counter.connect(signer) | |
| // contract deployment | |
| const deployTx = await counter.deploy(1) | |
| console.log('Counter contract deployed: ', deployTx.id) | |
| counter.bindTxBuilder( | |
| 'incrementOnChain', | |
| ( | |
| current: Counter, | |
| options: MethodCallOptions<Counter>, | |
| ...args: any | |
| ): Promise<ContractTransaction> => { | |
| // create the next instance from the current | |
| const nextInstance = current.next() | |
| // apply updates on the next instance locally | |
| nextInstance.count++ | |
| const tx = new bsv.Transaction() | |
| tx.addInput(current.buildContractInput(options.fromUTXO)).addOutput( | |
| new bsv.Transaction.Output({ | |
| script: nextInstance.lockingScript, | |
| satoshis: current.balance, | |
| }) | |
| ) | |
| return Promise.resolve({ | |
| tx: tx, | |
| atInputIndex: 0, | |
| nexts: [ | |
| { | |
| instance: nextInstance, | |
| balance: current.balance, | |
| atOutputIndex: 0, | |
| }, | |
| ], | |
| }) | |
| } | |
| ) | |
| const plainText = 'abc' | |
| const byteString = toByteString(plainText, true) | |
| const sha256Data = sha256(byteString) | |
| const hashPuzzle = new HashPuzzle(sha256Data) | |
| // connect to a signer | |
| await hashPuzzle.connect(signer) | |
| const deployTx1 = await hashPuzzle.deploy(1) | |
| console.log('HashPuzzle contract deployed: ', deployTx1.id) | |
| hashPuzzle.bindTxBuilder( | |
| 'unlock', | |
| ( | |
| current: HashPuzzle, | |
| options: MethodCallOptions<HashPuzzle>, | |
| ...args: any | |
| ): Promise<ContractTransaction> => { | |
| if (options.partialContractTx) { | |
| const unSignedTx = options.partialContractTx.tx | |
| unSignedTx.addInput( | |
| current.buildContractInput(options.fromUTXO) | |
| ) | |
| return Promise.resolve({ | |
| tx: unSignedTx, | |
| atInputIndex: 1, | |
| nexts: [], | |
| }) | |
| } | |
| throw new Error('no partialContractTx found') | |
| } | |
| ) | |
| const partialTx = await counter.methods.incrementOnChain({ | |
| multiContractCall: true, | |
| } as MethodCallOptions<Counter>) | |
| const finalTx = await hashPuzzle.methods.unlock( | |
| byteString, | |
| { | |
| multiContractCall: true, | |
| partialContractTx: partialTx, | |
| } as MethodCallOptions<HashPuzzle> | |
| ) | |
| const { tx: callTx, nexts } = await SmartContract.multiContractCall( | |
| finalTx, | |
| signer | |
| ) | |
| console.log('Counter, HashPuzzle contract `unlock` called: ', callTx.id) | |
| // hashPuzzle has terminated, but counter can still be called | |
| counter = nexts[0].instance | |
| } | |
| await main() | |
| ``` | |
| :::note | |
| - You must bind a [transition builder](../how-to-deploy-and-call-a-contract/how-to-deploy-and-call-a-contract.md#tx-builders) to each contract instance, since [the default](../how-to-deploy-and-call-a-contract/how-to-customize-a-contract-tx.md#customize-1) only spends a single contract UTXO. | |
| - If the called contracts need signatures from different private keys to be called, the signer passed to `multiContractCall` must have all private keys. | |
| ::: | |