@curator-studio/sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/config.test.d.ts +1 -0
- package/dist/__tests__/indexer.test.d.ts +1 -0
- package/dist/__tests__/tokens.test.d.ts +1 -0
- package/dist/__tests__/tx.test.d.ts +1 -0
- package/dist/__tests__/vaults.test.d.ts +1 -0
- package/dist/_esm-BLSSJTMS.mjs +3921 -0
- package/dist/ccip-3IGU3IYM.mjs +14 -0
- package/dist/chunk-5QPQJ76C.mjs +2247 -0
- package/dist/chunk-CALEUNGN.mjs +407 -0
- package/dist/chunk-QEWRRW3A.js +20249 -0
- package/dist/chunk-VANAZQ3S.mjs +6044 -0
- package/dist/chunk-XGB3TDIC.mjs +42 -0
- package/dist/components/provider.d.ts +14 -0
- package/dist/config.d.ts +39 -0
- package/dist/ens.d.ts +82 -0
- package/dist/hooks/index.d.ts +6 -0
- package/dist/hooks/use-ens.d.ts +17 -0
- package/dist/hooks/use-indexer.d.ts +21 -0
- package/dist/hooks/use-strategy.d.ts +55 -0
- package/dist/hooks/use-warehouse.d.ts +21 -0
- package/dist/hooks/use-yield.d.ts +35 -0
- package/dist/hooks/utils.d.ts +7 -0
- package/dist/index.d.ts +846 -0
- package/dist/index.js +1820 -0
- package/dist/index.mjs +34753 -0
- package/dist/lib/indexer.d.ts +298 -0
- package/dist/lib/tx.d.ts +11 -0
- package/dist/secp256k1-NX6XS3V3.mjs +16 -0
- package/dist/server-BOSsS_Jk.d.ts +304 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +10 -0
- package/dist/strategy.d.ts +81 -0
- package/dist/tokens.d.ts +34 -0
- package/dist/vaults.d.ts +19 -0
- package/dist/warehouse.d.ts +24 -0
- package/dist/yield-redirector.d.ts +68 -0
- package/package.json +43 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1820 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {
|
|
3
|
+
createPublicClient,
|
|
4
|
+
http
|
|
5
|
+
} from "viem";
|
|
6
|
+
import { mainnet as mainnet2, sepolia as sepolia2, baseSepolia as baseSepolia2, hardhat as hardhat2 } from "viem/chains";
|
|
7
|
+
|
|
8
|
+
// src/lib/indexer.ts
|
|
9
|
+
import { Client, fetchExchange, gql } from "@urql/core";
|
|
10
|
+
var strategiesQuery = gql`
|
|
11
|
+
query Strategies(
|
|
12
|
+
$where: strategyFilter
|
|
13
|
+
$orderBy: String
|
|
14
|
+
$orderDirection: String
|
|
15
|
+
$limit: Int
|
|
16
|
+
$after: String
|
|
17
|
+
) {
|
|
18
|
+
strategys(
|
|
19
|
+
where: $where
|
|
20
|
+
orderBy: $orderBy
|
|
21
|
+
orderDirection: $orderDirection
|
|
22
|
+
limit: $limit
|
|
23
|
+
after: $after
|
|
24
|
+
) {
|
|
25
|
+
items {
|
|
26
|
+
id
|
|
27
|
+
chainId
|
|
28
|
+
tenantId
|
|
29
|
+
owner
|
|
30
|
+
sourceStrategy
|
|
31
|
+
metadataURI
|
|
32
|
+
feeRecipient
|
|
33
|
+
feeBps
|
|
34
|
+
metadata
|
|
35
|
+
allocations {
|
|
36
|
+
items {
|
|
37
|
+
recipient
|
|
38
|
+
weight
|
|
39
|
+
label
|
|
40
|
+
version
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
allocationsVersion
|
|
44
|
+
ensLabel
|
|
45
|
+
timesForked
|
|
46
|
+
uniqueDonors
|
|
47
|
+
createdAt
|
|
48
|
+
createdAtBlock
|
|
49
|
+
lastUpdatedAt
|
|
50
|
+
lastUpdatedAtBlock
|
|
51
|
+
}
|
|
52
|
+
totalCount
|
|
53
|
+
pageInfo {
|
|
54
|
+
hasNextPage
|
|
55
|
+
endCursor
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
`;
|
|
60
|
+
var strategyQuery = gql`
|
|
61
|
+
query Strategy($id: String!) {
|
|
62
|
+
strategy(id: $id) {
|
|
63
|
+
id
|
|
64
|
+
chainId
|
|
65
|
+
tenantId
|
|
66
|
+
owner
|
|
67
|
+
sourceStrategy
|
|
68
|
+
metadataURI
|
|
69
|
+
feeRecipient
|
|
70
|
+
feeBps
|
|
71
|
+
metadata
|
|
72
|
+
ensLabel
|
|
73
|
+
allocations {
|
|
74
|
+
items {
|
|
75
|
+
recipient
|
|
76
|
+
weight
|
|
77
|
+
label
|
|
78
|
+
version
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
allocationsVersion
|
|
82
|
+
timesForked
|
|
83
|
+
uniqueDonors
|
|
84
|
+
createdAt
|
|
85
|
+
createdAtBlock
|
|
86
|
+
lastUpdatedAt
|
|
87
|
+
lastUpdatedAtBlock
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
`;
|
|
91
|
+
var distributionsQuery = gql`
|
|
92
|
+
query Distributions(
|
|
93
|
+
$where: distributionFilter
|
|
94
|
+
$orderBy: String
|
|
95
|
+
$orderDirection: String
|
|
96
|
+
$limit: Int
|
|
97
|
+
$after: String
|
|
98
|
+
) {
|
|
99
|
+
distributions(
|
|
100
|
+
where: $where
|
|
101
|
+
orderBy: $orderBy
|
|
102
|
+
orderDirection: $orderDirection
|
|
103
|
+
limit: $limit
|
|
104
|
+
after: $after
|
|
105
|
+
) {
|
|
106
|
+
items {
|
|
107
|
+
id
|
|
108
|
+
strategyId
|
|
109
|
+
token
|
|
110
|
+
totalAmount
|
|
111
|
+
totalAmountUSD
|
|
112
|
+
allocationsVersion
|
|
113
|
+
allocations
|
|
114
|
+
timestamp
|
|
115
|
+
blockNumber
|
|
116
|
+
txHash
|
|
117
|
+
}
|
|
118
|
+
totalCount
|
|
119
|
+
pageInfo {
|
|
120
|
+
hasNextPage
|
|
121
|
+
endCursor
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
`;
|
|
126
|
+
var payoutsQuery = gql`
|
|
127
|
+
query Payouts(
|
|
128
|
+
$where: payoutFilter
|
|
129
|
+
$orderBy: String
|
|
130
|
+
$orderDirection: String
|
|
131
|
+
$limit: Int
|
|
132
|
+
$after: String
|
|
133
|
+
) {
|
|
134
|
+
payouts(
|
|
135
|
+
where: $where
|
|
136
|
+
orderBy: $orderBy
|
|
137
|
+
orderDirection: $orderDirection
|
|
138
|
+
limit: $limit
|
|
139
|
+
after: $after
|
|
140
|
+
) {
|
|
141
|
+
items {
|
|
142
|
+
id
|
|
143
|
+
strategyId
|
|
144
|
+
distributionId
|
|
145
|
+
recipient
|
|
146
|
+
token
|
|
147
|
+
amount
|
|
148
|
+
amountUSD
|
|
149
|
+
timestamp
|
|
150
|
+
blockNumber
|
|
151
|
+
txHash
|
|
152
|
+
}
|
|
153
|
+
totalCount
|
|
154
|
+
pageInfo {
|
|
155
|
+
hasNextPage
|
|
156
|
+
endCursor
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
`;
|
|
161
|
+
var donorsQuery = gql`
|
|
162
|
+
query Donors(
|
|
163
|
+
$where: donorFilter
|
|
164
|
+
$orderBy: String
|
|
165
|
+
$orderDirection: String
|
|
166
|
+
$limit: Int
|
|
167
|
+
$after: String
|
|
168
|
+
) {
|
|
169
|
+
donors(
|
|
170
|
+
where: $where
|
|
171
|
+
orderBy: $orderBy
|
|
172
|
+
orderDirection: $orderDirection
|
|
173
|
+
limit: $limit
|
|
174
|
+
after: $after
|
|
175
|
+
) {
|
|
176
|
+
items {
|
|
177
|
+
id
|
|
178
|
+
strategyId
|
|
179
|
+
address
|
|
180
|
+
firstDonationAt
|
|
181
|
+
lastDonationAt
|
|
182
|
+
totalDonations
|
|
183
|
+
}
|
|
184
|
+
totalCount
|
|
185
|
+
pageInfo {
|
|
186
|
+
hasNextPage
|
|
187
|
+
endCursor
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
`;
|
|
192
|
+
var strategyBalancesQuery = gql`
|
|
193
|
+
query StrategyBalances(
|
|
194
|
+
$where: strategyBalanceFilter
|
|
195
|
+
$orderBy: String
|
|
196
|
+
$orderDirection: String
|
|
197
|
+
$limit: Int
|
|
198
|
+
$after: String
|
|
199
|
+
) {
|
|
200
|
+
strategyBalances(
|
|
201
|
+
where: $where
|
|
202
|
+
orderBy: $orderBy
|
|
203
|
+
orderDirection: $orderDirection
|
|
204
|
+
limit: $limit
|
|
205
|
+
after: $after
|
|
206
|
+
) {
|
|
207
|
+
items {
|
|
208
|
+
id
|
|
209
|
+
strategyId
|
|
210
|
+
token
|
|
211
|
+
balance
|
|
212
|
+
totalReceived
|
|
213
|
+
totalDistributed
|
|
214
|
+
totalReceivedUSD
|
|
215
|
+
totalDistributedUSD
|
|
216
|
+
lastUpdatedAt
|
|
217
|
+
}
|
|
218
|
+
totalCount
|
|
219
|
+
pageInfo {
|
|
220
|
+
hasNextPage
|
|
221
|
+
endCursor
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
`;
|
|
226
|
+
var warehouseBalancesQuery = gql`
|
|
227
|
+
query WarehouseBalances(
|
|
228
|
+
$where: warehouseBalanceFilter
|
|
229
|
+
$orderBy: String
|
|
230
|
+
$orderDirection: String
|
|
231
|
+
$limit: Int
|
|
232
|
+
$after: String
|
|
233
|
+
) {
|
|
234
|
+
warehouseBalances(
|
|
235
|
+
where: $where
|
|
236
|
+
orderBy: $orderBy
|
|
237
|
+
orderDirection: $orderDirection
|
|
238
|
+
limit: $limit
|
|
239
|
+
after: $after
|
|
240
|
+
) {
|
|
241
|
+
items {
|
|
242
|
+
id
|
|
243
|
+
user
|
|
244
|
+
token
|
|
245
|
+
balance
|
|
246
|
+
totalEarned
|
|
247
|
+
totalClaimed
|
|
248
|
+
totalEarnedUSD
|
|
249
|
+
lastUpdatedAt
|
|
250
|
+
}
|
|
251
|
+
totalCount
|
|
252
|
+
pageInfo {
|
|
253
|
+
hasNextPage
|
|
254
|
+
endCursor
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
`;
|
|
259
|
+
var forksQuery = gql`
|
|
260
|
+
query Forks(
|
|
261
|
+
$where: forkFilter
|
|
262
|
+
$orderBy: String
|
|
263
|
+
$orderDirection: String
|
|
264
|
+
$limit: Int
|
|
265
|
+
$after: String
|
|
266
|
+
) {
|
|
267
|
+
forks(
|
|
268
|
+
where: $where
|
|
269
|
+
orderBy: $orderBy
|
|
270
|
+
orderDirection: $orderDirection
|
|
271
|
+
limit: $limit
|
|
272
|
+
after: $after
|
|
273
|
+
) {
|
|
274
|
+
items {
|
|
275
|
+
id
|
|
276
|
+
sourceStrategyId
|
|
277
|
+
childStrategyId
|
|
278
|
+
createdAt
|
|
279
|
+
}
|
|
280
|
+
totalCount
|
|
281
|
+
pageInfo {
|
|
282
|
+
hasNextPage
|
|
283
|
+
endCursor
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
`;
|
|
288
|
+
var yieldRedirectorsQuery = gql`
|
|
289
|
+
query YieldRedirectors(
|
|
290
|
+
$where: yieldRedirectorFilter
|
|
291
|
+
$orderBy: String
|
|
292
|
+
$orderDirection: String
|
|
293
|
+
$limit: Int
|
|
294
|
+
$after: String
|
|
295
|
+
) {
|
|
296
|
+
yieldRedirectors(
|
|
297
|
+
where: $where
|
|
298
|
+
orderBy: $orderBy
|
|
299
|
+
orderDirection: $orderDirection
|
|
300
|
+
limit: $limit
|
|
301
|
+
after: $after
|
|
302
|
+
) {
|
|
303
|
+
items {
|
|
304
|
+
id
|
|
305
|
+
owner
|
|
306
|
+
sourceVault
|
|
307
|
+
yieldRecipient
|
|
308
|
+
asset
|
|
309
|
+
name
|
|
310
|
+
symbol
|
|
311
|
+
principal
|
|
312
|
+
totalHarvested
|
|
313
|
+
totalHarvestedUSD
|
|
314
|
+
harvestCount
|
|
315
|
+
lastHarvestAt
|
|
316
|
+
createdAt
|
|
317
|
+
createdAtBlock
|
|
318
|
+
}
|
|
319
|
+
totalCount
|
|
320
|
+
pageInfo {
|
|
321
|
+
hasNextPage
|
|
322
|
+
endCursor
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
`;
|
|
327
|
+
var yieldRedirectorQuery = gql`
|
|
328
|
+
query YieldRedirector($id: String!) {
|
|
329
|
+
yieldRedirector(id: $id) {
|
|
330
|
+
id
|
|
331
|
+
owner
|
|
332
|
+
sourceVault
|
|
333
|
+
yieldRecipient
|
|
334
|
+
asset
|
|
335
|
+
name
|
|
336
|
+
symbol
|
|
337
|
+
principal
|
|
338
|
+
totalHarvested
|
|
339
|
+
totalHarvestedUSD
|
|
340
|
+
harvestCount
|
|
341
|
+
lastHarvestAt
|
|
342
|
+
createdAt
|
|
343
|
+
createdAtBlock
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
`;
|
|
347
|
+
var harvestsQuery = gql`
|
|
348
|
+
query Harvests(
|
|
349
|
+
$where: harvestFilter
|
|
350
|
+
$orderBy: String
|
|
351
|
+
$orderDirection: String
|
|
352
|
+
$limit: Int
|
|
353
|
+
$after: String
|
|
354
|
+
) {
|
|
355
|
+
harvests(
|
|
356
|
+
where: $where
|
|
357
|
+
orderBy: $orderBy
|
|
358
|
+
orderDirection: $orderDirection
|
|
359
|
+
limit: $limit
|
|
360
|
+
after: $after
|
|
361
|
+
) {
|
|
362
|
+
items {
|
|
363
|
+
id
|
|
364
|
+
redirectorId
|
|
365
|
+
amount
|
|
366
|
+
amountUSD
|
|
367
|
+
recipient
|
|
368
|
+
timestamp
|
|
369
|
+
blockNumber
|
|
370
|
+
txHash
|
|
371
|
+
}
|
|
372
|
+
totalCount
|
|
373
|
+
pageInfo {
|
|
374
|
+
hasNextPage
|
|
375
|
+
endCursor
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
`;
|
|
380
|
+
function createIndexer(url, tenant) {
|
|
381
|
+
if (!url) throw new Error("No indexer URL provided. Pass indexerUrl to CuratorSDK or CuratorProvider.");
|
|
382
|
+
const baseUrl = url.replace(/\/graphql$/, "");
|
|
383
|
+
const client = new Client({
|
|
384
|
+
url,
|
|
385
|
+
exchanges: [fetchExchange],
|
|
386
|
+
requestPolicy: "network-only",
|
|
387
|
+
preferGetMethod: false
|
|
388
|
+
});
|
|
389
|
+
const indexer = {
|
|
390
|
+
client,
|
|
391
|
+
gql,
|
|
392
|
+
baseUrl,
|
|
393
|
+
extend(extension) {
|
|
394
|
+
const extended = extension(indexer);
|
|
395
|
+
return Object.assign(indexer, extended);
|
|
396
|
+
},
|
|
397
|
+
strategy: {
|
|
398
|
+
get: async (id) => {
|
|
399
|
+
const result = await client.query(strategyQuery, { id: id.toLowerCase() }).toPromise();
|
|
400
|
+
const strategy = result.data?.strategy;
|
|
401
|
+
if (!strategy) return null;
|
|
402
|
+
const currentAllocations = (strategy.allocations?.items ?? []).filter(
|
|
403
|
+
(a) => a.version === strategy.allocationsVersion
|
|
404
|
+
);
|
|
405
|
+
return {
|
|
406
|
+
...strategy,
|
|
407
|
+
allocations: currentAllocations
|
|
408
|
+
};
|
|
409
|
+
},
|
|
410
|
+
query: async (variables = {}) => {
|
|
411
|
+
const where = tenant ? { tenantId: tenant, ...variables.where } : variables.where;
|
|
412
|
+
const result = await client.query(strategiesQuery, { ...variables, where }).toPromise();
|
|
413
|
+
const data = result.data?.strategys;
|
|
414
|
+
if (!data) return null;
|
|
415
|
+
return {
|
|
416
|
+
...data,
|
|
417
|
+
items: data.items.map((s) => {
|
|
418
|
+
const currentAllocations = (s.allocations?.items ?? []).filter(
|
|
419
|
+
(a) => a.version === s.allocationsVersion
|
|
420
|
+
);
|
|
421
|
+
return {
|
|
422
|
+
...s,
|
|
423
|
+
allocations: currentAllocations
|
|
424
|
+
};
|
|
425
|
+
})
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
distribution: {
|
|
430
|
+
query: async (variables = {}) => {
|
|
431
|
+
const result = await client.query(distributionsQuery, variables).toPromise();
|
|
432
|
+
return result.data?.distributions ?? null;
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
payout: {
|
|
436
|
+
query: async (variables = {}) => {
|
|
437
|
+
const result = await client.query(payoutsQuery, variables).toPromise();
|
|
438
|
+
return result.data?.payouts ?? null;
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
donor: {
|
|
442
|
+
query: async (variables = {}) => {
|
|
443
|
+
const result = await client.query(donorsQuery, variables).toPromise();
|
|
444
|
+
return result.data?.donors ?? null;
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
strategyBalance: {
|
|
448
|
+
query: async (variables = {}) => {
|
|
449
|
+
const result = await client.query(strategyBalancesQuery, variables).toPromise();
|
|
450
|
+
return result.data?.strategyBalances ?? null;
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
warehouseBalance: {
|
|
454
|
+
query: async (variables = {}) => {
|
|
455
|
+
const result = await client.query(warehouseBalancesQuery, variables).toPromise();
|
|
456
|
+
return result.data?.warehouseBalances ?? null;
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
fork: {
|
|
460
|
+
query: async (variables = {}) => {
|
|
461
|
+
const result = await client.query(forksQuery, variables).toPromise();
|
|
462
|
+
return result.data?.forks ?? null;
|
|
463
|
+
}
|
|
464
|
+
},
|
|
465
|
+
yieldRedirector: {
|
|
466
|
+
get: async (id) => {
|
|
467
|
+
const result = await client.query(yieldRedirectorQuery, { id: id.toLowerCase() }).toPromise();
|
|
468
|
+
return result.data?.yieldRedirector ?? null;
|
|
469
|
+
},
|
|
470
|
+
query: async (variables = {}) => {
|
|
471
|
+
const result = await client.query(yieldRedirectorsQuery, variables).toPromise();
|
|
472
|
+
return result.data?.yieldRedirectors ?? null;
|
|
473
|
+
}
|
|
474
|
+
},
|
|
475
|
+
harvest: {
|
|
476
|
+
query: async (variables = {}) => {
|
|
477
|
+
const result = await client.query(harvestsQuery, variables).toPromise();
|
|
478
|
+
return result.data?.harvests ?? null;
|
|
479
|
+
}
|
|
480
|
+
},
|
|
481
|
+
// REST API methods
|
|
482
|
+
trending: async (options) => {
|
|
483
|
+
const params = new URLSearchParams();
|
|
484
|
+
if (options?.period) params.set("period", options.period);
|
|
485
|
+
if (options?.limit) params.set("limit", options.limit.toString());
|
|
486
|
+
const queryString = params.toString();
|
|
487
|
+
const url2 = `${baseUrl}/api/strategies/trending${queryString ? `?${queryString}` : ""}`;
|
|
488
|
+
const response = await fetch(url2);
|
|
489
|
+
return response.json();
|
|
490
|
+
},
|
|
491
|
+
stats: async () => {
|
|
492
|
+
const response = await fetch(`${baseUrl}/api/stats`);
|
|
493
|
+
const data = await response.json();
|
|
494
|
+
return data.data;
|
|
495
|
+
},
|
|
496
|
+
lineage: async (strategyAddress) => {
|
|
497
|
+
const response = await fetch(
|
|
498
|
+
`${baseUrl}/api/strategies/${strategyAddress.toLowerCase()}/lineage`
|
|
499
|
+
);
|
|
500
|
+
if (!response.ok) return null;
|
|
501
|
+
const data = await response.json();
|
|
502
|
+
return data.data;
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
return indexer;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// src/config.ts
|
|
509
|
+
import { mainnet, sepolia, baseSepolia, hardhat } from "viem/chains";
|
|
510
|
+
import deployments from "@curator-studio/contracts/deployments.json";
|
|
511
|
+
var tenants = buildTenantsFromDeployments();
|
|
512
|
+
function buildTenantsFromDeployments() {
|
|
513
|
+
const result = {};
|
|
514
|
+
const d = deployments;
|
|
515
|
+
for (const [chainIdStr, chainData] of Object.entries(d)) {
|
|
516
|
+
const chainId = Number(chainIdStr);
|
|
517
|
+
if (chainData.tenants) {
|
|
518
|
+
for (const [tenantId, tenantData] of Object.entries(chainData.tenants)) {
|
|
519
|
+
if (!result[tenantId]) result[tenantId] = {};
|
|
520
|
+
result[tenantId][chainId] = {
|
|
521
|
+
ensDomain: tenantData.ensDomain ?? tenantId,
|
|
522
|
+
factory: tenantData.StrategyFactory?.address,
|
|
523
|
+
startBlock: tenantData.StrategyFactory?.startBlock,
|
|
524
|
+
foreverSubnameRegistrar: tenantData.ForeverSubnameRegistrar?.address
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
if (!chainData.tenants && chainData.StrategyFactory) {
|
|
529
|
+
const defaultTenant = "default";
|
|
530
|
+
if (!result[defaultTenant]) result[defaultTenant] = {};
|
|
531
|
+
result[defaultTenant][chainId] = {
|
|
532
|
+
ensDomain: defaultTenant,
|
|
533
|
+
factory: chainData.StrategyFactory.address,
|
|
534
|
+
startBlock: chainData.StrategyFactory.startBlock,
|
|
535
|
+
foreverSubnameRegistrar: chainData.ForeverSubnameRegistrar?.address
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return result;
|
|
540
|
+
}
|
|
541
|
+
var config = {
|
|
542
|
+
[mainnet.id]: {
|
|
543
|
+
warehouse: "0x8fb66F38cF86A3d5e8768f8F1754A24A6c661Fb8"
|
|
544
|
+
},
|
|
545
|
+
[sepolia.id]: {
|
|
546
|
+
warehouse: "0x8fb66F38cF86A3d5e8768f8F1754A24A6c661Fb8"
|
|
547
|
+
},
|
|
548
|
+
[baseSepolia.id]: {
|
|
549
|
+
warehouse: "0x8fb66F38cF86A3d5e8768f8F1754A24A6c661Fb8"
|
|
550
|
+
},
|
|
551
|
+
[hardhat.id]: {
|
|
552
|
+
warehouse: deployments["31337"].SplitsWarehouse.address,
|
|
553
|
+
nameWrapper: deployments["31337"].NameWrapper.address,
|
|
554
|
+
ensUniversalResolver: deployments["31337"].UniversalResolver.address
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
function getFactoryTenantMap(chainId) {
|
|
558
|
+
const map = /* @__PURE__ */ new Map();
|
|
559
|
+
for (const [tenantId, chainConfigs] of Object.entries(tenants)) {
|
|
560
|
+
const factory = chainConfigs[chainId]?.factory;
|
|
561
|
+
if (factory) map.set(factory.toLowerCase(), tenantId);
|
|
562
|
+
}
|
|
563
|
+
return map;
|
|
564
|
+
}
|
|
565
|
+
function getAllFactoryAddresses(chainId) {
|
|
566
|
+
return Object.values(tenants).map((chainConfigs) => chainConfigs[chainId]?.factory).filter((addr) => Boolean(addr));
|
|
567
|
+
}
|
|
568
|
+
function getTenantConfig(tenantId, chainId) {
|
|
569
|
+
return tenants[tenantId]?.[chainId];
|
|
570
|
+
}
|
|
571
|
+
function getEnsDomain(chainId, tenantId) {
|
|
572
|
+
if (tenantId) {
|
|
573
|
+
return tenants[tenantId]?.[chainId]?.ensDomain ?? "support-dev.eth";
|
|
574
|
+
}
|
|
575
|
+
const first = Object.values(tenants).find((c) => c[chainId]?.ensDomain);
|
|
576
|
+
return first?.[chainId]?.ensDomain ?? "support-dev.eth";
|
|
577
|
+
}
|
|
578
|
+
var ENS_DOMAIN = getEnsDomain(sepolia.id);
|
|
579
|
+
|
|
580
|
+
// src/index.ts
|
|
581
|
+
import deployments4 from "@curator-studio/contracts/deployments.json";
|
|
582
|
+
|
|
583
|
+
// src/strategy.ts
|
|
584
|
+
import {
|
|
585
|
+
getContract
|
|
586
|
+
} from "viem";
|
|
587
|
+
import "viem";
|
|
588
|
+
|
|
589
|
+
// src/lib/tx.ts
|
|
590
|
+
import { parseEventLogs } from "viem";
|
|
591
|
+
import { waitForTransactionReceipt } from "viem/actions";
|
|
592
|
+
async function writeAndParse(wallet, hash, abi, eventName) {
|
|
593
|
+
const receipt = await waitForTransactionReceipt(wallet, { hash });
|
|
594
|
+
const logs = parseEventLogs({ abi, logs: receipt.logs });
|
|
595
|
+
const event = logs.find((log) => log.eventName === eventName);
|
|
596
|
+
if (!event) {
|
|
597
|
+
throw new Error(`${eventName} event not found`);
|
|
598
|
+
}
|
|
599
|
+
return event.args;
|
|
600
|
+
}
|
|
601
|
+
async function writeAndWait(wallet, hash) {
|
|
602
|
+
await waitForTransactionReceipt(wallet, { hash });
|
|
603
|
+
return { hash };
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// src/strategy.ts
|
|
607
|
+
function createStrategyMethods(wallet, publicClient, deployments5, uploadMetadata) {
|
|
608
|
+
const resolveMetadataURI = (metadata) => {
|
|
609
|
+
if (!uploadMetadata) throw new Error("uploadMetadata is required to create strategies");
|
|
610
|
+
return uploadMetadata(metadata);
|
|
611
|
+
};
|
|
612
|
+
return {
|
|
613
|
+
/**
|
|
614
|
+
* Create a new strategy with optional ENS subdomain.
|
|
615
|
+
* Metadata is uploaded to the configured uploadUrl (defaults to inline data URI if not set).
|
|
616
|
+
*/
|
|
617
|
+
create: async (config2) => {
|
|
618
|
+
if (!wallet) throw new Error("Wallet required");
|
|
619
|
+
const metadataURI = await resolveMetadataURI(config2.metadata);
|
|
620
|
+
const contract = getContract({
|
|
621
|
+
address: deployments5.StrategyFactory.address,
|
|
622
|
+
abi: deployments5.StrategyFactory.abi,
|
|
623
|
+
client: { public: publicClient, wallet }
|
|
624
|
+
});
|
|
625
|
+
const configTuple = {
|
|
626
|
+
owner: config2.owner,
|
|
627
|
+
sourceStrategy: config2.sourceStrategy,
|
|
628
|
+
allocations: config2.allocations.map((a) => ({
|
|
629
|
+
recipient: a.recipient,
|
|
630
|
+
weight: a.weight,
|
|
631
|
+
label: a.label ?? ""
|
|
632
|
+
})),
|
|
633
|
+
metadataURI
|
|
634
|
+
};
|
|
635
|
+
const hash = await contract.write.create(
|
|
636
|
+
[configTuple, config2.ensLabel],
|
|
637
|
+
{
|
|
638
|
+
account: wallet.account
|
|
639
|
+
}
|
|
640
|
+
);
|
|
641
|
+
return writeAndParse(
|
|
642
|
+
wallet,
|
|
643
|
+
hash,
|
|
644
|
+
deployments5.StrategyFactory.abi,
|
|
645
|
+
"StrategyCreated"
|
|
646
|
+
);
|
|
647
|
+
},
|
|
648
|
+
/**
|
|
649
|
+
* Get strategy data including owner, allocations, and metadata
|
|
650
|
+
* Note: Fee is the first allocation by convention
|
|
651
|
+
*/
|
|
652
|
+
getData: async (strategyAddress) => {
|
|
653
|
+
const contract = getContract({
|
|
654
|
+
address: strategyAddress,
|
|
655
|
+
abi: deployments5.Strategy.abi,
|
|
656
|
+
client: { public: publicClient }
|
|
657
|
+
});
|
|
658
|
+
const [owner, sourceStrategy, allocations, totalWeight, metadataURI] = await Promise.all([
|
|
659
|
+
contract.read.owner(),
|
|
660
|
+
contract.read.sourceStrategy(),
|
|
661
|
+
contract.read.getAllocations(),
|
|
662
|
+
contract.read.totalWeight(),
|
|
663
|
+
contract.read.metadataURI()
|
|
664
|
+
]);
|
|
665
|
+
return {
|
|
666
|
+
owner,
|
|
667
|
+
sourceStrategy,
|
|
668
|
+
allocations: allocations.map((a) => ({
|
|
669
|
+
recipient: a.recipient,
|
|
670
|
+
weight: BigInt(a.weight),
|
|
671
|
+
label: a.label || void 0
|
|
672
|
+
})),
|
|
673
|
+
totalWeight: BigInt(totalWeight),
|
|
674
|
+
metadataURI
|
|
675
|
+
};
|
|
676
|
+
},
|
|
677
|
+
/**
|
|
678
|
+
* Get the balance of a token held by the strategy
|
|
679
|
+
*/
|
|
680
|
+
balanceOf: async (strategyAddress, token) => {
|
|
681
|
+
const contract = getContract({
|
|
682
|
+
address: strategyAddress,
|
|
683
|
+
abi: deployments5.Strategy.abi,
|
|
684
|
+
client: { public: publicClient }
|
|
685
|
+
});
|
|
686
|
+
return contract.read.balanceOf([token]);
|
|
687
|
+
},
|
|
688
|
+
/**
|
|
689
|
+
* Update the allocation configuration and metadata (owner only).
|
|
690
|
+
* Metadata is uploaded to the configured uploadUrl (defaults to inline data URI if not set).
|
|
691
|
+
*/
|
|
692
|
+
rebalance: async (strategyAddress, allocations, metadata) => {
|
|
693
|
+
if (!wallet) throw new Error("Wallet required");
|
|
694
|
+
const metadataURI = await resolveMetadataURI(metadata);
|
|
695
|
+
const contract = getContract({
|
|
696
|
+
address: strategyAddress,
|
|
697
|
+
abi: deployments5.Strategy.abi,
|
|
698
|
+
client: { public: publicClient, wallet }
|
|
699
|
+
});
|
|
700
|
+
const allocationsTuple = allocations.map((a) => ({
|
|
701
|
+
recipient: a.recipient,
|
|
702
|
+
weight: a.weight,
|
|
703
|
+
label: a.label ?? ""
|
|
704
|
+
}));
|
|
705
|
+
const hash = await contract.write.rebalance(
|
|
706
|
+
[allocationsTuple, metadataURI],
|
|
707
|
+
{
|
|
708
|
+
account: wallet.account
|
|
709
|
+
}
|
|
710
|
+
);
|
|
711
|
+
return writeAndWait(wallet, hash);
|
|
712
|
+
},
|
|
713
|
+
/**
|
|
714
|
+
* Distribute the strategy's token balance to recipients according to allocations
|
|
715
|
+
*/
|
|
716
|
+
distribute: async (strategyAddress, token) => {
|
|
717
|
+
if (!wallet) throw new Error("Wallet required");
|
|
718
|
+
const contract = getContract({
|
|
719
|
+
address: strategyAddress,
|
|
720
|
+
abi: deployments5.Strategy.abi,
|
|
721
|
+
client: { public: publicClient, wallet }
|
|
722
|
+
});
|
|
723
|
+
const hash = await contract.write.distribute([token], {
|
|
724
|
+
account: wallet.account
|
|
725
|
+
});
|
|
726
|
+
return writeAndWait(wallet, hash);
|
|
727
|
+
},
|
|
728
|
+
/**
|
|
729
|
+
* Set ENS subdomain name for an existing strategy
|
|
730
|
+
* @param strategyAddress The strategy address to set ENS name for
|
|
731
|
+
* @param label ENS subdomain label (e.g., "mystrategy")
|
|
732
|
+
* @dev Automatically handles two cases:
|
|
733
|
+
* 1. If factory doesn't own the name: registers it via ENS registrar
|
|
734
|
+
* 2. If factory owns the name: connects pre-registered name
|
|
735
|
+
*/
|
|
736
|
+
setENSName: async (strategyAddress, label) => {
|
|
737
|
+
if (!wallet) throw new Error("Wallet required");
|
|
738
|
+
const contract = getContract({
|
|
739
|
+
address: deployments5.StrategyFactory.address,
|
|
740
|
+
abi: deployments5.StrategyFactory.abi,
|
|
741
|
+
client: { public: publicClient, wallet }
|
|
742
|
+
});
|
|
743
|
+
const hash = await contract.write.setENSName(
|
|
744
|
+
[strategyAddress, label],
|
|
745
|
+
{
|
|
746
|
+
account: wallet.account
|
|
747
|
+
}
|
|
748
|
+
);
|
|
749
|
+
return writeAndWait(wallet, hash);
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// src/ens.ts
|
|
755
|
+
import {
|
|
756
|
+
getContract as getContract2,
|
|
757
|
+
namehash
|
|
758
|
+
} from "viem";
|
|
759
|
+
function createENSMethods(wallet, publicClient, chainId, deployments5) {
|
|
760
|
+
const methods = {
|
|
761
|
+
/**
|
|
762
|
+
* Check if a subdomain label is available for registration
|
|
763
|
+
* Works on any network where ForeverSubnameRegistrar is deployed
|
|
764
|
+
*/
|
|
765
|
+
available: async (label) => {
|
|
766
|
+
const registrarAddress = deployments5.ForeverSubnameRegistrar?.address;
|
|
767
|
+
if (!registrarAddress) {
|
|
768
|
+
throw new Error("ForeverSubnameRegistrar not deployed on this network");
|
|
769
|
+
}
|
|
770
|
+
const contract = getContract2({
|
|
771
|
+
address: registrarAddress,
|
|
772
|
+
abi: deployments5.ForeverSubnameRegistrar.abi,
|
|
773
|
+
client: { public: publicClient }
|
|
774
|
+
});
|
|
775
|
+
return contract.read.available([label]);
|
|
776
|
+
},
|
|
777
|
+
/**
|
|
778
|
+
* Register a subdomain via ForeverSubnameRegistrar
|
|
779
|
+
* Works on any network where ForeverSubnameRegistrar is deployed
|
|
780
|
+
*/
|
|
781
|
+
register: async (label, owner) => {
|
|
782
|
+
if (!wallet) throw new Error("Wallet required");
|
|
783
|
+
const registrarAddress = deployments5.ForeverSubnameRegistrar?.address;
|
|
784
|
+
if (!registrarAddress) {
|
|
785
|
+
throw new Error("ForeverSubnameRegistrar not deployed on this network");
|
|
786
|
+
}
|
|
787
|
+
const to = owner ?? wallet.account?.address;
|
|
788
|
+
if (!to) throw new Error("Owner address required");
|
|
789
|
+
const contract = getContract2({
|
|
790
|
+
address: registrarAddress,
|
|
791
|
+
abi: deployments5.ForeverSubnameRegistrar.abi,
|
|
792
|
+
client: { public: publicClient, wallet }
|
|
793
|
+
});
|
|
794
|
+
const hash = await contract.write.register([label, to], {
|
|
795
|
+
account: wallet.account,
|
|
796
|
+
gas: 5000000n
|
|
797
|
+
// Explicit gas limit under Sepolia's 16.7M cap
|
|
798
|
+
});
|
|
799
|
+
return writeAndParse(
|
|
800
|
+
wallet,
|
|
801
|
+
hash,
|
|
802
|
+
deployments5.ForeverSubnameRegistrar.abi,
|
|
803
|
+
"NameRegistered"
|
|
804
|
+
);
|
|
805
|
+
},
|
|
806
|
+
/**
|
|
807
|
+
* Get the address record for an ENS name (forward resolution)
|
|
808
|
+
* @param name Full ENS name (e.g., "mystrategy.support.eth" - see ENS_DOMAIN config)
|
|
809
|
+
* @returns The resolved address or null if not set
|
|
810
|
+
*/
|
|
811
|
+
getAddress: async (name) => {
|
|
812
|
+
try {
|
|
813
|
+
const node = namehash(name);
|
|
814
|
+
const contract = getContract2({
|
|
815
|
+
address: deployments5.PublicResolver.address,
|
|
816
|
+
abi: deployments5.PublicResolver.abi,
|
|
817
|
+
client: { public: publicClient }
|
|
818
|
+
});
|
|
819
|
+
const addr = await contract.read.addr([node]);
|
|
820
|
+
return addr === "0x0000000000000000000000000000000000000000" ? null : addr;
|
|
821
|
+
} catch {
|
|
822
|
+
return null;
|
|
823
|
+
}
|
|
824
|
+
},
|
|
825
|
+
/**
|
|
826
|
+
* Set the address record for an ENS name (forward resolution)
|
|
827
|
+
* @param name Full ENS name (e.g., "mystrategy.support.eth" - see ENS_DOMAIN config)
|
|
828
|
+
* @param address The address the name should resolve to
|
|
829
|
+
*/
|
|
830
|
+
setAddress: async (name, address) => {
|
|
831
|
+
if (!wallet) throw new Error("Wallet required");
|
|
832
|
+
const node = namehash(name);
|
|
833
|
+
const contract = getContract2({
|
|
834
|
+
address: deployments5.PublicResolver.address,
|
|
835
|
+
abi: deployments5.PublicResolver.abi,
|
|
836
|
+
client: { public: publicClient, wallet }
|
|
837
|
+
});
|
|
838
|
+
const hash = await contract.write.setAddr([node, address], {
|
|
839
|
+
account: wallet.account,
|
|
840
|
+
gas: 2000000n
|
|
841
|
+
// Explicit gas limit under Sepolia's 16.7M cap
|
|
842
|
+
});
|
|
843
|
+
return writeAndWait(wallet, hash);
|
|
844
|
+
},
|
|
845
|
+
/**
|
|
846
|
+
* Set the reverse name record for the caller's address
|
|
847
|
+
* This allows resolving your wallet address back to an ENS name
|
|
848
|
+
* Note: You can only set reverse records for addresses you control
|
|
849
|
+
* @param name The full ENS name (e.g., "mystrategy.support.eth" - see ENS_DOMAIN config)
|
|
850
|
+
*/
|
|
851
|
+
setReverseRecord: async (name) => {
|
|
852
|
+
if (!wallet) throw new Error("Wallet required");
|
|
853
|
+
const contract = getContract2({
|
|
854
|
+
address: deployments5.ReverseRegistrar.address,
|
|
855
|
+
abi: deployments5.ReverseRegistrar.abi,
|
|
856
|
+
client: { public: publicClient, wallet }
|
|
857
|
+
});
|
|
858
|
+
const hash = await contract.write.setName([name], {
|
|
859
|
+
account: wallet.account,
|
|
860
|
+
gas: 2000000n
|
|
861
|
+
});
|
|
862
|
+
return writeAndWait(wallet, hash);
|
|
863
|
+
},
|
|
864
|
+
/**
|
|
865
|
+
* Set the reverse name record for any address (requires authorization)
|
|
866
|
+
* This allows resolving an address back to its ENS name
|
|
867
|
+
* Note: Only works if you're authorized to set reverse records for the address
|
|
868
|
+
* @param addr The address to set the reverse record for
|
|
869
|
+
* @param name The full ENS name (e.g., "mystrategy.support.eth" - see ENS_DOMAIN config)
|
|
870
|
+
*/
|
|
871
|
+
setReverseNameForAddr: async (addr, name) => {
|
|
872
|
+
if (!wallet) throw new Error("Wallet required");
|
|
873
|
+
const contract = getContract2({
|
|
874
|
+
address: deployments5.ReverseRegistrar.address,
|
|
875
|
+
abi: deployments5.ReverseRegistrar.abi,
|
|
876
|
+
client: { public: publicClient, wallet }
|
|
877
|
+
});
|
|
878
|
+
const hash = await contract.write.setNameForAddr(
|
|
879
|
+
[
|
|
880
|
+
addr,
|
|
881
|
+
wallet.account.address,
|
|
882
|
+
deployments5.PublicResolver.address,
|
|
883
|
+
name
|
|
884
|
+
],
|
|
885
|
+
{
|
|
886
|
+
account: wallet.account,
|
|
887
|
+
gas: 2000000n
|
|
888
|
+
// Explicit gas limit under Sepolia's 16.7M cap
|
|
889
|
+
}
|
|
890
|
+
);
|
|
891
|
+
return writeAndWait(wallet, hash);
|
|
892
|
+
},
|
|
893
|
+
/**
|
|
894
|
+
* Register a subdomain and set up full address resolution
|
|
895
|
+
* 1. Registers the subdomain
|
|
896
|
+
* 2. Sets forward resolution (name -> address)
|
|
897
|
+
* @param label The subdomain label (e.g., "mystrategy" for "mystrategy.support.eth" - see ENS_DOMAIN config)
|
|
898
|
+
* @param resolveToAddress The address the name should resolve to
|
|
899
|
+
* @param owner Optional owner of the ENS name (defaults to wallet address)
|
|
900
|
+
*/
|
|
901
|
+
registerWithAddress: async (label, resolveToAddress, owner) => {
|
|
902
|
+
const result = await methods.register(label, owner);
|
|
903
|
+
const fullName = `${label}.${ENS_DOMAIN}`;
|
|
904
|
+
await methods.setAddress(fullName, resolveToAddress);
|
|
905
|
+
return result;
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
return methods;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// src/warehouse.ts
|
|
912
|
+
import {
|
|
913
|
+
getContract as getContract3
|
|
914
|
+
} from "viem";
|
|
915
|
+
function createWarehouseMethods(wallet, publicClient, chainId, deployments5) {
|
|
916
|
+
return {
|
|
917
|
+
/**
|
|
918
|
+
* Withdraw tokens from the SplitsWarehouse
|
|
919
|
+
* @param owner Address to withdraw for (usually msg.sender)
|
|
920
|
+
* @param token Token address to withdraw
|
|
921
|
+
*/
|
|
922
|
+
withdraw: async (owner, token) => {
|
|
923
|
+
if (!wallet) throw new Error("Wallet required");
|
|
924
|
+
const warehouseAddress = config[chainId]?.warehouse;
|
|
925
|
+
if (!warehouseAddress)
|
|
926
|
+
throw new Error("SplitsWarehouse not deployed on this network");
|
|
927
|
+
const contract = getContract3({
|
|
928
|
+
address: warehouseAddress,
|
|
929
|
+
abi: deployments5.SplitsWarehouse.abi,
|
|
930
|
+
client: { public: publicClient, wallet }
|
|
931
|
+
});
|
|
932
|
+
const hash = await contract.write.withdraw([owner, token], {
|
|
933
|
+
account: wallet.account
|
|
934
|
+
});
|
|
935
|
+
return writeAndWait(wallet, hash);
|
|
936
|
+
},
|
|
937
|
+
/**
|
|
938
|
+
* Get warehouse balance for a user/token
|
|
939
|
+
* @param owner Address to check balance for
|
|
940
|
+
* @param token Token address
|
|
941
|
+
*/
|
|
942
|
+
balanceOf: async (owner, token) => {
|
|
943
|
+
const warehouseAddress = config[chainId]?.warehouse;
|
|
944
|
+
if (!warehouseAddress)
|
|
945
|
+
throw new Error("SplitsWarehouse not deployed on this network");
|
|
946
|
+
const contract = getContract3({
|
|
947
|
+
address: warehouseAddress,
|
|
948
|
+
abi: deployments5.SplitsWarehouse.abi,
|
|
949
|
+
client: { public: publicClient }
|
|
950
|
+
});
|
|
951
|
+
const tokenId = BigInt(token);
|
|
952
|
+
return contract.read.balanceOf([owner, tokenId]);
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// src/yield-redirector.ts
|
|
958
|
+
import {
|
|
959
|
+
getContract as getContract4
|
|
960
|
+
} from "viem";
|
|
961
|
+
function createYieldRedirectorMethods(wallet, publicClient, deployments5) {
|
|
962
|
+
return {
|
|
963
|
+
/**
|
|
964
|
+
* Create a new yield redirector
|
|
965
|
+
* @param sourceVault ERC-4626 vault address that generates yield
|
|
966
|
+
* @param yieldRecipient Address to receive harvested yield (typically a Strategy)
|
|
967
|
+
* @param owner Address that can update yield recipient
|
|
968
|
+
*/
|
|
969
|
+
create: async (sourceVault, yieldRecipient, owner) => {
|
|
970
|
+
if (!wallet) throw new Error("Wallet required");
|
|
971
|
+
const contract = getContract4({
|
|
972
|
+
address: deployments5.YieldRedirectorFactory.address,
|
|
973
|
+
abi: deployments5.YieldRedirectorFactory.abi,
|
|
974
|
+
client: { public: publicClient, wallet }
|
|
975
|
+
});
|
|
976
|
+
const hash = await contract.write.create(
|
|
977
|
+
[sourceVault, yieldRecipient, owner],
|
|
978
|
+
{
|
|
979
|
+
account: wallet.account
|
|
980
|
+
}
|
|
981
|
+
);
|
|
982
|
+
return writeAndParse(
|
|
983
|
+
wallet,
|
|
984
|
+
hash,
|
|
985
|
+
deployments5.YieldRedirectorFactory.abi,
|
|
986
|
+
"YieldRedirectorCreated"
|
|
987
|
+
);
|
|
988
|
+
},
|
|
989
|
+
/**
|
|
990
|
+
* Create a new yield redirector with deterministic address
|
|
991
|
+
* @param sourceVault ERC-4626 vault address that generates yield
|
|
992
|
+
* @param yieldRecipient Address to receive harvested yield (typically a Strategy)
|
|
993
|
+
* @param owner Address that can update yield recipient
|
|
994
|
+
* @param salt Bytes32 salt for deterministic address generation
|
|
995
|
+
*/
|
|
996
|
+
createDeterministic: async (sourceVault, yieldRecipient, owner, salt) => {
|
|
997
|
+
if (!wallet) throw new Error("Wallet required");
|
|
998
|
+
const contract = getContract4({
|
|
999
|
+
address: deployments5.YieldRedirectorFactory.address,
|
|
1000
|
+
abi: deployments5.YieldRedirectorFactory.abi,
|
|
1001
|
+
client: { public: publicClient, wallet }
|
|
1002
|
+
});
|
|
1003
|
+
const hash = await contract.write.createDeterministic(
|
|
1004
|
+
[sourceVault, yieldRecipient, owner, salt],
|
|
1005
|
+
{
|
|
1006
|
+
account: wallet.account
|
|
1007
|
+
}
|
|
1008
|
+
);
|
|
1009
|
+
return writeAndParse(
|
|
1010
|
+
wallet,
|
|
1011
|
+
hash,
|
|
1012
|
+
deployments5.YieldRedirectorFactory.abi,
|
|
1013
|
+
"YieldRedirectorCreated"
|
|
1014
|
+
);
|
|
1015
|
+
},
|
|
1016
|
+
/**
|
|
1017
|
+
* Harvest yield from a redirector and send to recipient
|
|
1018
|
+
* @param redirectorAddress The yield redirector contract address
|
|
1019
|
+
*/
|
|
1020
|
+
harvest: async (redirectorAddress) => {
|
|
1021
|
+
if (!wallet) throw new Error("Wallet required");
|
|
1022
|
+
const contract = getContract4({
|
|
1023
|
+
address: redirectorAddress,
|
|
1024
|
+
abi: deployments5.YieldRedirector4626.abi,
|
|
1025
|
+
client: { public: publicClient, wallet }
|
|
1026
|
+
});
|
|
1027
|
+
const hash = await contract.write.harvest([], {
|
|
1028
|
+
account: wallet.account
|
|
1029
|
+
});
|
|
1030
|
+
return writeAndWait(wallet, hash);
|
|
1031
|
+
},
|
|
1032
|
+
/**
|
|
1033
|
+
* Get the current surplus (harvestable yield) from a redirector
|
|
1034
|
+
* @param redirectorAddress The yield redirector contract address
|
|
1035
|
+
*/
|
|
1036
|
+
surplus: async (redirectorAddress) => {
|
|
1037
|
+
const contract = getContract4({
|
|
1038
|
+
address: redirectorAddress,
|
|
1039
|
+
abi: deployments5.YieldRedirector4626.abi,
|
|
1040
|
+
client: { public: publicClient }
|
|
1041
|
+
});
|
|
1042
|
+
return contract.read.surplus();
|
|
1043
|
+
},
|
|
1044
|
+
/**
|
|
1045
|
+
* Get the principal deposited in a redirector
|
|
1046
|
+
* @param redirectorAddress The yield redirector contract address
|
|
1047
|
+
*/
|
|
1048
|
+
principal: async (redirectorAddress) => {
|
|
1049
|
+
const contract = getContract4({
|
|
1050
|
+
address: redirectorAddress,
|
|
1051
|
+
abi: deployments5.YieldRedirector4626.abi,
|
|
1052
|
+
client: { public: publicClient }
|
|
1053
|
+
});
|
|
1054
|
+
return contract.read.principal();
|
|
1055
|
+
},
|
|
1056
|
+
/**
|
|
1057
|
+
* Get the total value in the source vault (principal + yield)
|
|
1058
|
+
* @param redirectorAddress The yield redirector contract address
|
|
1059
|
+
*/
|
|
1060
|
+
sourceVaultValue: async (redirectorAddress) => {
|
|
1061
|
+
const contract = getContract4({
|
|
1062
|
+
address: redirectorAddress,
|
|
1063
|
+
abi: deployments5.YieldRedirector4626.abi,
|
|
1064
|
+
client: { public: publicClient }
|
|
1065
|
+
});
|
|
1066
|
+
return contract.read.sourceVaultValue();
|
|
1067
|
+
},
|
|
1068
|
+
/**
|
|
1069
|
+
* Update the yield recipient (owner only)
|
|
1070
|
+
* @param redirectorAddress The yield redirector contract address
|
|
1071
|
+
* @param newRecipient New address to receive harvested yield
|
|
1072
|
+
*/
|
|
1073
|
+
setYieldRecipient: async (redirectorAddress, newRecipient) => {
|
|
1074
|
+
if (!wallet) throw new Error("Wallet required");
|
|
1075
|
+
const contract = getContract4({
|
|
1076
|
+
address: redirectorAddress,
|
|
1077
|
+
abi: deployments5.YieldRedirector4626.abi,
|
|
1078
|
+
client: { public: publicClient, wallet }
|
|
1079
|
+
});
|
|
1080
|
+
const hash = await contract.write.setYieldRecipient(
|
|
1081
|
+
[newRecipient],
|
|
1082
|
+
{
|
|
1083
|
+
account: wallet.account
|
|
1084
|
+
}
|
|
1085
|
+
);
|
|
1086
|
+
return writeAndWait(wallet, hash);
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
// src/components/provider.tsx
|
|
1092
|
+
import {
|
|
1093
|
+
createContext,
|
|
1094
|
+
useContext,
|
|
1095
|
+
useEffect,
|
|
1096
|
+
useState
|
|
1097
|
+
} from "react";
|
|
1098
|
+
import { jsx } from "react/jsx-runtime";
|
|
1099
|
+
var CuratorContext = createContext({
|
|
1100
|
+
sdk: null
|
|
1101
|
+
});
|
|
1102
|
+
function CuratorProvider({
|
|
1103
|
+
children,
|
|
1104
|
+
client,
|
|
1105
|
+
defaultChain,
|
|
1106
|
+
tenant,
|
|
1107
|
+
indexerUrl,
|
|
1108
|
+
uploadMetadata
|
|
1109
|
+
}) {
|
|
1110
|
+
const [sdk, setSdk] = useState(() => {
|
|
1111
|
+
return new CuratorSDK(client, { chain: defaultChain, tenant, indexerUrl, uploadMetadata });
|
|
1112
|
+
});
|
|
1113
|
+
useEffect(() => {
|
|
1114
|
+
setSdk(new CuratorSDK(client, { chain: defaultChain, tenant, indexerUrl, uploadMetadata }));
|
|
1115
|
+
}, [client, defaultChain, tenant, indexerUrl, uploadMetadata]);
|
|
1116
|
+
return /* @__PURE__ */ jsx(CuratorContext.Provider, { value: { sdk }, children });
|
|
1117
|
+
}
|
|
1118
|
+
function useCuratorSDK() {
|
|
1119
|
+
const context = useContext(CuratorContext);
|
|
1120
|
+
if (!context) {
|
|
1121
|
+
throw new Error("useCuratorSDK must be used within a CuratorProvider");
|
|
1122
|
+
}
|
|
1123
|
+
return context;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// src/hooks/utils.ts
|
|
1127
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
1128
|
+
var INVALIDATION_TIMEOUT_MS = 3e3;
|
|
1129
|
+
var ENS_INVALIDATION_TIMEOUT_MS = 1e3;
|
|
1130
|
+
function useInvalidate() {
|
|
1131
|
+
const queryClient = useQueryClient();
|
|
1132
|
+
return (queryKeys) => {
|
|
1133
|
+
setTimeout(
|
|
1134
|
+
() => queryKeys.map(
|
|
1135
|
+
(queryKey) => queryClient.invalidateQueries({ queryKey })
|
|
1136
|
+
),
|
|
1137
|
+
INVALIDATION_TIMEOUT_MS
|
|
1138
|
+
);
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
function useInvalidateENS() {
|
|
1142
|
+
const queryClient = useQueryClient();
|
|
1143
|
+
return () => {
|
|
1144
|
+
setTimeout(() => {
|
|
1145
|
+
queryClient.invalidateQueries({ queryKey: ["ens"] });
|
|
1146
|
+
queryClient.invalidateQueries({
|
|
1147
|
+
predicate: (query) => {
|
|
1148
|
+
const key = query.queryKey[0];
|
|
1149
|
+
return typeof key === "string" && (key.startsWith("ens") || key === "ensName" || key === "ensAddress" || key === "ensAvatar");
|
|
1150
|
+
}
|
|
1151
|
+
});
|
|
1152
|
+
}, ENS_INVALIDATION_TIMEOUT_MS);
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// src/hooks/use-ens.ts
|
|
1157
|
+
import { useQuery } from "@tanstack/react-query";
|
|
1158
|
+
function useENSGetAddress(name, opts) {
|
|
1159
|
+
const { sdk } = useCuratorSDK();
|
|
1160
|
+
return useQuery({
|
|
1161
|
+
queryKey: ["ens", "address", name],
|
|
1162
|
+
queryFn: () => sdk?.ens.getAddress(name) ?? null,
|
|
1163
|
+
enabled: Boolean(sdk) && Boolean(name) && (opts?.enabled ?? true),
|
|
1164
|
+
refetchInterval: opts?.refetchInterval
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
function useENSAvailable(label, opts) {
|
|
1168
|
+
const { sdk } = useCuratorSDK();
|
|
1169
|
+
return useQuery({
|
|
1170
|
+
queryKey: ["ens", "available", label],
|
|
1171
|
+
queryFn: () => sdk?.ens.available(label) ?? null,
|
|
1172
|
+
enabled: Boolean(sdk) && Boolean(label) && (opts?.enabled ?? true),
|
|
1173
|
+
refetchInterval: opts?.refetchInterval
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// src/hooks/use-strategy.ts
|
|
1178
|
+
import {
|
|
1179
|
+
useMutation,
|
|
1180
|
+
useQuery as useQuery2
|
|
1181
|
+
} from "@tanstack/react-query";
|
|
1182
|
+
import { toast } from "sonner";
|
|
1183
|
+
function useStrategyData(strategyAddress, opts) {
|
|
1184
|
+
const { sdk } = useCuratorSDK();
|
|
1185
|
+
return useQuery2({
|
|
1186
|
+
queryKey: ["strategy", "data", strategyAddress],
|
|
1187
|
+
queryFn: () => strategyAddress ? sdk?.strategy.getData(strategyAddress) ?? null : null,
|
|
1188
|
+
enabled: Boolean(sdk) && Boolean(strategyAddress) && (opts?.enabled ?? true),
|
|
1189
|
+
refetchInterval: opts?.refetchInterval
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
function useStrategyBalance(strategyAddress, token, opts) {
|
|
1193
|
+
const { sdk } = useCuratorSDK();
|
|
1194
|
+
return useQuery2({
|
|
1195
|
+
queryKey: ["strategy", "balance", strategyAddress, token],
|
|
1196
|
+
queryFn: () => strategyAddress && token ? sdk?.strategy.balanceOf(strategyAddress, token) ?? null : null,
|
|
1197
|
+
enabled: Boolean(sdk) && Boolean(strategyAddress) && Boolean(token) && (opts?.enabled ?? true),
|
|
1198
|
+
refetchInterval: opts?.refetchInterval
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
function useCreateStrategy(opts) {
|
|
1202
|
+
const { sdk } = useCuratorSDK();
|
|
1203
|
+
const invalidate = useInvalidate();
|
|
1204
|
+
return useMutation({
|
|
1205
|
+
mutationFn: async (config2) => {
|
|
1206
|
+
if (!sdk) throw new Error("SDK not initialized");
|
|
1207
|
+
return sdk.strategy.create(config2);
|
|
1208
|
+
},
|
|
1209
|
+
onSuccess: (data, ...args) => {
|
|
1210
|
+
invalidate([
|
|
1211
|
+
["strategy"],
|
|
1212
|
+
["strategies"]
|
|
1213
|
+
// Invalidate strategies list
|
|
1214
|
+
]);
|
|
1215
|
+
opts?.onSuccess?.(data, ...args);
|
|
1216
|
+
},
|
|
1217
|
+
onError: (error, ...args) => {
|
|
1218
|
+
toast.error(error.message || "Failed to create strategy");
|
|
1219
|
+
opts?.onError?.(error, ...args);
|
|
1220
|
+
},
|
|
1221
|
+
...opts
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
function useRebalanceStrategy(opts) {
|
|
1225
|
+
const { sdk } = useCuratorSDK();
|
|
1226
|
+
const invalidate = useInvalidate();
|
|
1227
|
+
return useMutation({
|
|
1228
|
+
mutationFn: async ({
|
|
1229
|
+
strategyAddress,
|
|
1230
|
+
allocations,
|
|
1231
|
+
metadata
|
|
1232
|
+
}) => {
|
|
1233
|
+
if (!sdk) throw new Error("SDK not initialized");
|
|
1234
|
+
return sdk.strategy.rebalance(strategyAddress, allocations, metadata);
|
|
1235
|
+
},
|
|
1236
|
+
onSuccess: (data, variables, ...args) => {
|
|
1237
|
+
invalidate([
|
|
1238
|
+
["strategy", "data", variables.strategyAddress],
|
|
1239
|
+
["strategy", variables.strategyAddress],
|
|
1240
|
+
// Invalidate indexer query
|
|
1241
|
+
["strategies"]
|
|
1242
|
+
// Invalidate strategies list
|
|
1243
|
+
]);
|
|
1244
|
+
opts?.onSuccess?.(data, variables, ...args);
|
|
1245
|
+
},
|
|
1246
|
+
onError: (error, ...args) => {
|
|
1247
|
+
toast.error(error.message || "Failed to rebalance strategy");
|
|
1248
|
+
opts?.onError?.(error, ...args);
|
|
1249
|
+
},
|
|
1250
|
+
...opts
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
function useDistributeStrategy(opts) {
|
|
1254
|
+
const { sdk } = useCuratorSDK();
|
|
1255
|
+
const invalidate = useInvalidate();
|
|
1256
|
+
return useMutation({
|
|
1257
|
+
mutationFn: async ({
|
|
1258
|
+
strategyAddress,
|
|
1259
|
+
token
|
|
1260
|
+
}) => {
|
|
1261
|
+
if (!sdk) throw new Error("SDK not initialized");
|
|
1262
|
+
return sdk.strategy.distribute(strategyAddress, token);
|
|
1263
|
+
},
|
|
1264
|
+
onSuccess: (data, variables, ...args) => {
|
|
1265
|
+
toast.success("Distribution completed successfully");
|
|
1266
|
+
invalidate([["strategy"]]);
|
|
1267
|
+
invalidate([["strategyBalances"]]);
|
|
1268
|
+
invalidate([["distributions"]]);
|
|
1269
|
+
opts?.onSuccess?.(data, variables, ...args);
|
|
1270
|
+
},
|
|
1271
|
+
onError: (error, ...args) => {
|
|
1272
|
+
toast.error(error.message || "Failed to distribute tokens");
|
|
1273
|
+
opts?.onError?.(error, ...args);
|
|
1274
|
+
},
|
|
1275
|
+
...opts
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
function useSetENSName(opts) {
|
|
1279
|
+
const { sdk } = useCuratorSDK();
|
|
1280
|
+
const invalidate = useInvalidate();
|
|
1281
|
+
const invalidateENS = useInvalidateENS();
|
|
1282
|
+
return useMutation({
|
|
1283
|
+
mutationFn: async ({
|
|
1284
|
+
strategyAddress,
|
|
1285
|
+
label
|
|
1286
|
+
}) => {
|
|
1287
|
+
if (!sdk) throw new Error("SDK not initialized");
|
|
1288
|
+
return sdk.strategy.setENSName(strategyAddress, label);
|
|
1289
|
+
},
|
|
1290
|
+
onSuccess: (data, variables, ...args) => {
|
|
1291
|
+
toast.success("ENS name connected successfully");
|
|
1292
|
+
invalidate([["strategy", variables.strategyAddress]]);
|
|
1293
|
+
invalidateENS();
|
|
1294
|
+
opts?.onSuccess?.(data, variables, ...args);
|
|
1295
|
+
},
|
|
1296
|
+
onError: (error, ...args) => {
|
|
1297
|
+
toast.error(error.message || "Failed to connect ENS name");
|
|
1298
|
+
opts?.onError?.(error, ...args);
|
|
1299
|
+
},
|
|
1300
|
+
...opts
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
// src/hooks/use-warehouse.ts
|
|
1305
|
+
import {
|
|
1306
|
+
useMutation as useMutation2,
|
|
1307
|
+
useQuery as useQuery3,
|
|
1308
|
+
useQueryClient as useQueryClient2
|
|
1309
|
+
} from "@tanstack/react-query";
|
|
1310
|
+
import { toast as toast2 } from "sonner";
|
|
1311
|
+
function useWarehouseBalances(variables = {}, opts) {
|
|
1312
|
+
const { sdk } = useCuratorSDK();
|
|
1313
|
+
return useQuery3({
|
|
1314
|
+
queryKey: ["warehouseBalances", variables],
|
|
1315
|
+
queryFn: () => sdk?.indexer?.warehouseBalance.query(variables) ?? null,
|
|
1316
|
+
enabled: Boolean(sdk?.indexer) && (opts?.enabled ?? true),
|
|
1317
|
+
refetchInterval: opts?.refetchInterval
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
function useWarehouseBalance(user, token, opts) {
|
|
1321
|
+
const { sdk } = useCuratorSDK();
|
|
1322
|
+
return useQuery3({
|
|
1323
|
+
queryKey: ["warehouseBalance", user, token],
|
|
1324
|
+
queryFn: async () => {
|
|
1325
|
+
if (!user || !token) return null;
|
|
1326
|
+
const result = await sdk?.indexer?.warehouseBalance.query({
|
|
1327
|
+
where: { user: user.toLowerCase(), token: token.toLowerCase() },
|
|
1328
|
+
limit: 1
|
|
1329
|
+
});
|
|
1330
|
+
return result?.items[0] ?? null;
|
|
1331
|
+
},
|
|
1332
|
+
enabled: Boolean(sdk?.indexer) && Boolean(user) && Boolean(token) && (opts?.enabled ?? true),
|
|
1333
|
+
refetchInterval: opts?.refetchInterval
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
function useWithdrawFromWarehouse(opts) {
|
|
1337
|
+
const { sdk } = useCuratorSDK();
|
|
1338
|
+
const queryClient = useQueryClient2();
|
|
1339
|
+
return useMutation2({
|
|
1340
|
+
mutationFn: async ({
|
|
1341
|
+
owner,
|
|
1342
|
+
token
|
|
1343
|
+
}) => {
|
|
1344
|
+
if (!sdk) throw new Error("SDK not initialized");
|
|
1345
|
+
return sdk.warehouse.withdraw(owner, token);
|
|
1346
|
+
},
|
|
1347
|
+
onSuccess: (data, variables, ...args) => {
|
|
1348
|
+
toast2.success("Withdrawal successful");
|
|
1349
|
+
queryClient.invalidateQueries({
|
|
1350
|
+
queryKey: ["warehouseBalance", variables.owner, variables.token]
|
|
1351
|
+
});
|
|
1352
|
+
queryClient.invalidateQueries({
|
|
1353
|
+
queryKey: ["warehouseBalances"]
|
|
1354
|
+
});
|
|
1355
|
+
opts?.onSuccess?.(data, variables, ...args);
|
|
1356
|
+
},
|
|
1357
|
+
onError: (error, ...args) => {
|
|
1358
|
+
toast2.error(error.message || "Failed to withdraw tokens");
|
|
1359
|
+
opts?.onError?.(error, ...args);
|
|
1360
|
+
},
|
|
1361
|
+
...opts
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
// src/hooks/use-yield.ts
|
|
1366
|
+
import {
|
|
1367
|
+
useMutation as useMutation3,
|
|
1368
|
+
useQuery as useQuery4
|
|
1369
|
+
} from "@tanstack/react-query";
|
|
1370
|
+
import { toast as toast3 } from "sonner";
|
|
1371
|
+
function useYieldRedirectors(variables = {}, opts) {
|
|
1372
|
+
const { sdk } = useCuratorSDK();
|
|
1373
|
+
return useQuery4({
|
|
1374
|
+
queryKey: ["yieldRedirectors", variables],
|
|
1375
|
+
queryFn: () => sdk?.indexer?.yieldRedirector.query(variables) ?? null,
|
|
1376
|
+
enabled: Boolean(sdk?.indexer) && (opts?.enabled ?? true),
|
|
1377
|
+
refetchInterval: opts?.refetchInterval
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
function useYieldRedirectorById(id, opts) {
|
|
1381
|
+
const { sdk } = useCuratorSDK();
|
|
1382
|
+
return useQuery4({
|
|
1383
|
+
queryKey: ["yieldRedirector", id],
|
|
1384
|
+
queryFn: () => id ? sdk?.indexer?.yieldRedirector.get(id) ?? null : null,
|
|
1385
|
+
enabled: Boolean(sdk?.indexer) && Boolean(id) && (opts?.enabled ?? true),
|
|
1386
|
+
refetchInterval: opts?.refetchInterval
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1389
|
+
function useHarvests(variables = {}, opts) {
|
|
1390
|
+
const { sdk } = useCuratorSDK();
|
|
1391
|
+
return useQuery4({
|
|
1392
|
+
queryKey: ["harvests", variables],
|
|
1393
|
+
queryFn: () => sdk?.indexer?.harvest.query(variables) ?? null,
|
|
1394
|
+
enabled: Boolean(sdk?.indexer) && (opts?.enabled ?? true),
|
|
1395
|
+
refetchInterval: opts?.refetchInterval
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
function useCreateYieldRedirector(opts) {
|
|
1399
|
+
const { sdk } = useCuratorSDK();
|
|
1400
|
+
const invalidate = useInvalidate();
|
|
1401
|
+
return useMutation3({
|
|
1402
|
+
mutationFn: async (params) => {
|
|
1403
|
+
if (!sdk) throw new Error("SDK not initialized");
|
|
1404
|
+
return sdk.yieldRedirector.create(
|
|
1405
|
+
params.sourceVault,
|
|
1406
|
+
params.yieldRecipient,
|
|
1407
|
+
params.owner
|
|
1408
|
+
);
|
|
1409
|
+
},
|
|
1410
|
+
onSuccess: (data, variables, ...args) => {
|
|
1411
|
+
toast3.success("Yield vault connected successfully");
|
|
1412
|
+
invalidate([["yieldRedirectors"]]);
|
|
1413
|
+
opts?.onSuccess?.(data, variables, ...args);
|
|
1414
|
+
},
|
|
1415
|
+
onError: (error, ...args) => {
|
|
1416
|
+
toast3.error(error.message || "Failed to connect yield vault");
|
|
1417
|
+
opts?.onError?.(error, ...args);
|
|
1418
|
+
},
|
|
1419
|
+
...opts
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
function useHarvestYield(opts) {
|
|
1423
|
+
const { sdk } = useCuratorSDK();
|
|
1424
|
+
const invalidate = useInvalidate();
|
|
1425
|
+
return useMutation3({
|
|
1426
|
+
mutationFn: async (redirectorAddress) => {
|
|
1427
|
+
if (!sdk) throw new Error("SDK not initialized");
|
|
1428
|
+
return sdk.yieldRedirector.harvest(redirectorAddress);
|
|
1429
|
+
},
|
|
1430
|
+
onSuccess: (data, redirectorAddress) => {
|
|
1431
|
+
toast3.success("Yield harvested successfully");
|
|
1432
|
+
invalidate([["yieldRedirector", redirectorAddress]]);
|
|
1433
|
+
invalidate([["yieldRedirectors"]]);
|
|
1434
|
+
invalidate([["harvests"]]);
|
|
1435
|
+
},
|
|
1436
|
+
onError: (error, ...args) => {
|
|
1437
|
+
toast3.error(error.message || "Failed to harvest yield");
|
|
1438
|
+
opts?.onError?.(error, ...args);
|
|
1439
|
+
},
|
|
1440
|
+
...opts
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
// src/hooks/use-indexer.ts
|
|
1445
|
+
import { useQuery as useQuery5 } from "@tanstack/react-query";
|
|
1446
|
+
function useStrategies(variables = {}, opts) {
|
|
1447
|
+
const { sdk } = useCuratorSDK();
|
|
1448
|
+
return useQuery5({
|
|
1449
|
+
queryKey: ["strategies", variables],
|
|
1450
|
+
queryFn: () => sdk?.indexer?.strategy.query(variables) ?? null,
|
|
1451
|
+
enabled: Boolean(sdk?.indexer) && (opts?.enabled ?? true),
|
|
1452
|
+
refetchInterval: opts?.refetchInterval
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
function useStrategyById(id, opts) {
|
|
1456
|
+
const { sdk } = useCuratorSDK();
|
|
1457
|
+
return useQuery5({
|
|
1458
|
+
queryKey: ["strategy", id],
|
|
1459
|
+
queryFn: () => sdk?.indexer?.strategy.get(id) ?? null,
|
|
1460
|
+
enabled: Boolean(sdk?.indexer) && Boolean(id) && (opts?.enabled ?? true),
|
|
1461
|
+
refetchInterval: opts?.refetchInterval
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1464
|
+
function useDistributions(variables = {}, opts) {
|
|
1465
|
+
const { sdk } = useCuratorSDK();
|
|
1466
|
+
return useQuery5({
|
|
1467
|
+
queryKey: ["distributions", variables],
|
|
1468
|
+
queryFn: () => sdk?.indexer?.distribution.query(variables) ?? null,
|
|
1469
|
+
enabled: Boolean(sdk?.indexer) && (opts?.enabled ?? true),
|
|
1470
|
+
refetchInterval: opts?.refetchInterval
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
function usePayouts(variables = {}, opts) {
|
|
1474
|
+
const { sdk } = useCuratorSDK();
|
|
1475
|
+
return useQuery5({
|
|
1476
|
+
queryKey: ["payouts", variables],
|
|
1477
|
+
queryFn: () => sdk?.indexer?.payout.query(variables) ?? null,
|
|
1478
|
+
enabled: Boolean(sdk?.indexer) && (opts?.enabled ?? true),
|
|
1479
|
+
refetchInterval: opts?.refetchInterval
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1482
|
+
function useDonors(variables = {}, opts) {
|
|
1483
|
+
const { sdk } = useCuratorSDK();
|
|
1484
|
+
return useQuery5({
|
|
1485
|
+
queryKey: ["donors", variables],
|
|
1486
|
+
queryFn: () => sdk?.indexer?.donor.query(variables) ?? null,
|
|
1487
|
+
enabled: Boolean(sdk?.indexer) && (opts?.enabled ?? true),
|
|
1488
|
+
refetchInterval: opts?.refetchInterval
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
function useStrategyBalances(variables = {}, opts) {
|
|
1492
|
+
const { sdk } = useCuratorSDK();
|
|
1493
|
+
return useQuery5({
|
|
1494
|
+
queryKey: ["strategyBalances", variables],
|
|
1495
|
+
queryFn: () => sdk?.indexer?.strategyBalance.query(variables) ?? null,
|
|
1496
|
+
enabled: Boolean(sdk?.indexer) && (opts?.enabled ?? true),
|
|
1497
|
+
refetchInterval: opts?.refetchInterval
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
function useForks(variables = {}, opts) {
|
|
1501
|
+
const { sdk } = useCuratorSDK();
|
|
1502
|
+
return useQuery5({
|
|
1503
|
+
queryKey: ["forks", variables],
|
|
1504
|
+
queryFn: () => sdk?.indexer?.fork.query(variables) ?? null,
|
|
1505
|
+
enabled: Boolean(sdk?.indexer) && (opts?.enabled ?? true),
|
|
1506
|
+
refetchInterval: opts?.refetchInterval
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
function useTrendingStrategies(options, opts) {
|
|
1510
|
+
const { sdk } = useCuratorSDK();
|
|
1511
|
+
return useQuery5({
|
|
1512
|
+
queryKey: ["trending", options],
|
|
1513
|
+
queryFn: () => sdk?.indexer?.trending(options) ?? null,
|
|
1514
|
+
enabled: Boolean(sdk?.indexer) && (opts?.enabled ?? true),
|
|
1515
|
+
refetchInterval: opts?.refetchInterval
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
function useProtocolStats(opts) {
|
|
1519
|
+
const { sdk } = useCuratorSDK();
|
|
1520
|
+
return useQuery5({
|
|
1521
|
+
queryKey: ["stats"],
|
|
1522
|
+
queryFn: () => sdk?.indexer?.stats() ?? null,
|
|
1523
|
+
enabled: Boolean(sdk?.indexer) && (opts?.enabled ?? true),
|
|
1524
|
+
refetchInterval: opts?.refetchInterval
|
|
1525
|
+
});
|
|
1526
|
+
}
|
|
1527
|
+
function useStrategyLineage(strategyAddress, opts) {
|
|
1528
|
+
const { sdk } = useCuratorSDK();
|
|
1529
|
+
return useQuery5({
|
|
1530
|
+
queryKey: ["lineage", strategyAddress],
|
|
1531
|
+
queryFn: () => strategyAddress ? sdk?.indexer?.lineage(strategyAddress) ?? null : null,
|
|
1532
|
+
enabled: Boolean(sdk?.indexer) && Boolean(strategyAddress) && (opts?.enabled ?? true),
|
|
1533
|
+
refetchInterval: opts?.refetchInterval
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
// src/tokens.ts
|
|
1538
|
+
import { zeroAddress as zeroAddress2 } from "viem";
|
|
1539
|
+
import deployments2 from "@curator-studio/contracts/deployments.json";
|
|
1540
|
+
var NATIVE_TOKEN = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
|
|
1541
|
+
var isNativeToken = (token = "") => {
|
|
1542
|
+
return [zeroAddress2, NATIVE_TOKEN].map((t) => t.toLowerCase()).includes(token.toLowerCase());
|
|
1543
|
+
};
|
|
1544
|
+
var ETH_TOKEN = {
|
|
1545
|
+
address: NATIVE_TOKEN,
|
|
1546
|
+
symbol: "ETH",
|
|
1547
|
+
name: "Ether",
|
|
1548
|
+
decimals: 18
|
|
1549
|
+
};
|
|
1550
|
+
var MAINNET_TOKENS = {
|
|
1551
|
+
ETH: ETH_TOKEN,
|
|
1552
|
+
WETH: {
|
|
1553
|
+
address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
|
1554
|
+
symbol: "WETH",
|
|
1555
|
+
name: "Wrapped Ether",
|
|
1556
|
+
decimals: 18
|
|
1557
|
+
},
|
|
1558
|
+
USDC: {
|
|
1559
|
+
address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
1560
|
+
symbol: "USDC",
|
|
1561
|
+
name: "USD Coin",
|
|
1562
|
+
decimals: 6
|
|
1563
|
+
},
|
|
1564
|
+
USDT: {
|
|
1565
|
+
address: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
|
1566
|
+
symbol: "USDT",
|
|
1567
|
+
name: "Tether USD",
|
|
1568
|
+
decimals: 6
|
|
1569
|
+
},
|
|
1570
|
+
DAI: {
|
|
1571
|
+
address: "0x6B175474E89094C44Da98b954EedeAC8cB7A86Bf",
|
|
1572
|
+
symbol: "DAI",
|
|
1573
|
+
name: "Dai Stablecoin",
|
|
1574
|
+
decimals: 18
|
|
1575
|
+
}
|
|
1576
|
+
};
|
|
1577
|
+
var TOKEN_METADATA = {
|
|
1578
|
+
TokenUSDC: { symbol: "USDC", name: "USD Coin", decimals: 6 },
|
|
1579
|
+
TokenWETH: { symbol: "WETH", name: "Wrapped Ether", decimals: 18 },
|
|
1580
|
+
TokenUSDT: { symbol: "USDT", name: "Tether USD", decimals: 6 },
|
|
1581
|
+
TokenDAI: { symbol: "DAI", name: "Dai Stablecoin", decimals: 18 }
|
|
1582
|
+
};
|
|
1583
|
+
function getTestnetTokens(chainId) {
|
|
1584
|
+
const d = deployments2[chainId];
|
|
1585
|
+
if (!d) return {};
|
|
1586
|
+
const tokens = {
|
|
1587
|
+
ETH: ETH_TOKEN
|
|
1588
|
+
// Always include native ETH
|
|
1589
|
+
};
|
|
1590
|
+
for (const [key, meta] of Object.entries(TOKEN_METADATA)) {
|
|
1591
|
+
if (d[key]?.address) {
|
|
1592
|
+
tokens[meta.symbol] = { ...meta, address: d[key].address };
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
return tokens;
|
|
1596
|
+
}
|
|
1597
|
+
var TOKEN_CONFIG = {
|
|
1598
|
+
1: MAINNET_TOKENS,
|
|
1599
|
+
11155111: getTestnetTokens("11155111"),
|
|
1600
|
+
// Sepolia
|
|
1601
|
+
31337: getTestnetTokens("31337")
|
|
1602
|
+
// Local
|
|
1603
|
+
};
|
|
1604
|
+
function getTokens(chainId) {
|
|
1605
|
+
return Object.values(TOKEN_CONFIG[chainId] ?? {});
|
|
1606
|
+
}
|
|
1607
|
+
function getToken(chainId, symbol) {
|
|
1608
|
+
return TOKEN_CONFIG[chainId]?.[symbol];
|
|
1609
|
+
}
|
|
1610
|
+
function getTokenByAddress(chainId, address) {
|
|
1611
|
+
return getTokens(chainId).find(
|
|
1612
|
+
(t) => t.address.toLowerCase() === address.toLowerCase()
|
|
1613
|
+
);
|
|
1614
|
+
}
|
|
1615
|
+
function getTokenAddresses(chainId) {
|
|
1616
|
+
return getTokens(chainId).map((t) => t.address);
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
// src/vaults.ts
|
|
1620
|
+
import "viem";
|
|
1621
|
+
import deployments3 from "@curator-studio/contracts/deployments.json";
|
|
1622
|
+
var VAULT_METADATA = {
|
|
1623
|
+
MockVaultUSDC: { name: "Mock USDC Vault", symbol: "mUSDC" },
|
|
1624
|
+
MockVaultDAI: { name: "Mock DAI Vault", symbol: "mDAI" }
|
|
1625
|
+
};
|
|
1626
|
+
function getTestnetVaults(chainId) {
|
|
1627
|
+
const d = deployments3[chainId];
|
|
1628
|
+
if (!d) return {};
|
|
1629
|
+
const vaults = {};
|
|
1630
|
+
for (const [key, meta] of Object.entries(VAULT_METADATA)) {
|
|
1631
|
+
if (d[key]?.address) {
|
|
1632
|
+
const tokenKey = key.replace("MockVault", "Token");
|
|
1633
|
+
const assetAddress = d[tokenKey]?.address;
|
|
1634
|
+
if (assetAddress) {
|
|
1635
|
+
vaults[key] = {
|
|
1636
|
+
...meta,
|
|
1637
|
+
address: d[key].address,
|
|
1638
|
+
asset: assetAddress
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
return vaults;
|
|
1644
|
+
}
|
|
1645
|
+
var VAULT_CONFIG = {
|
|
1646
|
+
1: {},
|
|
1647
|
+
// Mainnet - no mock vaults
|
|
1648
|
+
11155111: getTestnetVaults("11155111"),
|
|
1649
|
+
// Sepolia
|
|
1650
|
+
31337: getTestnetVaults("31337")
|
|
1651
|
+
// Local
|
|
1652
|
+
};
|
|
1653
|
+
function getVaults(chainId) {
|
|
1654
|
+
return Object.values(VAULT_CONFIG[chainId] ?? {});
|
|
1655
|
+
}
|
|
1656
|
+
function getVaultByAddress(chainId, address) {
|
|
1657
|
+
return getVaults(chainId).find(
|
|
1658
|
+
(v) => v.address.toLowerCase() === address.toLowerCase()
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
function getVaultAddresses(chainId) {
|
|
1662
|
+
return getVaults(chainId).map((v) => v.address);
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
// src/index.ts
|
|
1666
|
+
function createUploadFn(url, token) {
|
|
1667
|
+
return async (metadata) => {
|
|
1668
|
+
const res = await fetch(url, {
|
|
1669
|
+
method: "POST",
|
|
1670
|
+
headers: {
|
|
1671
|
+
"Content-Type": "application/json",
|
|
1672
|
+
"Authorization": `Bearer ${token}`
|
|
1673
|
+
},
|
|
1674
|
+
body: JSON.stringify(metadata)
|
|
1675
|
+
});
|
|
1676
|
+
if (!res.ok) throw new Error(`Upload failed: ${res.statusText}`);
|
|
1677
|
+
const { url: blobUrl } = await res.json();
|
|
1678
|
+
return blobUrl;
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1681
|
+
function getDeployments(chainId) {
|
|
1682
|
+
const d = deployments4[chainId.toString()];
|
|
1683
|
+
if (!d) throw new Error(`No deployments found for chain ${chainId}. Run the relevant deploy script first.`);
|
|
1684
|
+
return d;
|
|
1685
|
+
}
|
|
1686
|
+
function getChain(chainId) {
|
|
1687
|
+
switch (chainId) {
|
|
1688
|
+
case 1:
|
|
1689
|
+
return mainnet2;
|
|
1690
|
+
case 11155111:
|
|
1691
|
+
return sepolia2;
|
|
1692
|
+
case 84532:
|
|
1693
|
+
return baseSepolia2;
|
|
1694
|
+
case 31337:
|
|
1695
|
+
return hardhat2;
|
|
1696
|
+
default:
|
|
1697
|
+
return hardhat2;
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
var CuratorSDK = class {
|
|
1701
|
+
#wallet;
|
|
1702
|
+
#public;
|
|
1703
|
+
#chainId;
|
|
1704
|
+
#deployments;
|
|
1705
|
+
#indexer;
|
|
1706
|
+
#tenant;
|
|
1707
|
+
#uploadMetadata;
|
|
1708
|
+
ens;
|
|
1709
|
+
strategy;
|
|
1710
|
+
warehouse;
|
|
1711
|
+
yieldRedirector;
|
|
1712
|
+
constructor(wallet, options) {
|
|
1713
|
+
const opts = typeof options === "number" ? { chain: options } : options ?? {};
|
|
1714
|
+
const chainId = wallet?.chain?.id ?? opts.chain ?? 31337;
|
|
1715
|
+
const chain = getChain(chainId);
|
|
1716
|
+
const chainConfig = config[chainId];
|
|
1717
|
+
const chainWithEns = chainConfig?.ensUniversalResolver ? {
|
|
1718
|
+
...chain,
|
|
1719
|
+
contracts: {
|
|
1720
|
+
...chain.contracts,
|
|
1721
|
+
ensUniversalResolver: { address: chainConfig.ensUniversalResolver }
|
|
1722
|
+
}
|
|
1723
|
+
} : chain;
|
|
1724
|
+
const indexerUrl = opts.indexerUrl ?? "";
|
|
1725
|
+
this.#wallet = wallet;
|
|
1726
|
+
this.#chainId = chainId;
|
|
1727
|
+
this.#tenant = opts.tenant;
|
|
1728
|
+
this.#uploadMetadata = opts.uploadMetadata;
|
|
1729
|
+
this.#indexer = createIndexer(indexerUrl, opts.tenant);
|
|
1730
|
+
this.#public = createPublicClient({ chain: chainWithEns, transport: http() });
|
|
1731
|
+
this.#deployments = getDeployments(chainId);
|
|
1732
|
+
const tenantCfg = opts.tenant ? getTenantConfig(opts.tenant, chainId) : void 0;
|
|
1733
|
+
const deploymentOverrides = tenantCfg ? {
|
|
1734
|
+
...this.#deployments,
|
|
1735
|
+
...tenantCfg.factory && {
|
|
1736
|
+
StrategyFactory: { ...this.#deployments.StrategyFactory, address: tenantCfg.factory }
|
|
1737
|
+
},
|
|
1738
|
+
...tenantCfg.foreverSubnameRegistrar && {
|
|
1739
|
+
ForeverSubnameRegistrar: {
|
|
1740
|
+
...this.#deployments.ForeverSubnameRegistrar,
|
|
1741
|
+
address: tenantCfg.foreverSubnameRegistrar
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
} : this.#deployments;
|
|
1745
|
+
this.ens = createENSMethods(wallet, this.#public, chainId, deploymentOverrides);
|
|
1746
|
+
this.strategy = createStrategyMethods(wallet, this.#public, deploymentOverrides, this.#uploadMetadata);
|
|
1747
|
+
this.warehouse = createWarehouseMethods(wallet, this.#public, chainId, deploymentOverrides);
|
|
1748
|
+
this.yieldRedirector = createYieldRedirectorMethods(wallet, this.#public, deploymentOverrides);
|
|
1749
|
+
}
|
|
1750
|
+
get wallet() {
|
|
1751
|
+
return this.#wallet;
|
|
1752
|
+
}
|
|
1753
|
+
get publicClient() {
|
|
1754
|
+
return this.#public;
|
|
1755
|
+
}
|
|
1756
|
+
get chainId() {
|
|
1757
|
+
return this.#chainId;
|
|
1758
|
+
}
|
|
1759
|
+
get tenant() {
|
|
1760
|
+
return this.#tenant;
|
|
1761
|
+
}
|
|
1762
|
+
get indexer() {
|
|
1763
|
+
return this.#indexer;
|
|
1764
|
+
}
|
|
1765
|
+
};
|
|
1766
|
+
export {
|
|
1767
|
+
CuratorProvider,
|
|
1768
|
+
CuratorSDK,
|
|
1769
|
+
ENS_DOMAIN,
|
|
1770
|
+
ENS_INVALIDATION_TIMEOUT_MS,
|
|
1771
|
+
ETH_TOKEN,
|
|
1772
|
+
INVALIDATION_TIMEOUT_MS,
|
|
1773
|
+
NATIVE_TOKEN,
|
|
1774
|
+
config,
|
|
1775
|
+
createIndexer,
|
|
1776
|
+
createUploadFn,
|
|
1777
|
+
getAllFactoryAddresses,
|
|
1778
|
+
getFactoryTenantMap,
|
|
1779
|
+
getTenantConfig,
|
|
1780
|
+
getToken,
|
|
1781
|
+
getTokenAddresses,
|
|
1782
|
+
getTokenByAddress,
|
|
1783
|
+
getTokens,
|
|
1784
|
+
getVaultAddresses,
|
|
1785
|
+
getVaultByAddress,
|
|
1786
|
+
getVaults,
|
|
1787
|
+
isNativeToken,
|
|
1788
|
+
tenants,
|
|
1789
|
+
useCreateStrategy,
|
|
1790
|
+
useCreateYieldRedirector,
|
|
1791
|
+
useCuratorSDK,
|
|
1792
|
+
useDistributeStrategy,
|
|
1793
|
+
useDistributions,
|
|
1794
|
+
useDonors,
|
|
1795
|
+
useENSAvailable,
|
|
1796
|
+
useENSGetAddress,
|
|
1797
|
+
useForks,
|
|
1798
|
+
useHarvestYield,
|
|
1799
|
+
useHarvests,
|
|
1800
|
+
useInvalidate,
|
|
1801
|
+
useInvalidateENS,
|
|
1802
|
+
usePayouts,
|
|
1803
|
+
useProtocolStats,
|
|
1804
|
+
useRebalanceStrategy,
|
|
1805
|
+
useSetENSName,
|
|
1806
|
+
useStrategies,
|
|
1807
|
+
useStrategyBalance,
|
|
1808
|
+
useStrategyBalances,
|
|
1809
|
+
useStrategyById,
|
|
1810
|
+
useStrategyData,
|
|
1811
|
+
useStrategyLineage,
|
|
1812
|
+
useTrendingStrategies,
|
|
1813
|
+
useWarehouseBalance,
|
|
1814
|
+
useWarehouseBalances,
|
|
1815
|
+
useWithdrawFromWarehouse,
|
|
1816
|
+
useYieldRedirectorById,
|
|
1817
|
+
useYieldRedirectors,
|
|
1818
|
+
writeAndParse,
|
|
1819
|
+
writeAndWait
|
|
1820
|
+
};
|