@relai-fi/x402 0.4.0 → 0.5.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/server.cjs CHANGED
@@ -21,7 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var server_exports = {};
22
22
  __export(server_exports, {
23
23
  Relai: () => Relai,
24
- default: () => server_default
24
+ default: () => server_default,
25
+ stripePayTo: () => stripePayTo
25
26
  });
26
27
  module.exports = __toCommonJS(server_exports);
27
28
 
@@ -54,11 +55,98 @@ var USDC_SOLANA = USDC_ADDRESSES["solana"];
54
55
  var USDC_BASE = USDC_ADDRESSES["base"];
55
56
 
56
57
  // src/server.ts
58
+ function stripePayTo(stripeSecretKey, options) {
59
+ if (!stripeSecretKey) {
60
+ throw new Error("stripePayTo requires a Stripe secret key");
61
+ }
62
+ return {
63
+ __brand: "stripePayTo",
64
+ secretKey: stripeSecretKey,
65
+ stripeNetwork: options?.network || "base"
66
+ };
67
+ }
68
+ function isStripePayTo(payTo) {
69
+ return typeof payTo === "object" && payTo !== null && payTo.__brand === "stripePayTo";
70
+ }
71
+ async function createStripeDepositAddress(secretKey, amountUsdCents, network = "base") {
72
+ const params = new URLSearchParams();
73
+ params.append("amount", String(amountUsdCents));
74
+ params.append("currency", "usd");
75
+ params.append("payment_method_types[]", "crypto");
76
+ params.append("payment_method_data[type]", "crypto");
77
+ params.append("confirm", "true");
78
+ const res = await fetch("https://api.stripe.com/v1/payment_intents", {
79
+ method: "POST",
80
+ headers: {
81
+ "Authorization": `Bearer ${secretKey}`,
82
+ "Content-Type": "application/x-www-form-urlencoded"
83
+ },
84
+ body: params.toString()
85
+ });
86
+ if (!res.ok) {
87
+ const err = await res.json().catch(() => ({}));
88
+ const msg = err?.error?.message || res.statusText;
89
+ if (msg.includes("unknown parameter") || msg.includes("crypto")) {
90
+ throw new Error(
91
+ `Stripe crypto payins not enabled on this account. Enable at: https://support.stripe.com/questions/get-started-with-pay-with-crypto (Original: ${msg})`
92
+ );
93
+ }
94
+ throw new Error(`Stripe PaymentIntent creation failed: ${msg}`);
95
+ }
96
+ const pi = await res.json();
97
+ const depositDetails = pi.next_action?.crypto_collect_deposit_details;
98
+ if (!depositDetails) {
99
+ throw new Error(
100
+ "Stripe PaymentIntent missing crypto deposit details. Ensure crypto payins are enabled: https://support.stripe.com/questions/get-started-with-pay-with-crypto"
101
+ );
102
+ }
103
+ const address = depositDetails.deposit_addresses?.[network]?.address;
104
+ if (!address) {
105
+ throw new Error(`No Stripe deposit address for network: ${network}`);
106
+ }
107
+ return address;
108
+ }
57
109
  var Relai = class {
110
+ // Cache feePayer per network
58
111
  constructor(config) {
112
+ this.feePayerCache = /* @__PURE__ */ new Map();
59
113
  this.network = config.network;
60
114
  this.facilitatorUrl = config.facilitatorUrl || RELAI_FACILITATOR_URL;
61
115
  }
116
+ /**
117
+ * Get feePayer address for a network (cached)
118
+ */
119
+ async getFeePayer(caip2) {
120
+ if (this.feePayerCache.has(caip2)) {
121
+ return this.feePayerCache.get(caip2);
122
+ }
123
+ const isRelAI = this.facilitatorUrl.includes("facilitator.x402.fi") || this.facilitatorUrl.includes("relai");
124
+ if (isRelAI) {
125
+ const relaiFeePayer = "0x1892f72fdB3A966b2AD8595aA5f7741Ef72d6085";
126
+ this.feePayerCache.set(caip2, relaiFeePayer);
127
+ return relaiFeePayer;
128
+ }
129
+ try {
130
+ const supportedUrl = `${this.facilitatorUrl}/supported`;
131
+ const supportedRes = await fetch(supportedUrl);
132
+ if (supportedRes.ok) {
133
+ const supportedData = await supportedRes.json();
134
+ supportedData.kinds?.forEach((kind) => {
135
+ if (kind.network && kind.extra?.feePayer) {
136
+ this.feePayerCache.set(kind.network, kind.extra.feePayer);
137
+ }
138
+ });
139
+ return this.feePayerCache.get(caip2);
140
+ }
141
+ } catch (err) {
142
+ const envFeePayer = process.env.BACKEND_WALLET_ADDRESS || process.env.FEEPAYER_EVM_WALLET_ADDRESS;
143
+ if (envFeePayer) {
144
+ this.feePayerCache.set(caip2, envFeePayer);
145
+ return envFeePayer;
146
+ }
147
+ }
148
+ return void 0;
149
+ }
62
150
  /**
63
151
  * Express middleware to protect an endpoint with x402 micropayments.
64
152
  *
@@ -75,13 +163,41 @@ var Relai = class {
75
163
  if (typeof resolvedPrice !== "number" || !isFinite(resolvedPrice) || resolvedPrice <= 0) {
76
164
  return res.status(400).json({ error: "Invalid price configuration" });
77
165
  }
78
- const network = options.network || self.network;
166
+ const stripeConfig = isStripePayTo(options.payTo) ? options.payTo : null;
167
+ const network = stripeConfig ? stripeConfig.stripeNetwork || "base" : options.network || self.network;
79
168
  const caip2 = NETWORK_CAIP2[network];
80
169
  const asset = USDC_ADDRESSES[network];
81
170
  const amount = String(Math.floor(resolvedPrice * 1e6));
82
171
  const paymentHeader = req.headers["x-payment"] || req.headers["payment-signature"] || req.headers["x-payment-signature"];
83
172
  if (!paymentHeader) {
84
173
  options.onPaymentRequired?.(req, { price: resolvedPrice, network });
174
+ let resolvedPayTo;
175
+ if (stripeConfig) {
176
+ const amountInCents = Math.max(1, Math.round(resolvedPrice * 100));
177
+ resolvedPayTo = await createStripeDepositAddress(
178
+ stripeConfig.secretKey,
179
+ amountInCents,
180
+ stripeConfig.stripeNetwork
181
+ );
182
+ } else {
183
+ resolvedPayTo = options.payTo;
184
+ }
185
+ const feePayer = await self.getFeePayer(caip2);
186
+ const tokenMetadata = {
187
+ "eip155:103698795": { name: "USDC", version: "1" },
188
+ // SKALE BITE
189
+ "eip155:1187947933": { name: "USD Coin", version: "2" },
190
+ // SKALE Base
191
+ "eip155:8453": { name: "USD Coin", version: "2" },
192
+ // Base
193
+ "eip155:43114": { name: "USD Coin", version: "2" },
194
+ // Avalanche
195
+ "eip155:137": { name: "USD Coin", version: "2" },
196
+ // Polygon
197
+ "eip155:1": { name: "USD Coin", version: "2" }
198
+ // Ethereum
199
+ };
200
+ const metadata = tokenMetadata[caip2] || { name: "USDC", version: "1" };
85
201
  return res.status(402).json({
86
202
  x402Version: 2,
87
203
  error: "Payment required",
@@ -95,12 +211,14 @@ var Relai = class {
95
211
  network: caip2,
96
212
  amount,
97
213
  asset,
98
- payTo: options.payTo,
214
+ payTo: resolvedPayTo,
99
215
  maxTimeoutSeconds: options.maxTimeoutSeconds || 60,
100
216
  extra: {
101
- name: "USD Coin",
102
- version: "2",
103
- decimals: 6
217
+ name: metadata.name,
218
+ version: metadata.version,
219
+ decimals: 6,
220
+ ...feePayer && { feePayer }
221
+ // Add feePayer if available
104
222
  }
105
223
  }]
106
224
  });
@@ -119,12 +237,24 @@ var Relai = class {
119
237
  });
120
238
  }
121
239
  }
240
+ let settlePayTo;
241
+ if (stripeConfig) {
242
+ settlePayTo = paymentProof.payload?.authorization?.to || paymentProof.accepted?.payTo || "";
243
+ if (!settlePayTo) {
244
+ return res.status(400).json({
245
+ x402Version: 2,
246
+ error: "Cannot extract destination address from payment proof"
247
+ });
248
+ }
249
+ } else {
250
+ settlePayTo = options.payTo;
251
+ }
122
252
  const paymentRequirements = {
123
253
  scheme: "exact",
124
254
  network,
125
255
  amount,
126
256
  asset,
127
- payTo: options.payTo,
257
+ payTo: settlePayTo,
128
258
  maxTimeoutSeconds: options.maxTimeoutSeconds || 60
129
259
  };
130
260
  const settleUrl = `${self.facilitatorUrl}/settle`;
@@ -187,6 +317,7 @@ var Relai = class {
187
317
  var server_default = Relai;
188
318
  // Annotate the CommonJS export names for ESM import in node:
189
319
  0 && (module.exports = {
190
- Relai
320
+ Relai,
321
+ stripePayTo
191
322
  });
192
323
  //# sourceMappingURL=server.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.ts","../src/types.ts"],"sourcesContent":["// src/server.ts\nimport {\n NETWORK_CAIP2,\n USDC_ADDRESSES,\n RELAI_FACILITATOR_URL,\n type RelaiNetwork,\n} from './types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface RelaiServerConfig {\n /** Network to accept payments on */\n network: RelaiNetwork;\n /** RelAI facilitator URL (default: https://facilitator.x402.fi) */\n facilitatorUrl?: string;\n}\n\nexport type DynamicPrice = number | ((req: any) => number | Promise<number>);\n\nexport interface ProtectOptions {\n /** Price in USD (e.g., 0.01 for 1 cent) */\n price: DynamicPrice;\n /** Wallet address to receive payments */\n payTo: string;\n /** Description shown to payer */\n description?: string;\n /** MIME type of the response (default: application/json) */\n mimeType?: string;\n /** Maximum timeout in seconds (default: 60) */\n maxTimeoutSeconds?: number;\n /** Override network for this endpoint */\n network?: RelaiNetwork;\n /** Custom validation after payment is settled */\n customRules?: (req: any) => boolean | Promise<boolean>;\n /** Callback when 402 is returned (no payment provided) */\n onPaymentRequired?: (req: any, info: { price: number; network: RelaiNetwork }) => void;\n /** Callback when payment is settled successfully */\n onPaymentSettled?: (req: any, result: SettleResult) => void;\n /** Callback on error */\n onError?: (req: any, error: unknown) => void;\n}\n\nexport interface SettleResult {\n success: boolean;\n transaction?: string;\n payer?: string;\n network?: string;\n error?: string;\n errorReason?: string;\n}\n\nexport interface PaymentInfo {\n verified: boolean;\n transactionId?: string;\n payer?: string;\n network: RelaiNetwork;\n amount: number;\n}\n\n// ============================================================================\n// Relai Server SDK\n// ============================================================================\n\n/**\n * Server-side SDK for protecting Express endpoints with x402 micropayments.\n * Settles payments through the RelAI facilitator (zero gas fees for users).\n *\n * Supports: Solana, Base, Avalanche, SKALE Base.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n *\n * const relai = new Relai({ network: 'base' });\n *\n * app.get('/api/data', relai.protect({\n * payTo: '0xYourWallet',\n * price: 0.01, // $0.01 USDC\n * }), (req, res) => {\n * res.json({ data: 'Protected content', payment: req.payment });\n * });\n * ```\n */\nexport class Relai {\n private network: RelaiNetwork;\n private facilitatorUrl: string;\n\n constructor(config: RelaiServerConfig) {\n this.network = config.network;\n this.facilitatorUrl = config.facilitatorUrl || RELAI_FACILITATOR_URL;\n }\n\n /**\n * Express middleware to protect an endpoint with x402 micropayments.\n *\n * Flow:\n * 1. No payment header → returns 402 with payment requirements\n * 2. Payment header present → calls RelAI facilitator `/settle`\n * 3. Settlement success → sets `PAYMENT-RESPONSE` header, attaches `req.payment`, calls `next()`\n */\n protect(options: ProtectOptions) {\n const self = this;\n\n return async (req: any, res: any, next: any) => {\n try {\n // Resolve dynamic price\n const resolvedPrice = typeof options.price === 'function'\n ? await options.price(req)\n : options.price;\n\n if (typeof resolvedPrice !== 'number' || !isFinite(resolvedPrice) || resolvedPrice <= 0) {\n return res.status(400).json({ error: 'Invalid price configuration' });\n }\n\n const network = options.network || self.network;\n const caip2 = NETWORK_CAIP2[network];\n const asset = USDC_ADDRESSES[network];\n const amount = String(Math.floor(resolvedPrice * 1_000_000)); // USD → USDC atomic units (6 decimals)\n\n // Check for payment header (base64-encoded JSON)\n const paymentHeader =\n req.headers['x-payment'] ||\n req.headers['payment-signature'] ||\n req.headers['x-payment-signature'];\n\n // -----------------------------------------------------------\n // No payment → return 402 Payment Required\n // -----------------------------------------------------------\n if (!paymentHeader) {\n options.onPaymentRequired?.(req, { price: resolvedPrice, network });\n\n return res.status(402).json({\n x402Version: 2,\n error: 'Payment required',\n resource: {\n url: `${req.protocol}://${req.get('host')}${req.originalUrl}`,\n description: options.description || 'API access',\n mimeType: options.mimeType || 'application/json',\n },\n accepts: [{\n scheme: 'exact',\n network: caip2,\n amount,\n asset,\n payTo: options.payTo,\n maxTimeoutSeconds: options.maxTimeoutSeconds || 60,\n extra: {\n name: 'USD Coin',\n version: '2',\n decimals: 6,\n },\n }],\n });\n }\n\n // -----------------------------------------------------------\n // Payment header present → parse and settle via facilitator\n // -----------------------------------------------------------\n let paymentProof: any;\n try {\n // Try base64 first (standard x402 format)\n const decoded = Buffer.from(paymentHeader, 'base64').toString('utf-8');\n paymentProof = JSON.parse(decoded);\n } catch {\n try {\n // Fallback: raw JSON string\n paymentProof = JSON.parse(paymentHeader);\n } catch {\n return res.status(400).json({\n x402Version: 2,\n error: 'Invalid payment header — expected base64-encoded JSON',\n });\n }\n }\n\n // Build payment requirements for facilitator\n const paymentRequirements = {\n scheme: 'exact',\n network,\n amount,\n asset,\n payTo: options.payTo,\n maxTimeoutSeconds: options.maxTimeoutSeconds || 60,\n };\n\n // Call facilitator /settle\n const settleUrl = `${self.facilitatorUrl}/settle`;\n const settleRes = await fetch(settleUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n paymentPayload: paymentProof,\n paymentRequirements,\n }),\n });\n\n const result: SettleResult = await settleRes.json() as SettleResult;\n\n if (!result.success) {\n return res.status(402).json({\n x402Version: 2,\n error: result.errorReason || result.error || 'Payment settlement failed',\n });\n }\n\n // Attach payment info to request\n const paymentInfo: PaymentInfo = {\n verified: true,\n transactionId: result.transaction,\n payer: result.payer,\n network,\n amount: resolvedPrice,\n };\n req.payment = paymentInfo;\n req.x402Payer = result.payer;\n req.x402Paid = true;\n req.x402Transaction = result.transaction;\n req.x402Network = network;\n\n // Set x402 v2 PAYMENT-RESPONSE header (base64 JSON)\n const paymentResponse = {\n x402Version: 2,\n scheme: 'exact',\n network: caip2,\n transaction: result.transaction,\n payer: result.payer,\n amount,\n asset,\n };\n res.setHeader(\n 'PAYMENT-RESPONSE',\n Buffer.from(JSON.stringify(paymentResponse)).toString('base64'),\n );\n\n options.onPaymentSettled?.(req, result);\n\n // Custom validation after payment\n if (options.customRules) {\n const valid = await options.customRules(req);\n if (!valid) {\n return res.status(403).json({ error: 'Custom validation failed' });\n }\n }\n\n next();\n } catch (error) {\n options.onError?.(req, error);\n console.error('[Relai] Protection error:', error);\n res.status(500).json({ error: 'Internal server error' });\n }\n };\n }\n}\n\nexport default Relai;\n","// src/types.ts\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** RelAI Facilitator URL */\nexport const RELAI_FACILITATOR_URL = 'https://facilitator.x402.fi';\n\n// ============================================================================\n// Supported Networks\n// ============================================================================\n\n/** All networks supported by RelAI facilitator */\nexport type RelaiNetwork = 'solana' | 'base' | 'avalanche' | 'skale-base' | 'skale-bite' | 'polygon' | 'ethereum';\n\n/** CAIP-2 network identifiers */\nexport const NETWORK_CAIP2: Record<RelaiNetwork, string> = {\n 'solana': 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',\n 'base': 'eip155:8453',\n 'avalanche': 'eip155:43114',\n 'skale-base': 'eip155:1187947933',\n 'skale-bite': 'eip155:103698795',\n 'polygon': 'eip155:137',\n 'ethereum': 'eip155:1',\n};\n\n/** Reverse lookup: CAIP-2 → simple network name */\nexport const CAIP2_TO_NETWORK: Record<string, RelaiNetwork> = Object.fromEntries(\n Object.entries(NETWORK_CAIP2).map(([k, v]) => [v, k as RelaiNetwork])\n) as Record<string, RelaiNetwork>;\n\n/** Chain IDs for EVM networks */\nexport const CHAIN_IDS: Record<string, number> = {\n 'base': 8453,\n 'avalanche': 43114,\n 'skale-base': 1187947933,\n 'skale-bite': 103698795,\n 'polygon': 137,\n 'ethereum': 1,\n};\n\n/** USDC contract addresses per network */\nexport const USDC_ADDRESSES: Record<RelaiNetwork, string> = {\n 'solana': 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',\n 'base': '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',\n 'avalanche': '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',\n 'skale-base': '0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20',\n 'skale-bite': '0xc4083B1E81ceb461Ccef3FDa8A9F24F0d764B6D8',\n 'polygon': '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',\n 'ethereum': '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n};\n\n/** Explorer URLs per network */\nexport const EXPLORER_TX_URL: Record<RelaiNetwork, (tx: string) => string> = {\n 'solana': (tx) => `https://solscan.io/tx/${tx}`,\n 'base': (tx) => `https://basescan.org/tx/${tx}`,\n 'avalanche': (tx) => `https://snowtrace.io/tx/${tx}`,\n 'skale-base': (tx) => `https://skale-base-explorer.skalenodes.com/tx/${tx}`,\n 'skale-bite': (tx) => `https://base-sepolia-testnet.explorer.skalenodes.com/tx/${tx}`,\n 'polygon': (tx) => `https://polygonscan.com/tx/${tx}`,\n 'ethereum': (tx) => `https://etherscan.io/tx/${tx}`,\n};\n\n/** Human-readable network labels */\nexport const NETWORK_LABELS: Record<RelaiNetwork, string> = {\n 'solana': 'Solana',\n 'base': 'Base',\n 'avalanche': 'Avalanche',\n 'skale-base': 'SKALE Base',\n 'skale-bite': 'SKALE BITE V2',\n 'polygon': 'Polygon',\n 'ethereum': 'Ethereum',\n};\n\n/** Legacy CAIP-2 exports for backward compatibility */\nexport const SOLANA_MAINNET_NETWORK = NETWORK_CAIP2['solana'];\nexport const BASE_MAINNET_NETWORK = NETWORK_CAIP2['base'];\n\n/** Legacy USDC exports for backward compatibility */\nexport const USDC_SOLANA = USDC_ADDRESSES['solana'];\nexport const USDC_BASE = USDC_ADDRESSES['base'];\n\n/** All supported RelAI networks list */\nexport const RELAI_NETWORKS: RelaiNetwork[] = ['solana', 'base', 'avalanche', 'skale-base', 'skale-bite', 'polygon', 'ethereum'];\n\n/** Check if a network is Solana-based */\nexport function isSolana(network: string): boolean {\n return network === 'solana' || network.startsWith('solana:');\n}\n\n/** Check if a network is EVM-based */\nexport function isEvm(network: string): boolean {\n return ['base', 'avalanche', 'skale-base', 'skale-bite', 'polygon', 'ethereum'].includes(network) || network.startsWith('eip155:');\n}\n\n/** Normalize CAIP-2 or simple name to RelaiNetwork */\nexport function normalizeNetwork(network: string): RelaiNetwork | null {\n if (RELAI_NETWORKS.includes(network as RelaiNetwork)) return network as RelaiNetwork;\n const fromCaip2 = CAIP2_TO_NETWORK[network];\n if (fromCaip2) return fromCaip2;\n // Partial match\n if (network.startsWith('solana:')) return 'solana';\n if (network.startsWith('eip155:')) {\n const chainId = parseInt(network.split(':')[1]);\n const entry = Object.entries(CHAIN_IDS).find(([, id]) => id === chainId);\n if (entry) return entry[0] as RelaiNetwork;\n }\n return null;\n}\n\n// ============================================================================\n// Wallet Types\n// ============================================================================\n\n/** Solana wallet interface */\nexport interface SolanaWallet {\n publicKey: { toString(): string } | null;\n signTransaction: ((tx: unknown) => Promise<unknown>) | null;\n signAllTransactions?: ((txs: unknown[]) => Promise<unknown[]>) | null;\n}\n\n/** EVM wallet interface (viem-compatible) */\nexport interface EvmWallet {\n address: string;\n signTypedData: (params: unknown) => Promise<string>;\n chain?: { id: number };\n}\n\n/** Wallet set for multi-chain support */\nexport interface WalletSet {\n solana?: SolanaWallet;\n evm?: EvmWallet;\n}\n\n// ============================================================================\n// Payment Types\n// ============================================================================\n\n/** Extra fields in payment requirements */\nexport interface AcceptsExtra {\n feePayer?: string;\n decimals?: number;\n name?: string;\n version?: string;\n [key: string]: unknown;\n}\n\n/** A single payment option */\nexport interface PaymentAccept {\n x402Version?: 1 | 2;\n scheme: string;\n network: string;\n maxAmountRequired?: string;\n amount?: string;\n asset: string;\n payTo: string;\n maxTimeoutSeconds?: number;\n extra?: AcceptsExtra;\n resource?: string;\n description?: string;\n mimeType?: string;\n outputSchema?: unknown;\n}\n\n/** Resource info for v2 */\nexport interface ResourceInfo {\n url: string;\n description?: string;\n mimeType?: string;\n}\n\n/** Payment requirements (402 response) */\nexport interface PaymentRequired {\n x402Version: 1 | 2;\n error?: string;\n accepts: PaymentAccept[];\n resource?: ResourceInfo;\n extensions?: Record<string, unknown>;\n}\n\n// ============================================================================\n// Config Types (server-specific types are in server.ts)\n// ============================================================================\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACOO,IAAM,wBAAwB;AAU9B,IAAM,gBAA8C;AAAA,EACzD,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,cAAc;AAAA,EACd,cAAc;AAAA,EACd,WAAW;AAAA,EACX,YAAY;AACd;AAGO,IAAM,mBAAiD,OAAO;AAAA,EACnE,OAAO,QAAQ,aAAa,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAiB,CAAC;AACtE;AAaO,IAAM,iBAA+C;AAAA,EAC1D,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,cAAc;AAAA,EACd,cAAc;AAAA,EACd,WAAW;AAAA,EACX,YAAY;AACd;AAyBO,IAAM,yBAAyB,cAAc,QAAQ;AACrD,IAAM,uBAAuB,cAAc,MAAM;AAGjD,IAAM,cAAc,eAAe,QAAQ;AAC3C,IAAM,YAAY,eAAe,MAAM;;;ADIvC,IAAM,QAAN,MAAY;AAAA,EAIjB,YAAY,QAA2B;AACrC,SAAK,UAAU,OAAO;AACtB,SAAK,iBAAiB,OAAO,kBAAkB;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,QAAQ,SAAyB;AAC/B,UAAM,OAAO;AAEb,WAAO,OAAO,KAAU,KAAU,SAAc;AAC9C,UAAI;AAEF,cAAM,gBAAgB,OAAO,QAAQ,UAAU,aAC3C,MAAM,QAAQ,MAAM,GAAG,IACvB,QAAQ;AAEZ,YAAI,OAAO,kBAAkB,YAAY,CAAC,SAAS,aAAa,KAAK,iBAAiB,GAAG;AACvF,iBAAO,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,8BAA8B,CAAC;AAAA,QACtE;AAEA,cAAM,UAAU,QAAQ,WAAW,KAAK;AACxC,cAAM,QAAQ,cAAc,OAAO;AACnC,cAAM,QAAQ,eAAe,OAAO;AACpC,cAAM,SAAS,OAAO,KAAK,MAAM,gBAAgB,GAAS,CAAC;AAG3D,cAAM,gBACJ,IAAI,QAAQ,WAAW,KACvB,IAAI,QAAQ,mBAAmB,KAC/B,IAAI,QAAQ,qBAAqB;AAKnC,YAAI,CAAC,eAAe;AAClB,kBAAQ,oBAAoB,KAAK,EAAE,OAAO,eAAe,QAAQ,CAAC;AAElE,iBAAO,IAAI,OAAO,GAAG,EAAE,KAAK;AAAA,YAC1B,aAAa;AAAA,YACb,OAAO;AAAA,YACP,UAAU;AAAA,cACR,KAAK,GAAG,IAAI,QAAQ,MAAM,IAAI,IAAI,MAAM,CAAC,GAAG,IAAI,WAAW;AAAA,cAC3D,aAAa,QAAQ,eAAe;AAAA,cACpC,UAAU,QAAQ,YAAY;AAAA,YAChC;AAAA,YACA,SAAS,CAAC;AAAA,cACR,QAAQ;AAAA,cACR,SAAS;AAAA,cACT;AAAA,cACA;AAAA,cACA,OAAO,QAAQ;AAAA,cACf,mBAAmB,QAAQ,qBAAqB;AAAA,cAChD,OAAO;AAAA,gBACL,MAAM;AAAA,gBACN,SAAS;AAAA,gBACT,UAAU;AAAA,cACZ;AAAA,YACF,CAAC;AAAA,UACH,CAAC;AAAA,QACH;AAKA,YAAI;AACJ,YAAI;AAEF,gBAAM,UAAU,OAAO,KAAK,eAAe,QAAQ,EAAE,SAAS,OAAO;AACrE,yBAAe,KAAK,MAAM,OAAO;AAAA,QACnC,QAAQ;AACN,cAAI;AAEF,2BAAe,KAAK,MAAM,aAAa;AAAA,UACzC,QAAQ;AACN,mBAAO,IAAI,OAAO,GAAG,EAAE,KAAK;AAAA,cAC1B,aAAa;AAAA,cACb,OAAO;AAAA,YACT,CAAC;AAAA,UACH;AAAA,QACF;AAGA,cAAM,sBAAsB;AAAA,UAC1B,QAAQ;AAAA,UACR;AAAA,UACA;AAAA,UACA;AAAA,UACA,OAAO,QAAQ;AAAA,UACf,mBAAmB,QAAQ,qBAAqB;AAAA,QAClD;AAGA,cAAM,YAAY,GAAG,KAAK,cAAc;AACxC,cAAM,YAAY,MAAM,MAAM,WAAW;AAAA,UACvC,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU;AAAA,YACnB,gBAAgB;AAAA,YAChB;AAAA,UACF,CAAC;AAAA,QACH,CAAC;AAED,cAAM,SAAuB,MAAM,UAAU,KAAK;AAElD,YAAI,CAAC,OAAO,SAAS;AACnB,iBAAO,IAAI,OAAO,GAAG,EAAE,KAAK;AAAA,YAC1B,aAAa;AAAA,YACb,OAAO,OAAO,eAAe,OAAO,SAAS;AAAA,UAC/C,CAAC;AAAA,QACH;AAGA,cAAM,cAA2B;AAAA,UAC/B,UAAU;AAAA,UACV,eAAe,OAAO;AAAA,UACtB,OAAO,OAAO;AAAA,UACd;AAAA,UACA,QAAQ;AAAA,QACV;AACA,YAAI,UAAU;AACd,YAAI,YAAY,OAAO;AACvB,YAAI,WAAW;AACf,YAAI,kBAAkB,OAAO;AAC7B,YAAI,cAAc;AAGlB,cAAM,kBAAkB;AAAA,UACtB,aAAa;AAAA,UACb,QAAQ;AAAA,UACR,SAAS;AAAA,UACT,aAAa,OAAO;AAAA,UACpB,OAAO,OAAO;AAAA,UACd;AAAA,UACA;AAAA,QACF;AACA,YAAI;AAAA,UACF;AAAA,UACA,OAAO,KAAK,KAAK,UAAU,eAAe,CAAC,EAAE,SAAS,QAAQ;AAAA,QAChE;AAEA,gBAAQ,mBAAmB,KAAK,MAAM;AAGtC,YAAI,QAAQ,aAAa;AACvB,gBAAM,QAAQ,MAAM,QAAQ,YAAY,GAAG;AAC3C,cAAI,CAAC,OAAO;AACV,mBAAO,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,2BAA2B,CAAC;AAAA,UACnE;AAAA,QACF;AAEA,aAAK;AAAA,MACP,SAAS,OAAO;AACd,gBAAQ,UAAU,KAAK,KAAK;AAC5B,gBAAQ,MAAM,6BAA6B,KAAK;AAChD,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,wBAAwB,CAAC;AAAA,MACzD;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,iBAAQ;","names":[]}
