Contract Tx

In addition to using the .query.<messageName> on a contract, the .tx.<messageName> method is provides to send an actual encoded transaction to the contract, allow for execution and have this applied in a block. Expanding on our previous examples, we can now execute and then retrieve the subsequent value -

// We will use these values for the execution
const value = 0; // only useful on isPayable messages
const gasLimit = 3000n * 1000000n;
const incValue = 1;
// Send the transaction, like elsewhere this is a normal extrinsic
// with the same rules as applied in the API (As with the read example,
// additional params, if required can follow - here only one is needed)
await contract.tx
.inc({ value, gasLimit }, incValue)
.signAndSend(alicePair, (result) => {
if (result.status.isInBlock) {
console.log('in a block');
} else if (result.status.isFinalized) {
console.log('finalized');
}
});

If we perform the same query.get read on the value now, it would be 124. For lower-level access, like we have in the Blueprint via .createContract you can also perform the execution via the .exec function, which would yield equivalent results -

// Send the transaction, like elsewhere this is a normal submittable
// extrinsic with the same rules as applied in the API
await contract
.exec('inc', { value, gasLimit }, incValue)
.signAndSend(alicePair, (result) => {
...
});

For the above interface we can specify the message as the string name, the index of the actual message as retrieved via the Abi.

Weight estimation#

To estimate the gasLimit (which in the Substrate context refers to the weight used), we can use the .query (read) interfaces with a sufficiently large value to retrieve the actual gas consumed. The API makes this easy - with a gasLimit or -1 passed to the query it will use the maximum gas limit available to transactions and the return value will have the actual gas used.

To see this in practice -

// We will use these values for the execution
const value = 0;
const incValue = 1;
// Instead of sending we use the `call` interface via `.query` that will return
// the gas consumed (the API aut-fill the max block tx weight when -1 is the gasLimit)
const { gasConsumed, result } = await contract.query.inc(slicePair, { value, gasLimit: -1 }, incValue)
console.log(`outcome: ${result.isOk ? 'Ok' : 'Error'}`);
console.log(`gasConsumed ${gasConsumed.toString()}`);

We can use the gasConsumed input (potentially with a buffer for various execution paths) in any calls to contract.tx.inc(...) with the same input parameters specified on the query where the estimation was done.

Events#

On current versions of the API, any events raised by the contract will be transparently decoded with the relevant ABI and will be made available on the result (from .signAndSend(alicePair, (result) => {...}) as contractEvents.

Where no events were emitted this value would be undefined, however should events be emitted, the array will contain all the decoded values.

That is it... for now#

This was a whirl-wind tour of what the API provides in terms of the @polkadot/api-contract interface. It is not perfect yet, we would like to expand it to allow for greater type-checking on the contracts (instead of read/exec wit messages), but hopefully in the current state it already enhances the way you can interact with contracts.