@paylobster/cli 4.3.0 ā 4.5.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/README.md +82 -40
- package/dist/src/commands/dashboard.d.ts +3 -0
- package/dist/src/commands/dashboard.d.ts.map +1 -0
- package/dist/src/commands/dashboard.js +67 -0
- package/dist/src/commands/dashboard.js.map +1 -0
- package/dist/src/commands/export.d.ts +3 -0
- package/dist/src/commands/export.d.ts.map +1 -0
- package/dist/src/commands/export.js +97 -0
- package/dist/src/commands/export.js.map +1 -0
- package/dist/src/commands/health.d.ts +3 -0
- package/dist/src/commands/health.d.ts.map +1 -0
- package/dist/src/commands/health.js +165 -0
- package/dist/src/commands/health.js.map +1 -0
- package/dist/src/commands/invest.d.ts +3 -0
- package/dist/src/commands/invest.d.ts.map +1 -0
- package/dist/src/commands/invest.js +721 -0
- package/dist/src/commands/invest.js.map +1 -0
- package/dist/src/commands/quickstart.d.ts +3 -0
- package/dist/src/commands/quickstart.d.ts.map +1 -0
- package/dist/src/commands/quickstart.js +138 -0
- package/dist/src/commands/quickstart.js.map +1 -0
- package/dist/src/commands/search.d.ts +3 -0
- package/dist/src/commands/search.d.ts.map +1 -0
- package/dist/src/commands/search.js +126 -0
- package/dist/src/commands/search.js.map +1 -0
- package/dist/src/commands/treasury.d.ts.map +1 -1
- package/dist/src/commands/treasury.js +317 -0
- package/dist/src/commands/treasury.js.map +1 -1
- package/dist/src/commands/trust-graph.d.ts +3 -0
- package/dist/src/commands/trust-graph.d.ts.map +1 -0
- package/dist/src/commands/trust-graph.js +282 -0
- package/dist/src/commands/trust-graph.js.map +1 -0
- package/dist/src/commands/whoami.d.ts +3 -0
- package/dist/src/commands/whoami.d.ts.map +1 -0
- package/dist/src/commands/whoami.js +160 -0
- package/dist/src/commands/whoami.js.map +1 -0
- package/dist/src/index.js +18 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/lib/contracts.d.ts +177 -23
- package/dist/src/lib/contracts.d.ts.map +1 -1
- package/dist/src/lib/contracts.js +151 -0
- package/dist/src/lib/contracts.js.map +1 -1
- package/package.json +4 -2
- package/src/commands/dashboard.ts +69 -0
- package/src/commands/export.ts +132 -0
- package/src/commands/health.ts +212 -0
- package/src/commands/invest.ts +810 -0
- package/src/commands/quickstart.ts +150 -0
- package/src/commands/search.ts +151 -0
- package/src/commands/treasury.ts +385 -0
- package/src/commands/trust-graph.ts +267 -0
- package/src/commands/whoami.ts +172 -0
- package/src/index.ts +18 -2
- package/src/lib/contracts.ts +159 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { getAgentInfo, registerAgent } from '../lib/contracts';
|
|
3
|
+
import { getWalletAddress } from '../lib/wallet';
|
|
4
|
+
import { error, success } from '../lib/display';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import readline from 'readline';
|
|
8
|
+
import { exec } from 'child_process';
|
|
9
|
+
import { promisify } from 'util';
|
|
10
|
+
|
|
11
|
+
const execAsync = promisify(exec);
|
|
12
|
+
|
|
13
|
+
function prompt(question: string): Promise<string> {
|
|
14
|
+
const rl = readline.createInterface({
|
|
15
|
+
input: process.stdin,
|
|
16
|
+
output: process.stdout,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
rl.question(question, (answer) => {
|
|
21
|
+
rl.close();
|
|
22
|
+
resolve(answer.trim());
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function createQuickstartCommand(): Command {
|
|
28
|
+
const cmd = new Command('quickstart')
|
|
29
|
+
.description('š Interactive wizard to get started with PayLobster')
|
|
30
|
+
.action(async () => {
|
|
31
|
+
console.log(chalk.cyan.bold('\nš¦ PayLobster Quickstart Wizard\n'));
|
|
32
|
+
console.log(chalk.gray('Let\'s get you set up with PayLobster in just a few steps.\n'));
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
// Step 1: Check wallet configuration
|
|
36
|
+
console.log(chalk.cyan('Step 1/5:'), chalk.bold('Wallet Configuration'));
|
|
37
|
+
|
|
38
|
+
let address: `0x${string}`;
|
|
39
|
+
try {
|
|
40
|
+
address = getWalletAddress() as `0x${string}`;
|
|
41
|
+
} catch (err) {
|
|
42
|
+
console.log(chalk.yellow('ā ļø No wallet configured yet.'));
|
|
43
|
+
console.log('Please run:', chalk.green('plob auth setup'));
|
|
44
|
+
console.log('Then come back and run', chalk.green('plob quickstart'), 'again.\n');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
console.log(chalk.green('ā'), 'Wallet configured:', chalk.gray(address));
|
|
48
|
+
|
|
49
|
+
// Check if already registered
|
|
50
|
+
const spinner = ora('Checking registration status...').start();
|
|
51
|
+
const agentInfo = await getAgentInfo(address);
|
|
52
|
+
spinner.stop();
|
|
53
|
+
|
|
54
|
+
if (agentInfo.registered) {
|
|
55
|
+
console.log(chalk.green('ā'), 'Already registered as:', chalk.bold(agentInfo.name));
|
|
56
|
+
console.log(chalk.yellow('\nYou\'re all set! Run'), chalk.green('plob whoami'), chalk.yellow('to see your profile.\n'));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log(chalk.gray('Not registered yet. Let\'s continue...\n'));
|
|
61
|
+
|
|
62
|
+
// Step 2: Register agent identity
|
|
63
|
+
console.log(chalk.cyan('Step 2/5:'), chalk.bold('Agent Identity'));
|
|
64
|
+
|
|
65
|
+
const name = await prompt(chalk.white('Agent name: '));
|
|
66
|
+
if (!name) {
|
|
67
|
+
console.log(chalk.red('Name is required. Exiting.\n'));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const capabilities = await prompt(chalk.white('Capabilities (comma-separated, e.g., "code review, testing, docs"): '));
|
|
72
|
+
if (!capabilities) {
|
|
73
|
+
console.log(chalk.red('Capabilities are required. Exiting.\n'));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
spinner.start('Registering agent identity...');
|
|
78
|
+
try {
|
|
79
|
+
const tokenId = await registerAgent(name, capabilities);
|
|
80
|
+
spinner.succeed(chalk.green('ā Agent registered! Token ID: ') + chalk.bold(tokenId.toString()));
|
|
81
|
+
} catch (err: any) {
|
|
82
|
+
spinner.fail(chalk.red('Failed to register agent'));
|
|
83
|
+
error(`Registration error: ${err.message}`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Step 3: Create treasury
|
|
88
|
+
console.log(chalk.cyan('\nStep 3/5:'), chalk.bold('Treasury Setup'));
|
|
89
|
+
console.log(chalk.gray('A treasury manages your funds with budgets and spending rules.'));
|
|
90
|
+
|
|
91
|
+
const createTreasury = await prompt(chalk.white('Create a treasury? (y/n): '));
|
|
92
|
+
|
|
93
|
+
if (createTreasury.toLowerCase() === 'y') {
|
|
94
|
+
const treasuryName = await prompt(chalk.white('Treasury name: ')) || `${name} Treasury`;
|
|
95
|
+
|
|
96
|
+
spinner.start('Creating treasury...');
|
|
97
|
+
try {
|
|
98
|
+
await execAsync(`plob treasury create --name "${treasuryName}"`);
|
|
99
|
+
spinner.succeed(chalk.green('ā Treasury created: ') + chalk.bold(treasuryName));
|
|
100
|
+
} catch (err: any) {
|
|
101
|
+
spinner.fail(chalk.red('Failed to create treasury'));
|
|
102
|
+
console.log(chalk.yellow('You can create one later with:'), chalk.green('plob treasury create'));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Step 4: Set default budget
|
|
106
|
+
console.log(chalk.cyan('\nStep 4/5:'), chalk.bold('Budget Allocation'));
|
|
107
|
+
console.log(chalk.gray('Recommended: 40% operations, 30% growth, 20% reserve, 10% rewards'));
|
|
108
|
+
|
|
109
|
+
const setBudget = await prompt(chalk.white('Use default budget allocation? (y/n): '));
|
|
110
|
+
|
|
111
|
+
if (setBudget.toLowerCase() === 'y') {
|
|
112
|
+
spinner.start('Setting budget...');
|
|
113
|
+
try {
|
|
114
|
+
await execAsync('plob treasury budget --operations 40 --growth 30 --reserve 20 --rewards 10');
|
|
115
|
+
spinner.succeed(chalk.green('ā Budget configured'));
|
|
116
|
+
} catch (err: any) {
|
|
117
|
+
spinner.fail(chalk.red('Failed to set budget'));
|
|
118
|
+
console.log(chalk.yellow('You can set it later with:'), chalk.green('plob treasury budget'));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
console.log(chalk.gray('Skipped treasury setup.'));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Step 5: Summary
|
|
126
|
+
console.log(chalk.cyan('\nStep 5/5:'), chalk.bold('Summary'));
|
|
127
|
+
console.log(chalk.green.bold('\nš You\'re all set!\n'));
|
|
128
|
+
|
|
129
|
+
console.log(chalk.bold('What you created:'));
|
|
130
|
+
console.log(chalk.green(' ā'), 'Agent identity:', chalk.bold(name));
|
|
131
|
+
console.log(chalk.green(' ā'), 'Capabilities:', chalk.gray(capabilities));
|
|
132
|
+
if (createTreasury.toLowerCase() === 'y') {
|
|
133
|
+
console.log(chalk.green(' ā'), 'Treasury with budget allocation');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
console.log(chalk.bold('\nNext steps:'));
|
|
137
|
+
console.log(chalk.cyan(' ā¢'), 'View your profile:', chalk.green('plob whoami'));
|
|
138
|
+
console.log(chalk.cyan(' ā¢'), 'Check system health:', chalk.green('plob health'));
|
|
139
|
+
console.log(chalk.cyan(' ā¢'), 'Open dashboard:', chalk.green('plob dashboard'));
|
|
140
|
+
console.log(chalk.cyan(' ā¢'), 'Make your first payment:', chalk.green('plob pay <address> <amount>'));
|
|
141
|
+
console.log();
|
|
142
|
+
|
|
143
|
+
} catch (err: any) {
|
|
144
|
+
error(`Quickstart failed: ${err.message}`);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
return cmd;
|
|
150
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { getPublicClient, getContracts, getAgentInfo, getReputation } from '../lib/contracts';
|
|
3
|
+
import { loadConfig } from '../lib/config';
|
|
4
|
+
import { error, outputJSON } from '../lib/display';
|
|
5
|
+
import type { OutputOptions } from '../lib/types';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import ora from 'ora';
|
|
8
|
+
import type { Address } from 'viem';
|
|
9
|
+
|
|
10
|
+
interface AgentSearchResult {
|
|
11
|
+
address: Address;
|
|
12
|
+
name: string;
|
|
13
|
+
capabilities: string;
|
|
14
|
+
reputationScore: number;
|
|
15
|
+
tokenId: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ABI for AgentRegistered event
|
|
19
|
+
const AGENT_REGISTERED_EVENT = {
|
|
20
|
+
anonymous: false,
|
|
21
|
+
inputs: [
|
|
22
|
+
{ indexed: true, name: 'agent', type: 'address' },
|
|
23
|
+
{ indexed: true, name: 'tokenId', type: 'uint256' },
|
|
24
|
+
{ indexed: false, name: 'name', type: 'string' },
|
|
25
|
+
{ indexed: false, name: 'capabilities', type: 'string' },
|
|
26
|
+
],
|
|
27
|
+
name: 'AgentRegistered',
|
|
28
|
+
type: 'event',
|
|
29
|
+
} as const;
|
|
30
|
+
|
|
31
|
+
export function createSearchCommand(): Command {
|
|
32
|
+
const cmd = new Command('search')
|
|
33
|
+
.description('š Search for agents by name or capability')
|
|
34
|
+
.argument('<query>', 'Search query (name or capability)')
|
|
35
|
+
.option('--json', 'Output as JSON')
|
|
36
|
+
.option('--limit <number>', 'Maximum number of results', '10')
|
|
37
|
+
.action(async (query: string, options: OutputOptions & { limit?: string }) => {
|
|
38
|
+
const spinner = ora('Searching for agents...').start();
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const config = loadConfig();
|
|
42
|
+
const client = getPublicClient(config.network);
|
|
43
|
+
const contracts = getContracts(config.network);
|
|
44
|
+
const limit = parseInt(options.limit || '10');
|
|
45
|
+
|
|
46
|
+
// Get AgentRegistered events
|
|
47
|
+
// Note: This will scan the entire chain history which may be slow
|
|
48
|
+
// In production, you'd want to use a subgraph or indexer
|
|
49
|
+
const logs = await client.getLogs({
|
|
50
|
+
address: contracts.IDENTITY,
|
|
51
|
+
event: AGENT_REGISTERED_EVENT,
|
|
52
|
+
fromBlock: 'earliest',
|
|
53
|
+
toBlock: 'latest',
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
spinner.text = 'Processing results...';
|
|
57
|
+
|
|
58
|
+
// Filter by query (case-insensitive)
|
|
59
|
+
const queryLower = query.toLowerCase();
|
|
60
|
+
const matchingLogs = logs.filter((log) => {
|
|
61
|
+
const name = (log.args.name || '').toLowerCase();
|
|
62
|
+
const capabilities = (log.args.capabilities || '').toLowerCase();
|
|
63
|
+
return name.includes(queryLower) || capabilities.includes(queryLower);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Limit results
|
|
67
|
+
const limitedLogs = matchingLogs.slice(0, limit);
|
|
68
|
+
|
|
69
|
+
// Fetch reputation for each agent
|
|
70
|
+
const results: AgentSearchResult[] = await Promise.all(
|
|
71
|
+
limitedLogs.map(async (log) => {
|
|
72
|
+
const address = log.args.agent!;
|
|
73
|
+
const reputation = await getReputation(address);
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
address,
|
|
77
|
+
name: log.args.name || 'Unknown',
|
|
78
|
+
capabilities: log.args.capabilities || 'None',
|
|
79
|
+
reputationScore: Number(reputation.score),
|
|
80
|
+
tokenId: log.args.tokenId?.toString() || '0',
|
|
81
|
+
};
|
|
82
|
+
})
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
spinner.stop();
|
|
86
|
+
|
|
87
|
+
// JSON output
|
|
88
|
+
if (outputJSON({
|
|
89
|
+
query,
|
|
90
|
+
results,
|
|
91
|
+
total: matchingLogs.length,
|
|
92
|
+
showing: results.length,
|
|
93
|
+
}, options)) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Pretty output
|
|
98
|
+
console.log('\n' + chalk.cyan.bold('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā'));
|
|
99
|
+
console.log(chalk.cyan.bold(' š Agent Search'));
|
|
100
|
+
console.log(chalk.cyan.bold('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n'));
|
|
101
|
+
|
|
102
|
+
console.log(chalk.gray(' Query: '), chalk.white(query));
|
|
103
|
+
console.log(chalk.gray(' Results: '), chalk.white(`${results.length} / ${matchingLogs.length}`));
|
|
104
|
+
console.log();
|
|
105
|
+
|
|
106
|
+
if (results.length === 0) {
|
|
107
|
+
console.log(chalk.yellow(' No agents found matching your query.'));
|
|
108
|
+
console.log();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Display results
|
|
113
|
+
for (let i = 0; i < results.length; i++) {
|
|
114
|
+
const agent = results[i];
|
|
115
|
+
|
|
116
|
+
let reputationColor = chalk.red;
|
|
117
|
+
if (agent.reputationScore >= 80) reputationColor = chalk.green;
|
|
118
|
+
else if (agent.reputationScore >= 50) reputationColor = chalk.yellow;
|
|
119
|
+
|
|
120
|
+
console.log(chalk.bold(` ${i + 1}. ${agent.name}`), chalk.gray(`(#${agent.tokenId})`));
|
|
121
|
+
console.log(chalk.gray(' Address: '), chalk.dim(agent.address));
|
|
122
|
+
console.log(chalk.gray(' Reputation: '), reputationColor(agent.reputationScore.toString()));
|
|
123
|
+
console.log(chalk.gray(' Capabilities: '), chalk.white(agent.capabilities));
|
|
124
|
+
console.log();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (matchingLogs.length > results.length) {
|
|
128
|
+
console.log(chalk.gray(` ... and ${matchingLogs.length - results.length} more results`));
|
|
129
|
+
console.log(chalk.gray(' Use --limit to see more'));
|
|
130
|
+
console.log();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
console.log(chalk.cyan.bold('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n'));
|
|
134
|
+
|
|
135
|
+
} catch (err: any) {
|
|
136
|
+
spinner.stop();
|
|
137
|
+
error(`Search failed: ${err.message}`);
|
|
138
|
+
|
|
139
|
+
// Provide helpful error message for common issues
|
|
140
|
+
if (err.message.includes('block range')) {
|
|
141
|
+
console.log(chalk.yellow('\nā ļø The search requires scanning many blocks.'));
|
|
142
|
+
console.log(chalk.gray('This may be slow or hit rate limits with public RPCs.'));
|
|
143
|
+
console.log(chalk.gray('Consider using a dedicated RPC endpoint or subgraph.\n'));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return cmd;
|
|
151
|
+
}
|
package/src/commands/treasury.ts
CHANGED
|
@@ -176,6 +176,52 @@ const TREASURY_ABI = [
|
|
|
176
176
|
],
|
|
177
177
|
stateMutability: 'view',
|
|
178
178
|
},
|
|
179
|
+
{
|
|
180
|
+
type: 'function',
|
|
181
|
+
name: 'revokeRole',
|
|
182
|
+
inputs: [{ name: 'account', type: 'address' }],
|
|
183
|
+
outputs: [],
|
|
184
|
+
stateMutability: 'nonpayable',
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
type: 'function',
|
|
188
|
+
name: 'setReserveLock',
|
|
189
|
+
inputs: [{ name: 'newLockBps', type: 'uint256' }],
|
|
190
|
+
outputs: [],
|
|
191
|
+
stateMutability: 'nonpayable',
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
type: 'function',
|
|
195
|
+
name: 'approveSpender',
|
|
196
|
+
inputs: [
|
|
197
|
+
{ name: 'token', type: 'address' },
|
|
198
|
+
{ name: 'spender', type: 'address' },
|
|
199
|
+
{ name: 'amount', type: 'uint256' },
|
|
200
|
+
],
|
|
201
|
+
outputs: [],
|
|
202
|
+
stateMutability: 'nonpayable',
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
type: 'function',
|
|
206
|
+
name: 'setIntegration',
|
|
207
|
+
inputs: [
|
|
208
|
+
{ name: 'integrationName', type: 'string' },
|
|
209
|
+
{ name: 'addr', type: 'address' },
|
|
210
|
+
],
|
|
211
|
+
outputs: [],
|
|
212
|
+
stateMutability: 'nonpayable',
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
type: 'function',
|
|
216
|
+
name: 'withdrawETH',
|
|
217
|
+
inputs: [
|
|
218
|
+
{ name: 'to', type: 'address' },
|
|
219
|
+
{ name: 'amount', type: 'uint256' },
|
|
220
|
+
{ name: 'reason', type: 'string' },
|
|
221
|
+
],
|
|
222
|
+
outputs: [],
|
|
223
|
+
stateMutability: 'nonpayable',
|
|
224
|
+
},
|
|
179
225
|
] as const;
|
|
180
226
|
|
|
181
227
|
const ERC20_ABI = [
|
|
@@ -903,5 +949,344 @@ export function createTreasuryCommand(): Command {
|
|
|
903
949
|
}
|
|
904
950
|
});
|
|
905
951
|
|
|
952
|
+
// Revoke role
|
|
953
|
+
cmd
|
|
954
|
+
.command('revoke')
|
|
955
|
+
.description('Revoke role from an address')
|
|
956
|
+
.requiredOption('--address <addr>', 'Address to revoke role from')
|
|
957
|
+
.option('--json', 'Output as JSON')
|
|
958
|
+
.action(async (options: {
|
|
959
|
+
address: string;
|
|
960
|
+
} & OutputOptions) => {
|
|
961
|
+
try {
|
|
962
|
+
const accountAddress = options.address as Address;
|
|
963
|
+
const treasuryAddress = await getTreasuryAddress();
|
|
964
|
+
const { client, account } = await loadWallet();
|
|
965
|
+
const publicClient = getPublicClient('mainnet');
|
|
966
|
+
|
|
967
|
+
const hash = await withSpinner(
|
|
968
|
+
'Revoking role...',
|
|
969
|
+
async () =>
|
|
970
|
+
client.writeContract({
|
|
971
|
+
address: treasuryAddress,
|
|
972
|
+
abi: TREASURY_ABI,
|
|
973
|
+
functionName: 'revokeRole',
|
|
974
|
+
args: [accountAddress],
|
|
975
|
+
account,
|
|
976
|
+
chain: base,
|
|
977
|
+
})
|
|
978
|
+
);
|
|
979
|
+
|
|
980
|
+
await publicClient.waitForTransactionReceipt({ hash });
|
|
981
|
+
|
|
982
|
+
if (outputJSON({
|
|
983
|
+
address: accountAddress,
|
|
984
|
+
transactionHash: hash,
|
|
985
|
+
}, options)) {
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
console.log();
|
|
990
|
+
success('Role revoked successfully!');
|
|
991
|
+
console.log();
|
|
992
|
+
console.log(' Address: ', chalk.gray(accountAddress));
|
|
993
|
+
console.log(' Tx Hash: ', chalk.gray(hash));
|
|
994
|
+
console.log();
|
|
995
|
+
} catch (err) {
|
|
996
|
+
error(`Failed to revoke role: ${err}`);
|
|
997
|
+
process.exit(1);
|
|
998
|
+
}
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
// Set reserve lock
|
|
1002
|
+
cmd
|
|
1003
|
+
.command('reserve')
|
|
1004
|
+
.description('Set reserve lock percentage (0-5000 bps)')
|
|
1005
|
+
.requiredOption('--lock <bps>', 'Reserve lock in basis points (0-5000)')
|
|
1006
|
+
.option('--json', 'Output as JSON')
|
|
1007
|
+
.action(async (options: {
|
|
1008
|
+
lock: string;
|
|
1009
|
+
} & OutputOptions) => {
|
|
1010
|
+
try {
|
|
1011
|
+
const lockBps = BigInt(options.lock);
|
|
1012
|
+
if (lockBps < 0n || lockBps > 5000n) {
|
|
1013
|
+
throw new Error('Reserve lock must be between 0 and 5000 bps (0-50%)');
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
const treasuryAddress = await getTreasuryAddress();
|
|
1017
|
+
const { client, account } = await loadWallet();
|
|
1018
|
+
const publicClient = getPublicClient('mainnet');
|
|
1019
|
+
|
|
1020
|
+
const hash = await withSpinner(
|
|
1021
|
+
'Setting reserve lock...',
|
|
1022
|
+
async () =>
|
|
1023
|
+
client.writeContract({
|
|
1024
|
+
address: treasuryAddress,
|
|
1025
|
+
abi: TREASURY_ABI,
|
|
1026
|
+
functionName: 'setReserveLock',
|
|
1027
|
+
args: [lockBps],
|
|
1028
|
+
account,
|
|
1029
|
+
chain: base,
|
|
1030
|
+
})
|
|
1031
|
+
);
|
|
1032
|
+
|
|
1033
|
+
await publicClient.waitForTransactionReceipt({ hash });
|
|
1034
|
+
|
|
1035
|
+
if (outputJSON({
|
|
1036
|
+
lockBps: Number(lockBps),
|
|
1037
|
+
lockPercent: Number(lockBps) / 100,
|
|
1038
|
+
transactionHash: hash,
|
|
1039
|
+
}, options)) {
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
console.log();
|
|
1044
|
+
success('Reserve lock set successfully!');
|
|
1045
|
+
console.log();
|
|
1046
|
+
console.log(' Lock: ', chalk.white.bold(`${Number(lockBps) / 100}%`));
|
|
1047
|
+
console.log(' Tx Hash: ', chalk.gray(hash));
|
|
1048
|
+
console.log();
|
|
1049
|
+
} catch (err) {
|
|
1050
|
+
error(`Failed to set reserve lock: ${err}`);
|
|
1051
|
+
process.exit(1);
|
|
1052
|
+
}
|
|
1053
|
+
});
|
|
1054
|
+
|
|
1055
|
+
// Approve spender
|
|
1056
|
+
cmd
|
|
1057
|
+
.command('approve')
|
|
1058
|
+
.description('Approve token spender')
|
|
1059
|
+
.requiredOption('--token <addr>', 'Token address')
|
|
1060
|
+
.requiredOption('--spender <addr>', 'Spender address')
|
|
1061
|
+
.requiredOption('--amount <amount>', 'Amount to approve')
|
|
1062
|
+
.option('--json', 'Output as JSON')
|
|
1063
|
+
.action(async (options: {
|
|
1064
|
+
token: string;
|
|
1065
|
+
spender: string;
|
|
1066
|
+
amount: string;
|
|
1067
|
+
} & OutputOptions) => {
|
|
1068
|
+
try {
|
|
1069
|
+
const tokenAddress = options.token as Address;
|
|
1070
|
+
const spenderAddress = options.spender as Address;
|
|
1071
|
+
const treasuryAddress = await getTreasuryAddress();
|
|
1072
|
+
const { client, account } = await loadWallet();
|
|
1073
|
+
const publicClient = getPublicClient('mainnet');
|
|
1074
|
+
|
|
1075
|
+
// Get token decimals
|
|
1076
|
+
const decimals = await publicClient.readContract({
|
|
1077
|
+
address: tokenAddress,
|
|
1078
|
+
abi: ERC20_ABI,
|
|
1079
|
+
functionName: 'decimals',
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
const amount = parseUnits(options.amount, decimals);
|
|
1083
|
+
|
|
1084
|
+
const hash = await withSpinner(
|
|
1085
|
+
'Approving spender...',
|
|
1086
|
+
async () =>
|
|
1087
|
+
client.writeContract({
|
|
1088
|
+
address: treasuryAddress,
|
|
1089
|
+
abi: TREASURY_ABI,
|
|
1090
|
+
functionName: 'approveSpender',
|
|
1091
|
+
args: [tokenAddress, spenderAddress, amount],
|
|
1092
|
+
account,
|
|
1093
|
+
chain: base,
|
|
1094
|
+
})
|
|
1095
|
+
);
|
|
1096
|
+
|
|
1097
|
+
await publicClient.waitForTransactionReceipt({ hash });
|
|
1098
|
+
|
|
1099
|
+
if (outputJSON({
|
|
1100
|
+
token: tokenAddress,
|
|
1101
|
+
spender: spenderAddress,
|
|
1102
|
+
amount: options.amount,
|
|
1103
|
+
transactionHash: hash,
|
|
1104
|
+
}, options)) {
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
console.log();
|
|
1109
|
+
success('Spender approved successfully!');
|
|
1110
|
+
console.log();
|
|
1111
|
+
console.log(' Token: ', chalk.gray(tokenAddress));
|
|
1112
|
+
console.log(' Spender: ', chalk.gray(spenderAddress));
|
|
1113
|
+
console.log(' Amount: ', chalk.white.bold(options.amount));
|
|
1114
|
+
console.log(' Tx Hash: ', chalk.gray(hash));
|
|
1115
|
+
console.log();
|
|
1116
|
+
} catch (err) {
|
|
1117
|
+
error(`Failed to approve spender: ${err}`);
|
|
1118
|
+
process.exit(1);
|
|
1119
|
+
}
|
|
1120
|
+
});
|
|
1121
|
+
|
|
1122
|
+
// Set integration
|
|
1123
|
+
cmd
|
|
1124
|
+
.command('integrate')
|
|
1125
|
+
.description('Set integration contract')
|
|
1126
|
+
.requiredOption('--name <name>', 'Integration name (spendingMandate|policyRegistry|crossRailLedger)')
|
|
1127
|
+
.requiredOption('--address <addr>', 'Integration contract address')
|
|
1128
|
+
.option('--json', 'Output as JSON')
|
|
1129
|
+
.action(async (options: {
|
|
1130
|
+
name: string;
|
|
1131
|
+
address: string;
|
|
1132
|
+
} & OutputOptions) => {
|
|
1133
|
+
try {
|
|
1134
|
+
const validNames = ['spendingMandate', 'policyRegistry', 'crossRailLedger'];
|
|
1135
|
+
if (!validNames.includes(options.name)) {
|
|
1136
|
+
throw new Error(`Invalid integration name. Must be one of: ${validNames.join(', ')}`);
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
const integrationAddress = options.address as Address;
|
|
1140
|
+
const treasuryAddress = await getTreasuryAddress();
|
|
1141
|
+
const { client, account } = await loadWallet();
|
|
1142
|
+
const publicClient = getPublicClient('mainnet');
|
|
1143
|
+
|
|
1144
|
+
const hash = await withSpinner(
|
|
1145
|
+
'Setting integration...',
|
|
1146
|
+
async () =>
|
|
1147
|
+
client.writeContract({
|
|
1148
|
+
address: treasuryAddress,
|
|
1149
|
+
abi: TREASURY_ABI,
|
|
1150
|
+
functionName: 'setIntegration',
|
|
1151
|
+
args: [options.name, integrationAddress],
|
|
1152
|
+
account,
|
|
1153
|
+
chain: base,
|
|
1154
|
+
})
|
|
1155
|
+
);
|
|
1156
|
+
|
|
1157
|
+
await publicClient.waitForTransactionReceipt({ hash });
|
|
1158
|
+
|
|
1159
|
+
if (outputJSON({
|
|
1160
|
+
name: options.name,
|
|
1161
|
+
address: integrationAddress,
|
|
1162
|
+
transactionHash: hash,
|
|
1163
|
+
}, options)) {
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
console.log();
|
|
1168
|
+
success('Integration set successfully!');
|
|
1169
|
+
console.log();
|
|
1170
|
+
console.log(' Name: ', chalk.white.bold(options.name));
|
|
1171
|
+
console.log(' Address: ', chalk.gray(integrationAddress));
|
|
1172
|
+
console.log(' Tx Hash: ', chalk.gray(hash));
|
|
1173
|
+
console.log();
|
|
1174
|
+
} catch (err) {
|
|
1175
|
+
error(`Failed to set integration: ${err}`);
|
|
1176
|
+
process.exit(1);
|
|
1177
|
+
}
|
|
1178
|
+
});
|
|
1179
|
+
|
|
1180
|
+
// Withdraw ETH
|
|
1181
|
+
cmd
|
|
1182
|
+
.command('withdraw-eth')
|
|
1183
|
+
.description('Withdraw ETH from treasury')
|
|
1184
|
+
.requiredOption('--to <addr>', 'Recipient address')
|
|
1185
|
+
.requiredOption('--amount <amount>', 'Amount in ETH')
|
|
1186
|
+
.requiredOption('--reason <string>', 'Withdrawal reason')
|
|
1187
|
+
.option('--json', 'Output as JSON')
|
|
1188
|
+
.action(async (options: {
|
|
1189
|
+
to: string;
|
|
1190
|
+
amount: string;
|
|
1191
|
+
reason: string;
|
|
1192
|
+
} & OutputOptions) => {
|
|
1193
|
+
try {
|
|
1194
|
+
const toAddress = options.to as Address;
|
|
1195
|
+
const treasuryAddress = await getTreasuryAddress();
|
|
1196
|
+
const { client, account } = await loadWallet();
|
|
1197
|
+
const publicClient = getPublicClient('mainnet');
|
|
1198
|
+
|
|
1199
|
+
const amount = parseUnits(options.amount, 18);
|
|
1200
|
+
|
|
1201
|
+
const hash = await withSpinner(
|
|
1202
|
+
'Withdrawing ETH...',
|
|
1203
|
+
async () =>
|
|
1204
|
+
client.writeContract({
|
|
1205
|
+
address: treasuryAddress,
|
|
1206
|
+
abi: TREASURY_ABI,
|
|
1207
|
+
functionName: 'withdrawETH',
|
|
1208
|
+
args: [toAddress, amount, options.reason],
|
|
1209
|
+
account,
|
|
1210
|
+
chain: base,
|
|
1211
|
+
})
|
|
1212
|
+
);
|
|
1213
|
+
|
|
1214
|
+
await publicClient.waitForTransactionReceipt({ hash });
|
|
1215
|
+
|
|
1216
|
+
if (outputJSON({
|
|
1217
|
+
to: toAddress,
|
|
1218
|
+
amount: options.amount,
|
|
1219
|
+
reason: options.reason,
|
|
1220
|
+
transactionHash: hash,
|
|
1221
|
+
}, options)) {
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
console.log();
|
|
1226
|
+
success('ETH withdrawn successfully!');
|
|
1227
|
+
console.log();
|
|
1228
|
+
console.log(' Amount: ', chalk.white.bold(`${options.amount} ETH`));
|
|
1229
|
+
console.log(' To: ', chalk.gray(toAddress));
|
|
1230
|
+
console.log(' Reason: ', chalk.white(options.reason));
|
|
1231
|
+
console.log(' Tx Hash: ', chalk.gray(hash));
|
|
1232
|
+
console.log();
|
|
1233
|
+
} catch (err) {
|
|
1234
|
+
error(`Failed to withdraw ETH: ${err}`);
|
|
1235
|
+
process.exit(1);
|
|
1236
|
+
}
|
|
1237
|
+
});
|
|
1238
|
+
|
|
1239
|
+
// Get spend limits
|
|
1240
|
+
cmd
|
|
1241
|
+
.command('limits')
|
|
1242
|
+
.description('View spend limits for an operator')
|
|
1243
|
+
.requiredOption('--address <addr>', 'Operator address')
|
|
1244
|
+
.option('--json', 'Output as JSON')
|
|
1245
|
+
.action(async (address: string, options: OutputOptions & { address: string }) => {
|
|
1246
|
+
try {
|
|
1247
|
+
const operatorAddress = options.address as Address;
|
|
1248
|
+
const treasuryAddress = await getTreasuryAddress();
|
|
1249
|
+
const publicClient = getPublicClient('mainnet');
|
|
1250
|
+
|
|
1251
|
+
const limits = await withSpinner(
|
|
1252
|
+
'Fetching spend limits...',
|
|
1253
|
+
async () =>
|
|
1254
|
+
publicClient.readContract({
|
|
1255
|
+
address: treasuryAddress,
|
|
1256
|
+
abi: TREASURY_ABI,
|
|
1257
|
+
functionName: 'spendLimits',
|
|
1258
|
+
args: [operatorAddress],
|
|
1259
|
+
})
|
|
1260
|
+
);
|
|
1261
|
+
|
|
1262
|
+
const [maxPerTx, maxPerDay, spentToday, dayStart] = limits;
|
|
1263
|
+
|
|
1264
|
+
if (outputJSON({
|
|
1265
|
+
operator: operatorAddress,
|
|
1266
|
+
maxPerTx: formatUnits(maxPerTx, 6),
|
|
1267
|
+
maxPerDay: formatUnits(maxPerDay, 6),
|
|
1268
|
+
spentToday: formatUnits(spentToday, 6),
|
|
1269
|
+
dayStart: Number(dayStart),
|
|
1270
|
+
remainingToday: formatUnits(maxPerDay - spentToday, 6),
|
|
1271
|
+
}, options)) {
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
console.log();
|
|
1276
|
+
console.log(chalk.bold.cyan('š³ Spend Limits'));
|
|
1277
|
+
console.log();
|
|
1278
|
+
console.log(' Operator: ', chalk.gray(operatorAddress));
|
|
1279
|
+
console.log(' Max Per Tx: ', chalk.white.bold(`$${formatUnits(maxPerTx, 6)}`));
|
|
1280
|
+
console.log(' Max Per Day: ', chalk.white.bold(`$${formatUnits(maxPerDay, 6)}`));
|
|
1281
|
+
console.log(' Spent Today: ', chalk.yellow(`$${formatUnits(spentToday, 6)}`));
|
|
1282
|
+
console.log(' Remaining Today: ', chalk.green(`$${formatUnits(maxPerDay - spentToday, 6)}`));
|
|
1283
|
+
console.log(' Day Start: ', chalk.gray(new Date(Number(dayStart) * 1000).toLocaleString()));
|
|
1284
|
+
console.log();
|
|
1285
|
+
} catch (err) {
|
|
1286
|
+
error(`Failed to get spend limits: ${err}`);
|
|
1287
|
+
process.exit(1);
|
|
1288
|
+
}
|
|
1289
|
+
});
|
|
1290
|
+
|
|
906
1291
|
return cmd;
|
|
907
1292
|
}
|