@sage-protocol/cli 0.3.7 ā 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.
- package/dist/cli/commands/bounty.js +123 -85
- package/dist/cli/commands/doctor.js +18 -7
- package/dist/cli/commands/personal.js +64 -78
- package/dist/cli/commands/premium-pre.js +23 -875
- package/dist/cli/commands/premium.js +237 -667
- package/dist/cli/commands/sxxx.js +103 -0
- package/dist/cli/commands/timelock.js +17 -186
- package/dist/cli/index.js +1 -5
- package/dist/cli/utils/aliases.js +3 -1
- package/dist/cli/utils/error-handler.js +9 -9
- package/dist/cli/utils/lit.js +103 -0
- package/package.json +1 -1
|
@@ -350,6 +350,109 @@ function register(program) {
|
|
|
350
350
|
})
|
|
351
351
|
)
|
|
352
352
|
// authorize-burner related commands removed; now automated at deployment/creation
|
|
353
|
+
.addCommand(
|
|
354
|
+
new Command('faucet')
|
|
355
|
+
.description('Request testnet SXXX tokens from the faucet (Base Sepolia only)')
|
|
356
|
+
.option('--check', 'Check faucet status without requesting tokens')
|
|
357
|
+
.action(async (opts) => {
|
|
358
|
+
try {
|
|
359
|
+
const faucetAddress = process.env.SXXX_FAUCET_ADDRESS;
|
|
360
|
+
if (!faucetAddress) {
|
|
361
|
+
throw new Error('SXXX_FAUCET_ADDRESS not set. Faucet may not be deployed on this network.');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const rpcUrl = process.env.RPC_URL || 'https://base-sepolia.publicnode.com';
|
|
365
|
+
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
|
366
|
+
|
|
367
|
+
const faucetAbi = [
|
|
368
|
+
'function drip() external',
|
|
369
|
+
'function amountPerDrip() view returns (uint256)',
|
|
370
|
+
'function cooldown() view returns (uint256)',
|
|
371
|
+
'function lastDrip(address) view returns (uint256)',
|
|
372
|
+
'function sxxx() view returns (address)'
|
|
373
|
+
];
|
|
374
|
+
const faucet = new ethers.Contract(faucetAddress, faucetAbi, provider);
|
|
375
|
+
|
|
376
|
+
// Get faucet info
|
|
377
|
+
const [amountPerDrip, cooldown, sxxxAddr] = await Promise.all([
|
|
378
|
+
faucet.amountPerDrip(),
|
|
379
|
+
faucet.cooldown(),
|
|
380
|
+
faucet.sxxx()
|
|
381
|
+
]);
|
|
382
|
+
|
|
383
|
+
const dripAmount = ethers.formatEther(amountPerDrip);
|
|
384
|
+
const cooldownHours = Number(cooldown) / 3600;
|
|
385
|
+
|
|
386
|
+
console.log('š° SXXX Faucet');
|
|
387
|
+
console.log(` Faucet: ${faucetAddress}`);
|
|
388
|
+
console.log(` Token: ${sxxxAddr}`);
|
|
389
|
+
console.log(` Amount: ${dripAmount} SXXX per request`);
|
|
390
|
+
console.log(` Cooldown: ${cooldownHours} hours`);
|
|
391
|
+
console.log('');
|
|
392
|
+
|
|
393
|
+
// Connect wallet to check eligibility and request
|
|
394
|
+
const WalletManager = require('../wallet-manager');
|
|
395
|
+
const wm = new WalletManager();
|
|
396
|
+
await wm.connect();
|
|
397
|
+
const account = wm.getAccount();
|
|
398
|
+
|
|
399
|
+
if (!account) {
|
|
400
|
+
throw new Error('No wallet connected. Run `sage wallet connect` first.');
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Check last drip time
|
|
404
|
+
const lastDripTime = await faucet.lastDrip(account);
|
|
405
|
+
const now = Math.floor(Date.now() / 1000);
|
|
406
|
+
const nextEligible = Number(lastDripTime) + Number(cooldown);
|
|
407
|
+
const canDrip = now >= nextEligible;
|
|
408
|
+
|
|
409
|
+
if (lastDripTime > 0) {
|
|
410
|
+
const lastDripDate = new Date(Number(lastDripTime) * 1000).toLocaleString();
|
|
411
|
+
console.log(` Last request: ${lastDripDate}`);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (!canDrip) {
|
|
415
|
+
const waitSeconds = nextEligible - now;
|
|
416
|
+
const waitHours = Math.ceil(waitSeconds / 3600);
|
|
417
|
+
console.log(` ā³ Next eligible: in ~${waitHours} hour(s)`);
|
|
418
|
+
if (!opts.check) {
|
|
419
|
+
console.log('\nā You must wait before requesting more tokens.');
|
|
420
|
+
}
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
console.log(' ā
Eligible to request tokens');
|
|
425
|
+
|
|
426
|
+
if (opts.check) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Request tokens
|
|
431
|
+
console.log('\nš Requesting tokens...');
|
|
432
|
+
const signer = wm.getSigner();
|
|
433
|
+
const faucetWithSigner = faucet.connect(signer);
|
|
434
|
+
|
|
435
|
+
const tx = await faucetWithSigner.drip();
|
|
436
|
+
console.log(` TX: ${tx.hash}`);
|
|
437
|
+
await tx.wait();
|
|
438
|
+
|
|
439
|
+
console.log(`\nā
Received ${dripAmount} SXXX!`);
|
|
440
|
+
console.log('\nNext steps:');
|
|
441
|
+
console.log(' sage sxxx balance # Check your balance');
|
|
442
|
+
console.log(' sage sxxx delegate-self # Enable voting power');
|
|
443
|
+
console.log(' sage dao stake <dao> <amt> # Stake to join a DAO');
|
|
444
|
+
} catch (error) {
|
|
445
|
+
if (error.message?.includes('cooldown')) {
|
|
446
|
+
console.error('ā Cooldown period not elapsed. Please wait before requesting again.');
|
|
447
|
+
} else if (error.message?.includes('faucet off')) {
|
|
448
|
+
console.error('ā Faucet is currently disabled.');
|
|
449
|
+
} else {
|
|
450
|
+
console.error('ā Faucet request failed:', error.message);
|
|
451
|
+
}
|
|
452
|
+
process.exit(1);
|
|
453
|
+
}
|
|
454
|
+
})
|
|
455
|
+
)
|
|
353
456
|
.addCommand(
|
|
354
457
|
new Command('transfer-ownership')
|
|
355
458
|
.description('Owner-only: transfer SXXX ownership (uses Foundry --account deployment-wallet)')
|
|
@@ -509,8 +509,9 @@ function register(program) {
|
|
|
509
509
|
} else {
|
|
510
510
|
console.log(colors.red('ā Missing PROPOSER_ROLE on Timelock for current signer.'));
|
|
511
511
|
console.log(' Hint: use one of:');
|
|
512
|
-
console.log(' - sage timelock
|
|
513
|
-
console.log(' - sage roles
|
|
512
|
+
console.log(' - sage timelock doctor --dao <dao-address> # inspect roles and minDelay');
|
|
513
|
+
console.log(' - sage timelock fix-roles --dao <dao-address> # admin-only safe wiring helper');
|
|
514
|
+
console.log(' - sage governance enable-proposals --dao <dao-address> # grant PROPOSER_ROLE via governance');
|
|
514
515
|
console.log(' Or rerun with --auto if your signer is an admin (schedule will grant proposer automatically).');
|
|
515
516
|
return process.exit(1);
|
|
516
517
|
}
|
|
@@ -528,8 +529,9 @@ function register(program) {
|
|
|
528
529
|
if (msg.includes('AccessControl') || msg.includes('0xe2517d3f')) {
|
|
529
530
|
console.log(colors.red('ā Schedule failed: missing PROPOSER_ROLE on Timelock for current signer.'));
|
|
530
531
|
console.log(' Grant role via governance or admin helpers, for example:');
|
|
531
|
-
console.log(' - sage timelock
|
|
532
|
-
console.log(' - sage
|
|
532
|
+
console.log(' - sage timelock doctor --dao <dao-address>');
|
|
533
|
+
console.log(' - sage timelock fix-roles --dao <dao-address>');
|
|
534
|
+
console.log(' - sage governance enable-proposals --dao <dao-address>');
|
|
533
535
|
console.log(' Or rerun with --auto if your signer is an admin (schedule will grant proposer automatically).');
|
|
534
536
|
}
|
|
535
537
|
console.error(colors.red('ā Schedule failed:'), msg);
|
|
@@ -758,194 +760,23 @@ function register(program) {
|
|
|
758
760
|
}
|
|
759
761
|
});
|
|
760
762
|
|
|
761
|
-
//
|
|
763
|
+
// SubDAO premium roles - replaced by personal premium model
|
|
762
764
|
cmd
|
|
763
765
|
.command('premium-roles')
|
|
764
|
-
.description('
|
|
765
|
-
.
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
.option('--bootstrap', 'If signer is current admin on PremiumPrompts, directly grant Timelock admin/manager (one-time bootstrap)', false)
|
|
770
|
-
.action(async (opts) => {
|
|
771
|
-
try {
|
|
772
|
-
const WM = require('../wallet-manager');
|
|
773
|
-
const { ethers } = require('ethers');
|
|
774
|
-
const wm = new WM(); await wm.connect();
|
|
775
|
-
const provider = wm.getProvider();
|
|
776
|
-
const signer = wm.getSigner();
|
|
777
|
-
const tlAddr = await resolveTimelockAddress(opts, provider);
|
|
778
|
-
const prompts = ethers.getAddress(opts.prompts);
|
|
779
|
-
console.log(colors.cyan(`šÆ Timelock=${tlAddr} PremiumPrompts=${prompts}`));
|
|
780
|
-
|
|
781
|
-
// Read roles
|
|
782
|
-
const pp = new ethers.Contract(prompts, [
|
|
783
|
-
'function DEFAULT_ADMIN_ROLE() view returns (bytes32)',
|
|
784
|
-
'function MANAGER_ROLE() view returns (bytes32)',
|
|
785
|
-
'function hasRole(bytes32,address) view returns (bool)',
|
|
786
|
-
'function grantRole(bytes32,address)'
|
|
787
|
-
], provider);
|
|
788
|
-
const [ADMIN, MANAGER] = await Promise.all([
|
|
789
|
-
pp.DEFAULT_ADMIN_ROLE(), pp.MANAGER_ROLE()
|
|
790
|
-
]);
|
|
791
|
-
const [hasAdmin, hasManager] = await Promise.all([
|
|
792
|
-
pp.hasRole(ADMIN, tlAddr), pp.hasRole(MANAGER, tlAddr)
|
|
793
|
-
]);
|
|
794
|
-
console.log(' DEFAULT_ADMIN_ROLE ā Timelock:', hasAdmin ? 'ā
' : 'ā');
|
|
795
|
-
console.log(' MANAGER_ROLE ā Timelock:', hasManager ? 'ā
' : 'ā');
|
|
796
|
-
|
|
797
|
-
// Optional bootstrap path: if signer has admin on PremiumPrompts, perform direct grants now
|
|
798
|
-
if (opts.bootstrap) {
|
|
799
|
-
const signerAddr = await signer.getAddress();
|
|
800
|
-
const ppAs = new ethers.Contract(prompts, [
|
|
801
|
-
'function DEFAULT_ADMIN_ROLE() view returns (bytes32)',
|
|
802
|
-
'function MANAGER_ROLE() view returns (bytes32)',
|
|
803
|
-
'function hasRole(bytes32,address) view returns (bool)',
|
|
804
|
-
'function grantRole(bytes32,address)'
|
|
805
|
-
], signer);
|
|
806
|
-
const signerIsAdmin = await ppAs.hasRole(ADMIN, signerAddr).catch(()=>false);
|
|
807
|
-
if (!signerIsAdmin) {
|
|
808
|
-
console.log(colors.red('ā Bootstrap requested but current signer is not DEFAULT_ADMIN on PremiumPrompts.'));
|
|
809
|
-
} else {
|
|
810
|
-
if (!hasAdmin) {
|
|
811
|
-
console.log(colors.yellow('āļø Granting DEFAULT_ADMIN_ROLE to Timelock directly (bootstrap)...'));
|
|
812
|
-
const tx1 = await ppAs.grantRole(ADMIN, tlAddr); console.log(' tx:', tx1.hash); await tx1.wait();
|
|
813
|
-
console.log(colors.green(' ā
Granted DEFAULT_ADMIN_ROLE'));
|
|
814
|
-
}
|
|
815
|
-
if (!hasManager) {
|
|
816
|
-
console.log(colors.yellow('āļø Granting MANAGER_ROLE to Timelock directly (bootstrap)...'));
|
|
817
|
-
const tx2 = await ppAs.grantRole(MANAGER, tlAddr); console.log(' tx:', tx2.hash); await tx2.wait();
|
|
818
|
-
console.log(colors.green(' ā
Granted MANAGER_ROLE'));
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
if (!opts.fix) return;
|
|
824
|
-
|
|
825
|
-
// Schedule grantRole via Timelock for any missing roles
|
|
826
|
-
const tl = new ethers.Contract(tlAddr, timelockAbi(), signer);
|
|
827
|
-
const iface = new ethers.Interface(['function grantRole(bytes32,address)']);
|
|
828
|
-
const predecessor = ethers.ZeroHash;
|
|
829
|
-
const saltBase = ethers.hexlify(ethers.randomBytes(16));
|
|
830
|
-
const delay = await tl.getMinDelay().catch(()=>0n);
|
|
831
|
-
const ops = [];
|
|
832
|
-
if (!hasAdmin) ops.push({ role: ADMIN, label: 'DEFAULT_ADMIN_ROLE' });
|
|
833
|
-
if (!hasManager) ops.push({ role: MANAGER, label: 'MANAGER_ROLE' });
|
|
834
|
-
if (!ops.length) { console.log(colors.green('ā
No fixes needed.')); return; }
|
|
835
|
-
for (let i=0;i<ops.length;i++) {
|
|
836
|
-
const { role, label } = ops[i];
|
|
837
|
-
const data = iface.encodeFunctionData('grantRole', [role, tlAddr]);
|
|
838
|
-
const salt = ethers.keccak256(ethers.toUtf8Bytes(`${saltBase}:${label}`));
|
|
839
|
-
console.log(colors.yellow(`ā³ Scheduling grantRole(${label}, Timelock)`));
|
|
840
|
-
const stx = await tl.schedule(prompts, 0, data, predecessor, salt, delay);
|
|
841
|
-
console.log(' ā³ tx:', stx.hash); await stx.wait(); console.log(colors.green(' ā
scheduled'));
|
|
842
|
-
if (delay === 0n) {
|
|
843
|
-
const etx = await tl.execute(prompts, 0, data, predecessor, salt);
|
|
844
|
-
console.log(' ā” execute tx:', etx.hash); await etx.wait(); console.log(colors.green(' ā
executed'));
|
|
845
|
-
} else {
|
|
846
|
-
console.log(colors.gray(' Next: execute after delay:'));
|
|
847
|
-
console.log(` cast send ${tlAddr} \"execute(address,uint256,bytes,bytes32,bytes32)\" ${prompts} 0 ${data} ${predecessor} ${salt} --rpc-url ${process.env.RPC_URL || 'https://base-sepolia.publicnode.com'}`);
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
} catch (e) {
|
|
851
|
-
console.error(colors.red('ā premium-roles failed:'), e.message);
|
|
852
|
-
process.exit(1);
|
|
853
|
-
}
|
|
766
|
+
.description('Use "sage personal" for premium prompts')
|
|
767
|
+
.action(() => {
|
|
768
|
+
console.log('SubDAO premium has been replaced by personal premium.');
|
|
769
|
+
console.log('Use: sage personal sell/buy/access');
|
|
770
|
+
console.log('See: docs/specs/premium-endorsement-model.md');
|
|
854
771
|
});
|
|
855
772
|
|
|
856
|
-
// Inspect and (optionally) schedule SimpleKeyStore role grants via Timelock
|
|
857
773
|
cmd
|
|
858
774
|
.command('keystore-roles')
|
|
859
|
-
.description('
|
|
860
|
-
.
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
.option('--bootstrap', 'If signer is current admin on SimpleKeyStore, directly grant Timelock admin/manager (one-time bootstrap)', false)
|
|
865
|
-
.action(async (opts) => {
|
|
866
|
-
try {
|
|
867
|
-
const WM = require('../wallet-manager');
|
|
868
|
-
const { ethers } = require('ethers');
|
|
869
|
-
const wm = new WM(); await wm.connect();
|
|
870
|
-
const provider = wm.getProvider();
|
|
871
|
-
const signer = wm.getSigner();
|
|
872
|
-
const tlAddr = await resolveTimelockAddress(opts, provider);
|
|
873
|
-
const ksAddr = ethers.getAddress(opts.keystore);
|
|
874
|
-
console.log(colors.cyan(`šÆ Timelock=${tlAddr} SimpleKeyStore=${ksAddr}`));
|
|
875
|
-
|
|
876
|
-
// Read roles
|
|
877
|
-
const ks = new ethers.Contract(ksAddr, [
|
|
878
|
-
'function DEFAULT_ADMIN_ROLE() view returns (bytes32)',
|
|
879
|
-
'function MANAGER_ROLE() view returns (bytes32)',
|
|
880
|
-
'function hasRole(bytes32,address) view returns (bool)',
|
|
881
|
-
'function grantRole(bytes32,address)'
|
|
882
|
-
], provider);
|
|
883
|
-
const [ADMIN, MANAGER] = await Promise.all([
|
|
884
|
-
ks.DEFAULT_ADMIN_ROLE(), ks.MANAGER_ROLE()
|
|
885
|
-
]);
|
|
886
|
-
const [hasAdmin, hasManager] = await Promise.all([
|
|
887
|
-
ks.hasRole(ADMIN, tlAddr), ks.hasRole(MANAGER, tlAddr)
|
|
888
|
-
]);
|
|
889
|
-
console.log(' DEFAULT_ADMIN_ROLE ā Timelock:', hasAdmin ? 'ā
' : 'ā');
|
|
890
|
-
console.log(' MANAGER_ROLE ā Timelock:', hasManager ? 'ā
' : 'ā');
|
|
891
|
-
|
|
892
|
-
// Optional bootstrap path: if signer has admin on SimpleKeyStore, perform direct grants now
|
|
893
|
-
if (opts.bootstrap) {
|
|
894
|
-
const signerAddr = await signer.getAddress();
|
|
895
|
-
const ksAs = new ethers.Contract(ksAddr, [
|
|
896
|
-
'function DEFAULT_ADMIN_ROLE() view returns (bytes32)',
|
|
897
|
-
'function MANAGER_ROLE() view returns (bytes32)',
|
|
898
|
-
'function hasRole(bytes32,address) view returns (bool)',
|
|
899
|
-
'function grantRole(bytes32,address)'
|
|
900
|
-
], signer);
|
|
901
|
-
const signerIsAdmin = await ksAs.hasRole(ADMIN, signerAddr).catch(()=>false);
|
|
902
|
-
if (!signerIsAdmin) {
|
|
903
|
-
console.log(colors.red('ā Bootstrap requested but current signer is not DEFAULT_ADMIN on SimpleKeyStore.'));
|
|
904
|
-
} else {
|
|
905
|
-
if (!hasAdmin) {
|
|
906
|
-
console.log(colors.yellow('āļø Granting DEFAULT_ADMIN_ROLE to Timelock directly (bootstrap)...'));
|
|
907
|
-
const tx1 = await ksAs.grantRole(ADMIN, tlAddr); console.log(' tx:', tx1.hash); await tx1.wait();
|
|
908
|
-
console.log(colors.green(' ā
Granted DEFAULT_ADMIN_ROLE'));
|
|
909
|
-
}
|
|
910
|
-
if (!hasManager) {
|
|
911
|
-
console.log(colors.yellow('āļø Granting MANAGER_ROLE to Timelock directly (bootstrap)...'));
|
|
912
|
-
const tx2 = await ksAs.grantRole(MANAGER, tlAddr); console.log(' tx:', tx2.hash); await tx2.wait();
|
|
913
|
-
console.log(colors.green(' ā
Granted MANAGER_ROLE'));
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
if (!opts.fix) return;
|
|
919
|
-
|
|
920
|
-
// Schedule grantRole via Timelock for any missing roles
|
|
921
|
-
const tl = new ethers.Contract(tlAddr, timelockAbi(), signer);
|
|
922
|
-
const iface = new ethers.Interface(['function grantRole(bytes32,address)']);
|
|
923
|
-
const predecessor = ethers.ZeroHash;
|
|
924
|
-
const saltBase = ethers.hexlify(ethers.randomBytes(16));
|
|
925
|
-
const delay = await tl.getMinDelay().catch(()=>0n);
|
|
926
|
-
const ops = [];
|
|
927
|
-
if (!hasAdmin) ops.push({ role: ADMIN, label: 'DEFAULT_ADMIN_ROLE' });
|
|
928
|
-
if (!hasManager) ops.push({ role: MANAGER, label: 'MANAGER_ROLE' });
|
|
929
|
-
if (!ops.length) { console.log(colors.green('ā
No fixes needed.')); return; }
|
|
930
|
-
for (let i=0;i<ops.length;i++) {
|
|
931
|
-
const { role, label } = ops[i];
|
|
932
|
-
const data = iface.encodeFunctionData('grantRole', [role, tlAddr]);
|
|
933
|
-
const salt = ethers.keccak256(ethers.toUtf8Bytes(`${saltBase}:${label}`));
|
|
934
|
-
console.log(colors.yellow(`ā³ Scheduling grantRole(${label}, Timelock)`));
|
|
935
|
-
const stx = await tl.schedule(ksAddr, 0, data, predecessor, salt, delay);
|
|
936
|
-
console.log(' ā³ tx:', stx.hash); await stx.wait(); console.log(colors.green(' ā
scheduled'));
|
|
937
|
-
if (delay === 0n) {
|
|
938
|
-
const etx = await tl.execute(ksAddr, 0, data, predecessor, salt);
|
|
939
|
-
console.log(' ā” execute tx:', etx.hash); await etx.wait(); console.log(colors.green(' ā
executed'));
|
|
940
|
-
} else {
|
|
941
|
-
console.log(colors.gray(' Next: execute after delay:'));
|
|
942
|
-
console.log(` cast send ${tlAddr} \"execute(address,uint256,bytes,bytes32,bytes32)\" ${ksAddr} 0 ${data} ${predecessor} ${salt} --rpc-url ${process.env.RPC_URL || 'https://base-sepolia.publicnode.com'}`);
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
} catch (e) {
|
|
946
|
-
console.error(colors.red('ā keystore-roles failed:'), e.message);
|
|
947
|
-
process.exit(1);
|
|
948
|
-
}
|
|
775
|
+
.description('Use "sage personal" for premium prompts')
|
|
776
|
+
.action(() => {
|
|
777
|
+
console.log('SubDAO premium has been replaced by personal premium.');
|
|
778
|
+
console.log('Use: sage personal sell/buy/access');
|
|
779
|
+
console.log('See: docs/specs/premium-endorsement-model.md');
|
|
949
780
|
});
|
|
950
781
|
|
|
951
782
|
// Enable factory stable fees via Timelock from environment values
|
package/dist/cli/index.js
CHANGED
|
@@ -86,7 +86,7 @@ program
|
|
|
86
86
|
|
|
87
87
|
// Reordered to highlight consolidated namespaces ('prompts' and 'gov')
|
|
88
88
|
const commandGroups = [
|
|
89
|
-
{ title: 'Content (new)', modules: ['prompts', 'project', 'prompt', '
|
|
89
|
+
{ title: 'Content (new)', modules: ['prompts', 'project', 'prompt', 'personal', 'creator', 'contributor', 'bounty'] },
|
|
90
90
|
{ title: 'Governance (new)', modules: ['proposals', 'governance', 'dao', 'timelock', 'roles', 'members', 'gov-config', 'stake-status'] },
|
|
91
91
|
{ title: 'Treasury', modules: ['treasury', 'safe', 'boost'] },
|
|
92
92
|
{ title: 'Ops & Utilities', modules: ['config', 'wallet', 'sxxx', 'ipfs', 'ipns', 'context', 'doctor', 'factory', 'upgrade', 'resolve', 'dry-run-queue', 'mcp', 'start', 'wizard', 'init', 'dao-config', 'pin', 'council', 'sbt', 'completion', 'help', 'hook'] },
|
|
@@ -151,10 +151,6 @@ const additionalModules = [
|
|
|
151
151
|
];
|
|
152
152
|
additionalModules.forEach((mod) => moduleSet.add(mod));
|
|
153
153
|
|
|
154
|
-
// Experimental: enable 'personal' module when flagged
|
|
155
|
-
if (flags.isEnabled('SAGE_ENABLE_PERSONAL')) {
|
|
156
|
-
moduleSet.add('personal');
|
|
157
|
-
}
|
|
158
154
|
|
|
159
155
|
const cliTestMode = flags.isEnabled('SAGE_CLI_TEST_MODE');
|
|
160
156
|
if (cliTestMode) {
|
|
@@ -151,6 +151,7 @@ const COMMAND_CATALOG = {
|
|
|
151
151
|
'allowance',
|
|
152
152
|
'ensure-allowance',
|
|
153
153
|
'ensure-allowance-gov',
|
|
154
|
+
'faucet',
|
|
154
155
|
'transfer-ownership',
|
|
155
156
|
'transfer',
|
|
156
157
|
'delegate-self',
|
|
@@ -158,7 +159,8 @@ const COMMAND_CATALOG = {
|
|
|
158
159
|
],
|
|
159
160
|
subcommandAliases: {
|
|
160
161
|
del: 'delegate-self',
|
|
161
|
-
delegate: 'delegate-self'
|
|
162
|
+
delegate: 'delegate-self',
|
|
163
|
+
drip: 'faucet'
|
|
162
164
|
}
|
|
163
165
|
},
|
|
164
166
|
treasury: {
|
|
@@ -101,7 +101,7 @@ const ERROR_GUIDANCE = {
|
|
|
101
101
|
summary: 'The connected signer is missing the required on-chain role or delegation (e.g., PROPOSER_ROLE, EXECUTOR_ROLE, admin).',
|
|
102
102
|
suggestions: [
|
|
103
103
|
() => 'sage dao doctor --subdao <subdao-address>',
|
|
104
|
-
() => 'sage
|
|
104
|
+
() => 'sage timelock doctor --subdao <subdao-address>',
|
|
105
105
|
() => 'sage sxxx delegate-self'
|
|
106
106
|
],
|
|
107
107
|
perCommand: {
|
|
@@ -110,11 +110,17 @@ const ERROR_GUIDANCE = {
|
|
|
110
110
|
],
|
|
111
111
|
'library:push': [
|
|
112
112
|
(ctx) => (ctx?.subdao ? `sage dao doctor --subdao ${ctx.subdao}` : 'sage dao doctor --subdao <subdao-address>'),
|
|
113
|
-
() =>
|
|
113
|
+
(ctx) =>
|
|
114
|
+
ctx?.subdao
|
|
115
|
+
? `sage timelock doctor --subdao ${ctx.subdao}`
|
|
116
|
+
: 'sage timelock doctor --subdao <subdao-address>'
|
|
114
117
|
],
|
|
115
118
|
'prompts:publish': [
|
|
116
119
|
(ctx) => (ctx?.subdao ? `sage dao doctor --subdao ${ctx.subdao}` : 'sage dao doctor --subdao <subdao-address>'),
|
|
117
|
-
() =>
|
|
120
|
+
(ctx) =>
|
|
121
|
+
ctx?.subdao
|
|
122
|
+
? `sage timelock doctor --subdao ${ctx.subdao}`
|
|
123
|
+
: 'sage timelock doctor --subdao <subdao-address>'
|
|
118
124
|
],
|
|
119
125
|
'timelock:execute': [
|
|
120
126
|
(ctx) =>
|
|
@@ -122,12 +128,6 @@ const ERROR_GUIDANCE = {
|
|
|
122
128
|
? `sage timelock doctor --timelock ${ctx.timelock}`
|
|
123
129
|
: 'sage timelock doctor --subdao <subdao-address>'
|
|
124
130
|
],
|
|
125
|
-
'roles:apply': [
|
|
126
|
-
(ctx) =>
|
|
127
|
-
ctx?.subdao
|
|
128
|
-
? `sage roles matrix --subdao ${ctx.subdao}`
|
|
129
|
-
: 'sage roles matrix --subdao <subdao-address>'
|
|
130
|
-
],
|
|
131
131
|
'governance:vote': [
|
|
132
132
|
() => 'sage sxxx delegate-self',
|
|
133
133
|
() => 'sage governance power'
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const { LitNodeClientNodeJs } = require('@lit-protocol/lit-node-client-nodejs');
|
|
2
|
+
const { ethers } = require('ethers');
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const { encryptAesGcm, toB64 } = require('./aes');
|
|
5
|
+
|
|
6
|
+
let client;
|
|
7
|
+
|
|
8
|
+
async function getClient() {
|
|
9
|
+
if (client) return client;
|
|
10
|
+
client = new LitNodeClientNodeJs({
|
|
11
|
+
litNetwork: 'datil-test',
|
|
12
|
+
debug: false
|
|
13
|
+
});
|
|
14
|
+
await client.connect();
|
|
15
|
+
return client;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function buildNodeAuthSig(signer, chain = 'ethereum') {
|
|
19
|
+
const address = await signer.getAddress();
|
|
20
|
+
const domain = 'localhost';
|
|
21
|
+
const origin = 'https://localhost/login';
|
|
22
|
+
const statement = 'Sign in to Lit Protocol';
|
|
23
|
+
const version = '1';
|
|
24
|
+
const chainId = 1;
|
|
25
|
+
const expirationTime = new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString();
|
|
26
|
+
|
|
27
|
+
const siweMessage = `localhost wants you to sign in with your Ethereum account:
|
|
28
|
+
${address}
|
|
29
|
+
|
|
30
|
+
${statement}
|
|
31
|
+
|
|
32
|
+
URI: ${origin}
|
|
33
|
+
Version: ${version}
|
|
34
|
+
Chain ID: ${chainId}
|
|
35
|
+
Nonce: ${ethers.hexlify(crypto.randomBytes(8))}
|
|
36
|
+
Issued At: ${new Date().toISOString()}
|
|
37
|
+
Expiration Time: ${expirationTime}`;
|
|
38
|
+
|
|
39
|
+
const signature = await signer.signMessage(siweMessage);
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
sig: signature,
|
|
43
|
+
derivedVia: 'web3.eth.personal.sign',
|
|
44
|
+
signedMessage: siweMessage,
|
|
45
|
+
address: address,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function encryptStringWithLit({ content, receiptAddress, tokenId, authSig, chain = 'baseSepolia' }) {
|
|
50
|
+
const litNodeClient = await getClient();
|
|
51
|
+
|
|
52
|
+
// 1. Generate symmetric key
|
|
53
|
+
const symKey = crypto.randomBytes(32);
|
|
54
|
+
|
|
55
|
+
// 2. Encrypt content with symmetric key (using local AES)
|
|
56
|
+
const { ciphertext, iv, tag } = encryptAesGcm(content, symKey);
|
|
57
|
+
// Combine iv + tag + ciphertext for storage (standard format often used)
|
|
58
|
+
// Or return as blob?
|
|
59
|
+
// premium.js expects `encryptedBlob`.
|
|
60
|
+
// Let's pack it: IV (12) + Tag (16) + Ciphertext
|
|
61
|
+
const encryptedBlob = Buffer.concat([iv, tag, ciphertext]);
|
|
62
|
+
|
|
63
|
+
// 3. Encrypt symmetric key with Lit
|
|
64
|
+
const accessControlConditions = [
|
|
65
|
+
{
|
|
66
|
+
contractAddress: receiptAddress,
|
|
67
|
+
standardContractType: 'ERC1155',
|
|
68
|
+
chain,
|
|
69
|
+
method: 'balanceOf',
|
|
70
|
+
parameters: [':userAddress', tokenId],
|
|
71
|
+
returnValueTest: {
|
|
72
|
+
comparator: '>',
|
|
73
|
+
value: '0'
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
// Lit v6 encrypt
|
|
79
|
+
// Note: encrypt() returns { ciphertext, dataToEncryptHash } usually for access control?
|
|
80
|
+
// No, for encryption key provisioning:
|
|
81
|
+
// client.encrypt({ accessControlConditions, to_encrypt: symKey, ... })
|
|
82
|
+
// Convert Buffer to Uint8Array for Lit Protocol v7 compatibility
|
|
83
|
+
const symKeyUint8 = new Uint8Array(symKey);
|
|
84
|
+
|
|
85
|
+
const { ciphertext: litCiphertext, dataToEncryptHash } = await litNodeClient.encrypt({
|
|
86
|
+
accessControlConditions,
|
|
87
|
+
dataToEncrypt: symKeyUint8,
|
|
88
|
+
authSig,
|
|
89
|
+
chain,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Lit v7 returns ciphertext already as base64-encoded string
|
|
93
|
+
// No need to re-encode with toB64
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
encryptedBlob,
|
|
97
|
+
encryptedSymmetricKey: litCiphertext, // Already base64 in Lit v7
|
|
98
|
+
dataToEncryptHash, // Include hash for decryption
|
|
99
|
+
accessControlConditions
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = { encryptStringWithLit, buildNodeAuthSig };
|