@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.
- package/dist/cli/commands/boost.js +76 -68
- package/dist/cli/commands/bounty.js +123 -85
- package/dist/cli/commands/config.js +2 -0
- 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/prompts.js +6 -1
- package/dist/cli/commands/sxxx.js +103 -0
- package/dist/cli/commands/timelock.js +22 -192
- package/dist/cli/index.js +1 -5
- package/dist/cli/utils/aliases.js +3 -1
- package/dist/cli/utils/error-handler.js +7 -13
- package/dist/cli/utils/lit.js +103 -0
- package/dist/ops/config/env/load-env.sepolia.sh +4 -4
- package/package.json +1 -1
|
@@ -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
|
-
{
|
|
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
|
|
@@ -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)')
|
|
@@ -508,9 +508,11 @@ 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:
|
|
512
|
-
console.log(
|
|
513
|
-
console.log('
|
|
511
|
+
console.log(' Hint: use one of:');
|
|
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');
|
|
515
|
+
console.log(' Or rerun with --auto if your signer is an admin (schedule will grant proposer automatically).');
|
|
514
516
|
return process.exit(1);
|
|
515
517
|
}
|
|
516
518
|
}
|
|
@@ -525,13 +527,12 @@ function register(program) {
|
|
|
525
527
|
const msg = String(e?.message || e);
|
|
526
528
|
// Common revert: AccessControlUnauthorizedAccount on schedule
|
|
527
529
|
if (msg.includes('AccessControl') || msg.includes('0xe2517d3f')) {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
} catch (_) {}
|
|
530
|
+
console.log(colors.red('❌ Schedule failed: missing PROPOSER_ROLE on Timelock for current signer.'));
|
|
531
|
+
console.log(' Grant role via governance or admin helpers, for example:');
|
|
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>');
|
|
535
|
+
console.log(' Or rerun with --auto if your signer is an admin (schedule will grant proposer automatically).');
|
|
535
536
|
}
|
|
536
537
|
console.error(colors.red('❌ Schedule failed:'), msg);
|
|
537
538
|
process.exit(1);
|
|
@@ -759,194 +760,23 @@ function register(program) {
|
|
|
759
760
|
}
|
|
760
761
|
});
|
|
761
762
|
|
|
762
|
-
//
|
|
763
|
+
// SubDAO premium roles - replaced by personal premium model
|
|
763
764
|
cmd
|
|
764
765
|
.command('premium-roles')
|
|
765
|
-
.description('
|
|
766
|
-
.
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
.option('--bootstrap', 'If signer is current admin on PremiumPrompts, directly grant Timelock admin/manager (one-time bootstrap)', false)
|
|
771
|
-
.action(async (opts) => {
|
|
772
|
-
try {
|
|
773
|
-
const WM = require('../wallet-manager');
|
|
774
|
-
const { ethers } = require('ethers');
|
|
775
|
-
const wm = new WM(); await wm.connect();
|
|
776
|
-
const provider = wm.getProvider();
|
|
777
|
-
const signer = wm.getSigner();
|
|
778
|
-
const tlAddr = await resolveTimelockAddress(opts, provider);
|
|
779
|
-
const prompts = ethers.getAddress(opts.prompts);
|
|
780
|
-
console.log(colors.cyan(`🎯 Timelock=${tlAddr} PremiumPrompts=${prompts}`));
|
|
781
|
-
|
|
782
|
-
// Read roles
|
|
783
|
-
const pp = new ethers.Contract(prompts, [
|
|
784
|
-
'function DEFAULT_ADMIN_ROLE() view returns (bytes32)',
|
|
785
|
-
'function MANAGER_ROLE() view returns (bytes32)',
|
|
786
|
-
'function hasRole(bytes32,address) view returns (bool)',
|
|
787
|
-
'function grantRole(bytes32,address)'
|
|
788
|
-
], provider);
|
|
789
|
-
const [ADMIN, MANAGER] = await Promise.all([
|
|
790
|
-
pp.DEFAULT_ADMIN_ROLE(), pp.MANAGER_ROLE()
|
|
791
|
-
]);
|
|
792
|
-
const [hasAdmin, hasManager] = await Promise.all([
|
|
793
|
-
pp.hasRole(ADMIN, tlAddr), pp.hasRole(MANAGER, tlAddr)
|
|
794
|
-
]);
|
|
795
|
-
console.log(' DEFAULT_ADMIN_ROLE → Timelock:', hasAdmin ? '✅' : '❌');
|
|
796
|
-
console.log(' MANAGER_ROLE → Timelock:', hasManager ? '✅' : '❌');
|
|
797
|
-
|
|
798
|
-
// Optional bootstrap path: if signer has admin on PremiumPrompts, perform direct grants now
|
|
799
|
-
if (opts.bootstrap) {
|
|
800
|
-
const signerAddr = await signer.getAddress();
|
|
801
|
-
const ppAs = new ethers.Contract(prompts, [
|
|
802
|
-
'function DEFAULT_ADMIN_ROLE() view returns (bytes32)',
|
|
803
|
-
'function MANAGER_ROLE() view returns (bytes32)',
|
|
804
|
-
'function hasRole(bytes32,address) view returns (bool)',
|
|
805
|
-
'function grantRole(bytes32,address)'
|
|
806
|
-
], signer);
|
|
807
|
-
const signerIsAdmin = await ppAs.hasRole(ADMIN, signerAddr).catch(()=>false);
|
|
808
|
-
if (!signerIsAdmin) {
|
|
809
|
-
console.log(colors.red('❌ Bootstrap requested but current signer is not DEFAULT_ADMIN on PremiumPrompts.'));
|
|
810
|
-
} else {
|
|
811
|
-
if (!hasAdmin) {
|
|
812
|
-
console.log(colors.yellow('⚙️ Granting DEFAULT_ADMIN_ROLE to Timelock directly (bootstrap)...'));
|
|
813
|
-
const tx1 = await ppAs.grantRole(ADMIN, tlAddr); console.log(' tx:', tx1.hash); await tx1.wait();
|
|
814
|
-
console.log(colors.green(' ✅ Granted DEFAULT_ADMIN_ROLE'));
|
|
815
|
-
}
|
|
816
|
-
if (!hasManager) {
|
|
817
|
-
console.log(colors.yellow('⚙️ Granting MANAGER_ROLE to Timelock directly (bootstrap)...'));
|
|
818
|
-
const tx2 = await ppAs.grantRole(MANAGER, tlAddr); console.log(' tx:', tx2.hash); await tx2.wait();
|
|
819
|
-
console.log(colors.green(' ✅ Granted MANAGER_ROLE'));
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
if (!opts.fix) return;
|
|
825
|
-
|
|
826
|
-
// Schedule grantRole via Timelock for any missing roles
|
|
827
|
-
const tl = new ethers.Contract(tlAddr, timelockAbi(), signer);
|
|
828
|
-
const iface = new ethers.Interface(['function grantRole(bytes32,address)']);
|
|
829
|
-
const predecessor = ethers.ZeroHash;
|
|
830
|
-
const saltBase = ethers.hexlify(ethers.randomBytes(16));
|
|
831
|
-
const delay = await tl.getMinDelay().catch(()=>0n);
|
|
832
|
-
const ops = [];
|
|
833
|
-
if (!hasAdmin) ops.push({ role: ADMIN, label: 'DEFAULT_ADMIN_ROLE' });
|
|
834
|
-
if (!hasManager) ops.push({ role: MANAGER, label: 'MANAGER_ROLE' });
|
|
835
|
-
if (!ops.length) { console.log(colors.green('✅ No fixes needed.')); return; }
|
|
836
|
-
for (let i=0;i<ops.length;i++) {
|
|
837
|
-
const { role, label } = ops[i];
|
|
838
|
-
const data = iface.encodeFunctionData('grantRole', [role, tlAddr]);
|
|
839
|
-
const salt = ethers.keccak256(ethers.toUtf8Bytes(`${saltBase}:${label}`));
|
|
840
|
-
console.log(colors.yellow(`⏳ Scheduling grantRole(${label}, Timelock)`));
|
|
841
|
-
const stx = await tl.schedule(prompts, 0, data, predecessor, salt, delay);
|
|
842
|
-
console.log(' ⏳ tx:', stx.hash); await stx.wait(); console.log(colors.green(' ✅ scheduled'));
|
|
843
|
-
if (delay === 0n) {
|
|
844
|
-
const etx = await tl.execute(prompts, 0, data, predecessor, salt);
|
|
845
|
-
console.log(' ⚡ execute tx:', etx.hash); await etx.wait(); console.log(colors.green(' ✅ executed'));
|
|
846
|
-
} else {
|
|
847
|
-
console.log(colors.gray(' Next: execute after delay:'));
|
|
848
|
-
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'}`);
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
} catch (e) {
|
|
852
|
-
console.error(colors.red('❌ premium-roles failed:'), e.message);
|
|
853
|
-
process.exit(1);
|
|
854
|
-
}
|
|
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');
|
|
855
771
|
});
|
|
856
772
|
|
|
857
|
-
// Inspect and (optionally) schedule SimpleKeyStore role grants via Timelock
|
|
858
773
|
cmd
|
|
859
774
|
.command('keystore-roles')
|
|
860
|
-
.description('
|
|
861
|
-
.
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
.option('--bootstrap', 'If signer is current admin on SimpleKeyStore, directly grant Timelock admin/manager (one-time bootstrap)', false)
|
|
866
|
-
.action(async (opts) => {
|
|
867
|
-
try {
|
|
868
|
-
const WM = require('../wallet-manager');
|
|
869
|
-
const { ethers } = require('ethers');
|
|
870
|
-
const wm = new WM(); await wm.connect();
|
|
871
|
-
const provider = wm.getProvider();
|
|
872
|
-
const signer = wm.getSigner();
|
|
873
|
-
const tlAddr = await resolveTimelockAddress(opts, provider);
|
|
874
|
-
const ksAddr = ethers.getAddress(opts.keystore);
|
|
875
|
-
console.log(colors.cyan(`🎯 Timelock=${tlAddr} SimpleKeyStore=${ksAddr}`));
|
|
876
|
-
|
|
877
|
-
// Read roles
|
|
878
|
-
const ks = new ethers.Contract(ksAddr, [
|
|
879
|
-
'function DEFAULT_ADMIN_ROLE() view returns (bytes32)',
|
|
880
|
-
'function MANAGER_ROLE() view returns (bytes32)',
|
|
881
|
-
'function hasRole(bytes32,address) view returns (bool)',
|
|
882
|
-
'function grantRole(bytes32,address)'
|
|
883
|
-
], provider);
|
|
884
|
-
const [ADMIN, MANAGER] = await Promise.all([
|
|
885
|
-
ks.DEFAULT_ADMIN_ROLE(), ks.MANAGER_ROLE()
|
|
886
|
-
]);
|
|
887
|
-
const [hasAdmin, hasManager] = await Promise.all([
|
|
888
|
-
ks.hasRole(ADMIN, tlAddr), ks.hasRole(MANAGER, tlAddr)
|
|
889
|
-
]);
|
|
890
|
-
console.log(' DEFAULT_ADMIN_ROLE → Timelock:', hasAdmin ? '✅' : '❌');
|
|
891
|
-
console.log(' MANAGER_ROLE → Timelock:', hasManager ? '✅' : '❌');
|
|
892
|
-
|
|
893
|
-
// Optional bootstrap path: if signer has admin on SimpleKeyStore, perform direct grants now
|
|
894
|
-
if (opts.bootstrap) {
|
|
895
|
-
const signerAddr = await signer.getAddress();
|
|
896
|
-
const ksAs = new ethers.Contract(ksAddr, [
|
|
897
|
-
'function DEFAULT_ADMIN_ROLE() view returns (bytes32)',
|
|
898
|
-
'function MANAGER_ROLE() view returns (bytes32)',
|
|
899
|
-
'function hasRole(bytes32,address) view returns (bool)',
|
|
900
|
-
'function grantRole(bytes32,address)'
|
|
901
|
-
], signer);
|
|
902
|
-
const signerIsAdmin = await ksAs.hasRole(ADMIN, signerAddr).catch(()=>false);
|
|
903
|
-
if (!signerIsAdmin) {
|
|
904
|
-
console.log(colors.red('❌ Bootstrap requested but current signer is not DEFAULT_ADMIN on SimpleKeyStore.'));
|
|
905
|
-
} else {
|
|
906
|
-
if (!hasAdmin) {
|
|
907
|
-
console.log(colors.yellow('⚙️ Granting DEFAULT_ADMIN_ROLE to Timelock directly (bootstrap)...'));
|
|
908
|
-
const tx1 = await ksAs.grantRole(ADMIN, tlAddr); console.log(' tx:', tx1.hash); await tx1.wait();
|
|
909
|
-
console.log(colors.green(' ✅ Granted DEFAULT_ADMIN_ROLE'));
|
|
910
|
-
}
|
|
911
|
-
if (!hasManager) {
|
|
912
|
-
console.log(colors.yellow('⚙️ Granting MANAGER_ROLE to Timelock directly (bootstrap)...'));
|
|
913
|
-
const tx2 = await ksAs.grantRole(MANAGER, tlAddr); console.log(' tx:', tx2.hash); await tx2.wait();
|
|
914
|
-
console.log(colors.green(' ✅ Granted MANAGER_ROLE'));
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
if (!opts.fix) return;
|
|
920
|
-
|
|
921
|
-
// Schedule grantRole via Timelock for any missing roles
|
|
922
|
-
const tl = new ethers.Contract(tlAddr, timelockAbi(), signer);
|
|
923
|
-
const iface = new ethers.Interface(['function grantRole(bytes32,address)']);
|
|
924
|
-
const predecessor = ethers.ZeroHash;
|
|
925
|
-
const saltBase = ethers.hexlify(ethers.randomBytes(16));
|
|
926
|
-
const delay = await tl.getMinDelay().catch(()=>0n);
|
|
927
|
-
const ops = [];
|
|
928
|
-
if (!hasAdmin) ops.push({ role: ADMIN, label: 'DEFAULT_ADMIN_ROLE' });
|
|
929
|
-
if (!hasManager) ops.push({ role: MANAGER, label: 'MANAGER_ROLE' });
|
|
930
|
-
if (!ops.length) { console.log(colors.green('✅ No fixes needed.')); return; }
|
|
931
|
-
for (let i=0;i<ops.length;i++) {
|
|
932
|
-
const { role, label } = ops[i];
|
|
933
|
-
const data = iface.encodeFunctionData('grantRole', [role, tlAddr]);
|
|
934
|
-
const salt = ethers.keccak256(ethers.toUtf8Bytes(`${saltBase}:${label}`));
|
|
935
|
-
console.log(colors.yellow(`⏳ Scheduling grantRole(${label}, Timelock)`));
|
|
936
|
-
const stx = await tl.schedule(ksAddr, 0, data, predecessor, salt, delay);
|
|
937
|
-
console.log(' ⏳ tx:', stx.hash); await stx.wait(); console.log(colors.green(' ✅ scheduled'));
|
|
938
|
-
if (delay === 0n) {
|
|
939
|
-
const etx = await tl.execute(ksAddr, 0, data, predecessor, salt);
|
|
940
|
-
console.log(' ⚡ execute tx:', etx.hash); await etx.wait(); console.log(colors.green(' ✅ executed'));
|
|
941
|
-
} else {
|
|
942
|
-
console.log(colors.gray(' Next: execute after delay:'));
|
|
943
|
-
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'}`);
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
} catch (e) {
|
|
947
|
-
console.error(colors.red('❌ keystore-roles failed:'), e.message);
|
|
948
|
-
process.exit(1);
|
|
949
|
-
}
|
|
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');
|
|
950
780
|
});
|
|
951
781
|
|
|
952
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,16 +110,16 @@ 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
|
-
() => 'sage roles matrix --subdao <subdao-address>'
|
|
118
|
-
],
|
|
119
|
-
'timelock:grant-role': [
|
|
120
120
|
(ctx) =>
|
|
121
|
-
ctx?.
|
|
122
|
-
? `sage timelock doctor --
|
|
121
|
+
ctx?.subdao
|
|
122
|
+
? `sage timelock doctor --subdao ${ctx.subdao}`
|
|
123
123
|
: 'sage timelock doctor --subdao <subdao-address>'
|
|
124
124
|
],
|
|
125
125
|
'timelock:execute': [
|
|
@@ -128,12 +128,6 @@ const ERROR_GUIDANCE = {
|
|
|
128
128
|
? `sage timelock doctor --timelock ${ctx.timelock}`
|
|
129
129
|
: 'sage timelock doctor --subdao <subdao-address>'
|
|
130
130
|
],
|
|
131
|
-
'roles:apply': [
|
|
132
|
-
(ctx) =>
|
|
133
|
-
ctx?.subdao
|
|
134
|
-
? `sage roles matrix --subdao ${ctx.subdao}`
|
|
135
|
-
: 'sage roles matrix --subdao <subdao-address>'
|
|
136
|
-
],
|
|
137
131
|
'governance:vote': [
|
|
138
132
|
() => 'sage sxxx delegate-self',
|
|
139
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 };
|
|
@@ -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=
|
|
72
|
+
export BOOST_MANAGER_DIRECT_ADDRESS=0x7C9Ba615322FB9FdBC383040A77E6d0F80c7e349
|
|
73
73
|
|
|
74
|
-
export BOOST_VIEW_GLUE_ADDRESS=
|
|
74
|
+
export BOOST_VIEW_GLUE_ADDRESS=0x56747BfD90F3017Ef69A35DEe3e9560aF9778dD0
|
|
75
75
|
|
|
76
|
-
export BOOST_LOGGER_GLUE_ADDRESS=
|
|
76
|
+
export BOOST_LOGGER_GLUE_ADDRESS=0x2371093e9fA09c5e2Da4556b3487Db3f7863D438
|
|
77
77
|
|
|
78
|
-
export BOOST_CREATION_GLUE_ADDRESS=
|
|
78
|
+
export BOOST_CREATION_GLUE_ADDRESS=0x401e8BFbe2eEfC4b178Cc2Ff5FeeA84FaD7f7A96
|