1
+ {"version":3,"sources":["../src/server.ts","../src/types.ts"],"sourcesContent":["// src/server.ts\nimport {\n NETWORK_CAIP2,\n USDC_ADDRESSES,\n RELAI_FACILITATOR_URL,\n type RelaiNetwork,\n} from './types';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface RelaiServerConfig {\n /** Network to accept payments on */\n network: RelaiNetwork;\n /** RelAI facilitator URL (default: https://facilitator.x402.fi) */\n facilitatorUrl?: string;\n}\n\nexport type DynamicPrice = number | ((req: any) => number | Promise<number>);\n\nexport interface ProtectOptions {\n /** Price in USD (e.g., 0.01 for 1 cent) */\n price: DynamicPrice;\n /** Wallet address to receive payments, or stripePayTo() for Stripe settlement */\n payTo: string | StripePayTo;\n /** Description shown to payer */\n description?: string;\n /** MIME type of the response (default: application/json) */\n mimeType?: string;\n /** Maximum timeout in seconds (default: 60) */\n maxTimeoutSeconds?: number;\n /** Override network for this endpoint */\n network?: RelaiNetwork;\n /** Custom validation after payment is settled */\n customRules?: (req: any) => boolean | Promise<boolean>;\n /** Callback when 402 is returned (no payment provided) */\n onPaymentRequired?: (req: any, info: { price: number; network: RelaiNetwork }) => void;\n /** Callback when payment is settled successfully */\n onPaymentSettled?: (req: any, result: SettleResult) => void;\n /** Callback on error */\n onError?: (req: any, error: unknown) => void;\n}\n\nexport interface SettleResult {\n success: boolean;\n transaction?: string;\n payer?: string;\n network?: string;\n error?: string;\n errorReason?: string;\n}\n\nexport interface PaymentInfo {\n verified: boolean;\n transactionId?: string;\n payer?: string;\n network: RelaiNetwork;\n amount: number;\n}\n\n// ============================================================================\n// Stripe Pay-To Helper\n// ============================================================================\n\n/** Config returned by stripePayTo() - used by protect() to create Stripe deposit addresses */\nexport interface StripePayTo {\n readonly __brand: 'stripePayTo';\n readonly secretKey: string;\n /** Stripe crypto deposits network (default: 'base') */\n readonly stripeNetwork: string;\n}\n\n/**\n * Create a Stripe pay-to configuration for x402 payments.\n * Payments settle as USD in your Stripe Dashboard - no crypto knowledge required.\n *\n * Stripe creates a fresh PaymentIntent + deposit address per request.\n * Network is auto-set to Base (Stripe settles USDC on Base).\n *\n * @example\n * ```typescript\n * import Relai, { stripePayTo } from '@relai-fi/x402/server';\n *\n * const relai = new Relai({ network: 'base' });\n *\n * app.get('/api/data', relai.protect({\n * price: 0.01,\n * payTo: stripePayTo(process.env.STRIPE_SECRET_KEY!),\n * }), (req, res) => {\n * res.json({ data: 'paid content' });\n * });\n * ```\n */\nexport function stripePayTo(\n stripeSecretKey: string,\n options?: { network?: string },\n): StripePayTo {\n if (!stripeSecretKey) {\n throw new Error('stripePayTo requires a Stripe secret key');\n }\n return {\n __brand: 'stripePayTo' as const,\n secretKey: stripeSecretKey,\n stripeNetwork: options?.network || 'base',\n };\n}\n\n/** @internal Type guard for StripePayTo */\nfunction isStripePayTo(payTo: unknown): payTo is StripePayTo {\n return (\n typeof payTo === 'object' &&\n payTo !== null &&\n (payTo as any).__brand === 'stripePayTo'\n );\n}\n\n/**\n * @internal Create a Stripe PaymentIntent with crypto payment method\n * and extract the deposit address.\n */\nasync function createStripeDepositAddress(\n secretKey: string,\n amountUsdCents: number,\n network: string = 'base',\n): Promise<string> {\n const params = new URLSearchParams();\n params.append('amount', String(amountUsdCents));\n params.append('currency', 'usd');\n params.append('payment_method_types[]', 'crypto');\n params.append('payment_method_data[type]', 'crypto');\n params.append('confirm', 'true');\n\n const res = await fetch('https://api.stripe.com/v1/payment_intents', {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${secretKey}`,\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: params.toString(),\n });\n\n if (!res.ok) {\n const err = await res.json().catch(() => ({})) as any;\n const msg = err?.error?.message || res.statusText;\n\n // Provide actionable guidance for common issues\n if (msg.includes('unknown parameter') || msg.includes('crypto')) {\n throw new Error(\n `Stripe crypto payins not enabled on this account. ` +\n `Enable at: https://support.stripe.com/questions/get-started-with-pay-with-crypto ` +\n `(Original: ${msg})`,\n );\n }\n throw new Error(`Stripe PaymentIntent creation failed: ${msg}`);\n }\n\n const pi = await res.json() as any;\n const depositDetails = pi.next_action?.crypto_collect_deposit_details;\n if (!depositDetails) {\n throw new Error(\n 'Stripe PaymentIntent missing crypto deposit details. ' +\n 'Ensure crypto payins are enabled: https://support.stripe.com/questions/get-started-with-pay-with-crypto',\n );\n }\n\n const address = depositDetails.deposit_addresses?.[network]?.address;\n if (!address) {\n throw new Error(`No Stripe deposit address for network: ${network}`);\n }\n\n return address;\n}\n\n// ============================================================================\n// Relai Server SDK\n// ============================================================================\n\n/**\n * Server-side SDK for protecting Express endpoints with x402 micropayments.\n * Settles payments through the RelAI facilitator (zero gas fees for users).\n *\n * Supports: Solana, Base, Avalanche, SKALE Base.\n *\n * @example\n * ```typescript\n * import Relai from '@relai-fi/x402/server';\n *\n * const relai = new Relai({ network: 'base' });\n *\n * app.get('/api/data', relai.protect({\n * payTo: '0xYourWallet',\n * price: 0.01, // $0.01 USDC\n * }), (req, res) => {\n * res.json({ data: 'Protected content', payment: req.payment });\n * });\n * ```\n */\nexport class Relai {\n private network: RelaiNetwork;\n private facilitatorUrl: string;\n private feePayerCache: Map<string, string> = new Map(); // Cache feePayer per network\n\n constructor(config: RelaiServerConfig) {\n this.network = config.network;\n this.facilitatorUrl = config.facilitatorUrl || RELAI_FACILITATOR_URL;\n }\n\n /**\n * Get feePayer address for a network (cached)\n */\n private async getFeePayer(caip2: string): Promise<string | undefined> {\n // Check cache first\n if (this.feePayerCache.has(caip2)) {\n return this.feePayerCache.get(caip2);\n }\n\n // If using RelAI facilitator, use hardcoded address (no fetch needed)\n const isRelAI = this.facilitatorUrl.includes('facilitator.x402.fi') || \n this.facilitatorUrl.includes('relai');\n \n if (isRelAI) {\n const relaiFeePayer = '0x1892f72fdB3A966b2AD8595aA5f7741Ef72d6085';\n this.feePayerCache.set(caip2, relaiFeePayer);\n return relaiFeePayer;\n }\n\n // For other facilitators, fetch from /supported\n try {\n const supportedUrl = `${this.facilitatorUrl}/supported`;\n const supportedRes = await fetch(supportedUrl);\n if (supportedRes.ok) {\n const supportedData = await supportedRes.json();\n // Cache all feePayers from supported kinds\n supportedData.kinds?.forEach((kind: any) => {\n if (kind.network && kind.extra?.feePayer) {\n this.feePayerCache.set(kind.network, kind.extra.feePayer);\n }\n });\n return this.feePayerCache.get(caip2);\n }\n } catch (err) {\n // Fallback to env if facilitator doesn't respond\n const envFeePayer = process.env.BACKEND_WALLET_ADDRESS || \n process.env.FEEPAYER_EVM_WALLET_ADDRESS;\n if (envFeePayer) {\n this.feePayerCache.set(caip2, envFeePayer);\n return envFeePayer;\n }\n }\n return undefined;\n }\n\n /**\n * Express middleware to protect an endpoint with x402 micropayments.\n *\n * Flow:\n * 1. No payment header → returns 402 with payment requirements\n * 2. Payment header present → calls RelAI facilitator `/settle`\n * 3. Settlement success → sets `PAYMENT-RESPONSE` header, attaches `req.payment`, calls `next()`\n */\n protect(options: ProtectOptions) {\n const self = this;\n\n return async (req: any, res: any, next: any) => {\n try {\n // Resolve dynamic price\n const resolvedPrice = typeof options.price === 'function'\n ? await options.price(req)\n : options.price;\n\n if (typeof resolvedPrice !== 'number' || !isFinite(resolvedPrice) || resolvedPrice <= 0) {\n return res.status(400).json({ error: 'Invalid price configuration' });\n }\n\n // Resolve network (Stripe auto-sets to base)\n const stripeConfig = isStripePayTo(options.payTo) ? options.payTo : null;\n const network = stripeConfig\n ? (stripeConfig.stripeNetwork as RelaiNetwork) || 'base'\n : (options.network || self.network);\n const caip2 = NETWORK_CAIP2[network];\n const asset = USDC_ADDRESSES[network];\n const amount = String(Math.floor(resolvedPrice * 1_000_000)); // USD → USDC atomic units (6 decimals)\n\n // Check for payment header (base64-encoded JSON)\n const paymentHeader =\n req.headers['x-payment'] ||\n req.headers['payment-signature'] ||\n req.headers['x-payment-signature'];\n\n // -----------------------------------------------------------\n // No payment → return 402 Payment Required\n // -----------------------------------------------------------\n if (!paymentHeader) {\n options.onPaymentRequired?.(req, { price: resolvedPrice, network });\n\n // Resolve payTo address (Stripe creates a fresh deposit address per request)\n let resolvedPayTo: string;\n if (stripeConfig) {\n const amountInCents = Math.max(1, Math.round(resolvedPrice * 100));\n resolvedPayTo = await createStripeDepositAddress(\n stripeConfig.secretKey,\n amountInCents,\n stripeConfig.stripeNetwork,\n );\n } else {\n resolvedPayTo = options.payTo as string;\n }\n\n // Get facilitator feePayer address (cached)\n const feePayer = await self.getFeePayer(caip2);\n\n // Token metadata per network\n // IMPORTANT: These must match the actual EIP-712 domain on each network\n const tokenMetadata: Record<string, { name: string; version: string }> = {\n 'eip155:103698795': { name: 'USDC', version: '1' }, // SKALE BITE\n 'eip155:1187947933': { name: 'USD Coin', version: '2' }, // SKALE Base\n 'eip155:8453': { name: 'USD Coin', version: '2' }, // Base\n 'eip155:43114': { name: 'USD Coin', version: '2' }, // Avalanche\n 'eip155:137': { name: 'USD Coin', version: '2' }, // Polygon\n 'eip155:1': { name: 'USD Coin', version: '2' }, // Ethereum\n };\n const metadata = tokenMetadata[caip2] || { name: 'USDC', version: '1' };\n\n return res.status(402).json({\n x402Version: 2,\n error: 'Payment required',\n resource: {\n url: `${req.protocol}://${req.get('host')}${req.originalUrl}`,\n description: options.description || 'API access',\n mimeType: options.mimeType || 'application/json',\n },\n accepts: [{\n scheme: 'exact',\n network: caip2,\n amount,\n asset,\n payTo: resolvedPayTo,\n maxTimeoutSeconds: options.maxTimeoutSeconds || 60,\n extra: {\n name: metadata.name,\n version: metadata.version,\n decimals: 6,\n ...(feePayer && { feePayer }), // Add feePayer if available\n },\n }],\n });\n }\n\n // -----------------------------------------------------------\n // Payment header present → parse and settle via facilitator\n // -----------------------------------------------------------\n let paymentProof: any;\n try {\n // Try base64 first (standard x402 format)\n const decoded = Buffer.from(paymentHeader, 'base64').toString('utf-8');\n paymentProof = JSON.parse(decoded);\n } catch {\n try {\n // Fallback: raw JSON string\n paymentProof = JSON.parse(paymentHeader);\n } catch {\n return res.status(400).json({\n x402Version: 2,\n error: 'Invalid payment header — expected base64-encoded JSON',\n });\n }\n }\n\n // Resolve payTo for settle (extract from signed proof when using Stripe)\n let settlePayTo: string;\n if (stripeConfig) {\n settlePayTo =\n paymentProof.payload?.authorization?.to ||\n paymentProof.accepted?.payTo ||\n '';\n if (!settlePayTo) {\n return res.status(400).json({\n x402Version: 2,\n error: 'Cannot extract destination address from payment proof',\n });\n }\n } else {\n settlePayTo = options.payTo as string;\n }\n\n // Build payment requirements for facilitator\n const paymentRequirements = {\n scheme: 'exact',\n network,\n amount,\n asset,\n payTo: settlePayTo,\n maxTimeoutSeconds: options.maxTimeoutSeconds || 60,\n };\n\n // Call facilitator /settle\n const settleUrl = `${self.facilitatorUrl}/settle`;\n const settleRes = await fetch(settleUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n paymentPayload: paymentProof,\n paymentRequirements,\n }),\n });\n\n const result: SettleResult = await settleRes.json() as SettleResult;\n\n if (!result.success) {\n return res.status(402).json({\n x402Version: 2,\n error: result.errorReason || result.error || 'Payment settlement failed',\n });\n }\n\n // Attach payment info to request\n const paymentInfo: PaymentInfo = {\n verified: true,\n transactionId: result.transaction,\n payer: result.payer,\n network,\n amount: resolvedPrice,\n };\n req.payment = paymentInfo;\n req.x402Payer = result.payer;\n req.x402Paid = true;\n req.x402Transaction = result.transaction;\n req.x402Network = network;\n\n // Set x402 v2 PAYMENT-RESPONSE header (base64 JSON)\n const paymentResponse = {\n x402Version: 2,\n scheme: 'exact',\n network: caip2,\n transaction: result.transaction,\n payer: result.payer,\n amount,\n asset,\n };\n res.setHeader(\n 'PAYMENT-RESPONSE',\n Buffer.from(JSON.stringify(paymentResponse)).toString('base64'),\n );\n\n options.onPaymentSettled?.(req, result);\n\n // Custom validation after payment\n if (options.customRules) {\n const valid = await options.customRules(req);\n if (!valid) {\n return res.status(403).json({ error: 'Custom validation failed' });\n }\n }\n\n next();\n } catch (error) {\n options.onError?.(req, error);\n console.error('[Relai] Protection error:', error);\n res.status(500).json({ error: 'Internal server error' });\n }\n };\n }\n}\n\nexport default Relai;\n","// src/types.ts\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/** RelAI Facilitator URL */\nexport const RELAI_FACILITATOR_URL = 'https://facilitator.x402.fi';\n\n// ============================================================================\n// Supported Networks\n// ============================================================================\n\n/** All networks supported by RelAI facilitator */\nexport type RelaiNetwork = 'solana' | 'base' | 'avalanche' | 'skale-base' | 'skale-bite' | 'polygon' | 'ethereum';\n\n/** CAIP-2 network identifiers */\nexport const NETWORK_CAIP2: Record<RelaiNetwork, string> = {\n 'solana': 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',\n 'base': 'eip155:8453',\n 'avalanche': 'eip155:43114',\n 'skale-base': 'eip155:1187947933',\n 'skale-bite': 'eip155:103698795',\n 'polygon': 'eip155:137',\n 'ethereum': 'eip155:1',\n};\n\n/** Reverse lookup: CAIP-2 → simple network name */\nexport const CAIP2_TO_NETWORK: Record<string, RelaiNetwork> = Object.fromEntries(\n Object.entries(NETWORK_CAIP2).map(([k, v]) => [v, k as RelaiNetwork])\n) as Record<string, RelaiNetwork>;\n\n/** Chain IDs for EVM networks */\nexport const CHAIN_IDS: Record<string, number> = {\n 'base': 8453,\n 'avalanche': 43114,\n 'skale-base': 1187947933,\n 'skale-bite': 103698795,\n 'polygon': 137,\n 'ethereum': 1,\n};\n\n/** USDC contract addresses per network */\nexport const USDC_ADDRESSES: Record<RelaiNetwork, string> = {\n 'solana': 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',\n 'base': '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',\n 'avalanche': '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E',\n 'skale-base': '0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20',\n 'skale-bite': '0xc4083B1E81ceb461Ccef3FDa8A9F24F0d764B6D8',\n 'polygon': '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',\n 'ethereum': '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',\n};\n\n/** Explorer URLs per network */\nexport const EXPLORER_TX_URL: Record<RelaiNetwork, (tx: string) => string> = {\n 'solana': (tx) => `https://solscan.io/tx/${tx}`,\n 'base': (tx) => `https://basescan.org/tx/${tx}`,\n 'avalanche': (tx) => `https://snowtrace.io/tx/${tx}`,\n 'skale-base': (tx) => `https://skale-base-explorer.skalenodes.com/tx/${tx}`,\n 'skale-bite': (tx) => `https://base-sepolia-testnet.explorer.skalenodes.com/tx/${tx}`,\n 'polygon': (tx) => `https://polygonscan.com/tx/${tx}`,\n 'ethereum': (tx) => `https://etherscan.io/tx/${tx}`,\n};\n\n/** Human-readable network labels */\nexport const NETWORK_LABELS: Record<RelaiNetwork, string> = {\n 'solana': 'Solana',\n 'base': 'Base',\n 'avalanche': 'Avalanche',\n 'skale-base': 'SKALE Base',\n 'skale-bite': 'SKALE BITE V2',\n 'polygon': 'Polygon',\n 'ethereum': 'Ethereum',\n};\n\n/** Legacy CAIP-2 exports for backward compatibility */\nexport const SOLANA_MAINNET_NETWORK = NETWORK_CAIP2['solana'];\nexport const BASE_MAINNET_NETWORK = NETWORK_CAIP2['base'];\n\n/** Legacy USDC exports for backward compatibility */\nexport const USDC_SOLANA = USDC_ADDRESSES['solana'];\nexport const USDC_BASE = USDC_ADDRESSES['base'];\n\n/** All supported RelAI networks list */\nexport const RELAI_NETWORKS: RelaiNetwork[] = ['solana', 'base', 'avalanche', 'skale-base', 'skale-bite', 'polygon', 'ethereum'];\n\n/** Check if a network is Solana-based */\nexport function isSolana(network: string): boolean {\n return network === 'solana' || network.startsWith('solana:');\n}\n\n/** Check if a network is EVM-based */\nexport function isEvm(network: string): boolean {\n return ['base', 'avalanche', 'skale-base', 'skale-bite', 'polygon', 'ethereum'].includes(network) || network.startsWith('eip155:');\n}\n\n/** Normalize CAIP-2 or simple name to RelaiNetwork */\nexport function normalizeNetwork(network: string): RelaiNetwork | null {\n if (RELAI_NETWORKS.includes(network as RelaiNetwork)) return network as RelaiNetwork;\n const fromCaip2 = CAIP2_TO_NETWORK[network];\n if (fromCaip2) return fromCaip2;\n // Partial match\n if (network.startsWith('solana:')) return 'solana';\n if (network.startsWith('eip155:')) {\n const chainId = parseInt(network.split(':')[1]);\n const entry = Object.entries(CHAIN_IDS).find(([, id]) => id === chainId);\n if (entry) return entry[0] as RelaiNetwork;\n }\n return null;\n}\n\n// ============================================================================\n// Wallet Types\n// ============================================================================\n\n/** Solana wallet interface */\nexport interface SolanaWallet {\n publicKey: { toString(): string } | null;\n signTransaction: ((tx: unknown) => Promise<unknown>) | null;\n signAllTransactions?: ((txs: unknown[]) => Promise<unknown[]>) | null;\n}\n\n/** EVM wallet interface (viem-compatible) */\nexport interface EvmWallet {\n address: string;\n signTypedData: (params: unknown) => Promise<string>;\n chain?: { id: number };\n}\n\n/** Wallet set for multi-chain support */\nexport interface WalletSet {\n solana?: SolanaWallet;\n evm?: EvmWallet;\n}\n\n// ============================================================================\n// Payment Types\n// ============================================================================\n\n/** Extra fields in payment requirements */\nexport interface AcceptsExtra {\n feePayer?: string;\n decimals?: number;\n name?: string;\n version?: string;\n [key: string]: unknown;\n}\n\n/** A single payment option */\nexport interface PaymentAccept {\n x402Version?: 1 | 2;\n scheme: string;\n network: string;\n maxAmountRequired?: string;\n amount?: string;\n asset: string;\n payTo: string;\n maxTimeoutSeconds?: number;\n extra?: AcceptsExtra;\n resource?: string;\n description?: string;\n mimeType?: string;\n outputSchema?: unknown;\n}\n\n/** Resource info for v2 */\nexport interface ResourceInfo {\n url: string;\n description?: string;\n mimeType?: string;\n}\n\n/** Payment requirements (402 response) */\nexport interface PaymentRequired {\n x402Version: 1 | 2;\n error?: string;\n accepts: PaymentAccept[];\n resource?: ResourceInfo;\n extensions?: Record<string, unknown>;\n}\n\n// ============================================================================\n// Config Types (server-specific types are in server.ts)\n// ============================================================================\n"],"mappings":"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","names":[]}
package/dist/server.d.cts CHANGED
@@ -10,8 +10,8 @@ type DynamicPrice = number | ((req: any) => number | Promise<number>);
10
10
  interface ProtectOptions {
11
11
  /** Price in USD (e.g., 0.01 for 1 cent) */
12
12
  price: DynamicPrice;
13
- /** Wallet address to receive payments */
14
- payTo: string;
13
+ /** Wallet address to receive payments, or stripePayTo() for Stripe settlement */
14
+ payTo: string | StripePayTo;
15
15
  /** Description shown to payer */
16
16
  description?: string;
17
17
  /** MIME type of the response (default: application/json) */
@@ -47,6 +47,37 @@ interface PaymentInfo {
47
47
  network: RelaiNetwork;
48
48
  amount: number;
49
49
  }
50
+ /** Config returned by stripePayTo() - used by protect() to create Stripe deposit addresses */
51
+ interface StripePayTo {
52
+ readonly __brand: 'stripePayTo';
53
+ readonly secretKey: string;
54
+ /** Stripe crypto deposits network (default: 'base') */
55
+ readonly stripeNetwork: string;
56
+ }
57
+ /**
58
+ * Create a Stripe pay-to configuration for x402 payments.
59
+ * Payments settle as USD in your Stripe Dashboard - no crypto knowledge required.
60
+ *
61
+ * Stripe creates a fresh PaymentIntent + deposit address per request.
62
+ * Network is auto-set to Base (Stripe settles USDC on Base).
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * import Relai, { stripePayTo } from '@relai-fi/x402/server';
67
+ *
68
+ * const relai = new Relai({ network: 'base' });
69
+ *
70
+ * app.get('/api/data', relai.protect({
71
+ * price: 0.01,
72
+ * payTo: stripePayTo(process.env.STRIPE_SECRET_KEY!),
73
+ * }), (req, res) => {
74
+ * res.json({ data: 'paid content' });
75
+ * });
76
+ * ```
77
+ */
78
+ declare function stripePayTo(stripeSecretKey: string, options?: {
79
+ network?: string;
80
+ }): StripePayTo;
50
81
  /**
51
82
  * Server-side SDK for protecting Express endpoints with x402 micropayments.
52
83
  * Settles payments through the RelAI facilitator (zero gas fees for users).
@@ -70,7 +101,12 @@ interface PaymentInfo {
70
101
  declare class Relai {
71
102
  private network;
72
103
  private facilitatorUrl;
104
+ private feePayerCache;
73
105
  constructor(config: RelaiServerConfig);
106
+ /**
107
+ * Get feePayer address for a network (cached)
108
+ */
109
+ private getFeePayer;
74
110
  /**
75
111
  * Express middleware to protect an endpoint with x402 micropayments.
76
112
  *
@@ -82,4 +118,4 @@ declare class Relai {
82
118
  protect(options: ProtectOptions): (req: any, res: any, next: any) => Promise<any>;
83
119
  }
84
120
 
85
- export { type DynamicPrice, type PaymentInfo, type ProtectOptions, Relai, type RelaiServerConfig, type SettleResult, Relai as default };
121
+ export { type DynamicPrice, type PaymentInfo, type ProtectOptions, Relai, type RelaiServerConfig, type SettleResult, type StripePayTo, Relai as default, stripePayTo };
package/dist/server.d.ts CHANGED
@@ -10,8 +10,8 @@ type DynamicPrice = number | ((req: any) => number | Promise<number>);
10
10
  interface ProtectOptions {
11
11
  /** Price in USD (e.g., 0.01 for 1 cent) */
12
12
  price: DynamicPrice;
13
- /** Wallet address to receive payments */
14
- payTo: string;
13
+ /** Wallet address to receive payments, or stripePayTo() for Stripe settlement */
14
+ payTo: string | StripePayTo;
15
15
  /** Description shown to payer */
16
16
  description?: string;
17
17
  /** MIME type of the response (default: application/json) */
@@ -47,6 +47,37 @@ interface PaymentInfo {
47
47
  network: RelaiNetwork;
48
48
  amount: number;
49
49
  }
50
+ /** Config returned by stripePayTo() - used by protect() to create Stripe deposit addresses */
51
+ interface StripePayTo {
52
+ readonly __brand: 'stripePayTo';
53
+ readonly secretKey: string;
54
+ /** Stripe crypto deposits network (default: 'base') */
55
+ readonly stripeNetwork: string;
56
+ }
57
+ /**
58
+ * Create a Stripe pay-to configuration for x402 payments.
59
+ * Payments settle as USD in your Stripe Dashboard - no crypto knowledge required.
60
+ *
61
+ * Stripe creates a fresh PaymentIntent + deposit address per request.
62
+ * Network is auto-set to Base (Stripe settles USDC on Base).
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * import Relai, { stripePayTo } from '@relai-fi/x402/server';
67
+ *
68
+ * const relai = new Relai({ network: 'base' });
69
+ *
70
+ * app.get('/api/data', relai.protect({
71
+ * price: 0.01,
72
+ * payTo: stripePayTo(process.env.STRIPE_SECRET_KEY!),
73
+ * }), (req, res) => {
74
+ * res.json({ data: 'paid content' });
75
+ * });
76
+ * ```
77
+ */
78
+ declare function stripePayTo(stripeSecretKey: string, options?: {
79
+ network?: string;
80
+ }): StripePayTo;
50
81
  /**
51
82
  * Server-side SDK for protecting Express endpoints with x402 micropayments.
52
83
  * Settles payments through the RelAI facilitator (zero gas fees for users).
@@ -70,7 +101,12 @@ interface PaymentInfo {
70
101
  declare class Relai {
71
102
  private network;
72
103
  private facilitatorUrl;
104
+ private feePayerCache;
73
105
  constructor(config: RelaiServerConfig);
106
+ /**
107
+ * Get feePayer address for a network (cached)
108
+ */
109
+ private getFeePayer;
74
110
  /**
75
111
  * Express middleware to protect an endpoint with x402 micropayments.
76
112
  *
@@ -82,4 +118,4 @@ declare class Relai {
82
118
  protect(options: ProtectOptions): (req: any, res: any, next: any) => Promise<any>;
83
119
  }
84
120
 
85
- export { type DynamicPrice, type PaymentInfo, type ProtectOptions, Relai, type RelaiServerConfig, type SettleResult, Relai as default };
121
+ export { type DynamicPrice, type PaymentInfo, type ProtectOptions, Relai, type RelaiServerConfig, type SettleResult, type StripePayTo, Relai as default, stripePayTo };
package/dist/server.js CHANGED
@@ -27,11 +27,98 @@ var USDC_SOLANA = USDC_ADDRESSES["solana"];
27
27
  var USDC_BASE = USDC_ADDRESSES["base"];
28
28
 
29
29
  // src/server.ts
30
+ function stripePayTo(stripeSecretKey, options) {
31
+ if (!stripeSecretKey) {
32
+ throw new Error("stripePayTo requires a Stripe secret key");
33
+ }
34
+ return {
35
+ __brand: "stripePayTo",
36
+ secretKey: stripeSecretKey,
37
+ stripeNetwork: options?.network || "base"
38
+ };
39
+ }
40
+ function isStripePayTo(payTo) {
41
+ return typeof payTo === "object" && payTo !== null && payTo.__brand === "stripePayTo";
42
+ }
43
+ async function createStripeDepositAddress(secretKey, amountUsdCents, network = "base") {
44
+ const params = new URLSearchParams();
45
+ params.append("amount", String(amountUsdCents));
46
+ params.append("currency", "usd");
47
+ params.append("payment_method_types[]", "crypto");
48
+ params.append("payment_method_data[type]", "crypto");
49
+ params.append("confirm", "true");
50
+ const res = await fetch("https://api.stripe.com/v1/payment_intents", {
51
+ method: "POST",
52
+ headers: {
53
+ "Authorization": `Bearer ${secretKey}`,
54
+ "Content-Type": "application/x-www-form-urlencoded"
55
+ },
56
+ body: params.toString()
57
+ });
58
+ if (!res.ok) {
59
+ const err = await res.json().catch(() => ({}));
60
+ const msg = err?.error?.message || res.statusText;
61
+ if (msg.includes("unknown parameter") || msg.includes("crypto")) {
62
+ throw new Error(
63
+ `Stripe crypto payins not enabled on this account. Enable at: https://support.stripe.com/questions/get-started-with-pay-with-crypto (Original: ${msg})`
64
+ );
65
+ }
66
+ throw new Error(`Stripe PaymentIntent creation failed: ${msg}`);
67
+ }
68
+ const pi = await res.json();
69
+ const depositDetails = pi.next_action?.crypto_collect_deposit_details;
70
+ if (!depositDetails) {
71
+ throw new Error(
72
+ "Stripe PaymentIntent missing crypto deposit details. Ensure crypto payins are enabled: https://support.stripe.com/questions/get-started-with-pay-with-crypto"
73
+ );
74
+ }
75
+ const address = depositDetails.deposit_addresses?.[network]?.address;
76
+ if (!address) {
77
+ throw new Error(`No Stripe deposit address for network: ${network}`);
78
+ }
79
+ return address;
80
+ }
30
81
  var Relai = class {
82
+ // Cache feePayer per network
31
83
  constructor(config) {
84
+ this.feePayerCache = /* @__PURE__ */ new Map();
32
85
  this.network = config.network;
33
86
  this.facilitatorUrl = config.facilitatorUrl || RELAI_FACILITATOR_URL;
34
87
  }
