@paylobster/cli 4.0.2 → 4.2.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.
Files changed (64) hide show
  1. package/IMPLEMENTATION_SUMMARY.md +288 -0
  2. package/NEW_FEATURES.md +228 -0
  3. package/dist/src/commands/bridge.d.ts +6 -0
  4. package/dist/src/commands/bridge.d.ts.map +1 -0
  5. package/dist/src/commands/bridge.js +273 -0
  6. package/dist/src/commands/bridge.js.map +1 -0
  7. package/dist/src/commands/cascade.d.ts +3 -0
  8. package/dist/src/commands/cascade.d.ts.map +1 -0
  9. package/dist/src/commands/cascade.js +242 -0
  10. package/dist/src/commands/cascade.js.map +1 -0
  11. package/dist/src/commands/compliance.d.ts +3 -0
  12. package/dist/src/commands/compliance.d.ts.map +1 -0
  13. package/dist/src/commands/compliance.js +121 -0
  14. package/dist/src/commands/compliance.js.map +1 -0
  15. package/dist/src/commands/credit-score.d.ts +3 -0
  16. package/dist/src/commands/credit-score.d.ts.map +1 -0
  17. package/dist/src/commands/credit-score.js +174 -0
  18. package/dist/src/commands/credit-score.js.map +1 -0
  19. package/dist/src/commands/dispute.d.ts +3 -0
  20. package/dist/src/commands/dispute.d.ts.map +1 -0
  21. package/dist/src/commands/dispute.js +241 -0
  22. package/dist/src/commands/dispute.js.map +1 -0
  23. package/dist/src/commands/intent.d.ts +3 -0
  24. package/dist/src/commands/intent.d.ts.map +1 -0
  25. package/dist/src/commands/intent.js +227 -0
  26. package/dist/src/commands/intent.js.map +1 -0
  27. package/dist/src/commands/oracle.d.ts +3 -0
  28. package/dist/src/commands/oracle.d.ts.map +1 -0
  29. package/dist/src/commands/oracle.js +114 -0
  30. package/dist/src/commands/oracle.js.map +1 -0
  31. package/dist/src/commands/portfolio.d.ts +6 -0
  32. package/dist/src/commands/portfolio.d.ts.map +1 -0
  33. package/dist/src/commands/portfolio.js +179 -0
  34. package/dist/src/commands/portfolio.js.map +1 -0
  35. package/dist/src/commands/revenue-share.d.ts +3 -0
  36. package/dist/src/commands/revenue-share.d.ts.map +1 -0
  37. package/dist/src/commands/revenue-share.js +185 -0
  38. package/dist/src/commands/revenue-share.js.map +1 -0
  39. package/dist/src/commands/stream.d.ts +3 -0
  40. package/dist/src/commands/stream.d.ts.map +1 -0
  41. package/dist/src/commands/stream.js +213 -0
  42. package/dist/src/commands/stream.js.map +1 -0
  43. package/dist/src/commands/swap.d.ts +6 -0
  44. package/dist/src/commands/swap.d.ts.map +1 -0
  45. package/dist/src/commands/swap.js +278 -0
  46. package/dist/src/commands/swap.js.map +1 -0
  47. package/dist/src/index.js +23 -1
  48. package/dist/src/index.js.map +1 -1
  49. package/dist/src/lib/types.d.ts +1 -0
  50. package/dist/src/lib/types.d.ts.map +1 -1
  51. package/package.json +1 -1
  52. package/src/commands/bridge.ts +443 -0
  53. package/src/commands/cascade.ts +280 -0
  54. package/src/commands/compliance.ts +123 -0
  55. package/src/commands/credit-score.ts +193 -0
  56. package/src/commands/dispute.ts +274 -0
  57. package/src/commands/intent.ts +261 -0
  58. package/src/commands/oracle.ts +116 -0
  59. package/src/commands/portfolio.ts +227 -0
  60. package/src/commands/revenue-share.ts +213 -0
  61. package/src/commands/stream.ts +244 -0
  62. package/src/commands/swap.ts +365 -0
  63. package/src/index.ts +23 -1
  64. package/src/lib/types.ts +1 -0
