useContractTx
Contract write with dry-run pre-flight, then sign, submit and watch
useContractTx binds one contract method as a write. Call send(args?): it
dry-runs the call first, then signs with the connected account, submits and
watches the transaction, resolving with a TxResult once it is in a block.
Args are optional, so call send() bare for no-argument methods or pass the
args array (send([5n])). Mutation state (data, error, isPending,
reset) tracks the last send.
Like useTx, the resolved TxResult reports dispatch
failure as data: check result.ok, a failed dispatch carries the decoded
dispatchError. send rejects (and sets error) when the handle hasn't
resolved yet, when the method name doesn't exist on the handle, when the
dry-run fails, or when the user rejects the signature.
Before the first contract transaction from an account, the account must be
mapped in pallet-revive; see
useEnsureAccountMapped.
Usage
import { useContract, useContractQuery, useContractTx } from "@use-truapi/react";
import { COUNTER_LIBRARY, cdmJson } from "./counter-contract";
function Counter() {
const contract = useContract(cdmJson, COUNTER_LIBRARY);
const count = useContractQuery<bigint>(contract.data, "getCount", []);
const increment = useContractTx(contract.data, "increment", {
mutation: { onSuccess: () => count.refetch() },
});
const add = useContractTx(contract.data, "add", {
mutation: { onSuccess: () => count.refetch() },
});
return (
<>
<p>Count: {count.data?.toString() ?? "…"}</p>
<button
type="button"
disabled={!contract.data || increment.isPending}
onClick={() => void increment.send().catch(() => {})}
>
{increment.isPending ? "Incrementing…" : "Increment"}
</button>
<button
type="button"
disabled={!contract.data || add.isPending}
onClick={() => void add.send([5n]).catch(() => {})}
>
Add 5
</button>
{increment.error && <p role="alert">{increment.error.message}</p>}
</>
);
}In React, pass the resolved handle, contract.data, as the first argument.
<script setup lang="ts">
import { useContract, useContractQuery, useContractTx } from "@use-truapi/vue";
import { COUNTER_LIBRARY, cdmJson } from "./counter-contract";
const contract = useContract(cdmJson, COUNTER_LIBRARY);
const count = useContractQuery<bigint>(contract, "getCount", []);
const increment = useContractTx(contract, "increment", {
mutation: { onSuccess: () => count.refetch() },
});
const add = useContractTx(contract, "add", {
mutation: { onSuccess: () => count.refetch() },
});
</script>
<template>
<p>Count: {{ count.data?.toString() ?? "…" }}</p>
<button
type="button"
:disabled="!contract.data || increment.isPending"
@click="void increment.send().catch(() => {})"
>
{{ increment.isPending ? "Incrementing…" : "Increment" }}
</button>
<button
type="button"
:disabled="!contract.data || add.isPending"
@click="void add.send([5n]).catch(() => {})"
>
Add 5
</button>
<p v-if="increment.error" role="alert">{{ increment.error.message }}</p>
</template>In Vue, pass the whole query result returned by useContract or
useContractAt; the write unwraps contract.data when it runs.
Failures always land in error, so a fire-and-forget click handler only
needs .catch(() => {}) to silence the rejected promise. To inspect the
dispatch outcome, await the result:
const result = await increment.send();
if (!result.ok) console.error("dispatch failed", result.dispatchError);See also
useEnsureAccountMappedis required once before the first contract tx.useContractQueryis the read-side counterpart; refetch it after writes.useTxis the same lifecycle for plain transactions, with a granularphase.
API reference
React
function useContractTx(
contract,
method,
options?): NamedMutation<TxResult, OptionalVariables<readonly unknown[]>> & object;Contract transaction: dry-run pre-flight, then sign/submit/watch.
const increment = useContractTx(contract, "increment");
await increment.send();Parameters
| Parameter | Type |
|---|---|
contract | | Contract<ContractDef> | undefined |
method | string |
options? | { mutation?: MutationOptions<TxResult, OptionalVariables<readonly unknown[]>>; } |
options.mutation? | MutationOptions<TxResult, OptionalVariables<readonly unknown[]>> |
Returns
NamedMutation<TxResult, OptionalVariables<readonly unknown[]>> & object
Vue
function useContractTx(
contract,
method,
options?): NamedMutation<TxResult, OptionalVariables<readonly unknown[]>> & object;Contract transaction: dry-run pre-flight, then sign/submit/watch.
const increment = useContractTx(contract, "increment");
await increment.send();Parameters
| Parameter | Type |
|---|---|
contract | QueryResult<Contract<ContractDef>> |
method | string |
options? | { mutation?: MutationOptions<TxResult, OptionalVariables<readonly unknown[]>>; } |
options.mutation? | MutationOptions<TxResult, OptionalVariables<readonly unknown[]>> |
Returns
NamedMutation<TxResult, OptionalVariables<readonly unknown[]>> & object