@dexterai/x402 1.0.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,583 @@
1
+ var __getOwnPropNames = Object.getOwnPropertyNames;
2
+ var __esm = (fn, res) => function __init() {
3
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
4
+ };
5
+
6
+ // src/adapters/solana.ts
7
+ import {
8
+ PublicKey,
9
+ Connection,
10
+ TransactionMessage,
11
+ VersionedTransaction,
12
+ ComputeBudgetProgram
13
+ } from "@solana/web3.js";
14
+ import {
15
+ getAssociatedTokenAddress,
16
+ getAccount,
17
+ createTransferCheckedInstruction,
18
+ getMint,
19
+ TOKEN_PROGRAM_ID,
20
+ TOKEN_2022_PROGRAM_ID
21
+ } from "@solana/spl-token";
22
+ function isSolanaWallet(wallet) {
23
+ if (!wallet || typeof wallet !== "object") return false;
24
+ const w = wallet;
25
+ return "publicKey" in w && "signTransaction" in w && typeof w.signTransaction === "function";
26
+ }
27
+ function createSolanaAdapter(config) {
28
+ return new SolanaAdapter(config);
29
+ }
30
+ var SOLANA_MAINNET, SOLANA_DEVNET, SOLANA_TESTNET, DEFAULT_RPC_URLS, DEFAULT_COMPUTE_UNIT_LIMIT, DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS, SolanaAdapter;
31
+ var init_solana = __esm({
32
+ "src/adapters/solana.ts"() {
33
+ "use strict";
34
+ SOLANA_MAINNET = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
35
+ SOLANA_DEVNET = "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1";
36
+ SOLANA_TESTNET = "solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z";
37
+ DEFAULT_RPC_URLS = {
38
+ [SOLANA_MAINNET]: "https://api.mainnet-beta.solana.com",
39
+ [SOLANA_DEVNET]: "https://api.devnet.solana.com",
40
+ [SOLANA_TESTNET]: "https://api.testnet.solana.com"
41
+ };
42
+ DEFAULT_COMPUTE_UNIT_LIMIT = 12e3;
43
+ DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS = 1;
44
+ SolanaAdapter = class {
45
+ name = "Solana";
46
+ networks = [SOLANA_MAINNET, SOLANA_DEVNET, SOLANA_TESTNET];
47
+ config;
48
+ log;
49
+ constructor(config = {}) {
50
+ this.config = config;
51
+ this.log = config.verbose ? console.log.bind(console, "[x402:solana]") : () => {
52
+ };
53
+ }
54
+ canHandle(network) {
55
+ if (this.networks.includes(network)) return true;
56
+ if (network === "solana") return true;
57
+ if (network === "solana-devnet") return true;
58
+ if (network === "solana-testnet") return true;
59
+ if (network.startsWith("solana:")) return true;
60
+ return false;
61
+ }
62
+ getDefaultRpcUrl(network) {
63
+ if (this.config.rpcUrls?.[network]) {
64
+ return this.config.rpcUrls[network];
65
+ }
66
+ if (DEFAULT_RPC_URLS[network]) {
67
+ return DEFAULT_RPC_URLS[network];
68
+ }
69
+ if (network === "solana") return DEFAULT_RPC_URLS[SOLANA_MAINNET];
70
+ if (network === "solana-devnet") return DEFAULT_RPC_URLS[SOLANA_DEVNET];
71
+ if (network === "solana-testnet") return DEFAULT_RPC_URLS[SOLANA_TESTNET];
72
+ return DEFAULT_RPC_URLS[SOLANA_MAINNET];
73
+ }
74
+ getAddress(wallet) {
75
+ if (!isSolanaWallet(wallet)) return null;
76
+ return wallet.publicKey?.toBase58() ?? null;
77
+ }
78
+ isConnected(wallet) {
79
+ if (!isSolanaWallet(wallet)) return false;
80
+ return wallet.publicKey !== null;
81
+ }
82
+ async getBalance(accept, wallet, rpcUrl) {
83
+ if (!isSolanaWallet(wallet) || !wallet.publicKey) {
84
+ return 0;
85
+ }
86
+ const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
87
+ const connection = new Connection(url, "confirmed");
88
+ const userPubkey = new PublicKey(wallet.publicKey.toBase58());
89
+ const mintPubkey = new PublicKey(accept.asset);
90
+ try {
91
+ const mintInfo = await connection.getAccountInfo(mintPubkey, "confirmed");
92
+ const programId = mintInfo?.owner.toBase58() === TOKEN_2022_PROGRAM_ID.toBase58() ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID;
93
+ const ata = await getAssociatedTokenAddress(
94
+ mintPubkey,
95
+ userPubkey,
96
+ false,
97
+ programId
98
+ );
99
+ const account = await getAccount(connection, ata, void 0, programId);
100
+ const decimals = accept.extra?.decimals ?? 6;
101
+ return Number(account.amount) / Math.pow(10, decimals);
102
+ } catch {
103
+ return 0;
104
+ }
105
+ }
106
+ async buildTransaction(accept, wallet, rpcUrl) {
107
+ if (!isSolanaWallet(wallet)) {
108
+ throw new Error("Invalid Solana wallet");
109
+ }
110
+ if (!wallet.publicKey) {
111
+ throw new Error("Wallet not connected");
112
+ }
113
+ const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
114
+ const connection = new Connection(url, "confirmed");
115
+ const userPubkey = new PublicKey(wallet.publicKey.toBase58());
116
+ const { payTo, asset, amount, extra } = accept;
117
+ if (!extra?.feePayer) {
118
+ throw new Error("Missing feePayer in payment requirements");
119
+ }
120
+ if (typeof extra?.decimals !== "number") {
121
+ throw new Error("Missing decimals in payment requirements");
122
+ }
123
+ const feePayerPubkey = new PublicKey(extra.feePayer);
124
+ const mintPubkey = new PublicKey(asset);
125
+ const destinationPubkey = new PublicKey(payTo);
126
+ this.log("Building transaction:", {
127
+ from: userPubkey.toBase58(),
128
+ to: payTo,
129
+ amount,
130
+ asset,
131
+ feePayer: extra.feePayer
132
+ });
133
+ const instructions = [];
134
+ instructions.push(
135
+ ComputeBudgetProgram.setComputeUnitLimit({
136
+ units: DEFAULT_COMPUTE_UNIT_LIMIT
137
+ })
138
+ );
139
+ instructions.push(
140
+ ComputeBudgetProgram.setComputeUnitPrice({
141
+ microLamports: DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS
142
+ })
143
+ );
144
+ const mintInfo = await connection.getAccountInfo(mintPubkey, "confirmed");
145
+ if (!mintInfo) {
146
+ throw new Error(`Token mint ${asset} not found`);
147
+ }
148
+ const programId = mintInfo.owner.toBase58() === TOKEN_2022_PROGRAM_ID.toBase58() ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID;
149
+ const mint = await getMint(connection, mintPubkey, void 0, programId);
150
+ if (mint.decimals !== extra.decimals) {
151
+ this.log(
152
+ `Decimals mismatch: requirements say ${extra.decimals}, mint says ${mint.decimals}`
153
+ );
154
+ }
155
+ const sourceAta = await getAssociatedTokenAddress(
156
+ mintPubkey,
157
+ userPubkey,
158
+ false,
159
+ programId
160
+ );
161
+ const destinationAta = await getAssociatedTokenAddress(
162
+ mintPubkey,
163
+ destinationPubkey,
164
+ false,
165
+ programId
166
+ );
167
+ const sourceAtaInfo = await connection.getAccountInfo(sourceAta, "confirmed");
168
+ if (!sourceAtaInfo) {
169
+ throw new Error(
170
+ `No token account found for ${asset}. Please ensure you have USDC in your wallet.`
171
+ );
172
+ }
173
+ const destAtaInfo = await connection.getAccountInfo(destinationAta, "confirmed");
174
+ if (!destAtaInfo) {
175
+ throw new Error(
176
+ `Seller token account not found. The seller (${payTo}) must have a USDC account.`
177
+ );
178
+ }
179
+ const amountBigInt = BigInt(amount);
180
+ instructions.push(
181
+ createTransferCheckedInstruction(
182
+ sourceAta,
183
+ mintPubkey,
184
+ destinationAta,
185
+ userPubkey,
186
+ amountBigInt,
187
+ mint.decimals,
188
+ [],
189
+ programId
190
+ )
191
+ );
192
+ const { blockhash } = await connection.getLatestBlockhash("confirmed");
193
+ const message = new TransactionMessage({
194
+ payerKey: feePayerPubkey,
195
+ recentBlockhash: blockhash,
196
+ instructions
197
+ }).compileToV0Message();
198
+ const transaction = new VersionedTransaction(message);
199
+ const signedTx = await wallet.signTransaction(transaction);
200
+ this.log("Transaction signed successfully");
201
+ return {
202
+ serialized: Buffer.from(signedTx.serialize()).toString("base64")
203
+ };
204
+ }
205
+ };
206
+ }
207
+ });
208
+
209
+ // src/adapters/evm.ts
210
+ function isEvmWallet(wallet) {
211
+ if (!wallet || typeof wallet !== "object") return false;
212
+ const w = wallet;
213
+ return "address" in w && typeof w.address === "string" && w.address.startsWith("0x");
214
+ }
215
+ function createEvmAdapter(config) {
216
+ return new EvmAdapter(config);
217
+ }
218
+ var BASE_MAINNET, BASE_SEPOLIA, ETHEREUM_MAINNET, ARBITRUM_ONE, CHAIN_IDS, DEFAULT_RPC_URLS2, USDC_ADDRESSES, EvmAdapter;
219
+ var init_evm = __esm({
220
+ "src/adapters/evm.ts"() {
221
+ "use strict";
222
+ BASE_MAINNET = "eip155:8453";
223
+ BASE_SEPOLIA = "eip155:84532";
224
+ ETHEREUM_MAINNET = "eip155:1";
225
+ ARBITRUM_ONE = "eip155:42161";
226
+ CHAIN_IDS = {
227
+ [BASE_MAINNET]: 8453,
228
+ [BASE_SEPOLIA]: 84532,
229
+ [ETHEREUM_MAINNET]: 1,
230
+ [ARBITRUM_ONE]: 42161
231
+ };
232
+ DEFAULT_RPC_URLS2 = {
233
+ [BASE_MAINNET]: "https://mainnet.base.org",
234
+ [BASE_SEPOLIA]: "https://sepolia.base.org",
235
+ [ETHEREUM_MAINNET]: "https://eth.llamarpc.com",
236
+ [ARBITRUM_ONE]: "https://arb1.arbitrum.io/rpc"
237
+ };
238
+ USDC_ADDRESSES = {
239
+ [BASE_MAINNET]: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
240
+ [ETHEREUM_MAINNET]: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
241
+ [ARBITRUM_ONE]: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
242
+ };
243
+ EvmAdapter = class {
244
+ name = "EVM";
245
+ networks = [BASE_MAINNET, BASE_SEPOLIA, ETHEREUM_MAINNET, ARBITRUM_ONE];
246
+ config;
247
+ log;
248
+ constructor(config = {}) {
249
+ this.config = config;
250
+ this.log = config.verbose ? console.log.bind(console, "[x402:evm]") : () => {
251
+ };
252
+ }
253
+ canHandle(network) {
254
+ if (this.networks.includes(network)) return true;
255
+ if (network === "base") return true;
256
+ if (network === "ethereum") return true;
257
+ if (network === "arbitrum") return true;
258
+ if (network.startsWith("eip155:")) return true;
259
+ return false;
260
+ }
261
+ getDefaultRpcUrl(network) {
262
+ if (this.config.rpcUrls?.[network]) {
263
+ return this.config.rpcUrls[network];
264
+ }
265
+ if (DEFAULT_RPC_URLS2[network]) {
266
+ return DEFAULT_RPC_URLS2[network];
267
+ }
268
+ if (network === "base") return DEFAULT_RPC_URLS2[BASE_MAINNET];
269
+ if (network === "ethereum") return DEFAULT_RPC_URLS2[ETHEREUM_MAINNET];
270
+ if (network === "arbitrum") return DEFAULT_RPC_URLS2[ARBITRUM_ONE];
271
+ return DEFAULT_RPC_URLS2[BASE_MAINNET];
272
+ }
273
+ getAddress(wallet) {
274
+ if (!isEvmWallet(wallet)) return null;
275
+ return wallet.address;
276
+ }
277
+ isConnected(wallet) {
278
+ if (!isEvmWallet(wallet)) return false;
279
+ return !!wallet.address;
280
+ }
281
+ getChainId(network) {
282
+ if (CHAIN_IDS[network]) return CHAIN_IDS[network];
283
+ if (network.startsWith("eip155:")) {
284
+ const chainIdStr = network.split(":")[1];
285
+ return parseInt(chainIdStr, 10);
286
+ }
287
+ if (network === "base") return 8453;
288
+ if (network === "ethereum") return 1;
289
+ if (network === "arbitrum") return 42161;
290
+ return 8453;
291
+ }
292
+ async getBalance(accept, wallet, rpcUrl) {
293
+ if (!isEvmWallet(wallet) || !wallet.address) {
294
+ return 0;
295
+ }
296
+ const url = rpcUrl || this.getDefaultRpcUrl(accept.network);
297
+ try {
298
+ const data = this.encodeBalanceOf(wallet.address);
299
+ const response = await fetch(url, {
300
+ method: "POST",
301
+ headers: { "Content-Type": "application/json" },
302
+ body: JSON.stringify({
303
+ jsonrpc: "2.0",
304
+ id: 1,
305
+ method: "eth_call",
306
+ params: [
307
+ {
308
+ to: accept.asset,
309
+ data
310
+ },
311
+ "latest"
312
+ ]
313
+ })
314
+ });
315
+ const result = await response.json();
316
+ if (result.error || !result.result) {
317
+ return 0;
318
+ }
319
+ const balance = BigInt(result.result);
320
+ const decimals = accept.extra?.decimals ?? 6;
321
+ return Number(balance) / Math.pow(10, decimals);
322
+ } catch {
323
+ return 0;
324
+ }
325
+ }
326
+ encodeBalanceOf(address) {
327
+ const selector = "0x70a08231";
328
+ const paddedAddress = address.slice(2).toLowerCase().padStart(64, "0");
329
+ return selector + paddedAddress;
330
+ }
331
+ async buildTransaction(accept, wallet, _rpcUrl) {
332
+ if (!isEvmWallet(wallet)) {
333
+ throw new Error("Invalid EVM wallet");
334
+ }
335
+ if (!wallet.address) {
336
+ throw new Error("Wallet not connected");
337
+ }
338
+ const { payTo, asset, amount, extra } = accept;
339
+ this.log("Building EVM transaction:", {
340
+ from: wallet.address,
341
+ to: payTo,
342
+ amount,
343
+ asset,
344
+ network: accept.network
345
+ });
346
+ const chainId = this.getChainId(accept.network);
347
+ const domain = {
348
+ name: extra?.name ?? "USD Coin",
349
+ version: extra?.version ?? "2",
350
+ chainId: BigInt(chainId),
351
+ verifyingContract: asset
352
+ };
353
+ const types = {
354
+ TransferWithAuthorization: [
355
+ { name: "from", type: "address" },
356
+ { name: "to", type: "address" },
357
+ { name: "value", type: "uint256" },
358
+ { name: "validAfter", type: "uint256" },
359
+ { name: "validBefore", type: "uint256" },
360
+ { name: "nonce", type: "bytes32" }
361
+ ]
362
+ };
363
+ const nonce = "0x" + [...Array(32)].map(() => Math.floor(Math.random() * 256).toString(16).padStart(2, "0")).join("");
364
+ const now = Math.floor(Date.now() / 1e3);
365
+ const authorization = {
366
+ from: wallet.address,
367
+ to: payTo,
368
+ value: amount,
369
+ // string
370
+ validAfter: String(now - 600),
371
+ // 10 minutes before (matching upstream)
372
+ validBefore: String(now + (accept.maxTimeoutSeconds || 60)),
373
+ nonce
374
+ };
375
+ const message = {
376
+ from: wallet.address,
377
+ to: payTo,
378
+ value: BigInt(amount),
379
+ validAfter: BigInt(now - 600),
380
+ validBefore: BigInt(now + (accept.maxTimeoutSeconds || 60)),
381
+ nonce
382
+ };
383
+ if (!wallet.signTypedData) {
384
+ throw new Error("Wallet does not support signTypedData (EIP-712)");
385
+ }
386
+ const signature = await wallet.signTypedData({
387
+ domain,
388
+ types,
389
+ primaryType: "TransferWithAuthorization",
390
+ message
391
+ });
392
+ this.log("EIP-712 signature obtained");
393
+ const payload = {
394
+ authorization,
395
+ signature
396
+ };
397
+ return {
398
+ serialized: JSON.stringify(payload),
399
+ signature
400
+ };
401
+ }
402
+ };
403
+ }
404
+ });
405
+
406
+ // src/types.ts
407
+ var USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
408
+ var DEXTER_FACILITATOR_URL = "https://x402.dexter.cash";
409
+ var X402Error = class _X402Error extends Error {
410
+ /** Error code for programmatic handling */
411
+ code;
412
+ /** Additional error details */
413
+ details;
414
+ constructor(code, message, details) {
415
+ super(message);
416
+ this.name = "X402Error";
417
+ this.code = code;
418
+ this.details = details;
419
+ Object.setPrototypeOf(this, _X402Error.prototype);
420
+ }
421
+ };
422
+
423
+ // src/adapters/index.ts
424
+ init_solana();
425
+ init_evm();
426
+
427
+ // src/client/x402-client.ts
428
+ function createX402Client(config) {
429
+ const {
430
+ adapters = [createSolanaAdapter({ verbose: config.verbose }), createEvmAdapter({ verbose: config.verbose })],
431
+ wallets: walletSet,
432
+ wallet: legacyWallet,
433
+ preferredNetwork,
434
+ rpcUrls = {},
435
+ maxAmountAtomic,
436
+ fetch: customFetch = globalThis.fetch,
437
+ verbose = false
438
+ } = config;
439
+ const log = verbose ? console.log.bind(console, "[x402]") : () => {
440
+ };
441
+ const wallets = walletSet || {};
442
+ if (legacyWallet && !wallets.solana && isSolanaWallet(legacyWallet)) {
443
+ wallets.solana = legacyWallet;
444
+ }
445
+ if (legacyWallet && !wallets.evm && isEvmWallet(legacyWallet)) {
446
+ wallets.evm = legacyWallet;
447
+ }
448
+ function findPaymentOption(accepts) {
449
+ const candidates = [];
450
+ for (const accept of accepts) {
451
+ const adapter = adapters.find((a) => a.canHandle(accept.network));
452
+ if (!adapter) continue;
453
+ let wallet;
454
+ if (adapter.name === "Solana") {
455
+ wallet = wallets.solana;
456
+ } else if (adapter.name === "EVM") {
457
+ wallet = wallets.evm;
458
+ }
459
+ if (wallet && adapter.isConnected(wallet)) {
460
+ candidates.push({ accept, adapter, wallet });
461
+ }
462
+ }
463
+ if (candidates.length === 0) {
464
+ return null;
465
+ }
466
+ if (preferredNetwork) {
467
+ const preferred = candidates.find((c) => c.accept.network === preferredNetwork);
468
+ if (preferred) return preferred;
469
+ }
470
+ return candidates[0];
471
+ }
472
+ function getRpcUrl(network, adapter) {
473
+ return rpcUrls[network] || adapter.getDefaultRpcUrl(network);
474
+ }
475
+ async function x402Fetch(input, init) {
476
+ log("Making request:", input);
477
+ const response = await customFetch(input, init);
478
+ if (response.status !== 402) {
479
+ return response;
480
+ }
481
+ log("Received 402 Payment Required");
482
+ const paymentRequiredHeader = response.headers.get("PAYMENT-REQUIRED");
483
+ if (!paymentRequiredHeader) {
484
+ throw new X402Error(
485
+ "missing_payment_required_header",
486
+ "Server returned 402 but no PAYMENT-REQUIRED header"
487
+ );
488
+ }
489
+ let requirements;
490
+ try {
491
+ const decoded = atob(paymentRequiredHeader);
492
+ requirements = JSON.parse(decoded);
493
+ } catch {
494
+ throw new X402Error(
495
+ "invalid_payment_required",
496
+ "Failed to decode PAYMENT-REQUIRED header"
497
+ );
498
+ }
499
+ log("Payment requirements:", requirements);
500
+ const match = findPaymentOption(requirements.accepts);
501
+ if (!match) {
502
+ const availableNetworks = requirements.accepts.map((a) => a.network).join(", ");
503
+ throw new X402Error(
504
+ "no_matching_payment_option",
505
+ `No connected wallet for any available network: ${availableNetworks}`
506
+ );
507
+ }
508
+ const { accept, adapter, wallet } = match;
509
+ log(`Using ${adapter.name} for ${accept.network}`);
510
+ if (adapter.name === "Solana" && !accept.extra?.feePayer) {
511
+ throw new X402Error(
512
+ "missing_fee_payer",
513
+ "Solana payment option missing feePayer in extra"
514
+ );
515
+ }
516
+ if (typeof accept.extra?.decimals !== "number") {
517
+ throw new X402Error(
518
+ "missing_decimals",
519
+ "Payment option missing decimals in extra"
520
+ );
521
+ }
522
+ if (maxAmountAtomic && BigInt(accept.amount) > BigInt(maxAmountAtomic)) {
523
+ throw new X402Error(
524
+ "amount_exceeds_max",
525
+ `Payment amount ${accept.amount} exceeds maximum ${maxAmountAtomic}`
526
+ );
527
+ }
528
+ log("Building transaction...");
529
+ const rpcUrl = getRpcUrl(accept.network, adapter);
530
+ const signedTx = await adapter.buildTransaction(accept, wallet, rpcUrl);
531
+ log("Transaction signed");
532
+ let payload;
533
+ if (adapter.name === "EVM") {
534
+ payload = JSON.parse(signedTx.serialized);
535
+ } else {
536
+ payload = { transaction: signedTx.serialized };
537
+ }
538
+ const paymentSignature = {
539
+ x402Version: 2,
540
+ resource: requirements.resource,
541
+ accepted: accept,
542
+ payload
543
+ };
544
+ const paymentSignatureHeader = btoa(JSON.stringify(paymentSignature));
545
+ log("Retrying request with payment...");
546
+ const retryResponse = await customFetch(input, {
547
+ ...init,
548
+ headers: {
549
+ ...init?.headers || {},
550
+ "PAYMENT-SIGNATURE": paymentSignatureHeader
551
+ }
552
+ });
553
+ log("Retry response status:", retryResponse.status);
554
+ if (retryResponse.status === 402) {
555
+ let reason = "unknown";
556
+ try {
557
+ const body = await retryResponse.clone().json();
558
+ reason = String(body.error || body.message || JSON.stringify(body));
559
+ log("Rejection reason:", reason);
560
+ } catch {
561
+ }
562
+ throw new X402Error(
563
+ "payment_rejected",
564
+ `Payment was rejected by the server: ${reason}`
565
+ );
566
+ }
567
+ return retryResponse;
568
+ }
569
+ return {
570
+ fetch: x402Fetch
571
+ };
572
+ }
573
+ export {
574
+ BASE_MAINNET,
575
+ DEXTER_FACILITATOR_URL,
576
+ SOLANA_MAINNET,
577
+ USDC_MINT,
578
+ X402Error,
579
+ createEvmAdapter,
580
+ createSolanaAdapter,
581
+ createX402Client
582
+ };
583
+ //# sourceMappingURL=index.js.map