@sage-protocol/sdk 0.1.14 → 0.1.16

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
@@ -32,8 +32,9 @@ const subdaos = await sdk.subdao.discoverSubDAOs({
32
32
  fromBlock: 0,
33
33
  });
34
34
 
35
- // Fetch governance metadata for a SubDAO governor
36
- const info = await sdk.governance.getGovernorInfo({ provider, governor: subdaos[0].governor });
35
+ // Fetch governance + profile metadata for a SubDAO
36
+ const info = await sdk.subdao.getSubDAOInfo({ provider, subdao: subdaos[0].subdao });
37
+ // info.profileCID points at an IPFS JSON profile/playbook document (name, description, avatar, social links, etc.)
37
38
  ```
38
39
 
39
40
  **CommonJS usage**
@@ -641,6 +642,12 @@ await sdk.subdao.makeOperator({ signer, subdao: res.subdao, operator: '0xSafeOrE
641
642
 
642
643
  // Ensure SXXX approval for factory burn if needed
643
644
  await sdk.subdao.ensureSxxxBurnAllowance({ signer, sxxx: SXXX, spender: FACTORY, amount: 1500n * 10n**18n });
645
+
646
+ // (Optional) Build a setProfileCid tx for newer SubDAO implementations that expose setProfileCid(string)
647
+ // Use this in a Governor/Timelock proposal or Safe Transaction Builder, not as a direct EOA call.
648
+ const profileCid = 'bafy...'; // CID of dao-profile.json on IPFS
649
+ const { to, data, value } = sdk.subdao.buildSetProfileCidTx({ subdao: res.subdao, profileCid });
650
+ // Example: schedule via timelock or propose via Governor, depending on your governance mode
644
651
  ```
645
652
  # Governance Adapter (OpenZeppelin)
646
653
 
