@paylobster/cli 4.5.0 → 4.6.1

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.
Files changed (39) hide show
  1. package/EXAMPLES.md +61 -61
  2. package/dist/src/commands/alerts.d.ts +3 -0
  3. package/dist/src/commands/alerts.d.ts.map +1 -0
  4. package/dist/src/commands/alerts.js +181 -0
  5. package/dist/src/commands/alerts.js.map +1 -0
  6. package/dist/src/commands/init.d.ts +3 -0
  7. package/dist/src/commands/init.d.ts.map +1 -0
  8. package/dist/src/commands/init.js +232 -0
  9. package/dist/src/commands/init.js.map +1 -0
  10. package/dist/src/commands/link.d.ts +6 -0
  11. package/dist/src/commands/link.d.ts.map +1 -0
  12. package/dist/src/commands/link.js +188 -0
  13. package/dist/src/commands/link.js.map +1 -0
  14. package/dist/src/commands/refund.d.ts +6 -0
  15. package/dist/src/commands/refund.d.ts.map +1 -0
  16. package/dist/src/commands/refund.js +199 -0
  17. package/dist/src/commands/refund.js.map +1 -0
  18. package/dist/src/commands/swap.d.ts.map +1 -1
  19. package/dist/src/commands/swap.js +93 -1
  20. package/dist/src/commands/swap.js.map +1 -1
  21. package/dist/src/commands/webhook.d.ts +6 -0
  22. package/dist/src/commands/webhook.d.ts.map +1 -0
  23. package/dist/src/commands/webhook.js +208 -0
  24. package/dist/src/commands/webhook.js.map +1 -0
  25. package/dist/src/index.js +10 -0
  26. package/dist/src/index.js.map +1 -1
  27. package/dist/src/lib/contracts.d.ts +23 -161
  28. package/dist/src/lib/contracts.d.ts.map +1 -1
  29. package/dist/src/lib/contracts.js +6 -6
  30. package/dist/src/lib/contracts.js.map +1 -1
  31. package/package.json +1 -1
  32. package/src/commands/alerts.ts +210 -0
  33. package/src/commands/init.ts +256 -0
  34. package/src/commands/link.ts +240 -0
  35. package/src/commands/refund.ts +250 -0
  36. package/src/commands/swap.ts +120 -1
  37. package/src/commands/webhook.ts +260 -0
  38. package/src/index.ts +10 -0
  39. package/src/lib/contracts.ts +6 -6
