@armory-sh/base 0.2.27-alpha.23.76 → 0.2.27-alpha.23.78
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/client-hooks-runtime.d.ts +8 -0
- package/dist/encoding/x402.d.ts +1 -1
- package/dist/index.d.ts +27 -26
- package/dist/index.js +1223 -1029
- package/dist/payment-client.d.ts +1 -1
- package/dist/payment-requirements.d.ts +1 -1
- package/dist/types/api.d.ts +1 -1
- package/dist/types/hooks.d.ts +17 -1
- package/dist/types/protocol.d.ts +1 -1
- package/dist/utils/x402.d.ts +1 -1
- package/dist/validation.d.ts +1 -1
- package/package.json +14 -1
- package/src/abi/erc20.ts +84 -0
- package/src/client-hooks-runtime.ts +133 -0
- package/src/data/tokens.ts +199 -0
- package/src/eip712.ts +108 -0
- package/src/encoding/x402.ts +205 -0
- package/src/encoding.ts +98 -0
- package/src/errors.ts +23 -0
- package/src/index.ts +322 -0
- package/src/payment-client.ts +201 -0
- package/src/payment-requirements.ts +256 -0
- package/src/protocol.ts +57 -0
- package/src/types/api.ts +304 -0
- package/src/types/hooks.ts +85 -0
- package/src/types/networks.ts +175 -0
- package/src/types/protocol.ts +182 -0
- package/src/types/v2.ts +282 -0
- package/src/types/wallet.ts +30 -0
- package/src/types/x402.ts +151 -0
- package/src/utils/base64.ts +48 -0
- package/src/utils/routes.ts +240 -0
- package/src/utils/utils/index.ts +7 -0
- package/src/utils/utils/mock-facilitator.ts +184 -0
- package/src/utils/x402.ts +147 -0
- package/src/validation.ts +655 -0
package/dist/index.js
CHANGED
|
@@ -1,385 +1,161 @@
|
|
|
1
1
|
import { randomBytes } from 'crypto';
|
|
2
2
|
|
|
3
|
-
// src/
|
|
4
|
-
var
|
|
5
|
-
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
3
|
+
// src/abi/erc20.ts
|
|
4
|
+
var ERC20_ABI = [
|
|
5
|
+
{
|
|
6
|
+
type: "function",
|
|
7
|
+
name: "transferWithAuthorization",
|
|
8
|
+
stateMutability: "nonpayable",
|
|
9
|
+
inputs: [
|
|
10
|
+
{ name: "from", type: "address" },
|
|
11
|
+
{ name: "to", type: "address" },
|
|
12
|
+
{ name: "amount", type: "uint256" },
|
|
13
|
+
{ name: "validAfter", type: "uint256" },
|
|
14
|
+
{ name: "expiry", type: "uint256" },
|
|
15
|
+
{ name: "v", type: "uint8" },
|
|
16
|
+
{ name: "r", type: "bytes32" },
|
|
17
|
+
{ name: "s", type: "bytes32" }
|
|
18
|
+
],
|
|
19
|
+
outputs: []
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
type: "function",
|
|
23
|
+
name: "receiveWithAuthorization",
|
|
24
|
+
stateMutability: "nonpayable",
|
|
25
|
+
inputs: [
|
|
26
|
+
{ name: "from", type: "address" },
|
|
27
|
+
{ name: "to", type: "address" },
|
|
28
|
+
{ name: "amount", type: "uint256" },
|
|
29
|
+
{ name: "validAfter", type: "uint256" },
|
|
30
|
+
{ name: "expiry", type: "uint256" },
|
|
31
|
+
{ name: "v", type: "uint8" },
|
|
32
|
+
{ name: "r", type: "bytes32" },
|
|
33
|
+
{ name: "s", type: "bytes32" }
|
|
34
|
+
],
|
|
35
|
+
outputs: []
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
type: "function",
|
|
39
|
+
name: "balanceOf",
|
|
40
|
+
stateMutability: "view",
|
|
41
|
+
inputs: [{ name: "account", type: "address" }],
|
|
42
|
+
outputs: [{ name: "balance", type: "uint256" }]
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
type: "function",
|
|
46
|
+
name: "name",
|
|
47
|
+
stateMutability: "view",
|
|
48
|
+
inputs: [],
|
|
49
|
+
outputs: [{ name: "", type: "string" }]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: "function",
|
|
53
|
+
name: "symbol",
|
|
54
|
+
stateMutability: "view",
|
|
55
|
+
inputs: [],
|
|
56
|
+
outputs: [{ name: "", type: "string" }]
|
|
57
|
+
}
|
|
58
|
+
];
|
|
58
59
|
|
|
59
|
-
// src/
|
|
60
|
-
var
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (typeof btoa === "function") {
|
|
64
|
-
let binary = "";
|
|
65
|
-
for (let index = 0; index < bytes.length; index += 1) {
|
|
66
|
-
binary += String.fromCharCode(bytes[index]);
|
|
67
|
-
}
|
|
68
|
-
return btoa(binary);
|
|
60
|
+
// src/client-hooks-runtime.ts
|
|
61
|
+
var notifyError = async (hooks, context) => {
|
|
62
|
+
if (!hooks?.length) {
|
|
63
|
+
return;
|
|
69
64
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (typeof atob === "function") {
|
|
74
|
-
const binary = atob(base64);
|
|
75
|
-
const bytes = new Uint8Array(binary.length);
|
|
76
|
-
for (let index = 0; index < binary.length; index += 1) {
|
|
77
|
-
bytes[index] = binary.charCodeAt(index);
|
|
65
|
+
for (const hook of hooks) {
|
|
66
|
+
if (!hook.onError) {
|
|
67
|
+
continue;
|
|
78
68
|
}
|
|
79
|
-
|
|
69
|
+
await hook.onError(context);
|
|
80
70
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return toBase64(bytes);
|
|
86
|
-
}
|
|
87
|
-
function decodeBase64ToUtf8(value) {
|
|
88
|
-
const bytes = fromBase64(value);
|
|
89
|
-
return textDecoder.decode(bytes);
|
|
90
|
-
}
|
|
91
|
-
function toBase64Url(base64) {
|
|
92
|
-
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
93
|
-
}
|
|
94
|
-
function normalizeBase64Url(value) {
|
|
95
|
-
return value.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// src/encoding/x402.ts
|
|
99
|
-
function safeBase64Encode(str) {
|
|
100
|
-
return toBase64Url(encodeUtf8ToBase64(str));
|
|
101
|
-
}
|
|
102
|
-
function safeBase64Decode(str) {
|
|
103
|
-
return decodeBase64ToUtf8(normalizeBase64Url(str));
|
|
104
|
-
}
|
|
105
|
-
function encodePayment(payload) {
|
|
106
|
-
if (!isPaymentPayload(payload)) {
|
|
107
|
-
throw new Error("Invalid payment payload format");
|
|
71
|
+
};
|
|
72
|
+
var runOnPaymentRequiredHooks = async (hooks, context) => {
|
|
73
|
+
if (!hooks?.length) {
|
|
74
|
+
return;
|
|
108
75
|
}
|
|
109
|
-
const
|
|
110
|
-
if (
|
|
111
|
-
|
|
76
|
+
for (const hook of hooks) {
|
|
77
|
+
if (!hook.onPaymentRequired) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
await hook.onPaymentRequired(context);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
await notifyError(hooks, { error, phase: "onPaymentRequired" });
|
|
84
|
+
throw error;
|
|
112
85
|
}
|
|
113
|
-
return value;
|
|
114
|
-
}));
|
|
115
|
-
return safeBase64Encode(JSON.stringify(safePayload));
|
|
116
|
-
}
|
|
117
|
-
function decodePayment(encoded) {
|
|
118
|
-
let parsed;
|
|
119
|
-
try {
|
|
120
|
-
parsed = JSON.parse(encoded);
|
|
121
|
-
} catch {
|
|
122
|
-
const decoded = safeBase64Decode(encoded);
|
|
123
|
-
parsed = JSON.parse(decoded);
|
|
124
|
-
}
|
|
125
|
-
if (!isPaymentPayload(parsed)) {
|
|
126
|
-
throw new Error("Invalid payment payload format");
|
|
127
86
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
87
|
+
};
|
|
88
|
+
var selectRequirementWithHooks = async (hooks, context) => {
|
|
89
|
+
if (!context.accepts.length) {
|
|
90
|
+
throw new Error("No payment requirements found in accepts array");
|
|
91
|
+
}
|
|
92
|
+
let selected = context.accepts[0];
|
|
93
|
+
if (!hooks?.length) {
|
|
94
|
+
context.selectedRequirement = selected;
|
|
95
|
+
context.requirements = selected;
|
|
96
|
+
return selected;
|
|
97
|
+
}
|
|
98
|
+
for (const hook of hooks) {
|
|
99
|
+
if (!hook.selectRequirement) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
const override = await hook.selectRequirement({
|
|
104
|
+
...context,
|
|
105
|
+
selectedRequirement: selected,
|
|
106
|
+
requirements: selected
|
|
107
|
+
});
|
|
108
|
+
if (!override) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const isValid = context.accepts.some((accept) => accept === override);
|
|
112
|
+
if (!isValid) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
"Hook selectRequirement must return an item from accepts"
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
selected = override;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
await notifyError(hooks, { error, phase: "selectRequirement" });
|
|
120
|
+
throw error;
|
|
134
121
|
}
|
|
135
|
-
return value;
|
|
136
|
-
}));
|
|
137
|
-
return safeBase64Encode(JSON.stringify(safeResponse));
|
|
138
|
-
}
|
|
139
|
-
function decodeSettlementResponse(encoded) {
|
|
140
|
-
try {
|
|
141
|
-
return JSON.parse(encoded);
|
|
142
|
-
} catch {
|
|
143
|
-
const decoded = safeBase64Decode(encoded);
|
|
144
|
-
return JSON.parse(decoded);
|
|
145
122
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return
|
|
153
|
-
} catch {
|
|
154
|
-
const decoded = safeBase64Decode(encoded);
|
|
155
|
-
return JSON.parse(decoded);
|
|
123
|
+
context.selectedRequirement = selected;
|
|
124
|
+
context.requirements = selected;
|
|
125
|
+
return selected;
|
|
126
|
+
};
|
|
127
|
+
var runBeforeSignPaymentHooks = async (hooks, context) => {
|
|
128
|
+
if (!hooks?.length) {
|
|
129
|
+
return;
|
|
156
130
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
131
|
+
for (const hook of hooks) {
|
|
132
|
+
if (!hook.beforeSignPayment) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
await hook.beforeSignPayment(context);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
await notifyError(hooks, { error, phase: "beforeSignPayment" });
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
161
141
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if (!encoded) return null;
|
|
167
|
-
try {
|
|
168
|
-
return decodePayment(encoded);
|
|
169
|
-
} catch {
|
|
170
|
-
return null;
|
|
142
|
+
};
|
|
143
|
+
var runAfterPaymentResponseHooks = async (hooks, context) => {
|
|
144
|
+
if (!hooks?.length) {
|
|
145
|
+
return;
|
|
171
146
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
return {
|
|
183
|
-
[V2_HEADERS.PAYMENT_REQUIRED]: safeBase64Encode(JSON.stringify(response))
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
function createSettlementHeaders(settlement) {
|
|
187
|
-
return {
|
|
188
|
-
[V2_HEADERS.PAYMENT_RESPONSE]: encodeSettlementResponse(settlement)
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
function createNonce() {
|
|
192
|
-
const bytes = randomBytes(32);
|
|
193
|
-
return `0x${bytes.toString("hex")}`;
|
|
194
|
-
}
|
|
195
|
-
function toAtomicUnits(amount, decimals = 6) {
|
|
196
|
-
const parts = amount.split(".");
|
|
197
|
-
const whole = parts[0];
|
|
198
|
-
const fractional = parts[1] || "";
|
|
199
|
-
const paddedFractional = fractional.padEnd(decimals, "0").slice(0, decimals);
|
|
200
|
-
return `${whole}${paddedFractional}`;
|
|
201
|
-
}
|
|
202
|
-
function fromAtomicUnits(amount, decimals = 6) {
|
|
203
|
-
const value = BigInt(amount);
|
|
204
|
-
const divisor = BigInt(10 ** decimals);
|
|
205
|
-
const whole = (value / divisor).toString();
|
|
206
|
-
const fractional = (value % divisor).toString().padStart(decimals, "0").replace(/0+$/, "");
|
|
207
|
-
if (fractional.length === 0) {
|
|
208
|
-
return whole;
|
|
147
|
+
for (const hook of hooks) {
|
|
148
|
+
if (!hook.afterPaymentResponse) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
await hook.afterPaymentResponse(context);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
await notifyError(hooks, { error, phase: "afterPaymentResponse" });
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
209
157
|
}
|
|
210
|
-
return `${whole}.${fractional}`;
|
|
211
|
-
}
|
|
212
|
-
function caip2ToNetwork(caip2Id) {
|
|
213
|
-
const match = caip2Id.match(/^eip155:(\d+)$/);
|
|
214
|
-
if (!match) {
|
|
215
|
-
return caip2Id;
|
|
216
|
-
}
|
|
217
|
-
const chainId = parseInt(match[1], 10);
|
|
218
|
-
const chainIdToNetwork = {
|
|
219
|
-
1: "ethereum",
|
|
220
|
-
8453: "base",
|
|
221
|
-
84532: "base-sepolia",
|
|
222
|
-
137: "polygon",
|
|
223
|
-
42161: "arbitrum",
|
|
224
|
-
421614: "arbitrum-sepolia",
|
|
225
|
-
10: "optimism",
|
|
226
|
-
11155420: "optimism-sepolia",
|
|
227
|
-
11155111: "ethereum-sepolia"
|
|
228
|
-
};
|
|
229
|
-
return chainIdToNetwork[chainId] || caip2Id;
|
|
230
|
-
}
|
|
231
|
-
function networkToCaip2(network) {
|
|
232
|
-
const networkToChainId = {
|
|
233
|
-
ethereum: 1,
|
|
234
|
-
base: 8453,
|
|
235
|
-
"base-sepolia": 84532,
|
|
236
|
-
polygon: 137,
|
|
237
|
-
arbitrum: 42161,
|
|
238
|
-
"arbitrum-sepolia": 421614,
|
|
239
|
-
optimism: 10,
|
|
240
|
-
"optimism-sepolia": 11155420,
|
|
241
|
-
"ethereum-sepolia": 11155111
|
|
242
|
-
};
|
|
243
|
-
const chainId = networkToChainId[network];
|
|
244
|
-
if (chainId) {
|
|
245
|
-
return `eip155:${chainId}`;
|
|
246
|
-
}
|
|
247
|
-
if (network.startsWith("eip155:")) {
|
|
248
|
-
return network;
|
|
249
|
-
}
|
|
250
|
-
return `eip155:0`;
|
|
251
|
-
}
|
|
252
|
-
function getCurrentTimestamp() {
|
|
253
|
-
return Math.floor(Date.now() / 1e3);
|
|
254
|
-
}
|
|
255
|
-
function isValidAddress(address) {
|
|
256
|
-
return /^0x[a-fA-F0-9]{40}$/.test(address);
|
|
257
|
-
}
|
|
258
|
-
function normalizeAddress(address) {
|
|
259
|
-
return address.toLowerCase();
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// src/types/protocol.ts
|
|
263
|
-
function isX402V2Payload(obj) {
|
|
264
|
-
return typeof obj === "object" && obj !== null && "x402Version" in obj && obj.x402Version === 2 && "accepted" in obj && "payload" in obj;
|
|
265
|
-
}
|
|
266
|
-
function isX402V2Requirements(obj) {
|
|
267
|
-
return typeof obj === "object" && obj !== null && "scheme" in obj && obj.scheme === "exact" && "network" in obj && typeof obj.network === "string" && obj.network.startsWith("eip155:") && // CAIP-2 format
|
|
268
|
-
"amount" in obj && "asset" in obj && "payTo" in obj && "maxTimeoutSeconds" in obj;
|
|
269
|
-
}
|
|
270
|
-
function isX402V2Settlement(obj) {
|
|
271
|
-
return typeof obj === "object" && obj !== null && "success" in obj && typeof obj.success === "boolean" && "network" in obj && typeof obj.network === "string" && obj.network.startsWith("eip155:");
|
|
272
|
-
}
|
|
273
|
-
function isX402V2PaymentRequired(obj) {
|
|
274
|
-
return typeof obj === "object" && obj !== null && "x402Version" in obj && obj.x402Version === 2 && "resource" in obj && "accepts" in obj;
|
|
275
|
-
}
|
|
276
|
-
var PAYMENT_SIGNATURE_HEADER = "PAYMENT-SIGNATURE";
|
|
277
|
-
var PAYMENT_RESPONSE_HEADER = "PAYMENT-RESPONSE";
|
|
278
|
-
var PAYMENT_REQUIRED_HEADER = "PAYMENT-REQUIRED";
|
|
279
|
-
function isSettlementSuccessful(response) {
|
|
280
|
-
return "success" in response ? response.success : false;
|
|
281
|
-
}
|
|
282
|
-
function getTxHash(response) {
|
|
283
|
-
if ("transaction" in response) return response.transaction;
|
|
284
|
-
return void 0;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// src/types/networks.ts
|
|
288
|
-
var isEvmAddress = (value) => /^0x[a-fA-F0-9]{40}$/.test(value);
|
|
289
|
-
var tokenRegistry = /* @__PURE__ */ new Map();
|
|
290
|
-
var tokenKey = (chainId, contractAddress) => `${chainId}:${contractAddress.toLowerCase()}`;
|
|
291
|
-
var NETWORKS = {
|
|
292
|
-
ethereum: {
|
|
293
|
-
name: "Ethereum Mainnet",
|
|
294
|
-
chainId: 1,
|
|
295
|
-
usdcAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
296
|
-
rpcUrl: "https://eth.llamarpc.com",
|
|
297
|
-
caip2Id: "eip155:1",
|
|
298
|
-
caipAssetId: "eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
|
299
|
-
},
|
|
300
|
-
base: {
|
|
301
|
-
name: "Base Mainnet",
|
|
302
|
-
chainId: 8453,
|
|
303
|
-
usdcAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
304
|
-
rpcUrl: "https://mainnet.base.org",
|
|
305
|
-
caip2Id: "eip155:8453",
|
|
306
|
-
caipAssetId: "eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
|
|
307
|
-
},
|
|
308
|
-
"base-sepolia": {
|
|
309
|
-
name: "Base Sepolia",
|
|
310
|
-
chainId: 84532,
|
|
311
|
-
usdcAddress: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
312
|
-
rpcUrl: "https://sepolia.base.org",
|
|
313
|
-
caip2Id: "eip155:84532",
|
|
314
|
-
caipAssetId: "eip155:84532/erc20:0x036CbD53842c5426634e7929541eC2318f3dCF7e"
|
|
315
|
-
},
|
|
316
|
-
"skale-base": {
|
|
317
|
-
name: "SKALE Base",
|
|
318
|
-
chainId: 1187947933,
|
|
319
|
-
usdcAddress: "0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20",
|
|
320
|
-
rpcUrl: "https://skale-base.skalenodes.com/v1/base",
|
|
321
|
-
caip2Id: "eip155:1187947933",
|
|
322
|
-
caipAssetId: "eip155:1187947933/erc20:0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20"
|
|
323
|
-
},
|
|
324
|
-
"skale-base-sepolia": {
|
|
325
|
-
name: "SKALE Base Sepolia",
|
|
326
|
-
chainId: 324705682,
|
|
327
|
-
usdcAddress: "0x2e08028E3C4c2356572E096d8EF835cD5C6030bD",
|
|
328
|
-
rpcUrl: "https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha",
|
|
329
|
-
caip2Id: "eip155:324705682",
|
|
330
|
-
caipAssetId: "eip155:324705682/erc20:0x2e08028E3C4c2356572E096d8EF835cD5C6030bD"
|
|
331
|
-
},
|
|
332
|
-
"ethereum-sepolia": {
|
|
333
|
-
name: "Ethereum Sepolia",
|
|
334
|
-
chainId: 11155111,
|
|
335
|
-
usdcAddress: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
|
|
336
|
-
rpcUrl: "https://rpc.sepolia.org",
|
|
337
|
-
caip2Id: "eip155:11155111",
|
|
338
|
-
caipAssetId: "eip155:11155111/erc20:0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"
|
|
339
|
-
}
|
|
340
|
-
};
|
|
341
|
-
var getNetworkConfig = (name) => NETWORKS[name];
|
|
342
|
-
var getNetworkByChainId = (chainId) => Object.values(NETWORKS).find((c) => c.chainId === chainId);
|
|
343
|
-
var getMainnets = () => Object.values(NETWORKS).filter((c) => !c.name.toLowerCase().includes("sepolia"));
|
|
344
|
-
var getTestnets = () => Object.values(NETWORKS).filter((c) => c.name.toLowerCase().includes("sepolia"));
|
|
345
|
-
var registerToken = (token) => {
|
|
346
|
-
if (typeof token !== "object" || token === null) {
|
|
347
|
-
throw new Error("Invalid token: must be an object");
|
|
348
|
-
}
|
|
349
|
-
const t = token;
|
|
350
|
-
if (typeof t.symbol !== "string") {
|
|
351
|
-
throw new Error("Invalid token: symbol must be a string");
|
|
352
|
-
}
|
|
353
|
-
if (typeof t.name !== "string") {
|
|
354
|
-
throw new Error("Invalid token: name must be a string");
|
|
355
|
-
}
|
|
356
|
-
if (typeof t.version !== "string") {
|
|
357
|
-
throw new Error("Invalid token: version must be a string");
|
|
358
|
-
}
|
|
359
|
-
if (typeof t.contractAddress !== "string" || !isEvmAddress(t.contractAddress)) {
|
|
360
|
-
throw new Error("Invalid token: contractAddress must be a valid EVM address");
|
|
361
|
-
}
|
|
362
|
-
if (typeof t.chainId !== "number" || !Number.isInteger(t.chainId) || t.chainId <= 0) {
|
|
363
|
-
throw new Error("Invalid token: chainId must be a positive integer");
|
|
364
|
-
}
|
|
365
|
-
if (t.decimals !== void 0 && (typeof t.decimals !== "number" || !Number.isInteger(t.decimals) || t.decimals < 0)) {
|
|
366
|
-
throw new Error("Invalid token: decimals must be a non-negative integer");
|
|
367
|
-
}
|
|
368
|
-
const validated = {
|
|
369
|
-
symbol: t.symbol,
|
|
370
|
-
name: t.name,
|
|
371
|
-
version: t.version,
|
|
372
|
-
contractAddress: t.contractAddress,
|
|
373
|
-
chainId: t.chainId,
|
|
374
|
-
decimals: t.decimals
|
|
375
|
-
};
|
|
376
|
-
tokenRegistry.set(tokenKey(validated.chainId, validated.contractAddress), validated);
|
|
377
|
-
return validated;
|
|
378
158
|
};
|
|
379
|
-
var getCustomToken = (chainId, contractAddress) => tokenRegistry.get(tokenKey(chainId, contractAddress));
|
|
380
|
-
var getAllCustomTokens = () => Array.from(tokenRegistry.values());
|
|
381
|
-
var unregisterToken = (chainId, contractAddress) => tokenRegistry.delete(tokenKey(chainId, contractAddress));
|
|
382
|
-
var isCustomToken = (chainId, contractAddress) => tokenRegistry.has(tokenKey(chainId, contractAddress));
|
|
383
159
|
|
|
384
160
|
// src/data/tokens.ts
|
|
385
161
|
var USDC_BASE = {
|
|
@@ -511,7 +287,9 @@ function getAllTokens() {
|
|
|
511
287
|
return Object.values(TOKENS);
|
|
512
288
|
}
|
|
513
289
|
function getTokensBySymbol(symbol) {
|
|
514
|
-
return getAllTokens().filter(
|
|
290
|
+
return getAllTokens().filter(
|
|
291
|
+
(t) => t.symbol.toUpperCase() === symbol.toUpperCase()
|
|
292
|
+
);
|
|
515
293
|
}
|
|
516
294
|
function getTokensByChain(chainId) {
|
|
517
295
|
return getAllTokens().filter((t) => t.chainId === chainId);
|
|
@@ -535,63 +313,6 @@ function getWETHTokens() {
|
|
|
535
313
|
return getTokensBySymbol("WETH");
|
|
536
314
|
}
|
|
537
315
|
|
|
538
|
-
// src/abi/erc20.ts
|
|
539
|
-
var ERC20_ABI = [
|
|
540
|
-
{
|
|
541
|
-
type: "function",
|
|
542
|
-
name: "transferWithAuthorization",
|
|
543
|
-
stateMutability: "nonpayable",
|
|
544
|
-
inputs: [
|
|
545
|
-
{ name: "from", type: "address" },
|
|
546
|
-
{ name: "to", type: "address" },
|
|
547
|
-
{ name: "amount", type: "uint256" },
|
|
548
|
-
{ name: "validAfter", type: "uint256" },
|
|
549
|
-
{ name: "expiry", type: "uint256" },
|
|
550
|
-
{ name: "v", type: "uint8" },
|
|
551
|
-
{ name: "r", type: "bytes32" },
|
|
552
|
-
{ name: "s", type: "bytes32" }
|
|
553
|
-
],
|
|
554
|
-
outputs: []
|
|
555
|
-
},
|
|
556
|
-
{
|
|
557
|
-
type: "function",
|
|
558
|
-
name: "receiveWithAuthorization",
|
|
559
|
-
stateMutability: "nonpayable",
|
|
560
|
-
inputs: [
|
|
561
|
-
{ name: "from", type: "address" },
|
|
562
|
-
{ name: "to", type: "address" },
|
|
563
|
-
{ name: "amount", type: "uint256" },
|
|
564
|
-
{ name: "validAfter", type: "uint256" },
|
|
565
|
-
{ name: "expiry", type: "uint256" },
|
|
566
|
-
{ name: "v", type: "uint8" },
|
|
567
|
-
{ name: "r", type: "bytes32" },
|
|
568
|
-
{ name: "s", type: "bytes32" }
|
|
569
|
-
],
|
|
570
|
-
outputs: []
|
|
571
|
-
},
|
|
572
|
-
{
|
|
573
|
-
type: "function",
|
|
574
|
-
name: "balanceOf",
|
|
575
|
-
stateMutability: "view",
|
|
576
|
-
inputs: [{ name: "account", type: "address" }],
|
|
577
|
-
outputs: [{ name: "balance", type: "uint256" }]
|
|
578
|
-
},
|
|
579
|
-
{
|
|
580
|
-
type: "function",
|
|
581
|
-
name: "name",
|
|
582
|
-
stateMutability: "view",
|
|
583
|
-
inputs: [],
|
|
584
|
-
outputs: [{ name: "", type: "string" }]
|
|
585
|
-
},
|
|
586
|
-
{
|
|
587
|
-
type: "function",
|
|
588
|
-
name: "symbol",
|
|
589
|
-
stateMutability: "view",
|
|
590
|
-
inputs: [],
|
|
591
|
-
outputs: [{ name: "", type: "string" }]
|
|
592
|
-
}
|
|
593
|
-
];
|
|
594
|
-
|
|
595
316
|
// src/eip712.ts
|
|
596
317
|
var EIP712_TYPES = {
|
|
597
318
|
TransferWithAuthorization: [
|
|
@@ -621,20 +342,566 @@ var createTransferWithAuthorization = (params) => ({
|
|
|
621
342
|
validBefore: BigInt(params.validBefore),
|
|
622
343
|
nonce: params.nonce
|
|
623
344
|
});
|
|
624
|
-
var
|
|
345
|
+
var isAddress = (value) => /^0x[a-fA-F0-9]{40}$/.test(value);
|
|
625
346
|
var isBytes32 = (value) => /^0x[a-fA-F0-9]{64}$/.test(value);
|
|
626
347
|
var validateTransferWithAuthorization = (message) => {
|
|
627
|
-
if (!
|
|
628
|
-
|
|
629
|
-
if (
|
|
630
|
-
|
|
631
|
-
if (message.
|
|
348
|
+
if (!isAddress(message.from))
|
|
349
|
+
throw new Error(`Invalid "from" address: ${message.from}`);
|
|
350
|
+
if (!isAddress(message.to))
|
|
351
|
+
throw new Error(`Invalid "to" address: ${message.to}`);
|
|
352
|
+
if (message.value < 0n)
|
|
353
|
+
throw new Error(`"value" must be non-negative: ${message.value}`);
|
|
354
|
+
if (message.validAfter < 0n)
|
|
355
|
+
throw new Error(`"validAfter" must be non-negative: ${message.validAfter}`);
|
|
356
|
+
if (message.validBefore < 0n)
|
|
357
|
+
throw new Error(
|
|
358
|
+
`"validBefore" must be non-negative: ${message.validBefore}`
|
|
359
|
+
);
|
|
632
360
|
if (message.validAfter >= message.validBefore) {
|
|
633
|
-
throw new Error(
|
|
361
|
+
throw new Error(
|
|
362
|
+
`"validAfter" (${message.validAfter}) must be before "validBefore" (${message.validBefore})`
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
if (!isBytes32(message.nonce))
|
|
366
|
+
throw new Error(
|
|
367
|
+
`"nonce" must be a valid bytes32 hex string: ${message.nonce}`
|
|
368
|
+
);
|
|
369
|
+
return true;
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
// src/types/v2.ts
|
|
373
|
+
var V2_HEADERS = {
|
|
374
|
+
PAYMENT_SIGNATURE: "PAYMENT-SIGNATURE",
|
|
375
|
+
PAYMENT_REQUIRED: "PAYMENT-REQUIRED",
|
|
376
|
+
PAYMENT_RESPONSE: "PAYMENT-RESPONSE"
|
|
377
|
+
};
|
|
378
|
+
function isCAIP2ChainId(value) {
|
|
379
|
+
return /^eip155:\d+$/.test(value);
|
|
380
|
+
}
|
|
381
|
+
function isCAIPAssetId(value) {
|
|
382
|
+
return /^eip155:\d+\/erc20:0x[a-fA-F0-9]+$/.test(value);
|
|
383
|
+
}
|
|
384
|
+
function isAddress2(value) {
|
|
385
|
+
return /^0x[a-fA-F0-9]{40}$/.test(value);
|
|
386
|
+
}
|
|
387
|
+
function isPaymentRequiredV2(obj) {
|
|
388
|
+
return typeof obj === "object" && obj !== null && "x402Version" in obj && obj.x402Version === 2 && "resource" in obj && "accepts" in obj && Array.isArray(obj.accepts);
|
|
389
|
+
}
|
|
390
|
+
function isPaymentPayloadV2(obj) {
|
|
391
|
+
return typeof obj === "object" && obj !== null && "x402Version" in obj && obj.x402Version === 2 && "accepted" in obj && "payload" in obj;
|
|
392
|
+
}
|
|
393
|
+
function assetIdToAddress(assetId) {
|
|
394
|
+
const match = assetId.match(/\/erc20:(0x[a-fA-F0-9]{40})$/);
|
|
395
|
+
if (!match) throw new Error(`Invalid CAIP asset ID: ${assetId}`);
|
|
396
|
+
return match[1];
|
|
397
|
+
}
|
|
398
|
+
function addressToAssetId(address, chainId) {
|
|
399
|
+
const chain = typeof chainId === "number" ? `eip155:${chainId}` : chainId;
|
|
400
|
+
if (!isCAIP2ChainId(chain)) throw new Error(`Invalid chain ID: ${chain}`);
|
|
401
|
+
return `${chain}/erc20:${address}`;
|
|
402
|
+
}
|
|
403
|
+
function parseSignature(signature) {
|
|
404
|
+
const sig = signature.slice(2);
|
|
405
|
+
return {
|
|
406
|
+
r: `0x${sig.slice(0, 64)}`,
|
|
407
|
+
s: `0x${sig.slice(64, 128)}`,
|
|
408
|
+
v: parseInt(sig.slice(128, 130), 16)
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
function combineSignature(v, r, s) {
|
|
412
|
+
const rClean = r.startsWith("0x") ? r.slice(2) : r;
|
|
413
|
+
const sClean = s.startsWith("0x") ? s.slice(2) : s;
|
|
414
|
+
const vHex = v.toString(16).padStart(2, "0");
|
|
415
|
+
return `0x${rClean}${sClean}${vHex}`;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// src/utils/base64.ts
|
|
419
|
+
var textEncoder = new TextEncoder();
|
|
420
|
+
var textDecoder = new TextDecoder();
|
|
421
|
+
function toBase64(bytes) {
|
|
422
|
+
if (typeof btoa === "function") {
|
|
423
|
+
let binary = "";
|
|
424
|
+
for (let index = 0; index < bytes.length; index += 1) {
|
|
425
|
+
binary += String.fromCharCode(bytes[index]);
|
|
426
|
+
}
|
|
427
|
+
return btoa(binary);
|
|
428
|
+
}
|
|
429
|
+
throw new Error("No base64 encoder available in this runtime");
|
|
430
|
+
}
|
|
431
|
+
function fromBase64(base64) {
|
|
432
|
+
if (typeof atob === "function") {
|
|
433
|
+
const binary = atob(base64);
|
|
434
|
+
const bytes = new Uint8Array(binary.length);
|
|
435
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
436
|
+
bytes[index] = binary.charCodeAt(index);
|
|
437
|
+
}
|
|
438
|
+
return bytes;
|
|
439
|
+
}
|
|
440
|
+
throw new Error("No base64 decoder available in this runtime");
|
|
441
|
+
}
|
|
442
|
+
function encodeUtf8ToBase64(value) {
|
|
443
|
+
const bytes = textEncoder.encode(value);
|
|
444
|
+
return toBase64(bytes);
|
|
445
|
+
}
|
|
446
|
+
function decodeBase64ToUtf8(value) {
|
|
447
|
+
const bytes = fromBase64(value);
|
|
448
|
+
return textDecoder.decode(bytes);
|
|
449
|
+
}
|
|
450
|
+
function toBase64Url(base64) {
|
|
451
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
452
|
+
}
|
|
453
|
+
function normalizeBase64Url(value) {
|
|
454
|
+
return value.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// src/encoding.ts
|
|
458
|
+
function safeBase64Decode(str) {
|
|
459
|
+
return decodeBase64ToUtf8(normalizeBase64Url(str));
|
|
460
|
+
}
|
|
461
|
+
function safeBase64Encode(str) {
|
|
462
|
+
return toBase64Url(encodeUtf8ToBase64(str));
|
|
463
|
+
}
|
|
464
|
+
var base64JsonEncode = (data) => safeBase64Encode(JSON.stringify(data));
|
|
465
|
+
var base64JsonDecode = (encoded) => JSON.parse(safeBase64Decode(encoded));
|
|
466
|
+
var encodePaymentV2 = (payload) => base64JsonEncode(payload);
|
|
467
|
+
var decodePaymentV2 = (encoded) => base64JsonDecode(encoded);
|
|
468
|
+
var encodeSettlementV2 = (response) => base64JsonEncode(response);
|
|
469
|
+
var decodeSettlementV2 = (encoded) => base64JsonDecode(encoded);
|
|
470
|
+
var isPaymentV2 = (payload) => "x402Version" in payload && payload.x402Version === 2 && "accepted" in payload && "payload" in payload;
|
|
471
|
+
var isSettlementV2 = (response) => "success" in response && typeof response.success === "boolean" && "network" in response;
|
|
472
|
+
|
|
473
|
+
// src/types/x402.ts
|
|
474
|
+
var X402_VERSION = 2;
|
|
475
|
+
var SCHEMES = ["exact"];
|
|
476
|
+
function isPaymentPayload(obj) {
|
|
477
|
+
return typeof obj === "object" && obj !== null && "x402Version" in obj && "accepted" in obj && "payload" in obj;
|
|
478
|
+
}
|
|
479
|
+
function isExactEvmPayload(obj) {
|
|
480
|
+
return typeof obj === "object" && obj !== null && "signature" in obj && "authorization" in obj;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// src/encoding/x402.ts
|
|
484
|
+
function safeBase64Encode2(str) {
|
|
485
|
+
return toBase64Url(encodeUtf8ToBase64(str));
|
|
486
|
+
}
|
|
487
|
+
function safeBase64Decode2(str) {
|
|
488
|
+
return decodeBase64ToUtf8(normalizeBase64Url(str));
|
|
489
|
+
}
|
|
490
|
+
function encodePayment(payload) {
|
|
491
|
+
if (!isPaymentPayload(payload)) {
|
|
492
|
+
throw new Error("Invalid payment payload format");
|
|
493
|
+
}
|
|
494
|
+
const safePayload = JSON.parse(
|
|
495
|
+
JSON.stringify(payload, (_, value) => {
|
|
496
|
+
if (typeof value === "bigint") {
|
|
497
|
+
return value.toString();
|
|
498
|
+
}
|
|
499
|
+
return value;
|
|
500
|
+
})
|
|
501
|
+
);
|
|
502
|
+
return safeBase64Encode2(JSON.stringify(safePayload));
|
|
503
|
+
}
|
|
504
|
+
function decodePayment(encoded) {
|
|
505
|
+
let parsed;
|
|
506
|
+
try {
|
|
507
|
+
parsed = JSON.parse(encoded);
|
|
508
|
+
} catch {
|
|
509
|
+
const decoded = safeBase64Decode2(encoded);
|
|
510
|
+
parsed = JSON.parse(decoded);
|
|
511
|
+
}
|
|
512
|
+
if (!isPaymentPayload(parsed)) {
|
|
513
|
+
throw new Error("Invalid payment payload format");
|
|
514
|
+
}
|
|
515
|
+
return parsed;
|
|
516
|
+
}
|
|
517
|
+
function encodeSettlementResponse(response) {
|
|
518
|
+
const safeResponse = JSON.parse(
|
|
519
|
+
JSON.stringify(response, (_, value) => {
|
|
520
|
+
if (typeof value === "bigint") {
|
|
521
|
+
return value.toString();
|
|
522
|
+
}
|
|
523
|
+
return value;
|
|
524
|
+
})
|
|
525
|
+
);
|
|
526
|
+
return safeBase64Encode2(JSON.stringify(safeResponse));
|
|
527
|
+
}
|
|
528
|
+
function decodeSettlementResponse(encoded) {
|
|
529
|
+
try {
|
|
530
|
+
return JSON.parse(encoded);
|
|
531
|
+
} catch {
|
|
532
|
+
const decoded = safeBase64Decode2(encoded);
|
|
533
|
+
return JSON.parse(decoded);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
function encodeX402Response(response) {
|
|
537
|
+
return safeBase64Encode2(JSON.stringify(response));
|
|
538
|
+
}
|
|
539
|
+
function decodeX402Response(encoded) {
|
|
540
|
+
try {
|
|
541
|
+
return JSON.parse(encoded);
|
|
542
|
+
} catch {
|
|
543
|
+
const decoded = safeBase64Decode2(encoded);
|
|
544
|
+
return JSON.parse(decoded);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
function detectPaymentVersion(headers) {
|
|
548
|
+
if (headers.has("PAYMENT-SIGNATURE")) {
|
|
549
|
+
return 2;
|
|
550
|
+
}
|
|
551
|
+
return null;
|
|
552
|
+
}
|
|
553
|
+
function extractPaymentFromHeaders(headers) {
|
|
554
|
+
const encoded = headers.get("PAYMENT-SIGNATURE");
|
|
555
|
+
if (!encoded) return null;
|
|
556
|
+
try {
|
|
557
|
+
return decodePayment(encoded);
|
|
558
|
+
} catch {
|
|
559
|
+
return null;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
function createPaymentRequiredHeaders(requirements, options) {
|
|
563
|
+
const accepts = Array.isArray(requirements) ? requirements : [requirements];
|
|
564
|
+
const response = {
|
|
565
|
+
x402Version: 2,
|
|
566
|
+
error: options?.error ?? "Payment required",
|
|
567
|
+
resource: options?.resource ?? { url: "", mimeType: "application/json" },
|
|
568
|
+
accepts,
|
|
569
|
+
extensions: options?.extensions ?? {}
|
|
570
|
+
};
|
|
571
|
+
return {
|
|
572
|
+
[V2_HEADERS.PAYMENT_REQUIRED]: safeBase64Encode2(JSON.stringify(response))
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
function createSettlementHeaders(settlement) {
|
|
576
|
+
return {
|
|
577
|
+
[V2_HEADERS.PAYMENT_RESPONSE]: encodeSettlementResponse(settlement)
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// src/errors.ts
|
|
582
|
+
var X402ClientError = class extends Error {
|
|
583
|
+
cause;
|
|
584
|
+
constructor(message, cause) {
|
|
585
|
+
super(message);
|
|
586
|
+
this.name = "X402ClientError";
|
|
587
|
+
this.cause = cause;
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
var SigningError = class extends X402ClientError {
|
|
591
|
+
constructor(message, cause) {
|
|
592
|
+
super(`Signing failed: ${message}`, cause);
|
|
593
|
+
this.name = "SigningError";
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
var PaymentException = class extends X402ClientError {
|
|
597
|
+
constructor(message, cause) {
|
|
598
|
+
super(`Payment failed: ${message}`, cause);
|
|
599
|
+
this.name = "PaymentException";
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
// src/payment-client.ts
|
|
604
|
+
var DEFAULT_FACILITATOR_URL = "https://facilitator.payai.network";
|
|
605
|
+
function toJsonSafe(data) {
|
|
606
|
+
function convert(value) {
|
|
607
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
608
|
+
return Object.fromEntries(
|
|
609
|
+
Object.entries(value).map(([key, val]) => [key, convert(val)])
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
if (Array.isArray(value)) {
|
|
613
|
+
return value.map(convert);
|
|
614
|
+
}
|
|
615
|
+
if (typeof value === "bigint") {
|
|
616
|
+
return value.toString();
|
|
617
|
+
}
|
|
618
|
+
return value;
|
|
619
|
+
}
|
|
620
|
+
return convert(data);
|
|
621
|
+
}
|
|
622
|
+
function resolveUrl(config) {
|
|
623
|
+
return config?.url ?? DEFAULT_FACILITATOR_URL;
|
|
624
|
+
}
|
|
625
|
+
async function resolveHeaders(config) {
|
|
626
|
+
const base = { "Content-Type": "application/json" };
|
|
627
|
+
if (!config?.createHeaders) return base;
|
|
628
|
+
const extra = await config.createHeaders();
|
|
629
|
+
return { ...base, ...extra };
|
|
630
|
+
}
|
|
631
|
+
async function verifyPayment(payload, requirements, config) {
|
|
632
|
+
const url = resolveUrl(config);
|
|
633
|
+
const headers = await resolveHeaders(config);
|
|
634
|
+
const response = await fetch(`${url}/verify`, {
|
|
635
|
+
method: "POST",
|
|
636
|
+
headers,
|
|
637
|
+
body: JSON.stringify({
|
|
638
|
+
paymentPayload: toJsonSafe(payload),
|
|
639
|
+
paymentRequirements: toJsonSafe(requirements)
|
|
640
|
+
})
|
|
641
|
+
});
|
|
642
|
+
if (response.status !== 200) {
|
|
643
|
+
const text = await response.text().catch(() => response.statusText);
|
|
644
|
+
throw new Error(`Facilitator verify failed: ${response.status} ${text}`);
|
|
645
|
+
}
|
|
646
|
+
return await response.json();
|
|
647
|
+
}
|
|
648
|
+
async function settlePayment(payload, requirements, config) {
|
|
649
|
+
const url = resolveUrl(config);
|
|
650
|
+
const headers = await resolveHeaders(config);
|
|
651
|
+
const response = await fetch(`${url}/settle`, {
|
|
652
|
+
method: "POST",
|
|
653
|
+
headers,
|
|
654
|
+
body: JSON.stringify({
|
|
655
|
+
paymentPayload: toJsonSafe(payload),
|
|
656
|
+
paymentRequirements: toJsonSafe(requirements)
|
|
657
|
+
})
|
|
658
|
+
});
|
|
659
|
+
if (response.status !== 200) {
|
|
660
|
+
const text = await response.text().catch(() => response.statusText);
|
|
661
|
+
throw new Error(`Facilitator settle failed: ${response.status} ${text}`);
|
|
662
|
+
}
|
|
663
|
+
return await response.json();
|
|
664
|
+
}
|
|
665
|
+
async function getSupported(config) {
|
|
666
|
+
const url = resolveUrl(config);
|
|
667
|
+
const headers = await resolveHeaders(config);
|
|
668
|
+
const response = await fetch(`${url}/supported`, {
|
|
669
|
+
method: "GET",
|
|
670
|
+
headers
|
|
671
|
+
});
|
|
672
|
+
if (response.status !== 200) {
|
|
673
|
+
throw new Error(`Facilitator supported failed: ${response.statusText}`);
|
|
674
|
+
}
|
|
675
|
+
return await response.json();
|
|
676
|
+
}
|
|
677
|
+
function isCompactV2Payload(payload) {
|
|
678
|
+
if (typeof payload !== "object" || payload === null) return false;
|
|
679
|
+
const record = payload;
|
|
680
|
+
return typeof record.x402Version === "number" && "payload" in record && !("accepted" in record);
|
|
681
|
+
}
|
|
682
|
+
function decodePayloadHeader(headerValue, defaults) {
|
|
683
|
+
if (headerValue.startsWith("{")) {
|
|
684
|
+
const parsed = JSON.parse(headerValue);
|
|
685
|
+
if (isPaymentPayload(parsed)) return parsed;
|
|
686
|
+
if (isCompactV2Payload(parsed)) {
|
|
687
|
+
const compact = parsed;
|
|
688
|
+
if (!defaults?.accepted) {
|
|
689
|
+
throw new Error(
|
|
690
|
+
"Invalid payment payload: missing 'accepted' field and no defaults provided"
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
return {
|
|
694
|
+
x402Version: 2,
|
|
695
|
+
accepted: defaults.accepted,
|
|
696
|
+
payload: compact.payload
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
throw new Error("Invalid payment payload: unrecognized format");
|
|
700
|
+
}
|
|
701
|
+
const normalized = headerValue.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(headerValue.length / 4) * 4, "=");
|
|
702
|
+
try {
|
|
703
|
+
const decoded = JSON.parse(decodeBase64ToUtf8(normalized));
|
|
704
|
+
if (isPaymentPayload(decoded)) return decoded;
|
|
705
|
+
if (isCompactV2Payload(decoded)) {
|
|
706
|
+
const compact = decoded;
|
|
707
|
+
if (!defaults?.accepted) {
|
|
708
|
+
throw new Error(
|
|
709
|
+
"Invalid payment payload: missing 'accepted' field and no defaults provided"
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
return {
|
|
713
|
+
x402Version: 2,
|
|
714
|
+
accepted: defaults.accepted,
|
|
715
|
+
payload: compact.payload
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
throw new Error("Invalid payment payload: unrecognized format");
|
|
719
|
+
} catch {
|
|
720
|
+
throw new Error("Invalid payment payload: failed to decode");
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
function extractPayerAddress(payload) {
|
|
724
|
+
if (isExactEvmPayload(payload.payload)) {
|
|
725
|
+
return payload.payload.authorization.from;
|
|
726
|
+
}
|
|
727
|
+
throw new Error("Unable to extract payer address from payload");
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// src/types/networks.ts
|
|
731
|
+
var isEvmAddress = (value) => /^0x[a-fA-F0-9]{40}$/.test(value);
|
|
732
|
+
var tokenRegistry = /* @__PURE__ */ new Map();
|
|
733
|
+
var tokenKey = (chainId, contractAddress) => `${chainId}:${contractAddress.toLowerCase()}`;
|
|
734
|
+
var NETWORKS = {
|
|
735
|
+
ethereum: {
|
|
736
|
+
name: "Ethereum Mainnet",
|
|
737
|
+
chainId: 1,
|
|
738
|
+
usdcAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
739
|
+
rpcUrl: "https://eth.llamarpc.com",
|
|
740
|
+
caip2Id: "eip155:1",
|
|
741
|
+
caipAssetId: "eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
|
742
|
+
},
|
|
743
|
+
base: {
|
|
744
|
+
name: "Base Mainnet",
|
|
745
|
+
chainId: 8453,
|
|
746
|
+
usdcAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
747
|
+
rpcUrl: "https://mainnet.base.org",
|
|
748
|
+
caip2Id: "eip155:8453",
|
|
749
|
+
caipAssetId: "eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
|
|
750
|
+
},
|
|
751
|
+
"base-sepolia": {
|
|
752
|
+
name: "Base Sepolia",
|
|
753
|
+
chainId: 84532,
|
|
754
|
+
usdcAddress: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
755
|
+
rpcUrl: "https://sepolia.base.org",
|
|
756
|
+
caip2Id: "eip155:84532",
|
|
757
|
+
caipAssetId: "eip155:84532/erc20:0x036CbD53842c5426634e7929541eC2318f3dCF7e"
|
|
758
|
+
},
|
|
759
|
+
"skale-base": {
|
|
760
|
+
name: "SKALE Base",
|
|
761
|
+
chainId: 1187947933,
|
|
762
|
+
usdcAddress: "0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20",
|
|
763
|
+
rpcUrl: "https://skale-base.skalenodes.com/v1/base",
|
|
764
|
+
caip2Id: "eip155:1187947933",
|
|
765
|
+
caipAssetId: "eip155:1187947933/erc20:0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20"
|
|
766
|
+
},
|
|
767
|
+
"skale-base-sepolia": {
|
|
768
|
+
name: "SKALE Base Sepolia",
|
|
769
|
+
chainId: 324705682,
|
|
770
|
+
usdcAddress: "0x2e08028E3C4c2356572E096d8EF835cD5C6030bD",
|
|
771
|
+
rpcUrl: "https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha",
|
|
772
|
+
caip2Id: "eip155:324705682",
|
|
773
|
+
caipAssetId: "eip155:324705682/erc20:0x2e08028E3C4c2356572E096d8EF835cD5C6030bD"
|
|
774
|
+
},
|
|
775
|
+
"ethereum-sepolia": {
|
|
776
|
+
name: "Ethereum Sepolia",
|
|
777
|
+
chainId: 11155111,
|
|
778
|
+
usdcAddress: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
|
|
779
|
+
rpcUrl: "https://rpc.sepolia.org",
|
|
780
|
+
caip2Id: "eip155:11155111",
|
|
781
|
+
caipAssetId: "eip155:11155111/erc20:0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
var getNetworkConfig = (name) => NETWORKS[name];
|
|
785
|
+
var getNetworkByChainId = (chainId) => Object.values(NETWORKS).find((c) => c.chainId === chainId);
|
|
786
|
+
var getMainnets = () => Object.values(NETWORKS).filter(
|
|
787
|
+
(c) => !c.name.toLowerCase().includes("sepolia")
|
|
788
|
+
);
|
|
789
|
+
var getTestnets = () => Object.values(NETWORKS).filter(
|
|
790
|
+
(c) => c.name.toLowerCase().includes("sepolia")
|
|
791
|
+
);
|
|
792
|
+
var registerToken = (token) => {
|
|
793
|
+
if (typeof token !== "object" || token === null) {
|
|
794
|
+
throw new Error("Invalid token: must be an object");
|
|
795
|
+
}
|
|
796
|
+
const t = token;
|
|
797
|
+
if (typeof t.symbol !== "string") {
|
|
798
|
+
throw new Error("Invalid token: symbol must be a string");
|
|
799
|
+
}
|
|
800
|
+
if (typeof t.name !== "string") {
|
|
801
|
+
throw new Error("Invalid token: name must be a string");
|
|
802
|
+
}
|
|
803
|
+
if (typeof t.version !== "string") {
|
|
804
|
+
throw new Error("Invalid token: version must be a string");
|
|
805
|
+
}
|
|
806
|
+
if (typeof t.contractAddress !== "string" || !isEvmAddress(t.contractAddress)) {
|
|
807
|
+
throw new Error(
|
|
808
|
+
"Invalid token: contractAddress must be a valid EVM address"
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
if (typeof t.chainId !== "number" || !Number.isInteger(t.chainId) || t.chainId <= 0) {
|
|
812
|
+
throw new Error("Invalid token: chainId must be a positive integer");
|
|
813
|
+
}
|
|
814
|
+
if (t.decimals !== void 0 && (typeof t.decimals !== "number" || !Number.isInteger(t.decimals) || t.decimals < 0)) {
|
|
815
|
+
throw new Error("Invalid token: decimals must be a non-negative integer");
|
|
816
|
+
}
|
|
817
|
+
const validated = {
|
|
818
|
+
symbol: t.symbol,
|
|
819
|
+
name: t.name,
|
|
820
|
+
version: t.version,
|
|
821
|
+
contractAddress: t.contractAddress,
|
|
822
|
+
chainId: t.chainId,
|
|
823
|
+
decimals: t.decimals
|
|
824
|
+
};
|
|
825
|
+
tokenRegistry.set(
|
|
826
|
+
tokenKey(validated.chainId, validated.contractAddress),
|
|
827
|
+
validated
|
|
828
|
+
);
|
|
829
|
+
return validated;
|
|
830
|
+
};
|
|
831
|
+
var getCustomToken = (chainId, contractAddress) => tokenRegistry.get(tokenKey(chainId, contractAddress));
|
|
832
|
+
var getAllCustomTokens = () => Array.from(tokenRegistry.values());
|
|
833
|
+
var unregisterToken = (chainId, contractAddress) => tokenRegistry.delete(tokenKey(chainId, contractAddress));
|
|
834
|
+
var isCustomToken = (chainId, contractAddress) => tokenRegistry.has(tokenKey(chainId, contractAddress));
|
|
835
|
+
function createNonce() {
|
|
836
|
+
const bytes = randomBytes(32);
|
|
837
|
+
return `0x${bytes.toString("hex")}`;
|
|
838
|
+
}
|
|
839
|
+
function toAtomicUnits(amount, decimals = 6) {
|
|
840
|
+
const parts = amount.split(".");
|
|
841
|
+
const whole = parts[0];
|
|
842
|
+
const fractional = parts[1] || "";
|
|
843
|
+
const paddedFractional = fractional.padEnd(decimals, "0").slice(0, decimals);
|
|
844
|
+
return `${whole}${paddedFractional}`;
|
|
845
|
+
}
|
|
846
|
+
function fromAtomicUnits(amount, decimals = 6) {
|
|
847
|
+
const value = BigInt(amount);
|
|
848
|
+
const divisor = BigInt(10 ** decimals);
|
|
849
|
+
const whole = (value / divisor).toString();
|
|
850
|
+
const fractional = (value % divisor).toString().padStart(decimals, "0").replace(/0+$/, "");
|
|
851
|
+
if (fractional.length === 0) {
|
|
852
|
+
return whole;
|
|
853
|
+
}
|
|
854
|
+
return `${whole}.${fractional}`;
|
|
855
|
+
}
|
|
856
|
+
function caip2ToNetwork(caip2Id) {
|
|
857
|
+
const match = caip2Id.match(/^eip155:(\d+)$/);
|
|
858
|
+
if (!match) {
|
|
859
|
+
return caip2Id;
|
|
860
|
+
}
|
|
861
|
+
const chainId = parseInt(match[1], 10);
|
|
862
|
+
const chainIdToNetwork = {
|
|
863
|
+
1: "ethereum",
|
|
864
|
+
8453: "base",
|
|
865
|
+
84532: "base-sepolia",
|
|
866
|
+
137: "polygon",
|
|
867
|
+
42161: "arbitrum",
|
|
868
|
+
421614: "arbitrum-sepolia",
|
|
869
|
+
10: "optimism",
|
|
870
|
+
11155420: "optimism-sepolia",
|
|
871
|
+
11155111: "ethereum-sepolia"
|
|
872
|
+
};
|
|
873
|
+
return chainIdToNetwork[chainId] || caip2Id;
|
|
874
|
+
}
|
|
875
|
+
function networkToCaip2(network) {
|
|
876
|
+
const networkToChainId = {
|
|
877
|
+
ethereum: 1,
|
|
878
|
+
base: 8453,
|
|
879
|
+
"base-sepolia": 84532,
|
|
880
|
+
polygon: 137,
|
|
881
|
+
arbitrum: 42161,
|
|
882
|
+
"arbitrum-sepolia": 421614,
|
|
883
|
+
optimism: 10,
|
|
884
|
+
"optimism-sepolia": 11155420,
|
|
885
|
+
"ethereum-sepolia": 11155111
|
|
886
|
+
};
|
|
887
|
+
const chainId = networkToChainId[network];
|
|
888
|
+
if (chainId) {
|
|
889
|
+
return `eip155:${chainId}`;
|
|
634
890
|
}
|
|
635
|
-
if (
|
|
636
|
-
|
|
637
|
-
}
|
|
891
|
+
if (network.startsWith("eip155:")) {
|
|
892
|
+
return network;
|
|
893
|
+
}
|
|
894
|
+
return `eip155:0`;
|
|
895
|
+
}
|
|
896
|
+
function getCurrentTimestamp() {
|
|
897
|
+
return Math.floor(Date.now() / 1e3);
|
|
898
|
+
}
|
|
899
|
+
function isValidAddress(address) {
|
|
900
|
+
return /^0x[a-fA-F0-9]{40}$/.test(address);
|
|
901
|
+
}
|
|
902
|
+
function normalizeAddress(address) {
|
|
903
|
+
return address.toLowerCase();
|
|
904
|
+
}
|
|
638
905
|
|
|
639
906
|
// src/validation.ts
|
|
640
907
|
var isEvmAddress2 = (value) => /^0x[a-fA-F0-9]{40}$/.test(value);
|
|
@@ -648,7 +915,9 @@ var resolveNetwork = (input) => {
|
|
|
648
915
|
if (typeof input === "object" && input !== null && "chainId" in input) {
|
|
649
916
|
const config = input;
|
|
650
917
|
if (!config.chainId || !config.usdcAddress || !config.caip2Id) {
|
|
651
|
-
return createError("INVALID_CAIP_FORMAT", "Invalid network config", {
|
|
918
|
+
return createError("INVALID_CAIP_FORMAT", "Invalid network config", {
|
|
919
|
+
value: config
|
|
920
|
+
});
|
|
652
921
|
}
|
|
653
922
|
return {
|
|
654
923
|
input,
|
|
@@ -660,11 +929,10 @@ var resolveNetwork = (input) => {
|
|
|
660
929
|
const config = getNetworkByChainId(input);
|
|
661
930
|
if (!config) {
|
|
662
931
|
const validChainIds = Object.values(NETWORKS).map((n) => n.chainId);
|
|
663
|
-
return createError(
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
);
|
|
932
|
+
return createError("UNKNOWN_NETWORK", `Unknown chain ID: ${input}`, {
|
|
933
|
+
value: input,
|
|
934
|
+
validOptions: validChainIds.map(String)
|
|
935
|
+
});
|
|
668
936
|
}
|
|
669
937
|
return {
|
|
670
938
|
input,
|
|
@@ -675,13 +943,21 @@ var resolveNetwork = (input) => {
|
|
|
675
943
|
if (typeof input === "string") {
|
|
676
944
|
if (input.startsWith("eip155:")) {
|
|
677
945
|
const parts = input.split(":");
|
|
678
|
-
if (parts.length !== 2 || isNaN(Number(parts[1]))) {
|
|
679
|
-
return createError(
|
|
946
|
+
if (parts.length !== 2 || Number.isNaN(Number(parts[1]))) {
|
|
947
|
+
return createError(
|
|
948
|
+
"INVALID_CAIP_FORMAT",
|
|
949
|
+
`Invalid CAIP-2 format: ${input}`,
|
|
950
|
+
{ value: input }
|
|
951
|
+
);
|
|
680
952
|
}
|
|
681
953
|
const chainId = Number(parts[1]);
|
|
682
954
|
const config2 = getNetworkByChainId(chainId);
|
|
683
955
|
if (!config2) {
|
|
684
|
-
return createError(
|
|
956
|
+
return createError(
|
|
957
|
+
"UNKNOWN_NETWORK",
|
|
958
|
+
`Unknown CAIP-2 network: ${input}`,
|
|
959
|
+
{ value: input }
|
|
960
|
+
);
|
|
685
961
|
}
|
|
686
962
|
return {
|
|
687
963
|
input,
|
|
@@ -693,11 +969,10 @@ var resolveNetwork = (input) => {
|
|
|
693
969
|
const config = getNetworkConfig(normalizedName);
|
|
694
970
|
if (!config) {
|
|
695
971
|
const validNames = Object.keys(NETWORKS);
|
|
696
|
-
return createError(
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
);
|
|
972
|
+
return createError("UNKNOWN_NETWORK", `Unknown network: "${input}"`, {
|
|
973
|
+
value: input,
|
|
974
|
+
validOptions: validNames
|
|
975
|
+
});
|
|
701
976
|
}
|
|
702
977
|
return {
|
|
703
978
|
input,
|
|
@@ -705,7 +980,11 @@ var resolveNetwork = (input) => {
|
|
|
705
980
|
caip2: config.caip2Id
|
|
706
981
|
};
|
|
707
982
|
}
|
|
708
|
-
return createError(
|
|
983
|
+
return createError(
|
|
984
|
+
"UNKNOWN_NETWORK",
|
|
985
|
+
`Invalid network identifier type: ${typeof input}`,
|
|
986
|
+
{ value: input }
|
|
987
|
+
);
|
|
709
988
|
};
|
|
710
989
|
var getAvailableNetworks = () => Object.keys(NETWORKS);
|
|
711
990
|
var normalizeTokenSymbol = (symbol) => symbol.toUpperCase();
|
|
@@ -713,7 +992,9 @@ var resolveToken = (input, network) => {
|
|
|
713
992
|
if (typeof input === "object" && input !== null && "contractAddress" in input) {
|
|
714
993
|
const config = input;
|
|
715
994
|
if (!config.chainId || !config.contractAddress || !config.symbol) {
|
|
716
|
-
return createError("VALIDATION_FAILED", "Invalid token config", {
|
|
995
|
+
return createError("VALIDATION_FAILED", "Invalid token config", {
|
|
996
|
+
value: config
|
|
997
|
+
});
|
|
717
998
|
}
|
|
718
999
|
if (network && config.chainId !== network.config.chainId) {
|
|
719
1000
|
return createError(
|
|
@@ -738,7 +1019,10 @@ var resolveToken = (input, network) => {
|
|
|
738
1019
|
if (isEvmAddress2(input)) {
|
|
739
1020
|
const contractAddress = input;
|
|
740
1021
|
if (network) {
|
|
741
|
-
const customToken = getCustomToken(
|
|
1022
|
+
const customToken = getCustomToken(
|
|
1023
|
+
network.config.chainId,
|
|
1024
|
+
contractAddress
|
|
1025
|
+
);
|
|
742
1026
|
const config = customToken || {
|
|
743
1027
|
symbol: "CUSTOM",
|
|
744
1028
|
name: "Custom Token",
|
|
@@ -765,590 +1049,324 @@ var resolveToken = (input, network) => {
|
|
|
765
1049
|
return tokenNetwork;
|
|
766
1050
|
}
|
|
767
1051
|
const caipAsset = `eip155:${matchingToken2.chainId}/erc20:${contractAddress}`;
|
|
768
|
-
return {
|
|
769
|
-
input,
|
|
770
|
-
config: matchingToken2,
|
|
771
|
-
caipAsset,
|
|
772
|
-
network: tokenNetwork
|
|
773
|
-
};
|
|
774
|
-
}
|
|
775
|
-
return createError(
|
|
776
|
-
"UNKNOWN_TOKEN",
|
|
777
|
-
`Token address "${contractAddress}" not found in registry. Please specify a network.`,
|
|
778
|
-
{ value: input, validOptions: ["Specify network parameter"] }
|
|
779
|
-
);
|
|
780
|
-
}
|
|
781
|
-
if (input.includes("/erc20:")) {
|
|
782
|
-
const parts = input.split("/");
|
|
783
|
-
if (parts.length !== 2) {
|
|
784
|
-
return createError("INVALID_CAIP_FORMAT", `Invalid CAIP Asset ID format: ${input}`, { value: input });
|
|
785
|
-
}
|
|
786
|
-
const chainId = Number(parts[0].split(":")[1]);
|
|
787
|
-
const contractAddress = parts[1].split(":")[1];
|
|
788
|
-
const tokenNetwork = resolveNetwork(chainId);
|
|
789
|
-
if ("code" in tokenNetwork) {
|
|
790
|
-
return tokenNetwork;
|
|
791
|
-
}
|
|
792
|
-
const customToken = getCustomToken(chainId, contractAddress);
|
|
793
|
-
const config = customToken || {
|
|
794
|
-
symbol: "CUSTOM",
|
|
795
|
-
name: "Custom Token",
|
|
796
|
-
version: "1",
|
|
797
|
-
chainId,
|
|
798
|
-
contractAddress,
|
|
799
|
-
decimals: 18
|
|
800
|
-
};
|
|
801
|
-
return {
|
|
802
|
-
input,
|
|
803
|
-
config,
|
|
804
|
-
caipAsset: input,
|
|
805
|
-
network: tokenNetwork
|
|
806
|
-
};
|
|
807
|
-
}
|
|
808
|
-
const normalizedSymbol = normalizeTokenSymbol(input);
|
|
809
|
-
if (network) {
|
|
810
|
-
const customTokens2 = getAllCustomTokens();
|
|
811
|
-
const matchingToken2 = customTokens2.find(
|
|
812
|
-
(t) => t.symbol?.toUpperCase() === normalizedSymbol && t.chainId === network.config.chainId
|
|
813
|
-
);
|
|
814
|
-
if (matchingToken2) {
|
|
815
|
-
return {
|
|
816
|
-
input,
|
|
817
|
-
config: matchingToken2,
|
|
818
|
-
caipAsset: `eip155:${matchingToken2.chainId}/erc20:${matchingToken2.contractAddress}`,
|
|
819
|
-
network
|
|
820
|
-
};
|
|
821
|
-
}
|
|
822
|
-
return {
|
|
823
|
-
input,
|
|
824
|
-
config: {
|
|
825
|
-
symbol: normalizedSymbol,
|
|
826
|
-
name: network.config.name + " " + normalizedSymbol,
|
|
827
|
-
version: "2",
|
|
828
|
-
chainId: network.config.chainId,
|
|
829
|
-
contractAddress: network.config.usdcAddress,
|
|
830
|
-
decimals: 6
|
|
831
|
-
},
|
|
832
|
-
caipAsset: network.config.caipAssetId,
|
|
833
|
-
network
|
|
834
|
-
};
|
|
835
|
-
}
|
|
836
|
-
const customTokens = getAllCustomTokens();
|
|
837
|
-
const matchingToken = customTokens.find((t) => t.symbol?.toUpperCase() === normalizedSymbol);
|
|
838
|
-
if (matchingToken) {
|
|
839
|
-
const tokenNetwork = resolveNetwork(matchingToken.chainId);
|
|
840
|
-
if ("code" in tokenNetwork) {
|
|
841
|
-
return tokenNetwork;
|
|
842
|
-
}
|
|
843
|
-
return {
|
|
844
|
-
input,
|
|
845
|
-
config: matchingToken,
|
|
846
|
-
caipAsset: `eip155:${matchingToken.chainId}/erc20:${matchingToken.contractAddress}`,
|
|
847
|
-
network: tokenNetwork
|
|
848
|
-
};
|
|
849
|
-
}
|
|
850
|
-
const baseNetwork = resolveNetwork("base");
|
|
851
|
-
if ("code" in baseNetwork) {
|
|
852
|
-
return baseNetwork;
|
|
853
|
-
}
|
|
854
|
-
return {
|
|
855
|
-
input,
|
|
856
|
-
config: {
|
|
857
|
-
symbol: normalizedSymbol,
|
|
858
|
-
name: "USD Coin",
|
|
859
|
-
version: "2",
|
|
860
|
-
chainId: 8453,
|
|
861
|
-
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
862
|
-
decimals: 6
|
|
863
|
-
},
|
|
864
|
-
caipAsset: baseNetwork.config.caipAssetId,
|
|
865
|
-
network: baseNetwork
|
|
866
|
-
};
|
|
867
|
-
}
|
|
868
|
-
return createError("UNKNOWN_TOKEN", `Invalid token identifier type: ${typeof input}`, { value: input });
|
|
869
|
-
};
|
|
870
|
-
var getAvailableTokens = () => {
|
|
871
|
-
const customTokens = getAllCustomTokens();
|
|
872
|
-
const symbols = new Set(customTokens.map((t) => t.symbol.toUpperCase()));
|
|
873
|
-
return Array.from(symbols);
|
|
874
|
-
};
|
|
875
|
-
var resolveFacilitator = (input, supportedNetworks, supportedTokens) => {
|
|
876
|
-
try {
|
|
877
|
-
new URL(input.url);
|
|
878
|
-
} catch {
|
|
879
|
-
return createError("VALIDATION_FAILED", `Invalid facilitator URL: ${input.url}`, { value: input.url });
|
|
880
|
-
}
|
|
881
|
-
const networks = [];
|
|
882
|
-
if (input.networks) {
|
|
883
|
-
for (const network of input.networks) {
|
|
884
|
-
const resolved = resolveNetwork(network);
|
|
885
|
-
if ("code" in resolved) {
|
|
886
|
-
return resolved;
|
|
887
|
-
}
|
|
888
|
-
networks.push(resolved);
|
|
889
|
-
}
|
|
890
|
-
} else if (supportedNetworks) {
|
|
891
|
-
networks.push(...supportedNetworks);
|
|
892
|
-
}
|
|
893
|
-
const tokens = [];
|
|
894
|
-
if (input.tokens) {
|
|
895
|
-
for (const token of input.tokens) {
|
|
896
|
-
for (const network of networks.length > 0 ? networks : supportedNetworks || []) {
|
|
897
|
-
const resolved = resolveToken(token, network);
|
|
898
|
-
if ("code" in resolved) {
|
|
899
|
-
continue;
|
|
900
|
-
}
|
|
901
|
-
tokens.push(resolved);
|
|
902
|
-
break;
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
} else if (supportedTokens) {
|
|
906
|
-
tokens.push(...supportedTokens);
|
|
907
|
-
}
|
|
908
|
-
return {
|
|
909
|
-
input,
|
|
910
|
-
url: input.url,
|
|
911
|
-
networks,
|
|
912
|
-
tokens
|
|
913
|
-
};
|
|
914
|
-
};
|
|
915
|
-
var checkFacilitatorSupport = (facilitator, network, token) => {
|
|
916
|
-
if (facilitator.networks.length > 0) {
|
|
917
|
-
const networkSupported = facilitator.networks.some(
|
|
918
|
-
(n) => n.config.chainId === network.config.chainId
|
|
919
|
-
);
|
|
920
|
-
if (!networkSupported) {
|
|
921
|
-
const supportedNames = facilitator.networks.map((n) => n.config.name);
|
|
922
|
-
return createError(
|
|
923
|
-
"FACILITATOR_NO_NETWORK_SUPPORT",
|
|
924
|
-
`Facilitator "${facilitator.url}" does not support network "${network.config.name}" (chain ${network.config.chainId}). Supported: ${supportedNames.join(", ")}`,
|
|
925
|
-
{
|
|
926
|
-
value: { facilitator: facilitator.url, network: network.config.name },
|
|
927
|
-
validOptions: supportedNames
|
|
928
|
-
}
|
|
929
|
-
);
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
if (facilitator.tokens.length > 0) {
|
|
933
|
-
const tokenSupported = facilitator.tokens.some(
|
|
934
|
-
(t) => t.config.contractAddress.toLowerCase() === token.config.contractAddress.toLowerCase() && t.network.config.chainId === token.network.config.chainId
|
|
935
|
-
);
|
|
936
|
-
if (!tokenSupported) {
|
|
937
|
-
const supportedTokens = facilitator.tokens.map((t) => `${t.config.symbol} (${t.network.config.name})`);
|
|
938
|
-
return createError(
|
|
939
|
-
"FACILITATOR_NO_TOKEN_SUPPORT",
|
|
940
|
-
`Facilitator "${facilitator.url}" does not support token "${token.config.symbol}" on "${token.network.config.name}". Supported: ${supportedTokens.join(", ")}`,
|
|
941
|
-
{
|
|
942
|
-
value: { facilitator: facilitator.url, token: token.config.symbol, network: token.network.config.name },
|
|
943
|
-
validOptions: supportedTokens
|
|
944
|
-
}
|
|
945
|
-
);
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
return { supported: true };
|
|
949
|
-
};
|
|
950
|
-
var validatePaymentConfig = (network, token, facilitators, payTo = "0x0000000000000000000000000000000000000000", amount = "1.0") => {
|
|
951
|
-
const resolvedNetwork = resolveNetwork(network);
|
|
952
|
-
if ("code" in resolvedNetwork) {
|
|
953
|
-
return resolvedNetwork;
|
|
954
|
-
}
|
|
955
|
-
const resolvedToken = resolveToken(token, resolvedNetwork);
|
|
956
|
-
if ("code" in resolvedToken) {
|
|
957
|
-
return resolvedToken;
|
|
958
|
-
}
|
|
959
|
-
const resolvedFacilitators = [];
|
|
960
|
-
if (facilitators) {
|
|
961
|
-
const facilitatorArray = Array.isArray(facilitators) ? facilitators : [facilitators];
|
|
962
|
-
for (const facilitator of facilitatorArray) {
|
|
963
|
-
const resolved = resolveFacilitator(facilitator, [resolvedNetwork], [resolvedToken]);
|
|
964
|
-
if ("code" in resolved) {
|
|
965
|
-
return resolved;
|
|
966
|
-
}
|
|
967
|
-
const supportCheck = checkFacilitatorSupport(resolved, resolvedNetwork, resolvedToken);
|
|
968
|
-
if ("code" in supportCheck) {
|
|
969
|
-
return supportCheck;
|
|
970
|
-
}
|
|
971
|
-
resolvedFacilitators.push(resolved);
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
return {
|
|
975
|
-
network: resolvedNetwork,
|
|
976
|
-
token: resolvedToken,
|
|
977
|
-
facilitators: resolvedFacilitators,
|
|
978
|
-
version: 2,
|
|
979
|
-
payTo,
|
|
980
|
-
amount
|
|
981
|
-
};
|
|
982
|
-
};
|
|
983
|
-
var validateAcceptConfig = (options, payTo, amount) => {
|
|
984
|
-
const { networks: networkInputs, tokens: tokenInputs, facilitators, version = 2 } = options;
|
|
985
|
-
const networkIds = networkInputs?.length ? networkInputs : Object.keys(NETWORKS);
|
|
986
|
-
const tokenIds = tokenInputs?.length ? tokenInputs : ["usdc"];
|
|
987
|
-
const networks = [];
|
|
988
|
-
for (const networkId of networkIds) {
|
|
989
|
-
const resolved = resolveNetwork(networkId);
|
|
990
|
-
if ("code" in resolved) {
|
|
991
|
-
return { success: false, error: resolved };
|
|
992
|
-
}
|
|
993
|
-
networks.push(resolved);
|
|
994
|
-
}
|
|
995
|
-
const tokens = [];
|
|
996
|
-
for (const tokenId of tokenIds) {
|
|
997
|
-
let found = false;
|
|
998
|
-
for (const network of networks) {
|
|
999
|
-
const resolved = resolveToken(tokenId, network);
|
|
1000
|
-
if ("code" in resolved) {
|
|
1001
|
-
continue;
|
|
1052
|
+
return {
|
|
1053
|
+
input,
|
|
1054
|
+
config: matchingToken2,
|
|
1055
|
+
caipAsset,
|
|
1056
|
+
network: tokenNetwork
|
|
1057
|
+
};
|
|
1002
1058
|
}
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1059
|
+
return createError(
|
|
1060
|
+
"UNKNOWN_TOKEN",
|
|
1061
|
+
`Token address "${contractAddress}" not found in registry. Please specify a network.`,
|
|
1062
|
+
{ value: input, validOptions: ["Specify network parameter"] }
|
|
1063
|
+
);
|
|
1006
1064
|
}
|
|
1007
|
-
if (
|
|
1065
|
+
if (input.includes("/erc20:")) {
|
|
1066
|
+
const parts = input.split("/");
|
|
1067
|
+
if (parts.length !== 2) {
|
|
1068
|
+
return createError(
|
|
1069
|
+
"INVALID_CAIP_FORMAT",
|
|
1070
|
+
`Invalid CAIP Asset ID format: ${input}`,
|
|
1071
|
+
{ value: input }
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
const chainId = Number(parts[0].split(":")[1]);
|
|
1075
|
+
const contractAddress = parts[1].split(":")[1];
|
|
1076
|
+
const tokenNetwork = resolveNetwork(chainId);
|
|
1077
|
+
if ("code" in tokenNetwork) {
|
|
1078
|
+
return tokenNetwork;
|
|
1079
|
+
}
|
|
1080
|
+
const customToken = getCustomToken(chainId, contractAddress);
|
|
1081
|
+
const config = customToken || {
|
|
1082
|
+
symbol: "CUSTOM",
|
|
1083
|
+
name: "Custom Token",
|
|
1084
|
+
version: "1",
|
|
1085
|
+
chainId,
|
|
1086
|
+
contractAddress,
|
|
1087
|
+
decimals: 18
|
|
1088
|
+
};
|
|
1008
1089
|
return {
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
{ value: tokenId, validOptions: networks.map((n) => n.config.name) }
|
|
1014
|
-
)
|
|
1090
|
+
input,
|
|
1091
|
+
config,
|
|
1092
|
+
caipAsset: input,
|
|
1093
|
+
network: tokenNetwork
|
|
1015
1094
|
};
|
|
1016
1095
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
if (token.network.config.chainId === network.config.chainId) {
|
|
1031
|
-
configs.push({
|
|
1032
|
-
network,
|
|
1033
|
-
token,
|
|
1034
|
-
facilitators: resolvedFacilitators,
|
|
1035
|
-
version,
|
|
1036
|
-
payTo,
|
|
1037
|
-
amount
|
|
1038
|
-
});
|
|
1096
|
+
const normalizedSymbol = normalizeTokenSymbol(input);
|
|
1097
|
+
if (network) {
|
|
1098
|
+
const customTokens2 = getAllCustomTokens();
|
|
1099
|
+
const matchingToken2 = customTokens2.find(
|
|
1100
|
+
(t) => t.symbol?.toUpperCase() === normalizedSymbol && t.chainId === network.config.chainId
|
|
1101
|
+
);
|
|
1102
|
+
if (matchingToken2) {
|
|
1103
|
+
return {
|
|
1104
|
+
input,
|
|
1105
|
+
config: matchingToken2,
|
|
1106
|
+
caipAsset: `eip155:${matchingToken2.chainId}/erc20:${matchingToken2.contractAddress}`,
|
|
1107
|
+
network
|
|
1108
|
+
};
|
|
1039
1109
|
}
|
|
1110
|
+
return {
|
|
1111
|
+
input,
|
|
1112
|
+
config: {
|
|
1113
|
+
symbol: normalizedSymbol,
|
|
1114
|
+
name: `${network.config.name} ${normalizedSymbol}`,
|
|
1115
|
+
version: "2",
|
|
1116
|
+
chainId: network.config.chainId,
|
|
1117
|
+
contractAddress: network.config.usdcAddress,
|
|
1118
|
+
decimals: 6
|
|
1119
|
+
},
|
|
1120
|
+
caipAsset: network.config.caipAssetId,
|
|
1121
|
+
network
|
|
1122
|
+
};
|
|
1040
1123
|
}
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
};
|
|
1050
|
-
var isResolvedToken = (value) => {
|
|
1051
|
-
return typeof value === "object" && value !== null && "config" in value && "caipAsset" in value;
|
|
1052
|
-
};
|
|
1053
|
-
|
|
1054
|
-
// src/utils/routes.ts
|
|
1055
|
-
var PRIORITY_EXACT = 3;
|
|
1056
|
-
var PRIORITY_PARAMETRIZED = 2;
|
|
1057
|
-
var PRIORITY_WILDCARD = 1;
|
|
1058
|
-
function parseRoutePattern(pattern) {
|
|
1059
|
-
const normalizedPattern = pattern.startsWith("/") ? pattern : `/${pattern}`;
|
|
1060
|
-
const segments = normalizedPattern.split("/").filter(Boolean);
|
|
1061
|
-
let isWildcard = false;
|
|
1062
|
-
let isParametrized = false;
|
|
1063
|
-
const paramNames = [];
|
|
1064
|
-
const seenParamNames = /* @__PURE__ */ new Set();
|
|
1065
|
-
const recordParamName = (name) => {
|
|
1066
|
-
if (!name) {
|
|
1067
|
-
return;
|
|
1068
|
-
}
|
|
1069
|
-
if (seenParamNames.has(name)) {
|
|
1070
|
-
return;
|
|
1071
|
-
}
|
|
1072
|
-
seenParamNames.add(name);
|
|
1073
|
-
paramNames.push(name);
|
|
1074
|
-
};
|
|
1075
|
-
for (const segment of segments) {
|
|
1076
|
-
if (segment === "*") {
|
|
1077
|
-
isWildcard = true;
|
|
1078
|
-
continue;
|
|
1079
|
-
}
|
|
1080
|
-
const hasWildcardToken = segment.includes("*");
|
|
1081
|
-
if (segment.startsWith(":")) {
|
|
1082
|
-
isParametrized = true;
|
|
1083
|
-
const paramName = segment.replace(/\*+$/, "").slice(1);
|
|
1084
|
-
recordParamName(paramName);
|
|
1085
|
-
if (hasWildcardToken) {
|
|
1086
|
-
isWildcard = true;
|
|
1124
|
+
const customTokens = getAllCustomTokens();
|
|
1125
|
+
const matchingToken = customTokens.find(
|
|
1126
|
+
(t) => t.symbol?.toUpperCase() === normalizedSymbol
|
|
1127
|
+
);
|
|
1128
|
+
if (matchingToken) {
|
|
1129
|
+
const tokenNetwork = resolveNetwork(matchingToken.chainId);
|
|
1130
|
+
if ("code" in tokenNetwork) {
|
|
1131
|
+
return tokenNetwork;
|
|
1087
1132
|
}
|
|
1088
|
-
|
|
1133
|
+
return {
|
|
1134
|
+
input,
|
|
1135
|
+
config: matchingToken,
|
|
1136
|
+
caipAsset: `eip155:${matchingToken.chainId}/erc20:${matchingToken.contractAddress}`,
|
|
1137
|
+
network: tokenNetwork
|
|
1138
|
+
};
|
|
1089
1139
|
}
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
if (part.startsWith(":")) {
|
|
1094
|
-
isParametrized = true;
|
|
1095
|
-
recordParamName(part.slice(1));
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
isWildcard = true;
|
|
1140
|
+
const baseNetwork = resolveNetwork("base");
|
|
1141
|
+
if ("code" in baseNetwork) {
|
|
1142
|
+
return baseNetwork;
|
|
1099
1143
|
}
|
|
1144
|
+
return {
|
|
1145
|
+
input,
|
|
1146
|
+
config: {
|
|
1147
|
+
symbol: normalizedSymbol,
|
|
1148
|
+
name: "USD Coin",
|
|
1149
|
+
version: "2",
|
|
1150
|
+
chainId: 8453,
|
|
1151
|
+
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
1152
|
+
decimals: 6
|
|
1153
|
+
},
|
|
1154
|
+
caipAsset: baseNetwork.config.caipAssetId,
|
|
1155
|
+
network: baseNetwork
|
|
1156
|
+
};
|
|
1100
1157
|
}
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
return
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
"^" + patternSegment.replace(/\*/g, ".*").replace(/:/g, "") + "$"
|
|
1158
|
+
return createError(
|
|
1159
|
+
"UNKNOWN_TOKEN",
|
|
1160
|
+
`Invalid token identifier type: ${typeof input}`,
|
|
1161
|
+
{ value: input }
|
|
1162
|
+
);
|
|
1163
|
+
};
|
|
1164
|
+
var getAvailableTokens = () => {
|
|
1165
|
+
const customTokens = getAllCustomTokens();
|
|
1166
|
+
const symbols = new Set(customTokens.map((t) => t.symbol.toUpperCase()));
|
|
1167
|
+
return Array.from(symbols);
|
|
1168
|
+
};
|
|
1169
|
+
var resolveFacilitator = (input, supportedNetworks, supportedTokens) => {
|
|
1170
|
+
try {
|
|
1171
|
+
new URL(input.url);
|
|
1172
|
+
} catch {
|
|
1173
|
+
return createError(
|
|
1174
|
+
"VALIDATION_FAILED",
|
|
1175
|
+
`Invalid facilitator URL: ${input.url}`,
|
|
1176
|
+
{ value: input.url }
|
|
1121
1177
|
);
|
|
1122
|
-
return regex.test(pathSegment);
|
|
1123
|
-
}
|
|
1124
|
-
return patternSegment === pathSegment;
|
|
1125
|
-
}
|
|
1126
|
-
function matchWildcardPattern(patternSegments, pathSegments) {
|
|
1127
|
-
const requiredSegments = patternSegments.filter((s) => s !== "*");
|
|
1128
|
-
if (requiredSegments.length > pathSegments.length) {
|
|
1129
|
-
return false;
|
|
1130
|
-
}
|
|
1131
|
-
for (let i = 0; i < requiredSegments.length; i++) {
|
|
1132
|
-
const patternIndex = patternSegments.indexOf(requiredSegments[i]);
|
|
1133
|
-
if (pathSegments[patternIndex] !== requiredSegments[i].replace(/^\:/, "")) {
|
|
1134
|
-
if (!requiredSegments[i].startsWith(":") && requiredSegments[i] !== "*") {
|
|
1135
|
-
return false;
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
return true;
|
|
1140
|
-
}
|
|
1141
|
-
function matchRoute(pattern, path) {
|
|
1142
|
-
const normalizedPattern = pattern.startsWith("/") ? pattern : `/${pattern}`;
|
|
1143
|
-
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
1144
|
-
if (normalizedPattern === normalizedPath) {
|
|
1145
|
-
return true;
|
|
1146
|
-
}
|
|
1147
|
-
const parsed = parseRoutePattern(normalizedPattern);
|
|
1148
|
-
const patternSegments = parsed.segments;
|
|
1149
|
-
const pathSegments = normalizedPath.split("/").filter(Boolean);
|
|
1150
|
-
if (!parsed.isWildcard && patternSegments.length !== pathSegments.length) {
|
|
1151
|
-
return false;
|
|
1152
|
-
}
|
|
1153
|
-
if (parsed.isWildcard && patternSegments.length > pathSegments.length + 1) {
|
|
1154
|
-
return false;
|
|
1155
|
-
}
|
|
1156
|
-
if (parsed.isWildcard) {
|
|
1157
|
-
return matchWildcardPattern(patternSegments, pathSegments);
|
|
1158
1178
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
const matchingRoutes = [];
|
|
1168
|
-
for (const route of routes) {
|
|
1169
|
-
if (matchRoute(route.pattern, path)) {
|
|
1170
|
-
const parsed = parseRoutePattern(route.pattern);
|
|
1171
|
-
matchingRoutes.push({ route, parsed });
|
|
1179
|
+
const networks = [];
|
|
1180
|
+
if (input.networks) {
|
|
1181
|
+
for (const network of input.networks) {
|
|
1182
|
+
const resolved = resolveNetwork(network);
|
|
1183
|
+
if ("code" in resolved) {
|
|
1184
|
+
return resolved;
|
|
1185
|
+
}
|
|
1186
|
+
networks.push(resolved);
|
|
1172
1187
|
}
|
|
1188
|
+
} else if (supportedNetworks) {
|
|
1189
|
+
networks.push(...supportedNetworks);
|
|
1173
1190
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1191
|
+
const tokens = [];
|
|
1192
|
+
if (input.tokens) {
|
|
1193
|
+
for (const token of input.tokens) {
|
|
1194
|
+
for (const network of networks.length > 0 ? networks : supportedNetworks || []) {
|
|
1195
|
+
const resolved = resolveToken(token, network);
|
|
1196
|
+
if ("code" in resolved) {
|
|
1197
|
+
continue;
|
|
1198
|
+
}
|
|
1199
|
+
tokens.push(resolved);
|
|
1200
|
+
break;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
} else if (supportedTokens) {
|
|
1204
|
+
tokens.push(...supportedTokens);
|
|
1176
1205
|
}
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1206
|
+
return {
|
|
1207
|
+
input,
|
|
1208
|
+
url: input.url,
|
|
1209
|
+
networks,
|
|
1210
|
+
tokens
|
|
1211
|
+
};
|
|
1212
|
+
};
|
|
1213
|
+
var checkFacilitatorSupport = (facilitator, network, token) => {
|
|
1214
|
+
if (facilitator.networks.length > 0) {
|
|
1215
|
+
const networkSupported = facilitator.networks.some(
|
|
1216
|
+
(n) => n.config.chainId === network.config.chainId
|
|
1217
|
+
);
|
|
1218
|
+
if (!networkSupported) {
|
|
1219
|
+
const supportedNames = facilitator.networks.map((n) => n.config.name);
|
|
1220
|
+
return createError(
|
|
1221
|
+
"FACILITATOR_NO_NETWORK_SUPPORT",
|
|
1222
|
+
`Facilitator "${facilitator.url}" does not support network "${network.config.name}" (chain ${network.config.chainId}). Supported: ${supportedNames.join(", ")}`,
|
|
1223
|
+
{
|
|
1224
|
+
value: { facilitator: facilitator.url, network: network.config.name },
|
|
1225
|
+
validOptions: supportedNames
|
|
1226
|
+
}
|
|
1227
|
+
);
|
|
1180
1228
|
}
|
|
1181
|
-
|
|
1182
|
-
|
|
1229
|
+
}
|
|
1230
|
+
if (facilitator.tokens.length > 0) {
|
|
1231
|
+
const tokenSupported = facilitator.tokens.some(
|
|
1232
|
+
(t) => t.config.contractAddress.toLowerCase() === token.config.contractAddress.toLowerCase() && t.network.config.chainId === token.network.config.chainId
|
|
1233
|
+
);
|
|
1234
|
+
if (!tokenSupported) {
|
|
1235
|
+
const supportedTokens = facilitator.tokens.map(
|
|
1236
|
+
(t) => `${t.config.symbol} (${t.network.config.name})`
|
|
1237
|
+
);
|
|
1238
|
+
return createError(
|
|
1239
|
+
"FACILITATOR_NO_TOKEN_SUPPORT",
|
|
1240
|
+
`Facilitator "${facilitator.url}" does not support token "${token.config.symbol}" on "${token.network.config.name}". Supported: ${supportedTokens.join(", ")}`,
|
|
1241
|
+
{
|
|
1242
|
+
value: {
|
|
1243
|
+
facilitator: facilitator.url,
|
|
1244
|
+
token: token.config.symbol,
|
|
1245
|
+
network: token.network.config.name
|
|
1246
|
+
},
|
|
1247
|
+
validOptions: supportedTokens
|
|
1248
|
+
}
|
|
1249
|
+
);
|
|
1183
1250
|
}
|
|
1184
|
-
return b.route.pattern.length - a.route.pattern.length;
|
|
1185
|
-
});
|
|
1186
|
-
return matchingRoutes[0].route;
|
|
1187
|
-
}
|
|
1188
|
-
function containsWildcard(pattern) {
|
|
1189
|
-
return pattern.includes("*");
|
|
1190
|
-
}
|
|
1191
|
-
function validateRouteConfig(config) {
|
|
1192
|
-
const { route, routes } = config;
|
|
1193
|
-
if (!route && !routes) {
|
|
1194
|
-
return null;
|
|
1195
1251
|
}
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
};
|
|
1252
|
+
return { supported: true };
|
|
1253
|
+
};
|
|
1254
|
+
var validatePaymentConfig = (network, token, facilitators, payTo = "0x0000000000000000000000000000000000000000", amount = "1.0") => {
|
|
1255
|
+
const resolvedNetwork = resolveNetwork(network);
|
|
1256
|
+
if ("code" in resolvedNetwork) {
|
|
1257
|
+
return resolvedNetwork;
|
|
1203
1258
|
}
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
message: `Wildcard routes must use the routes array, not 'route'. Use 'routes: ["/api/*"]' instead of 'route: "/api/*"'.`,
|
|
1208
|
-
path: "route",
|
|
1209
|
-
value: route,
|
|
1210
|
-
validOptions: ['routes: ["/api/*"]', 'routes: ["/api/users", "/api/posts"]']
|
|
1211
|
-
};
|
|
1259
|
+
const resolvedToken = resolveToken(token, resolvedNetwork);
|
|
1260
|
+
if ("code" in resolvedToken) {
|
|
1261
|
+
return resolvedToken;
|
|
1212
1262
|
}
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
function toJsonSafe(data) {
|
|
1235
|
-
function convert(value) {
|
|
1236
|
-
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
1237
|
-
return Object.fromEntries(Object.entries(value).map(([key, val]) => [key, convert(val)]));
|
|
1238
|
-
}
|
|
1239
|
-
if (Array.isArray(value)) {
|
|
1240
|
-
return value.map(convert);
|
|
1241
|
-
}
|
|
1242
|
-
if (typeof value === "bigint") {
|
|
1243
|
-
return value.toString();
|
|
1263
|
+
const resolvedFacilitators = [];
|
|
1264
|
+
if (facilitators) {
|
|
1265
|
+
const facilitatorArray = Array.isArray(facilitators) ? facilitators : [facilitators];
|
|
1266
|
+
for (const facilitator of facilitatorArray) {
|
|
1267
|
+
const resolved = resolveFacilitator(
|
|
1268
|
+
facilitator,
|
|
1269
|
+
[resolvedNetwork],
|
|
1270
|
+
[resolvedToken]
|
|
1271
|
+
);
|
|
1272
|
+
if ("code" in resolved) {
|
|
1273
|
+
return resolved;
|
|
1274
|
+
}
|
|
1275
|
+
const supportCheck = checkFacilitatorSupport(
|
|
1276
|
+
resolved,
|
|
1277
|
+
resolvedNetwork,
|
|
1278
|
+
resolvedToken
|
|
1279
|
+
);
|
|
1280
|
+
if ("code" in supportCheck) {
|
|
1281
|
+
return supportCheck;
|
|
1282
|
+
}
|
|
1283
|
+
resolvedFacilitators.push(resolved);
|
|
1244
1284
|
}
|
|
1245
|
-
return value;
|
|
1246
|
-
}
|
|
1247
|
-
return convert(data);
|
|
1248
|
-
}
|
|
1249
|
-
function resolveUrl(config) {
|
|
1250
|
-
return config?.url ?? DEFAULT_FACILITATOR_URL;
|
|
1251
|
-
}
|
|
1252
|
-
async function resolveHeaders(config) {
|
|
1253
|
-
const base = { "Content-Type": "application/json" };
|
|
1254
|
-
if (!config?.createHeaders) return base;
|
|
1255
|
-
const extra = await config.createHeaders();
|
|
1256
|
-
return { ...base, ...extra };
|
|
1257
|
-
}
|
|
1258
|
-
async function verifyPayment(payload, requirements, config) {
|
|
1259
|
-
const url = resolveUrl(config);
|
|
1260
|
-
const headers = await resolveHeaders(config);
|
|
1261
|
-
const response = await fetch(`${url}/verify`, {
|
|
1262
|
-
method: "POST",
|
|
1263
|
-
headers,
|
|
1264
|
-
body: JSON.stringify({
|
|
1265
|
-
paymentPayload: toJsonSafe(payload),
|
|
1266
|
-
paymentRequirements: toJsonSafe(requirements)
|
|
1267
|
-
})
|
|
1268
|
-
});
|
|
1269
|
-
if (response.status !== 200) {
|
|
1270
|
-
const text = await response.text().catch(() => response.statusText);
|
|
1271
|
-
throw new Error(`Facilitator verify failed: ${response.status} ${text}`);
|
|
1272
|
-
}
|
|
1273
|
-
return await response.json();
|
|
1274
|
-
}
|
|
1275
|
-
async function settlePayment(payload, requirements, config) {
|
|
1276
|
-
const url = resolveUrl(config);
|
|
1277
|
-
const headers = await resolveHeaders(config);
|
|
1278
|
-
const response = await fetch(`${url}/settle`, {
|
|
1279
|
-
method: "POST",
|
|
1280
|
-
headers,
|
|
1281
|
-
body: JSON.stringify({
|
|
1282
|
-
paymentPayload: toJsonSafe(payload),
|
|
1283
|
-
paymentRequirements: toJsonSafe(requirements)
|
|
1284
|
-
})
|
|
1285
|
-
});
|
|
1286
|
-
if (response.status !== 200) {
|
|
1287
|
-
const text = await response.text().catch(() => response.statusText);
|
|
1288
|
-
throw new Error(`Facilitator settle failed: ${response.status} ${text}`);
|
|
1289
1285
|
}
|
|
1290
|
-
return
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1286
|
+
return {
|
|
1287
|
+
network: resolvedNetwork,
|
|
1288
|
+
token: resolvedToken,
|
|
1289
|
+
facilitators: resolvedFacilitators,
|
|
1290
|
+
version: 2,
|
|
1291
|
+
payTo,
|
|
1292
|
+
amount
|
|
1293
|
+
};
|
|
1294
|
+
};
|
|
1295
|
+
var validateAcceptConfig = (options, payTo, amount) => {
|
|
1296
|
+
const {
|
|
1297
|
+
networks: networkInputs,
|
|
1298
|
+
tokens: tokenInputs,
|
|
1299
|
+
facilitators,
|
|
1300
|
+
version = 2
|
|
1301
|
+
} = options;
|
|
1302
|
+
const networkIds = networkInputs?.length ? networkInputs : Object.keys(NETWORKS);
|
|
1303
|
+
const tokenIds = tokenInputs?.length ? tokenInputs : ["usdc"];
|
|
1304
|
+
const networks = [];
|
|
1305
|
+
for (const networkId of networkIds) {
|
|
1306
|
+
const resolved = resolveNetwork(networkId);
|
|
1307
|
+
if ("code" in resolved) {
|
|
1308
|
+
return { success: false, error: resolved };
|
|
1309
|
+
}
|
|
1310
|
+
networks.push(resolved);
|
|
1301
1311
|
}
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
function decodePayloadHeader(headerValue, defaults) {
|
|
1310
|
-
if (headerValue.startsWith("{")) {
|
|
1311
|
-
const parsed = JSON.parse(headerValue);
|
|
1312
|
-
if (isPaymentPayload(parsed)) return parsed;
|
|
1313
|
-
if (isCompactV2Payload(parsed)) {
|
|
1314
|
-
const compact = parsed;
|
|
1315
|
-
if (!defaults?.accepted) {
|
|
1316
|
-
throw new Error("Invalid payment payload: missing 'accepted' field and no defaults provided");
|
|
1312
|
+
const tokens = [];
|
|
1313
|
+
for (const tokenId of tokenIds) {
|
|
1314
|
+
let found = false;
|
|
1315
|
+
for (const network of networks) {
|
|
1316
|
+
const resolved = resolveToken(tokenId, network);
|
|
1317
|
+
if ("code" in resolved) {
|
|
1318
|
+
continue;
|
|
1317
1319
|
}
|
|
1320
|
+
tokens.push(resolved);
|
|
1321
|
+
found = true;
|
|
1322
|
+
break;
|
|
1323
|
+
}
|
|
1324
|
+
if (!found) {
|
|
1318
1325
|
return {
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1326
|
+
success: false,
|
|
1327
|
+
error: createError(
|
|
1328
|
+
"TOKEN_NOT_ON_NETWORK",
|
|
1329
|
+
`Token "${String(tokenId)}" not found on any of the specified networks`,
|
|
1330
|
+
{ value: tokenId, validOptions: networks.map((n) => n.config.name) }
|
|
1331
|
+
)
|
|
1322
1332
|
};
|
|
1323
1333
|
}
|
|
1324
|
-
throw new Error("Invalid payment payload: unrecognized format");
|
|
1325
1334
|
}
|
|
1326
|
-
const
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
if (
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1335
|
+
const facilitatorArray = facilitators ? Array.isArray(facilitators) ? facilitators : [facilitators] : [];
|
|
1336
|
+
const resolvedFacilitators = [];
|
|
1337
|
+
for (const facilitator of facilitatorArray) {
|
|
1338
|
+
const resolved = resolveFacilitator(facilitator, networks, tokens);
|
|
1339
|
+
if ("code" in resolved) {
|
|
1340
|
+
return { success: false, error: resolved };
|
|
1341
|
+
}
|
|
1342
|
+
resolvedFacilitators.push(resolved);
|
|
1343
|
+
}
|
|
1344
|
+
const configs = [];
|
|
1345
|
+
for (const network of networks) {
|
|
1346
|
+
for (const token of tokens) {
|
|
1347
|
+
if (token.network.config.chainId === network.config.chainId) {
|
|
1348
|
+
configs.push({
|
|
1349
|
+
network,
|
|
1350
|
+
token,
|
|
1351
|
+
facilitators: resolvedFacilitators,
|
|
1352
|
+
version,
|
|
1353
|
+
payTo,
|
|
1354
|
+
amount
|
|
1355
|
+
});
|
|
1334
1356
|
}
|
|
1335
|
-
return {
|
|
1336
|
-
x402Version: 2,
|
|
1337
|
-
accepted: defaults.accepted,
|
|
1338
|
-
payload: compact.payload
|
|
1339
|
-
};
|
|
1340
1357
|
}
|
|
1341
|
-
throw new Error("Invalid payment payload: unrecognized format");
|
|
1342
|
-
} catch {
|
|
1343
|
-
throw new Error("Invalid payment payload: failed to decode");
|
|
1344
|
-
}
|
|
1345
|
-
}
|
|
1346
|
-
function extractPayerAddress(payload) {
|
|
1347
|
-
if (isExactEvmPayload(payload.payload)) {
|
|
1348
|
-
return payload.payload.authorization.from;
|
|
1349
1358
|
}
|
|
1350
|
-
|
|
1351
|
-
}
|
|
1359
|
+
return { success: true, config: configs };
|
|
1360
|
+
};
|
|
1361
|
+
var isValidationError = (value) => {
|
|
1362
|
+
return typeof value === "object" && value !== null && "code" in value;
|
|
1363
|
+
};
|
|
1364
|
+
var isResolvedNetwork = (value) => {
|
|
1365
|
+
return typeof value === "object" && value !== null && "config" in value && "caip2" in value;
|
|
1366
|
+
};
|
|
1367
|
+
var isResolvedToken = (value) => {
|
|
1368
|
+
return typeof value === "object" && value !== null && "config" in value && "caipAsset" in value;
|
|
1369
|
+
};
|
|
1352
1370
|
|
|
1353
1371
|
// src/payment-requirements.ts
|
|
1354
1372
|
var DEFAULT_NETWORKS = [
|
|
@@ -1391,7 +1409,9 @@ function resolvePayTo(config, network, token) {
|
|
|
1391
1409
|
function resolveFacilitatorUrl(config, network, token) {
|
|
1392
1410
|
const chainId = network.config.chainId;
|
|
1393
1411
|
if (config.facilitatorUrlByToken) {
|
|
1394
|
-
for (const [chainKey, tokenMap] of Object.entries(
|
|
1412
|
+
for (const [chainKey, tokenMap] of Object.entries(
|
|
1413
|
+
config.facilitatorUrlByToken
|
|
1414
|
+
)) {
|
|
1395
1415
|
const resolvedChain = resolveNetwork(chainKey);
|
|
1396
1416
|
if (!isValidationError2(resolvedChain) && resolvedChain.config.chainId === chainId) {
|
|
1397
1417
|
for (const [tokenKey2, url] of Object.entries(tokenMap)) {
|
|
@@ -1404,7 +1424,9 @@ function resolveFacilitatorUrl(config, network, token) {
|
|
|
1404
1424
|
}
|
|
1405
1425
|
}
|
|
1406
1426
|
if (config.facilitatorUrlByChain) {
|
|
1407
|
-
for (const [chainKey, url] of Object.entries(
|
|
1427
|
+
for (const [chainKey, url] of Object.entries(
|
|
1428
|
+
config.facilitatorUrlByChain
|
|
1429
|
+
)) {
|
|
1408
1430
|
const resolvedChain = resolveNetwork(chainKey);
|
|
1409
1431
|
if (!isValidationError2(resolvedChain) && resolvedChain.config.chainId === chainId) {
|
|
1410
1432
|
return url;
|
|
@@ -1463,7 +1485,11 @@ function createPaymentRequirements(config) {
|
|
|
1463
1485
|
const atomicAmount = toAtomicUnits(amount);
|
|
1464
1486
|
const tokenConfig = resolvedToken.config;
|
|
1465
1487
|
const resolvedPayTo = resolvePayTo(config, network, resolvedToken);
|
|
1466
|
-
resolveFacilitatorUrl(
|
|
1488
|
+
resolveFacilitatorUrl(
|
|
1489
|
+
config,
|
|
1490
|
+
network,
|
|
1491
|
+
resolvedToken
|
|
1492
|
+
);
|
|
1467
1493
|
const requirement = {
|
|
1468
1494
|
scheme: "exact",
|
|
1469
1495
|
network: network.caip2,
|
|
@@ -1497,28 +1523,6 @@ function findRequirementByNetwork(requirements, network) {
|
|
|
1497
1523
|
return requirements.find((r) => r.network === netConfig.caip2Id);
|
|
1498
1524
|
}
|
|
1499
1525
|
|
|
1500
|
-
// src/errors.ts
|
|
1501
|
-
var X402ClientError = class extends Error {
|
|
1502
|
-
cause;
|
|
1503
|
-
constructor(message, cause) {
|
|
1504
|
-
super(message);
|
|
1505
|
-
this.name = "X402ClientError";
|
|
1506
|
-
this.cause = cause;
|
|
1507
|
-
}
|
|
1508
|
-
};
|
|
1509
|
-
var SigningError = class extends X402ClientError {
|
|
1510
|
-
constructor(message, cause) {
|
|
1511
|
-
super(`Signing failed: ${message}`, cause);
|
|
1512
|
-
this.name = "SigningError";
|
|
1513
|
-
}
|
|
1514
|
-
};
|
|
1515
|
-
var PaymentException = class extends X402ClientError {
|
|
1516
|
-
constructor(message, cause) {
|
|
1517
|
-
super(`Payment failed: ${message}`, cause);
|
|
1518
|
-
this.name = "PaymentException";
|
|
1519
|
-
}
|
|
1520
|
-
};
|
|
1521
|
-
|
|
1522
1526
|
// src/protocol.ts
|
|
1523
1527
|
function parseJsonOrBase64(value) {
|
|
1524
1528
|
try {
|
|
@@ -1542,4 +1546,194 @@ function calculateValidBefore(expirySeconds = 3600) {
|
|
|
1542
1546
|
return Math.floor(Date.now() / 1e3) + expirySeconds;
|
|
1543
1547
|
}
|
|
1544
1548
|
|
|
1545
|
-
|
|
1549
|
+
// src/types/protocol.ts
|
|
1550
|
+
function isX402V2Payload(obj) {
|
|
1551
|
+
return typeof obj === "object" && obj !== null && "x402Version" in obj && obj.x402Version === 2 && "accepted" in obj && "payload" in obj;
|
|
1552
|
+
}
|
|
1553
|
+
function isX402V2Requirements(obj) {
|
|
1554
|
+
return typeof obj === "object" && obj !== null && "scheme" in obj && obj.scheme === "exact" && "network" in obj && typeof obj.network === "string" && obj.network.startsWith("eip155:") && // CAIP-2 format
|
|
1555
|
+
"amount" in obj && "asset" in obj && "payTo" in obj && "maxTimeoutSeconds" in obj;
|
|
1556
|
+
}
|
|
1557
|
+
function isX402V2Settlement(obj) {
|
|
1558
|
+
return typeof obj === "object" && obj !== null && "success" in obj && typeof obj.success === "boolean" && "network" in obj && typeof obj.network === "string" && obj.network.startsWith("eip155:");
|
|
1559
|
+
}
|
|
1560
|
+
function isX402V2PaymentRequired(obj) {
|
|
1561
|
+
return typeof obj === "object" && obj !== null && "x402Version" in obj && obj.x402Version === 2 && "resource" in obj && "accepts" in obj;
|
|
1562
|
+
}
|
|
1563
|
+
var PAYMENT_SIGNATURE_HEADER = "PAYMENT-SIGNATURE";
|
|
1564
|
+
var PAYMENT_RESPONSE_HEADER = "PAYMENT-RESPONSE";
|
|
1565
|
+
var PAYMENT_REQUIRED_HEADER = "PAYMENT-REQUIRED";
|
|
1566
|
+
function isSettlementSuccessful(response) {
|
|
1567
|
+
return "success" in response ? response.success : false;
|
|
1568
|
+
}
|
|
1569
|
+
function getTxHash(response) {
|
|
1570
|
+
if ("transaction" in response) return response.transaction;
|
|
1571
|
+
return void 0;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
// src/utils/routes.ts
|
|
1575
|
+
var PRIORITY_EXACT = 3;
|
|
1576
|
+
var PRIORITY_PARAMETRIZED = 2;
|
|
1577
|
+
var PRIORITY_WILDCARD = 1;
|
|
1578
|
+
function parseRoutePattern(pattern) {
|
|
1579
|
+
const normalizedPattern = pattern.startsWith("/") ? pattern : `/${pattern}`;
|
|
1580
|
+
const segments = normalizedPattern.split("/").filter(Boolean);
|
|
1581
|
+
let isWildcard = false;
|
|
1582
|
+
let isParametrized = false;
|
|
1583
|
+
const paramNames = [];
|
|
1584
|
+
const seenParamNames = /* @__PURE__ */ new Set();
|
|
1585
|
+
const recordParamName = (name) => {
|
|
1586
|
+
if (!name) {
|
|
1587
|
+
return;
|
|
1588
|
+
}
|
|
1589
|
+
if (seenParamNames.has(name)) {
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
seenParamNames.add(name);
|
|
1593
|
+
paramNames.push(name);
|
|
1594
|
+
};
|
|
1595
|
+
for (const segment of segments) {
|
|
1596
|
+
if (segment === "*") {
|
|
1597
|
+
isWildcard = true;
|
|
1598
|
+
continue;
|
|
1599
|
+
}
|
|
1600
|
+
const hasWildcardToken = segment.includes("*");
|
|
1601
|
+
if (segment.startsWith(":")) {
|
|
1602
|
+
isParametrized = true;
|
|
1603
|
+
const paramName = segment.replace(/\*+$/, "").slice(1);
|
|
1604
|
+
recordParamName(paramName);
|
|
1605
|
+
if (hasWildcardToken) {
|
|
1606
|
+
isWildcard = true;
|
|
1607
|
+
}
|
|
1608
|
+
continue;
|
|
1609
|
+
}
|
|
1610
|
+
if (hasWildcardToken) {
|
|
1611
|
+
const parts = segment.split("*");
|
|
1612
|
+
for (const part of parts) {
|
|
1613
|
+
if (part.startsWith(":")) {
|
|
1614
|
+
isParametrized = true;
|
|
1615
|
+
recordParamName(part.slice(1));
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
isWildcard = true;
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
let priority = PRIORITY_WILDCARD;
|
|
1622
|
+
if (!isWildcard && !isParametrized) {
|
|
1623
|
+
priority = PRIORITY_EXACT;
|
|
1624
|
+
} else if (isParametrized && !isWildcard) {
|
|
1625
|
+
priority = PRIORITY_PARAMETRIZED;
|
|
1626
|
+
} else if (isParametrized && isWildcard) {
|
|
1627
|
+
priority = PRIORITY_PARAMETRIZED;
|
|
1628
|
+
}
|
|
1629
|
+
return { segments, isWildcard, isParametrized, paramNames, priority };
|
|
1630
|
+
}
|
|
1631
|
+
function matchSegment(patternSegment, pathSegment) {
|
|
1632
|
+
if (patternSegment === "*") {
|
|
1633
|
+
return true;
|
|
1634
|
+
}
|
|
1635
|
+
if (patternSegment.startsWith(":")) {
|
|
1636
|
+
return true;
|
|
1637
|
+
}
|
|
1638
|
+
if (patternSegment.includes("*")) {
|
|
1639
|
+
const regex = new RegExp(
|
|
1640
|
+
`^${patternSegment.replace(/\*/g, ".*").replace(/:/g, "")}$`
|
|
1641
|
+
);
|
|
1642
|
+
return regex.test(pathSegment);
|
|
1643
|
+
}
|
|
1644
|
+
return patternSegment === pathSegment;
|
|
1645
|
+
}
|
|
1646
|
+
function matchWildcardPattern(patternSegments, pathSegments) {
|
|
1647
|
+
const requiredSegments = patternSegments.filter((s) => s !== "*");
|
|
1648
|
+
if (requiredSegments.length > pathSegments.length) {
|
|
1649
|
+
return false;
|
|
1650
|
+
}
|
|
1651
|
+
for (let i = 0; i < requiredSegments.length; i++) {
|
|
1652
|
+
const patternIndex = patternSegments.indexOf(requiredSegments[i]);
|
|
1653
|
+
if (pathSegments[patternIndex] !== requiredSegments[i].replace(/^:/, "")) {
|
|
1654
|
+
if (!requiredSegments[i].startsWith(":") && requiredSegments[i] !== "*") {
|
|
1655
|
+
return false;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
return true;
|
|
1660
|
+
}
|
|
1661
|
+
function matchRoute(pattern, path) {
|
|
1662
|
+
const normalizedPattern = pattern.startsWith("/") ? pattern : `/${pattern}`;
|
|
1663
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
1664
|
+
if (normalizedPattern === normalizedPath) {
|
|
1665
|
+
return true;
|
|
1666
|
+
}
|
|
1667
|
+
const parsed = parseRoutePattern(normalizedPattern);
|
|
1668
|
+
const patternSegments = parsed.segments;
|
|
1669
|
+
const pathSegments = normalizedPath.split("/").filter(Boolean);
|
|
1670
|
+
if (!parsed.isWildcard && patternSegments.length !== pathSegments.length) {
|
|
1671
|
+
return false;
|
|
1672
|
+
}
|
|
1673
|
+
if (parsed.isWildcard && patternSegments.length > pathSegments.length + 1) {
|
|
1674
|
+
return false;
|
|
1675
|
+
}
|
|
1676
|
+
if (parsed.isWildcard) {
|
|
1677
|
+
return matchWildcardPattern(patternSegments, pathSegments);
|
|
1678
|
+
}
|
|
1679
|
+
for (let i = 0; i < patternSegments.length; i++) {
|
|
1680
|
+
if (!matchSegment(patternSegments[i], pathSegments[i])) {
|
|
1681
|
+
return false;
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
return true;
|
|
1685
|
+
}
|
|
1686
|
+
function findMatchingRoute(routes, path) {
|
|
1687
|
+
const matchingRoutes = [];
|
|
1688
|
+
for (const route of routes) {
|
|
1689
|
+
if (matchRoute(route.pattern, path)) {
|
|
1690
|
+
const parsed = parseRoutePattern(route.pattern);
|
|
1691
|
+
matchingRoutes.push({ route, parsed });
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
if (matchingRoutes.length === 0) {
|
|
1695
|
+
return null;
|
|
1696
|
+
}
|
|
1697
|
+
matchingRoutes.sort((a, b) => {
|
|
1698
|
+
if (b.parsed.priority !== a.parsed.priority) {
|
|
1699
|
+
return b.parsed.priority - a.parsed.priority;
|
|
1700
|
+
}
|
|
1701
|
+
if (b.parsed.segments.length !== a.parsed.segments.length) {
|
|
1702
|
+
return b.parsed.segments.length - a.parsed.segments.length;
|
|
1703
|
+
}
|
|
1704
|
+
return b.route.pattern.length - a.route.pattern.length;
|
|
1705
|
+
});
|
|
1706
|
+
return matchingRoutes[0].route;
|
|
1707
|
+
}
|
|
1708
|
+
function containsWildcard(pattern) {
|
|
1709
|
+
return pattern.includes("*");
|
|
1710
|
+
}
|
|
1711
|
+
function validateRouteConfig(config) {
|
|
1712
|
+
const { route, routes } = config;
|
|
1713
|
+
if (!route && !routes) {
|
|
1714
|
+
return null;
|
|
1715
|
+
}
|
|
1716
|
+
if (route && routes) {
|
|
1717
|
+
return {
|
|
1718
|
+
code: "INVALID_ROUTE_CONFIG",
|
|
1719
|
+
message: "Cannot specify both 'route' and 'routes'. Use 'route' for a single exact path or 'routes' for multiple paths.",
|
|
1720
|
+
path: "route",
|
|
1721
|
+
value: { route, routes }
|
|
1722
|
+
};
|
|
1723
|
+
}
|
|
1724
|
+
if (route && containsWildcard(route)) {
|
|
1725
|
+
return {
|
|
1726
|
+
code: "INVALID_ROUTE_PATTERN",
|
|
1727
|
+
message: `Wildcard routes must use the routes array, not 'route'. Use 'routes: ["/api/*"]' instead of 'route: "/api/*"'.`,
|
|
1728
|
+
path: "route",
|
|
1729
|
+
value: route,
|
|
1730
|
+
validOptions: [
|
|
1731
|
+
'routes: ["/api/*"]',
|
|
1732
|
+
'routes: ["/api/users", "/api/posts"]'
|
|
1733
|
+
]
|
|
1734
|
+
};
|
|
1735
|
+
}
|
|
1736
|
+
return null;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
export { EIP712_TYPES, ERC20_ABI, EURC_BASE, NETWORKS, PAYMENT_REQUIRED_HEADER, PAYMENT_RESPONSE_HEADER, PAYMENT_SIGNATURE_HEADER, PaymentException, SCHEMES, SKL_SKALE_BASE, SKL_SKALE_BASE_SEPOLIA, SigningError, TOKENS, USDC_BASE, USDC_BASE_SEPOLIA, USDC_DOMAIN, USDC_SKALE_BASE, USDC_SKALE_BASE_SEPOLIA, USDT_SKALE_BASE, USDT_SKALE_BASE_SEPOLIA, V2_HEADERS, WBTC_SKALE_BASE, WBTC_SKALE_BASE_SEPOLIA, WETH_SKALE_BASE, WETH_SKALE_BASE_SEPOLIA, X402ClientError, X402_VERSION, addressToAssetId, assetIdToAddress, caip2ToNetwork, calculateValidBefore, checkFacilitatorSupport, combineSignature as combineSignatureV2, createEIP712Domain, createError, createNonce, createPaymentRequiredHeaders, createPaymentRequirements, createSettlementHeaders, createTransferWithAuthorization, decodeBase64ToUtf8, decodePayloadHeader, decodePayment, decodePaymentV2, decodeSettlementResponse, decodeSettlementV2, decodeX402Response, detectPaymentVersion, detectX402Version, encodePayment, encodePaymentV2, encodeSettlementResponse, encodeSettlementV2, encodeUtf8ToBase64, encodeX402Response, extractPayerAddress, extractPaymentFromHeaders, findMatchingRoute, findRequirementByNetwork, fromAtomicUnits, generateNonce, getAllCustomTokens, getAllTokens, getAvailableNetworks, getAvailableTokens, getCurrentTimestamp, getCustomToken, getEURCTokens, getMainnets, getNetworkByChainId, getNetworkConfig, getPaymentHeaderName, getSKLTokens, getSupported, getTestnets, getToken, getTokensByChain, getTokensBySymbol, getTxHash, getUSDCTokens, getUSDTTokens, getWBTCTokens, getWETHTokens, isAddress2 as isAddress, isCAIP2ChainId, isCAIPAssetId, isCustomToken, isExactEvmPayload, isPaymentPayload, isPaymentPayloadV2, isPaymentRequiredV2, isPaymentV2, isResolvedNetwork, isResolvedToken, isSettlementSuccessful, isSettlementV2, isValidAddress, isValidationError, isX402V2Payload, isX402V2PaymentRequired, isX402V2Requirements, isX402V2Settlement, matchRoute, networkToCaip2, normalizeAddress, normalizeBase64Url, normalizeNetworkName, parseJsonOrBase64, parseRoutePattern, parseSignature as parseSignatureV2, registerToken, resolveFacilitator, resolveNetwork, resolveToken, runAfterPaymentResponseHooks, runBeforeSignPaymentHooks, runOnPaymentRequiredHooks, safeBase64Decode2 as safeBase64Decode, safeBase64Encode2 as safeBase64Encode, selectRequirementWithHooks, settlePayment, toAtomicUnits, toBase64Url, unregisterToken, validateAcceptConfig, validatePaymentConfig, validateRouteConfig, validateTransferWithAuthorization, verifyPayment };
|