@sage-protocol/sdk 0.1.6 → 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/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.6",
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
  }
@@ -250,19 +262,20 @@ var require_abi = __commonJS({
250
262
  var PersonalLicenseReceipt = [
251
263
  "function balanceOf(address account, uint256 id) view returns (uint256)"
252
264
  ];
253
- var TreasuryWrapper = [
254
- "function execute(address,uint256,bytes,bytes32) returns (bool)",
255
- "function allowedTargets(address) view returns (bool)",
256
- "function allowedSelectors(bytes4) view returns (bool)",
257
- "function owners(address) view returns (bool)",
258
- "function ownerCount() view returns (uint256)",
259
- "function registry() view returns (address)",
260
- "event TreasuryAction(address indexed caller, address indexed target, uint256 value, bytes data, bytes32 refId)",
261
- "event AllowedTargetUpdated(address indexed target, bool allowed)",
262
- "event AllowedSelectorUpdated(bytes4 indexed selector, bool allowed)",
263
- "event OwnerAdded(address indexed owner)",
264
- "event OwnerRemoved(address indexed owner)",
265
- "event TokensSwept(address indexed token, address indexed to, uint256 amount)"
265
+ var SageTreasury = [
266
+ "function totalReserves() view returns (uint256)",
267
+ "function totalPOL() view returns (uint256)",
268
+ "function totalDebt() view returns (uint256)",
269
+ "function canonicalPool() view returns (address)",
270
+ "function routerOrVault() view returns (address)",
271
+ "function maxWithdrawalRate() view returns (uint256)",
272
+ "function emergencyWithdrawalLimit() view returns (uint256)",
273
+ "function getReserveTokens() view returns (address[])",
274
+ "function getReserve(address token) view returns (address tokenAddress,uint256 amount,uint256 value,bool isLP,bool isActive)",
275
+ "function pendingWithdrawals(uint256) view returns (address token,address recipient,uint256 amount,uint256 value,address requester,uint256 balanceBefore,uint256 recipientBalanceBefore,uint256 depositSnapshot,bool isLP,bool isEmergency,bool exists)",
276
+ "function nextWithdrawalId() view returns (uint256)",
277
+ "function manualPrices(address token) view returns (uint256 price,uint256 expiresAt,bool active)",
278
+ "function lpContributions(address,address) view returns (uint256)"
266
279
  ];
267
280
  var GovernanceBoostMerkle = [
268
281
  "function getProposalConfig(uint256) view returns (tuple(uint256 proposalId,address token,uint256 totalAmount,uint64 startTime,uint64 endTime,uint256 merkleRoot))",
@@ -274,6 +287,29 @@ var require_abi = __commonJS({
274
287
  "function create(uint256 proposalId, address token, uint256 perVoter, uint256 maxVoters)",
275
288
  "function fund(uint256 proposalId, uint256 amount)"
276
289
  ];
290
+ var BondDepository = [
291
+ // Core getters
292
+ "function payoutToken() view returns (address)",
293
+ "function principalToken() view returns (address)",
294
+ "function treasury() view returns (address)",
295
+ // Terms
296
+ "function terms() view returns (tuple(uint256 controlVariable,uint256 minimumPrice,uint256 maxPayout,uint256 maxDebt,uint256 vestingTerm,uint256 fee))",
297
+ "function totalDebt(address) view returns (uint256)",
298
+ // Pricing
299
+ "function bondPrice() view returns (uint256)",
300
+ "function bondPrice(address) view returns (uint256)",
301
+ "function bondPriceInUSD() view returns (uint256)",
302
+ "function currentDebt() view returns (uint256)",
303
+ "function debtRatio() view returns (uint256)",
304
+ "function standardizedDebtRatio() view returns (uint256)",
305
+ // User views
306
+ "function bondInfo(address) view returns (tuple(uint256 payout,uint256 vesting,uint256 lastBlock,uint256 pricePaid))",
307
+ "function pendingPayout(address) view returns (uint256)",
308
+ "function percentVestedFor(address) view returns (uint256)",
309
+ // Actions
310
+ "function deposit(uint256 _amount, uint256 _maxPrice) returns (uint256 payout_)",
311
+ "function redeem(address _recipient, bool _stake) returns (uint256)"
312
+ ];
277
313
  var Events = {
278
314
  ProposalCreated: "event ProposalCreated(uint256 id, address proposer, address[] targets, uint256[] values, string[] signatures, bytes[] calldatas, uint256 startBlock, uint256 endBlock, string description)"
279
315
  };
@@ -292,10 +328,10 @@ var require_abi = __commonJS({
292
328
  PersonalLibraryFacet,
293
329
  PersonalMarketplace,
294
330
  PersonalLicenseReceipt,
295
- TreasuryWrapper,
296
- // Protocol treasury (replaces SageTreasury)
331
+ SageTreasury,
297
332
  GovernanceBoostMerkle,
298
333
  GovernanceBoostDirect,
334
+ BondDepository,
299
335
  Events
300
336
  };
301
337
  }
@@ -8093,6 +8129,952 @@ var require_doppler = __commonJS({
8093
8129
  }
8094
8130
  });
8095
8131
 
8132
+ // src/services/utils/cache.js
8133
+ var require_cache = __commonJS({
8134
+ "src/services/utils/cache.js"(exports2, module2) {
8135
+ var SimpleCache = class {
8136
+ constructor(options = {}) {
8137
+ this.enabled = options.enabled !== false;
8138
+ this.defaultTTL = options.ttl || 3e4;
8139
+ this.maxSize = options.maxSize || 100;
8140
+ this._store = /* @__PURE__ */ new Map();
8141
+ }
8142
+ /**
8143
+ * Get value from cache
8144
+ * @param {string} key - Cache key
8145
+ * @returns {any|null} - Cached value or null if expired/missing
8146
+ */
8147
+ get(key) {
8148
+ if (!this.enabled) return null;
8149
+ const entry = this._store.get(key);
8150
+ if (!entry) return null;
8151
+ if (Date.now() > entry.expires) {
8152
+ this._store.delete(key);
8153
+ return null;
8154
+ }
8155
+ return entry.data;
8156
+ }
8157
+ /**
8158
+ * Set value in cache with TTL
8159
+ * @param {string} key - Cache key
8160
+ * @param {any} data - Data to cache
8161
+ * @param {number} ttl - Time to live in milliseconds (optional)
8162
+ */
8163
+ set(key, data, ttl) {
8164
+ if (!this.enabled) return;
8165
+ if (this._store.size >= this.maxSize && !this._store.has(key)) {
8166
+ const firstKey = this._store.keys().next().value;
8167
+ this._store.delete(firstKey);
8168
+ }
8169
+ this._store.set(key, {
8170
+ data,
8171
+ expires: Date.now() + (ttl || this.defaultTTL)
8172
+ });
8173
+ }
8174
+ /**
8175
+ * Clear all cached entries
8176
+ */
8177
+ clear() {
8178
+ this._store.clear();
8179
+ }
8180
+ /**
8181
+ * Delete specific key
8182
+ * @param {string} key - Cache key to delete
8183
+ */
8184
+ delete(key) {
8185
+ this._store.delete(key);
8186
+ }
8187
+ /**
8188
+ * Get cache statistics
8189
+ * @returns {object} - Cache stats (size, enabled)
8190
+ */
8191
+ stats() {
8192
+ return {
8193
+ size: this._store.size,
8194
+ maxSize: this.maxSize,
8195
+ enabled: this.enabled
8196
+ };
8197
+ }
8198
+ };
8199
+ module2.exports = { SimpleCache };
8200
+ }
8201
+ });
8202
+
8203
+ // src/services/utils/retry.js
8204
+ var require_retry = __commonJS({
8205
+ "src/services/utils/retry.js"(exports2, module2) {
8206
+ async function retryWithBackoff(fn, options = {}) {
8207
+ const {
8208
+ attempts = 3,
8209
+ baseDelay = 1e3,
8210
+ maxDelay = 1e4,
8211
+ onRetry = null
8212
+ } = options;
8213
+ let lastError;
8214
+ for (let i = 0; i < attempts; i++) {
8215
+ try {
8216
+ return await fn();
8217
+ } catch (error) {
8218
+ lastError = error;
8219
+ if (i === attempts - 1) {
8220
+ break;
8221
+ }
8222
+ const delay = Math.min(baseDelay * Math.pow(2, i), maxDelay);
8223
+ if (onRetry) {
8224
+ onRetry({
8225
+ attempt: i + 1,
8226
+ totalAttempts: attempts,
8227
+ delay,
8228
+ error
8229
+ });
8230
+ }
8231
+ await sleep(delay);
8232
+ }
8233
+ }
8234
+ throw lastError;
8235
+ }
8236
+ function sleep(ms) {
8237
+ return new Promise((resolve) => setTimeout(resolve, ms));
8238
+ }
8239
+ module2.exports = { retryWithBackoff, sleep };
8240
+ }
8241
+ });
8242
+
8243
+ // src/errors/index.js
8244
+ var require_errors2 = __commonJS({
8245
+ "src/errors/index.js"(exports2, module2) {
8246
+ var SageSDKError = class extends Error {
8247
+ constructor(message, code, retryable = false, originalError = null) {
8248
+ super(message);
8249
+ this.name = this.constructor.name;
8250
+ this.code = code;
8251
+ this.retryable = retryable;
8252
+ this.originalError = originalError;
8253
+ }
8254
+ };
8255
+ var SubgraphError = class extends SageSDKError {
8256
+ /**
8257
+ * @param {string} message - Error message
8258
+ * @param {'TIMEOUT'|'NETWORK'|'INVALID_RESPONSE'|'NOT_FOUND'|'QUERY_FAILED'} code - Error code
8259
+ * @param {boolean} retryable - Whether error is retryable
8260
+ * @param {Error} originalError - Original error (optional)
8261
+ */
8262
+ constructor(message, code, retryable = false, originalError = null) {
8263
+ super(message, code, retryable, originalError);
8264
+ }
8265
+ };
8266
+ var IPFSError = class extends SageSDKError {
8267
+ /**
8268
+ * @param {string} message - Error message
8269
+ * @param {'TIMEOUT'|'PIN_FAILED'|'INVALID_CID'|'NOT_FOUND'|'GATEWAY_FAILED'|'UPLOAD_FAILED'} code - Error code
8270
+ * @param {boolean} retryable - Whether error is retryable
8271
+ * @param {Error} originalError - Original error (optional)
8272
+ */
8273
+ constructor(message, code, retryable = false, originalError = null) {
8274
+ super(message, code, retryable, originalError);
8275
+ }
8276
+ };
8277
+ function formatErrorMessage(error) {
8278
+ if (!error) return "An unknown error occurred";
8279
+ if (error instanceof SubgraphError || error instanceof IPFSError) {
8280
+ switch (error.code) {
8281
+ case "TIMEOUT":
8282
+ return "Request timed out. Please try again.";
8283
+ case "NETWORK":
8284
+ return "Network error. Check your connection and try again.";
8285
+ case "INVALID_RESPONSE":
8286
+ return "Received invalid data from server.";
8287
+ case "NOT_FOUND":
8288
+ return "Content not found.";
8289
+ case "PIN_FAILED":
8290
+ return "Failed to pin content to IPFS. Please try again.";
8291
+ case "INVALID_CID":
8292
+ return "Invalid content identifier (CID).";
8293
+ case "GATEWAY_FAILED":
8294
+ return "All IPFS gateways failed. Content may be temporarily unavailable.";
8295
+ case "UPLOAD_FAILED":
8296
+ return "Failed to upload content. Please try again.";
8297
+ case "QUERY_FAILED":
8298
+ return "Failed to query data. Please try again.";
8299
+ default:
8300
+ return error.message || "An error occurred";
8301
+ }
8302
+ }
8303
+ return error.message || "An error occurred";
8304
+ }
8305
+ function isRetryable(error) {
8306
+ if (error instanceof SageSDKError) {
8307
+ return error.retryable;
8308
+ }
8309
+ if (error.name === "AbortError" || error.name === "TimeoutError") {
8310
+ return true;
8311
+ }
8312
+ const message = String(error.message || "").toLowerCase();
8313
+ const retryablePatterns = [
8314
+ "timeout",
8315
+ "network",
8316
+ "econnrefused",
8317
+ "econnreset",
8318
+ "etimedout",
8319
+ "fetch failed"
8320
+ ];
8321
+ return retryablePatterns.some((pattern) => message.includes(pattern));
8322
+ }
8323
+ module2.exports = {
8324
+ SageSDKError,
8325
+ SubgraphError,
8326
+ IPFSError,
8327
+ formatErrorMessage,
8328
+ isRetryable
8329
+ };
8330
+ }
8331
+ });
8332
+
8333
+ // src/services/subgraph/client.js
8334
+ var require_client = __commonJS({
8335
+ "src/services/subgraph/client.js"(exports2, module2) {
8336
+ var subgraph = require_subgraph();
8337
+ var { SimpleCache } = require_cache();
8338
+ var { retryWithBackoff } = require_retry();
8339
+ var { SubgraphError } = require_errors2();
8340
+ var SubgraphService = class {
8341
+ /**
8342
+ * @param {object} config - Service configuration
8343
+ * @param {string} config.url - Subgraph GraphQL endpoint
8344
+ * @param {number} [config.timeout=10000] - Request timeout in ms
8345
+ * @param {number} [config.retries=3] - Number of retry attempts
8346
+ * @param {object} [config.cache] - Cache configuration
8347
+ * @param {boolean} [config.cache.enabled=true] - Enable caching
8348
+ * @param {number} [config.cache.ttl=30000] - Cache TTL in ms
8349
+ * @param {number} [config.cache.maxSize=100] - Max cache entries
8350
+ */
8351
+ constructor(config) {
8352
+ if (!config || !config.url) {
8353
+ throw new Error("SubgraphService requires a url in config");
8354
+ }
8355
+ this.url = config.url;
8356
+ this.timeout = config.timeout || 1e4;
8357
+ this.retries = config.retries || 3;
8358
+ this._cache = new SimpleCache({
8359
+ enabled: config.cache?.enabled !== false,
8360
+ ttl: config.cache?.ttl || 3e4,
8361
+ maxSize: config.cache?.maxSize || 100
8362
+ });
8363
+ }
8364
+ /**
8365
+ * Get list of SubDAOs
8366
+ * @param {object} [options] - Query options
8367
+ * @param {number} [options.limit=50] - Max results
8368
+ * @param {number} [options.skip=0] - Results to skip
8369
+ * @param {boolean} [options.cache=true] - Use cache
8370
+ * @returns {Promise<Array>} - List of SubDAOs
8371
+ */
8372
+ async getSubDAOs(options = {}) {
8373
+ const { limit = 50, skip = 0, cache = true } = options;
8374
+ const cacheKey = `subdaos:${limit}:${skip}`;
8375
+ if (cache) {
8376
+ const cached = this._cache.get(cacheKey);
8377
+ if (cached) return cached;
8378
+ }
8379
+ try {
8380
+ const result = await retryWithBackoff(
8381
+ () => this._querySubDAOs({ limit, skip }),
8382
+ { attempts: this.retries }
8383
+ );
8384
+ if (cache) {
8385
+ this._cache.set(cacheKey, result);
8386
+ }
8387
+ return result;
8388
+ } catch (error) {
8389
+ throw new SubgraphError(
8390
+ `Failed to fetch SubDAOs: ${error.message}`,
8391
+ "QUERY_FAILED",
8392
+ true,
8393
+ error
8394
+ );
8395
+ }
8396
+ }
8397
+ /**
8398
+ * Get list of proposals
8399
+ * @param {object} [options] - Query options
8400
+ * @param {string} [options.governor] - Filter by governor address
8401
+ * @param {string} [options.subdao] - Filter by SubDAO address
8402
+ * @param {string[]} [options.states] - Filter by proposal states
8403
+ * @param {number} [options.fromTimestamp] - Filter by creation time (>=)
8404
+ * @param {number} [options.toTimestamp] - Filter by creation time (<=)
8405
+ * @param {number} [options.limit=20] - Max results
8406
+ * @param {number} [options.skip=0] - Results to skip
8407
+ * @param {boolean} [options.cache=true] - Use cache
8408
+ * @returns {Promise<Array>} - List of proposals
8409
+ */
8410
+ async getProposals(options = {}) {
8411
+ const {
8412
+ governor,
8413
+ subdao,
8414
+ states,
8415
+ fromTimestamp,
8416
+ toTimestamp,
8417
+ limit = 20,
8418
+ skip = 0,
8419
+ cache = true
8420
+ } = options;
8421
+ const cacheKey = `proposals:${JSON.stringify({
8422
+ governor,
8423
+ subdao,
8424
+ states,
8425
+ fromTimestamp,
8426
+ toTimestamp,
8427
+ limit,
8428
+ skip
8429
+ })}`;
8430
+ if (cache) {
8431
+ const cached = this._cache.get(cacheKey);
8432
+ if (cached) return cached;
8433
+ }
8434
+ try {
8435
+ const result = await retryWithBackoff(
8436
+ () => this._queryProposals({
8437
+ governor,
8438
+ subdao,
8439
+ states,
8440
+ fromTimestamp,
8441
+ toTimestamp,
8442
+ limit,
8443
+ skip
8444
+ }),
8445
+ { attempts: this.retries }
8446
+ );
8447
+ if (cache) {
8448
+ this._cache.set(cacheKey, result);
8449
+ }
8450
+ return result;
8451
+ } catch (error) {
8452
+ throw new SubgraphError(
8453
+ `Failed to fetch proposals: ${error.message}`,
8454
+ "QUERY_FAILED",
8455
+ true,
8456
+ error
8457
+ );
8458
+ }
8459
+ }
8460
+ /**
8461
+ * Get proposal by ID
8462
+ * @param {string} id - Proposal ID
8463
+ * @param {object} [options] - Query options
8464
+ * @param {boolean} [options.cache=true] - Use cache
8465
+ * @returns {Promise<object|null>} - Proposal or null
8466
+ */
8467
+ async getProposalById(id2, options = {}) {
8468
+ const { cache = true } = options;
8469
+ const cacheKey = `proposal:${id2}`;
8470
+ if (cache) {
8471
+ const cached = this._cache.get(cacheKey);
8472
+ if (cached !== null) return cached;
8473
+ }
8474
+ try {
8475
+ const result = await retryWithBackoff(
8476
+ () => subgraph.getProposalById({ url: this.url, id: id2 }),
8477
+ { attempts: this.retries }
8478
+ );
8479
+ if (cache) {
8480
+ this._cache.set(cacheKey, result);
8481
+ }
8482
+ return result;
8483
+ } catch (error) {
8484
+ throw new SubgraphError(
8485
+ `Failed to fetch proposal ${id2}: ${error.message}`,
8486
+ "QUERY_FAILED",
8487
+ true,
8488
+ error
8489
+ );
8490
+ }
8491
+ }
8492
+ /**
8493
+ * Get libraries
8494
+ * @param {object} [options] - Query options
8495
+ * @param {string} [options.subdao] - Filter by SubDAO address
8496
+ * @param {number} [options.limit=50] - Max results
8497
+ * @param {number} [options.skip=0] - Results to skip
8498
+ * @param {boolean} [options.cache=true] - Use cache
8499
+ * @returns {Promise<Array>} - List of libraries
8500
+ */
8501
+ async getLibraries(options = {}) {
8502
+ const { subdao, limit = 50, skip = 0, cache = true } = options;
8503
+ const cacheKey = `libraries:${subdao || "all"}:${limit}:${skip}`;
8504
+ if (cache) {
8505
+ const cached = this._cache.get(cacheKey);
8506
+ if (cached) return cached;
8507
+ }
8508
+ try {
8509
+ const result = await retryWithBackoff(
8510
+ () => subgraph.listLibraries({ url: this.url, subdao, first: limit, skip }),
8511
+ { attempts: this.retries }
8512
+ );
8513
+ if (cache) {
8514
+ this._cache.set(cacheKey, result);
8515
+ }
8516
+ return result;
8517
+ } catch (error) {
8518
+ throw new SubgraphError(
8519
+ `Failed to fetch libraries: ${error.message}`,
8520
+ "QUERY_FAILED",
8521
+ true,
8522
+ error
8523
+ );
8524
+ }
8525
+ }
8526
+ /**
8527
+ * Get prompts by tag
8528
+ * @param {object} options - Query options
8529
+ * @param {string} options.tagsHash - Tag hash to filter by
8530
+ * @param {string} [options.registry] - Filter by registry address
8531
+ * @param {number} [options.limit=50] - Max results
8532
+ * @param {number} [options.skip=0] - Results to skip
8533
+ * @param {boolean} [options.cache=true] - Use cache
8534
+ * @returns {Promise<Array>} - List of prompts
8535
+ */
8536
+ async getPromptsByTag(options) {
8537
+ if (!options || !options.tagsHash) {
8538
+ throw new Error("tagsHash is required");
8539
+ }
8540
+ const { tagsHash, registry, limit = 50, skip = 0, cache = true } = options;
8541
+ const cacheKey = `prompts-tag:${tagsHash}:${registry || "all"}:${limit}:${skip}`;
8542
+ if (cache) {
8543
+ const cached = this._cache.get(cacheKey);
8544
+ if (cached) return cached;
8545
+ }
8546
+ try {
8547
+ const result = await retryWithBackoff(
8548
+ () => subgraph.listPromptsByTag({
8549
+ url: this.url,
8550
+ tagsHash,
8551
+ registry,
8552
+ first: limit,
8553
+ skip
8554
+ }),
8555
+ { attempts: this.retries }
8556
+ );
8557
+ if (cache) {
8558
+ this._cache.set(cacheKey, result);
8559
+ }
8560
+ return result;
8561
+ } catch (error) {
8562
+ throw new SubgraphError(
8563
+ `Failed to fetch prompts by tag: ${error.message}`,
8564
+ "QUERY_FAILED",
8565
+ true,
8566
+ error
8567
+ );
8568
+ }
8569
+ }
8570
+ /**
8571
+ * Get registry prompts
8572
+ * @param {object} options - Query options
8573
+ * @param {string} options.registry - Registry address
8574
+ * @param {number} [options.limit=50] - Max results
8575
+ * @param {number} [options.skip=0] - Results to skip
8576
+ * @param {boolean} [options.cache=true] - Use cache
8577
+ * @returns {Promise<Array>} - List of prompts
8578
+ */
8579
+ async getRegistryPrompts(options) {
8580
+ if (!options || !options.registry) {
8581
+ throw new Error("registry is required");
8582
+ }
8583
+ const { registry, limit = 50, skip = 0, cache = true } = options;
8584
+ const cacheKey = `registry-prompts:${registry}:${limit}:${skip}`;
8585
+ if (cache) {
8586
+ const cached = this._cache.get(cacheKey);
8587
+ if (cached) return cached;
8588
+ }
8589
+ try {
8590
+ const result = await retryWithBackoff(
8591
+ () => subgraph.listRegistryPrompts({
8592
+ url: this.url,
8593
+ registry,
8594
+ first: limit,
8595
+ skip
8596
+ }),
8597
+ { attempts: this.retries }
8598
+ );
8599
+ if (cache) {
8600
+ this._cache.set(cacheKey, result);
8601
+ }
8602
+ return result;
8603
+ } catch (error) {
8604
+ throw new SubgraphError(
8605
+ `Failed to fetch registry prompts: ${error.message}`,
8606
+ "QUERY_FAILED",
8607
+ true,
8608
+ error
8609
+ );
8610
+ }
8611
+ }
8612
+ /**
8613
+ * Clear cache
8614
+ */
8615
+ clearCache() {
8616
+ this._cache.clear();
8617
+ }
8618
+ /**
8619
+ * Get cache statistics
8620
+ * @returns {object} - Cache stats
8621
+ */
8622
+ getCacheStats() {
8623
+ return this._cache.stats();
8624
+ }
8625
+ // Private methods
8626
+ async _querySubDAOs({ limit, skip }) {
8627
+ const data = await subgraph.query(
8628
+ this.url,
8629
+ `
8630
+ query($first: Int!, $skip: Int!) {
8631
+ subDAOs(first: $first, skip: $skip, orderBy: createdAt, orderDirection: desc) {
8632
+ id
8633
+ address
8634
+ name
8635
+ description
8636
+ governor
8637
+ registry
8638
+ token
8639
+ createdAt
8640
+ }
8641
+ }
8642
+ `,
8643
+ { first: limit, skip }
8644
+ );
8645
+ return data?.subDAOs || [];
8646
+ }
8647
+ async _queryProposals({ governor, subdao, states, fromTimestamp, toTimestamp, limit, skip }) {
8648
+ return await subgraph.listProposalsFiltered({
8649
+ url: this.url,
8650
+ governor,
8651
+ states,
8652
+ fromTimestamp,
8653
+ toTimestamp,
8654
+ first: limit,
8655
+ skip
8656
+ });
8657
+ }
8658
+ };
8659
+ module2.exports = { SubgraphService };
8660
+ }
8661
+ });
8662
+
8663
+ // src/services/ipfs/client.js
8664
+ var require_client2 = __commonJS({
8665
+ "src/services/ipfs/client.js"(exports2, module2) {
8666
+ var ipfs = require_ipfs();
8667
+ var { SimpleCache } = require_cache();
8668
+ var { retryWithBackoff } = require_retry();
8669
+ var { IPFSError } = require_errors2();
8670
+ var IPFSService = class {
8671
+ /**
8672
+ * @param {object} config - Service configuration
8673
+ * @param {string} config.workerBaseUrl - IPFS worker base URL
8674
+ * @param {string} config.gateway - Primary IPFS gateway URL
8675
+ * @param {object} [config.signer] - ethers v6 signer for worker auth
8676
+ * @param {Function} [config.getAuth] - Function to get auth credentials
8677
+ * @param {string} [config.workerToken] - Bearer token for worker auth
8678
+ * @param {number} [config.timeout=15000] - Request timeout in ms
8679
+ * @param {number} [config.retries=2] - Number of retry attempts
8680
+ * @param {object} [config.cache] - Cache configuration
8681
+ * @param {boolean} [config.cache.enabled=true] - Enable caching
8682
+ * @param {number} [config.cache.ttl=300000] - Cache TTL in ms (default 5min for immutable CIDs)
8683
+ * @param {number} [config.cache.maxSize=50] - Max cache entries
8684
+ */
8685
+ constructor(config) {
8686
+ if (!config) {
8687
+ throw new Error("IPFSService requires a config object");
8688
+ }
8689
+ this.workerBaseUrl = config.workerBaseUrl;
8690
+ this.gateway = config.gateway;
8691
+ this.timeout = config.timeout || 15e3;
8692
+ this.retries = config.retries || 2;
8693
+ this._client = ipfs.createClient({
8694
+ workerBaseUrl: config.workerBaseUrl,
8695
+ gateway: config.gateway,
8696
+ workerSigner: config.signer,
8697
+ workerGetAuth: config.getAuth,
8698
+ workerToken: config.workerToken,
8699
+ timeoutMs: this.timeout,
8700
+ retries: this.retries
8701
+ });
8702
+ this._cache = new SimpleCache({
8703
+ enabled: config.cache?.enabled !== false,
8704
+ ttl: config.cache?.ttl || 3e5,
8705
+ // 5 minutes
8706
+ maxSize: config.cache?.maxSize || 50
8707
+ });
8708
+ }
8709
+ /**
8710
+ * Upload content to IPFS worker
8711
+ * @param {any} content - Content to upload (will be JSON stringified)
8712
+ * @param {object} [options] - Upload options
8713
+ * @param {string} [options.name] - Content name
8714
+ * @param {boolean} [options.warm=false] - Warm gateways after upload
8715
+ * @returns {Promise<string>} - CID of uploaded content
8716
+ */
8717
+ async upload(content, options = {}) {
8718
+ const { name = "upload", warm = false } = options;
8719
+ try {
8720
+ const result = await retryWithBackoff(
8721
+ () => this._client.uploadJson(content, name, {
8722
+ provider: "worker",
8723
+ warm
8724
+ }),
8725
+ { attempts: this.retries }
8726
+ );
8727
+ return result.cid;
8728
+ } catch (error) {
8729
+ throw new IPFSError(
8730
+ `Failed to upload content: ${error.message}`,
8731
+ "UPLOAD_FAILED",
8732
+ true,
8733
+ error
8734
+ );
8735
+ }
8736
+ }
8737
+ /**
8738
+ * Fetch JSON content by CID with parallel gateway fetching
8739
+ * @param {string} cid - IPFS CID
8740
+ * @param {object} [options] - Fetch options
8741
+ * @param {boolean} [options.cache=true] - Use cache
8742
+ * @param {number} [options.timeout=5000] - Timeout per gateway in ms
8743
+ * @param {string[]} [options.extraGateways] - Additional gateways to try
8744
+ * @returns {Promise<any|null>} - Parsed JSON content or null
8745
+ */
8746
+ async fetchByCID(cid, options = {}) {
8747
+ if (!cid) {
8748
+ throw new IPFSError("CID is required", "INVALID_CID", false);
8749
+ }
8750
+ const { cache = true, timeout = 5e3, extraGateways = [] } = options;
8751
+ if (cache) {
8752
+ const cached = this._cache.get(cid);
8753
+ if (cached) return cached;
8754
+ }
8755
+ const urls = this._client.buildGatewayUrls(cid, extraGateways);
8756
+ try {
8757
+ const result = await this._parallelFetch(urls, timeout);
8758
+ if (result === null) {
8759
+ throw new IPFSError(
8760
+ `All gateways failed for CID ${cid}`,
8761
+ "GATEWAY_FAILED",
8762
+ true
8763
+ );
8764
+ }
8765
+ if (cache) {
8766
+ this._cache.set(cid, result);
8767
+ }
8768
+ return result;
8769
+ } catch (error) {
8770
+ if (error instanceof IPFSError) throw error;
8771
+ throw new IPFSError(
8772
+ `Failed to fetch CID ${cid}: ${error.message}`,
8773
+ "GATEWAY_FAILED",
8774
+ true,
8775
+ error
8776
+ );
8777
+ }
8778
+ }
8779
+ /**
8780
+ * Pin CIDs to IPFS worker
8781
+ * @param {string|string[]} cids - CID or array of CIDs to pin
8782
+ * @param {object} [options] - Pin options
8783
+ * @param {boolean} [options.warm=false] - Warm gateways after pinning
8784
+ * @returns {Promise<void>}
8785
+ */
8786
+ async pin(cids, options = {}) {
8787
+ const { warm = false } = options;
8788
+ const cidList = Array.isArray(cids) ? cids : [cids];
8789
+ if (cidList.length === 0) {
8790
+ throw new IPFSError("At least one CID is required", "INVALID_CID", false);
8791
+ }
8792
+ try {
8793
+ await retryWithBackoff(
8794
+ () => this._client.pin({ cids: cidList, warm }),
8795
+ { attempts: this.retries }
8796
+ );
8797
+ } catch (error) {
8798
+ throw new IPFSError(
8799
+ `Failed to pin CIDs: ${error.message}`,
8800
+ "PIN_FAILED",
8801
+ true,
8802
+ error
8803
+ );
8804
+ }
8805
+ }
8806
+ /**
8807
+ * Warm gateways for a CID (prefetch)
8808
+ * @param {string} cid - CID to warm
8809
+ * @param {object} [options] - Warm options
8810
+ * @param {string[]} [options.gateways] - Specific gateways to warm
8811
+ * @returns {Promise<object>} - Warm result
8812
+ */
8813
+ async warm(cid, options = {}) {
8814
+ const { gateways } = options;
8815
+ try {
8816
+ return await this._client.warmGateways(cid, { gateways });
8817
+ } catch (error) {
8818
+ return { warmed: [] };
8819
+ }
8820
+ }
8821
+ /**
8822
+ * Clear cache
8823
+ */
8824
+ clearCache() {
8825
+ this._cache.clear();
8826
+ }
8827
+ /**
8828
+ * Get cache statistics
8829
+ * @returns {object} - Cache stats
8830
+ */
8831
+ getCacheStats() {
8832
+ return this._cache.stats();
8833
+ }
8834
+ // Private methods
8835
+ /**
8836
+ * Fetch from multiple gateways in parallel
8837
+ * @param {string[]} urls - Gateway URLs to try
8838
+ * @param {number} timeout - Timeout per gateway in ms
8839
+ * @returns {Promise<any|null>} - First successful result or null
8840
+ * @private
8841
+ */
8842
+ async _parallelFetch(urls, timeout) {
8843
+ if (!urls || urls.length === 0) {
8844
+ return null;
8845
+ }
8846
+ const fetchOne = async (url) => {
8847
+ const controller = new AbortController();
8848
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
8849
+ try {
8850
+ const response = await fetch(url, {
8851
+ signal: controller.signal,
8852
+ headers: { Accept: "application/json" }
8853
+ });
8854
+ clearTimeout(timeoutId);
8855
+ if (!response.ok) {
8856
+ return null;
8857
+ }
8858
+ const text = await response.text();
8859
+ try {
8860
+ return JSON.parse(text);
8861
+ } catch {
8862
+ return text;
8863
+ }
8864
+ } catch (error) {
8865
+ clearTimeout(timeoutId);
8866
+ return null;
8867
+ }
8868
+ };
8869
+ const results = await Promise.allSettled(urls.map((url) => fetchOne(url)));
8870
+ for (const result of results) {
8871
+ if (result.status === "fulfilled" && result.value !== null) {
8872
+ return result.value;
8873
+ }
8874
+ }
8875
+ return null;
8876
+ }
8877
+ };
8878
+ module2.exports = { IPFSService };
8879
+ }
8880
+ });
8881
+
8882
+ // src/hooks/useSubDAOs.js
8883
+ var require_useSubDAOs = __commonJS({
8884
+ "src/hooks/useSubDAOs.js"(exports2, module2) {
8885
+ var useSWR = __require("swr");
8886
+ function useSubDAOs(subgraphService, options = {}) {
8887
+ const {
8888
+ limit = 50,
8889
+ skip = 0,
8890
+ cache = true,
8891
+ refreshInterval,
8892
+ revalidateOnFocus = true,
8893
+ revalidateOnReconnect = true
8894
+ } = options;
8895
+ const cacheKey = subgraphService ? ["subdaos", subgraphService.url, limit, skip] : null;
8896
+ const fetcher = async () => {
8897
+ if (!subgraphService) {
8898
+ throw new Error("SubgraphService is required");
8899
+ }
8900
+ return await subgraphService.getSubDAOs({ limit, skip, cache });
8901
+ };
8902
+ return useSWR(cacheKey, fetcher, {
8903
+ refreshInterval,
8904
+ revalidateOnFocus,
8905
+ revalidateOnReconnect,
8906
+ dedupingInterval: cache ? 3e4 : 0
8907
+ // Match service cache TTL
8908
+ });
8909
+ }
8910
+ module2.exports = { useSubDAOs };
8911
+ }
8912
+ });
8913
+
8914
+ // src/hooks/useProposals.js
8915
+ var require_useProposals = __commonJS({
8916
+ "src/hooks/useProposals.js"(exports2, module2) {
8917
+ var useSWR = __require("swr");
8918
+ function useProposals(subgraphService, options = {}) {
8919
+ const {
8920
+ governor,
8921
+ subdao,
8922
+ states,
8923
+ fromTimestamp,
8924
+ toTimestamp,
8925
+ limit = 20,
8926
+ skip = 0,
8927
+ cache = true,
8928
+ refreshInterval,
8929
+ revalidateOnFocus = true,
8930
+ revalidateOnReconnect = true
8931
+ } = options;
8932
+ const cacheKey = subgraphService ? [
8933
+ "proposals",
8934
+ subgraphService.url,
8935
+ governor,
8936
+ subdao,
8937
+ states?.join(","),
8938
+ fromTimestamp,
8939
+ toTimestamp,
8940
+ limit,
8941
+ skip
8942
+ ] : null;
8943
+ const fetcher = async () => {
8944
+ if (!subgraphService) {
8945
+ throw new Error("SubgraphService is required");
8946
+ }
8947
+ return await subgraphService.getProposals({
8948
+ governor,
8949
+ subdao,
8950
+ states,
8951
+ fromTimestamp,
8952
+ toTimestamp,
8953
+ limit,
8954
+ skip,
8955
+ cache
8956
+ });
8957
+ };
8958
+ return useSWR(cacheKey, fetcher, {
8959
+ refreshInterval,
8960
+ revalidateOnFocus,
8961
+ revalidateOnReconnect,
8962
+ dedupingInterval: cache ? 3e4 : 0
8963
+ // Match service cache TTL
8964
+ });
8965
+ }
8966
+ module2.exports = { useProposals };
8967
+ }
8968
+ });
8969
+
8970
+ // src/hooks/useFetchCID.js
8971
+ var require_useFetchCID = __commonJS({
8972
+ "src/hooks/useFetchCID.js"(exports2, module2) {
8973
+ var useSWR = __require("swr");
8974
+ function useFetchCID(ipfsService, cid, options = {}) {
8975
+ const {
8976
+ cache = true,
8977
+ timeout = 5e3,
8978
+ extraGateways = [],
8979
+ refreshInterval,
8980
+ revalidateOnFocus = false,
8981
+ // CIDs are immutable
8982
+ revalidateOnReconnect = false
8983
+ // CIDs are immutable
8984
+ } = options;
8985
+ const cacheKey = ipfsService && cid ? ["ipfs-cid", cid] : null;
8986
+ const fetcher = async () => {
8987
+ if (!ipfsService) {
8988
+ throw new Error("IPFSService is required");
8989
+ }
8990
+ if (!cid) {
8991
+ throw new Error("CID is required");
8992
+ }
8993
+ return await ipfsService.fetchByCID(cid, {
8994
+ cache,
8995
+ timeout,
8996
+ extraGateways
8997
+ });
8998
+ };
8999
+ return useSWR(cacheKey, fetcher, {
9000
+ refreshInterval,
9001
+ revalidateOnFocus,
9002
+ revalidateOnReconnect,
9003
+ dedupingInterval: cache ? 3e5 : 0,
9004
+ // Match service cache TTL (5min)
9005
+ // CIDs are immutable, so we can cache errors too
9006
+ shouldRetryOnError: true,
9007
+ errorRetryInterval: 5e3,
9008
+ errorRetryCount: 3
9009
+ });
9010
+ }
9011
+ module2.exports = { useFetchCID };
9012
+ }
9013
+ });
9014
+
9015
+ // src/hooks/useUpload.js
9016
+ var require_useUpload = __commonJS({
9017
+ "src/hooks/useUpload.js"(exports2, module2) {
9018
+ var { useState, useCallback } = __require("react");
9019
+ function useUpload(ipfsService) {
9020
+ const [isUploading, setIsUploading] = useState(false);
9021
+ const [error, setError] = useState(null);
9022
+ const [data, setData] = useState(null);
9023
+ const upload = useCallback(
9024
+ async (content, options = {}) => {
9025
+ if (!ipfsService) {
9026
+ const err = new Error("IPFSService is required");
9027
+ setError(err);
9028
+ throw err;
9029
+ }
9030
+ setIsUploading(true);
9031
+ setError(null);
9032
+ try {
9033
+ const cid = await ipfsService.upload(content, options);
9034
+ setData(cid);
9035
+ return cid;
9036
+ } catch (err) {
9037
+ setError(err);
9038
+ throw err;
9039
+ } finally {
9040
+ setIsUploading(false);
9041
+ }
9042
+ },
9043
+ [ipfsService]
9044
+ );
9045
+ const reset = useCallback(() => {
9046
+ setIsUploading(false);
9047
+ setError(null);
9048
+ setData(null);
9049
+ }, []);
9050
+ return {
9051
+ upload,
9052
+ isUploading,
9053
+ error,
9054
+ data,
9055
+ reset
9056
+ };
9057
+ }
9058
+ module2.exports = { useUpload };
9059
+ }
9060
+ });
9061
+
9062
+ // src/hooks/index.js
9063
+ var require_hooks = __commonJS({
9064
+ "src/hooks/index.js"(exports2, module2) {
9065
+ var { useSubDAOs } = require_useSubDAOs();
9066
+ var { useProposals } = require_useProposals();
9067
+ var { useFetchCID } = require_useFetchCID();
9068
+ var { useUpload } = require_useUpload();
9069
+ module2.exports = {
9070
+ useSubDAOs,
9071
+ useProposals,
9072
+ useFetchCID,
9073
+ useUpload
9074
+ };
9075
+ }
9076
+ });
9077
+
8096
9078
  // src/index.js
8097
9079
  var require_src = __commonJS({
8098
9080
  "src/index.js"(exports2, module2) {
@@ -8133,6 +9115,16 @@ var require_src = __commonJS({
8133
9115
  openzeppelin: require_openzeppelin()
8134
9116
  }
8135
9117
  };
9118
+ var { SubgraphService } = require_client();
9119
+ var { IPFSService } = require_client2();
9120
+ var serviceErrors = require_errors2();
9121
+ var { SimpleCache } = require_cache();
9122
+ var { retryWithBackoff } = require_retry();
9123
+ var hooks = null;
9124
+ try {
9125
+ hooks = require_hooks();
9126
+ } catch (err) {
9127
+ }
8136
9128
  module2.exports = {
8137
9129
  version: pkg.version,
8138
9130
  getProvider: utils.getProvider,
@@ -8163,6 +9155,18 @@ var require_src = __commonJS({
8163
9155
  errors,
8164
9156
  doppler,
8165
9157
  adapters,
9158
+ // New service layer exports
9159
+ services: {
9160
+ SubgraphService,
9161
+ IPFSService
9162
+ },
9163
+ serviceErrors,
9164
+ serviceUtils: {
9165
+ SimpleCache,
9166
+ retryWithBackoff
9167
+ },
9168
+ // React hooks (optional - requires react + swr peer dependencies)
9169
+ hooks,
8166
9170
  // Legacy exports (deprecated): maintain compatibility while consumers migrate
8167
9171
  resolveGovernanceContext: async function legacyResolveGovernanceContext(args) {
8168
9172
  console.warn("[@sage-protocol/sdk] resolveGovernanceContext is deprecated. Use governance helpers instead.");