New:Socket for Asana Is Now Available.Learn more
Get Started

payagent

Package Overview
Dependencies
Maintainers
1
Versions
39
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

payagent - npm Package Compare versions

Comparing version
2.20.0
to
2.21.0
+504
dist/chunk-HRS6PG4A.js
// src/errors.ts
var PayAgentError = class extends Error {
constructor(message) {
super(message);
this.name = "PayAgentError";
}
};
var PaymentRejectedError = class extends PayAgentError {
status;
constructor(status, message) {
super(message ?? `Server rejected payment (HTTP ${status})`);
this.name = "PaymentRejectedError";
this.status = status;
}
};
var InvalidRequirementsError = class extends PayAgentError {
constructor(detail) {
super(`Could not parse 402 payment requirements${detail ? `: ${detail}` : ""}`);
this.name = "InvalidRequirementsError";
}
};
// src/payment.ts
var NETWORK_SHORT_TO_CAIP2 = {
ethereum: "eip155:1",
polygon: "eip155:137",
base: "eip155:8453",
"base-sepolia": "eip155:84532",
bsc: "eip155:56",
solana: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
"solana-devnet": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1"
};
function normalizeNetwork(network) {
if (network.includes(":")) return network;
return NETWORK_SHORT_TO_CAIP2[network] ?? network;
}
var NETWORK_CAIP2_TO_SHORT = Object.fromEntries(
Object.entries(NETWORK_SHORT_TO_CAIP2).map(([s, c]) => [c, s])
);
function isStandardFormat(body) {
return "accepts" in body && Array.isArray(body.accepts);
}
function isFlatFormat(body) {
return "scheme" in body && "payTo" in body && !("accepts" in body);
}
function extractAccepts(body) {
if (isStandardFormat(body)) {
return body.accepts.map((a) => {
const rawAmount = a.amount ?? a.maxAmountRequired;
return {
...a,
network: normalizeNetwork(a.network),
amount: rawAmount
};
});
}
if (isFlatFormat(body)) {
return [
{
scheme: body.scheme,
network: normalizeNetwork(body.network),
amount: body.amount ?? body.maxAmountRequired,
resource: body.resource,
asset: body.asset,
payTo: body.payTo,
extra: { name: "USDC", version: "2" }
}
];
}
throw new InvalidRequirementsError("unrecognized format");
}
function extractExtensions(source) {
const ext = source?.extensions;
return ext && typeof ext === "object" ? ext : void 0;
}
function extractRawAccepts(body) {
if (isStandardFormat(body)) return body.accepts;
if (isFlatFormat(body)) return [body];
return [];
}
var HEADER_LOOKUP_ORDER = [
"payment-required",
"x-payment-required",
"x-payment-requirements"
];
function tryParseHeaderValue(value) {
try {
const decoded = Buffer.from(value, "base64").toString("utf8");
const parsed = JSON.parse(decoded);
if (parsed && typeof parsed === "object") return parsed;
} catch {
}
try {
const parsed = JSON.parse(value);
if (parsed && typeof parsed === "object") return parsed;
} catch {
}
return null;
}
async function parseRequirements(response) {
for (const headerName of HEADER_LOOKUP_ORDER) {
const value = response.headers.get(headerName);
if (!value) continue;
const parsed = tryParseHeaderValue(value);
if (!parsed) continue;
try {
const body2 = parsed.requirements ?? parsed;
const accepts = extractAccepts(body2);
if (accepts.length > 0) {
return {
accepts,
rawAccepts: extractRawAccepts(body2),
x402Version: typeof parsed.x402Version === "number" ? parsed.x402Version : 2,
extensions: extractExtensions(parsed) ?? extractExtensions(body2)
};
}
} catch {
}
}
let json;
try {
json = await response.json();
} catch {
throw new InvalidRequirementsError("response body is not valid JSON");
}
const body = json.requirements ?? json;
if (!body || typeof body !== "object") {
throw new InvalidRequirementsError("invalid 402 response body");
}
const version = typeof body.x402Version === "number" ? body.x402Version : 1;
return {
accepts: extractAccepts(body),
rawAccepts: extractRawAccepts(body),
x402Version: version,
extensions: extractExtensions(json) ?? extractExtensions(body)
};
}
// src/fetch-delegated.ts
function payFetchDelegated(config) {
if (!config.arispayUrl) throw new Error("arispayUrl is required");
if (!config.apiKey) throw new Error("apiKey is required");
const baseUrl = config.arispayUrl.replace(/\/$/, "");
const signPath = config.signPath ?? "/v1/x402/delegated-sign";
const timeoutMs = config.signTimeoutMs ?? 15e3;
return async (url, init) => {
const urlStr = url.toString();
const response = await fetch(urlStr, init);
if (response.status !== 402) return response;
const { accepts, rawAccepts, x402Version, extensions } = await parseRequirements(response);
if (accepts.length === 0) {
throw new InvalidRequirementsError("no payment options in 402 response");
}
let acceptIdx = accepts.findIndex((a) => a.network.startsWith("eip155:"));
if (acceptIdx < 0) {
acceptIdx = accepts.findIndex((a) => a.network in SVM_CHAIN_LABELS);
}
const accept = acceptIdx >= 0 ? accepts[acceptIdx] : accepts[0];
const rawAccept = acceptIdx >= 0 ? rawAccepts[acceptIdx] : rawAccepts[0];
if (!accept.network.startsWith("eip155:") && !(accept.network in SVM_CHAIN_LABELS)) {
throw new InvalidRequirementsError(
`delegated-sign requires an eip155 or solana variant, got ${accept.network}`
);
}
const chainLabel = chainLabelForNetwork(accept.network);
if (!chainLabel) {
throw new InvalidRequirementsError(
`Unsupported network for delegated-sign: ${accept.network}`
);
}
const signRes = await fetch(`${baseUrl}${signPath}`, {
method: "POST",
headers: {
Authorization: `Bearer ${config.apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
paymentRequirements: {
chain: chainLabel,
tokenAddress: accept.asset,
payeeAddress: accept.payTo,
amount: accept.amount,
extra: accept.extra
},
resourceUrl: urlStr,
x402Version,
acceptedRequirement: rawAccept,
// Challenge extensions (bazaar discovery declaration): the signer
// echoes them into the payment payload so facilitators can catalog
// the seller. Keep in lockstep with fetch-local.ts.
...extensions ? { extensions } : {}
}),
signal: AbortSignal.timeout(timeoutMs)
});
if (!signRes.ok) {
const body = await signRes.json().catch(() => ({}));
const msg = body?.error?.message ?? `${signRes.status} ${signRes.statusText}`;
throw new PaymentRejectedError(signRes.status, `ArisPay delegated-sign rejected: ${msg}`);
}
const signed = await signRes.json();
if (signed.status === "failed" || !signed.paymentHeader) {
throw new PaymentRejectedError(502, "ArisPay delegated-sign returned no header");
}
if (config.onPayment && signed.spend) {
const { amountCents, dailySpend, monthlySpend, limits } = signed.spend;
try {
config.onPayment({
amountCents,
chain: signed.chain,
walletAddress: signed.walletAddress,
dailySpend,
monthlySpend,
limits,
remainingDaily: Math.max(0, limits.maxDaily - dailySpend),
remainingMonthly: Math.max(0, limits.maxMonthly - monthlySpend)
});
} catch {
}
}
if (process.env.PAYAGENT_DEBUG === "1") {
try {
const decoded = Buffer.from(signed.paymentHeader, "base64").toString("utf-8");
process.stderr.write(`[payagent] X-PAYMENT (decoded): ${decoded}
`);
} catch {
process.stderr.write(`[payagent] X-PAYMENT (base64): ${signed.paymentHeader}
`);
}
}
const retryHeaders = new Headers(init?.headers);
retryHeaders.set("X-PAYMENT", signed.paymentHeader);
if (x402Version === 2) {
retryHeaders.set("PAYMENT-SIGNATURE", signed.paymentHeader);
}
const paid = await fetch(urlStr, { ...init, headers: retryHeaders });
if (paid.status === 402) {
const body = await paid.text().catch(() => "");
throw new PaymentRejectedError(
402,
`Server returned 402 after payment was signed and sent. Seller response: ${body.slice(0, 1e3)}`
);
}
return paid;
};
}
var CHAIN_LABELS = {
1: "ethereum",
137: "polygon",
8453: "base",
84532: "base-sepolia"
};
var SVM_CHAIN_LABELS = {
"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp": "solana",
"solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1": "solana-devnet"
};
function chainLabelForNetwork(network) {
if (network.startsWith("eip155:")) {
const chainId = Number.parseInt(network.split(":")[1] ?? "", 10);
return CHAIN_LABELS[chainId];
}
return SVM_CHAIN_LABELS[network];
}
// src/balance.ts
import { Contract, JsonRpcProvider } from "ethers";
var USDC_CONTRACTS = {
// Base
base: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"base-sepolia": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
// Ethereum
ethereum: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
// Polygon
polygon: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"
};
var DEFAULT_RPCS = {
base: "https://mainnet.base.org",
"base-sepolia": "https://sepolia.base.org",
ethereum: "https://eth.llamarpc.com",
polygon: "https://polygon-rpc.com"
};
var ERC20_BALANCE_ABI = ["function balanceOf(address) view returns (uint256)"];
async function getUSDCBalance(walletAddress, chain = "base", rpcUrl) {
const contractAddress = USDC_CONTRACTS[chain];
if (!contractAddress) {
throw new Error(`getUSDCBalance: unsupported chain "${chain}"`);
}
const url = rpcUrl ?? DEFAULT_RPCS[chain];
if (!url) {
throw new Error(`getUSDCBalance: no RPC for chain "${chain}" \u2014 pass rpcUrl explicitly`);
}
const provider = new JsonRpcProvider(url);
const usdc = new Contract(contractAddress, ERC20_BALANCE_ABI, provider);
const raw = await usdc.balanceOf(walletAddress);
return raw;
}
var CAIP2_RPCS = {
"eip155:8453": "https://mainnet.base.org",
"eip155:84532": "https://sepolia.base.org",
"eip155:1": "https://eth.llamarpc.com",
"eip155:137": "https://polygon-rpc.com",
"eip155:56": "https://bsc-dataseed.bnbchain.org"
};
async function getErc20Balance(asset, walletAddress, caip2Network, rpcUrl) {
const url = rpcUrl ?? CAIP2_RPCS[caip2Network];
if (!url) return void 0;
const provider = new JsonRpcProvider(url);
try {
const token = new Contract(asset, ERC20_BALANCE_ABI, provider);
return await token.balanceOf(walletAddress);
} finally {
provider.destroy();
}
}
function formatUSDC(baseUnits) {
const s = baseUnits.toString().padStart(7, "0");
const whole = s.slice(0, -6);
const frac = s.slice(-6).replace(/0+$/, "");
return frac ? `${whole}.${frac}` : whole;
}
// src/fetch-local.ts
import { ethers } from "ethers";
var BALANCE_CHECK_TIMEOUT_MS = 4e3;
async function bestEffortBalance(config, args) {
const fetcher = config.balanceFetcher ?? (({ asset, walletAddress, network, rpcUrl }) => getErc20Balance(asset, walletAddress, network, rpcUrl));
try {
return await Promise.race([
fetcher({ ...args, rpcUrl: config.rpcUrl }),
new Promise(
(_, reject) => setTimeout(() => reject(new Error("balance check timed out")), BALANCE_CHECK_TIMEOUT_MS)
)
]);
} catch {
return void 0;
}
}
function retryRejectionReason(paid) {
const header = paid.headers.get("payment-required") ?? paid.headers.get("PAYMENT-REQUIRED");
if (!header) return void 0;
try {
const decoded = JSON.parse(Buffer.from(header, "base64").toString("utf-8"));
return typeof decoded.error === "string" && decoded.error ? decoded.error : void 0;
} catch {
return void 0;
}
}
function deriveLocalWalletAddress(privateKey) {
return new ethers.Wallet(privateKey).address;
}
var TRANSFER_WITH_AUTHORIZATION_TYPES = {
TransferWithAuthorization: [
{ name: "from", type: "address" },
{ name: "to", type: "address" },
{ name: "value", type: "uint256" },
{ name: "validAfter", type: "uint256" },
{ name: "validBefore", type: "uint256" },
{ name: "nonce", type: "bytes32" }
]
};
function payFetchLocal(config) {
if (!config.privateKey) throw new Error("privateKey is required");
const wallet = new ethers.Wallet(config.privateKey);
const maxPerTx = config.maxPerTxBaseUnits !== void 0 ? BigInt(config.maxPerTxBaseUnits) : void 0;
return async (url, init) => {
const urlStr = url.toString();
const response = await fetch(urlStr, init);
if (response.status !== 402) return response;
const { accepts, rawAccepts, x402Version, extensions } = await parseRequirements(response);
if (accepts.length === 0) {
throw new InvalidRequirementsError("no payment options in 402 response");
}
const acceptIdx = accepts.findIndex((a) => a.network.startsWith("eip155:"));
if (acceptIdx < 0) {
throw new InvalidRequirementsError(
`local signer requires an eip155 variant, got ${accepts[0]?.network ?? "none"}`
);
}
const accept = accepts[acceptIdx];
const rawAccept = rawAccepts[acceptIdx];
if (maxPerTx !== void 0 && BigInt(accept.amount) > maxPerTx) {
throw new PaymentRejectedError(
402,
`402 asks ${accept.amount} base units, above maxPerTxBaseUnits ${maxPerTx}`
);
}
if (config.balanceCheck !== false) {
const held = await bestEffortBalance(config, {
asset: accept.asset,
walletAddress: wallet.address,
network: accept.network
});
if (held !== void 0 && held < BigInt(accept.amount)) {
throw new PaymentRejectedError(
402,
`Wallet ${wallet.address} holds ${formatUSDC(held)} of the required asset but this request costs ${formatUSDC(BigInt(accept.amount))} (asset ${accept.asset} on ${accept.network}). Fund ${wallet.address} on that network and retry.`
);
}
}
const domainInfo = accept.extra;
if (!domainInfo?.name || !domainInfo?.version) {
throw new InvalidRequirementsError(
"402 accept is missing extra.name/extra.version (EIP-712 domain) \u2014 cannot sign locally"
);
}
const chainId = Number.parseInt(accept.network.split(":")[1] ?? "", 10);
if (!Number.isFinite(chainId)) {
throw new InvalidRequirementsError(`cannot parse chainId from network ${accept.network}`);
}
const now = Math.floor(Date.now() / 1e3);
const authorization = {
from: wallet.address,
to: accept.payTo,
value: accept.amount,
validAfter: (now - 60).toString(),
validBefore: (now + 480).toString(),
// 8-minute window, mirrors x402-core signing
nonce: ethers.hexlify(ethers.randomBytes(32))
};
const signature = await wallet.signTypedData(
{
name: domainInfo.name,
version: domainInfo.version,
chainId,
verifyingContract: ethers.getAddress(accept.asset)
},
TRANSFER_WITH_AUTHORIZATION_TYPES,
{
from: ethers.getAddress(authorization.from),
to: ethers.getAddress(authorization.to),
value: BigInt(authorization.value),
validAfter: BigInt(authorization.validAfter),
validBefore: BigInt(authorization.validBefore),
nonce: authorization.nonce
}
);
const effectiveVersion = x402Version ?? 2;
const paymentHeader = Buffer.from(
JSON.stringify({
x402Version: effectiveVersion,
payload: { signature, authorization },
accepted: rawAccept,
// v2 PaymentPayload.resource is a ResourceInfo object, not a bare
// string (spec + what bazaar discovery extraction reads).
resource: { url: accept.resource },
// Echo the challenge's extensions (bazaar discovery declaration):
// facilitators catalog discoverable sellers from this echo. Keep in
// lockstep with fetch-delegated.ts.
...extensions ? { extensions } : {}
})
).toString("base64");
if (config.onPayment) {
try {
config.onPayment({
amount: accept.amount,
network: accept.network,
asset: accept.asset,
payTo: accept.payTo,
walletAddress: wallet.address
});
} catch {
}
}
if (process.env.PAYAGENT_DEBUG === "1") {
try {
const decoded = Buffer.from(paymentHeader, "base64").toString("utf-8");
process.stderr.write(`[payagent] X-PAYMENT (decoded): ${decoded}
`);
} catch {
process.stderr.write(`[payagent] X-PAYMENT (base64): ${paymentHeader}
`);
}
}
const retryHeaders = new Headers(init?.headers);
retryHeaders.set("X-PAYMENT", paymentHeader);
if (effectiveVersion === 2) {
retryHeaders.set("PAYMENT-SIGNATURE", paymentHeader);
}
const paid = await fetch(urlStr, { ...init, headers: retryHeaders });
if (paid.status === 402) {
const reason = retryRejectionReason(paid);
const body = await paid.text().catch(() => "");
const detail = reason ?? (body.trim() && body.trim() !== "{}" ? body.slice(0, 1e3) : "");
throw new PaymentRejectedError(
402,
`Server returned 402 after payment was signed and sent` + (detail ? ` \u2014 reason: ${detail}` : "") + `. Paying wallet: ${wallet.address} on ${accept.network}. Common causes: insufficient ${accept.asset} balance, or the authorization window expired. Check the balance, then retry.`
);
}
return paid;
};
}
export {
PayAgentError,
PaymentRejectedError,
InvalidRequirementsError,
payFetchDelegated,
USDC_CONTRACTS,
getUSDCBalance,
getErc20Balance,
formatUSDC,
deriveLocalWalletAddress,
payFetchLocal
};
//# sourceMappingURL=chunk-HRS6PG4A.js.map
{"version":3,"sources":["../src/errors.ts","../src/payment.ts","../src/fetch-delegated.ts","../src/balance.ts","../src/fetch-local.ts"],"sourcesContent":["/**\n * payagent — Error classes.\n */\n\n/** Base class for all payagent errors. */\nexport class PayAgentError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"PayAgentError\";\n }\n}\n\n/** The payment was signed and sent but the server still rejected it, or ArisPay refused to sign. */\nexport class PaymentRejectedError extends PayAgentError {\n public readonly status: number;\n\n constructor(status: number, message?: string) {\n super(message ?? `Server rejected payment (HTTP ${status})`);\n this.name = \"PaymentRejectedError\";\n this.status = status;\n }\n}\n\n/** Could not parse 402 response body as valid payment requirements. */\nexport class InvalidRequirementsError extends PayAgentError {\n constructor(detail?: string) {\n super(`Could not parse 402 payment requirements${detail ? `: ${detail}` : \"\"}`);\n this.name = \"InvalidRequirementsError\";\n }\n}\n","import { InvalidRequirementsError } from \"./errors.js\";\n/**\n * payagent — x402 402-response parsing utilities.\n *\n * These are the shared primitives used by `payFetchDelegated` to normalize\n * a seller's 402 body into a usable `accepts` list, independent of whether\n * it's emitted in x402-v2 standard shape or the legacy flat/AgFac shape.\n */\nimport type {\n AgfacFlatRequirements,\n PaymentRequirementsBody,\n X402Accept,\n X402Requirements,\n} from \"./types.js\";\n\n// Coinbase's reference x402 middleware emits short network names\n// (\"base-sepolia\", \"solana\"), while the x402 v2 spec and our internal code\n// use CAIP-2 (\"eip155:84532\", \"solana:<genesis-hash>\"). Accept either on\n// the way in.\nconst NETWORK_SHORT_TO_CAIP2: Record<string, string> = {\n ethereum: \"eip155:1\",\n polygon: \"eip155:137\",\n base: \"eip155:8453\",\n \"base-sepolia\": \"eip155:84532\",\n bsc: \"eip155:56\",\n solana: \"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp\",\n \"solana-devnet\": \"solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1\",\n};\n\nexport function normalizeNetwork(network: string): string {\n if (network.includes(\":\")) return network; // already CAIP-2\n return NETWORK_SHORT_TO_CAIP2[network] ?? network;\n}\n\nconst NETWORK_CAIP2_TO_SHORT: Record<string, string> = Object.fromEntries(\n Object.entries(NETWORK_SHORT_TO_CAIP2).map(([s, c]) => [c, s]),\n);\n\n/** Convert CAIP-2 back to the short network name x402 sellers emit on the wire. */\nexport function denormalizeNetwork(network: string): string {\n if (!network.includes(\":\")) return network; // already short\n return NETWORK_CAIP2_TO_SHORT[network] ?? network;\n}\n\nfunction isStandardFormat(body: PaymentRequirementsBody): body is X402Requirements {\n return \"accepts\" in body && Array.isArray(body.accepts);\n}\n\nfunction isFlatFormat(body: PaymentRequirementsBody): body is AgfacFlatRequirements {\n return \"scheme\" in body && \"payTo\" in body && !(\"accepts\" in body);\n}\n\n/** Normalize both x402 v2 standard and AgFac flat format into accepts array. */\nfunction extractAccepts(body: PaymentRequirementsBody): X402Accept[] {\n if (isStandardFormat(body)) {\n return body.accepts.map((a) => {\n // x402 v2 canonical name is `amount`; some legacy sellers still emit\n // the v1 name `maxAmountRequired`. Accept either, normalize to `amount`.\n const rawAmount =\n (a as { amount?: string }).amount ??\n (a as { maxAmountRequired?: string }).maxAmountRequired;\n return {\n ...a,\n network: normalizeNetwork(a.network),\n amount: rawAmount as string,\n };\n });\n }\n if (isFlatFormat(body)) {\n return [\n {\n scheme: body.scheme,\n network: normalizeNetwork(body.network),\n amount: (body.amount ?? body.maxAmountRequired) as string,\n resource: body.resource,\n asset: body.asset,\n payTo: body.payTo,\n extra: { name: \"USDC\", version: \"2\" },\n },\n ];\n }\n throw new InvalidRequirementsError(\"unrecognized format\");\n}\n\nexport interface ParsedRequirements {\n accepts: X402Accept[];\n /**\n * Raw accept objects, byte-for-byte as the seller advertised them. x402 v2\n * servers match paymentPayload.accepted against their paymentRequirements\n * via deepEqual — our signed payload must echo the original accept without\n * added/normalised fields, else the merchant rejects with a silent 402.\n */\n rawAccepts: unknown[];\n /** x402 protocol version advertised by the seller. Defaults to 1. */\n x402Version: number;\n /**\n * Top-level `extensions` object from the challenge (e.g. the bazaar\n * discovery declaration), byte-for-byte. Payers echo it into the payment\n * payload — facilitators catalog discoverable resources from that echo,\n * so dropping it silently keeps every seller out of organic discovery.\n */\n extensions?: Record<string, unknown>;\n}\n\nfunction extractExtensions(source: unknown): Record<string, unknown> | undefined {\n const ext = (source as { extensions?: unknown } | null | undefined)?.extensions;\n return ext && typeof ext === \"object\" ? (ext as Record<string, unknown>) : undefined;\n}\n\nfunction extractRawAccepts(body: PaymentRequirementsBody): unknown[] {\n if (isStandardFormat(body)) return body.accepts as unknown[];\n if (isFlatFormat(body)) return [body as unknown];\n return [];\n}\n\n/**\n * Header names sellers use to advertise payment requirements, in\n * preference order. Audited against 128 live Bazaar-listed endpoints\n * on 2026-04-30 (see /docs/x402-wire-format-audit.md in the monorepo):\n *\n * - `payment-required` : 95.3% of canonical x402 emits\n * - `x-payment-required` : 10% (variant)\n * - `x-payment-requirements` : paygate ≤ 5.0 only (one in-house emit)\n *\n * Header values are usually base64-encoded JSON (canonical) but some\n * sellers (notably older paygate) emit raw JSON. Try base64 first,\n * fall back to raw JSON parse.\n */\nconst HEADER_LOOKUP_ORDER = [\n \"payment-required\",\n \"x-payment-required\",\n \"x-payment-requirements\",\n] as const;\n\nfunction tryParseHeaderValue(value: string): Record<string, unknown> | null {\n // base64 (canonical wire format).\n try {\n const decoded = Buffer.from(value, \"base64\").toString(\"utf8\");\n const parsed = JSON.parse(decoded);\n if (parsed && typeof parsed === \"object\") return parsed as Record<string, unknown>;\n } catch {\n // not valid base64 JSON, try raw.\n }\n // Raw JSON (paygate's pre-5.1 emit; some custom sellers).\n try {\n const parsed = JSON.parse(value);\n if (parsed && typeof parsed === \"object\") return parsed as Record<string, unknown>;\n } catch {\n // not parseable.\n }\n return null;\n}\n\n/** Parse the 402 response body and extract payment requirements. */\nexport async function parseRequirements(response: Response): Promise<ParsedRequirements> {\n // Header path: try the canonical name first, then variants. Any\n // header that yields a recognizable accepts array wins; otherwise we\n // fall through to body parsing.\n for (const headerName of HEADER_LOOKUP_ORDER) {\n const value = response.headers.get(headerName);\n if (!value) continue;\n const parsed = tryParseHeaderValue(value);\n if (!parsed) continue;\n try {\n const body = (parsed.requirements ?? parsed) as PaymentRequirementsBody;\n const accepts = extractAccepts(body);\n if (accepts.length > 0) {\n return {\n accepts,\n rawAccepts: extractRawAccepts(body),\n x402Version: typeof parsed.x402Version === \"number\" ? parsed.x402Version : 2,\n extensions: extractExtensions(parsed) ?? extractExtensions(body),\n };\n }\n } catch {\n // unrecognized format from this header — try the next.\n }\n }\n\n let json: Record<string, unknown>;\n try {\n json = await response.json();\n } catch {\n throw new InvalidRequirementsError(\"response body is not valid JSON\");\n }\n\n // Some servers nest requirements under a `requirements` key\n const body = (json.requirements ?? json) as PaymentRequirementsBody;\n\n if (!body || typeof body !== \"object\") {\n throw new InvalidRequirementsError(\"invalid 402 response body\");\n }\n // x402Version 1 is what the Coinbase reference middleware emits; v2 is the\n // newer draft. Echo whichever version the seller advertised.\n const version =\n typeof (body as { x402Version?: unknown }).x402Version === \"number\"\n ? (body as { x402Version: number }).x402Version\n : 1;\n\n return {\n accepts: extractAccepts(body),\n rawAccepts: extractRawAccepts(body),\n x402Version: version,\n extensions: extractExtensions(json) ?? extractExtensions(body),\n };\n}\n","import { InvalidRequirementsError, PaymentRejectedError } from \"./errors.js\";\n/**\n * payagent — Delegated fetch wrapper.\n *\n * Server-side signing variant: instead of holding the private key locally and\n * signing with ethers, call an ArisPay delegated-sign endpoint that signs via\n * the CDP-managed wallet AND enforces per-tx / daily / monthly limits +\n * allowedDomains. This is the path to use with agents created via\n * `DelegationClient.createX402Agent()`.\n *\n * Usage:\n * const fetch402 = payFetchDelegated({\n * arispayUrl: 'http://localhost:3001',\n * apiKey: 'ap_test_...', // the agent's own key\n * });\n * const res = await fetch402('https://api.example.com/premium');\n */\nimport { parseRequirements } from \"./payment.js\";\n\nexport interface PayFetchDelegatedConfig {\n /** Base URL for the ArisPay API (no trailing slash). */\n arispayUrl: string;\n /** The x402 agent's own API key (returned by DelegationClient.createX402Agent). */\n apiKey: string;\n /** Override for the ArisPay delegated-sign path. Default: /v1/x402/delegated-sign */\n signPath?: string;\n /** Request timeout for the sign call (ms). Default: 15000. */\n signTimeoutMs?: number;\n /**\n * Fires once per paid request, right after ArisPay signs (spend counters\n * are already committed server-side at that point), with the amount and\n * the agent's remaining budget. Advisory: a throwing callback is swallowed\n * and never breaks the payment flow.\n */\n onPayment?: (info: DelegatedPaymentInfo) => void;\n}\n\nexport type PayFetchFn = (url: string | URL, init?: RequestInit) => Promise<Response>;\n\n/**\n * Per-payment budget snapshot surfaced via `onPayment`. All amounts are\n * integer cents. Spend counters and remaining headroom are AFTER this\n * payment (the server returns post-increment counters).\n */\nexport interface DelegatedPaymentInfo {\n /** Cents charged for this payment. */\n amountCents: number;\n chain: string;\n walletAddress: string;\n /** Cents spent today (UTC calendar day), including this payment. */\n dailySpend: number;\n /** Cents spent this month (UTC calendar month), including this payment. */\n monthlySpend: number;\n limits: { maxPerTx: number; maxDaily: number; maxMonthly: number };\n /** Cents of daily budget left after this payment. */\n remainingDaily: number;\n /** Cents of monthly budget left after this payment. */\n remainingMonthly: number;\n}\n\ninterface DelegatedSignResponse {\n paymentHeader: string;\n chain: string;\n status: \"settled\" | \"pending\" | \"failed\";\n walletAddress: string;\n spend: {\n amountCents: number;\n dailySpend: number;\n monthlySpend: number;\n limits: { maxPerTx: number; maxDaily: number; maxMonthly: number };\n };\n}\n\n/**\n * Create a fetch wrapper that delegates EIP-3009 signing to ArisPay.\n * No private key lives on the caller's machine; ArisPay enforces the\n * delegation limits before signing and increments spend counters on success.\n */\nexport function payFetchDelegated(config: PayFetchDelegatedConfig): PayFetchFn {\n if (!config.arispayUrl) throw new Error(\"arispayUrl is required\");\n if (!config.apiKey) throw new Error(\"apiKey is required\");\n const baseUrl = config.arispayUrl.replace(/\\/$/, \"\");\n const signPath = config.signPath ?? \"/v1/x402/delegated-sign\";\n const timeoutMs = config.signTimeoutMs ?? 15_000;\n\n return async (url, init) => {\n const urlStr = url.toString();\n const response = await fetch(urlStr, init);\n if (response.status !== 402) return response;\n\n const { accepts, rawAccepts, x402Version, extensions } = await parseRequirements(response);\n if (accepts.length === 0) {\n throw new InvalidRequirementsError(\"no payment options in 402 response\");\n }\n // The delegated-sign endpoint supports eip155 (EVM) and solana (SVM)\n // variants. Prefer eip155 to keep existing sellers' behavior stable;\n // fall through to a known solana network when the seller offers no\n // EVM option.\n let acceptIdx = accepts.findIndex((a) => a.network.startsWith(\"eip155:\"));\n if (acceptIdx < 0) {\n acceptIdx = accepts.findIndex((a) => a.network in SVM_CHAIN_LABELS);\n }\n const accept = acceptIdx >= 0 ? accepts[acceptIdx] : accepts[0];\n // rawAccept is the byte-for-byte challenge accept for this option (pre-normalisation).\n // Required for v2 deepEqual(paymentRequirements, paymentPayload.accepted) matching.\n const rawAccept = acceptIdx >= 0 ? rawAccepts[acceptIdx] : rawAccepts[0];\n if (!accept.network.startsWith(\"eip155:\") && !(accept.network in SVM_CHAIN_LABELS)) {\n throw new InvalidRequirementsError(\n `delegated-sign requires an eip155 or solana variant, got ${accept.network}`,\n );\n }\n\n // Derive ArisPay's `chain` label from CAIP-2.\n const chainLabel = chainLabelForNetwork(accept.network);\n if (!chainLabel) {\n throw new InvalidRequirementsError(\n `Unsupported network for delegated-sign: ${accept.network}`,\n );\n }\n\n // Ask ArisPay to sign.\n //\n // We forward the RAW challenge accept object as `acceptedRequirement` so\n // the server can use it verbatim as `paymentPayload.accepted`. x402 v2's\n // findMatchingRequirements uses deepEqual(paymentRequirements, payload.accepted) —\n // any added/missing field breaks the match silently (402 with {}). Do NOT\n // modify or normalise fields between here and the signer.\n const signRes = await fetch(`${baseUrl}${signPath}`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${config.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n paymentRequirements: {\n chain: chainLabel,\n tokenAddress: accept.asset,\n payeeAddress: accept.payTo,\n amount: accept.amount,\n extra: accept.extra,\n },\n resourceUrl: urlStr,\n x402Version,\n acceptedRequirement: rawAccept,\n // Challenge extensions (bazaar discovery declaration): the signer\n // echoes them into the payment payload so facilitators can catalog\n // the seller. Keep in lockstep with fetch-local.ts.\n ...(extensions ? { extensions } : {}),\n }),\n signal: AbortSignal.timeout(timeoutMs),\n });\n\n if (!signRes.ok) {\n const body = (await signRes.json().catch(() => ({}))) as { error?: { message?: string } };\n const msg = body?.error?.message ?? `${signRes.status} ${signRes.statusText}`;\n throw new PaymentRejectedError(signRes.status, `ArisPay delegated-sign rejected: ${msg}`);\n }\n\n const signed = (await signRes.json()) as DelegatedSignResponse;\n if (signed.status === \"failed\" || !signed.paymentHeader) {\n throw new PaymentRejectedError(502, \"ArisPay delegated-sign returned no header\");\n }\n\n if (config.onPayment && signed.spend) {\n const { amountCents, dailySpend, monthlySpend, limits } = signed.spend;\n try {\n config.onPayment({\n amountCents,\n chain: signed.chain,\n walletAddress: signed.walletAddress,\n dailySpend,\n monthlySpend,\n limits,\n remainingDaily: Math.max(0, limits.maxDaily - dailySpend),\n remainingMonthly: Math.max(0, limits.maxMonthly - monthlySpend),\n });\n } catch {\n // Advisory metadata must never break the payment flow.\n }\n }\n\n // Debug: dump the decoded X-PAYMENT so we can diagnose verifier rejects.\n // Enable with PAYAGENT_DEBUG=1. Prints to stderr so it doesn't corrupt\n // stdout piping. Contains only public challenge data + a signature.\n if (process.env.PAYAGENT_DEBUG === \"1\") {\n try {\n const decoded = Buffer.from(signed.paymentHeader, \"base64\").toString(\"utf-8\");\n process.stderr.write(`[payagent] X-PAYMENT (decoded): ${decoded}\\n`);\n } catch {\n process.stderr.write(`[payagent] X-PAYMENT (base64): ${signed.paymentHeader}\\n`);\n }\n }\n\n // Retry with the version-appropriate payment header. The x402 v2 wire\n // protocol renamed it: v1 middlewares read `X-PAYMENT`, upstream v2\n // middlewares (@x402/* ≥2.x) read ONLY `PAYMENT-SIGNATURE` — sending a\n // v2 payload as X-PAYMENT is silently treated as \"no payment at all\"\n // (an unpaid 402 with empty body, no verify ever reaching the\n // facilitator). Both headers are set so mislabeled sellers on either\n // side of the rename still find the payload; servers read exactly one.\n const retryHeaders = new Headers(init?.headers);\n retryHeaders.set(\"X-PAYMENT\", signed.paymentHeader);\n if (x402Version === 2) {\n retryHeaders.set(\"PAYMENT-SIGNATURE\", signed.paymentHeader);\n }\n const paid = await fetch(urlStr, { ...init, headers: retryHeaders });\n if (paid.status === 402) {\n // Read the seller's response body so callers can see the verifier's\n // actual rejection reason instead of a generic \"server returned 402\".\n const body = await paid.text().catch(() => \"\");\n throw new PaymentRejectedError(\n 402,\n `Server returned 402 after payment was signed and sent. Seller response: ${body.slice(0, 1000)}`,\n );\n }\n return paid;\n };\n}\n\n// CAIP-2 chainId → ArisPay provider chain label.\nconst CHAIN_LABELS: Record<number, string> = {\n 1: \"ethereum\",\n 137: \"polygon\",\n 8453: \"base\",\n 84532: \"base-sepolia\",\n};\n\n// CAIP-2 solana genesis hash → ArisPay provider chain label.\nconst SVM_CHAIN_LABELS: Record<string, string> = {\n \"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp\": \"solana\",\n \"solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1\": \"solana-devnet\",\n};\n\nfunction chainLabelForNetwork(network: string): string | undefined {\n if (network.startsWith(\"eip155:\")) {\n const chainId = Number.parseInt(network.split(\":\")[1] ?? \"\", 10);\n return CHAIN_LABELS[chainId];\n }\n return SVM_CHAIN_LABELS[network];\n}\n","/**\n * payagent — On-chain USDC balance helper.\n *\n * Reads the USDC ERC-20 `balanceOf(address)` directly from an RPC.\n * Useful when you want an authoritative on-chain check independent\n * of any backend (e.g. ArisPay's balance endpoint is unreachable).\n */\nimport { Contract, JsonRpcProvider } from \"ethers\";\n\n/**\n * USDC contract addresses per chain.\n *\n * Canonical source is `@arispay/x402-core` (USDC_BASE_MAINNET, etc.).\n * This duplicate is intentional: `payagent` is a published, standalone npm\n * package and must NOT take a workspace dep on the private `@arispay/x402-core`\n * package — npm consumers wouldn't resolve it. Keep these four values in sync\n * with packages/x402-core/src/constants.ts by hand. Do not try to \"DRY\" by\n * importing — see P2-4 blocker discussion.\n */\nexport const USDC_CONTRACTS: Record<string, string> = {\n // Base\n base: \"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\",\n \"base-sepolia\": \"0x036CbD53842c5426634e7929541eC2318f3dCF7e\",\n // Ethereum\n ethereum: \"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\",\n // Polygon\n polygon: \"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359\",\n};\n\n/** Default public RPCs. Callers should pass their own for production. */\nconst DEFAULT_RPCS: Record<string, string> = {\n base: \"https://mainnet.base.org\",\n \"base-sepolia\": \"https://sepolia.base.org\",\n ethereum: \"https://eth.llamarpc.com\",\n polygon: \"https://polygon-rpc.com\",\n};\n\nconst ERC20_BALANCE_ABI = [\"function balanceOf(address) view returns (uint256)\"];\n\n/**\n * Fetch the USDC balance for a wallet on the given chain.\n *\n * @param walletAddress EVM address (0x…).\n * @param chain One of 'base' | 'base-sepolia' | 'ethereum' | 'polygon'. Default: 'base'.\n * @param rpcUrl Optional RPC URL override. Falls back to a public endpoint.\n * @returns USDC balance in 6-decimal base units as a bigint.\n */\nexport async function getUSDCBalance(\n walletAddress: string,\n chain: keyof typeof USDC_CONTRACTS = \"base\",\n rpcUrl?: string,\n): Promise<bigint> {\n const contractAddress = USDC_CONTRACTS[chain];\n if (!contractAddress) {\n throw new Error(`getUSDCBalance: unsupported chain \"${chain}\"`);\n }\n const url = rpcUrl ?? DEFAULT_RPCS[chain];\n if (!url) {\n throw new Error(`getUSDCBalance: no RPC for chain \"${chain}\" — pass rpcUrl explicitly`);\n }\n\n const provider = new JsonRpcProvider(url);\n const usdc = new Contract(contractAddress, ERC20_BALANCE_ABI, provider);\n const raw = (await usdc.balanceOf(walletAddress)) as bigint;\n return raw;\n}\n\n/** Default public RPCs keyed by CAIP-2 chain id (what an x402 challenge carries). */\nconst CAIP2_RPCS: Record<string, string> = {\n \"eip155:8453\": \"https://mainnet.base.org\",\n \"eip155:84532\": \"https://sepolia.base.org\",\n \"eip155:1\": \"https://eth.llamarpc.com\",\n \"eip155:137\": \"https://polygon-rpc.com\",\n \"eip155:56\": \"https://bsc-dataseed.bnbchain.org\",\n};\n\n/**\n * Read an ERC-20 `balanceOf` for the wallet on the challenge's network.\n * Returns `undefined` when no RPC is known for the chain (caller should\n * treat that as \"cannot check\", not \"zero\"). Throws on RPC failure —\n * callers doing a best-effort pre-check catch and proceed.\n *\n * @param asset ERC-20 contract address from the 402 accept.\n * @param walletAddress The paying EOA.\n * @param caip2Network CAIP-2 id from the accept (e.g. \"eip155:8453\").\n * @param rpcUrl Optional RPC override; falls back to a public endpoint.\n */\nexport async function getErc20Balance(\n asset: string,\n walletAddress: string,\n caip2Network: string,\n rpcUrl?: string,\n): Promise<bigint | undefined> {\n const url = rpcUrl ?? CAIP2_RPCS[caip2Network];\n if (!url) return undefined;\n const provider = new JsonRpcProvider(url);\n try {\n const token = new Contract(asset, ERC20_BALANCE_ABI, provider);\n return (await token.balanceOf(walletAddress)) as bigint;\n } finally {\n provider.destroy();\n }\n}\n\n/** Format a USDC base-unit bigint (6 decimals) as a human-readable string, e.g. \"1.234567\". */\nexport function formatUSDC(baseUnits: bigint): string {\n const s = baseUnits.toString().padStart(7, \"0\");\n const whole = s.slice(0, -6);\n const frac = s.slice(-6).replace(/0+$/, \"\");\n return frac ? `${whole}.${frac}` : whole;\n}\n","import { ethers } from \"ethers\";\nimport { formatUSDC, getErc20Balance } from \"./balance.js\";\nimport { InvalidRequirementsError, PaymentRejectedError } from \"./errors.js\";\n/**\n * payagent — Local-signer fetch wrapper (permissionless mode).\n *\n * Signs EIP-3009 `transferWithAuthorization` locally with ethers and settles\n * through whatever facilitator the *seller* uses. No ArisPay account, no\n * API key, no provisioning — `npm install payagent` plus a funded key is the\n * whole setup. This is the mode that makes payagent a drop-in x402 client\n * for the open ecosystem.\n *\n * Trade-offs vs `payFetchDelegated` (the custody mode), stated honestly:\n * spend limits, allowedDomains, suspension, and the payment feed are\n * server-side features of the delegated model and do NOT exist here — the\n * only guardrail is the optional per-transaction cap below, which is\n * client-side and therefore self-enforced. The private key lives in your\n * process; treat it accordingly (a dedicated low-balance wallet is the\n * intended pattern).\n *\n * Usage:\n * const fetch402 = payFetchLocal({ privateKey: process.env.PRIVATE_KEY! });\n * const res = await fetch402('https://api.example.com/premium');\n */\nimport { parseRequirements } from \"./payment.js\";\n\nexport interface PayFetchLocalConfig {\n /** Hex-encoded private key (`0x…`) of the EOA that pays. */\n privateKey: string;\n /**\n * Optional per-payment cap in the accepted asset's base units (USDC has 6\n * decimals, so \"1000000\" = $1.00). A 402 asking for more throws\n * `PaymentRejectedError` instead of signing. Client-side guardrail only.\n */\n maxPerTxBaseUnits?: string | bigint;\n /**\n * Fires once per paid request, right after signing. Advisory: a throwing\n * callback is swallowed and never breaks the payment flow.\n */\n onPayment?: (info: LocalPaymentInfo) => void;\n /**\n * RPC endpoint for the pre-payment balance check. Defaults to a public\n * endpoint for the challenge's chain. The check is best-effort: an\n * unreachable RPC never blocks the payment.\n */\n rpcUrl?: string;\n /**\n * Set false to skip the pre-payment balance check and sign regardless.\n * Default true — an unfunded wallet fails with a fundable address instead\n * of an opaque seller 402.\n */\n balanceCheck?: boolean;\n /**\n * Test seam: replaces the on-chain balance read. Returns base units, or\n * `undefined` when the balance cannot be determined (check is skipped).\n */\n balanceFetcher?: (args: {\n asset: string;\n walletAddress: string;\n network: string;\n rpcUrl?: string;\n }) => Promise<bigint | undefined>;\n}\n\nconst BALANCE_CHECK_TIMEOUT_MS = 4000;\n\nasync function bestEffortBalance(\n config: PayFetchLocalConfig,\n args: { asset: string; walletAddress: string; network: string },\n): Promise<bigint | undefined> {\n const fetcher =\n config.balanceFetcher ??\n (({ asset, walletAddress, network, rpcUrl }: Parameters<\n NonNullable<PayFetchLocalConfig[\"balanceFetcher\"]>\n >[0]) => getErc20Balance(asset, walletAddress, network, rpcUrl));\n try {\n return await Promise.race([\n fetcher({ ...args, rpcUrl: config.rpcUrl }),\n new Promise<undefined>((_, reject) =>\n setTimeout(() => reject(new Error(\"balance check timed out\")), BALANCE_CHECK_TIMEOUT_MS),\n ),\n ]);\n } catch {\n // Best-effort only: an unreachable RPC must never block a payment.\n return undefined;\n }\n}\n\n/** Decode the retry response's payment-required header for its error reason. */\nfunction retryRejectionReason(paid: Response): string | undefined {\n const header = paid.headers.get(\"payment-required\") ?? paid.headers.get(\"PAYMENT-REQUIRED\");\n if (!header) return undefined;\n try {\n const decoded = JSON.parse(Buffer.from(header, \"base64\").toString(\"utf-8\")) as {\n error?: unknown;\n };\n return typeof decoded.error === \"string\" && decoded.error ? decoded.error : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport interface LocalPaymentInfo {\n /** Amount paid, in the asset's base units. */\n amount: string;\n /** CAIP-2 network the payment settled on. */\n network: string;\n asset: string;\n payTo: string;\n /** The EOA that paid (derived from the configured key). */\n walletAddress: string;\n}\n\nexport type { PayFetchFn } from \"./fetch-delegated.js\";\nimport type { PayFetchFn } from \"./fetch-delegated.js\";\n\n/**\n * Derive the deposit address for a local (self-custody) private key. This is\n * the address a human funds with USDC in Lane P mode — surfaced by\n * `payagent wallet address` and the MCP `check_wallet` tool. Throws on an\n * invalid key.\n */\nexport function deriveLocalWalletAddress(privateKey: string): string {\n return new ethers.Wallet(privateKey).address;\n}\n\n// EIP-712 `TransferWithAuthorization` — the EIP-3009 typehash fields.\nconst TRANSFER_WITH_AUTHORIZATION_TYPES = {\n TransferWithAuthorization: [\n { name: \"from\", type: \"address\" },\n { name: \"to\", type: \"address\" },\n { name: \"value\", type: \"uint256\" },\n { name: \"validAfter\", type: \"uint256\" },\n { name: \"validBefore\", type: \"uint256\" },\n { name: \"nonce\", type: \"bytes32\" },\n ],\n};\n\n/**\n * Create a fetch wrapper that pays x402 402s by signing locally. Mirrors\n * `payFetchDelegated`'s flow (parse → sign → retry with X-PAYMENT) with the\n * server round-trip replaced by local EIP-712 signing.\n */\nexport function payFetchLocal(config: PayFetchLocalConfig): PayFetchFn {\n if (!config.privateKey) throw new Error(\"privateKey is required\");\n const wallet = new ethers.Wallet(config.privateKey);\n const maxPerTx =\n config.maxPerTxBaseUnits !== undefined ? BigInt(config.maxPerTxBaseUnits) : undefined;\n\n return async (url, init) => {\n const urlStr = url.toString();\n const response = await fetch(urlStr, init);\n if (response.status !== 402) return response;\n\n const { accepts, rawAccepts, x402Version, extensions } = await parseRequirements(response);\n if (accepts.length === 0) {\n throw new InvalidRequirementsError(\"no payment options in 402 response\");\n }\n // Local signing is EVM-only today (EIP-3009); pick the first eip155 option.\n const acceptIdx = accepts.findIndex((a) => a.network.startsWith(\"eip155:\"));\n if (acceptIdx < 0) {\n throw new InvalidRequirementsError(\n `local signer requires an eip155 variant, got ${accepts[0]?.network ?? \"none\"}`,\n );\n }\n const accept = accepts[acceptIdx]!;\n // The byte-for-byte challenge accept, used verbatim as\n // `paymentPayload.accepted` — x402 v2's findMatchingRequirements is a\n // deepEqual, so any added/missing field silently breaks the match.\n const rawAccept = rawAccepts[acceptIdx];\n\n if (maxPerTx !== undefined && BigInt(accept.amount) > maxPerTx) {\n throw new PaymentRejectedError(\n 402,\n `402 asks ${accept.amount} base units, above maxPerTxBaseUnits ${maxPerTx}`,\n );\n }\n\n // Pre-payment balance check. Without it, an unfunded wallet signs a\n // valid authorization, the facilitator rejects it on-chain funds, and the\n // agent sees an opaque second 402 with no way to know what to fix.\n if (config.balanceCheck !== false) {\n const held = await bestEffortBalance(config, {\n asset: accept.asset,\n walletAddress: wallet.address,\n network: accept.network,\n });\n if (held !== undefined && held < BigInt(accept.amount)) {\n throw new PaymentRejectedError(\n 402,\n `Wallet ${wallet.address} holds ${formatUSDC(held)} of the required asset ` +\n `but this request costs ${formatUSDC(BigInt(accept.amount))} ` +\n `(asset ${accept.asset} on ${accept.network}). ` +\n `Fund ${wallet.address} on that network and retry.`,\n );\n }\n }\n\n // The EIP-712 domain must come from the seller's requirements — every\n // token deployment has its own (name, version, chainId, contract), and a\n // wrong one produces a valid-looking signature that recovers wrong.\n const domainInfo = accept.extra;\n if (!domainInfo?.name || !domainInfo?.version) {\n throw new InvalidRequirementsError(\n \"402 accept is missing extra.name/extra.version (EIP-712 domain) — cannot sign locally\",\n );\n }\n const chainId = Number.parseInt(accept.network.split(\":\")[1] ?? \"\", 10);\n if (!Number.isFinite(chainId)) {\n throw new InvalidRequirementsError(`cannot parse chainId from network ${accept.network}`);\n }\n\n const now = Math.floor(Date.now() / 1000);\n const authorization = {\n from: wallet.address,\n to: accept.payTo,\n value: accept.amount,\n validAfter: (now - 60).toString(),\n validBefore: (now + 480).toString(), // 8-minute window, mirrors x402-core signing\n nonce: ethers.hexlify(ethers.randomBytes(32)),\n };\n\n const signature = await wallet.signTypedData(\n {\n name: domainInfo.name,\n version: domainInfo.version,\n chainId,\n verifyingContract: ethers.getAddress(accept.asset),\n },\n TRANSFER_WITH_AUTHORIZATION_TYPES,\n {\n from: ethers.getAddress(authorization.from),\n to: ethers.getAddress(authorization.to),\n value: BigInt(authorization.value),\n validAfter: BigInt(authorization.validAfter),\n validBefore: BigInt(authorization.validBefore),\n nonce: authorization.nonce,\n },\n );\n\n const effectiveVersion = x402Version ?? 2;\n const paymentHeader = Buffer.from(\n JSON.stringify({\n x402Version: effectiveVersion,\n payload: { signature, authorization },\n accepted: rawAccept,\n // v2 PaymentPayload.resource is a ResourceInfo object, not a bare\n // string (spec + what bazaar discovery extraction reads).\n resource: { url: accept.resource },\n // Echo the challenge's extensions (bazaar discovery declaration):\n // facilitators catalog discoverable sellers from this echo. Keep in\n // lockstep with fetch-delegated.ts.\n ...(extensions ? { extensions } : {}),\n }),\n ).toString(\"base64\");\n\n if (config.onPayment) {\n try {\n config.onPayment({\n amount: accept.amount,\n network: accept.network,\n asset: accept.asset,\n payTo: accept.payTo,\n walletAddress: wallet.address,\n });\n } catch {\n // Advisory metadata must never break the payment flow.\n }\n }\n\n if (process.env.PAYAGENT_DEBUG === \"1\") {\n try {\n const decoded = Buffer.from(paymentHeader, \"base64\").toString(\"utf-8\");\n process.stderr.write(`[payagent] X-PAYMENT (decoded): ${decoded}\\n`);\n } catch {\n process.stderr.write(`[payagent] X-PAYMENT (base64): ${paymentHeader}\\n`);\n }\n }\n\n // Retry with the version-appropriate payment header. v1 middlewares read\n // `X-PAYMENT`; upstream v2 middlewares (@x402/* ≥2.x) read ONLY\n // `PAYMENT-SIGNATURE` — a v2 payload sent as X-PAYMENT alone is silently\n // treated as \"no payment at all\". Set both so sellers on either side of\n // the rename find the payload; servers read exactly one. Keep this in\n // lockstep with fetch-delegated.ts.\n const retryHeaders = new Headers(init?.headers);\n retryHeaders.set(\"X-PAYMENT\", paymentHeader);\n if (effectiveVersion === 2) {\n retryHeaders.set(\"PAYMENT-SIGNATURE\", paymentHeader);\n }\n const paid = await fetch(urlStr, { ...init, headers: retryHeaders });\n if (paid.status === 402) {\n // Surface the verifier's actual rejection reason, not a generic 402.\n // v2 sellers put it in the retry's payment-required header; the body\n // is often empty.\n const reason = retryRejectionReason(paid);\n const body = await paid.text().catch(() => \"\");\n const detail = reason ?? (body.trim() && body.trim() !== \"{}\" ? body.slice(0, 1000) : \"\");\n throw new PaymentRejectedError(\n 402,\n `Server returned 402 after payment was signed and sent` +\n (detail ? ` — reason: ${detail}` : \"\") +\n `. Paying wallet: ${wallet.address} on ${accept.network}. ` +\n `Common causes: insufficient ${accept.asset} balance, or the ` +\n `authorization window expired. Check the balance, then retry.`,\n );\n }\n return paid;\n };\n}\n"],"mappings":"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","names":["body"]}
import {
payFetchDelegated
} from "./chunk-HRS6PG4A.js";
// src/config-store.ts
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
import { homedir } from "os";
import { join } from "path";
var DEFAULT_ARISPAY_URL = "https://api.arispay.app";
var CURRENT_VERSION = 1;
function configDir() {
return process.env.PAYAGENT_CONFIG_DIR ?? join(homedir(), ".payagent");
}
function configPath() {
return join(configDir(), "config.json");
}
function loadConfig() {
const path = configPath();
if (!existsSync(path)) return {};
try {
const raw = readFileSync(path, "utf-8");
const parsed = JSON.parse(raw);
return parsed && typeof parsed === "object" ? parsed : {};
} catch {
return {};
}
}
function saveConfig(cfg) {
const dir = configDir();
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true, mode: 448 });
}
const next = { ...cfg, version: CURRENT_VERSION };
writeFileSync(configPath(), JSON.stringify(next, null, 2), { mode: 384 });
}
function getApiKey(explicit) {
if (explicit) return explicit;
if (process.env.ARISPAY_API_KEY) return process.env.ARISPAY_API_KEY;
const cfg = loadConfig();
return cfg.apiKey;
}
function getArispayUrl(explicit) {
if (explicit) return explicit;
if (process.env.ARISPAY_URL) return process.env.ARISPAY_URL;
const cfg = loadConfig();
return cfg.arispayUrl ?? DEFAULT_ARISPAY_URL;
}
function setApiKey(apiKey) {
const cfg = loadConfig();
saveConfig({ ...cfg, apiKey });
}
function clearApiKey() {
const cfg = loadConfig();
const { apiKey: _discarded, ...rest } = cfg;
void _discarded;
saveConfig(rest);
}
function saveAgent(agent) {
const cfg = loadConfig();
const agents = { ...cfg.agents ?? {}, [agent.name]: agent };
saveConfig({ ...cfg, agents });
}
function upsertManyFromServer(agents) {
const cfg = loadConfig();
const next = { ...cfg.agents ?? {} };
for (const fresh of agents) {
const existing = next[fresh.name];
next[fresh.name] = {
...fresh,
// Preserve a usable plaintext agent key if we already had one
// (server side has only the hash, so it can't refresh this).
apiKey: fresh.apiKey || existing?.apiKey || ""
};
}
saveConfig({ ...cfg, agents: next });
}
function renameStoredAgent(oldName, newName) {
const cfg = loadConfig();
const agents = cfg.agents ?? {};
const existing = agents[oldName];
if (!existing) return false;
if (oldName === newName) return false;
const { [oldName]: _drop, ...rest } = agents;
void _drop;
const renamed = { ...existing, name: newName };
saveConfig({ ...cfg, agents: { ...rest, [newName]: renamed } });
return true;
}
function getAgent(name) {
const cfg = loadConfig();
return cfg.agents?.[name];
}
function listAgents() {
const cfg = loadConfig();
return Object.values(cfg.agents ?? {}).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
}
function removeAgent(name) {
const cfg = loadConfig();
if (!cfg.agents?.[name]) return false;
const { [name]: _removed, ...rest } = cfg.agents;
void _removed;
saveConfig({ ...cfg, agents: rest });
return true;
}
function getConfigPath() {
return configPath();
}
// src/delegation.ts
var HostedTopupNotConfiguredError = class extends Error {
walletAddress;
network;
constructor(message, walletAddress, network) {
super(message);
this.name = "HostedTopupNotConfiguredError";
this.walletAddress = walletAddress;
this.network = network;
}
};
function randomIdempotencyKey() {
const ts = Date.now().toString(36);
const rnd = Math.floor(Math.random() * 268435455).toString(36).padStart(6, "0");
return `payagent-${ts}-${rnd}`;
}
function paymentsFeedQuery(opts, base = {}) {
const params = new URLSearchParams(base);
if (opts.status) params.set("status", opts.status);
if (opts.rail) params.set("rail", opts.rail);
if (opts.cursor) params.set("cursor", opts.cursor);
if (opts.limit !== void 0) params.set("limit", String(opts.limit));
return params.toString();
}
var DelegationClient = class {
baseUrl;
authToken;
constructor(baseUrl, authToken) {
if (!baseUrl) throw new Error("DelegationClient: baseUrl is required");
if (!authToken) throw new Error("DelegationClient: authToken is required");
this.baseUrl = baseUrl.replace(/\/$/, "");
this.authToken = authToken;
}
/** Create an x402 payer agent. Returns wallet address + one-time API key. */
async createX402Agent(config) {
return this.request("POST", "/v1/agents/x402", config);
}
/** Create a wallet-centric sub-wallet (no on-chain x402 wallet). */
async createWallet(options) {
return this.request("POST", "/v1/wallets", options);
}
/** List sub-wallets for the authenticated developer user. */
async listWallets() {
return this.request("GET", "/v1/wallets");
}
/** Get a sub-wallet plus its master funding-account balances. */
async getWalletBalance(walletId) {
return this.request("GET", `/v1/wallets/${encodeURIComponent(walletId)}/balance`);
}
/** Fetch the on-chain USDC balance for a delegation's wallet. */
async getBalance(agentId) {
return this.request(
"GET",
`/v1/agents/${encodeURIComponent(agentId)}/x402-balance`
);
}
/**
* Per-wallet activity feed. Cursor-paginated, newest first, scoped
* to the agent's org. Use for "show recent activity on this wallet"
* surfaces (BuyForMe wallet detail, future CLI `payagent payments`).
*/
async listAgentPayments(agentId, options = {}) {
const params = paymentsFeedQuery(options);
const path = `/v1/agents/${encodeURIComponent(agentId)}/payments${params ? `?${params}` : ""}`;
return this.request("GET", path);
}
/**
* Cross-wallet activity feed for the developer key's org. The
* `scope=org` query parameter is required server-side — we pass it
* for the caller because it's the only meaningful scope today.
*/
async listOrgPayments(options = {}) {
const params = paymentsFeedQuery(options, { scope: "org" });
return this.request("GET", `/v1/payments?${params}`);
}
/**
* Server-authoritative wallet listing — every agent under the developer
* key's org that has an x402 wallet. Use this from the MCP / CLI rather
* than reading `~/.payagent/config.json` directly: the local file is a
* cache that goes stale (new machine, re-bootstrap), and a stale cache
* is how end-users end up minting fresh wallets and orphaning their
* funded ones. Balance is intentionally NOT included — fan out to
* `getBalance(agentId)` only for wallets you actually need to display
* live, to keep the list call cheap.
*/
async listAgents(options = {}) {
const params = new URLSearchParams({ withDelegation: "1" });
if (options.name) params.set("name", options.name);
if (options.status) params.set("status", options.status);
if (options.limit !== void 0) params.set("limit", String(options.limit));
if (options.offset !== void 0) params.set("offset", String(options.offset));
const raw = await this.request("GET", `/v1/agents?${params.toString()}`);
const agents = raw.agents.filter((a) => a.x402).map((a) => ({
agentId: a.id,
name: a.name,
walletAddress: a.x402.walletAddress,
network: a.x402.network,
limits: {
maxPerTx: a.x402.maxPerTx,
maxDaily: a.x402.maxDaily,
maxMonthly: a.x402.maxMonthly
},
allowedDomains: a.x402.allowedDomains,
custody: a.x402.custody,
suspended: a.x402.suspended,
fundedAt: a.x402.fundedAt,
createdAt: a.createdAt
}));
return { agents, total: raw.total, limit: raw.limit, offset: raw.offset };
}
/**
* Rename an agent. Server enforces per-org name uniqueness (the bootstrap
* recovery path keys on `(orgId, name)`, so duplicate names would silently
* flip which agent the next bootstrap reuses). Throws on a 409 conflict.
*
* Thin wrapper over `updateAgent({ name })` kept for compatibility and
* because rename is the most-common patch from the CLI/MCP surface.
*/
async renameAgent(agentId, newName) {
const updated = await this.updateAgent(agentId, { name: newName });
return {
agentId: updated.id,
name: updated.name,
walletAddress: updated.x402?.walletAddress,
network: updated.x402?.network
};
}
/**
* General-purpose agent patch — rename, limits, allowedDomains, and
* the suspend kill-switch all flow through here. The patch is sent
* as-is to `PATCH /v1/agents/:id`; only the keys you set are touched.
* Server enforces:
* - name uniqueness per org (409 on conflict)
* - maxPerTx ≤ maxDaily ≤ maxMonthly (400 on violation)
* - `suspended` requires an existing AgentDelegation row (400 otherwise)
*
* Returns the hydrated agent (including the refreshed `x402` block)
* so callers can update their local cache without a follow-up GET.
*/
async updateAgent(agentId, patch) {
return this.request(
"PATCH",
`/v1/agents/${encodeURIComponent(agentId)}`,
patch
);
}
/**
* Rotate an x402 agent's delegation key — the recovery path when the
* agent key from `createX402Agent` / bootstrap is lost. The server mints
* a fresh agent key and returns the new plaintext ONCE — it is never
* recoverable afterwards, so persist it immediately. Wallet, network,
* limits, allowedDomains, spend counters, and suspension are untouched.
*
* Revocation of the previous credential is two-layered and the second
* layer is CONDITIONAL: the old key always stops working for delegated
* signing, and its org ApiKey record is additionally revoked when the
* server can identify it — reported via `previousKeyRevoked`. When
* `previousKeyRevoked` is `false` (some legacy delegations), the old key
* can no longer sign but MAY STILL AUTHENTICATE to non-signing org API
* routes until you revoke that ApiKey manually (dashboard → API keys).
*
* Requires a developer management credential (or a dashboard session).
* Agent-scoped keys are rejected with 403 — an agent key cannot rotate
* itself or any sibling agent. A concurrent rotation of the same agent
* returns 409 `ROTATION_CONFLICT` for the loser; the winner's key is
* unaffected.
*/
async rotateX402Key(agentId) {
return this.request(
"POST",
`/v1/agents/${encodeURIComponent(agentId)}/x402-key/rotate`
);
}
/**
* Request a hosted top-up URL (Coinbase Onramp, etc.) for this agent.
*
* Returns `{ fundingUrl, provider, walletAddress, network, expiresAt }`.
* The caller (CLI, MCP, bot) shares the URL with an end-user who pays
* with card / Apple Pay / bank transfer; the onramp deposits USDC
* straight into the agent's CDP wallet. ArisPay never touches the funds.
*
* Throws `HostedTopupNotConfiguredError` (with 501 status) when the
* deployment hasn't set `ARISPAY_ONRAMP_PROVIDER`, so callers can
* gracefully fall back to "send USDC manually to this address".
*/
async getHostedTopup(agentId, options = {}) {
const body = {};
if (options.amount !== void 0) body.amount = options.amount;
try {
return await this.request(
"POST",
`/v1/agents/${encodeURIComponent(agentId)}/hosted-topup`,
body
);
} catch (err) {
if (err instanceof Error && /\(501\)/.test(err.message)) {
throw new HostedTopupNotConfiguredError(err.message);
}
throw err;
}
}
/**
* Poll `getBalance` until the wallet shows non-zero USDC (i.e. `fundedAt` latches).
*
* @param agentId The agent ID returned from createX402Agent.
* @param options.intervalMs Poll interval (default 5000).
* @param options.timeoutMs Give up after this many ms (default 10 minutes). Pass 0 for no timeout.
*/
async pollUntilFunded(agentId, options = {}) {
const interval = options.intervalMs ?? 5e3;
const timeout = options.timeoutMs ?? 10 * 60 * 1e3;
const deadline = timeout > 0 ? Date.now() + timeout : Number.POSITIVE_INFINITY;
while (true) {
const balance = await this.getBalance(agentId);
if (balance.fundedAt || BigInt(balance.usdcBalance || "0") > 0n) {
return balance;
}
if (Date.now() + interval > deadline) {
throw new Error(
`pollUntilFunded: timed out after ${timeout}ms waiting for funding of ${agentId}`
);
}
await new Promise((r) => setTimeout(r, interval));
}
}
// ── End-user helpers (Phase 3) ───────────────────────────────────────────
/** Create (or find-or-create) an end-user. `externalId` is your own id for this customer. */
async createEndUser(options) {
return this.request("POST", "/v1/users", options);
}
/** Fetch an end-user by internal id. */
async getEndUser(id) {
return this.request("GET", `/v1/users/${encodeURIComponent(id)}`);
}
/** Fetch an end-user by the developer's external id. */
async getEndUserByExternalId(externalId) {
return this.request(
"GET",
`/v1/users/by-external/${encodeURIComponent(externalId)}`
);
}
/**
* Create a hosted card-setup session for an end-user. Returns a URL you share
* with the end-user; they open it in a browser, enter their card, and ArisPay's
* hosted page handles tokenization + 3DS. Poll `getCardSetupStatus` or
* `pollCardSetup` to detect completion.
*/
async createCardSetupSession(options) {
return this.request("POST", "/v1/card-setup-sessions", options);
}
/** One-shot status check for a card-setup session. */
async getCardSetupStatus(token) {
return this.request(
"GET",
`/v1/card-setup-sessions/${encodeURIComponent(token)}/status`
);
}
/**
* Poll `getCardSetupStatus` until the session reaches a terminal state
* (`completed`, `expired`, `failed`, or `not_found`) or the timeout elapses.
*/
async pollCardSetup(token, options = {}) {
const interval = options.intervalMs ?? 3e3;
const timeout = options.timeoutMs ?? 15 * 60 * 1e3;
const deadline = timeout > 0 ? Date.now() + timeout : Number.POSITIVE_INFINITY;
const terminal = ["completed", "expired", "failed", "not_found"];
while (true) {
const status = await this.getCardSetupStatus(token);
if (terminal.includes(status.status)) {
return status;
}
if (Date.now() + interval > deadline) {
throw new Error(
`pollCardSetup: timed out after ${timeout}ms waiting for card-setup session`
);
}
await new Promise((r) => setTimeout(r, interval));
}
}
/** Attach a USDC wallet to an end-user as a payment method (non-custodial rail). */
async attachWallet(endUserId, options) {
return this.request(
"POST",
`/v1/users/${encodeURIComponent(endUserId)}/payment-methods`,
{ type: "wallet", ...options }
);
}
/**
* Set per-user, per-agent spend limits. Overrides the agent defaults for
* this specific end-user + agent pair.
*/
async setUserLimits(endUserId, options) {
return this.request(
"PUT",
`/v1/users/${encodeURIComponent(endUserId)}/limits`,
options
);
}
/** Current wallet state for a wallet-attached end-user: balance, allowance, readiness. */
async getWalletStatus(endUserId) {
return this.request(
"GET",
`/v1/users/${encodeURIComponent(endUserId)}/wallet-status`
);
}
/**
* Create a payment from an agent (and optionally an end-user) to a merchant.
* Picks the rail server-side based on `rail`, agent mode, and whether a
* `userId` is supplied. Autonomous agents pay from their ledger balance by
* default; platform agents pay via their end-user's card (or crypto / MPP).
*
* For x402-priced API calls, use `agent.fetch(url)` instead — that path
* handles the 402 challenge + signing transparently and is the right tool
* for buying a single HTTP resource.
*
* An `idempotencyKey` is auto-generated if not provided. Callers who need
* end-to-end idempotency across retries should supply their own.
*/
async createPayment(agentId, options) {
const body = {
agentId,
userId: options.userId,
amount: options.amount,
currency: options.currency ?? "USD",
memo: options.memo,
merchantUrl: options.merchantUrl,
merchantName: options.merchantName,
merchantCategoryCode: options.merchantCategoryCode,
merchantId: options.merchantId,
rail: options.rail,
idempotencyKey: options.idempotencyKey ?? randomIdempotencyKey(),
metadata: options.metadata,
token: options.token,
chain: options.chain,
recipientAddress: options.recipientAddress
};
return this.request("POST", "/v1/payments", body);
}
async request(method, path, body) {
const res = await fetch(this.baseUrl + path, {
method,
headers: {
Authorization: `Bearer ${this.authToken}`,
...body !== void 0 ? { "Content-Type": "application/json" } : {}
},
body: body !== void 0 ? JSON.stringify(body) : void 0
});
const text = await res.text();
let parsed;
try {
parsed = text ? JSON.parse(text) : null;
} catch {
parsed = text;
}
if (!res.ok) {
const message = parsed && typeof parsed === "object" && "error" in parsed && parsed.error?.message || (typeof parsed === "string" ? parsed : res.statusText);
throw new Error(`ArisPay ${method} ${path} failed (${res.status}): ${message}`);
}
return parsed;
}
};
// src/launch.ts
var MissingArisPayApiKeyError = class extends Error {
constructor() {
super(
"No ArisPay API key found. Set ARISPAY_API_KEY in your environment or run `npx payagent init`."
);
this.name = "MissingArisPayApiKeyError";
}
};
async function launchAgent(config) {
const apiKey = getApiKey(config.arispayApiKey);
if (!apiKey) throw new MissingArisPayApiKeyError();
const baseUrl = getArispayUrl(config.arispayUrl);
const client = new DelegationClient(baseUrl, apiKey);
const createConfig = {
name: config.name,
agentType: config.agentType,
maxPerTx: config.limits.perTx,
maxDaily: config.limits.daily,
maxMonthly: config.limits.monthly,
allowedDomains: config.allowedDomains,
description: config.description,
network: config.network
};
const created = await client.createX402Agent(createConfig);
if (!config.ephemeral) {
const stored = {
agentId: created.agentId,
name: config.name,
walletAddress: created.walletAddress,
apiKey: created.apiKey,
limits: {
perTx: created.limits.maxPerTx,
daily: created.limits.maxDaily,
monthly: created.limits.maxMonthly
},
allowedDomains: created.allowedDomains,
network: config.network ?? "base",
createdAt: (/* @__PURE__ */ new Date()).toISOString()
};
saveAgent(stored);
}
return buildLaunchedAgent({
name: config.name,
baseUrl,
created
});
}
function getLaunchedAgent(name, overrides = {}) {
const stored = getAgent(name);
if (!stored) return void 0;
const baseUrl = getArispayUrl(overrides.arispayUrl);
const synthetic = {
agentId: stored.agentId,
walletAddress: stored.walletAddress,
apiKey: stored.apiKey,
status: "pending_funding",
limits: {
maxPerTx: stored.limits.perTx,
maxDaily: stored.limits.daily,
maxMonthly: stored.limits.monthly
},
allowedDomains: stored.allowedDomains ?? [],
network: stored.network
};
return buildLaunchedAgent({
name,
baseUrl,
created: synthetic
});
}
function buildLaunchedAgent(input) {
const { name, baseUrl, created } = input;
const devKey = getApiKey();
if (!devKey) throw new MissingArisPayApiKeyError();
const client = new DelegationClient(baseUrl, devKey);
const boundFetch = payFetchDelegated({
arispayUrl: baseUrl,
apiKey: created.apiKey
});
return {
agentId: created.agentId,
name,
walletAddress: created.walletAddress,
apiKey: created.apiKey,
status: created.status,
limits: {
perTx: created.limits.maxPerTx,
daily: created.limits.maxDaily,
monthly: created.limits.maxMonthly
},
allowedDomains: created.allowedDomains,
network: created.network ?? "base",
fetch: boundFetch,
getBalance: () => client.getBalance(created.agentId),
waitUntilFunded: (options) => client.pollUntilFunded(created.agentId, options),
getFundingLink: (options) => client.getHostedTopup(created.agentId, options),
setUserLimits: (endUserId, options) => client.setUserLimits(endUserId, { ...options, agentId: created.agentId }),
pay: (options) => client.createPayment(created.agentId, options)
};
}
// src/bootstrap.ts
var BootstrapError = class extends Error {
status;
code;
constructor(status, code, message) {
super(message);
this.name = "BootstrapError";
this.status = status;
this.code = code;
}
};
async function bootstrapAgent(config) {
const baseUrl = (config.arispayUrl ?? DEFAULT_ARISPAY_URL).replace(/\/$/, "");
const body = {
email: config.email,
clientId: config.clientId ?? "payagent-cli"
};
if (config.name) body.name = config.name;
if (config.orgName) body.orgName = config.orgName;
if (config.agentName) body.agentName = config.agentName;
if (config.limits?.perTx !== void 0) body.maxPerTx = config.limits.perTx;
if (config.limits?.daily !== void 0) body.maxDaily = config.limits.daily;
if (config.limits?.monthly !== void 0) body.maxMonthly = config.limits.monthly;
if (config.allowedDomains?.length) body.allowedDomains = config.allowedDomains;
if (config.agentType) body.agentType = config.agentType;
if (config.description) body.description = config.description;
if (config.fund !== void 0) body.fund = config.fund;
const res = await fetch(`${baseUrl}/v1/bootstrap`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body)
});
if (!res.ok) {
const errBody = await safeJson(res);
const code = errBody?.error?.code ?? "BOOTSTRAP_FAILED";
const message = errBody?.error?.message ?? `${res.statusText} (HTTP ${res.status})`;
throw new BootstrapError(res.status, code, message);
}
const server = await res.json();
const network = server.network ?? "base";
const limits = {
perTx: server.limits.maxPerTx,
daily: server.limits.maxDaily,
monthly: server.limits.maxMonthly
};
const existingAgents = server.existingAgents ?? [];
if (!config.ephemeral) {
setApiKey(server.developerKey);
if (config.arispayUrl && config.arispayUrl !== DEFAULT_ARISPAY_URL) {
const cfg = loadConfig();
saveConfig({ ...cfg, arispayUrl: config.arispayUrl });
}
const stored = {
agentId: server.agentId,
name: server.agentName,
walletAddress: server.walletAddress,
apiKey: server.agentApiKey,
limits,
allowedDomains: server.allowedDomains,
network,
createdAt: (/* @__PURE__ */ new Date()).toISOString()
};
saveAgent(stored);
const knownNetworks = [
"base",
"base-sepolia",
"ethereum",
"polygon",
"bsc",
"solana",
"solana-devnet"
];
const others = existingAgents.filter((a) => a.agentId !== server.agentId).map((a) => {
const net = knownNetworks.includes(a.network) ? a.network : void 0;
return {
agentId: a.agentId,
name: a.name,
walletAddress: a.walletAddress,
apiKey: "",
limits: {
perTx: a.limits.maxPerTx,
daily: a.limits.maxDaily,
monthly: a.limits.maxMonthly
},
allowedDomains: a.allowedDomains,
network: net,
createdAt: a.createdAt
};
});
if (others.length) {
upsertManyFromServer(others);
}
}
const devClient = new DelegationClient(baseUrl, server.developerKey);
const boundFetch = payFetchDelegated({
arispayUrl: baseUrl,
apiKey: server.agentApiKey
});
const synthetic = {
agentId: server.agentId,
walletAddress: server.walletAddress,
apiKey: server.agentApiKey,
status: server.status === "active" ? "active" : "pending_funding",
limits: server.limits,
allowedDomains: server.allowedDomains,
network
};
return {
developerKey: server.developerKey,
userId: server.userId,
orgId: server.orgId,
orgName: server.orgName,
email: server.email,
agentId: server.agentId,
agentName: server.agentName,
agentApiKey: server.agentApiKey,
walletAddress: server.walletAddress,
network,
limits,
allowedDomains: server.allowedDomains,
custody: "delegated",
status: synthetic.status,
environment: "production",
reused: server.reused ?? false,
existingAgents,
pairing: server.pairing,
fundingUrl: server.fundingUrl,
fundingExpiresAt: server.fundingExpiresAt,
fundingProvider: server.fundingProvider,
fundingUrlError: server.fundingUrlError,
fetch: boundFetch,
getBalance: () => devClient.getBalance(server.agentId),
waitUntilFunded: (options) => devClient.pollUntilFunded(server.agentId, options),
getFundingLink: (options) => devClient.getHostedTopup(server.agentId, options)
};
}
async function safeJson(res) {
try {
return await res.clone().json();
} catch {
return void 0;
}
}
// src/device-code.ts
var PENDING_CODES = /* @__PURE__ */ new Set(["authorization_pending", "slow_down"]);
var DeviceCodeError = class extends Error {
code;
constructor(code, message) {
super(message);
this.name = "DeviceCodeError";
this.code = code;
}
};
async function requestDeviceCode(options = {}) {
const baseUrl = (options.arispayUrl ?? DEFAULT_ARISPAY_URL).replace(/\/$/, "");
const res = await fetch(`${baseUrl}/v1/auth/device/code`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ clientId: options.clientId ?? "payagent-cli" })
});
if (!res.ok) {
const body = await safeJson2(res);
const message = body?.error?.message ?? await safeText(res) ?? res.statusText;
throw new DeviceCodeError(body?.error?.code ?? "device_code_request_failed", message);
}
return await res.json();
}
async function pollDeviceToken(deviceCode, options = {}) {
const baseUrl = (options.arispayUrl ?? DEFAULT_ARISPAY_URL).replace(/\/$/, "");
const clientId = options.clientId ?? "payagent-cli";
const timeoutMs = options.timeoutMs ?? 10 * 60 * 1e3;
const start = Date.now();
let intervalMs = options.intervalMs ?? 5e3;
while (true) {
const elapsed = Date.now() - start;
options.onTick?.(elapsed);
if (elapsed >= timeoutMs) {
throw new DeviceCodeError(
"device_code_timeout",
`Timed out after ${Math.round(timeoutMs / 1e3)}s waiting for user to authorize`
);
}
const res = await fetch(`${baseUrl}/v1/auth/device/token`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ deviceCode, clientId })
});
if (res.ok) {
return await res.json();
}
const body = await safeJson2(res);
const code = body?.error?.code ?? "device_code_error";
const message = body?.error?.message ?? res.statusText;
if (code === "slow_down") {
intervalMs = Math.min(intervalMs * 2, 6e4);
} else if (!PENDING_CODES.has(code)) {
throw new DeviceCodeError(code, message);
}
await new Promise((r) => setTimeout(r, intervalMs));
}
}
async function runDeviceAuth(options = {}) {
const code = await requestDeviceCode({
arispayUrl: options.arispayUrl,
clientId: options.clientId
});
options.onCode?.(code);
return pollDeviceToken(code.deviceCode, {
arispayUrl: options.arispayUrl,
clientId: options.clientId,
intervalMs: code.interval * 1e3,
timeoutMs: options.pollTimeoutMs ?? code.expiresIn * 1e3,
onTick: options.onTick
});
}
async function safeJson2(res) {
try {
return await res.clone().json();
} catch {
return void 0;
}
}
async function safeText(res) {
try {
return await res.clone().text();
} catch {
return void 0;
}
}
// src/sync-agents.ts
var MissingDevKeyForSyncError = class extends Error {
constructor() {
super("syncAgents: developer API key required (set ARISPAY_API_KEY or run `payagent init`).");
this.name = "MissingDevKeyForSyncError";
}
};
async function syncAgents(options = {}) {
const apiKey = getApiKey(options.apiKey);
if (!apiKey) throw new MissingDevKeyForSyncError();
const baseUrl = getArispayUrl(options.arispayUrl) || DEFAULT_ARISPAY_URL;
const client = new DelegationClient(baseUrl, apiKey);
const list = await client.listAgents({
name: options.name,
status: options.status,
limit: options.limit,
offset: options.offset
});
let agents = list.agents;
if (options.includeBalance && agents.length) {
const balances = await Promise.allSettled(agents.map((a) => client.getBalance(a.agentId)));
agents = agents.map((a, i) => {
const settled = balances[i];
if (settled?.status === "fulfilled") {
return { ...a, usdcBalance: settled.value.usdcBalance };
}
return a;
});
}
const knownNetworks = [
"base",
"base-sepolia",
"ethereum",
"polygon",
"bsc",
"solana",
"solana-devnet"
];
const stored = agents.map((a) => {
const network = knownNetworks.includes(a.network) ? a.network : void 0;
return {
agentId: a.agentId,
name: a.name,
walletAddress: a.walletAddress,
apiKey: "",
// server has only the hash; preserved-from-local in upsertManyFromServer
limits: {
perTx: a.limits.maxPerTx,
daily: a.limits.maxDaily,
monthly: a.limits.maxMonthly
},
allowedDomains: a.allowedDomains,
network,
createdAt: a.createdAt
};
});
upsertManyFromServer(stored);
return { agents, total: list.total, limit: list.limit, offset: list.offset };
}
// src/discover.ts
var DEFAULT_MARKETPLACE_URL = "https://api.arispay.app/v1/marketplace";
async function discover(input = {}, opts = {}) {
const baseUrl = (opts.marketplaceUrl ?? DEFAULT_MARKETPLACE_URL).replace(/\/$/, "");
const fetchImpl = opts.fetch ?? globalThis.fetch;
const res = await fetchImpl(`${baseUrl}/discover`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(input),
signal: opts.signal
});
if (!res.ok) {
const text = await res.text().catch(() => "");
throw new Error(`payagent discover failed (${res.status}): ${text || res.statusText}`);
}
return await res.json();
}
var DEFAULT_FACILITATOR_URL = "https://facilitator.arispay.app";
async function discoverCatalog(query, opts = {}) {
const base = (opts.facilitatorUrl ?? DEFAULT_FACILITATOR_URL).replace(/\/$/, "");
const params = new URLSearchParams();
if (query) params.set("q", query);
if (opts.limit) params.set("limit", String(opts.limit));
const fetchImpl = opts.fetch ?? globalThis.fetch;
const res = await fetchImpl(`${base}/discovery/resources?${params.toString()}`, {
headers: { accept: "application/json" },
signal: opts.signal
});
if (!res.ok) {
throw new Error(`facilitator catalog query failed: HTTP ${res.status}`);
}
const body = await res.json();
const items = Array.isArray(body.items) ? body.items : [];
const resources = [];
for (const item of items) {
if (typeof item.resource !== "string") continue;
const accepts = Array.isArray(item.accepts) ? item.accepts : [];
const first = accepts[0];
resources.push({
resource: item.resource,
type: typeof item.type === "string" ? item.type : "http",
...typeof item.method === "string" ? { method: item.method } : {},
...typeof item.description === "string" ? { description: item.description } : {},
...typeof first?.network === "string" ? { network: first.network } : {},
...typeof first?.payTo === "string" ? { payTo: first.payTo } : {},
...typeof first?.amount === "string" ? { amountBaseUnits: first.amount } : {},
...typeof first?.asset === "string" ? { asset: first.asset } : {},
...typeof item.metadata?.status === "string" ? { status: item.metadata.status } : {}
});
}
return { resources, total: body.pagination?.total ?? resources.length };
}
// src/inspect.ts
var EURC_ASSETS = /* @__PURE__ */ new Set([
"0x60a3e35cc302bfa44cb288bc5a4f316fdb1adb42",
// Base mainnet
"0x808456652fdb597867f38412077a9182bf77359f"
// Base Sepolia
]);
var USDC_ASSETS = /* @__PURE__ */ new Set([
"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
// Base mainnet
"0x036cbd53842c5426634e7929541ec2318f3dcf7e",
// Base Sepolia
"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
// Ethereum
"0x3c499c542cef5e3811e1192ce70d8cc03d5c3359"
// Polygon
]);
var USD1_ASSETS = /* @__PURE__ */ new Set([
"0x8d0d000ee44948fc98c9b98a4fa4921476f08b0d"
// BSC mainnet
]);
var InspectParseError = class extends Error {
constructor(message) {
super(message);
this.name = "InspectParseError";
}
};
function assetDecimalsFor(asset) {
return USD1_ASSETS.has(asset.toLowerCase()) ? 18 : 6;
}
function centsFromBaseUnits(baseUnits, decimals = 6) {
if (!Number.isInteger(decimals) || decimals < 2) return void 0;
let bu;
try {
bu = BigInt(baseUnits);
} catch {
return void 0;
}
const perCent = 10n ** BigInt(decimals - 2);
if (bu < 0n || bu % perCent !== 0n) return void 0;
const cents = bu / perCent;
return cents <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(cents) : void 0;
}
function currencyFor(asset, assetName) {
const addr = asset.toLowerCase();
if (USDC_ASSETS.has(addr) || USD1_ASSETS.has(addr)) return "USD";
if (EURC_ASSETS.has(addr)) return "EUR";
if (assetName === "EURC") return "EUR";
if (assetName === "USD Coin" || assetName === "USDC") return "USD";
if (assetName === "USD1" || assetName === "World Liberty Financial USD") return "USD";
return void 0;
}
function normalizeAccept(raw) {
const amount = typeof raw.amount === "string" ? raw.amount : typeof raw.maxAmountRequired === "string" ? raw.maxAmountRequired : void 0;
const asset = typeof raw.asset === "string" ? raw.asset : void 0;
const payTo = typeof raw.payTo === "string" ? raw.payTo : void 0;
const network = typeof raw.network === "string" ? raw.network : void 0;
const scheme = typeof raw.scheme === "string" ? raw.scheme : void 0;
if (amount === void 0 || !asset || !payTo || !network || !scheme) return void 0;
const extra = raw.extra;
const assetName = typeof extra?.name === "string" ? extra.name : void 0;
const accept = {
scheme,
network,
amountBaseUnits: amount,
asset,
payTo
};
const cents = centsFromBaseUnits(amount, assetDecimalsFor(asset));
if (cents !== void 0) accept.amountCents = cents;
if (typeof raw.resource === "string") accept.resource = raw.resource;
if (typeof raw.description === "string") accept.description = raw.description;
if (typeof raw.mimeType === "string") accept.mimeType = raw.mimeType;
if (assetName) accept.assetName = assetName;
const currency = currencyFor(asset, assetName);
if (currency) accept.currency = currency;
return accept;
}
async function inspectChallenge(url, opts = {}) {
const fetchImpl = opts.fetch ?? globalThis.fetch;
const signal = opts.signal ?? AbortSignal.timeout(opts.timeoutMs ?? 15e3);
const res = await fetchImpl(url, { method: "GET", signal });
if (res.status !== 402) {
return { gated: false, url, status: res.status };
}
let body;
try {
body = await res.json();
} catch {
body = null;
}
const bodyHasAccepts = typeof body === "object" && body !== null && Array.isArray(body.accepts);
if (!bodyHasAccepts) {
const header = typeof res.headers?.get === "function" ? res.headers.get("payment-required") : null;
if (header) {
try {
const decoded = typeof atob === "function" ? atob(header) : Buffer.from(header, "base64").toString("utf8");
body = JSON.parse(decoded);
} catch {
}
}
}
if (body === null) {
throw new InspectParseError(
`${url} returned HTTP 402 but neither the body nor the payment-required header carries a parseable x402 challenge.`
);
}
if (typeof body !== "object") {
throw new InspectParseError(
`${url} returned HTTP 402 but the body is not an x402 challenge object.`
);
}
const obj = body;
const accepts = [];
if (Array.isArray(obj.accepts)) {
for (const raw of obj.accepts) {
if (typeof raw === "object" && raw !== null) {
const normalized = normalizeAccept(raw);
if (normalized) accepts.push(normalized);
}
}
} else {
const normalized = normalizeAccept(obj);
if (normalized) accepts.push(normalized);
}
if (accepts.length === 0) {
throw new InspectParseError(
`${url} returned HTTP 402 but no payment option in the body could be parsed (missing amount/asset/payTo/network).`
);
}
const result = {
gated: true,
url,
status: 402,
accepts
};
if (typeof obj.x402Version === "number") result.x402Version = obj.x402Version;
if (typeof obj.facilitator === "string") result.serverDeclaredFacilitator = obj.facilitator;
if (typeof obj.trustMinTier === "string") result.serverDeclaredTrustMinTier = obj.trustMinTier;
if (typeof obj.gasSponsored === "boolean") result.gasSponsored = obj.gasSponsored;
if (typeof obj.manifest === "string") result.manifestUrl = obj.manifest;
const bazaar = obj.extensions?.bazaar;
if (bazaar) result.bazaarDiscoverable = bazaar.discoverable !== false;
return result;
}
export {
DEFAULT_ARISPAY_URL,
loadConfig,
saveConfig,
getApiKey,
getArispayUrl,
setApiKey,
clearApiKey,
saveAgent,
upsertManyFromServer,
renameStoredAgent,
getAgent,
listAgents,
removeAgent,
getConfigPath,
HostedTopupNotConfiguredError,
DelegationClient,
MissingArisPayApiKeyError,
launchAgent,
getLaunchedAgent,
BootstrapError,
bootstrapAgent,
DeviceCodeError,
requestDeviceCode,
pollDeviceToken,
runDeviceAuth,
MissingDevKeyForSyncError,
syncAgents,
discover,
discoverCatalog,
InspectParseError,
centsFromBaseUnits,
inspectChallenge
};
//# sourceMappingURL=chunk-M6MYY3SD.js.map

