@sage-protocol/cli 0.8.0 → 0.8.3
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 +12 -11
- package/dist/cli/commands/boost.js +339 -62
- package/dist/cli/commands/bounty.js +28 -4
- package/dist/cli/commands/config.js +10 -1
- package/dist/cli/commands/contributor.js +16 -6
- package/dist/cli/commands/dao.js +1 -1
- package/dist/cli/commands/discover.js +3 -3
- package/dist/cli/commands/governance.js +141 -58
- package/dist/cli/commands/install.js +178 -36
- package/dist/cli/commands/ipfs.js +12 -2
- package/dist/cli/commands/library.js +277 -268
- package/dist/cli/commands/members.js +132 -18
- package/dist/cli/commands/multiplier.js +101 -13
- package/dist/cli/commands/nft.js +16 -3
- package/dist/cli/commands/personal.js +69 -2
- package/dist/cli/commands/prompt.js +1 -1
- package/dist/cli/commands/proposals.js +153 -3
- package/dist/cli/commands/stake-status.js +130 -56
- package/dist/cli/commands/sxxx.js +37 -4
- package/dist/cli/commands/wallet.js +5 -10
- package/dist/cli/contracts/index.js +2 -1
- package/dist/cli/index.js +5 -0
- package/dist/cli/privy-auth-wallet-manager.js +3 -2
- package/dist/cli/services/config/chain-defaults.js +1 -1
- package/dist/cli/services/config/manager.js +3 -0
- package/dist/cli/services/config/schema.js +1 -0
- package/dist/cli/services/ipfs/onboarding.js +11 -0
- package/dist/cli/utils/aliases.js +62 -3
- package/dist/cli/utils/cli-ui.js +1 -1
- package/dist/cli/utils/provider.js +7 -3
- package/dist/cli/wallet-manager.js +7 -12
- package/dist/prompts/e2e-test-prompt.md +22 -0
- package/dist/prompts/skills/build-web3/plugin.json +11 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ sage --help
|
|
|
9
9
|
|
|
10
10
|
## Quick Start
|
|
11
11
|
|
|
12
|
-
### Personal Library (No Governance)
|
|
12
|
+
### Personal/Vault Library (No Governance)
|
|
13
13
|
|
|
14
14
|
Create a wallet-owned library for your prompts without DAO overhead:
|
|
15
15
|
|
|
@@ -20,13 +20,13 @@ Create a wallet-owned library for your prompts without DAO overhead:
|
|
|
20
20
|
sage wallet connect
|
|
21
21
|
|
|
22
22
|
# 2. Create a personal library
|
|
23
|
-
sage library
|
|
23
|
+
sage library vault create --name "My Prompts"
|
|
24
24
|
|
|
25
25
|
# 3. Push prompts to your library
|
|
26
|
-
sage library
|
|
26
|
+
sage library vault push <libraryId> --dir ./prompts
|
|
27
27
|
|
|
28
28
|
# 4. List your libraries
|
|
29
|
-
sage library
|
|
29
|
+
sage library vault list
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
### DAO Library (With Governance)
|
|
@@ -70,6 +70,7 @@ sage install github:user/repo
|
|
|
70
70
|
| `sage library personal list` | List your personal libraries |
|
|
71
71
|
| `sage library personal push` | Push files to a personal library |
|
|
72
72
|
| `sage library personal delete` | Delete a personal library |
|
|
73
|
+
| `sage library vault ...` | Alias for personal libraries (clearer SIWE vault naming) |
|
|
73
74
|
|
|
74
75
|
### Prompt Workspace
|
|
75
76
|
|
|
@@ -133,7 +134,7 @@ SAGE_WALLET_TYPE=privy sage wallet connect
|
|
|
133
134
|
| `sage gov execute <proposalId>` | Execute a queued proposal |
|
|
134
135
|
| `sage proposals list` | List proposals for current DAO |
|
|
135
136
|
|
|
136
|
-
## Personal Libraries
|
|
137
|
+
## Personal/Vault Libraries
|
|
137
138
|
|
|
138
139
|
Personal libraries are wallet-owned collections that don't require DAO governance. They're ideal for:
|
|
139
140
|
|
|
@@ -144,20 +145,20 @@ Personal libraries are wallet-owned collections that don't require DAO governanc
|
|
|
144
145
|
### Create and Manage
|
|
145
146
|
|
|
146
147
|
```bash
|
|
147
|
-
# Create a new
|
|
148
|
-
sage library
|
|
148
|
+
# Create a new vault library (alias of personal)
|
|
149
|
+
sage library vault create --name "My AI Prompts" --description "Curated prompts for development"
|
|
149
150
|
|
|
150
151
|
# View your libraries
|
|
151
|
-
sage library
|
|
152
|
+
sage library vault list
|
|
152
153
|
|
|
153
154
|
# Push content from a directory
|
|
154
|
-
sage library
|
|
155
|
+
sage library vault push lib_abc123 --dir ./my-prompts
|
|
155
156
|
|
|
156
157
|
# Get library details
|
|
157
|
-
sage library
|
|
158
|
+
sage library vault info lib_abc123
|
|
158
159
|
|
|
159
160
|
# Delete a library
|
|
160
|
-
sage library
|
|
161
|
+
sage library vault delete lib_abc123
|
|
161
162
|
```
|
|
162
163
|
|
|
163
164
|
### Premium Content
|
|
@@ -9,6 +9,45 @@ function register(program) {
|
|
|
9
9
|
.description('USDC governance boosts (direct or policy-based)')
|
|
10
10
|
.addHelpText('after', `\nAgent/Automation Support:\n Use -y/--yes flag or SAGE_YES=1 environment variable to skip all interactive prompts.\n\nExamples:\n $ sage boost create --proposal-id 1 --governor 0x... --per-voter 1000000 --max-voters 100 --yes\n $ sage boost fund --proposal-id 1 --amount 10000000 -y\n`);
|
|
11
11
|
|
|
12
|
+
const resolveSubgraphUrl = () => (cliConfig.resolveSubgraphUrl ? cliConfig.resolveSubgraphUrl() : (
|
|
13
|
+
process.env.SUBGRAPH_URL || process.env.SAGE_SUBGRAPH_URL || process.env.NEXT_PUBLIC_GRAPH_ENDPOINT || process.env.NEXT_PUBLIC_SUBGRAPH_URL || null
|
|
14
|
+
));
|
|
15
|
+
|
|
16
|
+
const parseProofInput = (value) => {
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
if (!value) return null;
|
|
19
|
+
let raw = String(value).trim();
|
|
20
|
+
if (!raw.startsWith('[')) {
|
|
21
|
+
raw = fs.readFileSync(raw, 'utf8');
|
|
22
|
+
}
|
|
23
|
+
return JSON.parse(raw);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const detectManagerType = async (provider, address) => {
|
|
27
|
+
const directIface = new ethers.Interface([
|
|
28
|
+
'function getBoost(uint256) view returns (tuple(address creator, address governor, uint256 proposalId, uint256 snapshot, uint256 perVoter, uint256 maxVoters, uint256 votersPaid, uint96 minVotes, uint8 payoutMode, uint8 support, uint256 startAt, uint256 expiresAt, uint256 totalPool, uint256 totalPaid, uint8 kind, address policy, bool active, bool paused) boost)'
|
|
29
|
+
]);
|
|
30
|
+
const merkleIface = new ethers.Interface([
|
|
31
|
+
'function getBoost(uint256) view returns (uint256,uint256,bytes32,bool,bool,address)'
|
|
32
|
+
]);
|
|
33
|
+
try {
|
|
34
|
+
const callData = directIface.encodeFunctionData('getBoost', [0n]);
|
|
35
|
+
const ret = await provider.call({ to: address, data: callData });
|
|
36
|
+
directIface.decodeFunctionResult('getBoost', ret);
|
|
37
|
+
return 'direct';
|
|
38
|
+
} catch (_) {
|
|
39
|
+
try {
|
|
40
|
+
const callDataM = merkleIface.encodeFunctionData('getBoost', [0n]);
|
|
41
|
+
const retM = await provider.call({ to: address, data: callDataM });
|
|
42
|
+
merkleIface.decodeFunctionResult('getBoost', retM);
|
|
43
|
+
return 'merkle';
|
|
44
|
+
} catch (_) {
|
|
45
|
+
return 'unknown';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
|
|
12
51
|
boost
|
|
13
52
|
.command('create')
|
|
14
53
|
.description('Create a boost (direct/Merkle/custom) with payout mode and time window')
|
|
@@ -49,31 +88,7 @@ function register(program) {
|
|
|
49
88
|
if (!usdcAddr) throw new Error('Missing USDC token (env USDC_ADDRESS/MOCK_USDC_ADDRESS) and manager.usdc() not readable');
|
|
50
89
|
|
|
51
90
|
// ===== Manager-type preflight (DIRECT vs MERKLE) =====
|
|
52
|
-
const
|
|
53
|
-
const directIface = new ethers.Interface([
|
|
54
|
-
'function getBoost(uint256) view returns (tuple(address creator, address governor, uint256 proposalId, uint256 snapshot, uint256 perVoter, uint256 maxVoters, uint256 votersPaid, uint96 minVotes, uint8 payoutMode, uint8 support, uint256 startAt, uint256 expiresAt, uint256 totalPool, uint256 totalPaid, uint8 kind, address policy, bool active, bool paused) boost)'
|
|
55
|
-
]);
|
|
56
|
-
const merkleIface = new ethers.Interface([
|
|
57
|
-
'function getBoost(uint256) view returns (uint256,uint256,bytes32,bool,bool,address)'
|
|
58
|
-
]);
|
|
59
|
-
try {
|
|
60
|
-
const callData = directIface.encodeFunctionData('getBoost', [0n]);
|
|
61
|
-
const ret = await provider.call({ to: address, data: callData });
|
|
62
|
-
directIface.decodeFunctionResult('getBoost', ret); // will throw if not direct
|
|
63
|
-
return 'direct';
|
|
64
|
-
} catch (_) {
|
|
65
|
-
try {
|
|
66
|
-
const callDataM = merkleIface.encodeFunctionData('getBoost', [0n]);
|
|
67
|
-
const retM = await provider.call({ to: address, data: callDataM });
|
|
68
|
-
merkleIface.decodeFunctionResult('getBoost', retM);
|
|
69
|
-
return 'merkle';
|
|
70
|
-
} catch (_) {
|
|
71
|
-
return 'unknown';
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const mgrType = await detectManagerType(mgrAddr);
|
|
91
|
+
const mgrType = await detectManagerType(provider, mgrAddr);
|
|
77
92
|
if (mgrType === 'merkle') {
|
|
78
93
|
ui.error(`Detected Merkle boost manager at ${mgrAddr}`);
|
|
79
94
|
ui.output(' This command uses the DIRECT manager ABI (createBoost(address,uint256,...)).');
|
|
@@ -288,18 +303,266 @@ function register(program) {
|
|
|
288
303
|
} catch (e) { handleCLIError(e, 'set-root'); }
|
|
289
304
|
});
|
|
290
305
|
|
|
306
|
+
boost
|
|
307
|
+
.command('list')
|
|
308
|
+
.description('List boosts (direct/merkle) using subgraph or on-chain logs')
|
|
309
|
+
.option('--type <type>', 'direct|merkle|auto (default: auto)', 'auto')
|
|
310
|
+
.option('--manager <address>', 'Boost manager address (overrides default for the selected type)')
|
|
311
|
+
.option('--governor <address>', 'Filter by governor (direct only)')
|
|
312
|
+
.option('--proposal-id <id>', 'Filter by proposal ID')
|
|
313
|
+
.option('--active', 'Only show active boosts', false)
|
|
314
|
+
.option('--from-block <n>', 'Start block for on-chain log scan (fallback)')
|
|
315
|
+
.option('--limit <n>', 'Maximum number of results (default: 25)', '25')
|
|
316
|
+
.option('--json', 'Output JSON', false)
|
|
317
|
+
.action(async (opts) => {
|
|
318
|
+
try {
|
|
319
|
+
const { enterJsonMode, withJsonVersion } = require('../utils/json-output');
|
|
320
|
+
enterJsonMode(opts);
|
|
321
|
+
ui.configure({ json: opts.json });
|
|
322
|
+
const WM = require('../wallet-manager');
|
|
323
|
+
const wm = new WM(); await wm.connect();
|
|
324
|
+
const provider = wm.getProvider();
|
|
325
|
+
|
|
326
|
+
const limit = Math.max(1, Number(opts.limit || 25));
|
|
327
|
+
let type = String(opts.type || 'auto').toLowerCase();
|
|
328
|
+
const proposalId = opts.proposalId ? BigInt(opts.proposalId) : null;
|
|
329
|
+
const governor = opts.governor ? String(opts.governor).toLowerCase() : null;
|
|
330
|
+
|
|
331
|
+
let directManager = cliConfig.resolveAddress('BOOST_MANAGER_DIRECT_ADDRESS', process.env.BOOST_MANAGER_DIRECT_ADDRESS) || cliConfig.resolveAddress('BOOST_MANAGER_ADDRESS');
|
|
332
|
+
let merkleManager = cliConfig.resolveAddress('BOOST_MANAGER_ADDRESS');
|
|
333
|
+
const loggerGlue = cliConfig.resolveAddress('BOOST_LOGGER_GLUE_ADDRESS', process.env.BOOST_LOGGER_GLUE_ADDRESS);
|
|
334
|
+
|
|
335
|
+
const results = [];
|
|
336
|
+
const sources = [];
|
|
337
|
+
|
|
338
|
+
if (opts.manager) {
|
|
339
|
+
if (type === 'direct') {
|
|
340
|
+
directManager = opts.manager;
|
|
341
|
+
} else if (type === 'merkle') {
|
|
342
|
+
merkleManager = opts.manager;
|
|
343
|
+
} else {
|
|
344
|
+
const detected = await detectManagerType(provider, opts.manager);
|
|
345
|
+
if (detected === 'direct') {
|
|
346
|
+
directManager = opts.manager;
|
|
347
|
+
type = 'direct';
|
|
348
|
+
} else if (detected === 'merkle') {
|
|
349
|
+
merkleManager = opts.manager;
|
|
350
|
+
type = 'merkle';
|
|
351
|
+
} else {
|
|
352
|
+
throw new Error(`Unknown boost manager type at ${opts.manager}. Pass --type direct|merkle.`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const fetchDirectFromSubgraph = async () => {
|
|
358
|
+
const subgraphUrl = resolveSubgraphUrl();
|
|
359
|
+
if (!subgraphUrl) return null;
|
|
360
|
+
const { querySubgraph } = require('../utils/subgraph-client');
|
|
361
|
+
const where = {};
|
|
362
|
+
if (proposalId != null) where.proposalId = proposalId.toString();
|
|
363
|
+
if (governor) where.governor = governor;
|
|
364
|
+
const query = `
|
|
365
|
+
query BoostEvents($first: Int!, $skip: Int!, $orderBy: String!, $orderDirection: String!, $where: BoostEvent_filter) {
|
|
366
|
+
boostEvents(first: $first, skip: $skip, orderBy: $orderBy, orderDirection: $orderDirection, where: $where) {
|
|
367
|
+
id
|
|
368
|
+
proposalId
|
|
369
|
+
governor
|
|
370
|
+
perVoter
|
|
371
|
+
maxVoters
|
|
372
|
+
snapshot
|
|
373
|
+
kind
|
|
374
|
+
policy
|
|
375
|
+
minVotes
|
|
376
|
+
blockTimestamp
|
|
377
|
+
transactionHash
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
`;
|
|
381
|
+
const data = await querySubgraph(query, {
|
|
382
|
+
first: limit,
|
|
383
|
+
skip: 0,
|
|
384
|
+
orderBy: 'blockTimestamp',
|
|
385
|
+
orderDirection: 'desc',
|
|
386
|
+
where
|
|
387
|
+
}, { subgraphUrl });
|
|
388
|
+
return data?.boostEvents || [];
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const fetchDirectFromLogs = async () => {
|
|
392
|
+
if (!loggerGlue) throw new Error('Missing BOOST_LOGGER_GLUE_ADDRESS for log scan fallback');
|
|
393
|
+
const iface = new ethers.Interface([
|
|
394
|
+
'event BoostCreated(uint256 indexed proposalId, address indexed governor, uint256 perVoter, uint256 maxVoters, uint256 snapshot, uint8 kind, address policy, uint96 minVotes)'
|
|
395
|
+
]);
|
|
396
|
+
const latest = await provider.getBlockNumber();
|
|
397
|
+
const fromBlock = opts.fromBlock ? Number(opts.fromBlock) : Math.max(0, latest - 200000);
|
|
398
|
+
const topics = [iface.getEvent('BoostCreated').topicHash, null, null];
|
|
399
|
+
if (proposalId != null) topics[1] = ethers.zeroPadValue(ethers.toBeHex(proposalId), 32);
|
|
400
|
+
if (governor) topics[2] = ethers.zeroPadValue(governor, 32);
|
|
401
|
+
const logs = await provider.getLogs({ address: loggerGlue, fromBlock, toBlock: latest, topics });
|
|
402
|
+
return logs.map((log) => {
|
|
403
|
+
const parsed = iface.parseLog(log);
|
|
404
|
+
return {
|
|
405
|
+
id: `${log.transactionHash}-${log.logIndex}`,
|
|
406
|
+
proposalId: parsed.args.proposalId.toString(),
|
|
407
|
+
governor: parsed.args.governor,
|
|
408
|
+
perVoter: parsed.args.perVoter.toString(),
|
|
409
|
+
maxVoters: parsed.args.maxVoters.toString(),
|
|
410
|
+
snapshot: parsed.args.snapshot.toString(),
|
|
411
|
+
kind: Number(parsed.args.kind),
|
|
412
|
+
policy: parsed.args.policy,
|
|
413
|
+
minVotes: parsed.args.minVotes.toString(),
|
|
414
|
+
blockTimestamp: String(log.blockNumber || ''),
|
|
415
|
+
transactionHash: log.transactionHash
|
|
416
|
+
};
|
|
417
|
+
}).slice(0, limit);
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
const fetchMerkleFromLogs = async () => {
|
|
421
|
+
if (!merkleManager) throw new Error('Missing BOOST_MANAGER_ADDRESS for merkle listing');
|
|
422
|
+
const iface = new ethers.Interface([
|
|
423
|
+
'event BoostCreated(uint256 indexed proposalId, address indexed creator, uint256 totalPool)'
|
|
424
|
+
]);
|
|
425
|
+
const latest = await provider.getBlockNumber();
|
|
426
|
+
const fromBlock = opts.fromBlock ? Number(opts.fromBlock) : Math.max(0, latest - 200000);
|
|
427
|
+
const topics = [iface.getEvent('BoostCreated').topicHash];
|
|
428
|
+
if (proposalId != null) topics.push(ethers.zeroPadValue(ethers.toBeHex(proposalId), 32));
|
|
429
|
+
const logs = await provider.getLogs({ address: merkleManager, fromBlock, toBlock: latest, topics });
|
|
430
|
+
return logs.map((log) => {
|
|
431
|
+
const parsed = iface.parseLog(log);
|
|
432
|
+
return {
|
|
433
|
+
id: `${log.transactionHash}-${log.logIndex}`,
|
|
434
|
+
proposalId: parsed.args.proposalId.toString(),
|
|
435
|
+
creator: parsed.args.creator,
|
|
436
|
+
totalPool: parsed.args.totalPool.toString(),
|
|
437
|
+
transactionHash: log.transactionHash
|
|
438
|
+
};
|
|
439
|
+
}).slice(0, limit);
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
const enrichDirect = async (items) => {
|
|
443
|
+
if (!directManager) return items;
|
|
444
|
+
const ifDirect = new ethers.Interface([
|
|
445
|
+
'function getBoost(uint256) view returns (tuple(address creator, address governor, uint256 proposalId, uint256 snapshot, uint256 perVoter, uint256 maxVoters, uint256 votersPaid, uint96 minVotes, uint8 payoutMode, uint8 support, uint256 startAt, uint256 expiresAt, uint256 totalPool, uint256 totalPaid, uint8 kind, address policy, bool active, bool paused) boost)'
|
|
446
|
+
]);
|
|
447
|
+
const enriched = await Promise.all(items.map(async (row) => {
|
|
448
|
+
try {
|
|
449
|
+
const ret = await provider.call({ to: directManager, data: ifDirect.encodeFunctionData('getBoost', [BigInt(row.proposalId)]) });
|
|
450
|
+
const [b] = ifDirect.decodeFunctionResult('getBoost', ret);
|
|
451
|
+
return {
|
|
452
|
+
...row,
|
|
453
|
+
managerType: 'direct',
|
|
454
|
+
manager: directManager,
|
|
455
|
+
active: b.active,
|
|
456
|
+
paused: b.paused,
|
|
457
|
+
totalPool: b.totalPool.toString(),
|
|
458
|
+
totalPaid: b.totalPaid.toString(),
|
|
459
|
+
votersPaid: b.votersPaid.toString()
|
|
460
|
+
};
|
|
461
|
+
} catch (_) {
|
|
462
|
+
return { ...row, managerType: 'direct', manager: directManager };
|
|
463
|
+
}
|
|
464
|
+
}));
|
|
465
|
+
return enriched;
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
const enrichMerkle = async (items) => {
|
|
469
|
+
if (!merkleManager) return items;
|
|
470
|
+
const ifMerkle = new ethers.Interface([
|
|
471
|
+
'function getBoost(uint256) view returns (uint256,uint256,bytes32,bool,bool,address)'
|
|
472
|
+
]);
|
|
473
|
+
const enriched = await Promise.all(items.map(async (row) => {
|
|
474
|
+
try {
|
|
475
|
+
const ret = await provider.call({ to: merkleManager, data: ifMerkle.encodeFunctionData('getBoost', [BigInt(row.proposalId)]) });
|
|
476
|
+
const [totalPool, totalClaimed, merkleRoot, active, finalized, creator] = ifMerkle.decodeFunctionResult('getBoost', ret);
|
|
477
|
+
return {
|
|
478
|
+
...row,
|
|
479
|
+
managerType: 'merkle',
|
|
480
|
+
manager: merkleManager,
|
|
481
|
+
totalPool: totalPool.toString(),
|
|
482
|
+
totalClaimed: totalClaimed.toString(),
|
|
483
|
+
merkleRoot,
|
|
484
|
+
active,
|
|
485
|
+
finalized,
|
|
486
|
+
creator
|
|
487
|
+
};
|
|
488
|
+
} catch (_) {
|
|
489
|
+
return { ...row, managerType: 'merkle', manager: merkleManager };
|
|
490
|
+
}
|
|
491
|
+
}));
|
|
492
|
+
return enriched;
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
if (type === 'direct' || type === 'auto') {
|
|
496
|
+
let directItems = null;
|
|
497
|
+
try {
|
|
498
|
+
directItems = await fetchDirectFromSubgraph();
|
|
499
|
+
if (directItems?.length) sources.push('subgraph');
|
|
500
|
+
} catch (e) {
|
|
501
|
+
ui.warn(`Subgraph query failed: ${e.message}. Falling back to logs.`);
|
|
502
|
+
}
|
|
503
|
+
if (!directItems || !directItems.length) {
|
|
504
|
+
directItems = await fetchDirectFromLogs();
|
|
505
|
+
sources.push('logs');
|
|
506
|
+
}
|
|
507
|
+
const enriched = await enrichDirect(directItems || []);
|
|
508
|
+
results.push(...enriched);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (type === 'merkle' || type === 'auto') {
|
|
512
|
+
if (governor && type !== 'direct') {
|
|
513
|
+
ui.warn('Governor filter is only supported for direct boosts; ignoring for merkle listing.');
|
|
514
|
+
}
|
|
515
|
+
const merkleItems = await fetchMerkleFromLogs();
|
|
516
|
+
sources.push('logs');
|
|
517
|
+
const enriched = await enrichMerkle(merkleItems || []);
|
|
518
|
+
results.push(...enriched);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const filtered = opts.active ? results.filter(r => r.active) : results;
|
|
522
|
+
if (opts.json) {
|
|
523
|
+
ui.json(withJsonVersion({ count: filtered.length, source: sources, boosts: filtered }));
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (!filtered.length) {
|
|
528
|
+
ui.info('No boosts found matching the criteria.');
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
ui.output(`Found ${filtered.length} boosts (${sources.join(', ')}):\n`);
|
|
532
|
+
for (const b of filtered) {
|
|
533
|
+
if (b.managerType === 'merkle') {
|
|
534
|
+
ui.output(`[${b.proposalId}] merkle boost`);
|
|
535
|
+
ui.output(` Manager: ${b.manager || 'unknown'}`);
|
|
536
|
+
if (b.totalPool) ui.output(` Pool: ${b.totalPool} | Claimed: ${b.totalClaimed ?? '0'}`);
|
|
537
|
+
if (b.active != null) ui.output(` Active: ${b.active} | Finalized: ${b.finalized ?? false}`);
|
|
538
|
+
} else {
|
|
539
|
+
ui.output(`[${b.proposalId}] direct boost`);
|
|
540
|
+
ui.output(` Governor: ${b.governor || 'n/a'} | Manager: ${b.manager || 'unknown'}`);
|
|
541
|
+
ui.output(` Per Voter: ${b.perVoter} | Max Voters: ${b.maxVoters}`);
|
|
542
|
+
if (b.totalPool) ui.output(` Pool: ${b.totalPool} | Paid: ${b.totalPaid ?? '0'} | Voters Paid: ${b.votersPaid ?? '0'}`);
|
|
543
|
+
if (b.active != null) ui.output(` Active: ${b.active} | Paused: ${b.paused ?? false}`);
|
|
544
|
+
}
|
|
545
|
+
ui.output('');
|
|
546
|
+
}
|
|
547
|
+
} catch (e) { handleCLIError(e, 'list'); }
|
|
548
|
+
});
|
|
549
|
+
|
|
291
550
|
boost
|
|
292
551
|
.command('status')
|
|
293
552
|
.description('Show boost status for a proposal')
|
|
294
|
-
.
|
|
553
|
+
.option('--proposal-id <id>', 'Proposal ID')
|
|
554
|
+
.option('--boost-id <id>', 'Alias for proposal ID')
|
|
295
555
|
.option('--manager <address>', 'Boost manager')
|
|
296
556
|
.action(async (opts) => {
|
|
297
557
|
try {
|
|
558
|
+
if (!opts.proposalId && !opts.boostId) {
|
|
559
|
+
throw new Error('Missing --proposal-id (or --boost-id)');
|
|
560
|
+
}
|
|
298
561
|
const WM = require('../wallet-manager');
|
|
299
562
|
const wm = new WM(); await wm.connect();
|
|
300
563
|
const provider = wm.getProvider();
|
|
301
564
|
const mgrAddr = opts.manager || cliConfig.resolveAddress('BOOST_MANAGER_ADDRESS');
|
|
302
|
-
const id = BigInt(opts.proposalId);
|
|
565
|
+
const id = BigInt(opts.proposalId || opts.boostId);
|
|
303
566
|
const ifMerkle = new ethers.Interface(['function getBoost(uint256) view returns (uint256,uint256,bytes32,bool,bool,address)']);
|
|
304
567
|
const ifDirect = new ethers.Interface(['function getBoost(uint256) view returns (tuple(address creator, address governor, uint256 proposalId, uint256 snapshot, uint256 perVoter, uint256 maxVoters, uint256 votersPaid, uint96 minVotes, uint8 payoutMode, uint8 support, uint256 startAt, uint256 expiresAt, uint256 totalPool, uint256 totalPaid, uint8 kind, address policy, bool active, bool paused) boost)']);
|
|
305
568
|
// Try DIRECT first
|
|
@@ -307,7 +570,7 @@ function register(program) {
|
|
|
307
570
|
const retD = await provider.call({ to: mgrAddr, data: ifDirect.encodeFunctionData('getBoost', [id]) });
|
|
308
571
|
const [b] = ifDirect.decodeFunctionResult('getBoost', retD);
|
|
309
572
|
const boostData = {
|
|
310
|
-
managerType: 'direct', proposalId: opts.proposalId,
|
|
573
|
+
managerType: 'direct', proposalId: String(opts.proposalId || opts.boostId),
|
|
311
574
|
creator: b.creator, governor: b.governor, snapshot: b.snapshot.toString(), perVoter: b.perVoter.toString(),
|
|
312
575
|
maxVoters: b.maxVoters.toString(), votersPaid: b.votersPaid.toString(), minVotes: Number(b.minVotes),
|
|
313
576
|
kind: Number(b.kind), policy: b.policy, active: b.active, paused: b.paused,
|
|
@@ -321,7 +584,7 @@ function register(program) {
|
|
|
321
584
|
const retM = await provider.call({ to: mgrAddr, data: ifMerkle.encodeFunctionData('getBoost', [id]) });
|
|
322
585
|
const [totalPool, totalClaimed, merkleRoot, active, finalized, creator] = ifMerkle.decodeFunctionResult('getBoost', retM);
|
|
323
586
|
const boostData = {
|
|
324
|
-
managerType: 'merkle', proposalId: opts.proposalId,
|
|
587
|
+
managerType: 'merkle', proposalId: String(opts.proposalId || opts.boostId),
|
|
325
588
|
totalPool: totalPool.toString(), totalClaimed: totalClaimed.toString(), merkleRoot, active, finalized, creator
|
|
326
589
|
};
|
|
327
590
|
ui.json(boostData);
|
|
@@ -347,27 +610,7 @@ function register(program) {
|
|
|
347
610
|
const mgrAddr = opts.manager || cliConfig.resolveAddress('BOOST_MANAGER_ADDRESS');
|
|
348
611
|
|
|
349
612
|
// Manager-type preflight + helpful hint if mismatched
|
|
350
|
-
const
|
|
351
|
-
const directIface = new ethers.Interface([
|
|
352
|
-
'function getBoost(uint256) view returns (tuple(address creator, address governor, uint256 proposalId, uint256 snapshot, uint256 perVoter, uint256 maxVoters, uint256 votersPaid, uint96 minVotes, uint8 payoutMode, uint8 support, uint256 startAt, uint256 expiresAt, uint256 totalPool, uint256 totalPaid, uint8 kind, address policy, bool active, bool paused) boost)'
|
|
353
|
-
]);
|
|
354
|
-
const merkleIface = new ethers.Interface([
|
|
355
|
-
'function getBoost(uint256) view returns (uint256,uint256,bytes32,bool,bool,address)'
|
|
356
|
-
]);
|
|
357
|
-
try {
|
|
358
|
-
const retD = await provider.call({ to: address, data: directIface.encodeFunctionData('getBoost', [0n]) });
|
|
359
|
-
directIface.decodeFunctionResult('getBoost', retD);
|
|
360
|
-
return 'direct';
|
|
361
|
-
} catch (_) {
|
|
362
|
-
try {
|
|
363
|
-
const retM = await provider.call({ to: address, data: merkleIface.encodeFunctionData('getBoost', [0n]) });
|
|
364
|
-
merkleIface.decodeFunctionResult('getBoost', retM);
|
|
365
|
-
return 'merkle';
|
|
366
|
-
} catch (_) { return 'unknown'; }
|
|
367
|
-
}
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
const mgrType = await detectManagerType(mgrAddr);
|
|
613
|
+
const mgrType = await detectManagerType(provider, mgrAddr);
|
|
371
614
|
const id = BigInt(opts.proposalId);
|
|
372
615
|
let looksEmpty = false;
|
|
373
616
|
if (mgrType === 'direct') {
|
|
@@ -404,10 +647,12 @@ function register(program) {
|
|
|
404
647
|
|
|
405
648
|
boost
|
|
406
649
|
.command('claim')
|
|
407
|
-
.description('Claim a boost rebate
|
|
650
|
+
.description('Claim a boost rebate (direct or merkle)')
|
|
408
651
|
.requiredOption('--proposal-id <id>', 'Proposal ID')
|
|
409
|
-
.
|
|
410
|
-
.
|
|
652
|
+
.option('--type <type>', 'direct|merkle|auto (default: auto)', 'auto')
|
|
653
|
+
.option('--amount <usdc>', 'USDC amount (required for merkle or variable payouts)')
|
|
654
|
+
.option('--proof <json>', 'JSON array of hex nodes or path to json file')
|
|
655
|
+
.option('--data <hex>', 'ABI-encoded bytes (overrides proof/amount for direct custom eligibility)')
|
|
411
656
|
.option('--manager <address>', 'Boost manager')
|
|
412
657
|
.option('--json', 'Output JSON', false)
|
|
413
658
|
.action(async (opts) => {
|
|
@@ -416,19 +661,51 @@ function register(program) {
|
|
|
416
661
|
enterJsonMode(opts);
|
|
417
662
|
ui.configure({ json: opts.json });
|
|
418
663
|
const WM = require('../wallet-manager');
|
|
419
|
-
const fs = require('fs');
|
|
420
664
|
const wm = new WM(); await wm.connect();
|
|
421
665
|
const signer = wm.getSigner();
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
666
|
+
const provider = wm.getProvider();
|
|
667
|
+
const mgrAddr = opts.manager || cliConfig.resolveAddress('BOOST_MANAGER_ADDRESS') || cliConfig.resolveAddress('BOOST_MANAGER_DIRECT_ADDRESS');
|
|
668
|
+
|
|
669
|
+
const type = String(opts.type || 'auto').toLowerCase();
|
|
670
|
+
const managerType = type === 'auto' ? await detectManagerType(provider, mgrAddr) : type;
|
|
671
|
+
|
|
672
|
+
if (managerType === 'merkle') {
|
|
673
|
+
if (!opts.amount) throw new Error('--amount is required for merkle claims');
|
|
674
|
+
if (!opts.proof) throw new Error('--proof is required for merkle claims');
|
|
675
|
+
const proof = parseProofInput(opts.proof);
|
|
676
|
+
const iface = new ethers.Interface(['function claim(uint256,address,uint256,bytes32[])']);
|
|
677
|
+
const tx = await signer.sendTransaction({ to: mgrAddr, data: iface.encodeFunctionData('claim', [BigInt(opts.proposalId), await signer.getAddress(), BigInt(String(opts.amount).replace(/_/g, '')), proof]) });
|
|
678
|
+
if (opts.json) {
|
|
679
|
+
printOk({ proposalId: String(opts.proposalId), boostId: String(opts.proposalId), manager: mgrAddr, amount: String(opts.amount), txHash: tx.hash });
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
ui.success(`Transaction sent: ${tx.hash}`);
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
if (managerType !== 'direct') {
|
|
687
|
+
throw new Error(`Unknown boost manager type at ${mgrAddr}. Pass --type direct|merkle.`);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
let data = opts.data;
|
|
691
|
+
if (!data) {
|
|
692
|
+
if (opts.proof) {
|
|
693
|
+
const proof = parseProofInput(opts.proof);
|
|
694
|
+
const coder = ethers.AbiCoder.defaultAbiCoder();
|
|
695
|
+
if (opts.amount) {
|
|
696
|
+
data = coder.encode(['bytes32[]', 'uint256'], [proof, BigInt(String(opts.amount).replace(/_/g, ''))]);
|
|
697
|
+
} else {
|
|
698
|
+
data = coder.encode(['bytes32[]'], [proof]);
|
|
699
|
+
}
|
|
700
|
+
} else {
|
|
701
|
+
data = '0x';
|
|
702
|
+
}
|
|
426
703
|
}
|
|
427
|
-
|
|
428
|
-
const iface = new ethers.Interface(['function claim(uint256,
|
|
429
|
-
const tx = await signer.sendTransaction({ to: mgrAddr, data: iface.encodeFunctionData('claim', [BigInt(opts.proposalId),
|
|
704
|
+
|
|
705
|
+
const iface = new ethers.Interface(['function claim(uint256,bytes)']);
|
|
706
|
+
const tx = await signer.sendTransaction({ to: mgrAddr, data: iface.encodeFunctionData('claim', [BigInt(opts.proposalId), data]) });
|
|
430
707
|
if (opts.json) {
|
|
431
|
-
printOk({ proposalId: String(opts.proposalId), boostId: String(opts.proposalId), manager: mgrAddr,
|
|
708
|
+
printOk({ proposalId: String(opts.proposalId), boostId: String(opts.proposalId), manager: mgrAddr, txHash: tx.hash, managerType: 'direct' });
|
|
432
709
|
return;
|
|
433
710
|
}
|
|
434
711
|
ui.success(`Transaction sent: ${tx.hash}`);
|
|
@@ -1709,6 +1709,7 @@ function register(program) {
|
|
|
1709
1709
|
.option('--limit <number>', 'Maximum number of bounties to show', '10')
|
|
1710
1710
|
.option('--dao <address>', 'Filter by DAO/SubDAO/Governor address')
|
|
1711
1711
|
.option('--subdao <address>', 'Alias for --dao')
|
|
1712
|
+
.option('--json', 'Output JSON', false)
|
|
1712
1713
|
.action(async (opts) => {
|
|
1713
1714
|
try {
|
|
1714
1715
|
const bountySystem = cliConfig.resolveAddress('SIMPLE_BOUNTY_SYSTEM_ADDRESS');
|
|
@@ -1718,7 +1719,7 @@ function register(program) {
|
|
|
1718
1719
|
return;
|
|
1719
1720
|
}
|
|
1720
1721
|
|
|
1721
|
-
const rpcUrl = cliConfig.resolveRpcUrl();
|
|
1722
|
+
const rpcUrl = cliConfig.resolveRpcUrl({ allowEnv: true });
|
|
1722
1723
|
|
|
1723
1724
|
// Resolve dao filter to governor address if provided
|
|
1724
1725
|
let governorFilter = null;
|
|
@@ -1745,14 +1746,17 @@ function register(program) {
|
|
|
1745
1746
|
const nextIdBigInt = await contracts.getNextBountyId(bountySystem, rpcUrl);
|
|
1746
1747
|
const totalBounties = Number(nextIdBigInt);
|
|
1747
1748
|
|
|
1748
|
-
|
|
1749
|
-
|
|
1749
|
+
if (!opts.json) {
|
|
1750
|
+
ui.info(`Found ${totalBounties} total bounties`);
|
|
1751
|
+
ui.header('Bounty List');
|
|
1752
|
+
}
|
|
1750
1753
|
|
|
1751
1754
|
const limit = Math.min(parseInt(opts.limit), totalBounties);
|
|
1752
1755
|
const statusFilter = opts.status?.toUpperCase();
|
|
1753
1756
|
const statusMap = { 'ACTIVE': 0, 'CLAIMED': 1, 'COMPLETED': 2, 'CANCELLED': 3, 'EXPIRED': 4, 'UNDER_REVIEW': 5 };
|
|
1754
1757
|
|
|
1755
1758
|
let displayed = 0;
|
|
1759
|
+
const results = [];
|
|
1756
1760
|
for (let i = 0; i < totalBounties && displayed < limit; i++) {
|
|
1757
1761
|
try {
|
|
1758
1762
|
const bounty = await contracts.getBounty(bountySystem, i, rpcUrl);
|
|
@@ -1773,7 +1777,22 @@ function register(program) {
|
|
|
1773
1777
|
const statusNames = ['ACTIVE', 'CLAIMED', 'COMPLETED', 'CANCELLED', 'EXPIRED', 'UNDER_REVIEW'];
|
|
1774
1778
|
const rewardEther = ethers.formatEther(reward);
|
|
1775
1779
|
|
|
1776
|
-
|
|
1780
|
+
results.push({
|
|
1781
|
+
id: i,
|
|
1782
|
+
status: statusNames[status] || String(status),
|
|
1783
|
+
rewardWei: reward?.toString?.() || String(reward),
|
|
1784
|
+
rewardSxxx: rewardEther,
|
|
1785
|
+
creator: bounty.creator || null,
|
|
1786
|
+
deadline: bounty.deadline?.toString?.() || null,
|
|
1787
|
+
minContributorLevel: bounty.minContributorLevel?.toString?.() || null,
|
|
1788
|
+
minTokenBalance: bounty.minTokenBalance?.toString?.() || null,
|
|
1789
|
+
requiredBadgeId: bounty.requiredBadgeId?.toString?.() || null,
|
|
1790
|
+
libraryAction: bounty.libraryAction?.toString?.() || null
|
|
1791
|
+
});
|
|
1792
|
+
|
|
1793
|
+
if (!opts.json) {
|
|
1794
|
+
ui.output(`ID: ${i} | Status: ${statusNames[status]} | Reward: ${rewardEther} SXXX`);
|
|
1795
|
+
}
|
|
1777
1796
|
displayed++;
|
|
1778
1797
|
|
|
1779
1798
|
} catch (e) {
|
|
@@ -1782,6 +1801,11 @@ function register(program) {
|
|
|
1782
1801
|
}
|
|
1783
1802
|
}
|
|
1784
1803
|
|
|
1804
|
+
if (opts.json) {
|
|
1805
|
+
ui.json({ ok: true, total: totalBounties, count: results.length, bounties: results });
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1785
1809
|
if (displayed === 0) {
|
|
1786
1810
|
ui.info('No bounties found matching the criteria');
|
|
1787
1811
|
}
|
|
@@ -762,7 +762,16 @@ function register(program) {
|
|
|
762
762
|
.addCommand(
|
|
763
763
|
new Command('show')
|
|
764
764
|
.description('Show current configuration')
|
|
765
|
-
.
|
|
765
|
+
.option('--json', 'Output JSON', false)
|
|
766
|
+
.action((opts) => {
|
|
767
|
+
if (opts.json) {
|
|
768
|
+
ui.json({
|
|
769
|
+
ok: true,
|
|
770
|
+
projectDir: config.getProjectDir(),
|
|
771
|
+
envPath: path.join(config.getProjectDir(), '.env')
|
|
772
|
+
});
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
766
775
|
ui.keyValue({
|
|
767
776
|
'Current project directory': config.getProjectDir(),
|
|
768
777
|
'.env file location': path.join(config.getProjectDir(), '.env')
|