88
+ /**
89
+ * Get feePayer address for a network (cached)
90
+ */
91
+ async getFeePayer(caip2) {
92
+ if (this.feePayerCache.has(caip2)) {
93
+ return this.feePayerCache.get(caip2);
94
+ }
95
+ const isRelAI = this.facilitatorUrl.includes("facilitator.x402.fi") || this.facilitatorUrl.includes("relai");
96
+ if (isRelAI) {
97
+ const relaiFeePayer = "0x1892f72fdB3A966b2AD8595aA5f7741Ef72d6085";
98
+ this.feePayerCache.set(caip2, relaiFeePayer);
99
+ return relaiFeePayer;
100
+ }
101
+ try {
102
+ const supportedUrl = `${this.facilitatorUrl}/supported`;
103
+ const supportedRes = await fetch(supportedUrl);
104
+ if (supportedRes.ok) {
105
+ const supportedData = await supportedRes.json();
106
+ supportedData.kinds?.forEach((kind) => {
107
+ if (kind.network && kind.extra?.feePayer) {
108
+ this.feePayerCache.set(kind.network, kind.extra.feePayer);
109
+ }
110
+ });
111
+ return this.feePayerCache.get(caip2);
112
+ }
113
+ } catch (err) {
114
+ const envFeePayer = process.env.BACKEND_WALLET_ADDRESS || process.env.FEEPAYER_EVM_WALLET_ADDRESS;
115
+ if (envFeePayer) {
116
+ this.feePayerCache.set(caip2, envFeePayer);
117
+ return envFeePayer;
118
+ }
119
+ }
120
+ return void 0;
121
+ }
35
122
  /**
36
123
  * Express middleware to protect an endpoint with x402 micropayments.
37
124
  *
@@ -48,13 +135,41 @@ var Relai = class {
48
135
  if (typeof resolvedPrice !== "number" || !isFinite(resolvedPrice) || resolvedPrice <= 0) {
49
136
  return res.status(400).json({ error: "Invalid price configuration" });
50
137
  }
51
- const network = options.network || self.network;
138
+ const stripeConfig = isStripePayTo(options.payTo) ? options.payTo : null;
139
+ const network = stripeConfig ? stripeConfig.stripeNetwork || "base" : options.network || self.network;
52
140
  const caip2 = NETWORK_CAIP2[network];
53
141
  const asset = USDC_ADDRESSES[network];
54
142
  const amount = String(Math.floor(resolvedPrice * 1e6));
55
143
  const paymentHeader = req.headers["x-payment"] || req.headers["payment-signature"] || req.headers["x-payment-signature"];
56
144
  if (!paymentHeader) {
57
145
  options.onPaymentRequired?.(req, { price: resolvedPrice, network });
146
+ let resolvedPayTo;
147
+ if (stripeConfig) {
148
+ const amountInCents = Math.max(1, Math.round(resolvedPrice * 100));
149
+ resolvedPayTo = await createStripeDepositAddress(
150
+ stripeConfig.secretKey,
151
+ amountInCents,
152
+ stripeConfig.stripeNetwork
153
+ );
154
+ } else {
155
+ resolvedPayTo = options.payTo;
156
+ }
157
+ const feePayer = await self.getFeePayer(caip2);
158
+ const tokenMetadata = {
159
+ "eip155:103698795": { name: "USDC", version: "1" },
160
+ // SKALE BITE
161
+ "eip155:1187947933": { name: "USD Coin", version: "2" },
162
+ // SKALE Base
163
+ "eip155:8453": { name: "USD Coin", version: "2" },
164
+ // Base
165
+ "eip155:43114": { name: "USD Coin", version: "2" },
166
+ // Avalanche
167
+ "eip155:137": { name: "USD Coin", version: "2" },
168
+ // Polygon
169
+ "eip155:1": { name: "USD Coin", version: "2" }
170
+ // Ethereum
171
+ };
172
+ const metadata = tokenMetadata[caip2] || { name: "USDC", version: "1" };
58
173
  return res.status(402).json({
59
174
  x402Version: 2,
60
175
  error: "Payment required",
@@ -68,12 +183,14 @@ var Relai = class {
68
183
  network: caip2,
69
184
  amount,
70
185
  asset,
71
- payTo: options.payTo,
186
+ payTo: resolvedPayTo,
72
187
  maxTimeoutSeconds: options.maxTimeoutSeconds || 60,
73
188
  extra: {
74
- name: "USD Coin",
75
- version: "2",
76
- decimals: 6
189
+ name: metadata.name,
190
+ version: metadata.version,
191
+ decimals: 6,
192
+ ...feePayer && { feePayer }
193
+ // Add feePayer if available
77
194
  }
78
195
  }]
79
196
  });
@@ -92,12 +209,24 @@ var Relai = class {
92
209
  });
93
210
  }
94
211
  }
212
+ let settlePayTo;
213
+ if (stripeConfig) {
214
+ settlePayTo = paymentProof.payload?.authorization?.to || paymentProof.accepted?.payTo || "";
215
+ if (!settlePayTo) {
216
+ return res.status(400).json({
217
+ x402Version: 2,
218
+ error: "Cannot extract destination address from payment proof"
219
+ });
220
+ }
221
+ } else {
222
+ settlePayTo = options.payTo;
223
+ }
95
224
  const paymentRequirements = {
96
225
  scheme: "exact",
97
226
  network,
98
227
  amount,
99
228
  asset,
100
- payTo: options.payTo,
229
+ payTo: settlePayTo,
101
230
  maxTimeoutSeconds: options.maxTimeoutSeconds || 60
102
231
  };
103
232
  const settleUrl = `${self.facilitatorUrl}/settle`;
@@ -160,6 +289,7 @@ var Relai = class {
160
289
  var server_default = Relai;
161
290
  export {
162
291
  Relai,
163
- server_default as default
292
+ server_default as default,
293
+ stripePayTo
164
294
  };
165
295
  //# sourceMappingURL=server.js.map