@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.
@@ -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
@@ -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: 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 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
- 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 (_) {}
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
- // Inspect and (optionally) schedule PremiumPrompts role grants via Timelock
763
+ // SubDAO premium roles - replaced by personal premium model
763
764
  cmd
764
765
  .command('premium-roles')
765
- .description('Doctor/Fix PremiumPrompts roles for Timelock (team mode)')
766
- .requiredOption('--prompts <address>', 'PremiumPrompts contract address')
767
- .option('--subdao <address>', 'Resolve timelock from a SubDAO context')
768
- .option('--gov <address>', 'Resolve timelock via a Governor context')
769
- .option('--fix', 'Schedule grantRole(DEFAULT_ADMIN_ROLE and/or MANAGER_ROLE) to Timelock', false)
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('Doctor/Fix SimpleKeyStore roles for Timelock (team mode)')
861
- .requiredOption('--keystore <address>', 'SimpleKeyStore contract address')
862
- .option('--subdao <address>', 'Resolve timelock from a SubDAO context')
863
- .option('--gov <address>', 'Resolve timelock via a Governor context')
864
- .option('--fix', 'Schedule grantRole(DEFAULT_ADMIN_ROLE and/or MANAGER_ROLE) to Timelock', false)
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', 'premium', 'premium-pre', 'creator', 'contributor', 'bounty'] },
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 roles matrix --subdao <subdao-address>',
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
- () => 'sage roles matrix --subdao <subdao-address>'
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?.timelock
122
- ? `sage timelock doctor --timelock ${ctx.timelock}`
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=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.9",
4
4
  "description": "Sage Protocol CLI for managing AI prompt libraries",
5
5
  "bin": {
6
6
  "sage": "./bin/sage.js"