@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.
- package/IMPLEMENTATION_SUMMARY.md +288 -0
- package/NEW_FEATURES.md +228 -0
- package/dist/src/commands/bridge.d.ts +6 -0
- package/dist/src/commands/bridge.d.ts.map +1 -0
- package/dist/src/commands/bridge.js +273 -0
- package/dist/src/commands/bridge.js.map +1 -0
- package/dist/src/commands/cascade.d.ts +3 -0
- package/dist/src/commands/cascade.d.ts.map +1 -0
- package/dist/src/commands/cascade.js +242 -0
- package/dist/src/commands/cascade.js.map +1 -0
- package/dist/src/commands/compliance.d.ts +3 -0
- package/dist/src/commands/compliance.d.ts.map +1 -0
- package/dist/src/commands/compliance.js +121 -0
- package/dist/src/commands/compliance.js.map +1 -0
- package/dist/src/commands/credit-score.d.ts +3 -0
- package/dist/src/commands/credit-score.d.ts.map +1 -0
- package/dist/src/commands/credit-score.js +174 -0
- package/dist/src/commands/credit-score.js.map +1 -0
- package/dist/src/commands/dispute.d.ts +3 -0
- package/dist/src/commands/dispute.d.ts.map +1 -0
- package/dist/src/commands/dispute.js +241 -0
- package/dist/src/commands/dispute.js.map +1 -0
- package/dist/src/commands/intent.d.ts +3 -0
- package/dist/src/commands/intent.d.ts.map +1 -0
- package/dist/src/commands/intent.js +227 -0
- package/dist/src/commands/intent.js.map +1 -0
- package/dist/src/commands/oracle.d.ts +3 -0
- package/dist/src/commands/oracle.d.ts.map +1 -0
- package/dist/src/commands/oracle.js +114 -0
- package/dist/src/commands/oracle.js.map +1 -0
- package/dist/src/commands/portfolio.d.ts +6 -0
- package/dist/src/commands/portfolio.d.ts.map +1 -0
- package/dist/src/commands/portfolio.js +179 -0
- package/dist/src/commands/portfolio.js.map +1 -0
- package/dist/src/commands/revenue-share.d.ts +3 -0
- package/dist/src/commands/revenue-share.d.ts.map +1 -0
- package/dist/src/commands/revenue-share.js +185 -0
- package/dist/src/commands/revenue-share.js.map +1 -0
- package/dist/src/commands/stream.d.ts +3 -0
- package/dist/src/commands/stream.d.ts.map +1 -0
- package/dist/src/commands/stream.js +213 -0
- package/dist/src/commands/stream.js.map +1 -0
- package/dist/src/commands/swap.d.ts +6 -0
- package/dist/src/commands/swap.d.ts.map +1 -0
- package/dist/src/commands/swap.js +278 -0
- package/dist/src/commands/swap.js.map +1 -0
- package/dist/src/index.js +23 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/lib/types.d.ts +1 -0
- package/dist/src/lib/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/commands/bridge.ts +443 -0
- package/src/commands/cascade.ts +280 -0
- package/src/commands/compliance.ts +123 -0
- package/src/commands/credit-score.ts +193 -0
- package/src/commands/dispute.ts +274 -0
- package/src/commands/intent.ts +261 -0
- package/src/commands/oracle.ts +116 -0
- package/src/commands/portfolio.ts +227 -0
- package/src/commands/revenue-share.ts +213 -0
- package/src/commands/stream.ts +244 -0
- package/src/commands/swap.ts +365 -0
- package/src/index.ts +23 -1
- package/src/lib/types.ts +1 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { getWalletAddress } from '../lib/wallet';
|
|
3
|
+
import { getPublicClient, getContracts, formatUSDC, formatETH } from '../lib/contracts';
|
|
4
|
+
import { loadConfig } from '../lib/config';
|
|
5
|
+
import { error, outputJSON, withSpinner, createTable, formatNumber } from '../lib/display';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import type { Address } from 'viem';
|
|
8
|
+
import type { OutputOptions } from '../lib/types';
|
|
9
|
+
|
|
10
|
+
// Token configuration for Base
|
|
11
|
+
interface TokenConfig {
|
|
12
|
+
symbol: string;
|
|
13
|
+
address: Address;
|
|
14
|
+
decimals: number;
|
|
15
|
+
isNative?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const BASE_TOKENS: TokenConfig[] = [
|
|
19
|
+
{ symbol: 'ETH', address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' as Address, decimals: 18, isNative: true },
|
|
20
|
+
{ symbol: 'USDC', address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' as Address, decimals: 6 },
|
|
21
|
+
{ symbol: 'WETH', address: '0x4200000000000000000000000000000000000006' as Address, decimals: 18 },
|
|
22
|
+
{ symbol: 'DAI', address: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb' as Address, decimals: 18 },
|
|
23
|
+
{ symbol: 'USDbC', address: '0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA' as Address, decimals: 6 },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const ERC20_ABI = [
|
|
27
|
+
{
|
|
28
|
+
inputs: [{ name: 'account', type: 'address' }],
|
|
29
|
+
name: 'balanceOf',
|
|
30
|
+
outputs: [{ name: '', type: 'uint256' }],
|
|
31
|
+
stateMutability: 'view',
|
|
32
|
+
type: 'function',
|
|
33
|
+
},
|
|
34
|
+
] as const;
|
|
35
|
+
|
|
36
|
+
interface TokenBalance {
|
|
37
|
+
symbol: string;
|
|
38
|
+
balance: string;
|
|
39
|
+
balanceRaw: bigint;
|
|
40
|
+
valueUSD: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Format token amount with decimals
|
|
45
|
+
*/
|
|
46
|
+
function formatTokenAmount(amount: bigint, decimals: number): string {
|
|
47
|
+
const num = amount;
|
|
48
|
+
const divisor = BigInt(10 ** decimals);
|
|
49
|
+
const whole = num / divisor;
|
|
50
|
+
const fraction = num % divisor;
|
|
51
|
+
|
|
52
|
+
if (fraction === 0n) {
|
|
53
|
+
return whole.toString();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const fractionStr = fraction.toString().padStart(decimals, '0');
|
|
57
|
+
const trimmed = fractionStr.replace(/0+$/, '');
|
|
58
|
+
const limited = trimmed.slice(0, 6); // Limit to 6 decimal places for display
|
|
59
|
+
return `${whole}.${limited}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get token price in USD (mock - in production, use price oracle)
|
|
64
|
+
*/
|
|
65
|
+
async function getTokenPriceUSD(symbol: string): Promise<number> {
|
|
66
|
+
// Mock prices - in production, integrate with price oracle
|
|
67
|
+
const prices: Record<string, number> = {
|
|
68
|
+
ETH: 3000,
|
|
69
|
+
WETH: 3000,
|
|
70
|
+
USDC: 1,
|
|
71
|
+
DAI: 1,
|
|
72
|
+
USDbC: 1,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
return prices[symbol] || 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get token balance
|
|
80
|
+
*/
|
|
81
|
+
async function getTokenBalance(
|
|
82
|
+
address: Address,
|
|
83
|
+
token: TokenConfig
|
|
84
|
+
): Promise<bigint> {
|
|
85
|
+
const config = loadConfig();
|
|
86
|
+
const publicClient = getPublicClient(config.network);
|
|
87
|
+
|
|
88
|
+
if (token.isNative) {
|
|
89
|
+
// Get native ETH balance
|
|
90
|
+
return await publicClient.getBalance({ address });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Get ERC20 token balance
|
|
94
|
+
try {
|
|
95
|
+
const balance = await publicClient.readContract({
|
|
96
|
+
address: token.address,
|
|
97
|
+
abi: ERC20_ABI,
|
|
98
|
+
functionName: 'balanceOf',
|
|
99
|
+
args: [address],
|
|
100
|
+
});
|
|
101
|
+
return balance;
|
|
102
|
+
} catch (err) {
|
|
103
|
+
// If token doesn't exist or error, return 0
|
|
104
|
+
return 0n;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get all token balances
|
|
110
|
+
*/
|
|
111
|
+
async function getPortfolioBalances(
|
|
112
|
+
address: Address,
|
|
113
|
+
tokens: TokenConfig[]
|
|
114
|
+
): Promise<TokenBalance[]> {
|
|
115
|
+
const balances = await Promise.all(
|
|
116
|
+
tokens.map(async (token) => {
|
|
117
|
+
const balanceRaw = await getTokenBalance(address, token);
|
|
118
|
+
const balance = formatTokenAmount(balanceRaw, token.decimals);
|
|
119
|
+
const price = await getTokenPriceUSD(token.symbol);
|
|
120
|
+
const valueUSD = parseFloat(balance) * price;
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
symbol: token.symbol,
|
|
124
|
+
balance,
|
|
125
|
+
balanceRaw,
|
|
126
|
+
valueUSD,
|
|
127
|
+
};
|
|
128
|
+
})
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// Filter out zero balances and sort by USD value
|
|
132
|
+
return balances
|
|
133
|
+
.filter((b) => b.balanceRaw > 0n)
|
|
134
|
+
.sort((a, b) => b.valueUSD - a.valueUSD);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Create portfolio command
|
|
139
|
+
*/
|
|
140
|
+
export function createPortfolioCommand(): Command {
|
|
141
|
+
const cmd = new Command('portfolio')
|
|
142
|
+
.description('View token portfolio and balances')
|
|
143
|
+
.option('--chain <chain>', 'Filter by chain (currently only Base supported)', 'base')
|
|
144
|
+
.option('--json', 'Output as JSON')
|
|
145
|
+
.action(async (options: {
|
|
146
|
+
chain: string;
|
|
147
|
+
} & OutputOptions) => {
|
|
148
|
+
try {
|
|
149
|
+
const address = getWalletAddress() as `0x${string}`;
|
|
150
|
+
|
|
151
|
+
if (options.chain.toLowerCase() !== 'base') {
|
|
152
|
+
error('Currently only Base chain is supported');
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const balances = await withSpinner(
|
|
157
|
+
'Fetching portfolio balances...',
|
|
158
|
+
async () => getPortfolioBalances(address, BASE_TOKENS)
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
if (balances.length === 0) {
|
|
162
|
+
if (outputJSON({ balances: [], totalUSD: 0 }, options)) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
console.log();
|
|
166
|
+
console.log(chalk.yellow('No token balances found'));
|
|
167
|
+
console.log();
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const totalUSD = balances.reduce((sum, b) => sum + b.valueUSD, 0);
|
|
172
|
+
|
|
173
|
+
if (outputJSON({
|
|
174
|
+
address,
|
|
175
|
+
chain: 'Base',
|
|
176
|
+
balances: balances.map(b => ({
|
|
177
|
+
symbol: b.symbol,
|
|
178
|
+
balance: b.balance,
|
|
179
|
+
valueUSD: b.valueUSD.toFixed(2),
|
|
180
|
+
})),
|
|
181
|
+
totalUSD: totalUSD.toFixed(2),
|
|
182
|
+
}, options)) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Pretty output
|
|
187
|
+
console.log();
|
|
188
|
+
console.log(chalk.bold.cyan('💼 Portfolio'));
|
|
189
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
190
|
+
console.log(chalk.gray('Address: ') + chalk.white(address));
|
|
191
|
+
console.log(chalk.gray('Chain: ') + chalk.white('Base'));
|
|
192
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
193
|
+
console.log();
|
|
194
|
+
|
|
195
|
+
// Create table
|
|
196
|
+
const table = createTable({
|
|
197
|
+
head: ['Token', 'Balance', 'Value (USD)'],
|
|
198
|
+
colAligns: ['left', 'right', 'right'] as any,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
for (const token of balances) {
|
|
202
|
+
table.push([
|
|
203
|
+
chalk.white.bold(token.symbol),
|
|
204
|
+
chalk.white(formatNumber(token.balance)),
|
|
205
|
+
chalk.green('$' + formatNumber(token.valueUSD.toFixed(2))),
|
|
206
|
+
]);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
console.log(table.toString());
|
|
210
|
+
console.log();
|
|
211
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
212
|
+
console.log(
|
|
213
|
+
chalk.white.bold('Total Portfolio Value:'),
|
|
214
|
+
chalk.green.bold('$' + formatNumber(totalUSD.toFixed(2)))
|
|
215
|
+
);
|
|
216
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
217
|
+
console.log();
|
|
218
|
+
console.log(chalk.dim('Note: Prices are approximate and for display only.'));
|
|
219
|
+
console.log();
|
|
220
|
+
} catch (err) {
|
|
221
|
+
error(`Failed to fetch portfolio: ${err}`);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
return cmd;
|
|
227
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { success, error, info, warning, withSpinner, outputJSON, formatAddress, confirm, createTable } from '../lib/display';
|
|
3
|
+
import type { OutputOptions } from '../lib/types';
|
|
4
|
+
|
|
5
|
+
export function createRevenueShareCommand(): Command {
|
|
6
|
+
const cmd = new Command('revenue-share')
|
|
7
|
+
.description('Manage revenue sharing agreements');
|
|
8
|
+
|
|
9
|
+
// plob revenue-share create
|
|
10
|
+
cmd
|
|
11
|
+
.command('create')
|
|
12
|
+
.description('Create a revenue sharing agreement')
|
|
13
|
+
.requiredOption('--participants <addresses>', 'Comma-separated participant addresses')
|
|
14
|
+
.requiredOption('--splits <percentages>', 'Comma-separated split percentages (must sum to 100)')
|
|
15
|
+
.option('--json', 'Output as JSON')
|
|
16
|
+
.action(async (options: {
|
|
17
|
+
participants: string;
|
|
18
|
+
splits: string;
|
|
19
|
+
} & OutputOptions) => {
|
|
20
|
+
try {
|
|
21
|
+
const participants = options.participants.split(',').map(p => p.trim());
|
|
22
|
+
const splits = options.splits.split(',').map(s => parseFloat(s.trim()));
|
|
23
|
+
|
|
24
|
+
// Validate addresses
|
|
25
|
+
for (const participant of participants) {
|
|
26
|
+
if (!participant.startsWith('0x') || participant.length !== 42) {
|
|
27
|
+
error(`Invalid participant address: ${participant}`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Validate splits
|
|
33
|
+
if (participants.length !== splits.length) {
|
|
34
|
+
error('Number of participants must match number of splits');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (const split of splits) {
|
|
39
|
+
if (isNaN(split) || split < 0 || split > 100) {
|
|
40
|
+
error('Each split must be between 0 and 100');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const totalSplit = splits.reduce((a, b) => a + b, 0);
|
|
46
|
+
if (Math.abs(totalSplit - 100) > 0.01) {
|
|
47
|
+
error(`Splits must sum to 100 (current: ${totalSplit})`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
info('Creating revenue share agreement');
|
|
52
|
+
participants.forEach((p, i) => {
|
|
53
|
+
info(` ${formatAddress(p)}: ${splits[i]}%`);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const shareId = Math.floor(Math.random() * 1000000);
|
|
57
|
+
|
|
58
|
+
const txHash = await withSpinner(
|
|
59
|
+
'Creating revenue share...',
|
|
60
|
+
async () => {
|
|
61
|
+
// Placeholder - implement actual contract call
|
|
62
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
63
|
+
return '0x' + Math.random().toString(16).substring(2, 66);
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
if (outputJSON({
|
|
68
|
+
shareId,
|
|
69
|
+
participants,
|
|
70
|
+
splits,
|
|
71
|
+
txHash,
|
|
72
|
+
}, options)) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
success('Revenue share created!');
|
|
77
|
+
info(`Share ID: ${shareId}`);
|
|
78
|
+
info(`Transaction: ${txHash}`);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
error(`Failed to create revenue share: ${err}`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// plob revenue-share deposit
|
|
86
|
+
cmd
|
|
87
|
+
.command('deposit')
|
|
88
|
+
.description('Deposit funds to revenue share')
|
|
89
|
+
.argument('<shareId>', 'Share ID')
|
|
90
|
+
.requiredOption('--amount <amount>', 'Amount in USDC')
|
|
91
|
+
.option('--json', 'Output as JSON')
|
|
92
|
+
.action(async (shareId: string, options: { amount: string } & OutputOptions) => {
|
|
93
|
+
try {
|
|
94
|
+
const amount = parseFloat(options.amount);
|
|
95
|
+
if (isNaN(amount) || amount <= 0) {
|
|
96
|
+
error('Invalid amount. Must be a positive number');
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const txHash = await withSpinner(
|
|
101
|
+
`Depositing ${options.amount} USDC to revenue share...`,
|
|
102
|
+
async () => {
|
|
103
|
+
// Placeholder - implement actual contract call
|
|
104
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
105
|
+
return '0x' + Math.random().toString(16).substring(2, 66);
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
if (outputJSON({
|
|
110
|
+
shareId,
|
|
111
|
+
amount: options.amount,
|
|
112
|
+
txHash,
|
|
113
|
+
}, options)) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
success('Deposit successful!');
|
|
118
|
+
info(`Share ID: ${shareId}`);
|
|
119
|
+
info(`Amount: ${options.amount} USDC`);
|
|
120
|
+
info(`Transaction: ${txHash}`);
|
|
121
|
+
} catch (err) {
|
|
122
|
+
error(`Failed to deposit: ${err}`);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// plob revenue-share distribute
|
|
128
|
+
cmd
|
|
129
|
+
.command('distribute')
|
|
130
|
+
.description('Distribute revenue to participants')
|
|
131
|
+
.argument('<shareId>', 'Share ID')
|
|
132
|
+
.option('--yes', 'Skip confirmation')
|
|
133
|
+
.option('--json', 'Output as JSON')
|
|
134
|
+
.action(async (shareId: string, options: { yes?: boolean } & OutputOptions) => {
|
|
135
|
+
try {
|
|
136
|
+
if (!options.yes) {
|
|
137
|
+
const confirmed = await confirm(`Distribute revenue for share ${shareId}?`);
|
|
138
|
+
if (!confirmed) {
|
|
139
|
+
info('Cancelled');
|
|
140
|
+
process.exit(0);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const txHash = await withSpinner(
|
|
145
|
+
'Distributing revenue...',
|
|
146
|
+
async () => {
|
|
147
|
+
// Placeholder - implement actual contract call
|
|
148
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
149
|
+
return '0x' + Math.random().toString(16).substring(2, 66);
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
if (outputJSON({
|
|
154
|
+
shareId,
|
|
155
|
+
txHash,
|
|
156
|
+
}, options)) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
success('Revenue distributed!');
|
|
161
|
+
info(`Share ID: ${shareId}`);
|
|
162
|
+
info(`Transaction: ${txHash}`);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
error(`Failed to distribute: ${err}`);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// plob revenue-share get
|
|
170
|
+
cmd
|
|
171
|
+
.command('get')
|
|
172
|
+
.description('Get revenue share details')
|
|
173
|
+
.argument('<shareId>', 'Share ID')
|
|
174
|
+
.option('--json', 'Output as JSON')
|
|
175
|
+
.action(async (shareId: string, options: OutputOptions) => {
|
|
176
|
+
try {
|
|
177
|
+
// Placeholder data
|
|
178
|
+
const share = {
|
|
179
|
+
id: shareId,
|
|
180
|
+
totalDeposited: '1000 USDC',
|
|
181
|
+
totalDistributed: '600 USDC',
|
|
182
|
+
pending: '400 USDC',
|
|
183
|
+
participants: [
|
|
184
|
+
{ address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', split: 60, received: '360 USDC' },
|
|
185
|
+
{ address: '0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199', split: 40, received: '240 USDC' },
|
|
186
|
+
],
|
|
187
|
+
createdAt: new Date().toISOString(),
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
if (outputJSON(share, options)) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
console.log();
|
|
195
|
+
console.log('Share ID: ', share.id);
|
|
196
|
+
console.log('Total Deposited: ', share.totalDeposited);
|
|
197
|
+
console.log('Total Distributed: ', share.totalDistributed);
|
|
198
|
+
console.log('Pending: ', share.pending);
|
|
199
|
+
console.log('Created: ', share.createdAt);
|
|
200
|
+
console.log();
|
|
201
|
+
console.log('Participants:');
|
|
202
|
+
share.participants.forEach(p => {
|
|
203
|
+
console.log(` ${formatAddress(p.address)}: ${p.split}% (received ${p.received})`);
|
|
204
|
+
});
|
|
205
|
+
console.log();
|
|
206
|
+
} catch (err) {
|
|
207
|
+
error(`Failed to get revenue share: ${err}`);
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
return cmd;
|
|
213
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { success, error, info, withSpinner, outputJSON, formatAddress, confirm, createTable } from '../lib/display';
|
|
3
|
+
import type { OutputOptions } from '../lib/types';
|
|
4
|
+
|
|
5
|
+
export function createStreamCommand(): Command {
|
|
6
|
+
const cmd = new Command('stream')
|
|
7
|
+
.description('Manage payment streams');
|
|
8
|
+
|
|
9
|
+
// plob stream create
|
|
10
|
+
cmd
|
|
11
|
+
.command('create')
|
|
12
|
+
.description('Create a new payment stream')
|
|
13
|
+
.requiredOption('--to <address>', 'Recipient address')
|
|
14
|
+
.requiredOption('--amount <amount>', 'Total amount in USDC')
|
|
15
|
+
.requiredOption('--duration <duration>', 'Duration (e.g., 30d, 1h, 60m)')
|
|
16
|
+
.option('--interval <interval>', 'Payment interval (e.g., 1d, 1h)', '1d')
|
|
17
|
+
.option('--json', 'Output as JSON')
|
|
18
|
+
.action(async (options: {
|
|
19
|
+
to: string;
|
|
20
|
+
amount: string;
|
|
21
|
+
duration: string;
|
|
22
|
+
interval: string;
|
|
23
|
+
} & OutputOptions) => {
|
|
24
|
+
try {
|
|
25
|
+
// Validate address
|
|
26
|
+
if (!options.to.startsWith('0x') || options.to.length !== 42) {
|
|
27
|
+
error('Invalid recipient address format');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Validate amount
|
|
32
|
+
const amount = parseFloat(options.amount);
|
|
33
|
+
if (isNaN(amount) || amount <= 0) {
|
|
34
|
+
error('Invalid amount. Must be a positive number');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
info(`Creating stream: ${options.amount} USDC over ${options.duration}`);
|
|
39
|
+
info(`To: ${formatAddress(options.to)}`);
|
|
40
|
+
info(`Interval: ${options.interval}`);
|
|
41
|
+
|
|
42
|
+
// TODO: Implement actual stream creation
|
|
43
|
+
const streamId = Math.floor(Math.random() * 1000000); // Placeholder
|
|
44
|
+
|
|
45
|
+
const txHash = await withSpinner(
|
|
46
|
+
'Creating payment stream...',
|
|
47
|
+
async () => {
|
|
48
|
+
// Placeholder - implement actual contract call
|
|
49
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
50
|
+
return '0x' + Math.random().toString(16).substring(2, 66);
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
if (outputJSON({
|
|
55
|
+
streamId,
|
|
56
|
+
txHash,
|
|
57
|
+
to: options.to,
|
|
58
|
+
amount: options.amount,
|
|
59
|
+
duration: options.duration,
|
|
60
|
+
interval: options.interval,
|
|
61
|
+
}, options)) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
success('Payment stream created!');
|
|
66
|
+
info(`Stream ID: ${streamId}`);
|
|
67
|
+
info(`Transaction: ${txHash}`);
|
|
68
|
+
} catch (err) {
|
|
69
|
+
error(`Failed to create stream: ${err}`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// plob stream cancel
|
|
75
|
+
cmd
|
|
76
|
+
.command('cancel')
|
|
77
|
+
.description('Cancel a payment stream')
|
|
78
|
+
.argument('<streamId>', 'Stream ID')
|
|
79
|
+
.option('--yes', 'Skip confirmation')
|
|
80
|
+
.option('--json', 'Output as JSON')
|
|
81
|
+
.action(async (streamId: string, options: { yes?: boolean } & OutputOptions) => {
|
|
82
|
+
try {
|
|
83
|
+
if (!options.yes) {
|
|
84
|
+
const confirmed = await confirm(`Cancel stream ${streamId}?`);
|
|
85
|
+
if (!confirmed) {
|
|
86
|
+
info('Cancelled');
|
|
87
|
+
process.exit(0);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const txHash = await withSpinner(
|
|
92
|
+
'Cancelling stream...',
|
|
93
|
+
async () => {
|
|
94
|
+
// Placeholder - implement actual contract call
|
|
95
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
96
|
+
return '0x' + Math.random().toString(16).substring(2, 66);
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
if (outputJSON({ streamId, txHash }, options)) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
success(`Stream ${streamId} cancelled!`);
|
|
105
|
+
info(`Transaction: ${txHash}`);
|
|
106
|
+
} catch (err) {
|
|
107
|
+
error(`Failed to cancel stream: ${err}`);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// plob stream list
|
|
113
|
+
cmd
|
|
114
|
+
.command('list')
|
|
115
|
+
.description('List your payment streams')
|
|
116
|
+
.option('--json', 'Output as JSON')
|
|
117
|
+
.action(async (options: OutputOptions) => {
|
|
118
|
+
try {
|
|
119
|
+
// Placeholder data
|
|
120
|
+
const streams = [
|
|
121
|
+
{
|
|
122
|
+
id: 1,
|
|
123
|
+
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
|
|
124
|
+
amount: '1000 USDC',
|
|
125
|
+
streamed: '300 USDC',
|
|
126
|
+
remaining: '700 USDC',
|
|
127
|
+
status: 'active',
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: 2,
|
|
131
|
+
to: '0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199',
|
|
132
|
+
amount: '500 USDC',
|
|
133
|
+
streamed: '500 USDC',
|
|
134
|
+
remaining: '0 USDC',
|
|
135
|
+
status: 'completed',
|
|
136
|
+
},
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
if (outputJSON(streams, options)) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (streams.length === 0) {
|
|
144
|
+
info('No streams found');
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const table = createTable({
|
|
149
|
+
head: ['ID', 'To', 'Amount', 'Streamed', 'Remaining', 'Status'],
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
for (const stream of streams) {
|
|
153
|
+
table.push([
|
|
154
|
+
stream.id,
|
|
155
|
+
formatAddress(stream.to),
|
|
156
|
+
stream.amount,
|
|
157
|
+
stream.streamed,
|
|
158
|
+
stream.remaining,
|
|
159
|
+
stream.status,
|
|
160
|
+
]);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
console.log(table.toString());
|
|
164
|
+
} catch (err) {
|
|
165
|
+
error(`Failed to list streams: ${err}`);
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// plob stream get
|
|
171
|
+
cmd
|
|
172
|
+
.command('get')
|
|
173
|
+
.description('Get stream details')
|
|
174
|
+
.argument('<streamId>', 'Stream ID')
|
|
175
|
+
.option('--json', 'Output as JSON')
|
|
176
|
+
.action(async (streamId: string, options: OutputOptions) => {
|
|
177
|
+
try {
|
|
178
|
+
// Placeholder data
|
|
179
|
+
const stream = {
|
|
180
|
+
id: streamId,
|
|
181
|
+
from: '0x1234567890123456789012345678901234567890',
|
|
182
|
+
to: '0x0987654321098765432109876543210987654321',
|
|
183
|
+
totalAmount: '1000 USDC',
|
|
184
|
+
streamedAmount: '300 USDC',
|
|
185
|
+
remainingAmount: '700 USDC',
|
|
186
|
+
startTime: new Date().toISOString(),
|
|
187
|
+
duration: '30 days',
|
|
188
|
+
interval: '1 day',
|
|
189
|
+
status: 'active',
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
if (outputJSON(stream, options)) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
console.log();
|
|
197
|
+
console.log('Stream ID: ', stream.id);
|
|
198
|
+
console.log('From: ', stream.from);
|
|
199
|
+
console.log('To: ', stream.to);
|
|
200
|
+
console.log('Total Amount: ', stream.totalAmount);
|
|
201
|
+
console.log('Streamed: ', stream.streamedAmount);
|
|
202
|
+
console.log('Remaining: ', stream.remainingAmount);
|
|
203
|
+
console.log('Start Time: ', stream.startTime);
|
|
204
|
+
console.log('Duration: ', stream.duration);
|
|
205
|
+
console.log('Interval: ', stream.interval);
|
|
206
|
+
console.log('Status: ', stream.status);
|
|
207
|
+
console.log();
|
|
208
|
+
} catch (err) {
|
|
209
|
+
error(`Failed to get stream: ${err}`);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// plob stream withdraw
|
|
215
|
+
cmd
|
|
216
|
+
.command('withdraw')
|
|
217
|
+
.description('Withdraw from a stream')
|
|
218
|
+
.argument('<streamId>', 'Stream ID')
|
|
219
|
+
.option('--json', 'Output as JSON')
|
|
220
|
+
.action(async (streamId: string, options: OutputOptions) => {
|
|
221
|
+
try {
|
|
222
|
+
const txHash = await withSpinner(
|
|
223
|
+
'Withdrawing from stream...',
|
|
224
|
+
async () => {
|
|
225
|
+
// Placeholder - implement actual contract call
|
|
226
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
227
|
+
return '0x' + Math.random().toString(16).substring(2, 66);
|
|
228
|
+
}
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
if (outputJSON({ streamId, txHash }, options)) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
success('Withdrawal successful!');
|
|
236
|
+
info(`Transaction: ${txHash}`);
|
|
237
|
+
} catch (err) {
|
|
238
|
+
error(`Failed to withdraw: ${err}`);
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
return cmd;
|
|
244
|
+
}
|