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