All guide pages
Use the SDK
Reference
@fairpay/sdk gives you public and private payments plus merchant-side verification. These examples come from the SDK's README. Use the addresses from Contract addresses for Monad Testnet.
InstallLink to this section
Until it is on npm, install the packed tarball.
cd packages/sdk && npm run build && npm pack
npm install /path/to/fairpay-sdk-0.1.0.tgz viemSet up clientsLink to this section
import {
createPublicClient, createWalletClient, custom, http,
} from "viem"
import { monadTestnet } from "viem/chains"
import * as fairpay from "@fairpay/sdk"
const deployment: fairpay.FairPayDeployment = {
chainId: 10143,
token: "0x…", // USDC (or MockUSDC on testnet)
fairPay: "0x…",
pool: "0x…",
poolDeployBlock: 0n // block the pool was deployed in
}
const publicClient = createPublicClient({
chain: monadTestnet,
transport: http(),
})
const walletClient = createWalletClient({
chain: monadTestnet,
transport: custom(window.ethereum),
account,
})
const clients = { publicClient, walletClient }Every function that touches the chain checks the chain id first and throws WRONG_CHAIN otherwise.
1. Backend: create the share referenceLink to this section
const secret = fairpay.randomSecret() // store with the participant row
const orderRef = fairpay.createOrderRef({
orderId: "FE-001",
participantId: "p2",
secret,
})2a. Browser: pay publiclyLink to this section
const { txHash } = await fairpay.payPublic(deployment, clients, {
merchant,
amount: 12_500_000n,
orderRef,
})
// send txHash to your backend for verificationpayPublic approves only the exact amount, never an unlimited allowance.
2b. Browser: pay privatelyLink to this section
// Ahead of time (the further ahead, the better the privacy):
// top up the private balance.
const note = fairpay.createNote(deployment)
// store BEFORE depositing
localStorage.setItem("note", fairpay.serializeNote(note))
await fairpay.depositNote(deployment, clients, note)
// At checkout: prove and hand the payment to the relayer.
// Takes a few seconds.
const request = await fairpay.createPrivatePayment(
deployment,
publicClient,
{ note, merchant, orderRef },
)
const { txHash } = await fairpay.submitToRelayer("/api/relay", request)Privacy: a share must be a multiple of the note size. Paying a larger share with several notes sent together can link them. localStorage is only an example: anything that can read that storage can spend the notes. Offer users a backup.
3. Backend: the relayer routeLink to this section
import { createRelayer } from "@fairpay/sdk/relayer"
const relayer = createRelayer(deployment, {
publicClient,
walletClient: relayerWallet,
})
export async function POST(req: Request) {
try {
return Response.json(await relayer.handle(await req.json()))
} catch (e) {
console.error(e)
return Response.json(
{ error: "payment could not be submitted" },
{ status: 400 },
)
}
}Put rate limiting and your own checks in front of it (is this an open order of yours, does it still owe money?). The relayer pays gas for every request it sends. Map errors to fixed messages: raw errors can contain your RPC URL and the relayer's balance. apps/ is a complete example.
4. Backend: verify before releasing the orderLink to this section
const result = await fairpay.verifyPaymentTx(
deployment,
publicClient,
{
txHash, orderRef, merchant,
amount: share.amount,
expectedPayer: share.wallet, // checked for public payments only
minConfirmations: 2n, // default 1
},
)
if (result.status === "verified") {
// INSERT with UNIQUE (chain_id, tx_hash, log_index)
// so one payment can never settle two shares
await db.payments.insert({ ...result.payment, shareId })
}result.status is verified, pending or rejected (with a reason). Never mark a share paid from a client-side "success" message.
API at a glanceLink to this section
| Function | Side | What it does |
|---|---|---|
randomSecret(), createOrderRef({ orderId, participantId, secret }) | backend | Make a share reference. |
payPublic(deployment, clients, { merchant, amount, orderRef }) | browser | Approve the exact amount, then FairPay.directPay. |
createNote(deployment), serializeNote(note), parseNote(text) | browser | Create and store notes. |
depositNote(deployment, clients, note) | browser | Deposit one denomination against a note. |
isNoteSpent(deployment, publicClient, note) | browser | Whether the note's nullifier is used. |
fetchGroup(deployment, publicClient, options?) | browser | Rebuild the tree from Deposit logs; checked against the on-chain root. |
groupFromCommitments(deployment, publicClient, commitments) | browser | Rebuild the tree from an indexer's list; checked against knownRoots. |
createPrivatePayment(deployment, publicClient, { note, merchant, orderRef, group? }) | browser | Build the proof. |
submitToRelayer(url, request) | browser | POST the proof to your relayer. |
createRelayer(deployment, clients).handle(body) | backend | Validate, simulate and submit a private payment. Import from @fairpay/sdk/relayer. |
verifyPaymentTx(deployment, publicClient, params) | backend | Verify one payment transaction. |
findPayments(deployment, publicClient, { orderRef, merchant?, fromBlock }) | backend | Find payments for a reference by scanning logs. |
paymentMessage(merchant, orderRef) | either | The proof message, keccak256(merchant, orderRef) as a field element. |
ErrorsLink to this section
SDK errors are FairPayError with a code: WRONG_CHAIN, INVALID_INPUT, NO_POOL, GROUP_MISMATCH, NOTE_NOT_FOUND, NOTE_SPENT, NOT_MERCHANT, RELAYER_REJECTED, RELAYER_TIMEOUT, RELAYER_BUSY, RELAYER_UNFUNDED.