@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/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
+ };