@sage-protocol/sdk 0.0.5 → 0.0.7

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/README.md CHANGED
@@ -77,6 +77,44 @@ Next Phases
77
77
  -----------
78
78
  Phase 6 focuses on integration polish and packaging. Track progress in the [SDK Improvement Specification](../../docs/SDK_Improvement_Specification.md).
79
79
 
80
+ New governance/factory/library helpers (2025‑10)
81
+ -----------------------------------------------
82
+
83
+ Proposal ID and preflight
84
+ ```js
85
+ import sdk from '@sage-protocol/sdk';
86
+ const idHex = sdk.governance.computeProposalIdHex({ targets, values, calldatas, description });
87
+ const pre = await sdk.governance.simulatePropose({ provider, governor, targets, values, calldatas, description, sender });
88
+ if (!pre.ok) throw new Error(`preflight failed: ${pre.error?.message}`);
89
+ ```
90
+
91
+ Votes at latest‑1 (ERC20Votes)
92
+ ```js
93
+ // Token path
94
+ const votes1 = await sdk.governance.getVotesLatestMinusOne({ provider, token: sxxxToken, account: user });
95
+ // Governor path (auto‑resolves token)
96
+ const votes2 = await sdk.governance.getVotesLatestMinusOne({ provider, governor, account: user });
97
+ ```
98
+
99
+ Factory‑mapped registry
100
+ ```js
101
+ const mapped = await sdk.factory.getSubDAORegistry({ provider, factory, subdao });
102
+ ```
103
+
104
+ Registry preflight as timelock
105
+ ```js
106
+ const { to, data } = sdk.library.buildUpdateLibraryForSubDAOTx({ registry, subdao, manifestCID, promptCount, libraryId: 'main' });
107
+ const sim = await sdk.library.simulateAsTimelock({ provider, registry, to, data, timelock });
108
+ if (!sim.ok) throw new Error(`registry preflight failed: ${sim.error?.message}`);
109
+ ```
110
+
111
+ Propose by hash (arrays + bytes32)
112
+ ```js
113
+ // For governors that prefer descriptionHash (or deterministic IDs)
114
+ const dh = sdk.governance.hashDescription(description);
115
+ const tx = sdk.governance.buildProposeTxByHash({ governor, targets, values, calldatas, descriptionHash: dh });
116
+ ```
117
+
80
118
  API Notes and Examples
81
119
  ----------------------
82
120
 