@@ -14,7 +14,7 @@ var require_package = __commonJS({
14
14
  "package.json"(exports, module) {
15
15
  module.exports = {
16
16
  name: "@sage-protocol/sdk",
17
- version: "0.1.14",
17
+ version: "0.1.16",
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",
@@ -172,12 +172,13 @@ var require_abi = __commonJS({
172
172
  "function maxCreationBurn() view returns (uint256)"
173
173
  ];
174
174
  var LibraryRegistry = [
175
- "function getManifestCIDCount() view returns (uint256)",
176
- "function getManifestCIDs(uint256 offset, uint256 limit) view returns (string[] memory, uint256 total)",
177
- "function getManifestCID(uint256 index) view returns (string)",
178
- "function libraries(string manifestCID) view returns (string previousCID, uint256 timestamp, address proposer, uint256 promptCount)",
179
- "function subdaoLibraryLatest(bytes32 key) view returns (string)",
180
- "function hasScopedOwnership(address subdao, string manifestCID) view returns (bool)"
175
+ "function libraryByDAO(address) view returns (string manifestCID, address lastUpdater, uint256 lastUpdated, string version)",
176
+ "function daoTimelock(address) view returns (address)",
177
+ "function getLibrary(address) view returns (tuple(string manifestCID, address lastUpdater, uint256 lastUpdated, string version))",
178
+ "function updateLibrary(address dao, string manifestCID, string version)",
179
+ "function registerDAO(address dao, address timelock)",
180
+ "event LibraryUpdated(address indexed dao, string manifestCID, address indexed timelock, string version)",
181
+ "event DAORegistered(address indexed dao, address indexed timelock)"
181
182
  ];
182
183
  var PromptRegistry = [
183
184
  "function prompts(string key) view returns (string cid, uint256 version, uint256 timestamp, address author, string forkedFromCID, address originalAuthor, bool isFork, uint256 forkDepth)",
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.1.14",
17
+ version: "0.1.16",
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",
@@ -172,12 +172,13 @@ var require_abi = __commonJS({
172
172
  "function maxCreationBurn() view returns (uint256)"
173
173
  ];
174
174
  var LibraryRegistry = [
175
- "function getManifestCIDCount() view returns (uint256)",
176
- "function getManifestCIDs(uint256 offset, uint256 limit) view returns (string[] memory, uint256 total)",
177
- "function getManifestCID(uint256 index) view returns (string)",
178
- "function libraries(string manifestCID) view returns (string previousCID, uint256 timestamp, address proposer, uint256 promptCount)",
179
- "function subdaoLibraryLatest(bytes32 key) view returns (string)",
180
- "function hasScopedOwnership(address subdao, string manifestCID) view returns (bool)"
175
+ "function libraryByDAO(address) view returns (string manifestCID, address lastUpdater, uint256 lastUpdated, string version)",
176
+ "function daoTimelock(address) view returns (address)",
177
+ "function getLibrary(address) view returns (tuple(string manifestCID, address lastUpdater, uint256 lastUpdated, string version))",
178
+ "function updateLibrary(address dao, string manifestCID, string version)",
179
+ "function registerDAO(address dao, address timelock)",
180
+ "event LibraryUpdated(address indexed dao, string manifestCID, address indexed timelock, string version)",
181
+ "event DAORegistered(address indexed dao, address indexed timelock)"
181
182
  ];
182
183
  var PromptRegistry = [
183
184
  "function prompts(string key) view returns (string cid, uint256 version, uint256 timestamp, address author, string forkedFromCID, address originalAuthor, bool isFork, uint256 forkDepth)",
@@ -1231,15 +1232,35 @@ var require_ipfs = __commonJS({
1231
1232
  if (metadata) {
1232
1233
  formData.append("pinataMetadata", JSON.stringify(metadata));
1233
1234
  }
1234
- const headers = { ...formData.getHeaders() };
1235
+ const endpoint = `${config.pinata.apiUrl}/pinning/pinFileToIPFS`;
1236
+ const sendWithAuthMode = async (mode) => {
1237
+ const headers = { ...formData.getHeaders() };
1238
+ if (mode === "jwt") {
1239
+ headers.Authorization = `Bearer ${config.pinata.jwt}`;
1240
+ } else {
1241
+ headers.pinata_api_key = config.pinata.apiKey;
1242
+ headers.pinata_secret_api_key = config.pinata.secretKey;
1243
+ }
1244
+ return axiosInstance.post(endpoint, formData, { headers, timeout: config.timeoutMs });
1245
+ };
1246
+ let response;
1247
+ let lastError;
1235
1248
  if (config.pinata.jwt) {
1236
- headers.Authorization = `Bearer ${config.pinata.jwt}`;
1249
+ try {
1250
+ response = await sendWithAuthMode("jwt");
1251
+ } catch (error) {
1252
+ lastError = error;
1253
+ const status = error?.response?.status;
1254
+ const reason = error?.response?.data?.error?.reason || "";
1255
+ const isInvalidJwt = status === 401 && typeof reason === "string" && reason.toUpperCase().includes("INVALID_CREDENTIALS");
1256
+ if (!config.pinata.apiKey || !config.pinata.secretKey || !isInvalidJwt) {
1257
+ throw error;
1258
+ }
1259
+ response = await sendWithAuthMode("keys");
1260
+ }
1237
1261
  } else {
1238
- headers.pinata_api_key = config.pinata.apiKey;
1239
- headers.pinata_secret_api_key = config.pinata.secretKey;
1262
+ response = await sendWithAuthMode("keys");
1240
1263
  }
1241
- const endpoint = `${config.pinata.apiUrl}/pinning/pinFileToIPFS`;
1242
- const response = await axiosInstance.post(endpoint, formData, { headers, timeout: config.timeoutMs });
1243
1264
  const cid = response?.data?.IpfsHash || response?.data?.cid || response?.data?.Hash;
1244
1265
  if (!cid) {
1245
1266
  throw new Error("Pinata upload did not return IpfsHash");
@@ -1875,7 +1896,7 @@ var require_validation = __commonJS({
1875
1896
  schemaPath: null,
1876
1897
  errors: [{ keyword: "schema", instancePath: "/", message: "manifest schema not found" }],
1877
1898
  hints: [
1878
- 'Ensure docs/schemas/manifest.schema.json exists (run "sage library scaffold" to regenerate)'
1899
+ 'Ensure docs/schemas/manifest.schema.json exists (run "sage project scaffold" to regenerate)'
1879
1900
  ],
1880
1901
  manifestPath
1881
1902
  };
@@ -2016,93 +2037,95 @@ var require_library = __commonJS({
2016
2037
  throw new SageSDKError(CODES.INVALID_ARGS, `invalid ${label}`, { cause: err });
2017
2038
  }
2018
2039
  }
2019
- async function listManifests({ provider, registry, offset = 0, limit = 50 }) {
2040
+ async function listManifests({ provider, registry, factoryAddress, offset = 0, limit = 50 }) {
2020
2041
  if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2021
- const addr = normalise(registry, "registry");
2022
- const contract = new Contract(addr, ABI.LibraryRegistry, provider);
2023
- const [cids, total] = await contract.getManifestCIDs(offset, limit);
2042
+ if (!factoryAddress) throw new SageSDKError(CODES.INVALID_ARGS, "factoryAddress required for V4 listing");
2043
+ const factory2 = new Contract(factoryAddress, ABI.FactoryRead, provider);
2044
+ const totalSubDAOs = await factory2.getSubDAOCount();
2024
2045
  const manifests = [];
2025
- for (const cid of cids) {
2026
- manifests.push(await getManifestInfo({ provider, registry: addr, manifestCID: cid }));
2046
+ const start = Number(offset);
2047
+ const end = Math.min(start + Number(limit), Number(totalSubDAOs));
2048
+ for (let i = start; i < end; i++) {
2049
+ const subdao2 = await factory2.subDaos(i);
2050
+ const info = await getLatestLibrary({ provider, registry, subdao: subdao2 });
2051
+ if (info && info.manifestCID) {
2052
+ manifests.push(info);
2053
+ }
2027
2054
  }
2028
- return { total: BigInt(total.toString()), manifests };
2055
+ return { total: BigInt(totalSubDAOs), manifests };
2029
2056
  }
2030
- async function getManifestInfo({ provider, registry, manifestCID }) {
2057
+ async function getLibraryInfo({ provider, registry, subdao: subdao2 }) {
2031
2058
  if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2032
- if (!manifestCID) throw new SageSDKError(CODES.INVALID_ARGS, "manifestCID required");
2059
+ if (!subdao2) throw new SageSDKError(CODES.INVALID_ARGS, "subdao required");
2033
2060
  const addr = normalise(registry, "registry");
2061
+ const dao = normalise(subdao2, "subdao");
2034
2062
  const contract = new Contract(addr, ABI.LibraryRegistry, provider);
2035
- const [previousCID, timestamp, proposer, promptCount] = await contract.libraries(manifestCID);
2063
+ const info = await contract.getLibrary(dao);
2036
2064
  return {
2037
- manifestCID,
2038
- previousCID,
2039
- timestamp: BigInt(timestamp.toString()),
2040
- proposer: getAddress(proposer),
2041
- promptCount: Number(promptCount)
2065
+ manifestCID: info.manifestCID,
2066
+ previousCID: "",
2067
+ // Not tracked on-chain in V4 (history is in events/subgraph)
2068
+ timestamp: BigInt(info.lastUpdated.toString()),
2069
+ proposer: getAddress(info.lastUpdater),
2070
+ promptCount: 0,
2071
+ // Not tracked on-chain in V4
2072
+ version: info.version
2042
2073
  };
2043
2074
  }
2075
+ async function getManifestInfo({ provider, registry, manifestCID }) {
2076
+ throw new SageSDKError(CODES.UNSUPPORTED_OPERATION, "getManifestInfo(cid) is not supported in V4. Use getLibraryInfo(subdao).");
2077
+ }
2044
2078
  function _computeLibraryKey(subdao2, libraryId) {
2045
- const coder = AbiCoder.defaultAbiCoder ? AbiCoder.defaultAbiCoder() : new AbiCoder();
2046
- const encoded = coder.encode(["address", "string"], [getAddress(subdao2), String(libraryId)]);
2047
- return keccak256(encoded);
2079
+ return getAddress(subdao2);
2048
2080
  }
2049
- async function getLatestLibrary({ provider, registry, subdao: subdao2, libraryId = "main" }) {
2050
- if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2051
- const addr = normalise(registry, "registry");
2052
- const sub = normalise(subdao2, "subdao");
2053
- const contract = new Contract(addr, ABI.LibraryRegistry, provider);
2054
- const key = _computeLibraryKey(sub, libraryId);
2055
- const latestCID = await contract.subdaoLibraryLatest(key).catch(() => "");
2056
- if (!latestCID || latestCID.length === 0) return null;
2057
- const info = await getManifestInfo({ provider, registry: addr, manifestCID: latestCID });
2058
- return info;
2081
+ async function getLatestLibrary({ provider, registry, subdao: subdao2 }) {
2082
+ return getLibraryInfo({ provider, registry, subdao: subdao2 });
2059
2083
  }
2060
- async function getBeforeAfterForUpdate({ provider, registry, subdao: subdao2, libraryId = "main", newCid }) {
2084
+ async function getBeforeAfterForUpdate({ provider, registry, subdao: subdao2, newCid }) {
2061
2085
  if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2062
- const addr = normalise(registry, "registry");
2063
- const sub = normalise(subdao2, "subdao");
2064
- const contract = new Contract(addr, ABI.LibraryRegistry, provider);
2065
- const key = _computeLibraryKey(sub, libraryId);
2066
- const prev = await contract.subdaoLibraryLatest(key).catch(() => "");
2067
- return { previousCID: prev || null, newCID: String(newCid), libraryId: String(libraryId) };
2086
+ const info = await getLatestLibrary({ provider, registry, subdao: subdao2 });
2087
+ return {
2088
+ previousCID: info ? info.manifestCID : null,
2089
+ newCID: String(newCid),
2090
+ version: info ? info.version : "0.0.0"
2091
+ };
2068
2092
  }
2069
2093
  async function hasScopedOwnership({ provider, registry, subdao: subdao2, manifestCID }) {
2070
- if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2071
- const addr = normalise(registry, "registry");
2072
- const sub = normalise(subdao2, "subdao");
2073
- const contract = new Contract(addr, ABI.LibraryRegistry, provider);
2074
- return contract.hasScopedOwnership(sub, manifestCID);
2094
+ const info = await getLatestLibrary({ provider, registry, subdao: subdao2 });
2095
+ return info && info.manifestCID === manifestCID;
2075
2096
  }
2076
- function buildUpdateLibraryForSubDAOTx({ registry, subdao: subdao2, manifestCID, promptCount, libraryId = "main" }) {
2097
+ function buildUpdateLibraryTx({ registry, subdao: subdao2, manifestCID, version = "1.0.0" }) {
2077
2098
  const to = normalise(registry, "registry");
2078
2099
  if (!manifestCID) throw new SageSDKError(CODES.INVALID_ARGS, "manifestCID required");
2079
- const iface = new Interface(["function updateLibraryForSubDAO(address,string,string,uint256)"]);
2080
- const data = iface.encodeFunctionData("updateLibraryForSubDAO", [
2100
+ const iface = new Interface(["function updateLibrary(address,string,string)"]);
2101
+ const data = iface.encodeFunctionData("updateLibrary", [
2081
2102
  normalise(subdao2, "subdao"),
2082
- String(libraryId || "main"),
2083
2103
  String(manifestCID),
2084
- BigInt(promptCount ?? 0n)
2104
+ String(version)
2085
2105
  ]);
2086
2106
  return { to, data, value: 0n };
2087
2107
  }
2108
+ var buildUpdateLibraryForSubDAOTx = buildUpdateLibraryTx;
2088
2109
  module2.exports = {
2089
2110
  listManifests,
2090
2111
  getManifestInfo,
2112
+ getLibraryInfo,
2091
2113
  getLatestLibrary,
2092
2114
  hasScopedOwnership,
2115
+ buildUpdateLibraryTx,
2093
2116
  buildUpdateLibraryForSubDAOTx,
2094
2117
  _computeLibraryKey,
2095
2118
  getBeforeAfterForUpdate,
2096
2119
  /** Build an authorizeTimelock(timelock, subdao) call for a LibraryRegistry. */
2097
2120
  buildAuthorizeTimelockTx: function buildAuthorizeTimelockTx({ registry, timelock: timelock2, subdao: subdao2 }) {
2098
2121
  const to = normalise(registry, "registry");
2099
- const iface = new Interface(["function authorizeTimelock(address,address)"]);
2100
- const data = iface.encodeFunctionData("authorizeTimelock", [normalise(timelock2, "timelock"), normalise(subdao2, "subdao")]);
2122
+ const iface = new Interface(["function registerDAO(address,address)"]);
2123
+ const data = iface.encodeFunctionData("registerDAO", [normalise(subdao2, "subdao"), normalise(timelock2, "timelock")]);
2101
2124
  return { to, data, value: 0n };
2102
2125
  },
2103
2126
  searchRegistry,
2104
2127
  validation,
2105
- /** Simulate calling a registry function as the timelock to verify authority/roles */
2128
+ /** Simulate as timelock */
2106
2129
  simulateAsTimelock: async function simulateAsTimelock({ provider, registry, to, data, timelock: timelock2 }) {
2107
2130
  if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2108
2131
  const reg = normalise(registry, "registry");
@@ -2115,10 +2138,9 @@ var require_library = __commonJS({
2115
2138
  return { ok: false, error: { type: "Revert", message: String(err && err.message || err) } };
2116
2139
  }
2117
2140
  },
2118
- /** Best-effort execution readiness for updateLibraryForSubDAO by simulating as timelock. */
2119
- executionReadiness: async function executionReadiness({ provider, registry, timelock: timelock2, subdao: subdao2, libraryId = "main", manifestCID = "", promptCount = 0 }) {
2141
+ executionReadiness: async function executionReadiness({ provider, registry, timelock: timelock2, subdao: subdao2, manifestCID = "", version = "1.0.0" }) {
2120
2142
  if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2121
- const payload = buildUpdateLibraryForSubDAOTx({ registry, subdao: subdao2, manifestCID, promptCount, libraryId });
2143
+ const payload = buildUpdateLibraryTx({ registry, subdao: subdao2, manifestCID, version });
2122
2144
  const sim = await this.simulateAsTimelock({ provider, registry, to: payload.to, data: payload.data, timelock: timelock2 });
2123
2145
  const out = { ok: !!sim.ok, error: sim.ok ? null : sim.error?.message || "revert", missingRole: null };
2124
2146
  return out;
@@ -2931,7 +2953,9 @@ var require_subdao = __commonJS({
2931
2953
  }
2932
2954
  var SubDAOInterface = new Interface([
2933
2955
  "function stake(uint256)",
2934
- "function unstake(uint256)"
2956
+ "function unstake(uint256)",
2957
+ // Optional on newer SubDAO implementations; provided for tx building only.
2958
+ "function setProfileCid(string)"
2935
2959
  ]);
2936
2960
  function buildStakeTx({ subdao: subdao2, amount }) {
2937
2961
  const addr = normalise(subdao2, "subdao");
@@ -2943,12 +2967,21 @@ var require_subdao = __commonJS({
2943
2967
  const data = SubDAOInterface.encodeFunctionData("unstake", [BigInt(amount)]);
2944
2968
  return { to: addr, data, value: 0n };
2945
2969
  }
2970
+ function buildSetProfileCidTx({ subdao: subdao2, profileCid }) {
2971
+ const addr = normalise(subdao2, "subdao");
2972
+ if (!profileCid || typeof profileCid !== "string" || !profileCid.trim()) {
2973
+ throw new SageSDKError(CODES.INVALID_ARGS, "profileCid (CID string) required");
2974
+ }
2975
+ const data = SubDAOInterface.encodeFunctionData("setProfileCid", [String(profileCid)]);
2976
+ return { to: addr, data, value: 0n };
2977
+ }
2946
2978
  module2.exports = {
2947
2979
  discoverSubDAOs,
2948
2980
  getSubDAOInfo,
2949
2981
  getSubDAOUserStats,
2950
2982
  buildStakeTx,
2951
- buildUnstakeTx
2983
+ buildUnstakeTx,
2984
+ buildSetProfileCidTx
2952
2985
  };
2953
2986
  async function ensureSxxxBurnAllowance({ signer, sxxx, owner, spender, amount }) {
2954
2987
  if (!signer) throw new SageSDKError(CODES.INVALID_ARGS, "signer required");
package/dist/index.mjs CHANGED
@@ -20,7 +20,7 @@ var require_package = __commonJS({
20
20
  "package.json"(exports2, module2) {
21
21
  module2.exports = {
22
22
  name: "@sage-protocol/sdk",
23
- version: "0.1.14",
23
+ version: "0.1.16",
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",
@@ -178,12 +178,13 @@ var require_abi = __commonJS({
178
178
  "function maxCreationBurn() view returns (uint256)"
179
179
  ];
180
180
  var LibraryRegistry = [
181
- "function getManifestCIDCount() view returns (uint256)",
182
- "function getManifestCIDs(uint256 offset, uint256 limit) view returns (string[] memory, uint256 total)",
183
- "function getManifestCID(uint256 index) view returns (string)",
184
- "function libraries(string manifestCID) view returns (string previousCID, uint256 timestamp, address proposer, uint256 promptCount)",
185
- "function subdaoLibraryLatest(bytes32 key) view returns (string)",
186
- "function hasScopedOwnership(address subdao, string manifestCID) view returns (bool)"
181
+ "function libraryByDAO(address) view returns (string manifestCID, address lastUpdater, uint256 lastUpdated, string version)",
182
+ "function daoTimelock(address) view returns (address)",
183
+ "function getLibrary(address) view returns (tuple(string manifestCID, address lastUpdater, uint256 lastUpdated, string version))",
184
+ "function updateLibrary(address dao, string manifestCID, string version)",
185
+ "function registerDAO(address dao, address timelock)",
186
+ "event LibraryUpdated(address indexed dao, string manifestCID, address indexed timelock, string version)",
187
+ "event DAORegistered(address indexed dao, address indexed timelock)"
187
188
  ];
188
189
  var PromptRegistry = [
189
190
  "function prompts(string key) view returns (string cid, uint256 version, uint256 timestamp, address author, string forkedFromCID, address originalAuthor, bool isFork, uint256 forkDepth)",
@@ -1237,15 +1238,35 @@ var require_ipfs = __commonJS({
1237
1238
  if (metadata) {
1238
1239
  formData.append("pinataMetadata", JSON.stringify(metadata));
1239
1240
  }
1240
- const headers = { ...formData.getHeaders() };
1241
+ const endpoint = `${config.pinata.apiUrl}/pinning/pinFileToIPFS`;
1242
+ const sendWithAuthMode = async (mode) => {
1243
+ const headers = { ...formData.getHeaders() };
1244
+ if (mode === "jwt") {
1245
+ headers.Authorization = `Bearer ${config.pinata.jwt}`;
1246
+ } else {
1247
+ headers.pinata_api_key = config.pinata.apiKey;
1248
+ headers.pinata_secret_api_key = config.pinata.secretKey;
1249
+ }
1250
+ return axiosInstance.post(endpoint, formData, { headers, timeout: config.timeoutMs });
1251
+ };
1252
+ let response;
1253
+ let lastError;
1241
1254
  if (config.pinata.jwt) {
1242
- headers.Authorization = `Bearer ${config.pinata.jwt}`;
1255
+ try {
1256
+ response = await sendWithAuthMode("jwt");
1257
+ } catch (error) {
1258
+ lastError = error;
1259
+ const status = error?.response?.status;
1260
+ const reason = error?.response?.data?.error?.reason || "";
1261
+ const isInvalidJwt = status === 401 && typeof reason === "string" && reason.toUpperCase().includes("INVALID_CREDENTIALS");
1262
+ if (!config.pinata.apiKey || !config.pinata.secretKey || !isInvalidJwt) {
1263
+ throw error;
1264
+ }
1265
+ response = await sendWithAuthMode("keys");
1266
+ }
1243
1267
  } else {
1244
- headers.pinata_api_key = config.pinata.apiKey;
1245
- headers.pinata_secret_api_key = config.pinata.secretKey;
1268
+ response = await sendWithAuthMode("keys");
1246
1269
  }
1247
- const endpoint = `${config.pinata.apiUrl}/pinning/pinFileToIPFS`;
1248
- const response = await axiosInstance.post(endpoint, formData, { headers, timeout: config.timeoutMs });
1249
1270
  const cid = response?.data?.IpfsHash || response?.data?.cid || response?.data?.Hash;
1250
1271
  if (!cid) {
1251
1272
  throw new Error("Pinata upload did not return IpfsHash");
@@ -1881,7 +1902,7 @@ var require_validation = __commonJS({
1881
1902
  schemaPath: null,
1882
1903
  errors: [{ keyword: "schema", instancePath: "/", message: "manifest schema not found" }],
1883
1904
  hints: [
1884
- 'Ensure docs/schemas/manifest.schema.json exists (run "sage library scaffold" to regenerate)'
1905
+ 'Ensure docs/schemas/manifest.schema.json exists (run "sage project scaffold" to regenerate)'
1885
1906
  ],
1886
1907
  manifestPath
1887
1908
  };
@@ -2022,93 +2043,95 @@ var require_library = __commonJS({
2022
2043
  throw new SageSDKError(CODES.INVALID_ARGS, `invalid ${label}`, { cause: err });
2023
2044
  }
2024
2045
  }
2025
- async function listManifests({ provider, registry, offset = 0, limit = 50 }) {
2046
+ async function listManifests({ provider, registry, factoryAddress, offset = 0, limit = 50 }) {
2026
2047
  if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2027
- const addr = normalise(registry, "registry");
2028
- const contract = new Contract(addr, ABI.LibraryRegistry, provider);
2029
- const [cids, total] = await contract.getManifestCIDs(offset, limit);
2048
+ if (!factoryAddress) throw new SageSDKError(CODES.INVALID_ARGS, "factoryAddress required for V4 listing");
2049
+ const factory = new Contract(factoryAddress, ABI.FactoryRead, provider);
2050
+ const totalSubDAOs = await factory.getSubDAOCount();
2030
2051
  const manifests = [];
2031
- for (const cid of cids) {
2032
- manifests.push(await getManifestInfo({ provider, registry: addr, manifestCID: cid }));
2052
+ const start = Number(offset);
2053
+ const end = Math.min(start + Number(limit), Number(totalSubDAOs));
2054
+ for (let i = start; i < end; i++) {
2055
+ const subdao = await factory.subDaos(i);
2056
+ const info = await getLatestLibrary({ provider, registry, subdao });
2057
+ if (info && info.manifestCID) {
2058
+ manifests.push(info);
2059
+ }
2033
2060
  }
2034
- return { total: BigInt(total.toString()), manifests };
2061
+ return { total: BigInt(totalSubDAOs), manifests };
2035
2062
  }
2036
- async function getManifestInfo({ provider, registry, manifestCID }) {
2063
+ async function getLibraryInfo({ provider, registry, subdao }) {
2037
2064
  if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2038
- if (!manifestCID) throw new SageSDKError(CODES.INVALID_ARGS, "manifestCID required");
2065
+ if (!subdao) throw new SageSDKError(CODES.INVALID_ARGS, "subdao required");
2039
2066
  const addr = normalise(registry, "registry");
2067
+ const dao = normalise(subdao, "subdao");
2040
2068
  const contract = new Contract(addr, ABI.LibraryRegistry, provider);
2041
- const [previousCID, timestamp, proposer, promptCount] = await contract.libraries(manifestCID);
2069
+ const info = await contract.getLibrary(dao);
2042
2070
  return {
2043
- manifestCID,
2044
- previousCID,
2045
- timestamp: BigInt(timestamp.toString()),
2046
- proposer: getAddress(proposer),
2047
- promptCount: Number(promptCount)
2071
+ manifestCID: info.manifestCID,
2072
+ previousCID: "",
2073
+ // Not tracked on-chain in V4 (history is in events/subgraph)
2074
+ timestamp: BigInt(info.lastUpdated.toString()),
2075
+ proposer: getAddress(info.lastUpdater),
2076
+ promptCount: 0,
2077
+ // Not tracked on-chain in V4
2078
+ version: info.version
2048
2079
  };
2049
2080
  }
2081
+ async function getManifestInfo({ provider, registry, manifestCID }) {
2082
+ throw new SageSDKError(CODES.UNSUPPORTED_OPERATION, "getManifestInfo(cid) is not supported in V4. Use getLibraryInfo(subdao).");
2083
+ }
2050
2084
  function _computeLibraryKey(subdao, libraryId) {
2051
- const coder = AbiCoder.defaultAbiCoder ? AbiCoder.defaultAbiCoder() : new AbiCoder();
2052
- const encoded = coder.encode(["address", "string"], [getAddress(subdao), String(libraryId)]);
2053
- return keccak256(encoded);
2085
+ return getAddress(subdao);
2054
2086
  }
2055
- async function getLatestLibrary({ provider, registry, subdao, libraryId = "main" }) {
2056
- if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2057
- const addr = normalise(registry, "registry");
2058
- const sub = normalise(subdao, "subdao");
2059
- const contract = new Contract(addr, ABI.LibraryRegistry, provider);
2060
- const key = _computeLibraryKey(sub, libraryId);
2061
- const latestCID = await contract.subdaoLibraryLatest(key).catch(() => "");
2062
- if (!latestCID || latestCID.length === 0) return null;
2063
- const info = await getManifestInfo({ provider, registry: addr, manifestCID: latestCID });
2064
- return info;
2087
+ async function getLatestLibrary({ provider, registry, subdao }) {
2088
+ return getLibraryInfo({ provider, registry, subdao });
2065
2089
  }
2066
- async function getBeforeAfterForUpdate({ provider, registry, subdao, libraryId = "main", newCid }) {
2090
+ async function getBeforeAfterForUpdate({ provider, registry, subdao, newCid }) {
2067
2091
  if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2068
- const addr = normalise(registry, "registry");
2069
- const sub = normalise(subdao, "subdao");
2070
- const contract = new Contract(addr, ABI.LibraryRegistry, provider);
2071
- const key = _computeLibraryKey(sub, libraryId);
2072
- const prev = await contract.subdaoLibraryLatest(key).catch(() => "");
2073
- return { previousCID: prev || null, newCID: String(newCid), libraryId: String(libraryId) };
2092
+ const info = await getLatestLibrary({ provider, registry, subdao });
2093
+ return {
2094
+ previousCID: info ? info.manifestCID : null,
2095
+ newCID: String(newCid),
2096
+ version: info ? info.version : "0.0.0"
2097
+ };
2074
2098
  }
2075
2099
  async function hasScopedOwnership({ provider, registry, subdao, manifestCID }) {
2076
- if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2077
- const addr = normalise(registry, "registry");
2078
- const sub = normalise(subdao, "subdao");
2079
- const contract = new Contract(addr, ABI.LibraryRegistry, provider);
2080
- return contract.hasScopedOwnership(sub, manifestCID);
2100
+ const info = await getLatestLibrary({ provider, registry, subdao });
2101
+ return info && info.manifestCID === manifestCID;
2081
2102
  }
2082
- function buildUpdateLibraryForSubDAOTx({ registry, subdao, manifestCID, promptCount, libraryId = "main" }) {
2103
+ function buildUpdateLibraryTx({ registry, subdao, manifestCID, version = "1.0.0" }) {
2083
2104
  const to = normalise(registry, "registry");
2084
2105
  if (!manifestCID) throw new SageSDKError(CODES.INVALID_ARGS, "manifestCID required");
2085
- const iface = new Interface(["function updateLibraryForSubDAO(address,string,string,uint256)"]);
2086
- const data = iface.encodeFunctionData("updateLibraryForSubDAO", [
2106
+ const iface = new Interface(["function updateLibrary(address,string,string)"]);
2107
+ const data = iface.encodeFunctionData("updateLibrary", [
2087
2108
  normalise(subdao, "subdao"),
2088
- String(libraryId || "main"),
2089
2109
  String(manifestCID),
2090
- BigInt(promptCount ?? 0n)
2110
+ String(version)
2091
2111
  ]);
2092
2112
  return { to, data, value: 0n };
2093
2113
  }
2114
+ var buildUpdateLibraryForSubDAOTx = buildUpdateLibraryTx;
2094
2115
  module2.exports = {
2095
2116
  listManifests,
2096
2117
  getManifestInfo,
2118
+ getLibraryInfo,
2097
2119
  getLatestLibrary,
2098
2120
  hasScopedOwnership,
2121
+ buildUpdateLibraryTx,
2099
2122
  buildUpdateLibraryForSubDAOTx,
2100
2123
  _computeLibraryKey,
2101
2124
  getBeforeAfterForUpdate,
2102
2125
  /** Build an authorizeTimelock(timelock, subdao) call for a LibraryRegistry. */
2103
2126
  buildAuthorizeTimelockTx: function buildAuthorizeTimelockTx({ registry, timelock, subdao }) {
2104
2127
  const to = normalise(registry, "registry");
2105
- const iface = new Interface(["function authorizeTimelock(address,address)"]);
2106
- const data = iface.encodeFunctionData("authorizeTimelock", [normalise(timelock, "timelock"), normalise(subdao, "subdao")]);
2128
+ const iface = new Interface(["function registerDAO(address,address)"]);
2129
+ const data = iface.encodeFunctionData("registerDAO", [normalise(subdao, "subdao"), normalise(timelock, "timelock")]);
2107
2130
  return { to, data, value: 0n };
2108
2131
  },
2109
2132
  searchRegistry,
2110
2133
  validation,
2111
- /** Simulate calling a registry function as the timelock to verify authority/roles */
2134
+ /** Simulate as timelock */
2112
2135
  simulateAsTimelock: async function simulateAsTimelock({ provider, registry, to, data, timelock }) {
2113
2136
  if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2114
2137
  const reg = normalise(registry, "registry");
@@ -2121,10 +2144,9 @@ var require_library = __commonJS({
2121
2144
  return { ok: false, error: { type: "Revert", message: String(err && err.message || err) } };
2122
2145
  }
2123
2146
  },
2124
- /** Best-effort execution readiness for updateLibraryForSubDAO by simulating as timelock. */
2125
- executionReadiness: async function executionReadiness({ provider, registry, timelock, subdao, libraryId = "main", manifestCID = "", promptCount = 0 }) {
2147
+ executionReadiness: async function executionReadiness({ provider, registry, timelock, subdao, manifestCID = "", version = "1.0.0" }) {
2126
2148
  if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2127
- const payload = buildUpdateLibraryForSubDAOTx({ registry, subdao, manifestCID, promptCount, libraryId });
2149
+ const payload = buildUpdateLibraryTx({ registry, subdao, manifestCID, version });
2128
2150
  const sim = await this.simulateAsTimelock({ provider, registry, to: payload.to, data: payload.data, timelock });
2129
2151
  const out = { ok: !!sim.ok, error: sim.ok ? null : sim.error?.message || "revert", missingRole: null };
2130
2152
  return out;
@@ -2937,7 +2959,9 @@ var require_subdao = __commonJS({
2937
2959
  }
2938
2960
  var SubDAOInterface = new Interface([
2939
2961
  "function stake(uint256)",
2940
- "function unstake(uint256)"
2962
+ "function unstake(uint256)",
2963
+ // Optional on newer SubDAO implementations; provided for tx building only.
2964
+ "function setProfileCid(string)"
2941
2965
  ]);
2942
2966
  function buildStakeTx({ subdao, amount }) {
2943
2967
  const addr = normalise(subdao, "subdao");
@@ -2949,12 +2973,21 @@ var require_subdao = __commonJS({
2949
2973
  const data = SubDAOInterface.encodeFunctionData("unstake", [BigInt(amount)]);
2950
2974
  return { to: addr, data, value: 0n };
2951
2975
  }
2976
+ function buildSetProfileCidTx({ subdao, profileCid }) {
2977
+ const addr = normalise(subdao, "subdao");
2978
+ if (!profileCid || typeof profileCid !== "string" || !profileCid.trim()) {
2979
+ throw new SageSDKError(CODES.INVALID_ARGS, "profileCid (CID string) required");
2980
+ }
2981
+ const data = SubDAOInterface.encodeFunctionData("setProfileCid", [String(profileCid)]);
2982
+ return { to: addr, data, value: 0n };
2983
+ }
2952
2984
  module2.exports = {
2953
2985
  discoverSubDAOs,
2954
2986
  getSubDAOInfo,
2955
2987
  getSubDAOUserStats,
2956
2988
  buildStakeTx,
2957
- buildUnstakeTx
2989
+ buildUnstakeTx,
2990
+ buildSetProfileCidTx
2958
2991
  };
2959
2992
  async function ensureSxxxBurnAllowance({ signer, sxxx, owner, spender, amount }) {
2960
2993
  if (!signer) throw new SageSDKError(CODES.INVALID_ARGS, "signer required");
@@ -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.1.14",
17
+ version: "0.1.16",
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",
@@ -172,12 +172,13 @@ var require_abi = __commonJS({
172
172
  "function maxCreationBurn() view returns (uint256)"
173
173
  ];
174
174
  var LibraryRegistry = [
175
- "function getManifestCIDCount() view returns (uint256)",
176
- "function getManifestCIDs(uint256 offset, uint256 limit) view returns (string[] memory, uint256 total)",
177
- "function getManifestCID(uint256 index) view returns (string)",
178
- "function libraries(string manifestCID) view returns (string previousCID, uint256 timestamp, address proposer, uint256 promptCount)",
179
- "function subdaoLibraryLatest(bytes32 key) view returns (string)",
180
- "function hasScopedOwnership(address subdao, string manifestCID) view returns (bool)"
175
+ "function libraryByDAO(address) view returns (string manifestCID, address lastUpdater, uint256 lastUpdated, string version)",
176
+ "function daoTimelock(address) view returns (address)",
177
+ "function getLibrary(address) view returns (tuple(string manifestCID, address lastUpdater, uint256 lastUpdated, string version))",
178
+ "function updateLibrary(address dao, string manifestCID, string version)",
179
+ "function registerDAO(address dao, address timelock)",
180
+ "event LibraryUpdated(address indexed dao, string manifestCID, address indexed timelock, string version)",
181
+ "event DAORegistered(address indexed dao, address indexed timelock)"
181
182
  ];
182
183
  var PromptRegistry = [
183
184
  "function prompts(string key) view returns (string cid, uint256 version, uint256 timestamp, address author, string forkedFromCID, address originalAuthor, bool isFork, uint256 forkDepth)",
@@ -1231,15 +1232,35 @@ var require_ipfs = __commonJS({
1231
1232
  if (metadata) {
1232
1233
  formData.append("pinataMetadata", JSON.stringify(metadata));
1233
1234
  }
1234
- const headers = { ...formData.getHeaders() };
1235
+ const endpoint = `${config.pinata.apiUrl}/pinning/pinFileToIPFS`;
1236
+ const sendWithAuthMode = async (mode) => {
1237
+ const headers = { ...formData.getHeaders() };
1238
+ if (mode === "jwt") {
1239
+ headers.Authorization = `Bearer ${config.pinata.jwt}`;
1240
+ } else {
1241
+ headers.pinata_api_key = config.pinata.apiKey;
1242
+ headers.pinata_secret_api_key = config.pinata.secretKey;
1243
+ }
1244
+ return axiosInstance.post(endpoint, formData, { headers, timeout: config.timeoutMs });
1245
+ };
1246
+ let response;
1247
+ let lastError;
1235
1248
  if (config.pinata.jwt) {
1236
- headers.Authorization = `Bearer ${config.pinata.jwt}`;
1249
+ try {
1250
+ response = await sendWithAuthMode("jwt");
1251
+ } catch (error) {
1252
+ lastError = error;
1253
+ const status = error?.response?.status;
1254
+ const reason = error?.response?.data?.error?.reason || "";
1255
+ const isInvalidJwt = status === 401 && typeof reason === "string" && reason.toUpperCase().includes("INVALID_CREDENTIALS");
1256
+ if (!config.pinata.apiKey || !config.pinata.secretKey || !isInvalidJwt) {
1257
+ throw error;
1258
+ }
1259
+ response = await sendWithAuthMode("keys");
1260
+ }
1237
1261
  } else {
1238
- headers.pinata_api_key = config.pinata.apiKey;
1239
- headers.pinata_secret_api_key = config.pinata.secretKey;
1262
+ response = await sendWithAuthMode("keys");
1240
1263
  }
1241
- const endpoint = `${config.pinata.apiUrl}/pinning/pinFileToIPFS`;
1242
- const response = await axiosInstance.post(endpoint, formData, { headers, timeout: config.timeoutMs });
1243
1264
  const cid = response?.data?.IpfsHash || response?.data?.cid || response?.data?.Hash;
1244
1265
  if (!cid) {
1245
1266
  throw new Error("Pinata upload did not return IpfsHash");
@@ -1875,7 +1896,7 @@ var require_validation = __commonJS({
1875
1896
  schemaPath: null,
1876
1897
  errors: [{ keyword: "schema", instancePath: "/", message: "manifest schema not found" }],
1877
1898
  hints: [
1878
- 'Ensure docs/schemas/manifest.schema.json exists (run "sage library scaffold" to regenerate)'
1899
+ 'Ensure docs/schemas/manifest.schema.json exists (run "sage project scaffold" to regenerate)'
1879
1900
  ],
1880
1901
  manifestPath
1881
1902
  };
@@ -2016,93 +2037,95 @@ var require_library = __commonJS({
2016
2037
  throw new SageSDKError(CODES.INVALID_ARGS, `invalid ${label}`, { cause: err });
2017
2038
  }
2018
2039
  }
2019
- async function listManifests({ provider, registry, offset = 0, limit = 50 }) {
2040
+ async function listManifests({ provider, registry, factoryAddress, offset = 0, limit = 50 }) {
2020
2041
  if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2021
- const addr = normalise(registry, "registry");
2022
- const contract = new Contract(addr, ABI.LibraryRegistry, provider);
2023
- const [cids, total] = await contract.getManifestCIDs(offset, limit);
2042
+ if (!factoryAddress) throw new SageSDKError(CODES.INVALID_ARGS, "factoryAddress required for V4 listing");
2043
+ const factory = new Contract(factoryAddress, ABI.FactoryRead, provider);
2044
+ const totalSubDAOs = await factory.getSubDAOCount();
2024
2045
  const manifests = [];
2025
- for (const cid of cids) {
2026
- manifests.push(await getManifestInfo({ provider, registry: addr, manifestCID: cid }));
2046
+ const start = Number(offset);
2047
+ const end = Math.min(start + Number(limit), Number(totalSubDAOs));
2048
+ for (let i = start; i < end; i++) {
2049
+ const subdao = await factory.subDaos(i);
2050
+ const info = await getLatestLibrary({ provider, registry, subdao });
2051
+ if (info && info.manifestCID) {
2052
+ manifests.push(info);
2053
+ }
2027
2054
  }
2028
- return { total: BigInt(total.toString()), manifests };
2055
+ return { total: BigInt(totalSubDAOs), manifests };
2029
2056
  }
2030
- async function getManifestInfo({ provider, registry, manifestCID }) {
2057
+ async function getLibraryInfo({ provider, registry, subdao }) {
2031
2058
  if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2032
- if (!manifestCID) throw new SageSDKError(CODES.INVALID_ARGS, "manifestCID required");
2059
+ if (!subdao) throw new SageSDKError(CODES.INVALID_ARGS, "subdao required");
2033
2060
  const addr = normalise(registry, "registry");
2061
+ const dao = normalise(subdao, "subdao");
2034
2062
  const contract = new Contract(addr, ABI.LibraryRegistry, provider);
2035
- const [previousCID, timestamp, proposer, promptCount] = await contract.libraries(manifestCID);
2063
+ const info = await contract.getLibrary(dao);
2036
2064
  return {
2037
- manifestCID,
2038
- previousCID,
2039
- timestamp: BigInt(timestamp.toString()),
2040
- proposer: getAddress(proposer),
2041
- promptCount: Number(promptCount)
2065
+ manifestCID: info.manifestCID,
2066
+ previousCID: "",
2067
+ // Not tracked on-chain in V4 (history is in events/subgraph)
2068
+ timestamp: BigInt(info.lastUpdated.toString()),
2069
+ proposer: getAddress(info.lastUpdater),
2070
+ promptCount: 0,
2071
+ // Not tracked on-chain in V4
2072
+ version: info.version
2042
2073
  };
2043
2074
  }
2075
+ async function getManifestInfo({ provider, registry, manifestCID }) {
2076
+ throw new SageSDKError(CODES.UNSUPPORTED_OPERATION, "getManifestInfo(cid) is not supported in V4. Use getLibraryInfo(subdao).");
2077
+ }
2044
2078
  function _computeLibraryKey(subdao, libraryId) {
2045
- const coder = AbiCoder.defaultAbiCoder ? AbiCoder.defaultAbiCoder() : new AbiCoder();
2046
- const encoded = coder.encode(["address", "string"], [getAddress(subdao), String(libraryId)]);
2047
- return keccak256(encoded);
2079
+ return getAddress(subdao);
2048
2080
  }
2049
- async function getLatestLibrary({ provider, registry, subdao, libraryId = "main" }) {
2050
- if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2051
- const addr = normalise(registry, "registry");
2052
- const sub = normalise(subdao, "subdao");
2053
- const contract = new Contract(addr, ABI.LibraryRegistry, provider);
2054
- const key = _computeLibraryKey(sub, libraryId);
2055
- const latestCID = await contract.subdaoLibraryLatest(key).catch(() => "");
2056
- if (!latestCID || latestCID.length === 0) return null;
2057
- const info = await getManifestInfo({ provider, registry: addr, manifestCID: latestCID });
2058
- return info;
2081
+ async function getLatestLibrary({ provider, registry, subdao }) {
2082
+ return getLibraryInfo({ provider, registry, subdao });
2059
2083
  }
2060
- async function getBeforeAfterForUpdate({ provider, registry, subdao, libraryId = "main", newCid }) {
2084
+ async function getBeforeAfterForUpdate({ provider, registry, subdao, newCid }) {
2061
2085
  if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2062
- const addr = normalise(registry, "registry");
2063
- const sub = normalise(subdao, "subdao");
2064
- const contract = new Contract(addr, ABI.LibraryRegistry, provider);
2065
- const key = _computeLibraryKey(sub, libraryId);
2066
- const prev = await contract.subdaoLibraryLatest(key).catch(() => "");
2067
- return { previousCID: prev || null, newCID: String(newCid), libraryId: String(libraryId) };
2086
+ const info = await getLatestLibrary({ provider, registry, subdao });
2087
+ return {
2088
+ previousCID: info ? info.manifestCID : null,
2089
+ newCID: String(newCid),
2090
+ version: info ? info.version : "0.0.0"
2091
+ };
2068
2092
  }
2069
2093
  async function hasScopedOwnership({ provider, registry, subdao, manifestCID }) {
2070
- if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2071
- const addr = normalise(registry, "registry");
2072
- const sub = normalise(subdao, "subdao");
2073
- const contract = new Contract(addr, ABI.LibraryRegistry, provider);
2074
- return contract.hasScopedOwnership(sub, manifestCID);
2094
+ const info = await getLatestLibrary({ provider, registry, subdao });
2095
+ return info && info.manifestCID === manifestCID;
2075
2096
  }
2076
- function buildUpdateLibraryForSubDAOTx({ registry, subdao, manifestCID, promptCount, libraryId = "main" }) {
2097
+ function buildUpdateLibraryTx({ registry, subdao, manifestCID, version = "1.0.0" }) {
2077
2098
  const to = normalise(registry, "registry");
2078
2099
  if (!manifestCID) throw new SageSDKError(CODES.INVALID_ARGS, "manifestCID required");
2079
- const iface = new Interface(["function updateLibraryForSubDAO(address,string,string,uint256)"]);
2080
- const data = iface.encodeFunctionData("updateLibraryForSubDAO", [
2100
+ const iface = new Interface(["function updateLibrary(address,string,string)"]);
2101
+ const data = iface.encodeFunctionData("updateLibrary", [
2081
2102
  normalise(subdao, "subdao"),
2082
- String(libraryId || "main"),
2083
2103
  String(manifestCID),
2084
- BigInt(promptCount ?? 0n)
2104
+ String(version)
2085
2105
  ]);
2086
2106
  return { to, data, value: 0n };
2087
2107
  }
2108
+ var buildUpdateLibraryForSubDAOTx = buildUpdateLibraryTx;
2088
2109
  module2.exports = {
2089
2110
  listManifests,
2090
2111
  getManifestInfo,
2112
+ getLibraryInfo,
2091
2113
  getLatestLibrary,
2092
2114
  hasScopedOwnership,
2115
+ buildUpdateLibraryTx,
2093
2116
  buildUpdateLibraryForSubDAOTx,
2094
2117
  _computeLibraryKey,
2095
2118
  getBeforeAfterForUpdate,
2096
2119
  /** Build an authorizeTimelock(timelock, subdao) call for a LibraryRegistry. */
2097
2120
  buildAuthorizeTimelockTx: function buildAuthorizeTimelockTx({ registry, timelock, subdao }) {
2098
2121
  const to = normalise(registry, "registry");
2099
- const iface = new Interface(["function authorizeTimelock(address,address)"]);
2100
- const data = iface.encodeFunctionData("authorizeTimelock", [normalise(timelock, "timelock"), normalise(subdao, "subdao")]);
2122
+ const iface = new Interface(["function registerDAO(address,address)"]);
2123
+ const data = iface.encodeFunctionData("registerDAO", [normalise(subdao, "subdao"), normalise(timelock, "timelock")]);
2101
2124
  return { to, data, value: 0n };
2102
2125
  },
2103
2126
  searchRegistry,
2104
2127
  validation,
2105
- /** Simulate calling a registry function as the timelock to verify authority/roles */
2128
+ /** Simulate as timelock */
2106
2129
  simulateAsTimelock: async function simulateAsTimelock({ provider, registry, to, data, timelock }) {
2107
2130
  if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2108
2131
  const reg = normalise(registry, "registry");
@@ -2115,10 +2138,9 @@ var require_library = __commonJS({
2115
2138
  return { ok: false, error: { type: "Revert", message: String(err && err.message || err) } };
2116
2139
  }
2117
2140
  },
2118
- /** Best-effort execution readiness for updateLibraryForSubDAO by simulating as timelock. */
2119
- executionReadiness: async function executionReadiness({ provider, registry, timelock, subdao, libraryId = "main", manifestCID = "", promptCount = 0 }) {
2141
+ executionReadiness: async function executionReadiness({ provider, registry, timelock, subdao, manifestCID = "", version = "1.0.0" }) {
2120
2142
  if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2121
- const payload = buildUpdateLibraryForSubDAOTx({ registry, subdao, manifestCID, promptCount, libraryId });
2143
+ const payload = buildUpdateLibraryTx({ registry, subdao, manifestCID, version });
2122
2144
  const sim = await this.simulateAsTimelock({ provider, registry, to: payload.to, data: payload.data, timelock });
2123
2145
  const out = { ok: !!sim.ok, error: sim.ok ? null : sim.error?.message || "revert", missingRole: null };
2124
2146
  return out;
@@ -2931,7 +2953,9 @@ var require_subdao = __commonJS({
2931
2953
  }
2932
2954
  var SubDAOInterface = new Interface([
2933
2955
  "function stake(uint256)",
2934
- "function unstake(uint256)"
2956
+ "function unstake(uint256)",
2957
+ // Optional on newer SubDAO implementations; provided for tx building only.
2958
+ "function setProfileCid(string)"
2935
2959
  ]);
2936
2960
  function buildStakeTx({ subdao, amount }) {
2937
2961
  const addr = normalise(subdao, "subdao");
@@ -2943,12 +2967,21 @@ var require_subdao = __commonJS({
2943
2967
  const data = SubDAOInterface.encodeFunctionData("unstake", [BigInt(amount)]);
2944
2968
  return { to: addr, data, value: 0n };
2945
2969
  }
2970
+ function buildSetProfileCidTx({ subdao, profileCid }) {
2971
+ const addr = normalise(subdao, "subdao");
2972
+ if (!profileCid || typeof profileCid !== "string" || !profileCid.trim()) {
2973
+ throw new SageSDKError(CODES.INVALID_ARGS, "profileCid (CID string) required");
2974
+ }
2975
+ const data = SubDAOInterface.encodeFunctionData("setProfileCid", [String(profileCid)]);
2976
+ return { to: addr, data, value: 0n };
2977
+ }
2946
2978
  module2.exports = {
2947
2979
  discoverSubDAOs,
2948
2980
  getSubDAOInfo,
2949
2981
  getSubDAOUserStats,
2950
2982
  buildStakeTx,
2951
- buildUnstakeTx
2983
+ buildUnstakeTx,
2984
+ buildSetProfileCidTx
2952
2985
  };
2953
2986
  async function ensureSxxxBurnAllowance({ signer, sxxx, owner, spender, amount }) {
2954
2987
  if (!signer) throw new SageSDKError(CODES.INVALID_ARGS, "signer required");
@@ -20,7 +20,7 @@ var require_package = __commonJS({
20
20
  "package.json"(exports2, module2) {
21
21
  module2.exports = {
22
22
  name: "@sage-protocol/sdk",
23
- version: "0.1.14",
23
+ version: "0.1.16",
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",
@@ -178,12 +178,13 @@ var require_abi = __commonJS({
178
178
  "function maxCreationBurn() view returns (uint256)"
179
179
  ];
180
180
  var LibraryRegistry = [
181
- "function getManifestCIDCount() view returns (uint256)",
182
- "function getManifestCIDs(uint256 offset, uint256 limit) view returns (string[] memory, uint256 total)",
183
- "function getManifestCID(uint256 index) view returns (string)",
184
- "function libraries(string manifestCID) view returns (string previousCID, uint256 timestamp, address proposer, uint256 promptCount)",
185
- "function subdaoLibraryLatest(bytes32 key) view returns (string)",
186
- "function hasScopedOwnership(address subdao, string manifestCID) view returns (bool)"
181
+ "function libraryByDAO(address) view returns (string manifestCID, address lastUpdater, uint256 lastUpdated, string version)",
182
+ "function daoTimelock(address) view returns (address)",
183
+ "function getLibrary(address) view returns (tuple(string manifestCID, address lastUpdater, uint256 lastUpdated, string version))",
184
+ "function updateLibrary(address dao, string manifestCID, string version)",
185
+ "function registerDAO(address dao, address timelock)",
186
+ "event LibraryUpdated(address indexed dao, string manifestCID, address indexed timelock, string version)",
187
+ "event DAORegistered(address indexed dao, address indexed timelock)"
187
188
  ];
188
189
  var PromptRegistry = [
189
190
  "function prompts(string key) view returns (string cid, uint256 version, uint256 timestamp, address author, string forkedFromCID, address originalAuthor, bool isFork, uint256 forkDepth)",
@@ -1237,15 +1238,35 @@ var require_ipfs = __commonJS({
1237
1238
  if (metadata) {
1238
1239
  formData.append("pinataMetadata", JSON.stringify(metadata));
1239
1240
  }
1240
- const headers = { ...formData.getHeaders() };
1241
+ const endpoint = `${config.pinata.apiUrl}/pinning/pinFileToIPFS`;
1242
+ const sendWithAuthMode = async (mode) => {
1243
+ const headers = { ...formData.getHeaders() };
1244
+ if (mode === "jwt") {
1245
+ headers.Authorization = `Bearer ${config.pinata.jwt}`;
1246
+ } else {
1247
+ headers.pinata_api_key = config.pinata.apiKey;
1248
+ headers.pinata_secret_api_key = config.pinata.secretKey;
1249
+ }
1250
+ return axiosInstance.post(endpoint, formData, { headers, timeout: config.timeoutMs });
1251
+ };
1252
+ let response;
1253
+ let lastError;
1241
1254
  if (config.pinata.jwt) {
1242
- headers.Authorization = `Bearer ${config.pinata.jwt}`;
1255
+ try {
1256
+ response = await sendWithAuthMode("jwt");
1257
+ } catch (error) {
1258
+ lastError = error;
1259
+ const status = error?.response?.status;
1260
+ const reason = error?.response?.data?.error?.reason || "";
1261
+ const isInvalidJwt = status === 401 && typeof reason === "string" && reason.toUpperCase().includes("INVALID_CREDENTIALS");
1262
+ if (!config.pinata.apiKey || !config.pinata.secretKey || !isInvalidJwt) {
1263
+ throw error;
1264
+ }
1265
+ response = await sendWithAuthMode("keys");
1266
+ }
1243
1267
  } else {
1244
- headers.pinata_api_key = config.pinata.apiKey;
1245
- headers.pinata_secret_api_key = config.pinata.secretKey;
1268
+ response = await sendWithAuthMode("keys");
1246
1269
  }
1247
- const endpoint = `${config.pinata.apiUrl}/pinning/pinFileToIPFS`;
1248
- const response = await axiosInstance.post(endpoint, formData, { headers, timeout: config.timeoutMs });
1249
1270
  const cid = response?.data?.IpfsHash || response?.data?.cid || response?.data?.Hash;
1250
1271
  if (!cid) {
1251
1272
  throw new Error("Pinata upload did not return IpfsHash");
@@ -1881,7 +1902,7 @@ var require_validation = __commonJS({
1881
1902
  schemaPath: null,
1882
1903
  errors: [{ keyword: "schema", instancePath: "/", message: "manifest schema not found" }],
1883
1904
  hints: [
1884
- 'Ensure docs/schemas/manifest.schema.json exists (run "sage library scaffold" to regenerate)'
1905
+ 'Ensure docs/schemas/manifest.schema.json exists (run "sage project scaffold" to regenerate)'
1885
1906
  ],
1886
1907
  manifestPath
1887
1908
  };
@@ -2022,93 +2043,95 @@ var require_library = __commonJS({
2022
2043
  throw new SageSDKError(CODES.INVALID_ARGS, `invalid ${label}`, { cause: err });
2023
2044
  }
2024
2045
  }
2025
- async function listManifests({ provider, registry, offset = 0, limit = 50 }) {
2046
+ async function listManifests({ provider, registry, factoryAddress, offset = 0, limit = 50 }) {
2026
2047
  if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2027
- const addr = normalise(registry, "registry");
2028
- const contract = new Contract(addr, ABI.LibraryRegistry, provider);
2029
- const [cids, total] = await contract.getManifestCIDs(offset, limit);
2048
+ if (!factoryAddress) throw new SageSDKError(CODES.INVALID_ARGS, "factoryAddress required for V4 listing");
2049
+ const factory = new Contract(factoryAddress, ABI.FactoryRead, provider);
2050
+ const totalSubDAOs = await factory.getSubDAOCount();
2030
2051
  const manifests = [];
2031
- for (const cid of cids) {
2032
- manifests.push(await getManifestInfo({ provider, registry: addr, manifestCID: cid }));
2052
+ const start = Number(offset);
2053
+ const end = Math.min(start + Number(limit), Number(totalSubDAOs));
2054
+ for (let i = start; i < end; i++) {
2055
+ const subdao = await factory.subDaos(i);
2056
+ const info = await getLatestLibrary({ provider, registry, subdao });
2057
+ if (info && info.manifestCID) {
2058
+ manifests.push(info);
2059
+ }
2033
2060
  }
2034
- return { total: BigInt(total.toString()), manifests };
2061
+ return { total: BigInt(totalSubDAOs), manifests };
2035
2062
  }
2036
- async function getManifestInfo({ provider, registry, manifestCID }) {
2063
+ async function getLibraryInfo({ provider, registry, subdao }) {
2037
2064
  if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2038
- if (!manifestCID) throw new SageSDKError(CODES.INVALID_ARGS, "manifestCID required");
2065
+ if (!subdao) throw new SageSDKError(CODES.INVALID_ARGS, "subdao required");
2039
2066
  const addr = normalise(registry, "registry");
2067
+ const dao = normalise(subdao, "subdao");
2040
2068
  const contract = new Contract(addr, ABI.LibraryRegistry, provider);
2041
- const [previousCID, timestamp, proposer, promptCount] = await contract.libraries(manifestCID);
2069
+ const info = await contract.getLibrary(dao);
2042
2070
  return {
2043
- manifestCID,
2044
- previousCID,
2045
- timestamp: BigInt(timestamp.toString()),
2046
- proposer: getAddress(proposer),
2047
- promptCount: Number(promptCount)
2071
+ manifestCID: info.manifestCID,
2072
+ previousCID: "",
2073
+ // Not tracked on-chain in V4 (history is in events/subgraph)
2074
+ timestamp: BigInt(info.lastUpdated.toString()),
2075
+ proposer: getAddress(info.lastUpdater),
2076
+ promptCount: 0,
2077
+ // Not tracked on-chain in V4
2078
+ version: info.version
2048
2079
  };
2049
2080
  }
2081
+ async function getManifestInfo({ provider, registry, manifestCID }) {
2082
+ throw new SageSDKError(CODES.UNSUPPORTED_OPERATION, "getManifestInfo(cid) is not supported in V4. Use getLibraryInfo(subdao).");
2083
+ }
2050
2084
  function _computeLibraryKey(subdao, libraryId) {
2051
- const coder = AbiCoder.defaultAbiCoder ? AbiCoder.defaultAbiCoder() : new AbiCoder();
2052
- const encoded = coder.encode(["address", "string"], [getAddress(subdao), String(libraryId)]);
2053
- return keccak256(encoded);
2085
+ return getAddress(subdao);
2054
2086
  }
2055
- async function getLatestLibrary({ provider, registry, subdao, libraryId = "main" }) {
2056
- if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2057
- const addr = normalise(registry, "registry");
2058
- const sub = normalise(subdao, "subdao");
2059
- const contract = new Contract(addr, ABI.LibraryRegistry, provider);
2060
- const key = _computeLibraryKey(sub, libraryId);
2061
- const latestCID = await contract.subdaoLibraryLatest(key).catch(() => "");
2062
- if (!latestCID || latestCID.length === 0) return null;
2063
- const info = await getManifestInfo({ provider, registry: addr, manifestCID: latestCID });
2064
- return info;
2087
+ async function getLatestLibrary({ provider, registry, subdao }) {
2088
+ return getLibraryInfo({ provider, registry, subdao });
2065
2089
  }
2066
- async function getBeforeAfterForUpdate({ provider, registry, subdao, libraryId = "main", newCid }) {
2090
+ async function getBeforeAfterForUpdate({ provider, registry, subdao, newCid }) {
2067
2091
  if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2068
- const addr = normalise(registry, "registry");
2069
- const sub = normalise(subdao, "subdao");
2070
- const contract = new Contract(addr, ABI.LibraryRegistry, provider);
2071
- const key = _computeLibraryKey(sub, libraryId);
2072
- const prev = await contract.subdaoLibraryLatest(key).catch(() => "");
2073
- return { previousCID: prev || null, newCID: String(newCid), libraryId: String(libraryId) };
2092
+ const info = await getLatestLibrary({ provider, registry, subdao });
2093
+ return {
2094
+ previousCID: info ? info.manifestCID : null,
2095
+ newCID: String(newCid),
2096
+ version: info ? info.version : "0.0.0"
2097
+ };
2074
2098
  }
2075
2099
  async function hasScopedOwnership({ provider, registry, subdao, manifestCID }) {
2076
- if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2077
- const addr = normalise(registry, "registry");
2078
- const sub = normalise(subdao, "subdao");
2079
- const contract = new Contract(addr, ABI.LibraryRegistry, provider);
2080
- return contract.hasScopedOwnership(sub, manifestCID);
2100
+ const info = await getLatestLibrary({ provider, registry, subdao });
2101
+ return info && info.manifestCID === manifestCID;
2081
2102
  }
2082
- function buildUpdateLibraryForSubDAOTx({ registry, subdao, manifestCID, promptCount, libraryId = "main" }) {
2103
+ function buildUpdateLibraryTx({ registry, subdao, manifestCID, version = "1.0.0" }) {
2083
2104
  const to = normalise(registry, "registry");
2084
2105
  if (!manifestCID) throw new SageSDKError(CODES.INVALID_ARGS, "manifestCID required");
2085
- const iface = new Interface(["function updateLibraryForSubDAO(address,string,string,uint256)"]);
2086
- const data = iface.encodeFunctionData("updateLibraryForSubDAO", [
2106
+ const iface = new Interface(["function updateLibrary(address,string,string)"]);
2107
+ const data = iface.encodeFunctionData("updateLibrary", [
2087
2108
  normalise(subdao, "subdao"),
2088
- String(libraryId || "main"),
2089
2109
  String(manifestCID),
2090
- BigInt(promptCount ?? 0n)
2110
+ String(version)
2091
2111
  ]);
2092
2112
  return { to, data, value: 0n };
2093
2113
  }
2114
+ var buildUpdateLibraryForSubDAOTx = buildUpdateLibraryTx;
2094
2115
  module2.exports = {
2095
2116
  listManifests,
2096
2117
  getManifestInfo,
2118
+ getLibraryInfo,
2097
2119
  getLatestLibrary,
2098
2120
  hasScopedOwnership,
2121
+ buildUpdateLibraryTx,
2099
2122
  buildUpdateLibraryForSubDAOTx,
2100
2123
  _computeLibraryKey,
2101
2124
  getBeforeAfterForUpdate,
2102
2125
  /** Build an authorizeTimelock(timelock, subdao) call for a LibraryRegistry. */
2103
2126
  buildAuthorizeTimelockTx: function buildAuthorizeTimelockTx({ registry, timelock, subdao }) {
2104
2127
  const to = normalise(registry, "registry");
2105
- const iface = new Interface(["function authorizeTimelock(address,address)"]);
2106
- const data = iface.encodeFunctionData("authorizeTimelock", [normalise(timelock, "timelock"), normalise(subdao, "subdao")]);
2128
+ const iface = new Interface(["function registerDAO(address,address)"]);
2129
+ const data = iface.encodeFunctionData("registerDAO", [normalise(subdao, "subdao"), normalise(timelock, "timelock")]);
2107
2130
  return { to, data, value: 0n };
2108
2131
  },
2109
2132
  searchRegistry,
2110
2133
  validation,
2111
- /** Simulate calling a registry function as the timelock to verify authority/roles */
2134
+ /** Simulate as timelock */
2112
2135
  simulateAsTimelock: async function simulateAsTimelock({ provider, registry, to, data, timelock }) {
2113
2136
  if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2114
2137
  const reg = normalise(registry, "registry");
@@ -2121,10 +2144,9 @@ var require_library = __commonJS({
2121
2144
  return { ok: false, error: { type: "Revert", message: String(err && err.message || err) } };
2122
2145
  }
2123
2146
  },
2124
- /** Best-effort execution readiness for updateLibraryForSubDAO by simulating as timelock. */
2125
- executionReadiness: async function executionReadiness({ provider, registry, timelock, subdao, libraryId = "main", manifestCID = "", promptCount = 0 }) {
2147
+ executionReadiness: async function executionReadiness({ provider, registry, timelock, subdao, manifestCID = "", version = "1.0.0" }) {
2126
2148
  if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2127
- const payload = buildUpdateLibraryForSubDAOTx({ registry, subdao, manifestCID, promptCount, libraryId });
2149
+ const payload = buildUpdateLibraryTx({ registry, subdao, manifestCID, version });
2128
2150
  const sim = await this.simulateAsTimelock({ provider, registry, to: payload.to, data: payload.data, timelock });
2129
2151
  const out = { ok: !!sim.ok, error: sim.ok ? null : sim.error?.message || "revert", missingRole: null };
2130
2152
  return out;
@@ -2937,7 +2959,9 @@ var require_subdao = __commonJS({
2937
2959
  }
2938
2960
  var SubDAOInterface = new Interface([
2939
2961
  "function stake(uint256)",
2940
- "function unstake(uint256)"
2962
+ "function unstake(uint256)",
2963
+ // Optional on newer SubDAO implementations; provided for tx building only.
2964
+ "function setProfileCid(string)"
2941
2965
  ]);
2942
2966
  function buildStakeTx({ subdao, amount }) {
2943
2967
  const addr = normalise(subdao, "subdao");
@@ -2949,12 +2973,21 @@ var require_subdao = __commonJS({
2949
2973
  const data = SubDAOInterface.encodeFunctionData("unstake", [BigInt(amount)]);
2950
2974
  return { to: addr, data, value: 0n };
2951
2975
  }
2976
+ function buildSetProfileCidTx({ subdao, profileCid }) {
2977
+ const addr = normalise(subdao, "subdao");
2978
+ if (!profileCid || typeof profileCid !== "string" || !profileCid.trim()) {
2979
+ throw new SageSDKError(CODES.INVALID_ARGS, "profileCid (CID string) required");
2980
+ }
2981
+ const data = SubDAOInterface.encodeFunctionData("setProfileCid", [String(profileCid)]);
2982
+ return { to: addr, data, value: 0n };
2983
+ }
2952
2984
  module2.exports = {
2953
2985
  discoverSubDAOs,
2954
2986
  getSubDAOInfo,
2955
2987
  getSubDAOUserStats,
2956
2988
  buildStakeTx,
2957
- buildUnstakeTx
2989
+ buildUnstakeTx,
2990
+ buildSetProfileCidTx
2958
2991
  };
2959
2992
  async function ensureSxxxBurnAllowance({ signer, sxxx, owner, spender, amount }) {
2960
2993
  if (!signer) throw new SageSDKError(CODES.INVALID_ARGS, "signer required");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sage-protocol/sdk",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
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",