@@ -0,0 +1,365 @@
1
+ import { Command } from 'commander';
2
+ import { getWalletAddress } from '../lib/wallet';
3
+ import { loadConfig, getConfigValue } from '../lib/config';
4
+ import { success, error, info, withSpinner, outputJSON, confirm, formatNumber } from '../lib/display';
5
+ import chalk from 'chalk';
6
+ import type { OutputOptions } from '../lib/types';
7
+
8
+ const ZERO_X_API_URL = 'https://api.0x.org';
9
+ const BASE_CHAIN_ID = '8453';
10
+
11
+ interface SwapQuoteResponse {
12
+ sellAmount: string;
13
+ buyAmount: string;
14
+ price: string;
15
+ estimatedGas: string;
16
+ gasPrice: string;
17
+ sellTokenAddress: string;
18
+ buyTokenAddress: string;
19
+ allowanceTarget: string;
20
+ transaction: {
21
+ to: string;
22
+ data: string;
23
+ value: string;
24
+ gas: string;
25
+ gasPrice: string;
26
+ };
27
+ }
28
+
29
+ interface TokenInfo {
30
+ symbol: string;
31
+ address: string;
32
+ decimals: number;
33
+ }
34
+
35
+ // Common Base tokens
36
+ const BASE_TOKENS: Record<string, TokenInfo> = {
37
+ USDC: { symbol: 'USDC', address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', decimals: 6 },
38
+ WETH: { symbol: 'WETH', address: '0x4200000000000000000000000000000000000006', decimals: 18 },
39
+ DAI: { symbol: 'DAI', address: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb', decimals: 18 },
40
+ USDbC: { symbol: 'USDbC', address: '0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA', decimals: 6 },
41
+ ETH: { symbol: 'ETH', address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', decimals: 18 },
42
+ };
43
+
44
+ /**
45
+ * Resolve token symbol or address to token address
46
+ */
47
+ function resolveToken(input: string): string {
48
+ const upperInput = input.toUpperCase();
49
+ if (BASE_TOKENS[upperInput]) {
50
+ return BASE_TOKENS[upperInput].address;
51
+ }
52
+ // Assume it's an address
53
+ if (input.startsWith('0x')) {
54
+ return input;
55
+ }
56
+ throw new Error(`Unknown token: ${input}`);
57
+ }
58
+
59
+ /**
60
+ * Get token decimals
61
+ */
62
+ function getTokenDecimals(symbolOrAddress: string): number {
63
+ const upperInput = symbolOrAddress.toUpperCase();
64
+ if (BASE_TOKENS[upperInput]) {
65
+ return BASE_TOKENS[upperInput].decimals;
66
+ }
67
+ // Default to 18 decimals for unknown tokens
68
+ return 18;
69
+ }
70
+
71
+ /**
72
+ * Format amount with decimals
73
+ */
74
+ function formatTokenAmount(amount: string, decimals: number): string {
75
+ const num = BigInt(amount);
76
+ const divisor = BigInt(10 ** decimals);
77
+ const whole = num / divisor;
78
+ const fraction = num % divisor;
79
+
80
+ if (fraction === 0n) {
81
+ return whole.toString();
82
+ }
83
+
84
+ const fractionStr = fraction.toString().padStart(decimals, '0');
85
+ // Trim trailing zeros
86
+ const trimmed = fractionStr.replace(/0+$/, '');
87
+ return `${whole}.${trimmed}`;
88
+ }
89
+
90
+ /**
91
+ * Parse amount with decimals
92
+ */
93
+ function parseTokenAmount(amount: string, decimals: number): string {
94
+ const [whole = '0', fraction = '0'] = amount.split('.');
95
+ const paddedFraction = fraction.padEnd(decimals, '0').slice(0, decimals);
96
+ return (BigInt(whole) * BigInt(10 ** decimals) + BigInt(paddedFraction)).toString();
97
+ }
98
+
99
+ /**
100
+ * Call 0x API
101
+ */
102
+ async function call0xAPI(endpoint: string): Promise<any> {
103
+ const config = loadConfig();
104
+ const apiKey = getConfigValue('0xApiKey') || process.env.ZERO_X_API_KEY;
105
+
106
+ if (!apiKey) {
107
+ throw new Error('0x API key not configured. Set it with: plob config set 0xApiKey YOUR_KEY');
108
+ }
109
+
110
+ const response = await fetch(`${ZERO_X_API_URL}${endpoint}`, {
111
+ headers: {
112
+ '0x-api-key': apiKey,
113
+ '0x-version': 'v2',
114
+ },
115
+ });
116
+
117
+ if (!response.ok) {
118
+ const errorText = await response.text();
119
+ throw new Error(`0x API error: ${response.status} - ${errorText}`);
120
+ }
121
+
122
+ return response.json();
123
+ }
124
+
125
+ /**
126
+ * Get swap quote
127
+ */
128
+ async function getSwapQuote(
129
+ sellToken: string,
130
+ buyToken: string,
131
+ amount: string,
132
+ taker: string
133
+ ): Promise<SwapQuoteResponse> {
134
+ const endpoint = `/swap/allowance-holder/quote?chainId=${BASE_CHAIN_ID}&sellToken=${sellToken}&buyToken=${buyToken}&sellAmount=${amount}&taker=${taker}`;
135
+ return call0xAPI(endpoint);
136
+ }
137
+
138
+ /**
139
+ * Get swap price
140
+ */
141
+ async function getSwapPrice(
142
+ sellToken: string,
143
+ buyToken: string,
144
+ amount: string
145
+ ): Promise<SwapQuoteResponse> {
146
+ const endpoint = `/swap/allowance-holder/price?chainId=${BASE_CHAIN_ID}&sellToken=${sellToken}&buyToken=${buyToken}&sellAmount=${amount}`;
147
+ return call0xAPI(endpoint);
148
+ }
149
+
150
+ /**
151
+ * Get token price
152
+ */
153
+ async function getTokenPrice(tokenAddress: string): Promise<any> {
154
+ const endpoint = `/swap/allowance-holder/price?chainId=${BASE_CHAIN_ID}&sellToken=${tokenAddress}&buyToken=USDC&sellAmount=${parseTokenAmount('1', getTokenDecimals(tokenAddress))}`;
155
+ return call0xAPI(endpoint);
156
+ }
157
+
158
+ /**
159
+ * Create swap command
160
+ */
161
+ export function createSwapCommand(): Command {
162
+ const cmd = new Command('swap')
163
+ .description('Token swaps on Base using 0x');
164
+
165
+ // Swap quote subcommand
166
+ cmd
167
+ .command('quote')
168
+ .description('Get swap quote')
169
+ .requiredOption('--from <token>', 'Token to sell (symbol or address)')
170
+ .requiredOption('--to <token>', 'Token to buy (symbol or address)')
171
+ .requiredOption('--amount <amount>', 'Amount to sell')
172
+ .option('--json', 'Output as JSON')
173
+ .action(async (options: {
174
+ from: string;
175
+ to: string;
176
+ amount: string;
177
+ } & OutputOptions) => {
178
+ try {
179
+ const address = getWalletAddress() as `0x${string}`;
180
+
181
+ const sellToken = resolveToken(options.from);
182
+ const buyToken = resolveToken(options.to);
183
+ const sellDecimals = getTokenDecimals(options.from);
184
+ const buyDecimals = getTokenDecimals(options.to);
185
+
186
+ const sellAmount = parseTokenAmount(options.amount, sellDecimals);
187
+
188
+ const quote = await withSpinner(
189
+ 'Fetching swap quote...',
190
+ async () => getSwapQuote(sellToken, buyToken, sellAmount, address)
191
+ );
192
+
193
+ const buyAmountFormatted = formatTokenAmount(quote.buyAmount, buyDecimals);
194
+ const sellAmountFormatted = formatTokenAmount(quote.sellAmount, sellDecimals);
195
+ const pricePerToken = parseFloat(quote.price);
196
+
197
+ // Calculate USD values (approximation using price ratio)
198
+ const buyValueUSD = parseFloat(sellAmountFormatted) * pricePerToken;
199
+
200
+ // Calculate fee
201
+ const gasEstimate = BigInt(quote.estimatedGas);
202
+ const gasPrice = BigInt(quote.gasPrice);
203
+ const gasCostWei = gasEstimate * gasPrice;
204
+ const gasCostEth = formatTokenAmount(gasCostWei.toString(), 18);
205
+
206
+ if (outputJSON({
207
+ sellToken: options.from.toUpperCase(),
208
+ sellAmount: sellAmountFormatted,
209
+ buyToken: options.to.toUpperCase(),
210
+ buyAmount: buyAmountFormatted,
211
+ price: quote.price,
212
+ estimatedGas: quote.estimatedGas,
213
+ gasCostEth,
214
+ }, options)) {
215
+ return;
216
+ }
217
+
218
+ // Pretty output
219
+ console.log();
220
+ console.log(chalk.bold.cyan('🔄 Swap Quote'));
221
+ console.log();
222
+ console.log(' Sell:', chalk.white.bold(sellAmountFormatted), chalk.gray(options.from.toUpperCase()));
223
+ console.log(' Buy: ', chalk.white.bold(buyAmountFormatted), chalk.gray(options.to.toUpperCase()), chalk.green(`(~$${buyValueUSD.toFixed(2)})`));
224
+ console.log(' Rate:', chalk.white(`1 ${options.to.toUpperCase()} = ${(1 / pricePerToken).toFixed(6)} ${options.from.toUpperCase()}`));
225
+ console.log(' Gas: ', chalk.gray(`~$${(parseFloat(gasCostEth) * 3000).toFixed(2)}`), chalk.dim(`(${gasCostEth} ETH)`));
226
+ console.log();
227
+ } catch (err) {
228
+ error(`Failed to get swap quote: ${err}`);
229
+ process.exit(1);
230
+ }
231
+ });
232
+
233
+ // Swap execute subcommand
234
+ cmd
235
+ .command('execute')
236
+ .description('Execute token swap')
237
+ .requiredOption('--from <token>', 'Token to sell (symbol or address)')
238
+ .requiredOption('--to <token>', 'Token to buy (symbol or address)')
239
+ .requiredOption('--amount <amount>', 'Amount to sell')
240
+ .option('--yes', 'Skip confirmation')
241
+ .option('--json', 'Output as JSON')
242
+ .action(async (options: {
243
+ from: string;
244
+ to: string;
245
+ amount: string;
246
+ yes?: boolean;
247
+ } & OutputOptions) => {
248
+ try {
249
+ const address = getWalletAddress() as `0x${string}`;
250
+
251
+ const sellToken = resolveToken(options.from);
252
+ const buyToken = resolveToken(options.to);
253
+ const sellDecimals = getTokenDecimals(options.from);
254
+ const buyDecimals = getTokenDecimals(options.to);
255
+
256
+ const sellAmount = parseTokenAmount(options.amount, sellDecimals);
257
+
258
+ const quote = await withSpinner(
259
+ 'Fetching swap quote...',
260
+ async () => getSwapQuote(sellToken, buyToken, sellAmount, address)
261
+ );
262
+
263
+ const buyAmountFormatted = formatTokenAmount(quote.buyAmount, buyDecimals);
264
+ const sellAmountFormatted = formatTokenAmount(quote.sellAmount, sellDecimals);
265
+
266
+ // Show quote and confirm
267
+ if (!options.yes) {
268
+ console.log();
269
+ console.log(chalk.bold.cyan('🔄 Swap Quote'));
270
+ console.log();
271
+ console.log(' Sell:', chalk.white.bold(sellAmountFormatted), chalk.gray(options.from.toUpperCase()));
272
+ console.log(' Buy: ', chalk.white.bold(buyAmountFormatted), chalk.gray(options.to.toUpperCase()));
273
+ console.log();
274
+
275
+ const confirmed = await confirm('Confirm swap?');
276
+ if (!confirmed) {
277
+ info('Cancelled');
278
+ process.exit(0);
279
+ }
280
+ }
281
+
282
+ info('Swap execution not yet implemented - requires viem integration');
283
+ info('Transaction data available in quote response');
284
+
285
+ if (outputJSON({
286
+ quote,
287
+ note: 'Execute swap by sending transaction to quote.transaction.to with quote.transaction.data',
288
+ }, options)) {
289
+ return;
290
+ }
291
+
292
+ console.log();
293
+ info('To execute this swap, send a transaction with the following data:');
294
+ console.log(chalk.gray(' To: '), quote.transaction.to);
295
+ console.log(chalk.gray(' Data: '), quote.transaction.data.slice(0, 66) + '...');
296
+ console.log();
297
+ } catch (err) {
298
+ error(`Failed to execute swap: ${err}`);
299
+ process.exit(1);
300
+ }
301
+ });
302
+
303
+ // List tokens subcommand
304
+ cmd
305
+ .command('tokens')
306
+ .description('List available tokens on Base')
307
+ .option('--json', 'Output as JSON')
308
+ .action(async (options: OutputOptions) => {
309
+ const tokens = Object.values(BASE_TOKENS).map(t => ({
310
+ symbol: t.symbol,
311
+ address: t.address,
312
+ decimals: t.decimals,
313
+ }));
314
+
315
+ if (outputJSON({ tokens }, options)) {
316
+ return;
317
+ }
318
+
319
+ console.log();
320
+ console.log(chalk.bold.cyan('🪙 Available Tokens on Base'));
321
+ console.log();
322
+
323
+ for (const token of tokens) {
324
+ console.log(chalk.white.bold(token.symbol.padEnd(8)), chalk.gray(token.address), chalk.dim(`(${token.decimals} decimals)`));
325
+ }
326
+ console.log();
327
+ });
328
+
329
+ // Get token price subcommand
330
+ cmd
331
+ .command('price')
332
+ .description('Get token price in USDC')
333
+ .argument('<address>', 'Token address')
334
+ .option('--json', 'Output as JSON')
335
+ .action(async (address: string, options: OutputOptions) => {
336
+ try {
337
+ const priceData = await withSpinner(
338
+ 'Fetching token price...',
339
+ async () => getTokenPrice(address)
340
+ );
341
+
342
+ const decimals = getTokenDecimals(address);
343
+ const buyAmount = formatTokenAmount(priceData.buyAmount, 6); // USDC has 6 decimals
344
+
345
+ if (outputJSON({
346
+ tokenAddress: address,
347
+ priceUSDC: buyAmount,
348
+ }, options)) {
349
+ return;
350
+ }
351
+
352
+ console.log();
353
+ console.log(chalk.bold.cyan('💵 Token Price'));
354
+ console.log();
355
+ console.log(' Token: ', chalk.white(address));
356
+ console.log(' Price: ', chalk.white.bold(buyAmount), chalk.gray('USDC'));
357
+ console.log();
358
+ } catch (err) {
359
+ error(`Failed to get token price: ${err}`);
360
+ process.exit(1);
361
+ }
362
+ });
363
+
364
+ return cmd;
365
+ }
package/src/index.ts CHANGED
@@ -9,6 +9,17 @@ import { createEscrowCommand } from './commands/escrow';
9
9
  import { createMandateCommand } from './commands/mandate';
10
10
  import { createPayCommand } from './commands/pay';
11
11
  import { createReputationCommand } from './commands/reputation';
12
+ import { createStreamCommand } from './commands/stream';
13
+ import { createDisputeCommand } from './commands/dispute';
14
+ import { createCreditScoreCommand } from './commands/credit-score';
15
+ import { createCascadeCommand } from './commands/cascade';
16
+ import { createIntentCommand } from './commands/intent';
17
+ import { createComplianceCommand } from './commands/compliance';
18
+ import { createOracleCommand } from './commands/oracle';
19
+ import { createRevenueShareCommand } from './commands/revenue-share';
20
+ import { createSwapCommand } from './commands/swap';
21
+ import { createBridgeCommand } from './commands/bridge';
22
+ import { createPortfolioCommand } from './commands/portfolio';
12
23
  import chalk from 'chalk';
13
24
 
14
25
  // Load environment variables from .env file if present
@@ -20,7 +31,7 @@ const program = new Command();
20
31
  program
21
32
  .name('plob')
22
33
  .description('🦞 PayLobster CLI - Payment infrastructure for AI agents')
23
- .version('4.0.0');
34
+ .version('4.2.0');
24
35
 
25
36
  // ASCII art banner
26
37
  const banner = `
@@ -44,6 +55,17 @@ program.addCommand(createEscrowCommand());
44
55
  program.addCommand(createMandateCommand());
45
56
  program.addCommand(createPayCommand());
46
57
  program.addCommand(createReputationCommand());
58
+ program.addCommand(createStreamCommand());
59
+ program.addCommand(createDisputeCommand());
60
+ program.addCommand(createCreditScoreCommand());
61
+ program.addCommand(createCascadeCommand());
62
+ program.addCommand(createIntentCommand());
63
+ program.addCommand(createComplianceCommand());
64
+ program.addCommand(createOracleCommand());
65
+ program.addCommand(createRevenueShareCommand());
66
+ program.addCommand(createSwapCommand());
67
+ program.addCommand(createBridgeCommand());
68
+ program.addCommand(createPortfolioCommand());
47
69
 
48
70
  // Handle errors
49
71
  program.exitOverride((err) => {
package/src/lib/types.ts CHANGED
@@ -10,6 +10,7 @@ export interface CLIConfig {
10
10
  path?: string;
11
11
  };
12
12
  indexerUrl?: string;
13
+ '0xApiKey'?: string;
13
14
  }
14
15
 
15
16
  export interface AgentInfo {