@sage-protocol/cli 0.3.6 → 0.3.7

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.
@@ -41,7 +41,7 @@ function register(program) {
41
41
  // ===== Manager-type preflight (DIRECT vs MERKLE) =====
42
42
  const detectManagerType = async (address) => {
43
43
  const directIface = new ethers.Interface([
44
- 'function getBoost(uint256) view returns (address,address,uint256,uint256,uint256,uint256,uint96,uint8,address,bool,bool)'
44
+ '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)'
45
45
  ]);
46
46
  const merkleIface = new ethers.Interface([
47
47
  'function getBoost(uint256) view returns (uint256,uint256,bytes32,bool,bool,address)'
@@ -78,20 +78,20 @@ function register(program) {
78
78
  }
79
79
 
80
80
  const mapKind = (k) => {
81
- const v = String(k||'').toLowerCase();
81
+ const v = String(k || '').toLowerCase();
82
82
  if (v === 'direct') return 0; // EligibilityKind.DIRECT
83
83
  if (v === 'merkle') return 1; // EligibilityKind.MERKLE
84
84
  if (v === 'custom') return 2; // EligibilityKind.CUSTOM
85
85
  throw new Error(`Unknown kind: ${k}`);
86
86
  };
87
87
  const mapPayout = (m) => {
88
- const v = String(m||'').toLowerCase();
88
+ const v = String(m || '').toLowerCase();
89
89
  if (v === 'fixed') return 0; // PayoutMode.FIXED
90
90
  if (v === 'variable') return 1;// PayoutMode.VARIABLE
91
91
  throw new Error(`Unknown payout-mode: ${m}`);
92
92
  };
93
93
  const mapSupport = (s) => {
94
- const v = String(s||'').toLowerCase();
94
+ const v = String(s || '').toLowerCase();
95
95
  if (v === 'any') return 0; // EligibilitySupport.Any
96
96
  if (v === 'for') return 1; // EligibilitySupport.ForOnly
97
97
  throw new Error(`Unknown support: ${s}`);
@@ -116,7 +116,7 @@ function register(program) {
116
116
 
117
117
  // Preflight checks
118
118
  const owner = await signer.getAddress();
119
- const nowTs = BigInt(Math.floor(Date.now()/1000));
119
+ const nowTs = BigInt(Math.floor(Date.now() / 1000));
120
120
  if (startAt && expiresAt && expiresAt <= startAt) {
121
121
  console.log('⚠️ expires-at <= start-at; window may be invalid');
122
122
  }
@@ -149,54 +149,56 @@ function register(program) {
149
149
  'function nonces(address) view returns (uint256)',
150
150
  'function permit(address,address,uint256,uint256,uint8,bytes32,bytes32)'
151
151
  ], signer);
152
- const balance = await usdc.balanceOf(owner).catch(()=>0n);
152
+ const balance = await usdc.balanceOf(owner).catch(() => 0n);
153
153
  if (balance < totalPool) console.log(`⚠️ USDC balance ${balance.toString()} < required ${totalPool.toString()}`);
154
- let allowance = await usdc.allowance(owner, mgrAddr).catch(()=>0n);
154
+ let allowance = await usdc.allowance(owner, mgrAddr).catch(() => 0n);
155
155
  if (allowance < totalPool) {
156
156
  const wantPermit = !!opts.permit || process.env.SAGE_USE_PERMIT === '1' || process.env.SAGE_USE_PERMIT === 'true';
157
157
  if (wantPermit) {
158
158
  try {
159
159
  const chain = await provider.getNetwork();
160
- const name = await usdc.name().catch(()=> 'Token');
160
+ const name = await usdc.name().catch(() => 'Token');
161
161
  const nonce = await usdc.nonces(owner);
162
- const deadline = BigInt(Math.floor(Date.now()/1000) + 3600);
162
+ const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600);
163
163
  const domain = { name, version: '1', chainId: Number(chain.chainId), verifyingContract: usdcAddr };
164
- const types = { Permit: [
165
- { name:'owner', type:'address'},
166
- { name:'spender', type:'address'},
167
- { name:'value', type:'uint256'},
168
- { name:'nonce', type:'uint256'},
169
- { name:'deadline', type:'uint256'}
170
- ]};
164
+ const types = {
165
+ Permit: [
166
+ { name: 'owner', type: 'address' },
167
+ { name: 'spender', type: 'address' },
168
+ { name: 'value', type: 'uint256' },
169
+ { name: 'nonce', type: 'uint256' },
170
+ { name: 'deadline', type: 'uint256' }
171
+ ]
172
+ };
171
173
  const value = { owner, spender: mgrAddr, value: totalPool, nonce, deadline };
172
174
  const sig = await signer.signTypedData(domain, types, value);
173
175
  const { v, r, s } = ethers.Signature.from(sig);
174
176
  const txp = await usdc.permit(owner, mgrAddr, totalPool, deadline, v, r, s);
175
177
  { const { waitForReceipt } = require('../utils/tx-wait'); const { ethers } = require('ethers'); const pr = new ethers.JsonRpcProvider(process.env.RPC_URL || process.env.BASE_SEPOLIA_RPC || process.env.BASE_RPC_URL || 'https://base-sepolia.publicnode.com'); await waitForReceipt(pr, txp, Number(process.env.SAGE_TX_WAIT_MS || 60000)); }
176
178
  console.log('✅ Permit submitted');
177
- allowance = await usdc.allowance(owner, mgrAddr).catch(()=>0n);
179
+ allowance = await usdc.allowance(owner, mgrAddr).catch(() => 0n);
178
180
  } catch (e) {
179
181
  console.log('⚠️ Permit failed or unsupported, falling back to approve:', e.message);
180
182
  }
181
183
  }
182
184
  if (allowance < totalPool) {
183
- console.log('💳 Approving USDC to DIRECT manager');
184
- console.log(` token: ${usdcAddr}`);
185
- console.log(` owner: ${owner}`);
186
- console.log(` spender: ${mgrAddr}`);
187
- console.log(` need: ${totalPool.toString()} current: ${allowance.toString()}`);
188
- const txa = await usdc.approve(mgrAddr, totalPool); { const { waitForReceipt } = require('../utils/tx-wait'); const { ethers } = require('ethers'); const pr = new ethers.JsonRpcProvider(process.env.RPC_URL || process.env.BASE_SEPOLIA_RPC || process.env.BASE_RPC_URL || 'https://base-sepolia.publicnode.com'); await waitForReceipt(pr, txa, Number(process.env.SAGE_TX_WAIT_MS || 60000)); }
189
- console.log('✅ Approve confirmed');
185
+ console.log('💳 Approving USDC to DIRECT manager');
186
+ console.log(` token: ${usdcAddr}`);
187
+ console.log(` owner: ${owner}`);
188
+ console.log(` spender: ${mgrAddr}`);
189
+ console.log(` need: ${totalPool.toString()} current: ${allowance.toString()}`);
190
+ const txa = await usdc.approve(mgrAddr, totalPool); { const { waitForReceipt } = require('../utils/tx-wait'); const { ethers } = require('ethers'); const pr = new ethers.JsonRpcProvider(process.env.RPC_URL || process.env.BASE_SEPOLIA_RPC || process.env.BASE_RPC_URL || 'https://base-sepolia.publicnode.com'); await waitForReceipt(pr, txa, Number(process.env.SAGE_TX_WAIT_MS || 60000)); }
191
+ console.log('✅ Approve confirmed');
190
192
  }
191
193
  } else {
192
194
  console.log(`✅ USDC allowance sufficient: ${allowance.toString()} (need ${totalPool.toString()})`);
193
195
  }
194
196
 
195
197
  const iface = new ethers.Interface([
196
- 'function createBoost(address,uint256,uint256,uint256,uint8,address,uint96,uint8,uint8,uint256,uint256)'
198
+ 'function createBoost(tuple(address governor, uint256 proposalId, uint256 perVoter, uint256 maxVoters, uint8 kind, address policy, uint96 minVotes, uint8 payoutMode, uint8 support, uint256 startAt, uint256 expiresAt) params)'
197
199
  ]);
198
200
 
199
- const data = iface.encodeFunctionData('createBoost', [
201
+ const params = [
200
202
  opts.governor,
201
203
  BigInt(opts.proposalId),
202
204
  perVoter,
@@ -208,10 +210,11 @@ function register(program) {
208
210
  support,
209
211
  startAt,
210
212
  expiresAt,
211
- ]);
213
+ ];
214
+ const data = iface.encodeFunctionData('createBoost', [params]);
212
215
  const tx = await signer.sendTransaction({ to: mgrAddr, data });
213
216
  console.log('tx:', tx.hash);
214
- } catch (e) { console.error('❌ create failed:', e.message); process.exit(1);}
217
+ } catch (e) { console.error('❌ create failed:', e.message); process.exit(1); }
215
218
  });
216
219
 
217
220
  // New helper: set root on MerkleEligibilityPolicy adapter
@@ -231,7 +234,7 @@ function register(program) {
231
234
 
232
235
  if (opts.policy && opts.policy !== ethers.ZeroAddress) {
233
236
  const iface = new ethers.Interface(['function setRoot(uint256,bytes32)']);
234
- const tx = await signer.sendTransaction({ to: opts.policy, data: iface.encodeFunctionData('setRoot',[id, opts.root]) });
237
+ const tx = await signer.sendTransaction({ to: opts.policy, data: iface.encodeFunctionData('setRoot', [id, opts.root]) });
235
238
  console.log('tx:', tx.hash);
236
239
  return;
237
240
  }
@@ -242,9 +245,9 @@ function register(program) {
242
245
  }
243
246
  // Call manager.setMerkleRoot
244
247
  const ifMgr = new ethers.Interface(['function setMerkleRoot(uint256,bytes32)']);
245
- const tx = await signer.sendTransaction({ to: mgrAddr, data: ifMgr.encodeFunctionData('setMerkleRoot',[id, opts.root]) });
248
+ const tx = await signer.sendTransaction({ to: mgrAddr, data: ifMgr.encodeFunctionData('setMerkleRoot', [id, opts.root]) });
246
249
  console.log('tx:', tx.hash);
247
- } catch (e) { console.error('❌ set-root failed:', e.message); process.exit(1);}
250
+ } catch (e) { console.error('❌ set-root failed:', e.message); process.exit(1); }
248
251
  });
