@sage-protocol/cli 0.3.7 → 0.3.10

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.
@@ -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 fix-roles --subdao <subdao-address> (admin only, interactive)');
513
- console.log(' - sage roles plan --subdao <subdao-address> && sage roles apply --subdao <subdao-address>');
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 fix-roles --subdao <subdao-address>');
532
- console.log(' - sage roles plan --subdao <subdao-address> && sage roles apply --subdao <subdao-address>');
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
- // Inspect and (optionally) schedule PremiumPrompts role grants via Timelock
763
+ // SubDAO premium roles - replaced by personal premium model
762
764
  cmd
763
765
  .command('premium-roles')
764
- .description('Doctor/Fix PremiumPrompts roles for Timelock (team mode)')
765
- .requiredOption('--prompts <address>', 'PremiumPrompts contract address')
766
- .option('--subdao <address>', 'Resolve timelock from a SubDAO context')
767
- .option('--gov <address>', 'Resolve timelock via a Governor context')
768
- .option('--fix', 'Schedule grantRole(DEFAULT_ADMIN_ROLE and/or MANAGER_ROLE) to Timelock', false)
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('Doctor/Fix SimpleKeyStore roles for Timelock (team mode)')
860
- .requiredOption('--keystore <address>', 'SimpleKeyStore contract address')
861
- .option('--subdao <address>', 'Resolve timelock from a SubDAO context')
862
- .option('--gov <address>', 'Resolve timelock via a Governor context')
863
- .option('--fix', 'Schedule grantRole(DEFAULT_ADMIN_ROLE and/or MANAGER_ROLE) to Timelock', false)
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', '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) {
@@ -18,7 +18,7 @@ Create or update your `.env` file with the required configuration:
18
18
 
19
19
  ```bash
20
20
  # Subgraph (preferred) + Blockchain
21
- SUBGRAPH_URL=https://api.goldsky.com/api/public/project_cmhxp0fppsbdd01q56xp2gqw9/subgraphs/sxxx-protocol/1.0.0/gn
21
+ SUBGRAPH_URL=https://api.goldsky.com/api/public/project_cmhxp0fppsbdd01q56xp2gqw9/subgraphs/sxxx-protocol/1.0.1/gn
22
22
  RPC_URL=https://sepolia.base.org
23
23
  LIBRARY_REGISTRY_ADDRESS=0x032F2E93549640a319163Ba600fc50bA1AFBE50F
24
24
  SUBDAO_FACTORY_ADDRESS=0x6bb6A83149F84Df0584aC863e5fE3416AFb214aC
@@ -65,7 +65,7 @@ You should see a JSON response listing the available tools.
65
65
  "command": "node",
66
66
  "args": ["/absolute/path/to/sage-protocol/cli/mcp-server-stdio.js"],
67
67
  "env": {
68
- "SUBGRAPH_URL": "https://api.goldsky.com/api/public/project_cmhxp0fppsbdd01q56xp2gqw9/subgraphs/sxxx-protocol/1.0.0/gn",
68
+ "SUBGRAPH_URL": "https://api.goldsky.com/api/public/project_cmhxp0fppsbdd01q56xp2gqw9/subgraphs/sxxx-protocol/1.0.1/gn",
69
69
  "RPC_URL": "https://sepolia.base.org",
70
70
  "LIBRARY_REGISTRY_ADDRESS": "0x032F2E93549640a319163Ba600fc50bA1AFBE50F",
71
71
  "SUBDAO_FACTORY_ADDRESS": "0x6bb6A83149F84Df0584aC863e5fE3416AFb214aC"
@@ -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,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
- () => '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>'
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 };
@@ -44,7 +44,7 @@ function buildAccessControlConditions(receiptAddress, receiptId, chain) {
44
44
  type: 'function',
45
45
  },
46
46
  chain: litChain,
47
- returnValueTest: { comparator: '>', value: '0' },
47
+ returnValueTest: { key: '', comparator: '>', value: '0' },
48
48
  },
49
49
  ];
50
50
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sage-protocol/cli",
3
- "version": "0.3.7",
3
+ "version": "0.3.10",
4
4
  "description": "Sage Protocol CLI for managing AI prompt libraries",
5
5
  "bin": {
6
6
  "sage": "./bin/sage.js"