Sorry, the diff of this file is too big to display

+10
-8

@@ -42,3 +42,3 @@ import { b as PayFetchFn } from './fetch-local-BagfYk43.js';

/** EVM network label. Default: 'base' (mainnet). */
network?: "base" | "base-sepolia" | "ethereum" | "polygon";
network?: "base" | "base-sepolia" | "ethereum" | "polygon" | "bsc" | "solana" | "solana-devnet";
}

@@ -692,3 +692,3 @@ interface CreateX402AgentResponse {

/** EVM network. Defaults to `base` (mainnet). */
network?: "base" | "base-sepolia" | "ethereum" | "polygon";
network?: "base" | "base-sepolia" | "ethereum" | "polygon" | "bsc" | "solana" | "solana-devnet";
/** Optional metadata label, surfaces on the dashboard. */

@@ -873,3 +873,3 @@ agentType?: string;

walletAddress: string;
network: "base" | "base-sepolia" | "ethereum" | "polygon";
network: "base" | "base-sepolia" | "ethereum" | "polygon" | "bsc" | "solana" | "solana-devnet";
limits: {

@@ -1046,3 +1046,3 @@ perTx: number;

allowedDomains?: string[];
network?: "base" | "base-sepolia" | "ethereum" | "polygon";
network?: "base" | "base-sepolia" | "ethereum" | "polygon" | "bsc" | "solana" | "solana-devnet";
createdAt: string;

@@ -1272,3 +1272,4 @@ }

* they divide evenly, integer cents. Never floats. For 6-decimal assets
* (USDC, EURC): cents = baseUnits / 10^4.
* (USDC, EURC): cents = baseUnits / 10^4. For 18-decimal USD1 (BSC):
* cents = baseUnits / 10^16.
*/

@@ -1338,7 +1339,8 @@ /** One payment option from the 402 challenge, normalized for display. */

/**
* Convert a base-units string to integer cents for a 6-decimal asset
* (cents = baseUnits / 10^4). Returns undefined when the value isn't a
* Convert a base-units string to integer cents for an asset with
* `decimals` decimals (default 6: cents = baseUnits / 10^4; USD1's 18:
* cents = baseUnits / 10^16). Returns undefined when the value isn't a
* clean non-negative integer number of cents — never rounds, never floats.
*/
declare function centsFromBaseUnits(baseUnits: string): number | undefined;
declare function centsFromBaseUnits(baseUnits: string, decimals?: number): number | undefined;
/**

@@ -1345,0 +1347,0 @@ * Fetch `url` with no payment and no auth. On HTTP 402, parse the x402

@@ -34,3 +34,3 @@ import {

upsertManyFromServer
} from "./chunk-Y6TBMMTR.js";
} from "./chunk-M6MYY3SD.js";
import {

@@ -47,3 +47,3 @@ InvalidRequirementsError,

payFetchLocal
} from "./chunk-ZE73ANBP.js";
} from "./chunk-HRS6PG4A.js";

@@ -64,3 +64,3 @@ // src/types.ts

id: "x402",
supports: (terms) => terms.backend === "x402" || terms.scheme === "exact" && typeof terms.network === "string" && terms.network.startsWith("eip155:"),
supports: (terms) => terms.backend === "x402" || terms.scheme === "exact" && typeof terms.network === "string" && (terms.network.startsWith("eip155:") || terms.network.startsWith("solana:")),
authorize: async (terms) => ({

@@ -67,0 +67,0 @@ headerName: "X-PAYMENT",

@@ -1,1 +0,1 @@

{"version":3,"sources":["../src/types.ts","../src/handshake.ts"],"sourcesContent":["/**\n * payagent — Public type definitions.\n */\n\n/** Record of a completed payment. */\nexport interface PaymentReceipt {\n /** The URL that was paid for. */\n url: string;\n /** USDC amount paid (human-readable, e.g. \"0.10\"). */\n amount: string;\n /** USDC amount in base units (e.g. \"100000\"). */\n amountBaseUnits: string;\n /** CAIP-2 network the payment was signed for. */\n network: string;\n /** The wallet address that received payment. */\n payTo: string;\n /** ISO timestamp of the payment. */\n timestamp: string;\n}\n\n/**\n * x402 v2 payment requirements — returned in HTTP 402 response body.\n * Standard format uses `accepts` array.\n */\nexport interface X402Requirements {\n x402Version: 2;\n accepts: X402Accept[];\n}\n\n/**\n * The metered balance-rail scheme identifier (ArisPay balance-rail\n * metering spec §8.1). One value, landed at every seam — the literal is\n * repeated here because this package publishes standalone and cannot\n * depend on the workspace-only shared package.\n */\nexport const ARISPAY_METERED_SCHEME = \"arispay-metered\";\n\n/** Payment schemes payagent understands. */\nexport type PaymentScheme = \"exact\" | typeof ARISPAY_METERED_SCHEME;\n\n/** A single payment option within a 402 response. */\nexport interface X402Accept {\n scheme: PaymentScheme;\n network: string;\n /** USDC amount in base units (6 decimals). x402 v2 spec name. */\n amount: string;\n /** @deprecated v1 alias for `amount`. Read-only fallback for legacy sellers. */\n maxAmountRequired?: string;\n resource: string;\n asset: string;\n payTo: string;\n extra?: { name: string; version: string };\n}\n\n/**\n * AgFac flat format — some servers return requirements as a flat object\n * instead of the standard accepts array.\n */\nexport interface AgfacFlatRequirements {\n x402Version: 2;\n scheme: PaymentScheme;\n network: string;\n /** USDC amount in base units. x402 v2 spec name. */\n amount?: string;\n /** v1 alias for `amount`. Accepted on parse, normalized to `amount`. */\n maxAmountRequired?: string;\n payTo: string;\n resource: string;\n asset: string;\n description?: string;\n expiry?: string;\n}\n\n/** Union of 402 response body formats payagent can handle. */\nexport type PaymentRequirementsBody = X402Requirements | AgfacFlatRequirements;\n","/**\n * Rail-agnostic payment handshake seam (M2M Session 6, constraint 5).\n * Spec: docs/specs/handshake-abstraction-v1.md.\n *\n * The four-stage handshake (payment-required → terms → authorization →\n * delivery) is rail-agnostic; only stage 3 (authorization) is backend-\n * specific. This module defines the backend interface and registers the\n * LIVE x402 (permissionless crypto, Lane P) backend plus a SPECCED-not-built\n * mandate-card backend stub. The existing x402 code paths (`payFetchLocal`,\n * `payFetchDelegated`) are UNCHANGED — the x402 backend here is a thin\n * descriptor over them, not a reimplementation.\n */\n\nexport interface HandshakeTerms {\n /** Which backend can satisfy these terms. */\n backend: string;\n scheme: string;\n network?: string;\n asset?: string;\n amountBaseUnits?: string;\n payTo?: string;\n // mandate-card backend (future) adds: mandateId, currency, amountCents, merchantRef.\n [key: string]: unknown;\n}\n\nexport interface PaymentAuthorization {\n headerName: string;\n headerValue: string;\n backend: string;\n}\n\nexport interface PaymentBackend {\n id: string;\n supports(terms: HandshakeTerms): boolean;\n authorize(terms: HandshakeTerms): Promise<PaymentAuthorization>;\n}\n\nexport class BackendNotImplementedError extends Error {\n code = \"NOT_IMPLEMENTED\" as const;\n constructor(backend: string) {\n super(`payment backend \"${backend}\" is specced but not implemented`);\n this.name = \"BackendNotImplementedError\";\n }\n}\n\n/**\n * x402 permissionless crypto backend (LIVE, Lane P). A descriptor: it\n * declares support for exact-scheme crypto terms. Actual authorization runs\n * through the unchanged `payFetchLocal` / `payFetchDelegated` fetch wrappers\n * — this backend does not re-sign; callers using the fetch wrappers already\n * get stage 3+4 for free. `authorize` is provided for callers that drive the\n * handshake manually via an injected signer.\n */\nexport function makeX402Backend(\n sign: (terms: HandshakeTerms) => Promise<string>,\n): PaymentBackend {\n return {\n id: \"x402\",\n supports: (terms) =>\n terms.backend === \"x402\" ||\n (terms.scheme === \"exact\" && typeof terms.network === \"string\" && terms.network.startsWith(\"eip155:\")),\n authorize: async (terms) => ({\n headerName: \"X-PAYMENT\",\n headerValue: await sign(terms),\n backend: \"x402\",\n }),\n };\n}\n\n/**\n * arispay-metered backend (balance-rail metering spec §5.4, §8.1). Metered\n * terms advertise a prepaid-balance rail: the buyer tops up once, then\n * presents an opaque session token; the provider meters each call\n * server-side. Stage 3 for this rail is \"hold a live session\": the\n * injected `acquireSession` resolves the terms to a session token —\n * opening a session (and driving a top-up through `terms.topUpEndpoint`\n * when the balance is exhausted) is the caller's client logic, exactly as\n * `makeX402Backend` delegates signing. Selection is by\n * `terms.backend === \"arispay-metered\"` — no scheme sniffing, no overlap\n * with x402 (`selectBackend` treats ambiguity as a hard error).\n */\nexport const ARISPAY_METERED_BACKEND_ID = \"arispay-metered\";\nexport const METERING_SESSION_HEADER = \"X-Metering-Session\";\n\nexport function makeArispayMeteredBackend(\n acquireSession: (terms: HandshakeTerms) => Promise<string>,\n): PaymentBackend {\n return {\n id: ARISPAY_METERED_BACKEND_ID,\n supports: (terms) => terms.backend === ARISPAY_METERED_BACKEND_ID,\n authorize: async (terms) => ({\n headerName: METERING_SESSION_HEADER,\n headerValue: await acquireSession(terms),\n backend: ARISPAY_METERED_BACKEND_ID,\n }),\n };\n}\n\n/**\n * mandate-card backend (SPECCED, NOT BUILT — post-Gate-0). Ships as a stub\n * that declares its terms shape and throws on authorize. Do NOT implement\n * here; its mechanism is a follow-on session gated on a concluded rail\n * contract (docs/specs/handshake-abstraction-v1.md).\n */\nexport const mandateCardBackend: PaymentBackend = {\n id: \"mandate-card\",\n supports: (terms) => terms.backend === \"mandate-card\",\n authorize: async () => {\n throw new BackendNotImplementedError(\"mandate-card\");\n },\n};\n\n/** Select the backend that satisfies the terms. Ambiguity is a hard error —\n * never a silent fallback that could pay on the wrong rail. */\nexport function selectBackend(\n terms: HandshakeTerms,\n backends: PaymentBackend[],\n): PaymentBackend {\n const matches = backends.filter((b) => b.supports(terms));\n if (matches.length === 0) {\n throw new Error(`no payment backend supports these terms (backend=\"${terms.backend}\", scheme=\"${terms.scheme}\")`);\n }\n if (matches.length > 1) {\n throw new Error(`ambiguous terms — ${matches.length} backends match; set terms.backend explicitly`);\n }\n return matches[0]!;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCO,IAAM,yBAAyB;;;ACE/B,IAAM,6BAAN,cAAyC,MAAM;AAAA,EACpD,OAAO;AAAA,EACP,YAAY,SAAiB;AAC3B,UAAM,oBAAoB,OAAO,kCAAkC;AACnE,SAAK,OAAO;AAAA,EACd;AACF;AAUO,SAAS,gBACd,MACgB;AAChB,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,UAAU,CAAC,UACT,MAAM,YAAY,UACjB,MAAM,WAAW,WAAW,OAAO,MAAM,YAAY,YAAY,MAAM,QAAQ,WAAW,SAAS;AAAA,IACtG,WAAW,OAAO,WAAW;AAAA,MAC3B,YAAY;AAAA,MACZ,aAAa,MAAM,KAAK,KAAK;AAAA,MAC7B,SAAS;AAAA,IACX;AAAA,EACF;AACF;AAcO,IAAM,6BAA6B;AACnC,IAAM,0BAA0B;AAEhC,SAAS,0BACd,gBACgB;AAChB,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,UAAU,CAAC,UAAU,MAAM,YAAY;AAAA,IACvC,WAAW,OAAO,WAAW;AAAA,MAC3B,YAAY;AAAA,MACZ,aAAa,MAAM,eAAe,KAAK;AAAA,MACvC,SAAS;AAAA,IACX;AAAA,EACF;AACF;AAQO,IAAM,qBAAqC;AAAA,EAChD,IAAI;AAAA,EACJ,UAAU,CAAC,UAAU,MAAM,YAAY;AAAA,EACvC,WAAW,YAAY;AACrB,UAAM,IAAI,2BAA2B,cAAc;AAAA,EACrD;AACF;AAIO,SAAS,cACd,OACA,UACgB;AAChB,QAAM,UAAU,SAAS,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC;AACxD,MAAI,QAAQ,WAAW,GAAG;AACxB,UAAM,IAAI,MAAM,qDAAqD,MAAM,OAAO,cAAc,MAAM,MAAM,IAAI;AAAA,EAClH;AACA,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAM,IAAI,MAAM,0BAAqB,QAAQ,MAAM,+CAA+C;AAAA,EACpG;AACA,SAAO,QAAQ,CAAC;AAClB;","names":[]}
{"version":3,"sources":["../src/types.ts","../src/handshake.ts"],"sourcesContent":["/**\n * payagent — Public type definitions.\n */\n\n/** Record of a completed payment. */\nexport interface PaymentReceipt {\n /** The URL that was paid for. */\n url: string;\n /** USDC amount paid (human-readable, e.g. \"0.10\"). */\n amount: string;\n /** USDC amount in base units (e.g. \"100000\"). */\n amountBaseUnits: string;\n /** CAIP-2 network the payment was signed for. */\n network: string;\n /** The wallet address that received payment. */\n payTo: string;\n /** ISO timestamp of the payment. */\n timestamp: string;\n}\n\n/**\n * x402 v2 payment requirements — returned in HTTP 402 response body.\n * Standard format uses `accepts` array.\n */\nexport interface X402Requirements {\n x402Version: 2;\n accepts: X402Accept[];\n}\n\n/**\n * The metered balance-rail scheme identifier (ArisPay balance-rail\n * metering spec §8.1). One value, landed at every seam — the literal is\n * repeated here because this package publishes standalone and cannot\n * depend on the workspace-only shared package.\n */\nexport const ARISPAY_METERED_SCHEME = \"arispay-metered\";\n\n/** Payment schemes payagent understands. */\nexport type PaymentScheme = \"exact\" | typeof ARISPAY_METERED_SCHEME;\n\n/** A single payment option within a 402 response. */\nexport interface X402Accept {\n scheme: PaymentScheme;\n network: string;\n /** USDC amount in base units (6 decimals). x402 v2 spec name. */\n amount: string;\n /** @deprecated v1 alias for `amount`. Read-only fallback for legacy sellers. */\n maxAmountRequired?: string;\n resource: string;\n asset: string;\n payTo: string;\n extra?: { name: string; version: string };\n}\n\n/**\n * AgFac flat format — some servers return requirements as a flat object\n * instead of the standard accepts array.\n */\nexport interface AgfacFlatRequirements {\n x402Version: 2;\n scheme: PaymentScheme;\n network: string;\n /** USDC amount in base units. x402 v2 spec name. */\n amount?: string;\n /** v1 alias for `amount`. Accepted on parse, normalized to `amount`. */\n maxAmountRequired?: string;\n payTo: string;\n resource: string;\n asset: string;\n description?: string;\n expiry?: string;\n}\n\n/** Union of 402 response body formats payagent can handle. */\nexport type PaymentRequirementsBody = X402Requirements | AgfacFlatRequirements;\n","/**\n * Rail-agnostic payment handshake seam (M2M Session 6, constraint 5).\n * Spec: docs/specs/handshake-abstraction-v1.md.\n *\n * The four-stage handshake (payment-required → terms → authorization →\n * delivery) is rail-agnostic; only stage 3 (authorization) is backend-\n * specific. This module defines the backend interface and registers the\n * LIVE x402 (permissionless crypto, Lane P) backend plus a SPECCED-not-built\n * mandate-card backend stub. The existing x402 code paths (`payFetchLocal`,\n * `payFetchDelegated`) are UNCHANGED — the x402 backend here is a thin\n * descriptor over them, not a reimplementation.\n */\n\nexport interface HandshakeTerms {\n /** Which backend can satisfy these terms. */\n backend: string;\n scheme: string;\n network?: string;\n asset?: string;\n amountBaseUnits?: string;\n payTo?: string;\n // mandate-card backend (future) adds: mandateId, currency, amountCents, merchantRef.\n [key: string]: unknown;\n}\n\nexport interface PaymentAuthorization {\n headerName: string;\n headerValue: string;\n backend: string;\n}\n\nexport interface PaymentBackend {\n id: string;\n supports(terms: HandshakeTerms): boolean;\n authorize(terms: HandshakeTerms): Promise<PaymentAuthorization>;\n}\n\nexport class BackendNotImplementedError extends Error {\n code = \"NOT_IMPLEMENTED\" as const;\n constructor(backend: string) {\n super(`payment backend \"${backend}\" is specced but not implemented`);\n this.name = \"BackendNotImplementedError\";\n }\n}\n\n/**\n * x402 permissionless crypto backend (LIVE, Lane P). A descriptor: it\n * declares support for exact-scheme crypto terms. Actual authorization runs\n * through the unchanged `payFetchLocal` / `payFetchDelegated` fetch wrappers\n * — this backend does not re-sign; callers using the fetch wrappers already\n * get stage 3+4 for free. `authorize` is provided for callers that drive the\n * handshake manually via an injected signer.\n */\nexport function makeX402Backend(\n sign: (terms: HandshakeTerms) => Promise<string>,\n): PaymentBackend {\n return {\n id: \"x402\",\n supports: (terms) =>\n terms.backend === \"x402\" ||\n (terms.scheme === \"exact\" &&\n typeof terms.network === \"string\" &&\n (terms.network.startsWith(\"eip155:\") || terms.network.startsWith(\"solana:\"))),\n authorize: async (terms) => ({\n headerName: \"X-PAYMENT\",\n headerValue: await sign(terms),\n backend: \"x402\",\n }),\n };\n}\n\n/**\n * arispay-metered backend (balance-rail metering spec §5.4, §8.1). Metered\n * terms advertise a prepaid-balance rail: the buyer tops up once, then\n * presents an opaque session token; the provider meters each call\n * server-side. Stage 3 for this rail is \"hold a live session\": the\n * injected `acquireSession` resolves the terms to a session token —\n * opening a session (and driving a top-up through `terms.topUpEndpoint`\n * when the balance is exhausted) is the caller's client logic, exactly as\n * `makeX402Backend` delegates signing. Selection is by\n * `terms.backend === \"arispay-metered\"` — no scheme sniffing, no overlap\n * with x402 (`selectBackend` treats ambiguity as a hard error).\n */\nexport const ARISPAY_METERED_BACKEND_ID = \"arispay-metered\";\nexport const METERING_SESSION_HEADER = \"X-Metering-Session\";\n\nexport function makeArispayMeteredBackend(\n acquireSession: (terms: HandshakeTerms) => Promise<string>,\n): PaymentBackend {\n return {\n id: ARISPAY_METERED_BACKEND_ID,\n supports: (terms) => terms.backend === ARISPAY_METERED_BACKEND_ID,\n authorize: async (terms) => ({\n headerName: METERING_SESSION_HEADER,\n headerValue: await acquireSession(terms),\n backend: ARISPAY_METERED_BACKEND_ID,\n }),\n };\n}\n\n/**\n * mandate-card backend (SPECCED, NOT BUILT — post-Gate-0). Ships as a stub\n * that declares its terms shape and throws on authorize. Do NOT implement\n * here; its mechanism is a follow-on session gated on a concluded rail\n * contract (docs/specs/handshake-abstraction-v1.md).\n */\nexport const mandateCardBackend: PaymentBackend = {\n id: \"mandate-card\",\n supports: (terms) => terms.backend === \"mandate-card\",\n authorize: async () => {\n throw new BackendNotImplementedError(\"mandate-card\");\n },\n};\n\n/** Select the backend that satisfies the terms. Ambiguity is a hard error —\n * never a silent fallback that could pay on the wrong rail. */\nexport function selectBackend(\n terms: HandshakeTerms,\n backends: PaymentBackend[],\n): PaymentBackend {\n const matches = backends.filter((b) => b.supports(terms));\n if (matches.length === 0) {\n throw new Error(`no payment backend supports these terms (backend=\"${terms.backend}\", scheme=\"${terms.scheme}\")`);\n }\n if (matches.length > 1) {\n throw new Error(`ambiguous terms — ${matches.length} backends match; set terms.backend explicitly`);\n }\n return matches[0]!;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCO,IAAM,yBAAyB;;;ACE/B,IAAM,6BAAN,cAAyC,MAAM;AAAA,EACpD,OAAO;AAAA,EACP,YAAY,SAAiB;AAC3B,UAAM,oBAAoB,OAAO,kCAAkC;AACnE,SAAK,OAAO;AAAA,EACd;AACF;AAUO,SAAS,gBACd,MACgB;AAChB,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,UAAU,CAAC,UACT,MAAM,YAAY,UACjB,MAAM,WAAW,WAChB,OAAO,MAAM,YAAY,aACxB,MAAM,QAAQ,WAAW,SAAS,KAAK,MAAM,QAAQ,WAAW,SAAS;AAAA,IAC9E,WAAW,OAAO,WAAW;AAAA,MAC3B,YAAY;AAAA,MACZ,aAAa,MAAM,KAAK,KAAK;AAAA,MAC7B,SAAS;AAAA,IACX;AAAA,EACF;AACF;AAcO,IAAM,6BAA6B;AACnC,IAAM,0BAA0B;AAEhC,SAAS,0BACd,gBACgB;AAChB,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,UAAU,CAAC,UAAU,MAAM,YAAY;AAAA,IACvC,WAAW,OAAO,WAAW;AAAA,MAC3B,YAAY;AAAA,MACZ,aAAa,MAAM,eAAe,KAAK;AAAA,MACvC,SAAS;AAAA,IACX;AAAA,EACF;AACF;AAQO,IAAM,qBAAqC;AAAA,EAChD,IAAI;AAAA,EACJ,UAAU,CAAC,UAAU,MAAM,YAAY;AAAA,EACvC,WAAW,YAAY;AACrB,UAAM,IAAI,2BAA2B,cAAc;AAAA,EACrD;AACF;AAIO,SAAS,cACd,OACA,UACgB;AAChB,QAAM,UAAU,SAAS,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC;AACxD,MAAI,QAAQ,WAAW,GAAG;AACxB,UAAM,IAAI,MAAM,qDAAqD,MAAM,OAAO,cAAc,MAAM,MAAM,IAAI;AAAA,EAClH;AACA,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAM,IAAI,MAAM,0BAAqB,QAAQ,MAAM,+CAA+C;AAAA,EACpG;AACA,SAAO,QAAQ,CAAC;AAClB;","names":[]}

@@ -36,4 +36,4 @@ import { DynamicStructuredTool } from '@langchain/core/tools';

}, "strip", z.ZodTypeAny, {
method: "POST" | "GET" | "PUT" | "DELETE" | "PATCH";
url: string;
method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
headers?: Record<string, string> | undefined;

@@ -43,8 +43,8 @@ body?: string | undefined;

url: string;
method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | undefined;
headers?: Record<string, string> | undefined;
method?: "POST" | "GET" | "PUT" | "DELETE" | "PATCH" | undefined;
body?: string | undefined;
}>, {
method: "POST" | "GET" | "PUT" | "DELETE" | "PATCH";
url: string;
method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
headers?: Record<string, string> | undefined;

@@ -54,4 +54,4 @@ body?: string | undefined;

url: string;
method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | undefined;
headers?: Record<string, string> | undefined;
method?: "POST" | "GET" | "PUT" | "DELETE" | "PATCH" | undefined;
body?: string | undefined;

@@ -58,0 +58,0 @@ }, string, unknown, "pay_api">;