249
252
 
250
253
  // Deprecated: legacy setter on old manager; keep for guidance
@@ -263,7 +266,7 @@ function register(program) {
263
266
  const id = BigInt(opts.proposalId);
264
267
  if (opts.policy && opts.policy !== ethers.ZeroAddress) {
265
268
  const iface = new ethers.Interface(['function setRoot(uint256,bytes32)']);
266
- const tx = await signer.sendTransaction({ to: opts.policy, data: iface.encodeFunctionData('setRoot',[id, opts.root]) });
269
+ const tx = await signer.sendTransaction({ to: opts.policy, data: iface.encodeFunctionData('setRoot', [id, opts.root]) });
267
270
  console.log('tx:', tx.hash);
268
271
  console.log('ℹ️ Note: set-merkle-root is deprecated; use boost set-root going forward.');
269
272
  return;
@@ -271,14 +274,14 @@ function register(program) {
271
274
  const mgr = opts.manager || process.env.BOOST_MANAGER_ADDRESS;
272
275
  if (mgr && mgr !== ethers.ZeroAddress) {
273
276
  const ifMgr = new ethers.Interface(['function setMerkleRoot(uint256,bytes32)']);
274
- const tx = await signer.sendTransaction({ to: mgr, data: ifMgr.encodeFunctionData('setMerkleRoot',[id, opts.root]) });
277
+ const tx = await signer.sendTransaction({ to: mgr, data: ifMgr.encodeFunctionData('setMerkleRoot', [id, opts.root]) });
275
278
  console.log('tx:', tx.hash);
276
279
  console.log('ℹ️ Note: set-merkle-root is deprecated; use boost set-root going forward.');
277
280
  return;
278
281
  }
279
282
  console.error('❌ set-merkle-root is deprecated. Use: boost set-root --policy <address> --proposal-id <id> --root <hex32>');
280
283
  process.exit(1);
281
- } catch (e) { console.error('❌ set-merkle-root failed:', e.message); process.exit(1);}
284
+ } catch (e) { console.error('❌ set-merkle-root failed:', e.message); process.exit(1); }
282
285
  });
