@sage-protocol/sdk 0.1.2 → 0.1.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
@@ -68,9 +68,297 @@ Module Overview
68
68
  | `treasury` | Reserve/POL snapshot, pending withdrawals, liquidity plans | Treasury analytics, Safe operators |
69
69
  | `boost` | Merkle/Direct boost readers + builders | Incentive distribution tooling |
70
70
  | `subgraph` | GraphQL helpers for proposals/libraries | Historical analytics |
71
+ | `services` | High-level SubgraphService + IPFSService with retry/caching | Web app, CLI integration |
71
72
  | `adapters` | Normalized governance adapters (OZ) | UI layers needing a unified model |
72
73
  | `errors` | `SageSDKError` codes | Consistent downstream error handling |
73
74
 
75
+ ### Service Layer
76
+
77
+ The SDK provides high-level service classes with built-in retry logic, caching, and error handling for production applications.
78
+
79
+ **SubgraphService** - GraphQL queries with automatic retry and caching
80
+
81
+ ```js
82
+ import { services, serviceErrors } from '@sage-protocol/sdk';
83
+
84
+ // Initialize service
85
+ const subgraphService = new services.SubgraphService({
86
+ url: 'https://api.studio.thegraph.com/query/your-subgraph',
87
+ timeout: 10000, // 10s timeout (default)
88
+ retries: 3, // 3 retry attempts with exponential backoff (default)
89
+ cache: {
90
+ enabled: true, // Enable caching (default: true)
91
+ ttl: 30000, // 30s cache TTL (default)
92
+ maxSize: 100, // Max 100 cache entries (default)
93
+ },
94
+ });
95
+
96
+ // Fetch SubDAOs with caching
97
+ const subdaos = await subgraphService.getSubDAOs({ limit: 50, skip: 0 });
98
+
99
+ // Fetch proposals with filters
100
+ const proposals = await subgraphService.getProposals({
101
+ governor: '0xGovernor',
102
+ states: ['ACTIVE', 'PENDING'],
103
+ fromTimestamp: 1640000000,
104
+ limit: 20,
105
+ cache: true, // Use cache (default: true)
106
+ });
107
+
108
+ // Get single proposal by ID
109
+ const proposal = await subgraphService.getProposalById('0x123...', { cache: true });
110
+
111
+ // Fetch libraries
112
+ const libraries = await subgraphService.getLibraries({
113
+ subdao: '0xSubDAO',
114
+ limit: 50,
115
+ });
116
+
117
+ // Fetch prompts by tag
118
+ const prompts = await subgraphService.getPromptsByTag({
119
+ tagsHash: '0xabc123...',
120
+ registry: '0xRegistry',
121
+ limit: 50,
122
+ });
123
+
124
+ // Cache management
125
+ subgraphService.clearCache();
126
+ const stats = subgraphService.getCacheStats();
127
+ // → { enabled: true, size: 12, maxSize: 100 }
128
+ ```
129
+
130
+ **IPFSService** - Parallel gateway fetching with caching
131
+
132
+ ```js
133
+ import { services, serviceErrors } from '@sage-protocol/sdk';
134
+ import { ethers } from 'ethers';
135
+
136
+ // Initialize service
137
+ const ipfsService = new services.IPFSService({
138
+ workerBaseUrl: 'https://api.sageprotocol.io',
139
+ gateway: 'https://ipfs.io',
140
+ signer: ethers.Wallet.fromPhrase('...'), // Optional: for worker auth
141
+ timeout: 15000, // 15s timeout (default)
142
+ retries: 2, // 2 retry attempts (default)
143
+ cache: {
144
+ enabled: true, // Enable caching (default: true)
145
+ ttl: 300000, // 5min cache TTL for immutable CIDs (default)
146
+ maxSize: 50, // Max 50 cache entries (default)
147
+ },
148
+ });
149
+
150
+ // Fetch content by CID (parallel gateway race)
151
+ // Tries multiple gateways in parallel, returns first success
152
+ const content = await ipfsService.fetchByCID('QmTest123...', {
153
+ cache: true,
154
+ timeout: 5000, // 5s timeout per gateway
155
+ extraGateways: ['https://cloudflare-ipfs.com', 'https://gateway.pinata.cloud'],
156
+ });
157
+
158
+ // Upload content to IPFS worker
159
+ const cid = await ipfsService.upload(
160
+ { title: 'My Prompt', content: '...' },
161
+ { name: 'prompt.json', warm: true }
162
+ );
163
+ // → 'QmNewContent123...'
164
+
165
+ // Pin CIDs to worker
166
+ await ipfsService.pin(['QmTest1...', 'QmTest2...'], { warm: false });
167
+
168
+ // Warm gateways (prefetch)
169
+ await ipfsService.warm('QmTest123...', {
170
+ gateways: ['https://ipfs.io', 'https://cloudflare-ipfs.com'],
171
+ });
172
+
173
+ // Cache management
174
+ ipfsService.clearCache();
175
+ const stats = ipfsService.getCacheStats();
176
+ ```
177
+
178
+ **Error Handling**
179
+
180
+ ```js
181
+ import { services, serviceErrors } from '@sage-protocol/sdk';
182
+
183
+ try {
184
+ const subdaos = await subgraphService.getSubDAOs({ limit: 50 });
185
+ } catch (error) {
186
+ if (error instanceof serviceErrors.SubgraphError) {
187
+ console.error(`Subgraph error [${error.code}]:`, error.message);
188
+ console.log('Retryable:', error.retryable);
189
+ // Codes: TIMEOUT, NETWORK, INVALID_RESPONSE, NOT_FOUND, QUERY_FAILED
190
+ }
191
+ }
192
+
193
+ try {
194
+ const content = await ipfsService.fetchByCID('QmTest...');
195
+ } catch (error) {
196
+ if (error instanceof serviceErrors.IPFSError) {
197
+ console.error(`IPFS error [${error.code}]:`, error.message);
198
+ // Codes: TIMEOUT, PIN_FAILED, INVALID_CID, NOT_FOUND, GATEWAY_FAILED, UPLOAD_FAILED
199
+ }
200
+ }
201
+
202
+ // Format user-friendly error messages
203
+ const friendlyMsg = serviceErrors.formatErrorMessage(error);
204
+ ```
205
+
206
+ **Utility Exports**
207
+
208
+ ```js
209
+ import { serviceUtils } from '@sage-protocol/sdk';
210
+
211
+ // Simple in-memory cache with TTL
212
+ const cache = new serviceUtils.SimpleCache({
213
+ enabled: true,
214
+ ttl: 60000, // 1 minute
215
+ maxSize: 200,
216
+ });
217
+ cache.set('key', { data: 'value' });
218
+ const val = cache.get('key');
219
+
220
+ // Exponential backoff retry
221
+ const result = await serviceUtils.retryWithBackoff(
222
+ async () => {
223
+ // Your async operation
224
+ return await fetchData();
225
+ },
226
+ {
227
+ attempts: 3,
228
+ baseDelay: 1000, // Start at 1s
229
+ maxDelay: 10000, // Cap at 10s
230
+ onRetry: ({ attempt, totalAttempts, delay, error }) => {
231
+ console.log(`Retry ${attempt}/${totalAttempts} after ${delay}ms:`, error.message);
232
+ },
233
+ }
234
+ );
235
+ ```
236
+
237
+ **Performance Notes**
238
+
239
+ - **Parallel Gateway Fetching**: IPFSService fetches from multiple IPFS gateways simultaneously (Promise.race pattern), reducing typical fetch times from 7-28s to 1-2s (10-14x improvement)
240
+ - **In-Memory Caching**: Both services cache results in memory with configurable TTL to reduce redundant network calls
241
+ - **Exponential Backoff**: Automatic retry with exponential backoff (1s → 2s → 4s delays) for transient failures
242
+ - **Edge Runtime Compatible**: Uses `fetch()` instead of axios, compatible with Cloudflare Pages and Vercel Edge
243
+
244
+ **React Integration**
245
+
246
+ The SDK provides React hooks built on SWR for seamless integration in React applications:
247
+
248
+ **Prerequisites**:
249
+ ```bash
250
+ # Install peer dependencies
251
+ npm install react swr
252
+ # or
253
+ yarn add react swr
254
+ ```
255
+
256
+ **Usage**:
257
+
258
+ ```js
259
+ import { services, hooks } from '@sage-protocol/sdk';
260
+
261
+ // Initialize services (once, typically in a context provider)
262
+ const subgraphService = new services.SubgraphService({
263
+ url: 'https://api.studio.thegraph.com/query/your-subgraph',
264
+ });
265
+
266
+ const ipfsService = new services.IPFSService({
267
+ workerBaseUrl: 'https://api.sageprotocol.io',
268
+ gateway: 'https://ipfs.io',
269
+ signer: yourSigner, // Optional for uploads
270
+ });
271
+
272
+ // Use hooks in components
273
+ function SubDAOList() {
274
+ const { data: subdaos, error, isLoading } = hooks.useSubDAOs(subgraphService, {
275
+ limit: 50,
276
+ });
277
+
278
+ if (isLoading) return <div>Loading...</div>;
279
+ if (error) return <div>Error: {error.message}</div>;
280
+
281
+ return (
282
+ <ul>
283
+ {subdaos.map(subdao => (
284
+ <li key={subdao.id}>{subdao.name}</li>
285
+ ))}
286
+ </ul>
287
+ );
288
+ }
289
+
290
+ function ProposalList({ governorAddress }) {
291
+ const { data: proposals } = hooks.useProposals(subgraphService, {
292
+ governor: governorAddress,
293
+ states: ['ACTIVE', 'PENDING'],
294
+ limit: 20,
295
+ refreshInterval: 30000, // Auto-refresh every 30s
296
+ });
297
+
298
+ return (
299
+ <ul>
300
+ {proposals?.map(proposal => (
301
+ <li key={proposal.id}>{proposal.description}</li>
302
+ ))}
303
+ </ul>
304
+ );
305
+ }
306
+
307
+ function PromptViewer({ cid }) {
308
+ const { data: content, isLoading } = hooks.useFetchCID(ipfsService, cid, {
309
+ extraGateways: ['https://cloudflare-ipfs.com'],
310
+ });
311
+
312
+ if (isLoading) return <div>Loading prompt...</div>;
313
+
314
+ return <pre>{JSON.stringify(content, null, 2)}</pre>;
315
+ }
316
+
317
+ function UploadForm() {
318
+ const { upload, isUploading, error, data: cid } = hooks.useUpload(ipfsService);
319
+
320
+ const handleSubmit = async (e) => {
321
+ e.preventDefault();
322
+ const content = { title: 'My Prompt', content: '...' };
323
+ try {
324
+ const newCid = await upload(content, { name: 'prompt.json' });
325
+ console.log('Uploaded:', newCid);
326
+ } catch (err) {
327
+ console.error('Upload failed:', err);
328
+ }
329
+ };
330
+
331
+ return (
332
+ <form onSubmit={handleSubmit}>
333
+ <button type="submit" disabled={isUploading}>
334
+ {isUploading ? 'Uploading...' : 'Upload'}
335
+ </button>
336
+ {error && <div>Error: {error.message}</div>}
337
+ {cid && <div>Uploaded to: {cid}</div>}
338
+ </form>
339
+ );
340
+ }
341
+ ```
342
+
343
+ **Available Hooks**:
344
+
345
+ | Hook | Description | Returns |
346
+ |------|-------------|---------|
347
+ | `useSubDAOs(service, options)` | Fetch SubDAOs from subgraph | `{ data, error, isLoading, mutate }` |
348
+ | `useProposals(service, options)` | Fetch proposals from subgraph | `{ data, error, isLoading, mutate }` |
349
+ | `useFetchCID(service, cid, options)` | Fetch IPFS content by CID | `{ data, error, isLoading, mutate }` |
350
+ | `useUpload(service)` | Upload content to IPFS | `{ upload, isUploading, error, data, reset }` |
351
+
352
+ **Hook Options**:
353
+
354
+ All data-fetching hooks support SWR options:
355
+ - `refreshInterval` - Auto-refresh interval in ms
356
+ - `revalidateOnFocus` - Revalidate when window focused
357
+ - `revalidateOnReconnect` - Revalidate on network reconnect
358
+ - `cache` - Use service-level caching
359
+
360
+ **Note**: Hooks are optional and only available when `react` and `swr` peer dependencies are installed.
361
+
74
362
  ### Examples
