@t402/cli 2.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,952 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/types.ts
4
+ var DEFAULT_CONFIG = {
5
+ defaultNetwork: "eip155:8453",
6
+ facilitatorUrl: "https://facilitator.t402.io",
7
+ testnet: false,
8
+ rpcEndpoints: {}
9
+ };
10
+ var NETWORKS = [
11
+ // EVM Mainnets
12
+ { id: "eip155:1", name: "Ethereum", type: "evm", testnet: false, assets: ["usdt", "usdt0"] },
13
+ { id: "eip155:42161", name: "Arbitrum", type: "evm", testnet: false, assets: ["usdt0"] },
14
+ { id: "eip155:8453", name: "Base", type: "evm", testnet: false, assets: ["usdt0"] },
15
+ { id: "eip155:10", name: "Optimism", type: "evm", testnet: false, assets: ["usdt0"] },
16
+ { id: "eip155:57073", name: "Ink", type: "evm", testnet: false, assets: ["usdt0"] },
17
+ { id: "eip155:80094", name: "Berachain", type: "evm", testnet: false, assets: ["usdt0"] },
18
+ // EVM Testnets
19
+ { id: "eip155:11155111", name: "Sepolia", type: "evm", testnet: true, assets: ["usdt0"] },
20
+ { id: "eip155:421614", name: "Arbitrum Sepolia", type: "evm", testnet: true, assets: ["usdt0"] },
21
+ { id: "eip155:84532", name: "Base Sepolia", type: "evm", testnet: true, assets: ["usdt0"] },
22
+ // Solana
23
+ {
24
+ id: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
25
+ name: "Solana",
26
+ type: "solana",
27
+ testnet: false,
28
+ assets: ["usdt"]
29
+ },
30
+ {
31
+ id: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
32
+ name: "Solana Devnet",
33
+ type: "solana",
34
+ testnet: true,
35
+ assets: ["usdt"]
36
+ },
37
+ // TON
38
+ { id: "ton:-239", name: "TON", type: "ton", testnet: false, assets: ["usdt"] },
39
+ { id: "ton:-3", name: "TON Testnet", type: "ton", testnet: true, assets: ["usdt"] },
40
+ // TRON
41
+ { id: "tron:mainnet", name: "TRON", type: "tron", testnet: false, assets: ["usdt"] },
42
+ { id: "tron:nile", name: "TRON Nile", type: "tron", testnet: true, assets: ["usdt"] }
43
+ ];
44
+
45
+ // src/config/index.ts
46
+ import Conf from "conf";
47
+ var config = new Conf({
48
+ projectName: "t402",
49
+ defaults: DEFAULT_CONFIG
50
+ });
51
+ function getConfig(key) {
52
+ return config.get(key);
53
+ }
54
+ function setConfig(key, value) {
55
+ config.set(key, value);
56
+ }
57
+ function getAllConfig() {
58
+ return config.store;
59
+ }
60
+ function resetConfig() {
61
+ config.clear();
62
+ }
63
+ function getConfigPath() {
64
+ return config.path;
65
+ }
66
+ function hasSeedConfigured() {
67
+ return !!config.get("encryptedSeed");
68
+ }
69
+ function storeSeed(encryptedSeed) {
70
+ config.set("encryptedSeed", encryptedSeed);
71
+ }
72
+ function getEncryptedSeed() {
73
+ return config.get("encryptedSeed");
74
+ }
75
+ function clearSeed() {
76
+ config.delete("encryptedSeed");
77
+ }
78
+ function setRpcEndpoint(network, url) {
79
+ const endpoints = config.get("rpcEndpoints");
80
+ config.set("rpcEndpoints", { ...endpoints, [network]: url });
81
+ }
82
+ function getRpcEndpoint(network) {
83
+ return config.get("rpcEndpoints")[network];
84
+ }
85
+
86
+ // src/utils/index.ts
87
+ import chalk from "chalk";
88
+ import ora from "ora";
89
+ function createSpinner(text) {
90
+ return ora({
91
+ text,
92
+ color: "cyan"
93
+ });
94
+ }
95
+ function formatAddress(address, startChars = 6, endChars = 4) {
96
+ if (address.length <= startChars + endChars + 3) {
97
+ return address;
98
+ }
99
+ return `${address.slice(0, startChars)}...${address.slice(-endChars)}`;
100
+ }
101
+ function formatAmount(amount, decimals = 6) {
102
+ const value = BigInt(amount);
103
+ const divisor = BigInt(10 ** decimals);
104
+ const whole = value / divisor;
105
+ const fraction = value % divisor;
106
+ if (fraction === 0n) {
107
+ return whole.toString();
108
+ }
109
+ const fractionStr = fraction.toString().padStart(decimals, "0").replace(/0+$/, "");
110
+ return `${whole}.${fractionStr}`;
111
+ }
112
+ function formatBalanceResult(result) {
113
+ const network = getNetworkInfo(result.network);
114
+ const networkName = network?.name || result.network;
115
+ const assetName = result.asset.toUpperCase();
116
+ return `${chalk.bold(networkName)}: ${chalk.green(result.formatted)} ${assetName}`;
117
+ }
118
+ function formatPaymentResult(result) {
119
+ if (result.success) {
120
+ const lines = [chalk.green("\u2713 Payment successful!")];
121
+ if (result.txHash) {
122
+ lines.push(` Transaction: ${chalk.cyan(result.txHash)}`);
123
+ }
124
+ if (result.network) {
125
+ const network = getNetworkInfo(result.network);
126
+ lines.push(` Network: ${network?.name || result.network}`);
127
+ }
128
+ if (result.amount) {
129
+ lines.push(` Amount: ${result.amount}`);
130
+ }
131
+ return lines.join("\n");
132
+ } else {
133
+ return chalk.red(`\u2717 Payment failed: ${result.error || "Unknown error"}`);
134
+ }
135
+ }
136
+ function getNetworkInfo(networkId) {
137
+ return NETWORKS.find((n) => n.id === networkId);
138
+ }
139
+ function getNetworkName(networkId) {
140
+ const network = getNetworkInfo(networkId);
141
+ return network?.name || networkId;
142
+ }
143
+ function getAvailableNetworks(testnet) {
144
+ return NETWORKS.filter((n) => n.testnet === testnet);
145
+ }
146
+ function printTable(data) {
147
+ const maxKeyLength = Math.max(...Object.keys(data).map((k) => k.length));
148
+ for (const [key, value] of Object.entries(data)) {
149
+ const paddedKey = key.padEnd(maxKeyLength);
150
+ console.log(` ${chalk.gray(paddedKey)} ${value}`);
151
+ }
152
+ }
153
+ function printSuccess(message) {
154
+ console.log(chalk.green(`\u2713 ${message}`));
155
+ }
156
+ function printError(message) {
157
+ console.error(chalk.red(`\u2717 ${message}`));
158
+ }
159
+ function printWarning(message) {
160
+ console.log(chalk.yellow(`\u26A0 ${message}`));
161
+ }
162
+ function printInfo(message) {
163
+ console.log(chalk.cyan(`\u2139 ${message}`));
164
+ }
165
+ function printHeader(title) {
166
+ console.log();
167
+ console.log(chalk.bold.underline(title));
168
+ console.log();
169
+ }
170
+ function isValidSeedPhrase(phrase) {
171
+ const words = phrase.trim().split(/\s+/);
172
+ return words.length === 12 || words.length === 24;
173
+ }
174
+ function encryptSeed(seed, key) {
175
+ const seedBytes = Buffer.from(seed, "utf8");
176
+ const keyBytes = Buffer.from(key, "utf8");
177
+ const encrypted = Buffer.alloc(seedBytes.length);
178
+ for (let i = 0; i < seedBytes.length; i++) {
179
+ encrypted[i] = seedBytes[i] ^ keyBytes[i % keyBytes.length];
180
+ }
181
+ return encrypted.toString("base64");
182
+ }
183
+ function decryptSeed(encrypted, key) {
184
+ const encryptedBytes = Buffer.from(encrypted, "base64");
185
+ const keyBytes = Buffer.from(key, "utf8");
186
+ const decrypted = Buffer.alloc(encryptedBytes.length);
187
+ for (let i = 0; i < encryptedBytes.length; i++) {
188
+ decrypted[i] = encryptedBytes[i] ^ keyBytes[i % keyBytes.length];
189
+ }
190
+ return decrypted.toString("utf8");
191
+ }
192
+ function parseAmount(amount, decimals = 6) {
193
+ const parts = amount.split(".");
194
+ const whole = parts[0] || "0";
195
+ const fraction = (parts[1] || "").padEnd(decimals, "0").slice(0, decimals);
196
+ return (BigInt(whole) * BigInt(10 ** decimals) + BigInt(fraction)).toString();
197
+ }
198
+ function isValidUrl(url) {
199
+ try {
200
+ new URL(url);
201
+ return true;
202
+ } catch {
203
+ return false;
204
+ }
205
+ }
206
+
207
+ // src/cli.ts
208
+ import { Command } from "commander";
209
+
210
+ // src/commands/wallet.ts
211
+ import chalk2 from "chalk";
212
+ var MACHINE_KEY = `t402-cli-${process.env.USER || "default"}`;
213
+ function registerWalletCommands(program) {
214
+ const wallet = program.command("wallet").description("Wallet management commands");
215
+ wallet.command("create").description("Create a new wallet with a generated seed phrase").action(async () => {
216
+ if (hasSeedConfigured()) {
217
+ printWarning("A wallet is already configured. Use 'wallet clear' first to remove it.");
218
+ return;
219
+ }
220
+ try {
221
+ const { generateMnemonic, english } = await import("viem/accounts");
222
+ const spinner = createSpinner("Generating seed phrase...").start();
223
+ const mnemonic = generateMnemonic(english);
224
+ spinner.succeed("Seed phrase generated");
225
+ console.log();
226
+ console.log(chalk2.yellow("\u26A0 IMPORTANT: Write down these words and store them safely!"));
227
+ console.log(chalk2.yellow(" This is the ONLY way to recover your wallet."));
228
+ console.log();
229
+ console.log(chalk2.bold("Seed phrase:"));
230
+ console.log();
231
+ console.log(` ${chalk2.cyan(mnemonic)}`);
232
+ console.log();
233
+ const encrypted = encryptSeed(mnemonic, MACHINE_KEY);
234
+ storeSeed(encrypted);
235
+ printSuccess("Wallet created and saved to config");
236
+ console.log();
237
+ console.log("Run 'wallet show' to see your addresses.");
238
+ } catch (error) {
239
+ printError(`Failed to create wallet: ${error instanceof Error ? error.message : error}`);
240
+ }
241
+ });
242
+ wallet.command("import").description("Import an existing wallet from a seed phrase").argument("<seed-phrase>", "12 or 24 word seed phrase (in quotes)").action(async (seedPhrase) => {
243
+ if (hasSeedConfigured()) {
244
+ printWarning("A wallet is already configured. Use 'wallet clear' first to remove it.");
245
+ return;
246
+ }
247
+ const phrase = seedPhrase.trim();
248
+ if (!isValidSeedPhrase(phrase)) {
249
+ printError("Invalid seed phrase. Must be 12 or 24 words.");
250
+ return;
251
+ }
252
+ try {
253
+ const { mnemonicToAccount } = await import("viem/accounts");
254
+ mnemonicToAccount(phrase);
255
+ const encrypted = encryptSeed(phrase, MACHINE_KEY);
256
+ storeSeed(encrypted);
257
+ printSuccess("Wallet imported successfully");
258
+ console.log();
259
+ console.log("Run 'wallet show' to see your addresses.");
260
+ } catch (error) {
261
+ printError(`Failed to import wallet: ${error instanceof Error ? error.message : error}`);
262
+ }
263
+ });
264
+ wallet.command("show").description("Show wallet addresses").action(async () => {
265
+ const encrypted = getEncryptedSeed();
266
+ if (!encrypted) {
267
+ printError("No wallet configured. Run 'wallet create' or 'wallet import' first.");
268
+ return;
269
+ }
270
+ try {
271
+ const seedPhrase = decryptSeed(encrypted, MACHINE_KEY);
272
+ const { mnemonicToAccount } = await import("viem/accounts");
273
+ const spinner = createSpinner("Loading wallet...").start();
274
+ const account = mnemonicToAccount(seedPhrase);
275
+ spinner.succeed("Wallet loaded");
276
+ printHeader("Wallet Addresses");
277
+ const addresses = {
278
+ "EVM (Ethereum, Arbitrum, Base, etc.)": account.address
279
+ };
280
+ printTable(addresses);
281
+ console.log();
282
+ console.log(chalk2.gray("Note: Install @t402/wdk for full multi-chain support"));
283
+ } catch (error) {
284
+ printError(`Failed to load wallet: ${error instanceof Error ? error.message : error}`);
285
+ }
286
+ });
287
+ wallet.command("balance").description("Check wallet balances").option("-n, --network <network>", "Specific network to check (e.g., eip155:8453)").option("-a, --all", "Show all networks including zero balances").action(async (options) => {
288
+ const encrypted = getEncryptedSeed();
289
+ if (!encrypted) {
290
+ printError("No wallet configured. Run 'wallet create' or 'wallet import' first.");
291
+ return;
292
+ }
293
+ try {
294
+ const seedPhrase = decryptSeed(encrypted, MACHINE_KEY);
295
+ const wdkModule = await import("@t402/wdk");
296
+ const { T402WDK } = wdkModule;
297
+ const spinner = createSpinner("Fetching balances...").start();
298
+ const testnet = getConfig("testnet");
299
+ const chainConfig = testnet ? {
300
+ "arbitrum-sepolia": "https://sepolia-rollup.arbitrum.io/rpc",
301
+ "base-sepolia": "https://sepolia.base.org"
302
+ } : {
303
+ arbitrum: "https://arb1.arbitrum.io/rpc",
304
+ base: "https://mainnet.base.org"
305
+ };
306
+ const wdk = new T402WDK(seedPhrase, chainConfig);
307
+ if (options.network) {
308
+ const chainName = options.network.replace("eip155:", "").replace("8453", "base").replace("42161", "arbitrum");
309
+ spinner.text = `Fetching balance for ${getNetworkName(options.network)}...`;
310
+ const balance = await wdk.getUsdt0Balance(chainName);
311
+ spinner.succeed("Balance fetched");
312
+ printHeader(`Balance on ${getNetworkName(options.network)}`);
313
+ console.log(` ${chalk2.green(formatAmount(balance.toString()))} USDT0`);
314
+ } else {
315
+ const balances = await wdk.getAggregatedBalances();
316
+ spinner.succeed("Balances fetched");
317
+ printHeader("Wallet Balances");
318
+ console.log(` Total USDT0: ${chalk2.green(formatAmount(balances.totalUsdt0.toString()))}`);
319
+ if (balances.chains.length > 0) {
320
+ console.log();
321
+ for (const chain of balances.chains) {
322
+ const usdt0 = chain.tokens.find((t) => t.symbol === "USDT0");
323
+ if (usdt0 && (options.all || usdt0.balance > 0n)) {
324
+ const color = usdt0.balance > 0n ? chalk2.green : chalk2.gray;
325
+ console.log(` ${chain.chain.padEnd(20)} ${color(formatAmount(usdt0.balance.toString()))} USDT0`);
326
+ }
327
+ }
328
+ }
329
+ }
330
+ } catch (error) {
331
+ printError(`Failed to fetch balances: ${error instanceof Error ? error.message : error}`);
332
+ }
333
+ });
334
+ wallet.command("clear").description("Remove wallet from this device").option("-f, --force", "Skip confirmation").action(async (options) => {
335
+ if (!hasSeedConfigured()) {
336
+ printWarning("No wallet configured.");
337
+ return;
338
+ }
339
+ if (!options.force) {
340
+ console.log(chalk2.yellow("\u26A0 WARNING: This will remove your wallet from this device."));
341
+ console.log(chalk2.yellow(" Make sure you have your seed phrase backed up!"));
342
+ console.log();
343
+ console.log("Run with --force to confirm.");
344
+ return;
345
+ }
346
+ clearSeed();
347
+ printSuccess("Wallet removed from this device");
348
+ });
349
+ wallet.command("export").description("Export seed phrase (use with caution!)").option("-f, --force", "Skip warning").action(async (options) => {
350
+ const encrypted = getEncryptedSeed();
351
+ if (!encrypted) {
352
+ printError("No wallet configured.");
353
+ return;
354
+ }
355
+ if (!options.force) {
356
+ console.log(chalk2.red("\u26A0 DANGER: This will display your seed phrase on screen!"));
357
+ console.log(chalk2.red(" Anyone who sees this can steal your funds."));
358
+ console.log();
359
+ console.log("Run with --force to proceed.");
360
+ return;
361
+ }
362
+ const seedPhrase = decryptSeed(encrypted, MACHINE_KEY);
363
+ console.log();
364
+ console.log(chalk2.bold("Seed phrase:"));
365
+ console.log();
366
+ console.log(` ${chalk2.cyan(seedPhrase)}`);
367
+ console.log();
368
+ });
369
+ }
370
+
371
+ // src/commands/pay.ts
372
+ import chalk3 from "chalk";
373
+ var MACHINE_KEY2 = `t402-cli-${process.env.USER || "default"}`;
374
+ function registerPayCommands(program) {
375
+ program.command("pay").description("Send a payment to an address").argument("<to>", "Recipient address").argument("<amount>", "Amount to send (e.g., 1.5)").option("-n, --network <network>", "Network to use (e.g., eip155:8453)").option("-a, --asset <asset>", "Asset to send (default: usdt0)", "usdt0").option("-g, --gasless", "Use gasless transaction (ERC-4337)").action(
376
+ async (to, amount, options) => {
377
+ const encrypted = getEncryptedSeed();
378
+ if (!encrypted) {
379
+ printError("No wallet configured. Run 'wallet create' or 'wallet import' first.");
380
+ return;
381
+ }
382
+ const network = options.network || getConfig("defaultNetwork");
383
+ const testnet = getConfig("testnet");
384
+ const isTestnetNetwork = network.includes("sepolia") || network.includes("testnet") || network.includes("devnet") || network.includes("nile") || network === "ton:-3";
385
+ if (testnet !== isTestnetNetwork) {
386
+ printError(
387
+ `Network ${network} doesn't match testnet mode (${testnet ? "testnet" : "mainnet"}).`
388
+ );
389
+ printError(`Run 'config set testnet ${isTestnetNetwork}' to switch modes.`);
390
+ return;
391
+ }
392
+ try {
393
+ const seedPhrase = decryptSeed(encrypted, MACHINE_KEY2);
394
+ printHeader("Payment Details");
395
+ printTable({
396
+ To: formatAddress(to),
397
+ Amount: `${amount} ${options.asset.toUpperCase()}`,
398
+ Network: getNetworkName(network),
399
+ Mode: options.gasless ? "Gasless (ERC-4337)" : "Standard"
400
+ });
401
+ console.log();
402
+ const spinner = createSpinner("Preparing transaction...").start();
403
+ if (options.gasless) {
404
+ const wdkGasless = await import("@t402/wdk-gasless");
405
+ const { WdkGaslessClient } = wdkGasless;
406
+ spinner.text = "Initializing gasless client...";
407
+ const client = await WdkGaslessClient.create({
408
+ seedPhrase,
409
+ chain: network.replace("eip155:", "")
410
+ });
411
+ spinner.text = "Sending gasless payment...";
412
+ const result = await client.pay({
413
+ to,
414
+ amount: parseAmount(amount)
415
+ });
416
+ spinner.succeed("Payment sent!");
417
+ console.log();
418
+ printSuccess("Transaction submitted");
419
+ console.log(` User Operation Hash: ${chalk3.cyan(result.userOpHash)}`);
420
+ if (result.txHash) {
421
+ console.log(` Transaction Hash: ${chalk3.cyan(result.txHash)}`);
422
+ }
423
+ } else {
424
+ const wdk = await import("@t402/wdk");
425
+ const { createPaymentProcessor } = wdk;
426
+ spinner.text = "Initializing wallet...";
427
+ const processor = createPaymentProcessor({ seedPhrase });
428
+ await processor.initialize();
429
+ spinner.text = "Sending payment...";
430
+ const txHash = await processor.pay({
431
+ network,
432
+ asset: options.asset,
433
+ to,
434
+ amount: parseAmount(amount)
435
+ });
436
+ spinner.succeed("Payment sent!");
437
+ console.log();
438
+ printSuccess("Transaction submitted");
439
+ console.log(` Transaction Hash: ${chalk3.cyan(txHash)}`);
440
+ }
441
+ } catch (error) {
442
+ printError(`Payment failed: ${error instanceof Error ? error.message : error}`);
443
+ }
444
+ }
445
+ );
446
+ program.command("pay-invoice").description("Pay a 402 Payment Required response").argument("<url>", "URL that returned 402").option("-g, --gasless", "Use gasless transaction (ERC-4337)").option("-i, --index <index>", "Payment option index (if multiple)", "0").action(async (url, options) => {
447
+ if (!isValidUrl(url)) {
448
+ printError("Invalid URL format");
449
+ return;
450
+ }
451
+ const encrypted = getEncryptedSeed();
452
+ if (!encrypted) {
453
+ printError("No wallet configured. Run 'wallet create' or 'wallet import' first.");
454
+ return;
455
+ }
456
+ try {
457
+ const spinner = createSpinner("Fetching payment requirements...").start();
458
+ const response = await fetch(url);
459
+ if (response.status !== 402) {
460
+ spinner.fail(`Expected 402 status, got ${response.status}`);
461
+ return;
462
+ }
463
+ const paymentRequired = await response.json();
464
+ if (!paymentRequired.accepts || !Array.isArray(paymentRequired.accepts)) {
465
+ spinner.fail("Invalid 402 response format");
466
+ return;
467
+ }
468
+ const index = parseInt(options.index, 10);
469
+ const requirement = paymentRequired.accepts[index];
470
+ if (!requirement) {
471
+ spinner.fail(`No payment option at index ${index}`);
472
+ return;
473
+ }
474
+ spinner.succeed("Payment requirements fetched");
475
+ printHeader("Payment Requirements");
476
+ printTable({
477
+ Resource: paymentRequired.resource?.description || url,
478
+ Amount: `${formatAmount(requirement.amount)} ${requirement.asset?.toUpperCase() || "USDT"}`,
479
+ Network: getNetworkName(requirement.network),
480
+ Recipient: formatAddress(requirement.payTo)
481
+ });
482
+ console.log();
483
+ const seedPhrase = decryptSeed(encrypted, MACHINE_KEY2);
484
+ const paySpinner = createSpinner("Processing payment...").start();
485
+ if (options.gasless) {
486
+ const wdkGasless = await import("@t402/wdk-gasless");
487
+ const { WdkGaslessClient } = wdkGasless;
488
+ paySpinner.text = "Initializing gasless client...";
489
+ const client = await WdkGaslessClient.create({
490
+ seedPhrase,
491
+ chain: requirement.network.replace("eip155:", "")
492
+ });
493
+ paySpinner.text = "Sending gasless payment...";
494
+ const result = await client.pay({
495
+ to: requirement.payTo,
496
+ amount: requirement.amount
497
+ });
498
+ paySpinner.succeed("Payment sent!");
499
+ console.log();
500
+ printSuccess("Payment completed");
501
+ console.log(` User Operation Hash: ${chalk3.cyan(result.userOpHash)}`);
502
+ console.log();
503
+ const retrySpinner = createSpinner("Retrying request with payment...").start();
504
+ const retryResponse = await fetch(url, {
505
+ headers: {
506
+ "X-Payment": result.userOpHash
507
+ }
508
+ });
509
+ if (retryResponse.ok) {
510
+ retrySpinner.succeed("Request successful!");
511
+ console.log(` Status: ${chalk3.green(retryResponse.status)}`);
512
+ } else {
513
+ retrySpinner.warn(`Request returned ${retryResponse.status}`);
514
+ }
515
+ } else {
516
+ const wdk = await import("@t402/wdk");
517
+ const { createPaymentProcessor } = wdk;
518
+ paySpinner.text = "Initializing wallet...";
519
+ const processor = createPaymentProcessor({ seedPhrase });
520
+ await processor.initialize();
521
+ paySpinner.text = "Sending payment...";
522
+ const txHash = await processor.pay({
523
+ network: requirement.network,
524
+ asset: requirement.asset || "usdt",
525
+ to: requirement.payTo,
526
+ amount: requirement.amount
527
+ });
528
+ paySpinner.succeed("Payment sent!");
529
+ console.log();
530
+ printSuccess("Payment completed");
531
+ console.log(` Transaction Hash: ${chalk3.cyan(txHash)}`);
532
+ }
533
+ } catch (error) {
534
+ printError(`Failed to pay invoice: ${error instanceof Error ? error.message : error}`);
535
+ }
536
+ });
537
+ }
538
+
539
+ // src/commands/request.ts
540
+ import chalk4 from "chalk";
541
+
542
+ // src/version.ts
543
+ var VERSION = "2.0.0";
544
+
545
+ // src/commands/request.ts
546
+ var MACHINE_KEY3 = `t402-cli-${process.env.USER || "default"}`;
547
+ function registerRequestCommand(program) {
548
+ program.command("request").description("Make an HTTP request with automatic 402 payment handling").argument("<url>", "URL to request").option("-X, --method <method>", "HTTP method", "GET").option("-H, --header <header...>", "Additional headers (key:value)").option("-d, --data <data>", "Request body data").option("-g, --gasless", "Use gasless payments (ERC-4337)").option("-n, --network <network>", "Preferred network for payment").option("-o, --output <file>", "Save response to file").option("-v, --verbose", "Show detailed output").option("--dry-run", "Show what would be paid without executing").action(
549
+ async (url, options) => {
550
+ if (!isValidUrl(url)) {
551
+ printError("Invalid URL format");
552
+ return;
553
+ }
554
+ const encrypted = getEncryptedSeed();
555
+ if (!encrypted && !options.dryRun) {
556
+ printError("No wallet configured. Run 'wallet create' or 'wallet import' first.");
557
+ return;
558
+ }
559
+ const headers = {
560
+ "User-Agent": `t402-cli/${VERSION}`
561
+ };
562
+ if (options.header) {
563
+ for (const h of options.header) {
564
+ const [key, ...valueParts] = h.split(":");
565
+ if (key && valueParts.length > 0) {
566
+ headers[key.trim()] = valueParts.join(":").trim();
567
+ }
568
+ }
569
+ }
570
+ const fetchOptions = {
571
+ method: options.method,
572
+ headers
573
+ };
574
+ if (options.data) {
575
+ fetchOptions.body = options.data;
576
+ if (!headers["Content-Type"]) {
577
+ headers["Content-Type"] = "application/json";
578
+ }
579
+ }
580
+ try {
581
+ const spinner = createSpinner(`${options.method} ${url}`).start();
582
+ const response = await fetch(url, fetchOptions);
583
+ if (response.status === 402) {
584
+ spinner.info("Payment required (402)");
585
+ const paymentRequired = await response.json();
586
+ if (!paymentRequired.accepts || !Array.isArray(paymentRequired.accepts)) {
587
+ printError("Invalid 402 response format");
588
+ return;
589
+ }
590
+ const preferredNetwork = options.network || getConfig("defaultNetwork");
591
+ let requirement = paymentRequired.accepts.find(
592
+ (r) => r.network === preferredNetwork
593
+ );
594
+ if (!requirement) {
595
+ requirement = paymentRequired.accepts[0];
596
+ }
597
+ printHeader("Payment Required");
598
+ printTable({
599
+ Resource: paymentRequired.resource?.description || url,
600
+ Amount: `${formatAmount(requirement.amount)} ${requirement.asset?.toUpperCase() || "USDT"}`,
601
+ Network: getNetworkName(requirement.network),
602
+ Recipient: formatAddress(requirement.payTo)
603
+ });
604
+ console.log();
605
+ if (options.dryRun) {
606
+ printWarning("Dry run - no payment executed");
607
+ return;
608
+ }
609
+ if (!encrypted) {
610
+ printError("No wallet configured.");
611
+ return;
612
+ }
613
+ const seedPhrase = decryptSeed(encrypted, MACHINE_KEY3);
614
+ const paySpinner = createSpinner("Processing payment...").start();
615
+ let paymentProof;
616
+ if (options.gasless) {
617
+ const wdkGasless = await import("@t402/wdk-gasless");
618
+ const { WdkGaslessClient } = wdkGasless;
619
+ paySpinner.text = "Initializing gasless client...";
620
+ const client = await WdkGaslessClient.create({
621
+ seedPhrase,
622
+ chain: requirement.network.replace("eip155:", "")
623
+ });
624
+ paySpinner.text = "Sending gasless payment...";
625
+ const result = await client.pay({
626
+ to: requirement.payTo,
627
+ amount: requirement.amount
628
+ });
629
+ paymentProof = result.userOpHash;
630
+ paySpinner.succeed("Gasless payment sent");
631
+ if (options.verbose) {
632
+ console.log(` User Operation Hash: ${chalk4.cyan(result.userOpHash)}`);
633
+ }
634
+ } else {
635
+ const wdk = await import("@t402/wdk");
636
+ const { createPaymentProcessor } = wdk;
637
+ paySpinner.text = "Initializing wallet...";
638
+ const processor = createPaymentProcessor({ seedPhrase });
639
+ await processor.initialize();
640
+ paySpinner.text = "Sending payment...";
641
+ const txHash = await processor.pay({
642
+ network: requirement.network,
643
+ asset: requirement.asset || "usdt",
644
+ to: requirement.payTo,
645
+ amount: requirement.amount
646
+ });
647
+ paymentProof = txHash;
648
+ paySpinner.succeed("Payment sent");
649
+ if (options.verbose) {
650
+ console.log(` Transaction Hash: ${chalk4.cyan(txHash)}`);
651
+ }
652
+ }
653
+ console.log();
654
+ const retrySpinner = createSpinner("Retrying request with payment proof...").start();
655
+ const retryResponse = await fetch(url, {
656
+ ...fetchOptions,
657
+ headers: {
658
+ ...headers,
659
+ "X-Payment": paymentProof
660
+ }
661
+ });
662
+ if (retryResponse.ok) {
663
+ retrySpinner.succeed(`${retryResponse.status} ${retryResponse.statusText}`);
664
+ await handleResponse(retryResponse, options);
665
+ } else if (retryResponse.status === 402) {
666
+ retrySpinner.fail("Payment not accepted - still requires payment");
667
+ printError("The server did not accept the payment proof.");
668
+ } else {
669
+ retrySpinner.warn(`${retryResponse.status} ${retryResponse.statusText}`);
670
+ await handleResponse(retryResponse, options);
671
+ }
672
+ } else if (response.ok) {
673
+ spinner.succeed(`${response.status} ${response.statusText}`);
674
+ await handleResponse(response, options);
675
+ } else {
676
+ spinner.fail(`${response.status} ${response.statusText}`);
677
+ await handleResponse(response, options);
678
+ }
679
+ } catch (error) {
680
+ printError(`Request failed: ${error instanceof Error ? error.message : error}`);
681
+ }
682
+ }
683
+ );
684
+ }
685
+ async function handleResponse(response, options) {
686
+ const contentType = response.headers.get("content-type") || "";
687
+ if (options.verbose) {
688
+ console.log();
689
+ console.log(chalk4.gray("Response Headers:"));
690
+ response.headers.forEach((value, key) => {
691
+ console.log(chalk4.gray(` ${key}: ${value}`));
692
+ });
693
+ console.log();
694
+ }
695
+ if (options.output) {
696
+ const { writeFile } = await import("fs/promises");
697
+ const buffer = await response.arrayBuffer();
698
+ await writeFile(options.output, Buffer.from(buffer));
699
+ printSuccess(`Response saved to ${options.output}`);
700
+ } else if (contentType.includes("application/json")) {
701
+ const json = await response.json();
702
+ console.log();
703
+ console.log(JSON.stringify(json, null, 2));
704
+ } else if (contentType.includes("text/")) {
705
+ const text = await response.text();
706
+ console.log();
707
+ console.log(text);
708
+ } else {
709
+ const size = response.headers.get("content-length") || "unknown";
710
+ console.log();
711
+ console.log(chalk4.gray(`Binary response (${size} bytes)`));
712
+ console.log(chalk4.gray("Use --output to save to file"));
713
+ }
714
+ }
715
+
716
+ // src/commands/config.ts
717
+ import chalk5 from "chalk";
718
+ function registerConfigCommands(program) {
719
+ const config2 = program.command("config").description("Configuration management");
720
+ config2.command("show").description("Show current configuration").action(() => {
721
+ const cfg = getAllConfig();
722
+ printHeader("T402 CLI Configuration");
723
+ const displayConfig = {
724
+ "Default Network": getNetworkName(cfg.defaultNetwork),
725
+ "Network ID": cfg.defaultNetwork,
726
+ "Facilitator URL": cfg.facilitatorUrl,
727
+ "Testnet Mode": cfg.testnet ? chalk5.yellow("Yes") : "No",
728
+ "Wallet Configured": cfg.encryptedSeed ? chalk5.green("Yes") : chalk5.gray("No"),
729
+ "Config File": getConfigPath()
730
+ };
731
+ printTable(displayConfig);
732
+ const endpoints = cfg.rpcEndpoints;
733
+ if (Object.keys(endpoints).length > 0) {
734
+ console.log();
735
+ console.log(chalk5.bold("Custom RPC Endpoints:"));
736
+ for (const [network, url] of Object.entries(endpoints)) {
737
+ console.log(` ${getNetworkName(network)}: ${url}`);
738
+ }
739
+ }
740
+ });
741
+ config2.command("get").description("Get a configuration value").argument("<key>", "Configuration key (defaultNetwork, facilitatorUrl, testnet)").action((key) => {
742
+ const validKeys = ["defaultNetwork", "facilitatorUrl", "testnet", "rpcEndpoints"];
743
+ if (!validKeys.includes(key)) {
744
+ printError(`Invalid key. Valid keys: ${validKeys.join(", ")}`);
745
+ return;
746
+ }
747
+ const value = getConfig(key);
748
+ if (typeof value === "object") {
749
+ console.log(JSON.stringify(value, null, 2));
750
+ } else {
751
+ console.log(value);
752
+ }
753
+ });
754
+ config2.command("set").description("Set a configuration value").argument("<key>", "Configuration key").argument("<value>", "Value to set").action((key, value) => {
755
+ switch (key) {
756
+ case "defaultNetwork":
757
+ setConfig("defaultNetwork", value);
758
+ printSuccess(`Default network set to ${getNetworkName(value)}`);
759
+ break;
760
+ case "facilitatorUrl":
761
+ if (!isValidUrl(value)) {
762
+ printError("Invalid URL format");
763
+ return;
764
+ }
765
+ setConfig("facilitatorUrl", value);
766
+ printSuccess(`Facilitator URL set to ${value}`);
767
+ break;
768
+ case "testnet":
769
+ const isTestnet = value.toLowerCase() === "true" || value === "1";
770
+ setConfig("testnet", isTestnet);
771
+ printSuccess(`Testnet mode ${isTestnet ? "enabled" : "disabled"}`);
772
+ if (isTestnet) {
773
+ setConfig("defaultNetwork", "eip155:84532");
774
+ console.log(chalk5.gray(" Default network changed to Base Sepolia"));
775
+ } else {
776
+ setConfig("defaultNetwork", "eip155:8453");
777
+ console.log(chalk5.gray(" Default network changed to Base"));
778
+ }
779
+ break;
780
+ default:
781
+ printError(
782
+ `Unknown key: ${key}. Valid keys: defaultNetwork, facilitatorUrl, testnet`
783
+ );
784
+ }
785
+ });
786
+ config2.command("rpc").description("Set custom RPC endpoint for a network").argument("<network>", "Network ID (e.g., eip155:8453)").argument("<url>", "RPC endpoint URL").action((network, url) => {
787
+ if (!isValidUrl(url)) {
788
+ printError("Invalid URL format");
789
+ return;
790
+ }
791
+ setRpcEndpoint(network, url);
792
+ printSuccess(`RPC endpoint set for ${getNetworkName(network)}`);
793
+ });
794
+ config2.command("reset").description("Reset configuration to defaults").option("-f, --force", "Skip confirmation").action((options) => {
795
+ if (!options.force) {
796
+ printWarning("This will reset all configuration to defaults.");
797
+ printWarning("Your wallet will NOT be removed.");
798
+ console.log();
799
+ console.log("Run with --force to confirm.");
800
+ return;
801
+ }
802
+ const cfg = getAllConfig();
803
+ const encryptedSeed = cfg.encryptedSeed;
804
+ resetConfig();
805
+ if (encryptedSeed) {
806
+ setConfig("encryptedSeed", encryptedSeed);
807
+ }
808
+ printSuccess("Configuration reset to defaults");
809
+ });
810
+ config2.command("path").description("Show configuration file path").action(() => {
811
+ console.log(getConfigPath());
812
+ });
813
+ }
814
+
815
+ // src/commands/info.ts
816
+ import chalk6 from "chalk";
817
+ function registerInfoCommand(program) {
818
+ program.command("info").description("Show supported networks and assets").option("-a, --all", "Show all networks (mainnet and testnet)").option("-t, --testnet", "Show testnet networks only").action((options) => {
819
+ const showTestnet = options.testnet || getConfig("testnet");
820
+ const showAll = options.all;
821
+ printHeader("T402 Payment Protocol");
822
+ console.log(` Version: ${VERSION}`);
823
+ console.log(" Facilitator: " + chalk6.cyan(getConfig("facilitatorUrl")));
824
+ console.log();
825
+ console.log(chalk6.bold("Supported Networks:"));
826
+ console.log();
827
+ const evmMainnets = NETWORKS.filter((n) => n.type === "evm" && !n.testnet);
828
+ const evmTestnets = NETWORKS.filter((n) => n.type === "evm" && n.testnet);
829
+ if (showAll || !showTestnet) {
830
+ console.log(chalk6.underline(" EVM (Mainnet):"));
831
+ for (const network of evmMainnets) {
832
+ console.log(
833
+ ` ${chalk6.green("\u25CF")} ${network.name.padEnd(16)} ${chalk6.gray(network.id)}`
834
+ );
835
+ }
836
+ console.log();
837
+ }
838
+ if (showAll || showTestnet) {
839
+ console.log(chalk6.underline(" EVM (Testnet):"));
840
+ for (const network of evmTestnets) {
841
+ console.log(
842
+ ` ${chalk6.yellow("\u25CF")} ${network.name.padEnd(16)} ${chalk6.gray(network.id)}`
843
+ );
844
+ }
845
+ console.log();
846
+ }
847
+ const solanaNetworks = NETWORKS.filter(
848
+ (n) => n.type === "solana" && (showAll || n.testnet === showTestnet)
849
+ );
850
+ if (solanaNetworks.length > 0) {
851
+ console.log(chalk6.underline(" Solana:"));
852
+ for (const network of solanaNetworks) {
853
+ const color = network.testnet ? chalk6.yellow : chalk6.green;
854
+ console.log(` ${color("\u25CF")} ${network.name.padEnd(16)} ${chalk6.gray(network.id)}`);
855
+ }
856
+ console.log();
857
+ }
858
+ const tonNetworks = NETWORKS.filter(
859
+ (n) => n.type === "ton" && (showAll || n.testnet === showTestnet)
860
+ );
861
+ if (tonNetworks.length > 0) {
862
+ console.log(chalk6.underline(" TON:"));
863
+ for (const network of tonNetworks) {
864
+ const color = network.testnet ? chalk6.yellow : chalk6.green;
865
+ console.log(` ${color("\u25CF")} ${network.name.padEnd(16)} ${chalk6.gray(network.id)}`);
866
+ }
867
+ console.log();
868
+ }
869
+ const tronNetworks = NETWORKS.filter(
870
+ (n) => n.type === "tron" && (showAll || n.testnet === showTestnet)
871
+ );
872
+ if (tronNetworks.length > 0) {
873
+ console.log(chalk6.underline(" TRON:"));
874
+ for (const network of tronNetworks) {
875
+ const color = network.testnet ? chalk6.yellow : chalk6.green;
876
+ console.log(` ${color("\u25CF")} ${network.name.padEnd(16)} ${chalk6.gray(network.id)}`);
877
+ }
878
+ console.log();
879
+ }
880
+ console.log(chalk6.bold("Supported Assets:"));
881
+ console.log();
882
+ console.log(` ${chalk6.cyan("USDT0")} - Tether USD (OFT) on EVM chains`);
883
+ console.log(` ${chalk6.cyan("USDT")} - Tether USD on all chains`);
884
+ console.log();
885
+ console.log(chalk6.bold("Payment Schemes:"));
886
+ console.log();
887
+ console.log(` ${chalk6.magenta("exact")} - Exact amount payment (default)`);
888
+ console.log(` ${chalk6.magenta("upto")} - Payment up to specified amount`);
889
+ console.log();
890
+ console.log(chalk6.bold("Features:"));
891
+ console.log();
892
+ console.log(` ${chalk6.green("\u2713")} Standard payments (all chains)`);
893
+ console.log(` ${chalk6.green("\u2713")} Gasless payments via ERC-4337 (EVM chains)`);
894
+ console.log(` ${chalk6.green("\u2713")} Cross-chain bridging via LayerZero`);
895
+ console.log(` ${chalk6.green("\u2713")} WDK wallet integration`);
896
+ console.log();
897
+ console.log(chalk6.gray("Legend:"));
898
+ console.log(chalk6.gray(` ${chalk6.green("\u25CF")} Mainnet ${chalk6.yellow("\u25CF")} Testnet`));
899
+ });
900
+ program.command("version").description("Show CLI version").action(() => {
901
+ console.log(`t402 CLI v${VERSION}`);
902
+ });
903
+ }
904
+
905
+ // src/cli.ts
906
+ function createCli() {
907
+ const program = new Command();
908
+ program.name("t402").description("Command-line interface for the T402 payment protocol").version(VERSION);
909
+ registerWalletCommands(program);
910
+ registerPayCommands(program);
911
+ registerRequestCommand(program);
912
+ registerConfigCommands(program);
913
+ registerInfoCommand(program);
914
+ return program;
915
+ }
916
+ async function runCli(args = process.argv) {
917
+ const program = createCli();
918
+ await program.parseAsync(args);
919
+ }
920
+
921
+ export {
922
+ DEFAULT_CONFIG,
923
+ NETWORKS,
924
+ getConfig,
925
+ setConfig,
926
+ getAllConfig,
927
+ resetConfig,
928
+ getConfigPath,
929
+ hasSeedConfigured,
930
+ setRpcEndpoint,
931
+ getRpcEndpoint,
932
+ createSpinner,
933
+ formatAddress,
934
+ formatAmount,
935
+ formatBalanceResult,
936
+ formatPaymentResult,
937
+ getNetworkInfo,
938
+ getNetworkName,
939
+ getAvailableNetworks,
940
+ printTable,
941
+ printSuccess,
942
+ printError,
943
+ printWarning,
944
+ printInfo,
945
+ printHeader,
946
+ isValidSeedPhrase,
947
+ parseAmount,
948
+ isValidUrl,
949
+ createCli,
950
+ runCli
951
+ };
952
+ //# sourceMappingURL=chunk-2A2UCRTK.js.map