@@ -0,0 +1,240 @@
1
+ import { Command } from 'commander';
2
+ import { success, error, info, withSpinner, outputJSON, formatAddress } from '../lib/display';
3
+ import type { OutputOptions } from '../lib/types';
4
+
5
+ const API_URL = process.env.PAYLOBSTER_API_URL || 'https://paylobster.com';
6
+
7
+ interface PaymentLink {
8
+ id: string;
9
+ url: string;
10
+ shortUrl: string;
11
+ amount: string;
12
+ token: string;
13
+ memo: string;
14
+ recipient: string;
15
+ mode: 'direct' | 'escrow';
16
+ expiresAt: string | null;
17
+ createdAt: string;
18
+ paid?: boolean;
19
+ txHash?: string | null;
20
+ }
21
+
22
+ /**
23
+ * Payment links command
24
+ */
25
+ export function createLinkCommand(): Command {
26
+ const cmd = new Command('link')
27
+ .description('Create and manage payment links');
28
+
29
+ // Create subcommand
30
+ cmd
31
+ .command('create')
32
+ .description('Create a payment link')
33
+ .requiredOption('--amount <amount>', 'Amount in tokens')
34
+ .requiredOption('--recipient <address>', 'Recipient address')
35
+ .option('--memo <text>', 'Payment memo/description')
36
+ .option('--token <symbol>', 'Token symbol', 'USDC')
37
+ .option('--mode <mode>', 'Payment mode: direct or escrow', 'direct')
38
+ .option('--expires-in <seconds>', 'Expiration time in seconds')
39
+ .option('--json', 'Output as JSON')
40
+ .action(async (options: {
41
+ amount: string;
42
+ recipient: string;
43
+ memo?: string;
44
+ token?: string;
45
+ mode?: string;
46
+ expiresIn?: string;
47
+ } & OutputOptions) => {
48
+ try {
49
+ // Validate recipient address
50
+ if (!options.recipient.startsWith('0x') || options.recipient.length !== 42) {
51
+ error('Invalid recipient address format');
52
+ process.exit(1);
53
+ }
54
+
55
+ // Validate amount
56
+ const amount = parseFloat(options.amount);
57
+ if (isNaN(amount) || amount <= 0) {
58
+ error('Invalid amount. Must be a positive number');
59
+ process.exit(1);
60
+ }
61
+
62
+ // Validate mode
63
+ if (options.mode && !['direct', 'escrow'].includes(options.mode)) {
64
+ error('Mode must be either "direct" or "escrow"');
65
+ process.exit(1);
66
+ }
67
+
68
+ const link = await withSpinner(
69
+ 'Creating payment link...',
70
+ async () => {
71
+ const response = await fetch(`${API_URL}/api/v3/links`, {
72
+ method: 'POST',
73
+ headers: {
74
+ 'Content-Type': 'application/json',
75
+ },
76
+ body: JSON.stringify({
77
+ amount: options.amount,
78
+ recipient: options.recipient,
79
+ memo: options.memo,
80
+ token: options.token,
81
+ mode: options.mode,
82
+ expiresIn: options.expiresIn ? parseInt(options.expiresIn) : undefined,
83
+ }),
84
+ });
85
+
86
+ if (!response.ok) {
87
+ const err = await response.json() as { error?: string };
88
+ throw new Error(err.error || 'Failed to create payment link');
89
+ }
90
+
91
+ return response.json() as Promise<PaymentLink>;
92
+ }
93
+ );
94
+
95
+ if (outputJSON(link, options)) {
96
+ return;
97
+ }
98
+
99
+ success('Payment link created!');
100
+ console.log();
101
+ console.log(' Link ID: ', link.id);
102
+ console.log(' URL: ', link.url);
103
+ console.log(' Amount: ', link.amount, link.token);
104
+ console.log(' Recipient: ', formatAddress(link.recipient));
105
+ console.log(' Mode: ', link.mode);
106
+ if (link.memo) {
107
+ console.log(' Memo: ', link.memo);
108
+ }
109
+ if (link.expiresAt) {
110
+ console.log(' Expires: ', new Date(link.expiresAt).toLocaleString());
111
+ }
112
+ console.log();
113
+ info('Share this link with the payer!');
114
+ } catch (err) {
115
+ error(`Failed to create payment link: ${(err as Error).message}`);
116
+ process.exit(1);
117
+ }
118
+ });
119
+
120
+ // Get subcommand
121
+ cmd
122
+ .command('get <code>')
123
+ .description('Get payment link details')
124
+ .option('--json', 'Output as JSON')
125
+ .action(async (code: string, options: OutputOptions) => {
126
+ try {
127
+ const link = await withSpinner(
128
+ 'Fetching payment link...',
129
+ async () => {
130
+ const response = await fetch(`${API_URL}/api/v3/links/${code}`, {
131
+ method: 'GET',
132
+ headers: {
133
+ 'Content-Type': 'application/json',
134
+ },
135
+ });
136
+
137
+ if (!response.ok) {
138
+ const err = await response.json() as { error?: string };
139
+ throw new Error(err.error || 'Failed to fetch payment link');
140
+ }
141
+
142
+ return response.json() as Promise<PaymentLink & { expired?: boolean }>;
143
+ }
144
+ );
145
+
146
+ if (outputJSON(link, options)) {
147
+ return;
148
+ }
149
+
150
+ console.log();
151
+ console.log('Payment Link Details:');
152
+ console.log(' Link ID: ', link.id);
153
+ console.log(' Amount: ', link.amount, link.token);
154
+ console.log(' Recipient: ', formatAddress(link.recipient));
155
+ console.log(' Mode: ', link.mode);
156
+ console.log(' Status: ', link.paid ? '✅ Paid' : link.expired ? '❌ Expired' : '⏳ Pending');
157
+ if (link.memo) {
158
+ console.log(' Memo: ', link.memo);
159
+ }
160
+ if (link.txHash) {
161
+ console.log(' TX Hash: ', link.txHash);
162
+ }
163
+ console.log(' Created: ', new Date(link.createdAt).toLocaleString());
164
+ if (link.expiresAt) {
165
+ console.log(' Expires: ', new Date(link.expiresAt).toLocaleString());
166
+ }
167
+ console.log();
168
+ } catch (err) {
169
+ error(`Failed to fetch payment link: ${(err as Error).message}`);
170
+ process.exit(1);
171
+ }
172
+ });
173
+
174
+ // List subcommand
175
+ cmd
176
+ .command('list <recipient>')
177
+ .description('List payment links for a recipient')
178
+ .option('--json', 'Output as JSON')
179
+ .action(async (recipient: string, options: OutputOptions) => {
180
+ try {
181
+ // Validate recipient address
182
+ if (!recipient.startsWith('0x') || recipient.length !== 42) {
183
+ error('Invalid recipient address format');
184
+ process.exit(1);
185
+ }
186
+
187
+ const result = await withSpinner(
188
+ 'Fetching payment links...',
189
+ async () => {
190
+ const response = await fetch(
191
+ `${API_URL}/api/v3/links?recipient=${encodeURIComponent(recipient)}`,
192
+ {
193
+ method: 'GET',
194
+ headers: {
195
+ 'Content-Type': 'application/json',
196
+ },
197
+ }
198
+ );
199
+
200
+ if (!response.ok) {
201
+ const err = await response.json() as { error?: string };
202
+ throw new Error(err.error || 'Failed to fetch payment links');
203
+ }
204
+
205
+ return response.json() as Promise<{ links: PaymentLink[]; total: number }>;
206
+ }
207
+ );
208
+
209
+ if (outputJSON(result, options)) {
210
+ return;
211
+ }
212
+
213
+ if (result.links.length === 0) {
214
+ info('No payment links found for this recipient');
215
+ return;
216
+ }
217
+
218
+ console.log();
219
+ console.log(`Found ${result.total} payment link(s):`);
220
+ console.log();
221
+
222
+ for (const link of result.links) {
223
+ console.log(` ${link.id}`);
224
+ console.log(` Amount: ${link.amount} ${link.token}`);
225
+ console.log(` Status: ${link.paid ? '✅ Paid' : '⏳ Pending'}`);
226
+ console.log(` Mode: ${link.mode}`);
227
+ if (link.memo) {
228
+ console.log(` Memo: ${link.memo}`);
229
+ }
230
+ console.log(` Created: ${new Date(link.createdAt).toLocaleString()}`);
231
+ console.log();
232
+ }
233
+ } catch (err) {
234
+ error(`Failed to fetch payment links: ${(err as Error).message}`);
235
+ process.exit(1);
236
+ }
237
+ });
238
+
239
+ return cmd;
240
+ }
@@ -0,0 +1,250 @@
1
+ import { Command } from 'commander';
2
+ import { success, error, info, withSpinner, outputJSON, confirm } from '../lib/display';
3
+ import type { OutputOptions } from '../lib/types';
4
+
5
+ const API_URL = process.env.PAYLOBSTER_API_URL || 'https://paylobster.com';
6
+
7
+ interface Refund {
8
+ refundId: string;
9
+ escrowId: number;
10
+ status: 'pending' | 'approved' | 'rejected' | 'processed';
11
+ amount: string;
12
+ reason: string;
13
+ txHash?: string;
14
+ auto?: boolean;
15
+ createdAt: string;
16
+ processedAt?: string;
17
+ }
18
+
19
+ /**
20
+ * Refunds command
21
+ */
22
+ export function createRefundCommand(): Command {
23
+ const cmd = new Command('refund')
24
+ .description('Request and manage refunds');
25
+
26
+ // Request subcommand
27
+ cmd
28
+ .command('request')
29
+ .description('Request a refund for an escrow')
30
+ .requiredOption('--escrow-id <id>', 'Escrow ID')
31
+ .requiredOption('--reason <text>', 'Reason for refund')
32
+ .option('--tx-hash <hash>', 'Transaction hash (if applicable)')
33
+ .option('--yes', 'Skip confirmation')
34
+ .option('--json', 'Output as JSON')
35
+ .action(async (options: {
36
+ escrowId: string;
37
+ reason: string;
38
+ txHash?: string;
39
+ yes?: boolean;
40
+ } & OutputOptions) => {
41
+ try {
42
+ // Validate escrow ID
43
+ const escrowId = parseInt(options.escrowId);
44
+ if (isNaN(escrowId) || escrowId < 0) {
45
+ error('Invalid escrow ID. Must be a non-negative number');
46
+ process.exit(1);
47
+ }
48
+
49
+ // Confirm refund request
50
+ if (!options.yes) {
51
+ console.log();
52
+ console.log('Refund Request:');
53
+ console.log(' Escrow ID: ', escrowId);
54
+ console.log(' Reason: ', options.reason);
55
+ console.log();
56
+
57
+ const confirmed = await confirm('Submit this refund request?');
58
+ if (!confirmed) {
59
+ info('Cancelled');
60
+ process.exit(0);
61
+ }
62
+ }
63
+
64
+ const refund = await withSpinner(
65
+ 'Submitting refund request...',
66
+ async () => {
67
+ const response = await fetch(`${API_URL}/api/v3/refunds`, {
68
+ method: 'POST',
69
+ headers: {
70
+ 'Content-Type': 'application/json',
71
+ },
72
+ body: JSON.stringify({
73
+ escrowId,
74
+ reason: options.reason,
75
+ txHash: options.txHash,
76
+ }),
77
+ });
78
+
79
+ if (!response.ok) {
80
+ const err = await response.json() as { error?: string };
81
+ throw new Error(err.error || 'Failed to request refund');
82
+ }
83
+
84
+ return response.json() as Promise<Refund>;
85
+ }
86
+ );
87
+
88
+ if (outputJSON(refund, options)) {
89
+ return;
90
+ }
91
+
92
+ success('Refund request submitted!');
93
+ console.log();
94
+ console.log(' Refund ID: ', refund.refundId);
95
+ console.log(' Escrow ID: ', refund.escrowId);
96
+ console.log(' Status: ', getStatusEmoji(refund.status), refund.status);
97
+ console.log(' Amount: ', refund.amount);
98
+ console.log(' Reason: ', refund.reason);
99
+ console.log(' Created: ', new Date(refund.createdAt).toLocaleString());
100
+ console.log();
101
+ info('Your refund request will be processed according to the escrow terms');
102
+ } catch (err) {
103
+ error(`Failed to request refund: ${(err as Error).message}`);
104
+ process.exit(1);
105
+ }
106
+ });
107
+
108
+ // Get subcommand
109
+ cmd
110
+ .command('get <refund-id>')
111
+ .description('Get refund status')
112
+ .option('--json', 'Output as JSON')
113
+ .action(async (refundId: string, options: OutputOptions) => {
114
+ try {
115
+ const refund = await withSpinner(
116
+ 'Fetching refund details...',
117
+ async () => {
118
+ const response = await fetch(
119
+ `${API_URL}/api/v3/refunds?refundId=${encodeURIComponent(refundId)}`,
120
+ {
121
+ method: 'GET',
122
+ headers: {
123
+ 'Content-Type': 'application/json',
124
+ },
125
+ }
126
+ );
127
+
128
+ if (!response.ok) {
129
+ const err = await response.json() as { error?: string };
130
+ throw new Error(err.error || 'Failed to fetch refund');
131
+ }
132
+
133
+ return response.json() as Promise<Refund>;
134
+ }
135
+ );
136
+
137
+ if (outputJSON(refund, options)) {
138
+ return;
139
+ }
140
+
141
+ console.log();
142
+ console.log('Refund Details:');
143
+ console.log(' Refund ID: ', refund.refundId);
144
+ console.log(' Escrow ID: ', refund.escrowId);
145
+ console.log(' Status: ', getStatusEmoji(refund.status), refund.status);
146
+ console.log(' Amount: ', refund.amount);
147
+ console.log(' Reason: ', refund.reason);
148
+ if (refund.auto) {
149
+ console.log(' Auto: ', '✅ Automatic refund');
150
+ }
151
+ if (refund.txHash) {
152
+ console.log(' TX Hash: ', refund.txHash);
153
+ }
154
+ console.log(' Created: ', new Date(refund.createdAt).toLocaleString());
155
+ if (refund.processedAt) {
156
+ console.log(' Processed: ', new Date(refund.processedAt).toLocaleString());
157
+ }
158
+ console.log();
159
+ } catch (err) {
160
+ error(`Failed to fetch refund: ${(err as Error).message}`);
161
+ process.exit(1);
162
+ }
163
+ });
164
+
165
+ // List by escrow subcommand
166
+ cmd
167
+ .command('list <escrow-id>')
168
+ .description('List all refunds for an escrow')
169
+ .option('--json', 'Output as JSON')
170
+ .action(async (escrowIdStr: string, options: OutputOptions) => {
171
+ try {
172
+ // Validate escrow ID
173
+ const escrowId = parseInt(escrowIdStr);
174
+ if (isNaN(escrowId) || escrowId < 0) {
175
+ error('Invalid escrow ID. Must be a non-negative number');
176
+ process.exit(1);
177
+ }
178
+
179
+ const result = await withSpinner(
180
+ 'Fetching refunds...',
181
+ async () => {
182
+ const response = await fetch(
183
+ `${API_URL}/api/v3/refunds?escrowId=${escrowId}`,
184
+ {
185
+ method: 'GET',
186
+ headers: {
187
+ 'Content-Type': 'application/json',
188
+ },
189
+ }
190
+ );
191
+
192
+ if (!response.ok) {
193
+ const err = await response.json() as { error?: string };
194
+ throw new Error(err.error || 'Failed to fetch refunds');
195
+ }
196
+
197
+ return response.json() as Promise<{ escrowId: number; refunds: Refund[] }>;
198
+ }
199
+ );
200
+
201
+ if (outputJSON(result, options)) {
202
+ return;
203
+ }
204
+
205
+ if (result.refunds.length === 0) {
206
+ info(`No refunds found for escrow ID ${escrowId}`);
207
+ return;
208
+ }
209
+
210
+ console.log();
211
+ console.log(`Found ${result.refunds.length} refund(s) for escrow ${escrowId}:`);
212
+ console.log();
213
+
214
+ for (const refund of result.refunds) {
215
+ console.log(` ${refund.refundId}`);
216
+ console.log(` Status: ${getStatusEmoji(refund.status)} ${refund.status}`);
217
+ console.log(` Amount: ${refund.amount}`);
218
+ console.log(` Reason: ${refund.reason}`);
219
+ if (refund.auto) {
220
+ console.log(` Auto: ✅ Automatic`);
221
+ }
222
+ console.log(` Created: ${new Date(refund.createdAt).toLocaleString()}`);
223
+ if (refund.processedAt) {
224
+ console.log(` Processed: ${new Date(refund.processedAt).toLocaleString()}`);
225
+ }
226
+ console.log();
227
+ }
228
+ } catch (err) {
229
+ error(`Failed to fetch refunds: ${(err as Error).message}`);
230
+ process.exit(1);
231
+ }
232
+ });
233
+
234
+ return cmd;
235
+ }
236
+
237
+ function getStatusEmoji(status: string): string {
238
+ switch (status) {
239
+ case 'pending':
240
+ return '⏳';
241
+ case 'approved':
242
+ return '✅';
243
+ case 'rejected':
244
+ return '❌';
245
+ case 'processed':
246
+ return '✅';
247
+ default:
248
+ return '❓';
249
+ }
250
+ }
@@ -155,12 +155,42 @@ async function getTokenPrice(tokenAddress: string): Promise<any> {
155
155
  return call0xAPI(endpoint);
156
156
  }