75
363
 
76
364
  - [Legacy base mini app hook](examples/base-mini-app/README.md) – React-friendly context mirroring the former in-repo app (now maintained externally).
@@ -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.1.2",
23
+ version: "0.1.4",
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",
@@ -56,10 +56,10 @@ var require_package = __commonJS({
56
56
  ],
57
57
  sideEffects: false,
58
58
  browser: {
59
+ child_process: false,
59
60
  fs: false,
60
- path: false,
61
61
  os: false,
62
- child_process: false
62
+ path: false
63
63
  },
64
64
  repository: {
65
65
  type: "git",
@@ -88,6 +88,18 @@ var require_package = __commonJS({
88
88
  sinon: "^17.0.1",
89
89
  tsup: "^8.1.0",
90
90
  typescript: "^5.4.0"
91
+ },
92
+ peerDependencies: {
93
+ react: "^18.0.0 || ^19.0.0",
94
+ swr: "^2.0.0"
95
+ },
96
+ peerDependenciesMeta: {
97
+ react: {
98
+ optional: true
99
+ },
100
+ swr: {
101
+ optional: true
102
+ }
91
103
  }
92
104
  };
93
105
  }
@@ -1803,7 +1815,7 @@ var require_validation = __commonJS({
1803
1815
  // src/library/index.js
1804
1816
  var require_library = __commonJS({
1805
1817
  "src/library/index.js"(exports, module) {
1806
- var { Contract, getAddress, keccak256, toUtf8Bytes } = __require("ethers");
1818
+ var { Contract, getAddress, keccak256, toUtf8Bytes, AbiCoder } = __require("ethers");
1807
1819
  var { Interface } = __require("ethers");
1808
1820
  var ABI = require_abi();
1809
1821
  var { SageSDKError, CODES } = require_errors();
@@ -1842,17 +1854,31 @@ var require_library = __commonJS({
1842
1854
  promptCount: Number(promptCount)
1843
1855
  };
1844
1856
  }
1857
+ function _computeLibraryKey(subdao, libraryId) {
1858
+ const coder = AbiCoder.defaultAbiCoder ? AbiCoder.defaultAbiCoder() : new AbiCoder();
1859
+ const encoded = coder.encode(["address", "string"], [getAddress(subdao), String(libraryId)]);
1860
+ return keccak256(encoded);
1861
+ }
1845
1862
  async function getLatestLibrary({ provider, registry, subdao, libraryId = "main" }) {
1846
1863
  if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
1847
1864
  const addr = normalise(registry, "registry");
1848
1865
  const sub = normalise(subdao, "subdao");
1849
1866
  const contract = new Contract(addr, ABI.LibraryRegistry, provider);
1850
- const key = keccak256(toUtf8Bytes(`${sub.toLowerCase()}::${libraryId}`));
1867
+ const key = _computeLibraryKey(sub, libraryId);
1851
1868
  const latestCID = await contract.subdaoLibraryLatest(key).catch(() => "");
1852
1869
  if (!latestCID || latestCID.length === 0) return null;
1853
1870
  const info = await getManifestInfo({ provider, registry: addr, manifestCID: latestCID });
1854
1871
  return info;
1855
1872
  }
1873
+ async function getBeforeAfterForUpdate({ provider, registry, subdao, libraryId = "main", newCid }) {
1874
+ if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
1875
+ const addr = normalise(registry, "registry");
1876
+ const sub = normalise(subdao, "subdao");
1877
+ const contract = new Contract(addr, ABI.LibraryRegistry, provider);
1878
+ const key = _computeLibraryKey(sub, libraryId);
1879
+ const prev = await contract.subdaoLibraryLatest(key).catch(() => "");
1880
+ return { previousCID: prev || null, newCID: String(newCid), libraryId: String(libraryId) };
1881
+ }
1856
1882
  async function hasScopedOwnership({ provider, registry, subdao, manifestCID }) {
1857
1883
  if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
1858
1884
  const addr = normalise(registry, "registry");
@@ -1878,6 +1904,8 @@ var require_library = __commonJS({
1878
1904
  getLatestLibrary,
1879
1905
  hasScopedOwnership,
1880
1906
  buildUpdateLibraryForSubDAOTx,
1907
+ _computeLibraryKey,
1908
+ getBeforeAfterForUpdate,
1881
1909
  /** Build an authorizeTimelock(timelock, subdao) call for a LibraryRegistry. */
1882
1910
  buildAuthorizeTimelockTx: function buildAuthorizeTimelockTx({ registry, timelock, subdao }) {
1883
1911
  const to = normalise(registry, "registry");
@@ -2123,6 +2151,62 @@ var require_governance = __commonJS({
2123
2151
  const data = iface.encodeFunctionData("execute(uint256)", [id]);
2124
2152
  return { to: addr, data, value: BigIntZero };
2125
2153
  }
2154
+ async function decodeProposalEffects({ provider, governor, proposalId, fromBlock = 0, toBlock = "latest" }) {
2155
+ if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2156
+ const govAddr = normaliseGovernor(governor);
2157
+ const iface = new Interface([ABI.Events.ProposalCreated]);
2158
+ const topic = iface.getEvent("ProposalCreated").topicHash;
2159
+ const id = typeof proposalId === "bigint" ? proposalId : String(proposalId).startsWith("0x") ? BigInt(proposalId) : BigInt(String(proposalId));
2160
+ let parsed = null;
2161
+ let logTxHash = null;
2162
+ const logs = await provider.getLogs({ address: govAddr, fromBlock, toBlock, topics: [topic] });
2163
+ for (const log of logs) {
2164
+ try {
2165
+ const p = iface.parseLog(log);
2166
+ if (BigInt(p.args.id.toString()) === id) {
2167
+ parsed = p;
2168
+ logTxHash = log.transactionHash;
2169
+ break;
2170
+ }
2171
+ } catch (_) {
2172
+ }
2173
+ }
2174
+ if (!parsed) throw new Error("ProposalCreated event not found");
2175
+ const targets = parsed.args.targets.map(getAddress);
2176
+ const calldatas = parsed.args.calldatas.map((d) => typeof d === "string" ? d : "0x" + Buffer.from(d).toString("hex"));
2177
+ const LibraryIface = new Interface(["function updateLibraryForSubDAO(address,string,string,uint256)", "function subdaoLibraryLatest(bytes32) view returns (string)"]);
2178
+ const PromptIface = new Interface(["function updatePromptByGovernance(string,string)"]);
2179
+ const coder = AbiCoder.defaultAbiCoder ? AbiCoder.defaultAbiCoder() : new AbiCoder();
2180
+ const effects = [];
2181
+ for (let i = 0; i < calldatas.length; i++) {
2182
+ const data = calldatas[i];
2183
+ const sel = data.slice(0, 10);
2184
+ try {
2185
+ const decoded = LibraryIface.decodeFunctionData("updateLibraryForSubDAO", data);
2186
+ const [subdao, libraryId, newCid, promptCount] = decoded;
2187
+ let previousCID = null;
2188
+ try {
2189
+ const reg = new Contract(targets[i], ["function subdaoLibraryLatest(bytes32) view returns (string)"], provider);
2190
+ const key = keccak256(coder.encode(["address", "string"], [getAddress(subdao), String(libraryId)]));
2191
+ const prev = await reg.subdaoLibraryLatest(key).catch(() => "");
2192
+ previousCID = prev && prev.length ? String(prev) : null;
2193
+ } catch (_) {
2194
+ }
2195
+ effects.push({ type: "libraryUpdate", index: i, target: targets[i], subdao: getAddress(subdao), libraryId: String(libraryId), previousCid: previousCID, newCid: String(newCid), promptCount: Number(promptCount) });
2196
+ continue;
2197
+ } catch (_) {
2198
+ }
2199
+ try {
2200
+ const decodedP = PromptIface.decodeFunctionData("updatePromptByGovernance", data);
2201
+ const [key, newCid] = decodedP;
2202
+ effects.push({ type: "promptUpdate", index: i, target: targets[i], key: String(key), newCid: String(newCid) });
2203
+ continue;
2204
+ } catch (_) {
2205
+ }
2206
+ effects.push({ type: "unknown", index: i, target: targets[i], selector: sel });
2207
+ }
2208
+ return { governor: govAddr, proposalId: id, tx: logTxHash, actions: { count: calldatas.length }, effects };
2209
+ }
2126
2210
  async function getQuorumAt({ provider, governor, blockTag }) {
2127
2211
  if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
2128
2212
  const addr = normaliseGovernor(governor);
@@ -2252,6 +2336,7 @@ var require_governance = __commonJS({
2252
2336
  buildExecuteTx,
2253
2337
  buildQueueByIdTx,
2254
2338
  buildExecuteByIdTx,
2339
+ decodeProposalEffects,
2255
2340
  makeProposalDescription,
2256
2341
  stripProposalSalt,
2257
2342
  getQuorumAt,
@@ -4091,7 +4176,7 @@ var require_factory = __commonJS({
4091
4176
  // src/browser/library.js
4092
4177
  var require_library2 = __commonJS({
4093
4178
  "src/browser/library.js"(exports, module) {
4094
- var { Contract, getAddress, keccak256, toUtf8Bytes, Interface } = __require("ethers");
4179
+ var { Contract, getAddress, keccak256, AbiCoder, Interface } = __require("ethers");
4095
4180
  var ABI = require_abi();
4096
4181
  var { SageSDKError, CODES } = require_errors();
4097
4182
  var { searchRegistry } = require_search();
@@ -4128,12 +4213,17 @@ var require_library2 = __commonJS({
4128
4213
  promptCount: Number(promptCount)
4129
4214
  };
4130
4215
  }
4216
+ function _computeLibraryKey(subdao, libraryId) {
4217
+ const coder = AbiCoder.defaultAbiCoder ? AbiCoder.defaultAbiCoder() : new AbiCoder();
4218
+ const encoded = coder.encode(["address", "string"], [getAddress(subdao), String(libraryId)]);
4219
+ return keccak256(encoded);
4220
+ }
4131
4221
  async function getLatestLibrary({ provider, registry, subdao, libraryId = "main" }) {
4132
4222
  if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
4133
4223
  const addr = normalise(registry, "registry");
4134
4224
  const sub = normalise(subdao, "subdao");
4135
4225
  const contract = new Contract(addr, ABI.LibraryRegistry, provider);
4136
- const key = keccak256(toUtf8Bytes(`${sub.toLowerCase()}::${libraryId}`));
4226
+ const key = _computeLibraryKey(sub, libraryId);
4137
4227
  const latestCID = await contract.subdaoLibraryLatest(key).catch(() => "");
4138
4228
  if (!latestCID || latestCID.length === 0) return null;
4139
4229
  const info = await getManifestInfo({ provider, registry: addr, manifestCID: latestCID });
@@ -4158,6 +4248,15 @@ var require_library2 = __commonJS({
4158
4248
  ]);
4159
4249
  return { to, data, value: 0n };
4160
4250
  }
4251
+ async function getBeforeAfterForUpdate({ provider, registry, subdao, libraryId = "main", newCid }) {
4252
+ if (!provider) throw new SageSDKError(CODES.INVALID_ARGS, "provider required");
4253
+ const addr = normalise(registry, "registry");
4254
+ const sub = normalise(subdao, "subdao");
4255
+ const contract = new Contract(addr, ABI.LibraryRegistry, provider);
4256
+ const key = _computeLibraryKey(sub, libraryId);
4257
+ const prev = await contract.subdaoLibraryLatest(key).catch(() => "");
4258
+ return { previousCID: prev || null, newCID: String(newCid), libraryId: String(libraryId) };
4259
+ }
4161
4260
  function buildAuthorizeTimelockTx({ registry, timelock, subdao }) {
4162
4261
  const to = normalise(registry, "registry");
4163
4262
  const iface = new Interface(["function authorizeTimelock(address,address)"]);
@@ -4170,6 +4269,7 @@ var require_library2 = __commonJS({
4170
4269
  getLatestLibrary,
4171
4270
  hasScopedOwnership,
4172
4271
  buildUpdateLibraryForSubDAOTx,
4272
+ getBeforeAfterForUpdate,
4173
4273
  buildAuthorizeTimelockTx,
4174
4274
  searchRegistry
4175
4275
  };
@@ -5779,7 +5879,23 @@ var require_treasury = __commonJS({
5779
5879
  confirmWithdrawal,
5780
5880
  cancelWithdrawal,
5781
5881
  setPriceOverride,
5782
- clearPriceOverride
5882
+ clearPriceOverride,
5883
+ // TX builders for app usage
5884
+ buildApproveTx: ({ token, spender, amount, decimals = 18 }) => {
5885
+ if (!token || !spender) throw new SageSDKError(CODES.INVALID_ARGS, "token and spender required");
5886
+ const iface = new Interface(["function approve(address,uint256)"]);
5887
+ return { to: getAddress(token), data: iface.encodeFunctionData("approve", [getAddress(spender), BigInt(amount.toString ? amount.toString() : amount)]), value: 0n };
5888
+ },
5889
+ buildTransferTx: ({ token, to, amount, decimals = 18 }) => {
5890
+ if (!token || !to) throw new SageSDKError(CODES.INVALID_ARGS, "token and to required");
5891
+ const iface = new Interface(["function transfer(address,uint256)"]);
5892
+ return { to: getAddress(token), data: iface.encodeFunctionData("transfer", [getAddress(to), BigInt(amount.toString ? amount.toString() : amount)]), value: 0n };
5893
+ },
5894
+ buildWrapEthTx: ({ weth, amountWei }) => {
5895
+ if (!weth) throw new SageSDKError(CODES.INVALID_ARGS, "weth required");
5896
+ const iface = new Interface(["function deposit() payable"]);
5897
+ return { to: getAddress(weth), data: iface.encodeFunctionData("deposit", []), value: BigInt(amountWei.toString ? amountWei.toString() : amountWei) };
5898
+ }
5783
5899
  };
5784
5900
  }
5785
5901
  });
@@ -6120,12 +6236,40 @@ var require_doppler = __commonJS({
6120
6236
  }
6121
6237
  return { auctionAddress, note: "Lens API not available in this doppler-sdk version" };
6122
6238
  }
6239
+ async function listAuctions(sdk, { limit = 10, cursor } = {}) {
6240
+ const lens = sdk.lens;
6241
+ if (lens) {
6242
+ if (typeof lens.listAuctions === "function") {
6243
+ try {
6244
+ return await lens.listAuctions({ limit, cursor });
6245
+ } catch (e) {
6246
+ }
6247
+ }
6248
+ if (typeof lens.getAuctions === "function") {
6249
+ try {
6250
+ return await lens.getAuctions({ limit, cursor });
6251
+ } catch (e) {
6252
+ }
6253
+ }
6254
+ }
6255
+ const fac = sdk.factory;
6256
+ if (fac && typeof fac.listAuctions === "function") {
6257
+ try {
6258
+ return await fac.listAuctions({ limit, cursor });
6259
+ } catch (e) {
6260
+ }
6261
+ }
6262
+ return { ok: false, error: "LIST_UNSUPPORTED", message: "Listing auctions not supported by this doppler-sdk version" };
6263
+ }
6123
6264
  async function buyTokens(sdk, { auctionAddress, numeraireAmount }) {
6124
6265
  const trade = sdk.trade || sdk.swap || sdk.auction;
6125
6266
  if (trade && typeof trade.buy === "function") {
6126
- return await trade.buy({ auctionAddress, numeraireAmount });
6267
+ try {
6268
+ return await trade.buy({ auctionAddress, numeraireAmount });
6269
+ } catch (e) {
6270
+ }
6127
6271
  }
6128
- throw new Error("Buy is not supported by the current doppler-sdk version");
6272
+ return { ok: false, error: "BUY_UNSUPPORTED", message: "Buy not supported by this doppler-sdk version. Use executeV3BuyExactIn/urExecute helpers or upgrade doppler-sdk." };
6129
6273
  }
6130
6274
  async function migrate(sdk, asset) {
6131
6275
  if (sdk.airlock && typeof sdk.airlock.migrate === "function") {
@@ -6137,6 +6281,7 @@ var require_doppler = __commonJS({
6137
6281
  initDoppler,
6138
6282
  deployDynamicAuction,
6139
6283
  getAuctionStatus,
6284
+ listAuctions,
6140
6285
  buyTokens,
6141
6286
  migrate
6142
6287
  };