@bitpal/checkout 0.6.0 → 0.8.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/index.cjs CHANGED
@@ -37,13 +37,23 @@ __export(index_exports, {
37
37
  PaymentLinksResource: () => PaymentLinksResource,
38
38
  PricesResource: () => PricesResource,
39
39
  ProductsResource: () => ProductsResource,
40
+ TRANSFER_WITH_AUTHORIZATION_TYPES: () => TRANSFER_WITH_AUTHORIZATION_TYPES,
40
41
  WEBHOOK_HEADERS: () => WEBHOOK_HEADERS,
42
+ X402Gate: () => X402Gate,
43
+ X402_PAYMENT_HEADER: () => X402_PAYMENT_HEADER,
44
+ X402_PAYMENT_RESPONSE_HEADER: () => X402_PAYMENT_RESPONSE_HEADER,
45
+ createMemoryReplayStore: () => createMemoryReplayStore,
46
+ createX402Payment: () => createX402Payment,
47
+ decodePaymentHeader: () => decodePaymentHeader,
48
+ encodePaymentHeader: () => encodePaymentHeader,
41
49
  fromAtomic: () => fromAtomic,
42
50
  fromAtomicUSDC: () => fromAtomicUSDC,
43
51
  parseWebhookEvent: () => parseWebhookEvent,
52
+ splitSignature: () => splitSignature,
44
53
  toAtomic: () => toAtomic,
45
54
  toAtomicUSDC: () => toAtomicUSDC,
46
- verifyWebhookSignature: () => verifyWebhookSignature
55
+ verifyWebhookSignature: () => verifyWebhookSignature,
56
+ x402: () => x402
47
57
  });
48
58
  module.exports = __toCommonJS(index_exports);
49
59
 
