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.18.0
to
2.19.0
+1123
dist/chunk-5VO6XLTI.js
import {
payFetchDelegated
} from "./chunk-C2IJSCL4.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/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;
}
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/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,
USDC_CONTRACTS,
getUSDCBalance,
formatUSDC,
BootstrapError,
bootstrapAgent,
DeviceCodeError,
requestDeviceCode,
pollDeviceToken,
runDeviceAuth,
MissingDevKeyForSyncError,
syncAgents,
discover,
discoverCatalog,
InspectParseError,
centsFromBaseUnits,
inspectChallenge
};
//# sourceMappingURL=chunk-5VO6XLTI.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 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
};
}
} 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
};
}
// 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 } = 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
}),
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/fetch-local.ts
import { ethers } from "ethers";
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 } = 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}`
);
}
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 }
})
).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 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;
};
}
export {
PayAgentError,
PaymentRejectedError,
InvalidRequirementsError,
payFetchDelegated,
deriveLocalWalletAddress,
payFetchLocal
};
//# sourceMappingURL=chunk-C2IJSCL4.js.map
{"version":3,"sources":["../src/errors.ts","../src/payment.ts","../src/fetch-delegated.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\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 };\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 };\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 } = 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 }),\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","import { ethers } from \"ethers\";\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\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 } = 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 // 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 }),\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 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"],"mappings":"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","names":["body"]}
interface PayFetchDelegatedConfig {
/** Base URL for the ArisPay API (no trailing slash). */
arispayUrl: string;
/** The x402 agent's own API key (returned by DelegationClient.createX402Agent). */
apiKey: string;
/** Override for the ArisPay delegated-sign path. Default: /v1/x402/delegated-sign */
signPath?: string;
/** Request timeout for the sign call (ms). Default: 15000. */
signTimeoutMs?: number;
/**
* Fires once per paid request, right after ArisPay signs (spend counters
* are already committed server-side at that point), with the amount and
* the agent's remaining budget. Advisory: a throwing callback is swallowed
* and never breaks the payment flow.
*/
onPayment?: (info: DelegatedPaymentInfo) => void;
}
type PayFetchFn = (url: string | URL, init?: RequestInit) => Promise<Response>;
/**
* Per-payment budget snapshot surfaced via `onPayment`. All amounts are
* integer cents. Spend counters and remaining headroom are AFTER this
* payment (the server returns post-increment counters).
*/
interface DelegatedPaymentInfo {
/** Cents charged for this payment. */
amountCents: number;
chain: string;
walletAddress: string;
/** Cents spent today (UTC calendar day), including this payment. */
dailySpend: number;
/** Cents spent this month (UTC calendar month), including this payment. */
monthlySpend: number;
limits: {
maxPerTx: number;
maxDaily: number;
maxMonthly: number;
};
/** Cents of daily budget left after this payment. */
remainingDaily: number;
/** Cents of monthly budget left after this payment. */
remainingMonthly: number;
}
/**
* Create a fetch wrapper that delegates EIP-3009 signing to ArisPay.
* No private key lives on the caller's machine; ArisPay enforces the
* delegation limits before signing and increments spend counters on success.
*/
declare function payFetchDelegated(config: PayFetchDelegatedConfig): PayFetchFn;
interface PayFetchLocalConfig {
/** Hex-encoded private key (`0x…`) of the EOA that pays. */
privateKey: string;
/**
* Optional per-payment cap in the accepted asset's base units (USDC has 6
* decimals, so "1000000" = $1.00). A 402 asking for more throws
* `PaymentRejectedError` instead of signing. Client-side guardrail only.
*/
maxPerTxBaseUnits?: string | bigint;
/**
* Fires once per paid request, right after signing. Advisory: a throwing
* callback is swallowed and never breaks the payment flow.
*/
onPayment?: (info: LocalPaymentInfo) => void;
}
interface LocalPaymentInfo {
/** Amount paid, in the asset's base units. */
amount: string;
/** CAIP-2 network the payment settled on. */
network: string;
asset: string;
payTo: string;
/** The EOA that paid (derived from the configured key). */
walletAddress: string;
}
/**
* Derive the deposit address for a local (self-custody) private key. This is
* the address a human funds with USDC in Lane P mode — surfaced by
* `payagent wallet address` and the MCP `check_wallet` tool. Throws on an
* invalid key.
*/
declare function deriveLocalWalletAddress(privateKey: string): string;
/**
* Create a fetch wrapper that pays x402 402s by signing locally. Mirrors
* `payFetchDelegated`'s flow (parse → sign → retry with X-PAYMENT) with the
* server round-trip replaced by local EIP-712 signing.
*/
declare function payFetchLocal(config: PayFetchLocalConfig): PayFetchFn;
export { type DelegatedPaymentInfo as D, type LocalPaymentInfo as L, type PayFetchDelegatedConfig as P, type PayFetchLocalConfig as a, type PayFetchFn as b, payFetchLocal as c, deriveLocalWalletAddress as d, payFetchDelegated as p };
+2
-2

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

import { b as PayFetchFn } from './fetch-local-CjNSDxFt.js';
export { D as DelegatedPaymentInfo, L as LocalPaymentInfo, P as PayFetchDelegatedConfig, a as PayFetchLocalConfig, p as payFetchDelegated, c as payFetchLocal } from './fetch-local-CjNSDxFt.js';
import { b as PayFetchFn } from './fetch-local-CBTh3qb1.js';
export { D as DelegatedPaymentInfo, L as LocalPaymentInfo, P as PayFetchDelegatedConfig, a as PayFetchLocalConfig, d as deriveLocalWalletAddress, p as payFetchDelegated, c as payFetchLocal } from './fetch-local-CBTh3qb1.js';

@@ -4,0 +4,0 @@ /**

@@ -37,3 +37,3 @@ import {

upsertManyFromServer
} from "./chunk-BPVQWESF.js";
} from "./chunk-5VO6XLTI.js";
import {

@@ -43,5 +43,6 @@ InvalidRequirementsError,

PaymentRejectedError,
deriveLocalWalletAddress,
payFetchDelegated,
payFetchLocal
} from "./chunk-5DDTY352.js";
} from "./chunk-C2IJSCL4.js";

@@ -120,2 +121,3 @@ // src/types.ts

clearApiKey,
deriveLocalWalletAddress,
discover,

@@ -122,0 +124,0 @@ discoverCatalog,

@@ -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\" && 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":[]}
import { DynamicStructuredTool } from '@langchain/core/tools';
import { z } from 'zod/v3';
import { P as PayFetchDelegatedConfig, a as PayFetchLocalConfig } from './fetch-local-CjNSDxFt.js';
import { P as PayFetchDelegatedConfig, a as PayFetchLocalConfig } from './fetch-local-CBTh3qb1.js';

@@ -5,0 +5,0 @@ /**

import {
payFetchDelegated,
payFetchLocal
} from "./chunk-5DDTY352.js";
} from "./chunk-C2IJSCL4.js";

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

import { Tool } from 'ai';
import { z } from 'zod/v4';
import { P as PayFetchDelegatedConfig, a as PayFetchLocalConfig } from './fetch-local-CjNSDxFt.js';
import { P as PayFetchDelegatedConfig, a as PayFetchLocalConfig } from './fetch-local-CBTh3qb1.js';

@@ -5,0 +5,0 @@ /**

import {
payFetchDelegated,
payFetchLocal
} from "./chunk-5DDTY352.js";
} from "./chunk-C2IJSCL4.js";

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

{
"name": "payagent",
"version": "2.18.0",
"description": "Let AI agents pay for APIs. The ArisPay SDK + CLI for x402 payments with delegated-custody wallets — no private keys in your process.",
"version": "2.19.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.",
"type": "module",

@@ -55,2 +55,5 @@ "main": "./dist/index.js",

"delegated-custody",
"self-custody",
"permissionless",
"local-signer",
"eurc",

@@ -57,0 +60,0 @@ "euro",

@@ -22,8 +22,16 @@ # payagent

Point the CLI at any funded EOA key and pay in one step — nothing else to set up:
No key yet? Generate one and get a deposit address (nothing is stored; save the key):
```bash
npx payagent wallet new
```
Send USDC on Base to the printed address, then pay in one step:
```bash
PAYAGENT_PRIVATE_KEY=0x… npx payagent pay https://api.example.com/premium
```
`payagent wallet address` re-prints the deposit address for a key you already have.
Library equivalent:

@@ -319,7 +327,7 @@

v2 removes the self-custody path (`payFetch`, `PayAgent`, raw private-key signing). ArisPay's product is delegated-custody only — keys live in Coinbase CDP with server-enforced limits.
v2.0 removed the 1.x self-custody API (`payFetch`, `PayAgent`). Local signing returned as a first-class mode in 2.13 with a new surface: `payFetchLocal({ privateKey })` and `PAYAGENT_PRIVATE_KEY` (see the local-signer quick start above). The 1.x names are gone for good.
Migration:
- Replace `payFetch({ privateKey })` with `payFetchDelegated({ arispayUrl, apiKey })`.
- Replace `new PayAgent({ privateKey, budget, maxPerRequest })` with `DelegationClient.createX402Agent({ maxPerTx, maxDaily, maxMonthly, ... })` to provision, then `payFetchDelegated` to transact.
- Replace `payFetch({ privateKey })` with `payFetchLocal({ privateKey })` (self-custody) or `payFetchDelegated({ arispayUrl, apiKey })` (delegated custody with server-enforced limits).
- Replace `new PayAgent({ privateKey, budget, maxPerRequest })` with `DelegationClient.createX402Agent({ maxPerTx, maxDaily, maxMonthly, ... })` to provision, then `payFetchDelegated` to transact — or use `payFetchLocal` with `maxPerTxBaseUnits` for a client-side cap.
- `BudgetExceededError`, `UnsupportedChainError`, and `DomainNotAllowedError` are gone — their equivalents are now server-side rejections surfaced as `PaymentRejectedError`.

@@ -326,0 +334,0 @@

@@ -35,11 +35,10 @@ #!/usr/bin/env node

"",
` Get started: ${cyan}npx payagent init${reset}`,
` Docs: ${cyan}https://arispay.app/docs${reset}`,
` No account needed (self-custody):`,
` ${cyan}npx payagent wallet new${reset} ${dim}# keypair + deposit address${reset}`,
` ${cyan}PAYAGENT_PRIVATE_KEY=0x… npx payagent pay <url>${reset}`,
"",
`${dim} \`payagent init\` walks you through an OAuth sign-in and saves${reset}`,
`${dim} a developer key to ~/.payagent/config.json. Then:${reset}`,
` Managed wallet with server-enforced spend limits:`,
` ${cyan}npx payagent quickstart --email you@example.com${reset}`,
"",
` ${cyan}npx payagent agent create --name hermes \\\\${reset}`,
` ${cyan} --per-tx 0.50 --daily 10 --monthly 100${reset}`,
` ${cyan}npx payagent agent fund hermes${reset}`,
` Docs: ${cyan}https://arispay.app/docs${reset}`,
"",

