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

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

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

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

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';
import { b as PayFetchFn } from './fetch-local-BagfYk43.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-BagfYk43.js';

@@ -1117,2 +1117,14 @@ /**

declare function getUSDCBalance(walletAddress: string, chain?: keyof typeof USDC_CONTRACTS, rpcUrl?: string): Promise<bigint>;
/**
* Read an ERC-20 `balanceOf` for the wallet on the challenge's network.
* Returns `undefined` when no RPC is known for the chain (caller should
* treat that as "cannot check", not "zero"). Throws on RPC failure —
* callers doing a best-effort pre-check catch and proceed.
*
* @param asset ERC-20 contract address from the 402 accept.
* @param walletAddress The paying EOA.
* @param caip2Network CAIP-2 id from the accept (e.g. "eip155:8453").
* @param rpcUrl Optional RPC override; falls back to a public endpoint.
*/
declare function getErc20Balance(asset: string, walletAddress: string, caip2Network: string, rpcUrl?: string): Promise<bigint | undefined>;
/** Format a USDC base-unit bigint (6 decimals) as a human-readable string, e.g. "1.234567". */

@@ -1500,2 +1512,2 @@ declare function formatUSDC(baseUnits: bigint): string;

export { ARISPAY_METERED_BACKEND_ID, ARISPAY_METERED_SCHEME, type AgentSummary, type AgfacFlatRequirements, type AttachWalletOptions, BackendNotImplementedError, type BalanceResponse, type BootstrapAgentConfig, type BootstrapAgentResult, BootstrapError, type BootstrapExistingAgent, type BootstrapPairing, type CardSetupSessionResponse, type CardSetupStatus, type CardSetupStatusResponse, type CatalogResource, type CreateEndUserOptions, type CreatePaymentOptions, type CreateX402AgentResponse, DEFAULT_ARISPAY_URL, DelegationClient, DeviceCodeError, type DeviceCodeResponse, type DeviceTokenResponse, type DiscoverCandidate, type DiscoverCatalogOptions, type DiscoverCategory, type DiscoverEndpoint, type DiscoverInput, type DiscoverOptions, type DiscoverPricing, type DiscoverResult, type DiscoverSource, type DiscoverTransport, type EndUserResponse, type HandshakeTerms, HostedTopupNotConfiguredError, type HostedTopupOptions, type HostedTopupResponse, type InspectChallengeOptions, type InspectChallengeResult, InspectParseError, type InspectedAccept, InvalidRequirementsError, type LaunchAgentConfig, type LaunchedAgent, type ListAgentsOptions, type ListAgentsResponse, METERING_SESSION_HEADER, MissingArisPayApiKeyError, MissingDevKeyForSyncError, PayAgentError, PayFetchFn, type PaymentAuthorization, type PaymentBackend, type PaymentFeedItem, type PaymentNextAction, type PaymentRail, type PaymentReceipt, PaymentRejectedError, type PaymentRequirementsBody, type PaymentResponse, type PaymentScheme, type PaymentSpendInfo, type PaymentStatus, type PaymentsFeedQueryOptions, type PaymentsFeedResponse, type RenameAgentResponse, type SetUserLimitsOptions, type SpendLimitResponse, type StoredAgent, type StoredConfig, type SyncAgentsOptions, type SyncAgentsResult, type SyncedAgent, USDC_CONTRACTS, type UpdateAgentPatch, type UpdateAgentResponse, type WalletChain, type WalletPaymentMethodResponse, type WalletStatusResponse, type X402Accept, type X402AgentConfig, type X402Requirements, bootstrapAgent, centsFromBaseUnits, clearApiKey, discover, discoverCatalog, formatUSDC, getAgent, getApiKey, getArispayUrl, getConfigPath, getLaunchedAgent, getUSDCBalance, inspectChallenge, launchAgent, listAgents, loadConfig, makeArispayMeteredBackend, makeX402Backend, mandateCardBackend, pollDeviceToken, removeAgent, renameStoredAgent, requestDeviceCode, runDeviceAuth, saveAgent, saveConfig, selectBackend, setApiKey, syncAgents, upsertManyFromServer };
export { ARISPAY_METERED_BACKEND_ID, ARISPAY_METERED_SCHEME, type AgentSummary, type AgfacFlatRequirements, type AttachWalletOptions, BackendNotImplementedError, type BalanceResponse, type BootstrapAgentConfig, type BootstrapAgentResult, BootstrapError, type BootstrapExistingAgent, type BootstrapPairing, type CardSetupSessionResponse, type CardSetupStatus, type CardSetupStatusResponse, type CatalogResource, type CreateEndUserOptions, type CreatePaymentOptions, type CreateX402AgentResponse, DEFAULT_ARISPAY_URL, DelegationClient, DeviceCodeError, type DeviceCodeResponse, type DeviceTokenResponse, type DiscoverCandidate, type DiscoverCatalogOptions, type DiscoverCategory, type DiscoverEndpoint, type DiscoverInput, type DiscoverOptions, type DiscoverPricing, type DiscoverResult, type DiscoverSource, type DiscoverTransport, type EndUserResponse, type HandshakeTerms, HostedTopupNotConfiguredError, type HostedTopupOptions, type HostedTopupResponse, type InspectChallengeOptions, type InspectChallengeResult, InspectParseError, type InspectedAccept, InvalidRequirementsError, type LaunchAgentConfig, type LaunchedAgent, type ListAgentsOptions, type ListAgentsResponse, METERING_SESSION_HEADER, MissingArisPayApiKeyError, MissingDevKeyForSyncError, PayAgentError, PayFetchFn, type PaymentAuthorization, type PaymentBackend, type PaymentFeedItem, type PaymentNextAction, type PaymentRail, type PaymentReceipt, PaymentRejectedError, type PaymentRequirementsBody, type PaymentResponse, type PaymentScheme, type PaymentSpendInfo, type PaymentStatus, type PaymentsFeedQueryOptions, type PaymentsFeedResponse, type RenameAgentResponse, type SetUserLimitsOptions, type SpendLimitResponse, type StoredAgent, type StoredConfig, type SyncAgentsOptions, type SyncAgentsResult, type SyncedAgent, USDC_CONTRACTS, type UpdateAgentPatch, type UpdateAgentResponse, type WalletChain, type WalletPaymentMethodResponse, type WalletStatusResponse, type X402Accept, type X402AgentConfig, type X402Requirements, bootstrapAgent, centsFromBaseUnits, clearApiKey, discover, discoverCatalog, formatUSDC, getAgent, getApiKey, getArispayUrl, getConfigPath, getErc20Balance, getLaunchedAgent, getUSDCBalance, inspectChallenge, launchAgent, listAgents, loadConfig, makeArispayMeteredBackend, makeX402Backend, mandateCardBackend, pollDeviceToken, removeAgent, renameStoredAgent, requestDeviceCode, runDeviceAuth, saveAgent, saveConfig, selectBackend, setApiKey, syncAgents, upsertManyFromServer };
+7
-5