283
286
 
284
287
  boost
@@ -294,27 +297,30 @@ function register(program) {
294
297
  const mgrAddr = opts.manager || process.env.BOOST_MANAGER_ADDRESS;
295
298
  const id = BigInt(opts.proposalId);
296
299
  const ifMerkle = new ethers.Interface(['function getBoost(uint256) view returns (uint256,uint256,bytes32,bool,bool,address)']);
297
- const ifDirect = new ethers.Interface(['function getBoost(uint256) view returns (address,address,uint256,uint256,uint256,uint256,uint96,uint8,address,bool,bool)']);
300
+ 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)']);
298
301
  // Try DIRECT first
299
302
  try {
300
- const retD = await provider.call({ to: mgrAddr, data: ifDirect.encodeFunctionData('getBoost',[id]) });
301
- const [creator, governor, snapshot, perVoter, maxVoters, votersPaid, minVotes, kind, policy, active, paused] = ifDirect.decodeFunctionResult('getBoost', retD);
303
+ const retD = await provider.call({ to: mgrAddr, data: ifDirect.encodeFunctionData('getBoost', [id]) });
304
+ const [b] = ifDirect.decodeFunctionResult('getBoost', retD);
302
305
  console.log(JSON.stringify({
303
306
  managerType: 'direct', proposalId: opts.proposalId,
304
- creator, governor, snapshot: snapshot.toString(), perVoter: perVoter.toString(),
305
- maxVoters: maxVoters.toString(), votersPaid: votersPaid.toString(), minVotes: Number(minVotes),
306
- kind: Number(kind), policy, active, paused
307
+ creator: b.creator, governor: b.governor, snapshot: b.snapshot.toString(), perVoter: b.perVoter.toString(),
308
+ maxVoters: b.maxVoters.toString(), votersPaid: b.votersPaid.toString(), minVotes: Number(b.minVotes),
309
+ kind: Number(b.kind), policy: b.policy, active: b.active, paused: b.paused,
310
+ payoutMode: Number(b.payoutMode), support: Number(b.support),
311
+ startAt: b.startAt.toString(), expiresAt: b.expiresAt.toString(),
312
+ totalPool: b.totalPool.toString(), totalPaid: b.totalPaid.toString()
307
313
  }, null, 2));
308
314
  } catch (_) {
309
315
  // Fallback to MERKLE
310
- const retM = await provider.call({ to: mgrAddr, data: ifMerkle.encodeFunctionData('getBoost',[id]) });
311
- const [totalPool,totalClaimed,merkleRoot,active,finalized,creator] = ifMerkle.decodeFunctionResult('getBoost', retM);
316
+ const retM = await provider.call({ to: mgrAddr, data: ifMerkle.encodeFunctionData('getBoost', [id]) });
317
+ const [totalPool, totalClaimed, merkleRoot, active, finalized, creator] = ifMerkle.decodeFunctionResult('getBoost', retM);
312
318
  console.log(JSON.stringify({
313
319
  managerType: 'merkle', proposalId: opts.proposalId,
314
320
  totalPool: totalPool.toString(), totalClaimed: totalClaimed.toString(), merkleRoot, active, finalized, creator
315
321
  }, null, 2));
316
322
  }
