@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.
- package/EXAMPLES.md +61 -61
- package/dist/src/commands/alerts.d.ts +3 -0
- package/dist/src/commands/alerts.d.ts.map +1 -0
- package/dist/src/commands/alerts.js +181 -0
- package/dist/src/commands/alerts.js.map +1 -0
- package/dist/src/commands/init.d.ts +3 -0
- package/dist/src/commands/init.d.ts.map +1 -0
- package/dist/src/commands/init.js +232 -0
- package/dist/src/commands/init.js.map +1 -0
- package/dist/src/commands/link.d.ts +6 -0
- package/dist/src/commands/link.d.ts.map +1 -0
- package/dist/src/commands/link.js +188 -0
- package/dist/src/commands/link.js.map +1 -0
- package/dist/src/commands/refund.d.ts +6 -0
- package/dist/src/commands/refund.d.ts.map +1 -0
- package/dist/src/commands/refund.js +199 -0
- package/dist/src/commands/refund.js.map +1 -0
- package/dist/src/commands/swap.d.ts.map +1 -1
- package/dist/src/commands/swap.js +93 -1
- package/dist/src/commands/swap.js.map +1 -1
- package/dist/src/commands/webhook.d.ts +6 -0
- package/dist/src/commands/webhook.d.ts.map +1 -0
- package/dist/src/commands/webhook.js +208 -0
- package/dist/src/commands/webhook.js.map +1 -0
- package/dist/src/index.js +10 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/lib/contracts.d.ts +23 -161
- package/dist/src/lib/contracts.d.ts.map +1 -1
- package/dist/src/lib/contracts.js +6 -6
- package/dist/src/lib/contracts.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/alerts.ts +210 -0
- package/src/commands/init.ts +256 -0
- package/src/commands/link.ts +240 -0
- package/src/commands/refund.ts +250 -0
- package/src/commands/swap.ts +120 -1
- package/src/commands/webhook.ts +260 -0
- package/src/index.ts +10 -0
- 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
|
+
}
|
package/src/commands/swap.ts
CHANGED
|
@@ -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')
|