@@ -10,3 +10,2 @@ import {

MissingDevKeyForSyncError,
USDC_CONTRACTS,
bootstrapAgent,

@@ -17,3 +16,2 @@ centsFromBaseUnits,

discoverCatalog,
formatUSDC,
getAgent,

@@ -24,3 +22,2 @@ getApiKey,

getLaunchedAgent,
getUSDCBalance,
inspectChallenge,

@@ -40,3 +37,3 @@ launchAgent,

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

@@ -46,6 +43,10 @@ InvalidRequirementsError,

PaymentRejectedError,
USDC_CONTRACTS,
deriveLocalWalletAddress,
formatUSDC,
getErc20Balance,
getUSDCBalance,
payFetchDelegated,
payFetchLocal
} from "./chunk-C2IJSCL4.js";
} from "./chunk-ZE73ANBP.js";

@@ -132,2 +133,3 @@ // src/types.ts

getConfigPath,
getErc20Balance,
getLaunchedAgent,

@@ -134,0 +136,0 @@ getUSDCBalance,

@@ -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-CBTh3qb1.js';
import { P as PayFetchDelegatedConfig, a as PayFetchLocalConfig } from './fetch-local-BagfYk43.js';

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

import {
payFetchDelegated,
payFetchLocal
} from "./chunk-C2IJSCL4.js";
} from "./chunk-ZE73ANBP.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-CBTh3qb1.js';
import { P as PayFetchDelegatedConfig, a as PayFetchLocalConfig } from './fetch-local-BagfYk43.js';

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

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

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

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

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

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 };

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