Skip to content

Payments

This guide will walk you through the process of creating a payment flow from the perspective of interacting with a contract with EIP-5792 (the useSendCalls Hook) to submit a bundle of contract calls to Porto.

Demo
pnpx gitpick ithacaxyz/porto/tree/main/examples/payments
Source

Steps

Connect Account

Follow the Onboard & Discover Accounts guide to get this set up.

Create BuyNow Component

We will add a simple "Buy Now" button that will trigger the payment flow.

BuyNow.tsx
import * as React from 'react'
 
export function BuyNow() {
  return (
    <form>
      <button type="submit">Buy Now</button>
    </form>
  )
}

Hook up useSendCalls

Next, we will add the useSendCalls hook to submit a batch of contract calls.

  • For the first call, we will request for the user to allow us to spend 10 EXP (a payment hold),
  • For the second call, we will mint the NFT which will also debit 10 EXP from the user's account.
BuyNow.tsx
import * as React from 'react'
import { useSendCalls } from 'wagmi'
import { parseEther } from 'viem'
import { expConfig, nftConfig } from './abi'
 
export function BuyNow() {
  const { sendCalls } = useSendCalls() 
 
  async function submit(event: React.FormEvent<HTMLFormElement>) { 
    event.preventDefault() 
    sendCalls({ 
      calls: [ 
        { 
          abi: expConfig.abi, 
          args: [nftConfig.address, parseEther('10')], 
          functionName: 'approve', 
          to: expConfig.address 
        }, 
        { 
          abi: nftConfig.abi, 
          functionName: 'mint', 
          to: nftConfig.address 
        } 
      ] 
    }) 
  } 
 
  return (
    <form> 
    <form onSubmit={submit}> 
      <button type="submit">Buy Now</button>
    </form>
  )
}

Add Pending State

We will also display the pending state to the user while we are waiting for them to approve the request.

BuyNow.tsx
import * as React from 'react'
import { useSendCalls } from 'wagmi'
import { parseEther } from 'viem'
import { expConfig, nftConfig } from './abi'
 
export function BuyNow() {
  const { isPending, sendCalls } = useSendCalls()
 
  async function submit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault()
    sendCalls({
      calls: [
        {
          abi: expConfig.abi,
          args: [nftConfig.address, parseEther('10')],
          functionName: 'approve',
          to: expConfig.address
        },
        {
          abi: nftConfig.abi,
          functionName: 'mint',
          to: nftConfig.address
        }
      ]
    })
  }
 
  return (
    <form onSubmit={submit}>
      <button
        disabled={isPending}
        type="submit"
      >
        Buy now 
        {isPending ? 'Check prompt' : 'Buy Now'}
      </button>
    </form>
  )
}

Hook up useWaitForCallsStatus

Now that we have the calls submitted, we can hook up the useWaitForCallsStatus hook to wait for the calls to be confirmed, and show a "Completing purchase" message to the user.

BuyNow.tsx
import * as React from 'react'
import { useSendCalls } from 'wagmi'
import { useSendCalls, useWaitForCallsStatus } from 'wagmi'
import { parseEther }from 'viem'
import { expConfig, nftConfig } from './abi'
 
export function BuyNow() {
  const {
    data, 
    isPending,
    sendCalls
  } = useSendCalls()
 
  const { isLoading: isConfirming } = useWaitForCallsStatus({ 
    id: data?.id,  
  })  
 
  async function submit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault()
    sendCalls({
      calls: [
        {
          abi: expConfig.abi,
          args: [nftConfig.address, parseEther('10')],
          functionName: 'approve',
          to: expConfig.address
        },
        {
          abi: nftConfig.abi,
          functionName: 'mint',
          to: nftConfig.address
        }
      ]
    })
  }
 
  return (
    <form onSubmit={submit}>
      <button disabled={isPending || isConfirming} type="submit">
        {isPending ? ( {
          'Check prompt' {
        ) : isConfirming ? ( 
          'Completing purchase'
        ) : ( 
          'Buy Now'
        )}
      </button>
    </form>
  )
}

Display Success State

BuyNow.tsx
import * as React from 'react'
import { parseEther }from 'viem'
import { useSendCalls, useWaitForCallsStatus } from 'wagmi'
import { expConfig, nftConfig } from './abi'
 
export function BuyNow() {
  const { data, isPending, sendCalls } = useSendCalls()
 
  const {
    isLoading: isConfirming,
    isSuccess: isConfirmed, 
  } = useWaitForCallsStatus({
    id: data?.id,
  })
 
  async function submit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault()
    sendCalls({
      calls: [
        {
          abi: expConfig.abi,
          args: [nftConfig.address, parseEther('10')],
          functionName: 'approve',
          to: expConfig.address
        },
        {
          abi: nftConfig.abi,
          functionName: 'mint',
          to: nftConfig.address
        }
      ]
    })
  }
 
  if (isConfirmed) 
    return ( 
      <div> 
        <img alt="Running Sneaker" src="/sneaker.png" /> 
        <div>Purchase complete!</div> 
      </div> 
    ) 
 
  return (
    <form onSubmit={submit}>
      <button disabled={isPending || isConfirming} type="submit">
        {isPending ? (
          'Check prompt'
        ) : isConfirming ? (
          'Completing purchase'
        ) : (
          'Buy Now'
        )}
      </button>
    </form>
  )
}