@bosonprotocol/x402-client 0.2.0 → 0.3.0-alpha-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/dist/cjs/index.d.ts +126 -3
- package/dist/cjs/index.js +280 -21
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +276 -22
- package/dist/esm/index.js.map +1 -1
- package/package.json +2 -2
package/dist/esm/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export { FulfillmentValidationError, MaxAmountExceededError, NoCompatibleActionE
|
|
|
3
3
|
import Ajv from 'ajv';
|
|
4
4
|
import { parseEscrowPaymentRequirements, DECIMAL_UINT, parseEscrowPaymentPayload } from '@bosonprotocol/x402-core/schemes/escrow';
|
|
5
5
|
import { encodeSignedPayload } from '@bosonprotocol/x402-evm/codec';
|
|
6
|
+
import { ExchangeState, DisputeState } from '@bosonprotocol/x402-core/state-machine';
|
|
6
7
|
import { serializeTypedData, getAddress } from 'viem';
|
|
7
8
|
|
|
8
9
|
// src/action.ts
|
|
@@ -34,6 +35,30 @@ function pickAction(requirements, policy) {
|
|
|
34
35
|
`no commit-time action ('${FLOW_A}' or '${FLOW_B}') with 'server' channel found in requirements.actions.next`
|
|
35
36
|
);
|
|
36
37
|
}
|
|
38
|
+
|
|
39
|
+
// src/base64.ts
|
|
40
|
+
function decodeBase64(value) {
|
|
41
|
+
if (typeof Buffer !== "undefined") {
|
|
42
|
+
return Buffer.from(value, "base64").toString("utf8");
|
|
43
|
+
}
|
|
44
|
+
const binary = atob(value);
|
|
45
|
+
const bytes = new Uint8Array(binary.length);
|
|
46
|
+
for (let i = 0; i < binary.length; i++) {
|
|
47
|
+
bytes[i] = binary.charCodeAt(i);
|
|
48
|
+
}
|
|
49
|
+
return new TextDecoder().decode(bytes);
|
|
50
|
+
}
|
|
51
|
+
function encodeBase64(value) {
|
|
52
|
+
if (typeof Buffer !== "undefined") {
|
|
53
|
+
return Buffer.from(value, "utf8").toString("base64");
|
|
54
|
+
}
|
|
55
|
+
const bytes = new TextEncoder().encode(value);
|
|
56
|
+
let binary = "";
|
|
57
|
+
for (const b of bytes) {
|
|
58
|
+
binary += String.fromCharCode(b);
|
|
59
|
+
}
|
|
60
|
+
return btoa(binary);
|
|
61
|
+
}
|
|
37
62
|
function resolveFulfillment(requirements, config) {
|
|
38
63
|
const required = requirements.fulfillment?.required ?? false;
|
|
39
64
|
if (!required) {
|
|
@@ -119,17 +144,6 @@ function isClientStateShape(v) {
|
|
|
119
144
|
}
|
|
120
145
|
return true;
|
|
121
146
|
}
|
|
122
|
-
function decodeBase64(value) {
|
|
123
|
-
if (typeof Buffer !== "undefined") {
|
|
124
|
-
return Buffer.from(value, "base64").toString("utf8");
|
|
125
|
-
}
|
|
126
|
-
const binary = atob(value);
|
|
127
|
-
const bytes = new Uint8Array(binary.length);
|
|
128
|
-
for (let i = 0; i < binary.length; i++) {
|
|
129
|
-
bytes[i] = binary.charCodeAt(i);
|
|
130
|
-
}
|
|
131
|
-
return new TextDecoder().decode(bytes);
|
|
132
|
-
}
|
|
133
147
|
function pickString(obj, keys) {
|
|
134
148
|
for (const key of keys) {
|
|
135
149
|
const v = obj[key];
|
|
@@ -172,16 +186,7 @@ function assemblePayload({
|
|
|
172
186
|
}
|
|
173
187
|
function assembleAndEncodePayload(args) {
|
|
174
188
|
const payload = assemblePayload(args);
|
|
175
|
-
|
|
176
|
-
if (typeof Buffer !== "undefined") {
|
|
177
|
-
return Buffer.from(json, "utf8").toString("base64");
|
|
178
|
-
}
|
|
179
|
-
const bytes = new TextEncoder().encode(json);
|
|
180
|
-
let binary = "";
|
|
181
|
-
for (const b of bytes) {
|
|
182
|
-
binary += String.fromCharCode(b);
|
|
183
|
-
}
|
|
184
|
-
return btoa(binary);
|
|
189
|
+
return encodeBase64(JSON.stringify(payload));
|
|
185
190
|
}
|
|
186
191
|
|
|
187
192
|
// src/pre-commit.ts
|
|
@@ -280,6 +285,235 @@ async function callSignMetaTx(coreSdk, args, nonce) {
|
|
|
280
285
|
}
|
|
281
286
|
}
|
|
282
287
|
}
|
|
288
|
+
var SUBMIT_CHANNELS = ["server", "facilitator"];
|
|
289
|
+
var NoCompatibleChannelError = class extends Error {
|
|
290
|
+
constructor(actionId, advertisedChannels) {
|
|
291
|
+
super(
|
|
292
|
+
`x402-client: action '${actionId}' advertises channels [${advertisedChannels.join(", ")}] but none are submittable over HTTP (server / facilitator).`
|
|
293
|
+
);
|
|
294
|
+
this.name = "NoCompatibleChannelError";
|
|
295
|
+
this.actionId = actionId;
|
|
296
|
+
this.advertisedChannels = advertisedChannels;
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
var AllChannelsFailedError = class extends Error {
|
|
300
|
+
constructor(actionId, attempts) {
|
|
301
|
+
super(
|
|
302
|
+
`x402-client: action '${actionId}' failed on every attempted channel \u2014 ${attempts.map((a) => formatAttempt(a)).join("; ")}`
|
|
303
|
+
);
|
|
304
|
+
this.name = "AllChannelsFailedError";
|
|
305
|
+
this.attempts = attempts;
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
function formatAttempt(a) {
|
|
309
|
+
if (a.ok) return `${a.channel}=ok(${a.status})`;
|
|
310
|
+
const tail = a.status !== void 0 ? `(${a.status})` : "";
|
|
311
|
+
return `${a.channel}=${a.reason}${tail}`;
|
|
312
|
+
}
|
|
313
|
+
async function submitAction(args) {
|
|
314
|
+
const fetcher = args.fetch ?? globalThis.fetch.bind(globalThis);
|
|
315
|
+
const timeoutMs = args.timeoutMs ?? 1e4;
|
|
316
|
+
const ordered = orderedChannels(args.action);
|
|
317
|
+
if (ordered.length === 0) {
|
|
318
|
+
throw new NoCompatibleChannelError(args.action.id, args.action.channels);
|
|
319
|
+
}
|
|
320
|
+
const attempts = [];
|
|
321
|
+
for (const channel of ordered) {
|
|
322
|
+
const endpoint = args.action.endpoints?.[channel];
|
|
323
|
+
if (endpoint === void 0) {
|
|
324
|
+
attempts.push({
|
|
325
|
+
channel,
|
|
326
|
+
ok: false,
|
|
327
|
+
reason: "no-endpoint",
|
|
328
|
+
message: `no endpoint advertised for channel '${channel}'`
|
|
329
|
+
});
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
const outcome = await attemptChannel({
|
|
333
|
+
channel,
|
|
334
|
+
endpoint,
|
|
335
|
+
args,
|
|
336
|
+
fetcher,
|
|
337
|
+
timeoutMs
|
|
338
|
+
});
|
|
339
|
+
attempts.push(outcome.attempt);
|
|
340
|
+
if (outcome.attempt.ok && outcome.result !== void 0) {
|
|
341
|
+
return { ...outcome.result, attempts };
|
|
342
|
+
}
|
|
343
|
+
if (!outcome.attempt.ok && outcome.attempt.reason === "4xx") {
|
|
344
|
+
throw new AllChannelsFailedError(args.action.id, attempts);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
throw new AllChannelsFailedError(args.action.id, attempts);
|
|
348
|
+
}
|
|
349
|
+
function orderedChannels(action) {
|
|
350
|
+
const seen = /* @__PURE__ */ new Set();
|
|
351
|
+
const out = [];
|
|
352
|
+
for (const c of action.channels) {
|
|
353
|
+
if (SUBMIT_CHANNELS.includes(c) && !seen.has(c)) {
|
|
354
|
+
seen.add(c);
|
|
355
|
+
out.push(c);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return out;
|
|
359
|
+
}
|
|
360
|
+
async function attemptChannel(input) {
|
|
361
|
+
const { channel, endpoint, args, fetcher, timeoutMs } = input;
|
|
362
|
+
const body = channel === "server" ? buildServerBody(args) : buildFacilitatorBody(args);
|
|
363
|
+
let res;
|
|
364
|
+
try {
|
|
365
|
+
res = await fetchWithTimeout(fetcher, endpoint, body, timeoutMs);
|
|
366
|
+
} catch (e) {
|
|
367
|
+
const reason = isTimeout(e) ? "timeout" : "network";
|
|
368
|
+
return {
|
|
369
|
+
attempt: {
|
|
370
|
+
channel,
|
|
371
|
+
ok: false,
|
|
372
|
+
reason,
|
|
373
|
+
message: e instanceof Error ? e.message : String(e)
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
const parsed = await res.json().catch(() => null);
|
|
378
|
+
if (res.status >= 500) {
|
|
379
|
+
return {
|
|
380
|
+
attempt: { channel, ok: false, reason: "5xx", status: res.status }
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
if (!res.ok) {
|
|
384
|
+
return {
|
|
385
|
+
attempt: { channel, ok: false, reason: "4xx", status: res.status }
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
if (parsed === null || typeof parsed !== "object") {
|
|
389
|
+
return {
|
|
390
|
+
attempt: {
|
|
391
|
+
channel,
|
|
392
|
+
ok: false,
|
|
393
|
+
reason: "invalid-response",
|
|
394
|
+
status: res.status,
|
|
395
|
+
message: "response body is not a JSON object"
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
try {
|
|
400
|
+
const result = channel === "server" ? parseServerResult(parsed) : parseFacilitatorResult(parsed);
|
|
401
|
+
return {
|
|
402
|
+
attempt: { channel, ok: true, status: res.status },
|
|
403
|
+
result: { ...result, channelUsed: channel }
|
|
404
|
+
};
|
|
405
|
+
} catch (e) {
|
|
406
|
+
return {
|
|
407
|
+
attempt: {
|
|
408
|
+
channel,
|
|
409
|
+
ok: false,
|
|
410
|
+
reason: "invalid-response",
|
|
411
|
+
status: res.status,
|
|
412
|
+
message: e instanceof Error ? e.message : String(e)
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
function buildServerBody(args) {
|
|
418
|
+
const body = {
|
|
419
|
+
exchangeId: args.exchangeId,
|
|
420
|
+
signedPayload: args.signed.signedPayload
|
|
421
|
+
};
|
|
422
|
+
if (args.action.id === "boson-redeem" && args.fulfillment !== void 0) {
|
|
423
|
+
body.fulfillment = args.fulfillment;
|
|
424
|
+
}
|
|
425
|
+
return body;
|
|
426
|
+
}
|
|
427
|
+
function buildFacilitatorBody(args) {
|
|
428
|
+
return {
|
|
429
|
+
action: args.action.id,
|
|
430
|
+
exchangeId: args.exchangeId,
|
|
431
|
+
network: args.network,
|
|
432
|
+
escrowAddress: args.escrowAddress,
|
|
433
|
+
signedPayload: args.signed.signedPayload
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
async function fetchWithTimeout(fetcher, endpoint, body, timeoutMs) {
|
|
437
|
+
const controller = new AbortController();
|
|
438
|
+
const timer = setTimeout(() => controller.abort(new TimeoutError(timeoutMs)), timeoutMs);
|
|
439
|
+
try {
|
|
440
|
+
return await fetcher(endpoint, {
|
|
441
|
+
method: "POST",
|
|
442
|
+
headers: { "content-type": "application/json" },
|
|
443
|
+
body: JSON.stringify(body),
|
|
444
|
+
signal: controller.signal
|
|
445
|
+
});
|
|
446
|
+
} finally {
|
|
447
|
+
clearTimeout(timer);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
var TimeoutError = class extends Error {
|
|
451
|
+
constructor(timeoutMs) {
|
|
452
|
+
super(`x402-client/submit: channel attempt timed out after ${timeoutMs}ms`);
|
|
453
|
+
this.isTimeout = true;
|
|
454
|
+
this.name = "TimeoutError";
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
function isTimeout(e) {
|
|
458
|
+
if (e instanceof TimeoutError) return true;
|
|
459
|
+
if (typeof e !== "object" || e === null) return false;
|
|
460
|
+
const obj = e;
|
|
461
|
+
if (obj.isTimeout === true) return true;
|
|
462
|
+
if (obj.name === "AbortError" || obj.name === "TimeoutError") return true;
|
|
463
|
+
if (typeof obj.cause === "object" && obj.cause !== null) {
|
|
464
|
+
const cause = obj.cause;
|
|
465
|
+
if (cause.isTimeout === true) return true;
|
|
466
|
+
if (cause.name === "TimeoutError") return true;
|
|
467
|
+
}
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
function parseServerResult(body) {
|
|
471
|
+
if (typeof body !== "object" || body === null) {
|
|
472
|
+
throw new Error("server response body is not an object");
|
|
473
|
+
}
|
|
474
|
+
const raw = body;
|
|
475
|
+
const txHash = raw.txHash;
|
|
476
|
+
const exchangeState = raw.nextActions?.exchangeState;
|
|
477
|
+
if (!isHexHash(txHash) || !isExchangeState(exchangeState)) {
|
|
478
|
+
throw new Error("server response missing txHash or nextActions.exchangeState");
|
|
479
|
+
}
|
|
480
|
+
const out = {
|
|
481
|
+
txHash,
|
|
482
|
+
newExchangeState: exchangeState,
|
|
483
|
+
nextActions: raw.nextActions
|
|
484
|
+
};
|
|
485
|
+
if (isDisputeState(raw.nextActions?.disputeState)) {
|
|
486
|
+
out.newDisputeState = raw.nextActions.disputeState;
|
|
487
|
+
}
|
|
488
|
+
return out;
|
|
489
|
+
}
|
|
490
|
+
function parseFacilitatorResult(body) {
|
|
491
|
+
if (typeof body !== "object" || body === null) {
|
|
492
|
+
throw new Error("facilitator response body is not an object");
|
|
493
|
+
}
|
|
494
|
+
const raw = body;
|
|
495
|
+
if (raw.ok !== true || !isHexHash(raw.txHash) || !isExchangeState(raw.newExchangeState)) {
|
|
496
|
+
throw new Error("facilitator response missing ok/txHash/newExchangeState");
|
|
497
|
+
}
|
|
498
|
+
const out = {
|
|
499
|
+
txHash: raw.txHash,
|
|
500
|
+
newExchangeState: raw.newExchangeState
|
|
501
|
+
};
|
|
502
|
+
if (isDisputeState(raw.newDisputeState)) {
|
|
503
|
+
out.newDisputeState = raw.newDisputeState;
|
|
504
|
+
}
|
|
505
|
+
return out;
|
|
506
|
+
}
|
|
507
|
+
var TX_HASH_RE = /^0x[0-9a-fA-F]{64}$/;
|
|
508
|
+
function isHexHash(v) {
|
|
509
|
+
return typeof v === "string" && TX_HASH_RE.test(v);
|
|
510
|
+
}
|
|
511
|
+
function isExchangeState(v) {
|
|
512
|
+
return typeof v === "string" && Object.values(ExchangeState).includes(v);
|
|
513
|
+
}
|
|
514
|
+
function isDisputeState(v) {
|
|
515
|
+
return typeof v === "string" && Object.values(DisputeState).includes(v);
|
|
516
|
+
}
|
|
283
517
|
function normalizeEntityId(value) {
|
|
284
518
|
if (typeof value === "bigint") {
|
|
285
519
|
if (value < 0n) throw new Error(`entityId must be non-negative, got ${value.toString()}`);
|
|
@@ -492,6 +726,26 @@ function createX402bClient(config) {
|
|
|
492
726
|
signAction(args) {
|
|
493
727
|
return signPostCommitAction(args, { buildCoreSdk, getBuyerAddress });
|
|
494
728
|
},
|
|
729
|
+
async submitAction(args) {
|
|
730
|
+
const signed = await signPostCommitAction(args, { buildCoreSdk, getBuyerAddress });
|
|
731
|
+
const action = args.priorNextActions.next.find((entry) => entry.id === args.actionId);
|
|
732
|
+
if (action === void 0) {
|
|
733
|
+
throw new Error(
|
|
734
|
+
`x402-client/submitAction: actionId '${args.actionId}' is not present in priorNextActions.next[]`
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
const submitArgs = {
|
|
738
|
+
action,
|
|
739
|
+
signed,
|
|
740
|
+
exchangeId: String(args.exchangeId),
|
|
741
|
+
network: args.network,
|
|
742
|
+
escrowAddress: args.escrowAddress,
|
|
743
|
+
fulfillment: args.fulfillment,
|
|
744
|
+
fetch: args.fetch,
|
|
745
|
+
timeoutMs: args.timeoutMs
|
|
746
|
+
};
|
|
747
|
+
return submitAction(submitArgs);
|
|
748
|
+
},
|
|
495
749
|
signWithdrawFunds(args) {
|
|
496
750
|
return signWithdrawFunds(args, { buildCoreSdk, getSignerAddress: getBuyerAddress });
|
|
497
751
|
},
|
|
@@ -552,6 +806,6 @@ function deriveEip712DomainType(domain) {
|
|
|
552
806
|
return fields;
|
|
553
807
|
}
|
|
554
808
|
|
|
555
|
-
export { createX402bClient, parsePaymentResponse, pickAction, resolveFulfillment, signerFromEthersAdapter };
|
|
809
|
+
export { AllChannelsFailedError, NoCompatibleChannelError, createX402bClient, decodeBase64, encodeBase64, parsePaymentResponse, pickAction, resolveFulfillment, signerFromEthersAdapter, submitAction };
|
|
556
810
|
//# sourceMappingURL=index.js.map
|
|
557
811
|
//# sourceMappingURL=index.js.map
|