import {
payFetchDelegated,
payFetchLocal
} from "./chunk-ZE73ANBP.js";
} from "./chunk-HRS6PG4A.js";

@@ -6,0 +6,0 @@ // src/langchain.ts

@@ -31,4 +31,4 @@ import { Tool } from 'ai';

method: z.ZodDefault<z.ZodEnum<{
GET: "GET";
POST: "POST";
GET: "GET";
PUT: "PUT";

@@ -35,0 +35,0 @@ DELETE: "DELETE";

import {
payFetchDelegated,
payFetchLocal
} from "./chunk-ZE73ANBP.js";
} from "./chunk-HRS6PG4A.js";

@@ -6,0 +6,0 @@ // src/vercel.ts

{
"name": "payagent",
"version": "2.20.0",
"version": "2.21.0",
"description": "Let AI agents pay for APIs. x402 USDC payments two ways: sign locally with your own key (no account, no signup), or use ArisPay delegated custody with server-enforced spend limits.",

@@ -5,0 +5,0 @@ "type": "module",

@@ -78,3 +78,3 @@ # payagent

--per-tx 0.50 --daily 10 --monthly 100 \# dollars; cents stored server-side
--network base \ # base | base-sepolia | ethereum | polygon
--network base \ # base | base-sepolia | ethereum | polygon | bsc
--domains api.example.com,api.example.net \

@@ -144,3 +144,3 @@ --method POST --body '{"foo":"bar"}' \

| `payagent logout` | Clear the developer key. Local agent records are kept. |
| `payagent agent create --name N --per-tx N --daily N --monthly N [--domains a,b] [--network base|base-sepolia|ethereum|polygon]` | Create an x402 agent, cache its credentials locally, and print a hosted funding link when available. |
| `payagent agent create --name N --per-tx N --daily N --monthly N [--domains a,b] [--network base|base-sepolia|ethereum|polygon|bsc]` | Create an x402 agent, cache its credentials locally, and print a hosted funding link when available. |
| `payagent agent fund NAME` | Print the funding address, render a QR, and poll until the wallet first receives USDC. |

@@ -147,0 +147,0 @@ | `payagent agent balance NAME` | Show current balance + `fundedAt`. |

import {
payFetchDelegated
} from "./chunk-ZE73ANBP.js";
// src/config-store.ts
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
import { homedir } from "os";
import { join } from "path";
var DEFAULT_ARISPAY_URL = "https://api.arispay.app";
var CURRENT_VERSION = 1;
function configDir() {
return process.env.PAYAGENT_CONFIG_DIR ?? join(homedir(), ".payagent");
}
function configPath() {
return join(configDir(), "config.json");
}
function loadConfig() {
const path = configPath();
if (!existsSync(path)) return {};
try {
const raw = readFileSync(path, "utf-8");
const parsed = JSON.parse(raw);
return parsed && typeof parsed === "object" ? parsed : {};
} catch {
return {};
}
}
function saveConfig(cfg) {
const dir = configDir();
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true, mode: 448 });
}
const next = { ...cfg, version: CURRENT_VERSION };
writeFileSync(configPath(), JSON.stringify(next, null, 2), { mode: 384 });
}
function getApiKey(explicit) {
if (explicit) return explicit;
if (process.env.ARISPAY_API_KEY) return process.env.ARISPAY_API_KEY;
const cfg = loadConfig();
return cfg.apiKey;
}
function getArispayUrl(explicit) {
if (explicit) return explicit;
if (process.env.ARISPAY_URL) return process.env.ARISPAY_URL;
const cfg = loadConfig();
return cfg.arispayUrl ?? DEFAULT_ARISPAY_URL;
}
function setApiKey(apiKey) {
const cfg = loadConfig();
saveConfig({ ...cfg, apiKey });
}
function clearApiKey() {
const cfg = loadConfig();
const { apiKey: _discarded, ...rest } = cfg;
void _discarded;
saveConfig(rest);
}
function saveAgent(agent) {
const cfg = loadConfig();
const agents = { ...cfg.agents ?? {}, [agent.name]: agent };
saveConfig({ ...cfg, agents });
}
function upsertManyFromServer(agents) {
const cfg = loadConfig();
const next = { ...cfg.agents ?? {} };
for (const fresh of agents) {
const existing = next[fresh.name];
next[fresh.name] = {
...fresh,
// Preserve a usable plaintext agent key if we already had one
// (server side has only the hash, so it can't refresh this).
apiKey: fresh.apiKey || existing?.apiKey || ""
};
}
saveConfig({ ...cfg, agents: next });
}
function renameStoredAgent(oldName, newName) {
const cfg = loadConfig();
const agents = cfg.agents ?? {};
const existing = agents[oldName];
if (!existing) return false;
if (oldName === newName) return false;
const { [oldName]: _drop, ...rest } = agents;
void _drop;
const renamed = { ...existing, name: newName };
saveConfig({ ...cfg, agents: { ...rest, [newName]: renamed } });
return true;
}
function getAgent(name) {
const cfg = loadConfig();
return cfg.agents?.[name];
}
function listAgents() {
const cfg = loadConfig();
return Object.values(cfg.agents ?? {}).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
}
function removeAgent(name) {
const cfg = loadConfig();
if (!cfg.agents?.[name]) return false;
const { [name]: _removed, ...rest } = cfg.agents;
void _removed;
saveConfig({ ...cfg, agents: rest });
return true;
}
function getConfigPath() {
return configPath();
}
// src/delegation.ts
var HostedTopupNotConfiguredError = class extends Error {
walletAddress;
network;
constructor(message, walletAddress, network) {
super(message);
this.name = "HostedTopupNotConfiguredError";
this.walletAddress = walletAddress;
this.network = network;
}
};
function randomIdempotencyKey() {
const ts = Date.now().toString(36);
const rnd = Math.floor(Math.random() * 268435455).toString(36).padStart(6, "0");
return `payagent-${ts}-${rnd}`;
}
function paymentsFeedQuery(opts, base = {}) {
const params = new URLSearchParams(base);
if (opts.status) params.set("status", opts.status);
if (opts.rail) params.set("rail", opts.rail);
if (opts.cursor) params.set("cursor", opts.cursor);
if (opts.limit !== void 0) params.set("limit", String(opts.limit));
return params.toString();
}
var DelegationClient = class {
baseUrl;
authToken;
constructor(baseUrl, authToken) {
if (!baseUrl) throw new Error("DelegationClient: baseUrl is required");
if (!authToken) throw new Error("DelegationClient: authToken is required");
this.baseUrl = baseUrl.replace(/\/$/, "");
this.authToken = authToken;
}
/** Create an x402 payer agent. Returns wallet address + one-time API key. */
async createX402Agent(config) {
return this.request("POST", "/v1/agents/x402", config);
}
/** Create a wallet-centric sub-wallet (no on-chain x402 wallet). */
async createWallet(options) {
return this.request("POST", "/v1/wallets", options);
}
/** List sub-wallets for the authenticated developer user. */
async listWallets() {
return this.request("GET", "/v1/wallets");
}
/** Get a sub-wallet plus its master funding-account balances. */
async getWalletBalance(walletId) {
return this.request("GET", `/v1/wallets/${encodeURIComponent(walletId)}/balance`);
}
/** Fetch the on-chain USDC balance for a delegation's wallet. */
async getBalance(agentId) {
return this.request(
"GET",
`/v1/agents/${encodeURIComponent(agentId)}/x402-balance`
);
}
/**
* Per-wallet activity feed. Cursor-paginated, newest first, scoped
* to the agent's org. Use for "show recent activity on this wallet"
* surfaces (BuyForMe wallet detail, future CLI `payagent payments`).
*/
async listAgentPayments(agentId, options = {}) {
const params = paymentsFeedQuery(options);
const path = `/v1/agents/${encodeURIComponent(agentId)}/payments${params ? `?${params}` : ""}`;
return this.request("GET", path);
}
/**
* Cross-wallet activity feed for the developer key's org. The
* `scope=org` query parameter is required server-side — we pass it
* for the caller because it's the only meaningful scope today.
*/
async listOrgPayments(options = {}) {
const params = paymentsFeedQuery(options, { scope: "org" });
return this.request("GET", `/v1/payments?${params}`);
}
/**
* Server-authoritative wallet listing — every agent under the developer
* key's org that has an x402 wallet. Use this from the MCP / CLI rather
* than reading `~/.payagent/config.json` directly: the local file is a
* cache that goes stale (new machine, re-bootstrap), and a stale cache
* is how end-users end up minting fresh wallets and orphaning their
* funded ones. Balance is intentionally NOT included — fan out to
* `getBalance(agentId)` only for wallets you actually need to display
* live, to keep the list call cheap.
*/
async listAgents(options = {}) {
const params = new URLSearchParams({ withDelegation: "1" });
if (options.name) params.set("name", options.name);
if (options.status) params.set("status", options.status);
if (options.limit !== void 0) params.set("limit", String(options.limit));
if (options.offset !== void 0) params.set("offset", String(options.offset));
const raw = await this.request("GET", `/v1/agents?${params.toString()}`);
const agents = raw.agents.filter((a) => a.x402).map((a) => ({
agentId: a.id,
name: a.name,
walletAddress: a.x402.walletAddress,
network: a.x402.network,
limits: {
maxPerTx: a.x402.maxPerTx,
maxDaily: a.x402.maxDaily,
maxMonthly: a.x402.maxMonthly
},
allowedDomains: a.x402.allowedDomains,
custody: a.x402.custody,
suspended: a.x402.suspended,
fundedAt: a.x402.fundedAt,
createdAt: a.createdAt
}));
return { agents, total: raw.total, limit: raw.limit, offset: raw.offset };
}
/**
* Rename an agent. Server enforces per-org name uniqueness (the bootstrap
* recovery path keys on `(orgId, name)`, so duplicate names would silently
* flip which agent the next bootstrap reuses). Throws on a 409 conflict.
*
* Thin wrapper over `updateAgent({ name })` kept for compatibility and
* because rename is the most-common patch from the CLI/MCP surface.
*/
async renameAgent(agentId, newName) {
const updated = await this.updateAgent(agentId, { name: newName });
return {
agentId: updated.id,
name: updated.name,
walletAddress: updated.x402?.walletAddress,
network: updated.x402?.network
};
}
/**
* General-purpose agent patch — rename, limits, allowedDomains, and
* the suspend kill-switch all flow through here. The patch is sent
* as-is to `PATCH /v1/agents/:id`; only the keys you set are touched.
* Server enforces:
* - name uniqueness per org (409 on conflict)
* - maxPerTx ≤ maxDaily ≤ maxMonthly (400 on violation)
* - `suspended` requires an existing AgentDelegation row (400 otherwise)
*
* Returns the hydrated agent (including the refreshed `x402` block)
* so callers can update their local cache without a follow-up GET.
*/
async updateAgent(agentId, patch) {
return this.request(
"PATCH",
`/v1/agents/${encodeURIComponent(agentId)}`,
patch
);
}
/**
* Rotate an x402 agent's delegation key — the recovery path when the
* agent key from `createX402Agent` / bootstrap is lost. The server mints
* a fresh agent key and returns the new plaintext ONCE — it is never
* recoverable afterwards, so persist it immediately. Wallet, network,
* limits, allowedDomains, spend counters, and suspension are untouched.
*
* Revocation of the previous credential is two-layered and the second
* layer is CONDITIONAL: the old key always stops working for delegated
* signing, and its org ApiKey record is additionally revoked when the
* server can identify it — reported via `previousKeyRevoked`. When
* `previousKeyRevoked` is `false` (some legacy delegations), the old key
* can no longer sign but MAY STILL AUTHENTICATE to non-signing org API
* routes until you revoke that ApiKey manually (dashboard → API keys).
*
* Requires a developer management credential (or a dashboard session).
* Agent-scoped keys are rejected with 403 — an agent key cannot rotate
* itself or any sibling agent. A concurrent rotation of the same agent
* returns 409 `ROTATION_CONFLICT` for the loser; the winner's key is
* unaffected.
*/
async rotateX402Key(agentId) {
return this.request(
"POST",
`/v1/agents/${encodeURIComponent(agentId)}/x402-key/rotate`
);
}
/**
* Request a hosted top-up URL (Coinbase Onramp, etc.) for this agent.
*
* Returns `{ fundingUrl, provider, walletAddress, network, expiresAt }`.
* The caller (CLI, MCP, bot) shares the URL with an end-user who pays
* with card / Apple Pay / bank transfer; the onramp deposits USDC
* straight into the agent's CDP wallet. ArisPay never touches the funds.
*
* Throws `HostedTopupNotConfiguredError` (with 501 status) when the
* deployment hasn't set `ARISPAY_ONRAMP_PROVIDER`, so callers can
* gracefully fall back to "send USDC manually to this address".
*/
async getHostedTopup(agentId, options = {}) {
const body = {};
if (options.amount !== void 0) body.amount = options.amount;
try {
return await this.request(
"POST",
`/v1/agents/${encodeURIComponent(agentId)}/hosted-topup`,
body
);
} catch (err) {
if (err instanceof Error && /\(501\)/.test(err.message)) {
throw new HostedTopupNotConfiguredError(err.message);
}
throw err;
}
}
/**
* Poll `getBalance` until the wallet shows non-zero USDC (i.e. `fundedAt` latches).
*
* @param agentId The agent ID returned from createX402Agent.
* @param options.intervalMs Poll interval (default 5000).
* @param options.timeoutMs Give up after this many ms (default 10 minutes). Pass 0 for no timeout.
*/
async pollUntilFunded(agentId, options = {}) {
const interval = options.intervalMs ?? 5e3;
const timeout = options.timeoutMs ?? 10 * 60 * 1e3;
const deadline = timeout > 0 ? Date.now() + timeout : Number.POSITIVE_INFINITY;
while (true) {
const balance = await this.getBalance(agentId);
if (balance.fundedAt || BigInt(balance.usdcBalance || "0") > 0n) {
return balance;
}
if (Date.now() + interval > deadline) {
throw new Error(
`pollUntilFunded: timed out after ${timeout}ms waiting for funding of ${agentId}`
);
}
await new Promise((r) => setTimeout(r, interval));
}
}
// ── End-user helpers (Phase 3) ───────────────────────────────────────────
/** Create (or find-or-create) an end-user. `externalId` is your own id for this customer. */
async createEndUser(options) {
return this.request("POST", "/v1/users", options);
}
/** Fetch an end-user by internal id. */
async getEndUser(id) {
return this.request("GET", `/v1/users/${encodeURIComponent(id)}`);
}
/** Fetch an end-user by the developer's external id. */
async getEndUserByExternalId(externalId) {
return this.request(
"GET",
`/v1/users/by-external/${encodeURIComponent(externalId)}`
);
}
/**
* Create a hosted card-setup session for an end-user. Returns a URL you share
* with the end-user; they open it in a browser, enter their card, and ArisPay's
* hosted page handles tokenization + 3DS. Poll `getCardSetupStatus` or
* `pollCardSetup` to detect completion.
*/
async createCardSetupSession(options) {
return this.request("POST", "/v1/card-setup-sessions", options);
}
/** One-shot status check for a card-setup session. */
async getCardSetupStatus(token) {
return this.request(
"GET",
`/v1/card-setup-sessions/${encodeURIComponent(token)}/status`
);
}
/**
* Poll `getCardSetupStatus` until the session reaches a terminal state
* (`completed`, `expired`, `failed`, or `not_found`) or the timeout elapses.
*/
async pollCardSetup(token, options = {}) {
const interval = options.intervalMs ?? 3e3;
const timeout = options.timeoutMs ?? 15 * 60 * 1e3;
const deadline = timeout > 0 ? Date.now() + timeout : Number.POSITIVE_INFINITY;
const terminal = ["completed", "expired", "failed", "not_found"];
while (true) {
const status = await this.getCardSetupStatus(token);
if (terminal.includes(status.status)) {
return status;
}
if (Date.now() + interval > deadline) {
throw new Error(
`pollCardSetup: timed out after ${timeout}ms waiting for card-setup session`
);
}
await new Promise((r) => setTimeout(r, interval));
}
}
/** Attach a USDC wallet to an end-user as a payment method (non-custodial rail). */
async attachWallet(endUserId, options) {
return this.request(
"POST",
`/v1/users/${encodeURIComponent(endUserId)}/payment-methods`,
{ type: "wallet", ...options }
);
}
/**
* Set per-user, per-agent spend limits. Overrides the agent defaults for
* this specific end-user + agent pair.
*/
async setUserLimits(endUserId, options) {
return this.request(
"PUT",
`/v1/users/${encodeURIComponent(endUserId)}/limits`,
options
);
}
/** Current wallet state for a wallet-attached end-user: balance, allowance, readiness. */
async getWalletStatus(endUserId) {
return this.request(
"GET",
`/v1/users/${encodeURIComponent(endUserId)}/wallet-status`
);
}
/**
* Create a payment from an agent (and optionally an end-user) to a merchant.
* Picks the rail server-side based on `rail`, agent mode, and whether a
* `userId` is supplied. Autonomous agents pay from their ledger balance by
* default; platform agents pay via their end-user's card (or crypto / MPP).
*
* For x402-priced API calls, use `agent.fetch(url)` instead — that path
* handles the 402 challenge + signing transparently and is the right tool
* for buying a single HTTP resource.
*
* An `idempotencyKey` is auto-generated if not provided. Callers who need
* end-to-end idempotency across retries should supply their own.
*/
async createPayment(agentId, options) {
const body = {
agentId,
userId: options.userId,
amount: options.amount,
currency: options.currency ?? "USD",
memo: options.memo,
merchantUrl: options.merchantUrl,
merchantName: options.merchantName,
merchantCategoryCode: options.merchantCategoryCode,
merchantId: options.merchantId,
rail: options.rail,
idempotencyKey: options.idempotencyKey ?? randomIdempotencyKey(),
metadata: options.metadata,
token: options.token,
chain: options.chain,
recipientAddress: options.recipientAddress
};
return this.request("POST", "/v1/payments", body);
}
async request(method, path, body) {
const res = await fetch(this.baseUrl + path, {
method,
headers: {
Authorization: `Bearer ${this.authToken}`,
...body !== void 0 ? { "Content-Type": "application/json" } : {}
},
body: body !== void 0 ? JSON.stringify(body) : void 0
});
const text = await res.text();
let parsed;
try {
parsed = text ? JSON.parse(text) : null;
} catch {
parsed = text;
}
if (!res.ok) {
const message = parsed && typeof parsed === "object" && "error" in parsed && parsed.error?.message || (typeof parsed === "string" ? parsed : res.statusText);
throw new Error(`ArisPay ${method} ${path} failed (${res.status}): ${message}`);
}
return parsed;
}
};
// src/launch.ts
var MissingArisPayApiKeyError = class extends Error {
constructor() {
super(
"No ArisPay API key found. Set ARISPAY_API_KEY in your environment or run `npx payagent init`."
);
this.name = "MissingArisPayApiKeyError";
}
};
async function launchAgent(config) {
const apiKey = getApiKey(config.arispayApiKey);
if (!apiKey) throw new MissingArisPayApiKeyError();
const baseUrl = getArispayUrl(config.arispayUrl);
const client = new DelegationClient(baseUrl, apiKey);
const createConfig = {
name: config.name,
agentType: config.agentType,
maxPerTx: config.limits.perTx,
maxDaily: config.limits.daily,
maxMonthly: config.limits.monthly,
allowedDomains: config.allowedDomains,
description: config.description,
network: config.network
};
const created = await client.createX402Agent(createConfig);
if (!config.ephemeral) {
const stored = {
agentId: created.agentId,
name: config.name,
walletAddress: created.walletAddress,
apiKey: created.apiKey,
limits: {
perTx: created.limits.maxPerTx,
daily: created.limits.maxDaily,
monthly: created.limits.maxMonthly
},
allowedDomains: created.allowedDomains,
network: config.network ?? "base",
createdAt: (/* @__PURE__ */ new Date()).toISOString()
};
saveAgent(stored);
}
return buildLaunchedAgent({
name: config.name,
baseUrl,
created
});
}
function getLaunchedAgent(name, overrides = {}) {
const stored = getAgent(name);
if (!stored) return void 0;
const baseUrl = getArispayUrl(overrides.arispayUrl);
const synthetic = {
agentId: stored.agentId,
walletAddress: stored.walletAddress,
apiKey: stored.apiKey,
status: "pending_funding",
limits: {
maxPerTx: stored.limits.perTx,
maxDaily: stored.limits.daily,
maxMonthly: stored.limits.monthly
},
allowedDomains: stored.allowedDomains ?? [],
network: stored.network
};
return buildLaunchedAgent({
name,
baseUrl,
created: synthetic
});
}
function buildLaunchedAgent(input) {
const { name, baseUrl, created } = input;
const devKey = getApiKey();
if (!devKey) throw new MissingArisPayApiKeyError();
const client = new DelegationClient(baseUrl, devKey);
const boundFetch = payFetchDelegated({
arispayUrl: baseUrl,
apiKey: created.apiKey
});
return {
agentId: created.agentId,
name,
walletAddress: created.walletAddress,
apiKey: created.apiKey,
status: created.status,
limits: {
perTx: created.limits.maxPerTx,
daily: created.limits.maxDaily,
monthly: created.limits.maxMonthly
},
allowedDomains: created.allowedDomains,
network: created.network ?? "base",
fetch: boundFetch,
getBalance: () => client.getBalance(created.agentId),
waitUntilFunded: (options) => client.pollUntilFunded(created.agentId, options),
getFundingLink: (options) => client.getHostedTopup(created.agentId, options),
setUserLimits: (endUserId, options) => client.setUserLimits(endUserId, { ...options, agentId: created.agentId }),
pay: (options) => client.createPayment(created.agentId, options)
};
}
// src/bootstrap.ts
var BootstrapError = class extends Error {
status;
code;
constructor(status, code, message) {
super(message);
this.name = "BootstrapError";
this.status = status;
this.code = code;
}
};
async function bootstrapAgent(config) {
const baseUrl = (config.arispayUrl ?? DEFAULT_ARISPAY_URL).replace(/\/$/, "");
const body = {
email: config.email,
clientId: config.clientId ?? "payagent-cli"
};
if (config.name) body.name = config.name;
if (config.orgName) body.orgName = config.orgName;
if (config.agentName) body.agentName = config.agentName;
if (config.limits?.perTx !== void 0) body.maxPerTx = config.limits.perTx;
if (config.limits?.daily !== void 0) body.maxDaily = config.limits.daily;
if (config.limits?.monthly !== void 0) body.maxMonthly = config.limits.monthly;
if (config.allowedDomains?.length) body.allowedDomains = config.allowedDomains;
if (config.agentType) body.agentType = config.agentType;
if (config.description) body.description = config.description;
if (config.fund !== void 0) body.fund = config.fund;
const res = await fetch(`${baseUrl}/v1/bootstrap`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body)
});
if (!res.ok) {
const errBody = await safeJson(res);
const code = errBody?.error?.code ?? "BOOTSTRAP_FAILED";
const message = errBody?.error?.message ?? `${res.statusText} (HTTP ${res.status})`;
throw new BootstrapError(res.status, code, message);
}
const server = await res.json();
const network = server.network ?? "base";
const limits = {
perTx: server.limits.maxPerTx,
daily: server.limits.maxDaily,
monthly: server.limits.maxMonthly
};
const existingAgents = server.existingAgents ?? [];
if (!config.ephemeral) {
setApiKey(server.developerKey);
if (config.arispayUrl && config.arispayUrl !== DEFAULT_ARISPAY_URL) {
const cfg = loadConfig();
saveConfig({ ...cfg, arispayUrl: config.arispayUrl });
}
const stored = {
agentId: server.agentId,
name: server.agentName,
walletAddress: server.walletAddress,
apiKey: server.agentApiKey,
limits,
allowedDomains: server.allowedDomains,
network,
createdAt: (/* @__PURE__ */ new Date()).toISOString()
};
saveAgent(stored);
const knownNetworks = [
"base",
"base-sepolia",
"ethereum",
"polygon"
];
const others = existingAgents.filter((a) => a.agentId !== server.agentId).map((a) => {
const net = knownNetworks.includes(a.network) ? a.network : void 0;
return {
agentId: a.agentId,
name: a.name,
walletAddress: a.walletAddress,
apiKey: "",
limits: {
perTx: a.limits.maxPerTx,
daily: a.limits.maxDaily,
monthly: a.limits.maxMonthly
},
allowedDomains: a.allowedDomains,
network: net,
createdAt: a.createdAt
};
});
if (others.length) {
upsertManyFromServer(others);
}
}
const devClient = new DelegationClient(baseUrl, server.developerKey);
const boundFetch = payFetchDelegated({
arispayUrl: baseUrl,
apiKey: server.agentApiKey
});
const synthetic = {
agentId: server.agentId,
walletAddress: server.walletAddress,
apiKey: server.agentApiKey,
status: server.status === "active" ? "active" : "pending_funding",
limits: server.limits,
allowedDomains: server.allowedDomains,
network
};
return {
developerKey: server.developerKey,
userId: server.userId,
orgId: server.orgId,
orgName: server.orgName,
email: server.email,
agentId: server.agentId,
agentName: server.agentName,
agentApiKey: server.agentApiKey,
walletAddress: server.walletAddress,
network,
limits,
allowedDomains: server.allowedDomains,
custody: "delegated",
status: synthetic.status,
environment: "production",
reused: server.reused ?? false,
existingAgents,
pairing: server.pairing,
fundingUrl: server.fundingUrl,
fundingExpiresAt: server.fundingExpiresAt,
fundingProvider: server.fundingProvider,
fundingUrlError: server.fundingUrlError,
fetch: boundFetch,
getBalance: () => devClient.getBalance(server.agentId),
waitUntilFunded: (options) => devClient.pollUntilFunded(server.agentId, options),
getFundingLink: (options) => devClient.getHostedTopup(server.agentId, options)
};
}
async function safeJson(res) {
try {
return await res.clone().json();
} catch {
return void 0;
}
}
// src/device-code.ts
var PENDING_CODES = /* @__PURE__ */ new Set(["authorization_pending", "slow_down"]);
var DeviceCodeError = class extends Error {
code;
constructor(code, message) {
super(message);
this.name = "DeviceCodeError";
this.code = code;
}
};
async function requestDeviceCode(options = {}) {
const baseUrl = (options.arispayUrl ?? DEFAULT_ARISPAY_URL).replace(/\/$/, "");
const res = await fetch(`${baseUrl}/v1/auth/device/code`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ clientId: options.clientId ?? "payagent-cli" })
});
if (!res.ok) {
const body = await safeJson2(res);
const message = body?.error?.message ?? await safeText(res) ?? res.statusText;
throw new DeviceCodeError(body?.error?.code ?? "device_code_request_failed", message);
}
return await res.json();
}
async function pollDeviceToken(deviceCode, options = {}) {
const baseUrl = (options.arispayUrl ?? DEFAULT_ARISPAY_URL).replace(/\/$/, "");
const clientId = options.clientId ?? "payagent-cli";
const timeoutMs = options.timeoutMs ?? 10 * 60 * 1e3;
const start = Date.now();
let intervalMs = options.intervalMs ?? 5e3;
while (true) {
const elapsed = Date.now() - start;
options.onTick?.(elapsed);
if (elapsed >= timeoutMs) {
throw new DeviceCodeError(
"device_code_timeout",
`Timed out after ${Math.round(timeoutMs / 1e3)}s waiting for user to authorize`
);
}
const res = await fetch(`${baseUrl}/v1/auth/device/token`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ deviceCode, clientId })
});
if (res.ok) {
return await res.json();
}
const body = await safeJson2(res);
const code = body?.error?.code ?? "device_code_error";
const message = body?.error?.message ?? res.statusText;
if (code === "slow_down") {
intervalMs = Math.min(intervalMs * 2, 6e4);
} else if (!PENDING_CODES.has(code)) {
throw new DeviceCodeError(code, message);
}
await new Promise((r) => setTimeout(r, intervalMs));
}
}
async function runDeviceAuth(options = {}) {
const code = await requestDeviceCode({
arispayUrl: options.arispayUrl,
clientId: options.clientId
});
options.onCode?.(code);
return pollDeviceToken(code.deviceCode, {
arispayUrl: options.arispayUrl,
clientId: options.clientId,
intervalMs: code.interval * 1e3,
timeoutMs: options.pollTimeoutMs ?? code.expiresIn * 1e3,
onTick: options.onTick
});
}
async function safeJson2(res) {
try {
return await res.clone().json();
} catch {
return void 0;
}
}
async function safeText(res) {
try {
return await res.clone().text();
} catch {
return void 0;
}
}
// src/sync-agents.ts
var MissingDevKeyForSyncError = class extends Error {
constructor() {
super("syncAgents: developer API key required (set ARISPAY_API_KEY or run `payagent init`).");
this.name = "MissingDevKeyForSyncError";
}
};
async function syncAgents(options = {}) {
const apiKey = getApiKey(options.apiKey);
if (!apiKey) throw new MissingDevKeyForSyncError();
const baseUrl = getArispayUrl(options.arispayUrl) || DEFAULT_ARISPAY_URL;
const client = new DelegationClient(baseUrl, apiKey);
const list = await client.listAgents({
name: options.name,
status: options.status,
limit: options.limit,
offset: options.offset
});
let agents = list.agents;
if (options.includeBalance && agents.length) {
const balances = await Promise.allSettled(agents.map((a) => client.getBalance(a.agentId)));
agents = agents.map((a, i) => {
const settled = balances[i];
if (settled?.status === "fulfilled") {
return { ...a, usdcBalance: settled.value.usdcBalance };
}
return a;
});
}
const knownNetworks = [
"base",
"base-sepolia",
"ethereum",
"polygon"
];
const stored = agents.map((a) => {
const network = knownNetworks.includes(a.network) ? a.network : void 0;
return {
agentId: a.agentId,
name: a.name,
walletAddress: a.walletAddress,
apiKey: "",
// server has only the hash; preserved-from-local in upsertManyFromServer
limits: {
perTx: a.limits.maxPerTx,
daily: a.limits.maxDaily,
monthly: a.limits.maxMonthly
},
allowedDomains: a.allowedDomains,
network,
createdAt: a.createdAt
};
});
upsertManyFromServer(stored);
return { agents, total: list.total, limit: list.limit, offset: list.offset };
}
// src/discover.ts
var DEFAULT_MARKETPLACE_URL = "https://api.arispay.app/v1/marketplace";
async function discover(input = {}, opts = {}) {
const baseUrl = (opts.marketplaceUrl ?? DEFAULT_MARKETPLACE_URL).replace(/\/$/, "");
const fetchImpl = opts.fetch ?? globalThis.fetch;
const res = await fetchImpl(`${baseUrl}/discover`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(input),
signal: opts.signal
});
if (!res.ok) {
const text = await res.text().catch(() => "");
throw new Error(`payagent discover failed (${res.status}): ${text || res.statusText}`);
}
return await res.json();
}
var DEFAULT_FACILITATOR_URL = "https://facilitator.arispay.app";
async function discoverCatalog(query, opts = {}) {
const base = (opts.facilitatorUrl ?? DEFAULT_FACILITATOR_URL).replace(/\/$/, "");
const params = new URLSearchParams();
if (query) params.set("q", query);
if (opts.limit) params.set("limit", String(opts.limit));
const fetchImpl = opts.fetch ?? globalThis.fetch;
const res = await fetchImpl(`${base}/discovery/resources?${params.toString()}`, {
headers: { accept: "application/json" },
signal: opts.signal
});
if (!res.ok) {
throw new Error(`facilitator catalog query failed: HTTP ${res.status}`);
}
const body = await res.json();
const items = Array.isArray(body.items) ? body.items : [];
const resources = [];
for (const item of items) {
if (typeof item.resource !== "string") continue;
const accepts = Array.isArray(item.accepts) ? item.accepts : [];
const first = accepts[0];
resources.push({
resource: item.resource,
type: typeof item.type === "string" ? item.type : "http",
...typeof item.method === "string" ? { method: item.method } : {},
...typeof item.description === "string" ? { description: item.description } : {},
...typeof first?.network === "string" ? { network: first.network } : {},
...typeof first?.payTo === "string" ? { payTo: first.payTo } : {},
...typeof first?.amount === "string" ? { amountBaseUnits: first.amount } : {},
...typeof first?.asset === "string" ? { asset: first.asset } : {},
...typeof item.metadata?.status === "string" ? { status: item.metadata.status } : {}
});
}
return { resources, total: body.pagination?.total ?? resources.length };
}
// src/inspect.ts
var EURC_ASSETS = /* @__PURE__ */ new Set([
"0x60a3e35cc302bfa44cb288bc5a4f316fdb1adb42",
// Base mainnet
"0x808456652fdb597867f38412077a9182bf77359f"
// Base Sepolia
]);
var USDC_ASSETS = /* @__PURE__ */ new Set([
"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
// Base mainnet
"0x036cbd53842c5426634e7929541ec2318f3dcf7e",
// Base Sepolia
"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
// Ethereum
"0x3c499c542cef5e3811e1192ce70d8cc03d5c3359"
// Polygon
]);
var InspectParseError = class extends Error {
constructor(message) {
super(message);
this.name = "InspectParseError";
}
};
function centsFromBaseUnits(baseUnits) {
let bu;
try {
bu = BigInt(baseUnits);
} catch {
return void 0;
}
if (bu < 0n || bu % 10000n !== 0n) return void 0;
const cents = bu / 10000n;
return cents <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(cents) : void 0;
}
function currencyFor(asset, assetName) {
const addr = asset.toLowerCase();
if (USDC_ASSETS.has(addr)) return "USD";
if (EURC_ASSETS.has(addr)) return "EUR";
if (assetName === "EURC") return "EUR";
if (assetName === "USD Coin" || assetName === "USDC") return "USD";
return void 0;
}
function normalizeAccept(raw) {
const amount = typeof raw.amount === "string" ? raw.amount : typeof raw.maxAmountRequired === "string" ? raw.maxAmountRequired : void 0;
const asset = typeof raw.asset === "string" ? raw.asset : void 0;
const payTo = typeof raw.payTo === "string" ? raw.payTo : void 0;
const network = typeof raw.network === "string" ? raw.network : void 0;
const scheme = typeof raw.scheme === "string" ? raw.scheme : void 0;
if (amount === void 0 || !asset || !payTo || !network || !scheme) return void 0;
const extra = raw.extra;
const assetName = typeof extra?.name === "string" ? extra.name : void 0;
const accept = {
scheme,
network,
amountBaseUnits: amount,
asset,
payTo
};
const cents = centsFromBaseUnits(amount);
if (cents !== void 0) accept.amountCents = cents;
if (typeof raw.resource === "string") accept.resource = raw.resource;
if (typeof raw.description === "string") accept.description = raw.description;
if (typeof raw.mimeType === "string") accept.mimeType = raw.mimeType;
if (assetName) accept.assetName = assetName;
const currency = currencyFor(asset, assetName);
if (currency) accept.currency = currency;
return accept;
}
async function inspectChallenge(url, opts = {}) {
const fetchImpl = opts.fetch ?? globalThis.fetch;
const signal = opts.signal ?? AbortSignal.timeout(opts.timeoutMs ?? 15e3);
const res = await fetchImpl(url, { method: "GET", signal });
if (res.status !== 402) {
return { gated: false, url, status: res.status };
}
let body;
try {
body = await res.json();
} catch {
body = null;
}
const bodyHasAccepts = typeof body === "object" && body !== null && Array.isArray(body.accepts);
if (!bodyHasAccepts) {
const header = typeof res.headers?.get === "function" ? res.headers.get("payment-required") : null;
if (header) {
try {
const decoded = typeof atob === "function" ? atob(header) : Buffer.from(header, "base64").toString("utf8");
body = JSON.parse(decoded);
} catch {
}
}
}
if (body === null) {
throw new InspectParseError(
`${url} returned HTTP 402 but neither the body nor the payment-required header carries a parseable x402 challenge.`
);
}
if (typeof body !== "object") {
throw new InspectParseError(
`${url} returned HTTP 402 but the body is not an x402 challenge object.`
);
}
const obj = body;
const accepts = [];
if (Array.isArray(obj.accepts)) {
for (const raw of obj.accepts) {
if (typeof raw === "object" && raw !== null) {
const normalized = normalizeAccept(raw);
if (normalized) accepts.push(normalized);
}
}
} else {
const normalized = normalizeAccept(obj);
if (normalized) accepts.push(normalized);
}
if (accepts.length === 0) {
throw new InspectParseError(
`${url} returned HTTP 402 but no payment option in the body could be parsed (missing amount/asset/payTo/network).`
);
}
const result = {
gated: true,
url,
status: 402,
accepts
};
if (typeof obj.x402Version === "number") result.x402Version = obj.x402Version;
if (typeof obj.facilitator === "string") result.serverDeclaredFacilitator = obj.facilitator;
if (typeof obj.trustMinTier === "string") result.serverDeclaredTrustMinTier = obj.trustMinTier;
if (typeof obj.gasSponsored === "boolean") result.gasSponsored = obj.gasSponsored;
if (typeof obj.manifest === "string") result.manifestUrl = obj.manifest;
const bazaar = obj.extensions?.bazaar;
if (bazaar) result.bazaarDiscoverable = bazaar.discoverable !== false;
return result;
}
export {
DEFAULT_ARISPAY_URL,
loadConfig,
saveConfig,
getApiKey,
getArispayUrl,
setApiKey,
clearApiKey,
saveAgent,
upsertManyFromServer,
renameStoredAgent,
getAgent,
listAgents,
removeAgent,
getConfigPath,
HostedTopupNotConfiguredError,
DelegationClient,
MissingArisPayApiKeyError,
launchAgent,
getLaunchedAgent,
BootstrapError,
bootstrapAgent,
DeviceCodeError,
requestDeviceCode,
pollDeviceToken,
runDeviceAuth,
MissingDevKeyForSyncError,
syncAgents,
discover,
discoverCatalog,
InspectParseError,
centsFromBaseUnits,
inspectChallenge
};
//# sourceMappingURL=chunk-Y6TBMMTR.js.map