157
157
 
158
+ const API_BASE = process.env.PAYLOBSTER_API_URL || 'https://paylobster.com';
159
+
160
+ /**
161
+ * Get smart route from PayLobster API
162
+ */
163
+ async function getSmartRoute(
164
+ sellToken: string,
165
+ buyToken: string,
166
+ sellAmount: string,
167
+ takerAddress?: string
168
+ ): Promise<any> {
169
+ const res = await fetch(`${API_BASE}/api/v3/swap/route`, {
170
+ method: 'POST',
171
+ headers: { 'Content-Type': 'application/json' },
172
+ body: JSON.stringify({
173
+ tokenIn: sellToken,
174
+ tokenOut: buyToken,
175
+ amountIn: sellAmount,
176
+ takerAddress,
177
+ }),
178
+ });
179
+
180
+ if (!res.ok) {
181
+ const data = await res.json().catch(() => ({}));
182
+ throw new Error(data.error || `Smart route failed: ${res.status}`);
183
+ }
184
+
185
+ return res.json();
186
+ }
187
+
158
188
  /**
159
189
  * Create swap command
160
190
  */
161
191
  export function createSwapCommand(): Command {
162
192
  const cmd = new Command('swap')
163
- .description('Token swaps on Base using 0x');
193
+ .description('Token swaps on Base using 0x (with smart routing)');
164
194
 
165
195
  // Swap quote subcommand
166
196
  cmd
@@ -300,6 +330,95 @@ export function createSwapCommand(): Command {
300
330
  }
301
331
  });
