Interact with Contract
Writing to a contract is a common operation in a dApp. This guide demonstrates how to write to a contract using MIDL protocol with a web application.
Writing a contract in MIDL requires a BTC transaction to cover the fees, transfer assets required for transaction execution and to form EVM transaction to interact with the contract. You can read more about it here.
1. Install dependencies
Follow the Installation guide to install the required dependencies.
Also in this example we will use viem
and wagmi
packages to interact with EVM contracts. You can install them via your package manager of choice.
bash
pnpm add viem wagmi
bash
npm install viem wagmi
bash
yarn add viem wagmi
2. Setup the project
Follow the Connect Wallet guide to setup the project.
3. Add WriteContract component
tsx
import {
useAddTxIntention,
useFinalizeBTCTransaction,
useSignIntention,
} from "@midl/executor-react";
import {
useWaitForTransaction
} from "@midl/react";
import { encodeFunctionData } from "viem";
import { usePublicClient, useReadContract } from "wagmi";
import { SimpleStorage } from "./SimpleStorage";
export function WriteContract() {
const { addTxIntention, txIntentions } = useAddTxIntention();
const { finalizeBTCTransaction, data } =
useFinalizeBTCTransaction();
const { signIntentionAsync } = useSignIntention();
const publicClient = usePublicClient();
const { waitForTransaction } = useWaitForTransaction({
mutation: {
onSuccess: () => {
refetch();
},
},
});
const { data: message, refetch } = useReadContract({
abi: SimpleStorage.abi,
functionName: "getMessage",
address: SimpleStorage.address as `0x${string}`,
});
const onAddIntention = () => {
addTxIntention({
reset: true,
intention: {
evmTransaction: {
to: SimpleStorage.address as `0x${string}`,
data: encodeFunctionData({
abi: SimpleStorage.abi,
functionName: "setMessage",
args: [`Updated message at ${new Date().toISOString()}`],
}),
},
},
});
};
const onFinalizeBTCTransaction = () => {
finalizeBTCTransaction();
};
const onSignIntentions = async () => {
if (!data) {
alert("Please finalize BTC transaction first");
return;
}
for (const intention of txIntentions) {
await signIntentionAsync({
intention,
txId: data.tx.id,
});
}
};
const onBroadcast = async () => {
if (!data) {
alert("Please finalize BTC transaction first");
return;
}
await publicClient?.sendBTCTransactions({
serializedTransactions: txIntentions.map(it => it.signedEvmTransaction as `0x${string}`),
btcTransaction: data.tx.hex,
})
waitForTransaction({ txId: data.tx.id });
};
return (
<div>
<h2>Current message:</h2>
<div>{message as string}</div>
<div
style={{
display: "flex",
flexDirection: "column",
gap: "1rem",
maxWidth: "300px",
margin: "3rem auto",
}}
>
<div>
<h3>1. Add Transaction intention</h3>
<button
type="button"
onClick={onAddIntention}
disabled={txIntentions.length > 0}
>
Add Intention
</button>
</div>
<div>
<h3> 2. Calculate transaction costs and form BTC Tx</h3>
<button type="button" onClick={onFinalizeBTCTransaction}>
Finalize BTC Transaction
</button>
</div>
<div>
<h3>3. Sign transaction intentions</h3>
<button type="button" onClick={onSignIntentions}>
Sign Intentions
</button>
</div>
<div>
<h3>4. Publish transactions </h3>
<button type="button" onClick={onBroadcast}>
Broadcast transactions
</button>
</div>
</div>
<h4>Tx Intentions</h4>
<pre
style={{
wordBreak: "break-all",
whiteSpace: "pre-wrap",
textAlign: "left",
}}
>
{JSON.stringify(txIntentions, null, 2)}
</pre>
</div>
);
}
tsx
export const SimpleStorage = {
address: "0x015bceEFA137a662aFC0347Cb6fc204192960094",
abi: [
{
inputs: [
{
internalType: "string",
name: "initialMessage",
type: "string",
},
],
stateMutability: "nonpayable",
type: "constructor",
},
{
anonymous: false,
inputs: [
{
indexed: false,
internalType: "string",
name: "newMessage",
type: "string",
},
],
name: "MessageUpdated",
type: "event",
},
{
inputs: [],
name: "getMessage",
outputs: [
{
internalType: "string",
name: "",
type: "string",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [
{
internalType: "string",
name: "newMessage",
type: "string",
},
],
name: "setMessage",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
],
};
4. Put it all together
Add the WriteContract
component to your app:
tsx
import { useAccounts } from "@midl/react";
import { ConnectWallet } from "./ConnectWallet";
import { ConnectedAccounts } from "./ConnectedAccounts";
import { WriteContract } from "./WriteContract";
export function YourApp() {
const { isConnected } = useAccounts();
return (
<div>
{!isConnected && <ConnectWallet />}
{isConnected && (
<>
<ConnectedAccounts />
<WriteContract />
</>
)}
</div>
);
}