@@ -214,7 +224,11 @@ var BitPalCheckoutClient = class {
214
224
  if (params.pay_chain && params.allowed_pay_chains && params.allowed_pay_chains.length > 0) {
215
225
  throw new Error("[BitPal] pay_chain and allowed_pay_chains are mutually exclusive \u2014 use only one.");
216
226
  }
217
- const body = { ...params, line_items: params.line_items.map(validateLineItem) };
227
+ const body = {
228
+ ...params,
229
+ line_items: params.line_items.map(validateLineItem),
230
+ ...params.allowed_assets ? { allowed_assets: params.allowed_assets.map((a) => ({ caip2: a.chain, token: a.token })) } : {}
231
+ };
218
232
  const res = await this.request(
219
233
  "POST",
220
234
  "/v1/checkout/sessions",
@@ -404,6 +418,540 @@ function fromAtomicUSDC(atomic) {
404
418
  return fromAtomic(atomic, 6);
405
419
  }
406
420
 
421
+ // src/x402.ts
422
+ var X402_PAYMENT_HEADER = "x-payment";
423
+ var X402_PAYMENT_RESPONSE_HEADER = "X-PAYMENT-RESPONSE";
424
+ var RESIGN_REQUIRED_STATUSES = /* @__PURE__ */ new Set(["reverted", "expired", "failed"]);
425
+ var DEFAULT_BASE_URL2 = "https://api.bitpal.io";
426
+ var DEFAULT_TIMEOUT2 = 6e4;
427
+ var DEFAULT_MAX_TIMEOUT_SECONDS = 300;
428
+ var MIN_VALIDITY_SECONDS = 60;
429
+ var MAX_VALIDITY_SECONDS = 3600;
430
+ var CONSUMED_TTL_SECONDS = 24 * 60 * 60;
431
+ var MEMORY_STORE_MAX_ENTRIES = 5e4;
432
+ var MAX_PAYMENT_HEADER_BYTES = 8192;
433
+ function createMemoryReplayStore() {
434
+ const entries = /* @__PURE__ */ new Map();
435
+ const prune = (now) => {
436
+ for (const [k, v] of entries) {
437
+ if (v.expiresAt <= now) entries.delete(k);
438
+ }
439
+ if (entries.size <= MEMORY_STORE_MAX_ENTRIES) return;
440
+ for (const [k, v] of entries) {
441
+ if (entries.size <= MEMORY_STORE_MAX_ENTRIES) break;
442
+ if (v.state === "inflight") entries.delete(k);
443
+ }
444
+ };
445
+ return {
446
+ claim(key, inflightTtlSeconds) {
447
+ const now = Date.now();
448
+ prune(now);
449
+ const found = entries.get(key);
450
+ if (found) return found.state;
451
+ if (entries.size >= MEMORY_STORE_MAX_ENTRIES) {
452
+ throw new BitPalError(
453
+ "[BitPal] x402: replay store is full. Pass a shared replayStore (Redis/DB) for this traffic level.",
454
+ 503
455
+ );
456
+ }
457
+ entries.set(key, { state: "inflight", expiresAt: now + inflightTtlSeconds * 1e3 });
458
+ return "ok";
459
+ },
460
+ commit(key) {
461
+ entries.set(key, { state: "used", expiresAt: Date.now() + CONSUMED_TTL_SECONDS * 1e3 });
462
+ },
463
+ release(key) {
464
+ entries.delete(key);
465
+ }
466
+ };
467
+ }
468
+ var X402Gate = class {
469
+ opts;
470
+ baseUrl;
471
+ timeout;
472
+ store;
473
+ maxTimeoutSeconds;
474
+ constructor(opts) {
475
+ if (!opts.apiKey) throw new Error("[BitPal] x402: apiKey is required.");
476
+ if (!/^0x[a-fA-F0-9]{40}$/.test(opts.payTo)) {
477
+ throw new Error("[BitPal] x402: payTo must be a 0x EVM address (your registered payout wallet).");
478
+ }
479
+ if (!/^\d+$/.test(opts.amount)) {
480
+ throw new Error('[BitPal] x402: amount must be an atomic integer string (USDC 6dp, $1 = "1000000").');
481
+ }
482
+ const timeoutSeconds = opts.maxTimeoutSeconds ?? DEFAULT_MAX_TIMEOUT_SECONDS;
483
+ if (timeoutSeconds < MIN_VALIDITY_SECONDS || timeoutSeconds > MAX_VALIDITY_SECONDS) {
484
+ throw new Error(
485
+ `[BitPal] x402: maxTimeoutSeconds must be between ${MIN_VALIDITY_SECONDS} and ${MAX_VALIDITY_SECONDS} (got ${timeoutSeconds}).`
486
+ );
487
+ }
488
+ this.opts = opts;
489
+ this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL2).replace(/\/$/, "");
490
+ this.timeout = opts.timeout ?? DEFAULT_TIMEOUT2;
491
+ this.store = opts.replayStore ?? createMemoryReplayStore();
492
+ this.maxTimeoutSeconds = opts.maxTimeoutSeconds ?? DEFAULT_MAX_TIMEOUT_SECONDS;
493
+ }
494
+ /** 이 리소스의 402 바디를 서버에서 받아온다(수수료 분해 포함). */
495
+ async requirements(resource) {
496
+ return this.request("POST", "/v1/x402/requirements", {
497
+ payTo: this.opts.payTo,
498
+ amount: this.opts.amount,
499
+ resource,
500
+ ...this.opts.network ? { network: this.opts.network } : {},
501
+ ...this.opts.asset ? { asset: this.opts.asset } : {},
502
+ ...this.opts.description ? { description: this.opts.description } : {},
503
+ ...this.opts.mimeType ? { mimeType: this.opts.mimeType } : {},
504
+ maxTimeoutSeconds: this.maxTimeoutSeconds
505
+ });
506
+ }
507
+ /**
508
+ * `X-PAYMENT` 헤더 1건을 처리한다. 헤더가 없거나 깨졌으면 402, 정산까지 끝나면 `settled`.
509
+ *
510
+ * 순서가 중요하다: **점유 → verify → settle → 소진 기록**. 점유를 verify 앞에 두어야 같은
511
+ * authorization 을 동시에 밀어 넣어도 한 요청만 리소스를 받는다.
512
+ */
513
+ async collect(input) {
514
+ const { header, resource } = input;
515
+ if (!header) return this.paymentRequired(resource);
516
+ let payload;
517
+ try {
518
+ payload = decodePaymentHeader(header);
519
+ } catch {
520
+ return this.paymentRequired(resource, "MALFORMED_PAYMENT_HEADER");
521
+ }
522
+ const merchant = payload?.payload?.merchant;
523
+ if (!merchant?.from || !merchant?.nonce) {
524
+ return this.paymentRequired(resource, "MALFORMED_PAYMENT_HEADER");
525
+ }
526
+ const key = `${payload.network}:${merchant.from.toLowerCase()}:${merchant.nonce.toLowerCase()}`;
527
+ const claim = await this.store.claim(key, this.maxTimeoutSeconds);
528
+ if (claim === "used") {
529
+ return {
530
+ kind: "already_used",
531
+ status: 409,
532
+ body: {
533
+ error: "PAYMENT_ALREADY_USED",
534
+ message: "This payment has already been redeemed. One payment unlocks one response."
535
+ }
536
+ };
537
+ }
538
+ if (claim === "inflight") {
539
+ return {
540
+ kind: "unconfirmed",
541
+ status: 503,
542
+ body: {
543
+ error: "PAYMENT_IN_PROGRESS",
544
+ message: "This payment is still being settled. Retry with the same X-PAYMENT header."
545
+ }
546
+ };
547
+ }
548
+ let accept;
549
+ try {
550
+ accept = await this.firstAccept(resource);
551
+ } catch (err) {
552
+ await this.store.release(key);
553
+ throw err;
554
+ }
555
+ const body = {
556
+ x402Version: 1,
557
+ paymentPayload: {
558
+ scheme: "exact",
559
+ network: payload.network,
560
+ payload: payload.payload
561
+ },
562
+ paymentRequirements: {
563
+ asset: accept.asset,
564
+ payTo: accept.payTo,
565
+ maxAmountRequired: accept.maxAmountRequired,
566
+ resource: accept.resource
567
+ }
568
+ };
569
+ try {
570
+ await this.request("POST", "/v1/x402/verify", body);
571
+ } catch (err) {
572
+ await this.store.release(key);
573
+ if (err instanceof BitPalError && err.statusCode < 500) {
574
+ return this.paymentRequired(resource, err.message, accept);
575
+ }
576
+ throw err;
577
+ }
578
+ let settled;
579
+ try {
580
+ settled = await this.request("POST", "/v1/x402/settle", body);
581
+ } catch (err) {
582
+ if (err instanceof BitPalError && err.statusCode < 500) {
583
+ await this.store.release(key);
584
+ return this.paymentRequired(resource, err.message, accept);
585
+ }
586
+ await this.store.release(key);
587
+ return {
588
+ kind: "unconfirmed",
589
+ status: 503,
590
+ body: {
591
+ error: "SETTLEMENT_UNCONFIRMED",
592
+ message: "Payment settlement is not confirmed yet. Retry this request with the same X-PAYMENT header \u2014 settlement is idempotent."
593
+ }
594
+ };
595
+ }
596
+ if (settled.status !== "confirmed") {
597
+ await this.store.release(key);
598
+ if (RESIGN_REQUIRED_STATUSES.has(settled.status)) {
599
+ return this.paymentRequired(resource, `PAYMENT_${settled.status.toUpperCase()}`, accept);
600
+ }
601
+ return {
602
+ kind: "unconfirmed",
603
+ status: 503,
604
+ body: {
605
+ error: "SETTLEMENT_UNCONFIRMED",
606
+ message: `Payment is still settling (${settled.status}). Retry with the same X-PAYMENT header \u2014 do not create a new payment.`,
607
+ paymentId: settled.payment_id
608
+ }
609
+ };
610
+ }
611
+ const payment = {
612
+ paymentId: settled.payment_id,
613
+ txHash: settled.tx_hash,
614
+ feeTxHash: settled.fee_tx_hash ?? null,
615
+ network: settled.network,
616
+ payer: merchant.from,
617
+ amount: accept.maxAmountRequired,
618
+ idempotent: settled.idempotent ?? false
619
+ };
620
+ return {
621
+ kind: "settled",
622
+ status: 200,
623
+ payment,
624
+ paymentResponseHeader: encodeBase64Json({
625
+ success: true,
626
+ transaction: settled.tx_hash,
627
+ network: settled.network,
628
+ payer: merchant.from,
629
+ paymentId: settled.payment_id
630
+ }),
631
+ // 소진 확정은 **응답을 실제로 내준 뒤**다. 핸들러가 throw 하거나 프로세스가 죽으면 결제만 하고
632
+ // 리소스를 못 받는데, 그때 commit 되어 있으면 재시도가 409 로 막힌다.
633
+ commit: () => Promise.resolve(this.store.commit(key)),
634
+ release: () => Promise.resolve(this.store.release(key))
635
+ };
636
+ }
637
+ /* ─── 내부 ─── */
638
+ async firstAccept(resource) {
639
+ const requirements = await this.requirements(resource);
640
+ const accept = requirements.accepts[0];
641
+ if (!accept) {
642
+ throw new BitPalError("[BitPal] x402: no payment option available for this API key mode.", 500);
643
+ }
644
+ return accept;
645
+ }
646
+ async paymentRequired(resource, error, known) {
647
+ const requirements = known ? { x402Version: 1, accepts: [known] } : await this.requirements(resource);
648
+ return {
649
+ kind: "payment_required",
650
+ status: 402,
651
+ body: error ? { ...requirements, error } : requirements
652
+ };
653
+ }
654
+ async request(method, path, body) {
655
+ const controller = new AbortController();
656
+ const timer = setTimeout(() => controller.abort(), this.timeout);
657
+ try {
658
+ const res = await fetch(`${this.baseUrl}${path}`, {
659
+ method,
660
+ headers: {
661
+ "Content-Type": "application/json",
662
+ Authorization: `Bearer ${this.opts.apiKey}`
663
+ },
664
+ body: body ? JSON.stringify(body) : void 0,
665
+ signal: controller.signal
666
+ });
667
+ const json = await res.json();
668
+ if (!res.ok) {
669
+ throw new BitPalError(String(json.error ?? `HTTP ${res.status}`), res.status);
670
+ }
671
+ return json.data;
672
+ } catch (err) {
673
+ if (err instanceof BitPalError) throw err;
674
+ throw new BitPalError(err instanceof Error ? err.message : String(err), 599);
675
+ } finally {
676
+ clearTimeout(timer);
677
+ }
678
+ }
679
+ };
680
+ function x402(options) {
681
+ const gate = new X402Gate(options);
682
+ return async function x402Middleware(req, res, next) {
683
+ try {
684
+ const result = await gate.collect({
685
+ header: readHeader(req, X402_PAYMENT_HEADER),
686
+ resource: resolveResource(req, options.resource)
687
+ });
688
+ if (result.kind !== "settled") {
689
+ res.status(result.status).json(result.body);
690
+ return;
691
+ }
692
+ res.setHeader(X402_PAYMENT_RESPONSE_HEADER, result.paymentResponseHeader);
693
+ req.x402Payment = result.payment;
694
+ if (typeof res.once === "function") {
695
+ let finalized = false;
696
+ const finalize = (delivered) => {
697
+ if (finalized) return;
698
+ finalized = true;
699
+ const done = delivered ? result.commit() : result.release();
700
+ void done.catch(() => void 0);
701
+ };
702
+ res.once("finish", () => finalize((res.statusCode ?? 200) < 500));
703
+ res.once("close", () => finalize(res.writableEnded === true && (res.statusCode ?? 200) < 500));
704
+ } else {
705
+ await result.commit();
706
+ }
707
+ if (options.onSettled) await options.onSettled(result.payment, req);
708
+ next();
709
+ } catch (err) {
710
+ next(err);
711
+ }
712
+ };
713
+ }
714
+ function readHeader(req, name) {
715
+ const direct = req.headers?.[name] ?? req.headers?.[name.toUpperCase()];
716
+ const value = Array.isArray(direct) ? direct[0] : direct;
717
+ if (value) return value;
718
+ return req.get?.(name);
719
+ }
720
+ function resolveResource(req, override) {
721
+ if (typeof override === "function") return override(req);
722
+ if (override) return override;
723
+ const host = req.get?.("host") ?? readHeader(req, "host") ?? "localhost";
724
+ const protocol = req.protocol ?? "https";
725
+ const raw = req.path != null ? `${req.baseUrl ?? ""}${req.path}` : req.originalUrl ?? req.url ?? "/";
726
+ const path = raw.split("?")[0] || "/";
727
+ return `${protocol}://${host}${path}`;
728
+ }
729
+ function decodePaymentHeader(header) {
730
+ if (header.length > MAX_PAYMENT_HEADER_BYTES) {
731
+ throw new Error("[BitPal] x402: X-PAYMENT header too large.");
732
+ }
733
+ const trimmed = header.trim();
734
+ const json = trimmed.startsWith("{") ? trimmed : decodeBase64(trimmed);
735
+ const parsed = JSON.parse(json);
736
+ return assertPaymentPayload(parsed);
737
+ }
738
+ var ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;
739
+ var HEX32_RE = /^0x[a-fA-F0-9]{64}$/;
740
+ var DECIMAL_RE = /^\d{1,78}$/;
741
+ function assertPaymentPayload(value) {
742
+ const root = asObject(value, "payment payload");
743
+ if (root.x402Version !== 1) throw new Error("[BitPal] x402: unsupported x402Version.");
744
+ if (root.scheme !== "exact") throw new Error("[BitPal] x402: unsupported scheme.");
745
+ if (typeof root.network !== "string" || root.network.length === 0 || root.network.length > 64) {
746
+ throw new Error("[BitPal] x402: invalid network.");
747
+ }
748
+ const inner = asObject(root.payload, "payload");
749
+ const merchant = assertAuthorization(inner.merchant, "merchant");
750
+ const fee = inner.fee === void 0 ? void 0 : assertAuthorization(inner.fee, "fee");
751
+ return {
752
+ x402Version: 1,
753
+ scheme: "exact",
754
+ network: root.network,
755
+ payload: fee ? { merchant, fee } : { merchant }
756
+ };
757
+ }
758
+ function assertAuthorization(value, label) {
759
+ const a = asObject(value, `${label} authorization`);
760
+ const sig = asObject(a.signature, `${label} signature`);
761
+ const check = (ok, field) => {
762
+ if (!ok) throw new Error(`[BitPal] x402: invalid ${label}.${field}.`);
763
+ };
764
+ check(typeof a.from === "string" && ADDRESS_RE.test(a.from), "from");
765
+ check(typeof a.to === "string" && ADDRESS_RE.test(a.to), "to");
766
+ check(typeof a.value === "string" && DECIMAL_RE.test(a.value), "value");
767
+ check(typeof a.validAfter === "string" && DECIMAL_RE.test(a.validAfter), "validAfter");
768
+ check(typeof a.validBefore === "string" && DECIMAL_RE.test(a.validBefore), "validBefore");
769
+ check(typeof a.nonce === "string" && HEX32_RE.test(a.nonce), "nonce");
770
+ check(typeof sig.v === "number" && Number.isInteger(sig.v), "signature.v");
771
+ check(typeof sig.r === "string" && HEX32_RE.test(sig.r), "signature.r");
772
+ check(typeof sig.s === "string" && HEX32_RE.test(sig.s), "signature.s");
773
+ return {
774
+ from: a.from,
775
+ to: a.to,
776
+ value: a.value,
777
+ validAfter: a.validAfter,
778
+ validBefore: a.validBefore,
779
+ nonce: a.nonce,
780
+ signature: { v: sig.v, r: sig.r, s: sig.s }
781
+ };
782
+ }
783
+ function asObject(value, label) {
784
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
785
+ throw new Error(`[BitPal] x402: ${label} must be an object.`);
786
+ }
787
+ return value;
788
+ }
789
+ function encodePaymentHeader(payload) {
790
+ return encodeBase64Json(payload);
791
+ }
792
+ function encodeBase64Json(value) {
793
+ const json = JSON.stringify(value);
794
+ const g = globalThis;
795
+ if (typeof g.btoa === "function") {
796
+ const bytes = new TextEncoder().encode(json);
797
+ let binary = "";
798
+ for (const b of bytes) binary += String.fromCharCode(b);
799
+ return g.btoa(binary);
800
+ }
801
+ return bufferFrom(json, "utf8").toString("base64");
802
+ }
803
+ function decodeBase64(value) {
804
+ const g = globalThis;
805
+ if (typeof g.atob === "function") {
806
+ const binary = g.atob(value);
807
+ const bytes = new Uint8Array(binary.length);
808
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
809
+ return new TextDecoder().decode(bytes);
810
+ }
811
+ return bufferFrom(value, "base64").toString("utf8");
812
+ }
813
+ function bufferFrom(value, encoding) {
814
+ const g = globalThis;
815
+ if (!g.Buffer) throw new Error("[BitPal] x402: no base64 implementation available in this runtime.");
816
+ return g.Buffer.from(value, encoding);
817
+ }
818
+
819
+ // src/x402-pay.ts
820
+ var TRANSFER_WITH_AUTHORIZATION_TYPES = {
821
+ TransferWithAuthorization: [
822
+ { name: "from", type: "address" },
823
+ { name: "to", type: "address" },
824
+ { name: "value", type: "uint256" },
825
+ { name: "validAfter", type: "uint256" },
826
+ { name: "validBefore", type: "uint256" },
827
+ { name: "nonce", type: "bytes32" }
828
+ ]
829
+ };
830
+ var CLOCK_SKEW_MARGIN_SECONDS = 30;
831
+ var SERVER_MIN_VALIDITY_SECONDS = 60;
832
+ var MIN_VALIDITY_SECONDS2 = SERVER_MIN_VALIDITY_SECONDS + CLOCK_SKEW_MARGIN_SECONDS;
833
+ var MAX_VALIDITY_SECONDS2 = 3600;
834
+ async function createX402Payment(options) {
835
+ const accept = resolveAccept(options.requirements);
836
+ const now = Math.floor((options.now?.() ?? Date.now()) / 1e3);
837
+ const validFor = options.validForSeconds ?? accept.maxTimeoutSeconds;
838
+ if (validFor < MIN_VALIDITY_SECONDS2 || validFor > MAX_VALIDITY_SECONDS2) {
839
+ throw new Error(
840
+ `[BitPal] x402: validForSeconds must be between ${MIN_VALIDITY_SECONDS2} and ${MAX_VALIDITY_SECONDS2} (got ${validFor}). The server requires ${SERVER_MIN_VALIDITY_SECONDS}s minimum; the extra ${CLOCK_SKEW_MARGIN_SECONDS}s absorbs clock skew and round-trip latency.`
841
+ );
842
+ }
843
+ const domain = {
844
+ name: accept.extra.name,
845
+ version: accept.extra.version,
846
+ chainId: accept.extra.chainId,
847
+ verifyingContract: accept.asset
848
+ };
849
+ if (!domain.chainId) {
850
+ throw new Error("[BitPal] x402: the 402 response is missing extra.chainId \u2014 cannot build the EIP-712 domain.");
851
+ }
852
+ const validAfter = "0";
853
+ const validBefore = String(now + validFor);
854
+ const nextNonce = options.randomNonce ?? randomNonce;
855
+ const fee = accept.feeBreakdown;
856
+ const feeAmount = fee ? BigInt(fee.feeAmount) : 0n;
857
+ const merchantValue = fee ? fee.netAmount : accept.maxAmountRequired;
858
+ const merchant = await signAuthorization({
859
+ domain,
860
+ signTypedData: options.signTypedData,
861
+ from: options.from,
862
+ to: accept.payTo,
863
+ value: merchantValue,
864
+ validAfter,
865
+ validBefore,
866
+ nonce: nextNonce()
867
+ });
868
+ let feeLeg;
869
+ if (feeAmount > 0n) {
870
+ if (!fee?.feeRecipient) {
871
+ throw new Error("[BitPal] x402: feeAmount > 0 but feeRecipient is missing in the 402 response.");
872
+ }
873
+ feeLeg = await signAuthorization({
874
+ domain,
875
+ signTypedData: options.signTypedData,
876
+ from: options.from,
877
+ to: fee.feeRecipient,
878
+ value: fee.feeAmount,
879
+ validAfter,
880
+ validBefore,
881
+ nonce: nextNonce()
882
+ });
883
+ if (feeLeg.nonce.toLowerCase() === merchant.nonce.toLowerCase()) {
884
+ throw new Error("[BitPal] x402: the two authorizations must use different nonces.");
885
+ }
886
+ }
887
+ const payload = {
888
+ x402Version: 1,
889
+ scheme: "exact",
890
+ network: accept.network,
891
+ payload: feeLeg ? { merchant, fee: feeLeg } : { merchant }
892
+ };
893
+ return {
894
+ header: encodePaymentHeader(payload),
895
+ payload,
896
+ totalAmount: (BigInt(merchantValue) + feeAmount).toString()
897
+ };
898
+ }
899
+ function resolveAccept(input) {
900
+ if ("accepts" in input) {
901
+ const first = input.accepts?.[0];
902
+ if (!first) throw new Error("[BitPal] x402: the 402 response has no accepts[] entry.");
903
+ return first;
904
+ }
905
+ return input;
906
+ }
907
+ async function signAuthorization(args) {
908
+ const message = {
909
+ from: args.from,
910
+ to: args.to,
911
+ value: args.value,
912
+ validAfter: args.validAfter,
913
+ validBefore: args.validBefore,
914
+ nonce: args.nonce
915
+ };
916
+ const signature = await args.signTypedData({
917
+ domain: args.domain,
918
+ types: TRANSFER_WITH_AUTHORIZATION_TYPES,
919
+ primaryType: "TransferWithAuthorization",
920
+ message
921
+ });
922
+ return { ...message, signature: splitSignature(signature) };
923
+ }
924
+ function splitSignature(signature) {
925
+ const hex = signature.startsWith("0x") ? signature.slice(2) : signature;
926
+ if (!/^[0-9a-fA-F]+$/.test(hex) || hex.length !== 130 && hex.length !== 128) {
927
+ throw new Error(
928
+ `[BitPal] x402: expected a 65-byte or 64-byte (EIP-2098) hex signature, got ${signature.length} chars.`
929
+ );
930
+ }
931
+ const r = `0x${hex.slice(0, 64)}`;
932
+ if (hex.length === 128) {
933
+ const yParityAndS = BigInt(`0x${hex.slice(64, 128)}`);
934
+ const yParity = Number(yParityAndS >> 255n & 1n);
935
+ const s2 = yParityAndS & (1n << 255n) - 1n;
936
+ return { v: 27 + yParity, r, s: `0x${s2.toString(16).padStart(64, "0")}` };
937
+ }
938
+ const s = `0x${hex.slice(64, 128)}`;
939
+ const raw = parseInt(hex.slice(128, 130), 16);
940
+ if (raw === 27 || raw === 28) return { v: raw, r, s };
941
+ if (raw === 0 || raw === 1) return { v: raw + 27, r, s };
942
+ throw new Error(`[BitPal] x402: unexpected signature v=${raw} (expected 0, 1, 27 or 28).`);
943
+ }
944
+ function randomNonce() {
945
+ const g = globalThis;
946
+ if (!g.crypto?.getRandomValues) {
947
+ throw new Error("[BitPal] x402: crypto.getRandomValues is unavailable \u2014 pass randomNonce explicitly.");
948
+ }
949
+ const bytes = g.crypto.getRandomValues(new Uint8Array(32));
950
+ let hex = "";
951
+ for (const b of bytes) hex += b.toString(16).padStart(2, "0");
952
+ return `0x${hex}`;
953
+ }
954
+
407
955
  // src/index.ts
408
956
  var BitPal = class {
409
957
  checkout;
@@ -421,11 +969,21 @@ var BitPal = class {
421
969
  PaymentLinksResource,
422
970
  PricesResource,
423
971
  ProductsResource,
972
+ TRANSFER_WITH_AUTHORIZATION_TYPES,
424
973
  WEBHOOK_HEADERS,
974
+ X402Gate,
975
+ X402_PAYMENT_HEADER,
976
+ X402_PAYMENT_RESPONSE_HEADER,
977
+ createMemoryReplayStore,
978
+ createX402Payment,
979
+ decodePaymentHeader,
980
+ encodePaymentHeader,
425
981
  fromAtomic,
426
982
  fromAtomicUSDC,
427
983
  parseWebhookEvent,
984
+ splitSignature,
428
985
  toAtomic,
429
986
  toAtomicUSDC,
430
- verifyWebhookSignature
987
+ verifyWebhookSignature,
988
+ x402
431
989
  });