Sorry, the diff of this file is too big to display

// src/errors.ts
var PayAgentError = class extends Error {
constructor(message) {
super(message);
this.name = "PayAgentError";
}
};
var PaymentRejectedError = class extends PayAgentError {
status;
constructor(status, message) {
super(message ?? `Server rejected payment (HTTP ${status})`);
this.name = "PaymentRejectedError";
this.status = status;
}
};
var InvalidRequirementsError = class extends PayAgentError {
constructor(detail) {
super(`Could not parse 402 payment requirements${detail ? `: ${detail}` : ""}`);
this.name = "InvalidRequirementsError";
}
};
// src/payment.ts
var NETWORK_SHORT_TO_CAIP2 = {
ethereum: "eip155:1",
polygon: "eip155:137",
base: "eip155:8453",
"base-sepolia": "eip155:84532"
};
function normalizeNetwork(network) {
if (network.includes(":")) return network;
return NETWORK_SHORT_TO_CAIP2[network] ?? network;
}
var NETWORK_CAIP2_TO_SHORT = Object.fromEntries(
Object.entries(NETWORK_SHORT_TO_CAIP2).map(([s, c]) => [c, s])
);
function isStandardFormat(body) {
return "accepts" in body && Array.isArray(body.accepts);
}
function isFlatFormat(body) {
return "scheme" in body && "payTo" in body && !("accepts" in body);
}
function extractAccepts(body) {
if (isStandardFormat(body)) {
return body.accepts.map((a) => {
const rawAmount = a.amount ?? a.maxAmountRequired;
return {
...a,
network: normalizeNetwork(a.network),
amount: rawAmount
};
});
}
if (isFlatFormat(body)) {
return [
{
scheme: body.scheme,
network: normalizeNetwork(body.network),
amount: body.amount ?? body.maxAmountRequired,
resource: body.resource,
asset: body.asset,
payTo: body.payTo,
extra: { name: "USDC", version: "2" }
}
];
}
throw new InvalidRequirementsError("unrecognized format");
}
function extractExtensions(source) {
const ext = source?.extensions;
return ext && typeof ext === "object" ? ext : void 0;
}
function extractRawAccepts(body) {
if (isStandardFormat(body)) return body.accepts;
if (isFlatFormat(body)) return [body];
return [];
}
var HEADER_LOOKUP_ORDER = [
"payment-required",
"x-payment-required",
"x-payment-requirements"
];
function tryParseHeaderValue(value) {
try {
const decoded = Buffer.from(value, "base64").toString("utf8");
const parsed = JSON.parse(decoded);
if (parsed && typeof parsed === "object") return parsed;
} catch {
}
try {
const parsed = JSON.parse(value);
if (parsed && typeof parsed === "object") return parsed;
} catch {
}
return null;
}
async function parseRequirements(response) {
for (const headerName of HEADER_LOOKUP_ORDER) {
const value = response.headers.get(headerName);
if (!value) continue;
const parsed = tryParseHeaderValue(value);
if (!parsed) continue;
try {
const body2 = parsed.requirements ?? parsed;
const accepts = extractAccepts(body2);
if (accepts.length > 0) {
return {
accepts,
rawAccepts: extractRawAccepts(body2),
x402Version: typeof parsed.x402Version === "number" ? parsed.x402Version : 2,
extensions: extractExtensions(parsed) ?? extractExtensions(body2)
};
}
} catch {
}
}
let json;
try {
json = await response.json();
} catch {
throw new InvalidRequirementsError("response body is not valid JSON");
}
const body = json.requirements ?? json;
if (!body || typeof body !== "object") {
throw new InvalidRequirementsError("invalid 402 response body");
}
const version = typeof body.x402Version === "number" ? body.x402Version : 1;
return {
accepts: extractAccepts(body),
rawAccepts: extractRawAccepts(body),
x402Version: version,
extensions: extractExtensions(json) ?? extractExtensions(body)
};
}
// src/fetch-delegated.ts
function payFetchDelegated(config) {
if (!config.arispayUrl) throw new Error("arispayUrl is required");
if (!config.apiKey) throw new Error("apiKey is required");
const baseUrl = config.arispayUrl.replace(/\/$/, "");
const signPath = config.signPath ?? "/v1/x402/delegated-sign";
const timeoutMs = config.signTimeoutMs ?? 15e3;
return async (url, init) => {
const urlStr = url.toString();
const response = await fetch(urlStr, init);
if (response.status !== 402) return response;
const { accepts, rawAccepts, x402Version, extensions } = await parseRequirements(response);
if (accepts.length === 0) {
throw new InvalidRequirementsError("no payment options in 402 response");
}
const acceptIdx = accepts.findIndex((a) => a.network.startsWith("eip155:"));
const accept = acceptIdx >= 0 ? accepts[acceptIdx] : accepts[0];
const rawAccept = acceptIdx >= 0 ? rawAccepts[acceptIdx] : rawAccepts[0];
if (!accept.network.startsWith("eip155:")) {
throw new InvalidRequirementsError(
`delegated-sign requires an eip155 variant, got ${accept.network}`
);
}
const chainId = Number.parseInt(accept.network.split(":")[1] ?? "", 10);
const chainLabel = CHAIN_LABELS[chainId];
if (!chainLabel) {
throw new InvalidRequirementsError(`Unsupported chainId for delegated-sign: ${chainId}`);
}
const signRes = await fetch(`${baseUrl}${signPath}`, {
method: "POST",
headers: {
Authorization: `Bearer ${config.apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
paymentRequirements: {
chain: chainLabel,
tokenAddress: accept.asset,
payeeAddress: accept.payTo,
amount: accept.amount,
extra: accept.extra
},
resourceUrl: urlStr,
x402Version,
acceptedRequirement: rawAccept,
// Challenge extensions (bazaar discovery declaration): the signer
// echoes them into the payment payload so facilitators can catalog
// the seller. Keep in lockstep with fetch-local.ts.
...extensions ? { extensions } : {}
}),
signal: AbortSignal.timeout(timeoutMs)
});
if (!signRes.ok) {
const body = await signRes.json().catch(() => ({}));
const msg = body?.error?.message ?? `${signRes.status} ${signRes.statusText}`;
throw new PaymentRejectedError(signRes.status, `ArisPay delegated-sign rejected: ${msg}`);
}
const signed = await signRes.json();
if (signed.status === "failed" || !signed.paymentHeader) {
throw new PaymentRejectedError(502, "ArisPay delegated-sign returned no header");
}
if (config.onPayment && signed.spend) {
const { amountCents, dailySpend, monthlySpend, limits } = signed.spend;
try {
config.onPayment({
amountCents,
chain: signed.chain,
walletAddress: signed.walletAddress,
dailySpend,
monthlySpend,
limits,
remainingDaily: Math.max(0, limits.maxDaily - dailySpend),
remainingMonthly: Math.max(0, limits.maxMonthly - monthlySpend)
});
} catch {
}
}
if (process.env.PAYAGENT_DEBUG === "1") {
try {
const decoded = Buffer.from(signed.paymentHeader, "base64").toString("utf-8");
process.stderr.write(`[payagent] X-PAYMENT (decoded): ${decoded}
`);
} catch {
process.stderr.write(`[payagent] X-PAYMENT (base64): ${signed.paymentHeader}
`);
}
}
const retryHeaders = new Headers(init?.headers);
retryHeaders.set("X-PAYMENT", signed.paymentHeader);
if (x402Version === 2) {
retryHeaders.set("PAYMENT-SIGNATURE", signed.paymentHeader);
}
const paid = await fetch(urlStr, { ...init, headers: retryHeaders });
if (paid.status === 402) {
const body = await paid.text().catch(() => "");
throw new PaymentRejectedError(
402,
`Server returned 402 after payment was signed and sent. Seller response: ${body.slice(0, 1e3)}`
);
}
return paid;
};
}
var CHAIN_LABELS = {
1: "ethereum",
137: "polygon",
8453: "base",
84532: "base-sepolia"
};
// src/balance.ts
import { Contract, JsonRpcProvider } from "ethers";
var USDC_CONTRACTS = {
// Base
base: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"base-sepolia": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
// Ethereum
ethereum: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
// Polygon
polygon: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"
};
var DEFAULT_RPCS = {
base: "https://mainnet.base.org",
"base-sepolia": "https://sepolia.base.org",
ethereum: "https://eth.llamarpc.com",
polygon: "https://polygon-rpc.com"
};
var ERC20_BALANCE_ABI = ["function balanceOf(address) view returns (uint256)"];
async function getUSDCBalance(walletAddress, chain = "base", rpcUrl) {
const contractAddress = USDC_CONTRACTS[chain];
if (!contractAddress) {
throw new Error(`getUSDCBalance: unsupported chain "${chain}"`);
}
const url = rpcUrl ?? DEFAULT_RPCS[chain];
if (!url) {
throw new Error(`getUSDCBalance: no RPC for chain "${chain}" \u2014 pass rpcUrl explicitly`);
}
const provider = new JsonRpcProvider(url);
const usdc = new Contract(contractAddress, ERC20_BALANCE_ABI, provider);
const raw = await usdc.balanceOf(walletAddress);
return raw;
}
var CAIP2_RPCS = {
"eip155:8453": "https://mainnet.base.org",
"eip155:84532": "https://sepolia.base.org",
"eip155:1": "https://eth.llamarpc.com",
"eip155:137": "https://polygon-rpc.com"
};
async function getErc20Balance(asset, walletAddress, caip2Network, rpcUrl) {
const url = rpcUrl ?? CAIP2_RPCS[caip2Network];
if (!url) return void 0;
const provider = new JsonRpcProvider(url);
try {
const token = new Contract(asset, ERC20_BALANCE_ABI, provider);
return await token.balanceOf(walletAddress);
} finally {
provider.destroy();
}
}
function formatUSDC(baseUnits) {
const s = baseUnits.toString().padStart(7, "0");
const whole = s.slice(0, -6);
const frac = s.slice(-6).replace(/0+$/, "");
return frac ? `${whole}.${frac}` : whole;
}
// src/fetch-local.ts
import { ethers } from "ethers";
var BALANCE_CHECK_TIMEOUT_MS = 4e3;
async function bestEffortBalance(config, args) {
const fetcher = config.balanceFetcher ?? (({ asset, walletAddress, network, rpcUrl }) => getErc20Balance(asset, walletAddress, network, rpcUrl));
try {
return await Promise.race([
fetcher({ ...args, rpcUrl: config.rpcUrl }),
new Promise(
(_, reject) => setTimeout(() => reject(new Error("balance check timed out")), BALANCE_CHECK_TIMEOUT_MS)
)
]);
} catch {
return void 0;
}
}
function retryRejectionReason(paid) {
const header = paid.headers.get("payment-required") ?? paid.headers.get("PAYMENT-REQUIRED");
if (!header) return void 0;
try {
const decoded = JSON.parse(Buffer.from(header, "base64").toString("utf-8"));
return typeof decoded.error === "string" && decoded.error ? decoded.error : void 0;
} catch {
return void 0;
}
}
function deriveLocalWalletAddress(privateKey) {
return new ethers.Wallet(privateKey).address;
}
var TRANSFER_WITH_AUTHORIZATION_TYPES = {
TransferWithAuthorization: [
{ name: "from", type: "address" },
{ name: "to", type: "address" },
{ name: "value", type: "uint256" },
{ name: "validAfter", type: "uint256" },
{ name: "validBefore", type: "uint256" },
{ name: "nonce", type: "bytes32" }
]
};
function payFetchLocal(config) {
if (!config.privateKey) throw new Error("privateKey is required");
const wallet = new ethers.Wallet(config.privateKey);
const maxPerTx = config.maxPerTxBaseUnits !== void 0 ? BigInt(config.maxPerTxBaseUnits) : void 0;
return async (url, init) => {
const urlStr = url.toString();
const response = await fetch(urlStr, init);
if (response.status !== 402) return response;
const { accepts, rawAccepts, x402Version, extensions } = await parseRequirements(response);
if (accepts.length === 0) {
throw new InvalidRequirementsError("no payment options in 402 response");
}
const acceptIdx = accepts.findIndex((a) => a.network.startsWith("eip155:"));
if (acceptIdx < 0) {
throw new InvalidRequirementsError(
`local signer requires an eip155 variant, got ${accepts[0]?.network ?? "none"}`
);
}
const accept = accepts[acceptIdx];
const rawAccept = rawAccepts[acceptIdx];
if (maxPerTx !== void 0 && BigInt(accept.amount) > maxPerTx) {
throw new PaymentRejectedError(
402,
`402 asks ${accept.amount} base units, above maxPerTxBaseUnits ${maxPerTx}`
);
}
if (config.balanceCheck !== false) {
const held = await bestEffortBalance(config, {
asset: accept.asset,
walletAddress: wallet.address,
network: accept.network
});
if (held !== void 0 && held < BigInt(accept.amount)) {
throw new PaymentRejectedError(
402,
`Wallet ${wallet.address} holds ${formatUSDC(held)} of the required asset but this request costs ${formatUSDC(BigInt(accept.amount))} (asset ${accept.asset} on ${accept.network}). Fund ${wallet.address} on that network and retry.`
);
}
}
const domainInfo = accept.extra;
if (!domainInfo?.name || !domainInfo?.version) {
throw new InvalidRequirementsError(
"402 accept is missing extra.name/extra.version (EIP-712 domain) \u2014 cannot sign locally"
);
}
const chainId = Number.parseInt(accept.network.split(":")[1] ?? "", 10);
if (!Number.isFinite(chainId)) {
throw new InvalidRequirementsError(`cannot parse chainId from network ${accept.network}`);
}
const now = Math.floor(Date.now() / 1e3);
const authorization = {
from: wallet.address,
to: accept.payTo,
value: accept.amount,
validAfter: (now - 60).toString(),
validBefore: (now + 480).toString(),
// 8-minute window, mirrors x402-core signing
nonce: ethers.hexlify(ethers.randomBytes(32))
};
const signature = await wallet.signTypedData(
{
name: domainInfo.name,
version: domainInfo.version,
chainId,
verifyingContract: ethers.getAddress(accept.asset)
},
TRANSFER_WITH_AUTHORIZATION_TYPES,
{
from: ethers.getAddress(authorization.from),
to: ethers.getAddress(authorization.to),
value: BigInt(authorization.value),
validAfter: BigInt(authorization.validAfter),
validBefore: BigInt(authorization.validBefore),
nonce: authorization.nonce
}
);
const effectiveVersion = x402Version ?? 2;
const paymentHeader = Buffer.from(
JSON.stringify({
x402Version: effectiveVersion,
payload: { signature, authorization },
accepted: rawAccept,
// v2 PaymentPayload.resource is a ResourceInfo object, not a bare
// string (spec + what bazaar discovery extraction reads).
resource: { url: accept.resource },
// Echo the challenge's extensions (bazaar discovery declaration):
// facilitators catalog discoverable sellers from this echo. Keep in
// lockstep with fetch-delegated.ts.
...extensions ? { extensions } : {}
})
).toString("base64");
if (config.onPayment) {
try {
config.onPayment({
amount: accept.amount,
network: accept.network,
asset: accept.asset,
payTo: accept.payTo,
walletAddress: wallet.address
});
} catch {
}
}
if (process.env.PAYAGENT_DEBUG === "1") {
try {
const decoded = Buffer.from(paymentHeader, "base64").toString("utf-8");
process.stderr.write(`[payagent] X-PAYMENT (decoded): ${decoded}
`);
} catch {
process.stderr.write(`[payagent] X-PAYMENT (base64): ${paymentHeader}
`);
}
}
const retryHeaders = new Headers(init?.headers);
retryHeaders.set("X-PAYMENT", paymentHeader);
if (effectiveVersion === 2) {
retryHeaders.set("PAYMENT-SIGNATURE", paymentHeader);
}
const paid = await fetch(urlStr, { ...init, headers: retryHeaders });
if (paid.status === 402) {
const reason = retryRejectionReason(paid);
const body = await paid.text().catch(() => "");
const detail = reason ?? (body.trim() && body.trim() !== "{}" ? body.slice(0, 1e3) : "");
throw new PaymentRejectedError(
402,
`Server returned 402 after payment was signed and sent` + (detail ? ` \u2014 reason: ${detail}` : "") + `. Paying wallet: ${wallet.address} on ${accept.network}. Common causes: insufficient ${accept.asset} balance, or the authorization window expired. Check the balance, then retry.`
);
}
return paid;
};
}
export {
PayAgentError,
PaymentRejectedError,
InvalidRequirementsError,
payFetchDelegated,
USDC_CONTRACTS,
getUSDCBalance,
getErc20Balance,
formatUSDC,
deriveLocalWalletAddress,
payFetchLocal
};
//# sourceMappingURL=chunk-ZE73ANBP.js.map
{"version":3,"sources":["../src/errors.ts","../src/payment.ts","../src/fetch-delegated.ts","../src/balance.ts","../src/fetch-local.ts"],"sourcesContent":["/**\n * payagent — Error classes.\n */\n\n/** Base class for all payagent errors. */\nexport class PayAgentError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"PayAgentError\";\n }\n}\n\n/** The payment was signed and sent but the server still rejected it, or ArisPay refused to sign. */\nexport class PaymentRejectedError extends PayAgentError {\n public readonly status: number;\n\n constructor(status: number, message?: string) {\n super(message ?? `Server rejected payment (HTTP ${status})`);\n this.name = \"PaymentRejectedError\";\n this.status = status;\n }\n}\n\n/** Could not parse 402 response body as valid payment requirements. */\nexport class InvalidRequirementsError extends PayAgentError {\n constructor(detail?: string) {\n super(`Could not parse 402 payment requirements${detail ? `: ${detail}` : \"\"}`);\n this.name = \"InvalidRequirementsError\";\n }\n}\n","import { InvalidRequirementsError } from \"./errors.js\";\n/**\n * payagent — x402 402-response parsing utilities.\n *\n * These are the shared primitives used by `payFetchDelegated` to normalize\n * a seller's 402 body into a usable `accepts` list, independent of whether\n * it's emitted in x402-v2 standard shape or the legacy flat/AgFac shape.\n */\nimport type {\n AgfacFlatRequirements,\n PaymentRequirementsBody,\n X402Accept,\n X402Requirements,\n} from \"./types.js\";\n\n// Coinbase's reference x402 middleware emits short network names\n// (\"base-sepolia\"), while the x402 v2 spec and our internal code use CAIP-2\n// (\"eip155:84532\"). Accept either on the way in.\nconst NETWORK_SHORT_TO_CAIP2: Record<string, string> = {\n ethereum: \"eip155:1\",\n polygon: \"eip155:137\",\n base: \"eip155:8453\",\n \"base-sepolia\": \"eip155:84532\",\n};\n\nexport function normalizeNetwork(network: string): string {\n if (network.includes(\":\")) return network; // already CAIP-2\n return NETWORK_SHORT_TO_CAIP2[network] ?? network;\n}\n\nconst NETWORK_CAIP2_TO_SHORT: Record<string, string> = Object.fromEntries(\n Object.entries(NETWORK_SHORT_TO_CAIP2).map(([s, c]) => [c, s]),\n);\n\n/** Convert CAIP-2 back to the short network name x402 sellers emit on the wire. */\nexport function denormalizeNetwork(network: string): string {\n if (!network.includes(\":\")) return network; // already short\n return NETWORK_CAIP2_TO_SHORT[network] ?? network;\n}\n\nfunction isStandardFormat(body: PaymentRequirementsBody): body is X402Requirements {\n return \"accepts\" in body && Array.isArray(body.accepts);\n}\n\nfunction isFlatFormat(body: PaymentRequirementsBody): body is AgfacFlatRequirements {\n return \"scheme\" in body && \"payTo\" in body && !(\"accepts\" in body);\n}\n\n/** Normalize both x402 v2 standard and AgFac flat format into accepts array. */\nfunction extractAccepts(body: PaymentRequirementsBody): X402Accept[] {\n if (isStandardFormat(body)) {\n return body.accepts.map((a) => {\n // x402 v2 canonical name is `amount`; some legacy sellers still emit\n // the v1 name `maxAmountRequired`. Accept either, normalize to `amount`.\n const rawAmount =\n (a as { amount?: string }).amount ??\n (a as { maxAmountRequired?: string }).maxAmountRequired;\n return {\n ...a,\n network: normalizeNetwork(a.network),\n amount: rawAmount as string,\n };\n });\n }\n if (isFlatFormat(body)) {\n return [\n {\n scheme: body.scheme,\n network: normalizeNetwork(body.network),\n amount: (body.amount ?? body.maxAmountRequired) as string,\n resource: body.resource,\n asset: body.asset,\n payTo: body.payTo,\n extra: { name: \"USDC\", version: \"2\" },\n },\n ];\n }\n throw new InvalidRequirementsError(\"unrecognized format\");\n}\n\nexport interface ParsedRequirements {\n accepts: X402Accept[];\n /**\n * Raw accept objects, byte-for-byte as the seller advertised them. x402 v2\n * servers match paymentPayload.accepted against their paymentRequirements\n * via deepEqual — our signed payload must echo the original accept without\n * added/normalised fields, else the merchant rejects with a silent 402.\n */\n rawAccepts: unknown[];\n /** x402 protocol version advertised by the seller. Defaults to 1. */\n x402Version: number;\n /**\n * Top-level `extensions` object from the challenge (e.g. the bazaar\n * discovery declaration), byte-for-byte. Payers echo it into the payment\n * payload — facilitators catalog discoverable resources from that echo,\n * so dropping it silently keeps every seller out of organic discovery.\n */\n extensions?: Record<string, unknown>;\n}\n\nfunction extractExtensions(source: unknown): Record<string, unknown> | undefined {\n const ext = (source as { extensions?: unknown } | null | undefined)?.extensions;\n return ext && typeof ext === \"object\" ? (ext as Record<string, unknown>) : undefined;\n}\n\nfunction extractRawAccepts(body: PaymentRequirementsBody): unknown[] {\n if (isStandardFormat(body)) return body.accepts as unknown[];\n if (isFlatFormat(body)) return [body as unknown];\n return [];\n}\n\n/**\n * Header names sellers use to advertise payment requirements, in\n * preference order. Audited against 128 live Bazaar-listed endpoints\n * on 2026-04-30 (see /docs/x402-wire-format-audit.md in the monorepo):\n *\n * - `payment-required` : 95.3% of canonical x402 emits\n * - `x-payment-required` : 10% (variant)\n * - `x-payment-requirements` : paygate ≤ 5.0 only (one in-house emit)\n *\n * Header values are usually base64-encoded JSON (canonical) but some\n * sellers (notably older paygate) emit raw JSON. Try base64 first,\n * fall back to raw JSON parse.\n */\nconst HEADER_LOOKUP_ORDER = [\n \"payment-required\",\n \"x-payment-required\",\n \"x-payment-requirements\",\n] as const;\n\nfunction tryParseHeaderValue(value: string): Record<string, unknown> | null {\n // base64 (canonical wire format).\n try {\n const decoded = Buffer.from(value, \"base64\").toString(\"utf8\");\n const parsed = JSON.parse(decoded);\n if (parsed && typeof parsed === \"object\") return parsed as Record<string, unknown>;\n } catch {\n // not valid base64 JSON, try raw.\n }\n // Raw JSON (paygate's pre-5.1 emit; some custom sellers).\n try {\n const parsed = JSON.parse(value);\n if (parsed && typeof parsed === \"object\") return parsed as Record<string, unknown>;\n } catch {\n // not parseable.\n }\n return null;\n}\n\n/** Parse the 402 response body and extract payment requirements. */\nexport async function parseRequirements(response: Response): Promise<ParsedRequirements> {\n // Header path: try the canonical name first, then variants. Any\n // header that yields a recognizable accepts array wins; otherwise we\n // fall through to body parsing.\n for (const headerName of HEADER_LOOKUP_ORDER) {\n const value = response.headers.get(headerName);\n if (!value) continue;\n const parsed = tryParseHeaderValue(value);\n if (!parsed) continue;\n try {\n const body = (parsed.requirements ?? parsed) as PaymentRequirementsBody;\n const accepts = extractAccepts(body);\n if (accepts.length > 0) {\n return {\n accepts,\n rawAccepts: extractRawAccepts(body),\n x402Version: typeof parsed.x402Version === \"number\" ? parsed.x402Version : 2,\n extensions: extractExtensions(parsed) ?? extractExtensions(body),\n };\n }\n } catch {\n // unrecognized format from this header — try the next.\n }\n }\n\n let json: Record<string, unknown>;\n try {\n json = await response.json();\n } catch {\n throw new InvalidRequirementsError(\"response body is not valid JSON\");\n }\n\n // Some servers nest requirements under a `requirements` key\n const body = (json.requirements ?? json) as PaymentRequirementsBody;\n\n if (!body || typeof body !== \"object\") {\n throw new InvalidRequirementsError(\"invalid 402 response body\");\n }\n // x402Version 1 is what the Coinbase reference middleware emits; v2 is the\n // newer draft. Echo whichever version the seller advertised.\n const version =\n typeof (body as { x402Version?: unknown }).x402Version === \"number\"\n ? (body as { x402Version: number }).x402Version\n : 1;\n\n return {\n accepts: extractAccepts(body),\n rawAccepts: extractRawAccepts(body),\n x402Version: version,\n extensions: extractExtensions(json) ?? extractExtensions(body),\n };\n}\n","import { InvalidRequirementsError, PaymentRejectedError } from \"./errors.js\";\n/**\n * payagent — Delegated fetch wrapper.\n *\n * Server-side signing variant: instead of holding the private key locally and\n * signing with ethers, call an ArisPay delegated-sign endpoint that signs via\n * the CDP-managed wallet AND enforces per-tx / daily / monthly limits +\n * allowedDomains. This is the path to use with agents created via\n * `DelegationClient.createX402Agent()`.\n *\n * Usage:\n * const fetch402 = payFetchDelegated({\n * arispayUrl: 'http://localhost:3001',\n * apiKey: 'ap_test_...', // the agent's own key\n * });\n * const res = await fetch402('https://api.example.com/premium');\n */\nimport { parseRequirements } from \"./payment.js\";\n\nexport interface PayFetchDelegatedConfig {\n /** Base URL for the ArisPay API (no trailing slash). */\n arispayUrl: string;\n /** The x402 agent's own API key (returned by DelegationClient.createX402Agent). */\n apiKey: string;\n /** Override for the ArisPay delegated-sign path. Default: /v1/x402/delegated-sign */\n signPath?: string;\n /** Request timeout for the sign call (ms). Default: 15000. */\n signTimeoutMs?: number;\n /**\n * Fires once per paid request, right after ArisPay signs (spend counters\n * are already committed server-side at that point), with the amount and\n * the agent's remaining budget. Advisory: a throwing callback is swallowed\n * and never breaks the payment flow.\n */\n onPayment?: (info: DelegatedPaymentInfo) => void;\n}\n\nexport type PayFetchFn = (url: string | URL, init?: RequestInit) => Promise<Response>;\n\n/**\n * Per-payment budget snapshot surfaced via `onPayment`. All amounts are\n * integer cents. Spend counters and remaining headroom are AFTER this\n * payment (the server returns post-increment counters).\n */\nexport interface DelegatedPaymentInfo {\n /** Cents charged for this payment. */\n amountCents: number;\n chain: string;\n walletAddress: string;\n /** Cents spent today (UTC calendar day), including this payment. */\n dailySpend: number;\n /** Cents spent this month (UTC calendar month), including this payment. */\n monthlySpend: number;\n limits: { maxPerTx: number; maxDaily: number; maxMonthly: number };\n /** Cents of daily budget left after this payment. */\n remainingDaily: number;\n /** Cents of monthly budget left after this payment. */\n remainingMonthly: number;\n}\n\ninterface DelegatedSignResponse {\n paymentHeader: string;\n chain: string;\n status: \"settled\" | \"pending\" | \"failed\";\n walletAddress: string;\n spend: {\n amountCents: number;\n dailySpend: number;\n monthlySpend: number;\n limits: { maxPerTx: number; maxDaily: number; maxMonthly: number };\n };\n}\n\n/**\n * Create a fetch wrapper that delegates EIP-3009 signing to ArisPay.\n * No private key lives on the caller's machine; ArisPay enforces the\n * delegation limits before signing and increments spend counters on success.\n */\nexport function payFetchDelegated(config: PayFetchDelegatedConfig): PayFetchFn {\n if (!config.arispayUrl) throw new Error(\"arispayUrl is required\");\n if (!config.apiKey) throw new Error(\"apiKey is required\");\n const baseUrl = config.arispayUrl.replace(/\\/$/, \"\");\n const signPath = config.signPath ?? \"/v1/x402/delegated-sign\";\n const timeoutMs = config.signTimeoutMs ?? 15_000;\n\n return async (url, init) => {\n const urlStr = url.toString();\n const response = await fetch(urlStr, init);\n if (response.status !== 402) return response;\n\n const { accepts, rawAccepts, x402Version, extensions } = await parseRequirements(response);\n if (accepts.length === 0) {\n throw new InvalidRequirementsError(\"no payment options in 402 response\");\n }\n // The delegated-sign endpoint only supports eip155 (EVM) variants.\n const acceptIdx = accepts.findIndex((a) => a.network.startsWith(\"eip155:\"));\n const accept = acceptIdx >= 0 ? accepts[acceptIdx] : accepts[0];\n // rawAccept is the byte-for-byte challenge accept for this option (pre-normalisation).\n // Required for v2 deepEqual(paymentRequirements, paymentPayload.accepted) matching.\n const rawAccept = acceptIdx >= 0 ? rawAccepts[acceptIdx] : rawAccepts[0];\n if (!accept.network.startsWith(\"eip155:\")) {\n throw new InvalidRequirementsError(\n `delegated-sign requires an eip155 variant, got ${accept.network}`,\n );\n }\n\n // Derive ArisPay's `chain` label from CAIP-2.\n const chainId = Number.parseInt(accept.network.split(\":\")[1] ?? \"\", 10);\n const chainLabel = CHAIN_LABELS[chainId];\n if (!chainLabel) {\n throw new InvalidRequirementsError(`Unsupported chainId for delegated-sign: ${chainId}`);\n }\n\n // Ask ArisPay to sign.\n //\n // We forward the RAW challenge accept object as `acceptedRequirement` so\n // the server can use it verbatim as `paymentPayload.accepted`. x402 v2's\n // findMatchingRequirements uses deepEqual(paymentRequirements, payload.accepted) —\n // any added/missing field breaks the match silently (402 with {}). Do NOT\n // modify or normalise fields between here and the signer.\n const signRes = await fetch(`${baseUrl}${signPath}`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${config.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n paymentRequirements: {\n chain: chainLabel,\n tokenAddress: accept.asset,\n payeeAddress: accept.payTo,\n amount: accept.amount,\n extra: accept.extra,\n },\n resourceUrl: urlStr,\n x402Version,\n acceptedRequirement: rawAccept,\n // Challenge extensions (bazaar discovery declaration): the signer\n // echoes them into the payment payload so facilitators can catalog\n // the seller. Keep in lockstep with fetch-local.ts.\n ...(extensions ? { extensions } : {}),\n }),\n signal: AbortSignal.timeout(timeoutMs),\n });\n\n if (!signRes.ok) {\n const body = (await signRes.json().catch(() => ({}))) as { error?: { message?: string } };\n const msg = body?.error?.message ?? `${signRes.status} ${signRes.statusText}`;\n throw new PaymentRejectedError(signRes.status, `ArisPay delegated-sign rejected: ${msg}`);\n }\n\n const signed = (await signRes.json()) as DelegatedSignResponse;\n if (signed.status === \"failed\" || !signed.paymentHeader) {\n throw new PaymentRejectedError(502, \"ArisPay delegated-sign returned no header\");\n }\n\n if (config.onPayment && signed.spend) {\n const { amountCents, dailySpend, monthlySpend, limits } = signed.spend;\n try {\n config.onPayment({\n amountCents,\n chain: signed.chain,\n walletAddress: signed.walletAddress,\n dailySpend,\n monthlySpend,\n limits,\n remainingDaily: Math.max(0, limits.maxDaily - dailySpend),\n remainingMonthly: Math.max(0, limits.maxMonthly - monthlySpend),\n });\n } catch {\n // Advisory metadata must never break the payment flow.\n }\n }\n\n // Debug: dump the decoded X-PAYMENT so we can diagnose verifier rejects.\n // Enable with PAYAGENT_DEBUG=1. Prints to stderr so it doesn't corrupt\n // stdout piping. Contains only public challenge data + a signature.\n if (process.env.PAYAGENT_DEBUG === \"1\") {\n try {\n const decoded = Buffer.from(signed.paymentHeader, \"base64\").toString(\"utf-8\");\n process.stderr.write(`[payagent] X-PAYMENT (decoded): ${decoded}\\n`);\n } catch {\n process.stderr.write(`[payagent] X-PAYMENT (base64): ${signed.paymentHeader}\\n`);\n }\n }\n\n // Retry with the version-appropriate payment header. The x402 v2 wire\n // protocol renamed it: v1 middlewares read `X-PAYMENT`, upstream v2\n // middlewares (@x402/* ≥2.x) read ONLY `PAYMENT-SIGNATURE` — sending a\n // v2 payload as X-PAYMENT is silently treated as \"no payment at all\"\n // (an unpaid 402 with empty body, no verify ever reaching the\n // facilitator). Both headers are set so mislabeled sellers on either\n // side of the rename still find the payload; servers read exactly one.\n const retryHeaders = new Headers(init?.headers);\n retryHeaders.set(\"X-PAYMENT\", signed.paymentHeader);\n if (x402Version === 2) {\n retryHeaders.set(\"PAYMENT-SIGNATURE\", signed.paymentHeader);\n }\n const paid = await fetch(urlStr, { ...init, headers: retryHeaders });\n if (paid.status === 402) {\n // Read the seller's response body so callers can see the verifier's\n // actual rejection reason instead of a generic \"server returned 402\".\n const body = await paid.text().catch(() => \"\");\n throw new PaymentRejectedError(\n 402,\n `Server returned 402 after payment was signed and sent. Seller response: ${body.slice(0, 1000)}`,\n );\n }\n return paid;\n };\n}\n\n// CAIP-2 chainId → ArisPay provider chain label.\nconst CHAIN_LABELS: Record<number, string> = {\n 1: \"ethereum\",\n 137: \"polygon\",\n 8453: \"base\",\n 84532: \"base-sepolia\",\n};\n","/**\n * payagent — On-chain USDC balance helper.\n *\n * Reads the USDC ERC-20 `balanceOf(address)` directly from an RPC.\n * Useful when you want an authoritative on-chain check independent\n * of any backend (e.g. ArisPay's balance endpoint is unreachable).\n */\nimport { Contract, JsonRpcProvider } from \"ethers\";\n\n/**\n * USDC contract addresses per chain.\n *\n * Canonical source is `@arispay/x402-core` (USDC_BASE_MAINNET, etc.).\n * This duplicate is intentional: `payagent` is a published, standalone npm\n * package and must NOT take a workspace dep on the private `@arispay/x402-core`\n * package — npm consumers wouldn't resolve it. Keep these four values in sync\n * with packages/x402-core/src/constants.ts by hand. Do not try to \"DRY\" by\n * importing — see P2-4 blocker discussion.\n */\nexport const USDC_CONTRACTS: Record<string, string> = {\n // Base\n base: \"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\",\n \"base-sepolia\": \"0x036CbD53842c5426634e7929541eC2318f3dCF7e\",\n // Ethereum\n ethereum: \"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\",\n // Polygon\n polygon: \"0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359\",\n};\n\n/** Default public RPCs. Callers should pass their own for production. */\nconst DEFAULT_RPCS: Record<string, string> = {\n base: \"https://mainnet.base.org\",\n \"base-sepolia\": \"https://sepolia.base.org\",\n ethereum: \"https://eth.llamarpc.com\",\n polygon: \"https://polygon-rpc.com\",\n};\n\nconst ERC20_BALANCE_ABI = [\"function balanceOf(address) view returns (uint256)\"];\n\n/**\n * Fetch the USDC balance for a wallet on the given chain.\n *\n * @param walletAddress EVM address (0x…).\n * @param chain One of 'base' | 'base-sepolia' | 'ethereum' | 'polygon'. Default: 'base'.\n * @param rpcUrl Optional RPC URL override. Falls back to a public endpoint.\n * @returns USDC balance in 6-decimal base units as a bigint.\n */\nexport async function getUSDCBalance(\n walletAddress: string,\n chain: keyof typeof USDC_CONTRACTS = \"base\",\n rpcUrl?: string,\n): Promise<bigint> {\n const contractAddress = USDC_CONTRACTS[chain];\n if (!contractAddress) {\n throw new Error(`getUSDCBalance: unsupported chain \"${chain}\"`);\n }\n const url = rpcUrl ?? DEFAULT_RPCS[chain];\n if (!url) {\n throw new Error(`getUSDCBalance: no RPC for chain \"${chain}\" — pass rpcUrl explicitly`);\n }\n\n const provider = new JsonRpcProvider(url);\n const usdc = new Contract(contractAddress, ERC20_BALANCE_ABI, provider);\n const raw = (await usdc.balanceOf(walletAddress)) as bigint;\n return raw;\n}\n\n/** Default public RPCs keyed by CAIP-2 chain id (what an x402 challenge carries). */\nconst CAIP2_RPCS: Record<string, string> = {\n \"eip155:8453\": \"https://mainnet.base.org\",\n \"eip155:84532\": \"https://sepolia.base.org\",\n \"eip155:1\": \"https://eth.llamarpc.com\",\n \"eip155:137\": \"https://polygon-rpc.com\",\n};\n\n/**\n * Read an ERC-20 `balanceOf` for the wallet on the challenge's network.\n * Returns `undefined` when no RPC is known for the chain (caller should\n * treat that as \"cannot check\", not \"zero\"). Throws on RPC failure —\n * callers doing a best-effort pre-check catch and proceed.\n *\n * @param asset ERC-20 contract address from the 402 accept.\n * @param walletAddress The paying EOA.\n * @param caip2Network CAIP-2 id from the accept (e.g. \"eip155:8453\").\n * @param rpcUrl Optional RPC override; falls back to a public endpoint.\n */\nexport async function getErc20Balance(\n asset: string,\n walletAddress: string,\n caip2Network: string,\n rpcUrl?: string,\n): Promise<bigint | undefined> {\n const url = rpcUrl ?? CAIP2_RPCS[caip2Network];\n if (!url) return undefined;\n const provider = new JsonRpcProvider(url);\n try {\n const token = new Contract(asset, ERC20_BALANCE_ABI, provider);\n return (await token.balanceOf(walletAddress)) as bigint;\n } finally {\n provider.destroy();\n }\n}\n\n/** Format a USDC base-unit bigint (6 decimals) as a human-readable string, e.g. \"1.234567\". */\nexport function formatUSDC(baseUnits: bigint): string {\n const s = baseUnits.toString().padStart(7, \"0\");\n const whole = s.slice(0, -6);\n const frac = s.slice(-6).replace(/0+$/, \"\");\n return frac ? `${whole}.${frac}` : whole;\n}\n","import { ethers } from \"ethers\";\nimport { formatUSDC, getErc20Balance } from \"./balance.js\";\nimport { InvalidRequirementsError, PaymentRejectedError } from \"./errors.js\";\n/**\n * payagent — Local-signer fetch wrapper (permissionless mode).\n *\n * Signs EIP-3009 `transferWithAuthorization` locally with ethers and settles\n * through whatever facilitator the *seller* uses. No ArisPay account, no\n * API key, no provisioning — `npm install payagent` plus a funded key is the\n * whole setup. This is the mode that makes payagent a drop-in x402 client\n * for the open ecosystem.\n *\n * Trade-offs vs `payFetchDelegated` (the custody mode), stated honestly:\n * spend limits, allowedDomains, suspension, and the payment feed are\n * server-side features of the delegated model and do NOT exist here — the\n * only guardrail is the optional per-transaction cap below, which is\n * client-side and therefore self-enforced. The private key lives in your\n * process; treat it accordingly (a dedicated low-balance wallet is the\n * intended pattern).\n *\n * Usage:\n * const fetch402 = payFetchLocal({ privateKey: process.env.PRIVATE_KEY! });\n * const res = await fetch402('https://api.example.com/premium');\n */\nimport { parseRequirements } from \"./payment.js\";\n\nexport interface PayFetchLocalConfig {\n /** Hex-encoded private key (`0x…`) of the EOA that pays. */\n privateKey: string;\n /**\n * Optional per-payment cap in the accepted asset's base units (USDC has 6\n * decimals, so \"1000000\" = $1.00). A 402 asking for more throws\n * `PaymentRejectedError` instead of signing. Client-side guardrail only.\n */\n maxPerTxBaseUnits?: string | bigint;\n /**\n * Fires once per paid request, right after signing. Advisory: a throwing\n * callback is swallowed and never breaks the payment flow.\n */\n onPayment?: (info: LocalPaymentInfo) => void;\n /**\n * RPC endpoint for the pre-payment balance check. Defaults to a public\n * endpoint for the challenge's chain. The check is best-effort: an\n * unreachable RPC never blocks the payment.\n */\n rpcUrl?: string;\n /**\n * Set false to skip the pre-payment balance check and sign regardless.\n * Default true — an unfunded wallet fails with a fundable address instead\n * of an opaque seller 402.\n */\n balanceCheck?: boolean;\n /**\n * Test seam: replaces the on-chain balance read. Returns base units, or\n * `undefined` when the balance cannot be determined (check is skipped).\n */\n balanceFetcher?: (args: {\n asset: string;\n walletAddress: string;\n network: string;\n rpcUrl?: string;\n }) => Promise<bigint | undefined>;\n}\n\nconst BALANCE_CHECK_TIMEOUT_MS = 4000;\n\nasync function bestEffortBalance(\n config: PayFetchLocalConfig,\n args: { asset: string; walletAddress: string; network: string },\n): Promise<bigint | undefined> {\n const fetcher =\n config.balanceFetcher ??\n (({ asset, walletAddress, network, rpcUrl }: Parameters<\n NonNullable<PayFetchLocalConfig[\"balanceFetcher\"]>\n >[0]) => getErc20Balance(asset, walletAddress, network, rpcUrl));\n try {\n return await Promise.race([\n fetcher({ ...args, rpcUrl: config.rpcUrl }),\n new Promise<undefined>((_, reject) =>\n setTimeout(() => reject(new Error(\"balance check timed out\")), BALANCE_CHECK_TIMEOUT_MS),\n ),\n ]);\n } catch {\n // Best-effort only: an unreachable RPC must never block a payment.\n return undefined;\n }\n}\n\n/** Decode the retry response's payment-required header for its error reason. */\nfunction retryRejectionReason(paid: Response): string | undefined {\n const header = paid.headers.get(\"payment-required\") ?? paid.headers.get(\"PAYMENT-REQUIRED\");\n if (!header) return undefined;\n try {\n const decoded = JSON.parse(Buffer.from(header, \"base64\").toString(\"utf-8\")) as {\n error?: unknown;\n };\n return typeof decoded.error === \"string\" && decoded.error ? decoded.error : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport interface LocalPaymentInfo {\n /** Amount paid, in the asset's base units. */\n amount: string;\n /** CAIP-2 network the payment settled on. */\n network: string;\n asset: string;\n payTo: string;\n /** The EOA that paid (derived from the configured key). */\n walletAddress: string;\n}\n\nexport type { PayFetchFn } from \"./fetch-delegated.js\";\nimport type { PayFetchFn } from \"./fetch-delegated.js\";\n\n/**\n * Derive the deposit address for a local (self-custody) private key. This is\n * the address a human funds with USDC in Lane P mode — surfaced by\n * `payagent wallet address` and the MCP `check_wallet` tool. Throws on an\n * invalid key.\n */\nexport function deriveLocalWalletAddress(privateKey: string): string {\n return new ethers.Wallet(privateKey).address;\n}\n\n// EIP-712 `TransferWithAuthorization` — the EIP-3009 typehash fields.\nconst TRANSFER_WITH_AUTHORIZATION_TYPES = {\n TransferWithAuthorization: [\n { name: \"from\", type: \"address\" },\n { name: \"to\", type: \"address\" },\n { name: \"value\", type: \"uint256\" },\n { name: \"validAfter\", type: \"uint256\" },\n { name: \"validBefore\", type: \"uint256\" },\n { name: \"nonce\", type: \"bytes32\" },\n ],\n};\n\n/**\n * Create a fetch wrapper that pays x402 402s by signing locally. Mirrors\n * `payFetchDelegated`'s flow (parse → sign → retry with X-PAYMENT) with the\n * server round-trip replaced by local EIP-712 signing.\n */\nexport function payFetchLocal(config: PayFetchLocalConfig): PayFetchFn {\n if (!config.privateKey) throw new Error(\"privateKey is required\");\n const wallet = new ethers.Wallet(config.privateKey);\n const maxPerTx =\n config.maxPerTxBaseUnits !== undefined ? BigInt(config.maxPerTxBaseUnits) : undefined;\n\n return async (url, init) => {\n const urlStr = url.toString();\n const response = await fetch(urlStr, init);\n if (response.status !== 402) return response;\n\n const { accepts, rawAccepts, x402Version, extensions } = await parseRequirements(response);\n if (accepts.length === 0) {\n throw new InvalidRequirementsError(\"no payment options in 402 response\");\n }\n // Local signing is EVM-only today (EIP-3009); pick the first eip155 option.\n const acceptIdx = accepts.findIndex((a) => a.network.startsWith(\"eip155:\"));\n if (acceptIdx < 0) {\n throw new InvalidRequirementsError(\n `local signer requires an eip155 variant, got ${accepts[0]?.network ?? \"none\"}`,\n );\n }\n const accept = accepts[acceptIdx]!;\n // The byte-for-byte challenge accept, used verbatim as\n // `paymentPayload.accepted` — x402 v2's findMatchingRequirements is a\n // deepEqual, so any added/missing field silently breaks the match.\n const rawAccept = rawAccepts[acceptIdx];\n\n if (maxPerTx !== undefined && BigInt(accept.amount) > maxPerTx) {\n throw new PaymentRejectedError(\n 402,\n `402 asks ${accept.amount} base units, above maxPerTxBaseUnits ${maxPerTx}`,\n );\n }\n\n // Pre-payment balance check. Without it, an unfunded wallet signs a\n // valid authorization, the facilitator rejects it on-chain funds, and the\n // agent sees an opaque second 402 with no way to know what to fix.\n if (config.balanceCheck !== false) {\n const held = await bestEffortBalance(config, {\n asset: accept.asset,\n walletAddress: wallet.address,\n network: accept.network,\n });\n if (held !== undefined && held < BigInt(accept.amount)) {\n throw new PaymentRejectedError(\n 402,\n `Wallet ${wallet.address} holds ${formatUSDC(held)} of the required asset ` +\n `but this request costs ${formatUSDC(BigInt(accept.amount))} ` +\n `(asset ${accept.asset} on ${accept.network}). ` +\n `Fund ${wallet.address} on that network and retry.`,\n );\n }\n }\n\n // The EIP-712 domain must come from the seller's requirements — every\n // token deployment has its own (name, version, chainId, contract), and a\n // wrong one produces a valid-looking signature that recovers wrong.\n const domainInfo = accept.extra;\n if (!domainInfo?.name || !domainInfo?.version) {\n throw new InvalidRequirementsError(\n \"402 accept is missing extra.name/extra.version (EIP-712 domain) — cannot sign locally\",\n );\n }\n const chainId = Number.parseInt(accept.network.split(\":\")[1] ?? \"\", 10);\n if (!Number.isFinite(chainId)) {\n throw new InvalidRequirementsError(`cannot parse chainId from network ${accept.network}`);\n }\n\n const now = Math.floor(Date.now() / 1000);\n const authorization = {\n from: wallet.address,\n to: accept.payTo,\n value: accept.amount,\n validAfter: (now - 60).toString(),\n validBefore: (now + 480).toString(), // 8-minute window, mirrors x402-core signing\n nonce: ethers.hexlify(ethers.randomBytes(32)),\n };\n\n const signature = await wallet.signTypedData(\n {\n name: domainInfo.name,\n version: domainInfo.version,\n chainId,\n verifyingContract: ethers.getAddress(accept.asset),\n },\n TRANSFER_WITH_AUTHORIZATION_TYPES,\n {\n from: ethers.getAddress(authorization.from),\n to: ethers.getAddress(authorization.to),\n value: BigInt(authorization.value),\n validAfter: BigInt(authorization.validAfter),\n validBefore: BigInt(authorization.validBefore),\n nonce: authorization.nonce,\n },\n );\n\n const effectiveVersion = x402Version ?? 2;\n const paymentHeader = Buffer.from(\n JSON.stringify({\n x402Version: effectiveVersion,\n payload: { signature, authorization },\n accepted: rawAccept,\n // v2 PaymentPayload.resource is a ResourceInfo object, not a bare\n // string (spec + what bazaar discovery extraction reads).\n resource: { url: accept.resource },\n // Echo the challenge's extensions (bazaar discovery declaration):\n // facilitators catalog discoverable sellers from this echo. Keep in\n // lockstep with fetch-delegated.ts.\n ...(extensions ? { extensions } : {}),\n }),\n ).toString(\"base64\");\n\n if (config.onPayment) {\n try {\n config.onPayment({\n amount: accept.amount,\n network: accept.network,\n asset: accept.asset,\n payTo: accept.payTo,\n walletAddress: wallet.address,\n });\n } catch {\n // Advisory metadata must never break the payment flow.\n }\n }\n\n if (process.env.PAYAGENT_DEBUG === \"1\") {\n try {\n const decoded = Buffer.from(paymentHeader, \"base64\").toString(\"utf-8\");\n process.stderr.write(`[payagent] X-PAYMENT (decoded): ${decoded}\\n`);\n } catch {\n process.stderr.write(`[payagent] X-PAYMENT (base64): ${paymentHeader}\\n`);\n }\n }\n\n // Retry with the version-appropriate payment header. v1 middlewares read\n // `X-PAYMENT`; upstream v2 middlewares (@x402/* ≥2.x) read ONLY\n // `PAYMENT-SIGNATURE` — a v2 payload sent as X-PAYMENT alone is silently\n // treated as \"no payment at all\". Set both so sellers on either side of\n // the rename find the payload; servers read exactly one. Keep this in\n // lockstep with fetch-delegated.ts.\n const retryHeaders = new Headers(init?.headers);\n retryHeaders.set(\"X-PAYMENT\", paymentHeader);\n if (effectiveVersion === 2) {\n retryHeaders.set(\"PAYMENT-SIGNATURE\", paymentHeader);\n }\n const paid = await fetch(urlStr, { ...init, headers: retryHeaders });\n if (paid.status === 402) {\n // Surface the verifier's actual rejection reason, not a generic 402.\n // v2 sellers put it in the retry's payment-required header; the body\n // is often empty.\n const reason = retryRejectionReason(paid);\n const body = await paid.text().catch(() => \"\");\n const detail = reason ?? (body.trim() && body.trim() !== \"{}\" ? body.slice(0, 1000) : \"\");\n throw new PaymentRejectedError(\n 402,\n `Server returned 402 after payment was signed and sent` +\n (detail ? ` — reason: ${detail}` : \"\") +\n `. Paying wallet: ${wallet.address} on ${accept.network}. ` +\n `Common causes: insufficient ${accept.asset} balance, or the ` +\n `authorization window expired. Check the balance, then retry.`,\n );\n }\n return paid;\n };\n}\n"],"mappings":"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","names":["body"]}

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display