317
- } catch (e) { console.error('❌ status failed:', e.message); process.exit(1);}
323
+ } catch (e) { console.error('❌ status failed:', e.message); process.exit(1); }
318
324
  });
319
325
 
320
326
  boost
@@ -333,18 +339,18 @@ function register(program) {
333
339
  // Manager-type preflight + helpful hint if mismatched
334
340
  const detectManagerType = async (address) => {
335
341
  const directIface = new ethers.Interface([
336
- 'function getBoost(uint256) view returns (address,address,uint256,uint256,uint256,uint256,uint96,uint8,address,bool,bool)'
342
+ '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)'
337
343
  ]);
338
344
  const merkleIface = new ethers.Interface([
339
345
  'function getBoost(uint256) view returns (uint256,uint256,bytes32,bool,bool,address)'
340
346
  ]);
341
347
  try {
342
- const retD = await provider.call({ to: address, data: directIface.encodeFunctionData('getBoost',[0n]) });
348
+ const retD = await provider.call({ to: address, data: directIface.encodeFunctionData('getBoost', [0n]) });
343
349
  directIface.decodeFunctionResult('getBoost', retD);
344
350
  return 'direct';
345
351
  } catch (_) {
346
352
  try {
347
- const retM = await provider.call({ to: address, data: merkleIface.encodeFunctionData('getBoost',[0n]) });
353
+ const retM = await provider.call({ to: address, data: merkleIface.encodeFunctionData('getBoost', [0n]) });
348
354
  merkleIface.decodeFunctionResult('getBoost', retM);
349
355
  return 'merkle';
350
356
  } catch (_) { return 'unknown'; }
@@ -356,16 +362,16 @@ function register(program) {
356
362
  let looksEmpty = false;
357
363
  if (mgrType === 'direct') {
358
364
  try {
359
- const ifD = new ethers.Interface(['function getBoost(uint256) view returns (address,address,uint256,uint256,uint256,uint256,uint96,uint8,address,bool,bool)']);
360
- const ret = await provider.call({ to: mgrAddr, data: ifD.encodeFunctionData('getBoost',[id]) });
361
- const [creator,, , , , , , , , active] = ifD.decodeFunctionResult('getBoost', ret);
362
- looksEmpty = (creator === ethers.ZeroAddress) || !active;
365
+ const ifD = 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)']);
366
+ const ret = await provider.call({ to: mgrAddr, data: ifD.encodeFunctionData('getBoost', [id]) });
367
+ const [b] = ifD.decodeFunctionResult('getBoost', ret);
368
+ looksEmpty = (b.creator === ethers.ZeroAddress) || !b.active;
363
369
  } catch (_) { /* ignore */ }
364
370
  } else if (mgrType === 'merkle') {
365
371
  try {
366
372
  const ifM = new ethers.Interface(['function getBoost(uint256) view returns (uint256,uint256,bytes32,bool,bool,address)']);
367
- const ret = await provider.call({ to: mgrAddr, data: ifM.encodeFunctionData('getBoost',[id]) });
368
- const [totalPool,, , active] = ifM.decodeFunctionResult('getBoost', ret);
373
+ const ret = await provider.call({ to: mgrAddr, data: ifM.encodeFunctionData('getBoost', [id]) });
374
+ const [totalPool, , , active] = ifM.decodeFunctionResult('getBoost', ret);
369
375
  looksEmpty = (totalPool === 0n) || !active;
370
376
  } catch (_) { /* ignore */ }
371
377
  }
@@ -377,9 +383,9 @@ function register(program) {
377
383
  }
378
384
 
379
385
  const iface = new ethers.Interface(['function finalize(uint256)']);
380
- const tx = await signer.sendTransaction({ to: mgrAddr, data: iface.encodeFunctionData('finalize',[id]) });
386
+ const tx = await signer.sendTransaction({ to: mgrAddr, data: iface.encodeFunctionData('finalize', [id]) });
381
387
  console.log('tx:', tx.hash);
382
- } catch (e) { console.error('❌ finalize failed:', e.message); process.exit(1);}
388
+ } catch (e) { console.error('❌ finalize failed:', e.message); process.exit(1); }
383
389
  });
