@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/README.md +288 -0
- package/dist/browser/index.mjs +54 -18
- package/dist/index.cjs +1022 -18
- package/dist/index.mjs +1022 -18
- package/dist/node/index.cjs +1022 -18
- package/dist/node/index.mjs +1022 -18
- package/package.json +15 -3
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).
|
package/dist/browser/index.mjs
CHANGED
|
@@ -20,7 +20,7 @@ var require_package = __commonJS({
|
|
|
20
20
|
"package.json"(exports, module) {
|
|
21
21
|
module.exports = {
|
|
22
22
|
name: "@sage-protocol/sdk",
|
|
23
|
-
version: "0.1.
|
|
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
|
-
|
|
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
|
|
254
|
-
"function
|
|
255
|
-
"function
|
|
256
|
-
"function
|
|
257
|
-
"function
|
|
258
|
-
"function
|
|
259
|
-
"function
|
|
260
|
-
"
|
|
261
|
-
"
|
|
262
|
-
"
|
|
263
|
-
"
|
|
264
|
-
"
|
|
265
|
-
"
|
|
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
|
-
|
|
296
|
-
// Protocol treasury (replaces SageTreasury)
|
|
331
|
+
SageTreasury,
|
|
297
332
|
GovernanceBoostMerkle,
|
|
298
333
|
GovernanceBoostDirect,
|
|
334
|
+
BondDepository,
|
|
299
335
|
Events
|
|
300
336
|
};
|
|
301
337
|
}
|