302
332
 
333
+ // Smart swap subcommand (natural language style)
334
+ cmd
335
+ .command('auto')
336
+ .description('Smart swap with auto-routing (multi-hop if needed)')
337
+ .argument('<amount>', 'Amount to sell')
338
+ .argument('<fromToken>', 'Token to sell')
339
+ .argument('[toKeyword]', '"to" keyword (ignored)')
340
+ .argument('[toToken]', 'Token to buy')
341
+ .option('--usd', 'Interpret amount as USD value')
342
+ .option('--slippage <percent>', 'Slippage tolerance (default: 0.5)', '0.5')
343
+ .option('--yes', 'Skip confirmation')
344
+ .option('--json', 'Output as JSON')
345
+ .action(async (amount: string, fromTokenArg: string, toKeyword: string, toTokenArg: string, options: {
346
+ usd?: boolean;
347
+ slippage?: string;
348
+ yes?: boolean;
349
+ } & OutputOptions) => {
350
+ try {
351
+ const address = getWalletAddress() as `0x${string}`;
352
+
353
+ // Handle "10 USDC to ANTIHUNTER" pattern
354
+ let toToken = toTokenArg;
355
+ if (toKeyword && toKeyword.toLowerCase() !== 'to') {
356
+ // "swap auto 10 USDC ANTIHUNTER" — no "to" keyword
357
+ toToken = toKeyword;
358
+ }
359
+
360
+ if (!toToken) {
361
+ error('Missing destination token. Usage: paylobster swap auto 10 USDC to ANTIHUNTER');
362
+ process.exit(1);
363
+ }
364
+
365
+ const sellTokenAddr = resolveToken(fromTokenArg);
366
+ const buyTokenAddr = resolveToken(toToken);
367
+ const sellDecimals = getTokenDecimals(fromTokenArg);
368
+ const sellAmount = parseTokenAmount(amount.replace('$', ''), sellDecimals);
369
+
370
+ const routeData = await withSpinner(
371
+ '🧠 Finding best route...',
372
+ async () => getSmartRoute(sellTokenAddr, buyTokenAddr, sellAmount, address)
373
+ );
374
+
375
+ if (!routeData.bestRoute) {
376
+ error('No route found for this swap');
377
+ process.exit(1);
378
+ }
379
+
380
+ const route = routeData.bestRoute;
381
+ const hopCount = route.hops?.length || 1;
382
+
383
+ if (outputJSON(routeData, options)) return;
384
+
385
+ console.log();
386
+ console.log(chalk.bold.cyan('🧠 Smart Route Found'));
387
+ console.log();
388
+ console.log(' From: ', chalk.white.bold(amount), chalk.gray(fromTokenArg.toUpperCase()));
389
+ console.log(' To: ', chalk.white.bold(toToken.toUpperCase()));
390
+ console.log(' Route: ', chalk.white(route.source || 'Auto'));
391
+ console.log(' Hops: ', chalk.white(hopCount.toString()));
392
+
393
+ // Show each hop
394
+ if (route.hops) {
395
+ for (let i = 0; i < route.hops.length; i++) {
396
+ const hop = route.hops[i];
397
+ console.log(chalk.gray(` Step ${i + 1}: `), chalk.dim(`${hop.dex}: ${hop.tokenIn.slice(0, 8)}... → ${hop.tokenOut.slice(0, 8)}...`));
398
+ }
399
+ }
400
+
401
+ console.log(' Output: ', chalk.green.bold(routeData.estimatedOutput));
402
+ console.log(' Impact: ', chalk.yellow(`${((routeData.priceImpact || 0) * 100).toFixed(2)}%`));
403
+ console.log(' Gas: ', chalk.gray(`~${routeData.gasEstimate}`));
404
+ console.log();
405
+
406
+ if (!options.yes) {
407
+ const confirmed = await confirm('Execute this swap?');
408
+ if (!confirmed) {
409
+ info('Cancelled');
410
+ process.exit(0);
411
+ }
412
+ }
413
+
414
+ info('Swap execution requires wallet signing. Transaction data is available above.');
415
+ console.log();
416
+ } catch (err) {
417
+ error(`Smart swap failed: ${err}`);
418
+ process.exit(1);
419
+ }
420
+ });
421
+
303
422
  // List tokens subcommand
304
423
  cmd
305
424
  .command('tokens')