384
390
 
385
391
  boost
@@ -402,9 +408,9 @@ function register(program) {
402
408
  }
403
409
  const proof = JSON.parse(proofStr);
404
410
  const iface = new ethers.Interface(['function claim(uint256,address,uint256,bytes32[])']);
405
- const tx = await signer.sendTransaction({ to: mgrAddr, data: iface.encodeFunctionData('claim',[BigInt(opts.proposalId), await signer.getAddress(), BigInt(opts.amount), proof]) });
411
+ const tx = await signer.sendTransaction({ to: mgrAddr, data: iface.encodeFunctionData('claim', [BigInt(opts.proposalId), await signer.getAddress(), BigInt(opts.amount), proof]) });
406
412
  console.log('tx:', tx.hash);
407
- } catch (e) { console.error('❌ claim failed:', e.message); process.exit(1);}
413
+ } catch (e) { console.error('❌ claim failed:', e.message); process.exit(1); }
408
414
  });
409
415
 
410
416
  // New: fund a Merkle boost pool and create boost entry
@@ -429,7 +435,7 @@ function register(program) {
429
435
  // Sanity: detect manager is Merkle flavor
430
436
  try {
431
437
  const ifMerkle = new ethers.Interface(['function getBoost(uint256) view returns (uint256,uint256,bytes32,bool,bool,address)']);
432
- await provider.call({ to: mgrAddr, data: ifMerkle.encodeFunctionData('getBoost',[0n]) });
438
+ await provider.call({ to: mgrAddr, data: ifMerkle.encodeFunctionData('getBoost', [0n]) });
433
439
  } catch (_) {
434
440
  console.warn('⚠️ Manager at', mgrAddr, 'does not look like Merkle manager; proceeding anyway');
435
441
  }
@@ -451,17 +457,19 @@ function register(program) {
451
457
  if (wantPermit) {
452
458
  try {
453
459
  const chain = await provider.getNetwork();
454
- const name = await usdc.name().catch(()=> 'Token');
460
+ const name = await usdc.name().catch(() => 'Token');
455
461
  const nonce = await usdc.nonces(signerAddr);
456
- const deadline = BigInt(Math.floor(Date.now()/1000) + 3600);
462
+ const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600);
457
463
  const domain = { name, version: '1', chainId: Number(chain.chainId), verifyingContract: usdcAddr };
458
- const types = { Permit: [
459
- { name:'owner', type:'address'},
460
- { name:'spender', type:'address'},
461
- { name:'value', type:'uint256'},
462
- { name:'nonce', type:'uint256'},
463
- { name:'deadline', type:'uint256'}
464
- ]};
464
+ const types = {
465
+ Permit: [
466
+ { name: 'owner', type: 'address' },
467
+ { name: 'spender', type: 'address' },
468
+ { name: 'value', type: 'uint256' },
469
+ { name: 'nonce', type: 'uint256' },
470
+ { name: 'deadline', type: 'uint256' }
471
+ ]
472
+ };
465
473
  const value = { owner: signerAddr, spender: mgrAddr, value: amount, nonce, deadline };
466
474
  const sig = await signer.signTypedData(domain, types, value);
467
475
  const { v, r, s } = ethers.Signature.from(sig);
@@ -480,9 +488,9 @@ function register(program) {
480
488
  }
481
489
  }
482
490
  const ifMgr = new ethers.Interface(['function createBoost(uint256,uint256)']);
483
- const tx = await signer.sendTransaction({ to: mgrAddr, data: ifMgr.encodeFunctionData('createBoost',[BigInt(opts.proposalId), amount]) });
491
+ const tx = await signer.sendTransaction({ to: mgrAddr, data: ifMgr.encodeFunctionData('createBoost', [BigInt(opts.proposalId), amount]) });
484
492
  console.log('tx:', tx.hash);
485
- } catch (e) { console.error('❌ fund failed:', e.message); process.exit(1);}
493
+ } catch (e) { console.error('❌ fund failed:', e.message); process.exit(1); }
486
494
  });
