Accept x402 USDC payments on NEAR
Introduction
Section titled “Introduction”NEAR is the “user-owned AI” chain (its co-founder co-authored the Transformer paper), with both
Circle USDC and Tether USDT native on-chain. Name chain: 'near' and the driver auto-mounts
on first use, so a pure-EVM install never downloads its library.
NEAR is unusual in PipRail: it uses both proof templates. Native NEAR is digest-bound (the
easy, zero-setup path); the NEP-141 token path is memo-bound and needs a one-time
storage_deposit. Both are covered below.
Install the peer dependency
Section titled “Install the peer dependency”The NEAR library is an optional peer, lazy-loaded the first time you name chain: 'near':
npm install near-api-jsWallet shape
Section titled “Wallet shape”A NEAR wallet is { accountId, key }, where key is an ed25519:… secret key. NEAR signing
needs both an account id and the secret key (not just a private key). The presence of
accountId is what tells the SDK this is a NEAR wallet.
import { PipRailClient } from '@piprail/sdk'
const client = new PipRailClient({ wallet: { accountId: 'agent.near', key: process.env.AGENT_KEY }, // ed25519:… secret chain: 'near',})payTo is a NEAR account id: a named account like merchant.near, or a 64-hex implicit
account. See Wallets by family for every family’s shape.
Supported tokens
Section titled “Supported tokens”Name a token by symbol, 'native', or a custom NEP-141 contract:
token | What it is |
|---|---|
'native' | Native NEAR (24 decimals). Zero-setup, digest-bound, no storage_deposit. |
'USDC' | Circle’s native USDC (17208628…36133a1, 6dp). Not the bridged …factory.bridge.near (USDC.e). |
'USDT' | Tether’s native USDt (usdt.tether-token.near, 6dp). |
{ contractId, decimals } | Any other NEP-141 token, by contract account id. |
import { requirePayment } from '@piprail/sdk'
// Express/Connect middleware that turns this route paid-only:requirePayment({ chain: 'near', token: 'USDC', amount: '0.10', payTo: 'merchant.near' })NEAR is the volatile gas coin, so for stable pricing pay in USDC/USDT; for no-setup flows, native NEAR is ideal.
Native NEAR, the zero-setup path
Section titled “Native NEAR, the zero-setup path”token: 'native' pays in NEAR via a plain Transfer, digest-bound like EVM/Solana/Sui:
the proof is <accountId>:<txHash>, verified by tx hash + a recency window + the gate’s
single-use proof set. Native needs no storage_deposit and a transfer even creates a
fresh implicit recipient, so there is nothing to register first.
requirePayment({ chain: 'near', token: 'native', amount: '0.10', payTo: 'merchant.near' })Tokens need storage_deposit (the receive prerequisite)
Section titled “Tokens need storage_deposit (the receive prerequisite)”Before an account can receive a NEP-141 token it must be storage-registered on that exact
token contract (NEP-145): a one-time ~0.00125 NEAR call, per account per token. Both the
merchant (payTo) and the payer must be registered, or the payer’s ft_transfer
panics.
planPayment() surfaces an unregistered recipient as a
RECIPIENT_NOT_READY blocker before you spend, and a payment to an unready recipient raises a
RecipientNotReadyError:
const url = 'https://api.example.com/report'const plan = await client.planPayment(url) // → PaymentPlan | null (null when not 402-gated)
if (!plan) { await client.fetch(url) // not payment-gated, just fetch it} else if (plan.payable) { await client.fetch(url) // safe, we checked} else { console.log(plan.fundingHint) // e.g. "recipient isn't registered on usdt.tether-token.near"}The payer needs a little NEAR for gas either way, native or token.
When a payment can’t go through
Section titled “When a payment can’t go through”client.fetch(url) pays the cheapest settleable rail. If the wallet or recipient isn’t ready it
throws a typed PipRailError. Branch on the stable .code rather than
the message. On NEAR the two you’ll meet are RECIPIENT_NOT_READY (the payTo account isn’t
storage_deposit-registered on the token) and INSUFFICIENT_FUNDS (the payer is short on the
token or on NEAR for gas):
import { RecipientNotReadyError, InsufficientFundsError,} from '@piprail/sdk'
try { const res = await client.fetch(url) // → a normal Response once the proof verifies (200 + the gated resource)} catch (err) { if (err instanceof RecipientNotReadyError) { // fix the RECIPIENT: storage_deposit-register payTo on the token (~0.00125 NEAR) console.error('recipient not ready:', err.message) } else if (err instanceof InsufficientFundsError) { // fix the PAYER: top up the token, or add NEAR for gas console.error('payer is short:', err.message) } else { throw err }}Proof binding: both templates
Section titled “Proof binding: both templates”NEAR is the one family that uses both proof templates:
| Asset | Template | How it’s bound |
|---|---|---|
| Native NEAR | B, digest-bound | proof <accountId>:<txHash>, verified by tx hash + recency + single-use set |
| NEP-141 tokens | A, memo-bound | the challenge nonce rides in the ft_transfer memo |
NEAR has no account-history RPC, so the token path verifies by tx hash and only trusts an
ft_transfer event emitted by the real token contract. verify() re-derives every checked
field from the trusted accept, never the client-supplied ref. See Replay
protection for the single-use proof set.
Standard exact rail: gasless for the buyer via NEP-366 (NEP-141 tokens)
Section titled “Standard exact rail: gasless for the buyer via NEP-366 (NEP-141 tokens)”Beside the default onchain-proof rail, PipRail also speaks the ratified x402 exact scheme for
NEAR (scheme_exact_near.md,
x402 v2). The buyer signs a NEP-366 SignedDelegateAction authorizing exactly one NEP-141
ft_transfer (to payTo, the exact amount, the mandatory 1 yoctoNEAR) with a full-access key,
and never broadcasts it or holds any NEAR. A relayer wraps that delegate in its own outer
transaction, prepays the gas + the yocto, and submits it. The agent (buyer) is completely gasless.
This rail is opt-in and only ever offered for NEP-141 tokens (USDC / USDT, or a custom token
you pass). Native NEAR is not exact-payable, because the scheme is defined over ft_transfer, so native
always stays on the zero-setup onchain-proof rail. Defaults are unchanged: an onchain-proof-only
client behaves exactly as before.
Paying (the buyer or agent, gasless)
Section titled “Paying (the buyer or agent, gasless)”Enable 'exact' in schemes and the client builds + signs the SignedDelegateAction for any NEAR
exact rail it’s offered. It spends zero NEAR (the merchant’s relayer pays):
const client = new PipRailClient({ chain: 'near', wallet: { accountId: 'agent.near', key: 'ed25519:…' }, // MUST be a full-access key (see below) schemes: ['onchain-proof', 'exact'],})await client.fetch('https://api.example.com/data') // 402 → signs a delegate (0 NEAR) → 200Receiving (the merchant): self-settle today
Section titled “Receiving (the merchant): self-settle today”NEAR exact is self-settled today: the merchant runs a small relayer (a funded NEAR account)
that the gate uses to submit the buyer’s signed delegate. The buyer stays gasless; the merchant’s
relayer pays the sub-cent NEAR network fee to receive, exactly like PipRail’s self-settle on
Solana / Algorand / Aptos. This is the standard, working configuration:
createPaymentGate({ chain: 'near', token: 'USDC', // or 'USDT', any NEP-141; native is onchain-proof only amount: '0.01', payTo: 'merchant.near', exact: { settle: 'self', relayer: { accountId: 'relayer.near', key: 'ed25519:…' }, // a funded NEAR key that pays settle gas },})The relayer:
- needs a little NEAR for gas (each settle costs ≈ 0.0003 NEAR, well under a cent);
- must not equal the payer (that’s the buyer), but may equal
payTo(the merchant can relay its own incoming payment, because NEAR’s outer/inner-transaction split allows it); - should use a dedicated key you can fund + rotate, not your cold treasury key (it’s a hot key the gate signs with on every settle).
Unlike EVM/Solana/Algorand, where the merchant co-signs one slot of the buyer’s transaction,
the NEAR relayer wraps the delegate in a separate outer transaction it fully owns, then waits for
the inner ft_transfer receipt to finish executing across shards before the gate returns 200.
Gas model + the sponsor drain guard
Section titled “Gas model + the sponsor drain guard”| Party | Pays gas? |
|---|---|
| Buyer / agent | No. Signs off-chain, holds zero NEAR |
| Merchant relayer (self-settle) | Yes. The sub-cent settle fee + the 1 yoctoNEAR, prepaid on submit |
Because the relayer prepays both the gas and the attached deposit of the delegated call, a hostile
buyer could try to drain it by signing a valid sub-cent transfer with a huge gas or a large
deposit. PipRail’s gate re-derives every field from your trusted rail and refuses the delegate
before the relayer ever signs if the attached deposit ≠ exactly 1 yoctoNEAR or the gas
exceeds 300 TGas (an honest payload uses ~30 TGas). It also re-checks the token contract, payTo,
amount, single-action shape, and expiry against the rail, never the client’s echo.
Storage registration (NEP-145), required to send AND receive
Section titled “Storage registration (NEP-145), required to send AND receive”A NEP-141 token only moves between accounts that are storage-registered on that token
(storage_deposit, ≈ 0.00125 NEAR once per account+token). For the exact rail that means both the
buyer (sender) and payTo (recipient) must be registered on the token, or the ft_transfer panics.
Check the recipient with planPayment() /
recipientReady() (planPayment() surfaces the RECIPIENT_NOT_READY blocker; recipientReady()
returns the NOT_REGISTERED reason) before you rely on the rail. Register out of band once; it persists.
Swapping on NEAR
Section titled “Swapping on NEAR”Holding the wrong token? quoteSwap() prices a same-chain swap read-only and swap() is the
only call that moves anything. It is opt-in and never automatic: paying never swaps and
planning never swaps.
NEAR has no protocol-level swap, so a named venue routes it: Ref Finance. It is keyless, and PipRail sets no integrator or platform fee on it.
1 real mainnet swap backs this route, and the transaction hash is published so you can read it back off the chain yourself.
const quote = await client.quoteSwap({ from: 'native', to: 'USDC', wantAmount: '0.50' })if (quote) await client.swap(quote) // null means no route, never "no funds"Full guide: Swapping tokens. Every route, indexed by chain as well as by venue, is at piprail.com/swaps.