@paylobster/cli 4.2.0 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,781 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createTreasuryCommand = createTreasuryCommand;
7
+ const commander_1 = require("commander");
8
+ const viem_1 = require("viem");
9
+ const chains_1 = require("viem/chains");
10
+ const wallet_1 = require("../lib/wallet");
11
+ const contracts_1 = require("../lib/contracts");
12
+ const display_1 = require("../lib/display");
13
+ const chalk_1 = __importDefault(require("chalk"));
14
+ // Treasury contracts
15
+ const TREASURY_FACTORY_ADDRESS = '0x171a685f28546a0ebb13059184db1f808b915066';
16
+ const BASE_CHAIN_ID = 8453;
17
+ // Role enum mapping
18
+ var Role;
19
+ (function (Role) {
20
+ Role[Role["NONE"] = 0] = "NONE";
21
+ Role[Role["VIEWER"] = 1] = "VIEWER";
22
+ Role[Role["OPERATOR"] = 2] = "OPERATOR";
23
+ Role[Role["ADMIN"] = 3] = "ADMIN";
24
+ })(Role || (Role = {}));
25
+ const ROLE_NAMES = {
26
+ 0: 'None',
27
+ 1: 'Viewer',
28
+ 2: 'Operator',
29
+ 3: 'Admin',
30
+ };
31
+ // ABIs
32
+ const TREASURY_FACTORY_ABI = [
33
+ {
34
+ type: 'function',
35
+ name: 'createTreasury',
36
+ inputs: [{ name: '_name', type: 'string' }],
37
+ outputs: [{ name: '', type: 'address' }],
38
+ stateMutability: 'nonpayable',
39
+ },
40
+ {
41
+ type: 'function',
42
+ name: 'treasuries',
43
+ inputs: [{ name: '', type: 'address' }],
44
+ outputs: [{ name: '', type: 'address' }],
45
+ stateMutability: 'view',
46
+ },
47
+ {
48
+ type: 'event',
49
+ name: 'TreasuryCreated',
50
+ inputs: [
51
+ { name: 'owner', type: 'address', indexed: true },
52
+ { name: 'treasury', type: 'address', indexed: true },
53
+ { name: 'name', type: 'string', indexed: false },
54
+ ],
55
+ },
56
+ ];
57
+ const TREASURY_ABI = [
58
+ {
59
+ type: 'function',
60
+ name: 'getSummary',
61
+ inputs: [],
62
+ outputs: [
63
+ { name: '_name', type: 'string' },
64
+ { name: '_owner', type: 'address' },
65
+ { name: 'ethBalance', type: 'uint256' },
66
+ { name: 'tokenCount', type: 'uint256' },
67
+ { name: 'memberCount', type: 'uint256' },
68
+ { name: '_totalDeposited', type: 'uint256' },
69
+ { name: '_totalWithdrawn', type: 'uint256' },
70
+ { name: '_reserveLockBps', type: 'uint256' },
71
+ { name: '_createdAt', type: 'uint256' },
72
+ ],
73
+ stateMutability: 'view',
74
+ },
75
+ {
76
+ type: 'function',
77
+ name: 'getBalances',
78
+ inputs: [],
79
+ outputs: [
80
+ {
81
+ name: '',
82
+ type: 'tuple[]',
83
+ components: [
84
+ { name: 'token', type: 'address' },
85
+ { name: 'balance', type: 'uint256' },
86
+ ],
87
+ },
88
+ ],
89
+ stateMutability: 'view',
90
+ },
91
+ {
92
+ type: 'function',
93
+ name: 'deposit',
94
+ inputs: [
95
+ { name: 'token', type: 'address' },
96
+ { name: 'amount', type: 'uint256' },
97
+ ],
98
+ outputs: [],
99
+ stateMutability: 'nonpayable',
100
+ },
101
+ {
102
+ type: 'function',
103
+ name: 'withdraw',
104
+ inputs: [
105
+ { name: 'token', type: 'address' },
106
+ { name: 'to', type: 'address' },
107
+ { name: 'amount', type: 'uint256' },
108
+ { name: 'reason', type: 'string' },
109
+ ],
110
+ outputs: [],
111
+ stateMutability: 'nonpayable',
112
+ },
113
+ {
114
+ type: 'function',
115
+ name: 'setBudget',
116
+ inputs: [
117
+ { name: 'operationsBps', type: 'uint256' },
118
+ { name: 'growthBps', type: 'uint256' },
119
+ { name: 'reservesBps', type: 'uint256' },
120
+ { name: 'yieldBps', type: 'uint256' },
121
+ ],
122
+ outputs: [],
123
+ stateMutability: 'nonpayable',
124
+ },
125
+ {
126
+ type: 'function',
127
+ name: 'budget',
128
+ inputs: [],
129
+ outputs: [
130
+ { name: 'operationsBps', type: 'uint256' },
131
+ { name: 'growthBps', type: 'uint256' },
132
+ { name: 'reservesBps', type: 'uint256' },
133
+ { name: 'yieldBps', type: 'uint256' },
134
+ ],
135
+ stateMutability: 'view',
136
+ },
137
+ {
138
+ type: 'function',
139
+ name: 'getMembers',
140
+ inputs: [],
141
+ outputs: [
142
+ { name: '', type: 'address[]' },
143
+ { name: '', type: 'uint8[]' },
144
+ ],
145
+ stateMutability: 'view',
146
+ },
147
+ {
148
+ type: 'function',
149
+ name: 'grantRole',
150
+ inputs: [
151
+ { name: 'account', type: 'address' },
152
+ { name: 'role', type: 'uint8' },
153
+ ],
154
+ outputs: [],
155
+ stateMutability: 'nonpayable',
156
+ },
157
+ {
158
+ type: 'function',
159
+ name: 'setSpendLimit',
160
+ inputs: [
161
+ { name: 'operator', type: 'address' },
162
+ { name: 'maxPerTx', type: 'uint256' },
163
+ { name: 'maxPerDay', type: 'uint256' },
164
+ ],
165
+ outputs: [],
166
+ stateMutability: 'nonpayable',
167
+ },
168
+ {
169
+ type: 'function',
170
+ name: 'spendLimits',
171
+ inputs: [{ name: '', type: 'address' }],
172
+ outputs: [
173
+ { name: 'maxPerTx', type: 'uint256' },
174
+ { name: 'maxPerDay', type: 'uint256' },
175
+ { name: 'spentToday', type: 'uint256' },
176
+ { name: 'dayStart', type: 'uint256' },
177
+ ],
178
+ stateMutability: 'view',
179
+ },
180
+ ];
181
+ const ERC20_ABI = [
182
+ {
183
+ type: 'function',
184
+ name: 'approve',
185
+ inputs: [
186
+ { name: 'spender', type: 'address' },
187
+ { name: 'amount', type: 'uint256' },
188
+ ],
189
+ outputs: [{ name: '', type: 'bool' }],
190
+ stateMutability: 'nonpayable',
191
+ },
192
+ {
193
+ type: 'function',
194
+ name: 'symbol',
195
+ inputs: [],
196
+ outputs: [{ name: '', type: 'string' }],
197
+ stateMutability: 'view',
198
+ },
199
+ {
200
+ type: 'function',
201
+ name: 'decimals',
202
+ inputs: [],
203
+ outputs: [{ name: '', type: 'uint8' }],
204
+ stateMutability: 'view',
205
+ },
206
+ ];
207
+ /**
208
+ * Get treasury address for the connected wallet
209
+ */
210
+ async function getTreasuryAddress(address) {
211
+ if (address) {
212
+ return address;
213
+ }
214
+ const walletAddress = (0, wallet_1.getWalletAddress)();
215
+ const publicClient = (0, contracts_1.getPublicClient)('mainnet');
216
+ const treasuryAddress = await publicClient.readContract({
217
+ address: TREASURY_FACTORY_ADDRESS,
218
+ abi: TREASURY_FACTORY_ABI,
219
+ functionName: 'treasuries',
220
+ args: [walletAddress],
221
+ });
222
+ if (treasuryAddress === '0x0000000000000000000000000000000000000000') {
223
+ throw new Error('No treasury found for connected wallet. Create one with: plob treasury create <name>');
224
+ }
225
+ return treasuryAddress;
226
+ }
227
+ /**
228
+ * Format time ago
229
+ */
230
+ function timeAgo(timestamp) {
231
+ const now = Math.floor(Date.now() / 1000);
232
+ const secondsAgo = now - Number(timestamp);
233
+ if (secondsAgo < 60)
234
+ return `${secondsAgo}s ago`;
235
+ if (secondsAgo < 3600)
236
+ return `${Math.floor(secondsAgo / 60)}m ago`;
237
+ if (secondsAgo < 86400)
238
+ return `${Math.floor(secondsAgo / 3600)}h ago`;
239
+ return `${Math.floor(secondsAgo / 86400)}d ago`;
240
+ }
241
+ /**
242
+ * Get token price in USD (mock for now, can integrate with price oracle)
243
+ */
244
+ async function getTokenPriceUSD(token) {
245
+ // ETH address
246
+ if (token === '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE') {
247
+ return 3000; // Mock ETH price
248
+ }
249
+ // USDC
250
+ if (token.toLowerCase() === '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913') {
251
+ return 1;
252
+ }
253
+ // Default
254
+ return 0;
255
+ }
256
+ /**
257
+ * Parse role string to enum
258
+ */
259
+ function parseRole(roleStr) {
260
+ const normalized = roleStr.toLowerCase();
261
+ switch (normalized) {
262
+ case 'viewer':
263
+ return Role.VIEWER;
264
+ case 'operator':
265
+ return Role.OPERATOR;
266
+ case 'admin':
267
+ return Role.ADMIN;
268
+ default:
269
+ throw new Error(`Invalid role: ${roleStr}. Must be one of: viewer, operator, admin`);
270
+ }
271
+ }
272
+ /**
273
+ * Create treasury command
274
+ */
275
+ function createTreasuryCommand() {
276
+ const cmd = new commander_1.Command('treasury')
277
+ .description('Manage agent treasuries on Base');
278
+ // Create treasury
279
+ cmd
280
+ .command('create')
281
+ .description('Create a new treasury')
282
+ .argument('<name>', 'Treasury name')
283
+ .option('--json', 'Output as JSON')
284
+ .action(async (name, options) => {
285
+ try {
286
+ const { client, account } = await (0, wallet_1.loadWallet)();
287
+ const hash = await (0, display_1.withSpinner)('Creating treasury...', async () => client.writeContract({
288
+ address: TREASURY_FACTORY_ADDRESS,
289
+ abi: TREASURY_FACTORY_ABI,
290
+ functionName: 'createTreasury',
291
+ args: [name],
292
+ account,
293
+ chain: chains_1.base,
294
+ }));
295
+ // Wait for transaction and get receipt
296
+ const publicClient = (0, contracts_1.getPublicClient)('mainnet');
297
+ const receipt = await publicClient.waitForTransactionReceipt({ hash });
298
+ // Parse logs to get treasury address
299
+ let treasuryAddress = null;
300
+ for (const log of receipt.logs) {
301
+ if (log.address.toLowerCase() === TREASURY_FACTORY_ADDRESS.toLowerCase()) {
302
+ try {
303
+ const decoded = (0, viem_1.decodeEventLog)({
304
+ abi: TREASURY_FACTORY_ABI,
305
+ data: log.data,
306
+ topics: log.topics,
307
+ });
308
+ if (decoded.eventName === 'TreasuryCreated') {
309
+ treasuryAddress = decoded.args.treasury;
310
+ break;
311
+ }
312
+ }
313
+ catch { }
314
+ }
315
+ }
316
+ if ((0, display_1.outputJSON)({
317
+ name,
318
+ treasuryAddress,
319
+ transactionHash: hash,
320
+ }, options)) {
321
+ return;
322
+ }
323
+ console.log();
324
+ (0, display_1.success)('Treasury created successfully!');
325
+ console.log();
326
+ console.log(' Name: ', chalk_1.default.white.bold(name));
327
+ console.log(' Address: ', chalk_1.default.cyan(treasuryAddress || 'Check transaction receipt'));
328
+ console.log(' Tx Hash: ', chalk_1.default.gray(hash));
329
+ console.log();
330
+ (0, display_1.info)(`View on BaseScan: https://basescan.org/tx/${hash}`);
331
+ console.log();
332
+ }
333
+ catch (err) {
334
+ (0, display_1.error)(`Failed to create treasury: ${err}`);
335
+ process.exit(1);
336
+ }
337
+ });
338
+ // Get treasury info
339
+ cmd
340
+ .command('info')
341
+ .description('Show treasury summary')
342
+ .argument('[address]', 'Treasury address (defaults to your treasury)')
343
+ .option('--json', 'Output as JSON')
344
+ .action(async (address, options) => {
345
+ try {
346
+ const treasuryAddress = await getTreasuryAddress(address);
347
+ const publicClient = (0, contracts_1.getPublicClient)('mainnet');
348
+ const summary = await (0, display_1.withSpinner)('Fetching treasury info...', async () => publicClient.readContract({
349
+ address: treasuryAddress,
350
+ abi: TREASURY_ABI,
351
+ functionName: 'getSummary',
352
+ }));
353
+ const [name, owner, ethBalance, tokenCount, memberCount, totalDeposited, totalWithdrawn, reserveLockBps, createdAt] = summary;
354
+ if ((0, display_1.outputJSON)({
355
+ address: treasuryAddress,
356
+ name,
357
+ owner,
358
+ ethBalance: ethBalance.toString(),
359
+ tokenCount: Number(tokenCount),
360
+ memberCount: Number(memberCount),
361
+ totalDeposited: totalDeposited.toString(),
362
+ totalWithdrawn: totalWithdrawn.toString(),
363
+ reserveLockBps: Number(reserveLockBps),
364
+ createdAt: Number(createdAt),
365
+ age: timeAgo(createdAt),
366
+ }, options)) {
367
+ return;
368
+ }
369
+ console.log();
370
+ console.log(chalk_1.default.bold.cyan('🏛️ Treasury Summary'));
371
+ console.log();
372
+ console.log(' Name: ', chalk_1.default.white.bold(name));
373
+ console.log(' Address: ', chalk_1.default.gray(treasuryAddress));
374
+ console.log(' Owner: ', chalk_1.default.gray(owner));
375
+ console.log(' ETH Balance: ', chalk_1.default.white((0, viem_1.formatUnits)(ethBalance, 18)), chalk_1.default.gray('ETH'));
376
+ console.log(' Token Count: ', chalk_1.default.white(tokenCount.toString()));
377
+ console.log(' Members: ', chalk_1.default.white(memberCount.toString()));
378
+ console.log(' Total Deposited:', chalk_1.default.green(`$${(0, viem_1.formatUnits)(totalDeposited, 6)}`));
379
+ console.log(' Total Withdrawn:', chalk_1.default.yellow(`$${(0, viem_1.formatUnits)(totalWithdrawn, 6)}`));
380
+ console.log(' Reserve Lock: ', chalk_1.default.white(`${Number(reserveLockBps) / 100}%`));
381
+ console.log(' Age: ', chalk_1.default.gray(timeAgo(createdAt)));
382
+ console.log();
383
+ }
384
+ catch (err) {
385
+ (0, display_1.error)(`Failed to get treasury info: ${err}`);
386
+ process.exit(1);
387
+ }
388
+ });
389
+ // Get balances
390
+ cmd
391
+ .command('balances')
392
+ .description('Show all token balances')
393
+ .argument('[address]', 'Treasury address (defaults to your treasury)')
394
+ .option('--json', 'Output as JSON')
395
+ .action(async (address, options) => {
396
+ try {
397
+ const treasuryAddress = await getTreasuryAddress(address);
398
+ const publicClient = (0, contracts_1.getPublicClient)('mainnet');
399
+ const balances = await (0, display_1.withSpinner)('Fetching balances...', async () => publicClient.readContract({
400
+ address: treasuryAddress,
401
+ abi: TREASURY_ABI,
402
+ functionName: 'getBalances',
403
+ }));
404
+ // Get token symbols and decimals
405
+ const enrichedBalances = await Promise.all(balances.map(async (bal) => {
406
+ try {
407
+ const [symbol, decimals] = await Promise.all([
408
+ publicClient.readContract({
409
+ address: bal.token,
410
+ abi: ERC20_ABI,
411
+ functionName: 'symbol',
412
+ }),
413
+ publicClient.readContract({
414
+ address: bal.token,
415
+ abi: ERC20_ABI,
416
+ functionName: 'decimals',
417
+ }),
418
+ ]);
419
+ const amount = (0, viem_1.formatUnits)(bal.balance, decimals);
420
+ const priceUSD = await getTokenPriceUSD(bal.token);
421
+ const valueUSD = parseFloat(amount) * priceUSD;
422
+ return {
423
+ token: bal.token,
424
+ symbol,
425
+ balance: amount,
426
+ decimals,
427
+ valueUSD,
428
+ };
429
+ }
430
+ catch {
431
+ return {
432
+ token: bal.token,
433
+ symbol: 'UNKNOWN',
434
+ balance: bal.balance.toString(),
435
+ decimals: 18,
436
+ valueUSD: 0,
437
+ };
438
+ }
439
+ }));
440
+ if ((0, display_1.outputJSON)({ balances: enrichedBalances }, options)) {
441
+ return;
442
+ }
443
+ console.log();
444
+ console.log(chalk_1.default.bold.cyan('💰 Treasury Balances'));
445
+ console.log();
446
+ if (enrichedBalances.length === 0) {
447
+ (0, display_1.info)('No token balances');
448
+ return;
449
+ }
450
+ const table = (0, display_1.createTable)({
451
+ head: ['Token', 'Balance', 'Value (USD)'],
452
+ });
453
+ for (const bal of enrichedBalances) {
454
+ table.push([
455
+ chalk_1.default.white.bold(bal.symbol),
456
+ chalk_1.default.white(bal.balance),
457
+ chalk_1.default.green(`$${bal.valueUSD.toFixed(2)}`),
458
+ ]);
459
+ }
460
+ console.log(table.toString());
461
+ console.log();
462
+ const totalValue = enrichedBalances.reduce((sum, bal) => sum + bal.valueUSD, 0);
463
+ console.log(' Total Value: ', chalk_1.default.bold.green(`$${totalValue.toFixed(2)}`));
464
+ console.log();
465
+ }
466
+ catch (err) {
467
+ (0, display_1.error)(`Failed to get balances: ${err}`);
468
+ process.exit(1);
469
+ }
470
+ });
471
+ // Deposit
472
+ cmd
473
+ .command('deposit')
474
+ .description('Deposit tokens into treasury')
475
+ .requiredOption('--token <address>', 'Token address')
476
+ .requiredOption('--amount <amount>', 'Amount to deposit')
477
+ .option('--json', 'Output as JSON')
478
+ .action(async (options) => {
479
+ try {
480
+ const treasuryAddress = await getTreasuryAddress();
481
+ const { client, account } = await (0, wallet_1.loadWallet)();
482
+ const publicClient = (0, contracts_1.getPublicClient)('mainnet');
483
+ const tokenAddress = options.token;
484
+ // Get token decimals
485
+ const decimals = await publicClient.readContract({
486
+ address: tokenAddress,
487
+ abi: ERC20_ABI,
488
+ functionName: 'decimals',
489
+ });
490
+ const amount = (0, viem_1.parseUnits)(options.amount, decimals);
491
+ // First approve token
492
+ const approveHash = await (0, display_1.withSpinner)('Approving token...', async () => client.writeContract({
493
+ address: tokenAddress,
494
+ abi: ERC20_ABI,
495
+ functionName: 'approve',
496
+ args: [treasuryAddress, amount],
497
+ account,
498
+ chain: chains_1.base,
499
+ }));
500
+ await publicClient.waitForTransactionReceipt({ hash: approveHash });
501
+ // Deposit
502
+ const depositHash = await (0, display_1.withSpinner)('Depositing tokens...', async () => client.writeContract({
503
+ address: treasuryAddress,
504
+ abi: TREASURY_ABI,
505
+ functionName: 'deposit',
506
+ args: [tokenAddress, amount],
507
+ account,
508
+ chain: chains_1.base,
509
+ }));
510
+ await publicClient.waitForTransactionReceipt({ hash: depositHash });
511
+ if ((0, display_1.outputJSON)({
512
+ token: tokenAddress,
513
+ amount: options.amount,
514
+ transactionHash: depositHash,
515
+ }, options)) {
516
+ return;
517
+ }
518
+ console.log();
519
+ (0, display_1.success)('Deposit successful!');
520
+ console.log();
521
+ console.log(' Amount: ', chalk_1.default.white.bold(options.amount));
522
+ console.log(' Token: ', chalk_1.default.gray(tokenAddress));
523
+ console.log(' Tx Hash: ', chalk_1.default.gray(depositHash));
524
+ console.log();
525
+ }
526
+ catch (err) {
527
+ (0, display_1.error)(`Failed to deposit: ${err}`);
528
+ process.exit(1);
529
+ }
530
+ });
531
+ // Withdraw
532
+ cmd
533
+ .command('withdraw')
534
+ .description('Withdraw tokens from treasury')
535
+ .requiredOption('--token <address>', 'Token address')
536
+ .requiredOption('--to <address>', 'Recipient address')
537
+ .requiredOption('--amount <amount>', 'Amount to withdraw')
538
+ .requiredOption('--reason <string>', 'Reason for withdrawal')
539
+ .option('--json', 'Output as JSON')
540
+ .action(async (options) => {
541
+ try {
542
+ const treasuryAddress = await getTreasuryAddress();
543
+ const { client, account } = await (0, wallet_1.loadWallet)();
544
+ const publicClient = (0, contracts_1.getPublicClient)('mainnet');
545
+ const tokenAddress = options.token;
546
+ const toAddress = options.to;
547
+ // Get token decimals
548
+ const decimals = await publicClient.readContract({
549
+ address: tokenAddress,
550
+ abi: ERC20_ABI,
551
+ functionName: 'decimals',
552
+ });
553
+ const amount = (0, viem_1.parseUnits)(options.amount, decimals);
554
+ const hash = await (0, display_1.withSpinner)('Withdrawing tokens...', async () => client.writeContract({
555
+ address: treasuryAddress,
556
+ abi: TREASURY_ABI,
557
+ functionName: 'withdraw',
558
+ args: [tokenAddress, toAddress, amount, options.reason],
559
+ account,
560
+ chain: chains_1.base,
561
+ }));
562
+ await publicClient.waitForTransactionReceipt({ hash });
563
+ if ((0, display_1.outputJSON)({
564
+ token: tokenAddress,
565
+ to: toAddress,
566
+ amount: options.amount,
567
+ reason: options.reason,
568
+ transactionHash: hash,
569
+ }, options)) {
570
+ return;
571
+ }
572
+ console.log();
573
+ (0, display_1.success)('Withdrawal successful!');
574
+ console.log();
575
+ console.log(' Amount: ', chalk_1.default.white.bold(options.amount));
576
+ console.log(' To: ', chalk_1.default.gray(toAddress));
577
+ console.log(' Reason: ', chalk_1.default.white(options.reason));
578
+ console.log(' Tx Hash: ', chalk_1.default.gray(hash));
579
+ console.log();
580
+ }
581
+ catch (err) {
582
+ (0, display_1.error)(`Failed to withdraw: ${err}`);
583
+ process.exit(1);
584
+ }
585
+ });
586
+ // Set budget
587
+ cmd
588
+ .command('budget')
589
+ .description('Set budget allocation (must total 10000 bps)')
590
+ .requiredOption('--ops <bps>', 'Operations allocation in basis points')
591
+ .requiredOption('--growth <bps>', 'Growth allocation in basis points')
592
+ .requiredOption('--reserves <bps>', 'Reserves allocation in basis points')
593
+ .requiredOption('--yield <bps>', 'Yield allocation in basis points')
594
+ .option('--json', 'Output as JSON')
595
+ .action(async (options) => {
596
+ try {
597
+ const opsBps = BigInt(options.ops);
598
+ const growthBps = BigInt(options.growth);
599
+ const reservesBps = BigInt(options.reserves);
600
+ const yieldBps = BigInt(options.yield);
601
+ const total = opsBps + growthBps + reservesBps + yieldBps;
602
+ if (total !== 10000n) {
603
+ throw new Error(`Budget allocations must total 10000 bps. Got: ${total}`);
604
+ }
605
+ const treasuryAddress = await getTreasuryAddress();
606
+ const { client, account } = await (0, wallet_1.loadWallet)();
607
+ const publicClient = (0, contracts_1.getPublicClient)('mainnet');
608
+ const hash = await (0, display_1.withSpinner)('Setting budget allocation...', async () => client.writeContract({
609
+ address: treasuryAddress,
610
+ abi: TREASURY_ABI,
611
+ functionName: 'setBudget',
612
+ args: [opsBps, growthBps, reservesBps, yieldBps],
613
+ account,
614
+ chain: chains_1.base,
615
+ }));
616
+ await publicClient.waitForTransactionReceipt({ hash });
617
+ if ((0, display_1.outputJSON)({
618
+ operations: Number(opsBps),
619
+ growth: Number(growthBps),
620
+ reserves: Number(reservesBps),
621
+ yield: Number(yieldBps),
622
+ transactionHash: hash,
623
+ }, options)) {
624
+ return;
625
+ }
626
+ console.log();
627
+ (0, display_1.success)('Budget allocation updated!');
628
+ console.log();
629
+ console.log(' Operations: ', chalk_1.default.white(`${Number(opsBps) / 100}%`));
630
+ console.log(' Growth: ', chalk_1.default.white(`${Number(growthBps) / 100}%`));
631
+ console.log(' Reserves: ', chalk_1.default.white(`${Number(reservesBps) / 100}%`));
632
+ console.log(' Yield: ', chalk_1.default.white(`${Number(yieldBps) / 100}%`));
633
+ console.log();
634
+ }
635
+ catch (err) {
636
+ (0, display_1.error)(`Failed to set budget: ${err}`);
637
+ process.exit(1);
638
+ }
639
+ });
640
+ // List members
641
+ cmd
642
+ .command('members')
643
+ .description('List all treasury members with roles')
644
+ .argument('[address]', 'Treasury address (defaults to your treasury)')
645
+ .option('--json', 'Output as JSON')
646
+ .action(async (address, options) => {
647
+ try {
648
+ const treasuryAddress = await getTreasuryAddress(address);
649
+ const publicClient = (0, contracts_1.getPublicClient)('mainnet');
650
+ const [addresses, roles] = await (0, display_1.withSpinner)('Fetching members...', async () => publicClient.readContract({
651
+ address: treasuryAddress,
652
+ abi: TREASURY_ABI,
653
+ functionName: 'getMembers',
654
+ }));
655
+ const members = addresses.map((addr, i) => ({
656
+ address: addr,
657
+ role: ROLE_NAMES[roles[i]] || 'Unknown',
658
+ roleValue: roles[i],
659
+ }));
660
+ if ((0, display_1.outputJSON)({ members }, options)) {
661
+ return;
662
+ }
663
+ console.log();
664
+ console.log(chalk_1.default.bold.cyan('👥 Treasury Members'));
665
+ console.log();
666
+ if (members.length === 0) {
667
+ (0, display_1.info)('No members found');
668
+ return;
669
+ }
670
+ const table = (0, display_1.createTable)({
671
+ head: ['Address', 'Role'],
672
+ });
673
+ for (const member of members) {
674
+ const roleColor = member.roleValue === 3 ? chalk_1.default.red : member.roleValue === 2 ? chalk_1.default.yellow : chalk_1.default.blue;
675
+ table.push([
676
+ chalk_1.default.gray(member.address),
677
+ roleColor(member.role),
678
+ ]);
679
+ }
680
+ console.log(table.toString());
681
+ console.log();
682
+ }
683
+ catch (err) {
684
+ (0, display_1.error)(`Failed to get members: ${err}`);
685
+ process.exit(1);
686
+ }
687
+ });
688
+ // Grant role
689
+ cmd
690
+ .command('grant')
691
+ .description('Grant role to an address')
692
+ .requiredOption('--address <addr>', 'Address to grant role')
693
+ .requiredOption('--role <role>', 'Role to grant (viewer|operator|admin)')
694
+ .option('--json', 'Output as JSON')
695
+ .action(async (options) => {
696
+ try {
697
+ const roleEnum = parseRole(options.role);
698
+ const memberAddress = options.address;
699
+ const treasuryAddress = await getTreasuryAddress();
700
+ const { client, account } = await (0, wallet_1.loadWallet)();
701
+ const publicClient = (0, contracts_1.getPublicClient)('mainnet');
702
+ const hash = await (0, display_1.withSpinner)('Granting role...', async () => client.writeContract({
703
+ address: treasuryAddress,
704
+ abi: TREASURY_ABI,
705
+ functionName: 'grantRole',
706
+ args: [memberAddress, roleEnum],
707
+ account,
708
+ chain: chains_1.base,
709
+ }));
710
+ await publicClient.waitForTransactionReceipt({ hash });
711
+ if ((0, display_1.outputJSON)({
712
+ address: memberAddress,
713
+ role: options.role,
714
+ transactionHash: hash,
715
+ }, options)) {
716
+ return;
717
+ }
718
+ console.log();
719
+ (0, display_1.success)('Role granted successfully!');
720
+ console.log();
721
+ console.log(' Address: ', chalk_1.default.gray(memberAddress));
722
+ console.log(' Role: ', chalk_1.default.white.bold(options.role));
723
+ console.log(' Tx Hash: ', chalk_1.default.gray(hash));
724
+ console.log();
725
+ }
726
+ catch (err) {
727
+ (0, display_1.error)(`Failed to grant role: ${err}`);
728
+ process.exit(1);
729
+ }
730
+ });
731
+ // Set spend limit
732
+ cmd
733
+ .command('limit')
734
+ .description('Set spending limits for an operator')
735
+ .requiredOption('--address <addr>', 'Operator address')
736
+ .requiredOption('--per-tx <amount>', 'Max amount per transaction (in USD)')
737
+ .requiredOption('--per-day <amount>', 'Max amount per day (in USD)')
738
+ .option('--json', 'Output as JSON')
739
+ .action(async (options) => {
740
+ try {
741
+ const operatorAddress = options.address;
742
+ const treasuryAddress = await getTreasuryAddress();
743
+ const { client, account } = await (0, wallet_1.loadWallet)();
744
+ const publicClient = (0, contracts_1.getPublicClient)('mainnet');
745
+ // Convert USD amounts to USDC wei (6 decimals)
746
+ const maxPerTx = (0, viem_1.parseUnits)(options.perTx, 6);
747
+ const maxPerDay = (0, viem_1.parseUnits)(options.perDay, 6);
748
+ const hash = await (0, display_1.withSpinner)('Setting spend limits...', async () => client.writeContract({
749
+ address: treasuryAddress,
750
+ abi: TREASURY_ABI,
751
+ functionName: 'setSpendLimit',
752
+ args: [operatorAddress, maxPerTx, maxPerDay],
753
+ account,
754
+ chain: chains_1.base,
755
+ }));
756
+ await publicClient.waitForTransactionReceipt({ hash });
757
+ if ((0, display_1.outputJSON)({
758
+ operator: operatorAddress,
759
+ maxPerTx: options.perTx,
760
+ maxPerDay: options.perDay,
761
+ transactionHash: hash,
762
+ }, options)) {
763
+ return;
764
+ }
765
+ console.log();
766
+ (0, display_1.success)('Spend limits set successfully!');
767
+ console.log();
768
+ console.log(' Operator: ', chalk_1.default.gray(operatorAddress));
769
+ console.log(' Max Per Tx: ', chalk_1.default.white.bold(`$${options.perTx}`));
770
+ console.log(' Max Per Day: ', chalk_1.default.white.bold(`$${options.perDay}`));
771
+ console.log(' Tx Hash: ', chalk_1.default.gray(hash));
772
+ console.log();
773
+ }
774
+ catch (err) {
775
+ (0, display_1.error)(`Failed to set spend limits: ${err}`);
776
+ process.exit(1);
777
+ }
778
+ });
779
+ return cmd;
780
+ }
781
+ //# sourceMappingURL=treasury.js.map