@sage-protocol/cli 0.3.6 → 0.3.9

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);