useCall
A hook for calling a contract message and decoding a successful response or receiving an error. See useink/utils helpers for compatible functions that work well with this hook.
Usage
import { useCall } from 'useink'
import { pickDecoded } from 'useink/utils'
import metadata from 'contract/metadata.json'
const CONTRACT_ADDRESS = '...'
// We define a response type so that `get.result.value.decoded` is of type SuccessfulResponse
interface SuccessfulResponse {
foo: 'bar'
}
export const MyContractView: React.FC = () => {
const contract = useContract(CONTRACT_ADDRESS, metadata, 'astar');
const get = useCall<SuccessfulResponse>(contract, 'get');
const args = ['arg-1', 2];
return (
<>
<h1>Get the Result the hard way: {get.result?.ok ? get.result.value.decoded.foo : '--'}</h1>
<h1>Or the easy way: {pickDecoded(get.result)?.foo || '--'}</h1>
<button disabled={get.isSubmitting} onClick={() => get.send(args)}>
Get Result
</button>
</>
);
}
Calling with a default caller address
You must first define a default caller in configuration, then call the contract with options:
const call = useCall(contract, 'get');
const args = [];
call.send(args, { defaultCaller: true })
Handling Result<T, E>
responses from an ink! contract
Result<T, E>
responses from an ink! contractOne of the benefits of using ink! is ability to return meaningful errors with Result<T, E>
(since ink! v4.0.0). In this example we will distinguish between two kinds of errors and a successful result. Let's say that you have the following ink! code in your contract.
use ink::prelude::string::String;
// ...other contract code omitted
#[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub struct Unhappy {
boo: String,
}
// A successful response
#[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub struct Happy {
yippee: String,
}
#[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum Error {
Sad(Unhappy),
}
impl MyMoodyContract {
#[ink(message)]
pub fn mood(&self, value: u64) -> Result<Happy, Error> {
if value % 2 == 0 {
return Ok(Happy {
yippee: String::from("😃"),
});
}
Err(Error::Sad(Unhappy {
boo: String::from("😢"),
}))
}
}
In this example, when you call mood(2)
, you will get an Ok
response. If you call mood(1)
you will get an Err
. If you call mood(5)
you will get another type of Err
.
Here is how we could handle the view using useink.
import { useCall, useContract, useBlockNumber, decodeError } from 'useink'
import metadata from 'contract/metadata.json'
const CONTRACT_ADDRESS = '...'
// We define the interface for the response.
interface MoodResult {
Ok?: { yippee: string };
Err?: {
Sad?: { boo: string; },
},
};
export const MyFickleContract: React.FC = () => {
const { blockNumber } = useBlockNumber();
const contract = useContract(CONTRACT_ADDRESS, metadata);
const getMood = useCall<MoodResult>(contract, 'mood');
// Fetch the mood of the contract on each new block
useEffect(() => {
if(blockNumber) getMood.send([blockNumber]);
}, [blockNumber])
// result is undefined before it is called the first time
if (!getMood.result) return <h1>Loading...</h1>
// if result.ok is false then one of two things happened.
// One possibility is that a pallet in the Substrate runtime threw an error.
// A second possibility is a contract method may have called panic!
// OR called assert! and it failed. In these situations no Response has been returned.
// We need to handle the error using decodeError.
if (!getMood.result.ok) {
return (
<div>
<p>An error occurred in runtime, not our contract function.</p>
<p>
{decodeError(getMood, {
ContractTrapped: 'This is a custom message. Something went wrong.',
})}
</p>
</div>
)
}
// We now know we have decoded value of type `MoodResult`
const { decoded } = getMood.result.value;
return (
<h1>
Block Number {blockNumber} makes me feel
{decoded.Ok && decoded.Ok.yippee}
{decoded.Err?.Sad && decoded.Err.Sad.boo}
</h1>
);
}
Return Value
type DecodedContractResult<T> = {
result?: {
ok: true;
value: {
decoded: T; // The response is decoded using contract Metadata and of type `T`
raw: ContractExecResult; // encoded raw data
} | {
ok: false;
// error
// This error occurs if any pallet throws an error,
// or if a contract method calls panic! or assert!() and it fails.
error: DispatchError | undefined;
}
}
}
// useCall returns
{
isSubmitting: boolean;
// args: a list of arguments your contract message receives
// options: additional option overrides
// caller: the calling address. This can be used in ink! contracts with `self.env.caller()`
// `caller` defaults to the connected wallet address.
send: (args?: unknown[], options?: ContractOptions, caller?: string) =>
Promise<DecodedContractResult<T> | undefined>;
result?: DecodedContractResult<T>;
}
Last updated
Was this helpful?