@catena/sdk 0.0.0-bootstrap.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,135 @@
1
+ import { F as OnchainNetworkInput, U as X402Authorization, W as X402PaymentRequirements, a as IntentSubmitError, n as CatenaClient, t as ApiError } from "./client-Cm2wNUuB.mjs";
2
+ import { X402ApprovalPendingError, X402PaymentError, X402SubmitInterruptedError } from "./x402.mjs";
3
+ import { LocalAccount } from "viem";
4
+ //#region src/viem.d.ts
5
+ /**
6
+ * The account was asked for a capability a custodial Catena wallet does not
7
+ * have. The wallet's signing policy authorizes only EIP-3009
8
+ * `TransferWithAuthorization` typed data; `signMessage`, `signTransaction`,
9
+ * and raw-hash signing are structurally unavailable.
10
+ */
11
+ declare class UnsupportedAccountCapabilityError extends Error {
12
+ readonly capability: "signMessage" | "signTransaction";
13
+ constructor(capability: "signMessage" | "signTransaction");
14
+ }
15
+ /**
16
+ * Machine-readable reason a typed-data payload was refused before any
17
+ * request was made. Branch on `reason`, not message prose.
18
+ */
19
+ type UnsupportedTypedDataReason = "primary_type" | "types" | "network" | "asset" | "domain" | "message" | "nonce" | "from_mismatch" | "recipient" | "expired" | "window";
20
+ /**
21
+ * The typed data is not something a Catena wallet can sign. Thrown
22
+ * client-side, before any intent exists; nothing was submitted or paid.
23
+ */
24
+ declare class UnsupportedTypedDataError extends Error {
25
+ readonly reason: UnsupportedTypedDataReason;
26
+ constructor(reason: UnsupportedTypedDataReason, message: string);
27
+ }
28
+ /**
29
+ * The account id resolves to a liquidation deposit address rather than the
30
+ * wallet's own address. x402 authorizations can only be signed for a wallet
31
+ * account; pick an account whose deposit address source is `wallet`.
32
+ */
33
+ declare class NotAWalletAccountError extends Error {
34
+ readonly accountId: string;
35
+ readonly source: string;
36
+ constructor(accountId: string, source: string);
37
+ }
38
+ /**
39
+ * Catena declined the payment (policy block or terminal failure). `reasons`
40
+ * carries the server's detail; the same struct will not succeed on retry.
41
+ */
42
+ declare class X402PaymentDeclinedError extends X402PaymentError {
43
+ readonly intentId: string;
44
+ constructor(intentId: string, reasons: readonly string[]);
45
+ }
46
+ /**
47
+ * The payment was accepted and is still in flight, which a synchronous
48
+ * `signTypedData` cannot resume. Do not pay again on the strength of this:
49
+ * reconcile the intent instead. Note that polling does not advance an
50
+ * approved x402 payment resting in this state — it waits for the paid
51
+ * request to be re-run.
52
+ */
53
+ declare class X402PaymentInFlightError extends X402PaymentError {
54
+ readonly intentId: string;
55
+ constructor(intentId: string, status: string);
56
+ }
57
+ /**
58
+ * The payment completed but the signature it carried cannot be used — absent,
59
+ * or an envelope this SDK could not decode. The money has moved, so this is
60
+ * not a retry: reconcile the intent before paying again. Re-reading it will
61
+ * return the same unusable value, so recovery here means reconciling the
62
+ * payment with the operator, not fetching the signature again.
63
+ */
64
+ declare class X402PaymentSignatureUnusableError extends X402PaymentError {
65
+ readonly intentId: string;
66
+ constructor(intentId: string, message: string);
67
+ }
68
+ /**
69
+ * The completed payment's signature did not recover to the wallet address
70
+ * for the typed data that was submitted. The signature must not be used;
71
+ * this indicates server-side drift and is worth reporting.
72
+ *
73
+ * An `X402PaymentError` because it is thrown after the payment completed:
74
+ * money has moved, so it belongs to the family a caller branches on before
75
+ * deciding whether to pay again. Reconcile with `getIntent(intentId)`.
76
+ */
77
+ declare class SignatureVerificationFailedError extends X402PaymentError {
78
+ readonly intentId: string;
79
+ readonly expectedAddress: string;
80
+ readonly recoveredAddress: string | undefined;
81
+ constructor(intentId: string, expectedAddress: string, recoveredAddress: string | undefined);
82
+ }
83
+ interface CatenaAccountOptions {
84
+ /**
85
+ * The wallet account payments draw from. Its deposit address must resolve
86
+ * with source `wallet`.
87
+ */
88
+ accountId: string;
89
+ /**
90
+ * Network used to resolve the wallet address once at creation, defaulting
91
+ * to `base`. It must be the network the wallet account is configured for:
92
+ * a wallet accepts deposits on exactly one network, and asking for another
93
+ * fails the call rather than falling back. Pass `base-sepolia` explicitly
94
+ * for a testnet wallet. The same network then bounds what the account can
95
+ * pay: typed data naming the other supported chain is refused server-side,
96
+ * because the account cannot fund a payment off its own network.
97
+ */
98
+ network?: OnchainNetworkInput;
99
+ }
100
+ /**
101
+ * The account shape `createCatenaAccount` resolves to: a viem local account
102
+ * whose only working capability is `signTypedData` for x402 EIP-3009
103
+ * payment authorizations.
104
+ */
105
+ type CatenaAccount = LocalAccount;
106
+ /**
107
+ * Present a Catena wallet as a viem `Account` for x402 client tooling.
108
+ *
109
+ * The returned account's `signTypedData` accepts exactly one shape — EIP-3009
110
+ * `TransferWithAuthorization` for USDC on Base or Base Sepolia, the payload
111
+ * x402 exact-scheme clients construct — and routes it through a Catena x402
112
+ * intent: policy evaluates the payment, the custodial key signs the caller's
113
+ * exact struct, and the signature is verified to recover to the wallet
114
+ * address before it is returned. `signMessage` and `signTransaction` throw
115
+ * `UnsupportedAccountCapabilityError`; there is no raw-hash `sign`.
116
+ *
117
+ * Failure modes: `UnsupportedTypedDataError` (refused client-side, nothing
118
+ * submitted), `X402ApprovalPendingError` (parked for a human approval —
119
+ * approve in the console, then have the tooling retry; a retry with a fresh
120
+ * nonce still consumes the approval), `X402PaymentDeclinedError` (policy
121
+ * block or terminal failure), and — all of these after the payment may have
122
+ * moved, so reconcile with `getIntent(intentId)` rather than paying again —
123
+ * `SignatureVerificationFailedError` (the returned signature did not verify
124
+ * and was withheld), `X402SubmitInterruptedError`,
125
+ * `X402PaymentSignatureUnusableError`, and `X402PaymentInFlightError`. Then
126
+ * `IntentSubmitError` with `outcome: "not-submitted"` (the
127
+ * intent exists but nothing was submitted and no money moved, so a retry is
128
+ * safe), and `ApiError` for request-level failures such as a reused nonce.
129
+ *
130
+ * Requires the optional `viem` peer dependency; importing this module
131
+ * without viem installed fails with a module-not-found error naming it.
132
+ */
133
+ declare function createCatenaAccount(client: CatenaClient, options: CatenaAccountOptions): Promise<CatenaAccount>;
134
+ //#endregion
135
+ export { ApiError, CatenaAccount, CatenaAccountOptions, IntentSubmitError, NotAWalletAccountError, type OnchainNetworkInput, SignatureVerificationFailedError, UnsupportedAccountCapabilityError, UnsupportedTypedDataError, UnsupportedTypedDataReason, X402ApprovalPendingError, type X402Authorization, X402PaymentDeclinedError, X402PaymentError, X402PaymentInFlightError, type X402PaymentRequirements, X402PaymentSignatureUnusableError, X402SubmitInterruptedError, createCatenaAccount };
package/dist/viem.mjs ADDED
@@ -0,0 +1,301 @@
1
+ import { b as x402CredentialFromIntentData, n as IntentSubmitError, t as ApiError } from "./client-CHZMO00P.mjs";
2
+ import { X402ApprovalPendingError, X402PaymentError, X402SubmitInterruptedError } from "./x402.mjs";
3
+ import * as v from "valibot";
4
+ import { getAddress, isAddress, isHex, recoverTypedDataAddress } from "viem";
5
+ import { toAccount } from "viem/accounts";
6
+ //#region src/viem.ts
7
+ const NETWORKS = {
8
+ "8453": {
9
+ network: "eip155:8453",
10
+ usdc: getAddress("0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913")
11
+ },
12
+ "84532": {
13
+ network: "eip155:84532",
14
+ usdc: getAddress("0x036CbD53842c5426634e7929541eC2318f3dCF7e")
15
+ }
16
+ };
17
+ const EIP712_DOMAIN_TYPE = [
18
+ {
19
+ name: "name",
20
+ type: "string"
21
+ },
22
+ {
23
+ name: "version",
24
+ type: "string"
25
+ },
26
+ {
27
+ name: "chainId",
28
+ type: "uint256"
29
+ },
30
+ {
31
+ name: "verifyingContract",
32
+ type: "address"
33
+ }
34
+ ];
35
+ const CANONICAL_DOMAIN_FIELDS = new Set([
36
+ "name",
37
+ "version",
38
+ "chainId",
39
+ "verifyingContract"
40
+ ]);
41
+ const TRANSFER_WITH_AUTHORIZATION_TYPES = { TransferWithAuthorization: [
42
+ {
43
+ name: "from",
44
+ type: "address"
45
+ },
46
+ {
47
+ name: "to",
48
+ type: "address"
49
+ },
50
+ {
51
+ name: "value",
52
+ type: "uint256"
53
+ },
54
+ {
55
+ name: "validAfter",
56
+ type: "uint256"
57
+ },
58
+ {
59
+ name: "validBefore",
60
+ type: "uint256"
61
+ },
62
+ {
63
+ name: "nonce",
64
+ type: "bytes32"
65
+ }
66
+ ] };
67
+ const MIN_REMAINING_VALIDITY_SECONDS = 5;
68
+ const MAX_TIMEOUT_SECONDS = 3600;
69
+ const VALIDBEFORE_SKEW_SECONDS = 30;
70
+ var UnsupportedAccountCapabilityError = class extends Error {
71
+ capability;
72
+ constructor(capability) {
73
+ super(`A Catena wallet is custodial and its signing policy authorizes only EIP-3009 TransferWithAuthorization typed data; ${capability} is not available. Use signTypedData with an x402 payment authorization.`);
74
+ this.name = "UnsupportedAccountCapabilityError";
75
+ this.capability = capability;
76
+ }
77
+ };
78
+ var UnsupportedTypedDataError = class extends Error {
79
+ reason;
80
+ constructor(reason, message) {
81
+ super(message);
82
+ this.name = "UnsupportedTypedDataError";
83
+ this.reason = reason;
84
+ }
85
+ };
86
+ var NotAWalletAccountError = class extends Error {
87
+ accountId;
88
+ source;
89
+ constructor(accountId, source) {
90
+ super(`Account ${accountId} resolves to a ${source} deposit address, not the wallet's own address; x402 authorizations can only be signed for a wallet account`);
91
+ this.name = "NotAWalletAccountError";
92
+ this.accountId = accountId;
93
+ this.source = source;
94
+ }
95
+ };
96
+ var X402PaymentDeclinedError = class extends X402PaymentError {
97
+ intentId;
98
+ constructor(intentId, reasons) {
99
+ super(`Catena declined the payment (intent ${intentId})`, reasons);
100
+ this.name = "X402PaymentDeclinedError";
101
+ this.intentId = intentId;
102
+ }
103
+ };
104
+ var X402PaymentInFlightError = class extends X402PaymentError {
105
+ intentId;
106
+ constructor(intentId, status) {
107
+ super(`The payment is still ${status} (intent ${intentId}); this client cannot resume an in-flight x402 settlement. Reconcile with getIntent("${intentId}") rather than paying again`);
108
+ this.name = "X402PaymentInFlightError";
109
+ this.intentId = intentId;
110
+ }
111
+ };
112
+ var X402PaymentSignatureUnusableError = class extends X402PaymentError {
113
+ intentId;
114
+ constructor(intentId, message) {
115
+ super(`${message} (intent ${intentId}); the payment may have settled — check getIntent("${intentId}") before paying again`);
116
+ this.name = "X402PaymentSignatureUnusableError";
117
+ this.intentId = intentId;
118
+ }
119
+ };
120
+ var SignatureVerificationFailedError = class extends X402PaymentError {
121
+ intentId;
122
+ expectedAddress;
123
+ recoveredAddress;
124
+ constructor(intentId, expectedAddress, recoveredAddress) {
125
+ super(`The signature for intent ${intentId} did not recover to the wallet address; do not use it. The payment may have settled — check getIntent("${intentId}") before paying again`);
126
+ this.name = "SignatureVerificationFailedError";
127
+ this.intentId = intentId;
128
+ this.expectedAddress = expectedAddress;
129
+ this.recoveredAddress = recoveredAddress;
130
+ }
131
+ };
132
+ const MAX_UINT256 = 2n ** 256n - 1n;
133
+ const uint256Schema = v.pipe(v.union([
134
+ v.pipe(v.string(), v.regex(/^(0|[1-9]\d*)$/), v.maxLength(78)),
135
+ v.pipe(v.number(), v.check((value) => Number.isSafeInteger(value) && value >= 0)),
136
+ v.pipe(v.bigint(), v.check((value) => value >= 0n))
137
+ ]), v.check((value) => {
138
+ const decimal = toDecimalString(value);
139
+ return /^\d+$/.test(decimal) && BigInt(decimal) <= MAX_UINT256;
140
+ }));
141
+ const typedDataInputSchema = v.looseObject({
142
+ primaryType: v.string(),
143
+ domain: v.looseObject({
144
+ name: v.optional(v.string()),
145
+ version: v.optional(v.string()),
146
+ chainId: v.optional(v.union([v.number(), v.bigint()])),
147
+ verifyingContract: v.optional(v.string())
148
+ }),
149
+ types: v.record(v.string(), v.unknown()),
150
+ message: v.looseObject({
151
+ from: v.string(),
152
+ to: v.string(),
153
+ value: uint256Schema,
154
+ validAfter: uint256Schema,
155
+ validBefore: uint256Schema,
156
+ nonce: v.string()
157
+ })
158
+ });
159
+ const paymentSignatureEnvelopeSchema = v.looseObject({ payload: v.looseObject({ signature: v.string() }) });
160
+ function decodeBase64Json(value) {
161
+ const normalized = value.replaceAll("-", "+").replaceAll("_", "/");
162
+ try {
163
+ const bytes = Uint8Array.from(atob(normalized), (char) => char.charCodeAt(0));
164
+ return JSON.parse(new TextDecoder().decode(bytes));
165
+ } catch {
166
+ return;
167
+ }
168
+ }
169
+ function toDecimalString(value) {
170
+ return typeof value === "string" ? value : value.toString();
171
+ }
172
+ const typeTableSchema = v.array(v.looseObject({
173
+ name: v.string(),
174
+ type: v.string()
175
+ }));
176
+ function encodeTypeTable(value) {
177
+ const parsed = v.safeParse(typeTableSchema, value);
178
+ return parsed.success ? JSON.stringify(parsed.output.map((entry) => [entry.name, entry.type])) : void 0;
179
+ }
180
+ function encodeCanonicalTable(table) {
181
+ return JSON.stringify(table.map((entry) => [entry.name, entry.type]));
182
+ }
183
+ const CANONICAL_TRANSFER_TYPE = encodeCanonicalTable(TRANSFER_WITH_AUTHORIZATION_TYPES.TransferWithAuthorization);
184
+ const CANONICAL_DOMAIN_TYPE = encodeCanonicalTable(EIP712_DOMAIN_TYPE);
185
+ function isEvmAddress(value) {
186
+ return isAddress(value);
187
+ }
188
+ async function createCatenaAccount(client, options) {
189
+ const deposit = await client.getAccountDepositAddress(options.accountId, {
190
+ network: options.network ?? "base",
191
+ asset: "usdc"
192
+ });
193
+ if (deposit.source !== "wallet") throw new NotAWalletAccountError(options.accountId, deposit.source);
194
+ const address = getAddress(deposit.address);
195
+ return toAccount({
196
+ address,
197
+ signMessage: () => Promise.reject(new UnsupportedAccountCapabilityError("signMessage")),
198
+ signTransaction: () => Promise.reject(new UnsupportedAccountCapabilityError("signTransaction")),
199
+ signTypedData: (parameters) => signX402TypedData(client, {
200
+ accountId: options.accountId,
201
+ address,
202
+ parameters
203
+ })
204
+ });
205
+ }
206
+ async function signX402TypedData(client, args) {
207
+ const parsed = v.safeParse(typedDataInputSchema, args.parameters);
208
+ if (!parsed.success) throw new UnsupportedTypedDataError("message", "The typed data is not a TransferWithAuthorization payload this account can sign");
209
+ const { primaryType, domain, types, message } = parsed.output;
210
+ if (primaryType !== "TransferWithAuthorization") throw new UnsupportedTypedDataError("primary_type", `This account signs only TransferWithAuthorization typed data; got ${primaryType}`);
211
+ const transferType = types["TransferWithAuthorization"];
212
+ if (encodeTypeTable(transferType) !== CANONICAL_TRANSFER_TYPE) throw new UnsupportedTypedDataError("types", "The TransferWithAuthorization type definition does not match EIP-3009");
213
+ if (domain.chainId === void 0) throw new UnsupportedTypedDataError("network", "The typed data names no chainId");
214
+ const chain = Object.hasOwn(NETWORKS, String(domain.chainId)) ? NETWORKS[String(domain.chainId)] : void 0;
215
+ if (!chain) throw new UnsupportedTypedDataError("network", `Chain ${String(domain.chainId)} is not supported; use Base (8453) or Base Sepolia (84532)`);
216
+ if (domain.verifyingContract === void 0 || !isEvmAddress(domain.verifyingContract) || getAddress(domain.verifyingContract) !== chain.usdc) throw new UnsupportedTypedDataError("asset", `The verifying contract must be USDC ${chain.usdc} on chain ${String(domain.chainId)}`);
217
+ if (!domain.name || !domain.version) throw new UnsupportedTypedDataError("domain", "The typed-data domain must carry the token's name and version");
218
+ const unsupportedDomainFields = Object.entries(domain).filter(([key, value]) => !CANONICAL_DOMAIN_FIELDS.has(key) && value !== void 0).map(([key]) => key);
219
+ if (unsupportedDomainFields.length > 0) throw new UnsupportedTypedDataError("domain", `The typed-data domain carries fields this account cannot sign over: ${unsupportedDomainFields.join(", ")}`);
220
+ const domainType = types["EIP712Domain"];
221
+ if (domainType !== void 0 && encodeTypeTable(domainType) !== CANONICAL_DOMAIN_TYPE) throw new UnsupportedTypedDataError("domain", "The EIP712Domain type definition must be the canonical name, version, chainId, verifyingContract");
222
+ const nonce = message.nonce.toLowerCase();
223
+ if (!/^0x[0-9a-f]{64}$/.test(nonce) || !isHex(nonce)) throw new UnsupportedTypedDataError("nonce", "The nonce must be 32 bytes of hex");
224
+ if (!isEvmAddress(message.from) || getAddress(message.from) !== args.address) throw new UnsupportedTypedDataError("from_mismatch", `The authorization must draw from this account's wallet address ${args.address}`);
225
+ if (!isEvmAddress(message.to)) throw new UnsupportedTypedDataError("recipient", "The authorization's recipient must be a 20-byte hex address, either all-lowercase or with a valid EIP-55 checksum");
226
+ const nowSeconds = Math.floor(Date.now() / 1e3);
227
+ const validAfter = BigInt(toDecimalString(message.validAfter));
228
+ const validBefore = BigInt(toDecimalString(message.validBefore));
229
+ if (validBefore <= BigInt(nowSeconds + MIN_REMAINING_VALIDITY_SECONDS)) throw new UnsupportedTypedDataError("expired", "The authorization expires too soon to settle; build one with more validity");
230
+ if (validAfter >= validBefore) throw new UnsupportedTypedDataError("window", "The authorization expires before it becomes valid");
231
+ if (validAfter > BigInt(nowSeconds)) throw new UnsupportedTypedDataError("window", "The authorization must already be valid; it starts in the future");
232
+ if (validBefore > BigInt(nowSeconds + MAX_TIMEOUT_SECONDS + VALIDBEFORE_SKEW_SECONDS)) throw new UnsupportedTypedDataError("window", `The authorization is valid for longer than the ${MAX_TIMEOUT_SECONDS}s a payment may be authorized for`);
233
+ const authorization = {
234
+ from: message.from,
235
+ to: message.to,
236
+ value: toDecimalString(message.value),
237
+ validAfter: toDecimalString(message.validAfter),
238
+ validBefore: toDecimalString(message.validBefore),
239
+ nonce
240
+ };
241
+ const paymentRequirements = {
242
+ scheme: "exact",
243
+ network: chain.network,
244
+ asset: getAddress(domain.verifyingContract),
245
+ amount: authorization.value,
246
+ payTo: message.to,
247
+ maxTimeoutSeconds: Math.min(MAX_TIMEOUT_SECONDS, Math.max(1, Number(validBefore - validAfter))),
248
+ extra: {
249
+ name: domain.name,
250
+ version: domain.version
251
+ }
252
+ };
253
+ const intent = await client.submitIntent({
254
+ action: {
255
+ type: "x402",
256
+ accountId: args.accountId,
257
+ paymentRequirements,
258
+ authorization
259
+ },
260
+ idempotencyKey: crypto.randomUUID()
261
+ }).catch((err) => {
262
+ if (err instanceof IntentSubmitError && err.outcome === "unknown") throw new X402SubmitInterruptedError(err.intentId, paymentRequirements, err);
263
+ throw err;
264
+ });
265
+ if (intent.status === "pending") throw new X402ApprovalPendingError(intent.id, intent.reasons, intent.expiresAt);
266
+ if (intent.status === "blocked" || intent.status === "failed") throw new X402PaymentDeclinedError(intent.id, intent.reasons);
267
+ if (intent.status !== "completed") throw new X402PaymentInFlightError(intent.id, intent.status);
268
+ const paymentSignature = x402CredentialFromIntentData(intent.data);
269
+ if (paymentSignature === void 0) throw new X402PaymentSignatureUnusableError(intent.id, "The completed payment did not carry an x402 payment signature");
270
+ const envelope = v.safeParse(paymentSignatureEnvelopeSchema, decodeBase64Json(paymentSignature));
271
+ if (!envelope.success || !isHex(envelope.output.payload.signature)) throw new X402PaymentSignatureUnusableError(intent.id, "The payment signature payload could not be decoded");
272
+ const signature = envelope.output.payload.signature;
273
+ let recovered;
274
+ try {
275
+ recovered = await recoverTypedDataAddress({
276
+ domain: {
277
+ name: domain.name,
278
+ version: domain.version,
279
+ chainId: Number(domain.chainId),
280
+ verifyingContract: getAddress(domain.verifyingContract)
281
+ },
282
+ types: TRANSFER_WITH_AUTHORIZATION_TYPES,
283
+ primaryType: "TransferWithAuthorization",
284
+ message: {
285
+ from: getAddress(message.from),
286
+ to: getAddress(message.to),
287
+ value: BigInt(authorization.value),
288
+ validAfter: BigInt(authorization.validAfter),
289
+ validBefore: BigInt(authorization.validBefore),
290
+ nonce
291
+ },
292
+ signature
293
+ });
294
+ } catch {
295
+ recovered = void 0;
296
+ }
297
+ if (recovered !== args.address) throw new SignatureVerificationFailedError(intent.id, args.address, recovered);
298
+ return signature;
299
+ }
300
+ //#endregion
301
+ export { ApiError, IntentSubmitError, NotAWalletAccountError, SignatureVerificationFailedError, UnsupportedAccountCapabilityError, UnsupportedTypedDataError, X402ApprovalPendingError, X402PaymentDeclinedError, X402PaymentError, X402PaymentInFlightError, X402PaymentSignatureUnusableError, X402SubmitInterruptedError, createCatenaAccount };
@@ -0,0 +1,206 @@
1
+ import { G as X402Resource, W as X402PaymentRequirements, i as FetchLike, n as CatenaClient } from "./client-Cm2wNUuB.mjs";
2
+ import * as v from "valibot";
3
+ //#region src/x402.d.ts
4
+ declare const paymentRequiredSchema: v.LooseObjectSchema<{
5
+ /**
6
+ * Pinned to the wire version this loop speaks. Paying a v1 or future
7
+ * incompatible challenge would settle money for a retry header the server
8
+ * cannot consume.
9
+ */
10
+ readonly x402Version: v.LiteralSchema<2, undefined>;
11
+ readonly accepts: v.ArraySchema<v.LooseObjectSchema<{
12
+ readonly scheme: v.StringSchema<undefined>;
13
+ readonly network: v.StringSchema<undefined>;
14
+ readonly asset: v.StringSchema<undefined>;
15
+ readonly amount: v.StringSchema<undefined>;
16
+ readonly payTo: v.StringSchema<undefined>;
17
+ readonly maxTimeoutSeconds: v.NumberSchema<undefined>;
18
+ }, undefined>, undefined>;
19
+ readonly resource: v.OptionalSchema<v.LooseObjectSchema<{
20
+ readonly url: v.StringSchema<undefined>;
21
+ readonly serviceName: v.SchemaWithFallback<v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MaxLengthAction<string, 255, undefined>]>, undefined>, undefined>;
22
+ }, undefined>, undefined>;
23
+ /**
24
+ * x402 v2 extension envelope; may carry the seller's signed offers
25
+ * (offer-receipt extension). Untyped here — extraction is tolerant.
26
+ */
27
+ readonly extensions: v.OptionalSchema<v.UnknownSchema, undefined>;
28
+ }, undefined>;
29
+ type X402PaymentRequired = v.InferOutput<typeof paymentRequiredSchema>;
30
+ /**
31
+ * Decode a 402 response's `PAYMENT-REQUIRED` challenge. Returns undefined for
32
+ * a 402 that carries no decodable v2 challenge (not an x402 endpoint).
33
+ */
34
+ declare function decodePaymentRequired(response: Response): X402PaymentRequired | undefined;
35
+ /**
36
+ * Payment failure raised by this module; `reasons` carries the policy or
37
+ * decline detail when the server provided any, and subclasses mark the cases
38
+ * with a specific recovery path. Not every loop failure is one of these:
39
+ * a create-phase `ApiError` the loop does not absorb as a candidate skip
40
+ * and a "not-submitted" `IntentSubmitError` propagate unchanged. A create-phase
41
+ * `TimeoutError` also propagates unchanged; no payment was authorized, so
42
+ * re-running cannot double-pay.
43
+ */
44
+ declare class X402PaymentError extends Error {
45
+ readonly reasons: readonly string[];
46
+ constructor(message: string, reasons?: readonly string[],
47
+ /**
48
+ * Subclasses whose message is a single actionable recovery sentence pass
49
+ * `false` so `reasons` stays inspection detail on the property instead of
50
+ * restating server text mid-sentence in the primary output.
51
+ */
52
+ options?: {
53
+ appendReasonsToMessage?: boolean;
54
+ });
55
+ }
56
+ /**
57
+ * Every payable candidate in the challenge targets a network the source
58
+ * account is not on, so nothing could be funded. Distinct from a generic
59
+ * non-payable requirement: the fix is to fund the payment from a wallet
60
+ * account on one of `requiredNetworks`, not to change policy. Consumers can
61
+ * name those networks (and, for a CLI, tell the user how to switch accounts).
62
+ */
63
+ declare class X402NetworkMismatchError extends X402PaymentError {
64
+ readonly accountId: string;
65
+ readonly requiredNetworks: readonly string[];
66
+ constructor(accountId: string, requiredNetworks: readonly string[], reasons?: readonly string[]);
67
+ }
68
+ /**
69
+ * The challenge's `payTo` is not a saved counterparty wallet rail, so the
70
+ * allowlist has nothing to authorize and the payment fails closed. The 402's
71
+ * `payTo` is attacker-controlled, so Catena never auto-adds it: the fix is a
72
+ * deliberate `create_counterparty` (itself policy-gated) for `payTo` on
73
+ * `network`, then a re-run. Consumers can name the recipient (and, for a CLI,
74
+ * print the exact counterparty-create command).
75
+ */
76
+ declare class X402CounterpartyNotFoundError extends X402PaymentError {
77
+ readonly payTo: string;
78
+ readonly network: string;
79
+ /**
80
+ * The challenge's advertised service name, when present — a suggested
81
+ * counterparty name.
82
+ */
83
+ readonly serviceName: string | undefined;
84
+ constructor(payTo: string, network: string, serviceName?: string, reasons?: readonly string[]);
85
+ }
86
+ /**
87
+ * The payment exceeded a policy limit and is parked for a human approval.
88
+ * Not a decline: once an operator approves it in the console, re-running the
89
+ * same request (same endpoint, same price) consumes the approval and pays.
90
+ * `expiresAt` is when the pending approval self-expires.
91
+ */
92
+ declare class X402ApprovalPendingError extends X402PaymentError {
93
+ readonly intentId: string;
94
+ readonly expiresAt: string | null;
95
+ constructor(intentId: string, reasons: readonly string[], expiresAt: string | null);
96
+ }
97
+ /**
98
+ * The x402 loop failed AFTER the payment settled, either in `onPayment` or the
99
+ * paid retry. The paid retry may not have been issued. The receipt is the
100
+ * caller's recovery handle: retry the request manually with its
101
+ * `paymentSignature` as the `PAYMENT-SIGNATURE` header (also re-readable via
102
+ * `getIntent(intentId)` at `data.paymentCredential.value` while the
103
+ * authorization is valid) — re-running the whole loop would mint a second
104
+ * payment.
105
+ */
106
+ declare class X402RetryFailedError extends X402PaymentError {
107
+ readonly receipt: X402PaymentReceipt;
108
+ constructor(receipt: X402PaymentReceipt, cause: unknown);
109
+ }
110
+ /**
111
+ * The submission failed after the intent was created with its outcome
112
+ * unknown — the server may have already claimed and settled (a response lost
113
+ * in transit is the canonical case). Re-running the loop would mint a second
114
+ * intent and risk a double-pay, so the caller must reconcile via
115
+ * `getIntent(intentId)`: if it completed, reuse
116
+ * `data.paymentCredential.value` as the `PAYMENT-SIGNATURE` header; if
117
+ * processing, it is mid-settlement — keep polling. Pay again only once it is
118
+ * terminally blocked or failed.
119
+ */
120
+ declare class X402SubmitInterruptedError extends X402PaymentError {
121
+ readonly intentId: string;
122
+ readonly requirements: X402PaymentRequirements;
123
+ constructor(intentId: string, requirements: X402PaymentRequirements, cause: unknown);
124
+ }
125
+ interface X402PaymentReceipt {
126
+ /**
127
+ * The Catena intent that settled the payment; pass to `getIntent`.
128
+ */
129
+ intentId: string;
130
+ /**
131
+ * Encoded value for the retry's `PAYMENT-SIGNATURE` header. Re-readable
132
+ * via `getIntent(intentId)` at `data.paymentCredential.value` while the
133
+ * authorization is valid.
134
+ */
135
+ paymentSignature: string;
136
+ /**
137
+ * The challenge candidate that was paid.
138
+ */
139
+ requirements: X402PaymentRequirements;
140
+ }
141
+ /**
142
+ * Pay one decoded 402 challenge from `accountId` and return the
143
+ * `PAYMENT-SIGNATURE` header value. Tries the challenge's exact-scheme
144
+ * candidates in order, skipping ones Catena reports as unpayable (wrong
145
+ * network/asset). When nothing is payable, the terminal error reflects the
146
+ * most actionable skip — an unsaved counterparty, then an unfunded network —
147
+ * and every skipped candidate's reason rides its `reasons`. Failures the
148
+ * loop does not absorb as a skip — policy blocks, counterparty rejections,
149
+ * signing failures — surface as `X402PaymentError` (or the underlying
150
+ * `ApiError` / `IntentSubmitError`). A create-phase `TimeoutError`
151
+ * propagates unchanged; no payment was authorized, so re-running cannot
152
+ * double-pay.
153
+ */
154
+ declare function payX402Challenge(client: CatenaClient, args: {
155
+ accountId: string;
156
+ paymentRequired: X402PaymentRequired;
157
+ /**
158
+ * Display override for the challenge's own `resource` (for example a
159
+ * suggested counterparty name). The signed offer/receipt evidence is
160
+ * bound to the resolved resource's `url`, so overriding it with a URL
161
+ * that differs from the one the seller signed makes the seller's offer
162
+ * record a resource mismatch in its stored verification. The payment
163
+ * itself is unaffected. Omit to use the challenge's resource unchanged.
164
+ */
165
+ resource?: X402Resource;
166
+ /**
167
+ * Per-call ceiling in atomic USDC units (6 decimals; "1000000" = $1).
168
+ * The org policy's spend limits still govern server-side; this lets the
169
+ * caller refuse a price it never meant to accept, before any intent.
170
+ */
171
+ maxAtomicAmount?: bigint;
172
+ }): Promise<X402PaymentReceipt>;
173
+ interface X402FetchOptions {
174
+ /**
175
+ * Source wallet account the payments draw from.
176
+ */
177
+ accountId: string;
178
+ baseFetch?: FetchLike;
179
+ /**
180
+ * Per-call price ceiling in atomic USDC units (6 decimals). Challenges
181
+ * asking for more are refused before any intent is created.
182
+ */
183
+ maxAtomicAmount?: bigint;
184
+ /**
185
+ * Called after each successful payment, before the retry — for receipts,
186
+ * logging, or spend telemetry. A thrown error becomes an
187
+ * `X402RetryFailedError` carrying the receipt.
188
+ */
189
+ onPayment?: (receipt: X402PaymentReceipt) => void | Promise<void>;
190
+ }
191
+ /**
192
+ * Wrap `fetch` so a v2 x402 402 is paid via Catena and retried once with the
193
+ * `PAYMENT-SIGNATURE` header. Non-402 responses (and 402s without a decodable
194
+ * challenge) pass through untouched. Throws `X402PaymentError` before paying
195
+ * when the request body cannot be replayed — pass bodies via the `init`
196
+ * argument as a string, `URLSearchParams`, Blob, ArrayBuffer, or typed
197
+ * array/DataView; a `Request` input must be bodiless. Payment failures surface
198
+ * as `X402PaymentError` (or a subclass documenting its recovery path); an
199
+ * `ApiError` the loop does not absorb as a candidate skip and a "not-submitted"
200
+ * `IntentSubmitError` propagate unchanged. A create-phase `TimeoutError` also
201
+ * propagates unchanged; no payment was authorized, so re-running cannot
202
+ * double-pay.
203
+ */
204
+ declare function wrapFetchWithX402Payment(client: CatenaClient, options: X402FetchOptions): FetchLike;
205
+ //#endregion
206
+ export { X402ApprovalPendingError, X402CounterpartyNotFoundError, X402FetchOptions, X402NetworkMismatchError, X402PaymentError, X402PaymentReceipt, X402PaymentRequired, type X402PaymentRequirements, type X402Resource, X402RetryFailedError, X402SubmitInterruptedError, decodePaymentRequired, payX402Challenge, wrapFetchWithX402Payment };