@sage-protocol/cli 0.8.2 → 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/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/discover.js +3 -3
- package/dist/cli/commands/governance.js +141 -58
- package/dist/cli/commands/ipfs.js +12 -2
- package/dist/cli/commands/library.js +2 -1
- 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/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/contracts/index.js +2 -1
- package/dist/cli/utils/aliases.js +61 -3
- package/package.json +1 -1
|
@@ -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')
|
|
@@ -16,10 +16,19 @@ function register(program) {
|
|
|
16
16
|
try {
|
|
17
17
|
const chalk = { green: (s)=>s, yellow: (s)=>s, red: (s)=>s, cyan: (s)=>s, gray: (s)=>s };
|
|
18
18
|
const { ethers } = require('ethers');
|
|
19
|
-
const sxxxTokenAddress = cliConfig.resolveAddress('SXXX_TOKEN_ADDRESS');
|
|
20
19
|
const system = cliConfig.resolveAddress('SIMPLE_CONTRIBUTOR_SYSTEM_ADDRESS');
|
|
21
20
|
if (!system) throw new Error('SimpleContributorSystem address not configured');
|
|
21
|
+
const rpcUrl = cliConfig.resolveRpcUrl({ allowEnv: true });
|
|
22
|
+
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
|
23
|
+
const sysAbi = resolveArtifact('contracts/SimpleContributorSystem.sol/SimpleContributorSystem.json').abi;
|
|
24
|
+
const sysRead = new ethers.Contract(system, sysAbi, provider);
|
|
25
|
+
const systemToken = await sysRead.sxxxToken().catch(() => null);
|
|
26
|
+
const cfgToken = cliConfig.resolveAddress('SXXX_TOKEN_ADDRESS');
|
|
27
|
+
const sxxxTokenAddress = systemToken || cfgToken;
|
|
22
28
|
if (!sxxxTokenAddress) throw new Error('SXXX token address not configured');
|
|
29
|
+
if (systemToken && cfgToken && systemToken.toLowerCase() !== cfgToken.toLowerCase()) {
|
|
30
|
+
ui.warn(`SXXX token mismatch: contributor system uses ${systemToken}, config has ${cfgToken}. Using system token.`);
|
|
31
|
+
}
|
|
23
32
|
const WM = require('../wallet-manager');
|
|
24
33
|
const wm = new WM();
|
|
25
34
|
await wm.connect();
|
|
@@ -36,13 +45,12 @@ function register(program) {
|
|
|
36
45
|
const ERC20 = [ 'function approve(address,uint256) returns (bool)' ];
|
|
37
46
|
const tok = new ethers.Contract(sxxxTokenAddress, ERC20, signer);
|
|
38
47
|
const txa = await tok.approve(system, amountWei);
|
|
39
|
-
{ const { waitForReceipt } = require('../utils/tx-wait');
|
|
48
|
+
{ const { waitForReceipt } = require('../utils/tx-wait'); await waitForReceipt(provider, txa, Number(process.env.SAGE_TX_WAIT_MS || 60000)); }
|
|
40
49
|
ui.success('SXXX approval confirmed');
|
|
41
50
|
ui.info('Staking tokens...');
|
|
42
|
-
const sysAbi = resolveArtifact('contracts/SimpleContributorSystem.sol/SimpleContributorSystem.json').abi;
|
|
43
51
|
const c = new ethers.Contract(system, sysAbi, signer);
|
|
44
52
|
const txs = await c.stake(amountWei);
|
|
45
|
-
{ const { waitForReceipt } = require('../utils/tx-wait');
|
|
53
|
+
{ const { waitForReceipt } = require('../utils/tx-wait'); await waitForReceipt(provider, txs, Number(process.env.SAGE_TX_WAIT_MS || 60000)); }
|
|
46
54
|
if (options.json) ui.json({ success: true, amount: amountWei.toString() });
|
|
47
55
|
else ui.success(`Successfully staked ${ethers.formatEther(amountWei)} SXXX`);
|
|
48
56
|
} catch (error) {
|
|
@@ -61,6 +69,8 @@ function register(program) {
|
|
|
61
69
|
const { ethers } = require('ethers');
|
|
62
70
|
const system = cliConfig.resolveAddress('SIMPLE_CONTRIBUTOR_SYSTEM_ADDRESS');
|
|
63
71
|
if (!system) throw new Error('SimpleContributorSystem address not configured');
|
|
72
|
+
const rpcUrl = cliConfig.resolveRpcUrl({ allowEnv: true });
|
|
73
|
+
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
|
64
74
|
const WM = require('../wallet-manager');
|
|
65
75
|
const wm = new WM();
|
|
66
76
|
await wm.connect();
|
|
@@ -72,7 +82,7 @@ function register(program) {
|
|
|
72
82
|
const sysAbi = resolveArtifact('contracts/SimpleContributorSystem.sol/SimpleContributorSystem.json').abi;
|
|
73
83
|
const c = new ethers.Contract(system, sysAbi, signer);
|
|
74
84
|
const tx = await c.withdrawStake(amountWei);
|
|
75
|
-
{ const { waitForReceipt } = require('../utils/tx-wait');
|
|
85
|
+
{ const { waitForReceipt } = require('../utils/tx-wait'); await waitForReceipt(provider, tx, Number(process.env.SAGE_TX_WAIT_MS || 60000)); }
|
|
76
86
|
if (options.json) ui.json({ success: true, amount: amountWei.toString() });
|
|
77
87
|
else ui.success(`Successfully unstaked ${ethers.formatEther(amountWei)} SXXX`);
|
|
78
88
|
} catch (error) {
|
|
@@ -91,7 +101,7 @@ function register(program) {
|
|
|
91
101
|
const { ethers } = require('ethers');
|
|
92
102
|
const system = cliConfig.resolveAddress('SIMPLE_CONTRIBUTOR_SYSTEM_ADDRESS');
|
|
93
103
|
if (!system) throw new Error('SimpleContributorSystem address not configured');
|
|
94
|
-
const rpcUrl =
|
|
104
|
+
const rpcUrl = cliConfig.resolveRpcUrl({ allowEnv: true });
|
|
95
105
|
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
|
96
106
|
const sysAbi = resolveArtifact('contracts/SimpleContributorSystem.sol/SimpleContributorSystem.json').abi;
|
|
97
107
|
const c = new ethers.Contract(system, sysAbi, provider);
|
|
@@ -41,7 +41,7 @@ function register(program) {
|
|
|
41
41
|
if (options.tags) params.set('tags', options.tags);
|
|
42
42
|
if (options.vector === false) params.set('useVector', 'false');
|
|
43
43
|
|
|
44
|
-
const { body } = await client._fetchJson(`/
|
|
44
|
+
const { body } = await client._fetchJson(`/prompts/search?${params}`);
|
|
45
45
|
|
|
46
46
|
if (options.json) {
|
|
47
47
|
ui.json(body);
|
|
@@ -167,7 +167,7 @@ function register(program) {
|
|
|
167
167
|
method: options.method,
|
|
168
168
|
});
|
|
169
169
|
|
|
170
|
-
const { body } = await client._fetchJson(`/
|
|
170
|
+
const { body } = await client._fetchJson(`/prompts/similar/${cid}?${params}`);
|
|
171
171
|
|
|
172
172
|
if (options.json) {
|
|
173
173
|
ui.json(body);
|
|
@@ -246,7 +246,7 @@ function register(program) {
|
|
|
246
246
|
payload.diversify = true;
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
-
const { body } = await client._fetchJson('/
|
|
249
|
+
const { body } = await client._fetchJson('/prompts/match', {
|
|
250
250
|
method: 'POST',
|
|
251
251
|
headers: client.headers(),
|
|
252
252
|
body: JSON.stringify(payload),
|