x402-mantle-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,617 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // src/server/constants.ts
12
+ var constants_exports = {};
13
+ __export(constants_exports, {
14
+ AMOUNT_TOLERANCE: () => AMOUNT_TOLERANCE,
15
+ DEFAULT_PLATFORM_URL: () => DEFAULT_PLATFORM_URL,
16
+ ERC20_TRANSFER_SIGNATURE: () => ERC20_TRANSFER_SIGNATURE,
17
+ NETWORKS: () => NETWORKS,
18
+ TOKENS: () => TOKENS,
19
+ getAvailableNetworks: () => getAvailableNetworks,
20
+ getChainId: () => getChainId,
21
+ getNetworkConfig: () => getNetworkConfig,
22
+ getNetworksByEnvironment: () => getNetworksByEnvironment,
23
+ getRpcUrl: () => getRpcUrl,
24
+ getTokenConfig: () => getTokenConfig,
25
+ isMainnet: () => isMainnet,
26
+ isTestnet: () => isTestnet,
27
+ registerCustomNetwork: () => registerCustomNetwork,
28
+ registerCustomTokens: () => registerCustomTokens
29
+ });
30
+ function registerCustomNetwork(networkId, config) {
31
+ customNetworks.set(networkId.toLowerCase(), {
32
+ chainId: config.chainId,
33
+ rpcUrl: config.rpcUrl,
34
+ name: config.name || networkId,
35
+ environment: config.environment || "testnet",
36
+ nativeCurrency: { name: "Native", symbol: "ETH", decimals: 18 },
37
+ blockExplorer: config.blockExplorer || ""
38
+ });
39
+ }
40
+ function registerCustomTokens(networkId, tokens) {
41
+ const networkKey = networkId.toLowerCase();
42
+ if (!customTokens.has(networkKey)) {
43
+ customTokens.set(networkKey, /* @__PURE__ */ new Map());
44
+ }
45
+ const tokenMap = customTokens.get(networkKey);
46
+ for (const [symbol, config] of Object.entries(tokens)) {
47
+ tokenMap.set(symbol.toUpperCase(), {
48
+ address: config.address,
49
+ decimals: config.decimals,
50
+ symbol: symbol.toUpperCase()
51
+ });
52
+ }
53
+ }
54
+ function isTestnet(network) {
55
+ const config = getNetworkConfig(network);
56
+ return config.environment === "testnet";
57
+ }
58
+ function isMainnet(network) {
59
+ const config = getNetworkConfig(network);
60
+ return config.environment === "mainnet";
61
+ }
62
+ function getNetworkConfig(network) {
63
+ const networkKey = network.toLowerCase();
64
+ const custom = customNetworks.get(networkKey);
65
+ if (custom) {
66
+ return custom;
67
+ }
68
+ const preset = NETWORKS[networkKey];
69
+ if (preset) {
70
+ return preset;
71
+ }
72
+ return NETWORKS.mantle;
73
+ }
74
+ function getTokenConfig(token, network) {
75
+ const networkKey = network.toLowerCase();
76
+ const tokenKey = token.toUpperCase();
77
+ if (tokenKey === "MNT") {
78
+ return null;
79
+ }
80
+ const customMap = customTokens.get(networkKey);
81
+ if (customMap?.has(tokenKey)) {
82
+ return customMap.get(tokenKey);
83
+ }
84
+ return TOKENS[networkKey]?.[tokenKey] || null;
85
+ }
86
+ function getRpcUrl(network, customRpcUrl) {
87
+ if (customRpcUrl) {
88
+ return customRpcUrl;
89
+ }
90
+ const envRpc = process.env.X402_RPC_URL || process.env[`X402_RPC_URL_${network.toUpperCase()}`];
91
+ if (envRpc) {
92
+ return envRpc;
93
+ }
94
+ return getNetworkConfig(network).rpcUrl;
95
+ }
96
+ function getChainId(network) {
97
+ return getNetworkConfig(network).chainId;
98
+ }
99
+ function getAvailableNetworks() {
100
+ const preset = Object.keys(NETWORKS);
101
+ const custom = Array.from(customNetworks.keys());
102
+ return [.../* @__PURE__ */ new Set([...preset, ...custom])];
103
+ }
104
+ function getNetworksByEnvironment(env) {
105
+ return getAvailableNetworks().filter((network) => {
106
+ const config = getNetworkConfig(network);
107
+ return config.environment === env;
108
+ });
109
+ }
110
+ var NETWORKS, TOKENS, ERC20_TRANSFER_SIGNATURE, DEFAULT_PLATFORM_URL, AMOUNT_TOLERANCE, customNetworks, customTokens;
111
+ var init_constants = __esm({
112
+ "src/server/constants.ts"() {
113
+ "use strict";
114
+ NETWORKS = {
115
+ mantle: {
116
+ chainId: 5e3,
117
+ rpcUrl: "https://rpc.mantle.xyz",
118
+ name: "Mantle",
119
+ environment: "mainnet",
120
+ nativeCurrency: { name: "Mantle", symbol: "MNT", decimals: 18 },
121
+ blockExplorer: "https://explorer.mantle.xyz"
122
+ },
123
+ "mantle-sepolia": {
124
+ chainId: 5003,
125
+ rpcUrl: "https://rpc.sepolia.mantle.xyz",
126
+ name: "Mantle Sepolia",
127
+ environment: "testnet",
128
+ nativeCurrency: { name: "Mantle", symbol: "MNT", decimals: 18 },
129
+ blockExplorer: "https://explorer.sepolia.mantle.xyz"
130
+ },
131
+ "mantle-testnet": {
132
+ chainId: 5003,
133
+ rpcUrl: "https://rpc.sepolia.mantle.xyz",
134
+ name: "Mantle Testnet",
135
+ environment: "testnet",
136
+ nativeCurrency: { name: "Mantle", symbol: "MNT", decimals: 18 },
137
+ blockExplorer: "https://explorer.sepolia.mantle.xyz"
138
+ }
139
+ };
140
+ TOKENS = {
141
+ mantle: {
142
+ USDC: {
143
+ address: "0x09Bc4E0D864854c6aFB6eB9A9cdF58aC190D0dF9",
144
+ decimals: 6,
145
+ symbol: "USDC"
146
+ },
147
+ USDT: {
148
+ address: "0x201EBa5CC46D216Ce6DC03F6a759e8E766e956aE",
149
+ decimals: 6,
150
+ symbol: "USDT"
151
+ },
152
+ mETH: {
153
+ address: "0xcDA86A272531e8640cD7F1a92c01839911B90bb0",
154
+ decimals: 18,
155
+ symbol: "mETH"
156
+ },
157
+ WMNT: {
158
+ address: "0x78c1b0C915c4FAA5FffA6CAbf0219DA63d7f4cb8",
159
+ decimals: 18,
160
+ symbol: "WMNT"
161
+ }
162
+ },
163
+ "mantle-sepolia": {
164
+ USDC: {
165
+ address: "0x09Bc4E0D864854c6aFB6eB9A9cdF58aC190D0dF9",
166
+ decimals: 6,
167
+ symbol: "USDC"
168
+ },
169
+ mETH: {
170
+ address: "0xdEAddEaDdeadDEadDEADDEAddEADDEAddead1111",
171
+ decimals: 18,
172
+ symbol: "mETH"
173
+ },
174
+ WMNT: {
175
+ address: "0x78c1b0C915c4FAA5FffA6CAbf0219DA63d7f4cb8",
176
+ decimals: 18,
177
+ symbol: "WMNT"
178
+ }
179
+ },
180
+ "mantle-testnet": {
181
+ USDC: {
182
+ address: "0x09Bc4E0D864854c6aFB6eB9A9cdF58aC190D0dF9",
183
+ decimals: 6,
184
+ symbol: "USDC"
185
+ },
186
+ mETH: {
187
+ address: "0xdEAddEaDdeadDEadDEADDEAddEADDEAddead1111",
188
+ decimals: 18,
189
+ symbol: "mETH"
190
+ },
191
+ WMNT: {
192
+ address: "0x78c1b0C915c4FAA5FffA6CAbf0219DA63d7f4cb8",
193
+ decimals: 18,
194
+ symbol: "WMNT"
195
+ }
196
+ }
197
+ };
198
+ ERC20_TRANSFER_SIGNATURE = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
199
+ DEFAULT_PLATFORM_URL = "https://api.x402.dev";
200
+ AMOUNT_TOLERANCE = BigInt(1e15);
201
+ customNetworks = /* @__PURE__ */ new Map();
202
+ customTokens = /* @__PURE__ */ new Map();
203
+ }
204
+ });
205
+
206
+ // src/server/platform.ts
207
+ init_constants();
208
+ var cachedConfig = null;
209
+ var validationPromise = null;
210
+ function getPlatformBaseUrl() {
211
+ return process.env.X402_PLATFORM_URL || process.env.NEXT_PUBLIC_X402_PLATFORM_URL || DEFAULT_PLATFORM_URL;
212
+ }
213
+ async function validateProject(appId) {
214
+ const baseUrl = getPlatformBaseUrl();
215
+ const url = `${baseUrl}/v1/validate?appId=${encodeURIComponent(appId)}`;
216
+ const response = await fetch(url, {
217
+ method: "GET",
218
+ headers: { "Content-Type": "application/json" }
219
+ });
220
+ if (!response.ok) {
221
+ if (response.status === 404) {
222
+ throw new Error("Project not found: Invalid X402_APP_ID");
223
+ }
224
+ if (response.status === 401) {
225
+ throw new Error("Unauthorized: Invalid X402_APP_ID");
226
+ }
227
+ throw new Error(`Platform validation failed: ${response.status} ${response.statusText}`);
228
+ }
229
+ const data = await response.json();
230
+ if (!data.appId || !data.payTo || !data.network) {
231
+ throw new Error("Invalid platform response: missing required fields");
232
+ }
233
+ if (data.status !== "ACTIVE") {
234
+ throw new Error(`Project is not active: status is ${data.status}`);
235
+ }
236
+ return {
237
+ appId: data.appId,
238
+ name: data.name,
239
+ payTo: data.payTo,
240
+ network: data.network,
241
+ status: data.status
242
+ };
243
+ }
244
+ async function initializePlatform() {
245
+ if (cachedConfig) {
246
+ return cachedConfig;
247
+ }
248
+ if (validationPromise) {
249
+ return validationPromise;
250
+ }
251
+ const appId = process.env.X402_APP_ID || process.env.NEXT_PUBLIC_X402_APP_ID;
252
+ if (!appId || typeof appId !== "string" || appId.trim().length === 0) {
253
+ throw new Error("X402_APP_ID is required. Set it in your environment variables.");
254
+ }
255
+ validationPromise = validateProject(appId.trim());
256
+ try {
257
+ cachedConfig = await validationPromise;
258
+ return cachedConfig;
259
+ } catch (error) {
260
+ validationPromise = null;
261
+ throw error;
262
+ }
263
+ }
264
+ function getProjectConfig() {
265
+ if (!cachedConfig) {
266
+ throw new Error("Platform not initialized. Call initializePlatform() first.");
267
+ }
268
+ return cachedConfig;
269
+ }
270
+ function clearCache() {
271
+ cachedConfig = null;
272
+ validationPromise = null;
273
+ }
274
+
275
+ // src/server/blockchain.ts
276
+ init_constants();
277
+ async function callRPC(url, method, params) {
278
+ const response = await fetch(url, {
279
+ method: "POST",
280
+ headers: { "Content-Type": "application/json" },
281
+ body: JSON.stringify({
282
+ jsonrpc: "2.0",
283
+ id: 1,
284
+ method,
285
+ params
286
+ })
287
+ });
288
+ if (!response.ok) {
289
+ throw new Error(`RPC call failed: ${response.status} ${response.statusText}`);
290
+ }
291
+ const data = await response.json();
292
+ if (data.error) {
293
+ throw new Error(`RPC error: ${data.error.message}`);
294
+ }
295
+ return data.result;
296
+ }
297
+ async function getTransactionReceipt(rpcUrl, txHash) {
298
+ const receipt = await callRPC(
299
+ rpcUrl,
300
+ "eth_getTransactionReceipt",
301
+ [txHash]
302
+ );
303
+ if (!receipt) {
304
+ return null;
305
+ }
306
+ return {
307
+ transactionHash: receipt.transactionHash,
308
+ blockNumber: parseInt(receipt.blockNumber, 16),
309
+ status: receipt.status === "0x1" ? "success" : "failed",
310
+ from: receipt.from,
311
+ to: receipt.to,
312
+ value: receipt.value || "0x0",
313
+ logs: receipt.logs || []
314
+ };
315
+ }
316
+ function decodeERC20Transfer(log) {
317
+ if (log.topics[0]?.toLowerCase() !== ERC20_TRANSFER_SIGNATURE.toLowerCase()) {
318
+ return null;
319
+ }
320
+ if (log.topics.length !== 3) {
321
+ return null;
322
+ }
323
+ return {
324
+ from: ("0x" + log.topics[1].slice(-40)).toLowerCase(),
325
+ to: ("0x" + log.topics[2].slice(-40)).toLowerCase(),
326
+ value: BigInt(log.data),
327
+ tokenAddress: log.address.toLowerCase()
328
+ };
329
+ }
330
+ function weiToTokenUnits(wei, decimals = 18) {
331
+ const divisor = 10n ** BigInt(decimals);
332
+ const whole = wei / divisor;
333
+ const remainder = wei % divisor;
334
+ if (remainder === 0n) {
335
+ return whole.toString();
336
+ }
337
+ const remainderStr = remainder.toString().padStart(decimals, "0");
338
+ const trimmed = remainderStr.replace(/0+$/, "");
339
+ return `${whole}.${trimmed}`;
340
+ }
341
+ function parseAmountToWei(amount, decimals = 18) {
342
+ const parts = amount.split(".");
343
+ const whole = parts[0] || "0";
344
+ const fraction = (parts[1] || "").padEnd(decimals, "0").slice(0, decimals);
345
+ return BigInt(whole) * 10n ** BigInt(decimals) + BigInt(fraction);
346
+ }
347
+ async function verifyPaymentOnChain(transactionHash, config, requiredAmount, requiredToken, customRpcUrl) {
348
+ try {
349
+ const rpcUrl = getRpcUrl(config.network, customRpcUrl);
350
+ const receipt = await getTransactionReceipt(rpcUrl, transactionHash);
351
+ if (!receipt) {
352
+ return {
353
+ valid: false,
354
+ error: "Transaction not found or not yet confirmed"
355
+ };
356
+ }
357
+ if (receipt.status !== "success") {
358
+ return {
359
+ valid: false,
360
+ error: "Transaction failed"
361
+ };
362
+ }
363
+ const recipient = config.payTo.toLowerCase();
364
+ if (requiredToken.toUpperCase() === "MNT") {
365
+ const valueWei = BigInt(receipt.value);
366
+ const requiredWei2 = parseAmountToWei(requiredAmount, 18);
367
+ if (receipt.to?.toLowerCase() !== recipient) {
368
+ return {
369
+ valid: false,
370
+ error: `Recipient mismatch: expected ${config.payTo}, got ${receipt.to}`
371
+ };
372
+ }
373
+ if (valueWei < requiredWei2 - AMOUNT_TOLERANCE || valueWei > requiredWei2 + AMOUNT_TOLERANCE) {
374
+ return {
375
+ valid: false,
376
+ error: `Amount mismatch: expected ${requiredAmount} MNT, got ${weiToTokenUnits(valueWei)} MNT`
377
+ };
378
+ }
379
+ return {
380
+ valid: true,
381
+ transactionHash: receipt.transactionHash,
382
+ amount: weiToTokenUnits(valueWei),
383
+ token: "MNT",
384
+ blockNumber: receipt.blockNumber
385
+ };
386
+ }
387
+ const tokenConfig = getTokenConfig(requiredToken, config.network);
388
+ if (!tokenConfig) {
389
+ return {
390
+ valid: false,
391
+ error: `Unknown token: ${requiredToken}`
392
+ };
393
+ }
394
+ let transfer = null;
395
+ for (const log of receipt.logs) {
396
+ if (log.address.toLowerCase() !== tokenConfig.address.toLowerCase()) {
397
+ continue;
398
+ }
399
+ const decoded = decodeERC20Transfer(log);
400
+ if (decoded && decoded.to === recipient) {
401
+ transfer = decoded;
402
+ break;
403
+ }
404
+ }
405
+ if (!transfer) {
406
+ return {
407
+ valid: false,
408
+ error: `No ${requiredToken} transfer found to ${config.payTo}`
409
+ };
410
+ }
411
+ const requiredWei = parseAmountToWei(requiredAmount, tokenConfig.decimals);
412
+ const toleranceAdjusted = AMOUNT_TOLERANCE / 10n ** BigInt(18 - tokenConfig.decimals);
413
+ if (transfer.value < requiredWei - toleranceAdjusted || transfer.value > requiredWei + toleranceAdjusted) {
414
+ return {
415
+ valid: false,
416
+ error: `Amount mismatch: expected ${requiredAmount} ${requiredToken}, got ${weiToTokenUnits(transfer.value, tokenConfig.decimals)} ${requiredToken}`
417
+ };
418
+ }
419
+ return {
420
+ valid: true,
421
+ transactionHash: receipt.transactionHash,
422
+ amount: weiToTokenUnits(transfer.value, tokenConfig.decimals),
423
+ token: requiredToken,
424
+ blockNumber: receipt.blockNumber
425
+ };
426
+ } catch (error) {
427
+ return {
428
+ valid: false,
429
+ error: error instanceof Error ? error.message : "Failed to verify payment on blockchain"
430
+ };
431
+ }
432
+ }
433
+
434
+ // src/server/verify.ts
435
+ async function verifyPayment(receipt, config, requiredAmount, requiredToken, _useBlockchain = true, customRpcUrl) {
436
+ const result = await verifyPaymentOnChain(
437
+ receipt.transactionHash,
438
+ config,
439
+ requiredAmount,
440
+ requiredToken,
441
+ customRpcUrl
442
+ );
443
+ return {
444
+ valid: result.valid,
445
+ transactionHash: result.transactionHash,
446
+ amount: result.amount,
447
+ token: result.token,
448
+ error: result.error
449
+ };
450
+ }
451
+ function extractPaymentReceipt(headers) {
452
+ const getHeader = (name) => {
453
+ if (headers instanceof Headers) {
454
+ return headers.get(name);
455
+ }
456
+ const value = headers[name.toLowerCase()] || headers[name];
457
+ return Array.isArray(value) ? value[0] || null : value || null;
458
+ };
459
+ const transactionHash = getHeader("x-402-transaction-hash") || getHeader("X-402-Transaction-Hash");
460
+ if (!transactionHash) {
461
+ return null;
462
+ }
463
+ return {
464
+ transactionHash,
465
+ timestamp: getHeader("x-402-timestamp") || getHeader("X-402-Timestamp") || void 0
466
+ };
467
+ }
468
+
469
+ // src/server/middleware.ts
470
+ init_constants();
471
+ function resolveNetwork(options, projectNetwork) {
472
+ if (options.network) {
473
+ return options.network;
474
+ }
475
+ if (options.testnet) {
476
+ return "mantle-sepolia";
477
+ }
478
+ return projectNetwork;
479
+ }
480
+ function createPaymentRequiredResponse(options, config, chainId) {
481
+ const network = resolveNetwork(options, config.network);
482
+ return {
483
+ status: 402,
484
+ headers: {
485
+ "Content-Type": "application/json",
486
+ "X-402-Amount": options.price,
487
+ "X-402-Token": options.token,
488
+ "X-402-Network": network,
489
+ "X-402-Chain-Id": chainId.toString(),
490
+ "X-402-Recipient": config.payTo
491
+ },
492
+ body: {
493
+ error: "Payment Required",
494
+ amount: options.price,
495
+ token: options.token,
496
+ network,
497
+ chainId,
498
+ recipient: config.payTo
499
+ }
500
+ };
501
+ }
502
+ async function processPaymentMiddleware(options, headers) {
503
+ try {
504
+ if (options.customNetwork) {
505
+ const networkId = options.network || "custom-network";
506
+ registerCustomNetwork(networkId, options.customNetwork);
507
+ }
508
+ if (options.customTokens) {
509
+ const config2 = getProjectConfig();
510
+ const network2 = resolveNetwork(options, config2.network);
511
+ registerCustomTokens(network2, options.customTokens);
512
+ }
513
+ const config = getProjectConfig();
514
+ const network = resolveNetwork(options, config.network);
515
+ const { getChainId: getChainId3 } = await Promise.resolve().then(() => (init_constants(), constants_exports));
516
+ const chainId = getChainId3(network);
517
+ const receipt = extractPaymentReceipt(headers);
518
+ if (!receipt) {
519
+ return {
520
+ allowed: false,
521
+ paymentRequired: createPaymentRequiredResponse(options, config, chainId)
522
+ };
523
+ }
524
+ const verification = await verifyPayment(
525
+ receipt,
526
+ { ...config, network },
527
+ options.price,
528
+ options.token,
529
+ true,
530
+ options.rpcUrl
531
+ );
532
+ if (!verification.valid) {
533
+ return {
534
+ allowed: false,
535
+ error: {
536
+ status: 402,
537
+ message: verification.error || "Payment verification failed"
538
+ }
539
+ };
540
+ }
541
+ return { allowed: true };
542
+ } catch (error) {
543
+ return {
544
+ allowed: false,
545
+ error: {
546
+ status: 500,
547
+ message: error instanceof Error ? error.message : "Internal server error"
548
+ }
549
+ };
550
+ }
551
+ }
552
+
553
+ // src/server/hono.ts
554
+ function x402(options) {
555
+ if (!options.price || !options.token) {
556
+ throw new Error("x402 middleware requires price and token options");
557
+ }
558
+ let initPromise = null;
559
+ let initialized = false;
560
+ const ensureInitialized = async () => {
561
+ if (initialized) return;
562
+ if (initPromise) {
563
+ await initPromise;
564
+ return;
565
+ }
566
+ initPromise = initializePlatform().then(() => {
567
+ initialized = true;
568
+ initPromise = null;
569
+ });
570
+ await initPromise;
571
+ };
572
+ return async (c, next) => {
573
+ try {
574
+ await ensureInitialized();
575
+ const result = await processPaymentMiddleware(options, c.req.header());
576
+ if (!result.allowed) {
577
+ if (result.paymentRequired) {
578
+ return c.json(result.paymentRequired.body, 402, result.paymentRequired.headers);
579
+ }
580
+ if (result.error) {
581
+ return c.json({ error: result.error.message }, result.error.status);
582
+ }
583
+ }
584
+ await next();
585
+ } catch (error) {
586
+ const message = error instanceof Error ? error.message : "Internal server error";
587
+ return c.json({ error: message }, 500);
588
+ }
589
+ };
590
+ }
591
+
592
+ // src/server/index.ts
593
+ init_constants();
594
+ export {
595
+ DEFAULT_PLATFORM_URL,
596
+ NETWORKS,
597
+ TOKENS,
598
+ clearCache,
599
+ extractPaymentReceipt,
600
+ getAvailableNetworks,
601
+ getChainId,
602
+ getNetworkConfig,
603
+ getNetworksByEnvironment,
604
+ getProjectConfig,
605
+ getRpcUrl,
606
+ getTokenConfig,
607
+ initializePlatform,
608
+ isMainnet,
609
+ isTestnet,
610
+ processPaymentMiddleware,
611
+ registerCustomNetwork,
612
+ registerCustomTokens,
613
+ verifyPayment,
614
+ verifyPaymentOnChain,
615
+ x402
616
+ };
617
+ //# sourceMappingURL=index.js.map