@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.
Potentially problematic release.
This version of @sage-protocol/cli might be problematic. Click here for more details.
- 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
|
@@ -52,6 +52,10 @@ const AUCTION_ABI = [
|
|
|
52
52
|
'function settleCurrentAndCreateNewAuction()'
|
|
53
53
|
];
|
|
54
54
|
|
|
55
|
+
const SUBDAO_ABI = [
|
|
56
|
+
'function governor() view returns (address)'
|
|
57
|
+
];
|
|
58
|
+
|
|
55
59
|
const TOKEN_ABI = [
|
|
56
60
|
'function name() view returns (string)',
|
|
57
61
|
'function symbol() view returns (string)',
|
|
@@ -61,10 +65,38 @@ const TOKEN_ABI = [
|
|
|
61
65
|
];
|
|
62
66
|
|
|
63
67
|
function getProvider() {
|
|
64
|
-
const rpcUrl =
|
|
68
|
+
const rpcUrl = cliConfig.resolveRpcUrl({ allowEnv: true });
|
|
65
69
|
return new ethers.JsonRpcProvider(rpcUrl);
|
|
66
70
|
}
|
|
67
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Resolve a Governor address from a SubDAO or Governor address.
|
|
74
|
+
* If the address is a SubDAO, calls governor() to get the Governor.
|
|
75
|
+
* If the address is already a Governor, returns it directly.
|
|
76
|
+
*/
|
|
77
|
+
async function resolveGovernorAddress(address, provider) {
|
|
78
|
+
const normalized = ethers.getAddress(address);
|
|
79
|
+
// Try calling governor() - if it works, the address is a SubDAO
|
|
80
|
+
try {
|
|
81
|
+
const subdao = new ethers.Contract(normalized, SUBDAO_ABI, provider);
|
|
82
|
+
const governorAddr = await subdao.governor();
|
|
83
|
+
if (governorAddr && governorAddr !== ethers.ZeroAddress) {
|
|
84
|
+
return { governor: governorAddr, subdao: normalized, isSubDAO: true };
|
|
85
|
+
}
|
|
86
|
+
} catch (_) {
|
|
87
|
+
// Not a SubDAO or doesn't have governor() - assume it's a Governor
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Verify governor interface before returning
|
|
91
|
+
try {
|
|
92
|
+
const gov = new ethers.Contract(normalized, GOVERNOR_ABI, provider);
|
|
93
|
+
await gov.token();
|
|
94
|
+
return { governor: normalized, subdao: null, isSubDAO: false };
|
|
95
|
+
} catch (err) {
|
|
96
|
+
throw new Error(`Address ${normalized} is not a SubDAO or Governor. Use --gov for governor address or --subdao for SubDAO.`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
68
100
|
async function resolveAccountAddress(address) {
|
|
69
101
|
if (address) return address;
|
|
70
102
|
|
|
@@ -92,11 +124,21 @@ function register(program) {
|
|
|
92
124
|
addDAOAddressOptions(statusCmd);
|
|
93
125
|
statusCmd.action(async (options) => {
|
|
94
126
|
try {
|
|
95
|
-
const
|
|
127
|
+
const inputAddr = resolveDAOAddress(options, { required: true });
|
|
96
128
|
const provider = getProvider();
|
|
97
|
-
const governor = new ethers.Contract(governorAddr, GOVERNOR_ABI, provider);
|
|
98
129
|
|
|
99
|
-
|
|
130
|
+
// Auto-resolve Governor from SubDAO if needed
|
|
131
|
+
const resolved = await resolveGovernorAddress(inputAddr, provider);
|
|
132
|
+
const governorAddr = resolved.governor;
|
|
133
|
+
|
|
134
|
+
if (resolved.isSubDAO) {
|
|
135
|
+
ui.info(`SubDAO detected: ${inputAddr}`);
|
|
136
|
+
ui.info(`Resolved Governor: ${governorAddr}`);
|
|
137
|
+
} else {
|
|
138
|
+
ui.info(`Checking multiplier status for governor: ${governorAddr}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const governor = new ethers.Contract(governorAddr, GOVERNOR_ABI, provider);
|
|
100
142
|
|
|
101
143
|
// Check if multipliers are enabled
|
|
102
144
|
const isEnabled = await governor.isMultiplierEnabled().catch(() => false);
|
|
@@ -158,30 +200,44 @@ function register(program) {
|
|
|
158
200
|
// Build calculate command
|
|
159
201
|
const calculateCmd = new Command('calculate')
|
|
160
202
|
.description('Show voting power breakdown for an account')
|
|
161
|
-
.argument('[address]', 'Account address (defaults to connected wallet)')
|
|
203
|
+
.argument('[address]', 'Account address (defaults to connected wallet)')
|
|
204
|
+
.option('--json', 'Output JSON', false);
|
|
162
205
|
addDAOAddressOptions(calculateCmd);
|
|
163
206
|
calculateCmd.action(async (address, options) => {
|
|
164
207
|
try {
|
|
165
|
-
const
|
|
208
|
+
const inputAddr = resolveDAOAddress(options, { required: true });
|
|
166
209
|
const account = await resolveAccountAddress(address);
|
|
167
210
|
if (!account) {
|
|
168
211
|
throw new Error('No address provided. Pass an address or connect a wallet.');
|
|
169
212
|
}
|
|
170
213
|
|
|
171
214
|
const provider = getProvider();
|
|
215
|
+
|
|
216
|
+
// Auto-resolve Governor from SubDAO if needed
|
|
217
|
+
const resolved = await resolveGovernorAddress(inputAddr, provider);
|
|
218
|
+
const governorAddr = resolved.governor;
|
|
219
|
+
|
|
172
220
|
const governor = new ethers.Contract(governorAddr, GOVERNOR_ABI, provider);
|
|
173
221
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
'Account': account
|
|
177
|
-
|
|
178
|
-
|
|
222
|
+
if (!options.json) {
|
|
223
|
+
ui.output('\nVoting Power Breakdown');
|
|
224
|
+
const displayInfo = { 'Account': account };
|
|
225
|
+
if (resolved.isSubDAO) {
|
|
226
|
+
displayInfo['SubDAO'] = resolved.subdao;
|
|
227
|
+
displayInfo['Governor'] = governorAddr;
|
|
228
|
+
} else {
|
|
229
|
+
displayInfo['Governor'] = governorAddr;
|
|
230
|
+
}
|
|
231
|
+
ui.keyValue(displayInfo);
|
|
232
|
+
}
|
|
179
233
|
|
|
180
234
|
// Check if multipliers are enabled
|
|
181
235
|
const isEnabled = await governor.isMultiplierEnabled().catch(() => false);
|
|
182
236
|
const votingToken = await governor.token();
|
|
237
|
+
const baseVotesToken = await governor.baseVotesToken().catch(() => null);
|
|
238
|
+
const isWrapperMode = isEnabled || (baseVotesToken && baseVotesToken.toLowerCase() !== votingToken.toLowerCase());
|
|
183
239
|
|
|
184
|
-
if (!
|
|
240
|
+
if (!isWrapperMode) {
|
|
185
241
|
// Simple ERC20Votes - no multiplier
|
|
186
242
|
const token = new ethers.Contract(votingToken, TOKEN_ABI, provider);
|
|
187
243
|
const [name, symbol, decimals, balance, votes] = await Promise.all([
|
|
@@ -192,6 +248,22 @@ function register(program) {
|
|
|
192
248
|
token.getVotes(account)
|
|
193
249
|
]);
|
|
194
250
|
|
|
251
|
+
if (options.json) {
|
|
252
|
+
ui.json({
|
|
253
|
+
ok: true,
|
|
254
|
+
account,
|
|
255
|
+
governor: governorAddr,
|
|
256
|
+
token: votingToken,
|
|
257
|
+
name,
|
|
258
|
+
symbol,
|
|
259
|
+
decimals,
|
|
260
|
+
balance: balance.toString(),
|
|
261
|
+
votingPower: votes.toString(),
|
|
262
|
+
multiplier: '1x'
|
|
263
|
+
});
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
195
267
|
ui.output(`\n${name} (${symbol})`);
|
|
196
268
|
ui.keyValue({
|
|
197
269
|
'Balance': `${ethers.formatUnits(balance, decimals)}`,
|
|
@@ -203,7 +275,7 @@ function register(program) {
|
|
|
203
275
|
|
|
204
276
|
// Multiplied votes - get breakdown
|
|
205
277
|
const multipliedVotes = new ethers.Contract(votingToken, MULTIPLIED_VOTES_ABI, provider);
|
|
206
|
-
const baseTokenAddr = await multipliedVotes.baseToken();
|
|
278
|
+
const baseTokenAddr = baseVotesToken || await multipliedVotes.baseToken();
|
|
207
279
|
const nftAddr = await multipliedVotes.multiplierNFT();
|
|
208
280
|
const basis = await multipliedVotes.BASIS();
|
|
209
281
|
|
|
@@ -221,6 +293,22 @@ function register(program) {
|
|
|
221
293
|
const baseDisplay = ethers.formatUnits(baseVotes, decimals);
|
|
222
294
|
const effectiveDisplay = ethers.formatUnits(effectiveVotes, decimals);
|
|
223
295
|
|
|
296
|
+
if (options.json) {
|
|
297
|
+
ui.json({
|
|
298
|
+
ok: true,
|
|
299
|
+
account,
|
|
300
|
+
governor: governorAddr,
|
|
301
|
+
baseToken: baseTokenAddr,
|
|
302
|
+
votingToken,
|
|
303
|
+
baseVotes: baseVotes.toString(),
|
|
304
|
+
multiplier: multiplier.toString(),
|
|
305
|
+
multiplierDisplay: multDisplay,
|
|
306
|
+
effectiveVotes: effectiveVotes.toString(),
|
|
307
|
+
hasBonus: !!hasBonus
|
|
308
|
+
});
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
224
312
|
ui.output(`\n${name} (${symbol}) with NFT Multipliers`);
|
|
225
313
|
ui.keyValue({
|
|
226
314
|
'Base Votes': `${baseDisplay} ${symbol}`,
|
package/dist/cli/commands/nft.js
CHANGED
|
@@ -176,9 +176,10 @@ Automation:
|
|
|
176
176
|
.option('--dao <address>', 'DAO address (auto-detected from context if not provided)')
|
|
177
177
|
.option('--account <address>', 'Account to check (default: your wallet)')
|
|
178
178
|
.option('-v, --verbose', 'Show detailed output')
|
|
179
|
+
.option('--json', 'Output JSON', false)
|
|
179
180
|
.action(async (opts) => {
|
|
180
181
|
try {
|
|
181
|
-
ui.configure({ verbose: opts.verbose });
|
|
182
|
+
ui.configure({ verbose: opts.verbose, json: opts.json });
|
|
182
183
|
const rpcUrl = cliConfig.resolveRpcUrl();
|
|
183
184
|
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
|
184
185
|
const nftAddr = cliConfig.resolveAddress('VOTING_MULTIPLIER_NFT_ADDRESS');
|
|
@@ -208,6 +209,18 @@ Automation:
|
|
|
208
209
|
const multiplier = await contract.getMultiplier(account, daoAddr);
|
|
209
210
|
const multiplierDisplay = (Number(multiplier) / 10000).toFixed(4);
|
|
210
211
|
|
|
212
|
+
if (opts.json) {
|
|
213
|
+
ui.json({
|
|
214
|
+
ok: true,
|
|
215
|
+
account,
|
|
216
|
+
dao: daoAddr,
|
|
217
|
+
nft: nftAddr,
|
|
218
|
+
multiplier: multiplier.toString(),
|
|
219
|
+
multiplierDisplay: `${multiplierDisplay}x`
|
|
220
|
+
});
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
211
224
|
ui.header(`Voting Multiplier for ${ui.formatAddress(account)}`);
|
|
212
225
|
ui.keyValue([
|
|
213
226
|
['DAO', ui.formatAddress(daoAddr)],
|
|
@@ -269,10 +282,10 @@ Automation:
|
|
|
269
282
|
throw new Error('Multiplier must be >= 100 (100 = 1x)');
|
|
270
283
|
}
|
|
271
284
|
|
|
272
|
-
// Build calldata for
|
|
285
|
+
// Build calldata for governance-safe createTierViaGovernance
|
|
273
286
|
const abi = resolveArtifact('contracts/VotingMultiplierNFT.sol/VotingMultiplierNFT.json').abi;
|
|
274
287
|
const iface = new ethers.Interface(abi);
|
|
275
|
-
const calldata = iface.encodeFunctionData('
|
|
288
|
+
const calldata = iface.encodeFunctionData('createTierViaGovernance', [
|
|
276
289
|
daoAddr,
|
|
277
290
|
String(opts.name),
|
|
278
291
|
BigInt(multiplier),
|
|
@@ -105,7 +105,7 @@ async function fetchPlatformPrompts(opts = {}) {
|
|
|
105
105
|
|
|
106
106
|
if (opts.search) {
|
|
107
107
|
params.set('q', opts.search);
|
|
108
|
-
const { body } = await client._fetchJson(`/
|
|
108
|
+
const { body } = await client._fetchJson(`/prompts/search?${params}`);
|
|
109
109
|
return (body.results || []).map(r => ({
|
|
110
110
|
...r,
|
|
111
111
|
source: 'platform',
|
|
@@ -7,6 +7,57 @@ const { handleCLIError } = require('../utils/error-handler');
|
|
|
7
7
|
const subgraphClient = require('../utils/subgraph-client');
|
|
8
8
|
const cliConfig = require('../config');
|
|
9
9
|
|
|
10
|
+
async function getLogsChunked(provider, filter, maxRange = 50000) {
|
|
11
|
+
// Default to 50k blocks to stay under most RPC limits (Base Sepolia limits to 100k)
|
|
12
|
+
const latest = filter.toBlock === 'latest' || typeof filter.toBlock === 'undefined'
|
|
13
|
+
? await provider.getBlockNumber()
|
|
14
|
+
: Number(filter.toBlock);
|
|
15
|
+
const from = Number(filter.fromBlock || 0);
|
|
16
|
+
const results = [];
|
|
17
|
+
for (let start = from; start <= latest; start += maxRange) {
|
|
18
|
+
const end = Math.min(latest, start + maxRange - 1);
|
|
19
|
+
try {
|
|
20
|
+
const batch = await provider.getLogs({ ...filter, fromBlock: start, toBlock: end });
|
|
21
|
+
if (batch && batch.length) results.push(...batch);
|
|
22
|
+
} catch (e) {
|
|
23
|
+
if (process.env.SAGE_VERBOSE === '1') {
|
|
24
|
+
console.warn(`Log query failed for blocks ${start}-${end}: ${e.message}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return results;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function findProposalCreatedLog(provider, govAddr, gov, pid, lookbacks) {
|
|
32
|
+
const { ethers } = require('ethers');
|
|
33
|
+
const eventSig = gov.interface.getEvent('ProposalCreated').topicHash;
|
|
34
|
+
const currentBlock = await provider.getBlockNumber();
|
|
35
|
+
const pidHex = ethers.hexlify(ethers.toBeHex(pid));
|
|
36
|
+
for (const span of lookbacks) {
|
|
37
|
+
const from = currentBlock > span ? currentBlock - span : 0;
|
|
38
|
+
let logs = await getLogsChunked(provider, {
|
|
39
|
+
topics: [eventSig, ethers.zeroPadValue(pidHex, 32)],
|
|
40
|
+
address: govAddr,
|
|
41
|
+
fromBlock: from,
|
|
42
|
+
toBlock: 'latest'
|
|
43
|
+
});
|
|
44
|
+
if (!logs.length) {
|
|
45
|
+
const all = await getLogsChunked(provider, { topics: [eventSig], address: govAddr, fromBlock: from, toBlock: 'latest' });
|
|
46
|
+
for (const l of all) {
|
|
47
|
+
try {
|
|
48
|
+
const p = gov.interface.parseLog(l);
|
|
49
|
+
if (p && p.name === 'ProposalCreated' && BigInt(p.args.proposalId.toString()) === pid) {
|
|
50
|
+
logs = [l];
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
} catch { }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (logs.length) return logs[0];
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
10
61
|
function register(program) {
|
|
11
62
|
const cmd = new Command('proposals')
|
|
12
63
|
.description('Proposals navigation: inbox, preview, vote, execute');
|
|
@@ -219,10 +270,92 @@ function register(program) {
|
|
|
219
270
|
if (summary.description) ui.output(` Description: ${summary.description.slice(0, 100)}...`);
|
|
220
271
|
}
|
|
221
272
|
} catch (e) {
|
|
222
|
-
// Final fallback: minimal info
|
|
223
273
|
if (process.env.SAGE_VERBOSE === '1') {
|
|
224
274
|
ui.warn(`SDK getProposalDetails failed: ${e.message}`);
|
|
225
275
|
}
|
|
276
|
+
// Fallback 1: try subgraph directly (no block range limits)
|
|
277
|
+
let foundViaSubgraph = false;
|
|
278
|
+
try {
|
|
279
|
+
const status = await subgraphClient.checkSubgraphStatus(provider);
|
|
280
|
+
if (status.available && ctx.governor) {
|
|
281
|
+
const proposals = await subgraphClient.getProposals(ctx.governor, { first: 200 });
|
|
282
|
+
const pidDec = pid.toString();
|
|
283
|
+
const match = proposals.find((p) => {
|
|
284
|
+
const parts = String(p.id || '').split('-');
|
|
285
|
+
const last = parts.length > 1 ? parts[parts.length - 1] : String(p.id || '');
|
|
286
|
+
if (!last) return false;
|
|
287
|
+
if (last === pidDec) return true;
|
|
288
|
+
try { if (String(last).startsWith('0x') && BigInt(String(last)) === pid) return true; } catch (_) { }
|
|
289
|
+
return false;
|
|
290
|
+
});
|
|
291
|
+
if (match && match.targets?.length) {
|
|
292
|
+
const targets = Array.from(match.targets || [], (a) => String(a));
|
|
293
|
+
const values = Array.from(match.values || [], (v) => BigInt(String(v || 0)));
|
|
294
|
+
const calldatas = Array.from(match.calldatas || [], (b) => String(b));
|
|
295
|
+
const description = String(match.description || '');
|
|
296
|
+
const selectors = calldatas.map((d) => (typeof d === 'string' ? d.slice(0, 10) : '0x'));
|
|
297
|
+
const summary = {
|
|
298
|
+
id: pid.toString(),
|
|
299
|
+
source: 'subgraph',
|
|
300
|
+
actionCount: targets.length,
|
|
301
|
+
targets,
|
|
302
|
+
values: values.map((v) => v.toString()),
|
|
303
|
+
selectors,
|
|
304
|
+
description
|
|
305
|
+
};
|
|
306
|
+
if (opts.json) ui.json(withJsonVersion(summary));
|
|
307
|
+
else {
|
|
308
|
+
ui.output(`Proposal ${summary.id} (subgraph)`);
|
|
309
|
+
ui.output(` Actions: ${summary.actionCount}`);
|
|
310
|
+
if (summary.targets.length) ui.output(` First target: ${summary.targets[0]}`);
|
|
311
|
+
if (summary.selectors.length) ui.output(` First selector: ${summary.selectors[0]}`);
|
|
312
|
+
if (summary.description) ui.output(` Description: ${summary.description.slice(0, 100)}...`);
|
|
313
|
+
}
|
|
314
|
+
foundViaSubgraph = true;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
} catch (sgErr) {
|
|
318
|
+
if (process.env.SAGE_VERBOSE === '1') {
|
|
319
|
+
ui.warn(`Subgraph fallback failed: ${sgErr.message}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (foundViaSubgraph) return;
|
|
323
|
+
|
|
324
|
+
// Fallback 2: scan ProposalCreated logs with chunked queries
|
|
325
|
+
try {
|
|
326
|
+
if (ctx.governor) {
|
|
327
|
+
const GovABI = require('../utils/artifacts').resolveArtifact('contracts/cloneable/PromptGovernorCloneable.sol/PromptGovernorCloneable.json').abi;
|
|
328
|
+
const gov = new ethers.Contract(ctx.governor, GovABI, provider);
|
|
329
|
+
const hit = await findProposalCreatedLog(provider, ctx.governor, gov, pid, [20000, 100000, 500000]);
|
|
330
|
+
if (hit) {
|
|
331
|
+
const parsed = gov.interface.parseLog(hit);
|
|
332
|
+
const targets = Array.from(parsed.args.targets || [], (a) => String(a));
|
|
333
|
+
const values = Array.from(parsed.args.values || [], (v) => BigInt(v.toString()));
|
|
334
|
+
const calldatas = Array.from(parsed.args.calldatas || [], (b) => ethers.hexlify(b));
|
|
335
|
+
const description = String(parsed.args.description ?? '');
|
|
336
|
+
const selectors = calldatas.map((d) => (typeof d === 'string' ? d.slice(0, 10) : '0x'));
|
|
337
|
+
const summary = {
|
|
338
|
+
id: pid.toString(),
|
|
339
|
+
source: 'logs',
|
|
340
|
+
actionCount: targets.length,
|
|
341
|
+
targets,
|
|
342
|
+
values: values.map((v) => v.toString()),
|
|
343
|
+
selectors,
|
|
344
|
+
description
|
|
345
|
+
};
|
|
346
|
+
if (opts.json) ui.json(withJsonVersion(summary));
|
|
347
|
+
else {
|
|
348
|
+
ui.output(`Proposal ${summary.id} (logs)`);
|
|
349
|
+
ui.output(` Actions: ${summary.actionCount}`);
|
|
350
|
+
if (summary.targets.length) ui.output(` First target: ${summary.targets[0]}`);
|
|
351
|
+
if (summary.selectors.length) ui.output(` First selector: ${summary.selectors[0]}`);
|
|
352
|
+
if (summary.description) ui.output(` Description: ${summary.description.slice(0, 100)}...`);
|
|
353
|
+
}
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
} catch (_) { }
|
|
358
|
+
// Final fallback: minimal info
|
|
226
359
|
const summary = { id: pid.toString(), description: '(unable to fetch proposal details)' };
|
|
227
360
|
if (opts.json) ui.json(withJsonVersion(summary));
|
|
228
361
|
else ui.output(`Proposal ${summary.id} - ${summary.description}`);
|
|
@@ -795,10 +928,27 @@ function register(program) {
|
|
|
795
928
|
return;
|
|
796
929
|
}
|
|
797
930
|
|
|
798
|
-
//
|
|
931
|
+
// Queue only when needed; skip queue if already queued
|
|
932
|
+
let stateNum = null;
|
|
933
|
+
let stateName = 'unknown';
|
|
799
934
|
try {
|
|
800
|
-
await gm.
|
|
935
|
+
const state = await gm.governor.state(gm.normalizeProposalId(id));
|
|
936
|
+
stateNum = Number(state);
|
|
937
|
+
stateName = gm.getStateName ? gm.getStateName(stateNum) : String(stateNum);
|
|
801
938
|
} catch (_) {}
|
|
939
|
+
|
|
940
|
+
if (stateNum === 4) {
|
|
941
|
+
try {
|
|
942
|
+
await gm.queueProposal(id);
|
|
943
|
+
} catch (_) {}
|
|
944
|
+
} else if (stateNum === 5) {
|
|
945
|
+
if (!process.env.SAGE_QUIET_JSON) {
|
|
946
|
+
console.log(`ℹ️ Proposal already queued (state ${stateName}). Skipping queue step.`);
|
|
947
|
+
}
|
|
948
|
+
} else if (stateNum !== null && !process.env.SAGE_QUIET_JSON) {
|
|
949
|
+
console.log(`⚠️ Proposal is in state ${stateName}; queue may fail.`);
|
|
950
|
+
}
|
|
951
|
+
|
|
802
952
|
await gm.executeProposal(id);
|
|
803
953
|
ui.success('Execute complete');
|
|
804
954
|
} catch (e) {
|