Payments and receipts
Nibgate earnings are payment analytics, not withdrawals. Funds flow to the creator’s on-chain fee wallet contract, which enforces a 99% creator / 1% treasury split — see Revenue model.
Payment model
With hosted pay enabled, the hub resolves each creator’s payment destination to their GatewayFeeWallet contract (deployed deterministically per creator). A creator can run multiple sites, and every site resolves to the same fee wallet for that creator.
Nibgate does not need to custody funds to show earnings. It records payment events and receipt references after the creator site completes an unlock. Receipts store the gross amount plus a derived protocolFee (1% under hosted pay), so analytics match the fee wallet contract’s on-chain 99/1 split — see Revenue model.
Unlock model
One reliable path powers every rail:
one resource -> one payment -> one verified unlock receipt -> one entitlement -> one unlock proofThe entitlement is lifetime for paid unlocks: once a receipt exists, the wallet keeps access even after the proof expires or browser storage clears (the server re-issues a fresh proof on every legit visit). Free grants are re-granted per visit and are not lifetime. Metered streaming, metered reading, passes, and agent quotas are future unlock strategies; they should still produce receipt events that can be indexed by Nibgate.
Unlock proofs (the short-lived cache)
Proofs are the browser/agent cache over the database; they are bindable and short-lived. Full grant logic lives on Whitelists & access control.
- Nibshare — server-minted:
{wallet}.{iat}.{exp}.{mac},mac = HMAC-SHA256("nibshare:{shareId}:{wallet}:{iat}:{exp}")keyed byNIBGATE_GATEWAY_SECRET/NIBGATE_PROOF_SECRET. 12h window. - Subblogs — SDK-signed: unlock token minted by
createNibgateServer(...)after proof verification, 12h TTL.
An expired proof is harmless — entitlements + receipts are checked before proof freshness, so the server simply re-mints on the next legit visit. The x-nibgate-payment-proof header carries the proof to media/document routes; ?wallet= on the query can authenticate <img>/<audio> tags that cannot send headers.
Payment idempotency: one grant per payment
x402 has no native payment id — the settlement txHash is the only stable identity. Both backends enforce a unique (paymentNonce=txHash, resourceId) index and do find-or-create on grant. A replayed proof hits the stored receipt and returns {receipt, replay: true} — no second grant, no double unlock count, no double event. This is the #1 x402 production failure mode (248 grants per single payment measured on a live endpoint without the guard).
Receipt types
x402 gateway receipt
Use this when the unlock flow completes through a gateway-style x402 payment flow. Store the gateway receipt id or proof URL when available.
Headless x402 (agent/API flow)
For server-to-server or AI agent payments, use GatewayClient from @circle-fin/x402-batching/client:
import { GatewayClient } from '@circle-fin/x402-batching/client'
const agent = new GatewayClient({
chain: 'arcTestnet',
privateKey: process.env.AGENT_KEY,
rpcUrl: 'https://rpc.testnet.arc.io'
})
const result = await agent.pay(contentAccessUrl, {
headers: { 'x-nibgate-actor': 'agent' }
})The GatewayClient.pay() method auto-handles the 402 challenge → EIP-3009 signing → retry → settlement flow.
Direct transfer receipt
Use the direct rail when the buyer’s wallet sends USDC straight to the creator’s receiver (no Gateway facilitator). The browser checkout is createTransferCheckout/payWithTransfer from @nibgate/sdk — it broadcasts a USDC transfer to the seller’s payTo address and submits the tx hash as the x-nibgate-transfer-tx header on the access retry.
The retry must also carry an ownership proof: an EIP-191 signature (made by the paying wallet) over transferOwnershipMessage(txHash, resource) — i.e. Nibgate transfer ownership\ntx:<txHash lowercased>\nresource:<resource path or url> — sent as the x-nibgate-tx-owner header. This binds the public txHash to the payer and this specific resource. Missing proofs fail with transfer-ownership-proof-required, mismatched signers with transfer-owner-mismatch. Self-hosters can opt out with NIBGATE_TX_OWNER_PROOF_OPTIONAL=true; Nibgate-hosted surfaces additionally claim each txHash single-use per content id.
The server verifies on-chain (createTransferVerifier): the transaction mined successfully, a USDC Transfer log credits the seller’s address, and the amount is at least the resource price. On success it mints the unlock proof and returns the content, storing a paymentProvider: 'direct-transfer' receipt keyed by txHash.
import { payWithTransfer, createTransferCheckout } from '@nibgate/sdk'
const result = await payWithTransfer(resource, {
accessPath: '/api/nibgate/access',
checkout: createTransferCheckout(resource, {
// Broadcast the transfer from the connected wallet; return the txHash.
sendTransfer: async ({ recipient, amount, currency }) =>
(await walletClient.sendTransaction({
account,
to: recipient,
value: 0n,
data: encodeFunctionData({ abi: usdcTransferAbi, args: [recipient, parseUnits(amount, 6)] })
}))
})
})Enabling hub event reporting
After a successful x402 payment, the SDK automatically emits payment_completed and unlock_completed events to the Hub — if the creator site has these env vars set:
| Env var | Purpose |
|---|---|
NIBGATE_SITE_ID | Hub website UUID |
NIBGATE_SITE_TOKEN | Hub website verify token |
NIBGATE_API_BASE | Hub API URL (e.g. https://nibgate.xyz) |
Without these, x402 payments still work and return unlock proofs, but unlock counts and revenue on Explore do not update.
Arc testnet receipt
Use this when the payment can link to an Arc testnet transaction hash or Arcscan URL.
Gateway balance & deposit
The hub backend exposes Circle Gateway helper status on both rails via the shared packages/internal/src/payments.js:
POST /api/nibshare/gateway/balance(hub) andGET /api/nibgate/gateway/balances(subblogs) — return the depositor’s testnet USDC balance (needsCIRCLE_API_KEYset on the backend; response shows{ balance: "0.00 USDC" }).- Deposit/withdraw are handled through Circle’s Gateway API with the configured depositor/source — no custody by Nibgate.
Event payload
Unlock completion events should include:
amountcurrencyreceiverrecipientsiteIdcontentIdtxHashfor chain transactions (this doubles as thepaymentNoncefor idempotent granting)paymentIdfor gateway/provider recordsreceiptUrlwhen the provider returns a real receipt URLchainExplorerUrlwhen availablepaidAt
Earnings dashboard
The dashboard should group earnings by site, receiver, content, currency, and time range. Clicking a receipt should open the source transaction or gateway receipt when available.