@inflowpayai/x402-buyer 0.5.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,427 @@
1
+ import { x402Client, PaymentPolicy, ClientExtension, BeforePaymentCreationHook, AfterPaymentCreationHook, OnPaymentCreationFailureHook, OnPaymentResponseHook } from '@x402/core/client';
2
+ import { PaymentRequired, PaymentPayload, Network, SchemeNetworkClient } from '@x402/core/types';
3
+ import { InflowPaymentPayload, PaymentScheme, PaymentRequirements, ResourceInfo, X402BuyerSupportedResponse, InflowClientOptions, InstrumentType } from '@inflowpayai/x402';
4
+
5
+ /**
6
+ * Status of an x402 transaction's signing flow. `'INITIATED'` means the server-side approval is still pending. Any
7
+ * other value indicates the approval has cleared — successful when paired with an `encodedPayload`, failed otherwise.
8
+ * The SDK treats values other than `'INITIATED'` opaquely so new statuses don't require client changes.
9
+ */
10
+ type TransactionStatus = 'INITIATED' | (string & {});
11
+ /**
12
+ * Status of a buyer-side approval. `'APPROVED'` means the server has synchronously signed; `'PENDING'` means the buyer
13
+ * must approve in their dashboard. Other terminal values (declined, cancelled, etc.) are surfaced verbatim and treated
14
+ * opaquely.
15
+ */
16
+ type ApprovalStatus = 'APPROVED' | 'PENDING' | (string & {});
17
+ /**
18
+ * Response body of `POST /v1/transactions/x402`. The buyer creates a transaction and Approval; this is what the server
19
+ * returns synchronously.
20
+ */
21
+ interface X402TransactionResponse {
22
+ approvalId: string;
23
+ approvalStatus: ApprovalStatus;
24
+ transactionId: string;
25
+ amount: string;
26
+ currency: string;
27
+ resource?: ResourceInfo;
28
+ }
29
+ /**
30
+ * Response body of `GET /v1/transactions/{transactionId}/x402`. While `status === 'INITIATED'`, neither
31
+ * `encodedPayload` nor `paymentPayload` are populated. Once the server has signed, both appear together.
32
+ */
33
+ interface X402PayloadResponse {
34
+ status: TransactionStatus;
35
+ encodedPayload?: string;
36
+ paymentPayload?: InflowPaymentPayload;
37
+ }
38
+ /**
39
+ * Context the buyer learned from the seller's 402 response, threaded into `prepare()` / `sign()` so the server-side
40
+ * `POST /v1/transactions/x402` endpoint receives the seller's exact `resource` and `x402Version`.
41
+ */
42
+ interface SigningContext {
43
+ /** From `PaymentRequired.resource`. */
44
+ resource: ResourceInfo;
45
+ /** From `PaymentRequired.x402Version`. Should be `2`. */
46
+ x402Version: number;
47
+ /**
48
+ * From `PaymentRequired.extensions`. The signer dispatches handlers registered in the core extensions registry for
49
+ * the names it sees here.
50
+ */
51
+ extensions?: Record<string, unknown>;
52
+ }
53
+ /**
54
+ * Output of a successful signing. `encodedPayload` is the base64 string to set as the `PAYMENT-SIGNATURE` header — the
55
+ * SDK never re-encodes the server-produced value.
56
+ */
57
+ interface EncodedPayment {
58
+ /** Base64-encoded `InflowPaymentPayload` (re-exported from `@inflowpayai/x402`). */
59
+ encodedPayload: string;
60
+ /** Parsed payload for inspection. */
61
+ paymentPayload: InflowPaymentPayload;
62
+ /**
63
+ * Server-side transaction id for correlation. Present when the InFlow-signed branch produced this payment; absent on
64
+ * the foundation-signed branch (no InFlow Approval was created).
65
+ */
66
+ transactionId?: string;
67
+ }
68
+ /** Per-call options accepted by every signing entry point. */
69
+ interface SignOptions {
70
+ /**
71
+ * Poll cadence while approval is `'INITIATED'`. Default 5000 ms (fixed — no exponential backoff, no jitter; the
72
+ * polling loop is itself the retry mechanism for the approval window).
73
+ */
74
+ pollIntervalMs?: number;
75
+ /**
76
+ * Hard timeout for the full sign / `awaitPayload` call. Default 900 000 ms (15 minutes) — matches the server-side
77
+ * approval expiry.
78
+ */
79
+ timeoutMs?: number;
80
+ /** Cooperative cancellation. */
81
+ signal?: AbortSignal;
82
+ /**
83
+ * Caller-supplied payment identifier forwarded to the server's `remotePaymentId` field. Validated client-side via the
84
+ * `payment-identifier` extension rules (16–128 chars, `^[a-zA-Z0-9_-]+$`); invalid values throw
85
+ * {@link X402PaymentIdFormatError} before any server round trip.
86
+ */
87
+ paymentId?: string;
88
+ }
89
+ /** Constructor options for {@link createInflowClient}. */
90
+ interface SignerOptions extends InflowClientOptions {
91
+ /**
92
+ * Ordered scheme preference used when picking among multiple InFlow-acceptable requirements inside
93
+ * {@link InflowClient.createPaymentPayload}. Default `['balance', 'exact']`.
94
+ */
95
+ prefer?: PaymentScheme[];
96
+ /** Reserved instrument-scheme configuration. */
97
+ instrument?: {
98
+ id?: string;
99
+ types?: InstrumentType[];
100
+ };
101
+ /** Default poll / timeout / paymentId values applied to every signing call. */
102
+ signDefaults?: SignOptions;
103
+ }
104
+ /**
105
+ * Handle returned by {@link InflowClient.prepareInflowPayment}. The transaction + approval have already been created
106
+ * server-side; the caller decides when to await the signed payload.
107
+ */
108
+ interface PreparedPayment {
109
+ readonly transactionId: string;
110
+ readonly approvalId: string;
111
+ /**
112
+ * Poll `GET /v1/transactions/{transactionId}/x402` at `pollIntervalMs` cadence until the server has signed, the call
113
+ * times out, or the caller's `AbortSignal` aborts.
114
+ *
115
+ * @param options - Per-call overrides. Concurrent callers share the underlying loop; only the FIRST call's
116
+ * `pollIntervalMs` / `timeoutMs` are honored.
117
+ * @returns The signed {@link EncodedPayment}.
118
+ * @throws {@link X402ApprovalFailedError} When the server moves out of `'INITIATED'` without producing an
119
+ * `encodedPayload`.
120
+ * @throws {@link X402ApprovalTimeoutError} When wall-clock exceeds `timeoutMs` or the caller's `signal` aborts.
121
+ */
122
+ awaitPayload(options?: SignOptions): Promise<EncodedPayment>;
123
+ /**
124
+ * One-shot poll: returns the current {@link TransactionStatus} without waiting.
125
+ *
126
+ * @returns The latest status reported by the server.
127
+ */
128
+ status(): Promise<TransactionStatus>;
129
+ /**
130
+ * Best-effort cancel of the underlying server-side approval. Calls `POST /v1/approvals/{approvalId}/cancel`.
131
+ * **Fire-and-forget by design** — always resolves, never rejects. Use to release a `PreparedPayment` the caller no
132
+ * longer intends to await.
133
+ *
134
+ * @returns A promise that always resolves.
135
+ */
136
+ cancel(): Promise<void>;
137
+ }
138
+ /**
139
+ * Minimal buyer-side signer contract. Implementation detail of {@link InflowClient}; not re-exported from the package
140
+ * barrel.
141
+ *
142
+ * @internal
143
+ */
144
+ interface Signer {
145
+ /** Ordered scheme preference this signer would like callers to honor. */
146
+ readonly prefer: readonly PaymentScheme[];
147
+ /**
148
+ * Set of extension names this signer can satisfy on the buyer side. Used to filter `accepts[]` entries whose
149
+ * `PaymentRequired` declares a `required: true` extension this signer cannot handle.
150
+ */
151
+ readonly extensionsHandled: ReadonlySet<string>;
152
+ /**
153
+ * Synchronous predicate: does this signer know how to sign the given requirement?
154
+ *
155
+ * @param requirement - The candidate {@link PaymentRequirements}.
156
+ * @returns `true` if `sign(requirement, ctx)` is expected to succeed.
157
+ */
158
+ supports(requirement: PaymentRequirements): boolean;
159
+ /**
160
+ * Single-shot signing. Produces a base64-encoded {@link InflowPaymentPayload} ready to set as the `PAYMENT-SIGNATURE`
161
+ * header.
162
+ *
163
+ * @param requirement - The chosen {@link PaymentRequirements}.
164
+ * @param context - Seller-side {@link SigningContext} (resource + `x402Version` + extensions declarations).
165
+ * @param options - Per-call {@link SignOptions}.
166
+ * @returns The signed {@link EncodedPayment}.
167
+ */
168
+ sign(requirement: PaymentRequirements, context: SigningContext, options?: SignOptions): Promise<EncodedPayment>;
169
+ }
170
+ /**
171
+ * InFlow-specific buyer signer. Adds the InFlow-server capability table, priming hooks, and a two-phase `prepare()` /
172
+ * `awaitPayload()` flow for callers that want to surface pending-approval UI before the protected request.
173
+ * Implementation detail of {@link InflowClient}; not re-exported from the package barrel.
174
+ *
175
+ * @internal
176
+ */
177
+ interface InflowSigner extends Signer {
178
+ /**
179
+ * Idempotent no-op for callers that have a long-lived signer and want to assert readiness explicitly. The async
180
+ * factory has already primed the capability cache; `ready()` exists for ergonomics only.
181
+ *
182
+ * @returns A resolved promise.
183
+ */
184
+ ready(): Promise<void>;
185
+ /**
186
+ * Return the cached buyer capability set. 60-min TTL.
187
+ *
188
+ * @returns The {@link X402BuyerSupportedResponse}.
189
+ */
190
+ getSupported(): Promise<X402BuyerSupportedResponse>;
191
+ /**
192
+ * Force a refetch of the buyer capability table.
193
+ *
194
+ * @returns The freshly fetched {@link X402BuyerSupportedResponse}.
195
+ */
196
+ refreshSupported(): Promise<X402BuyerSupportedResponse>;
197
+ /**
198
+ * Kick off the buyer's transaction and Approval and return a handle the caller can await independently.
199
+ *
200
+ * @param requirement - The chosen {@link PaymentRequirements}.
201
+ * @param context - Seller-side {@link SigningContext}.
202
+ * @param options - Per-call {@link SignOptions}.
203
+ * @returns A {@link PreparedPayment} handle.
204
+ * @throws {@link X402PaymentIdFormatError} When `options.paymentId` is set but doesn't satisfy `validatePaymentId`.
205
+ */
206
+ prepare(requirement: PaymentRequirements, context: SigningContext, options?: SignOptions): Promise<PreparedPayment>;
207
+ }
208
+
209
+ /**
210
+ * Subclass of `@x402/core`'s `x402Client` that adds the InFlow MPC signing branch and the two-phase
211
+ * {@link InflowClient.prepareInflowPayment} flow. Construct via {@link createInflowClient}.
212
+ */
213
+ declare class InflowClient extends x402Client {
214
+ /**
215
+ * The InFlow-signed branch of the routing decision. Kept private — callers compose by passing the {@link InflowClient}
216
+ * to `x402HTTPClient` (and to `registerExactEvmScheme` / `registerExactSvmScheme` for foundation-signed networks).
217
+ */
218
+ private readonly inflowSigner;
219
+ /**
220
+ * Ordered scheme preference used when picking among multiple InFlow-acceptable requirements. Sourced from the InFlow
221
+ * signer at construction so the buyer's intent (`prefer: ['balance', 'exact']` by default) survives across the
222
+ * override.
223
+ */
224
+ private readonly preferOrder;
225
+ /**
226
+ * Construct via {@link createInflowClient}. The factory primes the buyer capability cache before resolving, so the
227
+ * routing decision in {@link InflowClient.createPaymentPayload} is synchronous against in-memory data. The constructor
228
+ * is exported only so the class is referenceable for `instanceof` checks, generic constraints, and return types.
229
+ *
230
+ * @param inflowSigner - Primed InFlow signer carrying the buyer capability cache, MPC signing flow, and prefer order.
231
+ * @internal
232
+ */
233
+ constructor(inflowSigner: InflowSigner);
234
+ /**
235
+ * Routing override. InFlow signs when the buyer capability cache covers a requirement (preferred-scheme order);
236
+ * otherwise the foundation signs and any registered extension handlers are folded into `payload.extensions`. A
237
+ * `required: true` extension whose handler returns `null` throws.
238
+ */
239
+ createPaymentPayload(paymentRequired: PaymentRequired): Promise<PaymentPayload>;
240
+ /**
241
+ * Two-phase signing flow for callers that want to surface pending- approval UI before the protected request is
242
+ * replayed. Forwarded to the InFlow signer's `prepare()`; returns a {@link PreparedPayment} the caller can
243
+ * `awaitPayload()` or `cancel()` independently.
244
+ *
245
+ * Has no foundation equivalent — `x402Client.createPaymentPayload` is one-shot. The handle is InFlow-specific and
246
+ * only applies to requirements InFlow can sign.
247
+ *
248
+ * @param requirement - The chosen `PaymentRequirements` (re-exported from `@inflowpayai/x402`) — must match an entry
249
+ * in the InFlow buyer capability cache.
250
+ * @param context - Seller-side {@link SigningContext}.
251
+ * @param options - Per-call {@link SignOptions}.
252
+ * @returns A {@link PreparedPayment} handle.
253
+ * @throws {@link X402AdapterRoutingError} When the requirement is not in the InFlow buyer capability cache
254
+ * (foundation-signed requirements have no two-phase flow).
255
+ */
256
+ prepareInflowPayment(requirement: PaymentRequirements, context: SigningContext, options?: SignOptions): Promise<PreparedPayment>;
257
+ register(network: Network, schemeNetworkClient: SchemeNetworkClient): this;
258
+ registerV1(network: string, schemeNetworkClient: SchemeNetworkClient): this;
259
+ registerPolicy(policy: PaymentPolicy): this;
260
+ registerExtension(extension: ClientExtension): this;
261
+ onBeforePaymentCreation(hook: BeforePaymentCreationHook): this;
262
+ onAfterPaymentCreation(hook: AfterPaymentCreationHook): this;
263
+ onPaymentCreationFailure(hook: OnPaymentCreationFailureHook): this;
264
+ onPaymentResponse(hook: OnPaymentResponseHook): this;
265
+ /**
266
+ * Pick the buyer's preferred `accepts[]` entry the InFlow signer can handle. Walks {@link InflowClient.preferOrder}
267
+ * and returns the first entry whose `(scheme, network)` is in the InFlow capability cache; returns `null` when no
268
+ * entry matches (the foundation branch takes over).
269
+ */
270
+ private pickInflowMatch;
271
+ }
272
+ /**
273
+ * Async factory for {@link InflowClient}. Constructs the InFlow signer (which primes the buyer-supported cache) and
274
+ * attaches it to a fresh `InflowClient` instance.
275
+ *
276
+ * Foundation-managed scheme registrations (`registerExactEvmScheme(client, …)`, `registerExactSvmScheme(client, …)`)
277
+ * are applied to the returned instance by the caller after this factory resolves.
278
+ *
279
+ * @param options - {@link SignerOptions}.
280
+ * @returns A primed {@link InflowClient} ready for `x402HTTPClient` and any further foundation scheme registrations.
281
+ */
282
+ declare function createInflowClient(options: SignerOptions): Promise<InflowClient>;
283
+
284
+ /**
285
+ * Normalize a secp256k1 secret to viem's `0x`-prefixed 32-byte hex form. Accepts `0x`-prefixed hex, bare hex, or an
286
+ * InFlow Java seed (`BigInteger.toByteArray()` output — two's-complement, so 33 bytes with a leading sign byte for
287
+ * high-bit-set secrets, or short when leading zero bytes were dropped). Both edges renormalize to 32 bytes.
288
+ *
289
+ * @example
290
+ *
291
+ * ```ts
292
+ * import { privateKeyToAccount } from 'viem/accounts';
293
+ * import { parseEvmPrivateKey } from '@inflowpayai/x402-buyer';
294
+ *
295
+ * const account = privateKeyToAccount(parseEvmPrivateKey(process.env.EVM_PRIVATE_KEY!));
296
+ * ```
297
+ *
298
+ * @throws {@link X402InvalidEvmKeyError} On non-hex input or a payload that doesn't reduce to exactly 32 bytes.
299
+ */
300
+ declare function parseEvmPrivateKey(value: string): `0x${string}`;
301
+
302
+ /**
303
+ * Decode a Solana secret into the 64-byte Ed25519 form expected by `@solana/kit`'s `createKeyPairSignerFromBytes`.
304
+ * Auto-detects between a JSON byte array (`[...]`, as written by `solana-keygen`) and base58 (as emitted by InFlow's
305
+ * `SolanaClient.Account.getSeed()` and by Phantom's exported secret).
306
+ *
307
+ * @example
308
+ *
309
+ * ```ts
310
+ * import { createKeyPairSignerFromBytes } from '@solana/kit';
311
+ * import { decodeSolanaSecret } from '@inflowpayai/x402-buyer';
312
+ *
313
+ * const bytes = decodeSolanaSecret(process.env.SOLANA_PRIVATE_KEY!);
314
+ * const signer = await createKeyPairSignerFromBytes(bytes);
315
+ * ```
316
+ *
317
+ * @throws {@link X402InvalidSolanaKeyError} On empty input, unparseable payloads, or anything that doesn't decode to
318
+ * exactly 64 bytes.
319
+ */
320
+ declare function decodeSolanaSecret(value: string): Uint8Array;
321
+
322
+ /**
323
+ * Thrown by `awaitPayload` when the server moves the approval out of `'INITIATED'` without producing an
324
+ * `encodedPayload` — the server has decided not to sign (insufficient funds, user-rejected, internal error, etc.). The
325
+ * terminal `status` string is surfaced verbatim so callers can branch on it without the SDK having to enumerate every
326
+ * server-side failure state.
327
+ */
328
+ declare class X402ApprovalFailedError extends Error {
329
+ /** The approval id the server returned from `POST /v1/transactions/x402`. */
330
+ readonly approvalId: string;
331
+ /** Terminal status reported by the server. */
332
+ readonly status: TransactionStatus;
333
+ /**
334
+ * @param approvalId - Server-issued approval id.
335
+ * @param status - Terminal status string from the polling response.
336
+ */
337
+ constructor(approvalId: string, status: TransactionStatus);
338
+ }
339
+ /**
340
+ * Thrown by `awaitPayload` when `cancel()` is called on the same `PreparedPayment`. The cancel is fire-and-forget
341
+ * against the server; the SDK exits the polling loop immediately and never returns the partially-fetched state to the
342
+ * caller.
343
+ */
344
+ declare class X402ApprovalCancelledError extends Error {
345
+ /** The approval id the server returned from `POST /v1/transactions/x402`. */
346
+ readonly approvalId: string;
347
+ constructor(approvalId: string);
348
+ }
349
+ /**
350
+ * Thrown by `awaitPayload` when wall-clock exceeds `timeoutMs` or the caller's `signal` aborts before the server has
351
+ * signed.
352
+ */
353
+ declare class X402ApprovalTimeoutError extends Error {
354
+ /** The approval id the server returned from `POST /v1/transactions/x402`. */
355
+ readonly approvalId: string;
356
+ /** Effective timeout in milliseconds. */
357
+ readonly timeoutMs: number;
358
+ /**
359
+ * @param approvalId - Server-issued approval id.
360
+ * @param timeoutMs - The configured timeout that elapsed.
361
+ */
362
+ constructor(approvalId: string, timeoutMs: number);
363
+ }
364
+ /**
365
+ * Thrown by `prepare()` / `sign()` when `SignOptions.paymentId` does not satisfy the `payment-identifier` extension's
366
+ * format rules (16–128 chars, `^[a-zA-Z0-9_-]+$`). Surfaced client-side before any server round trip.
367
+ */
368
+ declare class X402PaymentIdFormatError extends Error {
369
+ /** The offending input. */
370
+ readonly input: string;
371
+ /** @param input - The {@link SignOptions.paymentId} value that failed validation. */
372
+ constructor(input: string);
373
+ }
374
+ /**
375
+ * Thrown by {@link InflowClient.prepareInflowPayment} when the caller asks the two-phase flow for a requirement the
376
+ * InFlow signer cannot sign. The two-phase flow only exists for InFlow-signed requirements; foundation-signed schemes
377
+ * have no equivalent.
378
+ */
379
+ declare class X402AdapterRoutingError extends Error {
380
+ /** Scheme of the requirement the adapter could not route. */
381
+ readonly scheme: string;
382
+ /** Network of the requirement the adapter could not route. */
383
+ readonly network: string;
384
+ /**
385
+ * @param scheme - Scheme of the offending requirement.
386
+ * @param network - Network of the offending requirement.
387
+ */
388
+ constructor(scheme: string, network: string);
389
+ }
390
+ /**
391
+ * Thrown by `parseEvmPrivateKey` when the input cannot be normalized to a 32-byte secp256k1 secret. The accepted input
392
+ * forms are `0x`-prefixed hex, bare hex, and InFlow's Java `Hex.encodeHexString(BigInteger.toByteArray())` seed
393
+ * (including the 33-byte sign-byte and sub-32-byte leading-zero-stripped shapes).
394
+ *
395
+ * The raw input is intentionally **not** preserved on the error: a decoding failure can be the user pasting a real key
396
+ * in the wrong encoding (e.g. a Solana base58 secret into the EVM decoder), so the offending value is treated as
397
+ * potential cryptographic material. Use {@link X402InvalidEvmKeyError.reason} for diagnostics; never log the input.
398
+ */
399
+ declare class X402InvalidEvmKeyError extends Error {
400
+ /** Why the input failed validation. Never contains key bytes. */
401
+ readonly reason: string;
402
+ /**
403
+ * @param reason - Short explanation appended to the error message. Must not contain the raw input — produce a length
404
+ * / shape description instead (e.g. `'expected 32 bytes, got 31'`).
405
+ */
406
+ constructor(reason: string);
407
+ }
408
+ /**
409
+ * Thrown by `decodeSolanaSecret` when the input cannot be decoded to a 64-byte Ed25519 secret key. Accepted input forms
410
+ * are a base58 string (matches InFlow's `SolanaClient.Account.getSeed()` and Phantom's export) or a JSON byte array
411
+ * (matches `solana-keygen`'s output file).
412
+ *
413
+ * The raw input is intentionally **not** preserved on the error: a decoding failure can be the user pasting a real key
414
+ * in the wrong encoding (e.g. a base58 secret with one character mistyped), so the offending value is treated as
415
+ * potential cryptographic material. Use {@link X402InvalidSolanaKeyError.reason} for diagnostics; never log the input.
416
+ */
417
+ declare class X402InvalidSolanaKeyError extends Error {
418
+ /** Why the input failed validation. Never contains key bytes. */
419
+ readonly reason: string;
420
+ /**
421
+ * @param reason - Short explanation appended to the error message. Must not contain the raw input — produce a length
422
+ * / shape description instead (e.g. `'expected 64 bytes, got 32'`).
423
+ */
424
+ constructor(reason: string);
425
+ }
426
+
427
+ export { type ApprovalStatus, type EncodedPayment, InflowClient, type PreparedPayment, type SignOptions, type SignerOptions, type SigningContext, type TransactionStatus, X402AdapterRoutingError, X402ApprovalCancelledError, X402ApprovalFailedError, X402ApprovalTimeoutError, X402InvalidEvmKeyError, X402InvalidSolanaKeyError, type X402PayloadResponse, X402PaymentIdFormatError, type X402TransactionResponse, createInflowClient, decodeSolanaSecret, parseEvmPrivateKey };