Demos
Send Transaction

Send Transaction

This example shows how to send a multicall transaction using the useContractWrite hook. We also show how to use the useWaitForTransaction hook to fetch the transaction receipt to display a success or error message.

Send Transaction

Change the number of transactions to send:

1

The useContractWrite hook

This hook accepts a list of Starknet calls object, represented as a list of Call objects. The hook returns, among other things, a write method that submits the transaction, and a writeAsync method that submits the transaction and returns a promise with the transaction object. You should use write if you're not interested in using the return value, and writeAsync if you are. The value returned by writeAsync is also available in data.

Building the Call list

Use the Contract.populateTransaction functions to easily build a list of Call objects. First we instantiate a Contract object using the useContract hook. In this example, we connect it to the network's native currency contract.

send-transaction.tsx

_16
import {
_16
useAccount,
_16
useContract,
_16
useContractWrite,
_16
useNetwork,
_16
} from "@starknet-react/core";
_16
_16
function MyComponent() {
_16
const { address } = useAccount();
_16
const { chain } = useNetwork();
_16
_16
const { contract } = useContract({
_16
abi: erc20ABI,
_16
address: chain.nativeCurrency.address,
_16
});
_16
}

The next step is to build a list of calls based on user's input. In this example, we create a number of calls equal to an user-provided counter. Notice how we use contract.populateTransaction to build a Call object using a syntax similar to a function call.

send-transaction.tsx

_38
import {
_38
useAccount,
_38
useContract,
_38
useContractWrite,
_38
useNetwork,
_38
} from "@starknet-react/core";
_38
_38
function MyComponent() {
_38
const { address } = useAccount();
_38
const { chain } = useNetwork();
_38
_38
const { contract } = useContract({
_38
abi: erc20ABI,
_38
address: chain.nativeCurrency.address,
_38
});
_38
_38
const [count, setCount] = useState(1);
_38
_38
const calls = useMemo(() => {
_38
if (!address || !contract) return [];
_38
_38
return Array.from({ length: count }, (_, i) => {
_38
const amount = uint256.bnToUint256(BigInt(i));
_38
return contract.populateTransaction["transfer"]!(address, amount);
_38
});
_38
}, [contract, address, count]);
_38
_38
const {
_38
write,
_38
reset,
_38
data: tx,
_38
isLoading: isSubmitting,
_38
isError: isSubmitError,
_38
error: submitError,
_38
} = useContractWrite({
_38
calls,
_38
});
_38
}

Fetching the transaction receipt

We fetch the transaction receipt using the useWaitForTransaction hook. Notice that before the user submits their transaction, the value of tx?.transaction_hash is undefined and the hook won't fetch any data. Only after the user submits their transaction this value will become available and the hook will start fetching data. We set the retry flag to true to refetch data if the request fails. This can happen if the RPC provider is slower at picking up pending data.

send-transaction.tsx

_50
import {
_50
useAccount,
_50
useContract,
_50
useContractWrite,
_50
useNetwork,
_50
} from "@starknet-react/core";
_50
_50
function MyComponent() {
_50
const { address } = useAccount();
_50
const { chain } = useNetwork();
_50
_50
const { contract } = useContract({
_50
abi: erc20ABI,
_50
address: chain.nativeCurrency.address,
_50
});
_50
_50
const [count, setCount] = useState(1);
_50
_50
const calls = useMemo(() => {
_50
if (!address || !contract) return [];
_50
_50
return Array.from({ length: count }, (_, i) => {
_50
const amount = uint256.bnToUint256(BigInt(i));
_50
return contract.populateTransaction["transfer"]!(address, amount);
_50
});
_50
}, [contract, address, count]);
_50
_50
const {
_50
write,
_50
reset,
_50
data: tx,
_50
isLoading: isSubmitting,
_50
isError: isSubmitError,
_50
error: submitError,
_50
} = useContractWrite({
_50
calls,
_50
});
_50
_50
const {
_50
data: receipt,
_50
isLoading,
_50
isError,
_50
error,
_50
} = useWaitForTransaction({
_50
hash: tx?.transaction_hash,
_50
watch: true,
_50
retry: true,
_50
refetchInterval: 2000,
_50
});
_50
}

Adding the UI

