@armory-sh/base 0.2.28 → 0.2.29
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 +9 -0
- package/dist/encoding/x402.d.ts +1 -1
- package/dist/errors.d.ts +10 -0
- package/dist/index.d.ts +29 -22
- package/dist/index.js +1121 -689
- package/dist/payment-client.d.ts +1 -1
- package/dist/payment-requirements.d.ts +29 -0
- package/dist/protocol.d.ts +33 -0
- 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/types/wallet.d.ts +24 -0
- package/dist/utils/base64.d.ts +4 -0
- package/dist/utils/x402.d.ts +1 -1
- package/dist/validation.d.ts +1 -1
- package/package.json +15 -2
- package/src/abi/erc20.ts +84 -0
- package/src/client-hooks-runtime.ts +153 -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 +323 -0
- package/src/payment-client.ts +201 -0
- package/src/payment-requirements.ts +300 -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 +654 -0
package/dist/index.js
CHANGED
|
@@ -1,15 +1,374 @@
|
|
|
1
1
|
import { randomBytes } from 'crypto';
|
|
2
2
|
|
|
3
|
-
// src/
|
|
4
|
-
var
|
|
5
|
-
|
|
6
|
-
function
|
|
7
|
-
|
|
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
|
+
];
|
|
59
|
+
|
|
60
|
+
// src/client-hooks-runtime.ts
|
|
61
|
+
var notifyError = async (hooks, context) => {
|
|
62
|
+
if (!hooks?.length) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
for (const hook of hooks) {
|
|
66
|
+
if (!hook.onError) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
await hook.onError(context);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var runOnPaymentRequiredHooks = async (hooks, context) => {
|
|
73
|
+
if (!hooks?.length) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
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;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
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;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
context.selectedRequirement = selected;
|
|
124
|
+
context.requirements = selected;
|
|
125
|
+
return selected;
|
|
126
|
+
};
|
|
127
|
+
var runBeforeSignPaymentHooks = async (hooks, context) => {
|
|
128
|
+
if (!hooks?.length) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
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
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
var runAfterPaymentResponseHooks = async (hooks, context) => {
|
|
144
|
+
if (!hooks?.length) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
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
|
+
}
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// src/data/tokens.ts
|
|
161
|
+
var USDC_BASE = {
|
|
162
|
+
symbol: "USDC",
|
|
163
|
+
name: "USDC",
|
|
164
|
+
version: "2",
|
|
165
|
+
contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
166
|
+
chainId: 8453,
|
|
167
|
+
decimals: 6
|
|
168
|
+
};
|
|
169
|
+
var EURC_BASE = {
|
|
170
|
+
symbol: "EURC",
|
|
171
|
+
name: "EURC",
|
|
172
|
+
version: "2",
|
|
173
|
+
contractAddress: "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42",
|
|
174
|
+
chainId: 8453,
|
|
175
|
+
decimals: 6
|
|
176
|
+
};
|
|
177
|
+
var USDC_BASE_SEPOLIA = {
|
|
178
|
+
symbol: "USDC",
|
|
179
|
+
name: "USDC",
|
|
180
|
+
version: "2",
|
|
181
|
+
contractAddress: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
182
|
+
chainId: 84532,
|
|
183
|
+
decimals: 6
|
|
184
|
+
};
|
|
185
|
+
var USDC_SKALE_BASE = {
|
|
186
|
+
symbol: "USDC",
|
|
187
|
+
name: "Bridged USDC (SKALE Bridge)",
|
|
188
|
+
version: "2",
|
|
189
|
+
contractAddress: "0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20",
|
|
190
|
+
chainId: 1187947933,
|
|
191
|
+
decimals: 6
|
|
192
|
+
};
|
|
193
|
+
var SKL_SKALE_BASE = {
|
|
194
|
+
symbol: "SKL",
|
|
195
|
+
name: "SKALE",
|
|
196
|
+
version: "1",
|
|
197
|
+
contractAddress: "0xE0595a049d02b7674572b0d59cd4880Db60EDC50",
|
|
198
|
+
chainId: 1187947933,
|
|
199
|
+
decimals: 18
|
|
200
|
+
};
|
|
201
|
+
var USDT_SKALE_BASE = {
|
|
202
|
+
symbol: "USDT",
|
|
203
|
+
name: "Tether USD",
|
|
204
|
+
version: "1",
|
|
205
|
+
contractAddress: "0x2bF5bF154b515EaA82C31a65ec11554fF5aF7fCA",
|
|
206
|
+
chainId: 1187947933,
|
|
207
|
+
decimals: 6
|
|
208
|
+
};
|
|
209
|
+
var WBTC_SKALE_BASE = {
|
|
210
|
+
symbol: "WBTC",
|
|
211
|
+
name: "Wrapped BTC",
|
|
212
|
+
version: "1",
|
|
213
|
+
contractAddress: "0x1aeeCFE5454c83B42D8A316246CAc9739E7f690e",
|
|
214
|
+
chainId: 1187947933,
|
|
215
|
+
decimals: 8
|
|
216
|
+
};
|
|
217
|
+
var WETH_SKALE_BASE = {
|
|
218
|
+
symbol: "WETH",
|
|
219
|
+
name: "Wrapped Ether",
|
|
220
|
+
version: "1",
|
|
221
|
+
contractAddress: "0x7bD39ABBd0Dd13103542cAe3276C7fA332bCA486",
|
|
222
|
+
chainId: 1187947933,
|
|
223
|
+
decimals: 18
|
|
224
|
+
};
|
|
225
|
+
var SKL_SKALE_BASE_SEPOLIA = {
|
|
226
|
+
symbol: "SKL",
|
|
227
|
+
name: "SKALE",
|
|
228
|
+
version: "1",
|
|
229
|
+
contractAddress: "0xaf2e0ff5b5f51553fdb34ce7f04a6c3201cee57b",
|
|
230
|
+
chainId: 324705682,
|
|
231
|
+
decimals: 18
|
|
232
|
+
};
|
|
233
|
+
var USDC_SKALE_BASE_SEPOLIA = {
|
|
234
|
+
symbol: "USDC",
|
|
235
|
+
name: "Bridged USDC (SKALE Bridge)",
|
|
236
|
+
version: "2",
|
|
237
|
+
contractAddress: "0x2e08028E3C4c2356572E096d8EF835cD5C6030bD",
|
|
238
|
+
chainId: 324705682,
|
|
239
|
+
decimals: 6
|
|
240
|
+
};
|
|
241
|
+
var USDT_SKALE_BASE_SEPOLIA = {
|
|
242
|
+
symbol: "USDT",
|
|
243
|
+
name: "Tether USD",
|
|
244
|
+
version: "1",
|
|
245
|
+
contractAddress: "0x3ca0a49f511c2c89c4dcbbf1731120d8919050bf",
|
|
246
|
+
chainId: 324705682,
|
|
247
|
+
decimals: 6
|
|
248
|
+
};
|
|
249
|
+
var WBTC_SKALE_BASE_SEPOLIA = {
|
|
250
|
+
symbol: "WBTC",
|
|
251
|
+
name: "Wrapped BTC",
|
|
252
|
+
version: "1",
|
|
253
|
+
contractAddress: "0x4512eacd4186b025186e1cf6cc0d89497c530e87",
|
|
254
|
+
chainId: 324705682,
|
|
255
|
+
decimals: 8
|
|
256
|
+
};
|
|
257
|
+
var WETH_SKALE_BASE_SEPOLIA = {
|
|
258
|
+
symbol: "WETH",
|
|
259
|
+
name: "Wrapped Ether",
|
|
260
|
+
version: "1",
|
|
261
|
+
contractAddress: "0xf94056bd7f6965db3757e1b145f200b7346b4fc0",
|
|
262
|
+
chainId: 324705682,
|
|
263
|
+
decimals: 18
|
|
264
|
+
};
|
|
265
|
+
var TOKENS = {
|
|
266
|
+
USDC_BASE,
|
|
267
|
+
USDC_BASE_SEPOLIA,
|
|
268
|
+
EURC_BASE,
|
|
269
|
+
USDC_SKALE_BASE,
|
|
270
|
+
USDT_SKALE_BASE,
|
|
271
|
+
WBTC_SKALE_BASE,
|
|
272
|
+
WETH_SKALE_BASE,
|
|
273
|
+
SKL_SKALE_BASE,
|
|
274
|
+
SKL_SKALE_BASE_SEPOLIA,
|
|
275
|
+
USDC_SKALE_BASE_SEPOLIA,
|
|
276
|
+
USDT_SKALE_BASE_SEPOLIA,
|
|
277
|
+
WBTC_SKALE_BASE_SEPOLIA,
|
|
278
|
+
WETH_SKALE_BASE_SEPOLIA
|
|
279
|
+
};
|
|
280
|
+
function getToken(chainId, contractAddress) {
|
|
281
|
+
const tokens = Object.values(TOKENS);
|
|
282
|
+
return tokens.find(
|
|
283
|
+
(t) => t.chainId === chainId && t.contractAddress.toLowerCase() === contractAddress.toLowerCase()
|
|
284
|
+
);
|
|
8
285
|
}
|
|
9
|
-
function
|
|
10
|
-
return
|
|
286
|
+
function getAllTokens() {
|
|
287
|
+
return Object.values(TOKENS);
|
|
288
|
+
}
|
|
289
|
+
function getTokensBySymbol(symbol) {
|
|
290
|
+
return getAllTokens().filter(
|
|
291
|
+
(t) => t.symbol.toUpperCase() === symbol.toUpperCase()
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
function getTokensByChain(chainId) {
|
|
295
|
+
return getAllTokens().filter((t) => t.chainId === chainId);
|
|
296
|
+
}
|
|
297
|
+
function getUSDCTokens() {
|
|
298
|
+
return getTokensBySymbol("USDC");
|
|
299
|
+
}
|
|
300
|
+
function getEURCTokens() {
|
|
301
|
+
return getTokensBySymbol("EURC");
|
|
302
|
+
}
|
|
303
|
+
function getSKLTokens() {
|
|
304
|
+
return getTokensBySymbol("SKL");
|
|
305
|
+
}
|
|
306
|
+
function getUSDTTokens() {
|
|
307
|
+
return getTokensBySymbol("USDT");
|
|
308
|
+
}
|
|
309
|
+
function getWBTCTokens() {
|
|
310
|
+
return getTokensBySymbol("WBTC");
|
|
311
|
+
}
|
|
312
|
+
function getWETHTokens() {
|
|
313
|
+
return getTokensBySymbol("WETH");
|
|
11
314
|
}
|
|
12
315
|
|
|
316
|
+
// src/eip712.ts
|
|
317
|
+
var EIP712_TYPES = {
|
|
318
|
+
TransferWithAuthorization: [
|
|
319
|
+
{ name: "from", type: "address" },
|
|
320
|
+
{ name: "to", type: "address" },
|
|
321
|
+
{ name: "value", type: "uint256" },
|
|
322
|
+
{ name: "validAfter", type: "uint256" },
|
|
323
|
+
{ name: "validBefore", type: "uint256" },
|
|
324
|
+
{ name: "nonce", type: "bytes32" }
|
|
325
|
+
]
|
|
326
|
+
};
|
|
327
|
+
var USDC_DOMAIN = {
|
|
328
|
+
NAME: "USD Coin",
|
|
329
|
+
VERSION: "2"
|
|
330
|
+
};
|
|
331
|
+
var createEIP712Domain = (chainId, contractAddress) => ({
|
|
332
|
+
name: USDC_DOMAIN.NAME,
|
|
333
|
+
version: USDC_DOMAIN.VERSION,
|
|
334
|
+
chainId,
|
|
335
|
+
verifyingContract: contractAddress
|
|
336
|
+
});
|
|
337
|
+
var createTransferWithAuthorization = (params) => ({
|
|
338
|
+
from: params.from,
|
|
339
|
+
to: params.to,
|
|
340
|
+
value: BigInt(params.value),
|
|
341
|
+
validAfter: BigInt(params.validAfter),
|
|
342
|
+
validBefore: BigInt(params.validBefore),
|
|
343
|
+
nonce: params.nonce
|
|
344
|
+
});
|
|
345
|
+
var isAddress = (value) => /^0x[a-fA-F0-9]{40}$/.test(value);
|
|
346
|
+
var isBytes32 = (value) => /^0x[a-fA-F0-9]{64}$/.test(value);
|
|
347
|
+
var validateTransferWithAuthorization = (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
|
+
);
|
|
360
|
+
if (message.validAfter >= message.validBefore) {
|
|
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
|
+
|
|
13
372
|
// src/types/v2.ts
|
|
14
373
|
var V2_HEADERS = {
|
|
15
374
|
PAYMENT_SIGNATURE: "PAYMENT-SIGNATURE",
|
|
@@ -22,7 +381,7 @@ function isCAIP2ChainId(value) {
|
|
|
22
381
|
function isCAIPAssetId(value) {
|
|
23
382
|
return /^eip155:\d+\/erc20:0x[a-fA-F0-9]+$/.test(value);
|
|
24
383
|
}
|
|
25
|
-
function
|
|
384
|
+
function isAddress2(value) {
|
|
26
385
|
return /^0x[a-fA-F0-9]{40}$/.test(value);
|
|
27
386
|
}
|
|
28
387
|
function isPaymentRequiredV2(obj) {
|
|
@@ -36,56 +395,118 @@ function assetIdToAddress(assetId) {
|
|
|
36
395
|
if (!match) throw new Error(`Invalid CAIP asset ID: ${assetId}`);
|
|
37
396
|
return match[1];
|
|
38
397
|
}
|
|
39
|
-
function addressToAssetId(address, chainId) {
|
|
40
|
-
const chain = typeof chainId === "number" ? `eip155:${chainId}` : chainId;
|
|
41
|
-
if (!isCAIP2ChainId(chain)) throw new Error(`Invalid chain ID: ${chain}`);
|
|
42
|
-
return `${chain}/erc20:${address}`;
|
|
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));
|
|
43
463
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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;
|
|
51
478
|
}
|
|
52
|
-
function
|
|
53
|
-
|
|
54
|
-
const sClean = s.startsWith("0x") ? s.slice(2) : s;
|
|
55
|
-
const vHex = v.toString(16).padStart(2, "0");
|
|
56
|
-
return `0x${rClean}${sClean}${vHex}`;
|
|
479
|
+
function isExactEvmPayload(obj) {
|
|
480
|
+
return typeof obj === "object" && obj !== null && "signature" in obj && "authorization" in obj;
|
|
57
481
|
}
|
|
58
482
|
|
|
59
483
|
// src/encoding/x402.ts
|
|
60
|
-
function
|
|
61
|
-
return
|
|
484
|
+
function safeBase64Encode2(str) {
|
|
485
|
+
return toBase64Url(encodeUtf8ToBase64(str));
|
|
62
486
|
}
|
|
63
|
-
function
|
|
64
|
-
|
|
65
|
-
if (padding !== 4) {
|
|
66
|
-
str += "=".repeat(padding);
|
|
67
|
-
}
|
|
68
|
-
str = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
69
|
-
return Buffer.from(str, "base64").toString("utf-8");
|
|
487
|
+
function safeBase64Decode2(str) {
|
|
488
|
+
return decodeBase64ToUtf8(normalizeBase64Url(str));
|
|
70
489
|
}
|
|
71
490
|
function encodePayment(payload) {
|
|
72
491
|
if (!isPaymentPayload(payload)) {
|
|
73
492
|
throw new Error("Invalid payment payload format");
|
|
74
493
|
}
|
|
75
|
-
const safePayload = JSON.parse(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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));
|
|
82
503
|
}
|
|
83
504
|
function decodePayment(encoded) {
|
|
84
505
|
let parsed;
|
|
85
506
|
try {
|
|
86
507
|
parsed = JSON.parse(encoded);
|
|
87
508
|
} catch {
|
|
88
|
-
const decoded =
|
|
509
|
+
const decoded = safeBase64Decode2(encoded);
|
|
89
510
|
parsed = JSON.parse(decoded);
|
|
90
511
|
}
|
|
91
512
|
if (!isPaymentPayload(parsed)) {
|
|
@@ -94,513 +515,393 @@ function decodePayment(encoded) {
|
|
|
94
515
|
return parsed;
|
|
95
516
|
}
|
|
96
517
|
function encodeSettlementResponse(response) {
|
|
97
|
-
const safeResponse = JSON.parse(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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));
|
|
104
527
|
}
|
|
105
528
|
function decodeSettlementResponse(encoded) {
|
|
106
529
|
try {
|
|
107
530
|
return JSON.parse(encoded);
|
|
108
531
|
} catch {
|
|
109
|
-
const decoded =
|
|
532
|
+
const decoded = safeBase64Decode2(encoded);
|
|
110
533
|
return JSON.parse(decoded);
|
|
111
534
|
}
|
|
112
535
|
}
|
|
113
536
|
function encodeX402Response(response) {
|
|
114
|
-
return
|
|
115
|
-
}
|
|
116
|
-
function decodeX402Response(encoded) {
|
|
117
|
-
try {
|
|
118
|
-
return JSON.parse(encoded);
|
|
119
|
-
} catch {
|
|
120
|
-
const decoded = safeBase64Decode(encoded);
|
|
121
|
-
return JSON.parse(decoded);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
function detectPaymentVersion(headers) {
|
|
125
|
-
if (headers.has("PAYMENT-SIGNATURE")) {
|
|
126
|
-
return 2;
|
|
127
|
-
}
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
function extractPaymentFromHeaders(headers) {
|
|
131
|
-
const encoded = headers.get("PAYMENT-SIGNATURE");
|
|
132
|
-
if (!encoded) return null;
|
|
133
|
-
try {
|
|
134
|
-
return decodePayment(encoded);
|
|
135
|
-
} catch {
|
|
136
|
-
return null;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
function createPaymentRequiredHeaders(requirements, options) {
|
|
140
|
-
const accepts = Array.isArray(requirements) ? requirements : [requirements];
|
|
141
|
-
const response = {
|
|
142
|
-
x402Version: 2,
|
|
143
|
-
error: options?.error ?? "Payment required",
|
|
144
|
-
resource: options?.resource ?? { url: "", mimeType: "application/json" },
|
|
145
|
-
accepts,
|
|
146
|
-
extensions: options?.extensions ?? {}
|
|
147
|
-
};
|
|
148
|
-
return {
|
|
149
|
-
[V2_HEADERS.PAYMENT_REQUIRED]: safeBase64Encode(JSON.stringify(response))
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
function createSettlementHeaders(settlement) {
|
|
153
|
-
return {
|
|
154
|
-
[V2_HEADERS.PAYMENT_RESPONSE]: encodeSettlementResponse(settlement)
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
function createNonce() {
|
|
158
|
-
const bytes = randomBytes(32);
|
|
159
|
-
return `0x${bytes.toString("hex")}`;
|
|
160
|
-
}
|
|
161
|
-
function toAtomicUnits(amount, decimals = 6) {
|
|
162
|
-
const parts = amount.split(".");
|
|
163
|
-
const whole = parts[0];
|
|
164
|
-
const fractional = parts[1] || "";
|
|
165
|
-
const paddedFractional = fractional.padEnd(decimals, "0").slice(0, decimals);
|
|
166
|
-
return `${whole}${paddedFractional}`;
|
|
167
|
-
}
|
|
168
|
-
function fromAtomicUnits(amount, decimals = 6) {
|
|
169
|
-
const value = BigInt(amount);
|
|
170
|
-
const divisor = BigInt(10 ** decimals);
|
|
171
|
-
const whole = (value / divisor).toString();
|
|
172
|
-
const fractional = (value % divisor).toString().padStart(decimals, "0").replace(/0+$/, "");
|
|
173
|
-
if (fractional.length === 0) {
|
|
174
|
-
return whole;
|
|
175
|
-
}
|
|
176
|
-
return `${whole}.${fractional}`;
|
|
177
|
-
}
|
|
178
|
-
function caip2ToNetwork(caip2Id) {
|
|
179
|
-
const match = caip2Id.match(/^eip155:(\d+)$/);
|
|
180
|
-
if (!match) {
|
|
181
|
-
return caip2Id;
|
|
182
|
-
}
|
|
183
|
-
const chainId = parseInt(match[1], 10);
|
|
184
|
-
const chainIdToNetwork = {
|
|
185
|
-
1: "ethereum",
|
|
186
|
-
8453: "base",
|
|
187
|
-
84532: "base-sepolia",
|
|
188
|
-
137: "polygon",
|
|
189
|
-
42161: "arbitrum",
|
|
190
|
-
421614: "arbitrum-sepolia",
|
|
191
|
-
10: "optimism",
|
|
192
|
-
11155420: "optimism-sepolia",
|
|
193
|
-
11155111: "ethereum-sepolia"
|
|
194
|
-
};
|
|
195
|
-
return chainIdToNetwork[chainId] || caip2Id;
|
|
196
|
-
}
|
|
197
|
-
function networkToCaip2(network) {
|
|
198
|
-
const networkToChainId = {
|
|
199
|
-
ethereum: 1,
|
|
200
|
-
base: 8453,
|
|
201
|
-
"base-sepolia": 84532,
|
|
202
|
-
polygon: 137,
|
|
203
|
-
arbitrum: 42161,
|
|
204
|
-
"arbitrum-sepolia": 421614,
|
|
205
|
-
optimism: 10,
|
|
206
|
-
"optimism-sepolia": 11155420,
|
|
207
|
-
"ethereum-sepolia": 11155111
|
|
208
|
-
};
|
|
209
|
-
const chainId = networkToChainId[network];
|
|
210
|
-
if (chainId) {
|
|
211
|
-
return `eip155:${chainId}`;
|
|
212
|
-
}
|
|
213
|
-
if (network.startsWith("eip155:")) {
|
|
214
|
-
return network;
|
|
215
|
-
}
|
|
216
|
-
return `eip155:0`;
|
|
217
|
-
}
|
|
218
|
-
function getCurrentTimestamp() {
|
|
219
|
-
return Math.floor(Date.now() / 1e3);
|
|
220
|
-
}
|
|
221
|
-
function isValidAddress(address) {
|
|
222
|
-
return /^0x[a-fA-F0-9]{40}$/.test(address);
|
|
223
|
-
}
|
|
224
|
-
function normalizeAddress(address) {
|
|
225
|
-
return address.toLowerCase();
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// src/types/protocol.ts
|
|
229
|
-
function isX402V2Payload(obj) {
|
|
230
|
-
return typeof obj === "object" && obj !== null && "x402Version" in obj && obj.x402Version === 2 && "accepted" in obj && "payload" in obj;
|
|
231
|
-
}
|
|
232
|
-
function isX402V2Requirements(obj) {
|
|
233
|
-
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
|
|
234
|
-
"amount" in obj && "asset" in obj && "payTo" in obj && "maxTimeoutSeconds" in obj;
|
|
235
|
-
}
|
|
236
|
-
function isX402V2Settlement(obj) {
|
|
237
|
-
return typeof obj === "object" && obj !== null && "success" in obj && typeof obj.success === "boolean" && "network" in obj && typeof obj.network === "string" && obj.network.startsWith("eip155:");
|
|
238
|
-
}
|
|
239
|
-
function isX402V2PaymentRequired(obj) {
|
|
240
|
-
return typeof obj === "object" && obj !== null && "x402Version" in obj && obj.x402Version === 2 && "resource" in obj && "accepts" in obj;
|
|
241
|
-
}
|
|
242
|
-
var PAYMENT_SIGNATURE_HEADER = "PAYMENT-SIGNATURE";
|
|
243
|
-
var PAYMENT_RESPONSE_HEADER = "PAYMENT-RESPONSE";
|
|
244
|
-
var PAYMENT_REQUIRED_HEADER = "PAYMENT-REQUIRED";
|
|
245
|
-
function isSettlementSuccessful(response) {
|
|
246
|
-
return "success" in response ? response.success : false;
|
|
247
|
-
}
|
|
248
|
-
function getTxHash(response) {
|
|
249
|
-
if ("transaction" in response) return response.transaction;
|
|
250
|
-
return void 0;
|
|
537
|
+
return safeBase64Encode2(JSON.stringify(response));
|
|
251
538
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
ethereum: {
|
|
259
|
-
name: "Ethereum Mainnet",
|
|
260
|
-
chainId: 1,
|
|
261
|
-
usdcAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
262
|
-
rpcUrl: "https://eth.llamarpc.com",
|
|
263
|
-
caip2Id: "eip155:1",
|
|
264
|
-
caipAssetId: "eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
|
265
|
-
},
|
|
266
|
-
base: {
|
|
267
|
-
name: "Base Mainnet",
|
|
268
|
-
chainId: 8453,
|
|
269
|
-
usdcAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
270
|
-
rpcUrl: "https://mainnet.base.org",
|
|
271
|
-
caip2Id: "eip155:8453",
|
|
272
|
-
caipAssetId: "eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
|
|
273
|
-
},
|
|
274
|
-
"base-sepolia": {
|
|
275
|
-
name: "Base Sepolia",
|
|
276
|
-
chainId: 84532,
|
|
277
|
-
usdcAddress: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
278
|
-
rpcUrl: "https://sepolia.base.org",
|
|
279
|
-
caip2Id: "eip155:84532",
|
|
280
|
-
caipAssetId: "eip155:84532/erc20:0x036CbD53842c5426634e7929541eC2318f3dCF7e"
|
|
281
|
-
},
|
|
282
|
-
"skale-base": {
|
|
283
|
-
name: "SKALE Base",
|
|
284
|
-
chainId: 1187947933,
|
|
285
|
-
usdcAddress: "0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20",
|
|
286
|
-
rpcUrl: "https://skale-base.skalenodes.com/v1/base",
|
|
287
|
-
caip2Id: "eip155:1187947933",
|
|
288
|
-
caipAssetId: "eip155:1187947933/erc20:0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20"
|
|
289
|
-
},
|
|
290
|
-
"skale-base-sepolia": {
|
|
291
|
-
name: "SKALE Base Sepolia",
|
|
292
|
-
chainId: 324705682,
|
|
293
|
-
usdcAddress: "0x2e08028E3C4c2356572E096d8EF835cD5C6030bD",
|
|
294
|
-
rpcUrl: "https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha",
|
|
295
|
-
caip2Id: "eip155:324705682",
|
|
296
|
-
caipAssetId: "eip155:324705682/erc20:0x2e08028E3C4c2356572E096d8EF835cD5C6030bD"
|
|
297
|
-
},
|
|
298
|
-
"ethereum-sepolia": {
|
|
299
|
-
name: "Ethereum Sepolia",
|
|
300
|
-
chainId: 11155111,
|
|
301
|
-
usdcAddress: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
|
|
302
|
-
rpcUrl: "https://rpc.sepolia.org",
|
|
303
|
-
caip2Id: "eip155:11155111",
|
|
304
|
-
caipAssetId: "eip155:11155111/erc20:0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"
|
|
305
|
-
}
|
|
306
|
-
};
|
|
307
|
-
var getNetworkConfig = (name) => NETWORKS[name];
|
|
308
|
-
var getNetworkByChainId = (chainId) => Object.values(NETWORKS).find((c) => c.chainId === chainId);
|
|
309
|
-
var getMainnets = () => Object.values(NETWORKS).filter((c) => !c.name.toLowerCase().includes("sepolia"));
|
|
310
|
-
var getTestnets = () => Object.values(NETWORKS).filter((c) => c.name.toLowerCase().includes("sepolia"));
|
|
311
|
-
var registerToken = (token) => {
|
|
312
|
-
if (typeof token !== "object" || token === null) {
|
|
313
|
-
throw new Error("Invalid token: must be an object");
|
|
314
|
-
}
|
|
315
|
-
const t = token;
|
|
316
|
-
if (typeof t.symbol !== "string") {
|
|
317
|
-
throw new Error("Invalid token: symbol must be a string");
|
|
318
|
-
}
|
|
319
|
-
if (typeof t.name !== "string") {
|
|
320
|
-
throw new Error("Invalid token: name must be a string");
|
|
321
|
-
}
|
|
322
|
-
if (typeof t.version !== "string") {
|
|
323
|
-
throw new Error("Invalid token: version must be a string");
|
|
324
|
-
}
|
|
325
|
-
if (typeof t.contractAddress !== "string" || !isEvmAddress(t.contractAddress)) {
|
|
326
|
-
throw new Error("Invalid token: contractAddress must be a valid EVM address");
|
|
539
|
+
function decodeX402Response(encoded) {
|
|
540
|
+
try {
|
|
541
|
+
return JSON.parse(encoded);
|
|
542
|
+
} catch {
|
|
543
|
+
const decoded = safeBase64Decode2(encoded);
|
|
544
|
+
return JSON.parse(decoded);
|
|
327
545
|
}
|
|
328
|
-
|
|
329
|
-
|
|
546
|
+
}
|
|
547
|
+
function detectPaymentVersion(headers) {
|
|
548
|
+
if (headers.has("PAYMENT-SIGNATURE")) {
|
|
549
|
+
return 2;
|
|
330
550
|
}
|
|
331
|
-
|
|
332
|
-
|
|
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;
|
|
333
560
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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 ?? {}
|
|
341
570
|
};
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
};
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
+
}
|
|
349
580
|
|
|
350
|
-
// src/
|
|
351
|
-
var
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
};
|
|
359
|
-
var EURC_BASE = {
|
|
360
|
-
symbol: "EURC",
|
|
361
|
-
name: "EURC",
|
|
362
|
-
version: "2",
|
|
363
|
-
contractAddress: "0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42",
|
|
364
|
-
chainId: 8453,
|
|
365
|
-
decimals: 6
|
|
366
|
-
};
|
|
367
|
-
var USDC_BASE_SEPOLIA = {
|
|
368
|
-
symbol: "USDC",
|
|
369
|
-
name: "USDC",
|
|
370
|
-
version: "2",
|
|
371
|
-
contractAddress: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
372
|
-
chainId: 84532,
|
|
373
|
-
decimals: 6
|
|
374
|
-
};
|
|
375
|
-
var USDC_SKALE_BASE = {
|
|
376
|
-
symbol: "USDC",
|
|
377
|
-
name: "Bridged USDC (SKALE Bridge)",
|
|
378
|
-
version: "2",
|
|
379
|
-
contractAddress: "0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20",
|
|
380
|
-
chainId: 1187947933,
|
|
381
|
-
decimals: 6
|
|
382
|
-
};
|
|
383
|
-
var SKL_SKALE_BASE = {
|
|
384
|
-
symbol: "SKL",
|
|
385
|
-
name: "SKALE",
|
|
386
|
-
version: "1",
|
|
387
|
-
contractAddress: "0xE0595a049d02b7674572b0d59cd4880Db60EDC50",
|
|
388
|
-
chainId: 1187947933,
|
|
389
|
-
decimals: 18
|
|
390
|
-
};
|
|
391
|
-
var USDT_SKALE_BASE = {
|
|
392
|
-
symbol: "USDT",
|
|
393
|
-
name: "Tether USD",
|
|
394
|
-
version: "1",
|
|
395
|
-
contractAddress: "0x2bF5bF154b515EaA82C31a65ec11554fF5aF7fCA",
|
|
396
|
-
chainId: 1187947933,
|
|
397
|
-
decimals: 6
|
|
398
|
-
};
|
|
399
|
-
var WBTC_SKALE_BASE = {
|
|
400
|
-
symbol: "WBTC",
|
|
401
|
-
name: "Wrapped BTC",
|
|
402
|
-
version: "1",
|
|
403
|
-
contractAddress: "0x1aeeCFE5454c83B42D8A316246CAc9739E7f690e",
|
|
404
|
-
chainId: 1187947933,
|
|
405
|
-
decimals: 8
|
|
406
|
-
};
|
|
407
|
-
var WETH_SKALE_BASE = {
|
|
408
|
-
symbol: "WETH",
|
|
409
|
-
name: "Wrapped Ether",
|
|
410
|
-
version: "1",
|
|
411
|
-
contractAddress: "0x7bD39ABBd0Dd13103542cAe3276C7fA332bCA486",
|
|
412
|
-
chainId: 1187947933,
|
|
413
|
-
decimals: 18
|
|
414
|
-
};
|
|
415
|
-
var SKL_SKALE_BASE_SEPOLIA = {
|
|
416
|
-
symbol: "SKL",
|
|
417
|
-
name: "SKALE",
|
|
418
|
-
version: "1",
|
|
419
|
-
contractAddress: "0xaf2e0ff5b5f51553fdb34ce7f04a6c3201cee57b",
|
|
420
|
-
chainId: 324705682,
|
|
421
|
-
decimals: 18
|
|
422
|
-
};
|
|
423
|
-
var USDC_SKALE_BASE_SEPOLIA = {
|
|
424
|
-
symbol: "USDC",
|
|
425
|
-
name: "Bridged USDC (SKALE Bridge)",
|
|
426
|
-
version: "2",
|
|
427
|
-
contractAddress: "0x2e08028E3C4c2356572E096d8EF835cD5C6030bD",
|
|
428
|
-
chainId: 324705682,
|
|
429
|
-
decimals: 6
|
|
430
|
-
};
|
|
431
|
-
var USDT_SKALE_BASE_SEPOLIA = {
|
|
432
|
-
symbol: "USDT",
|
|
433
|
-
name: "Tether USD",
|
|
434
|
-
version: "1",
|
|
435
|
-
contractAddress: "0x3ca0a49f511c2c89c4dcbbf1731120d8919050bf",
|
|
436
|
-
chainId: 324705682,
|
|
437
|
-
decimals: 6
|
|
438
|
-
};
|
|
439
|
-
var WBTC_SKALE_BASE_SEPOLIA = {
|
|
440
|
-
symbol: "WBTC",
|
|
441
|
-
name: "Wrapped BTC",
|
|
442
|
-
version: "1",
|
|
443
|
-
contractAddress: "0x4512eacd4186b025186e1cf6cc0d89497c530e87",
|
|
444
|
-
chainId: 324705682,
|
|
445
|
-
decimals: 8
|
|
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
|
+
}
|
|
446
589
|
};
|
|
447
|
-
var
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
chainId: 324705682,
|
|
453
|
-
decimals: 18
|
|
590
|
+
var SigningError = class extends X402ClientError {
|
|
591
|
+
constructor(message, cause) {
|
|
592
|
+
super(`Signing failed: ${message}`, cause);
|
|
593
|
+
this.name = "SigningError";
|
|
594
|
+
}
|
|
454
595
|
};
|
|
455
|
-
var
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
USDT_SKALE_BASE,
|
|
461
|
-
WBTC_SKALE_BASE,
|
|
462
|
-
WETH_SKALE_BASE,
|
|
463
|
-
SKL_SKALE_BASE,
|
|
464
|
-
SKL_SKALE_BASE_SEPOLIA,
|
|
465
|
-
USDC_SKALE_BASE_SEPOLIA,
|
|
466
|
-
USDT_SKALE_BASE_SEPOLIA,
|
|
467
|
-
WBTC_SKALE_BASE_SEPOLIA,
|
|
468
|
-
WETH_SKALE_BASE_SEPOLIA
|
|
596
|
+
var PaymentException = class extends X402ClientError {
|
|
597
|
+
constructor(message, cause) {
|
|
598
|
+
super(`Payment failed: ${message}`, cause);
|
|
599
|
+
this.name = "PaymentException";
|
|
600
|
+
}
|
|
469
601
|
};
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
)
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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);
|
|
478
621
|
}
|
|
479
|
-
function
|
|
480
|
-
return
|
|
622
|
+
function resolveUrl(config) {
|
|
623
|
+
return config?.url ?? DEFAULT_FACILITATOR_URL;
|
|
481
624
|
}
|
|
482
|
-
function
|
|
483
|
-
|
|
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 };
|
|
484
630
|
}
|
|
485
|
-
function
|
|
486
|
-
|
|
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();
|
|
487
647
|
}
|
|
488
|
-
function
|
|
489
|
-
|
|
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();
|
|
490
664
|
}
|
|
491
|
-
function
|
|
492
|
-
|
|
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();
|
|
493
676
|
}
|
|
494
|
-
function
|
|
495
|
-
|
|
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);
|
|
496
681
|
}
|
|
497
|
-
function
|
|
498
|
-
|
|
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
|
+
}
|
|
499
722
|
}
|
|
500
|
-
function
|
|
501
|
-
|
|
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");
|
|
502
728
|
}
|
|
503
729
|
|
|
504
|
-
// src/
|
|
505
|
-
var
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
{ name: "v", type: "uint8" },
|
|
517
|
-
{ name: "r", type: "bytes32" },
|
|
518
|
-
{ name: "s", type: "bytes32" }
|
|
519
|
-
],
|
|
520
|
-
outputs: []
|
|
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"
|
|
521
742
|
},
|
|
522
|
-
{
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
{ name: "amount", type: "uint256" },
|
|
530
|
-
{ name: "validAfter", type: "uint256" },
|
|
531
|
-
{ name: "expiry", type: "uint256" },
|
|
532
|
-
{ name: "v", type: "uint8" },
|
|
533
|
-
{ name: "r", type: "bytes32" },
|
|
534
|
-
{ name: "s", type: "bytes32" }
|
|
535
|
-
],
|
|
536
|
-
outputs: []
|
|
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"
|
|
537
750
|
},
|
|
538
|
-
{
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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"
|
|
544
758
|
},
|
|
545
|
-
{
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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"
|
|
551
766
|
},
|
|
552
|
-
{
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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"
|
|
558
782
|
}
|
|
559
|
-
];
|
|
560
|
-
|
|
561
|
-
// src/eip712.ts
|
|
562
|
-
var EIP712_TYPES = {
|
|
563
|
-
TransferWithAuthorization: [
|
|
564
|
-
{ name: "from", type: "address" },
|
|
565
|
-
{ name: "to", type: "address" },
|
|
566
|
-
{ name: "value", type: "uint256" },
|
|
567
|
-
{ name: "validAfter", type: "uint256" },
|
|
568
|
-
{ name: "validBefore", type: "uint256" },
|
|
569
|
-
{ name: "nonce", type: "bytes32" }
|
|
570
|
-
]
|
|
571
783
|
};
|
|
572
|
-
var
|
|
573
|
-
|
|
574
|
-
|
|
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;
|
|
575
830
|
};
|
|
576
|
-
var
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
if (
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
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}`;
|
|
600
890
|
}
|
|
601
|
-
if (
|
|
602
|
-
|
|
603
|
-
}
|
|
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
|
+
}
|
|
604
905
|
|
|
605
906
|
// src/validation.ts
|
|
606
907
|
var isEvmAddress2 = (value) => /^0x[a-fA-F0-9]{40}$/.test(value);
|
|
@@ -614,7 +915,9 @@ var resolveNetwork = (input) => {
|
|
|
614
915
|
if (typeof input === "object" && input !== null && "chainId" in input) {
|
|
615
916
|
const config = input;
|
|
616
917
|
if (!config.chainId || !config.usdcAddress || !config.caip2Id) {
|
|
617
|
-
return createError("INVALID_CAIP_FORMAT", "Invalid network config", {
|
|
918
|
+
return createError("INVALID_CAIP_FORMAT", "Invalid network config", {
|
|
919
|
+
value: config
|
|
920
|
+
});
|
|
618
921
|
}
|
|
619
922
|
return {
|
|
620
923
|
input,
|
|
@@ -626,11 +929,10 @@ var resolveNetwork = (input) => {
|
|
|
626
929
|
const config = getNetworkByChainId(input);
|
|
627
930
|
if (!config) {
|
|
628
931
|
const validChainIds = Object.values(NETWORKS).map((n) => n.chainId);
|
|
629
|
-
return createError(
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
);
|
|
932
|
+
return createError("UNKNOWN_NETWORK", `Unknown chain ID: ${input}`, {
|
|
933
|
+
value: input,
|
|
934
|
+
validOptions: validChainIds.map(String)
|
|
935
|
+
});
|
|
634
936
|
}
|
|
635
937
|
return {
|
|
636
938
|
input,
|
|
@@ -641,13 +943,21 @@ var resolveNetwork = (input) => {
|
|
|
641
943
|
if (typeof input === "string") {
|
|
642
944
|
if (input.startsWith("eip155:")) {
|
|
643
945
|
const parts = input.split(":");
|
|
644
|
-
if (parts.length !== 2 || isNaN(Number(parts[1]))) {
|
|
645
|
-
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
|
+
);
|
|
646
952
|
}
|
|
647
953
|
const chainId = Number(parts[1]);
|
|
648
954
|
const config2 = getNetworkByChainId(chainId);
|
|
649
955
|
if (!config2) {
|
|
650
|
-
return createError(
|
|
956
|
+
return createError(
|
|
957
|
+
"UNKNOWN_NETWORK",
|
|
958
|
+
`Unknown CAIP-2 network: ${input}`,
|
|
959
|
+
{ value: input }
|
|
960
|
+
);
|
|
651
961
|
}
|
|
652
962
|
return {
|
|
653
963
|
input,
|
|
@@ -659,11 +969,10 @@ var resolveNetwork = (input) => {
|
|
|
659
969
|
const config = getNetworkConfig(normalizedName);
|
|
660
970
|
if (!config) {
|
|
661
971
|
const validNames = Object.keys(NETWORKS);
|
|
662
|
-
return createError(
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
);
|
|
972
|
+
return createError("UNKNOWN_NETWORK", `Unknown network: "${input}"`, {
|
|
973
|
+
value: input,
|
|
974
|
+
validOptions: validNames
|
|
975
|
+
});
|
|
667
976
|
}
|
|
668
977
|
return {
|
|
669
978
|
input,
|
|
@@ -671,7 +980,11 @@ var resolveNetwork = (input) => {
|
|
|
671
980
|
caip2: config.caip2Id
|
|
672
981
|
};
|
|
673
982
|
}
|
|
674
|
-
return createError(
|
|
983
|
+
return createError(
|
|
984
|
+
"UNKNOWN_NETWORK",
|
|
985
|
+
`Invalid network identifier type: ${typeof input}`,
|
|
986
|
+
{ value: input }
|
|
987
|
+
);
|
|
675
988
|
};
|
|
676
989
|
var getAvailableNetworks = () => Object.keys(NETWORKS);
|
|
677
990
|
var normalizeTokenSymbol = (symbol) => symbol.toUpperCase();
|
|
@@ -679,7 +992,9 @@ var resolveToken = (input, network) => {
|
|
|
679
992
|
if (typeof input === "object" && input !== null && "contractAddress" in input) {
|
|
680
993
|
const config = input;
|
|
681
994
|
if (!config.chainId || !config.contractAddress || !config.symbol) {
|
|
682
|
-
return createError("VALIDATION_FAILED", "Invalid token config", {
|
|
995
|
+
return createError("VALIDATION_FAILED", "Invalid token config", {
|
|
996
|
+
value: config
|
|
997
|
+
});
|
|
683
998
|
}
|
|
684
999
|
if (network && config.chainId !== network.config.chainId) {
|
|
685
1000
|
return createError(
|
|
@@ -704,7 +1019,10 @@ var resolveToken = (input, network) => {
|
|
|
704
1019
|
if (isEvmAddress2(input)) {
|
|
705
1020
|
const contractAddress = input;
|
|
706
1021
|
if (network) {
|
|
707
|
-
const customToken = getCustomToken(
|
|
1022
|
+
const customToken = getCustomToken(
|
|
1023
|
+
network.config.chainId,
|
|
1024
|
+
contractAddress
|
|
1025
|
+
);
|
|
708
1026
|
const config = customToken || {
|
|
709
1027
|
symbol: "CUSTOM",
|
|
710
1028
|
name: "Custom Token",
|
|
@@ -747,7 +1065,11 @@ var resolveToken = (input, network) => {
|
|
|
747
1065
|
if (input.includes("/erc20:")) {
|
|
748
1066
|
const parts = input.split("/");
|
|
749
1067
|
if (parts.length !== 2) {
|
|
750
|
-
return createError(
|
|
1068
|
+
return createError(
|
|
1069
|
+
"INVALID_CAIP_FORMAT",
|
|
1070
|
+
`Invalid CAIP Asset ID format: ${input}`,
|
|
1071
|
+
{ value: input }
|
|
1072
|
+
);
|
|
751
1073
|
}
|
|
752
1074
|
const chainId = Number(parts[0].split(":")[1]);
|
|
753
1075
|
const contractAddress = parts[1].split(":")[1];
|
|
@@ -789,7 +1111,7 @@ var resolveToken = (input, network) => {
|
|
|
789
1111
|
input,
|
|
790
1112
|
config: {
|
|
791
1113
|
symbol: normalizedSymbol,
|
|
792
|
-
name: network.config.name
|
|
1114
|
+
name: `${network.config.name} ${normalizedSymbol}`,
|
|
793
1115
|
version: "2",
|
|
794
1116
|
chainId: network.config.chainId,
|
|
795
1117
|
contractAddress: network.config.usdcAddress,
|
|
@@ -800,7 +1122,9 @@ var resolveToken = (input, network) => {
|
|
|
800
1122
|
};
|
|
801
1123
|
}
|
|
802
1124
|
const customTokens = getAllCustomTokens();
|
|
803
|
-
const matchingToken = customTokens.find(
|
|
1125
|
+
const matchingToken = customTokens.find(
|
|
1126
|
+
(t) => t.symbol?.toUpperCase() === normalizedSymbol
|
|
1127
|
+
);
|
|
804
1128
|
if (matchingToken) {
|
|
805
1129
|
const tokenNetwork = resolveNetwork(matchingToken.chainId);
|
|
806
1130
|
if ("code" in tokenNetwork) {
|
|
@@ -831,7 +1155,11 @@ var resolveToken = (input, network) => {
|
|
|
831
1155
|
network: baseNetwork
|
|
832
1156
|
};
|
|
833
1157
|
}
|
|
834
|
-
return createError(
|
|
1158
|
+
return createError(
|
|
1159
|
+
"UNKNOWN_TOKEN",
|
|
1160
|
+
`Invalid token identifier type: ${typeof input}`,
|
|
1161
|
+
{ value: input }
|
|
1162
|
+
);
|
|
835
1163
|
};
|
|
836
1164
|
var getAvailableTokens = () => {
|
|
837
1165
|
const customTokens = getAllCustomTokens();
|
|
@@ -842,7 +1170,11 @@ var resolveFacilitator = (input, supportedNetworks, supportedTokens) => {
|
|
|
842
1170
|
try {
|
|
843
1171
|
new URL(input.url);
|
|
844
1172
|
} catch {
|
|
845
|
-
return createError(
|
|
1173
|
+
return createError(
|
|
1174
|
+
"VALIDATION_FAILED",
|
|
1175
|
+
`Invalid facilitator URL: ${input.url}`,
|
|
1176
|
+
{ value: input.url }
|
|
1177
|
+
);
|
|
846
1178
|
}
|
|
847
1179
|
const networks = [];
|
|
848
1180
|
if (input.networks) {
|
|
@@ -900,12 +1232,18 @@ var checkFacilitatorSupport = (facilitator, network, token) => {
|
|
|
900
1232
|
(t) => t.config.contractAddress.toLowerCase() === token.config.contractAddress.toLowerCase() && t.network.config.chainId === token.network.config.chainId
|
|
901
1233
|
);
|
|
902
1234
|
if (!tokenSupported) {
|
|
903
|
-
const supportedTokens = facilitator.tokens.map(
|
|
1235
|
+
const supportedTokens = facilitator.tokens.map(
|
|
1236
|
+
(t) => `${t.config.symbol} (${t.network.config.name})`
|
|
1237
|
+
);
|
|
904
1238
|
return createError(
|
|
905
1239
|
"FACILITATOR_NO_TOKEN_SUPPORT",
|
|
906
1240
|
`Facilitator "${facilitator.url}" does not support token "${token.config.symbol}" on "${token.network.config.name}". Supported: ${supportedTokens.join(", ")}`,
|
|
907
1241
|
{
|
|
908
|
-
value: {
|
|
1242
|
+
value: {
|
|
1243
|
+
facilitator: facilitator.url,
|
|
1244
|
+
token: token.config.symbol,
|
|
1245
|
+
network: token.network.config.name
|
|
1246
|
+
},
|
|
909
1247
|
validOptions: supportedTokens
|
|
910
1248
|
}
|
|
911
1249
|
);
|
|
@@ -926,11 +1264,19 @@ var validatePaymentConfig = (network, token, facilitators, payTo = "0x0000000000
|
|
|
926
1264
|
if (facilitators) {
|
|
927
1265
|
const facilitatorArray = Array.isArray(facilitators) ? facilitators : [facilitators];
|
|
928
1266
|
for (const facilitator of facilitatorArray) {
|
|
929
|
-
const resolved = resolveFacilitator(
|
|
1267
|
+
const resolved = resolveFacilitator(
|
|
1268
|
+
facilitator,
|
|
1269
|
+
[resolvedNetwork],
|
|
1270
|
+
[resolvedToken]
|
|
1271
|
+
);
|
|
930
1272
|
if ("code" in resolved) {
|
|
931
1273
|
return resolved;
|
|
932
1274
|
}
|
|
933
|
-
const supportCheck = checkFacilitatorSupport(
|
|
1275
|
+
const supportCheck = checkFacilitatorSupport(
|
|
1276
|
+
resolved,
|
|
1277
|
+
resolvedNetwork,
|
|
1278
|
+
resolvedToken
|
|
1279
|
+
);
|
|
934
1280
|
if ("code" in supportCheck) {
|
|
935
1281
|
return supportCheck;
|
|
936
1282
|
}
|
|
@@ -947,7 +1293,12 @@ var validatePaymentConfig = (network, token, facilitators, payTo = "0x0000000000
|
|
|
947
1293
|
};
|
|
948
1294
|
};
|
|
949
1295
|
var validateAcceptConfig = (options, payTo, amount) => {
|
|
950
|
-
const {
|
|
1296
|
+
const {
|
|
1297
|
+
networks: networkInputs,
|
|
1298
|
+
tokens: tokenInputs,
|
|
1299
|
+
facilitators,
|
|
1300
|
+
version = 2
|
|
1301
|
+
} = options;
|
|
951
1302
|
const networkIds = networkInputs?.length ? networkInputs : Object.keys(NETWORKS);
|
|
952
1303
|
const tokenIds = tokenInputs?.length ? tokenInputs : ["usdc"];
|
|
953
1304
|
const networks = [];
|
|
@@ -960,17 +1311,16 @@ var validateAcceptConfig = (options, payTo, amount) => {
|
|
|
960
1311
|
}
|
|
961
1312
|
const tokens = [];
|
|
962
1313
|
for (const tokenId of tokenIds) {
|
|
963
|
-
let
|
|
1314
|
+
let matches = 0;
|
|
964
1315
|
for (const network of networks) {
|
|
965
1316
|
const resolved = resolveToken(tokenId, network);
|
|
966
1317
|
if ("code" in resolved) {
|
|
967
1318
|
continue;
|
|
968
1319
|
}
|
|
969
1320
|
tokens.push(resolved);
|
|
970
|
-
|
|
971
|
-
break;
|
|
1321
|
+
matches += 1;
|
|
972
1322
|
}
|
|
973
|
-
if (
|
|
1323
|
+
if (matches === 0) {
|
|
974
1324
|
return {
|
|
975
1325
|
success: false,
|
|
976
1326
|
error: createError(
|
|
@@ -1017,6 +1367,227 @@ var isResolvedToken = (value) => {
|
|
|
1017
1367
|
return typeof value === "object" && value !== null && "config" in value && "caipAsset" in value;
|
|
1018
1368
|
};
|
|
1019
1369
|
|
|
1370
|
+
// src/payment-requirements.ts
|
|
1371
|
+
var DEFAULT_NETWORKS = [
|
|
1372
|
+
"ethereum",
|
|
1373
|
+
"base",
|
|
1374
|
+
"base-sepolia",
|
|
1375
|
+
"skale-base",
|
|
1376
|
+
"skale-base-sepolia",
|
|
1377
|
+
"ethereum-sepolia"
|
|
1378
|
+
];
|
|
1379
|
+
var DEFAULT_TOKENS = ["usdc"];
|
|
1380
|
+
var isValidationError2 = (value) => {
|
|
1381
|
+
return typeof value === "object" && value !== null && "code" in value;
|
|
1382
|
+
};
|
|
1383
|
+
function resolvePayTo(config, network, token) {
|
|
1384
|
+
const chainId = network.config.chainId;
|
|
1385
|
+
if (config.payToByToken) {
|
|
1386
|
+
for (const [chainKey, tokenMap] of Object.entries(config.payToByToken)) {
|
|
1387
|
+
const resolvedChain = resolveNetwork(chainKey);
|
|
1388
|
+
if (!isValidationError2(resolvedChain) && resolvedChain.config.chainId === chainId) {
|
|
1389
|
+
for (const [tokenKey2, address] of Object.entries(tokenMap)) {
|
|
1390
|
+
const resolvedToken = resolveToken(tokenKey2, network);
|
|
1391
|
+
if (!isValidationError2(resolvedToken) && resolvedToken.config.contractAddress.toLowerCase() === token.config.contractAddress.toLowerCase()) {
|
|
1392
|
+
return address;
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
if (config.payToByChain) {
|
|
1399
|
+
for (const [chainKey, address] of Object.entries(config.payToByChain)) {
|
|
1400
|
+
const resolvedChain = resolveNetwork(chainKey);
|
|
1401
|
+
if (!isValidationError2(resolvedChain) && resolvedChain.config.chainId === chainId) {
|
|
1402
|
+
return address;
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
return config.payTo;
|
|
1407
|
+
}
|
|
1408
|
+
function resolveFacilitatorUrl(config, network, token) {
|
|
1409
|
+
const chainId = network.config.chainId;
|
|
1410
|
+
if (config.facilitatorUrlByToken) {
|
|
1411
|
+
for (const [chainKey, tokenMap] of Object.entries(
|
|
1412
|
+
config.facilitatorUrlByToken
|
|
1413
|
+
)) {
|
|
1414
|
+
const resolvedChain = resolveNetwork(chainKey);
|
|
1415
|
+
if (!isValidationError2(resolvedChain) && resolvedChain.config.chainId === chainId) {
|
|
1416
|
+
for (const [tokenKey2, url] of Object.entries(tokenMap)) {
|
|
1417
|
+
const resolvedToken = resolveToken(tokenKey2, network);
|
|
1418
|
+
if (!isValidationError2(resolvedToken) && resolvedToken.config.contractAddress.toLowerCase() === token.config.contractAddress.toLowerCase()) {
|
|
1419
|
+
return url;
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
if (config.facilitatorUrlByChain) {
|
|
1426
|
+
for (const [chainKey, url] of Object.entries(
|
|
1427
|
+
config.facilitatorUrlByChain
|
|
1428
|
+
)) {
|
|
1429
|
+
const resolvedChain = resolveNetwork(chainKey);
|
|
1430
|
+
if (!isValidationError2(resolvedChain) && resolvedChain.config.chainId === chainId) {
|
|
1431
|
+
return url;
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
return config.facilitatorUrl;
|
|
1436
|
+
}
|
|
1437
|
+
function resolveNetworks(chainInputs) {
|
|
1438
|
+
const resolvedNetworks = [];
|
|
1439
|
+
const errors = [];
|
|
1440
|
+
const networks = chainInputs?.length ? chainInputs : DEFAULT_NETWORKS;
|
|
1441
|
+
for (const networkId of networks) {
|
|
1442
|
+
const resolved = resolveNetwork(networkId);
|
|
1443
|
+
if (isValidationError2(resolved)) {
|
|
1444
|
+
errors.push(`Network "${networkId}": ${resolved.message}`);
|
|
1445
|
+
} else {
|
|
1446
|
+
resolvedNetworks.push(resolved);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
if (errors.length > 0) {
|
|
1450
|
+
return {
|
|
1451
|
+
networks: resolvedNetworks,
|
|
1452
|
+
error: {
|
|
1453
|
+
code: "VALIDATION_FAILED",
|
|
1454
|
+
message: errors.join("; ")
|
|
1455
|
+
}
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
return { networks: resolvedNetworks };
|
|
1459
|
+
}
|
|
1460
|
+
function createPaymentRequirements(config) {
|
|
1461
|
+
const {
|
|
1462
|
+
payTo,
|
|
1463
|
+
chains,
|
|
1464
|
+
chain,
|
|
1465
|
+
tokens,
|
|
1466
|
+
token,
|
|
1467
|
+
amount = "1.0",
|
|
1468
|
+
maxTimeoutSeconds = 300
|
|
1469
|
+
} = config;
|
|
1470
|
+
const chainInputs = chain ? [chain] : chains;
|
|
1471
|
+
const tokenInputs = token ? [token] : tokens;
|
|
1472
|
+
const { networks, error: networksError } = resolveNetworks(chainInputs);
|
|
1473
|
+
if (networksError) {
|
|
1474
|
+
return { requirements: [], error: networksError };
|
|
1475
|
+
}
|
|
1476
|
+
const requirements = [];
|
|
1477
|
+
for (const network of networks) {
|
|
1478
|
+
const tokensToResolve = tokenInputs?.length ? tokenInputs : DEFAULT_TOKENS;
|
|
1479
|
+
for (const tokenId of tokensToResolve) {
|
|
1480
|
+
const resolvedToken = resolveToken(tokenId, network);
|
|
1481
|
+
if (isValidationError2(resolvedToken)) {
|
|
1482
|
+
continue;
|
|
1483
|
+
}
|
|
1484
|
+
const atomicAmount = toAtomicUnits(amount);
|
|
1485
|
+
const tokenConfig = resolvedToken.config;
|
|
1486
|
+
const resolvedPayTo = resolvePayTo(config, network, resolvedToken);
|
|
1487
|
+
resolveFacilitatorUrl(
|
|
1488
|
+
config,
|
|
1489
|
+
network,
|
|
1490
|
+
resolvedToken
|
|
1491
|
+
);
|
|
1492
|
+
const requirement = {
|
|
1493
|
+
scheme: "exact",
|
|
1494
|
+
network: network.caip2,
|
|
1495
|
+
amount: atomicAmount,
|
|
1496
|
+
asset: tokenConfig.contractAddress,
|
|
1497
|
+
payTo: resolvedPayTo,
|
|
1498
|
+
maxTimeoutSeconds,
|
|
1499
|
+
extra: {
|
|
1500
|
+
name: tokenConfig.name,
|
|
1501
|
+
version: tokenConfig.version
|
|
1502
|
+
}
|
|
1503
|
+
};
|
|
1504
|
+
requirements.push(requirement);
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
if (requirements.length === 0) {
|
|
1508
|
+
return {
|
|
1509
|
+
requirements: [],
|
|
1510
|
+
error: {
|
|
1511
|
+
code: "VALIDATION_FAILED",
|
|
1512
|
+
message: "No valid network/token combinations found"
|
|
1513
|
+
}
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
return { requirements };
|
|
1517
|
+
}
|
|
1518
|
+
function findRequirementByNetwork(requirements, network) {
|
|
1519
|
+
const normalized = normalizeNetworkName(network);
|
|
1520
|
+
const netConfig = getNetworkConfig(normalized);
|
|
1521
|
+
if (!netConfig) return void 0;
|
|
1522
|
+
return requirements.find((r) => r.network === netConfig.caip2Id);
|
|
1523
|
+
}
|
|
1524
|
+
function findRequirementByAccepted(requirements, accepted) {
|
|
1525
|
+
const acceptedPayTo = typeof accepted.payTo === "string" ? accepted.payTo.toLowerCase() : "";
|
|
1526
|
+
const exactMatch = requirements.find(
|
|
1527
|
+
(requirement) => requirement.scheme === accepted.scheme && requirement.network === accepted.network && requirement.amount === accepted.amount && requirement.asset.toLowerCase() === accepted.asset.toLowerCase() && typeof requirement.payTo === "string" && requirement.payTo.toLowerCase() === acceptedPayTo
|
|
1528
|
+
);
|
|
1529
|
+
if (exactMatch) {
|
|
1530
|
+
return exactMatch;
|
|
1531
|
+
}
|
|
1532
|
+
const payToAwareMatch = requirements.find(
|
|
1533
|
+
(requirement) => requirement.scheme === accepted.scheme && requirement.network === accepted.network && acceptedPayTo.length > 0 && typeof requirement.payTo === "string" && requirement.payTo.toLowerCase() === acceptedPayTo
|
|
1534
|
+
);
|
|
1535
|
+
if (payToAwareMatch) {
|
|
1536
|
+
return payToAwareMatch;
|
|
1537
|
+
}
|
|
1538
|
+
return requirements.find(
|
|
1539
|
+
(requirement) => requirement.scheme === accepted.scheme && requirement.network === accepted.network
|
|
1540
|
+
);
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
// src/protocol.ts
|
|
1544
|
+
function parseJsonOrBase64(value) {
|
|
1545
|
+
try {
|
|
1546
|
+
return JSON.parse(value);
|
|
1547
|
+
} catch {
|
|
1548
|
+
const normalized = normalizeBase64Url(value);
|
|
1549
|
+
return JSON.parse(decodeBase64ToUtf8(normalized));
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
function detectX402Version(_response) {
|
|
1553
|
+
return 2;
|
|
1554
|
+
}
|
|
1555
|
+
function getPaymentHeaderName(_version) {
|
|
1556
|
+
return V2_HEADERS.PAYMENT_SIGNATURE;
|
|
1557
|
+
}
|
|
1558
|
+
function generateNonce() {
|
|
1559
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1560
|
+
return `0x${(now * 1e3).toString(16).padStart(64, "0")}`;
|
|
1561
|
+
}
|
|
1562
|
+
function calculateValidBefore(expirySeconds = 3600) {
|
|
1563
|
+
return Math.floor(Date.now() / 1e3) + expirySeconds;
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
// src/types/protocol.ts
|
|
1567
|
+
function isX402V2Payload(obj) {
|
|
1568
|
+
return typeof obj === "object" && obj !== null && "x402Version" in obj && obj.x402Version === 2 && "accepted" in obj && "payload" in obj;
|
|
1569
|
+
}
|
|
1570
|
+
function isX402V2Requirements(obj) {
|
|
1571
|
+
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
|
|
1572
|
+
"amount" in obj && "asset" in obj && "payTo" in obj && "maxTimeoutSeconds" in obj;
|
|
1573
|
+
}
|
|
1574
|
+
function isX402V2Settlement(obj) {
|
|
1575
|
+
return typeof obj === "object" && obj !== null && "success" in obj && typeof obj.success === "boolean" && "network" in obj && typeof obj.network === "string" && obj.network.startsWith("eip155:");
|
|
1576
|
+
}
|
|
1577
|
+
function isX402V2PaymentRequired(obj) {
|
|
1578
|
+
return typeof obj === "object" && obj !== null && "x402Version" in obj && obj.x402Version === 2 && "resource" in obj && "accepts" in obj;
|
|
1579
|
+
}
|
|
1580
|
+
var PAYMENT_SIGNATURE_HEADER = "PAYMENT-SIGNATURE";
|
|
1581
|
+
var PAYMENT_RESPONSE_HEADER = "PAYMENT-RESPONSE";
|
|
1582
|
+
var PAYMENT_REQUIRED_HEADER = "PAYMENT-REQUIRED";
|
|
1583
|
+
function isSettlementSuccessful(response) {
|
|
1584
|
+
return "success" in response ? response.success : false;
|
|
1585
|
+
}
|
|
1586
|
+
function getTxHash(response) {
|
|
1587
|
+
if ("transaction" in response) return response.transaction;
|
|
1588
|
+
return void 0;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1020
1591
|
// src/utils/routes.ts
|
|
1021
1592
|
var PRIORITY_EXACT = 3;
|
|
1022
1593
|
var PRIORITY_PARAMETRIZED = 2;
|
|
@@ -1083,7 +1654,7 @@ function matchSegment(patternSegment, pathSegment) {
|
|
|
1083
1654
|
}
|
|
1084
1655
|
if (patternSegment.includes("*")) {
|
|
1085
1656
|
const regex = new RegExp(
|
|
1086
|
-
|
|
1657
|
+
`^${patternSegment.replace(/\*/g, ".*").replace(/:/g, "")}$`
|
|
1087
1658
|
);
|
|
1088
1659
|
return regex.test(pathSegment);
|
|
1089
1660
|
}
|
|
@@ -1096,7 +1667,7 @@ function matchWildcardPattern(patternSegments, pathSegments) {
|
|
|
1096
1667
|
}
|
|
1097
1668
|
for (let i = 0; i < requiredSegments.length; i++) {
|
|
1098
1669
|
const patternIndex = patternSegments.indexOf(requiredSegments[i]);
|
|
1099
|
-
if (pathSegments[patternIndex] !== requiredSegments[i].replace(
|
|
1670
|
+
if (pathSegments[patternIndex] !== requiredSegments[i].replace(/^:/, "")) {
|
|
1100
1671
|
if (!requiredSegments[i].startsWith(":") && requiredSegments[i] !== "*") {
|
|
1101
1672
|
return false;
|
|
1102
1673
|
}
|
|
@@ -1173,152 +1744,13 @@ function validateRouteConfig(config) {
|
|
|
1173
1744
|
message: `Wildcard routes must use the routes array, not 'route'. Use 'routes: ["/api/*"]' instead of 'route: "/api/*"'.`,
|
|
1174
1745
|
path: "route",
|
|
1175
1746
|
value: route,
|
|
1176
|
-
validOptions: [
|
|
1747
|
+
validOptions: [
|
|
1748
|
+
'routes: ["/api/*"]',
|
|
1749
|
+
'routes: ["/api/users", "/api/posts"]'
|
|
1750
|
+
]
|
|
1177
1751
|
};
|
|
1178
1752
|
}
|
|
1179
1753
|
return null;
|
|
1180
1754
|
}
|
|
1181
1755
|
|
|
1182
|
-
|
|
1183
|
-
function safeBase64Decode2(str) {
|
|
1184
|
-
const padding = 4 - str.length % 4;
|
|
1185
|
-
if (padding !== 4) {
|
|
1186
|
-
str += "=".repeat(padding);
|
|
1187
|
-
}
|
|
1188
|
-
str = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
1189
|
-
return Buffer.from(str, "base64").toString("utf-8");
|
|
1190
|
-
}
|
|
1191
|
-
function safeBase64Encode2(str) {
|
|
1192
|
-
return Buffer.from(str).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
1193
|
-
}
|
|
1194
|
-
var base64JsonEncode = (data) => safeBase64Encode2(JSON.stringify(data));
|
|
1195
|
-
var base64JsonDecode = (encoded) => JSON.parse(safeBase64Decode2(encoded));
|
|
1196
|
-
var encodePaymentV2 = (payload) => base64JsonEncode(payload);
|
|
1197
|
-
var decodePaymentV2 = (encoded) => base64JsonDecode(encoded);
|
|
1198
|
-
var encodeSettlementV2 = (response) => base64JsonEncode(response);
|
|
1199
|
-
var decodeSettlementV2 = (encoded) => base64JsonDecode(encoded);
|
|
1200
|
-
var isPaymentV2 = (payload) => "x402Version" in payload && payload.x402Version === 2 && "accepted" in payload && "payload" in payload;
|
|
1201
|
-
var isSettlementV2 = (response) => "success" in response && typeof response.success === "boolean" && "network" in response;
|
|
1202
|
-
|
|
1203
|
-
// src/payment-client.ts
|
|
1204
|
-
var DEFAULT_FACILITATOR_URL = "https://facilitator.payai.network";
|
|
1205
|
-
function toJsonSafe(data) {
|
|
1206
|
-
function convert(value) {
|
|
1207
|
-
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
1208
|
-
return Object.fromEntries(Object.entries(value).map(([key, val]) => [key, convert(val)]));
|
|
1209
|
-
}
|
|
1210
|
-
if (Array.isArray(value)) {
|
|
1211
|
-
return value.map(convert);
|
|
1212
|
-
}
|
|
1213
|
-
if (typeof value === "bigint") {
|
|
1214
|
-
return value.toString();
|
|
1215
|
-
}
|
|
1216
|
-
return value;
|
|
1217
|
-
}
|
|
1218
|
-
return convert(data);
|
|
1219
|
-
}
|
|
1220
|
-
function resolveUrl(config) {
|
|
1221
|
-
return config?.url ?? DEFAULT_FACILITATOR_URL;
|
|
1222
|
-
}
|
|
1223
|
-
async function resolveHeaders(config) {
|
|
1224
|
-
const base = { "Content-Type": "application/json" };
|
|
1225
|
-
if (!config?.createHeaders) return base;
|
|
1226
|
-
const extra = await config.createHeaders();
|
|
1227
|
-
return { ...base, ...extra };
|
|
1228
|
-
}
|
|
1229
|
-
async function verifyPayment(payload, requirements, config) {
|
|
1230
|
-
const url = resolveUrl(config);
|
|
1231
|
-
const headers = await resolveHeaders(config);
|
|
1232
|
-
const response = await fetch(`${url}/verify`, {
|
|
1233
|
-
method: "POST",
|
|
1234
|
-
headers,
|
|
1235
|
-
body: JSON.stringify({
|
|
1236
|
-
paymentPayload: toJsonSafe(payload),
|
|
1237
|
-
paymentRequirements: toJsonSafe(requirements)
|
|
1238
|
-
})
|
|
1239
|
-
});
|
|
1240
|
-
if (response.status !== 200) {
|
|
1241
|
-
const text = await response.text().catch(() => response.statusText);
|
|
1242
|
-
throw new Error(`Facilitator verify failed: ${response.status} ${text}`);
|
|
1243
|
-
}
|
|
1244
|
-
return await response.json();
|
|
1245
|
-
}
|
|
1246
|
-
async function settlePayment(payload, requirements, config) {
|
|
1247
|
-
const url = resolveUrl(config);
|
|
1248
|
-
const headers = await resolveHeaders(config);
|
|
1249
|
-
const response = await fetch(`${url}/settle`, {
|
|
1250
|
-
method: "POST",
|
|
1251
|
-
headers,
|
|
1252
|
-
body: JSON.stringify({
|
|
1253
|
-
paymentPayload: toJsonSafe(payload),
|
|
1254
|
-
paymentRequirements: toJsonSafe(requirements)
|
|
1255
|
-
})
|
|
1256
|
-
});
|
|
1257
|
-
if (response.status !== 200) {
|
|
1258
|
-
const text = await response.text().catch(() => response.statusText);
|
|
1259
|
-
throw new Error(`Facilitator settle failed: ${response.status} ${text}`);
|
|
1260
|
-
}
|
|
1261
|
-
return await response.json();
|
|
1262
|
-
}
|
|
1263
|
-
async function getSupported(config) {
|
|
1264
|
-
const url = resolveUrl(config);
|
|
1265
|
-
const headers = await resolveHeaders(config);
|
|
1266
|
-
const response = await fetch(`${url}/supported`, {
|
|
1267
|
-
method: "GET",
|
|
1268
|
-
headers
|
|
1269
|
-
});
|
|
1270
|
-
if (response.status !== 200) {
|
|
1271
|
-
throw new Error(`Facilitator supported failed: ${response.statusText}`);
|
|
1272
|
-
}
|
|
1273
|
-
return await response.json();
|
|
1274
|
-
}
|
|
1275
|
-
function isCompactV2Payload(payload) {
|
|
1276
|
-
if (typeof payload !== "object" || payload === null) return false;
|
|
1277
|
-
const record = payload;
|
|
1278
|
-
return typeof record.x402Version === "number" && "payload" in record && !("accepted" in record);
|
|
1279
|
-
}
|
|
1280
|
-
function decodePayloadHeader(headerValue, defaults) {
|
|
1281
|
-
if (headerValue.startsWith("{")) {
|
|
1282
|
-
const parsed = JSON.parse(headerValue);
|
|
1283
|
-
if (isPaymentPayload(parsed)) return parsed;
|
|
1284
|
-
if (isCompactV2Payload(parsed)) {
|
|
1285
|
-
const compact = parsed;
|
|
1286
|
-
if (!defaults?.accepted) {
|
|
1287
|
-
throw new Error("Invalid payment payload: missing 'accepted' field and no defaults provided");
|
|
1288
|
-
}
|
|
1289
|
-
return {
|
|
1290
|
-
x402Version: 2,
|
|
1291
|
-
accepted: defaults.accepted,
|
|
1292
|
-
payload: compact.payload
|
|
1293
|
-
};
|
|
1294
|
-
}
|
|
1295
|
-
throw new Error("Invalid payment payload: unrecognized format");
|
|
1296
|
-
}
|
|
1297
|
-
const normalized = headerValue.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(headerValue.length / 4) * 4, "=");
|
|
1298
|
-
try {
|
|
1299
|
-
const decoded = JSON.parse(Buffer.from(normalized, "base64").toString("utf-8"));
|
|
1300
|
-
if (isPaymentPayload(decoded)) return decoded;
|
|
1301
|
-
if (isCompactV2Payload(decoded)) {
|
|
1302
|
-
const compact = decoded;
|
|
1303
|
-
if (!defaults?.accepted) {
|
|
1304
|
-
throw new Error("Invalid payment payload: missing 'accepted' field and no defaults provided");
|
|
1305
|
-
}
|
|
1306
|
-
return {
|
|
1307
|
-
x402Version: 2,
|
|
1308
|
-
accepted: defaults.accepted,
|
|
1309
|
-
payload: compact.payload
|
|
1310
|
-
};
|
|
1311
|
-
}
|
|
1312
|
-
throw new Error("Invalid payment payload: unrecognized format");
|
|
1313
|
-
} catch {
|
|
1314
|
-
throw new Error("Invalid payment payload: failed to decode");
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
function extractPayerAddress(payload) {
|
|
1318
|
-
if (isExactEvmPayload(payload.payload)) {
|
|
1319
|
-
return payload.payload.authorization.from;
|
|
1320
|
-
}
|
|
1321
|
-
throw new Error("Unable to extract payer address from payload");
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
export { EIP712_TYPES, ERC20_ABI, EURC_BASE, NETWORKS, PAYMENT_REQUIRED_HEADER, PAYMENT_RESPONSE_HEADER, PAYMENT_SIGNATURE_HEADER, SCHEMES, SKL_SKALE_BASE, SKL_SKALE_BASE_SEPOLIA, 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, X402_VERSION, addressToAssetId, assetIdToAddress, caip2ToNetwork, checkFacilitatorSupport, combineSignature as combineSignatureV2, createEIP712Domain, createError, createNonce, createPaymentRequiredHeaders, createSettlementHeaders, createTransferWithAuthorization, decodePayloadHeader, decodePayment, decodePaymentV2, decodeSettlementResponse, decodeSettlementV2, decodeX402Response, detectPaymentVersion, encodePayment, encodePaymentV2, encodeSettlementResponse, encodeSettlementV2, encodeX402Response, extractPayerAddress, extractPaymentFromHeaders, findMatchingRoute, fromAtomicUnits, getAllCustomTokens, getAllTokens, getAvailableNetworks, getAvailableTokens, getCurrentTimestamp, getCustomToken, getEURCTokens, getMainnets, getNetworkByChainId, getNetworkConfig, getSKLTokens, getSupported, getTestnets, getToken, getTokensByChain, getTokensBySymbol, getTxHash, getUSDCTokens, getUSDTTokens, getWBTCTokens, getWETHTokens, isAddress, isCAIP2ChainId, isCAIPAssetId, isCustomToken, isExactEvmPayload, isPaymentPayload, isPaymentPayloadV2, isPaymentRequiredV2, isPaymentV2, isResolvedNetwork, isResolvedToken, isSettlementSuccessful, isSettlementV2, isValidAddress, isValidationError, isX402V2Payload, isX402V2PaymentRequired, isX402V2Requirements, isX402V2Settlement, matchRoute, networkToCaip2, normalizeAddress, normalizeNetworkName, parseRoutePattern, parseSignature as parseSignatureV2, registerToken, resolveFacilitator, resolveNetwork, resolveToken, safeBase64Decode, safeBase64Encode, settlePayment, toAtomicUnits, unregisterToken, validateAcceptConfig, validatePaymentConfig, validateRouteConfig, validateTransferWithAuthorization, verifyPayment };
|
|
1756
|
+
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, findRequirementByAccepted, 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 };
|