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