@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 +835 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +748 -0
- package/dist/index.d.ts +748 -0
- package/dist/index.js +810 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -0
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
|