Skip to main content

Simple Usage 🚀

Quick examples to get you going! No complicated stuff — just copy-paste and you're good to go.

For a full step-by-step walkthrough, check out Quick Start →.


1️⃣ Make a Payment (React)

The easiest way to pay — just one hook and you're done!

import { usePayIDFlow } from 'payid-react'
import { zeroAddress, parseUnits } from 'viem'

function PayButton({ receiver }: { receiver: `0x${string}` }) {
const { execute, status, isPending, isSuccess, txHash } = usePayIDFlow()

return (
<button
onClick={() => execute({
receiver,
asset: zeroAddress, // Native token (ETH, MATIC, A0GI, etc.)
amount: parseUnits('0.01', 18),
payId: 'pay.id/merchant',
})}
disabled={isPending}
>
{isPending ? 'Processing...' : isSuccess ? 'Paid!' : 'Pay 0.01 Native'}
</button>
)
}

Status states: idlefetching-ruleevaluatingprovingawaiting-walletconfirmingsuccess | denied | error


2️⃣ Pay with ERC20 Token (USDC, USDT, etc.)

Same flow, just change the asset address — easy peasy!

const USDC_ADDRESS = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'

<button
onClick={() => execute({
receiver,
asset: USDC_ADDRESS,
amount: parseUnits('50', 6), // 50 USDC (6 decimals)
payId: 'pay.id/merchant',
})}
>
Pay 50 USDC
</button>

3️⃣ Add a Loading Spinner 🔄

Users love knowing something is happening! Add a spinner while processing:

import { Loader2 } from 'lucide-react'

<button onClick={handlePay} disabled={isPending}>
{isPending ? (
<>
<Loader2 className="animate-spin w-4 h-4" />
Processing...
</>
) : 'Pay'}
</button>

4️⃣ Show the Right Currency Symbol per Chain 💱

PAY.ID works across many chains, and each has its own native token. Show the right symbol automatically:

import { useChains, useChainId } from 'wagmi'

function TokenDisplay() {
const chainId = useChainId()
const chains = useChains()
const currentChain = chains.find(c => c.id === chainId)
const nativeSymbol = currentChain?.nativeCurrency.symbol ?? 'ETH'

return <span>Balance: {balance} {nativeSymbol}</span>
}

Supported chains:

  • Ethereum → ETH
  • Polygon → MATIC
  • 0G Newton Testnet (Real) → A0GI (Chain 16600)
  • 0G Newton Testnet (Fork) → A0GI (Chain 16601)
  • 0G Galileo Testnet → A0GI (Chain 16602) ⭐ Recommended
  • Lisk Sepolia → LISK
  • Monad Testnet → MON
  • Base Sepolia → ETH
  • Sepolia → ETH
  • Moonbase Alpha → DEV

5️⃣ Check if a Merchant Has an Active Subscription

See if a merchant is set up to receive payments:

import { useSubscription } from 'payid-react'

function MerchantStatus({ address }: { address: `0x${string}` }) {
const { data: sub } = useSubscription(address)

if (!sub) return <p>No subscription</p>

return (
<div>
<p>Status: {sub.isActive ? '✅ Active' : '❌ Expired'}</p>
<p>Slots: {sub.logicalRuleCount} / {sub.maxSlots}</p>
</div>
)
}

6️⃣ Generate a QR Code (Merchant Side) 📱

Let customers scan a QR code to pay you:

import { usePayIDQR } from 'payid-react'
import { parseUnits } from 'viem'

function MerchantQR() {
const { generate, payload, isPending } = usePayIDQR()

return (
<div>
<button onClick={() => generate({
payId: 'pay.id/my-shop',
allowedAsset: USDC_ADDRESS,
maxAmount: parseUnits('100', 6),
expiresAt: Math.floor(Date.now() / 1000) + 3600,
})} disabled={isPending}>
Generate QR
</button>

{payload && <QRCode value={payload} size={256} />}
</div>
)
}

7️⃣ Get a Merchant's Active Policy

Check what rules a merchant has set up:

import { useActiveCombinedRule } from 'payid-react'

function MerchantPolicy({ address }: { address: `0x${string}` }) {
const { data: policy } = useActiveCombinedRule(address)

if (!policy) return <p>No active policy — all payments allowed</p>

return (
<div>
<p>Policy hash: {policy.hash.slice(0, 10)}...</p>
<p>Version: {policy.version}</p>
<p>Rules: {policy.ruleRefs.length}</p>
</div>
)
}

8️⃣ Server-Side Payment (Node.js) 🖥️

For backend scripts, bots, or servers:

import { createPayIDServer } from 'payid/server'
import { ethers } from 'ethers'

const payid = createPayIDServer({
signer: new ethers.Wallet(process.env.PRIVATE_KEY!),
})

const { result, proof } = await payid.evaluateAndProve({
context: {
tx: {
sender: payerAddress,
receiver: merchantAddress,
asset: usdcAddress,
amount: '50000000',
chainId: 1,
},
env: { timestamp: Math.floor(Date.now() / 1000) },
},
authorityRule: { version: '1', logic: 'AND', rules: ruleConfigs },
payId: 'pay.id/merchant',
payer: payerAddress,
receiver: merchantAddress,
asset: usdcAddress,
amount: 50_000_000n,
verifyingContract: process.env.PAYID_VERIFIER!,
ruleAuthority: process.env.COMBINED_RULE_STORAGE!,
chainId: 1,
blockTimestamp: Math.floor(Date.now() / 1000),
ttlSeconds: 300,
})

if (result.decision === 'REJECT') {
throw new Error(`Rejected: ${result.reason}`)
}

// Submit proof to contract
await payContract.payERC20(proof.payload, proof.signature, [])

9️⃣ Simple Rule Example 📝

A basic rule to set a minimum payment amount:

const MY_RULE = {
id: 'min_amount',
if: { field: 'tx.amount', op: '>=', value: '10000000' }, // >= 10 USDC
message: 'Minimum payment is 10 USDC',
}

🔟 Handle Payment Errors Gracefully

Don't leave users hanging when things go wrong:

const { execute, status, error, denyReason } = usePayIDFlow()

const handlePay = async () => {
try {
await execute({ receiver, asset, amount, payId })
} catch (e) {
console.error('Payment failed:', e)
}
}

return (
<div>
<button onClick={handlePay}>Pay</button>

{status === 'denied' && <p>Denied: {denyReason}</p>}
{error && <p style={{ color: 'red' }}>{error}</p>}
</div>
)

Quick Reference 📋

What you wantHow to do it
Native token paymentasset: zeroAddress
ERC20 paymentasset: '0x...'
Show loading spinner{isPending && <Loader2 className="animate-spin" />}
Get native symboluseChains().find(c => c.id === chainId)?.nativeCurrency.symbol
Check subscriptionuseSubscription(address)
Get active policyuseActiveCombinedRule(address)
Generate QRusePayIDQR().generate(...)
Server signingcreatePayIDServer({ signer })

Next Steps 🎯


That's it! You're now ready to use PAY.ID in your app. 🎉