@routedock/routedock 0.1.0 → 0.1.2

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.
@@ -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)