We can now connect the data from the hooks to our UI. In this example, we customize the button icon and text based on the current interaction and transaction status. We also ensure that the user can restart the flow by clicking the button after the transaction has been accepted.

send-transaction.tsx

_146
import {
_146
useAccount,
_146
useContract,
_146
useContractWrite,
_146
useNetwork,
_146
} from "@starknet-react/core";
_146
_146
function MyComponent() {
_146
const { address } = useAccount();
_146
const { chain } = useNetwork();
_146
_146
const { contract } = useContract({
_146
abi: erc20ABI,
_146
address: chain.nativeCurrency.address,
_146
});
_146
_146
const [count, setCount] = useState(1);
_146
_146
const calls = useMemo(() => {
_146
if (!address || !contract) return [];
_146
_146
return Array.from({ length: count }, (_, i) => {
_146
const amount = uint256.bnToUint256(BigInt(i));
_146
return contract.populateTransaction["transfer"]!(address, amount);
_146
});
_146
}, [contract, address, count]);
_146
_146
const {
_146
write,
_146
reset,
_146
data: tx,
_146
isLoading: isSubmitting,
_146
isError: isSubmitError,
_146
error: submitError,
_146
} = useContractWrite({
_146
calls,
_146
});
_146
_146
const {
_146
data: receipt,
_146
isLoading,
_146
isError,
_146
error,
_146
} = useWaitForTransaction({
_146
hash: tx?.transaction_hash,
_146
watch: true,
_146
retry: true,
_146
refetchInterval: 2000,
_146
});
_146
_146
const buttonContent = useMemo(() => {
_146
if (isSubmitting) {
_146
return (
_146
<>
_146
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
_146
Send Transactions
_146
</>
_146
);
_146
}
_146
_146
if (isLoading) {
_146
return (
_146
<>
_146
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
_146
Waiting for confirmation
_146
</>
_146
);
_146
}
_146
_146
if (receipt && receipt.status === "REJECTED") {
_146
return (
_146
<>
_146
<Cross className="h-4 w-4 mr-2" />
_146
Transaction rejected
_146
</>
_146
);
_146
}
_146
_146
if (receipt) {
_146
return (
_146
<>
_146
<Check className="h-4 w-4 mr-2" />
_146
Transaction confirmed
_146
</>
_146
);
_146
}
_146
_146
return (
_146
<>
_146
<SendHorizonal className="h-4 w-4 mr-2" />
_146
Send Transactions
_146
</>
_146
);
_146
}, [isSubmitting, isLoading, receipt]);
_146
_146
const action = () => receipt ? reset() : write({});
_146
_146
return (
_146
<Card className="mx-auto max-w-[400px]">
_146
<CardHeader>
_146
<CardTitle>Send Transaction</CardTitle>
_146
</CardHeader>
_146
<CardContent className="space-y-4">
_146
<p>Change the number of transactions to send:</p>
_146
<div className="w-full flex flex-row justify-center items-center">
_146
<Button
_146
variant="outline"
_146
size="icon"
_146
onClick={() => setCount((c) => Math.max(0, c - 1))}
_146
>
_146
<Minus className="h-4 w-4" />
_146
</Button>
_146
<span className="mx-8 text-center">{count}</span>
_146
<Button
_146
variant="outline"
_146
size="icon"
_146
onClick={() => setCount((c) => c + 1)}
_146
>
_146
<Plus className="h-4 w-4" />
_146
</Button>
_146
</div>
_146
<Button
_146
className="w-full"
_146
onClick={action}
_146
disabled={!address || isSubmitting || isLoading}
_146
>
_146
{buttonContent}
_146
</Button>
_146
{isSubmitError ? (
_146
<Alert variant="destructive">
_146
<AlertCircle className="h-4 w-4" />
_146
<AlertTitle>Error</AlertTitle>
_146
<AlertDescription>{submitError?.message}</AlertDescription>
_146
</Alert>
_146
) : null}
_146
{isError ? (
_146
<Alert variant="destructive">
_146
<AlertCircle className="h-4 w-4" />
_146
<AlertTitle>Error</AlertTitle>
_146
<AlertDescription>{error?.message}</AlertDescription>
_146
</Alert>
_146
) : null}
_146
</CardContent>
_146
</Card>
_146
);
_146
}