@aifinpay/agent (Node / TypeScript)
The signed AIFP-1 fetchPaid flow supports owner-selected v1.4 payment chains.
Polygon is the default; select another chain explicitly with v14.chain.
Client descriptors are not production approval: payment availability is
determined by the installed deployment registry, runtime verification and
backend/merchant capabilities. Runtime, signer, token, RPC-chain and current
profile checks remain mandatory, along with a durable pre-broadcast journal and
explicit native gas budget. Source builds and tests do not establish live
payment acceptance.
Native purchases require a fresh independent nativeUsdPrice for POL, ETH, AVAX,
BNB or XRP on the selected chain. Stablecoins use an independently pinned token
address and decimals. USD micro-units remain unchanged; token_settlement binds
exact6/18-decimal token units and every gross-inclusive leg to the signed gross
and exact approval.18-decimal tokens require this metadata; legacy6-decimal
quotes remain accepted. Inspect current assets through the SDK capability API
and installed deployment registry.
maxGasWei covers approval plus settlement. OP chains (Base/Optimism/Unichain)
include buffered L1/operator estimates; Nitro (Arbitrum/Robinhood) already includes
parent-data gas in the RPC estimate, so it is counted once. Unavailable OP fees
block signing. These are preflight estimates, not inclusion-time guarantees.
Current admin profiles remain mutable; receipt and recovery policy is unchanged.
See payment configuration and recovery.
Aptos and NEAR deployment support
The Node SDK generates typed Aptos and NEAR deployment pins from the installed
@aifinpay/deployments package. Wallet derivation and Agent Passport already
provide identifiers for both families. Select deployment metadata explicitly:
import { getAptosDeployment, getNearDeployment, resolveAptosDeployment } from "@aifinpay/agent";
const aptos = getAptosDeployment({ environment: "prod", network: "mainnet" });
const near = getNearDeployment({ environment: "dev", network: "testnet" });
// Selection additionally requires settlementEnabled in the packaged record.
const selected = resolveAptosDeployment({ environment: "prod", network: "mainnet", version: "v1.4" });
get*Deployment permits read-only inspection of disabled records.
resolveAptosDeployment and resolveNearDeployment reject disabled records,
unknown networks, environment mismatches, and unsupported versions; no legacy
fallback or default network is selected. Aptos pins include module hashes, the
referenced ABI digest, and per-network source provenance. NEAR pins include the
account and WASM hash; council membership is not treated as governance authority.
See the Aptos and
NEAR registry guides.
Payment execution is not implemented for Aptos or NEAR. These resolvers read
packaged metadata, not current chain state. Neither family is an EVM v14.chain,
and fetchPaid cannot sign or settle their payments. Executable support requires
backend quote/receipt contracts, independently verified runtime/profile checks,
chain-specific signing, fee caps, and durable recovery; a registry flag or payout
address does not supply these.
Stablecoin-only chains
On a stablecoinOnly chain (Arc) the gas token is the pinned stablecoin itself:
set v14.asset to it, maxGasWei is in its 18-decimal native units, and gas
and the gross are checked against one balance. A testnet chain accepts only
network_mode: "test" quotes. On a noNativePayment chain (BOT Chain) the
native token pays gas only and has no price, so v14.asset must name its
pinned stablecoin.
The solanaV14 option provides owner-selected paid access for SOL and classic
SPL assets. Network and environment, local Ed25519 payer,
independently sourced SOL/USD and maxFeeLamports (transaction fees plus all
nonce/ATA rent) are separate from EVM options. v14 and solanaV14 together
are rejected. Availability is checked against the installed deployment
registry and runtime/backend policy; application configuration cannot override
an unavailable route.
The exact 216-byte quote and 297-byte instruction follow the packaged pinned IDL. Fresh genesis/config/profile/mint/nonce/account evidence and unsigned final-message simulation precede budget reservation and local signing. Signed bytes are saved before the single send. Unknown signature outcomes never expire or authorize a replacement transaction. A canonical finalized failure retains only its proven fee debit; missing proof retains the full reservation. Public history/quota select Solana cluster explicitly and never expose bearer receipts.
Link the agent to its owner's dashboard
At https://dash.aifinpay.io → My Agents → Add agent by address the owner gets a challenge. Sign it and hand back the signature:
const signature = await agent.signDashboardClaim(challenge);
signDashboardClaim signs only AiFinPay-claim:polygon:<this address>:<nonce>
and refuses any other text. The owner then sees the agent's balance, payments
and receipts.
The accepted v1.4 contract model allows administrators to change profile fees and treasury. Preflight verifies current values; it does not make them immutable. See receipt configuration and recovery.
Non-custodial payment client for autonomous AI agents on
AiFinPay. AIFP-1 is gross-inclusive: payer total equals
the quote, merchant receives 99%, AiFinPay receives 1%, creator/referral
receives 0%. AIFP-2/x402 currently charges 0% at the protocol layer. Legacy
/api/b2b split-invoice methods are retired. The v1.3 executor checks the
independently supplied deployment/runtime pin, chain, fee profile, merchant
target and asset. A matching runtime value alone is not production approval;
use the installed registry, capability APIs, executor tests and CI as evidence,
and do not bypass runtime checks.
The Ed25519 keypair is generated locally with tweetnacl and never leaves
your process. The SDK only sends a one-time SHA-256 + Ed25519 signature in
the x-signature header to authenticate against AiFinPay-protected endpoints.
Install
Install the published stable package:
npm install @aifinpay/agent
Or build this source checkout:
# From the SDK repository root
cd node
npm ci --no-audit --no-fund
npm run build
npm pack
Install the resulting tarball in your application:
npm install /absolute/path/to/sdk/node/aifinpay-agent-<version>.tgz
Quick start
Load the wallet you already configured before sharing a deposit address:
import { AiFinPayAgent } from "@aifinpay/agent";
const agent = await AiFinPayAgent.fromEnvironment();
console.log({ evm: agent.evmAddress, solana: agent.solanaAddress });
fromEnvironment() is a load-only Node API. It selects one identity in
this order, matching MCP:
SEED_HASH: a 32-byte seed encoded as 64 hex characters, optionally0xprefixed../aifinpay/agents.jsonrelative to the process working directory. SetAIFINPAY_AGENTS_FILEfor another path andAIFINPAY_AGENT_IDwhen selecting from multiple records ({"agents":[{"id":"crawler","seed_hash":"…"}]}).AIFINPAY_AGENT_SECRET: an existing base58 Solana secret.~/.aifinpay/agent.json, orAIFINPAY_HOME/agent.json: the existing MCP keystore. Encrypted keystores requireAIFINPAY_WALLET_PASSPHRASE.
The loader never creates or overwrites a wallet, prints its keys, or calls the
network. Missing configuration, an invalid seed, an ambiguous project file or
a decryption failure throws instead of selecting a new wallet. Load any .env
through your runtime before calling it; this API reads process.env and does
not read .env files. SEED_HEX is not an alias for SEED_HASH. Keep private
inputs and wallet files out of chats, logs and version control.
The same configured inputs restore the same addresses after a process restart.
An explicit evmPrivateKey option overrides the derived EVM identity; retain
that separate key as well to recover the imported wallet. Loading a wallet
does not by itself pay for anything; paid execution goes through fetchPaid
and its checks.
AiFinPayAgent.fromSeed(seed, { derivationIndex }) deterministically selects a
child wallet. Each chain uses its own versioned domain, followed by the
decimal UTF-8 index after the domain's NUL separator and then the recovery
seed. Omitting derivationIndex preserves existing addresses.
AiFinPayAgent.new() and Agent.new() intentionally create a fresh ephemeral
wallet each time. They do not load existing environment variables or keystores
and do not persist their generated keys. Use them only when you deliberately
need a new identity and will store its recovery material privately before
funding it. Never rerun new() to recover an existing funded address.
Loading an existing keypair
import { Agent } from "@aifinpay/agent";
// from solana-keygen JSON file (Node only)
const agent = await Agent.fromKeypairFile("./agent-wallet.json");
// from base58 secret string (works in browser too)
const agent2 = Agent.fromSecretB58("3RvZm7Gw...");
Inject an external EVM signer
Agent.new() and AiFinPayAgent.fromSeed() derive the EVM identity from the
agent seed. To keep the key outside the SDK — an in-house signer, a
hardware-backed key, or a future vendor adapter — inject any wallet matching
the AgentWallet interface via evmWallet (any viem LocalAccount
qualifies structurally):
import { Agent, evmPrivateKeyWallet } from "@aifinpay/agent";
// self-custodial default: the same key `evmPrivateKey` builds internally
const agent = Agent.new({ evmWallet: evmPrivateKeyWallet("0x…") });
Two zero-dependency adapters cover the rest of the wallet market through the same interface:
import { eip1193Wallet, viemWalletClientWallet } from "@aifinpay/agent";
// MetaMask and any generic EVM browser wallet (async: reads the address
// via eth_requestAccounts)
const injected = Agent.new({ evmWallet: await eip1193Wallet(window.ethereum) });
// Privy, Crossmint, ZeroDev, Coinbase Smart Wallet, custom transports —
// anything that hands out a viem WalletClient with an account
const embedded = Agent.new({ evmWallet: viemWalletClientWallet(walletClient) });
Server-side vendor SDKs (Coinbase CDP server wallets, Circle) are out of scope: they need vendor API credentials, which the SDK must not hold.
AgentWallet is address plus signMessage/signTypedData; it is sufficient
for message-based x402 authorization. AiFinPayAgent also accepts
evmWalletClient for EVM on-chain flows, using that exact viem client for
signing. Its account must be a viem LocalAccount with signTransaction:
v1.4 signs and journals exact raw bytes before the SDK broadcasts them through
the independently selected chain RPC for recovery. JSON-RPC/send-only and
smart-account clients are refused for this flow. The client must be configured
for the selected chain. This option cannot be combined with evmWallet or
evmPrivateKey. The MCP programmatic integration uses the same option; its
local identity continues to provide Solana identity and signing.
How x402 auth works under the hood
Agent.pay(url) (and get/post/request) handles an AiFinPay-native 402
with request-bound auth (version 2). For a gated request the SDK:
- sends the request without credentials; the 402 body carries a one-time
x-nonce, its expiryx-nonce-expires-at,x-aifinpay-auth-version: 2and the SHA-256 of the request body it was issued for; - refuses to sign unless the request went to the origin configured as the
agent's
baseUrl, the response came from that origin, and the body digest matches the body it sent; - signs, with Ed25519, the SHA-256 of
JSON.stringify(["AiFinPay-x402", "v2", nonce, pubkey, origin, METHOD, path+query, bodySha256, expiresAt])and base58-encodes the signature; - retries the original request with headers:
x-agent-pubkey: <base58 pubkey>x-nonce: <nonce>x-signature: <base58 sig>x-aifinpay-auth-version: 2
The server verifies the signature, consumes the nonce, checks the agent has a
live Seat PDA on-chain, and serves the resource. The retired v1 format
(GET /nonce, SHA-256("AiFinPay-x402:{nonce}:{pubkey}")) is refused, and
Agent.authHeaders() throws.
Privacy
- The server never sees your private key. Period.
- Nonces are consumed on use; replay-resistant.
- Payments settle on public chains — the explicitly selected EVM network — and are visible on-chain.
License
MIT.
Payment receipt authorization
AIFP-1 receipts use the paying wallet signature. See authorize and recover payment receipts for retries and recovery without a second transfer.
A gateway site keeps its merchant
fetchPaid pins each gateway site (https://gateway.aifinpay.io/<slug>) to the
merchant it was first paid as, before any money moves. A later 402 that names
another merchant for the same site throws Aifp1MerchantChangedError
(code: "AIFP1_MERCHANT_CHANGED") before anything is quoted. If the change is
legitimate, say so for that call; paying re-pins the site:
await agent.fetchPaid(url, {}, { allowMerchantChange: true });
// or name the merchant you trust; any other one is refused:
await agent.fetchPaid(url, {}, { expectedMerchantId: "mrch_…" });
Pins are one file per site under ~/.aifinpay/merchant-pins (or
AIFINPAY_STATE_DIR), so they survive restarts; pass merchantPins to the
agent to keep them elsewhere. An unreadable pin refuses payment rather than
pinning afresh. resourcePathMode: "direct" is not pinned, because its cache
key already names the merchant. A slug the gateway retired answers
410 gateway_retired, which throws Aifp1GatewayRetiredError
(code: "AIFP1_GATEWAY_RETIRED") after one request; do not retry it. Batches
already held are kept.
Already paid: Aifp1ActiveReceiptError
Within a minute of a settled purchase, /v1/quote answers the same payer and
resource with the existing receipt instead of a quote, so a retry cannot buy
twice. A process that holds no receipt for it (a new process, or one whose
fetchPaid failed after paying) gets Aifp1ActiveReceiptError
(code: "AIFP1_ACTIVE_RECEIPT", a subclass of Aifp1QuoteError). Nothing was
quoted or paid. It names receiptId, txRef, chain, merchantId,
resource and settledAt; recover that receipt from the recovery your
onPrepared saved for txRef:
try {
return await agent.fetchPaid(url, {}, opts);
} catch (e) {
if (!(e instanceof Aifp1ActiveReceiptError)) throw e;
const paid = await agent.recoverPaidPayment(savedRecoveryFor(e.txRef));
return fetch(url, { headers: { "AIFP-Receipt": paid.receipt } });
}
Do not retry fetchPaid to get past it: once the minute is over a new quote is
a new purchase. With no saved recovery for txRef the receipt cannot be
reissued; buying again is a new payment and the owner's decision.
Name the AI client that pays
Set framework to the AI client the agent runs in, and every request of the
payment flow carries AIFP-Agent-Framework: <value>: the first request that
may answer 402, /v1/quote, /v1/pay and the paid retry (and the generic
x402 pay()). The merchant dashboard then shows which AI client paid.
const agent = await AiFinPayAgent.fromEnvironment({ framework: "claude" });
Accepted values (AGENT_FRAMEWORKS): chatgpt, claude, perplexity,
gemini, cursor, openai-agents, windsurf, codex, custom. Anything
else throws
AiFinPayError when the agent is constructed. Leave it unset and no header is
sent; the SDK never guesses. A host that learns its client after constructing
the agent passes it per call: fetchPaid(url, init, { framework }) or
recoverPaidPayment(recovery, { framework }).
Payment history
import { getAgentHistory } from "@aifinpay/agent";
const history = await getAgentHistory({ address: "0x…", source: "transactions" });
// Or { passport: 'AIFP-000000042', network: 'polygon', source: 'receipts' }
transactions covers indexed AiFinPay Polygon settlements, not arbitrary
wallet transfers. receipts covers retained prepaid batches, including test
payments. Follow next_offset with limit/offset. A passport requires a
backend with the verified-wallet resolver deployed; passing both address and
passport checks their match. Never pass a holder private key or API secret as
an identifier. Public history does not return bearer receipt tokens.