@@ -46,0 +45,0 @@ ];

// 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 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
};
}
} 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
};
}
// 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 } = 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
}),
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/fetch-local.ts
import { ethers } from "ethers";
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 } = 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}`
);
}
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 paymentHeader = Buffer.from(
JSON.stringify({
x402Version: x402Version ?? 2,
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 }
})
).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);
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;
};
}
export {
PayAgentError,
PaymentRejectedError,
InvalidRequirementsError,
payFetchDelegated,
payFetchLocal
};
//# sourceMappingURL=chunk-5DDTY352.js.map
{"version":3,"sources":["../src/errors.ts","../src/payment.ts","../src/fetch-delegated.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\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 };\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 };\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 } = 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 }),\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","import { ethers } from \"ethers\";\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\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// 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 } = 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 // 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 paymentHeader = Buffer.from(\n JSON.stringify({\n x402Version: x402Version ?? 2,\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 }),\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 X-PAYMENT header.\n const retryHeaders = new Headers(init?.headers);\n retryHeaders.set(\"X-PAYMENT\", paymentHeader);\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 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"],"mappings":"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","names":["body"]}
import {
payFetchDelegated
} from "./chunk-5DDTY352.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/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;
}
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/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,
USDC_CONTRACTS,
getUSDCBalance,
formatUSDC,
BootstrapError,
bootstrapAgent,
DeviceCodeError,
requestDeviceCode,
pollDeviceToken,
runDeviceAuth,
MissingDevKeyForSyncError,
syncAgents,
discover,
discoverCatalog,
InspectParseError,
centsFromBaseUnits,
inspectChallenge
};
//# sourceMappingURL=chunk-BPVQWESF.js.map

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

interface PayFetchDelegatedConfig {
/** Base URL for the ArisPay API (no trailing slash). */
arispayUrl: string;
/** The x402 agent's own API key (returned by DelegationClient.createX402Agent). */
apiKey: string;
/** Override for the ArisPay delegated-sign path. Default: /v1/x402/delegated-sign */
signPath?: string;
/** Request timeout for the sign call (ms). Default: 15000. */
signTimeoutMs?: number;
/**
* Fires once per paid request, right after ArisPay signs (spend counters
* are already committed server-side at that point), with the amount and
* the agent's remaining budget. Advisory: a throwing callback is swallowed
* and never breaks the payment flow.
*/
onPayment?: (info: DelegatedPaymentInfo) => void;
}
type PayFetchFn = (url: string | URL, init?: RequestInit) => Promise<Response>;
/**
* Per-payment budget snapshot surfaced via `onPayment`. All amounts are
* integer cents. Spend counters and remaining headroom are AFTER this
* payment (the server returns post-increment counters).
*/
interface DelegatedPaymentInfo {
/** Cents charged for this payment. */
amountCents: number;
chain: string;
walletAddress: string;
/** Cents spent today (UTC calendar day), including this payment. */
dailySpend: number;
/** Cents spent this month (UTC calendar month), including this payment. */
monthlySpend: number;
limits: {
maxPerTx: number;
maxDaily: number;
maxMonthly: number;
};
/** Cents of daily budget left after this payment. */
remainingDaily: number;
/** Cents of monthly budget left after this payment. */
remainingMonthly: number;
}
/**
* Create a fetch wrapper that delegates EIP-3009 signing to ArisPay.
* No private key lives on the caller's machine; ArisPay enforces the
* delegation limits before signing and increments spend counters on success.
*/
declare function payFetchDelegated(config: PayFetchDelegatedConfig): PayFetchFn;
interface PayFetchLocalConfig {
/** Hex-encoded private key (`0x…`) of the EOA that pays. */
privateKey: string;
/**
* Optional per-payment cap in the accepted asset's base units (USDC has 6
* decimals, so "1000000" = $1.00). A 402 asking for more throws
* `PaymentRejectedError` instead of signing. Client-side guardrail only.
*/
maxPerTxBaseUnits?: string | bigint;
/**
* Fires once per paid request, right after signing. Advisory: a throwing
* callback is swallowed and never breaks the payment flow.
*/
onPayment?: (info: LocalPaymentInfo) => void;
}
interface LocalPaymentInfo {
/** Amount paid, in the asset's base units. */
amount: string;
/** CAIP-2 network the payment settled on. */
network: string;
asset: string;
payTo: string;
/** The EOA that paid (derived from the configured key). */
walletAddress: string;
}
/**
* Create a fetch wrapper that pays x402 402s by signing locally. Mirrors
* `payFetchDelegated`'s flow (parse → sign → retry with X-PAYMENT) with the
* server round-trip replaced by local EIP-712 signing.
*/
declare function payFetchLocal(config: PayFetchLocalConfig): PayFetchFn;
export { type DelegatedPaymentInfo as D, type LocalPaymentInfo as L, type PayFetchDelegatedConfig as P, type PayFetchLocalConfig as a, type PayFetchFn as b, payFetchLocal as c, payFetchDelegated as p };

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

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