@sage-protocol/sdk 0.2.2 → 0.2.4

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
@@ -1,820 +1,203 @@
1
- [![Node.js Package](https://github.com/sage-protocol/sdk/actions/workflows/npm-publish-github-packages.yml/badge.svg)](https://github.com/sage-protocol/sdk/actions/workflows/npm-publish-github-packages.yml)
1
+ # Sage Protocol SDK
2
2
 
3
- Sage Protocol SDK
4
- =================
3
+ Backend-agnostic helpers for interacting with Sage Protocol contracts, IPFS, and governance systems.
5
4
 
6
- Purpose
7
- -------
8
- - Backend-agnostic helpers for interacting with Sage Protocol contracts (governance, SubDAOs, libraries, prompts, tokens).
9
- - Shared foundation for the CLI, backend services, and upcoming web UI.
10
-
11
- Design Principles
12
- -----------------
13
- - Pure data in/out; no console prompts or environment coupling.
14
- - Minimal ABI fragments maintained alongside the contracts (see `src/abi`).
15
- - Composable modules – import only what you need.
16
-
17
- Quick Start
18
- -----------
19
5
  ```bash
20
- npm i --workspace @sage-protocol/sdk
6
+ npm i @sage-protocol/sdk
21
7
  ```
22
8
 
9
+ ## Quick Start
10
+
23
11
  ```js
24
12
  import sdk from '@sage-protocol/sdk';
25
13
 
26
14
  const provider = sdk.getProvider({ rpcUrl: 'https://base-sepolia.publicnode.com' });
27
15
 
28
- // Discover SubDAOs deployed by the latest factory
16
+ // Discover SubDAOs
29
17
  const subdaos = await sdk.subdao.discoverSubDAOs({
30
18
  provider,
31
19
  factoryAddress: '0x89Fd9FfD04503A62c52E6769401AB928abbeD5cA',
32
20
  fromBlock: 0,
33
21
  });
34
22
 
35
- // Fetch governance + profile metadata for a SubDAO
23
+ // Fetch SubDAO info
36
24
  const info = await sdk.subdao.getSubDAOInfo({ provider, subdao: subdaos[0].subdao });
37
- // info.profileCID points at an IPFS JSON profile/playbook document (name, description, avatar, social links, etc.)
38
25
  ```
39
26
 
40
- **CommonJS usage**
41
-
27
+ **CommonJS**
42
28
  ```js
43
29
  const sdk = require('@sage-protocol/sdk');
44
30
  ```
45
31
 
46
- What’s New in 0.0.8
47
- --------------------
48
- - 0.0.8 Highlights
49
- - Governance helpers (additive): resolveVotesToken, buildDelegateSelfPreferred, delegateSelfAndVerify, ensureProposeGates, readinessToPropose, listActiveProposals
50
- - Library helpers (additive): buildAuthorizeTimelockTx, executionReadiness
51
- - Subgraph normalization: listProposalsFiltered returns state (string) and stateNum (0–7)
52
- - Backward compatible: no removed/renamed exports; all additions are optional
53
- - IPFS worker helpers: discovery signal APIs (`recordMcpUsage`, `recordLaunchEvent`, `recordDiscoveryEvent`) and governance queue actions (`submitGovernanceReport`, `listGovernanceReports`, `reviewGovernanceReport`, `batchGovernanceReports`)
54
-
55
- Module Overview
56
- ---------------
57
-
58
- | Module | Highlights | Typical Use |
59
- | ------ | ---------- | ----------- |
60
- | `getProvider()` | Minimal ethers v6 RPC helper | Shared provider across mini app, CLI, agents |
61
- | `governance` | Governor metadata, proposal listings, tx builders | Voting dashboards, automation bots |
62
- | `timelock` | Min delay, queued operation inspection, schedule/cancel builders | Safe workflows, monitoring |
63
- | `factory` | Config reads, template enumeration, SubDAO create/fork builders | Launch tooling, analytics |
64
- | `library` | Manifest listings & scoped ownership checks | Prompt library browsers |
65
- | `lineage` | Library fork ancestry, per-library fork fees | Fork tracking, royalty analytics |
66
- | `prompt` | Prompt metadata, fork counts, usage counters | UI prompt catalogues, agent prompt selection |
67
- | `ipfs` | Upload helpers + discovery/governance worker APIs | CLI sandbox, dashboards, worker automation |
68
- | `subdao` | Discovery, staking helpers, per-user stats | SubDAO directories, onboarding |
69
- | `token` | SXXX balances/allowances, burner discovery, tx builders | Wallet gating, burner dashboards |
70
- | `treasury` | Reserve/POL snapshot, pending withdrawals, liquidity plans | Treasury analytics, Safe operators |
71
- | `boost` | Merkle/Direct boost readers + builders | Incentive distribution tooling |
72
- | `subgraph` | GraphQL helpers for proposals/libraries | Historical analytics |
73
- | `services` | High-level SubgraphService + IPFSService with retry/caching | Web app, CLI integration |
74
- | `adapters` | Normalized governance adapters (OZ) | UI layers needing a unified model |
75
- | `errors` | `SageSDKError` codes | Consistent downstream error handling |
76
-
77
- ### Service Layer
78
-
79
- The SDK provides high-level service classes with built-in retry logic, caching, and error handling for production applications.
80
-
81
- **SubgraphService** - GraphQL queries with automatic retry and caching
32
+ ## Module Overview
33
+
34
+ | Module | Description |
35
+ |--------|-------------|
36
+ | `getProvider()` | Minimal ethers v6 RPC helper |
37
+ | `governance` | Governor metadata, proposals, voting |
38
+ | `timelock` | Queue operations, scheduling |
39
+ | `factory` | SubDAO creation and forking |
40
+ | `library` | Manifest listings, ownership |
41
+ | `lineage` | Fork ancestry, per-library fees |
42
+ | `prompt` | Prompt metadata, usage counters |
43
+ | `ipfs` | Upload, discovery, worker APIs |
44
+ | `subdao` | Discovery, staking, user stats |
45
+ | `token` | SXXX balances, allowances |
46
+ | `treasury` | Reserve snapshots, withdrawals |
47
+ | `reputation` | Author metrics, leaderboard, achievements |
48
+ | `boost` | Merkle/Direct boost helpers |
49
+ | `subgraph` | GraphQL queries |
50
+ | `services` | High-level SubgraphService + IPFSService |
51
+ | `adapters` | Normalized governance adapters |
52
+ | `errors` | `SageSDKError` codes |
53
+
54
+ ## Services
55
+
56
+ ### SubgraphService
82
57
 
83
58
  ```js
84
- import { services, serviceErrors } from '@sage-protocol/sdk';
59
+ import { services } from '@sage-protocol/sdk';
85
60
 
86
- // Initialize service
87
61
  const subgraphService = new services.SubgraphService({
88
62
  url: 'https://api.studio.thegraph.com/query/your-subgraph',
89
- timeout: 10000, // 10s timeout (default)
90
- retries: 3, // 3 retry attempts with exponential backoff (default)
91
- cache: {
92
- enabled: true, // Enable caching (default: true)
93
- ttl: 30000, // 30s cache TTL (default)
94
- maxSize: 100, // Max 100 cache entries (default)
95
- },
63
+ timeout: 10000,
64
+ retries: 3,
65
+ cache: { enabled: true, ttl: 30000 },
96
66
  });
97
67
 
98
- // Fetch SubDAOs with caching
99
- const subdaos = await subgraphService.getSubDAOs({ limit: 50, skip: 0 });
100
-
101
- // Fetch proposals with filters
68
+ const subdaos = await subgraphService.getSubDAOs({ limit: 50 });
102
69
  const proposals = await subgraphService.getProposals({
103
70
  governor: '0xGovernor',
104
71
  states: ['ACTIVE', 'PENDING'],
105
- fromTimestamp: 1640000000,
106
- limit: 20,
107
- cache: true, // Use cache (default: true)
108
72
  });
109
-
110
- // Get single proposal by ID
111
- const proposal = await subgraphService.getProposalById('0x123...', { cache: true });
112
-
113
- // Fetch libraries
114
- const libraries = await subgraphService.getLibraries({
115
- subdao: '0xSubDAO',
116
- limit: 50,
117
- });
118
-
119
- // Fetch prompts by tag
120
- const prompts = await subgraphService.getPromptsByTag({
121
- tagsHash: '0xabc123...',
122
- registry: '0xRegistry',
123
- limit: 50,
124
- });
125
-
126
- // Cache management
127
- subgraphService.clearCache();
128
- const stats = subgraphService.getCacheStats();
129
- // → { enabled: true, size: 12, maxSize: 100 }
130
73
  ```
131
74
 
132
- **IPFSService** - Parallel gateway fetching with caching
75
+ ### IPFSService
133
76
 
134
77
  ```js
135
- import { services, serviceErrors } from '@sage-protocol/sdk';
136
- import { ethers } from 'ethers';
78
+ import { services } from '@sage-protocol/sdk';
137
79
 
138
- // Initialize service
139
80
  const ipfsService = new services.IPFSService({
140
81
  workerBaseUrl: 'https://api.sageprotocol.io',
141
82
  gateway: 'https://ipfs.io',
142
- signer: ethers.Wallet.fromPhrase('...'), // Optional: for worker auth
143
- timeout: 15000, // 15s timeout (default)
144
- retries: 2, // 2 retry attempts (default)
145
- cache: {
146
- enabled: true, // Enable caching (default: true)
147
- ttl: 300000, // 5min cache TTL for immutable CIDs (default)
148
- maxSize: 50, // Max 50 cache entries (default)
149
- },
83
+ signer: yourSigner, // Optional for uploads
150
84
  });
151
85
 
152
- // Fetch content by CID (parallel gateway race)
153
- // Tries multiple gateways in parallel, returns first success
154
- const content = await ipfsService.fetchByCID('QmTest123...', {
155
- cache: true,
156
- timeout: 5000, // 5s timeout per gateway
157
- extraGateways: ['https://cloudflare-ipfs.com', 'https://gateway.pinata.cloud'],
158
- });
86
+ // Fetch (parallel gateway race)
87
+ const content = await ipfsService.fetchByCID('QmTest123...');
159
88
 
160
- // Upload content to IPFS worker
89
+ // Upload
161
90
  const cid = await ipfsService.upload(
162
91
  { title: 'My Prompt', content: '...' },
163
92
  { name: 'prompt.json', warm: true }
164
93
  );
165
- // → 'QmNewContent123...'
166
-
167
- // Pin CIDs to worker
168
- await ipfsService.pin(['QmTest1...', 'QmTest2...'], { warm: false });
169
-
170
- // Warm gateways (prefetch)
171
- await ipfsService.warm('QmTest123...', {
172
- gateways: ['https://ipfs.io', 'https://cloudflare-ipfs.com'],
173
- });
174
-
175
- // Cache management
176
- ipfsService.clearCache();
177
- const stats = ipfsService.getCacheStats();
178
- ```
179
-
180
- **Error Handling**
181
-
182
- ```js
183
- import { services, serviceErrors } from '@sage-protocol/sdk';
184
-
185
- try {
186
- const subdaos = await subgraphService.getSubDAOs({ limit: 50 });
187
- } catch (error) {
188
- if (error instanceof serviceErrors.SubgraphError) {
189
- console.error(`Subgraph error [${error.code}]:`, error.message);
190
- console.log('Retryable:', error.retryable);
191
- // Codes: TIMEOUT, NETWORK, INVALID_RESPONSE, NOT_FOUND, QUERY_FAILED
192
- }
193
- }
194
-
195
- try {
196
- const content = await ipfsService.fetchByCID('QmTest...');
197
- } catch (error) {
198
- if (error instanceof serviceErrors.IPFSError) {
199
- console.error(`IPFS error [${error.code}]:`, error.message);
200
- // Codes: TIMEOUT, PIN_FAILED, INVALID_CID, NOT_FOUND, GATEWAY_FAILED, UPLOAD_FAILED
201
- }
202
- }
203
-
204
- // Format user-friendly error messages
205
- const friendlyMsg = serviceErrors.formatErrorMessage(error);
206
- ```
207
-
208
- **Utility Exports**
209
-
210
- ```js
211
- import { serviceUtils } from '@sage-protocol/sdk';
212
-
213
- // Simple in-memory cache with TTL
214
- const cache = new serviceUtils.SimpleCache({
215
- enabled: true,
216
- ttl: 60000, // 1 minute
217
- maxSize: 200,
218
- });
219
- cache.set('key', { data: 'value' });
220
- const val = cache.get('key');
221
-
222
- // Exponential backoff retry
223
- const result = await serviceUtils.retryWithBackoff(
224
- async () => {
225
- // Your async operation
226
- return await fetchData();
227
- },
228
- {
229
- attempts: 3,
230
- baseDelay: 1000, // Start at 1s
231
- maxDelay: 10000, // Cap at 10s
232
- onRetry: ({ attempt, totalAttempts, delay, error }) => {
233
- console.log(`Retry ${attempt}/${totalAttempts} after ${delay}ms:`, error.message);
234
- },
235
- }
236
- );
237
- ```
238
-
239
- **Performance Notes**
240
-
241
- - **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)
242
- - **In-Memory Caching**: Both services cache results in memory with configurable TTL to reduce redundant network calls
243
- - **Exponential Backoff**: Automatic retry with exponential backoff (1s → 2s → 4s delays) for transient failures
244
- - **Edge Runtime Compatible**: Uses `fetch()` instead of axios, compatible with Cloudflare Pages and Vercel Edge
245
-
246
- **React Integration**
247
-
248
- The SDK provides React hooks built on SWR for seamless integration in React applications:
249
-
250
- **Prerequisites**:
251
- ```bash
252
- # Install peer dependencies
253
- npm install react swr
254
- # or
255
- yarn add react swr
256
- ```
257
-
258
- **Usage**:
259
-
260
- ```js
261
- import { services, hooks } from '@sage-protocol/sdk';
262
-
263
- // Initialize services (once, typically in a context provider)
264
- const subgraphService = new services.SubgraphService({
265
- url: 'https://api.studio.thegraph.com/query/your-subgraph',
266
- });
267
-
268
- const ipfsService = new services.IPFSService({
269
- workerBaseUrl: 'https://api.sageprotocol.io',
270
- gateway: 'https://ipfs.io',
271
- signer: yourSigner, // Optional for uploads
272
- });
273
-
274
- // Use hooks in components
275
- function SubDAOList() {
276
- const { data: subdaos, error, isLoading } = hooks.useSubDAOs(subgraphService, {
277
- limit: 50,
278
- });
279
-
280
- if (isLoading) return <div>Loading...</div>;
281
- if (error) return <div>Error: {error.message}</div>;
282
-
283
- return (
284
- <ul>
285
- {subdaos.map(subdao => (
286
- <li key={subdao.id}>{subdao.name}</li>
287
- ))}
288
- </ul>
289
- );
290
- }
291
-
292
- function ProposalList({ governorAddress }) {
293
- const { data: proposals } = hooks.useProposals(subgraphService, {
294
- governor: governorAddress,
295
- states: ['ACTIVE', 'PENDING'],
296
- limit: 20,
297
- refreshInterval: 30000, // Auto-refresh every 30s
298
- });
299
-
300
- return (
301
- <ul>
302
- {proposals?.map(proposal => (
303
- <li key={proposal.id}>{proposal.description}</li>
304
- ))}
305
- </ul>
306
- );
307
- }
308
-
309
- function PromptViewer({ cid }) {
310
- const { data: content, isLoading } = hooks.useFetchCID(ipfsService, cid, {
311
- extraGateways: ['https://cloudflare-ipfs.com'],
312
- });
313
-
314
- if (isLoading) return <div>Loading prompt...</div>;
315
-
316
- return <pre>{JSON.stringify(content, null, 2)}</pre>;
317
- }
318
-
319
- function UploadForm() {
320
- const { upload, isUploading, error, data: cid } = hooks.useUpload(ipfsService);
321
-
322
- const handleSubmit = async (e) => {
323
- e.preventDefault();
324
- const content = { title: 'My Prompt', content: '...' };
325
- try {
326
- const newCid = await upload(content, { name: 'prompt.json' });
327
- console.log('Uploaded:', newCid);
328
- } catch (err) {
329
- console.error('Upload failed:', err);
330
- }
331
- };
332
-
333
- return (
334
- <form onSubmit={handleSubmit}>
335
- <button type="submit" disabled={isUploading}>
336
- {isUploading ? 'Uploading...' : 'Upload'}
337
- </button>
338
- {error && <div>Error: {error.message}</div>}
339
- {cid && <div>Uploaded to: {cid}</div>}
340
- </form>
341
- );
342
- }
343
94
  ```
344
95
 
345
- **Available Hooks**:
346
-
347
- | Hook | Description | Returns |
348
- |------|-------------|---------|
349
- | `useSubDAOs(service, options)` | Fetch SubDAOs from subgraph | `{ data, error, isLoading, mutate }` |
350
- | `useProposals(service, options)` | Fetch proposals from subgraph | `{ data, error, isLoading, mutate }` |
351
- | `useFetchCID(service, cid, options)` | Fetch IPFS content by CID | `{ data, error, isLoading, mutate }` |
352
- | `useUpload(service)` | Upload content to IPFS | `{ upload, isUploading, error, data, reset }` |
353
-
354
- **Hook Options**:
355
-
356
- All data-fetching hooks support SWR options:
357
- - `refreshInterval` - Auto-refresh interval in ms
358
- - `revalidateOnFocus` - Revalidate when window focused
359
- - `revalidateOnReconnect` - Revalidate on network reconnect
360
- - `cache` - Use service-level caching
361
-
362
- **Note**: Hooks are optional and only available when `react` and `swr` peer dependencies are installed.
363
-
364
- ### Examples
365
-
366
- - [Legacy base mini app hook](examples/base-mini-app/README.md) – React-friendly context mirroring the former in-repo app (now maintained externally).
367
- - [Eliza agent recipe](examples/agent-eliza/README.md) – exposes SDK helpers to agent toolchains.
368
-
369
- Important
370
- - For all write calls to the factory (create/fork/stable-fee flows), pass the SubDAOFactory diamond proxy address as `factoryAddress`. Legacy monolithic factory routes have been removed.
96
+ ## Common Operations
371
97
 
372
- Deprecations
373
- ------------
374
- - `resolveGovernanceContext` is still exported for compatibility but now logs a deprecation warning. Use the richer `governance` + `subdao` helpers instead.
375
- - **Prompt-level forking** is deprecated. On-chain prompt forking via `SubDAO.forkPrompt()` and `SubDAO.forkPromptWithStable()` is no longer supported. Use `forked_from` metadata in prompt frontmatter instead. Library-level forks (entire SubDAOs) remain fully supported via the factory.
376
- - **Prompt fork fees** (stable fee configuration for prompt forks) have been removed. Per-library SXXX fork fees are now the standard model for monetizing library forks. See the `lineage` module documentation above.
98
+ ### Governance
377
99
 
378
- Next Phases
379
- -----------
380
- Phase 6 focuses on integration polish and packaging. Track progress in the [SDK Improvement Specification](../../docs/SDK_Improvement_Specification.md).
381
-
382
- New governance/factory/library helpers (2025‑10)
383
- -----------------------------------------------
384
-
385
- Proposal ID and preflight
386
100
  ```js
387
- import sdk from '@sage-protocol/sdk';
101
+ // Compute proposal ID
388
102
  const idHex = sdk.governance.computeProposalIdHex({ targets, values, calldatas, description });
389
- const pre = await sdk.governance.simulatePropose({ provider, governor, targets, values, calldatas, description, sender });
390
- if (!pre.ok) throw new Error(`preflight failed: ${pre.error?.message}`);
391
- ```
392
-
393
- Votes at latest‑1 (ERC20Votes)
394
- ```js
395
- // Token path
396
- const votes1 = await sdk.governance.getVotesLatestMinusOne({ provider, token: sxxxToken, account: user });
397
- // Governor path (auto‑resolves token)
398
- const votes2 = await sdk.governance.getVotesLatestMinusOne({ provider, governor, account: user });
399
- ```
400
-
401
- Factory‑mapped registry
402
- ```js
403
- const mapped = await sdk.factory.getSubDAORegistry({ provider, factory, subdao });
404
- ```
405
-
406
- Registry preflight as timelock
407
- ```js
408
- const { to, data } = sdk.library.buildUpdateLibraryTx({ registry, subdao, manifestCID, version: '1.0.0' });
409
- const sim = await sdk.library.simulateAsTimelock({ provider, registry, to, data, timelock });
410
- if (!sim.ok) throw new Error(`registry preflight failed: ${sim.error?.message}`);
411
- ```
412
-
413
- Propose by hash (arrays + bytes32)
414
- ```js
415
- // For governors that prefer descriptionHash (or deterministic IDs)
416
- const dh = sdk.governance.hashDescription(description);
417
- const tx = sdk.governance.buildProposeTxByHash({ governor, targets, values, calldatas, descriptionHash: dh });
418
- ```
419
-
420
- API Notes and Examples
421
- ----------------------
422
-
423
- Salted Proposal Descriptions
424
- ```js
425
- import sdk from '@sage-protocol/sdk';
426
- // buildProposeTx() auto‑salts descriptions unless you pass a bytes32 description hash
427
- const tx = sdk.governance.buildProposeTx({
428
- governor: '0xGov',
429
- targets: ['0xTarget'],
430
- values: [0n],
431
- calldatas: ['0x...'],
432
- descriptionOrHash: 'My Title', // will append \n\n[SALT:0x...] automatically
433
- });
434
- ```
435
-
436
- Private Transaction Submission
437
- ```js
438
- import sdk from '@sage-protocol/sdk';
439
- // Submit signed private transaction via builder/relay RPC (eth_sendPrivateTransaction)
440
- const { hash } = await sdk.utils.privateTx.sendTransaction({
441
- signer, // ethers v6 signer
442
- tx: { to: '0xGov', data: '0x...', value: 0n },
443
- privateRpcUrl: process.env.SAGE_PRIVATE_RPC,
444
- });
445
- ```
446
-
447
- Subgraph‑First Prompt Reads (with tag filters)
448
- ```js
449
- import sdk from '@sage-protocol/sdk';
450
-
451
- // Bulk listing by registry (subgraph)
452
- const items = await sdk.subgraph.listRegistryPrompts({
453
- url: process.env.SAGE_SUBGRAPH_URL,
454
- registry: '0xRegistry',
455
- first: 50,
456
- });
457
103
 
458
- // Filter by tagsHash (bytes32), optionally scope to a registry
459
- const tagged = await sdk.subgraph.listPromptsByTag({
460
- url: process.env.SAGE_SUBGRAPH_URL,
461
- tagsHash: '0xabc123...'
462
- });
463
-
464
- // On‑chain bounded fallback (IDs): latest prompts
465
- const onchainLatest = await sdk.prompt.listPrompts({ provider, registry: '0xRegistry', limit: 50 });
466
-
467
- // On‑chain bounded tag page
468
- const onchainPage = await sdk.prompt.listByTagPage({ provider, registry: '0xRegistry', tagHash: '0xabc123...', offset: 0, limit: 25 });
469
- ```
470
-
471
- Environment Controls
472
- ```bash
473
- # Private txs
474
- export SAGE_PRIVATE_RPC=https://builder.your-relay.example/rpc
475
-
476
- # Deterministic proposal salt (for reproducible ids)
477
- export SAGE_GOV_SALT=0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
478
-
479
- # Disable auto‑salt (not recommended)
480
- export SAGE_GOV_SALT_DISABLE=1
481
- ```
482
- # New/Updated Helpers (2025-10)
483
-
484
- Factory enumeration (CLI parity)
485
- ```js
486
- // Full list of SubDAO addresses from factory storage
487
- const addrs = await sdk.factory.listSubDAOs({ provider, factory: FACTORY });
488
-
489
- // Indexed range with indices returned
490
- const indexed = await sdk.factory.listSubDAOsIndexed({ provider, factory: FACTORY, from: 0n, to: 99n });
491
- // → [{ index: 0, address: '0x...' }, ...]
492
- ```
493
-
494
- Governance overloads and quorum
495
- ```js
496
- // Build queue/execute using arrays+descriptionHash when supported; else fall back to id-based
497
- const ol = await sdk.governance.detectGovernorOverloads({ provider, governor });
498
- if (ol.hasQueueArrays) {
499
- const q = sdk.governance.buildQueueTx({ governor, targets, values, calldatas, descriptionOrHash: descHash });
500
- } else if (ol.hasQueueById) {
501
- const q = sdk.governance.buildQueueByIdTx({ governor, proposalId });
502
- }
104
+ // Preflight simulation
105
+ const pre = await sdk.governance.simulatePropose({ provider, governor, targets, values, calldatas, description, sender });
503
106
 
504
- // Quorum at a snapshot
505
- const quorum = await sdk.governance.getQuorumAt({ provider, governor, blockTag: 12_345n });
506
- // Convenience: quorum at latest-1
507
- const qLatest = await sdk.governance.getQuorumLatestMinusOne({ provider, governor });
508
- ```
107
+ // Get votes
108
+ const votes = await sdk.governance.getVotesLatestMinusOne({ provider, governor, account: user });
509
109
 
510
- Selfdelegate convenience (ERC20Votes)
511
- ```js
512
- // Build a delegate(self) tx from a Governor (reads sxxxToken address)
110
+ // Self-delegate
513
111
  const tx = await sdk.governance.buildDelegateSelfTx({ provider, governor, account: user });
514
- // send via userOp (CDP) or EOA
515
- ```
516
-
517
- Votes token resolution and gates
518
- ```js
519
- // Resolve the IVotes token used for proposals
520
- const votesToken = await sdk.governance.resolveVotesToken({ provider, governor });
521
-
522
- // Build preferred self-delegate using resolved token
523
- const del = await sdk.governance.buildDelegateSelfPreferred({ provider, governor, account: user });
524
-
525
- // Delegate and verify votes (no throw)
526
- const res = await sdk.governance.delegateSelfAndVerify({ provider, governor, account: user, signer, minVotes: 1n });
527
- // → { txHash, ok, votes, payload }
528
-
529
- // Readiness to propose (preflight)
530
- const gates = await sdk.governance.ensureProposeGates({ provider, governor, proposer: user });
531
- // Optionally include execution check (registry update as timelock)
532
- const ready = await sdk.governance.readinessToPropose({
533
- provider,
534
- governor,
535
- proposer: user,
536
- execution: { registry, timelock, subdao, libraryId: 'main', manifestCID, promptCount: 12 },
537
- });
538
- ```
539
-
540
- Subgraph normalization
541
- ```js
542
- // listProposalsFiltered now returns both state (string) and stateNum (0–7)
543
- const items = await sdk.subgraph.listProposalsFiltered({ url: SUBGRAPH_URL, governor });
544
- // item.state → 'PENDING' | 'ACTIVE' | ... ; item.stateNum → 0..7 or null when unknown
545
- ```
546
-
547
- Subgraph State Normalization
548
- ----------------------------
549
- - State Mapping
550
- - PENDING → 0
551
- - ACTIVE → 1
552
- - CANCELED/CANCELLED → 2
553
- - DEFEATED → 3
554
- - SUCCEEDED → 4
555
- - QUEUED → 5
556
- - EXPIRED → 6
557
- - EXECUTED → 7
558
- - Example
559
- ```js
560
- const items = await sdk.subgraph.listProposalsFiltered({ url: SUBGRAPH_URL, governor });
561
- console.log(items[0].state); // 'PENDING'
562
- console.log(items[0].stateNum); // 0
563
- ```
564
-
565
- New Helper Examples
566
- -------------------
567
-
568
- - Votes token + self‑delegate
569
- ```js
570
- const votesToken = await sdk.governance.resolveVotesToken({ provider, governor });
571
- const tx = await sdk.governance.buildDelegateSelfPreferred({ provider, governor, account: user });
572
- const res = await sdk.governance.delegateSelfAndVerify({ provider, governor, account: user, signer, minVotes: 1n });
573
- console.log(res.ok, res.votes, res.txHash);
574
- ```
575
-
576
- - Propose gates and execution readiness (one‑shot)
577
- ```js
578
- const gates = await sdk.governance.ensureProposeGates({ provider, governor, proposer: user });
579
- const ready = await sdk.governance.readinessToPropose({
580
- provider, governor, proposer: user,
581
- execution: { registry, timelock, subdao, libraryId: 'main', manifestCID, promptCount: 12 }
582
- });
583
- console.log({ threshold: gates.threshold, votesOk: gates.votesOk, execReady: ready.executionReady });
584
- ```
585
-
586
- - List active proposals (subgraph)
587
- ```js
588
- const active = await sdk.governance.listActiveProposals({ url: SUBGRAPH_URL, governor });
589
- // active[i].state (string), active[i].stateNum (0–7)
590
- ```
591
-
592
- - Library authorize + readiness
593
- ```js
594
- const auth = sdk.library.buildAuthorizeTimelockTx({ registry, timelock, subdao });
595
- const exec = await sdk.library.executionReadiness({ provider, registry, timelock, subdao, libraryId: 'main', manifestCID, promptCount });
596
- console.log(exec.ok, exec.error);
597
- ```
598
-
599
- Proposal timeline (subgraph)
600
- ```js
601
- const t = await sdk.subgraph.getProposalTimeline({ url: SUBGRAPH_URL, id: idHexOrDecimal });
602
- // { id, createdAt, queuedAt, executedAt, canceledAt, eta, state }
603
- ```
604
-
605
- Compatibility
606
- -------------
607
- - 0.0.8 Compatibility
608
- - All changes are additive; no removed or renamed exports. Existing callers can continue to rely on string state. stateNum is optional.
609
-
610
- Troubleshooting
611
- ---------------
612
- - JSON + BigInt
613
- - Use a replacer when printing SDK results:
614
- ```js
615
- JSON.stringify(obj, (_, v) => (typeof v === 'bigint' ? v.toString() : v));
616
112
  ```
617
- - Governor filter casing
618
- - The SDK normalizes governor for Bytes filters; if you issue manual GraphQL, use lowercase governor addresses to avoid case sensitivity pitfalls.
619
- - Votes snapshot timing
620
- - getVotesLatestMinusOne reads at latest‑1 to avoid same‑block edge cases on ERC20Votes.
621
113
 
622
- Prompt pagination helpers
623
- ```js
624
- const totalByTag = await sdk.prompt.getByTagCount({ provider, registry, tagHash });
625
- const page = await sdk.prompt.listByTagPage({ provider, registry, tagHash, offset: 0, limit: 25 });
626
- ```
114
+ ### SubDAO Creation
627
115
 
628
- Team SubDAO helpers
629
116
  ```js
630
- // One-click create + operatorize
117
+ // Create operator SubDAO (team-controlled)
631
118
  const res = await sdk.subdao.createOperatorSubDAO({
632
119
  signer,
633
120
  factoryAddress: FACTORY,
634
121
  operator: '0xSafeOrEOA',
635
122
  name: 'My SubDAO',
636
- description: 'Team controlled (Timelock + Safe/EOA executor)',
637
- accessModel: 0,
638
- minStakeAmount: 0n,
123
+ description: 'Team controlled',
639
124
  burnAmount: 1500n * 10n**18n,
640
125
  sxxx: SXXX,
641
126
  });
642
-
643
- // Convert existing SubDAO to operator mode
644
- await sdk.subdao.makeOperator({ signer, subdao: res.subdao, operator: '0xSafeOrEOA', grantAdmin: false });
645
-
646
- // Ensure SXXX approval for factory burn if needed
647
- await sdk.subdao.ensureSxxxBurnAllowance({ signer, sxxx: SXXX, spender: FACTORY, amount: 1500n * 10n**18n });
648
-
649
- // (Optional) Build a setProfileCid tx for newer SubDAO implementations that expose setProfileCid(string)
650
- // Use this in a Governor/Timelock proposal or Safe Transaction Builder, not as a direct EOA call.
651
- const profileCid = 'bafy...'; // CID of dao-profile.json on IPFS
652
- const { to, data, value } = sdk.subdao.buildSetProfileCidTx({ subdao: res.subdao, profileCid });
653
- // Example: schedule via timelock or propose via Governor, depending on your governance mode
654
127
  ```
655
128
 
656
- # Library Lineage and Fork Fees
657
-
658
- The `lineage` module provides helpers for tracking library fork relationships and per-library SXXX fork fees.
659
-
660
- ## Fork Ancestry
661
-
662
- Libraries can be forked from other libraries. The `lineage` module tracks this ancestry:
129
+ ### Library Fork Fees
663
130
 
664
131
  ```js
665
- import sdk from '@sage-protocol/sdk';
666
-
667
- const provider = sdk.getProvider({ rpcUrl: process.env.RPC_URL });
668
- const registry = '0xLibraryRegistry';
669
- const subdao = '0xSubDAO';
670
-
671
- // Check if a library was forked from another
672
- const isFork = await sdk.lineage.isFork({ provider, registry, subdao });
673
- // → true if this library has a parent
674
-
675
- // Get the parent library (null if original)
676
- const parent = await sdk.lineage.getParentLibrary({ provider, registry, subdao });
677
- // → '0xParentSubDAO' or null
678
-
679
- // Get full ancestry chain from root to current
680
- const chain = await sdk.lineage.getLineageChain({ provider, registry, subdao });
681
- // → { chain: ['0xRoot', '0xChild', '0xGrandchild'], depth: 2, root: '0xRoot' }
682
- ```
683
-
684
- ## Per-Library Fork Fees
685
-
686
- Library owners can set an SXXX fee for forking their library. This fee is paid by the forker to the parent library's treasury when creating a forked SubDAO.
687
-
688
- ```js
689
- // Get the SXXX fork fee for a library (0n = free fork)
132
+ // Get fork fee for a library
690
133
  const fee = await sdk.lineage.getLibraryForkFee({ provider, registry, subdao });
691
- // → 1000000000000000000n (1 SXXX in wei)
692
134
 
693
- // Get comprehensive library info including fork fee
135
+ // Get full library info
694
136
  const info = await sdk.lineage.getLibraryInfo({ provider, registry, subdao });
695
- // → {
696
- // parentDAO: '0xParent' | null,
697
- // forkFee: 1000000000000000000n,
698
- // manifestCID: 'Qm...',
699
- // version: '1.0.0'
700
- // }
701
137
  ```
702
138
 
703
- ## Setting Fork Fees (via Governance)
139
+ ## React Hooks
704
140
 
705
- Fork fees can only be set by the library's timelock. This is typically done through a governance proposal:
141
+ Requires `react` and `swr` peer dependencies:
706
142
 
707
143
  ```js
708
- import { ethers } from 'ethers';
709
-
710
- // Build the setLibraryForkFee transaction (for use in a proposal)
711
- const registryIface = new ethers.Interface([
712
- 'function setLibraryForkFee(address dao, uint256 fee)'
713
- ]);
714
-
715
- const feeInWei = ethers.parseUnits('10', 18); // 10 SXXX
716
- const calldata = registryIface.encodeFunctionData('setLibraryForkFee', [subdao, feeInWei]);
717
-
718
- // Include in a governance proposal
719
- const proposeTx = sdk.governance.buildProposeTx({
720
- governor,
721
- targets: [registry],
722
- values: [0n],
723
- calldatas: [calldata],
724
- descriptionOrHash: 'Set library fork fee to 10 SXXX'
725
- });
726
- ```
727
-
728
- ## Fork Fee Flow
729
-
730
- When a SubDAO is forked via the factory:
731
-
732
- 1. Factory reads the parent library's fork fee from LibraryRegistry
733
- 2. If fee > 0, SXXX is transferred from forker to parent's treasury
734
- 3. `LibraryForkFeePaid(parentDAO, forker, amount)` event is emitted
735
- 4. Fork proceeds with standard SubDAO creation
736
-
737
- Note: Forkers must approve SXXX to the factory before forking a library with a fee set.
738
-
739
- ## Deprecation: Prompt-Level Forks
144
+ import { services, hooks } from '@sage-protocol/sdk';
740
145
 
741
- On-chain prompt forking is deprecated. Prompt forks are now tracked via metadata only:
146
+ function SubDAOList() {
147
+ const { data, error, isLoading } = hooks.useSubDAOs(subgraphService, { limit: 50 });
742
148
 
743
- 1. Add `forked_from: <original-cid>` to your prompt frontmatter
744
- 2. Upload the new prompt to IPFS
745
- 3. Register via governance proposal
149
+ if (isLoading) return <div>Loading...</div>;
150
+ return <ul>{data.map(s => <li key={s.id}>{s.name}</li>)}</ul>;
151
+ }
746
152
 
747
- Library-level forks (entire SubDAOs) continue to work and are tracked via `LibraryRegistry.registerForkedDAO()`.
153
+ function PromptViewer({ cid }) {
154
+ const { data } = hooks.useFetchCID(ipfsService, cid);
155
+ return <pre>{JSON.stringify(data, null, 2)}</pre>;
156
+ }
157
+ ```
748
158
 
749
- # Governance Adapter (OpenZeppelin)
159
+ | Hook | Description |
160
+ |------|-------------|
161
+ | `useSubDAOs` | Fetch SubDAOs from subgraph |
162
+ | `useProposals` | Fetch proposals from subgraph |
163
+ | `useFetchCID` | Fetch IPFS content by CID |
164
+ | `useUpload` | Upload content to IPFS |
750
165
 
751
- Normalize proposals/timelines across OZ governors with a compact adapter, inspired by Boardroom’s pattern.
166
+ ## Error Handling
752
167
 
753
168
  ```js
754
- import sdk from '@sage-protocol/sdk';
755
- import { getProvider } from '@sage-protocol/sdk';
169
+ import { serviceErrors } from '@sage-protocol/sdk';
756
170
 
757
- const provider = getProvider({ rpcUrl: process.env.RPC_URL });
758
- const tr = sdk.adapters.transports.createTransports({ provider, signer: null, subgraph: process.env.SAGE_SUBGRAPH_URL });
171
+ try {
172
+ const data = await subgraphService.getSubDAOs({ limit: 50 });
173
+ } catch (error) {
174
+ if (error instanceof serviceErrors.SubgraphError) {
175
+ console.error(`[${error.code}]:`, error.message);
176
+ }
177
+ }
178
+ ```
759
179
 
760
- // List proposals (paged by block ranges)
761
- const page = await sdk.adapters.governance.openzeppelin.getProposals({
762
- provider,
763
- governor: '0xGovernor',
764
- fromBlock: 0,
765
- toBlock: 'latest',
766
- pageSize: 10_000,
767
- // Optional: improve signature resolution
768
- // ChainId is auto-detected from provider; you can override via chainId if needed.
769
- // The adapter will try Sourcify → Etherscan/BaseScan automatically using NEXT_PUBLIC_ETHERSCAN_API_KEY.
770
- // You may override with your own abiResolver as needed.
771
- abiResolver: async ({ address, chainId }) => null,
772
- selectorResolver: async (selector) => null, // plug in 4byte or your mapping
773
- });
180
+ ## Environment Variables
774
181
 
775
- // On‑chain timeline fallback
776
- const t = await sdk.adapters.governance.openzeppelin.getTimelineOnchain({
777
- provider,
778
- governor: '0xGovernor',
779
- id: page.items[0].id,
780
- });
781
-
782
- // Signature list (best effort; uses Sourcify → Etherscan/BaseScan (with NEXT_PUBLIC_ETHERSCAN_API_KEY) → 4byte)
783
- const sigs = await sdk.adapters.governance.openzeppelin.getSignatureList({
784
- provider,
785
- targets: page.items[0].targets,
786
- calldatas: page.items[0].calldatas,
787
- // optional resolvers (provide only if you have your own fetchers)
788
- abiResolver: async ({ address, chainId }) => null,
789
- selectorResolver: async (selector) => null,
790
- });
182
+ ```bash
183
+ # Private transaction relay
184
+ SAGE_PRIVATE_RPC=https://builder.your-relay.example/rpc
791
185
 
792
- // Proposal events page (Created/Queued/Executed/Canceled) with pagination cursor
793
- const evPage = await sdk.adapters.governance.openzeppelin.getProposalEventsPage({
794
- provider,
795
- governor: '0xGovernor',
796
- fromBlock: 0,
797
- toBlock: 'latest',
798
- pageSize: 20_000,
799
- });
800
- // evPage.items: [{ type:'created'|'queued'|'executed'|'canceled', blockNumber, timestamp, txHash, id }]
801
- // evPage.nextCursor: { fromBlock } | null
186
+ # Explorer API (for ABI resolution)
187
+ NEXT_PUBLIC_ETHERSCAN_API_KEY=YourKey
802
188
  ```
803
189
 
804
- Proposal model returned by adapter:
805
- - id (bigint), proposer (address), createdAt (seconds), startBlock, endBlock, quorum (bigint|null), txHash, targets[], values[], calldatas[], signatures[]
806
- # Explorer API keys
190
+ ## Design Principles
807
191
 
808
- Set one or more of the following to improve ABI resolution in adapters:
192
+ - Pure data in/out; no console prompts or environment coupling
193
+ - Minimal ABI fragments maintained alongside contracts
194
+ - Composable modules - import only what you need
195
+ - Edge runtime compatible (uses `fetch()`)
809
196
 
810
- ```
811
- NEXT_PUBLIC_ETHERSCAN_API_KEY=YourEtherscanOrBaseScanKey
812
- NEXT_PUBLIC_ALCHEMY_API_KEY=YourAlchemyKey # optional (not used for ABI directly)
813
- NEXT_PUBLIC_TENDERLY_ACCESS_TOKEN=YourTenderlyToken # optional (not used by default)
814
- ```
197
+ ## Documentation
198
+
199
+ - [Full Docs](https://docs.sageprotocol.io)
200
+
201
+ ## License
815
202
 
816
- Notes:
817
- - For Base Sepolia (chainId 84532) we prefer BaseScan V2 (chainid=84532) automatically.
818
- - For Base mainnet (8453) we use BaseScan v1 endpoint by default.
819
- - For Ethereum mainnet (1) we use Etherscan v1.
820
- - If Sourcify/Etherscan fail, the adapter falls back to 4byte signatures; unknown selectors appear as their 4‑byte hex.
203
+ Apache 2.0