@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.
- package/LICENSE +21 -0
- package/README.md +129 -0
- package/dist/index.cjs +592 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +427 -0
- package/dist/index.d.ts +427 -0
- package/dist/index.js +576 -0
- package/dist/index.js.map +1 -0
- package/package.json +78 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var client = require('@x402/core/client');
|
|
4
|
+
var extensions = require('@inflowpayai/x402/extensions');
|
|
5
|
+
var x402 = require('@inflowpayai/x402');
|
|
6
|
+
var bs58 = require('bs58');
|
|
7
|
+
|
|
8
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
|
+
|
|
10
|
+
var bs58__default = /*#__PURE__*/_interopDefault(bs58);
|
|
11
|
+
|
|
12
|
+
// src/inflow-client.ts
|
|
13
|
+
|
|
14
|
+
// src/errors.ts
|
|
15
|
+
var X402ApprovalFailedError = class extends Error {
|
|
16
|
+
/** The approval id the server returned from `POST /v1/transactions/x402`. */
|
|
17
|
+
approvalId;
|
|
18
|
+
/** Terminal status reported by the server. */
|
|
19
|
+
status;
|
|
20
|
+
/**
|
|
21
|
+
* @param approvalId - Server-issued approval id.
|
|
22
|
+
* @param status - Terminal status string from the polling response.
|
|
23
|
+
*/
|
|
24
|
+
constructor(approvalId, status) {
|
|
25
|
+
super(`Approval ${approvalId} terminated as ${status} with no payload`);
|
|
26
|
+
this.name = "X402ApprovalFailedError";
|
|
27
|
+
this.approvalId = approvalId;
|
|
28
|
+
this.status = status;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var X402ApprovalCancelledError = class extends Error {
|
|
32
|
+
/** The approval id the server returned from `POST /v1/transactions/x402`. */
|
|
33
|
+
approvalId;
|
|
34
|
+
constructor(approvalId) {
|
|
35
|
+
super(`Approval ${approvalId} cancelled by caller`);
|
|
36
|
+
this.name = "X402ApprovalCancelledError";
|
|
37
|
+
this.approvalId = approvalId;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var X402ApprovalTimeoutError = class extends Error {
|
|
41
|
+
/** The approval id the server returned from `POST /v1/transactions/x402`. */
|
|
42
|
+
approvalId;
|
|
43
|
+
/** Effective timeout in milliseconds. */
|
|
44
|
+
timeoutMs;
|
|
45
|
+
/**
|
|
46
|
+
* @param approvalId - Server-issued approval id.
|
|
47
|
+
* @param timeoutMs - The configured timeout that elapsed.
|
|
48
|
+
*/
|
|
49
|
+
constructor(approvalId, timeoutMs) {
|
|
50
|
+
super(`Approval ${approvalId} not signed within ${String(timeoutMs)}ms`);
|
|
51
|
+
this.name = "X402ApprovalTimeoutError";
|
|
52
|
+
this.approvalId = approvalId;
|
|
53
|
+
this.timeoutMs = timeoutMs;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
var X402PaymentIdFormatError = class extends Error {
|
|
57
|
+
/** The offending input. */
|
|
58
|
+
input;
|
|
59
|
+
/** @param input - The {@link SignOptions.paymentId} value that failed validation. */
|
|
60
|
+
constructor(input) {
|
|
61
|
+
super(`Invalid paymentId ${JSON.stringify(input)}; must be 16-128 chars matching ^[a-zA-Z0-9_-]+$`);
|
|
62
|
+
this.name = "X402PaymentIdFormatError";
|
|
63
|
+
this.input = input;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
var X402AdapterRoutingError = class extends Error {
|
|
67
|
+
/** Scheme of the requirement the adapter could not route. */
|
|
68
|
+
scheme;
|
|
69
|
+
/** Network of the requirement the adapter could not route. */
|
|
70
|
+
network;
|
|
71
|
+
/**
|
|
72
|
+
* @param scheme - Scheme of the offending requirement.
|
|
73
|
+
* @param network - Network of the offending requirement.
|
|
74
|
+
*/
|
|
75
|
+
constructor(scheme, network) {
|
|
76
|
+
super(
|
|
77
|
+
`InflowClient cannot route requirement (scheme: "${scheme}", network: "${network}"): not in the InFlow buyer capability cache and no two-phase flow exists for foundation-signed schemes`
|
|
78
|
+
);
|
|
79
|
+
this.name = "X402AdapterRoutingError";
|
|
80
|
+
this.scheme = scheme;
|
|
81
|
+
this.network = network;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
var X402InvalidEvmKeyError = class extends Error {
|
|
85
|
+
/** Why the input failed validation. Never contains key bytes. */
|
|
86
|
+
reason;
|
|
87
|
+
/**
|
|
88
|
+
* @param reason - Short explanation appended to the error message. Must not contain the raw input — produce a length
|
|
89
|
+
* / shape description instead (e.g. `'expected 32 bytes, got 31'`).
|
|
90
|
+
*/
|
|
91
|
+
constructor(reason) {
|
|
92
|
+
super(`Invalid EVM private key: ${reason}`);
|
|
93
|
+
this.name = "X402InvalidEvmKeyError";
|
|
94
|
+
this.reason = reason;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
var X402InvalidSolanaKeyError = class extends Error {
|
|
98
|
+
/** Why the input failed validation. Never contains key bytes. */
|
|
99
|
+
reason;
|
|
100
|
+
/**
|
|
101
|
+
* @param reason - Short explanation appended to the error message. Must not contain the raw input — produce a length
|
|
102
|
+
* / shape description instead (e.g. `'expected 64 bytes, got 32'`).
|
|
103
|
+
*/
|
|
104
|
+
constructor(reason) {
|
|
105
|
+
super(
|
|
106
|
+
`Invalid Solana private key: ${reason}. Expected a 64-byte Ed25519 secret key, supplied either as a base58 string (matches InFlow SolanaClient.Account.getSeed()) or a JSON byte array (matches solana-keygen).`
|
|
107
|
+
);
|
|
108
|
+
this.name = "X402InvalidSolanaKeyError";
|
|
109
|
+
this.reason = reason;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// src/_foundation-bridge.ts
|
|
114
|
+
function fromFoundationRequirements(r) {
|
|
115
|
+
return r;
|
|
116
|
+
}
|
|
117
|
+
function toFoundationPayload(p) {
|
|
118
|
+
return p;
|
|
119
|
+
}
|
|
120
|
+
var SUPPORTED_PATH = "/v1/transactions/x402-supported";
|
|
121
|
+
var TRANSACTIONS_PATH = "/v1/transactions/x402";
|
|
122
|
+
var APPROVAL_CANCEL_PATH = (id) => `/v1/approvals/${id}/cancel`;
|
|
123
|
+
var TRANSACTION_X402_PATH = (id) => `/v1/transactions/${id}/x402`;
|
|
124
|
+
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
125
|
+
var DEFAULT_POLL_INTERVAL_MS = 5e3;
|
|
126
|
+
var DEFAULT_TIMEOUT_MS = 15 * 60 * 1e3;
|
|
127
|
+
var DEFAULT_PREFER = ["balance", "exact"];
|
|
128
|
+
var APPROVAL_APPROVED = "APPROVED";
|
|
129
|
+
async function createInflowSigner(options) {
|
|
130
|
+
const http = new x402.InflowHttpClient(options);
|
|
131
|
+
const prefer = options.prefer ?? DEFAULT_PREFER;
|
|
132
|
+
const extensionsHandled = new Set(extensions.EXTENSION_REGISTRY.keys());
|
|
133
|
+
const supportedCache = {
|
|
134
|
+
value: void 0,
|
|
135
|
+
expiresAt: 0,
|
|
136
|
+
inFlight: void 0
|
|
137
|
+
};
|
|
138
|
+
async function fetchSupported() {
|
|
139
|
+
const fresh = await http.get(SUPPORTED_PATH);
|
|
140
|
+
supportedCache.value = fresh;
|
|
141
|
+
supportedCache.expiresAt = Date.now() + CACHE_TTL_MS;
|
|
142
|
+
return fresh;
|
|
143
|
+
}
|
|
144
|
+
function getSupported() {
|
|
145
|
+
if (supportedCache.value !== void 0 && Date.now() < supportedCache.expiresAt) {
|
|
146
|
+
return Promise.resolve(supportedCache.value);
|
|
147
|
+
}
|
|
148
|
+
if (supportedCache.inFlight !== void 0) return supportedCache.inFlight;
|
|
149
|
+
const inFlight = fetchSupported().finally(() => {
|
|
150
|
+
supportedCache.inFlight = void 0;
|
|
151
|
+
});
|
|
152
|
+
supportedCache.inFlight = inFlight;
|
|
153
|
+
return inFlight;
|
|
154
|
+
}
|
|
155
|
+
function refreshSupported() {
|
|
156
|
+
if (supportedCache.inFlight !== void 0) return supportedCache.inFlight;
|
|
157
|
+
const inFlight = fetchSupported().finally(() => {
|
|
158
|
+
supportedCache.inFlight = void 0;
|
|
159
|
+
});
|
|
160
|
+
supportedCache.inFlight = inFlight;
|
|
161
|
+
return inFlight;
|
|
162
|
+
}
|
|
163
|
+
function supports(requirement) {
|
|
164
|
+
const cached = supportedCache.value;
|
|
165
|
+
if (cached === void 0) return false;
|
|
166
|
+
return cached.kinds.some((k) => k.scheme === requirement.scheme && k.network === requirement.network);
|
|
167
|
+
}
|
|
168
|
+
async function prepare(requirement, context, callOptions) {
|
|
169
|
+
const merged = { ...options.signDefaults, ...callOptions };
|
|
170
|
+
if (merged.paymentId !== void 0 && !extensions.validatePaymentId(merged.paymentId)) {
|
|
171
|
+
throw new X402PaymentIdFormatError(merged.paymentId);
|
|
172
|
+
}
|
|
173
|
+
const body = {
|
|
174
|
+
accept: requirement,
|
|
175
|
+
resource: context.resource,
|
|
176
|
+
x402Version: context.x402Version,
|
|
177
|
+
...merged.paymentId !== void 0 ? { remotePaymentId: merged.paymentId } : {}
|
|
178
|
+
};
|
|
179
|
+
const created = await http.post(TRANSACTIONS_PATH, body, {
|
|
180
|
+
retries: 0,
|
|
181
|
+
...merged.signal !== void 0 ? { signal: merged.signal } : {}
|
|
182
|
+
});
|
|
183
|
+
return makePreparedPayment(http, created, merged);
|
|
184
|
+
}
|
|
185
|
+
async function sign(requirement, context, callOptions) {
|
|
186
|
+
const prepared = await prepare(requirement, context, callOptions);
|
|
187
|
+
try {
|
|
188
|
+
return await prepared.awaitPayload(callOptions);
|
|
189
|
+
} catch (err) {
|
|
190
|
+
void prepared.cancel();
|
|
191
|
+
throw err;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
await fetchSupported();
|
|
195
|
+
const signer = {
|
|
196
|
+
prefer,
|
|
197
|
+
extensionsHandled,
|
|
198
|
+
supports,
|
|
199
|
+
sign,
|
|
200
|
+
prepare,
|
|
201
|
+
ready: () => Promise.resolve(),
|
|
202
|
+
getSupported,
|
|
203
|
+
refreshSupported
|
|
204
|
+
};
|
|
205
|
+
return signer;
|
|
206
|
+
}
|
|
207
|
+
function makePreparedPayment(http, created, preparedOptions) {
|
|
208
|
+
let awaitInFlight;
|
|
209
|
+
let cancelled = false;
|
|
210
|
+
const cancelController = new AbortController();
|
|
211
|
+
function buildEncodedPayment(encodedPayload, paymentPayload) {
|
|
212
|
+
return { encodedPayload, paymentPayload, transactionId: created.transactionId };
|
|
213
|
+
}
|
|
214
|
+
async function pollOnce(signal) {
|
|
215
|
+
return http.get(TRANSACTION_X402_PATH(created.transactionId), {
|
|
216
|
+
retries: 0,
|
|
217
|
+
...signal !== void 0 ? { signal } : {}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
async function awaitPayload(callOptions) {
|
|
221
|
+
if (cancelled) {
|
|
222
|
+
throw new X402ApprovalCancelledError(created.approvalId);
|
|
223
|
+
}
|
|
224
|
+
if (awaitInFlight !== void 0) return awaitInFlight;
|
|
225
|
+
const merged = { ...preparedOptions, ...callOptions };
|
|
226
|
+
const pollIntervalMs = merged.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
227
|
+
const timeoutMs = merged.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
228
|
+
const callerSignal = merged.signal;
|
|
229
|
+
const signal = composeSignals(callerSignal, cancelController.signal);
|
|
230
|
+
const promise = runPollLoop({
|
|
231
|
+
pollOnce,
|
|
232
|
+
buildEncodedPayment,
|
|
233
|
+
approvalId: created.approvalId,
|
|
234
|
+
pollIntervalMs,
|
|
235
|
+
timeoutMs,
|
|
236
|
+
signal,
|
|
237
|
+
// Short-circuit for the synchronous-approval path: when the server
|
|
238
|
+
// signed during `POST /v1/transactions/x402` (approvalStatus = APPROVED),
|
|
239
|
+
// the very first GET should already have the payload; the loop is
|
|
240
|
+
// still robust to a missed first poll.
|
|
241
|
+
createdApprovalStatus: created.approvalStatus
|
|
242
|
+
}).catch((err) => {
|
|
243
|
+
if (cancelled) {
|
|
244
|
+
throw new X402ApprovalCancelledError(created.approvalId);
|
|
245
|
+
}
|
|
246
|
+
throw err;
|
|
247
|
+
});
|
|
248
|
+
awaitInFlight = promise;
|
|
249
|
+
promise.catch(() => {
|
|
250
|
+
awaitInFlight = void 0;
|
|
251
|
+
});
|
|
252
|
+
return promise;
|
|
253
|
+
}
|
|
254
|
+
async function statusFn() {
|
|
255
|
+
const payload = await pollOnce();
|
|
256
|
+
return payload.status;
|
|
257
|
+
}
|
|
258
|
+
async function cancel() {
|
|
259
|
+
cancelled = true;
|
|
260
|
+
cancelController.abort();
|
|
261
|
+
try {
|
|
262
|
+
await http.post(APPROVAL_CANCEL_PATH(created.approvalId), void 0, { retries: 0 });
|
|
263
|
+
} catch {
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
transactionId: created.transactionId,
|
|
268
|
+
approvalId: created.approvalId,
|
|
269
|
+
awaitPayload,
|
|
270
|
+
status: statusFn,
|
|
271
|
+
cancel
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
function composeSignals(...signals) {
|
|
275
|
+
const live = signals.filter((s) => s !== void 0);
|
|
276
|
+
const [first, ...rest] = live;
|
|
277
|
+
if (first !== void 0 && rest.length === 0) return first;
|
|
278
|
+
const anyFn = AbortSignal.any;
|
|
279
|
+
if (typeof anyFn === "function") return anyFn(live);
|
|
280
|
+
const controller = new AbortController();
|
|
281
|
+
const cleanups = [];
|
|
282
|
+
const fire = (reason) => {
|
|
283
|
+
controller.abort(reason);
|
|
284
|
+
for (const c of cleanups) c();
|
|
285
|
+
};
|
|
286
|
+
for (const s of live) {
|
|
287
|
+
if (s.aborted) {
|
|
288
|
+
fire(s.reason);
|
|
289
|
+
return controller.signal;
|
|
290
|
+
}
|
|
291
|
+
const handler = () => fire(s.reason);
|
|
292
|
+
s.addEventListener("abort", handler, { once: true });
|
|
293
|
+
cleanups.push(() => s.removeEventListener("abort", handler));
|
|
294
|
+
}
|
|
295
|
+
return controller.signal;
|
|
296
|
+
}
|
|
297
|
+
async function runPollLoop(input) {
|
|
298
|
+
const { pollOnce, buildEncodedPayment, approvalId, pollIntervalMs, timeoutMs, signal } = input;
|
|
299
|
+
const startedAt = Date.now();
|
|
300
|
+
const deadline = startedAt + timeoutMs;
|
|
301
|
+
const isAborted = () => signal !== void 0 && signal.aborted;
|
|
302
|
+
if (isAborted()) {
|
|
303
|
+
throw new X402ApprovalTimeoutError(approvalId, timeoutMs);
|
|
304
|
+
}
|
|
305
|
+
let firstPoll = input.createdApprovalStatus === APPROVAL_APPROVED;
|
|
306
|
+
while (Date.now() < deadline) {
|
|
307
|
+
if (isAborted()) {
|
|
308
|
+
throw new X402ApprovalTimeoutError(approvalId, timeoutMs);
|
|
309
|
+
}
|
|
310
|
+
let response;
|
|
311
|
+
try {
|
|
312
|
+
response = await pollOnce(signal);
|
|
313
|
+
} catch (err) {
|
|
314
|
+
if (signal !== void 0 && signal.aborted) {
|
|
315
|
+
throw new X402ApprovalTimeoutError(approvalId, timeoutMs);
|
|
316
|
+
}
|
|
317
|
+
response = void 0;
|
|
318
|
+
}
|
|
319
|
+
if (response !== void 0) {
|
|
320
|
+
const settled = evaluatePoll(response);
|
|
321
|
+
if (settled === "pending") ; else if (settled === "failed") {
|
|
322
|
+
throw new X402ApprovalFailedError(approvalId, response.status);
|
|
323
|
+
} else {
|
|
324
|
+
return buildEncodedPayment(response.encodedPayload, response.paymentPayload);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (firstPoll) {
|
|
328
|
+
firstPoll = false;
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
await sleep(pollIntervalMs, signal);
|
|
332
|
+
}
|
|
333
|
+
throw new X402ApprovalTimeoutError(approvalId, timeoutMs);
|
|
334
|
+
}
|
|
335
|
+
var TERMINAL_FAILURE_STATUSES = /* @__PURE__ */ new Set([
|
|
336
|
+
"DECLINED",
|
|
337
|
+
"EXPIRED",
|
|
338
|
+
"GENERAL_ERROR",
|
|
339
|
+
"INSUFFICIENT_FUNDS"
|
|
340
|
+
]);
|
|
341
|
+
function evaluatePoll(response) {
|
|
342
|
+
if (response.encodedPayload != null && response.paymentPayload != null) {
|
|
343
|
+
return "signed";
|
|
344
|
+
}
|
|
345
|
+
if (TERMINAL_FAILURE_STATUSES.has(response.status)) {
|
|
346
|
+
return "failed";
|
|
347
|
+
}
|
|
348
|
+
return "pending";
|
|
349
|
+
}
|
|
350
|
+
function sleep(ms, signal) {
|
|
351
|
+
return new Promise((resolve) => {
|
|
352
|
+
const timer = setTimeout(resolve, ms);
|
|
353
|
+
if (signal !== void 0) {
|
|
354
|
+
const onAbort = () => {
|
|
355
|
+
clearTimeout(timer);
|
|
356
|
+
resolve();
|
|
357
|
+
};
|
|
358
|
+
if (signal.aborted) {
|
|
359
|
+
clearTimeout(timer);
|
|
360
|
+
resolve();
|
|
361
|
+
} else {
|
|
362
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// src/inflow-client.ts
|
|
369
|
+
var InflowClient = class extends client.x402Client {
|
|
370
|
+
/**
|
|
371
|
+
* The InFlow-signed branch of the routing decision. Kept private — callers compose by passing the {@link InflowClient}
|
|
372
|
+
* to `x402HTTPClient` (and to `registerExactEvmScheme` / `registerExactSvmScheme` for foundation-signed networks).
|
|
373
|
+
*/
|
|
374
|
+
inflowSigner;
|
|
375
|
+
/**
|
|
376
|
+
* Ordered scheme preference used when picking among multiple InFlow-acceptable requirements. Sourced from the InFlow
|
|
377
|
+
* signer at construction so the buyer's intent (`prefer: ['balance', 'exact']` by default) survives across the
|
|
378
|
+
* override.
|
|
379
|
+
*/
|
|
380
|
+
preferOrder;
|
|
381
|
+
/**
|
|
382
|
+
* Construct via {@link createInflowClient}. The factory primes the buyer capability cache before resolving, so the
|
|
383
|
+
* routing decision in {@link InflowClient.createPaymentPayload} is synchronous against in-memory data. The constructor
|
|
384
|
+
* is exported only so the class is referenceable for `instanceof` checks, generic constraints, and return types.
|
|
385
|
+
*
|
|
386
|
+
* @param inflowSigner - Primed InFlow signer carrying the buyer capability cache, MPC signing flow, and prefer order.
|
|
387
|
+
* @internal
|
|
388
|
+
*/
|
|
389
|
+
constructor(inflowSigner) {
|
|
390
|
+
super();
|
|
391
|
+
this.inflowSigner = inflowSigner;
|
|
392
|
+
this.preferOrder = inflowSigner.prefer;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Routing override. InFlow signs when the buyer capability cache covers a requirement (preferred-scheme order);
|
|
396
|
+
* otherwise the foundation signs and any registered extension handlers are folded into `payload.extensions`. A
|
|
397
|
+
* `required: true` extension whose handler returns `null` throws.
|
|
398
|
+
*/
|
|
399
|
+
async createPaymentPayload(paymentRequired) {
|
|
400
|
+
const inflowMatch = this.pickInflowMatch(fromFoundationRequirements(paymentRequired.accepts));
|
|
401
|
+
if (inflowMatch !== null) {
|
|
402
|
+
const context = {
|
|
403
|
+
resource: paymentRequired.resource,
|
|
404
|
+
x402Version: paymentRequired.x402Version,
|
|
405
|
+
...paymentRequired.extensions !== void 0 ? { extensions: paymentRequired.extensions } : {}
|
|
406
|
+
};
|
|
407
|
+
const result = await this.inflowSigner.sign(inflowMatch, context);
|
|
408
|
+
return toFoundationPayload(result.paymentPayload);
|
|
409
|
+
}
|
|
410
|
+
const payload = await super.createPaymentPayload(paymentRequired);
|
|
411
|
+
const folded = foldInflowExtensions(payload, paymentRequired);
|
|
412
|
+
return toFoundationPayload(folded);
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Two-phase signing flow for callers that want to surface pending- approval UI before the protected request is
|
|
416
|
+
* replayed. Forwarded to the InFlow signer's `prepare()`; returns a {@link PreparedPayment} the caller can
|
|
417
|
+
* `awaitPayload()` or `cancel()` independently.
|
|
418
|
+
*
|
|
419
|
+
* Has no foundation equivalent — `x402Client.createPaymentPayload` is one-shot. The handle is InFlow-specific and
|
|
420
|
+
* only applies to requirements InFlow can sign.
|
|
421
|
+
*
|
|
422
|
+
* @param requirement - The chosen `PaymentRequirements` (re-exported from `@inflowpayai/x402`) — must match an entry
|
|
423
|
+
* in the InFlow buyer capability cache.
|
|
424
|
+
* @param context - Seller-side {@link SigningContext}.
|
|
425
|
+
* @param options - Per-call {@link SignOptions}.
|
|
426
|
+
* @returns A {@link PreparedPayment} handle.
|
|
427
|
+
* @throws {@link X402AdapterRoutingError} When the requirement is not in the InFlow buyer capability cache
|
|
428
|
+
* (foundation-signed requirements have no two-phase flow).
|
|
429
|
+
*/
|
|
430
|
+
async prepareInflowPayment(requirement, context, options) {
|
|
431
|
+
if (!this.inflowSigner.supports(requirement)) {
|
|
432
|
+
throw new X402AdapterRoutingError(requirement.scheme, requirement.network);
|
|
433
|
+
}
|
|
434
|
+
return this.inflowSigner.prepare(requirement, context, options);
|
|
435
|
+
}
|
|
436
|
+
// The eight overrides below preserve foundation `x402Client` behavior
|
|
437
|
+
// verbatim and only narrow the return type to `this` so chaining
|
|
438
|
+
// stays in the {@link InflowClient} subclass.
|
|
439
|
+
register(network, schemeNetworkClient) {
|
|
440
|
+
super.register(network, schemeNetworkClient);
|
|
441
|
+
return this;
|
|
442
|
+
}
|
|
443
|
+
registerV1(network, schemeNetworkClient) {
|
|
444
|
+
super.registerV1(network, schemeNetworkClient);
|
|
445
|
+
return this;
|
|
446
|
+
}
|
|
447
|
+
registerPolicy(policy) {
|
|
448
|
+
super.registerPolicy(policy);
|
|
449
|
+
return this;
|
|
450
|
+
}
|
|
451
|
+
registerExtension(extension) {
|
|
452
|
+
super.registerExtension(extension);
|
|
453
|
+
return this;
|
|
454
|
+
}
|
|
455
|
+
onBeforePaymentCreation(hook) {
|
|
456
|
+
super.onBeforePaymentCreation(hook);
|
|
457
|
+
return this;
|
|
458
|
+
}
|
|
459
|
+
onAfterPaymentCreation(hook) {
|
|
460
|
+
super.onAfterPaymentCreation(hook);
|
|
461
|
+
return this;
|
|
462
|
+
}
|
|
463
|
+
onPaymentCreationFailure(hook) {
|
|
464
|
+
super.onPaymentCreationFailure(hook);
|
|
465
|
+
return this;
|
|
466
|
+
}
|
|
467
|
+
onPaymentResponse(hook) {
|
|
468
|
+
super.onPaymentResponse(hook);
|
|
469
|
+
return this;
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Pick the buyer's preferred `accepts[]` entry the InFlow signer can handle. Walks {@link InflowClient.preferOrder}
|
|
473
|
+
* and returns the first entry whose `(scheme, network)` is in the InFlow capability cache; returns `null` when no
|
|
474
|
+
* entry matches (the foundation branch takes over).
|
|
475
|
+
*/
|
|
476
|
+
pickInflowMatch(accepts) {
|
|
477
|
+
for (const scheme of this.preferOrder) {
|
|
478
|
+
const match = accepts.find((r) => r.scheme === scheme && this.inflowSigner.supports(r));
|
|
479
|
+
if (match !== void 0) return match;
|
|
480
|
+
}
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
async function createInflowClient(options) {
|
|
485
|
+
const inflowSigner = await createInflowSigner(options);
|
|
486
|
+
return new InflowClient(inflowSigner);
|
|
487
|
+
}
|
|
488
|
+
function foldInflowExtensions(paymentPayload, paymentRequired) {
|
|
489
|
+
const declared = paymentRequired.extensions;
|
|
490
|
+
if (declared === void 0) return paymentPayload;
|
|
491
|
+
const signContext = {};
|
|
492
|
+
let extensions$1 = paymentPayload.extensions;
|
|
493
|
+
for (const handler of extensions.EXTENSION_REGISTRY.values()) {
|
|
494
|
+
const declaration = extensions.getExtension(declared, handler);
|
|
495
|
+
if (declaration === void 0) continue;
|
|
496
|
+
const entry = handler.buildPayloadEntry(declaration, signContext);
|
|
497
|
+
if (entry !== null) {
|
|
498
|
+
extensions$1 = extensions.setExtension(extensions$1, handler, entry);
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
const required = declaration !== null && typeof declaration === "object" && "required" in declaration && declaration.required === true;
|
|
502
|
+
if (required) {
|
|
503
|
+
throw new Error(
|
|
504
|
+
`InflowClient: extension "${handler.name}" is declared as required but no payload entry was produced`
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
if (extensions$1 === void 0) return paymentPayload;
|
|
509
|
+
return { ...paymentPayload, extensions: extensions$1 };
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// src/decode-evm-key.ts
|
|
513
|
+
var HEX_RE = /^[0-9a-f]+$/;
|
|
514
|
+
function parseEvmPrivateKey(value) {
|
|
515
|
+
let hex = value.trim().toLowerCase();
|
|
516
|
+
if (hex.startsWith("0x")) hex = hex.slice(2);
|
|
517
|
+
if (hex === "" || !HEX_RE.test(hex)) {
|
|
518
|
+
throw new X402InvalidEvmKeyError("expected hex characters (0x-prefixed or bare)");
|
|
519
|
+
}
|
|
520
|
+
if (hex.length === 66 && hex.startsWith("00")) {
|
|
521
|
+
hex = hex.slice(2);
|
|
522
|
+
}
|
|
523
|
+
if (hex.length < 64) hex = hex.padStart(64, "0");
|
|
524
|
+
if (hex.length !== 64) {
|
|
525
|
+
throw new X402InvalidEvmKeyError(`expected 32 bytes after normalization; got ${(hex.length / 2).toString()}`);
|
|
526
|
+
}
|
|
527
|
+
return `0x${hex}`;
|
|
528
|
+
}
|
|
529
|
+
var EXPECTED_LENGTH = 64;
|
|
530
|
+
function decodeSolanaSecret(value) {
|
|
531
|
+
const trimmed = value.trim();
|
|
532
|
+
if (trimmed === "") {
|
|
533
|
+
throw new X402InvalidSolanaKeyError("input is empty");
|
|
534
|
+
}
|
|
535
|
+
if (trimmed.startsWith("[")) {
|
|
536
|
+
return decodeJsonByteArray(trimmed);
|
|
537
|
+
}
|
|
538
|
+
return decodeBase58(trimmed);
|
|
539
|
+
}
|
|
540
|
+
function decodeJsonByteArray(trimmed) {
|
|
541
|
+
let parsed;
|
|
542
|
+
try {
|
|
543
|
+
parsed = JSON.parse(trimmed);
|
|
544
|
+
} catch (err) {
|
|
545
|
+
throw new X402InvalidSolanaKeyError(`JSON parse failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
546
|
+
}
|
|
547
|
+
if (!Array.isArray(parsed)) {
|
|
548
|
+
throw new X402InvalidSolanaKeyError("JSON value is not an array");
|
|
549
|
+
}
|
|
550
|
+
if (parsed.length !== EXPECTED_LENGTH) {
|
|
551
|
+
throw new X402InvalidSolanaKeyError(
|
|
552
|
+
`JSON array length is ${parsed.length.toString()}, expected ${EXPECTED_LENGTH.toString()}`
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
const bytes = new Uint8Array(EXPECTED_LENGTH);
|
|
556
|
+
for (let i = 0; i < EXPECTED_LENGTH; i += 1) {
|
|
557
|
+
const element = parsed[i];
|
|
558
|
+
if (typeof element !== "number" || !Number.isInteger(element) || element < 0 || element > 255) {
|
|
559
|
+
throw new X402InvalidSolanaKeyError(`JSON array element at index ${i.toString()} is not an integer in 0..255`);
|
|
560
|
+
}
|
|
561
|
+
bytes[i] = element;
|
|
562
|
+
}
|
|
563
|
+
return bytes;
|
|
564
|
+
}
|
|
565
|
+
function decodeBase58(trimmed) {
|
|
566
|
+
let decoded;
|
|
567
|
+
try {
|
|
568
|
+
decoded = bs58__default.default.decode(trimmed);
|
|
569
|
+
} catch (err) {
|
|
570
|
+
throw new X402InvalidSolanaKeyError(`base58 decode failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
571
|
+
}
|
|
572
|
+
if (decoded.length !== EXPECTED_LENGTH) {
|
|
573
|
+
throw new X402InvalidSolanaKeyError(
|
|
574
|
+
`base58 decoded to ${decoded.length.toString()} bytes, expected ${EXPECTED_LENGTH.toString()}`
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
return decoded;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
exports.InflowClient = InflowClient;
|
|
581
|
+
exports.X402AdapterRoutingError = X402AdapterRoutingError;
|
|
582
|
+
exports.X402ApprovalCancelledError = X402ApprovalCancelledError;
|
|
583
|
+
exports.X402ApprovalFailedError = X402ApprovalFailedError;
|
|
584
|
+
exports.X402ApprovalTimeoutError = X402ApprovalTimeoutError;
|
|
585
|
+
exports.X402InvalidEvmKeyError = X402InvalidEvmKeyError;
|
|
586
|
+
exports.X402InvalidSolanaKeyError = X402InvalidSolanaKeyError;
|
|
587
|
+
exports.X402PaymentIdFormatError = X402PaymentIdFormatError;
|
|
588
|
+
exports.createInflowClient = createInflowClient;
|
|
589
|
+
exports.decodeSolanaSecret = decodeSolanaSecret;
|
|
590
|
+
exports.parseEvmPrivateKey = parseEvmPrivateKey;
|
|
591
|
+
//# sourceMappingURL=index.cjs.map
|
|
592
|
+
//# sourceMappingURL=index.cjs.map
|