package/dist/index.cjs CHANGED
@@ -14,7 +14,7 @@ var require_package = __commonJS({
14
14
  "package.json"(exports2, module2) {
15
15
  module2.exports = {
16
16
  name: "@sage-protocol/sdk",
17
- version: "0.0.5",
17
+ version: "0.0.7",
18
18
  description: "Backend-agnostic SDK for interacting with the Sage Protocol (governance, SubDAOs, tokens).",
19
19
  main: "dist/index.cjs",
20
20
  module: "dist/index.mjs",
@@ -107,6 +107,8 @@ var require_abi = __commonJS({
107
107
  "function simpleKeyStoreAddress() view returns (address)",
108
108
  "function governanceConfigAddress() view returns (address)",
109
109
  "function libraryRegistryAddress() view returns (address)",
110
+ // Canonical per‑SubDAO mapping
111
+ "function subdaoToRegistry(address) view returns (address)",
110
112
  "function templateModule() view returns (address)",
111
113
  "function stats() view returns (uint128 totalSubDAOsCreated, uint128 totalBurnedForCreation)",
112
114
  // On-chain enumeration fallback (naming follows FactoryCoreFacet)
@@ -194,6 +196,7 @@ var require_abi = __commonJS({
194
196
  var ERC20Votes = [
195
197
  "function balanceOf(address) view returns (uint256)",
196
198
  "function getVotes(address) view returns (uint256)",
199
+ "function getPastVotes(address,uint256) view returns (uint256)",
197
200
  "function allowance(address,address) view returns (uint256)",
198
201
  "function delegate(address)",
199
202
  "function delegates(address) view returns (address)",
@@ -377,7 +380,7 @@ var require_description = __commonJS({
377
380
  // src/governance/index.js
378
381
  var require_governance = __commonJS({
379
382
  "src/governance/index.js"(exports2, module2) {
380
- var { Contract, Interface, getAddress, hexlify, keccak256, toUtf8Bytes } = require("ethers");
383
+ var { Contract, Interface, AbiCoder, getAddress, hexlify, keccak256, toUtf8Bytes } = require("ethers");
381
384
  var ABI = require_abi();
382
385
  var { BigIntZero } = require_types();
383
386
  var { SageSDKError, CODES } = require_errors();
@@ -518,6 +521,15 @@ var require_governance = __commonJS({
518
521
  function hashDescription(description) {
519
522
  return keccak256(toUtf8Bytes(String(description)));
520
523
  }
524
+ function computeProposalIdHex({ targets = [], values = [], calldatas = [], description = "" }) {
525
+ const coder = AbiCoder.defaultAbiCoder();
526
+ const t = targets.map((a) => getAddress(a));
527
+ const v = values.map((value) => BigInt(String(value)));
528
+ const c = calldatas.map((data) => hexlify(data));
529
+ const descHash = hashDescription(description);
530
+ const encoded = coder.encode(["address[]", "uint256[]", "bytes[]", "bytes32"], [t, v, c, descHash]);
531
+ return keccak256(encoded);
532
+ }
521
533
  function buildProposeTx({ governor, targets = [], values = [], calldatas = [], descriptionOrHash = "" }) {
522
534
  const addr = normaliseGovernor(governor);
523
535
  const iface = new Interface(ABI.Governor);
@@ -594,6 +606,73 @@ var require_governance = __commonJS({
594
606
  const tag = latest > 0 ? BigInt(latest - 1) : 0n;
595
607
  return getQuorumAt({ provider, governor, blockTag: tag });
596
608
  }
609
+ async function getVotesLatestMinusOne({ provider, token: token2, governor, account }) {
610
+ if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
611
+ if (!token2 && !governor) throw new SageSDKError(CODES.INVALID_ARGS, "token or governor required");
612
+ if (!account) throw new SageSDKError(CODES.INVALID_ARGS, "account required");
613
+ let addr;
614
+ if (token2) {
615
+ addr = getAddress(token2);
616
+ } else {
617
+ const govAddr = normaliseGovernor(governor);
618
+ try {
619
+ const iface = new Interface(["function sxxxToken() view returns (address)"]);
620
+ const data = iface.encodeFunctionData("sxxxToken", []);
621
+ const ret = await provider.call({ to: govAddr, data });
622
+ const [tok] = AbiCoder.defaultAbiCoder().decode(["address"], ret);
623
+ addr = getAddress(tok);
624
+ } catch (err) {
625
+ throw new SageSDKError(CODES.NOT_FOUND, "failed to resolve governance token from governor", { cause: err });
626
+ }
627
+ }
628
+ const user = getAddress(account);
629
+ const latest = await provider.getBlockNumber();
630
+ const snapshot = latest > 0 ? BigInt(latest - 1) : 0n;
631
+ try {
632
+ const iface = new Interface(["function getPastVotes(address,uint256) view returns (uint256)"]);
633
+ const data = iface.encodeFunctionData("getPastVotes", [user, snapshot]);
634
+ const ret = await provider.call({ to: addr, data });
635
+ if (ret && ret !== "0x") {
636
+ const [val] = AbiCoder.defaultAbiCoder().decode(["uint256"], ret);
637
+ return BigInt(val.toString());
638
+ }
639
+ } catch (_) {
640
+ }
641
+ try {
642
+ const iface2 = new Interface(["function getVotes(address) view returns (uint256)"]);
643
+ const d2 = iface2.encodeFunctionData("getVotes", [user]);
644
+ const ret2 = await provider.call({ to: addr, data: d2 });
645
+ if (ret2 && ret2 !== "0x") {
646
+ const [val2] = AbiCoder.defaultAbiCoder().decode(["uint256"], ret2);
647
+ return BigInt(val2.toString());
648
+ }
649
+ } catch (_) {
650
+ }
651
+ return 0n;
652
+ }
653
+ async function simulatePropose({ provider, governor, targets = [], values = [], calldatas = [], description = "", sender }) {
654
+ if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
655
+ const addr = normaliseGovernor(governor);
656
+ const iface = new Interface(ABI.Governor);
657
+ const salted = makeProposalDescription(String(description || ""));
658
+ const t = targets.map((a) => getAddress(a));
659
+ const v = values.map((value) => BigInt(String(value)));
660
+ const c = calldatas.map((data2) => hexlify(data2));
661
+ let data;
662
+ try {
663
+ data = iface.encodeFunctionData("propose(address[],uint256[],bytes[],string)", [t, v, c, salted]);
664
+ } catch (err) {
665
+ return { ok: false, error: { type: "EncodeError", message: String(err && err.message || err) } };
666
+ }
667
+ try {
668
+ await provider.call({ to: addr, data, from: sender ? getAddress(sender) : void 0 });
669
+ return { ok: true };
670
+ } catch (err) {
671
+ let message = "execution reverted";
672
+ if (err && err.message) message = err.message;
673
+ return { ok: false, error: { type: "Revert", message } };
674
+ }
675
+ }
597
676
  async function detectGovernorOverloads({ provider, governor }) {
598
677
  if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
599
678
  const addr = normaliseGovernor(governor);
@@ -643,6 +722,25 @@ var require_governance = __commonJS({
643
722
  detectGovernorOverloads,
644
723
  buildDelegateTx,
645
724
  buildDelegateSelfTx,
725
+ computeProposalIdHex,
726
+ getVotesLatestMinusOne,
727
+ simulatePropose,
728
+ /**
729
+ * Explicit arrays+bytes32 builder for propose-by-hash governors or deterministic descriptions.
730
+ */
731
+ buildProposeTxByHash: function buildProposeTxByHash({ governor, targets = [], values = [], calldatas = [], descriptionHash }) {
732
+ const addr = normaliseGovernor(governor);
733
+ const iface = new Interface(ABI.Governor);
734
+ const t = targets.map((a) => getAddress(a));
735
+ const v = values.map((value) => BigInt(String(value)));
736
+ const c = calldatas.map((data2) => hexlify(data2));
737
+ const dh = typeof descriptionHash === "string" && /^0x[0-9a-fA-F]{64}$/.test(descriptionHash) ? descriptionHash : hashDescription(String(descriptionHash || ""));
738
+ if (t.length !== v.length || t.length !== c.length) {
739
+ throw new SageSDKError(CODES.INVALID_ARGS, "targets, values, calldatas length mismatch");
740
+ }
741
+ const data = iface.encodeFunctionData("propose(address[],uint256[],bytes[],bytes32)", [t, v, c, dh]);
742
+ return { to: addr, data, value: BigIntZero };
743
+ },
646
744
  /**
647
745
  * Detect governance mode and operator status.
648
746
  * Heuristics mirror CLI detectGovMode.
@@ -2169,11 +2267,24 @@ var require_factory = __commonJS({
2169
2267
  }
2170
2268
  return out;
2171
2269
  }
2270
+ async function getSubDAORegistry({ provider, factory: factory2, subdao: subdao2 }) {
2271
+ if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2272
+ const addr = normalise(factory2, "factory");
2273
+ const contract = new Contract(addr, ABI.FactoryRead, provider);
2274
+ try {
2275
+ const mapped = await contract.subdaoToRegistry(normalise(subdao2, "subdao"));
2276
+ const zero = "0x0000000000000000000000000000000000000000";
2277
+ return mapped && mapped !== zero ? getAddress(mapped) : null;
2278
+ } catch (_) {
2279
+ return null;
2280
+ }
2281
+ }
2172
2282
  module2.exports = {
2173
2283
  getFactoryConfig,
2174
2284
  getFactoryStats,
2175
2285
  listSubDAOs,
2176
2286
  listSubDAOsIndexed,
2287
+ getSubDAORegistry,
2177
2288
  listTemplates,
2178
2289
  getTemplateDetails,
2179
2290
  buildCreateSubDAOTx,
@@ -3100,7 +3211,20 @@ var require_library = __commonJS({
3100
3211
  hasScopedOwnership,
3101
3212
  buildUpdateLibraryForSubDAOTx,
3102
3213
  searchRegistry,
3103
- validation
3214
+ validation,
3215
+ /** Simulate calling a registry function as the timelock to verify authority/roles */
3216
+ simulateAsTimelock: async function simulateAsTimelock({ provider, registry, to, data, timelock: timelock2 }) {
3217
+ if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
3218
+ const reg = normalise(registry, "registry");
3219
+ const tgt = to ? normalise(to, "to") : reg;
3220
+ const from = normalise(timelock2, "timelock");
3221
+ try {
3222
+ await provider.call({ to: tgt, data, from });
3223
+ return { ok: true };
3224
+ } catch (err) {
3225
+ return { ok: false, error: { type: "Revert", message: String(err && err.message || err) } };
3226
+ }
3227
+ }
3104
3228
  };
3105
3229
  }
3106
3230
  });
package/dist/index.mjs CHANGED
@@ -20,7 +20,7 @@ var require_package = __commonJS({
20
20
  "package.json"(exports, module) {
21
21
  module.exports = {
22
22
  name: "@sage-protocol/sdk",
23
- version: "0.0.5",
23
+ version: "0.0.7",
24
24
  description: "Backend-agnostic SDK for interacting with the Sage Protocol (governance, SubDAOs, tokens).",
25
25
  main: "dist/index.cjs",
26
26
  module: "dist/index.mjs",
@@ -113,6 +113,8 @@ var require_abi = __commonJS({
113
113
  "function simpleKeyStoreAddress() view returns (address)",
114
114
  "function governanceConfigAddress() view returns (address)",
115
115
  "function libraryRegistryAddress() view returns (address)",
116
+ // Canonical per‑SubDAO mapping
117
+ "function subdaoToRegistry(address) view returns (address)",
116
118
  "function templateModule() view returns (address)",
117
119
  "function stats() view returns (uint128 totalSubDAOsCreated, uint128 totalBurnedForCreation)",
118
120
  // On-chain enumeration fallback (naming follows FactoryCoreFacet)
@@ -200,6 +202,7 @@ var require_abi = __commonJS({
200
202
  var ERC20Votes = [
201
203
  "function balanceOf(address) view returns (uint256)",
202
204
  "function getVotes(address) view returns (uint256)",
205
+ "function getPastVotes(address,uint256) view returns (uint256)",
203
206
  "function allowance(address,address) view returns (uint256)",
204
207
  "function delegate(address)",
205
208
  "function delegates(address) view returns (address)",
@@ -383,7 +386,7 @@ var require_description = __commonJS({
383
386
  // src/governance/index.js
384
387
  var require_governance = __commonJS({
385
388
  "src/governance/index.js"(exports, module) {
386
- var { Contract, Interface, getAddress, hexlify, keccak256, toUtf8Bytes } = __require("ethers");
389
+ var { Contract, Interface, AbiCoder, getAddress, hexlify, keccak256, toUtf8Bytes } = __require("ethers");
387
390
  var ABI = require_abi();
388
391
  var { BigIntZero } = require_types();
389
392
  var { SageSDKError, CODES } = require_errors();
@@ -524,6 +527,15 @@ var require_governance = __commonJS({
524
527
  function hashDescription(description) {
525
528
  return keccak256(toUtf8Bytes(String(description)));
526
529
  }
530
+ function computeProposalIdHex({ targets = [], values = [], calldatas = [], description = "" }) {
531
+ const coder = AbiCoder.defaultAbiCoder();
532
+ const t = targets.map((a) => getAddress(a));
533
+ const v = values.map((value) => BigInt(String(value)));
534
+ const c = calldatas.map((data) => hexlify(data));
535
+ const descHash = hashDescription(description);
536
+ const encoded = coder.encode(["address[]", "uint256[]", "bytes[]", "bytes32"], [t, v, c, descHash]);
537
+ return keccak256(encoded);
538
+ }
527
539
  function buildProposeTx({ governor, targets = [], values = [], calldatas = [], descriptionOrHash = "" }) {
528
540
  const addr = normaliseGovernor(governor);
529
541
  const iface = new Interface(ABI.Governor);
@@ -600,6 +612,73 @@ var require_governance = __commonJS({
600
612
  const tag = latest > 0 ? BigInt(latest - 1) : 0n;
601
613
  return getQuorumAt({ provider, governor, blockTag: tag });
602
614
  }
615
+ async function getVotesLatestMinusOne({ provider, token, governor, account }) {
616
+ if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
617
+ if (!token && !governor) throw new SageSDKError(CODES.INVALID_ARGS, "token or governor required");
618
+ if (!account) throw new SageSDKError(CODES.INVALID_ARGS, "account required");
619
+ let addr;
620
+ if (token) {
621
+ addr = getAddress(token);
622
+ } else {
623
+ const govAddr = normaliseGovernor(governor);
624
+ try {
625
+ const iface = new Interface(["function sxxxToken() view returns (address)"]);
626
+ const data = iface.encodeFunctionData("sxxxToken", []);
627
+ const ret = await provider.call({ to: govAddr, data });
628
+ const [tok] = AbiCoder.defaultAbiCoder().decode(["address"], ret);
629
+ addr = getAddress(tok);
630
+ } catch (err) {
631
+ throw new SageSDKError(CODES.NOT_FOUND, "failed to resolve governance token from governor", { cause: err });
632
+ }
633
+ }
634
+ const user = getAddress(account);
635
+ const latest = await provider.getBlockNumber();
636
+ const snapshot = latest > 0 ? BigInt(latest - 1) : 0n;
637
+ try {
638
+ const iface = new Interface(["function getPastVotes(address,uint256) view returns (uint256)"]);
639
+ const data = iface.encodeFunctionData("getPastVotes", [user, snapshot]);
640
+ const ret = await provider.call({ to: addr, data });
641
+ if (ret && ret !== "0x") {
642
+ const [val] = AbiCoder.defaultAbiCoder().decode(["uint256"], ret);
643
+ return BigInt(val.toString());
644
+ }
645
+ } catch (_) {
646
+ }
647
+ try {
648
+ const iface2 = new Interface(["function getVotes(address) view returns (uint256)"]);
649
+ const d2 = iface2.encodeFunctionData("getVotes", [user]);
650
+ const ret2 = await provider.call({ to: addr, data: d2 });
651
+ if (ret2 && ret2 !== "0x") {
652
+ const [val2] = AbiCoder.defaultAbiCoder().decode(["uint256"], ret2);
653
+ return BigInt(val2.toString());
654
+ }
655
+ } catch (_) {
656
+ }
657
+ return 0n;
658
+ }
659
+ async function simulatePropose({ provider, governor, targets = [], values = [], calldatas = [], description = "", sender }) {
660
+ if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
661
+ const addr = normaliseGovernor(governor);
662
+ const iface = new Interface(ABI.Governor);
663
+ const salted = makeProposalDescription(String(description || ""));
664
+ const t = targets.map((a) => getAddress(a));
665
+ const v = values.map((value) => BigInt(String(value)));
666
+ const c = calldatas.map((data2) => hexlify(data2));
667
+ let data;
668
+ try {
669
+ data = iface.encodeFunctionData("propose(address[],uint256[],bytes[],string)", [t, v, c, salted]);
670
+ } catch (err) {
671
+ return { ok: false, error: { type: "EncodeError", message: String(err && err.message || err) } };
672
+ }
673
+ try {
674
+ await provider.call({ to: addr, data, from: sender ? getAddress(sender) : void 0 });
675
+ return { ok: true };
676
+ } catch (err) {
677
+ let message = "execution reverted";
678
+ if (err && err.message) message = err.message;
679
+ return { ok: false, error: { type: "Revert", message } };
680
+ }
681
+ }
603
682
  async function detectGovernorOverloads({ provider, governor }) {
604
683
  if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
605
684
  const addr = normaliseGovernor(governor);
@@ -649,6 +728,25 @@ var require_governance = __commonJS({
649
728
  detectGovernorOverloads,
650
729
  buildDelegateTx,
651
730
  buildDelegateSelfTx,
731
+ computeProposalIdHex,
732
+ getVotesLatestMinusOne,
733
+ simulatePropose,
734
+ /**
735
+ * Explicit arrays+bytes32 builder for propose-by-hash governors or deterministic descriptions.
736
+ */
737
+ buildProposeTxByHash: function buildProposeTxByHash({ governor, targets = [], values = [], calldatas = [], descriptionHash }) {
738
+ const addr = normaliseGovernor(governor);
739
+ const iface = new Interface(ABI.Governor);
740
+ const t = targets.map((a) => getAddress(a));
741
+ const v = values.map((value) => BigInt(String(value)));
742
+ const c = calldatas.map((data2) => hexlify(data2));
743
+ const dh = typeof descriptionHash === "string" && /^0x[0-9a-fA-F]{64}$/.test(descriptionHash) ? descriptionHash : hashDescription(String(descriptionHash || ""));
744
+ if (t.length !== v.length || t.length !== c.length) {
745
+ throw new SageSDKError(CODES.INVALID_ARGS, "targets, values, calldatas length mismatch");
746
+ }
747
+ const data = iface.encodeFunctionData("propose(address[],uint256[],bytes[],bytes32)", [t, v, c, dh]);
748
+ return { to: addr, data, value: BigIntZero };
749
+ },
652
750
  /**
653
751
  * Detect governance mode and operator status.
654
752
  * Heuristics mirror CLI detectGovMode.
@@ -2175,11 +2273,24 @@ var require_factory = __commonJS({
2175
2273
  }
2176
2274
  return out;
2177
2275
  }
2276
+ async function getSubDAORegistry({ provider, factory, subdao }) {
2277
+ if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2278
+ const addr = normalise(factory, "factory");
2279
+ const contract = new Contract(addr, ABI.FactoryRead, provider);
2280
+ try {
2281
+ const mapped = await contract.subdaoToRegistry(normalise(subdao, "subdao"));
2282
+ const zero = "0x0000000000000000000000000000000000000000";
2283
+ return mapped && mapped !== zero ? getAddress(mapped) : null;
2284
+ } catch (_) {
2285
+ return null;
2286
+ }
2287
+ }
2178
2288
  module.exports = {
2179
2289
  getFactoryConfig,
2180
2290
  getFactoryStats,
2181
2291
  listSubDAOs,
2182
2292
  listSubDAOsIndexed,
2293
+ getSubDAORegistry,
2183
2294
  listTemplates,
2184
2295
  getTemplateDetails,
2185
2296
  buildCreateSubDAOTx,
@@ -3106,7 +3217,20 @@ var require_library = __commonJS({
3106
3217
  hasScopedOwnership,
3107
3218
  buildUpdateLibraryForSubDAOTx,
3108
3219
  searchRegistry,
3109
- validation
3220
+ validation,
3221
+ /** Simulate calling a registry function as the timelock to verify authority/roles */
3222
+ simulateAsTimelock: async function simulateAsTimelock({ provider, registry, to, data, timelock }) {
3223
+ if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
3224
+ const reg = normalise(registry, "registry");
3225
+ const tgt = to ? normalise(to, "to") : reg;
3226
+ const from = normalise(timelock, "timelock");
3227
+ try {
3228
+ await provider.call({ to: tgt, data, from });
3229
+ return { ok: true };
3230
+ } catch (err) {
3231
+ return { ok: false, error: { type: "Revert", message: String(err && err.message || err) } };
3232
+ }
3233
+ }
3110
3234
  };
3111
3235
  }
3112
3236
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sage-protocol/sdk",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Backend-agnostic SDK for interacting with the Sage Protocol (governance, SubDAOs, tokens).",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.mjs",