@tbookdev/vault-react 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,810 @@
1
+ import React2, { createContext, useMemo, useContext, useState, useCallback } from 'react';
2
+ import { useConnection, useWallet } from '@solana/wallet-adapter-react';
3
+ import { RpcTimeoutError, VaultPausedError, SharePriceUnavailableError, VaultSdkError, TBookVault, confirmTransactionWithRetry, explorerUrl } from '@tbookdev/vault-sdk';
4
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
5
+ import { useQuery, useInfiniteQuery, useMutation, useQueryClient } from '@tanstack/react-query';
6
+ import { PublicKey } from '@solana/web3.js';
7
+ import { getAssociatedTokenAddress } from '@solana/spl-token';
8
+
9
+ // src/provider.tsx
10
+ var TBookVaultContext = createContext(null);
11
+ function TBookVaultProvider({
12
+ network,
13
+ apiKey,
14
+ rpcUrl,
15
+ children
16
+ }) {
17
+ const { connection } = useConnection();
18
+ const vault = useMemo(() => {
19
+ return new TBookVault({
20
+ network,
21
+ apiKey,
22
+ // Use the wallet adapter's RPC URL if no explicit rpcUrl provided
23
+ rpcUrl: rpcUrl ?? connection.rpcEndpoint
24
+ });
25
+ }, [network, apiKey, rpcUrl, connection.rpcEndpoint]);
26
+ const value = useMemo(
27
+ () => ({ vault, network }),
28
+ [vault, network]
29
+ );
30
+ return /* @__PURE__ */ jsx(TBookVaultContext.Provider, { value, children });
31
+ }
32
+ function useTBookVault() {
33
+ const ctx = useContext(TBookVaultContext);
34
+ if (!ctx) {
35
+ throw new Error(
36
+ 'useTBookVault must be used within a <TBookVaultProvider>. Wrap your app with <TBookVaultProvider network="devnet"> to fix this.'
37
+ );
38
+ }
39
+ return ctx;
40
+ }
41
+ function useVaultInfo(vaultId) {
42
+ const { vault } = useTBookVault();
43
+ return useQuery({
44
+ queryKey: ["tbook", "vault", vaultId],
45
+ queryFn: () => vault.getVaultInfo(vaultId),
46
+ staleTime: 3e4,
47
+ refetchInterval: 6e4
48
+ });
49
+ }
50
+ function useVault(vaultId) {
51
+ const { data, ...rest } = useVaultInfo(vaultId);
52
+ return { vault: data?.vault ?? null, ...rest };
53
+ }
54
+ function useCurrentDepositEpoch(vaultId) {
55
+ const { data, ...rest } = useVaultInfo(vaultId);
56
+ return { epoch: data?.depositEpoch ?? null, ...rest };
57
+ }
58
+ function useCurrentRedeemEpoch(vaultId) {
59
+ const { data, ...rest } = useVaultInfo(vaultId);
60
+ return { epoch: data?.redeemEpoch ?? null, ...rest };
61
+ }
62
+ function useVaultUser(vaultId) {
63
+ const { vault } = useTBookVault();
64
+ const { publicKey } = useWallet();
65
+ return useQuery({
66
+ queryKey: ["tbook", "user", publicKey?.toString(), vaultId],
67
+ queryFn: () => vault.getUserAccount(publicKey, vaultId),
68
+ enabled: !!publicKey,
69
+ staleTime: 3e4,
70
+ refetchInterval: 6e4
71
+ });
72
+ }
73
+ function useSharePrice() {
74
+ const { vault } = useTBookVault();
75
+ return useQuery({
76
+ queryKey: ["tbook", "share-price"],
77
+ queryFn: () => vault.getSharePrice(),
78
+ staleTime: 6e4,
79
+ refetchInterval: 3e5,
80
+ // 5 minutes
81
+ retry: 2
82
+ });
83
+ }
84
+ async function fetchUsdcBalance(connection, publicKey, usdcMint) {
85
+ try {
86
+ const ata = await getAssociatedTokenAddress(usdcMint, publicKey);
87
+ const balance = await connection.getTokenAccountBalance(ata);
88
+ return balance.value.uiAmount ?? 0;
89
+ } catch {
90
+ return 0;
91
+ }
92
+ }
93
+ function useUsdcBalance() {
94
+ const { connection } = useConnection();
95
+ const { publicKey } = useWallet();
96
+ const { vault } = useVault();
97
+ const usdcMint = vault?.usdcMint ?? null;
98
+ return useQuery({
99
+ queryKey: ["tbook", "usdc-balance", publicKey?.toString()],
100
+ queryFn: () => fetchUsdcBalance(
101
+ connection,
102
+ publicKey,
103
+ usdcMint instanceof PublicKey ? usdcMint : new PublicKey(usdcMint)
104
+ ),
105
+ enabled: !!publicKey && !!usdcMint,
106
+ staleTime: 3e4,
107
+ refetchInterval: 6e4
108
+ });
109
+ }
110
+ function useTransactionHistory(options) {
111
+ const { vault } = useTBookVault();
112
+ const { publicKey } = useWallet();
113
+ const limit = options?.limit ?? 20;
114
+ const vaultId = options?.vaultId;
115
+ return useInfiniteQuery({
116
+ queryKey: ["tbook", "history", publicKey?.toString(), vaultId],
117
+ queryFn: ({ pageParam }) => vault.getTransactionHistory(
118
+ publicKey,
119
+ { limit, before: pageParam },
120
+ vaultId
121
+ ),
122
+ initialPageParam: void 0,
123
+ getNextPageParam: (lastPage) => lastPage.length > 0 ? lastPage[lastPage.length - 1].signature : void 0,
124
+ enabled: !!publicKey,
125
+ staleTime: 6e4
126
+ });
127
+ }
128
+ function useRefreshVaultData() {
129
+ const queryClient = useQueryClient();
130
+ const { publicKey } = useWallet();
131
+ return () => {
132
+ queryClient.invalidateQueries({ queryKey: ["tbook", "vault"] });
133
+ queryClient.invalidateQueries({ queryKey: ["tbook", "share-price"] });
134
+ if (publicKey) {
135
+ queryClient.invalidateQueries({
136
+ queryKey: ["tbook", "user", publicKey.toString()]
137
+ });
138
+ queryClient.invalidateQueries({
139
+ queryKey: ["tbook", "usdc-balance", publicKey.toString()]
140
+ });
141
+ }
142
+ };
143
+ }
144
+ function useDeposit(vaultId) {
145
+ const { vault } = useTBookVault();
146
+ const { publicKey, sendTransaction } = useWallet();
147
+ const { connection } = useConnection();
148
+ const refreshData = useRefreshVaultData();
149
+ return useMutation({
150
+ mutationFn: async (amountUsdc) => {
151
+ if (!publicKey) throw new Error("Wallet not connected");
152
+ const { transaction, blockhash, lastValidBlockHeight } = await vault.buildDeposit({ user: publicKey, amountUsdc }, vaultId);
153
+ const signature = await sendTransaction(transaction, connection);
154
+ await confirmTransactionWithRetry({
155
+ connection,
156
+ signature,
157
+ blockhash,
158
+ lastValidBlockHeight
159
+ });
160
+ return signature;
161
+ },
162
+ onSuccess: () => {
163
+ refreshData();
164
+ }
165
+ });
166
+ }
167
+ function useRedeem(vaultId) {
168
+ const { vault } = useTBookVault();
169
+ const { publicKey, sendTransaction } = useWallet();
170
+ const { connection } = useConnection();
171
+ const refreshData = useRefreshVaultData();
172
+ return useMutation({
173
+ mutationFn: async (shares) => {
174
+ if (!publicKey) throw new Error("Wallet not connected");
175
+ const { transaction, blockhash, lastValidBlockHeight } = await vault.buildRedeem({ user: publicKey, shares }, vaultId);
176
+ const signature = await sendTransaction(transaction, connection);
177
+ await confirmTransactionWithRetry({
178
+ connection,
179
+ signature,
180
+ blockhash,
181
+ lastValidBlockHeight
182
+ });
183
+ return signature;
184
+ },
185
+ onSuccess: () => {
186
+ refreshData();
187
+ }
188
+ });
189
+ }
190
+ function useClaim(vaultId) {
191
+ const { vault } = useTBookVault();
192
+ const { publicKey, sendTransaction } = useWallet();
193
+ const { connection } = useConnection();
194
+ const refreshData = useRefreshVaultData();
195
+ return useMutation({
196
+ mutationFn: async () => {
197
+ if (!publicKey) throw new Error("Wallet not connected");
198
+ const { transaction, blockhash, lastValidBlockHeight } = await vault.buildClaim({ user: publicKey }, vaultId);
199
+ const signature = await sendTransaction(transaction, connection);
200
+ await confirmTransactionWithRetry({
201
+ connection,
202
+ signature,
203
+ blockhash,
204
+ lastValidBlockHeight
205
+ });
206
+ return signature;
207
+ },
208
+ onSuccess: () => {
209
+ refreshData();
210
+ }
211
+ });
212
+ }
213
+ function useCancelDeposit(vaultId) {
214
+ const { vault } = useTBookVault();
215
+ const { publicKey, sendTransaction } = useWallet();
216
+ const { connection } = useConnection();
217
+ const refreshData = useRefreshVaultData();
218
+ return useMutation({
219
+ mutationFn: async () => {
220
+ if (!publicKey) throw new Error("Wallet not connected");
221
+ const { transaction, blockhash, lastValidBlockHeight } = await vault.buildCancelDeposit({ user: publicKey }, vaultId);
222
+ const signature = await sendTransaction(transaction, connection);
223
+ await confirmTransactionWithRetry({
224
+ connection,
225
+ signature,
226
+ blockhash,
227
+ lastValidBlockHeight
228
+ });
229
+ return signature;
230
+ },
231
+ onSuccess: () => {
232
+ refreshData();
233
+ }
234
+ });
235
+ }
236
+
237
+ // src/components/styles.ts
238
+ var baseStyles = {
239
+ container: {
240
+ fontFamily: "var(--tbook-font, inherit)",
241
+ color: "var(--tbook-text, #111827)",
242
+ background: "var(--tbook-bg, #ffffff)",
243
+ border: "1px solid var(--tbook-border, #e5e7eb)",
244
+ borderRadius: "var(--tbook-radius, 8px)",
245
+ padding: "16px"
246
+ },
247
+ label: {
248
+ fontSize: "12px",
249
+ fontWeight: 500,
250
+ color: "var(--tbook-text-secondary, #6b7280)",
251
+ textTransform: "uppercase",
252
+ letterSpacing: "0.05em",
253
+ marginBottom: "4px"
254
+ },
255
+ value: {
256
+ fontSize: "24px",
257
+ fontWeight: 700,
258
+ color: "var(--tbook-text, #111827)",
259
+ lineHeight: 1.2
260
+ },
261
+ smallValue: {
262
+ fontSize: "14px",
263
+ fontWeight: 500,
264
+ color: "var(--tbook-text, #111827)"
265
+ },
266
+ button: {
267
+ display: "inline-flex",
268
+ alignItems: "center",
269
+ justifyContent: "center",
270
+ padding: "10px 20px",
271
+ fontSize: "14px",
272
+ fontWeight: 600,
273
+ color: "#ffffff",
274
+ background: "var(--tbook-primary, #6366f1)",
275
+ border: "none",
276
+ borderRadius: "var(--tbook-radius, 8px)",
277
+ cursor: "pointer",
278
+ transition: "background 0.15s",
279
+ width: "100%"
280
+ },
281
+ buttonDisabled: {
282
+ opacity: 0.5,
283
+ cursor: "not-allowed"
284
+ },
285
+ input: {
286
+ width: "100%",
287
+ padding: "10px 12px",
288
+ fontSize: "16px",
289
+ border: "1px solid var(--tbook-border, #e5e7eb)",
290
+ borderRadius: "var(--tbook-radius, 8px)",
291
+ background: "var(--tbook-bg, #ffffff)",
292
+ color: "var(--tbook-text, #111827)",
293
+ outline: "none",
294
+ boxSizing: "border-box"
295
+ },
296
+ badge: {
297
+ display: "inline-flex",
298
+ alignItems: "center",
299
+ gap: "4px",
300
+ padding: "4px 10px",
301
+ fontSize: "13px",
302
+ fontWeight: 600,
303
+ borderRadius: "9999px"
304
+ },
305
+ row: {
306
+ display: "flex",
307
+ justifyContent: "space-between",
308
+ alignItems: "center",
309
+ padding: "8px 0"
310
+ },
311
+ divider: {
312
+ height: "1px",
313
+ background: "var(--tbook-border, #e5e7eb)",
314
+ margin: "12px 0"
315
+ }
316
+ };
317
+ function DepositWidget({
318
+ onSuccess,
319
+ onError,
320
+ minAmount,
321
+ maxAmount,
322
+ vaultId,
323
+ className,
324
+ style
325
+ }) {
326
+ const { connected } = useWallet();
327
+ const { vault } = useVault(vaultId);
328
+ const { data: price } = useSharePrice();
329
+ const { data: usdcBalance } = useUsdcBalance();
330
+ const deposit = useDeposit(vaultId);
331
+ const [amount, setAmount] = useState("");
332
+ const [error, setError] = useState(null);
333
+ const effectiveMin = minAmount ?? vault?.minDeposit ?? 1;
334
+ const effectiveMax = maxAmount ?? vault?.maxEpochDeposit ?? Infinity;
335
+ const amountNum = Number(amount) || 0;
336
+ const estimatedShares = price?.priceNum ? amountNum / price.priceNum : 0;
337
+ const isPaused = vault?.paused ?? false;
338
+ const validate = useCallback(() => {
339
+ if (!amount || amountNum <= 0) return "Enter an amount";
340
+ if (amountNum < effectiveMin) return `Minimum deposit is ${effectiveMin} USDC`;
341
+ if (amountNum > effectiveMax) return `Maximum deposit is ${effectiveMax} USDC`;
342
+ if (usdcBalance !== void 0 && amountNum > usdcBalance) return "Insufficient USDC balance";
343
+ if (isPaused) return "Vault is paused";
344
+ return null;
345
+ }, [amount, amountNum, effectiveMin, effectiveMax, usdcBalance, isPaused]);
346
+ const handleDeposit = async () => {
347
+ const err = validate();
348
+ if (err) {
349
+ setError(err);
350
+ return;
351
+ }
352
+ setError(null);
353
+ try {
354
+ const sig = await deposit.mutateAsync(amountNum);
355
+ setAmount("");
356
+ onSuccess?.(sig);
357
+ } catch (e) {
358
+ const err2 = e instanceof Error ? e : new Error(String(e));
359
+ setError(err2.message);
360
+ onError?.(err2);
361
+ }
362
+ };
363
+ const handleMax = () => {
364
+ if (usdcBalance !== void 0) {
365
+ const max = Math.min(usdcBalance, effectiveMax);
366
+ setAmount(String(Math.floor(max * 100) / 100));
367
+ }
368
+ };
369
+ if (!connected) {
370
+ return /* @__PURE__ */ jsx("div", { className, style: { ...baseStyles.container, textAlign: "center", ...style }, children: /* @__PURE__ */ jsx("p", { style: { ...baseStyles.label, marginBottom: 8 }, children: "Connect wallet to deposit" }) });
371
+ }
372
+ return /* @__PURE__ */ jsxs("div", { className, style: { ...baseStyles.container, ...style }, children: [
373
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }, children: [
374
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 16, fontWeight: 600 }, children: "Deposit USDC" }),
375
+ price && /* @__PURE__ */ jsxs("span", { style: { ...baseStyles.badge, background: "var(--tbook-success, #10b981)", color: "#fff", fontSize: 12 }, children: [
376
+ price.apy,
377
+ "% APY"
378
+ ] })
379
+ ] }),
380
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative", marginBottom: 8 }, children: [
381
+ /* @__PURE__ */ jsx(
382
+ "input",
383
+ {
384
+ type: "number",
385
+ placeholder: "0.00",
386
+ value: amount,
387
+ onChange: (e) => {
388
+ setAmount(e.target.value);
389
+ setError(null);
390
+ },
391
+ style: baseStyles.input,
392
+ min: 0,
393
+ step: "0.01"
394
+ }
395
+ ),
396
+ usdcBalance !== void 0 && /* @__PURE__ */ jsx(
397
+ "button",
398
+ {
399
+ onClick: handleMax,
400
+ style: {
401
+ position: "absolute",
402
+ right: 8,
403
+ top: "50%",
404
+ transform: "translateY(-50%)",
405
+ padding: "2px 8px",
406
+ fontSize: 11,
407
+ fontWeight: 600,
408
+ background: "var(--tbook-bg-secondary, #f9fafb)",
409
+ border: "1px solid var(--tbook-border, #e5e7eb)",
410
+ borderRadius: 4,
411
+ cursor: "pointer",
412
+ color: "var(--tbook-primary, #6366f1)"
413
+ },
414
+ children: "MAX"
415
+ }
416
+ )
417
+ ] }),
418
+ /* @__PURE__ */ jsxs("div", { style: { ...baseStyles.row, padding: "4px 0", marginBottom: 12 }, children: [
419
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: 12, color: "var(--tbook-text-secondary, #6b7280)" }, children: [
420
+ "Balance: ",
421
+ usdcBalance !== void 0 ? `${usdcBalance.toFixed(2)} USDC` : "\u2014"
422
+ ] }),
423
+ amountNum > 0 && price?.priceNum && /* @__PURE__ */ jsxs("span", { style: { fontSize: 12, color: "var(--tbook-text-secondary, #6b7280)" }, children: [
424
+ "\u2248 ",
425
+ estimatedShares.toFixed(4),
426
+ " shares"
427
+ ] })
428
+ ] }),
429
+ (error || deposit.error) && /* @__PURE__ */ jsx("div", { style: { color: "var(--tbook-error, #ef4444)", fontSize: 13, marginBottom: 8 }, children: error || deposit.error?.message }),
430
+ /* @__PURE__ */ jsx(
431
+ "button",
432
+ {
433
+ onClick: handleDeposit,
434
+ disabled: deposit.isPending || isPaused || !amount,
435
+ style: {
436
+ ...baseStyles.button,
437
+ ...deposit.isPending || isPaused || !amount ? baseStyles.buttonDisabled : {}
438
+ },
439
+ children: deposit.isPending ? "Confirming..." : isPaused ? "Vault Paused" : amountNum > 0 ? `Deposit ${amountNum} USDC` : "Deposit"
440
+ }
441
+ ),
442
+ deposit.isSuccess && /* @__PURE__ */ jsx("div", { style: { color: "var(--tbook-success, #10b981)", fontSize: 13, marginTop: 8, textAlign: "center" }, children: "Deposit submitted! Settlement typically takes 1-2 days." })
443
+ ] });
444
+ }
445
+ function PortfolioCard({ showClaim, onClaim, vaultId, className, style }) {
446
+ const { connected } = useWallet();
447
+ const { data: user, isLoading } = useVaultUser(vaultId);
448
+ const { data: price } = useSharePrice();
449
+ const claim = useClaim(vaultId);
450
+ if (!connected) {
451
+ return /* @__PURE__ */ jsx("div", { className, style: { ...baseStyles.container, textAlign: "center", ...style }, children: /* @__PURE__ */ jsx("p", { style: baseStyles.label, children: "Connect wallet to view portfolio" }) });
452
+ }
453
+ if (isLoading) {
454
+ return /* @__PURE__ */ jsx("div", { className, style: { ...baseStyles.container, ...style }, children: /* @__PURE__ */ jsx("p", { style: baseStyles.label, children: "Loading..." }) });
455
+ }
456
+ if (!user) {
457
+ return /* @__PURE__ */ jsx("div", { className, style: { ...baseStyles.container, ...style }, children: /* @__PURE__ */ jsx("p", { style: baseStyles.label, children: "No vault account found" }) });
458
+ }
459
+ const portfolioValue = (user.effectiveShares ?? user.shares) * (price?.priceNum ?? 1);
460
+ const hasClaimable = (user.effectiveClaimableUsdc ?? user.claimableUsdc) > 0;
461
+ const handleClaim = async () => {
462
+ const sig = await claim.mutateAsync(void 0);
463
+ onClaim?.(sig);
464
+ };
465
+ return /* @__PURE__ */ jsxs("div", { className, style: { ...baseStyles.container, ...style }, children: [
466
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: 16 }, children: [
467
+ /* @__PURE__ */ jsx("div", { style: baseStyles.label, children: "Portfolio Value" }),
468
+ /* @__PURE__ */ jsxs("div", { style: baseStyles.value, children: [
469
+ "$",
470
+ portfolioValue.toFixed(2)
471
+ ] })
472
+ ] }),
473
+ /* @__PURE__ */ jsx("div", { style: baseStyles.divider }),
474
+ /* @__PURE__ */ jsxs("div", { style: baseStyles.row, children: [
475
+ /* @__PURE__ */ jsx("span", { style: baseStyles.label, children: "Shares" }),
476
+ /* @__PURE__ */ jsx("span", { style: baseStyles.smallValue, children: (user.effectiveShares ?? user.shares).toFixed(6) })
477
+ ] }),
478
+ price && /* @__PURE__ */ jsxs("div", { style: baseStyles.row, children: [
479
+ /* @__PURE__ */ jsx("span", { style: baseStyles.label, children: "Share Price" }),
480
+ /* @__PURE__ */ jsxs("span", { style: baseStyles.smallValue, children: [
481
+ "$",
482
+ Number(price.price).toFixed(6)
483
+ ] })
484
+ ] }),
485
+ user.pendingDepositUsdc > 0 && /* @__PURE__ */ jsxs("div", { style: baseStyles.row, children: [
486
+ /* @__PURE__ */ jsx("span", { style: baseStyles.label, children: "Pending Deposit" }),
487
+ /* @__PURE__ */ jsxs("span", { style: { ...baseStyles.smallValue, color: "var(--tbook-primary, #6366f1)" }, children: [
488
+ user.pendingDepositUsdc.toFixed(2),
489
+ " USDC"
490
+ ] })
491
+ ] }),
492
+ user.pendingRedeemShares > 0 && /* @__PURE__ */ jsxs("div", { style: baseStyles.row, children: [
493
+ /* @__PURE__ */ jsx("span", { style: baseStyles.label, children: "Pending Redeem" }),
494
+ /* @__PURE__ */ jsxs("span", { style: { ...baseStyles.smallValue, color: "var(--tbook-primary, #6366f1)" }, children: [
495
+ user.pendingRedeemShares.toFixed(6),
496
+ " shares"
497
+ ] })
498
+ ] }),
499
+ hasClaimable && /* @__PURE__ */ jsxs(Fragment, { children: [
500
+ /* @__PURE__ */ jsxs("div", { style: baseStyles.row, children: [
501
+ /* @__PURE__ */ jsx("span", { style: baseStyles.label, children: "Claimable" }),
502
+ /* @__PURE__ */ jsxs("span", { style: { ...baseStyles.smallValue, color: "var(--tbook-success, #10b981)" }, children: [
503
+ (user.effectiveClaimableUsdc ?? user.claimableUsdc).toFixed(6),
504
+ " USDC"
505
+ ] })
506
+ ] }),
507
+ showClaim && /* @__PURE__ */ jsx(
508
+ "button",
509
+ {
510
+ onClick: handleClaim,
511
+ disabled: claim.isPending,
512
+ style: {
513
+ ...baseStyles.button,
514
+ marginTop: 8,
515
+ ...claim.isPending ? baseStyles.buttonDisabled : {}
516
+ },
517
+ children: claim.isPending ? "Claiming..." : "Claim USDC"
518
+ }
519
+ )
520
+ ] })
521
+ ] });
522
+ }
523
+ function YieldBadge({ showPrice, className, style }) {
524
+ const { data: price, isLoading } = useSharePrice();
525
+ if (isLoading || !price) {
526
+ return /* @__PURE__ */ jsx("span", { className, style: { ...baseStyles.badge, background: "#f3f4f6", color: "#9ca3af", ...style }, children: "Loading..." });
527
+ }
528
+ return /* @__PURE__ */ jsxs(
529
+ "span",
530
+ {
531
+ className,
532
+ style: {
533
+ ...baseStyles.badge,
534
+ background: "var(--tbook-success, #10b981)",
535
+ color: "#ffffff",
536
+ ...style
537
+ },
538
+ children: [
539
+ price.apy,
540
+ "% APY",
541
+ showPrice && /* @__PURE__ */ jsxs("span", { style: { opacity: 0.85 }, children: [
542
+ " \xB7 $",
543
+ Number(price.price).toFixed(3)
544
+ ] })
545
+ ]
546
+ }
547
+ );
548
+ }
549
+ var STATUS_COLORS = {
550
+ Open: { bg: "#dcfce7", text: "#166534" },
551
+ Frozen: { bg: "#fef3c7", text: "#92400e" },
552
+ Bridging: { bg: "#dbeafe", text: "#1e40af" },
553
+ Settled: { bg: "#f3e8ff", text: "#6b21a8" },
554
+ RolledBack: { bg: "#fecaca", text: "#991b1b" }
555
+ };
556
+ function EpochStatusBadge({ type, vaultId, className, style }) {
557
+ const { data: vaultResult, isLoading } = useVaultInfo(vaultId);
558
+ if (isLoading || !vaultResult) {
559
+ return /* @__PURE__ */ jsx("span", { className, style: { ...baseStyles.badge, background: "#f3f4f6", color: "#9ca3af", ...style }, children: "Loading..." });
560
+ }
561
+ const epoch = type === "deposit" ? vaultResult.depositEpoch : vaultResult.redeemEpoch;
562
+ if (!epoch) return null;
563
+ const status = epoch.status;
564
+ const colors = STATUS_COLORS[status] ?? { bg: "#f3f4f6", text: "#374151" };
565
+ const label = type === "deposit" ? "Deposit" : "Redeem";
566
+ return /* @__PURE__ */ jsxs(
567
+ "span",
568
+ {
569
+ className,
570
+ style: {
571
+ ...baseStyles.badge,
572
+ background: colors.bg,
573
+ color: colors.text,
574
+ ...style
575
+ },
576
+ children: [
577
+ label,
578
+ " #",
579
+ epoch.epoch,
580
+ " \u2014 ",
581
+ status
582
+ ]
583
+ }
584
+ );
585
+ }
586
+ function getFriendlyMessage(error) {
587
+ if (error instanceof RpcTimeoutError) {
588
+ return "Network is slow. Please try again.";
589
+ }
590
+ if (error instanceof VaultPausedError) {
591
+ return "The vault is temporarily paused.";
592
+ }
593
+ if (error instanceof SharePriceUnavailableError) {
594
+ return "Price data is currently unavailable.";
595
+ }
596
+ if (error instanceof VaultSdkError) {
597
+ return error.message;
598
+ }
599
+ return "Something went wrong. Please try again.";
600
+ }
601
+ var VaultErrorBoundary = class extends React2.Component {
602
+ constructor(props) {
603
+ super(props);
604
+ this.state = { error: null };
605
+ }
606
+ static getDerivedStateFromError(error) {
607
+ return { error };
608
+ }
609
+ componentDidCatch(error, errorInfo) {
610
+ this.props.onError?.(error, errorInfo);
611
+ }
612
+ /** Reset the error state so children are re-rendered. */
613
+ resetErrorBoundary = () => {
614
+ this.setState({ error: null });
615
+ };
616
+ render() {
617
+ const { error } = this.state;
618
+ const { children, fallback } = this.props;
619
+ if (error === null) {
620
+ return children;
621
+ }
622
+ if (typeof fallback === "function") {
623
+ return fallback(error, this.resetErrorBoundary);
624
+ }
625
+ if (fallback !== void 0) {
626
+ return fallback;
627
+ }
628
+ const message = getFriendlyMessage(error);
629
+ return /* @__PURE__ */ jsxs(
630
+ "div",
631
+ {
632
+ role: "alert",
633
+ style: {
634
+ padding: 16,
635
+ borderRadius: 8,
636
+ border: "1px solid var(--tbook-error, #ef4444)",
637
+ background: "var(--tbook-bg-error, #fef2f2)",
638
+ color: "var(--tbook-error, #ef4444)",
639
+ fontSize: 14,
640
+ textAlign: "center"
641
+ },
642
+ children: [
643
+ /* @__PURE__ */ jsx("p", { style: { margin: "0 0 12px" }, children: message }),
644
+ /* @__PURE__ */ jsx(
645
+ "button",
646
+ {
647
+ onClick: this.resetErrorBoundary,
648
+ style: {
649
+ padding: "6px 16px",
650
+ fontSize: 13,
651
+ fontWeight: 600,
652
+ borderRadius: 6,
653
+ border: "1px solid var(--tbook-error, #ef4444)",
654
+ background: "transparent",
655
+ color: "var(--tbook-error, #ef4444)",
656
+ cursor: "pointer"
657
+ },
658
+ children: "Retry"
659
+ }
660
+ )
661
+ ]
662
+ }
663
+ );
664
+ }
665
+ };
666
+ var TYPE_ICONS = {
667
+ deposit: "\u2B06",
668
+ // up arrow
669
+ redeem: "\u2B07",
670
+ // down arrow
671
+ claim: "\u2705",
672
+ // checkmark
673
+ cancel_deposit: "\u274C",
674
+ // cross
675
+ unknown: "\u2753"
676
+ // question mark
677
+ };
678
+ var TYPE_LABELS = {
679
+ deposit: "Deposit",
680
+ redeem: "Redeem",
681
+ claim: "Claim",
682
+ cancel_deposit: "Cancel Deposit",
683
+ unknown: "Unknown"
684
+ };
685
+ function formatTimestamp(ts) {
686
+ if (ts === 0) return "Unknown time";
687
+ return new Date(ts * 1e3).toLocaleString();
688
+ }
689
+ function shortenSig(sig) {
690
+ if (sig.length <= 16) return sig;
691
+ return `${sig.slice(0, 8)}...${sig.slice(-8)}`;
692
+ }
693
+ function TransactionHistory({
694
+ limit,
695
+ vaultId,
696
+ className,
697
+ style
698
+ }) {
699
+ const { connected } = useWallet();
700
+ const { network } = useTBookVault();
701
+ const {
702
+ data,
703
+ isLoading,
704
+ isError,
705
+ error,
706
+ hasNextPage,
707
+ fetchNextPage,
708
+ isFetchingNextPage
709
+ } = useTransactionHistory({ limit, vaultId });
710
+ if (!connected) {
711
+ return /* @__PURE__ */ jsx("div", { className, style: { ...baseStyles.container, textAlign: "center", ...style }, children: /* @__PURE__ */ jsx("p", { style: baseStyles.label, children: "Connect wallet to view transaction history" }) });
712
+ }
713
+ if (isLoading) {
714
+ return /* @__PURE__ */ jsx("div", { className, style: { ...baseStyles.container, ...style }, children: /* @__PURE__ */ jsx("p", { style: baseStyles.label, children: "Loading transaction history..." }) });
715
+ }
716
+ if (isError) {
717
+ return /* @__PURE__ */ jsx("div", { className, style: { ...baseStyles.container, ...style }, children: /* @__PURE__ */ jsxs("p", { style: { ...baseStyles.label, color: "var(--tbook-error, #ef4444)" }, children: [
718
+ "Failed to load history: ",
719
+ error instanceof Error ? error.message : "Unknown error"
720
+ ] }) });
721
+ }
722
+ const transactions = data?.pages.flat() ?? [];
723
+ if (transactions.length === 0) {
724
+ return /* @__PURE__ */ jsx("div", { className, style: { ...baseStyles.container, textAlign: "center", ...style }, children: /* @__PURE__ */ jsx("p", { style: baseStyles.label, children: "No transactions found" }) });
725
+ }
726
+ return /* @__PURE__ */ jsxs("div", { className, style: { ...baseStyles.container, ...style }, children: [
727
+ /* @__PURE__ */ jsx("div", { style: { marginBottom: 12 }, children: /* @__PURE__ */ jsx("div", { style: baseStyles.label, children: "Transaction History" }) }),
728
+ transactions.map((tx) => /* @__PURE__ */ jsxs("div", { style: txRowStyle, children: [
729
+ /* @__PURE__ */ jsx("span", { style: txIconStyle, title: TYPE_LABELS[tx.type], children: TYPE_ICONS[tx.type] }),
730
+ /* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
731
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
732
+ /* @__PURE__ */ jsx("span", { style: baseStyles.smallValue, children: TYPE_LABELS[tx.type] }),
733
+ /* @__PURE__ */ jsx(
734
+ "span",
735
+ {
736
+ style: {
737
+ ...baseStyles.label,
738
+ fontSize: "11px",
739
+ marginBottom: 0,
740
+ textTransform: "none"
741
+ },
742
+ children: formatTimestamp(tx.timestamp)
743
+ }
744
+ )
745
+ ] }),
746
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 2 }, children: [
747
+ /* @__PURE__ */ jsx(
748
+ "a",
749
+ {
750
+ href: explorerUrl("tx", tx.signature, network),
751
+ target: "_blank",
752
+ rel: "noopener noreferrer",
753
+ style: txLinkStyle,
754
+ title: tx.signature,
755
+ children: shortenSig(tx.signature)
756
+ }
757
+ ),
758
+ /* @__PURE__ */ jsx(
759
+ "span",
760
+ {
761
+ style: {
762
+ fontSize: "11px",
763
+ fontWeight: 600,
764
+ color: tx.success ? "var(--tbook-success, #10b981)" : "var(--tbook-error, #ef4444)"
765
+ },
766
+ children: tx.success ? "Success" : "Failed"
767
+ }
768
+ )
769
+ ] })
770
+ ] })
771
+ ] }, tx.signature)),
772
+ hasNextPage && /* @__PURE__ */ jsx(
773
+ "button",
774
+ {
775
+ onClick: () => fetchNextPage(),
776
+ disabled: isFetchingNextPage,
777
+ style: {
778
+ ...baseStyles.button,
779
+ marginTop: 12,
780
+ ...isFetchingNextPage ? baseStyles.buttonDisabled : {}
781
+ },
782
+ children: isFetchingNextPage ? "Loading..." : "Load More"
783
+ }
784
+ )
785
+ ] });
786
+ }
787
+ var txRowStyle = {
788
+ display: "flex",
789
+ alignItems: "flex-start",
790
+ gap: "12px",
791
+ padding: "10px 0",
792
+ borderBottom: "1px solid var(--tbook-border, #e5e7eb)"
793
+ };
794
+ var txIconStyle = {
795
+ fontSize: "18px",
796
+ lineHeight: "24px",
797
+ flexShrink: 0,
798
+ width: "24px",
799
+ textAlign: "center"
800
+ };
801
+ var txLinkStyle = {
802
+ fontSize: "12px",
803
+ color: "var(--tbook-primary, #6366f1)",
804
+ textDecoration: "none",
805
+ fontFamily: "monospace"
806
+ };
807
+
808
+ export { DepositWidget, EpochStatusBadge, PortfolioCard, TBookVaultProvider, TransactionHistory, VaultErrorBoundary, YieldBadge, useCancelDeposit, useClaim, useCurrentDepositEpoch, useCurrentRedeemEpoch, useDeposit, useRedeem, useSharePrice, useTBookVault, useTransactionHistory, useUsdcBalance, useVault, useVaultInfo, useVaultUser };
809
+ //# sourceMappingURL=index.js.map
810
+ //# sourceMappingURL=index.js.map