487
495
 
488
496
  program.addCommand(boost);
@@ -568,6 +568,8 @@ function register(program) {
568
568
  setIf('BOUNTY_ANNOUNCEMENTS_ADDRESS', data.BountyAnnouncements || data.Bounty);
569
569
  setIf('SIMPLE_BOUNTY_SYSTEM_ADDRESS', data.SimpleBountySystem || data.BountySystem);
570
570
  setIf('SIMPLE_CONTRIBUTOR_SYSTEM_ADDRESS', data.SimpleContributorSystem || data.ContributorSystem);
571
+ setIf('MOCK_USDC_ADDRESS', data.MockUSDC || data.MockUsdc || data.USDC);
572
+ setIf('BOOST_MANAGER_DIRECT_ADDRESS', data.GovernanceBoostManagerDirect || data.BoostManagerDirect);
571
573
  // Optionally include test/deployer addresses under separate keys
572
574
  // but avoid setting SUBDAO/GOV/TIMELOCK unless user decides explicitly
573
575
 
@@ -552,6 +552,8 @@ function register(program) {
552
552
  .option('--title <t>', 'Proposal title', 'Batch Prompt Update')
553
553
  .option('--desc <d>', 'Proposal description', '')
554
554
  .option('--library-id <id>', 'Logical library id', 'main')
555
+ .option('--dao <address>', 'Target DAO address')
556
+ .option('--subdao <address>', '[deprecated] Alias for --dao')
555
557
  .option('--strict', 'Strict validation (recompute CIDs, require remote availability)', false)
556
558
  .option('--preview', 'Show payload and pointer (previous→new) before submit', true)
557
559
  .option('--pin', 'Pin manifest CID before proposing (Pinata)', false)
@@ -637,7 +639,10 @@ function register(program) {
637
639
  prompts.length,
638
640
  opts.title,
639
641
  opts.desc || `Manifest CID: ${manifestCID}`,
640
- { libraryId: opts.libraryId || 'main' }
642
+ {
643
+ libraryId: opts.libraryId || 'main',
644
+ ctx: { subdaoOpt: opts.dao || opts.subdao }
645
+ }
641
646
  );
642
647
  console.log(`🆔 Proposal ID: ${pid}`);
643
648
  // Update snapshot now that the proposal has been created
@@ -508,9 +508,10 @@ function register(program) {
508
508
  console.log('Tx:', gtx.hash); await gtx.wait();
509
509
  } else {
510
510
  console.log(colors.red('❌ Missing PROPOSER_ROLE on Timelock for current signer.'));
511
- console.log(' Hint: as Timelock admin run:');
512
- console.log(` sage timelock grant-role --timelock ${tlAddr} --role proposer --account ${signerAddr}`);
513
- console.log(' Or rerun with --auto if your signer is an admin.');
511
+ console.log(' Hint: use one of:');
512
+ console.log(' - sage timelock fix-roles --subdao <subdao-address> (admin only, interactive)');
513
+ console.log(' - sage roles plan --subdao <subdao-address> && sage roles apply --subdao <subdao-address>');
514
+ console.log(' Or rerun with --auto if your signer is an admin (schedule will grant proposer automatically).');
514
515
  return process.exit(1);
515
516
  }
516
517
  }
@@ -525,13 +526,11 @@ function register(program) {
525
526
  const msg = String(e?.message || e);
526
527
  // Common revert: AccessControlUnauthorizedAccount on schedule
527
528
  if (msg.includes('AccessControl') || msg.includes('0xe2517d3f')) {
528
- try {
529
- const signerAddr = await (new WalletManager().connect().then(w=>w.getSigner().getAddress()).catch(()=>null));
530
- console.log(colors.red(' Schedule failed: missing PROPOSER_ROLE on Timelock for current signer.'));
531
- console.log(' Grant role (requires ADMIN):');
532
- console.log(` sage timelock grant-role --timelock ${tlAddr} --role proposer --account ${signerAddr || '<your-address>'}`);
533
- console.log(' Or rerun with --auto if your signer is an admin (schedule will grant proposer automatically).');
534
- } catch (_) {}
529
+ console.log(colors.red('❌ Schedule failed: missing PROPOSER_ROLE on Timelock for current signer.'));
530
+ console.log(' Grant role via governance or admin helpers, for example:');
531
+ console.log(' - sage timelock fix-roles --subdao <subdao-address>');
532
+ console.log(' - sage roles plan --subdao <subdao-address> && sage roles apply --subdao <subdao-address>');
533
+ console.log(' Or rerun with --auto if your signer is an admin (schedule will grant proposer automatically).');
535
534
  }
536
535
  console.error(colors.red('❌ Schedule failed:'), msg);
537
536
  process.exit(1);
@@ -116,12 +116,6 @@ const ERROR_GUIDANCE = {
116
116
  (ctx) => (ctx?.subdao ? `sage dao doctor --subdao ${ctx.subdao}` : 'sage dao doctor --subdao <subdao-address>'),
117
117
  () => 'sage roles matrix --subdao <subdao-address>'
118
118
  ],
119
- 'timelock:grant-role': [
120
- (ctx) =>
121
- ctx?.timelock
122
- ? `sage timelock doctor --timelock ${ctx.timelock}`
123
- : 'sage timelock doctor --subdao <subdao-address>'
124
- ],
125
119
  'timelock:execute': [
126
120
  (ctx) =>
127
121
  ctx?.timelock
@@ -69,10 +69,10 @@ echo "GRAPH_STUDIO_PROJECT_ID: ${GRAPH_STUDIO_PROJECT_ID:-unset}"
69
69
 
70
70
  export BOOST_MANAGER_ADDRESS=0xCaF1439bAbAb97C081Eac8aAb7749fE25B0515e7
71
71
 
72
- export BOOST_MANAGER_DIRECT_ADDRESS=0x45F3538929e665eA3a8E4488C48B31D8DC1f9B88
72
+ export BOOST_MANAGER_DIRECT_ADDRESS=0x7C9Ba615322FB9FdBC383040A77E6d0F80c7e349
73
73
 
74
- export BOOST_VIEW_GLUE_ADDRESS=0xF7877ece1AAde6D7a06A9E9b44322C18e8fC18F4
74
+ export BOOST_VIEW_GLUE_ADDRESS=0x56747BfD90F3017Ef69A35DEe3e9560aF9778dD0
75
75
 
76
- export BOOST_LOGGER_GLUE_ADDRESS=0x83385F64c0a4F87694AD0Bb0c4B22039790e2DB3
76
+ export BOOST_LOGGER_GLUE_ADDRESS=0x2371093e9fA09c5e2Da4556b3487Db3f7863D438
77
77
 
78
- export BOOST_CREATION_GLUE_ADDRESS=0x26039021063a1aF1a27De0f7490c21A1bDA8F63A
78
+ export BOOST_CREATION_GLUE_ADDRESS=0x401e8BFbe2eEfC4b178Cc2Ff5FeeA84FaD7f7A96
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sage-protocol/cli",
3
- "version": "0.3.6",
3
+ "version": "0.3.7",
4
4
  "description": "Sage Protocol CLI for managing AI prompt libraries",
5
5
  "bin": {
6
6
  "sage": "./bin/sage.js"