@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.
- package/dist/src/commands/treasury.d.ts +6 -0
- package/dist/src/commands/treasury.d.ts.map +1 -0
- package/dist/src/commands/treasury.js +781 -0
- package/dist/src/commands/treasury.js.map +1 -0
- package/dist/src/index.js +3 -1
- package/dist/src/index.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/treasury.ts +907 -0
- package/src/index.ts +3 -1
|
@@ -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
|