@routedock/routedock 0.1.0 → 0.1.1

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
@@ -10,9 +10,11 @@ var client$3 = require('@stellar/mpp/charge/client');
10
10
  var client$2 = require('mppx/client');
11
11
  var client$4 = require('@stellar/mpp/channel/client');
12
12
  var facilitator = require('@x402/stellar/exact/facilitator');
13
- var server$1 = require('@stellar/mpp/charge/server');
14
- var server = require('mppx/server');
15
- var server$2 = require('@stellar/mpp/channel/server');
13
+ var server$1 = require('@x402/stellar/exact/server');
14
+ var server = require('@x402/core/server');
15
+ var server$3 = require('@stellar/mpp/charge/server');
16
+ var server$2 = require('mppx/server');
17
+ var server$4 = require('@stellar/mpp/channel/server');
16
18
 
17
19
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
18
20
 
@@ -611,13 +613,29 @@ var CAIP22 = {
611
613
  testnet: "stellar:testnet",
612
614
  mainnet: "stellar:pubnet"
613
615
  };
616
+ var OZ_FACILITATOR_URL = "https://channels.openzeppelin.com/x402";
614
617
  function createX402Handler(opts) {
615
618
  const caip2 = CAIP22[opts.network];
616
619
  const payeeKeypair = stellarSdk.Keypair.fromSecret(opts.payeeSecretKey);
617
620
  const signer = stellar.createEd25519Signer(opts.payeeSecretKey, caip2);
618
- const facilitatorScheme = new facilitator.ExactStellarScheme([signer], {
621
+ const useOzFacilitator = opts.network === "mainnet" && opts.facilitatorApiKey;
622
+ const localFacilitator = new facilitator.ExactStellarScheme([signer], {
619
623
  areFeesSponsored: true
620
624
  });
625
+ let ozServer = null;
626
+ if (useOzFacilitator) {
627
+ const apiKey = opts.facilitatorApiKey;
628
+ const facilitator = new server.HTTPFacilitatorClient({
629
+ url: OZ_FACILITATOR_URL,
630
+ createAuthHeaders: async () => ({
631
+ verify: { Authorization: `Bearer ${apiKey}` },
632
+ settle: { Authorization: `Bearer ${apiKey}` },
633
+ supported: { Authorization: `Bearer ${apiKey}` }
634
+ })
635
+ });
636
+ ozServer = new server.x402ResourceServer(facilitator);
637
+ ozServer.register(caip2, new server$1.ExactStellarScheme());
638
+ }
621
639
  const amountInBaseUnits = String(Math.round(parseFloat(opts.amount) * 1e7));
622
640
  const requirements = {
623
641
  scheme: "exact",
@@ -628,52 +646,78 @@ function createX402Handler(opts) {
628
646
  maxTimeoutSeconds: 60,
629
647
  extra: {
630
648
  areFeesSponsored: true,
631
- facilitatorAddresses: [payeeKeypair.publicKey()]
649
+ ...useOzFacilitator ? {} : { facilitatorAddresses: [payeeKeypair.publicKey()] }
632
650
  }
633
651
  };
634
652
  return async (req, res, next) => {
635
653
  try {
636
654
  const paymentHeader = req.headers["payment-signature"] ?? req.headers["x-payment"];
637
655
  if (!paymentHeader) {
638
- const x402Response = {
639
- x402Version: 2,
640
- resource: {
656
+ if (ozServer) {
657
+ const resourceInfo = {
641
658
  url: `${req.protocol}://${req.get("host") ?? ""}${req.originalUrl}`,
642
659
  description: opts.manifest.name
643
- },
644
- accepts: [requirements]
645
- };
646
- res.status(402).setHeader("Content-Type", "application/json").setHeader("X-Payment-Requirements", http.encodePaymentRequiredHeader(x402Response)).json({ error: "Payment Required" });
660
+ };
661
+ const paymentRequired = await ozServer.createPaymentRequiredResponse(
662
+ [requirements],
663
+ resourceInfo
664
+ );
665
+ res.status(402).setHeader("Content-Type", "application/json").setHeader("X-Payment-Requirements", http.encodePaymentRequiredHeader(paymentRequired)).json({ error: "Payment Required" });
666
+ } else {
667
+ const x402Response = {
668
+ x402Version: 2,
669
+ resource: {
670
+ url: `${req.protocol}://${req.get("host") ?? ""}${req.originalUrl}`,
671
+ description: opts.manifest.name
672
+ },
673
+ accepts: [requirements]
674
+ };
675
+ res.status(402).setHeader("Content-Type", "application/json").setHeader("X-Payment-Requirements", http.encodePaymentRequiredHeader(x402Response)).json({ error: "Payment Required" });
676
+ }
647
677
  return;
648
678
  }
649
679
  const payload = http.decodePaymentSignatureHeader(paymentHeader);
650
- const verifyResult = await facilitatorScheme.verify(
651
- payload,
652
- requirements
653
- );
654
- if (!verifyResult.isValid) {
655
- res.status(401).json({
656
- error: "Payment verification failed",
657
- reason: verifyResult.invalidReason
658
- });
659
- return;
680
+ let txHash = null;
681
+ if (ozServer) {
682
+ const settleResult = await ozServer.settlePayment(payload, requirements);
683
+ txHash = settleResult.transaction ?? null;
684
+ if (settleResult) {
685
+ res.setHeader(
686
+ "X-Payment-Response",
687
+ http.encodePaymentResponseHeader(
688
+ settleResult
689
+ )
690
+ );
691
+ }
692
+ } else {
693
+ const verifyResult = await localFacilitator.verify(
694
+ payload,
695
+ requirements
696
+ );
697
+ if (!verifyResult.isValid) {
698
+ res.status(401).json({
699
+ error: "Payment verification failed",
700
+ reason: verifyResult.invalidReason
701
+ });
702
+ return;
703
+ }
704
+ const settleResult = await localFacilitator.settle(
705
+ payload,
706
+ requirements
707
+ );
708
+ txHash = settleResult.transaction ?? null;
709
+ if (settleResult) {
710
+ res.setHeader(
711
+ "X-Payment-Response",
712
+ http.encodePaymentResponseHeader(
713
+ settleResult
714
+ )
715
+ );
716
+ }
660
717
  }
661
- const settleResult = await facilitatorScheme.settle(
662
- payload,
663
- requirements
664
- );
665
- const txHash = settleResult.transaction ?? null;
666
718
  if (txHash && opts.onSettled) {
667
719
  await opts.onSettled(txHash, opts.amount, "x402");
668
720
  }
669
- if (settleResult) {
670
- res.setHeader(
671
- "X-Payment-Response",
672
- http.encodePaymentResponseHeader(
673
- settleResult
674
- )
675
- );
676
- }
677
721
  next();
678
722
  } catch (err) {
679
723
  console.error("[x402] Settlement error:", err);
@@ -688,10 +732,10 @@ var MPP_NETWORK = {
688
732
  function createMppChargeHandler(opts) {
689
733
  const networkId = MPP_NETWORK[opts.network];
690
734
  const amountHumanReadable = opts.amount;
691
- const mppx = server.Mppx.create({
735
+ const mppx = server$2.Mppx.create({
692
736
  secretKey: opts.payeeSecretKey,
693
737
  methods: [
694
- server$1.stellar.charge({
738
+ server$3.stellar.charge({
695
739
  recipient: opts.manifest.payee,
696
740
  currency: opts.assetContract,
697
741
  network: networkId,
@@ -701,7 +745,7 @@ function createMppChargeHandler(opts) {
701
745
  });
702
746
  return async (req, res, next) => {
703
747
  try {
704
- const fetchReq = server.Request.fromNodeListener(req, res);
748
+ const fetchReq = server$2.Request.fromNodeListener(req, res);
705
749
  const handler = mppx["stellar/charge"];
706
750
  const result = await handler({
707
751
  amount: amountHumanReadable,
@@ -745,8 +789,10 @@ function createMppSessionHandler(opts) {
745
789
  const rateHuman = opts.rate;
746
790
  const payeeKeypair = stellarSdk.Keypair.fromSecret(opts.payeeSecretKey);
747
791
  const cumulativeKey = `stellar:channel:cumulative:${opts.channelContract}`;
748
- const innerStore = server$2.Store.memory();
792
+ const innerStore = server$4.Store.memory();
749
793
  let lastCumulativeAmount = 0n;
794
+ let voucherCount = 0;
795
+ let sessionOpened = false;
750
796
  const wrappedStore = {
751
797
  async get(key) {
752
798
  return innerStore.get(key);
@@ -755,16 +801,27 @@ function createMppSessionHandler(opts) {
755
801
  await innerStore.put(key, value);
756
802
  if (key === cumulativeKey && value && typeof value === "object" && "amount" in value) {
757
803
  lastCumulativeAmount = BigInt(value.amount);
804
+ voucherCount++;
805
+ if (!sessionOpened) {
806
+ sessionOpened = true;
807
+ if (opts.onSessionOpen) {
808
+ await opts.onSessionOpen(opts.channelContract);
809
+ }
810
+ }
811
+ if (opts.onVoucher) {
812
+ const humanAmount = (Number(lastCumulativeAmount) / 1e7).toFixed(7);
813
+ await opts.onVoucher(voucherCount, humanAmount);
814
+ }
758
815
  }
759
816
  },
760
817
  async delete(key) {
761
818
  return innerStore.delete(key);
762
819
  }
763
820
  };
764
- const mppx = server.Mppx.create({
821
+ const mppx = server$2.Mppx.create({
765
822
  secretKey: opts.payeeSecretKey,
766
823
  methods: [
767
- server$2.stellar.channel({
824
+ server$4.stellar.channel({
768
825
  channel: opts.channelContract,
769
826
  commitmentKey: opts.commitmentPublicKey,
770
827
  network: networkId,
@@ -782,7 +839,7 @@ function createMppSessionHandler(opts) {
782
839
  const closeAmount = body?.amount ? BigInt(body.amount) : lastCumulativeAmount;
783
840
  const closeSig = body?.signature ?? lastSignatureHex;
784
841
  if (closeAmount > 0n && closeSig) {
785
- const closeTxHash = await server$2.close({
842
+ const closeTxHash = await server$4.close({
786
843
  channel: opts.channelContract,
787
844
  amount: closeAmount,
788
845
  signature: Buffer.from(closeSig, "hex"),
@@ -797,6 +854,9 @@ function createMppSessionHandler(opts) {
797
854
  } else {
798
855
  res.json({ closeTxHash: null, message: "no vouchers received" });
799
856
  }
857
+ sessionOpened = false;
858
+ voucherCount = 0;
859
+ lastCumulativeAmount = 0n;
800
860
  return;
801
861
  }
802
862
  const authHeader = req.headers["authorization"];
@@ -813,7 +873,7 @@ function createMppSessionHandler(opts) {
813
873
  } catch {
814
874
  }
815
875
  }
816
- const fetchReq = server.Request.fromNodeListener(req, res);
876
+ const fetchReq = server$2.Request.fromNodeListener(req, res);
817
877
  const result = await mppx.channel({
818
878
  amount: rateHuman,
819
879
  description: opts.manifest.name
@@ -882,7 +942,9 @@ function routedock(opts) {
882
942
  assetContract: opts.assetContract,
883
943
  manifest: opts.manifest,
884
944
  commitmentPublicKey: opts.commitmentPublicKey,
885
- ...opts.onSettled ? { onSettled: opts.onSettled } : {}
945
+ ...opts.onSettled ? { onSettled: opts.onSettled } : {},
946
+ ...opts.onSessionOpen ? { onSessionOpen: opts.onSessionOpen } : {},
947
+ ...opts.onVoucher ? { onVoucher: opts.onVoucher } : {}
886
948
  })
887
949
  );
888
950
  }
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { P as PaymentMode, a as PaymentResult, b as PricingConfig, R as RouteDockError, c as RouteDockManifest, d as RouteDockManifestError, e as RouteDockNoSupportedModeError, f as RouteDockPolicyRejectedError, g as RouteDockSessionError, S as SessionCloseResult, h as SessionHandle, i as SessionPricingConfig, j as SessionState, k as SessionStore, l as SupabaseSessionStore } from './SessionStore-CCVO0PHM.cjs';
2
2
  export { ModeSelectOptions, MppChargeClient, MppSessionClient, RouteDockClient, RouteDockClientConfig, SpendCap, X402Client, fetchManifest, selectMode } from './client.cjs';
3
- export { R as RouteDockMiddlewareOptions, r as routedock } from './routedockMiddleware-Beqan6QS.cjs';
3
+ export { R as RouteDockMiddlewareOptions, r as routedock } from './routedockMiddleware-B7WTB3LT.cjs';
4
4
  import '@supabase/supabase-js';
5
5
  import '@stellar/stellar-sdk';
6
6
  import 'express';
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { P as PaymentMode, a as PaymentResult, b as PricingConfig, R as RouteDockError, c as RouteDockManifest, d as RouteDockManifestError, e as RouteDockNoSupportedModeError, f as RouteDockPolicyRejectedError, g as RouteDockSessionError, S as SessionCloseResult, h as SessionHandle, i as SessionPricingConfig, j as SessionState, k as SessionStore, l as SupabaseSessionStore } from './SessionStore-CCVO0PHM.js';
2
2
  export { ModeSelectOptions, MppChargeClient, MppSessionClient, RouteDockClient, RouteDockClientConfig, SpendCap, X402Client, fetchManifest, selectMode } from './client.js';
3
- export { R as RouteDockMiddlewareOptions, r as routedock } from './routedockMiddleware-CqnyHjhO.js';
3
+ export { R as RouteDockMiddlewareOptions, r as routedock } from './routedockMiddleware-DRgdrycG.js';
4
4
  import '@supabase/supabase-js';
5
5
  import '@stellar/stellar-sdk';
6
6
  import 'express';
package/dist/index.js CHANGED
@@ -8,6 +8,8 @@ import { stellar } from '@stellar/mpp/charge/client';
8
8
  import { Mppx } from 'mppx/client';
9
9
  import { stellar as stellar$1 } from '@stellar/mpp/channel/client';
10
10
  import { ExactStellarScheme as ExactStellarScheme$1 } from '@x402/stellar/exact/facilitator';
11
+ import { ExactStellarScheme as ExactStellarScheme$2 } from '@x402/stellar/exact/server';
12
+ import { HTTPFacilitatorClient, x402ResourceServer } from '@x402/core/server';
11
13
  import { stellar as stellar$2 } from '@stellar/mpp/charge/server';
12
14
  import { Mppx as Mppx$1, Request } from 'mppx/server';
13
15
  import { Store, stellar as stellar$3, close } from '@stellar/mpp/channel/server';
@@ -605,13 +607,29 @@ var CAIP22 = {
605
607
  testnet: "stellar:testnet",
606
608
  mainnet: "stellar:pubnet"
607
609
  };
610
+ var OZ_FACILITATOR_URL = "https://channels.openzeppelin.com/x402";
608
611
  function createX402Handler(opts) {
609
612
  const caip2 = CAIP22[opts.network];
610
613
  const payeeKeypair = Keypair.fromSecret(opts.payeeSecretKey);
611
614
  const signer = createEd25519Signer(opts.payeeSecretKey, caip2);
612
- const facilitatorScheme = new ExactStellarScheme$1([signer], {
615
+ const useOzFacilitator = opts.network === "mainnet" && opts.facilitatorApiKey;
616
+ const localFacilitator = new ExactStellarScheme$1([signer], {
613
617
  areFeesSponsored: true
614
618
  });
619
+ let ozServer = null;
620
+ if (useOzFacilitator) {
621
+ const apiKey = opts.facilitatorApiKey;
622
+ const facilitator = new HTTPFacilitatorClient({
623
+ url: OZ_FACILITATOR_URL,
624
+ createAuthHeaders: async () => ({
625
+ verify: { Authorization: `Bearer ${apiKey}` },
626
+ settle: { Authorization: `Bearer ${apiKey}` },
627
+ supported: { Authorization: `Bearer ${apiKey}` }
628
+ })
629
+ });
630
+ ozServer = new x402ResourceServer(facilitator);
631
+ ozServer.register(caip2, new ExactStellarScheme$2());
632
+ }
615
633
  const amountInBaseUnits = String(Math.round(parseFloat(opts.amount) * 1e7));
616
634
  const requirements = {
617
635
  scheme: "exact",
@@ -622,52 +640,78 @@ function createX402Handler(opts) {
622
640
  maxTimeoutSeconds: 60,
623
641
  extra: {
624
642
  areFeesSponsored: true,
625
- facilitatorAddresses: [payeeKeypair.publicKey()]
643
+ ...useOzFacilitator ? {} : { facilitatorAddresses: [payeeKeypair.publicKey()] }
626
644
  }
627
645
  };
628
646
  return async (req, res, next) => {
629
647
  try {
630
648
  const paymentHeader = req.headers["payment-signature"] ?? req.headers["x-payment"];
631
649
  if (!paymentHeader) {
632
- const x402Response = {
633
- x402Version: 2,
634
- resource: {
650
+ if (ozServer) {
651
+ const resourceInfo = {
635
652
  url: `${req.protocol}://${req.get("host") ?? ""}${req.originalUrl}`,
636
653
  description: opts.manifest.name
637
- },
638
- accepts: [requirements]
639
- };
640
- res.status(402).setHeader("Content-Type", "application/json").setHeader("X-Payment-Requirements", encodePaymentRequiredHeader(x402Response)).json({ error: "Payment Required" });
654
+ };
655
+ const paymentRequired = await ozServer.createPaymentRequiredResponse(
656
+ [requirements],
657
+ resourceInfo
658
+ );
659
+ res.status(402).setHeader("Content-Type", "application/json").setHeader("X-Payment-Requirements", encodePaymentRequiredHeader(paymentRequired)).json({ error: "Payment Required" });
660
+ } else {
661
+ const x402Response = {
662
+ x402Version: 2,
663
+ resource: {
664
+ url: `${req.protocol}://${req.get("host") ?? ""}${req.originalUrl}`,
665
+ description: opts.manifest.name
666
+ },
667
+ accepts: [requirements]
668
+ };
669
+ res.status(402).setHeader("Content-Type", "application/json").setHeader("X-Payment-Requirements", encodePaymentRequiredHeader(x402Response)).json({ error: "Payment Required" });
670
+ }
641
671
  return;
642
672
  }
643
673
  const payload = decodePaymentSignatureHeader(paymentHeader);
644
- const verifyResult = await facilitatorScheme.verify(
645
- payload,
646
- requirements
647
- );
648
- if (!verifyResult.isValid) {
649
- res.status(401).json({
650
- error: "Payment verification failed",
651
- reason: verifyResult.invalidReason
652
- });
653
- return;
674
+ let txHash = null;
675
+ if (ozServer) {
676
+ const settleResult = await ozServer.settlePayment(payload, requirements);
677
+ txHash = settleResult.transaction ?? null;
678
+ if (settleResult) {
679
+ res.setHeader(
680
+ "X-Payment-Response",
681
+ encodePaymentResponseHeader(
682
+ settleResult
683
+ )
684
+ );
685
+ }
686
+ } else {
687
+ const verifyResult = await localFacilitator.verify(
688
+ payload,
689
+ requirements
690
+ );
691
+ if (!verifyResult.isValid) {
692
+ res.status(401).json({
693
+ error: "Payment verification failed",
694
+ reason: verifyResult.invalidReason
695
+ });
696
+ return;
697
+ }
698
+ const settleResult = await localFacilitator.settle(
699
+ payload,
700
+ requirements
701
+ );
702
+ txHash = settleResult.transaction ?? null;
703
+ if (settleResult) {
704
+ res.setHeader(
705
+ "X-Payment-Response",
706
+ encodePaymentResponseHeader(
707
+ settleResult
708
+ )
709
+ );
710
+ }
654
711
  }
655
- const settleResult = await facilitatorScheme.settle(
656
- payload,
657
- requirements
658
- );
659
- const txHash = settleResult.transaction ?? null;
660
712
  if (txHash && opts.onSettled) {
661
713
  await opts.onSettled(txHash, opts.amount, "x402");
662
714
  }
663
- if (settleResult) {
664
- res.setHeader(
665
- "X-Payment-Response",
666
- encodePaymentResponseHeader(
667
- settleResult
668
- )
669
- );
670
- }
671
715
  next();
672
716
  } catch (err) {
673
717
  console.error("[x402] Settlement error:", err);
@@ -741,6 +785,8 @@ function createMppSessionHandler(opts) {
741
785
  const cumulativeKey = `stellar:channel:cumulative:${opts.channelContract}`;
742
786
  const innerStore = Store.memory();
743
787
  let lastCumulativeAmount = 0n;
788
+ let voucherCount = 0;
789
+ let sessionOpened = false;
744
790
  const wrappedStore = {
745
791
  async get(key) {
746
792
  return innerStore.get(key);
@@ -749,6 +795,17 @@ function createMppSessionHandler(opts) {
749
795
  await innerStore.put(key, value);
750
796
  if (key === cumulativeKey && value && typeof value === "object" && "amount" in value) {
751
797
  lastCumulativeAmount = BigInt(value.amount);
798
+ voucherCount++;
799
+ if (!sessionOpened) {
800
+ sessionOpened = true;
801
+ if (opts.onSessionOpen) {
802
+ await opts.onSessionOpen(opts.channelContract);
803
+ }
804
+ }
805
+ if (opts.onVoucher) {
806
+ const humanAmount = (Number(lastCumulativeAmount) / 1e7).toFixed(7);
807
+ await opts.onVoucher(voucherCount, humanAmount);
808
+ }
752
809
  }
753
810
  },
754
811
  async delete(key) {
@@ -791,6 +848,9 @@ function createMppSessionHandler(opts) {
791
848
  } else {
792
849
  res.json({ closeTxHash: null, message: "no vouchers received" });
793
850
  }
851
+ sessionOpened = false;
852
+ voucherCount = 0;
853
+ lastCumulativeAmount = 0n;
794
854
  return;
795
855
  }
796
856
  const authHeader = req.headers["authorization"];
@@ -876,7 +936,9 @@ function routedock(opts) {
876
936
  assetContract: opts.assetContract,
877
937
  manifest: opts.manifest,
878
938
  commitmentPublicKey: opts.commitmentPublicKey,
879
- ...opts.onSettled ? { onSettled: opts.onSettled } : {}
939
+ ...opts.onSettled ? { onSettled: opts.onSettled } : {},
940
+ ...opts.onSessionOpen ? { onSessionOpen: opts.onSessionOpen } : {},
941
+ ...opts.onVoucher ? { onVoucher: opts.onVoucher } : {}
880
942
  })
881
943
  );
882
944
  }
package/dist/provider.cjs CHANGED
@@ -2,11 +2,13 @@
2
2
 
3
3
  var stellarSdk = require('@stellar/stellar-sdk');
4
4
  var facilitator = require('@x402/stellar/exact/facilitator');
5
+ var server$1 = require('@x402/stellar/exact/server');
6
+ var server = require('@x402/core/server');
5
7
  var stellar = require('@x402/stellar');
6
8
  var http = require('@x402/core/http');
7
- var server$1 = require('@stellar/mpp/charge/server');
8
- var server = require('mppx/server');
9
- var server$2 = require('@stellar/mpp/channel/server');
9
+ var server$3 = require('@stellar/mpp/charge/server');
10
+ var server$2 = require('mppx/server');
11
+ var server$4 = require('@stellar/mpp/channel/server');
10
12
 
11
13
  // src/types.ts
12
14
  var RouteDockError = class extends Error {
@@ -45,13 +47,29 @@ var CAIP2 = {
45
47
  testnet: "stellar:testnet",
46
48
  mainnet: "stellar:pubnet"
47
49
  };
50
+ var OZ_FACILITATOR_URL = "https://channels.openzeppelin.com/x402";
48
51
  function createX402Handler(opts) {
49
52
  const caip2 = CAIP2[opts.network];
50
53
  const payeeKeypair = stellarSdk.Keypair.fromSecret(opts.payeeSecretKey);
51
54
  const signer = stellar.createEd25519Signer(opts.payeeSecretKey, caip2);
52
- const facilitatorScheme = new facilitator.ExactStellarScheme([signer], {
55
+ const useOzFacilitator = opts.network === "mainnet" && opts.facilitatorApiKey;
56
+ const localFacilitator = new facilitator.ExactStellarScheme([signer], {
53
57
  areFeesSponsored: true
54
58
  });
59
+ let ozServer = null;
60
+ if (useOzFacilitator) {
61
+ const apiKey = opts.facilitatorApiKey;
62
+ const facilitator = new server.HTTPFacilitatorClient({
63
+ url: OZ_FACILITATOR_URL,
64
+ createAuthHeaders: async () => ({
65
+ verify: { Authorization: `Bearer ${apiKey}` },
66
+ settle: { Authorization: `Bearer ${apiKey}` },
67
+ supported: { Authorization: `Bearer ${apiKey}` }
68
+ })
69
+ });
70
+ ozServer = new server.x402ResourceServer(facilitator);
71
+ ozServer.register(caip2, new server$1.ExactStellarScheme());
72
+ }
55
73
  const amountInBaseUnits = String(Math.round(parseFloat(opts.amount) * 1e7));
56
74
  const requirements = {
57
75
  scheme: "exact",
@@ -62,52 +80,78 @@ function createX402Handler(opts) {
62
80
  maxTimeoutSeconds: 60,
63
81
  extra: {
64
82
  areFeesSponsored: true,
65
- facilitatorAddresses: [payeeKeypair.publicKey()]
83
+ ...useOzFacilitator ? {} : { facilitatorAddresses: [payeeKeypair.publicKey()] }
66
84
  }
67
85
  };
68
86
  return async (req, res, next) => {
69
87
  try {
70
88
  const paymentHeader = req.headers["payment-signature"] ?? req.headers["x-payment"];
71
89
  if (!paymentHeader) {
72
- const x402Response = {
73
- x402Version: 2,
74
- resource: {
90
+ if (ozServer) {
91
+ const resourceInfo = {
75
92
  url: `${req.protocol}://${req.get("host") ?? ""}${req.originalUrl}`,
76
93
  description: opts.manifest.name
77
- },
78
- accepts: [requirements]
79
- };
80
- res.status(402).setHeader("Content-Type", "application/json").setHeader("X-Payment-Requirements", http.encodePaymentRequiredHeader(x402Response)).json({ error: "Payment Required" });
94
+ };
95
+ const paymentRequired = await ozServer.createPaymentRequiredResponse(
96
+ [requirements],
97
+ resourceInfo
98
+ );
99
+ res.status(402).setHeader("Content-Type", "application/json").setHeader("X-Payment-Requirements", http.encodePaymentRequiredHeader(paymentRequired)).json({ error: "Payment Required" });
100
+ } else {
101
+ const x402Response = {
102
+ x402Version: 2,
103
+ resource: {
104
+ url: `${req.protocol}://${req.get("host") ?? ""}${req.originalUrl}`,
105
+ description: opts.manifest.name
106
+ },
107
+ accepts: [requirements]
108
+ };
109
+ res.status(402).setHeader("Content-Type", "application/json").setHeader("X-Payment-Requirements", http.encodePaymentRequiredHeader(x402Response)).json({ error: "Payment Required" });
110
+ }
81
111
  return;
82
112
  }
83
113
  const payload = http.decodePaymentSignatureHeader(paymentHeader);
84
- const verifyResult = await facilitatorScheme.verify(
85
- payload,
86
- requirements
87
- );
88
- if (!verifyResult.isValid) {
89
- res.status(401).json({
90
- error: "Payment verification failed",
91
- reason: verifyResult.invalidReason
92
- });
93
- return;
114
+ let txHash = null;
115
+ if (ozServer) {
116
+ const settleResult = await ozServer.settlePayment(payload, requirements);
117
+ txHash = settleResult.transaction ?? null;
118
+ if (settleResult) {
119
+ res.setHeader(
120
+ "X-Payment-Response",
121
+ http.encodePaymentResponseHeader(
122
+ settleResult
123
+ )
124
+ );
125
+ }
126
+ } else {
127
+ const verifyResult = await localFacilitator.verify(
128
+ payload,
129
+ requirements
130
+ );
131
+ if (!verifyResult.isValid) {
132
+ res.status(401).json({
133
+ error: "Payment verification failed",
134
+ reason: verifyResult.invalidReason
135
+ });
136
+ return;
137
+ }
138
+ const settleResult = await localFacilitator.settle(
139
+ payload,
140
+ requirements
141
+ );
142
+ txHash = settleResult.transaction ?? null;
143
+ if (settleResult) {
144
+ res.setHeader(
145
+ "X-Payment-Response",
146
+ http.encodePaymentResponseHeader(
147
+ settleResult
148
+ )
149
+ );
150
+ }
94
151
  }
95
- const settleResult = await facilitatorScheme.settle(
96
- payload,
97
- requirements
98
- );
99
- const txHash = settleResult.transaction ?? null;
100
152
  if (txHash && opts.onSettled) {
101
153
  await opts.onSettled(txHash, opts.amount, "x402");
102
154
  }
103
- if (settleResult) {
104
- res.setHeader(
105
- "X-Payment-Response",
106
- http.encodePaymentResponseHeader(
107
- settleResult
108
- )
109
- );
110
- }
111
155
  next();
112
156
  } catch (err) {
113
157
  console.error("[x402] Settlement error:", err);
@@ -122,10 +166,10 @@ var MPP_NETWORK = {
122
166
  function createMppChargeHandler(opts) {
123
167
  const networkId = MPP_NETWORK[opts.network];
124
168
  const amountHumanReadable = opts.amount;
125
- const mppx = server.Mppx.create({
169
+ const mppx = server$2.Mppx.create({
126
170
  secretKey: opts.payeeSecretKey,
127
171
  methods: [
128
- server$1.stellar.charge({
172
+ server$3.stellar.charge({
129
173
  recipient: opts.manifest.payee,
130
174
  currency: opts.assetContract,
131
175
  network: networkId,
@@ -135,7 +179,7 @@ function createMppChargeHandler(opts) {
135
179
  });
136
180
  return async (req, res, next) => {
137
181
  try {
138
- const fetchReq = server.Request.fromNodeListener(req, res);
182
+ const fetchReq = server$2.Request.fromNodeListener(req, res);
139
183
  const handler = mppx["stellar/charge"];
140
184
  const result = await handler({
141
185
  amount: amountHumanReadable,
@@ -179,8 +223,10 @@ function createMppSessionHandler(opts) {
179
223
  const rateHuman = opts.rate;
180
224
  const payeeKeypair = stellarSdk.Keypair.fromSecret(opts.payeeSecretKey);
181
225
  const cumulativeKey = `stellar:channel:cumulative:${opts.channelContract}`;
182
- const innerStore = server$2.Store.memory();
226
+ const innerStore = server$4.Store.memory();
183
227
  let lastCumulativeAmount = 0n;
228
+ let voucherCount = 0;
229
+ let sessionOpened = false;
184
230
  const wrappedStore = {
185
231
  async get(key) {
186
232
  return innerStore.get(key);
@@ -189,16 +235,27 @@ function createMppSessionHandler(opts) {
189
235
  await innerStore.put(key, value);
190
236
  if (key === cumulativeKey && value && typeof value === "object" && "amount" in value) {
191
237
  lastCumulativeAmount = BigInt(value.amount);
238
+ voucherCount++;
239
+ if (!sessionOpened) {
240
+ sessionOpened = true;
241
+ if (opts.onSessionOpen) {
242
+ await opts.onSessionOpen(opts.channelContract);
243
+ }
244
+ }
245
+ if (opts.onVoucher) {
246
+ const humanAmount = (Number(lastCumulativeAmount) / 1e7).toFixed(7);
247
+ await opts.onVoucher(voucherCount, humanAmount);
248
+ }
192
249
  }
193
250
  },
194
251
  async delete(key) {
195
252
  return innerStore.delete(key);
196
253
  }
197
254
  };
198
- const mppx = server.Mppx.create({
255
+ const mppx = server$2.Mppx.create({
199
256
  secretKey: opts.payeeSecretKey,
200
257
  methods: [
201
- server$2.stellar.channel({
258
+ server$4.stellar.channel({
202
259
  channel: opts.channelContract,
203
260
  commitmentKey: opts.commitmentPublicKey,
204
261
  network: networkId,
@@ -216,7 +273,7 @@ function createMppSessionHandler(opts) {
216
273
  const closeAmount = body?.amount ? BigInt(body.amount) : lastCumulativeAmount;
217
274
  const closeSig = body?.signature ?? lastSignatureHex;
218
275
  if (closeAmount > 0n && closeSig) {
219
- const closeTxHash = await server$2.close({
276
+ const closeTxHash = await server$4.close({
220
277
  channel: opts.channelContract,
221
278
  amount: closeAmount,
222
279
  signature: Buffer.from(closeSig, "hex"),
@@ -231,6 +288,9 @@ function createMppSessionHandler(opts) {
231
288
  } else {
232
289
  res.json({ closeTxHash: null, message: "no vouchers received" });
233
290
  }
291
+ sessionOpened = false;
292
+ voucherCount = 0;
293
+ lastCumulativeAmount = 0n;
234
294
  return;
235
295
  }
236
296
  const authHeader = req.headers["authorization"];
@@ -247,7 +307,7 @@ function createMppSessionHandler(opts) {
247
307
  } catch {
248
308
  }
249
309
  }
250
- const fetchReq = server.Request.fromNodeListener(req, res);
310
+ const fetchReq = server$2.Request.fromNodeListener(req, res);
251
311
  const result = await mppx.channel({
252
312
  amount: rateHuman,
253
313
  description: opts.manifest.name
@@ -316,7 +376,9 @@ function routedock(opts) {
316
376
  assetContract: opts.assetContract,
317
377
  manifest: opts.manifest,
318
378
  commitmentPublicKey: opts.commitmentPublicKey,
319
- ...opts.onSettled ? { onSettled: opts.onSettled } : {}
379
+ ...opts.onSettled ? { onSettled: opts.onSettled } : {},
380
+ ...opts.onSessionOpen ? { onSessionOpen: opts.onSessionOpen } : {},
381
+ ...opts.onVoucher ? { onVoucher: opts.onVoucher } : {}
320
382
  })
321
383
  );
322
384
  }
@@ -1,6 +1,6 @@
1
1
  import { c as RouteDockManifest, k as SessionStore } from './SessionStore-CCVO0PHM.cjs';
2
2
  export { P as PaymentMode, a as PaymentResult, b as PricingConfig, R as RouteDockError, d as RouteDockManifestError, e as RouteDockNoSupportedModeError, f as RouteDockPolicyRejectedError, g as RouteDockSessionError, S as SessionCloseResult, h as SessionHandle, i as SessionPricingConfig, j as SessionState, l as SupabaseSessionStore } from './SessionStore-CCVO0PHM.cjs';
3
- export { R as RouteDockMiddlewareOptions, r as routedock } from './routedockMiddleware-Beqan6QS.cjs';
3
+ export { R as RouteDockMiddlewareOptions, r as routedock } from './routedockMiddleware-B7WTB3LT.cjs';
4
4
  import { RequestHandler } from 'express';
5
5
  import '@supabase/supabase-js';
6
6
 
@@ -38,6 +38,8 @@ interface MppSessionHandlerOptions {
38
38
  manifest: RouteDockManifest;
39
39
  commitmentPublicKey: string;
40
40
  onSettled?: (txHash: string, totalPaid: string, mode: string) => Promise<void>;
41
+ onSessionOpen?: (channelId: string) => Promise<void>;
42
+ onVoucher?: (voucherIndex: number, cumulativeAmount: string) => Promise<void>;
41
43
  }
42
44
  declare function createMppSessionHandler(opts: MppSessionHandlerOptions): RequestHandler;
43
45
 
@@ -1,6 +1,6 @@
1
1
  import { c as RouteDockManifest, k as SessionStore } from './SessionStore-CCVO0PHM.js';
2
2
  export { P as PaymentMode, a as PaymentResult, b as PricingConfig, R as RouteDockError, d as RouteDockManifestError, e as RouteDockNoSupportedModeError, f as RouteDockPolicyRejectedError, g as RouteDockSessionError, S as SessionCloseResult, h as SessionHandle, i as SessionPricingConfig, j as SessionState, l as SupabaseSessionStore } from './SessionStore-CCVO0PHM.js';
3
- export { R as RouteDockMiddlewareOptions, r as routedock } from './routedockMiddleware-CqnyHjhO.js';
3
+ export { R as RouteDockMiddlewareOptions, r as routedock } from './routedockMiddleware-DRgdrycG.js';
4
4
  import { RequestHandler } from 'express';
5
5
  import '@supabase/supabase-js';
6
6
 
@@ -38,6 +38,8 @@ interface MppSessionHandlerOptions {
38
38
  manifest: RouteDockManifest;
39
39
  commitmentPublicKey: string;
40
40
  onSettled?: (txHash: string, totalPaid: string, mode: string) => Promise<void>;
41
+ onSessionOpen?: (channelId: string) => Promise<void>;
42
+ onVoucher?: (voucherIndex: number, cumulativeAmount: string) => Promise<void>;
41
43
  }
42
44
  declare function createMppSessionHandler(opts: MppSessionHandlerOptions): RequestHandler;
43
45
 
package/dist/provider.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { Keypair } from '@stellar/stellar-sdk';
2
2
  import { ExactStellarScheme } from '@x402/stellar/exact/facilitator';
3
+ import { ExactStellarScheme as ExactStellarScheme$1 } from '@x402/stellar/exact/server';
4
+ import { HTTPFacilitatorClient, x402ResourceServer } from '@x402/core/server';
3
5
  import { createEd25519Signer } from '@x402/stellar';
4
6
  import { encodePaymentRequiredHeader, decodePaymentSignatureHeader, encodePaymentResponseHeader } from '@x402/core/http';
5
7
  import { stellar } from '@stellar/mpp/charge/server';
@@ -43,13 +45,29 @@ var CAIP2 = {
43
45
  testnet: "stellar:testnet",
44
46
  mainnet: "stellar:pubnet"
45
47
  };
48
+ var OZ_FACILITATOR_URL = "https://channels.openzeppelin.com/x402";
46
49
  function createX402Handler(opts) {
47
50
  const caip2 = CAIP2[opts.network];
48
51
  const payeeKeypair = Keypair.fromSecret(opts.payeeSecretKey);
49
52
  const signer = createEd25519Signer(opts.payeeSecretKey, caip2);
50
- const facilitatorScheme = new ExactStellarScheme([signer], {
53
+ const useOzFacilitator = opts.network === "mainnet" && opts.facilitatorApiKey;
54
+ const localFacilitator = new ExactStellarScheme([signer], {
51
55
  areFeesSponsored: true
52
56
  });
57
+ let ozServer = null;
58
+ if (useOzFacilitator) {
59
+ const apiKey = opts.facilitatorApiKey;
60
+ const facilitator = new HTTPFacilitatorClient({
61
+ url: OZ_FACILITATOR_URL,
62
+ createAuthHeaders: async () => ({
63
+ verify: { Authorization: `Bearer ${apiKey}` },
64
+ settle: { Authorization: `Bearer ${apiKey}` },
65
+ supported: { Authorization: `Bearer ${apiKey}` }
66
+ })
67
+ });
68
+ ozServer = new x402ResourceServer(facilitator);
69
+ ozServer.register(caip2, new ExactStellarScheme$1());
70
+ }
53
71
  const amountInBaseUnits = String(Math.round(parseFloat(opts.amount) * 1e7));
54
72
  const requirements = {
55
73
  scheme: "exact",
@@ -60,52 +78,78 @@ function createX402Handler(opts) {
60
78
  maxTimeoutSeconds: 60,
61
79
  extra: {
62
80
  areFeesSponsored: true,
63
- facilitatorAddresses: [payeeKeypair.publicKey()]
81
+ ...useOzFacilitator ? {} : { facilitatorAddresses: [payeeKeypair.publicKey()] }
64
82
  }
65
83
  };
66
84
  return async (req, res, next) => {
67
85
  try {
68
86
  const paymentHeader = req.headers["payment-signature"] ?? req.headers["x-payment"];
69
87
  if (!paymentHeader) {
70
- const x402Response = {
71
- x402Version: 2,
72
- resource: {
88
+ if (ozServer) {
89
+ const resourceInfo = {
73
90
  url: `${req.protocol}://${req.get("host") ?? ""}${req.originalUrl}`,
74
91
  description: opts.manifest.name
75
- },
76
- accepts: [requirements]
77
- };
78
- res.status(402).setHeader("Content-Type", "application/json").setHeader("X-Payment-Requirements", encodePaymentRequiredHeader(x402Response)).json({ error: "Payment Required" });
92
+ };
93
+ const paymentRequired = await ozServer.createPaymentRequiredResponse(
94
+ [requirements],
95
+ resourceInfo
96
+ );
97
+ res.status(402).setHeader("Content-Type", "application/json").setHeader("X-Payment-Requirements", encodePaymentRequiredHeader(paymentRequired)).json({ error: "Payment Required" });
98
+ } else {
99
+ const x402Response = {
100
+ x402Version: 2,
101
+ resource: {
102
+ url: `${req.protocol}://${req.get("host") ?? ""}${req.originalUrl}`,
103
+ description: opts.manifest.name
104
+ },
105
+ accepts: [requirements]
106
+ };
107
+ res.status(402).setHeader("Content-Type", "application/json").setHeader("X-Payment-Requirements", encodePaymentRequiredHeader(x402Response)).json({ error: "Payment Required" });
108
+ }
79
109
  return;
80
110
  }
81
111
  const payload = decodePaymentSignatureHeader(paymentHeader);
82
- const verifyResult = await facilitatorScheme.verify(
83
- payload,
84
- requirements
85
- );
86
- if (!verifyResult.isValid) {
87
- res.status(401).json({
88
- error: "Payment verification failed",
89
- reason: verifyResult.invalidReason
90
- });
91
- return;
112
+ let txHash = null;
113
+ if (ozServer) {
114
+ const settleResult = await ozServer.settlePayment(payload, requirements);
115
+ txHash = settleResult.transaction ?? null;
116
+ if (settleResult) {
117
+ res.setHeader(
118
+ "X-Payment-Response",
119
+ encodePaymentResponseHeader(
120
+ settleResult
121
+ )
122
+ );
123
+ }
124
+ } else {
125
+ const verifyResult = await localFacilitator.verify(
126
+ payload,
127
+ requirements
128
+ );
129
+ if (!verifyResult.isValid) {
130
+ res.status(401).json({
131
+ error: "Payment verification failed",
132
+ reason: verifyResult.invalidReason
133
+ });
134
+ return;
135
+ }
136
+ const settleResult = await localFacilitator.settle(
137
+ payload,
138
+ requirements
139
+ );
140
+ txHash = settleResult.transaction ?? null;
141
+ if (settleResult) {
142
+ res.setHeader(
143
+ "X-Payment-Response",
144
+ encodePaymentResponseHeader(
145
+ settleResult
146
+ )
147
+ );
148
+ }
92
149
  }
93
- const settleResult = await facilitatorScheme.settle(
94
- payload,
95
- requirements
96
- );
97
- const txHash = settleResult.transaction ?? null;
98
150
  if (txHash && opts.onSettled) {
99
151
  await opts.onSettled(txHash, opts.amount, "x402");
100
152
  }
101
- if (settleResult) {
102
- res.setHeader(
103
- "X-Payment-Response",
104
- encodePaymentResponseHeader(
105
- settleResult
106
- )
107
- );
108
- }
109
153
  next();
110
154
  } catch (err) {
111
155
  console.error("[x402] Settlement error:", err);
@@ -179,6 +223,8 @@ function createMppSessionHandler(opts) {
179
223
  const cumulativeKey = `stellar:channel:cumulative:${opts.channelContract}`;
180
224
  const innerStore = Store.memory();
181
225
  let lastCumulativeAmount = 0n;
226
+ let voucherCount = 0;
227
+ let sessionOpened = false;
182
228
  const wrappedStore = {
183
229
  async get(key) {
184
230
  return innerStore.get(key);
@@ -187,6 +233,17 @@ function createMppSessionHandler(opts) {
187
233
  await innerStore.put(key, value);
188
234
  if (key === cumulativeKey && value && typeof value === "object" && "amount" in value) {
189
235
  lastCumulativeAmount = BigInt(value.amount);
236
+ voucherCount++;
237
+ if (!sessionOpened) {
238
+ sessionOpened = true;
239
+ if (opts.onSessionOpen) {
240
+ await opts.onSessionOpen(opts.channelContract);
241
+ }
242
+ }
243
+ if (opts.onVoucher) {
244
+ const humanAmount = (Number(lastCumulativeAmount) / 1e7).toFixed(7);
245
+ await opts.onVoucher(voucherCount, humanAmount);
246
+ }
190
247
  }
191
248
  },
192
249
  async delete(key) {
@@ -229,6 +286,9 @@ function createMppSessionHandler(opts) {
229
286
  } else {
230
287
  res.json({ closeTxHash: null, message: "no vouchers received" });
231
288
  }
289
+ sessionOpened = false;
290
+ voucherCount = 0;
291
+ lastCumulativeAmount = 0n;
232
292
  return;
233
293
  }
234
294
  const authHeader = req.headers["authorization"];
@@ -314,7 +374,9 @@ function routedock(opts) {
314
374
  assetContract: opts.assetContract,
315
375
  manifest: opts.manifest,
316
376
  commitmentPublicKey: opts.commitmentPublicKey,
317
- ...opts.onSettled ? { onSettled: opts.onSettled } : {}
377
+ ...opts.onSettled ? { onSettled: opts.onSettled } : {},
378
+ ...opts.onSessionOpen ? { onSessionOpen: opts.onSessionOpen } : {},
379
+ ...opts.onVoucher ? { onVoucher: opts.onVoucher } : {}
318
380
  })
319
381
  );
320
382
  }
@@ -26,6 +26,10 @@ interface RouteDockMiddlewareOptions {
26
26
  manifest: RouteDockManifest;
27
27
  /** Called after each successful on-chain settlement */
28
28
  onSettled?: (txHash: string, amount: string, mode: string) => Promise<void>;
29
+ /** Called when a new mpp-session is opened (first voucher received) */
30
+ onSessionOpen?: (channelId: string) => Promise<void>;
31
+ /** Called after each verified voucher in an mpp-session */
32
+ onVoucher?: (voucherIndex: number, cumulativeAmount: string) => Promise<void>;
29
33
  }
30
34
  /**
31
35
  * Express middleware factory for RouteDock provider endpoints.
@@ -26,6 +26,10 @@ interface RouteDockMiddlewareOptions {
26
26
  manifest: RouteDockManifest;
27
27
  /** Called after each successful on-chain settlement */
28
28
  onSettled?: (txHash: string, amount: string, mode: string) => Promise<void>;
29
+ /** Called when a new mpp-session is opened (first voucher received) */
30
+ onSessionOpen?: (channelId: string) => Promise<void>;
31
+ /** Called after each verified voucher in an mpp-session */
32
+ onVoucher?: (voucherIndex: number, cumulativeAmount: string) => Promise<void>;
29
33
  }
30
34
  /**
31
35
  * Express middleware factory for RouteDock provider endpoints.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@routedock/routedock",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Unified payment execution layer for autonomous agents on Stellar",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -20,6 +20,8 @@ export interface MppSessionHandlerOptions {
20
20
  manifest: RouteDockManifest
21
21
  commitmentPublicKey: string
22
22
  onSettled?: (txHash: string, totalPaid: string, mode: string) => Promise<void>
23
+ onSessionOpen?: (channelId: string) => Promise<void>
24
+ onVoucher?: (voucherIndex: number, cumulativeAmount: string) => Promise<void>
23
25
  }
24
26
 
25
27
  export function createMppSessionHandler(opts: MppSessionHandlerOptions): RequestHandler {
@@ -30,14 +32,29 @@ export function createMppSessionHandler(opts: MppSessionHandlerOptions): Request
30
32
 
31
33
  const innerStore = Store.memory()
32
34
 
33
- // Wrap the store to intercept cumulative amount writes
34
35
  let lastCumulativeAmount = 0n
36
+ let voucherCount = 0
37
+ let sessionOpened = false
38
+
35
39
  const wrappedStore: ReturnType<typeof Store.memory> = {
36
40
  async get(key: string) { return innerStore.get(key) },
37
41
  async put(key: string, value: unknown) {
38
42
  await innerStore.put(key, value)
39
43
  if (key === cumulativeKey && value && typeof value === 'object' && 'amount' in (value as Record<string, unknown>)) {
40
44
  lastCumulativeAmount = BigInt((value as { amount: string }).amount)
45
+ voucherCount++
46
+
47
+ if (!sessionOpened) {
48
+ sessionOpened = true
49
+ if (opts.onSessionOpen) {
50
+ await opts.onSessionOpen(opts.channelContract)
51
+ }
52
+ }
53
+
54
+ if (opts.onVoucher) {
55
+ const humanAmount = (Number(lastCumulativeAmount) / 1e7).toFixed(7)
56
+ await opts.onVoucher(voucherCount, humanAmount)
57
+ }
41
58
  }
42
59
  },
43
60
  async delete(key: string) { return innerStore.delete(key) },
@@ -85,6 +102,10 @@ export function createMppSessionHandler(opts: MppSessionHandlerOptions): Request
85
102
  } else {
86
103
  res.json({ closeTxHash: null, message: 'no vouchers received' })
87
104
  }
105
+
106
+ sessionOpened = false
107
+ voucherCount = 0
108
+ lastCumulativeAmount = 0n
88
109
  return
89
110
  }
90
111
 
@@ -26,6 +26,10 @@ export interface RouteDockMiddlewareOptions {
26
26
  manifest: RouteDockManifest
27
27
  /** Called after each successful on-chain settlement */
28
28
  onSettled?: (txHash: string, amount: string, mode: string) => Promise<void>
29
+ /** Called when a new mpp-session is opened (first voucher received) */
30
+ onSessionOpen?: (channelId: string) => Promise<void>
31
+ /** Called after each verified voucher in an mpp-session */
32
+ onVoucher?: (voucherIndex: number, cumulativeAmount: string) => Promise<void>
29
33
  }
30
34
 
31
35
  /**
@@ -97,6 +101,8 @@ export function routedock(opts: RouteDockMiddlewareOptions): RequestHandler {
97
101
  manifest: opts.manifest,
98
102
  commitmentPublicKey: opts.commitmentPublicKey,
99
103
  ...(opts.onSettled ? { onSettled: opts.onSettled } : {}),
104
+ ...(opts.onSessionOpen ? { onSessionOpen: opts.onSessionOpen } : {}),
105
+ ...(opts.onVoucher ? { onVoucher: opts.onVoucher } : {}),
100
106
  }),
101
107
  )
102
108
  }
@@ -1,6 +1,8 @@
1
1
  import type { Request, Response, NextFunction, RequestHandler } from 'express'
2
2
  import { Keypair } from '@stellar/stellar-sdk'
3
3
  import { ExactStellarScheme as ExactStellarFacilitatorScheme } from '@x402/stellar/exact/facilitator'
4
+ import { ExactStellarScheme as ExactStellarServerScheme } from '@x402/stellar/exact/server'
5
+ import { HTTPFacilitatorClient, x402ResourceServer } from '@x402/core/server'
4
6
  import { createEd25519Signer } from '@x402/stellar'
5
7
  import {
6
8
  decodePaymentSignatureHeader,
@@ -17,6 +19,8 @@ const CAIP2: Record<Network, X402Network> = {
17
19
  mainnet: 'stellar:pubnet',
18
20
  }
19
21
 
22
+ const OZ_FACILITATOR_URL = 'https://channels.openzeppelin.com/x402'
23
+
20
24
  export interface X402HandlerOptions {
21
25
  payeeSecretKey: string
22
26
  network: Network
@@ -32,10 +36,29 @@ export function createX402Handler(opts: X402HandlerOptions): RequestHandler {
32
36
  const payeeKeypair = Keypair.fromSecret(opts.payeeSecretKey)
33
37
  const signer = createEd25519Signer(opts.payeeSecretKey, caip2)
34
38
 
35
- const facilitatorScheme = new ExactStellarFacilitatorScheme([signer], {
39
+ const useOzFacilitator = opts.network === 'mainnet' && opts.facilitatorApiKey
40
+
41
+ // Mainnet: OZ hosted facilitator via x402ResourceServer
42
+ // Testnet: local ExactStellarFacilitatorScheme (OZ facilitator does not serve testnet)
43
+ const localFacilitator = new ExactStellarFacilitatorScheme([signer], {
36
44
  areFeesSponsored: true,
37
45
  })
38
46
 
47
+ let ozServer: x402ResourceServer | null = null
48
+ if (useOzFacilitator) {
49
+ const apiKey = opts.facilitatorApiKey!
50
+ const facilitator = new HTTPFacilitatorClient({
51
+ url: OZ_FACILITATOR_URL,
52
+ createAuthHeaders: async () => ({
53
+ verify: { Authorization: `Bearer ${apiKey}` },
54
+ settle: { Authorization: `Bearer ${apiKey}` },
55
+ supported: { Authorization: `Bearer ${apiKey}` },
56
+ }),
57
+ })
58
+ ozServer = new x402ResourceServer(facilitator)
59
+ ozServer.register(caip2, new ExactStellarServerScheme())
60
+ }
61
+
39
62
  const amountInBaseUnits = String(Math.round(parseFloat(opts.amount) * 1e7))
40
63
  const requirements = {
41
64
  scheme: 'exact' as const,
@@ -46,7 +69,7 @@ export function createX402Handler(opts: X402HandlerOptions): RequestHandler {
46
69
  maxTimeoutSeconds: 60,
47
70
  extra: {
48
71
  areFeesSponsored: true,
49
- facilitatorAddresses: [payeeKeypair.publicKey()],
72
+ ...(useOzFacilitator ? {} : { facilitatorAddresses: [payeeKeypair.publicKey()] }),
50
73
  },
51
74
  }
52
75
 
@@ -55,54 +78,83 @@ export function createX402Handler(opts: X402HandlerOptions): RequestHandler {
55
78
  const paymentHeader = (req.headers['payment-signature'] ?? req.headers['x-payment']) as string | undefined
56
79
 
57
80
  if (!paymentHeader) {
58
- const x402Response = {
59
- x402Version: 2,
60
- resource: {
81
+ if (ozServer) {
82
+ const resourceInfo = {
61
83
  url: `${req.protocol}://${req.get('host') ?? ''}${req.originalUrl}`,
62
84
  description: opts.manifest.name,
63
- },
64
- accepts: [requirements],
85
+ }
86
+ const paymentRequired = await ozServer.createPaymentRequiredResponse(
87
+ [requirements],
88
+ resourceInfo,
89
+ )
90
+ res
91
+ .status(402)
92
+ .setHeader('Content-Type', 'application/json')
93
+ .setHeader('X-Payment-Requirements', encodePaymentRequiredHeader(paymentRequired))
94
+ .json({ error: 'Payment Required' })
95
+ } else {
96
+ const x402Response = {
97
+ x402Version: 2,
98
+ resource: {
99
+ url: `${req.protocol}://${req.get('host') ?? ''}${req.originalUrl}`,
100
+ description: opts.manifest.name,
101
+ },
102
+ accepts: [requirements],
103
+ }
104
+ res
105
+ .status(402)
106
+ .setHeader('Content-Type', 'application/json')
107
+ .setHeader('X-Payment-Requirements', encodePaymentRequiredHeader(x402Response))
108
+ .json({ error: 'Payment Required' })
65
109
  }
66
- res
67
- .status(402)
68
- .setHeader('Content-Type', 'application/json')
69
- .setHeader('X-Payment-Requirements', encodePaymentRequiredHeader(x402Response))
70
- .json({ error: 'Payment Required' })
71
110
  return
72
111
  }
73
112
 
74
113
  const payload = decodePaymentSignatureHeader(paymentHeader)
75
- const verifyResult = await facilitatorScheme.verify(
76
- payload as Parameters<typeof facilitatorScheme.verify>[0],
77
- requirements,
78
- )
114
+ let txHash: string | null = null
79
115
 
80
- if (!verifyResult.isValid) {
81
- res.status(401).json({
82
- error: 'Payment verification failed',
83
- reason: (verifyResult as { invalidReason?: string }).invalidReason,
84
- })
85
- return
116
+ if (ozServer) {
117
+ const settleResult = await ozServer.settlePayment(payload, requirements)
118
+ txHash = (settleResult as { transaction?: string }).transaction ?? null
119
+ if (settleResult) {
120
+ res.setHeader(
121
+ 'X-Payment-Response',
122
+ encodePaymentResponseHeader(
123
+ settleResult as Parameters<typeof encodePaymentResponseHeader>[0],
124
+ ),
125
+ )
126
+ }
127
+ } else {
128
+ const verifyResult = await localFacilitator.verify(
129
+ payload as Parameters<typeof localFacilitator.verify>[0],
130
+ requirements,
131
+ )
132
+ if (!verifyResult.isValid) {
133
+ res.status(401).json({
134
+ error: 'Payment verification failed',
135
+ reason: (verifyResult as { invalidReason?: string }).invalidReason,
136
+ })
137
+ return
138
+ }
139
+ const settleResult = await localFacilitator.settle(
140
+ payload as Parameters<typeof localFacilitator.settle>[0],
141
+ requirements,
142
+ )
143
+ txHash = (settleResult as { transaction?: string }).transaction ?? null
144
+ if (settleResult) {
145
+ res.setHeader(
146
+ 'X-Payment-Response',
147
+ encodePaymentResponseHeader(
148
+ settleResult as Parameters<typeof encodePaymentResponseHeader>[0],
149
+ ),
150
+ )
151
+ }
86
152
  }
87
153
 
88
- const settleResult = await facilitatorScheme.settle(
89
- payload as Parameters<typeof facilitatorScheme.settle>[0],
90
- requirements,
91
- )
92
-
93
- const txHash = (settleResult as { transaction?: string }).transaction ?? null
94
154
  if (txHash && opts.onSettled) {
95
155
  await opts.onSettled(txHash, opts.amount, 'x402')
96
156
  }
97
157
 
98
- if (settleResult) {
99
- res.setHeader(
100
- 'X-Payment-Response',
101
- encodePaymentResponseHeader(
102
- settleResult as Parameters<typeof encodePaymentResponseHeader>[0],
103
- ),
104
- )
105
- }
106
158
  next()
107
159
  } catch (err) {
108
160
  console.error('[x402] Settlement error:', err)