Skip to content

Write to Contract

The useWriteContract Composable allows you to mutate data on a smart contract, from a payable or nonpayable (write) function. These types of functions require gas to be executed, hence a transaction is broadcasted in order to change the state.

In the guide below, we will teach you how to implement a "Mint NFT" form that takes in a dynamic argument (token ID) using Wagmi. The example below builds on the Connect Wallet guide and uses the useWriteContract & useWaitForTransaction composables.

If you have already completed the Sending Transactions guide, this guide will look very similar! That's because writing to a contract internally broadcasts & sends a transaction.

Example

Feel free to check out the example before moving on:

Steps

1. Connect Wallet

Follow the Connect Wallet guide guide to get this set up.

2. Create a new component

Create your MintNft component that will contain the Mint NFT logic.

vue
<script setup lang="ts">
</script>
 
<template>
  <form>
    <input name="tokenId" placeholder="69420" required />
    <button type="submit">Mint</button>
  </form>
</template>

3. Add a form handler

Next, we will need to add a handler to the form that will send the transaction when the user hits "Mint". This will be a basic handler in this step.

vue
<script setup lang="ts">
function submit(event: Event) {
  const formData = new FormData(e.target as HTMLFormElement)
  const tokenId = formData.get('tokenId') as string
}
</script>

<template
  <form>
  <form @submit.prevent="submit">
    <input name="tokenId" placeholder="69420" required />
    <button type="submit">Mint</button>
  </form>
</template>

4. Hook up the useWriteContract Composable

Now that we have the form handler, we can hook up the useWriteContract Composable to send the transaction.

vue
<script setup lang="ts">
import { useWriteContract } from 'wagmi'
import { abi } from './abi'

const { data: hash, writeContract } = useWriteContract()

function submit(event: Event) { 
  const formData = new FormData(e.target as HTMLFormElement) 
  const tokenId = formData.get('tokenId') as string 
  writeContract({ 
    address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', 
    abi, 
    functionName: 'mint', 
    args: [BigInt(tokenId)], 
  })
} 
</script>

<template
  <form @submit.prevent="submit"> 
    <input name="tokenId" placeholder="69420" required />
    <button type="submit">Mint</button>
    <div v-if="hash">Transaction Hash: {{ hash }}</div>
  </form>
</template>
ts
export const abi = [
  {
    name: 'mint',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [{ internalType: 'uint32', name: 'tokenId', type: 'uint32' }],
    outputs: [],
  },
] as const

5. Add loading state (optional)

We can optionally add a loading state to the "Mint" button while we are waiting confirmation from the user's wallet.

vue
<script setup lang="ts">
import { useWriteContract } from 'wagmi'
import { abi } from './abi'

const { 
  data: hash, 
  isPending,
  writeContract 
} = useWriteContract()

function submit(event: Event) { 
  const formData = new FormData(e.target as HTMLFormElement) 
  const tokenId = formData.get('tokenId') as string 
  writeContract({
    address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
    abi,
    functionName: 'mint',
    args: [BigInt(tokenId)],
  })
} 
</script>

<template
  <form @submit.prevent="submit"> 
    <input name="tokenId" placeholder="69420" required />
    <button :disabled="isPending" type="submit">
      <span v-if="isPending">Sending...</span>
      <span v-else>Send</span>
    </button>
    <div v-if="hash">Transaction Hash: {{ hash }}</div>
  </form>
</template>
ts
export const abi = [
  {
    name: 'mint',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [{ internalType: 'uint32', name: 'tokenId', type: 'uint32' }],
    outputs: [],
  },
] as const

6. Wait for transaction receipt (optional)

We can also display the transaction confirmation status to the user by using the useWaitForTransactionReceipt Composable.

vue
<script setup lang="ts">
import { 
  useWaitForTransactionReceipt,
  useWriteContract 
} from 'wagmi'
import { abi } from './abi'

const { 
  data: hash, 
  isPending,
  writeContract 
} = useWriteContract()

function submit(event: Event) { 
  const formData = new FormData(e.target as HTMLFormElement) 
  const tokenId = formData.get('tokenId') as string 
  writeContract({
    address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
    abi,
    functionName: 'mint',
    args: [BigInt(tokenId)],
  })
} 

