@relai-fi/x402 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -3
- package/dist/client.cjs +18 -6
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +18 -6
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +114 -83
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +114 -73
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +26 -8
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +26 -8
- package/dist/react/index.js.map +1 -1
- package/dist/server.cjs +95 -74
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +77 -10
- package/dist/server.d.ts +77 -10
- package/dist/server.js +95 -66
- package/dist/server.js.map +1 -1
- package/dist/{types-DGRfrYd3.d.cts → types-C9LlAJj8.d.cts} +2 -35
- package/dist/{types-DGRfrYd3.d.ts → types-C9LlAJj8.d.ts} +2 -35
- package/dist/utils/index.cjs +1 -1
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +1 -1
- package/package.json +2 -2
package/dist/server.d.cts
CHANGED
|
@@ -1,18 +1,85 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as RelaiNetwork } from './types-C9LlAJj8.cjs';
|
|
2
2
|
|
|
3
|
+
interface RelaiServerConfig {
|
|
4
|
+
/** Network to accept payments on */
|
|
5
|
+
network: RelaiNetwork;
|
|
6
|
+
/** RelAI facilitator URL (default: https://facilitator.x402.fi) */
|
|
7
|
+
facilitatorUrl?: string;
|
|
8
|
+
}
|
|
9
|
+
type DynamicPrice = number | ((req: any) => number | Promise<number>);
|
|
10
|
+
interface ProtectOptions {
|
|
11
|
+
/** Price in USD (e.g., 0.01 for 1 cent) */
|
|
12
|
+
price: DynamicPrice;
|
|
13
|
+
/** Wallet address to receive payments */
|
|
14
|
+
payTo: string;
|
|
15
|
+
/** Description shown to payer */
|
|
16
|
+
description?: string;
|
|
17
|
+
/** MIME type of the response (default: application/json) */
|
|
18
|
+
mimeType?: string;
|
|
19
|
+
/** Maximum timeout in seconds (default: 60) */
|
|
20
|
+
maxTimeoutSeconds?: number;
|
|
21
|
+
/** Override network for this endpoint */
|
|
22
|
+
network?: RelaiNetwork;
|
|
23
|
+
/** Custom validation after payment is settled */
|
|
24
|
+
customRules?: (req: any) => boolean | Promise<boolean>;
|
|
25
|
+
/** Callback when 402 is returned (no payment provided) */
|
|
26
|
+
onPaymentRequired?: (req: any, info: {
|
|
27
|
+
price: number;
|
|
28
|
+
network: RelaiNetwork;
|
|
29
|
+
}) => void;
|
|
30
|
+
/** Callback when payment is settled successfully */
|
|
31
|
+
onPaymentSettled?: (req: any, result: SettleResult) => void;
|
|
32
|
+
/** Callback on error */
|
|
33
|
+
onError?: (req: any, error: unknown) => void;
|
|
34
|
+
}
|
|
35
|
+
interface SettleResult {
|
|
36
|
+
success: boolean;
|
|
37
|
+
transaction?: string;
|
|
38
|
+
payer?: string;
|
|
39
|
+
network?: string;
|
|
40
|
+
error?: string;
|
|
41
|
+
errorReason?: string;
|
|
42
|
+
}
|
|
43
|
+
interface PaymentInfo {
|
|
44
|
+
verified: boolean;
|
|
45
|
+
transactionId?: string;
|
|
46
|
+
payer?: string;
|
|
47
|
+
network: RelaiNetwork;
|
|
48
|
+
amount: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Server-side SDK for protecting Express endpoints with x402 micropayments.
|
|
52
|
+
* Settles payments through the RelAI facilitator (zero gas fees for users).
|
|
53
|
+
*
|
|
54
|
+
* Supports: Solana, Base, Avalanche, SKALE Base.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* import Relai from '@relai-fi/x402/server';
|
|
59
|
+
*
|
|
60
|
+
* const relai = new Relai({ network: 'base' });
|
|
61
|
+
*
|
|
62
|
+
* app.get('/api/data', relai.protect({
|
|
63
|
+
* payTo: '0xYourWallet',
|
|
64
|
+
* price: 0.01, // $0.01 USDC
|
|
65
|
+
* }), (req, res) => {
|
|
66
|
+
* res.json({ data: 'Protected content', payment: req.payment });
|
|
67
|
+
* });
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
3
70
|
declare class Relai {
|
|
4
|
-
private
|
|
5
|
-
private
|
|
6
|
-
constructor(config:
|
|
71
|
+
private network;
|
|
72
|
+
private facilitatorUrl;
|
|
73
|
+
constructor(config: RelaiServerConfig);
|
|
7
74
|
/**
|
|
8
75
|
* Express middleware to protect an endpoint with x402 micropayments.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
76
|
+
*
|
|
77
|
+
* Flow:
|
|
78
|
+
* 1. No payment header → returns 402 with payment requirements
|
|
79
|
+
* 2. Payment header present → calls RelAI facilitator `/settle`
|
|
80
|
+
* 3. Settlement success → sets `PAYMENT-RESPONSE` header, attaches `req.payment`, calls `next()`
|
|
11
81
|
*/
|
|
12
82
|
protect(options: ProtectOptions): (req: any, res: any, next: any) => Promise<any>;
|
|
13
|
-
private verifyPayment;
|
|
14
|
-
getStats(apiId?: string): Promise<any>;
|
|
15
|
-
createProtectedEndpoint(options: ProtectOptions): (req: any, res: any, next: any) => Promise<any>;
|
|
16
83
|
}
|
|
17
84
|
|
|
18
|
-
export { Relai, Relai as default };
|
|
85
|
+
export { type DynamicPrice, type PaymentInfo, type ProtectOptions, Relai, type RelaiServerConfig, type SettleResult, Relai as default };
|
package/dist/server.d.ts
CHANGED
|
@@ -1,18 +1,85 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as RelaiNetwork } from './types-C9LlAJj8.js';
|
|
2
2
|
|
|
3
|
+
interface RelaiServerConfig {
|
|
4
|
+
/** Network to accept payments on */
|
|
5
|
+
network: RelaiNetwork;
|
|
6
|
+
/** RelAI facilitator URL (default: https://facilitator.x402.fi) */
|
|
7
|
+
facilitatorUrl?: string;
|
|
8
|
+
}
|
|
9
|
+
type DynamicPrice = number | ((req: any) => number | Promise<number>);
|
|
10
|
+
interface ProtectOptions {
|
|
11
|
+
/** Price in USD (e.g., 0.01 for 1 cent) */
|
|
12
|
+
price: DynamicPrice;
|
|
13
|
+
/** Wallet address to receive payments */
|
|
14
|
+
payTo: string;
|
|
15
|
+
/** Description shown to payer */
|
|
16
|
+
description?: string;
|
|
17
|
+
/** MIME type of the response (default: application/json) */
|
|
18
|
+
mimeType?: string;
|
|
19
|
+
/** Maximum timeout in seconds (default: 60) */
|
|
20
|
+
maxTimeoutSeconds?: number;
|
|
21
|
+
/** Override network for this endpoint */
|
|
22
|
+
network?: RelaiNetwork;
|
|
23
|
+
/** Custom validation after payment is settled */
|
|
24
|
+
customRules?: (req: any) => boolean | Promise<boolean>;
|
|
25
|
+
/** Callback when 402 is returned (no payment provided) */
|
|
26
|
+
onPaymentRequired?: (req: any, info: {
|
|
27
|
+
price: number;
|
|
28
|
+
network: RelaiNetwork;
|
|
29
|
+
}) => void;
|
|
30
|
+
/** Callback when payment is settled successfully */
|
|
31
|
+
onPaymentSettled?: (req: any, result: SettleResult) => void;
|
|
32
|
+
/** Callback on error */
|
|
33
|
+
onError?: (req: any, error: unknown) => void;
|
|
34
|
+
}
|
|
35
|
+
interface SettleResult {
|
|
36
|
+
success: boolean;
|
|
37
|
+
transaction?: string;
|
|
38
|
+
payer?: string;
|
|
39
|
+
network?: string;
|
|
40
|
+
error?: string;
|
|
41
|
+
errorReason?: string;
|
|
42
|
+
}
|
|
43
|
+
interface PaymentInfo {
|
|
44
|
+
verified: boolean;
|
|
45
|
+
transactionId?: string;
|
|
46
|
+
payer?: string;
|
|
47
|
+
network: RelaiNetwork;
|
|
48
|
+
amount: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Server-side SDK for protecting Express endpoints with x402 micropayments.
|
|
52
|
+
* Settles payments through the RelAI facilitator (zero gas fees for users).
|
|
53
|
+
*
|
|
54
|
+
* Supports: Solana, Base, Avalanche, SKALE Base.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* import Relai from '@relai-fi/x402/server';
|
|
59
|
+
*
|
|
60
|
+
* const relai = new Relai({ network: 'base' });
|
|
61
|
+
*
|
|
62
|
+
* app.get('/api/data', relai.protect({
|
|
63
|
+
* payTo: '0xYourWallet',
|
|
64
|
+
* price: 0.01, // $0.01 USDC
|
|
65
|
+
* }), (req, res) => {
|
|
66
|
+
* res.json({ data: 'Protected content', payment: req.payment });
|
|
67
|
+
* });
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
3
70
|
declare class Relai {
|
|
4
|
-
private
|
|
5
|
-
private
|
|
6
|
-
constructor(config:
|
|
71
|
+
private network;
|
|
72
|
+
private facilitatorUrl;
|
|
73
|
+
constructor(config: RelaiServerConfig);
|
|
7
74
|
/**
|
|
8
75
|
* Express middleware to protect an endpoint with x402 micropayments.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
76
|
+
*
|
|
77
|
+
* Flow:
|
|
78
|
+
* 1. No payment header → returns 402 with payment requirements
|
|
79
|
+
* 2. Payment header present → calls RelAI facilitator `/settle`
|
|
80
|
+
* 3. Settlement success → sets `PAYMENT-RESPONSE` header, attaches `req.payment`, calls `next()`
|
|
11
81
|
*/
|
|
12
82
|
protect(options: ProtectOptions): (req: any, res: any, next: any) => Promise<any>;
|
|
13
|
-
private verifyPayment;
|
|
14
|
-
getStats(apiId?: string): Promise<any>;
|
|
15
|
-
createProtectedEndpoint(options: ProtectOptions): (req: any, res: any, next: any) => Promise<any>;
|
|
16
83
|
}
|
|
17
84
|
|
|
18
|
-
export { Relai, Relai as default };
|
|
85
|
+
export { type DynamicPrice, type PaymentInfo, type ProtectOptions, Relai, type RelaiServerConfig, type SettleResult, Relai as default };
|
package/dist/server.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
// src/server.ts
|
|
2
|
-
import axios from "axios";
|
|
3
|
-
|
|
4
1
|
// src/types.ts
|
|
5
2
|
var RELAI_FACILITATOR_URL = "https://facilitator.x402.fi";
|
|
6
3
|
var NETWORK_CAIP2 = {
|
|
7
4
|
"solana": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
|
|
8
5
|
"base": "eip155:8453",
|
|
9
6
|
"avalanche": "eip155:43114",
|
|
10
|
-
"skale-base": "eip155:1187947933"
|
|
7
|
+
"skale-base": "eip155:1187947933",
|
|
8
|
+
"skale-bite": "eip155:103698795",
|
|
9
|
+
"polygon": "eip155:137",
|
|
10
|
+
"ethereum": "eip155:1"
|
|
11
11
|
};
|
|
12
12
|
var CAIP2_TO_NETWORK = Object.fromEntries(
|
|
13
13
|
Object.entries(NETWORK_CAIP2).map(([k, v]) => [v, k])
|
|
@@ -16,7 +16,10 @@ var USDC_ADDRESSES = {
|
|
|
16
16
|
"solana": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
|
17
17
|
"base": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
18
18
|
"avalanche": "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
|
|
19
|
-
"skale-base": "0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20"
|
|
19
|
+
"skale-base": "0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20",
|
|
20
|
+
"skale-bite": "0xc4083B1E81ceb461Ccef3FDa8A9F24F0d764B6D8",
|
|
21
|
+
"polygon": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
22
|
+
"ethereum": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
|
20
23
|
};
|
|
21
24
|
var SOLANA_MAINNET_NETWORK = NETWORK_CAIP2["solana"];
|
|
22
25
|
var BASE_MAINNET_NETWORK = NETWORK_CAIP2["base"];
|
|
@@ -26,107 +29,133 @@ var USDC_BASE = USDC_ADDRESSES["base"];
|
|
|
26
29
|
// src/server.ts
|
|
27
30
|
var Relai = class {
|
|
28
31
|
constructor(config) {
|
|
29
|
-
this.
|
|
30
|
-
|
|
31
|
-
apiBaseUrl: "https://relai.fi/api",
|
|
32
|
-
facilitatorUrl: RELAI_FACILITATOR_URL,
|
|
33
|
-
...config
|
|
34
|
-
};
|
|
35
|
-
if (!this.config.apiKey || typeof this.config.apiKey !== "string") {
|
|
36
|
-
throw new Error("[Relai] Missing required apiKey in configuration");
|
|
37
|
-
}
|
|
38
|
-
this.client = axios.create({
|
|
39
|
-
baseURL: this.config.apiBaseUrl,
|
|
40
|
-
timeout: 1e4,
|
|
41
|
-
headers: {
|
|
42
|
-
"Authorization": `Bearer ${this.config.apiKey}`,
|
|
43
|
-
"Content-Type": "application/json"
|
|
44
|
-
}
|
|
45
|
-
});
|
|
32
|
+
this.network = config.network;
|
|
33
|
+
this.facilitatorUrl = config.facilitatorUrl || RELAI_FACILITATOR_URL;
|
|
46
34
|
}
|
|
47
35
|
/**
|
|
48
36
|
* Express middleware to protect an endpoint with x402 micropayments.
|
|
49
|
-
*
|
|
50
|
-
*
|
|
37
|
+
*
|
|
38
|
+
* Flow:
|
|
39
|
+
* 1. No payment header → returns 402 with payment requirements
|
|
40
|
+
* 2. Payment header present → calls RelAI facilitator `/settle`
|
|
41
|
+
* 3. Settlement success → sets `PAYMENT-RESPONSE` header, attaches `req.payment`, calls `next()`
|
|
51
42
|
*/
|
|
52
43
|
protect(options) {
|
|
44
|
+
const self = this;
|
|
53
45
|
return async (req, res, next) => {
|
|
54
46
|
try {
|
|
55
47
|
const resolvedPrice = typeof options.price === "function" ? await options.price(req) : options.price;
|
|
56
48
|
if (typeof resolvedPrice !== "number" || !isFinite(resolvedPrice) || resolvedPrice <= 0) {
|
|
57
|
-
return res.status(400).json({ error: "Invalid price configuration"
|
|
49
|
+
return res.status(400).json({ error: "Invalid price configuration" });
|
|
58
50
|
}
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
51
|
+
const network = options.network || self.network;
|
|
52
|
+
const caip2 = NETWORK_CAIP2[network];
|
|
53
|
+
const asset = USDC_ADDRESSES[network];
|
|
54
|
+
const amount = String(Math.floor(resolvedPrice * 1e6));
|
|
55
|
+
const paymentHeader = req.headers["x-payment"] || req.headers["payment-signature"] || req.headers["x-payment-signature"];
|
|
56
|
+
if (!paymentHeader) {
|
|
57
|
+
options.onPaymentRequired?.(req, { price: resolvedPrice, network });
|
|
64
58
|
return res.status(402).json({
|
|
65
59
|
x402Version: 2,
|
|
66
60
|
error: "Payment required",
|
|
67
61
|
resource: {
|
|
68
62
|
url: `${req.protocol}://${req.get("host")}${req.originalUrl}`,
|
|
69
63
|
description: options.description || "API access",
|
|
70
|
-
mimeType: "application/json"
|
|
64
|
+
mimeType: options.mimeType || "application/json"
|
|
71
65
|
},
|
|
72
66
|
accepts: [{
|
|
73
67
|
scheme: "exact",
|
|
74
68
|
network: caip2,
|
|
75
|
-
|
|
69
|
+
amount,
|
|
76
70
|
asset,
|
|
77
71
|
payTo: options.payTo,
|
|
78
|
-
maxTimeoutSeconds: 60,
|
|
72
|
+
maxTimeoutSeconds: options.maxTimeoutSeconds || 60,
|
|
79
73
|
extra: {
|
|
80
74
|
name: "USD Coin",
|
|
81
75
|
version: "2",
|
|
82
|
-
decimals: 6
|
|
83
|
-
facilitatorUrl: this.config.facilitatorUrl
|
|
76
|
+
decimals: 6
|
|
84
77
|
}
|
|
85
78
|
}]
|
|
86
79
|
});
|
|
87
80
|
}
|
|
88
|
-
|
|
89
|
-
|
|
81
|
+
let paymentProof;
|
|
82
|
+
try {
|
|
83
|
+
const decoded = Buffer.from(paymentHeader, "base64").toString("utf-8");
|
|
84
|
+
paymentProof = JSON.parse(decoded);
|
|
85
|
+
} catch {
|
|
86
|
+
try {
|
|
87
|
+
paymentProof = JSON.parse(paymentHeader);
|
|
88
|
+
} catch {
|
|
89
|
+
return res.status(400).json({
|
|
90
|
+
x402Version: 2,
|
|
91
|
+
error: "Invalid payment header \u2014 expected base64-encoded JSON"
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const paymentRequirements = {
|
|
96
|
+
scheme: "exact",
|
|
97
|
+
network,
|
|
98
|
+
amount,
|
|
99
|
+
asset,
|
|
100
|
+
payTo: options.payTo,
|
|
101
|
+
maxTimeoutSeconds: options.maxTimeoutSeconds || 60
|
|
102
|
+
};
|
|
103
|
+
const settleUrl = `${self.facilitatorUrl}/settle`;
|
|
104
|
+
const settleRes = await fetch(settleUrl, {
|
|
105
|
+
method: "POST",
|
|
106
|
+
headers: { "Content-Type": "application/json" },
|
|
107
|
+
body: JSON.stringify({
|
|
108
|
+
paymentPayload: paymentProof,
|
|
109
|
+
paymentRequirements
|
|
110
|
+
})
|
|
111
|
+
});
|
|
112
|
+
const result = await settleRes.json();
|
|
113
|
+
if (!result.success) {
|
|
90
114
|
return res.status(402).json({
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
details: verification.error
|
|
115
|
+
x402Version: 2,
|
|
116
|
+
error: result.errorReason || result.error || "Payment settlement failed"
|
|
94
117
|
});
|
|
95
118
|
}
|
|
96
|
-
|
|
119
|
+
const paymentInfo = {
|
|
120
|
+
verified: true,
|
|
121
|
+
transactionId: result.transaction,
|
|
122
|
+
payer: result.payer,
|
|
123
|
+
network,
|
|
124
|
+
amount: resolvedPrice
|
|
125
|
+
};
|
|
126
|
+
req.payment = paymentInfo;
|
|
127
|
+
req.x402Payer = result.payer;
|
|
128
|
+
req.x402Paid = true;
|
|
129
|
+
req.x402Transaction = result.transaction;
|
|
130
|
+
req.x402Network = network;
|
|
131
|
+
const paymentResponse = {
|
|
132
|
+
x402Version: 2,
|
|
133
|
+
scheme: "exact",
|
|
134
|
+
network: caip2,
|
|
135
|
+
transaction: result.transaction,
|
|
136
|
+
payer: result.payer,
|
|
137
|
+
amount,
|
|
138
|
+
asset
|
|
139
|
+
};
|
|
140
|
+
res.setHeader(
|
|
141
|
+
"PAYMENT-RESPONSE",
|
|
142
|
+
Buffer.from(JSON.stringify(paymentResponse)).toString("base64")
|
|
143
|
+
);
|
|
144
|
+
options.onPaymentSettled?.(req, result);
|
|
97
145
|
if (options.customRules) {
|
|
98
|
-
const
|
|
99
|
-
if (!
|
|
100
|
-
return res.status(403).json({ error: "Custom validation failed"
|
|
146
|
+
const valid = await options.customRules(req);
|
|
147
|
+
if (!valid) {
|
|
148
|
+
return res.status(403).json({ error: "Custom validation failed" });
|
|
101
149
|
}
|
|
102
150
|
}
|
|
103
151
|
next();
|
|
104
152
|
} catch (error) {
|
|
105
|
-
|
|
106
|
-
|
|
153
|
+
options.onError?.(req, error);
|
|
154
|
+
console.error("[Relai] Protection error:", error);
|
|
155
|
+
res.status(500).json({ error: "Internal server error" });
|
|
107
156
|
}
|
|
108
157
|
};
|
|
109
158
|
}
|
|
110
|
-
async verifyPayment(signature, expectedPrice, timeoutMs) {
|
|
111
|
-
try {
|
|
112
|
-
const response = await this.client.post(
|
|
113
|
-
"/verify-payment",
|
|
114
|
-
{ signature, expectedPrice, network: this.config.network },
|
|
115
|
-
{ timeout: typeof timeoutMs === "number" ? Math.max(1, timeoutMs) : void 0 }
|
|
116
|
-
);
|
|
117
|
-
return response.data;
|
|
118
|
-
} catch (error) {
|
|
119
|
-
return { verified: false, error: error.response?.data?.error || "Verification failed" };
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
async getStats(apiId) {
|
|
123
|
-
const params = apiId ? { apiId } : {};
|
|
124
|
-
const response = await this.client.get("/stats", { params });
|
|
125
|
-
return response.data;
|
|
126
|
-
}
|
|
127
|
-
createProtectedEndpoint(options) {
|
|
128
|
-
return this.protect(options);
|
|
129
|
-
}
|
|
130
159
|
};
|
|
131
160
|
var server_default = Relai;
|
|
132
161
|
export {
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts","../src/types.ts"],"sourcesContent":["// src/server.ts\nimport axios, { AxiosInstance } from 'axios';\nimport {\n RelaiConfig,\n ProtectOptions,\n PaymentResult,\n NETWORK_CAIP2,\n USDC_ADDRESSES,\n RELAI_FACILITATOR_URL,\n type RelaiNetwork,\n} from './types';\n\nexport class Relai {\n private config: Required<Pick<RelaiConfig, 'network' | 'facilitatorUrl' | 'apiBaseUrl'>> & RelaiConfig;\n private client: AxiosInstance;\n\n constructor(config: RelaiConfig) {\n this.config = {\n network: 'solana',\n apiBaseUrl: 'https://relai.fi/api',\n facilitatorUrl: RELAI_FACILITATOR_URL,\n ...config,\n };\n\n if (!this.config.apiKey || typeof this.config.apiKey !== 'string') {\n throw new Error('[Relai] Missing required apiKey in configuration');\n }\n\n this.client = axios.create({\n baseURL: this.config.apiBaseUrl,\n timeout: 10000,\n headers: {\n 'Authorization': `Bearer ${this.config.apiKey}`,\n 'Content-Type': 'application/json',\n },\n });\n }\n\n /**\n * Express middleware to protect an endpoint with x402 micropayments.\n * Returns a 402 with `accepts` for the configured network.\n * Supports: Solana, Base, Avalanche, SKALE Base.\n */\n protect(options: ProtectOptions) {\n return async (req: any, res: any, next: any) => {\n try {\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', code: 'INVALID_PRICE' });\n }\n\n // Check for payment header\n const paymentSignature = req.headers['x-payment-signature'] || req.headers['x-relai-payment'];\n\n if (!paymentSignature) {\n const network = this.config.network as RelaiNetwork;\n const caip2 = NETWORK_CAIP2[network] || NETWORK_CAIP2['solana'];\n const asset = USDC_ADDRESSES[network] || USDC_ADDRESSES['solana'];\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: 'application/json',\n },\n accepts: [{\n scheme: 'exact',\n network: caip2,\n maxAmountRequired: String(Math.floor(resolvedPrice * 1_000_000)),\n asset,\n payTo: options.payTo,\n maxTimeoutSeconds: 60,\n extra: {\n name: 'USD Coin',\n version: '2',\n decimals: 6,\n facilitatorUrl: this.config.facilitatorUrl,\n },\n }],\n });\n }\n\n // Verify payment\n const verification = await this.verifyPayment(paymentSignature, resolvedPrice, options.maxTimeout);\n\n if (!verification.verified) {\n return res.status(402).json({\n error: 'Payment verification failed',\n code: 'PAYMENT_INVALID',\n details: verification.error,\n });\n }\n\n req.payment = verification;\n\n if (options.customRules) {\n const customValid = await options.customRules(req);\n if (!customValid) {\n return res.status(403).json({ error: 'Custom validation failed', code: 'VALIDATION_FAILED' });\n }\n }\n\n next();\n } catch (error) {\n console.error('Relai protection error:', error);\n res.status(500).json({ error: 'Internal server error', code: 'SERVER_ERROR' });\n }\n };\n }\n\n private async verifyPayment(signature: string, expectedPrice: number, timeoutMs?: number): Promise<PaymentResult> {\n try {\n const response = await this.client.post(\n '/verify-payment',\n { signature, expectedPrice, network: this.config.network },\n { timeout: typeof timeoutMs === 'number' ? Math.max(1, timeoutMs) : undefined },\n );\n return response.data;\n } catch (error: any) {\n return { verified: false, error: error.response?.data?.error || 'Verification failed' };\n }\n }\n\n async getStats(apiId?: string) {\n const params = apiId ? { apiId } : {};\n const response = await this.client.get('/stats', { params });\n return response.data;\n }\n\n createProtectedEndpoint(options: ProtectOptions) {\n return this.protect(options);\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';\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};\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};\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};\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};\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};\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'];\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'].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\n// ============================================================================\n\nexport interface RelaiConfig {\n apiKey?: string;\n network?: RelaiNetwork;\n facilitatorUrl?: string;\n apiBaseUrl?: string;\n}\n\nexport type DynamicPrice = number | ((req: unknown) => 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 /** Maximum timeout in seconds */\n maxTimeout?: number;\n /** Custom validation rules */\n customRules?: (req: unknown) => boolean | Promise<boolean>;\n onPaymentRequired?: (\n req: unknown,\n info: { price: number; description?: string; facilitatorUrl?: string }\n ) => void;\n onPaymentVerified?: (req: unknown, result: PaymentResult) => void;\n onError?: (req: unknown, error: unknown) => void;\n}\n\nexport interface PaymentResult {\n verified: boolean;\n transactionId?: string;\n amount?: number;\n currency?: string;\n error?: string;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/server.ts"],"sourcesContent":["// 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","// 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"],"mappings":"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","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** RelAI Facilitator URL */
|
|
2
2
|
declare const RELAI_FACILITATOR_URL = "https://facilitator.x402.fi";
|
|
3
3
|
/** All networks supported by RelAI facilitator */
|
|
4
|
-
type RelaiNetwork = 'solana' | 'base' | 'avalanche' | 'skale-base';
|
|
4
|
+
type RelaiNetwork = 'solana' | 'base' | 'avalanche' | 'skale-base' | 'skale-bite' | 'polygon' | 'ethereum';
|
|
5
5
|
/** CAIP-2 network identifiers */
|
|
6
6
|
declare const NETWORK_CAIP2: Record<RelaiNetwork, string>;
|
|
7
7
|
/** Reverse lookup: CAIP-2 → simple network name */
|
|
@@ -87,38 +87,5 @@ interface PaymentRequired {
|
|
|
87
87
|
resource?: ResourceInfo;
|
|
88
88
|
extensions?: Record<string, unknown>;
|
|
89
89
|
}
|
|
90
|
-
interface RelaiConfig {
|
|
91
|
-
apiKey?: string;
|
|
92
|
-
network?: RelaiNetwork;
|
|
93
|
-
facilitatorUrl?: string;
|
|
94
|
-
apiBaseUrl?: string;
|
|
95
|
-
}
|
|
96
|
-
type DynamicPrice = number | ((req: unknown) => number | Promise<number>);
|
|
97
|
-
interface ProtectOptions {
|
|
98
|
-
/** Price in USD (e.g., 0.01 for 1 cent) */
|
|
99
|
-
price: DynamicPrice;
|
|
100
|
-
/** Wallet address to receive payments */
|
|
101
|
-
payTo: string;
|
|
102
|
-
/** Description shown to payer */
|
|
103
|
-
description?: string;
|
|
104
|
-
/** Maximum timeout in seconds */
|
|
105
|
-
maxTimeout?: number;
|
|
106
|
-
/** Custom validation rules */
|
|
107
|
-
customRules?: (req: unknown) => boolean | Promise<boolean>;
|
|
108
|
-
onPaymentRequired?: (req: unknown, info: {
|
|
109
|
-
price: number;
|
|
110
|
-
description?: string;
|
|
111
|
-
facilitatorUrl?: string;
|
|
112
|
-
}) => void;
|
|
113
|
-
onPaymentVerified?: (req: unknown, result: PaymentResult) => void;
|
|
114
|
-
onError?: (req: unknown, error: unknown) => void;
|
|
115
|
-
}
|
|
116
|
-
interface PaymentResult {
|
|
117
|
-
verified: boolean;
|
|
118
|
-
transactionId?: string;
|
|
119
|
-
amount?: number;
|
|
120
|
-
currency?: string;
|
|
121
|
-
error?: string;
|
|
122
|
-
}
|
|
123
90
|
|
|
124
|
-
export { type AcceptsExtra as A, BASE_MAINNET_NETWORK as B, CAIP2_TO_NETWORK as C,
|
|
91
|
+
export { type AcceptsExtra as A, BASE_MAINNET_NETWORK as B, CAIP2_TO_NETWORK as C, EXPLORER_TX_URL as E, NETWORK_CAIP2 as N, type PaymentAccept as P, RELAI_FACILITATOR_URL as R, SOLANA_MAINNET_NETWORK as S, USDC_ADDRESSES as U, type WalletSet as W, type RelaiNetwork as a, CHAIN_IDS as b, NETWORK_LABELS as c, USDC_SOLANA as d, USDC_BASE as e, RELAI_NETWORKS as f, isEvm as g, type SolanaWallet as h, isSolana as i, type EvmWallet as j, type ResourceInfo as k, type PaymentRequired as l, normalizeNetwork as n };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** RelAI Facilitator URL */
|
|
2
2
|
declare const RELAI_FACILITATOR_URL = "https://facilitator.x402.fi";
|
|
3
3
|
/** All networks supported by RelAI facilitator */
|
|
4
|
-
type RelaiNetwork = 'solana' | 'base' | 'avalanche' | 'skale-base';
|
|
4
|
+
type RelaiNetwork = 'solana' | 'base' | 'avalanche' | 'skale-base' | 'skale-bite' | 'polygon' | 'ethereum';
|
|
5
5
|
/** CAIP-2 network identifiers */
|
|
6
6
|
declare const NETWORK_CAIP2: Record<RelaiNetwork, string>;
|
|
7
7
|
/** Reverse lookup: CAIP-2 → simple network name */
|
|
@@ -87,38 +87,5 @@ interface PaymentRequired {
|
|
|
87
87
|
resource?: ResourceInfo;
|
|
88
88
|
extensions?: Record<string, unknown>;
|
|
89
89
|
}
|
|
90
|
-
interface RelaiConfig {
|
|
91
|
-
apiKey?: string;
|
|
92
|
-
network?: RelaiNetwork;
|
|
93
|
-
facilitatorUrl?: string;
|
|
94
|
-
apiBaseUrl?: string;
|
|
95
|
-
}
|
|
96
|
-
type DynamicPrice = number | ((req: unknown) => number | Promise<number>);
|
|
97
|
-
interface ProtectOptions {
|
|
98
|
-
/** Price in USD (e.g., 0.01 for 1 cent) */
|
|
99
|
-
price: DynamicPrice;
|
|
100
|
-
/** Wallet address to receive payments */
|
|
101
|
-
payTo: string;
|
|
102
|
-
/** Description shown to payer */
|
|
103
|
-
description?: string;
|
|
104
|
-
/** Maximum timeout in seconds */
|
|
105
|
-
maxTimeout?: number;
|
|
106
|
-
/** Custom validation rules */
|
|
107
|
-
customRules?: (req: unknown) => boolean | Promise<boolean>;
|
|
108
|
-
onPaymentRequired?: (req: unknown, info: {
|
|
109
|
-
price: number;
|
|
110
|
-
description?: string;
|
|
111
|
-
facilitatorUrl?: string;
|
|
112
|
-
}) => void;
|
|
113
|
-
onPaymentVerified?: (req: unknown, result: PaymentResult) => void;
|
|
114
|
-
onError?: (req: unknown, error: unknown) => void;
|
|
115
|
-
}
|
|
116
|
-
interface PaymentResult {
|
|
117
|
-
verified: boolean;
|
|
118
|
-
transactionId?: string;
|
|
119
|
-
amount?: number;
|
|
120
|
-
currency?: string;
|
|
121
|
-
error?: string;
|
|
122
|
-
}
|
|
123
90
|
|
|
124
|
-
export { type AcceptsExtra as A, BASE_MAINNET_NETWORK as B, CAIP2_TO_NETWORK as C,
|
|
91
|
+
export { type AcceptsExtra as A, BASE_MAINNET_NETWORK as B, CAIP2_TO_NETWORK as C, EXPLORER_TX_URL as E, NETWORK_CAIP2 as N, type PaymentAccept as P, RELAI_FACILITATOR_URL as R, SOLANA_MAINNET_NETWORK as S, USDC_ADDRESSES as U, type WalletSet as W, type RelaiNetwork as a, CHAIN_IDS as b, NETWORK_LABELS as c, USDC_SOLANA as d, USDC_BASE as e, RELAI_NETWORKS as f, isEvm as g, type SolanaWallet as h, isSolana as i, type EvmWallet as j, type ResourceInfo as k, type PaymentRequired as l, normalizeNetwork as n };
|
package/dist/utils/index.cjs
CHANGED
|
@@ -158,7 +158,7 @@ function isSolanaNetwork(network) {
|
|
|
158
158
|
return network === "solana" || network === "solana-devnet" || network.startsWith("solana:");
|
|
159
159
|
}
|
|
160
160
|
function isEvmNetwork(network) {
|
|
161
|
-
const evmNetworks = ["base", "base-sepolia", "ethereum", "polygon", "avalanche", "skale-base", "peaq", "sei"];
|
|
161
|
+
const evmNetworks = ["base", "base-sepolia", "ethereum", "polygon", "avalanche", "skale-base", "skale-bite", "peaq", "sei"];
|
|
162
162
|
return evmNetworks.includes(network) || network.startsWith("eip155:");
|
|
163
163
|
}
|
|
164
164
|
function toAtomicUnits(usd, decimals = 6) {
|