const { isLoading: isConfirming, isSuccess: isConfirmed } =
  useWaitForTransactionReceipt({ 
    hash, 
  })
</script>

<template
  <form @submit.prevent="submit"> 
    <input name="tokenId" placeholder="69420" required />
    <button :disabled="isPending" type="submit">
      <span v-if="isPending">Sending...</span>
      <span v-else>Send</span>
    </button>
    <div v-if="hash">Transaction Hash: {{ hash }}</div>
    <div v-if="isConfirming">Waiting for confirmation...</div>
    <div v-if="isConfirmed">Transaction Confirmed!</div>
  </form>
</template>
ts
export const abi = [
  {
    name: 'mint',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [{ internalType: 'uint32', name: 'tokenId', type: 'uint32' }],
    outputs: [],
  },
] as const

7. Handle errors (optional)

If the user rejects the transaction, or the user does not have enough funds to cover the transaction, we can display an error message to the user.

vue
<script setup lang="ts">
import { 
  useWaitForTransactionReceipt,
  useWriteContract 
} from 'wagmi'
import { abi } from './abi'

const { 
  data: hash,
  error,
  isPending,
  writeContract 
} = useWriteContract()

function submit(event: Event) { 
  const formData = new FormData(e.target as HTMLFormElement) 
  const tokenId = formData.get('tokenId') as string 
  writeContract({
    address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
    abi,
    functionName: 'mint',
    args: [BigInt(tokenId)],
  })
} 

const { isLoading: isConfirming, isSuccess: isConfirmed } =
  useWaitForTransactionReceipt({
    hash,
  })
</script>

<template
  <form @submit.prevent="submit"> 
    <input name="tokenId" placeholder="69420" required />
    <button :disabled="isPending" type="submit">
      <span v-if="isPending">Sending...</span>
      <span v-else>Send</span>
    </button>
    <div v-if="hash">Transaction Hash: {{ hash }}</div>
    <div v-if="isConfirming">Waiting for confirmation...</div>
    <div v-if="isConfirmed">Transaction Confirmed!</div>
    <div v-if="error">
      Error: {{ (error as BaseError).shortMessage || error.message }}
    </div>
  </form>
</template>
ts
export const abi = [
  {
    name: 'mint',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [{ internalType: 'uint32', name: 'tokenId', type: 'uint32' }],
    outputs: [],
  },
] as const

8. Wire it up!

Finally, we can wire up our Send Transaction component to our application's entrypoint.

vue
<script setup lang="ts">
import { useAccount } from '@wagmi/vue';
import Account from './Account.vue';
import Connect from './Connect.vue';
import MintNft from './MintNft.vue';

const { isConnected } = useAccount();
</script>

<template>
  <Account v-if="isConnected" />
  <Connect v-else />
  <MintNft v-if="isConnected" />
</template>
vue
<script setup lang="ts">
import { 
  useWaitForTransactionReceipt,
  useWriteContract 
} from 'wagmi'
import { abi } from './abi'

const { 
  data: hash,
  error, 
  isPending,
  writeContract 
} = useWriteContract()

function submit(event: Event) { 
  const formData = new FormData(e.target as HTMLFormElement) 
  const tokenId = formData.get('tokenId') as string 
  writeContract({
    address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
    abi,
    functionName: 'mint',
    args: [BigInt(tokenId)],
  })
} 

const { isLoading: isConfirming, isSuccess: isConfirmed } =
  useWaitForTransactionReceipt({
    hash,
  })
</script>

<template
  <form @submit.prevent="submit"> 
    <input name="tokenId" placeholder="69420" required />
    <button :disabled="isPending" type="submit">
      <span v-if="isPending">Sending...</span>
      <span v-else>Send</span>
    </button>
    <div v-if="hash">Transaction Hash: {{ hash }}</div>
    <div v-if="isConfirming">Waiting for confirmation...</div>
    <div v-if="isConfirmed">Transaction Confirmed!</div>
    <div v-if="error">
      Error: {{ (error as BaseError).shortMessage || error.message }}
    </div>
  </form>
</template>
ts
export const abi = [
  {
    name: 'mint',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [{ internalType: 'uint32', name: 'tokenId', type: 'uint32' }],
    outputs: [],
  },
] as const

See the Example.

Released under the MIT License.