@oasisprotocol/privana-sdk 0.2.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/LICENSE +202 -0
- package/README.md +239 -0
- package/dist/index.cjs +4438 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +2 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.cts +1059 -0
- package/dist/index.d.ts +1059 -0
- package/dist/index.js +4340 -0
- package/dist/index.js.map +1 -0
- package/package.json +90 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,4438 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var viem = require('viem');
|
|
6
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
+
var reactQuery = require('@tanstack/react-query');
|
|
8
|
+
var clsx = require('clsx');
|
|
9
|
+
var tailwindMerge = require('tailwind-merge');
|
|
10
|
+
var siwe = require('viem/siwe');
|
|
11
|
+
var wagmi = require('wagmi');
|
|
12
|
+
var actions = require('wagmi/actions');
|
|
13
|
+
var actions$1 = require('viem/actions');
|
|
14
|
+
var reactSlot = require('@radix-ui/react-slot');
|
|
15
|
+
var classVarianceAuthority = require('class-variance-authority');
|
|
16
|
+
var DialogPrimitive = require('@radix-ui/react-dialog');
|
|
17
|
+
var lucideReact = require('lucide-react');
|
|
18
|
+
var sonner = require('sonner');
|
|
19
|
+
|
|
20
|
+
function _interopNamespace(e) {
|
|
21
|
+
if (e && e.__esModule) return e;
|
|
22
|
+
var n = Object.create(null);
|
|
23
|
+
if (e) {
|
|
24
|
+
Object.keys(e).forEach(function (k) {
|
|
25
|
+
if (k !== 'default') {
|
|
26
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
27
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
28
|
+
enumerable: true,
|
|
29
|
+
get: function () { return e[k]; }
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
n.default = e;
|
|
35
|
+
return Object.freeze(n);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
var DialogPrimitive__namespace = /*#__PURE__*/_interopNamespace(DialogPrimitive);
|
|
39
|
+
|
|
40
|
+
// ../../shared/config.json
|
|
41
|
+
var config_default = {
|
|
42
|
+
chains: [
|
|
43
|
+
{
|
|
44
|
+
id: 84532,
|
|
45
|
+
name: "Base Sepolia",
|
|
46
|
+
explorerUrl: "https://sepolia.basescan.org"
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
networks: {
|
|
50
|
+
testnet: {
|
|
51
|
+
chainId: 23295,
|
|
52
|
+
name: "Sapphire Testnet",
|
|
53
|
+
accountingContract: "0xaF8e5de153A584528B57DD4B9B0195956BBDF571",
|
|
54
|
+
apiUrl: "https://flexvaults-staging.rofl.build"
|
|
55
|
+
},
|
|
56
|
+
mainnet: {
|
|
57
|
+
chainId: 23294,
|
|
58
|
+
name: "Sapphire Mainnet",
|
|
59
|
+
accountingContract: "0x0000000000000000000000000000000000000000",
|
|
60
|
+
apiUrl: ""
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// src/sdk/types/common.ts
|
|
66
|
+
var NETWORK_CONFIG = {
|
|
67
|
+
testnet: {
|
|
68
|
+
...config_default.networks.testnet,
|
|
69
|
+
accountingContract: config_default.networks.testnet.accountingContract
|
|
70
|
+
},
|
|
71
|
+
mainnet: {
|
|
72
|
+
...config_default.networks.mainnet,
|
|
73
|
+
accountingContract: config_default.networks.mainnet.accountingContract
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
function getChainId(network) {
|
|
77
|
+
return NETWORK_CONFIG[network].chainId;
|
|
78
|
+
}
|
|
79
|
+
function getAccountingContract(network) {
|
|
80
|
+
return NETWORK_CONFIG[network].accountingContract;
|
|
81
|
+
}
|
|
82
|
+
function getApiUrl(network) {
|
|
83
|
+
return NETWORK_CONFIG[network].apiUrl;
|
|
84
|
+
}
|
|
85
|
+
function normalizeHex(value) {
|
|
86
|
+
const normalized = value.trim().toLowerCase();
|
|
87
|
+
return normalized.startsWith("0x") ? normalized : `0x${normalized}`;
|
|
88
|
+
}
|
|
89
|
+
function normalizeAddress(value) {
|
|
90
|
+
return normalizeHex(value);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/sdk/types/chains.ts
|
|
94
|
+
var SUPPORTED_CHAINS = config_default.chains.map((chain) => ({
|
|
95
|
+
id: chain.id,
|
|
96
|
+
name: chain.name,
|
|
97
|
+
explorerUrl: chain.explorerUrl
|
|
98
|
+
}));
|
|
99
|
+
function getChainById(chainId) {
|
|
100
|
+
return SUPPORTED_CHAINS.find((c) => c.id === chainId);
|
|
101
|
+
}
|
|
102
|
+
function getExplorerAddressUrl(chainId, address) {
|
|
103
|
+
const chain = getChainById(chainId);
|
|
104
|
+
if (!chain) return void 0;
|
|
105
|
+
return `${chain.explorerUrl}/address/${address}#tokentxns`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/sdk/auth/hosted-auth.ts
|
|
109
|
+
var HOSTED_AUTH_CLOCK_SKEW_MS = 3e4;
|
|
110
|
+
var PKCE_CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
111
|
+
var DEFAULT_RANDOM_LENGTH = 64;
|
|
112
|
+
var HOSTED_AUTH_CALLBACK_QUERY_KEYS = ["code", "error", "error_description", "state"];
|
|
113
|
+
function randomString(length) {
|
|
114
|
+
const values = new Uint8Array(length);
|
|
115
|
+
crypto.getRandomValues(values);
|
|
116
|
+
let output = "";
|
|
117
|
+
for (const value of values) {
|
|
118
|
+
output += PKCE_CHARSET[value % PKCE_CHARSET.length];
|
|
119
|
+
}
|
|
120
|
+
return output;
|
|
121
|
+
}
|
|
122
|
+
function toBase64Url(bytes) {
|
|
123
|
+
let binary = "";
|
|
124
|
+
bytes.forEach((byte) => {
|
|
125
|
+
binary += String.fromCharCode(byte);
|
|
126
|
+
});
|
|
127
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
128
|
+
}
|
|
129
|
+
function createPkceVerifier(length = DEFAULT_RANDOM_LENGTH) {
|
|
130
|
+
return randomString(length);
|
|
131
|
+
}
|
|
132
|
+
async function createPkceChallenge(verifier) {
|
|
133
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
134
|
+
return toBase64Url(new Uint8Array(digest));
|
|
135
|
+
}
|
|
136
|
+
function createHostedAuthState(length = DEFAULT_RANDOM_LENGTH) {
|
|
137
|
+
return randomString(length);
|
|
138
|
+
}
|
|
139
|
+
function createHostedAuthStorageKey(apiUrl, config) {
|
|
140
|
+
const normalizedApiUrl = apiUrl.replace(/\/$/, "");
|
|
141
|
+
return [
|
|
142
|
+
"privana",
|
|
143
|
+
"hosted-auth",
|
|
144
|
+
normalizedApiUrl,
|
|
145
|
+
config.clientId.trim(),
|
|
146
|
+
config.redirectUri.trim()
|
|
147
|
+
].join(":");
|
|
148
|
+
}
|
|
149
|
+
function createHostedAuthPendingStorageKey(apiUrl, config) {
|
|
150
|
+
return `${createHostedAuthStorageKey(apiUrl, config)}:pending`;
|
|
151
|
+
}
|
|
152
|
+
function persistHostedAuthPendingTransaction(storage, key, transaction) {
|
|
153
|
+
storage.setItem(key, JSON.stringify(transaction));
|
|
154
|
+
}
|
|
155
|
+
function readHostedAuthPendingTransaction(storage, key) {
|
|
156
|
+
const raw = storage.getItem(key);
|
|
157
|
+
if (!raw) return null;
|
|
158
|
+
try {
|
|
159
|
+
const parsed = JSON.parse(raw);
|
|
160
|
+
if (typeof parsed.codeVerifier !== "string" || typeof parsed.state !== "string") {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
codeVerifier: parsed.codeVerifier,
|
|
165
|
+
state: parsed.state
|
|
166
|
+
};
|
|
167
|
+
} catch {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function clearHostedAuthPendingTransaction(storage, key) {
|
|
172
|
+
storage.removeItem(key);
|
|
173
|
+
}
|
|
174
|
+
function parseHostedAuthCallback(url, redirectUri) {
|
|
175
|
+
const expectedUrl = new URL(redirectUri);
|
|
176
|
+
if (url.origin !== expectedUrl.origin || url.pathname !== expectedUrl.pathname) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
const code = url.searchParams.get("code");
|
|
180
|
+
if (code) {
|
|
181
|
+
return {
|
|
182
|
+
code,
|
|
183
|
+
state: url.searchParams.get("state")
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
const error = url.searchParams.get("error");
|
|
187
|
+
if (error) {
|
|
188
|
+
return {
|
|
189
|
+
error,
|
|
190
|
+
errorDescription: url.searchParams.get("error_description") ?? void 0,
|
|
191
|
+
state: url.searchParams.get("state")
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
function stripHostedAuthCallbackParams(url) {
|
|
197
|
+
const nextUrl = new URL(url.toString());
|
|
198
|
+
HOSTED_AUTH_CALLBACK_QUERY_KEYS.forEach((key) => {
|
|
199
|
+
nextUrl.searchParams.delete(key);
|
|
200
|
+
});
|
|
201
|
+
return `${nextUrl.pathname}${nextUrl.search}${nextUrl.hash}`;
|
|
202
|
+
}
|
|
203
|
+
function buildHostedAuthSession(response, config, now = Date.now()) {
|
|
204
|
+
return {
|
|
205
|
+
accessToken: response.access_token,
|
|
206
|
+
refreshToken: response.refresh_token,
|
|
207
|
+
idToken: response.id_token,
|
|
208
|
+
tokenType: response.token_type,
|
|
209
|
+
address: response.address,
|
|
210
|
+
clientId: config.clientId,
|
|
211
|
+
redirectUri: config.redirectUri,
|
|
212
|
+
expiresAt: now + response.expires_in * 1e3,
|
|
213
|
+
refreshExpiresAt: now + response.refresh_expires_in * 1e3
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function applyRefreshResponse(session, response, now = Date.now()) {
|
|
217
|
+
return {
|
|
218
|
+
...session,
|
|
219
|
+
accessToken: response.token,
|
|
220
|
+
refreshToken: response.refresh_token,
|
|
221
|
+
expiresAt: now + response.expires_in * 1e3,
|
|
222
|
+
refreshExpiresAt: now + response.refresh_expires_in * 1e3
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function isHostedAuthSessionActive(session, now = Date.now(), skewMs = HOSTED_AUTH_CLOCK_SKEW_MS) {
|
|
226
|
+
return session.expiresAt > now + skewMs;
|
|
227
|
+
}
|
|
228
|
+
function isHostedAuthRefreshActive(session, now = Date.now(), skewMs = HOSTED_AUTH_CLOCK_SKEW_MS) {
|
|
229
|
+
return session.refreshExpiresAt > now + skewMs;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/sdk/client/errors.ts
|
|
233
|
+
var AccountingApiError = class _AccountingApiError extends Error {
|
|
234
|
+
constructor(message, statusCode, detail) {
|
|
235
|
+
super(message);
|
|
236
|
+
this.statusCode = statusCode;
|
|
237
|
+
this.detail = detail;
|
|
238
|
+
this.name = "AccountingApiError";
|
|
239
|
+
Object.setPrototypeOf(this, _AccountingApiError.prototype);
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
var NetworkError = class _NetworkError extends Error {
|
|
243
|
+
constructor(message, cause) {
|
|
244
|
+
super(message);
|
|
245
|
+
this.cause = cause;
|
|
246
|
+
this.name = "NetworkError";
|
|
247
|
+
Object.setPrototypeOf(this, _NetworkError.prototype);
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
var ValidationError = class _ValidationError extends Error {
|
|
251
|
+
constructor(message, field) {
|
|
252
|
+
super(message);
|
|
253
|
+
this.field = field;
|
|
254
|
+
this.name = "ValidationError";
|
|
255
|
+
Object.setPrototypeOf(this, _ValidationError.prototype);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
var HostedAuthError = class _HostedAuthError extends Error {
|
|
259
|
+
constructor(message) {
|
|
260
|
+
super(message);
|
|
261
|
+
this.name = "HostedAuthError";
|
|
262
|
+
Object.setPrototypeOf(this, _HostedAuthError.prototype);
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
var HostedAuthRequiredError = class _HostedAuthRequiredError extends HostedAuthError {
|
|
266
|
+
constructor(message = "Hosted redirect authentication is required. Start login with useHostedRedirectAuth().") {
|
|
267
|
+
super(message);
|
|
268
|
+
this.name = "HostedAuthRequiredError";
|
|
269
|
+
Object.setPrototypeOf(this, _HostedAuthRequiredError.prototype);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
var HostedAuthStateMismatchError = class _HostedAuthStateMismatchError extends HostedAuthError {
|
|
273
|
+
constructor(message = "Hosted authentication returned an invalid state value.") {
|
|
274
|
+
super(message);
|
|
275
|
+
this.name = "HostedAuthStateMismatchError";
|
|
276
|
+
Object.setPrototypeOf(this, _HostedAuthStateMismatchError.prototype);
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// src/sdk/client/http-client.ts
|
|
281
|
+
var HttpClient = class {
|
|
282
|
+
constructor(config) {
|
|
283
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
284
|
+
this.timeout = config.timeout ?? 3e4;
|
|
285
|
+
this.headers = {
|
|
286
|
+
"Content-Type": "application/json",
|
|
287
|
+
...config.headers
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
async get(path) {
|
|
291
|
+
return this.request("GET", path);
|
|
292
|
+
}
|
|
293
|
+
async post(path, body) {
|
|
294
|
+
return this.request("POST", path, body);
|
|
295
|
+
}
|
|
296
|
+
getBaseUrl() {
|
|
297
|
+
return this.baseUrl;
|
|
298
|
+
}
|
|
299
|
+
setHeader(name, value) {
|
|
300
|
+
this.headers[name] = value;
|
|
301
|
+
}
|
|
302
|
+
removeHeader(name) {
|
|
303
|
+
delete this.headers[name];
|
|
304
|
+
}
|
|
305
|
+
getHeader(name) {
|
|
306
|
+
return this.headers[name];
|
|
307
|
+
}
|
|
308
|
+
async request(method, path, body) {
|
|
309
|
+
const url = `${this.baseUrl}${path}`;
|
|
310
|
+
const controller = new AbortController();
|
|
311
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
312
|
+
try {
|
|
313
|
+
const response = await fetch(url, {
|
|
314
|
+
method,
|
|
315
|
+
headers: this.headers,
|
|
316
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
317
|
+
signal: controller.signal
|
|
318
|
+
});
|
|
319
|
+
clearTimeout(timeoutId);
|
|
320
|
+
if (!response.ok) {
|
|
321
|
+
let detail;
|
|
322
|
+
try {
|
|
323
|
+
const errorBody = await response.json();
|
|
324
|
+
detail = errorBody.detail || errorBody.error_description || errorBody.message;
|
|
325
|
+
} catch {
|
|
326
|
+
detail = await response.text().catch(() => void 0);
|
|
327
|
+
}
|
|
328
|
+
throw new AccountingApiError(
|
|
329
|
+
`API request failed: ${response.status} ${response.statusText}`,
|
|
330
|
+
response.status,
|
|
331
|
+
detail
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
return response.json();
|
|
335
|
+
} catch (error) {
|
|
336
|
+
clearTimeout(timeoutId);
|
|
337
|
+
if (error instanceof AccountingApiError) {
|
|
338
|
+
throw error;
|
|
339
|
+
}
|
|
340
|
+
if (error instanceof Error) {
|
|
341
|
+
if (error.name === "AbortError") {
|
|
342
|
+
throw new NetworkError(`Request timeout after ${this.timeout}ms`);
|
|
343
|
+
}
|
|
344
|
+
throw new NetworkError(`Network request failed: ${error.message}`, error);
|
|
345
|
+
}
|
|
346
|
+
throw new NetworkError("Unknown network error occurred");
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
// src/sdk/client/privana-client.ts
|
|
352
|
+
var PRIVATE_READ_TOKEN_HEADER = "X-SIWE-Token";
|
|
353
|
+
var MAX_BATCH_BALANCE_TOKEN_IDS = 100;
|
|
354
|
+
var MAX_HISTORY_PAGE_SIZE = 100;
|
|
355
|
+
var PrivanaClient = class {
|
|
356
|
+
constructor(config) {
|
|
357
|
+
this.http = new HttpClient(config);
|
|
358
|
+
}
|
|
359
|
+
getBaseUrl() {
|
|
360
|
+
return this.http.getBaseUrl();
|
|
361
|
+
}
|
|
362
|
+
async getDepositAddress(request = {}) {
|
|
363
|
+
return this.http.post("/v1/accounting/deposits/address", {
|
|
364
|
+
chain_type: request.chain_type ?? "evm",
|
|
365
|
+
version: request.version ?? 0
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
async checkDeposit(request) {
|
|
369
|
+
return this.http.post("/v1/accounting/deposits/check", {
|
|
370
|
+
chain_type: request.chain_type ?? "evm",
|
|
371
|
+
chain_id: request.chain_id,
|
|
372
|
+
tx_hash: normalizeHex(request.tx_hash),
|
|
373
|
+
amount: String(request.amount),
|
|
374
|
+
log_index: request.log_index ?? 0,
|
|
375
|
+
version: request.version ?? 0
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
async getDepositStatus(depositId) {
|
|
379
|
+
return this.http.get(`/v1/accounting/deposits/status/${depositId}`);
|
|
380
|
+
}
|
|
381
|
+
async getBalance(tokenId) {
|
|
382
|
+
const token = normalizeHex(tokenId);
|
|
383
|
+
return this.http.get(`/v1/accounting/balances/${token}`);
|
|
384
|
+
}
|
|
385
|
+
async getBatchBalances(request) {
|
|
386
|
+
if (request.token_ids.length > MAX_BATCH_BALANCE_TOKEN_IDS) {
|
|
387
|
+
throw new Error(
|
|
388
|
+
`Batch balance requests support at most ${MAX_BATCH_BALANCE_TOKEN_IDS} token IDs`
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
return this.http.post("/v1/accounting/balances/batch", {
|
|
392
|
+
token_ids: request.token_ids.map((id) => normalizeHex(id))
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
async getHistory(request = {}) {
|
|
396
|
+
const offset = request.offset ?? -1;
|
|
397
|
+
const limit = request.limit ?? 50;
|
|
398
|
+
if (!Number.isSafeInteger(offset)) {
|
|
399
|
+
throw new Error("History offset must be an integer");
|
|
400
|
+
}
|
|
401
|
+
if (!Number.isSafeInteger(limit)) {
|
|
402
|
+
throw new Error("History limit must be an integer");
|
|
403
|
+
}
|
|
404
|
+
if (limit < 0 || limit > MAX_HISTORY_PAGE_SIZE) {
|
|
405
|
+
throw new Error(`History requests support between 0 and ${MAX_HISTORY_PAGE_SIZE} entries`);
|
|
406
|
+
}
|
|
407
|
+
const params = new URLSearchParams({
|
|
408
|
+
offset: String(offset),
|
|
409
|
+
limit: String(limit)
|
|
410
|
+
});
|
|
411
|
+
return this.http.get(`/v1/accounting/history?${params.toString()}`);
|
|
412
|
+
}
|
|
413
|
+
async listTokens() {
|
|
414
|
+
return this.http.get("/v1/accounting/tokens");
|
|
415
|
+
}
|
|
416
|
+
async getTokenInfo(tokenId) {
|
|
417
|
+
const token = normalizeHex(tokenId);
|
|
418
|
+
return this.http.get(`/v1/accounting/tokens/${token}`);
|
|
419
|
+
}
|
|
420
|
+
async lockFunds(request) {
|
|
421
|
+
return this.http.post("/v1/accounting/funds/lock", {
|
|
422
|
+
service_address: normalizeAddress(request.service_address),
|
|
423
|
+
token_id: normalizeHex(request.token_id),
|
|
424
|
+
amount: String(request.amount),
|
|
425
|
+
expiry: String(request.expiry),
|
|
426
|
+
nonce: String(request.nonce),
|
|
427
|
+
signature: normalizeHex(request.signature)
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
async modifyLock(request) {
|
|
431
|
+
return this.http.post("/v1/accounting/funds/modify-lock", {
|
|
432
|
+
lock_id: request.lock_id,
|
|
433
|
+
amount: String(request.amount),
|
|
434
|
+
new_expiry: String(request.new_expiry),
|
|
435
|
+
nonce: String(request.nonce),
|
|
436
|
+
signature: normalizeHex(request.signature)
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
async unlockFunds(request) {
|
|
440
|
+
return this.http.post("/v1/accounting/funds/unlock", {
|
|
441
|
+
user_address: normalizeAddress(request.user_address),
|
|
442
|
+
lock_id: request.lock_id
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
async unlockAllExpired(request) {
|
|
446
|
+
return this.http.post(
|
|
447
|
+
"/v1/accounting/funds/unlock-all-expired",
|
|
448
|
+
{
|
|
449
|
+
user_address: normalizeAddress(request.user_address)
|
|
450
|
+
}
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
async getLockedFunds(serviceAddress) {
|
|
454
|
+
const queryParams = serviceAddress ? `?service_address=${normalizeAddress(serviceAddress)}` : "";
|
|
455
|
+
return this.http.get(`/v1/accounting/funds/locked${queryParams}`);
|
|
456
|
+
}
|
|
457
|
+
async getTotalLockedBalance(tokenId) {
|
|
458
|
+
const token = normalizeHex(tokenId);
|
|
459
|
+
return this.http.get(`/v1/accounting/funds/locked/total/${token}`);
|
|
460
|
+
}
|
|
461
|
+
async getExpiredLocks() {
|
|
462
|
+
return this.http.get("/v1/accounting/funds/expired");
|
|
463
|
+
}
|
|
464
|
+
async transferFunds(request) {
|
|
465
|
+
return this.http.post("/v1/accounting/funds/transfer", {
|
|
466
|
+
to_address: normalizeAddress(request.to_address),
|
|
467
|
+
token_id: normalizeHex(request.token_id),
|
|
468
|
+
amount: String(request.amount),
|
|
469
|
+
nonce: String(request.nonce),
|
|
470
|
+
signature: normalizeHex(request.signature)
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
async getTransferNonce(userAddress) {
|
|
474
|
+
const user = normalizeAddress(userAddress);
|
|
475
|
+
return this.http.get(`/v1/accounting/funds/transfer/nonce/${user}`);
|
|
476
|
+
}
|
|
477
|
+
async getLockNonce(userAddress) {
|
|
478
|
+
const user = normalizeAddress(userAddress);
|
|
479
|
+
return this.http.get(`/v1/accounting/funds/lock/nonce/${user}`);
|
|
480
|
+
}
|
|
481
|
+
async getModifyLockNonce(userAddress) {
|
|
482
|
+
const user = normalizeAddress(userAddress);
|
|
483
|
+
return this.http.get(`/v1/accounting/funds/modify-lock/nonce/${user}`);
|
|
484
|
+
}
|
|
485
|
+
async transferLockedFunds(request) {
|
|
486
|
+
return this.http.post("/v1/accounting/funds/transfer-locked", {
|
|
487
|
+
user_address: normalizeAddress(request.user_address),
|
|
488
|
+
lock_id: request.lock_id,
|
|
489
|
+
to_address: normalizeAddress(request.to_address),
|
|
490
|
+
amount: String(request.amount),
|
|
491
|
+
service_address: normalizeAddress(request.service_address),
|
|
492
|
+
nonce: String(request.nonce),
|
|
493
|
+
signature: normalizeHex(request.signature)
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
async withdrawFromLock(request) {
|
|
497
|
+
return this.http.post(
|
|
498
|
+
"/v1/accounting/funds/withdraw-from-lock",
|
|
499
|
+
{
|
|
500
|
+
to_address: normalizeAddress(request.to_address),
|
|
501
|
+
lock_id: request.lock_id,
|
|
502
|
+
amount: String(request.amount),
|
|
503
|
+
nonce: String(request.nonce),
|
|
504
|
+
signature: normalizeHex(request.signature)
|
|
505
|
+
}
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
async requestWithdrawal(request) {
|
|
509
|
+
return this.http.post("/v1/accounting/withdraw", {
|
|
510
|
+
token_id: normalizeHex(request.token_id),
|
|
511
|
+
amount: String(request.amount),
|
|
512
|
+
nonce: String(request.nonce),
|
|
513
|
+
signature: normalizeHex(request.signature)
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
async getWithdrawalNonce(userAddress) {
|
|
517
|
+
const user = normalizeAddress(userAddress);
|
|
518
|
+
return this.http.get(`/v1/accounting/withdraw/nonce/${user}`);
|
|
519
|
+
}
|
|
520
|
+
async getTransferLockedNonce(serviceAddress) {
|
|
521
|
+
const service = normalizeAddress(serviceAddress);
|
|
522
|
+
return this.http.get(
|
|
523
|
+
`/v1/accounting/funds/transfer-locked/nonce/${service}`
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
async getPendingWithdrawals(userAddress) {
|
|
527
|
+
const user = normalizeAddress(userAddress);
|
|
528
|
+
return this.http.get(`/v1/accounting/withdraw/pending/${user}`);
|
|
529
|
+
}
|
|
530
|
+
async getWithdrawalInfo(index) {
|
|
531
|
+
return this.http.get(`/v1/accounting/withdraw/${index}`);
|
|
532
|
+
}
|
|
533
|
+
async getSiweDomain() {
|
|
534
|
+
return this.http.get("/v1/accounting/auth/domain");
|
|
535
|
+
}
|
|
536
|
+
async getSiweNonce(userAddress) {
|
|
537
|
+
const user = normalizeAddress(userAddress);
|
|
538
|
+
return this.http.get(`/v1/accounting/auth/nonce?address=${user}`);
|
|
539
|
+
}
|
|
540
|
+
async loginWithSiwe(request) {
|
|
541
|
+
return this.http.post("/v1/accounting/auth/login", {
|
|
542
|
+
siwe_message: request.siwe_message,
|
|
543
|
+
signature: normalizeHex(request.signature)
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
getHostedAuthAuthorizeUrl(request) {
|
|
547
|
+
const url = new URL(
|
|
548
|
+
"v1/accounting/auth/authorize",
|
|
549
|
+
`${this.http.getBaseUrl().replace(/\/$/, "")}/`
|
|
550
|
+
);
|
|
551
|
+
url.searchParams.set("client_id", request.client_id);
|
|
552
|
+
url.searchParams.set("redirect_uri", request.redirect_uri);
|
|
553
|
+
url.searchParams.set("code_challenge", request.code_challenge);
|
|
554
|
+
url.searchParams.set("state", request.state);
|
|
555
|
+
url.searchParams.set("chain_id", String(request.chain_id));
|
|
556
|
+
url.searchParams.set("response_mode", request.response_mode ?? "redirect");
|
|
557
|
+
url.searchParams.set("code_challenge_method", request.code_challenge_method ?? "S256");
|
|
558
|
+
return url.toString();
|
|
559
|
+
}
|
|
560
|
+
async exchangeHostedAuthCode(request) {
|
|
561
|
+
return this.http.post("/v1/accounting/auth/token", {
|
|
562
|
+
grant_type: request.grant_type ?? "authorization_code",
|
|
563
|
+
code: request.code,
|
|
564
|
+
code_verifier: request.code_verifier,
|
|
565
|
+
client_id: request.client_id,
|
|
566
|
+
redirect_uri: request.redirect_uri
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
async refreshJwtSession(request) {
|
|
570
|
+
return this.http.post("/v1/accounting/auth/jwt/refresh", {
|
|
571
|
+
refresh_token: request.refresh_token
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
async logoutJwtSession(request = {}) {
|
|
575
|
+
return this.http.post("/v1/accounting/auth/jwt/logout", {
|
|
576
|
+
refresh_token: request.refresh_token,
|
|
577
|
+
revoke_all: request.revoke_all ?? false
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
setPrivateReadToken(token) {
|
|
581
|
+
this.http.removeHeader("Authorization");
|
|
582
|
+
this.http.setHeader(PRIVATE_READ_TOKEN_HEADER, token);
|
|
583
|
+
}
|
|
584
|
+
getPrivateReadToken() {
|
|
585
|
+
return this.http.getHeader(PRIVATE_READ_TOKEN_HEADER);
|
|
586
|
+
}
|
|
587
|
+
clearPrivateReadToken() {
|
|
588
|
+
this.http.removeHeader(PRIVATE_READ_TOKEN_HEADER);
|
|
589
|
+
}
|
|
590
|
+
setBearerToken(token) {
|
|
591
|
+
this.http.removeHeader(PRIVATE_READ_TOKEN_HEADER);
|
|
592
|
+
this.http.setHeader("Authorization", `Bearer ${token}`);
|
|
593
|
+
}
|
|
594
|
+
clearBearerToken() {
|
|
595
|
+
this.http.removeHeader("Authorization");
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
// src/sdk/signatures/eip712-types.ts
|
|
600
|
+
function createDomain(chainId, verifyingContract) {
|
|
601
|
+
return {
|
|
602
|
+
name: "AccountingModule",
|
|
603
|
+
version: "1",
|
|
604
|
+
chainId,
|
|
605
|
+
verifyingContract
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
var LOCK_TYPES = {
|
|
609
|
+
Lock: [
|
|
610
|
+
{ name: "serviceAddress", type: "address" },
|
|
611
|
+
{ name: "tokenId", type: "bytes32" },
|
|
612
|
+
{ name: "amount", type: "uint256" },
|
|
613
|
+
{ name: "expiry", type: "uint256" },
|
|
614
|
+
{ name: "nonce", type: "uint256" }
|
|
615
|
+
]
|
|
616
|
+
};
|
|
617
|
+
var TRANSFER_TYPES = {
|
|
618
|
+
Transfer: [
|
|
619
|
+
{ name: "toAddress", type: "address" },
|
|
620
|
+
{ name: "tokenId", type: "bytes32" },
|
|
621
|
+
{ name: "amount", type: "uint256" },
|
|
622
|
+
{ name: "nonce", type: "uint256" }
|
|
623
|
+
]
|
|
624
|
+
};
|
|
625
|
+
var TRANSFER_LOCKED_TYPES = {
|
|
626
|
+
TransferLocked: [
|
|
627
|
+
{ name: "userAddress", type: "address" },
|
|
628
|
+
{ name: "toAddress", type: "address" },
|
|
629
|
+
{ name: "lockId", type: "uint256" },
|
|
630
|
+
{ name: "amount", type: "uint256" },
|
|
631
|
+
{ name: "nonce", type: "uint256" },
|
|
632
|
+
{ name: "serviceAddress", type: "address" }
|
|
633
|
+
]
|
|
634
|
+
};
|
|
635
|
+
var WITHDRAW_TYPES = {
|
|
636
|
+
Withdraw: [
|
|
637
|
+
{ name: "tokenId", type: "bytes32" },
|
|
638
|
+
{ name: "amount", type: "uint256" },
|
|
639
|
+
{ name: "nonce", type: "uint256" }
|
|
640
|
+
]
|
|
641
|
+
};
|
|
642
|
+
var MODIFY_LOCK_TYPES = {
|
|
643
|
+
ModifyLock: [
|
|
644
|
+
{ name: "lockId", type: "uint256" },
|
|
645
|
+
{ name: "amount", type: "uint256" },
|
|
646
|
+
{ name: "newExpiry", type: "uint256" },
|
|
647
|
+
{ name: "nonce", type: "uint256" }
|
|
648
|
+
]
|
|
649
|
+
};
|
|
650
|
+
var WITHDRAW_FROM_LOCK_TYPES = {
|
|
651
|
+
WithdrawFromLock: [
|
|
652
|
+
{ name: "userAddress", type: "address" },
|
|
653
|
+
{ name: "toAddress", type: "address" },
|
|
654
|
+
{ name: "lockId", type: "uint256" },
|
|
655
|
+
{ name: "amount", type: "uint256" },
|
|
656
|
+
{ name: "nonce", type: "uint256" }
|
|
657
|
+
]
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
// src/sdk/signatures/sign-lock.ts
|
|
661
|
+
async function signLockMessage({
|
|
662
|
+
walletClient,
|
|
663
|
+
chainId,
|
|
664
|
+
verifyingContract,
|
|
665
|
+
message
|
|
666
|
+
}) {
|
|
667
|
+
const account = walletClient.account;
|
|
668
|
+
if (!account) {
|
|
669
|
+
throw new Error("No account connected to wallet client");
|
|
670
|
+
}
|
|
671
|
+
const domain = createDomain(chainId, verifyingContract);
|
|
672
|
+
const signature = await walletClient.signTypedData({
|
|
673
|
+
account,
|
|
674
|
+
domain,
|
|
675
|
+
types: LOCK_TYPES,
|
|
676
|
+
primaryType: "Lock",
|
|
677
|
+
message
|
|
678
|
+
});
|
|
679
|
+
return signature;
|
|
680
|
+
}
|
|
681
|
+
function createLockExpiry(minutesFromNow = 60) {
|
|
682
|
+
return BigInt(Math.floor(Date.now() / 1e3) + minutesFromNow * 60);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// src/sdk/signatures/sign-modify-lock.ts
|
|
686
|
+
async function signModifyLockMessage({
|
|
687
|
+
walletClient,
|
|
688
|
+
chainId,
|
|
689
|
+
verifyingContract,
|
|
690
|
+
message
|
|
691
|
+
}) {
|
|
692
|
+
const account = walletClient.account;
|
|
693
|
+
if (!account) {
|
|
694
|
+
throw new Error("No account connected to wallet client");
|
|
695
|
+
}
|
|
696
|
+
const domain = createDomain(chainId, verifyingContract);
|
|
697
|
+
const signature = await walletClient.signTypedData({
|
|
698
|
+
account,
|
|
699
|
+
domain,
|
|
700
|
+
types: MODIFY_LOCK_TYPES,
|
|
701
|
+
primaryType: "ModifyLock",
|
|
702
|
+
message
|
|
703
|
+
});
|
|
704
|
+
return signature;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// src/sdk/signatures/sign-transfer.ts
|
|
708
|
+
async function signTransferMessage({
|
|
709
|
+
walletClient,
|
|
710
|
+
chainId,
|
|
711
|
+
verifyingContract,
|
|
712
|
+
message
|
|
713
|
+
}) {
|
|
714
|
+
const account = walletClient.account;
|
|
715
|
+
if (!account) {
|
|
716
|
+
throw new Error("No account connected to wallet client");
|
|
717
|
+
}
|
|
718
|
+
const domain = createDomain(chainId, verifyingContract);
|
|
719
|
+
const signature = await walletClient.signTypedData({
|
|
720
|
+
account,
|
|
721
|
+
domain,
|
|
722
|
+
types: TRANSFER_TYPES,
|
|
723
|
+
primaryType: "Transfer",
|
|
724
|
+
message
|
|
725
|
+
});
|
|
726
|
+
return signature;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// src/sdk/signatures/sign-transfer-locked.ts
|
|
730
|
+
async function signTransferLockedMessage({
|
|
731
|
+
walletClient,
|
|
732
|
+
chainId,
|
|
733
|
+
verifyingContract,
|
|
734
|
+
message
|
|
735
|
+
}) {
|
|
736
|
+
const account = walletClient.account;
|
|
737
|
+
if (!account) {
|
|
738
|
+
throw new Error("No account connected to wallet client");
|
|
739
|
+
}
|
|
740
|
+
const domain = createDomain(chainId, verifyingContract);
|
|
741
|
+
const signature = await walletClient.signTypedData({
|
|
742
|
+
account,
|
|
743
|
+
domain,
|
|
744
|
+
types: TRANSFER_LOCKED_TYPES,
|
|
745
|
+
primaryType: "TransferLocked",
|
|
746
|
+
message
|
|
747
|
+
});
|
|
748
|
+
return signature;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// src/sdk/signatures/sign-withdraw.ts
|
|
752
|
+
async function signWithdrawMessage({
|
|
753
|
+
walletClient,
|
|
754
|
+
chainId,
|
|
755
|
+
verifyingContract,
|
|
756
|
+
message
|
|
757
|
+
}) {
|
|
758
|
+
const account = walletClient.account;
|
|
759
|
+
if (!account) {
|
|
760
|
+
throw new Error("No account connected to wallet client");
|
|
761
|
+
}
|
|
762
|
+
const domain = createDomain(chainId, verifyingContract);
|
|
763
|
+
const signature = await walletClient.signTypedData({
|
|
764
|
+
account,
|
|
765
|
+
domain,
|
|
766
|
+
types: WITHDRAW_TYPES,
|
|
767
|
+
primaryType: "Withdraw",
|
|
768
|
+
message
|
|
769
|
+
});
|
|
770
|
+
return signature;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// src/sdk/signatures/sign-withdraw-from-lock.ts
|
|
774
|
+
async function signWithdrawFromLockMessage({
|
|
775
|
+
walletClient,
|
|
776
|
+
chainId,
|
|
777
|
+
verifyingContract,
|
|
778
|
+
message
|
|
779
|
+
}) {
|
|
780
|
+
const account = walletClient.account;
|
|
781
|
+
if (!account) {
|
|
782
|
+
throw new Error("No account connected to wallet client");
|
|
783
|
+
}
|
|
784
|
+
const domain = createDomain(chainId, verifyingContract);
|
|
785
|
+
const signature = await walletClient.signTypedData({
|
|
786
|
+
account,
|
|
787
|
+
domain,
|
|
788
|
+
types: WITHDRAW_FROM_LOCK_TYPES,
|
|
789
|
+
primaryType: "WithdrawFromLock",
|
|
790
|
+
message
|
|
791
|
+
});
|
|
792
|
+
return signature;
|
|
793
|
+
}
|
|
794
|
+
var PrivanaContext = react.createContext(null);
|
|
795
|
+
function readStoredHostedAuthSession(storage, hostedAuthStorageKey, now = Date.now()) {
|
|
796
|
+
const raw = storage.getItem(hostedAuthStorageKey);
|
|
797
|
+
if (!raw) {
|
|
798
|
+
return null;
|
|
799
|
+
}
|
|
800
|
+
try {
|
|
801
|
+
const parsed = JSON.parse(raw);
|
|
802
|
+
if (!isHostedAuthRefreshActive(parsed, now, 0)) {
|
|
803
|
+
storage.removeItem(hostedAuthStorageKey);
|
|
804
|
+
return null;
|
|
805
|
+
}
|
|
806
|
+
return parsed;
|
|
807
|
+
} catch {
|
|
808
|
+
storage.removeItem(hostedAuthStorageKey);
|
|
809
|
+
return null;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
function syncHostedAuthSessionToClient(client, hostedAuthConfig, hostedAuthSession) {
|
|
813
|
+
if (!hostedAuthConfig) {
|
|
814
|
+
client.clearBearerToken();
|
|
815
|
+
client.clearPrivateReadToken();
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
if (hostedAuthSession && isHostedAuthSessionActive(hostedAuthSession)) {
|
|
819
|
+
client.setBearerToken(hostedAuthSession.accessToken);
|
|
820
|
+
client.clearPrivateReadToken();
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
client.clearBearerToken();
|
|
824
|
+
client.clearPrivateReadToken();
|
|
825
|
+
}
|
|
826
|
+
var DEFAULT_NETWORK_CONFIG = NETWORK_CONFIG.testnet;
|
|
827
|
+
function PrivanaProvider({
|
|
828
|
+
children,
|
|
829
|
+
networkConfig: networkConfigOverride,
|
|
830
|
+
tokens,
|
|
831
|
+
chains,
|
|
832
|
+
pollingInterval = 1e4,
|
|
833
|
+
serviceAddress,
|
|
834
|
+
hostedAuth
|
|
835
|
+
}) {
|
|
836
|
+
const networkConfig = react.useMemo(() => {
|
|
837
|
+
const config = {
|
|
838
|
+
...DEFAULT_NETWORK_CONFIG,
|
|
839
|
+
...networkConfigOverride
|
|
840
|
+
};
|
|
841
|
+
if (!config.chainId || config.chainId <= 0) {
|
|
842
|
+
throw new Error("PrivanaProvider: networkConfig.chainId must be a positive number");
|
|
843
|
+
}
|
|
844
|
+
if (!config.accountingContract || !config.accountingContract.startsWith("0x")) {
|
|
845
|
+
throw new Error("PrivanaProvider: networkConfig.accountingContract must be a valid address");
|
|
846
|
+
}
|
|
847
|
+
if (!config.apiUrl) {
|
|
848
|
+
throw new Error("PrivanaProvider: networkConfig.apiUrl must be provided");
|
|
849
|
+
}
|
|
850
|
+
return config;
|
|
851
|
+
}, [
|
|
852
|
+
networkConfigOverride?.chainId,
|
|
853
|
+
networkConfigOverride?.name,
|
|
854
|
+
networkConfigOverride?.accountingContract,
|
|
855
|
+
networkConfigOverride?.apiUrl
|
|
856
|
+
]);
|
|
857
|
+
const resolvedChains = react.useMemo(() => {
|
|
858
|
+
if (chains && chains.length > 0) return chains;
|
|
859
|
+
return SUPPORTED_CHAINS;
|
|
860
|
+
}, [chains]);
|
|
861
|
+
const client = react.useMemo(
|
|
862
|
+
() => new PrivanaClient({ baseUrl: networkConfig.apiUrl }),
|
|
863
|
+
[networkConfig.apiUrl]
|
|
864
|
+
);
|
|
865
|
+
const [allTokens, setAllTokens] = react.useState([]);
|
|
866
|
+
const [tokensStatus, setTokensStatus] = react.useState("loading");
|
|
867
|
+
const [tokensError, setTokensError] = react.useState();
|
|
868
|
+
react.useEffect(() => {
|
|
869
|
+
setTokensStatus("loading");
|
|
870
|
+
setTokensError(void 0);
|
|
871
|
+
client.listTokens().then(({ tokens: list }) => {
|
|
872
|
+
setAllTokens(
|
|
873
|
+
list.map((t) => ({
|
|
874
|
+
id: t.token_id,
|
|
875
|
+
symbol: t.symbol,
|
|
876
|
+
decimals: t.decimals,
|
|
877
|
+
contract: t.token_address ?? viem.zeroAddress,
|
|
878
|
+
name: t.name,
|
|
879
|
+
chainId: t.chain_id
|
|
880
|
+
}))
|
|
881
|
+
);
|
|
882
|
+
setTokensStatus("ready");
|
|
883
|
+
}).catch((err) => {
|
|
884
|
+
setTokensError(err instanceof Error ? err : new Error(String(err)));
|
|
885
|
+
setTokensStatus("error");
|
|
886
|
+
});
|
|
887
|
+
}, [client]);
|
|
888
|
+
const enabledTokens = react.useMemo(() => {
|
|
889
|
+
if (tokens && tokens.length > 0) {
|
|
890
|
+
const allowed = new Set(tokens.map((id) => id.toLowerCase()));
|
|
891
|
+
return allTokens.filter((t) => allowed.has(t.id.toLowerCase()));
|
|
892
|
+
}
|
|
893
|
+
return allTokens;
|
|
894
|
+
}, [allTokens, tokens]);
|
|
895
|
+
const tokenById = react.useMemo(
|
|
896
|
+
() => Object.fromEntries(enabledTokens.map((t) => [t.id.toLowerCase(), t])),
|
|
897
|
+
[enabledTokens]
|
|
898
|
+
);
|
|
899
|
+
const getTokenById = react.useMemo(() => (id) => tokenById[id.toLowerCase()], [tokenById]);
|
|
900
|
+
const chainById = react.useMemo(
|
|
901
|
+
() => Object.fromEntries(resolvedChains.map((c) => [c.id, c])),
|
|
902
|
+
[resolvedChains]
|
|
903
|
+
);
|
|
904
|
+
const getChainById2 = react.useMemo(() => (id) => chainById[id], [chainById]);
|
|
905
|
+
const hostedAuthConfig = react.useMemo(() => {
|
|
906
|
+
if (!hostedAuth) return null;
|
|
907
|
+
const clientId = hostedAuth.clientId.trim();
|
|
908
|
+
const redirectUri = hostedAuth.redirectUri.trim();
|
|
909
|
+
if (!clientId) {
|
|
910
|
+
throw new Error(
|
|
911
|
+
"PrivanaProvider: hostedAuth.clientId must be provided when hostedAuth is enabled"
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
if (!redirectUri) {
|
|
915
|
+
throw new Error(
|
|
916
|
+
"PrivanaProvider: hostedAuth.redirectUri must be provided when hostedAuth is enabled"
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
return {
|
|
920
|
+
clientId,
|
|
921
|
+
redirectUri
|
|
922
|
+
};
|
|
923
|
+
}, [hostedAuth]);
|
|
924
|
+
const hostedAuthStorageKey = react.useMemo(
|
|
925
|
+
() => hostedAuthConfig ? createHostedAuthStorageKey(networkConfig.apiUrl, hostedAuthConfig) : null,
|
|
926
|
+
[hostedAuthConfig, networkConfig.apiUrl]
|
|
927
|
+
);
|
|
928
|
+
const [hostedAuthSession, setHostedAuthSessionState] = react.useState(null);
|
|
929
|
+
const hostedAuthSessionRef = react.useRef(null);
|
|
930
|
+
const hostedAuthStateVersionRef = react.useRef(0);
|
|
931
|
+
const hostedAuthRefreshInflight = react.useRef(null);
|
|
932
|
+
const clearHostedAuthSession = react.useCallback(() => {
|
|
933
|
+
hostedAuthStateVersionRef.current += 1;
|
|
934
|
+
hostedAuthSessionRef.current = null;
|
|
935
|
+
hostedAuthRefreshInflight.current = null;
|
|
936
|
+
setHostedAuthSessionState(null);
|
|
937
|
+
client.clearBearerToken();
|
|
938
|
+
client.clearPrivateReadToken();
|
|
939
|
+
if (hostedAuthStorageKey && typeof window !== "undefined") {
|
|
940
|
+
window.sessionStorage.removeItem(hostedAuthStorageKey);
|
|
941
|
+
}
|
|
942
|
+
}, [client, hostedAuthStorageKey]);
|
|
943
|
+
const setHostedAuthSession = react.useCallback(
|
|
944
|
+
(session) => {
|
|
945
|
+
if (!session) {
|
|
946
|
+
clearHostedAuthSession();
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
hostedAuthStateVersionRef.current += 1;
|
|
950
|
+
hostedAuthSessionRef.current = session;
|
|
951
|
+
setHostedAuthSessionState(session);
|
|
952
|
+
if (isHostedAuthSessionActive(session)) {
|
|
953
|
+
client.setBearerToken(session.accessToken);
|
|
954
|
+
} else {
|
|
955
|
+
client.clearBearerToken();
|
|
956
|
+
}
|
|
957
|
+
client.clearPrivateReadToken();
|
|
958
|
+
if (hostedAuthStorageKey && typeof window !== "undefined") {
|
|
959
|
+
window.sessionStorage.setItem(hostedAuthStorageKey, JSON.stringify(session));
|
|
960
|
+
}
|
|
961
|
+
},
|
|
962
|
+
[clearHostedAuthSession, client, hostedAuthStorageKey]
|
|
963
|
+
);
|
|
964
|
+
const refreshHostedAuthSession = react.useCallback(async () => {
|
|
965
|
+
if (!hostedAuthConfig) {
|
|
966
|
+
throw new HostedAuthRequiredError(
|
|
967
|
+
"Hosted redirect authentication is not configured for this provider."
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
const currentSession = hostedAuthSessionRef.current;
|
|
971
|
+
if (!currentSession) {
|
|
972
|
+
throw new HostedAuthRequiredError();
|
|
973
|
+
}
|
|
974
|
+
if (!isHostedAuthRefreshActive(currentSession)) {
|
|
975
|
+
clearHostedAuthSession();
|
|
976
|
+
throw new HostedAuthRequiredError(
|
|
977
|
+
"Hosted redirect authentication has expired. Start login again."
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
if (hostedAuthRefreshInflight.current) {
|
|
981
|
+
return hostedAuthRefreshInflight.current;
|
|
982
|
+
}
|
|
983
|
+
const refreshPromise = (async () => {
|
|
984
|
+
const refreshVersion = hostedAuthStateVersionRef.current;
|
|
985
|
+
try {
|
|
986
|
+
const response = await client.refreshJwtSession({
|
|
987
|
+
refresh_token: currentSession.refreshToken
|
|
988
|
+
});
|
|
989
|
+
if (refreshVersion !== hostedAuthStateVersionRef.current) {
|
|
990
|
+
const latestSession = hostedAuthSessionRef.current;
|
|
991
|
+
if (latestSession) {
|
|
992
|
+
return latestSession;
|
|
993
|
+
}
|
|
994
|
+
throw new HostedAuthRequiredError(
|
|
995
|
+
"Hosted redirect authentication has changed. Start login again."
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
const nextSession = applyRefreshResponse(currentSession, response);
|
|
999
|
+
setHostedAuthSession(nextSession);
|
|
1000
|
+
return nextSession;
|
|
1001
|
+
} catch (error) {
|
|
1002
|
+
if (refreshVersion === hostedAuthStateVersionRef.current) {
|
|
1003
|
+
clearHostedAuthSession();
|
|
1004
|
+
}
|
|
1005
|
+
throw error;
|
|
1006
|
+
} finally {
|
|
1007
|
+
hostedAuthRefreshInflight.current = null;
|
|
1008
|
+
}
|
|
1009
|
+
})();
|
|
1010
|
+
hostedAuthRefreshInflight.current = refreshPromise;
|
|
1011
|
+
return refreshPromise;
|
|
1012
|
+
}, [clearHostedAuthSession, client, hostedAuthConfig, setHostedAuthSession]);
|
|
1013
|
+
react.useEffect(() => {
|
|
1014
|
+
hostedAuthSessionRef.current = hostedAuthSession;
|
|
1015
|
+
}, [hostedAuthSession]);
|
|
1016
|
+
react.useEffect(() => {
|
|
1017
|
+
if (!hostedAuthStorageKey || typeof window === "undefined") {
|
|
1018
|
+
hostedAuthStateVersionRef.current += 1;
|
|
1019
|
+
hostedAuthSessionRef.current = null;
|
|
1020
|
+
hostedAuthRefreshInflight.current = null;
|
|
1021
|
+
setHostedAuthSessionState(null);
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
const restoredSession = readStoredHostedAuthSession(window.sessionStorage, hostedAuthStorageKey);
|
|
1025
|
+
hostedAuthStateVersionRef.current += 1;
|
|
1026
|
+
hostedAuthSessionRef.current = restoredSession;
|
|
1027
|
+
hostedAuthRefreshInflight.current = null;
|
|
1028
|
+
setHostedAuthSessionState(restoredSession);
|
|
1029
|
+
}, [hostedAuthStorageKey]);
|
|
1030
|
+
react.useEffect(() => {
|
|
1031
|
+
syncHostedAuthSessionToClient(client, hostedAuthConfig, hostedAuthSession);
|
|
1032
|
+
}, [client, hostedAuthConfig, hostedAuthSession]);
|
|
1033
|
+
const value = react.useMemo(
|
|
1034
|
+
() => ({
|
|
1035
|
+
client,
|
|
1036
|
+
networkConfig,
|
|
1037
|
+
enabledTokens,
|
|
1038
|
+
defaultToken: enabledTokens[0],
|
|
1039
|
+
getTokenById,
|
|
1040
|
+
getChainById: getChainById2,
|
|
1041
|
+
chains: resolvedChains,
|
|
1042
|
+
tokensStatus,
|
|
1043
|
+
tokensError,
|
|
1044
|
+
pollingInterval,
|
|
1045
|
+
serviceAddress,
|
|
1046
|
+
hostedAuthConfig,
|
|
1047
|
+
hostedAuthSession,
|
|
1048
|
+
setHostedAuthSession,
|
|
1049
|
+
clearHostedAuthSession,
|
|
1050
|
+
refreshHostedAuthSession
|
|
1051
|
+
}),
|
|
1052
|
+
[
|
|
1053
|
+
client,
|
|
1054
|
+
networkConfig,
|
|
1055
|
+
enabledTokens,
|
|
1056
|
+
getTokenById,
|
|
1057
|
+
getChainById2,
|
|
1058
|
+
resolvedChains,
|
|
1059
|
+
tokensStatus,
|
|
1060
|
+
tokensError,
|
|
1061
|
+
pollingInterval,
|
|
1062
|
+
serviceAddress,
|
|
1063
|
+
hostedAuthConfig,
|
|
1064
|
+
hostedAuthSession,
|
|
1065
|
+
setHostedAuthSession,
|
|
1066
|
+
clearHostedAuthSession,
|
|
1067
|
+
refreshHostedAuthSession
|
|
1068
|
+
]
|
|
1069
|
+
);
|
|
1070
|
+
return /* @__PURE__ */ jsxRuntime.jsx(PrivanaContext.Provider, { value, children });
|
|
1071
|
+
}
|
|
1072
|
+
function usePrivanaContext() {
|
|
1073
|
+
const context = react.useContext(PrivanaContext);
|
|
1074
|
+
if (!context) {
|
|
1075
|
+
throw new Error("usePrivanaContext must be used within a PrivanaProvider");
|
|
1076
|
+
}
|
|
1077
|
+
return context;
|
|
1078
|
+
}
|
|
1079
|
+
function useSafePrivanaContext() {
|
|
1080
|
+
return react.useContext(PrivanaContext);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// src/sdk/hooks/use-privana-client.ts
|
|
1084
|
+
function usePrivanaClient() {
|
|
1085
|
+
const { client } = usePrivanaContext();
|
|
1086
|
+
return client;
|
|
1087
|
+
}
|
|
1088
|
+
var hostedAuthExchangeInflight = /* @__PURE__ */ new Map();
|
|
1089
|
+
function normalizeHostedAuthError(error) {
|
|
1090
|
+
if (error instanceof AccountingApiError && error.detail) {
|
|
1091
|
+
return new HostedAuthError(error.detail);
|
|
1092
|
+
}
|
|
1093
|
+
if (error instanceof Error) {
|
|
1094
|
+
return error;
|
|
1095
|
+
}
|
|
1096
|
+
return new HostedAuthError("Hosted authentication failed.");
|
|
1097
|
+
}
|
|
1098
|
+
function useHostedRedirectAuth() {
|
|
1099
|
+
const {
|
|
1100
|
+
client,
|
|
1101
|
+
hostedAuthConfig,
|
|
1102
|
+
hostedAuthSession,
|
|
1103
|
+
networkConfig,
|
|
1104
|
+
setHostedAuthSession,
|
|
1105
|
+
clearHostedAuthSession,
|
|
1106
|
+
refreshHostedAuthSession
|
|
1107
|
+
} = usePrivanaContext();
|
|
1108
|
+
const [error, setError] = react.useState(null);
|
|
1109
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
1110
|
+
const loginInflight = react.useRef(null);
|
|
1111
|
+
const completionInflight = react.useRef(null);
|
|
1112
|
+
const pendingStorageKey = react.useMemo(
|
|
1113
|
+
() => hostedAuthConfig ? createHostedAuthPendingStorageKey(client.getBaseUrl(), hostedAuthConfig) : null,
|
|
1114
|
+
[client, hostedAuthConfig]
|
|
1115
|
+
);
|
|
1116
|
+
const clearPendingLogin = react.useCallback(() => {
|
|
1117
|
+
if (!pendingStorageKey || typeof window === "undefined") return;
|
|
1118
|
+
clearHostedAuthPendingTransaction(window.sessionStorage, pendingStorageKey);
|
|
1119
|
+
}, [pendingStorageKey]);
|
|
1120
|
+
const login = react.useCallback(async () => {
|
|
1121
|
+
if (!hostedAuthConfig) {
|
|
1122
|
+
throw new HostedAuthRequiredError(
|
|
1123
|
+
"Hosted redirect authentication is not configured for this provider."
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
if (typeof window === "undefined") {
|
|
1127
|
+
throw new HostedAuthError("Hosted redirect authentication requires a browser environment.");
|
|
1128
|
+
}
|
|
1129
|
+
if (!pendingStorageKey) {
|
|
1130
|
+
throw new HostedAuthError("Hosted redirect authentication storage is not configured.");
|
|
1131
|
+
}
|
|
1132
|
+
if (loginInflight.current) {
|
|
1133
|
+
return loginInflight.current;
|
|
1134
|
+
}
|
|
1135
|
+
const loginPromise = (async () => {
|
|
1136
|
+
setIsLoading(true);
|
|
1137
|
+
setError(null);
|
|
1138
|
+
try {
|
|
1139
|
+
const verifier = createPkceVerifier();
|
|
1140
|
+
const codeChallenge = await createPkceChallenge(verifier);
|
|
1141
|
+
const state = createHostedAuthState();
|
|
1142
|
+
persistHostedAuthPendingTransaction(window.sessionStorage, pendingStorageKey, {
|
|
1143
|
+
codeVerifier: verifier,
|
|
1144
|
+
state
|
|
1145
|
+
});
|
|
1146
|
+
const authorizeUrl = client.getHostedAuthAuthorizeUrl({
|
|
1147
|
+
client_id: hostedAuthConfig.clientId,
|
|
1148
|
+
redirect_uri: hostedAuthConfig.redirectUri,
|
|
1149
|
+
code_challenge: codeChallenge,
|
|
1150
|
+
chain_id: networkConfig.chainId,
|
|
1151
|
+
code_challenge_method: "S256",
|
|
1152
|
+
response_mode: "redirect",
|
|
1153
|
+
state
|
|
1154
|
+
});
|
|
1155
|
+
window.location.assign(authorizeUrl);
|
|
1156
|
+
} catch (loginError) {
|
|
1157
|
+
clearPendingLogin();
|
|
1158
|
+
const normalizedError = normalizeHostedAuthError(loginError);
|
|
1159
|
+
setError(normalizedError);
|
|
1160
|
+
throw normalizedError;
|
|
1161
|
+
} finally {
|
|
1162
|
+
loginInflight.current = null;
|
|
1163
|
+
setIsLoading(false);
|
|
1164
|
+
}
|
|
1165
|
+
})();
|
|
1166
|
+
loginInflight.current = loginPromise;
|
|
1167
|
+
return loginPromise;
|
|
1168
|
+
}, [clearPendingLogin, client, hostedAuthConfig, networkConfig.chainId, pendingStorageKey]);
|
|
1169
|
+
const completeLogin = react.useCallback(async () => {
|
|
1170
|
+
if (!hostedAuthConfig) {
|
|
1171
|
+
throw new HostedAuthRequiredError(
|
|
1172
|
+
"Hosted redirect authentication is not configured for this provider."
|
|
1173
|
+
);
|
|
1174
|
+
}
|
|
1175
|
+
if (typeof window === "undefined") {
|
|
1176
|
+
throw new HostedAuthError("Hosted redirect authentication requires a browser environment.");
|
|
1177
|
+
}
|
|
1178
|
+
if (!pendingStorageKey) {
|
|
1179
|
+
throw new HostedAuthError("Hosted redirect authentication storage is not configured.");
|
|
1180
|
+
}
|
|
1181
|
+
if (completionInflight.current) {
|
|
1182
|
+
return completionInflight.current;
|
|
1183
|
+
}
|
|
1184
|
+
const completionPromise = (async () => {
|
|
1185
|
+
setIsLoading(true);
|
|
1186
|
+
setError(null);
|
|
1187
|
+
const callbackUrl = new URL(window.location.href);
|
|
1188
|
+
const cleanupCallbackUrl = () => {
|
|
1189
|
+
window.history.replaceState(null, "", stripHostedAuthCallbackParams(callbackUrl));
|
|
1190
|
+
};
|
|
1191
|
+
try {
|
|
1192
|
+
const callback = parseHostedAuthCallback(callbackUrl, hostedAuthConfig.redirectUri);
|
|
1193
|
+
if (!callback) {
|
|
1194
|
+
return null;
|
|
1195
|
+
}
|
|
1196
|
+
const pending = readHostedAuthPendingTransaction(window.sessionStorage, pendingStorageKey);
|
|
1197
|
+
if (!pending) {
|
|
1198
|
+
clearPendingLogin();
|
|
1199
|
+
cleanupCallbackUrl();
|
|
1200
|
+
throw new HostedAuthError(
|
|
1201
|
+
"Hosted authentication response could not be matched to a pending login request."
|
|
1202
|
+
);
|
|
1203
|
+
}
|
|
1204
|
+
if (!callback.state || callback.state !== pending.state) {
|
|
1205
|
+
clearPendingLogin();
|
|
1206
|
+
cleanupCallbackUrl();
|
|
1207
|
+
throw new HostedAuthStateMismatchError();
|
|
1208
|
+
}
|
|
1209
|
+
if ("error" in callback) {
|
|
1210
|
+
clearPendingLogin();
|
|
1211
|
+
cleanupCallbackUrl();
|
|
1212
|
+
throw new HostedAuthError(
|
|
1213
|
+
callback.errorDescription || callback.error || "Hosted authentication failed."
|
|
1214
|
+
);
|
|
1215
|
+
}
|
|
1216
|
+
const { codeVerifier } = pending;
|
|
1217
|
+
const exchangeKey = `${pendingStorageKey}:${callback.code}:${pending.state}`;
|
|
1218
|
+
let exchangePromise = hostedAuthExchangeInflight.get(exchangeKey);
|
|
1219
|
+
if (!exchangePromise) {
|
|
1220
|
+
exchangePromise = (async () => {
|
|
1221
|
+
const response = await client.exchangeHostedAuthCode({
|
|
1222
|
+
code: callback.code,
|
|
1223
|
+
code_verifier: codeVerifier,
|
|
1224
|
+
client_id: hostedAuthConfig.clientId,
|
|
1225
|
+
redirect_uri: hostedAuthConfig.redirectUri
|
|
1226
|
+
});
|
|
1227
|
+
const session = buildHostedAuthSession(response, hostedAuthConfig);
|
|
1228
|
+
setHostedAuthSession(session);
|
|
1229
|
+
clearPendingLogin();
|
|
1230
|
+
cleanupCallbackUrl();
|
|
1231
|
+
return session;
|
|
1232
|
+
})();
|
|
1233
|
+
hostedAuthExchangeInflight.set(exchangeKey, exchangePromise);
|
|
1234
|
+
}
|
|
1235
|
+
try {
|
|
1236
|
+
return await exchangePromise;
|
|
1237
|
+
} finally {
|
|
1238
|
+
if (hostedAuthExchangeInflight.get(exchangeKey) === exchangePromise) {
|
|
1239
|
+
hostedAuthExchangeInflight.delete(exchangeKey);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
} catch (completionError) {
|
|
1243
|
+
const normalizedError = normalizeHostedAuthError(completionError);
|
|
1244
|
+
setError(normalizedError);
|
|
1245
|
+
throw normalizedError;
|
|
1246
|
+
} finally {
|
|
1247
|
+
completionInflight.current = null;
|
|
1248
|
+
setIsLoading(false);
|
|
1249
|
+
}
|
|
1250
|
+
})();
|
|
1251
|
+
completionInflight.current = completionPromise;
|
|
1252
|
+
return completionPromise;
|
|
1253
|
+
}, [clearPendingLogin, client, hostedAuthConfig, pendingStorageKey, setHostedAuthSession]);
|
|
1254
|
+
const logout = react.useCallback(async () => {
|
|
1255
|
+
setError(null);
|
|
1256
|
+
try {
|
|
1257
|
+
if (hostedAuthSession) {
|
|
1258
|
+
await client.logoutJwtSession({
|
|
1259
|
+
refresh_token: hostedAuthSession.refreshToken
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
} finally {
|
|
1263
|
+
clearHostedAuthSession();
|
|
1264
|
+
}
|
|
1265
|
+
}, [clearHostedAuthSession, client, hostedAuthSession]);
|
|
1266
|
+
const refresh = react.useCallback(async () => {
|
|
1267
|
+
setIsLoading(true);
|
|
1268
|
+
setError(null);
|
|
1269
|
+
try {
|
|
1270
|
+
return await refreshHostedAuthSession();
|
|
1271
|
+
} catch (refreshError) {
|
|
1272
|
+
const normalizedError = refreshError instanceof Error ? refreshError : new HostedAuthError("Hosted authentication refresh failed.");
|
|
1273
|
+
setError(normalizedError);
|
|
1274
|
+
throw normalizedError;
|
|
1275
|
+
} finally {
|
|
1276
|
+
setIsLoading(false);
|
|
1277
|
+
}
|
|
1278
|
+
}, [refreshHostedAuthSession]);
|
|
1279
|
+
return {
|
|
1280
|
+
session: hostedAuthSession,
|
|
1281
|
+
isAuthenticated: !!hostedAuthSession,
|
|
1282
|
+
isLoading,
|
|
1283
|
+
error,
|
|
1284
|
+
login,
|
|
1285
|
+
completeLogin,
|
|
1286
|
+
logout,
|
|
1287
|
+
refresh
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
function cn(...inputs) {
|
|
1291
|
+
return tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
1292
|
+
}
|
|
1293
|
+
function formatTokenAmount(amount, decimals = 18) {
|
|
1294
|
+
const value = typeof amount === "string" ? BigInt(amount) : amount;
|
|
1295
|
+
const divisor = BigInt(10 ** decimals);
|
|
1296
|
+
const integerPart = value / divisor;
|
|
1297
|
+
const fractionalPart = value % divisor;
|
|
1298
|
+
const fractionalStr = fractionalPart.toString().padStart(decimals, "0");
|
|
1299
|
+
const twoDecimals = fractionalStr.slice(0, 2).padEnd(2, "0");
|
|
1300
|
+
const integerWithSpaces = integerPart.toString().replace(/\B(?=(\d{3})+(?!\d))/g, "\u2009");
|
|
1301
|
+
return `${integerWithSpaces}.${twoDecimals}`;
|
|
1302
|
+
}
|
|
1303
|
+
function parseTokenAmount(amount, decimals = 18) {
|
|
1304
|
+
const sanitized = amount.replace(/[\s\u2009]/g, "").replace(/,/g, ".");
|
|
1305
|
+
const lastDot = sanitized.lastIndexOf(".");
|
|
1306
|
+
const integerPart = lastDot === -1 ? sanitized : sanitized.slice(0, lastDot).replace(/\./g, "");
|
|
1307
|
+
const fractionalPart = lastDot === -1 ? "" : sanitized.slice(lastDot + 1);
|
|
1308
|
+
const paddedFractional = fractionalPart.padEnd(decimals, "0").slice(0, decimals);
|
|
1309
|
+
return BigInt(integerPart + paddedFractional);
|
|
1310
|
+
}
|
|
1311
|
+
function shortenAddress(address, chars = 4) {
|
|
1312
|
+
if (!address || address.length < chars * 2 + 2) return address;
|
|
1313
|
+
return `${address.slice(0, chars + 2)}...${address.slice(-chars)}`;
|
|
1314
|
+
}
|
|
1315
|
+
function formatTimeRemaining(expiryTimestamp) {
|
|
1316
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1317
|
+
const diff = expiryTimestamp - now;
|
|
1318
|
+
if (diff <= 0) return "Expired";
|
|
1319
|
+
const days = Math.floor(diff / 86400);
|
|
1320
|
+
const hours = Math.floor(diff % 86400 / 3600);
|
|
1321
|
+
const minutes = Math.floor(diff % 3600 / 60);
|
|
1322
|
+
if (days > 0) {
|
|
1323
|
+
return hours > 0 ? `${days}d ${hours}h left` : `${days}d left`;
|
|
1324
|
+
}
|
|
1325
|
+
if (hours > 0) {
|
|
1326
|
+
return minutes > 0 ? `${hours}h ${minutes}m left` : `${hours}h left`;
|
|
1327
|
+
}
|
|
1328
|
+
return `${minutes}m left`;
|
|
1329
|
+
}
|
|
1330
|
+
var defaultResult = {
|
|
1331
|
+
address: void 0,
|
|
1332
|
+
isConnected: false
|
|
1333
|
+
};
|
|
1334
|
+
function useSafeAccount() {
|
|
1335
|
+
const context = react.useContext(wagmi.WagmiContext);
|
|
1336
|
+
const cacheRef = react.useRef(defaultResult);
|
|
1337
|
+
const subscribe = react.useCallback(
|
|
1338
|
+
(onChange) => {
|
|
1339
|
+
if (!context) return () => {
|
|
1340
|
+
};
|
|
1341
|
+
return actions.watchAccount(context, { onChange });
|
|
1342
|
+
},
|
|
1343
|
+
[context]
|
|
1344
|
+
);
|
|
1345
|
+
const getSnapshot = react.useCallback(() => {
|
|
1346
|
+
if (!context) return defaultResult;
|
|
1347
|
+
const account = actions.getAccount(context);
|
|
1348
|
+
if (cacheRef.current.address !== account.address || cacheRef.current.isConnected !== account.isConnected) {
|
|
1349
|
+
cacheRef.current = { address: account.address, isConnected: account.isConnected };
|
|
1350
|
+
}
|
|
1351
|
+
return cacheRef.current;
|
|
1352
|
+
}, [context]);
|
|
1353
|
+
const getServerSnapshot = react.useCallback(() => defaultResult, []);
|
|
1354
|
+
return react.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
// src/sdk/hooks/use-private-read-request.ts
|
|
1358
|
+
var AUTH_CLOCK_SKEW_MS = 3e4;
|
|
1359
|
+
var INITIAL_AUTH_BACKOFF_MS = 5e3;
|
|
1360
|
+
var MAX_AUTH_BACKOFF_MS = 6e4;
|
|
1361
|
+
var DEFAULT_SIWE_AUTH_VALIDITY_MS = 24 * 60 * 60 * 1e3;
|
|
1362
|
+
var PRIVATE_READ_STATEMENT = "Sign in to Privana to access private account data.";
|
|
1363
|
+
var privateReadTokenCache = /* @__PURE__ */ new Map();
|
|
1364
|
+
var privateReadFailureCache = /* @__PURE__ */ new Map();
|
|
1365
|
+
var privateReadInflight = /* @__PURE__ */ new Map();
|
|
1366
|
+
async function executeHostedAuthPrivateReadRequest({
|
|
1367
|
+
client,
|
|
1368
|
+
hostedAuthSession,
|
|
1369
|
+
refreshHostedAuthSession,
|
|
1370
|
+
request
|
|
1371
|
+
}) {
|
|
1372
|
+
const ensureHostedAuth = async (forceRefresh) => {
|
|
1373
|
+
if (!hostedAuthSession) {
|
|
1374
|
+
throw new HostedAuthRequiredError();
|
|
1375
|
+
}
|
|
1376
|
+
if (!forceRefresh && isHostedAuthSessionActive(hostedAuthSession)) {
|
|
1377
|
+
client.clearPrivateReadToken();
|
|
1378
|
+
client.setBearerToken(hostedAuthSession.accessToken);
|
|
1379
|
+
return hostedAuthSession.accessToken;
|
|
1380
|
+
}
|
|
1381
|
+
const refreshed = await refreshHostedAuthSession();
|
|
1382
|
+
client.clearPrivateReadToken();
|
|
1383
|
+
client.setBearerToken(refreshed.accessToken);
|
|
1384
|
+
return refreshed.accessToken;
|
|
1385
|
+
};
|
|
1386
|
+
await ensureHostedAuth(false);
|
|
1387
|
+
try {
|
|
1388
|
+
return await request();
|
|
1389
|
+
} catch (error) {
|
|
1390
|
+
if (!(error instanceof AccountingApiError) || error.statusCode !== 401) {
|
|
1391
|
+
throw error;
|
|
1392
|
+
}
|
|
1393
|
+
await ensureHostedAuth(true);
|
|
1394
|
+
return request();
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
function createScopeKey(apiUrl, deploymentChainId, address) {
|
|
1398
|
+
return `${apiUrl.replace(/\/$/, "")}:${deploymentChainId}:${address.toLowerCase()}`;
|
|
1399
|
+
}
|
|
1400
|
+
function getCachedPrivateReadToken(scopeKey) {
|
|
1401
|
+
const cached = privateReadTokenCache.get(scopeKey);
|
|
1402
|
+
if (!cached) return null;
|
|
1403
|
+
if (cached.expiresAt <= Date.now() + AUTH_CLOCK_SKEW_MS) {
|
|
1404
|
+
privateReadTokenCache.delete(scopeKey);
|
|
1405
|
+
return null;
|
|
1406
|
+
}
|
|
1407
|
+
return cached.token;
|
|
1408
|
+
}
|
|
1409
|
+
function clearPrivateReadScope(scopeKey, client) {
|
|
1410
|
+
privateReadTokenCache.delete(scopeKey);
|
|
1411
|
+
privateReadFailureCache.delete(scopeKey);
|
|
1412
|
+
client.clearPrivateReadToken();
|
|
1413
|
+
}
|
|
1414
|
+
function recordPrivateReadFailure(scopeKey) {
|
|
1415
|
+
const previous = privateReadFailureCache.get(scopeKey);
|
|
1416
|
+
const backoffMs = Math.min(
|
|
1417
|
+
previous ? previous.backoffMs * 2 : INITIAL_AUTH_BACKOFF_MS,
|
|
1418
|
+
MAX_AUTH_BACKOFF_MS
|
|
1419
|
+
);
|
|
1420
|
+
privateReadFailureCache.set(scopeKey, {
|
|
1421
|
+
backoffMs,
|
|
1422
|
+
retryAt: Date.now() + backoffMs
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1425
|
+
function ensureFailureBackoff(scopeKey) {
|
|
1426
|
+
const failure = privateReadFailureCache.get(scopeKey);
|
|
1427
|
+
if (!failure) return;
|
|
1428
|
+
if (failure.retryAt <= Date.now()) {
|
|
1429
|
+
privateReadFailureCache.delete(scopeKey);
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
throw new Error(
|
|
1433
|
+
`Private-read authentication is temporarily paused after a recent failure. Retry in ${Math.ceil(
|
|
1434
|
+
(failure.retryAt - Date.now()) / 1e3
|
|
1435
|
+
)}s.`
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1438
|
+
function usePrivateReadRequest() {
|
|
1439
|
+
const wagmiContext = react.useContext(wagmi.WagmiContext);
|
|
1440
|
+
const { client, networkConfig, hostedAuthConfig, hostedAuthSession, refreshHostedAuthSession } = usePrivanaContext();
|
|
1441
|
+
const { address: walletAddress } = useSafeAccount();
|
|
1442
|
+
const privateReadAddress = hostedAuthConfig ? hostedAuthSession?.address ?? null : walletAddress ?? null;
|
|
1443
|
+
const privateReadReady = hostedAuthConfig ? !!hostedAuthSession : !!walletAddress;
|
|
1444
|
+
const executePrivateRead = react.useCallback(
|
|
1445
|
+
async (request) => {
|
|
1446
|
+
if (hostedAuthConfig) {
|
|
1447
|
+
return executeHostedAuthPrivateReadRequest({
|
|
1448
|
+
client,
|
|
1449
|
+
hostedAuthSession,
|
|
1450
|
+
refreshHostedAuthSession,
|
|
1451
|
+
request
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
if (!wagmiContext) {
|
|
1455
|
+
throw new Error("WagmiProvider is required for authenticated private reads");
|
|
1456
|
+
}
|
|
1457
|
+
if (!walletAddress) {
|
|
1458
|
+
throw new Error("No wallet connected");
|
|
1459
|
+
}
|
|
1460
|
+
const walletClient = await actions.getWalletClient(wagmiContext);
|
|
1461
|
+
if (!walletClient) {
|
|
1462
|
+
throw new Error("No wallet client available");
|
|
1463
|
+
}
|
|
1464
|
+
const apiUrl = networkConfig.apiUrl;
|
|
1465
|
+
const scopeKey = createScopeKey(apiUrl, networkConfig.chainId, walletAddress);
|
|
1466
|
+
const getToken = async (forceRefresh) => {
|
|
1467
|
+
const inflight = privateReadInflight.get(scopeKey);
|
|
1468
|
+
if (inflight) {
|
|
1469
|
+
const token = await inflight;
|
|
1470
|
+
client.setPrivateReadToken(token);
|
|
1471
|
+
return token;
|
|
1472
|
+
}
|
|
1473
|
+
if (!forceRefresh) {
|
|
1474
|
+
const cached = getCachedPrivateReadToken(scopeKey);
|
|
1475
|
+
if (cached) {
|
|
1476
|
+
client.setPrivateReadToken(cached);
|
|
1477
|
+
return cached;
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
ensureFailureBackoff(scopeKey);
|
|
1481
|
+
const authPromise = (async () => {
|
|
1482
|
+
try {
|
|
1483
|
+
const [{ domain }, nonceResponse] = await Promise.all([
|
|
1484
|
+
client.getSiweDomain(),
|
|
1485
|
+
client.getSiweNonce(walletAddress)
|
|
1486
|
+
]);
|
|
1487
|
+
const issuedAt = /* @__PURE__ */ new Date();
|
|
1488
|
+
const expirationTime = new Date(issuedAt.getTime() + DEFAULT_SIWE_AUTH_VALIDITY_MS);
|
|
1489
|
+
const uri = typeof window !== "undefined" && window.location.origin ? window.location.origin : apiUrl;
|
|
1490
|
+
const message = siwe.createSiweMessage({
|
|
1491
|
+
address: walletAddress,
|
|
1492
|
+
chainId: walletClient.chain?.id ?? networkConfig.chainId,
|
|
1493
|
+
domain,
|
|
1494
|
+
expirationTime,
|
|
1495
|
+
issuedAt,
|
|
1496
|
+
nonce: nonceResponse.nonce,
|
|
1497
|
+
statement: PRIVATE_READ_STATEMENT,
|
|
1498
|
+
uri,
|
|
1499
|
+
version: "1"
|
|
1500
|
+
});
|
|
1501
|
+
const signature = await walletClient.signMessage({
|
|
1502
|
+
account: walletClient.account ?? walletAddress,
|
|
1503
|
+
message
|
|
1504
|
+
});
|
|
1505
|
+
const login = await client.loginWithSiwe({
|
|
1506
|
+
siwe_message: message,
|
|
1507
|
+
signature
|
|
1508
|
+
});
|
|
1509
|
+
privateReadTokenCache.set(scopeKey, {
|
|
1510
|
+
token: login.siwe_token,
|
|
1511
|
+
expiresAt: expirationTime.getTime()
|
|
1512
|
+
});
|
|
1513
|
+
privateReadFailureCache.delete(scopeKey);
|
|
1514
|
+
client.setPrivateReadToken(login.siwe_token);
|
|
1515
|
+
return login.siwe_token;
|
|
1516
|
+
} catch (error) {
|
|
1517
|
+
const authError = error instanceof Error ? error : new Error("Failed to authenticate private reads");
|
|
1518
|
+
clearPrivateReadScope(scopeKey, client);
|
|
1519
|
+
recordPrivateReadFailure(scopeKey);
|
|
1520
|
+
throw authError;
|
|
1521
|
+
} finally {
|
|
1522
|
+
privateReadInflight.delete(scopeKey);
|
|
1523
|
+
}
|
|
1524
|
+
})();
|
|
1525
|
+
privateReadInflight.set(scopeKey, authPromise);
|
|
1526
|
+
return authPromise;
|
|
1527
|
+
};
|
|
1528
|
+
await getToken(false);
|
|
1529
|
+
try {
|
|
1530
|
+
return await request();
|
|
1531
|
+
} catch (error) {
|
|
1532
|
+
if (!(error instanceof AccountingApiError) || error.statusCode !== 401) {
|
|
1533
|
+
throw error;
|
|
1534
|
+
}
|
|
1535
|
+
clearPrivateReadScope(scopeKey, client);
|
|
1536
|
+
await getToken(true);
|
|
1537
|
+
return request();
|
|
1538
|
+
}
|
|
1539
|
+
},
|
|
1540
|
+
[
|
|
1541
|
+
client,
|
|
1542
|
+
hostedAuthConfig,
|
|
1543
|
+
hostedAuthSession,
|
|
1544
|
+
networkConfig.apiUrl,
|
|
1545
|
+
networkConfig.chainId,
|
|
1546
|
+
refreshHostedAuthSession,
|
|
1547
|
+
walletAddress,
|
|
1548
|
+
wagmiContext
|
|
1549
|
+
]
|
|
1550
|
+
);
|
|
1551
|
+
const privateReadQueryScope = react.useMemo(
|
|
1552
|
+
() => [networkConfig.apiUrl, networkConfig.chainId, privateReadAddress],
|
|
1553
|
+
[networkConfig.apiUrl, networkConfig.chainId, privateReadAddress]
|
|
1554
|
+
);
|
|
1555
|
+
return {
|
|
1556
|
+
executePrivateRead,
|
|
1557
|
+
privateReadAddress,
|
|
1558
|
+
privateReadReady,
|
|
1559
|
+
privateReadQueryScope
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
// src/sdk/hooks/use-balance.ts
|
|
1564
|
+
function useBalance(options = {}) {
|
|
1565
|
+
const queryClient = react.useContext(reactQuery.QueryClientContext);
|
|
1566
|
+
const accountingContext = useSafePrivanaContext();
|
|
1567
|
+
const hasProviders = !!queryClient && !!accountingContext;
|
|
1568
|
+
const client = accountingContext?.client;
|
|
1569
|
+
const defaultToken = accountingContext?.defaultToken;
|
|
1570
|
+
const pollingInterval = accountingContext?.pollingInterval ?? 1e4;
|
|
1571
|
+
const { executePrivateRead, privateReadAddress, privateReadQueryScope, privateReadReady } = usePrivateReadRequest();
|
|
1572
|
+
const tokenId = options.tokenId ?? defaultToken?.id;
|
|
1573
|
+
const query = reactQuery.useQuery({
|
|
1574
|
+
queryKey: ["accounting-balance", ...privateReadQueryScope, tokenId],
|
|
1575
|
+
queryFn: async () => {
|
|
1576
|
+
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
1577
|
+
if (!tokenId) throw new Error("No token ID provided");
|
|
1578
|
+
if (!client) throw new Error("No accounting client");
|
|
1579
|
+
return executePrivateRead(() => client.getBalance(tokenId));
|
|
1580
|
+
},
|
|
1581
|
+
enabled: hasProviders && (options.enabled ?? true) && privateReadReady && !!privateReadAddress && !!tokenId && !!client,
|
|
1582
|
+
refetchInterval: pollingInterval
|
|
1583
|
+
});
|
|
1584
|
+
const balanceWei = query.data?.balance ?? "0";
|
|
1585
|
+
return {
|
|
1586
|
+
balance: balanceWei,
|
|
1587
|
+
balanceWei,
|
|
1588
|
+
balanceFormatted: formatTokenAmount(balanceWei),
|
|
1589
|
+
tokenSymbol: query.data?.token_symbol ?? "",
|
|
1590
|
+
chainId: query.data?.chain_id ?? "",
|
|
1591
|
+
isLoading: query.isPending || query.isLoading,
|
|
1592
|
+
isError: query.isError,
|
|
1593
|
+
error: query.error,
|
|
1594
|
+
refetch: query.refetch
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
function useBatchBalances(options) {
|
|
1598
|
+
const { client, pollingInterval } = usePrivanaContext();
|
|
1599
|
+
const { executePrivateRead, privateReadAddress, privateReadQueryScope, privateReadReady } = usePrivateReadRequest();
|
|
1600
|
+
const query = reactQuery.useQuery({
|
|
1601
|
+
queryKey: ["accounting-batch-balances", ...privateReadQueryScope, options.tokenIds],
|
|
1602
|
+
queryFn: async () => {
|
|
1603
|
+
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
1604
|
+
return executePrivateRead(() => client.getBatchBalances({ token_ids: options.tokenIds }));
|
|
1605
|
+
},
|
|
1606
|
+
enabled: (options.enabled ?? true) && privateReadReady && !!privateReadAddress && options.tokenIds.length > 0 && !!client,
|
|
1607
|
+
refetchInterval: pollingInterval
|
|
1608
|
+
});
|
|
1609
|
+
return {
|
|
1610
|
+
balances: query.data?.balances ?? [],
|
|
1611
|
+
isLoading: query.isLoading,
|
|
1612
|
+
isError: query.isError,
|
|
1613
|
+
error: query.error,
|
|
1614
|
+
refetch: query.refetch
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
// ../../node_modules/@wagmi/core/dist/esm/utils/getAction.js
|
|
1619
|
+
function getAction(client, actionFn, name) {
|
|
1620
|
+
const action_implicit = client[actionFn.name];
|
|
1621
|
+
if (typeof action_implicit === "function")
|
|
1622
|
+
return action_implicit;
|
|
1623
|
+
const action_explicit = client[name];
|
|
1624
|
+
if (typeof action_explicit === "function")
|
|
1625
|
+
return action_explicit;
|
|
1626
|
+
return (params) => actionFn(client, params);
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
// ../../node_modules/@wagmi/core/dist/esm/actions/getChainId.js
|
|
1630
|
+
function getChainId2(config) {
|
|
1631
|
+
return config.state.chainId;
|
|
1632
|
+
}
|
|
1633
|
+
async function waitForTransactionReceipt(config, parameters) {
|
|
1634
|
+
const { chainId, timeout = 0, ...rest } = parameters;
|
|
1635
|
+
const client = config.getClient({ chainId });
|
|
1636
|
+
const action = getAction(client, actions$1.waitForTransactionReceipt, "waitForTransactionReceipt");
|
|
1637
|
+
const receipt = await action({ ...rest, timeout });
|
|
1638
|
+
if (receipt.status === "reverted") {
|
|
1639
|
+
const action_getTransaction = getAction(client, actions$1.getTransaction, "getTransaction");
|
|
1640
|
+
const { from: account, ...txn } = await action_getTransaction({
|
|
1641
|
+
hash: receipt.transactionHash
|
|
1642
|
+
});
|
|
1643
|
+
const action_call = getAction(client, actions$1.call, "call");
|
|
1644
|
+
const code = await action_call({
|
|
1645
|
+
...txn,
|
|
1646
|
+
account,
|
|
1647
|
+
data: txn.input,
|
|
1648
|
+
gasPrice: txn.type !== "eip1559" ? txn.gasPrice : void 0,
|
|
1649
|
+
maxFeePerGas: txn.type === "eip1559" ? txn.maxFeePerGas : void 0,
|
|
1650
|
+
maxPriorityFeePerGas: txn.type === "eip1559" ? txn.maxPriorityFeePerGas : void 0
|
|
1651
|
+
});
|
|
1652
|
+
const reason = code?.data ? viem.hexToString(`0x${code.data.substring(138)}`) : "unknown reason";
|
|
1653
|
+
throw new Error(reason);
|
|
1654
|
+
}
|
|
1655
|
+
return {
|
|
1656
|
+
...receipt,
|
|
1657
|
+
chainId: client.chain.id
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
function useEnsureCorrectChain() {
|
|
1661
|
+
const config = wagmi.useConfig();
|
|
1662
|
+
const chainId = wagmi.useChainId();
|
|
1663
|
+
const { switchChainAsync } = wagmi.useSwitchChain();
|
|
1664
|
+
const waitUntilOnChain = react.useCallback(
|
|
1665
|
+
async (expectedChainId, timeoutMs, pollIntervalMs = 250) => {
|
|
1666
|
+
const startedAt = Date.now();
|
|
1667
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
1668
|
+
const currentChainId = getChainId2(config);
|
|
1669
|
+
if (currentChainId === expectedChainId) return true;
|
|
1670
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
1671
|
+
}
|
|
1672
|
+
return false;
|
|
1673
|
+
},
|
|
1674
|
+
[config]
|
|
1675
|
+
);
|
|
1676
|
+
const ensureCorrectChain = react.useCallback(
|
|
1677
|
+
async (targetChainId) => {
|
|
1678
|
+
const currentChainId = getChainId2(config);
|
|
1679
|
+
if (currentChainId === targetChainId) return false;
|
|
1680
|
+
let switchErrorMessage;
|
|
1681
|
+
try {
|
|
1682
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
1683
|
+
setTimeout(() => reject(new Error("Chain switch timeout")), 3e3);
|
|
1684
|
+
});
|
|
1685
|
+
await Promise.race([switchChainAsync({ chainId: targetChainId }), timeoutPromise]);
|
|
1686
|
+
} catch (error) {
|
|
1687
|
+
if (error instanceof Error && error.message.includes("Unsupported Chain")) {
|
|
1688
|
+
console.warn("Got 'Unsupported Chain' error, chain may have switched anyway.");
|
|
1689
|
+
} else {
|
|
1690
|
+
switchErrorMessage = error instanceof Error ? error.message : "Unknown chain switch error";
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
const settled = await waitUntilOnChain(targetChainId, 2e4);
|
|
1694
|
+
if (settled) return true;
|
|
1695
|
+
if (switchErrorMessage) {
|
|
1696
|
+
throw new Error(
|
|
1697
|
+
`Failed to switch to chain (Chain ID: ${targetChainId}): ${switchErrorMessage}`
|
|
1698
|
+
);
|
|
1699
|
+
}
|
|
1700
|
+
throw new Error(`Chain switch did not settle in time (expected ${targetChainId}).`);
|
|
1701
|
+
},
|
|
1702
|
+
[config, switchChainAsync, waitUntilOnChain]
|
|
1703
|
+
);
|
|
1704
|
+
const isOnChain = react.useCallback((targetChainId) => chainId === targetChainId, [chainId]);
|
|
1705
|
+
return {
|
|
1706
|
+
chainId,
|
|
1707
|
+
ensureCorrectChain,
|
|
1708
|
+
isOnChain
|
|
1709
|
+
};
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
// src/sdk/hooks/use-deposit.ts
|
|
1713
|
+
function useDeposit(options = {}) {
|
|
1714
|
+
const { address } = wagmi.useAccount();
|
|
1715
|
+
const { client, enabledTokens, getChainById: getChainById2 } = usePrivanaContext();
|
|
1716
|
+
const { data: walletClient } = wagmi.useWalletClient();
|
|
1717
|
+
const queryClient = reactQuery.useQueryClient();
|
|
1718
|
+
const config = wagmi.useConfig();
|
|
1719
|
+
const { executePrivateRead } = usePrivateReadRequest();
|
|
1720
|
+
const pollInterval = options.pollInterval ?? 5e3;
|
|
1721
|
+
const pollTimeout = options.pollTimeout ?? 18e4;
|
|
1722
|
+
const confirmations = options.confirmations ?? 15;
|
|
1723
|
+
const [depositAddress, setDepositAddress] = react.useState(null);
|
|
1724
|
+
const [txHash, setTxHash] = react.useState();
|
|
1725
|
+
const [isSwitchingChain, setIsSwitchingChain] = react.useState(false);
|
|
1726
|
+
const [isWaitingForConfirmation, setIsWaitingForConfirmation] = react.useState(false);
|
|
1727
|
+
const [isWaitingForProcessing, setIsWaitingForProcessing] = react.useState(false);
|
|
1728
|
+
const [didTimeout, setDidTimeout] = react.useState(false);
|
|
1729
|
+
const [verificationFailed, setVerificationFailed] = react.useState(false);
|
|
1730
|
+
const [depositError, setDepositError] = react.useState(null);
|
|
1731
|
+
const generationRef = react.useRef(0);
|
|
1732
|
+
const pollIntervalRef = react.useRef(null);
|
|
1733
|
+
const verificationContextRef = react.useRef(null);
|
|
1734
|
+
const onDepositAddressReceivedRef = react.useRef(options.onDepositAddressReceived);
|
|
1735
|
+
const onDepositSuccessRef = react.useRef(options.onDepositSuccess);
|
|
1736
|
+
const onCreditedRef = react.useRef(options.onCredited);
|
|
1737
|
+
const onCheckTimeoutRef = react.useRef(options.onCheckTimeout);
|
|
1738
|
+
const onErrorRef = react.useRef(options.onError);
|
|
1739
|
+
react.useEffect(() => {
|
|
1740
|
+
onDepositAddressReceivedRef.current = options.onDepositAddressReceived;
|
|
1741
|
+
onDepositSuccessRef.current = options.onDepositSuccess;
|
|
1742
|
+
onCreditedRef.current = options.onCredited;
|
|
1743
|
+
onCheckTimeoutRef.current = options.onCheckTimeout;
|
|
1744
|
+
onErrorRef.current = options.onError;
|
|
1745
|
+
}, [
|
|
1746
|
+
options.onDepositAddressReceived,
|
|
1747
|
+
options.onDepositSuccess,
|
|
1748
|
+
options.onCredited,
|
|
1749
|
+
options.onCheckTimeout,
|
|
1750
|
+
options.onError
|
|
1751
|
+
]);
|
|
1752
|
+
const addressMutation = reactQuery.useMutation({
|
|
1753
|
+
mutationFn: async () => {
|
|
1754
|
+
if (!address) throw new Error("No wallet connected");
|
|
1755
|
+
return executePrivateRead(() => client.getDepositAddress());
|
|
1756
|
+
},
|
|
1757
|
+
onSuccess: (data) => {
|
|
1758
|
+
setDepositAddress(data);
|
|
1759
|
+
onDepositAddressReceivedRef.current?.(data);
|
|
1760
|
+
},
|
|
1761
|
+
onError: (error2) => {
|
|
1762
|
+
onErrorRef.current?.(error2);
|
|
1763
|
+
}
|
|
1764
|
+
});
|
|
1765
|
+
const {
|
|
1766
|
+
writeContractAsync,
|
|
1767
|
+
isPending: isWritingContract,
|
|
1768
|
+
error: writeError,
|
|
1769
|
+
reset: resetWriteContract
|
|
1770
|
+
} = wagmi.useWriteContract();
|
|
1771
|
+
const {
|
|
1772
|
+
sendTransactionAsync,
|
|
1773
|
+
isPending: isSendingNative,
|
|
1774
|
+
error: sendNativeError,
|
|
1775
|
+
reset: resetSendTransaction
|
|
1776
|
+
} = wagmi.useSendTransaction();
|
|
1777
|
+
const isSendingTx = isWritingContract || isSendingNative;
|
|
1778
|
+
const sendError = writeError ?? sendNativeError;
|
|
1779
|
+
const { ensureCorrectChain } = useEnsureCorrectChain();
|
|
1780
|
+
react.useEffect(() => {
|
|
1781
|
+
return () => {
|
|
1782
|
+
if (pollIntervalRef.current) {
|
|
1783
|
+
clearTimeout(pollIntervalRef.current);
|
|
1784
|
+
}
|
|
1785
|
+
};
|
|
1786
|
+
}, []);
|
|
1787
|
+
const stopPolling = react.useCallback(() => {
|
|
1788
|
+
if (pollIntervalRef.current) {
|
|
1789
|
+
clearTimeout(pollIntervalRef.current);
|
|
1790
|
+
pollIntervalRef.current = null;
|
|
1791
|
+
}
|
|
1792
|
+
}, []);
|
|
1793
|
+
const reset = react.useCallback(() => {
|
|
1794
|
+
generationRef.current++;
|
|
1795
|
+
stopPolling();
|
|
1796
|
+
verificationContextRef.current = null;
|
|
1797
|
+
setDepositAddress(null);
|
|
1798
|
+
setTxHash(void 0);
|
|
1799
|
+
setIsSwitchingChain(false);
|
|
1800
|
+
setIsWaitingForConfirmation(false);
|
|
1801
|
+
setIsWaitingForProcessing(false);
|
|
1802
|
+
setDidTimeout(false);
|
|
1803
|
+
setVerificationFailed(false);
|
|
1804
|
+
setDepositError(null);
|
|
1805
|
+
addressMutation.reset();
|
|
1806
|
+
resetWriteContract();
|
|
1807
|
+
resetSendTransaction();
|
|
1808
|
+
}, [addressMutation, resetWriteContract, resetSendTransaction, stopPolling]);
|
|
1809
|
+
const runVerification = react.useCallback(
|
|
1810
|
+
async (ctx, generation) => {
|
|
1811
|
+
const isStale = () => generation !== generationRef.current;
|
|
1812
|
+
const { hash, chainId, amount } = ctx;
|
|
1813
|
+
setVerificationFailed(false);
|
|
1814
|
+
setDepositError(null);
|
|
1815
|
+
setDidTimeout(false);
|
|
1816
|
+
setIsWaitingForProcessing(true);
|
|
1817
|
+
const pollStartTime = Date.now();
|
|
1818
|
+
const markVerificationFailed = (err) => {
|
|
1819
|
+
setIsWaitingForProcessing(false);
|
|
1820
|
+
setDepositError(err);
|
|
1821
|
+
setVerificationFailed(true);
|
|
1822
|
+
onErrorRef.current?.(err);
|
|
1823
|
+
};
|
|
1824
|
+
try {
|
|
1825
|
+
const triggerResult = await executePrivateRead(
|
|
1826
|
+
() => client.checkDeposit({
|
|
1827
|
+
chain_id: chainId,
|
|
1828
|
+
tx_hash: hash,
|
|
1829
|
+
amount: amount.toString()
|
|
1830
|
+
})
|
|
1831
|
+
);
|
|
1832
|
+
if (isStale()) return;
|
|
1833
|
+
if (triggerResult.status === "credited") {
|
|
1834
|
+
setIsWaitingForProcessing(false);
|
|
1835
|
+
verificationContextRef.current = null;
|
|
1836
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
|
|
1837
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
|
|
1838
|
+
onCreditedRef.current?.(hash, triggerResult);
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
if (triggerResult.status === "error") {
|
|
1842
|
+
markVerificationFailed(new Error(triggerResult.detail ?? "Deposit verification failed"));
|
|
1843
|
+
return;
|
|
1844
|
+
}
|
|
1845
|
+
const depositId = triggerResult.deposit_id;
|
|
1846
|
+
if (!depositId) {
|
|
1847
|
+
markVerificationFailed(new Error("Deposit check did not return a deposit id"));
|
|
1848
|
+
return;
|
|
1849
|
+
}
|
|
1850
|
+
let consecutiveFailures = 0;
|
|
1851
|
+
const checkStatus = async () => {
|
|
1852
|
+
if (isStale()) return true;
|
|
1853
|
+
if (Date.now() - pollStartTime > pollTimeout) {
|
|
1854
|
+
stopPolling();
|
|
1855
|
+
setIsWaitingForProcessing(false);
|
|
1856
|
+
setDidTimeout(true);
|
|
1857
|
+
onCheckTimeoutRef.current?.(hash);
|
|
1858
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
|
|
1859
|
+
return true;
|
|
1860
|
+
}
|
|
1861
|
+
try {
|
|
1862
|
+
const result = await executePrivateRead(() => client.getDepositStatus(depositId));
|
|
1863
|
+
if (isStale()) return true;
|
|
1864
|
+
consecutiveFailures = 0;
|
|
1865
|
+
if (result.status === "credited") {
|
|
1866
|
+
stopPolling();
|
|
1867
|
+
setIsWaitingForProcessing(false);
|
|
1868
|
+
verificationContextRef.current = null;
|
|
1869
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
|
|
1870
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
|
|
1871
|
+
onCreditedRef.current?.(hash, result);
|
|
1872
|
+
return true;
|
|
1873
|
+
}
|
|
1874
|
+
if (result.status === "error") {
|
|
1875
|
+
stopPolling();
|
|
1876
|
+
markVerificationFailed(new Error(result.detail ?? "Deposit verification failed"));
|
|
1877
|
+
return true;
|
|
1878
|
+
}
|
|
1879
|
+
} catch (err) {
|
|
1880
|
+
if (isStale()) return true;
|
|
1881
|
+
consecutiveFailures++;
|
|
1882
|
+
console.warn("Error polling deposit status:", err);
|
|
1883
|
+
if (consecutiveFailures >= 3) {
|
|
1884
|
+
stopPolling();
|
|
1885
|
+
markVerificationFailed(
|
|
1886
|
+
err instanceof Error ? err : new Error("Deposit status polling failed")
|
|
1887
|
+
);
|
|
1888
|
+
return true;
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
return false;
|
|
1892
|
+
};
|
|
1893
|
+
const pollLoop = async () => {
|
|
1894
|
+
const done = await checkStatus();
|
|
1895
|
+
if (!done && !isStale() && pollIntervalRef.current !== null) {
|
|
1896
|
+
pollIntervalRef.current = setTimeout(pollLoop, pollInterval);
|
|
1897
|
+
}
|
|
1898
|
+
};
|
|
1899
|
+
pollIntervalRef.current = setTimeout(pollLoop, pollInterval);
|
|
1900
|
+
} catch (err) {
|
|
1901
|
+
if (isStale()) return;
|
|
1902
|
+
stopPolling();
|
|
1903
|
+
markVerificationFailed(
|
|
1904
|
+
err instanceof Error ? err : new Error("Deposit verification failed")
|
|
1905
|
+
);
|
|
1906
|
+
}
|
|
1907
|
+
},
|
|
1908
|
+
[client, executePrivateRead, pollInterval, pollTimeout, queryClient, stopPolling]
|
|
1909
|
+
);
|
|
1910
|
+
const retryVerification = react.useCallback(async () => {
|
|
1911
|
+
const ctx = verificationContextRef.current;
|
|
1912
|
+
if (!ctx) {
|
|
1913
|
+
throw new Error("No pending deposit to verify");
|
|
1914
|
+
}
|
|
1915
|
+
generationRef.current++;
|
|
1916
|
+
stopPolling();
|
|
1917
|
+
const generation = generationRef.current;
|
|
1918
|
+
await runVerification(ctx, generation);
|
|
1919
|
+
}, [runVerification, stopPolling]);
|
|
1920
|
+
const deposit = react.useCallback(
|
|
1921
|
+
async (params) => {
|
|
1922
|
+
if (verificationContextRef.current) {
|
|
1923
|
+
throw new Error(
|
|
1924
|
+
"A deposit is pending verification. Call retryVerification() or reset() first."
|
|
1925
|
+
);
|
|
1926
|
+
}
|
|
1927
|
+
reset();
|
|
1928
|
+
const generation = generationRef.current;
|
|
1929
|
+
const isStale = () => generation !== generationRef.current;
|
|
1930
|
+
try {
|
|
1931
|
+
if (!address || !walletClient) throw new Error("No wallet connected");
|
|
1932
|
+
const token = enabledTokens.find((t) => t.id.toLowerCase() === params.tokenId.toLowerCase());
|
|
1933
|
+
if (!token) throw new Error(`Unknown token ID: ${params.tokenId}`);
|
|
1934
|
+
const sourceChain = getChainById2(token.chainId);
|
|
1935
|
+
if (!sourceChain) throw new Error(`Chain ${token.chainId} not configured`);
|
|
1936
|
+
const addrResponse = await addressMutation.mutateAsync();
|
|
1937
|
+
if (isStale()) return;
|
|
1938
|
+
setIsSwitchingChain(true);
|
|
1939
|
+
try {
|
|
1940
|
+
await ensureCorrectChain(sourceChain.id);
|
|
1941
|
+
} finally {
|
|
1942
|
+
if (!isStale()) setIsSwitchingChain(false);
|
|
1943
|
+
}
|
|
1944
|
+
if (isStale()) return;
|
|
1945
|
+
const depositAddr = addrResponse.deposit_address;
|
|
1946
|
+
if (!depositAddr || depositAddr === viem.zeroAddress) {
|
|
1947
|
+
throw new Error("Invalid deposit address received from API");
|
|
1948
|
+
}
|
|
1949
|
+
const isNative = token.contract === viem.zeroAddress;
|
|
1950
|
+
const minsForChain = addrResponse.min_deposit?.[String(sourceChain.id)];
|
|
1951
|
+
const minAmountStr = isNative ? minsForChain?.native : minsForChain?.erc20;
|
|
1952
|
+
if (minAmountStr && params.amount < BigInt(minAmountStr)) {
|
|
1953
|
+
throw new Error(
|
|
1954
|
+
`Amount is below the minimum deposit (${minAmountStr}) for ${isNative ? "native" : "ERC-20"} on chain ${sourceChain.id}`
|
|
1955
|
+
);
|
|
1956
|
+
}
|
|
1957
|
+
const hash = token.contract === viem.zeroAddress ? await sendTransactionAsync({
|
|
1958
|
+
to: depositAddr,
|
|
1959
|
+
value: params.amount,
|
|
1960
|
+
chainId: sourceChain.id
|
|
1961
|
+
}) : await writeContractAsync({
|
|
1962
|
+
address: token.contract,
|
|
1963
|
+
abi: viem.erc20Abi,
|
|
1964
|
+
functionName: "transfer",
|
|
1965
|
+
args: [depositAddr, params.amount],
|
|
1966
|
+
chainId: sourceChain.id
|
|
1967
|
+
});
|
|
1968
|
+
if (isStale()) return;
|
|
1969
|
+
setTxHash(hash);
|
|
1970
|
+
const ctx = {
|
|
1971
|
+
hash,
|
|
1972
|
+
chainId: sourceChain.id,
|
|
1973
|
+
amount: params.amount
|
|
1974
|
+
};
|
|
1975
|
+
verificationContextRef.current = ctx;
|
|
1976
|
+
try {
|
|
1977
|
+
setIsWaitingForConfirmation(true);
|
|
1978
|
+
try {
|
|
1979
|
+
await waitForTransactionReceipt(config, { hash, confirmations });
|
|
1980
|
+
} finally {
|
|
1981
|
+
if (!isStale()) setIsWaitingForConfirmation(false);
|
|
1982
|
+
}
|
|
1983
|
+
if (isStale()) return;
|
|
1984
|
+
onDepositSuccessRef.current?.(hash);
|
|
1985
|
+
queryClient.invalidateQueries({ queryKey: ["readContract"] });
|
|
1986
|
+
await runVerification(ctx, generation);
|
|
1987
|
+
} catch (err) {
|
|
1988
|
+
if (isStale()) return;
|
|
1989
|
+
setIsWaitingForConfirmation(false);
|
|
1990
|
+
stopPolling();
|
|
1991
|
+
const error2 = err instanceof Error ? err : new Error("Deposit verification failed");
|
|
1992
|
+
setIsWaitingForProcessing(false);
|
|
1993
|
+
setDepositError(error2);
|
|
1994
|
+
setVerificationFailed(true);
|
|
1995
|
+
onErrorRef.current?.(error2);
|
|
1996
|
+
}
|
|
1997
|
+
} catch (err) {
|
|
1998
|
+
if (isStale()) return;
|
|
1999
|
+
const error2 = err instanceof Error ? err : new Error("Deposit failed");
|
|
2000
|
+
setDepositError(error2);
|
|
2001
|
+
onErrorRef.current?.(error2);
|
|
2002
|
+
}
|
|
2003
|
+
},
|
|
2004
|
+
[
|
|
2005
|
+
address,
|
|
2006
|
+
walletClient,
|
|
2007
|
+
addressMutation,
|
|
2008
|
+
getChainById2,
|
|
2009
|
+
config,
|
|
2010
|
+
confirmations,
|
|
2011
|
+
enabledTokens,
|
|
2012
|
+
ensureCorrectChain,
|
|
2013
|
+
queryClient,
|
|
2014
|
+
writeContractAsync,
|
|
2015
|
+
sendTransactionAsync,
|
|
2016
|
+
stopPolling,
|
|
2017
|
+
reset,
|
|
2018
|
+
runVerification
|
|
2019
|
+
]
|
|
2020
|
+
);
|
|
2021
|
+
const isPending = addressMutation.isPending || isSwitchingChain || isSendingTx || isWaitingForConfirmation || isWaitingForProcessing;
|
|
2022
|
+
const error = addressMutation.error || sendError || depositError;
|
|
2023
|
+
return {
|
|
2024
|
+
depositAddress,
|
|
2025
|
+
txHash,
|
|
2026
|
+
isGettingAddress: addressMutation.isPending,
|
|
2027
|
+
isSwitchingChain,
|
|
2028
|
+
isSendingTransaction: isSendingTx,
|
|
2029
|
+
isWaitingForConfirmation,
|
|
2030
|
+
isWaitingForProcessing,
|
|
2031
|
+
didTimeout,
|
|
2032
|
+
verificationFailed,
|
|
2033
|
+
isPending,
|
|
2034
|
+
error,
|
|
2035
|
+
deposit,
|
|
2036
|
+
retryVerification,
|
|
2037
|
+
reset
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2040
|
+
function useWithdraw(options = {}) {
|
|
2041
|
+
const { address } = wagmi.useAccount();
|
|
2042
|
+
const { data: walletClient } = wagmi.useWalletClient();
|
|
2043
|
+
const { client, networkConfig } = usePrivanaContext();
|
|
2044
|
+
const queryClient = reactQuery.useQueryClient();
|
|
2045
|
+
const { chainId, ensureCorrectChain } = useEnsureCorrectChain();
|
|
2046
|
+
const pollInterval = options.pollInterval ?? 3e3;
|
|
2047
|
+
const pollTimeout = options.pollTimeout ?? 18e4;
|
|
2048
|
+
const [currentStep, setCurrentStep] = react.useState("idle");
|
|
2049
|
+
const [didTimeout, setDidTimeout] = react.useState(false);
|
|
2050
|
+
const [isSuccess, setIsSuccess] = react.useState(false);
|
|
2051
|
+
const [withdrawError, setWithdrawError] = react.useState(null);
|
|
2052
|
+
const generationRef = react.useRef(0);
|
|
2053
|
+
const pollIntervalRef = react.useRef(null);
|
|
2054
|
+
const onSubmitSuccessRef = react.useRef(options.onSubmitSuccess);
|
|
2055
|
+
const onProcessingSuccessRef = react.useRef(options.onProcessingSuccess);
|
|
2056
|
+
const onProcessingTimeoutRef = react.useRef(options.onProcessingTimeout);
|
|
2057
|
+
const onSuccessRef = react.useRef(options.onSuccess);
|
|
2058
|
+
const onErrorRef = react.useRef(options.onError);
|
|
2059
|
+
react.useEffect(() => {
|
|
2060
|
+
onSubmitSuccessRef.current = options.onSubmitSuccess;
|
|
2061
|
+
onProcessingSuccessRef.current = options.onProcessingSuccess;
|
|
2062
|
+
onProcessingTimeoutRef.current = options.onProcessingTimeout;
|
|
2063
|
+
onSuccessRef.current = options.onSuccess;
|
|
2064
|
+
onErrorRef.current = options.onError;
|
|
2065
|
+
}, [
|
|
2066
|
+
options.onSubmitSuccess,
|
|
2067
|
+
options.onProcessingSuccess,
|
|
2068
|
+
options.onProcessingTimeout,
|
|
2069
|
+
options.onSuccess,
|
|
2070
|
+
options.onError
|
|
2071
|
+
]);
|
|
2072
|
+
const { chainId: signingChainId } = networkConfig;
|
|
2073
|
+
react.useEffect(() => {
|
|
2074
|
+
return () => {
|
|
2075
|
+
if (pollIntervalRef.current) {
|
|
2076
|
+
clearTimeout(pollIntervalRef.current);
|
|
2077
|
+
}
|
|
2078
|
+
};
|
|
2079
|
+
}, []);
|
|
2080
|
+
const stopPolling = react.useCallback(() => {
|
|
2081
|
+
if (pollIntervalRef.current) {
|
|
2082
|
+
clearTimeout(pollIntervalRef.current);
|
|
2083
|
+
pollIntervalRef.current = null;
|
|
2084
|
+
}
|
|
2085
|
+
}, []);
|
|
2086
|
+
const reset = react.useCallback(() => {
|
|
2087
|
+
generationRef.current++;
|
|
2088
|
+
stopPolling();
|
|
2089
|
+
setCurrentStep("idle");
|
|
2090
|
+
setDidTimeout(false);
|
|
2091
|
+
setIsSuccess(false);
|
|
2092
|
+
setWithdrawError(null);
|
|
2093
|
+
}, [stopPolling]);
|
|
2094
|
+
const withdraw = react.useCallback(
|
|
2095
|
+
async (params) => {
|
|
2096
|
+
reset();
|
|
2097
|
+
const generation = generationRef.current;
|
|
2098
|
+
const isStale = () => generation !== generationRef.current;
|
|
2099
|
+
try {
|
|
2100
|
+
if (!address || !walletClient) throw new Error("Wallet not connected");
|
|
2101
|
+
const pendingResponse = await client.getPendingWithdrawals(address);
|
|
2102
|
+
if (isStale()) return void 0;
|
|
2103
|
+
const knownIndices = new Set(pendingResponse.pending_withdrawals.map((w) => w.index));
|
|
2104
|
+
if (chainId !== signingChainId) {
|
|
2105
|
+
setCurrentStep("switching-chain");
|
|
2106
|
+
}
|
|
2107
|
+
await ensureCorrectChain(signingChainId);
|
|
2108
|
+
if (isStale()) return void 0;
|
|
2109
|
+
const nonceResponse = await client.getWithdrawalNonce(address);
|
|
2110
|
+
if (isStale()) return void 0;
|
|
2111
|
+
const nonce = BigInt(nonceResponse.nonce);
|
|
2112
|
+
setCurrentStep("signing");
|
|
2113
|
+
const signature = await signWithdrawMessage({
|
|
2114
|
+
walletClient,
|
|
2115
|
+
chainId: signingChainId,
|
|
2116
|
+
verifyingContract: networkConfig.accountingContract,
|
|
2117
|
+
message: {
|
|
2118
|
+
tokenId: params.tokenId,
|
|
2119
|
+
amount: params.amount,
|
|
2120
|
+
nonce
|
|
2121
|
+
}
|
|
2122
|
+
});
|
|
2123
|
+
if (isStale()) return void 0;
|
|
2124
|
+
setCurrentStep("submitting");
|
|
2125
|
+
const submissionResponse = await client.requestWithdrawal({
|
|
2126
|
+
token_id: params.tokenId,
|
|
2127
|
+
amount: params.amount.toString(),
|
|
2128
|
+
nonce: String(nonce),
|
|
2129
|
+
signature
|
|
2130
|
+
});
|
|
2131
|
+
if (isStale()) return submissionResponse;
|
|
2132
|
+
onSubmitSuccessRef.current?.(submissionResponse);
|
|
2133
|
+
onSuccessRef.current?.(submissionResponse);
|
|
2134
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
|
|
2135
|
+
setCurrentStep("processing");
|
|
2136
|
+
const pollStartTime = Date.now();
|
|
2137
|
+
let withdrawalIndex = null;
|
|
2138
|
+
let consecutiveFailures = 0;
|
|
2139
|
+
const handleSuccess = () => {
|
|
2140
|
+
stopPolling();
|
|
2141
|
+
setCurrentStep("idle");
|
|
2142
|
+
setIsSuccess(true);
|
|
2143
|
+
onProcessingSuccessRef.current?.();
|
|
2144
|
+
queryClient.refetchQueries({ queryKey: ["accounting-balance"] });
|
|
2145
|
+
queryClient.refetchQueries({ queryKey: ["accounting-pending-withdrawals"] });
|
|
2146
|
+
};
|
|
2147
|
+
const handleTimeout = () => {
|
|
2148
|
+
stopPolling();
|
|
2149
|
+
setCurrentStep("idle");
|
|
2150
|
+
setDidTimeout(true);
|
|
2151
|
+
onProcessingTimeoutRef.current?.();
|
|
2152
|
+
queryClient.refetchQueries({ queryKey: ["accounting-balance"] });
|
|
2153
|
+
queryClient.refetchQueries({ queryKey: ["accounting-pending-withdrawals"] });
|
|
2154
|
+
};
|
|
2155
|
+
const checkWithdrawalStatus = async () => {
|
|
2156
|
+
if (isStale()) return true;
|
|
2157
|
+
if (Date.now() - pollStartTime > pollTimeout) {
|
|
2158
|
+
handleTimeout();
|
|
2159
|
+
return true;
|
|
2160
|
+
}
|
|
2161
|
+
try {
|
|
2162
|
+
if (withdrawalIndex !== null) {
|
|
2163
|
+
const info = await client.getWithdrawalInfo(withdrawalIndex);
|
|
2164
|
+
if (isStale()) return true;
|
|
2165
|
+
consecutiveFailures = 0;
|
|
2166
|
+
if (info.resolved) {
|
|
2167
|
+
handleSuccess();
|
|
2168
|
+
return true;
|
|
2169
|
+
}
|
|
2170
|
+
return false;
|
|
2171
|
+
}
|
|
2172
|
+
const pending = await client.getPendingWithdrawals(address);
|
|
2173
|
+
if (isStale()) return true;
|
|
2174
|
+
consecutiveFailures = 0;
|
|
2175
|
+
const match = pending.pending_withdrawals.find(
|
|
2176
|
+
(w) => !knownIndices.has(w.index) && w.user_address.toLowerCase() === address.toLowerCase() && w.token_id.toLowerCase() === params.tokenId.toLowerCase() && w.amount === String(params.amount)
|
|
2177
|
+
);
|
|
2178
|
+
if (match) {
|
|
2179
|
+
withdrawalIndex = match.index;
|
|
2180
|
+
if (match.resolved) {
|
|
2181
|
+
handleSuccess();
|
|
2182
|
+
return true;
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
} catch (err) {
|
|
2186
|
+
if (isStale()) return true;
|
|
2187
|
+
consecutiveFailures++;
|
|
2188
|
+
console.warn("Error polling withdrawal status:", err);
|
|
2189
|
+
if (consecutiveFailures >= 3) {
|
|
2190
|
+
handleTimeout();
|
|
2191
|
+
return true;
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
return false;
|
|
2195
|
+
};
|
|
2196
|
+
const pollLoop = async () => {
|
|
2197
|
+
const done = await checkWithdrawalStatus();
|
|
2198
|
+
if (!done && !isStale() && pollIntervalRef.current !== null) {
|
|
2199
|
+
pollIntervalRef.current = setTimeout(pollLoop, pollInterval);
|
|
2200
|
+
}
|
|
2201
|
+
};
|
|
2202
|
+
pollIntervalRef.current = setTimeout(pollLoop, 0);
|
|
2203
|
+
return submissionResponse;
|
|
2204
|
+
} catch (err) {
|
|
2205
|
+
if (isStale()) return void 0;
|
|
2206
|
+
const error = err instanceof Error ? err : new Error("Withdrawal failed");
|
|
2207
|
+
setCurrentStep("idle");
|
|
2208
|
+
setWithdrawError(error);
|
|
2209
|
+
onErrorRef.current?.(error);
|
|
2210
|
+
return void 0;
|
|
2211
|
+
}
|
|
2212
|
+
},
|
|
2213
|
+
[
|
|
2214
|
+
address,
|
|
2215
|
+
walletClient,
|
|
2216
|
+
client,
|
|
2217
|
+
chainId,
|
|
2218
|
+
signingChainId,
|
|
2219
|
+
networkConfig.accountingContract,
|
|
2220
|
+
ensureCorrectChain,
|
|
2221
|
+
pollInterval,
|
|
2222
|
+
pollTimeout,
|
|
2223
|
+
queryClient,
|
|
2224
|
+
stopPolling,
|
|
2225
|
+
reset
|
|
2226
|
+
]
|
|
2227
|
+
);
|
|
2228
|
+
const isPending = currentStep !== "idle";
|
|
2229
|
+
return {
|
|
2230
|
+
withdraw,
|
|
2231
|
+
isPending,
|
|
2232
|
+
isSuccess,
|
|
2233
|
+
currentStep,
|
|
2234
|
+
didTimeout,
|
|
2235
|
+
error: withdrawError,
|
|
2236
|
+
reset
|
|
2237
|
+
};
|
|
2238
|
+
}
|
|
2239
|
+
function useLockFunds(options = {}) {
|
|
2240
|
+
const { address } = wagmi.useAccount();
|
|
2241
|
+
const { data: walletClient } = wagmi.useWalletClient();
|
|
2242
|
+
const { client, networkConfig, serviceAddress } = usePrivanaContext();
|
|
2243
|
+
const queryClient = reactQuery.useQueryClient();
|
|
2244
|
+
const mutation = reactQuery.useMutation({
|
|
2245
|
+
mutationFn: async (params) => {
|
|
2246
|
+
if (!address || !walletClient) {
|
|
2247
|
+
throw new Error("Wallet not connected");
|
|
2248
|
+
}
|
|
2249
|
+
if (!serviceAddress) {
|
|
2250
|
+
throw new Error("Service address not configured");
|
|
2251
|
+
}
|
|
2252
|
+
const { nonce } = await client.getLockNonce(address);
|
|
2253
|
+
const signature = await signLockMessage({
|
|
2254
|
+
walletClient,
|
|
2255
|
+
chainId: networkConfig.chainId,
|
|
2256
|
+
verifyingContract: networkConfig.accountingContract,
|
|
2257
|
+
message: {
|
|
2258
|
+
serviceAddress,
|
|
2259
|
+
tokenId: params.tokenId,
|
|
2260
|
+
amount: params.amount,
|
|
2261
|
+
expiry: params.expiry,
|
|
2262
|
+
nonce: BigInt(nonce)
|
|
2263
|
+
}
|
|
2264
|
+
});
|
|
2265
|
+
return client.lockFunds({
|
|
2266
|
+
service_address: serviceAddress,
|
|
2267
|
+
token_id: params.tokenId,
|
|
2268
|
+
amount: params.amount.toString(),
|
|
2269
|
+
expiry: params.expiry.toString(),
|
|
2270
|
+
nonce: String(nonce),
|
|
2271
|
+
signature
|
|
2272
|
+
});
|
|
2273
|
+
},
|
|
2274
|
+
onSuccess: (data) => {
|
|
2275
|
+
options.onSuccess?.(data);
|
|
2276
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
|
|
2277
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-locked-funds"] });
|
|
2278
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-total-locked-balance"] });
|
|
2279
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
|
|
2280
|
+
},
|
|
2281
|
+
onError: (error) => {
|
|
2282
|
+
options.onError?.(error);
|
|
2283
|
+
}
|
|
2284
|
+
});
|
|
2285
|
+
const lockFunds = react.useCallback(
|
|
2286
|
+
async (params) => {
|
|
2287
|
+
return mutation.mutateAsync(params);
|
|
2288
|
+
},
|
|
2289
|
+
[mutation]
|
|
2290
|
+
);
|
|
2291
|
+
return {
|
|
2292
|
+
lockFunds,
|
|
2293
|
+
isPending: mutation.isPending,
|
|
2294
|
+
isSuccess: mutation.isSuccess,
|
|
2295
|
+
error: mutation.error,
|
|
2296
|
+
reset: mutation.reset
|
|
2297
|
+
};
|
|
2298
|
+
}
|
|
2299
|
+
function useUnlockFunds(options = {}) {
|
|
2300
|
+
const { address } = wagmi.useAccount();
|
|
2301
|
+
const { client } = usePrivanaContext();
|
|
2302
|
+
const queryClient = reactQuery.useQueryClient();
|
|
2303
|
+
const unlockMutation = reactQuery.useMutation({
|
|
2304
|
+
mutationFn: async (params) => {
|
|
2305
|
+
if (!address) {
|
|
2306
|
+
throw new Error("Wallet not connected");
|
|
2307
|
+
}
|
|
2308
|
+
return client.unlockFunds({
|
|
2309
|
+
user_address: address,
|
|
2310
|
+
lock_id: params.lockId
|
|
2311
|
+
});
|
|
2312
|
+
},
|
|
2313
|
+
onSuccess: (data) => {
|
|
2314
|
+
options.onSuccess?.(data);
|
|
2315
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
|
|
2316
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-locked-funds"] });
|
|
2317
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-total-locked-balance"] });
|
|
2318
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-expired-locks"] });
|
|
2319
|
+
},
|
|
2320
|
+
onError: (error) => {
|
|
2321
|
+
options.onError?.(error);
|
|
2322
|
+
}
|
|
2323
|
+
});
|
|
2324
|
+
const unlockAllMutation = reactQuery.useMutation({
|
|
2325
|
+
mutationFn: async () => {
|
|
2326
|
+
if (!address) {
|
|
2327
|
+
throw new Error("Wallet not connected");
|
|
2328
|
+
}
|
|
2329
|
+
return client.unlockAllExpired({
|
|
2330
|
+
user_address: address
|
|
2331
|
+
});
|
|
2332
|
+
},
|
|
2333
|
+
onSuccess: (data) => {
|
|
2334
|
+
options.onSuccess?.(data);
|
|
2335
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
|
|
2336
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-locked-funds"] });
|
|
2337
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-total-locked-balance"] });
|
|
2338
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-expired-locks"] });
|
|
2339
|
+
},
|
|
2340
|
+
onError: (error) => {
|
|
2341
|
+
options.onError?.(error);
|
|
2342
|
+
}
|
|
2343
|
+
});
|
|
2344
|
+
const unlockFunds = react.useCallback(
|
|
2345
|
+
async (params) => {
|
|
2346
|
+
return unlockMutation.mutateAsync(params);
|
|
2347
|
+
},
|
|
2348
|
+
[unlockMutation]
|
|
2349
|
+
);
|
|
2350
|
+
const unlockAllExpired = react.useCallback(async () => {
|
|
2351
|
+
return unlockAllMutation.mutateAsync();
|
|
2352
|
+
}, [unlockAllMutation]);
|
|
2353
|
+
const reset = react.useCallback(() => {
|
|
2354
|
+
unlockMutation.reset();
|
|
2355
|
+
unlockAllMutation.reset();
|
|
2356
|
+
}, [unlockMutation, unlockAllMutation]);
|
|
2357
|
+
return {
|
|
2358
|
+
unlockFunds,
|
|
2359
|
+
unlockAllExpired,
|
|
2360
|
+
isPending: unlockMutation.isPending || unlockAllMutation.isPending,
|
|
2361
|
+
isSuccess: unlockMutation.isSuccess || unlockAllMutation.isSuccess,
|
|
2362
|
+
error: unlockMutation.error || unlockAllMutation.error,
|
|
2363
|
+
reset
|
|
2364
|
+
};
|
|
2365
|
+
}
|
|
2366
|
+
function useTransfer(options = {}) {
|
|
2367
|
+
const { address } = wagmi.useAccount();
|
|
2368
|
+
const { data: walletClient } = wagmi.useWalletClient();
|
|
2369
|
+
const { client, networkConfig, serviceAddress } = usePrivanaContext();
|
|
2370
|
+
const queryClient = reactQuery.useQueryClient();
|
|
2371
|
+
const transferMutation = reactQuery.useMutation({
|
|
2372
|
+
mutationFn: async (params) => {
|
|
2373
|
+
if (!address || !walletClient) {
|
|
2374
|
+
throw new Error("Wallet not connected");
|
|
2375
|
+
}
|
|
2376
|
+
const { nonce } = await client.getTransferNonce(address);
|
|
2377
|
+
const signature = await signTransferMessage({
|
|
2378
|
+
walletClient,
|
|
2379
|
+
chainId: networkConfig.chainId,
|
|
2380
|
+
verifyingContract: networkConfig.accountingContract,
|
|
2381
|
+
message: {
|
|
2382
|
+
toAddress: params.toAddress,
|
|
2383
|
+
tokenId: params.tokenId,
|
|
2384
|
+
amount: params.amount,
|
|
2385
|
+
nonce: BigInt(nonce)
|
|
2386
|
+
}
|
|
2387
|
+
});
|
|
2388
|
+
return client.transferFunds({
|
|
2389
|
+
to_address: params.toAddress,
|
|
2390
|
+
token_id: params.tokenId,
|
|
2391
|
+
amount: params.amount.toString(),
|
|
2392
|
+
nonce: String(nonce),
|
|
2393
|
+
signature
|
|
2394
|
+
});
|
|
2395
|
+
},
|
|
2396
|
+
onSuccess: (data) => {
|
|
2397
|
+
options.onSuccess?.(data);
|
|
2398
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
|
|
2399
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
|
|
2400
|
+
},
|
|
2401
|
+
onError: (error) => {
|
|
2402
|
+
options.onError?.(error);
|
|
2403
|
+
}
|
|
2404
|
+
});
|
|
2405
|
+
const transferLockedMutation = reactQuery.useMutation({
|
|
2406
|
+
mutationFn: async (params) => {
|
|
2407
|
+
if (!address || !walletClient) {
|
|
2408
|
+
throw new Error("Wallet not connected");
|
|
2409
|
+
}
|
|
2410
|
+
if (!serviceAddress) {
|
|
2411
|
+
throw new Error("Service address not configured");
|
|
2412
|
+
}
|
|
2413
|
+
const { nonce } = await client.getTransferLockedNonce(serviceAddress);
|
|
2414
|
+
const signature = await signTransferLockedMessage({
|
|
2415
|
+
walletClient,
|
|
2416
|
+
chainId: networkConfig.chainId,
|
|
2417
|
+
verifyingContract: networkConfig.accountingContract,
|
|
2418
|
+
message: {
|
|
2419
|
+
userAddress: address,
|
|
2420
|
+
toAddress: params.toAddress,
|
|
2421
|
+
lockId: BigInt(params.lockId),
|
|
2422
|
+
amount: params.amount,
|
|
2423
|
+
nonce: BigInt(nonce),
|
|
2424
|
+
serviceAddress
|
|
2425
|
+
}
|
|
2426
|
+
});
|
|
2427
|
+
return client.transferLockedFunds({
|
|
2428
|
+
user_address: address,
|
|
2429
|
+
lock_id: params.lockId,
|
|
2430
|
+
to_address: params.toAddress,
|
|
2431
|
+
amount: params.amount.toString(),
|
|
2432
|
+
service_address: serviceAddress,
|
|
2433
|
+
nonce: String(nonce),
|
|
2434
|
+
signature
|
|
2435
|
+
});
|
|
2436
|
+
},
|
|
2437
|
+
onSuccess: (data) => {
|
|
2438
|
+
options.onSuccess?.(data);
|
|
2439
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
|
|
2440
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-locked-funds"] });
|
|
2441
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-total-locked-balance"] });
|
|
2442
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
|
|
2443
|
+
},
|
|
2444
|
+
onError: (error) => {
|
|
2445
|
+
options.onError?.(error);
|
|
2446
|
+
}
|
|
2447
|
+
});
|
|
2448
|
+
const transfer = react.useCallback(
|
|
2449
|
+
async (params) => {
|
|
2450
|
+
return transferMutation.mutateAsync(params);
|
|
2451
|
+
},
|
|
2452
|
+
[transferMutation]
|
|
2453
|
+
);
|
|
2454
|
+
const transferLocked = react.useCallback(
|
|
2455
|
+
async (params) => {
|
|
2456
|
+
return transferLockedMutation.mutateAsync(params);
|
|
2457
|
+
},
|
|
2458
|
+
[transferLockedMutation]
|
|
2459
|
+
);
|
|
2460
|
+
const reset = react.useCallback(() => {
|
|
2461
|
+
transferMutation.reset();
|
|
2462
|
+
transferLockedMutation.reset();
|
|
2463
|
+
}, [transferMutation, transferLockedMutation]);
|
|
2464
|
+
return {
|
|
2465
|
+
transfer,
|
|
2466
|
+
transferLocked,
|
|
2467
|
+
isPending: transferMutation.isPending || transferLockedMutation.isPending,
|
|
2468
|
+
isSuccess: transferMutation.isSuccess || transferLockedMutation.isSuccess,
|
|
2469
|
+
error: transferMutation.error || transferLockedMutation.error,
|
|
2470
|
+
reset
|
|
2471
|
+
};
|
|
2472
|
+
}
|
|
2473
|
+
function useLockedFunds(options = {}) {
|
|
2474
|
+
const { client, pollingInterval, serviceAddress } = usePrivanaContext();
|
|
2475
|
+
const { executePrivateRead, privateReadAddress, privateReadQueryScope, privateReadReady } = usePrivateReadRequest();
|
|
2476
|
+
const query = reactQuery.useQuery({
|
|
2477
|
+
queryKey: ["accounting-locked-funds", ...privateReadQueryScope, serviceAddress ?? null],
|
|
2478
|
+
queryFn: async () => {
|
|
2479
|
+
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
2480
|
+
return executePrivateRead(() => client.getLockedFunds(serviceAddress));
|
|
2481
|
+
},
|
|
2482
|
+
enabled: (options.enabled ?? true) && privateReadReady && !!privateReadAddress && !!client,
|
|
2483
|
+
refetchInterval: pollingInterval,
|
|
2484
|
+
placeholderData: reactQuery.keepPreviousData,
|
|
2485
|
+
staleTime: 5e3
|
|
2486
|
+
});
|
|
2487
|
+
return {
|
|
2488
|
+
locks: query.data?.locks ?? [],
|
|
2489
|
+
totalLocked: query.data?.total_locked ?? "0",
|
|
2490
|
+
isLoading: query.isLoading,
|
|
2491
|
+
isError: query.isError,
|
|
2492
|
+
error: query.error,
|
|
2493
|
+
refetch: query.refetch
|
|
2494
|
+
};
|
|
2495
|
+
}
|
|
2496
|
+
function useTotalLockedBalance(options = {}) {
|
|
2497
|
+
const { client, pollingInterval, defaultToken } = usePrivanaContext();
|
|
2498
|
+
const { executePrivateRead, privateReadAddress, privateReadQueryScope, privateReadReady } = usePrivateReadRequest();
|
|
2499
|
+
const tokenId = options.tokenId ?? defaultToken?.id;
|
|
2500
|
+
const query = reactQuery.useQuery({
|
|
2501
|
+
queryKey: ["accounting-total-locked-balance", ...privateReadQueryScope, tokenId],
|
|
2502
|
+
queryFn: async () => {
|
|
2503
|
+
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
2504
|
+
if (!tokenId) throw new Error("No token ID provided");
|
|
2505
|
+
return executePrivateRead(() => client.getTotalLockedBalance(tokenId));
|
|
2506
|
+
},
|
|
2507
|
+
enabled: (options.enabled ?? true) && privateReadReady && !!privateReadAddress && !!tokenId && !!client,
|
|
2508
|
+
refetchInterval: pollingInterval
|
|
2509
|
+
});
|
|
2510
|
+
return {
|
|
2511
|
+
totalLocked: query.data?.total_locked ?? "0",
|
|
2512
|
+
isLoading: query.isLoading,
|
|
2513
|
+
isError: query.isError,
|
|
2514
|
+
error: query.error,
|
|
2515
|
+
refetch: query.refetch
|
|
2516
|
+
};
|
|
2517
|
+
}
|
|
2518
|
+
function useExpiredLocks(options = {}) {
|
|
2519
|
+
const { client, pollingInterval } = usePrivanaContext();
|
|
2520
|
+
const { executePrivateRead, privateReadAddress, privateReadQueryScope, privateReadReady } = usePrivateReadRequest();
|
|
2521
|
+
const query = reactQuery.useQuery({
|
|
2522
|
+
queryKey: ["accounting-expired-locks", ...privateReadQueryScope],
|
|
2523
|
+
queryFn: async () => {
|
|
2524
|
+
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
2525
|
+
return executePrivateRead(() => client.getExpiredLocks());
|
|
2526
|
+
},
|
|
2527
|
+
enabled: (options.enabled ?? true) && privateReadReady && !!privateReadAddress && !!client,
|
|
2528
|
+
refetchInterval: pollingInterval
|
|
2529
|
+
});
|
|
2530
|
+
const expiredLocks = query.data?.expired_locks ?? [];
|
|
2531
|
+
return {
|
|
2532
|
+
expiredLocks,
|
|
2533
|
+
hasExpiredLocks: expiredLocks.length > 0,
|
|
2534
|
+
isLoading: query.isLoading,
|
|
2535
|
+
isError: query.isError,
|
|
2536
|
+
error: query.error,
|
|
2537
|
+
refetch: query.refetch
|
|
2538
|
+
};
|
|
2539
|
+
}
|
|
2540
|
+
function usePendingWithdrawals(options = {}) {
|
|
2541
|
+
const { address, isConnected } = wagmi.useAccount();
|
|
2542
|
+
const { client, pollingInterval } = usePrivanaContext();
|
|
2543
|
+
const query = reactQuery.useQuery({
|
|
2544
|
+
queryKey: ["accounting-pending-withdrawals", address],
|
|
2545
|
+
queryFn: async () => {
|
|
2546
|
+
if (!address) throw new Error("No wallet connected");
|
|
2547
|
+
try {
|
|
2548
|
+
return await client.getPendingWithdrawals(address);
|
|
2549
|
+
} catch (error) {
|
|
2550
|
+
if (error && typeof error === "object" && "statusCode" in error && error.statusCode === 404) {
|
|
2551
|
+
return { user_address: address, pending_withdrawals: [] };
|
|
2552
|
+
}
|
|
2553
|
+
throw error;
|
|
2554
|
+
}
|
|
2555
|
+
},
|
|
2556
|
+
enabled: (options.enabled ?? true) && isConnected && !!address,
|
|
2557
|
+
refetchInterval: pollingInterval,
|
|
2558
|
+
placeholderData: reactQuery.keepPreviousData,
|
|
2559
|
+
staleTime: 5e3,
|
|
2560
|
+
retry: (failureCount, error) => {
|
|
2561
|
+
if (error && typeof error === "object" && "statusCode" in error && error.statusCode === 404) {
|
|
2562
|
+
return false;
|
|
2563
|
+
}
|
|
2564
|
+
return failureCount < 3;
|
|
2565
|
+
}
|
|
2566
|
+
});
|
|
2567
|
+
const withdrawals = query.data?.pending_withdrawals ?? [];
|
|
2568
|
+
return {
|
|
2569
|
+
withdrawals,
|
|
2570
|
+
hasPendingWithdrawals: withdrawals.length > 0,
|
|
2571
|
+
isLoading: query.isLoading,
|
|
2572
|
+
isError: query.isError,
|
|
2573
|
+
error: query.error,
|
|
2574
|
+
refetch: query.refetch
|
|
2575
|
+
};
|
|
2576
|
+
}
|
|
2577
|
+
function useHistory(options = {}) {
|
|
2578
|
+
const { client, pollingInterval } = usePrivanaContext();
|
|
2579
|
+
const { executePrivateRead, privateReadAddress, privateReadQueryScope, privateReadReady } = usePrivateReadRequest();
|
|
2580
|
+
const offset = options.offset ?? -1;
|
|
2581
|
+
const limit = options.limit ?? 50;
|
|
2582
|
+
const query = reactQuery.useQuery({
|
|
2583
|
+
queryKey: ["accounting-history", ...privateReadQueryScope, offset, limit],
|
|
2584
|
+
queryFn: async () => {
|
|
2585
|
+
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
2586
|
+
return executePrivateRead(() => client.getHistory({ offset, limit }));
|
|
2587
|
+
},
|
|
2588
|
+
enabled: (options.enabled ?? true) && privateReadReady && !!privateReadAddress && !!client,
|
|
2589
|
+
refetchInterval: pollingInterval,
|
|
2590
|
+
placeholderData: reactQuery.keepPreviousData,
|
|
2591
|
+
staleTime: 5e3
|
|
2592
|
+
});
|
|
2593
|
+
return {
|
|
2594
|
+
history: query.data?.history ?? [],
|
|
2595
|
+
total: query.data?.total ?? 0,
|
|
2596
|
+
isLoading: query.isLoading,
|
|
2597
|
+
isError: query.isError,
|
|
2598
|
+
error: query.error,
|
|
2599
|
+
refetch: query.refetch
|
|
2600
|
+
};
|
|
2601
|
+
}
|
|
2602
|
+
function useTokenInfo(options = {}) {
|
|
2603
|
+
const { client } = usePrivanaContext();
|
|
2604
|
+
const { tokenId } = options;
|
|
2605
|
+
const query = reactQuery.useQuery({
|
|
2606
|
+
queryKey: ["accounting-token-info", tokenId],
|
|
2607
|
+
queryFn: () => {
|
|
2608
|
+
if (!tokenId) throw new Error("No token ID provided");
|
|
2609
|
+
return client.getTokenInfo(tokenId);
|
|
2610
|
+
},
|
|
2611
|
+
enabled: (options.enabled ?? true) && !!tokenId && !!client,
|
|
2612
|
+
staleTime: Infinity
|
|
2613
|
+
});
|
|
2614
|
+
return {
|
|
2615
|
+
data: query.data,
|
|
2616
|
+
isLoading: query.isLoading,
|
|
2617
|
+
isError: query.isError,
|
|
2618
|
+
error: query.error,
|
|
2619
|
+
refetch: query.refetch
|
|
2620
|
+
};
|
|
2621
|
+
}
|
|
2622
|
+
function useTokenList(options = {}) {
|
|
2623
|
+
const { client } = usePrivanaContext();
|
|
2624
|
+
const query = reactQuery.useQuery({
|
|
2625
|
+
queryKey: ["accounting-token-list"],
|
|
2626
|
+
queryFn: () => client.listTokens(),
|
|
2627
|
+
enabled: (options.enabled ?? true) && !!client,
|
|
2628
|
+
staleTime: Infinity
|
|
2629
|
+
});
|
|
2630
|
+
return {
|
|
2631
|
+
tokens: query.data?.tokens ?? [],
|
|
2632
|
+
isLoading: query.isLoading,
|
|
2633
|
+
isError: query.isError,
|
|
2634
|
+
error: query.error,
|
|
2635
|
+
refetch: query.refetch
|
|
2636
|
+
};
|
|
2637
|
+
}
|
|
2638
|
+
function useModifyLock(options = {}) {
|
|
2639
|
+
const { address } = wagmi.useAccount();
|
|
2640
|
+
const { data: walletClient } = wagmi.useWalletClient();
|
|
2641
|
+
const { client, networkConfig } = usePrivanaContext();
|
|
2642
|
+
const queryClient = reactQuery.useQueryClient();
|
|
2643
|
+
const mutation = reactQuery.useMutation({
|
|
2644
|
+
mutationFn: async (params) => {
|
|
2645
|
+
if (!address || !walletClient) {
|
|
2646
|
+
throw new Error("Wallet not connected");
|
|
2647
|
+
}
|
|
2648
|
+
const { nonce } = await client.getModifyLockNonce(address);
|
|
2649
|
+
const signature = await signModifyLockMessage({
|
|
2650
|
+
walletClient,
|
|
2651
|
+
chainId: networkConfig.chainId,
|
|
2652
|
+
verifyingContract: networkConfig.accountingContract,
|
|
2653
|
+
message: {
|
|
2654
|
+
lockId: BigInt(params.lockId),
|
|
2655
|
+
amount: params.amount,
|
|
2656
|
+
newExpiry: params.newExpiry,
|
|
2657
|
+
nonce: BigInt(nonce)
|
|
2658
|
+
}
|
|
2659
|
+
});
|
|
2660
|
+
return client.modifyLock({
|
|
2661
|
+
lock_id: params.lockId,
|
|
2662
|
+
amount: params.amount.toString(),
|
|
2663
|
+
new_expiry: params.newExpiry.toString(),
|
|
2664
|
+
nonce: String(nonce),
|
|
2665
|
+
signature
|
|
2666
|
+
});
|
|
2667
|
+
},
|
|
2668
|
+
onSuccess: (data) => {
|
|
2669
|
+
options.onSuccess?.(data);
|
|
2670
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
|
|
2671
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-locked-funds"] });
|
|
2672
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-total-locked-balance"] });
|
|
2673
|
+
},
|
|
2674
|
+
onError: (error) => {
|
|
2675
|
+
options.onError?.(error);
|
|
2676
|
+
}
|
|
2677
|
+
});
|
|
2678
|
+
const modifyLock = react.useCallback(
|
|
2679
|
+
async (params) => {
|
|
2680
|
+
return mutation.mutateAsync(params);
|
|
2681
|
+
},
|
|
2682
|
+
[mutation]
|
|
2683
|
+
);
|
|
2684
|
+
return {
|
|
2685
|
+
modifyLock,
|
|
2686
|
+
isPending: mutation.isPending,
|
|
2687
|
+
isSuccess: mutation.isSuccess,
|
|
2688
|
+
error: mutation.error,
|
|
2689
|
+
reset: mutation.reset
|
|
2690
|
+
};
|
|
2691
|
+
}
|
|
2692
|
+
var buttonVariants = classVarianceAuthority.cva(
|
|
2693
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
2694
|
+
{
|
|
2695
|
+
variants: {
|
|
2696
|
+
variant: {
|
|
2697
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
2698
|
+
destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
2699
|
+
outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
|
2700
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
2701
|
+
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
|
2702
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
2703
|
+
},
|
|
2704
|
+
size: {
|
|
2705
|
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
2706
|
+
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
|
2707
|
+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
2708
|
+
icon: "size-9",
|
|
2709
|
+
"icon-sm": "size-8",
|
|
2710
|
+
"icon-lg": "size-10"
|
|
2711
|
+
}
|
|
2712
|
+
},
|
|
2713
|
+
defaultVariants: {
|
|
2714
|
+
variant: "default",
|
|
2715
|
+
size: "default"
|
|
2716
|
+
}
|
|
2717
|
+
}
|
|
2718
|
+
);
|
|
2719
|
+
function Button({
|
|
2720
|
+
className,
|
|
2721
|
+
variant = "default",
|
|
2722
|
+
size = "default",
|
|
2723
|
+
asChild = false,
|
|
2724
|
+
...props
|
|
2725
|
+
}) {
|
|
2726
|
+
const Comp = asChild ? reactSlot.Slot : "button";
|
|
2727
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2728
|
+
Comp,
|
|
2729
|
+
{
|
|
2730
|
+
"data-slot": "button",
|
|
2731
|
+
"data-variant": variant,
|
|
2732
|
+
"data-size": size,
|
|
2733
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
2734
|
+
...props
|
|
2735
|
+
}
|
|
2736
|
+
);
|
|
2737
|
+
}
|
|
2738
|
+
function Dialog({ ...props }) {
|
|
2739
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DialogPrimitive__namespace.Root, { "data-slot": "dialog", ...props });
|
|
2740
|
+
}
|
|
2741
|
+
function DialogPortal({ ...props }) {
|
|
2742
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DialogPrimitive__namespace.Portal, { "data-slot": "dialog-portal", ...props });
|
|
2743
|
+
}
|
|
2744
|
+
function DialogOverlay({
|
|
2745
|
+
className,
|
|
2746
|
+
...props
|
|
2747
|
+
}) {
|
|
2748
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2749
|
+
DialogPrimitive__namespace.Overlay,
|
|
2750
|
+
{
|
|
2751
|
+
"data-slot": "dialog-overlay",
|
|
2752
|
+
"data-privana": true,
|
|
2753
|
+
className: cn(
|
|
2754
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
|
2755
|
+
className
|
|
2756
|
+
),
|
|
2757
|
+
...props
|
|
2758
|
+
}
|
|
2759
|
+
);
|
|
2760
|
+
}
|
|
2761
|
+
function DialogContent({
|
|
2762
|
+
className,
|
|
2763
|
+
children,
|
|
2764
|
+
showCloseButton = true,
|
|
2765
|
+
overlayClassName,
|
|
2766
|
+
...props
|
|
2767
|
+
}) {
|
|
2768
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(DialogPortal, { "data-slot": "dialog-portal", children: [
|
|
2769
|
+
/* @__PURE__ */ jsxRuntime.jsx(DialogOverlay, { className: overlayClassName }),
|
|
2770
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2771
|
+
DialogPrimitive__namespace.Content,
|
|
2772
|
+
{
|
|
2773
|
+
"data-slot": "dialog-content",
|
|
2774
|
+
"data-privana": true,
|
|
2775
|
+
className: cn(
|
|
2776
|
+
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
|
|
2777
|
+
className
|
|
2778
|
+
),
|
|
2779
|
+
...props,
|
|
2780
|
+
children: [
|
|
2781
|
+
children,
|
|
2782
|
+
showCloseButton && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2783
|
+
DialogPrimitive__namespace.Close,
|
|
2784
|
+
{
|
|
2785
|
+
"data-slot": "dialog-close",
|
|
2786
|
+
className: "text-muted-foreground hover:text-foreground absolute top-4 right-4 cursor-pointer rounded-xs transition-colors focus:outline-none disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
2787
|
+
children: [
|
|
2788
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.XIcon, {}),
|
|
2789
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: "Close" })
|
|
2790
|
+
]
|
|
2791
|
+
}
|
|
2792
|
+
)
|
|
2793
|
+
]
|
|
2794
|
+
}
|
|
2795
|
+
)
|
|
2796
|
+
] });
|
|
2797
|
+
}
|
|
2798
|
+
function DialogHeader({ className, ...props }) {
|
|
2799
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2800
|
+
"div",
|
|
2801
|
+
{
|
|
2802
|
+
"data-slot": "dialog-header",
|
|
2803
|
+
className: cn("flex flex-col gap-2 text-center sm:text-left", className),
|
|
2804
|
+
...props
|
|
2805
|
+
}
|
|
2806
|
+
);
|
|
2807
|
+
}
|
|
2808
|
+
function DialogTitle({ className, ...props }) {
|
|
2809
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2810
|
+
DialogPrimitive__namespace.Title,
|
|
2811
|
+
{
|
|
2812
|
+
"data-slot": "dialog-title",
|
|
2813
|
+
className: cn("text-lg leading-none font-semibold", className),
|
|
2814
|
+
...props
|
|
2815
|
+
}
|
|
2816
|
+
);
|
|
2817
|
+
}
|
|
2818
|
+
function DialogDescription({
|
|
2819
|
+
className,
|
|
2820
|
+
...props
|
|
2821
|
+
}) {
|
|
2822
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2823
|
+
DialogPrimitive__namespace.Description,
|
|
2824
|
+
{
|
|
2825
|
+
"data-slot": "dialog-description",
|
|
2826
|
+
className: cn("text-muted-foreground text-sm", className),
|
|
2827
|
+
...props
|
|
2828
|
+
}
|
|
2829
|
+
);
|
|
2830
|
+
}
|
|
2831
|
+
function ChevronRightIcon() {
|
|
2832
|
+
return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "10", height: "5", viewBox: "0 0 12 6", className: "-rotate-90", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2833
|
+
"path",
|
|
2834
|
+
{
|
|
2835
|
+
d: "M0 0l6 6 6-6",
|
|
2836
|
+
stroke: "currentColor",
|
|
2837
|
+
strokeWidth: "1.5",
|
|
2838
|
+
strokeLinecap: "round",
|
|
2839
|
+
strokeLinejoin: "round",
|
|
2840
|
+
fill: "none"
|
|
2841
|
+
}
|
|
2842
|
+
) });
|
|
2843
|
+
}
|
|
2844
|
+
function USDCIcon({ className, size = 24 }) {
|
|
2845
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2846
|
+
"svg",
|
|
2847
|
+
{
|
|
2848
|
+
width: size,
|
|
2849
|
+
height: size,
|
|
2850
|
+
viewBox: "0 0 2000 2000",
|
|
2851
|
+
fill: "none",
|
|
2852
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
2853
|
+
className,
|
|
2854
|
+
children: [
|
|
2855
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2856
|
+
"path",
|
|
2857
|
+
{
|
|
2858
|
+
d: "M1000 2000c554.17 0 1000-445.83 1000-1000S1554.17 0 1000 0 0 445.83 0 1000s445.83 1000 1000 1000z",
|
|
2859
|
+
fill: "#2775ca"
|
|
2860
|
+
}
|
|
2861
|
+
),
|
|
2862
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2863
|
+
"path",
|
|
2864
|
+
{
|
|
2865
|
+
d: "M1275 1158.33c0-145.83-87.5-195.83-262.5-216.66-125-16.67-150-50-150-108.34s41.67-95.83 125-95.83c75 0 116.67 25 137.5 87.5 4.17 12.5 16.67 20.83 29.17 20.83h66.66c16.67 0 29.17-12.5 29.17-29.16v-4.17c-16.67-91.67-91.67-162.5-187.5-170.83v-100c0-16.67-12.5-29.17-33.33-33.34h-62.5c-16.67 0-29.17 12.5-33.34 33.34v95.83c-125 16.67-204.16 100-204.16 204.17 0 137.5 83.33 191.66 258.33 212.5 116.67 20.83 154.17 45.83 154.17 112.5s-58.34 112.5-137.5 112.5c-108.34 0-145.84-45.84-158.34-108.34-4.16-16.66-16.66-25-29.16-25h-70.84c-16.66 0-29.16 12.5-29.16 29.17v4.17c16.66 104.16 83.33 179.16 220.83 200v100c0 16.66 12.5 29.16 33.33 33.33h62.5c16.67 0 29.17-12.5 33.34-33.33v-100c125-20.84 208.33-108.34 208.33-220.84z",
|
|
2866
|
+
fill: "#fff"
|
|
2867
|
+
}
|
|
2868
|
+
),
|
|
2869
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2870
|
+
"path",
|
|
2871
|
+
{
|
|
2872
|
+
d: "M787.5 1595.83c-325-116.66-491.67-479.16-370.83-800 62.5-175 200-308.33 370.83-370.83 16.67-8.33 25-20.83 25-41.67V325c0-16.67-8.33-29.17-25-33.33-4.17 0-12.5 0-16.67 4.16-395.83 125-612.5 545.84-487.5 941.67 75 233.33 254.17 412.5 487.5 487.5 16.67 8.33 33.34 0 37.5-16.67 4.17-4.16 4.17-8.33 4.17-16.66v-58.34c0-12.5-12.5-29.16-25-37.5zM1229.17 295.83c-16.67-8.33-33.34 0-37.5 16.67-4.17 4.17-4.17 8.33-4.17 16.67v58.33c0 16.67 12.5 33.33 25 41.67 325 116.66 491.67 479.16 370.83 800-62.5 175-200 308.33-370.83 370.83-16.67 8.33-25 20.83-25 41.67V1700c0 16.67 8.33 29.17 25 33.33 4.17 0 12.5 0 16.67-4.16 395.83-125 612.5-545.84 487.5-941.67-75-237.5-258.34-416.67-487.5-491.67z",
|
|
2873
|
+
fill: "#fff"
|
|
2874
|
+
}
|
|
2875
|
+
)
|
|
2876
|
+
]
|
|
2877
|
+
}
|
|
2878
|
+
);
|
|
2879
|
+
}
|
|
2880
|
+
function BaseIcon({ className, size = 24 }) {
|
|
2881
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2882
|
+
"svg",
|
|
2883
|
+
{
|
|
2884
|
+
width: size,
|
|
2885
|
+
height: size,
|
|
2886
|
+
viewBox: "0 0 2500 2500",
|
|
2887
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
2888
|
+
className,
|
|
2889
|
+
children: [
|
|
2890
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "1250", cy: "1250", r: "1250", fill: "#0052FF" }),
|
|
2891
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2892
|
+
"path",
|
|
2893
|
+
{
|
|
2894
|
+
d: "M1247.8,2500c691.6,0,1252.2-559.6,1252.2-1250C2500,559.6,1939.4,0,1247.8,0C591.7,0,53.5,503.8,0,1144.9h1655.1v210.2H0C53.5,1996.2,591.7,2500,1247.8,2500z",
|
|
2895
|
+
fill: "white"
|
|
2896
|
+
}
|
|
2897
|
+
)
|
|
2898
|
+
]
|
|
2899
|
+
}
|
|
2900
|
+
);
|
|
2901
|
+
}
|
|
2902
|
+
function WETHIcon({ className, size = 24 }) {
|
|
2903
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2904
|
+
"svg",
|
|
2905
|
+
{
|
|
2906
|
+
width: size,
|
|
2907
|
+
height: size,
|
|
2908
|
+
viewBox: "0 0 250 250",
|
|
2909
|
+
fill: "none",
|
|
2910
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
2911
|
+
className,
|
|
2912
|
+
children: [
|
|
2913
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2914
|
+
"path",
|
|
2915
|
+
{
|
|
2916
|
+
d: "M0 125C0 55.9644 55.9644 0 125 0C194.036 0 250 55.9644 250 125C250 194.036 194.036 250 125 250C55.9644 250 0 194.036 0 125Z",
|
|
2917
|
+
fill: "#627EEA"
|
|
2918
|
+
}
|
|
2919
|
+
),
|
|
2920
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2921
|
+
"path",
|
|
2922
|
+
{
|
|
2923
|
+
d: "M125.047 30.5V100.351L184.086 126.732L125.047 30.5Z",
|
|
2924
|
+
fill: "white",
|
|
2925
|
+
fillOpacity: "0.602"
|
|
2926
|
+
}
|
|
2927
|
+
),
|
|
2928
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M125.047 30.5L66 126.732L125.047 100.351V30.5Z", fill: "white" }),
|
|
2929
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2930
|
+
"path",
|
|
2931
|
+
{
|
|
2932
|
+
d: "M125.047 172V219.462L184.125 137.728L125.047 172Z",
|
|
2933
|
+
fill: "white",
|
|
2934
|
+
fillOpacity: "0.602"
|
|
2935
|
+
}
|
|
2936
|
+
),
|
|
2937
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M125.047 219.462V171.992L66 137.728L125.047 219.462Z", fill: "white" }),
|
|
2938
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2939
|
+
"path",
|
|
2940
|
+
{
|
|
2941
|
+
d: "M125.047 161.013L184.086 126.733L125.047 100.368V161.013Z",
|
|
2942
|
+
fill: "white",
|
|
2943
|
+
fillOpacity: "0.2"
|
|
2944
|
+
}
|
|
2945
|
+
),
|
|
2946
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M66 126.733L125.047 161.013V100.368L66 126.733Z", fill: "white", fillOpacity: "0.602" })
|
|
2947
|
+
]
|
|
2948
|
+
}
|
|
2949
|
+
);
|
|
2950
|
+
}
|
|
2951
|
+
function getTokenIcon(symbol, size) {
|
|
2952
|
+
const iconSize = size ?? 24;
|
|
2953
|
+
switch (symbol.toUpperCase()) {
|
|
2954
|
+
case "USDC":
|
|
2955
|
+
return /* @__PURE__ */ jsxRuntime.jsx(USDCIcon, { size: iconSize });
|
|
2956
|
+
case "WETH":
|
|
2957
|
+
return /* @__PURE__ */ jsxRuntime.jsx(WETHIcon, { size: iconSize });
|
|
2958
|
+
default:
|
|
2959
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2960
|
+
"div",
|
|
2961
|
+
{
|
|
2962
|
+
className: "bg-muted flex items-center justify-center rounded-full text-xs font-bold",
|
|
2963
|
+
style: { width: iconSize, height: iconSize },
|
|
2964
|
+
children: symbol.slice(0, 2)
|
|
2965
|
+
}
|
|
2966
|
+
);
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
function getChainIcon(chainId, size) {
|
|
2970
|
+
const iconSize = size ?? 24;
|
|
2971
|
+
switch (chainId) {
|
|
2972
|
+
case 84532:
|
|
2973
|
+
return /* @__PURE__ */ jsxRuntime.jsx(BaseIcon, { size: iconSize });
|
|
2974
|
+
default:
|
|
2975
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2976
|
+
"div",
|
|
2977
|
+
{
|
|
2978
|
+
className: "bg-muted flex items-center justify-center rounded-full text-xs font-bold",
|
|
2979
|
+
style: { width: iconSize, height: iconSize },
|
|
2980
|
+
children: "?"
|
|
2981
|
+
}
|
|
2982
|
+
);
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
function CheckIcon() {
|
|
2986
|
+
return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "14", height: "14", viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2987
|
+
"path",
|
|
2988
|
+
{
|
|
2989
|
+
d: "M2.5 6l2.5 2.5 4.5-4.5",
|
|
2990
|
+
stroke: "currentColor",
|
|
2991
|
+
strokeWidth: "1.5",
|
|
2992
|
+
strokeLinecap: "round",
|
|
2993
|
+
strokeLinejoin: "round"
|
|
2994
|
+
}
|
|
2995
|
+
) });
|
|
2996
|
+
}
|
|
2997
|
+
function ExternalLinkIcon() {
|
|
2998
|
+
return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2999
|
+
"path",
|
|
3000
|
+
{
|
|
3001
|
+
d: "M3.5 1.5h7v7M10.5 1.5L1.5 10.5",
|
|
3002
|
+
stroke: "currentColor",
|
|
3003
|
+
strokeWidth: "1.5",
|
|
3004
|
+
strokeLinecap: "round",
|
|
3005
|
+
strokeLinejoin: "round"
|
|
3006
|
+
}
|
|
3007
|
+
) });
|
|
3008
|
+
}
|
|
3009
|
+
function Spinner({ size = 20 }) {
|
|
3010
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 20 20", fill: "none", className: "animate-spin", children: [
|
|
3011
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "10", cy: "10", r: "8", stroke: "currentColor", strokeWidth: "2", opacity: "0.15" }),
|
|
3012
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10 2a8 8 0 016.93 4", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" })
|
|
3013
|
+
] });
|
|
3014
|
+
}
|
|
3015
|
+
function TransactionProgressView({ title, steps, onCancel }) {
|
|
3016
|
+
const completed = steps.filter((s) => s.status === "completed").length;
|
|
3017
|
+
const active = steps.find((s) => s.status === "active");
|
|
3018
|
+
const total = steps.length;
|
|
3019
|
+
const progress = (completed + (active ? 0.5 : 0)) / total * 100;
|
|
3020
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full flex-col gap-4", children: [
|
|
3021
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
3022
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-primary", children: /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 20 }) }),
|
|
3023
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-sm font-medium", children: active?.label ?? title })
|
|
3024
|
+
] }),
|
|
3025
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
|
|
3026
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-secondary h-1.5 w-full overflow-hidden rounded-full", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3027
|
+
"div",
|
|
3028
|
+
{
|
|
3029
|
+
className: "bg-primary h-full rounded-full transition-all duration-500",
|
|
3030
|
+
style: { width: `${progress}%` }
|
|
3031
|
+
}
|
|
3032
|
+
) }),
|
|
3033
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground text-xs", children: [
|
|
3034
|
+
"Step ",
|
|
3035
|
+
Math.max(1, completed + (active ? 1 : 0)),
|
|
3036
|
+
" of ",
|
|
3037
|
+
total
|
|
3038
|
+
] })
|
|
3039
|
+
] }),
|
|
3040
|
+
onCancel && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3041
|
+
"button",
|
|
3042
|
+
{
|
|
3043
|
+
onClick: onCancel,
|
|
3044
|
+
className: "border-border text-foreground hover:bg-secondary flex h-10 w-full cursor-pointer items-center justify-center rounded-[10px] border px-3 py-2 text-sm font-medium transition-colors",
|
|
3045
|
+
children: "Cancel"
|
|
3046
|
+
}
|
|
3047
|
+
)
|
|
3048
|
+
] });
|
|
3049
|
+
}
|
|
3050
|
+
function TransactionSuccessView({
|
|
3051
|
+
title,
|
|
3052
|
+
message,
|
|
3053
|
+
explorerUrl,
|
|
3054
|
+
explorerLabel,
|
|
3055
|
+
onDone
|
|
3056
|
+
}) {
|
|
3057
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full flex-col gap-4", children: [
|
|
3058
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
3059
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-emerald-500/10 text-emerald-400", children: /* @__PURE__ */ jsxRuntime.jsx(CheckIcon, {}) }),
|
|
3060
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-0.5", children: [
|
|
3061
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-sm font-medium", children: title }),
|
|
3062
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-xs", children: message })
|
|
3063
|
+
] })
|
|
3064
|
+
] }),
|
|
3065
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
3066
|
+
explorerUrl && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3067
|
+
"a",
|
|
3068
|
+
{
|
|
3069
|
+
href: explorerUrl,
|
|
3070
|
+
target: "_blank",
|
|
3071
|
+
rel: "noopener noreferrer",
|
|
3072
|
+
className: "border-border text-foreground hover:bg-secondary flex h-10 flex-1 items-center justify-center gap-1.5 rounded-[10px] border text-sm transition-colors",
|
|
3073
|
+
children: [
|
|
3074
|
+
explorerLabel ?? "View on Explorer",
|
|
3075
|
+
/* @__PURE__ */ jsxRuntime.jsx(ExternalLinkIcon, {})
|
|
3076
|
+
]
|
|
3077
|
+
}
|
|
3078
|
+
),
|
|
3079
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3080
|
+
"button",
|
|
3081
|
+
{
|
|
3082
|
+
onClick: onDone,
|
|
3083
|
+
className: "bg-primary text-primary-foreground hover:bg-primary/90 flex h-10 flex-1 cursor-pointer items-center justify-center rounded-[10px] px-3 py-2 text-sm font-medium transition-colors",
|
|
3084
|
+
children: "Done"
|
|
3085
|
+
}
|
|
3086
|
+
)
|
|
3087
|
+
] })
|
|
3088
|
+
] });
|
|
3089
|
+
}
|
|
3090
|
+
function WarningIcon() {
|
|
3091
|
+
return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3092
|
+
"path",
|
|
3093
|
+
{
|
|
3094
|
+
d: "M7 5v2.5M7 10h.005M6.13 2.5h1.74L13 11.5H1L6.13 2.5z",
|
|
3095
|
+
stroke: "currentColor",
|
|
3096
|
+
strokeWidth: "1.5",
|
|
3097
|
+
strokeLinecap: "round",
|
|
3098
|
+
strokeLinejoin: "round"
|
|
3099
|
+
}
|
|
3100
|
+
) });
|
|
3101
|
+
}
|
|
3102
|
+
function TransactionWarningView({ title, message, onDone }) {
|
|
3103
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full flex-col gap-4", children: [
|
|
3104
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
3105
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-amber-500/10 text-amber-400", children: /* @__PURE__ */ jsxRuntime.jsx(WarningIcon, {}) }),
|
|
3106
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-0.5", children: [
|
|
3107
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-sm font-medium", children: title }),
|
|
3108
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-xs", children: message })
|
|
3109
|
+
] })
|
|
3110
|
+
] }),
|
|
3111
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3112
|
+
"button",
|
|
3113
|
+
{
|
|
3114
|
+
onClick: onDone,
|
|
3115
|
+
className: "bg-primary text-primary-foreground hover:bg-primary/90 flex h-10 w-full cursor-pointer items-center justify-center rounded-[10px] px-3 py-2 text-sm font-medium transition-colors",
|
|
3116
|
+
children: "Done"
|
|
3117
|
+
}
|
|
3118
|
+
)
|
|
3119
|
+
] });
|
|
3120
|
+
}
|
|
3121
|
+
function TransactionErrorView({
|
|
3122
|
+
title,
|
|
3123
|
+
message,
|
|
3124
|
+
explorerUrl,
|
|
3125
|
+
explorerLabel,
|
|
3126
|
+
onRetry,
|
|
3127
|
+
onDismiss,
|
|
3128
|
+
isRetrying
|
|
3129
|
+
}) {
|
|
3130
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full flex-col gap-4", children: [
|
|
3131
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
3132
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-amber-500/10 text-amber-400", children: /* @__PURE__ */ jsxRuntime.jsx(WarningIcon, {}) }),
|
|
3133
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-0.5", children: [
|
|
3134
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-sm font-medium", children: title }),
|
|
3135
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-xs", children: message })
|
|
3136
|
+
] })
|
|
3137
|
+
] }),
|
|
3138
|
+
explorerUrl && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3139
|
+
"a",
|
|
3140
|
+
{
|
|
3141
|
+
href: explorerUrl,
|
|
3142
|
+
target: "_blank",
|
|
3143
|
+
rel: "noopener noreferrer",
|
|
3144
|
+
className: "border-border text-foreground hover:bg-secondary flex h-10 w-full items-center justify-center gap-1.5 rounded-[10px] border text-sm transition-colors",
|
|
3145
|
+
children: [
|
|
3146
|
+
explorerLabel ?? "View on Explorer",
|
|
3147
|
+
/* @__PURE__ */ jsxRuntime.jsx(ExternalLinkIcon, {})
|
|
3148
|
+
]
|
|
3149
|
+
}
|
|
3150
|
+
),
|
|
3151
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
3152
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3153
|
+
"button",
|
|
3154
|
+
{
|
|
3155
|
+
onClick: onDismiss,
|
|
3156
|
+
disabled: isRetrying,
|
|
3157
|
+
className: "border-border text-foreground hover:bg-secondary flex h-10 flex-1 cursor-pointer items-center justify-center rounded-[10px] border px-3 py-2 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50",
|
|
3158
|
+
children: "Close"
|
|
3159
|
+
}
|
|
3160
|
+
),
|
|
3161
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3162
|
+
"button",
|
|
3163
|
+
{
|
|
3164
|
+
onClick: onRetry,
|
|
3165
|
+
disabled: isRetrying,
|
|
3166
|
+
className: "bg-primary text-primary-foreground hover:bg-primary/90 flex h-10 flex-1 cursor-pointer items-center justify-center rounded-[10px] px-3 py-2 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50",
|
|
3167
|
+
children: isRetrying ? "Retrying..." : "Retry Verification"
|
|
3168
|
+
}
|
|
3169
|
+
)
|
|
3170
|
+
] })
|
|
3171
|
+
] });
|
|
3172
|
+
}
|
|
3173
|
+
function DepositForm({
|
|
3174
|
+
selectedToken,
|
|
3175
|
+
onTokenSelect,
|
|
3176
|
+
onPendingChange,
|
|
3177
|
+
onSuccess
|
|
3178
|
+
}) {
|
|
3179
|
+
const { isConnected, address } = wagmi.useAccount();
|
|
3180
|
+
const { chains, getChainById: getChainById2 } = usePrivanaContext();
|
|
3181
|
+
const [amount, setAmount] = react.useState("");
|
|
3182
|
+
const [showSuccess, setShowSuccess] = react.useState(false);
|
|
3183
|
+
const [showTimeout, setShowTimeout] = react.useState(false);
|
|
3184
|
+
const [cancelled, setCancelled] = react.useState(false);
|
|
3185
|
+
const targetChain = getChainById2(selectedToken.chainId) ?? chains[0];
|
|
3186
|
+
const isNative = selectedToken.contract === viem.zeroAddress;
|
|
3187
|
+
const { data: nativeBalanceData } = wagmi.useBalance({
|
|
3188
|
+
address,
|
|
3189
|
+
chainId: targetChain?.id,
|
|
3190
|
+
query: {
|
|
3191
|
+
enabled: !!address && isNative
|
|
3192
|
+
}
|
|
3193
|
+
});
|
|
3194
|
+
const { data: erc20Balance } = wagmi.useReadContract({
|
|
3195
|
+
address: selectedToken.contract,
|
|
3196
|
+
abi: viem.erc20Abi,
|
|
3197
|
+
functionName: "balanceOf",
|
|
3198
|
+
args: address ? [address] : void 0,
|
|
3199
|
+
chainId: targetChain?.id,
|
|
3200
|
+
query: {
|
|
3201
|
+
enabled: !!address && !isNative
|
|
3202
|
+
}
|
|
3203
|
+
});
|
|
3204
|
+
const walletBalance = isNative ? nativeBalanceData?.value : erc20Balance;
|
|
3205
|
+
const formattedWalletBalance = walletBalance ? formatTokenAmount(walletBalance.toString(), selectedToken.decimals) : "0";
|
|
3206
|
+
const handleMaxClick = () => {
|
|
3207
|
+
if (formattedWalletBalance && parseFloat(formattedWalletBalance) > 0) {
|
|
3208
|
+
setAmount(formattedWalletBalance.replace(/[\s\u2009]/g, ""));
|
|
3209
|
+
}
|
|
3210
|
+
};
|
|
3211
|
+
const hasValidAmount = amount && parseFloat(amount) > 0;
|
|
3212
|
+
const tooManyDecimals = hasValidAmount && amount.includes(".") && amount.split(".")[1].length > selectedToken.decimals;
|
|
3213
|
+
const exceedsBalance = hasValidAmount && !tooManyDecimals && walletBalance != null && parseTokenAmount(amount, selectedToken.decimals) > walletBalance;
|
|
3214
|
+
const {
|
|
3215
|
+
txHash,
|
|
3216
|
+
isGettingAddress,
|
|
3217
|
+
isSwitchingChain,
|
|
3218
|
+
isSendingTransaction,
|
|
3219
|
+
isWaitingForConfirmation,
|
|
3220
|
+
isWaitingForProcessing,
|
|
3221
|
+
verificationFailed,
|
|
3222
|
+
isPending,
|
|
3223
|
+
error,
|
|
3224
|
+
deposit,
|
|
3225
|
+
retryVerification,
|
|
3226
|
+
reset
|
|
3227
|
+
} = useDeposit({
|
|
3228
|
+
onCredited: () => {
|
|
3229
|
+
setAmount("");
|
|
3230
|
+
if (onSuccess) {
|
|
3231
|
+
reset();
|
|
3232
|
+
onSuccess();
|
|
3233
|
+
} else {
|
|
3234
|
+
setShowSuccess(true);
|
|
3235
|
+
}
|
|
3236
|
+
},
|
|
3237
|
+
onCheckTimeout: () => {
|
|
3238
|
+
setAmount("");
|
|
3239
|
+
setShowTimeout(true);
|
|
3240
|
+
}
|
|
3241
|
+
});
|
|
3242
|
+
const depositSteps = [
|
|
3243
|
+
{
|
|
3244
|
+
label: "Getting deposit address",
|
|
3245
|
+
status: isGettingAddress ? "active" : isSwitchingChain || isSendingTransaction || isWaitingForConfirmation || isWaitingForProcessing ? "completed" : "pending"
|
|
3246
|
+
},
|
|
3247
|
+
{
|
|
3248
|
+
label: `Switching to ${targetChain?.name ?? "deposit chain"}`,
|
|
3249
|
+
status: isSwitchingChain ? "active" : isSendingTransaction || isWaitingForConfirmation || isWaitingForProcessing ? "completed" : "pending"
|
|
3250
|
+
},
|
|
3251
|
+
{
|
|
3252
|
+
label: "Confirm in wallet",
|
|
3253
|
+
status: isSendingTransaction ? "active" : isWaitingForConfirmation || isWaitingForProcessing ? "completed" : "pending"
|
|
3254
|
+
},
|
|
3255
|
+
{
|
|
3256
|
+
label: "Confirming transaction",
|
|
3257
|
+
status: isWaitingForConfirmation ? "active" : isWaitingForProcessing ? "completed" : "pending"
|
|
3258
|
+
},
|
|
3259
|
+
{
|
|
3260
|
+
label: "Verifying deposit \u2014 may take up to a few minutes",
|
|
3261
|
+
status: isWaitingForProcessing ? "active" : "pending"
|
|
3262
|
+
}
|
|
3263
|
+
];
|
|
3264
|
+
react.useEffect(() => {
|
|
3265
|
+
if (error && !verificationFailed) {
|
|
3266
|
+
sonner.toast.error(error.message.length > 100 ? `${error.message.slice(0, 100)}...` : error.message);
|
|
3267
|
+
}
|
|
3268
|
+
}, [error, verificationFailed]);
|
|
3269
|
+
react.useEffect(() => {
|
|
3270
|
+
onPendingChange?.(isPending && !cancelled);
|
|
3271
|
+
}, [isPending, cancelled, onPendingChange]);
|
|
3272
|
+
const handleCancel = () => {
|
|
3273
|
+
setCancelled(true);
|
|
3274
|
+
reset();
|
|
3275
|
+
};
|
|
3276
|
+
const handleDone = () => {
|
|
3277
|
+
setShowSuccess(false);
|
|
3278
|
+
setShowTimeout(false);
|
|
3279
|
+
setCancelled(false);
|
|
3280
|
+
reset();
|
|
3281
|
+
};
|
|
3282
|
+
const handleDismissVerificationError = () => {
|
|
3283
|
+
setAmount("");
|
|
3284
|
+
setCancelled(false);
|
|
3285
|
+
reset();
|
|
3286
|
+
};
|
|
3287
|
+
const handleRetryVerification = () => {
|
|
3288
|
+
retryVerification().catch(() => {
|
|
3289
|
+
});
|
|
3290
|
+
};
|
|
3291
|
+
const explorerTxUrl = txHash && targetChain?.explorerUrl ? `${targetChain.explorerUrl}/tx/${txHash}` : void 0;
|
|
3292
|
+
const handleSubmit = async () => {
|
|
3293
|
+
if (!amount || !selectedToken || exceedsBalance) return;
|
|
3294
|
+
setCancelled(false);
|
|
3295
|
+
const amountInWei = parseTokenAmount(amount, selectedToken.decimals);
|
|
3296
|
+
await deposit({
|
|
3297
|
+
tokenId: selectedToken.id,
|
|
3298
|
+
amount: amountInWei
|
|
3299
|
+
});
|
|
3300
|
+
};
|
|
3301
|
+
const getButtonText = () => {
|
|
3302
|
+
if (!isConnected) return "Connect Wallet";
|
|
3303
|
+
if (isSwitchingChain) return "Switching...";
|
|
3304
|
+
return "Deposit";
|
|
3305
|
+
};
|
|
3306
|
+
if (showSuccess) {
|
|
3307
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3308
|
+
TransactionSuccessView,
|
|
3309
|
+
{
|
|
3310
|
+
title: "Deposit Successful",
|
|
3311
|
+
message: `Your ${selectedToken.symbol} deposit has been processed.`,
|
|
3312
|
+
onDone: handleDone
|
|
3313
|
+
}
|
|
3314
|
+
);
|
|
3315
|
+
}
|
|
3316
|
+
if (showTimeout) {
|
|
3317
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3318
|
+
TransactionWarningView,
|
|
3319
|
+
{
|
|
3320
|
+
title: "Deposit Processing",
|
|
3321
|
+
message: `Your transaction was confirmed but the deposit is still being processed. Please check your balance - it should update shortly.`,
|
|
3322
|
+
onDone: handleDone
|
|
3323
|
+
}
|
|
3324
|
+
);
|
|
3325
|
+
}
|
|
3326
|
+
if (verificationFailed) {
|
|
3327
|
+
const baseMessage = "Your transfer was sent on-chain but we could not verify the deposit. The funds are already at the deposit address \u2014 retry verification instead of starting a new deposit.";
|
|
3328
|
+
const detail = error?.message;
|
|
3329
|
+
const message = detail ? `${baseMessage} (${detail})` : baseMessage;
|
|
3330
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3331
|
+
TransactionErrorView,
|
|
3332
|
+
{
|
|
3333
|
+
title: "Verification failed",
|
|
3334
|
+
message,
|
|
3335
|
+
explorerUrl: explorerTxUrl,
|
|
3336
|
+
explorerLabel: "View transaction",
|
|
3337
|
+
onRetry: handleRetryVerification,
|
|
3338
|
+
onDismiss: handleDismissVerificationError
|
|
3339
|
+
}
|
|
3340
|
+
);
|
|
3341
|
+
}
|
|
3342
|
+
if (isPending && !cancelled) {
|
|
3343
|
+
const canCancel = isGettingAddress || isSendingTransaction;
|
|
3344
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3345
|
+
TransactionProgressView,
|
|
3346
|
+
{
|
|
3347
|
+
title: "Depositing...",
|
|
3348
|
+
steps: depositSteps,
|
|
3349
|
+
onCancel: canCancel ? handleCancel : void 0
|
|
3350
|
+
}
|
|
3351
|
+
);
|
|
3352
|
+
}
|
|
3353
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full flex-col gap-6", children: [
|
|
3354
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full flex-col gap-3", children: [
|
|
3355
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-muted-foreground text-sm", children: "Token" }),
|
|
3356
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3357
|
+
"button",
|
|
3358
|
+
{
|
|
3359
|
+
onClick: onTokenSelect,
|
|
3360
|
+
className: "border-border bg-input flex w-full cursor-pointer items-center gap-3 rounded-lg border p-3",
|
|
3361
|
+
children: [
|
|
3362
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-8 w-8 shrink-0 overflow-hidden rounded-full", children: getTokenIcon(selectedToken.symbol, 32) }),
|
|
3363
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col items-start gap-1", children: [
|
|
3364
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-sm font-medium", children: selectedToken.symbol }),
|
|
3365
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground text-xs", children: [
|
|
3366
|
+
"on ",
|
|
3367
|
+
targetChain?.name ?? "Base Sepolia"
|
|
3368
|
+
] })
|
|
3369
|
+
] }),
|
|
3370
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-muted-foreground flex h-5 w-5 items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ChevronRightIcon, {}) })
|
|
3371
|
+
]
|
|
3372
|
+
}
|
|
3373
|
+
)
|
|
3374
|
+
] }),
|
|
3375
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full flex-col gap-3", children: [
|
|
3376
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
3377
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-muted-foreground text-sm", children: "Amount" }),
|
|
3378
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground text-sm", children: [
|
|
3379
|
+
formattedWalletBalance,
|
|
3380
|
+
" ",
|
|
3381
|
+
selectedToken.symbol
|
|
3382
|
+
] })
|
|
3383
|
+
] }),
|
|
3384
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-border bg-input flex items-center gap-2 rounded-[10px] border py-1 pr-1 pl-3", children: [
|
|
3385
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3386
|
+
"input",
|
|
3387
|
+
{
|
|
3388
|
+
type: "text",
|
|
3389
|
+
inputMode: "decimal",
|
|
3390
|
+
placeholder: "Enter Amount",
|
|
3391
|
+
value: amount,
|
|
3392
|
+
onChange: (e) => {
|
|
3393
|
+
const value = e.target.value.replace(/[^0-9.,]/g, "").replace(/,/g, ".");
|
|
3394
|
+
if (value.split(".").length <= 2) {
|
|
3395
|
+
setAmount(value);
|
|
3396
|
+
}
|
|
3397
|
+
},
|
|
3398
|
+
className: cn(
|
|
3399
|
+
"text-foreground flex-1 bg-transparent text-sm outline-none",
|
|
3400
|
+
"placeholder:text-muted-foreground/50"
|
|
3401
|
+
)
|
|
3402
|
+
}
|
|
3403
|
+
),
|
|
3404
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3405
|
+
"button",
|
|
3406
|
+
{
|
|
3407
|
+
onClick: handleMaxClick,
|
|
3408
|
+
className: "bg-secondary text-foreground hover:bg-secondary/80 cursor-pointer rounded px-3 py-2.5 text-xs font-semibold transition-colors",
|
|
3409
|
+
children: "MAX"
|
|
3410
|
+
}
|
|
3411
|
+
)
|
|
3412
|
+
] }),
|
|
3413
|
+
tooManyDecimals && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-destructive text-sm", children: [
|
|
3414
|
+
"Too many decimal places (max: ",
|
|
3415
|
+
selectedToken.decimals,
|
|
3416
|
+
")"
|
|
3417
|
+
] }),
|
|
3418
|
+
exceedsBalance && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-destructive text-sm", children: "Insufficient balance" })
|
|
3419
|
+
] }),
|
|
3420
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3421
|
+
"button",
|
|
3422
|
+
{
|
|
3423
|
+
onClick: handleSubmit,
|
|
3424
|
+
disabled: !isConnected || !hasValidAmount || tooManyDecimals || !!exceedsBalance || isPending,
|
|
3425
|
+
className: cn(
|
|
3426
|
+
"flex h-10 w-full cursor-pointer items-center justify-center rounded-[10px] px-3 py-2 text-sm font-medium transition-colors",
|
|
3427
|
+
"bg-primary text-primary-foreground hover:bg-primary/90",
|
|
3428
|
+
"disabled:cursor-not-allowed disabled:opacity-50"
|
|
3429
|
+
),
|
|
3430
|
+
children: getButtonText()
|
|
3431
|
+
}
|
|
3432
|
+
)
|
|
3433
|
+
] });
|
|
3434
|
+
}
|
|
3435
|
+
function WithdrawForm({ selectedToken, onTokenSelect, onPendingChange }) {
|
|
3436
|
+
const { isConnected, address } = wagmi.useAccount();
|
|
3437
|
+
const { chains, getChainById: getChainById2 } = usePrivanaContext();
|
|
3438
|
+
const [amount, setAmount] = react.useState("");
|
|
3439
|
+
const [showSuccess, setShowSuccess] = react.useState(false);
|
|
3440
|
+
const [showTimeout, setShowTimeout] = react.useState(false);
|
|
3441
|
+
const [cancelled, setCancelled] = react.useState(false);
|
|
3442
|
+
const targetChain = getChainById2(selectedToken.chainId) ?? chains[0];
|
|
3443
|
+
const {
|
|
3444
|
+
balanceWei,
|
|
3445
|
+
isLoading: isBalanceLoading,
|
|
3446
|
+
isError: isBalanceError
|
|
3447
|
+
} = useBalance({
|
|
3448
|
+
tokenId: selectedToken.id
|
|
3449
|
+
});
|
|
3450
|
+
const formattedBalance = formatTokenAmount(balanceWei, selectedToken.decimals);
|
|
3451
|
+
const { withdraw, isPending, currentStep, error, reset } = useWithdraw({
|
|
3452
|
+
onProcessingSuccess: () => {
|
|
3453
|
+
setAmount("");
|
|
3454
|
+
setShowSuccess(true);
|
|
3455
|
+
},
|
|
3456
|
+
onProcessingTimeout: () => {
|
|
3457
|
+
setAmount("");
|
|
3458
|
+
setShowTimeout(true);
|
|
3459
|
+
}
|
|
3460
|
+
});
|
|
3461
|
+
const explorerUrl = address && targetChain ? getExplorerAddressUrl(targetChain.id, address) : void 0;
|
|
3462
|
+
const getStepStatus = (step, after) => {
|
|
3463
|
+
if (currentStep === step) return "active";
|
|
3464
|
+
if (after.includes(currentStep)) return "completed";
|
|
3465
|
+
return "pending";
|
|
3466
|
+
};
|
|
3467
|
+
const withdrawSteps = [
|
|
3468
|
+
{
|
|
3469
|
+
label: "Switching to signing chain",
|
|
3470
|
+
status: getStepStatus("switching-chain", ["signing", "submitting", "processing"])
|
|
3471
|
+
},
|
|
3472
|
+
{ label: "Sign in wallet", status: getStepStatus("signing", ["submitting", "processing"]) },
|
|
3473
|
+
{
|
|
3474
|
+
label: "Submitting withdrawal",
|
|
3475
|
+
status: getStepStatus("submitting", ["processing"])
|
|
3476
|
+
},
|
|
3477
|
+
{
|
|
3478
|
+
label: "Processing \u2014 may take a minute or two",
|
|
3479
|
+
status: getStepStatus("processing", [])
|
|
3480
|
+
}
|
|
3481
|
+
];
|
|
3482
|
+
react.useEffect(() => {
|
|
3483
|
+
if (error) {
|
|
3484
|
+
sonner.toast.error(error.message.length > 100 ? `${error.message.slice(0, 100)}...` : error.message);
|
|
3485
|
+
}
|
|
3486
|
+
}, [error]);
|
|
3487
|
+
react.useEffect(() => {
|
|
3488
|
+
onPendingChange?.(isPending && !cancelled);
|
|
3489
|
+
}, [isPending, cancelled, onPendingChange]);
|
|
3490
|
+
const handleCancel = () => {
|
|
3491
|
+
setCancelled(true);
|
|
3492
|
+
reset();
|
|
3493
|
+
};
|
|
3494
|
+
const handleDone = () => {
|
|
3495
|
+
setShowSuccess(false);
|
|
3496
|
+
setShowTimeout(false);
|
|
3497
|
+
setCancelled(false);
|
|
3498
|
+
reset();
|
|
3499
|
+
};
|
|
3500
|
+
const handleWithdraw = async () => {
|
|
3501
|
+
if (!amount || !selectedToken || exceedsBalance) return;
|
|
3502
|
+
setCancelled(false);
|
|
3503
|
+
const amountInWei = parseTokenAmount(amount, selectedToken.decimals);
|
|
3504
|
+
await withdraw({
|
|
3505
|
+
tokenId: selectedToken.id,
|
|
3506
|
+
amount: amountInWei
|
|
3507
|
+
});
|
|
3508
|
+
};
|
|
3509
|
+
const handleMaxClick = () => {
|
|
3510
|
+
if (formattedBalance && parseFloat(formattedBalance) > 0) {
|
|
3511
|
+
setAmount(formattedBalance.replace(/[\s\u2009]/g, ""));
|
|
3512
|
+
}
|
|
3513
|
+
};
|
|
3514
|
+
const hasValidAmount = amount && parseFloat(amount) > 0;
|
|
3515
|
+
const tooManyDecimals = hasValidAmount && amount.includes(".") && amount.split(".")[1].length > selectedToken.decimals;
|
|
3516
|
+
const exceedsBalance = hasValidAmount && !tooManyDecimals && !isBalanceLoading && !isBalanceError && parseTokenAmount(amount, selectedToken.decimals) > BigInt(balanceWei);
|
|
3517
|
+
const getButtonText = () => {
|
|
3518
|
+
if (!isConnected) return "Connect Wallet";
|
|
3519
|
+
return "Withdraw";
|
|
3520
|
+
};
|
|
3521
|
+
if (showSuccess) {
|
|
3522
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3523
|
+
TransactionSuccessView,
|
|
3524
|
+
{
|
|
3525
|
+
title: "Withdrawal Complete",
|
|
3526
|
+
message: `Your ${selectedToken.symbol} withdrawal has been processed. Funds should appear in your wallet shortly.`,
|
|
3527
|
+
explorerUrl,
|
|
3528
|
+
explorerLabel: "View on BaseScan",
|
|
3529
|
+
onDone: handleDone
|
|
3530
|
+
}
|
|
3531
|
+
);
|
|
3532
|
+
}
|
|
3533
|
+
if (showTimeout) {
|
|
3534
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3535
|
+
TransactionWarningView,
|
|
3536
|
+
{
|
|
3537
|
+
title: "Withdrawal Processing",
|
|
3538
|
+
message: `Your withdrawal is still being processed. Please check your balance \u2014 it should update shortly.`,
|
|
3539
|
+
onDone: handleDone
|
|
3540
|
+
}
|
|
3541
|
+
);
|
|
3542
|
+
}
|
|
3543
|
+
if (isPending && !cancelled) {
|
|
3544
|
+
const canCancel = currentStep === "idle" || currentStep === "switching-chain" || currentStep === "signing";
|
|
3545
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3546
|
+
TransactionProgressView,
|
|
3547
|
+
{
|
|
3548
|
+
title: "Withdrawing...",
|
|
3549
|
+
steps: withdrawSteps,
|
|
3550
|
+
onCancel: canCancel ? handleCancel : void 0
|
|
3551
|
+
}
|
|
3552
|
+
);
|
|
3553
|
+
}
|
|
3554
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full flex-col gap-6", children: [
|
|
3555
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full flex-col gap-3", children: [
|
|
3556
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-muted-foreground text-sm", children: "Token" }),
|
|
3557
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3558
|
+
"button",
|
|
3559
|
+
{
|
|
3560
|
+
onClick: onTokenSelect,
|
|
3561
|
+
className: "border-border bg-input flex w-full cursor-pointer items-center gap-3 rounded-lg border p-3",
|
|
3562
|
+
children: [
|
|
3563
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-8 w-8 shrink-0 overflow-hidden rounded-full", children: getTokenIcon(selectedToken.symbol, 32) }),
|
|
3564
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col items-start gap-1", children: [
|
|
3565
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-sm font-medium", children: selectedToken.symbol }),
|
|
3566
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground text-xs", children: [
|
|
3567
|
+
"on ",
|
|
3568
|
+
targetChain?.name ?? "Base Sepolia"
|
|
3569
|
+
] })
|
|
3570
|
+
] }),
|
|
3571
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-muted-foreground flex h-5 w-5 items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ChevronRightIcon, {}) })
|
|
3572
|
+
]
|
|
3573
|
+
}
|
|
3574
|
+
)
|
|
3575
|
+
] }),
|
|
3576
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full flex-col gap-3", children: [
|
|
3577
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
3578
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-muted-foreground text-sm", children: "Amount" }),
|
|
3579
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground text-sm", children: [
|
|
3580
|
+
formattedBalance,
|
|
3581
|
+
" ",
|
|
3582
|
+
selectedToken.symbol
|
|
3583
|
+
] })
|
|
3584
|
+
] }),
|
|
3585
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-border bg-input flex items-center gap-2 rounded-[10px] border py-1 pr-1 pl-3", children: [
|
|
3586
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3587
|
+
"input",
|
|
3588
|
+
{
|
|
3589
|
+
type: "text",
|
|
3590
|
+
inputMode: "decimal",
|
|
3591
|
+
placeholder: "Enter Amount",
|
|
3592
|
+
value: amount,
|
|
3593
|
+
onChange: (e) => {
|
|
3594
|
+
const value = e.target.value.replace(/[^0-9.,]/g, "").replace(/,/g, ".");
|
|
3595
|
+
if (value.split(".").length <= 2) {
|
|
3596
|
+
setAmount(value);
|
|
3597
|
+
}
|
|
3598
|
+
},
|
|
3599
|
+
className: cn(
|
|
3600
|
+
"text-foreground flex-1 bg-transparent text-sm outline-none",
|
|
3601
|
+
"placeholder:text-muted-foreground/50"
|
|
3602
|
+
)
|
|
3603
|
+
}
|
|
3604
|
+
),
|
|
3605
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3606
|
+
"button",
|
|
3607
|
+
{
|
|
3608
|
+
onClick: handleMaxClick,
|
|
3609
|
+
className: "bg-secondary text-foreground hover:bg-secondary/80 cursor-pointer rounded px-3 py-2.5 text-xs font-semibold transition-colors",
|
|
3610
|
+
children: "MAX"
|
|
3611
|
+
}
|
|
3612
|
+
)
|
|
3613
|
+
] }),
|
|
3614
|
+
tooManyDecimals && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-destructive text-sm", children: [
|
|
3615
|
+
"Too many decimal places (max: ",
|
|
3616
|
+
selectedToken.decimals,
|
|
3617
|
+
")"
|
|
3618
|
+
] }),
|
|
3619
|
+
exceedsBalance && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-destructive text-sm", children: "Insufficient balance" })
|
|
3620
|
+
] }),
|
|
3621
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3622
|
+
"button",
|
|
3623
|
+
{
|
|
3624
|
+
onClick: handleWithdraw,
|
|
3625
|
+
disabled: !isConnected || !hasValidAmount || tooManyDecimals || !!exceedsBalance || isPending,
|
|
3626
|
+
className: cn(
|
|
3627
|
+
"flex h-10 w-full cursor-pointer items-center justify-center rounded-[10px] px-3 py-2 text-sm font-medium transition-colors",
|
|
3628
|
+
"bg-primary text-primary-foreground hover:bg-primary/90",
|
|
3629
|
+
"disabled:cursor-not-allowed disabled:opacity-50"
|
|
3630
|
+
),
|
|
3631
|
+
children: getButtonText()
|
|
3632
|
+
}
|
|
3633
|
+
)
|
|
3634
|
+
] });
|
|
3635
|
+
}
|
|
3636
|
+
function CloseIcon() {
|
|
3637
|
+
return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "10", height: "10", viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3638
|
+
"path",
|
|
3639
|
+
{
|
|
3640
|
+
d: "M11.99997 11.99997l-5.99995-5.99995-6.00002-6.00002m6.00002 6.00002l6.00001-6.00002m-12.00003 12.00003l6.00002-6.00001",
|
|
3641
|
+
stroke: "currentColor",
|
|
3642
|
+
strokeWidth: "1.5",
|
|
3643
|
+
strokeLinecap: "round",
|
|
3644
|
+
strokeLinejoin: "round"
|
|
3645
|
+
}
|
|
3646
|
+
) });
|
|
3647
|
+
}
|
|
3648
|
+
function SearchIcon() {
|
|
3649
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3 w-3 rounded-full border-[1.5px] border-current" });
|
|
3650
|
+
}
|
|
3651
|
+
function ChevronRight() {
|
|
3652
|
+
return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "10", height: "5", viewBox: "0 0 12 6", className: "-rotate-90", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3653
|
+
"path",
|
|
3654
|
+
{
|
|
3655
|
+
d: "M0 0l6 6 6-6",
|
|
3656
|
+
stroke: "currentColor",
|
|
3657
|
+
strokeWidth: "1.5",
|
|
3658
|
+
strokeLinecap: "round",
|
|
3659
|
+
strokeLinejoin: "round",
|
|
3660
|
+
fill: "none"
|
|
3661
|
+
}
|
|
3662
|
+
) });
|
|
3663
|
+
}
|
|
3664
|
+
function ChevronLeft() {
|
|
3665
|
+
return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "10", height: "5", viewBox: "0 0 12 6", className: "rotate-90", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3666
|
+
"path",
|
|
3667
|
+
{
|
|
3668
|
+
d: "M0 0l6 6 6-6",
|
|
3669
|
+
stroke: "currentColor",
|
|
3670
|
+
strokeWidth: "1.5",
|
|
3671
|
+
strokeLinecap: "round",
|
|
3672
|
+
strokeLinejoin: "round",
|
|
3673
|
+
fill: "none"
|
|
3674
|
+
}
|
|
3675
|
+
) });
|
|
3676
|
+
}
|
|
3677
|
+
function ChevronDown({ collapsed }) {
|
|
3678
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3679
|
+
"svg",
|
|
3680
|
+
{
|
|
3681
|
+
width: "12",
|
|
3682
|
+
height: "6",
|
|
3683
|
+
viewBox: "0 0 12 6",
|
|
3684
|
+
className: cn("transition-transform", collapsed && "-rotate-90"),
|
|
3685
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3686
|
+
"path",
|
|
3687
|
+
{
|
|
3688
|
+
d: "M0 0l6 6 6-6",
|
|
3689
|
+
stroke: "currentColor",
|
|
3690
|
+
strokeWidth: "1.5",
|
|
3691
|
+
strokeLinecap: "round",
|
|
3692
|
+
strokeLinejoin: "round",
|
|
3693
|
+
fill: "none"
|
|
3694
|
+
}
|
|
3695
|
+
)
|
|
3696
|
+
}
|
|
3697
|
+
);
|
|
3698
|
+
}
|
|
3699
|
+
function BalanceCards({
|
|
3700
|
+
selectedToken,
|
|
3701
|
+
onLockedFundsClick,
|
|
3702
|
+
onBalanceClick,
|
|
3703
|
+
showLockedFunds = true,
|
|
3704
|
+
disabled
|
|
3705
|
+
}) {
|
|
3706
|
+
const { balanceWei, isLoading: balanceLoading } = useBalance({
|
|
3707
|
+
tokenId: selectedToken.id
|
|
3708
|
+
});
|
|
3709
|
+
const { totalLocked, isLoading: lockedLoading } = useLockedFunds({ enabled: showLockedFunds });
|
|
3710
|
+
const formattedBalance = formatTokenAmount(balanceWei, selectedToken.decimals);
|
|
3711
|
+
const formattedLocked = showLockedFunds ? formatTokenAmount(String(totalLocked), selectedToken.decimals) : "0";
|
|
3712
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex gap-2", disabled && "opacity-50"), children: [
|
|
3713
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3714
|
+
"button",
|
|
3715
|
+
{
|
|
3716
|
+
onClick: onBalanceClick,
|
|
3717
|
+
disabled,
|
|
3718
|
+
className: cn(
|
|
3719
|
+
"bg-muted flex flex-1 flex-col gap-2 rounded-[10px] p-5 text-left transition-colors",
|
|
3720
|
+
disabled ? "cursor-not-allowed" : "hover:bg-muted/80 cursor-pointer"
|
|
3721
|
+
),
|
|
3722
|
+
children: [
|
|
3723
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full items-center justify-between", children: [
|
|
3724
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
3725
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-sm", children: "Balance" }),
|
|
3726
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "bg-secondary text-muted-foreground rounded-full px-2 py-[5px] text-[10px] font-bold", children: selectedToken.symbol })
|
|
3727
|
+
] }),
|
|
3728
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-muted-foreground flex h-6 w-6 items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ChevronRight, {}) })
|
|
3729
|
+
] }),
|
|
3730
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-foreground text-xl font-medium", children: balanceLoading ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bg-secondary inline-block h-6 w-24 animate-pulse rounded" }) : formattedBalance })
|
|
3731
|
+
]
|
|
3732
|
+
}
|
|
3733
|
+
),
|
|
3734
|
+
showLockedFunds && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3735
|
+
"button",
|
|
3736
|
+
{
|
|
3737
|
+
onClick: onLockedFundsClick,
|
|
3738
|
+
disabled,
|
|
3739
|
+
className: cn(
|
|
3740
|
+
"bg-muted flex flex-1 flex-col gap-2 rounded-[10px] p-5 text-left transition-colors",
|
|
3741
|
+
disabled ? "cursor-not-allowed" : "hover:bg-muted/80 cursor-pointer"
|
|
3742
|
+
),
|
|
3743
|
+
children: [
|
|
3744
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full items-center justify-between", children: [
|
|
3745
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
3746
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-sm", children: "Locked Funds" }),
|
|
3747
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "bg-secondary text-muted-foreground rounded-full px-2 py-[5px] text-[10px] font-bold", children: selectedToken.symbol })
|
|
3748
|
+
] }),
|
|
3749
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-muted-foreground flex h-6 w-6 items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(ChevronRight, {}) })
|
|
3750
|
+
] }),
|
|
3751
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-foreground text-xl font-medium", children: lockedLoading ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bg-secondary inline-block h-6 w-20 animate-pulse rounded" }) : formattedLocked })
|
|
3752
|
+
]
|
|
3753
|
+
}
|
|
3754
|
+
)
|
|
3755
|
+
] });
|
|
3756
|
+
}
|
|
3757
|
+
function Tabs({
|
|
3758
|
+
activeTab,
|
|
3759
|
+
onTabChange,
|
|
3760
|
+
disabled
|
|
3761
|
+
}) {
|
|
3762
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3763
|
+
"div",
|
|
3764
|
+
{
|
|
3765
|
+
className: cn(
|
|
3766
|
+
"bg-muted relative flex gap-2 overflow-hidden rounded-[10px] p-1",
|
|
3767
|
+
disabled && "opacity-50"
|
|
3768
|
+
),
|
|
3769
|
+
children: [
|
|
3770
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3771
|
+
"div",
|
|
3772
|
+
{
|
|
3773
|
+
className: cn(
|
|
3774
|
+
"bg-input absolute top-1 bottom-1 left-1 w-[calc(50%-4px)] rounded-md transition-transform duration-200",
|
|
3775
|
+
activeTab === "withdraw" && "translate-x-[calc(100%+8px)]"
|
|
3776
|
+
)
|
|
3777
|
+
}
|
|
3778
|
+
),
|
|
3779
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3780
|
+
"button",
|
|
3781
|
+
{
|
|
3782
|
+
onClick: () => !disabled && onTabChange("deposit"),
|
|
3783
|
+
disabled,
|
|
3784
|
+
className: cn(
|
|
3785
|
+
"relative z-10 flex-1 rounded-md px-3 py-[9px] text-sm transition-colors",
|
|
3786
|
+
activeTab === "deposit" ? "text-foreground" : "text-muted-foreground",
|
|
3787
|
+
disabled ? "cursor-not-allowed" : "cursor-pointer"
|
|
3788
|
+
),
|
|
3789
|
+
children: "Deposit"
|
|
3790
|
+
}
|
|
3791
|
+
),
|
|
3792
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3793
|
+
"button",
|
|
3794
|
+
{
|
|
3795
|
+
onClick: () => !disabled && onTabChange("withdraw"),
|
|
3796
|
+
disabled,
|
|
3797
|
+
className: cn(
|
|
3798
|
+
"relative z-10 flex-1 rounded-md px-3 py-[9px] text-sm transition-colors",
|
|
3799
|
+
activeTab === "withdraw" ? "text-foreground" : "text-muted-foreground",
|
|
3800
|
+
disabled ? "cursor-not-allowed" : "cursor-pointer"
|
|
3801
|
+
),
|
|
3802
|
+
children: "Withdraw"
|
|
3803
|
+
}
|
|
3804
|
+
)
|
|
3805
|
+
]
|
|
3806
|
+
}
|
|
3807
|
+
);
|
|
3808
|
+
}
|
|
3809
|
+
function LockedFundsView({ onBack, onClose }) {
|
|
3810
|
+
const { getTokenById } = usePrivanaContext();
|
|
3811
|
+
const { locks, isLoading } = useLockedFunds();
|
|
3812
|
+
const { unlockFunds, unlockAllExpired, isPending } = useUnlockFunds();
|
|
3813
|
+
const [collapsedSections, setCollapsedSections] = react.useState({});
|
|
3814
|
+
const sections = react.useMemo(() => {
|
|
3815
|
+
const sectionMap = {};
|
|
3816
|
+
locks.forEach((lock) => {
|
|
3817
|
+
const serviceName = shortenAddress(lock.service_address);
|
|
3818
|
+
if (!sectionMap[lock.service_address]) {
|
|
3819
|
+
sectionMap[lock.service_address] = {
|
|
3820
|
+
title: `Service ${serviceName}`,
|
|
3821
|
+
items: []
|
|
3822
|
+
};
|
|
3823
|
+
}
|
|
3824
|
+
sectionMap[lock.service_address].items.push({
|
|
3825
|
+
lockId: lock.lock_id,
|
|
3826
|
+
amount: formatTokenAmount(String(lock.amount), getTokenById(lock.token_id)?.decimals ?? 18),
|
|
3827
|
+
serviceAddress: lock.service_address,
|
|
3828
|
+
time: lock.is_expired ? "Click to unlock" : formatTimeRemaining(lock.expiry),
|
|
3829
|
+
isExpired: lock.is_expired
|
|
3830
|
+
});
|
|
3831
|
+
});
|
|
3832
|
+
return Object.values(sectionMap);
|
|
3833
|
+
}, [getTokenById, locks]);
|
|
3834
|
+
const toggleSection = (title) => {
|
|
3835
|
+
setCollapsedSections((prev) => ({
|
|
3836
|
+
...prev,
|
|
3837
|
+
[title]: !prev[title]
|
|
3838
|
+
}));
|
|
3839
|
+
};
|
|
3840
|
+
const expiredCount = locks.filter((l) => l.is_expired).length;
|
|
3841
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3842
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-5 py-4", children: [
|
|
3843
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5", children: [
|
|
3844
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3845
|
+
"button",
|
|
3846
|
+
{
|
|
3847
|
+
onClick: onBack,
|
|
3848
|
+
className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
|
|
3849
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ChevronLeft, {})
|
|
3850
|
+
}
|
|
3851
|
+
),
|
|
3852
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Locked Funds" })
|
|
3853
|
+
] }),
|
|
3854
|
+
onClose && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3855
|
+
"button",
|
|
3856
|
+
{
|
|
3857
|
+
onClick: onClose,
|
|
3858
|
+
className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
|
|
3859
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {})
|
|
3860
|
+
}
|
|
3861
|
+
)
|
|
3862
|
+
] }),
|
|
3863
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-muted flex min-h-0 flex-1 flex-col rounded-[10px] p-2", children: [
|
|
3864
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-y-auto", children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-2 p-3", children: [1, 2].map((i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex animate-pulse items-center gap-3 rounded-lg p-3", children: [
|
|
3865
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-secondary h-10 w-10 rounded-full" }),
|
|
3866
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
|
|
3867
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-secondary mb-2 h-3.5 w-24 rounded" }),
|
|
3868
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-secondary h-3 w-32 rounded" })
|
|
3869
|
+
] })
|
|
3870
|
+
] }, i)) }) : sections.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center p-8", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-sm", children: "No locked funds" }) }) : sections.map((section) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3871
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3872
|
+
"button",
|
|
3873
|
+
{
|
|
3874
|
+
onClick: () => toggleSection(section.title),
|
|
3875
|
+
className: "text-muted-foreground hover:bg-secondary flex w-full cursor-pointer items-center justify-between rounded-lg px-4 py-4 transition-colors",
|
|
3876
|
+
children: [
|
|
3877
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: section.title }),
|
|
3878
|
+
/* @__PURE__ */ jsxRuntime.jsx(ChevronDown, { collapsed: collapsedSections[section.title] })
|
|
3879
|
+
]
|
|
3880
|
+
}
|
|
3881
|
+
),
|
|
3882
|
+
!collapsedSections[section.title] && section.items.map((item) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3883
|
+
"div",
|
|
3884
|
+
{
|
|
3885
|
+
className: cn(
|
|
3886
|
+
"flex items-center justify-between gap-3 rounded-lg p-3",
|
|
3887
|
+
item.isExpired && "bg-secondary"
|
|
3888
|
+
),
|
|
3889
|
+
children: [
|
|
3890
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 items-center gap-3", children: [
|
|
3891
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-secondary h-10 w-10 rounded-full" }),
|
|
3892
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
3893
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-foreground text-sm font-medium", children: [
|
|
3894
|
+
item.amount,
|
|
3895
|
+
" USDC"
|
|
3896
|
+
] }),
|
|
3897
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-muted-foreground text-xs", children: [
|
|
3898
|
+
"Service: ",
|
|
3899
|
+
shortenAddress(item.serviceAddress)
|
|
3900
|
+
] })
|
|
3901
|
+
] })
|
|
3902
|
+
] }),
|
|
3903
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col items-end gap-2", children: [
|
|
3904
|
+
item.isExpired ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3905
|
+
"button",
|
|
3906
|
+
{
|
|
3907
|
+
onClick: () => unlockFunds({ lockId: Number(item.lockId) }),
|
|
3908
|
+
disabled: isPending,
|
|
3909
|
+
className: "text-foreground hover:text-foreground/80 cursor-pointer text-sm transition-colors disabled:opacity-50",
|
|
3910
|
+
children: "Click to unlock"
|
|
3911
|
+
}
|
|
3912
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-amber-500", children: item.time }),
|
|
3913
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-xs", children: "on Base Sepolia" })
|
|
3914
|
+
] })
|
|
3915
|
+
]
|
|
3916
|
+
},
|
|
3917
|
+
`${item.serviceAddress}-${item.lockId}`
|
|
3918
|
+
))
|
|
3919
|
+
] }, section.title)) }),
|
|
3920
|
+
expiredCount > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-3", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3921
|
+
"button",
|
|
3922
|
+
{
|
|
3923
|
+
onClick: () => unlockAllExpired(),
|
|
3924
|
+
disabled: isPending,
|
|
3925
|
+
className: "border-border text-foreground hover:bg-secondary flex h-10 w-full cursor-pointer items-center justify-center rounded-[10px] border px-3 py-2 text-sm font-medium transition-colors disabled:opacity-50",
|
|
3926
|
+
children: [
|
|
3927
|
+
"Unlock All (",
|
|
3928
|
+
expiredCount,
|
|
3929
|
+
")"
|
|
3930
|
+
]
|
|
3931
|
+
}
|
|
3932
|
+
) })
|
|
3933
|
+
] })
|
|
3934
|
+
] });
|
|
3935
|
+
}
|
|
3936
|
+
function BalanceTokenRow({ token }) {
|
|
3937
|
+
const { balanceWei, isLoading } = useBalance({
|
|
3938
|
+
tokenId: token.id
|
|
3939
|
+
});
|
|
3940
|
+
const formattedBalance = formatTokenAmount(balanceWei, token.decimals);
|
|
3941
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full items-center gap-2 rounded-lg px-3 py-2.5", children: [
|
|
3942
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[18px] w-[18px] overflow-hidden rounded-full", children: getTokenIcon(token.symbol, 18) }),
|
|
3943
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground flex-1 text-sm", children: token.symbol }),
|
|
3944
|
+
isLoading ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bg-secondary h-4 w-16 animate-pulse rounded" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-sm", children: formattedBalance })
|
|
3945
|
+
] });
|
|
3946
|
+
}
|
|
3947
|
+
function BalanceDetailsView({ onBack, onClose }) {
|
|
3948
|
+
const { enabledTokens, chains } = usePrivanaContext();
|
|
3949
|
+
const [selectedChainId, setSelectedChainId] = react.useState(chains[0]?.id ?? 84532);
|
|
3950
|
+
const chainTokens = react.useMemo(() => {
|
|
3951
|
+
return enabledTokens.filter((t) => t.chainId === selectedChainId);
|
|
3952
|
+
}, [enabledTokens, selectedChainId]);
|
|
3953
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3954
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-5 py-4", children: [
|
|
3955
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5", children: [
|
|
3956
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3957
|
+
"button",
|
|
3958
|
+
{
|
|
3959
|
+
onClick: onBack,
|
|
3960
|
+
className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
|
|
3961
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ChevronLeft, {})
|
|
3962
|
+
}
|
|
3963
|
+
),
|
|
3964
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Token Balances" })
|
|
3965
|
+
] }),
|
|
3966
|
+
onClose && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3967
|
+
"button",
|
|
3968
|
+
{
|
|
3969
|
+
onClick: onClose,
|
|
3970
|
+
className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
|
|
3971
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {})
|
|
3972
|
+
}
|
|
3973
|
+
)
|
|
3974
|
+
] }),
|
|
3975
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 gap-2", children: [
|
|
3976
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-muted flex flex-1 flex-col overflow-hidden rounded-[10px] p-2", children: [
|
|
3977
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 pt-4 pb-2", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-sm", children: "Network" }) }),
|
|
3978
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 flex-1 overflow-y-auto", children: chains.map((chain) => {
|
|
3979
|
+
const isSelected = selectedChainId === chain.id;
|
|
3980
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3981
|
+
"button",
|
|
3982
|
+
{
|
|
3983
|
+
onClick: () => setSelectedChainId(chain.id),
|
|
3984
|
+
className: cn(
|
|
3985
|
+
"hover:bg-secondary flex w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-2.5 text-left transition-colors",
|
|
3986
|
+
isSelected && "bg-secondary"
|
|
3987
|
+
),
|
|
3988
|
+
children: [
|
|
3989
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[18px] w-[18px] overflow-hidden rounded-full", children: getChainIcon(chain.id, 18) }),
|
|
3990
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground flex-1 text-sm", children: chain.name })
|
|
3991
|
+
]
|
|
3992
|
+
},
|
|
3993
|
+
chain.id
|
|
3994
|
+
);
|
|
3995
|
+
}) })
|
|
3996
|
+
] }),
|
|
3997
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-muted flex flex-[2] flex-col overflow-hidden rounded-[10px] p-2", children: [
|
|
3998
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 pt-4 pb-2", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-sm", children: "Token Balance" }) }),
|
|
3999
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 flex-1 overflow-y-auto", children: chainTokens.map((token) => /* @__PURE__ */ jsxRuntime.jsx(BalanceTokenRow, { token }, token.id)) })
|
|
4000
|
+
] })
|
|
4001
|
+
] })
|
|
4002
|
+
] });
|
|
4003
|
+
}
|
|
4004
|
+
function TokenRow({
|
|
4005
|
+
token,
|
|
4006
|
+
isSelected,
|
|
4007
|
+
onClick
|
|
4008
|
+
}) {
|
|
4009
|
+
const { address } = wagmi.useAccount();
|
|
4010
|
+
const isNative = token.contract === viem.zeroAddress;
|
|
4011
|
+
const { data: nativeBalanceData } = wagmi.useBalance({
|
|
4012
|
+
address,
|
|
4013
|
+
chainId: token.chainId,
|
|
4014
|
+
query: { enabled: !!address && isNative }
|
|
4015
|
+
});
|
|
4016
|
+
const { data: erc20Balance } = wagmi.useReadContract({
|
|
4017
|
+
address: token.contract,
|
|
4018
|
+
abi: viem.erc20Abi,
|
|
4019
|
+
functionName: "balanceOf",
|
|
4020
|
+
args: address ? [address] : void 0,
|
|
4021
|
+
chainId: token.chainId,
|
|
4022
|
+
query: { enabled: !!address && !isNative }
|
|
4023
|
+
});
|
|
4024
|
+
const walletBalance = isNative ? nativeBalanceData?.value : erc20Balance;
|
|
4025
|
+
const formattedBalance = walletBalance ? formatTokenAmount(walletBalance.toString(), token.decimals) : "0.00";
|
|
4026
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4027
|
+
"button",
|
|
4028
|
+
{
|
|
4029
|
+
onClick,
|
|
4030
|
+
className: cn(
|
|
4031
|
+
"hover:bg-secondary flex w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-2.5 text-left transition-colors",
|
|
4032
|
+
isSelected && "bg-secondary"
|
|
4033
|
+
),
|
|
4034
|
+
children: [
|
|
4035
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[18px] w-[18px] overflow-hidden rounded-full", children: getTokenIcon(token.symbol, 18) }),
|
|
4036
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground flex-1 text-sm", children: token.symbol }),
|
|
4037
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-sm", children: formattedBalance })
|
|
4038
|
+
]
|
|
4039
|
+
}
|
|
4040
|
+
);
|
|
4041
|
+
}
|
|
4042
|
+
function TokenSelectorView({
|
|
4043
|
+
onBack,
|
|
4044
|
+
onClose,
|
|
4045
|
+
onSelect,
|
|
4046
|
+
selectedTokenId
|
|
4047
|
+
}) {
|
|
4048
|
+
const [tokenSearch, setTokenSearch] = react.useState("");
|
|
4049
|
+
const { enabledTokens, chains } = usePrivanaContext();
|
|
4050
|
+
const [selectedChainId, setSelectedChainId] = react.useState(chains[0]?.id ?? 84532);
|
|
4051
|
+
const chainTokens = react.useMemo(() => {
|
|
4052
|
+
return enabledTokens.filter((t) => t.chainId === selectedChainId);
|
|
4053
|
+
}, [enabledTokens, selectedChainId]);
|
|
4054
|
+
const filteredTokens = react.useMemo(() => {
|
|
4055
|
+
if (!tokenSearch) return chainTokens;
|
|
4056
|
+
return chainTokens.filter(
|
|
4057
|
+
(t) => t.symbol.toLowerCase().includes(tokenSearch.toLowerCase()) || t.name.toLowerCase().includes(tokenSearch.toLowerCase())
|
|
4058
|
+
);
|
|
4059
|
+
}, [tokenSearch, chainTokens]);
|
|
4060
|
+
const handleTokenSelect = (token) => {
|
|
4061
|
+
onSelect(token);
|
|
4062
|
+
onBack();
|
|
4063
|
+
};
|
|
4064
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
4065
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-5 py-4", children: [
|
|
4066
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5", children: [
|
|
4067
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4068
|
+
"button",
|
|
4069
|
+
{
|
|
4070
|
+
onClick: onBack,
|
|
4071
|
+
className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
|
|
4072
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ChevronLeft, {})
|
|
4073
|
+
}
|
|
4074
|
+
),
|
|
4075
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Select Token" })
|
|
4076
|
+
] }),
|
|
4077
|
+
onClose && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4078
|
+
"button",
|
|
4079
|
+
{
|
|
4080
|
+
onClick: onClose,
|
|
4081
|
+
className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
|
|
4082
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {})
|
|
4083
|
+
}
|
|
4084
|
+
)
|
|
4085
|
+
] }),
|
|
4086
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 gap-2", children: [
|
|
4087
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-muted flex flex-1 flex-col overflow-hidden rounded-[10px] p-2", children: [
|
|
4088
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 pt-4 pb-2", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-sm", children: "Network" }) }),
|
|
4089
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 flex-1 overflow-y-auto", children: chains.map((chain) => {
|
|
4090
|
+
const isSelected = selectedChainId === chain.id;
|
|
4091
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4092
|
+
"button",
|
|
4093
|
+
{
|
|
4094
|
+
onClick: () => setSelectedChainId(chain.id),
|
|
4095
|
+
className: cn(
|
|
4096
|
+
"hover:bg-secondary flex w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-2.5 text-left transition-colors",
|
|
4097
|
+
isSelected && "bg-secondary"
|
|
4098
|
+
),
|
|
4099
|
+
children: [
|
|
4100
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[18px] w-[18px] overflow-hidden rounded-full", children: getChainIcon(chain.id, 18) }),
|
|
4101
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground flex-1 text-sm", children: chain.name })
|
|
4102
|
+
]
|
|
4103
|
+
},
|
|
4104
|
+
chain.id
|
|
4105
|
+
);
|
|
4106
|
+
}) })
|
|
4107
|
+
] }),
|
|
4108
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-muted flex flex-[2] flex-col overflow-hidden rounded-[10px] p-2", children: [
|
|
4109
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
4110
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 pt-4 pb-2", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-sm", children: "Token" }) }),
|
|
4111
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-border bg-input flex items-center gap-2.5 rounded-lg border px-3 py-2.5", children: [
|
|
4112
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(SearchIcon, {}) }),
|
|
4113
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4114
|
+
"input",
|
|
4115
|
+
{
|
|
4116
|
+
type: "text",
|
|
4117
|
+
placeholder: "Search",
|
|
4118
|
+
value: tokenSearch,
|
|
4119
|
+
onChange: (e) => setTokenSearch(e.target.value),
|
|
4120
|
+
className: "text-foreground placeholder:text-muted-foreground flex-1 bg-transparent text-sm outline-none"
|
|
4121
|
+
}
|
|
4122
|
+
)
|
|
4123
|
+
] }) })
|
|
4124
|
+
] }),
|
|
4125
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 flex-1 overflow-y-auto", children: filteredTokens.map((token) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
4126
|
+
TokenRow,
|
|
4127
|
+
{
|
|
4128
|
+
token,
|
|
4129
|
+
isSelected: selectedTokenId === token.id,
|
|
4130
|
+
onClick: () => handleTokenSelect(token)
|
|
4131
|
+
},
|
|
4132
|
+
token.id
|
|
4133
|
+
)) })
|
|
4134
|
+
] })
|
|
4135
|
+
] })
|
|
4136
|
+
] });
|
|
4137
|
+
}
|
|
4138
|
+
function ModalBody({
|
|
4139
|
+
onClose,
|
|
4140
|
+
onViewChange,
|
|
4141
|
+
onTransactionPendingChange,
|
|
4142
|
+
showLockedFunds = true,
|
|
4143
|
+
defaultTab = "deposit",
|
|
4144
|
+
onDepositSuccess
|
|
4145
|
+
}) {
|
|
4146
|
+
const { defaultToken, tokensStatus } = usePrivanaContext();
|
|
4147
|
+
const [selectedToken, setSelectedToken] = react.useState(defaultToken);
|
|
4148
|
+
const [activeTab, setActiveTab] = react.useState(defaultTab);
|
|
4149
|
+
const [currentView, setCurrentView] = react.useState("main");
|
|
4150
|
+
const [isTransactionPending, setIsTransactionPending] = react.useState(false);
|
|
4151
|
+
react.useEffect(() => {
|
|
4152
|
+
if (!selectedToken && defaultToken) {
|
|
4153
|
+
setSelectedToken(defaultToken);
|
|
4154
|
+
}
|
|
4155
|
+
}, [selectedToken, defaultToken]);
|
|
4156
|
+
const handleTransactionPendingChange = (isPending) => {
|
|
4157
|
+
setIsTransactionPending(isPending);
|
|
4158
|
+
onTransactionPendingChange?.(isPending);
|
|
4159
|
+
};
|
|
4160
|
+
const handleViewChange = (view) => {
|
|
4161
|
+
setCurrentView(view);
|
|
4162
|
+
onViewChange?.(view);
|
|
4163
|
+
};
|
|
4164
|
+
const handleTokenSelect = (token) => {
|
|
4165
|
+
setSelectedToken(token);
|
|
4166
|
+
};
|
|
4167
|
+
if (tokensStatus === "loading" || !selectedToken) {
|
|
4168
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 pb-4", children: [
|
|
4169
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-secondary h-25 animate-pulse rounded-[10px]" }),
|
|
4170
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-secondary h-11 animate-pulse rounded-[10px]" }),
|
|
4171
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-secondary h-50 animate-pulse rounded-[10px]" })
|
|
4172
|
+
] });
|
|
4173
|
+
}
|
|
4174
|
+
if (currentView === "locked-funds") {
|
|
4175
|
+
return /* @__PURE__ */ jsxRuntime.jsx(LockedFundsView, { onBack: () => handleViewChange("main"), onClose });
|
|
4176
|
+
}
|
|
4177
|
+
if (currentView === "balance-details") {
|
|
4178
|
+
return /* @__PURE__ */ jsxRuntime.jsx(BalanceDetailsView, { onBack: () => handleViewChange("main"), onClose });
|
|
4179
|
+
}
|
|
4180
|
+
if (currentView === "select-token") {
|
|
4181
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4182
|
+
TokenSelectorView,
|
|
4183
|
+
{
|
|
4184
|
+
onBack: () => handleViewChange("main"),
|
|
4185
|
+
onClose,
|
|
4186
|
+
onSelect: handleTokenSelect,
|
|
4187
|
+
selectedTokenId: selectedToken.id
|
|
4188
|
+
}
|
|
4189
|
+
);
|
|
4190
|
+
}
|
|
4191
|
+
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 pb-4", children: [
|
|
4192
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(isTransactionPending && "pointer-events-none"), children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4193
|
+
BalanceCards,
|
|
4194
|
+
{
|
|
4195
|
+
selectedToken,
|
|
4196
|
+
onLockedFundsClick: () => handleViewChange("locked-funds"),
|
|
4197
|
+
onBalanceClick: () => handleViewChange("balance-details"),
|
|
4198
|
+
showLockedFunds,
|
|
4199
|
+
disabled: isTransactionPending
|
|
4200
|
+
}
|
|
4201
|
+
) }),
|
|
4202
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(isTransactionPending && "pointer-events-none"), children: /* @__PURE__ */ jsxRuntime.jsx(Tabs, { activeTab, onTabChange: setActiveTab, disabled: isTransactionPending }) }),
|
|
4203
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-muted rounded-[10px] p-5", children: activeTab === "deposit" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
4204
|
+
DepositForm,
|
|
4205
|
+
{
|
|
4206
|
+
selectedToken,
|
|
4207
|
+
onTokenSelect: () => handleViewChange("select-token"),
|
|
4208
|
+
onPendingChange: handleTransactionPendingChange,
|
|
4209
|
+
onSuccess: onDepositSuccess
|
|
4210
|
+
}
|
|
4211
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
4212
|
+
WithdrawForm,
|
|
4213
|
+
{
|
|
4214
|
+
selectedToken,
|
|
4215
|
+
onTokenSelect: () => handleViewChange("select-token"),
|
|
4216
|
+
onPendingChange: handleTransactionPendingChange
|
|
4217
|
+
}
|
|
4218
|
+
) })
|
|
4219
|
+
] }) });
|
|
4220
|
+
}
|
|
4221
|
+
function PrivanaModal({
|
|
4222
|
+
open,
|
|
4223
|
+
onClose,
|
|
4224
|
+
showLockedFunds,
|
|
4225
|
+
defaultTab,
|
|
4226
|
+
onDepositSuccess
|
|
4227
|
+
}) {
|
|
4228
|
+
const [isTransactionPending, setIsTransactionPending] = react.useState(false);
|
|
4229
|
+
const titleId = react.useId();
|
|
4230
|
+
const descId = react.useId();
|
|
4231
|
+
const handleOpenChange = (isOpen) => {
|
|
4232
|
+
if (!isOpen && isTransactionPending) {
|
|
4233
|
+
return;
|
|
4234
|
+
}
|
|
4235
|
+
if (!isOpen) {
|
|
4236
|
+
onClose();
|
|
4237
|
+
}
|
|
4238
|
+
};
|
|
4239
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Dialog, { open, onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4240
|
+
DialogContent,
|
|
4241
|
+
{
|
|
4242
|
+
"data-privana": true,
|
|
4243
|
+
showCloseButton: false,
|
|
4244
|
+
className: "bg-card flex w-[560px] max-w-[95vw] flex-col gap-2 overflow-hidden rounded-2xl border-0 p-2",
|
|
4245
|
+
overlayClassName: isTransactionPending ? "cursor-not-allowed" : void 0,
|
|
4246
|
+
"aria-labelledby": titleId,
|
|
4247
|
+
"aria-describedby": descId,
|
|
4248
|
+
children: [
|
|
4249
|
+
/* @__PURE__ */ jsxRuntime.jsxs(DialogHeader, { children: [
|
|
4250
|
+
/* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { id: titleId, className: "sr-only", children: "Privana" }),
|
|
4251
|
+
/* @__PURE__ */ jsxRuntime.jsx(DialogDescription, { id: descId, className: "sr-only", children: "Deposit or withdraw tokens from your Flexvault" }),
|
|
4252
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-5 py-4", children: [
|
|
4253
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Privana" }),
|
|
4254
|
+
onClose && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4255
|
+
"button",
|
|
4256
|
+
{
|
|
4257
|
+
onClick: onClose,
|
|
4258
|
+
className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
|
|
4259
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {})
|
|
4260
|
+
}
|
|
4261
|
+
)
|
|
4262
|
+
] })
|
|
4263
|
+
] }),
|
|
4264
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4265
|
+
ModalBody,
|
|
4266
|
+
{
|
|
4267
|
+
onClose: isTransactionPending ? void 0 : onClose,
|
|
4268
|
+
onTransactionPendingChange: setIsTransactionPending,
|
|
4269
|
+
showLockedFunds,
|
|
4270
|
+
defaultTab,
|
|
4271
|
+
onDepositSuccess
|
|
4272
|
+
}
|
|
4273
|
+
)
|
|
4274
|
+
]
|
|
4275
|
+
}
|
|
4276
|
+
) });
|
|
4277
|
+
}
|
|
4278
|
+
function PrivanaInlineModal({
|
|
4279
|
+
className,
|
|
4280
|
+
showLockedFunds,
|
|
4281
|
+
defaultTab,
|
|
4282
|
+
onDepositSuccess
|
|
4283
|
+
}) {
|
|
4284
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4285
|
+
"div",
|
|
4286
|
+
{
|
|
4287
|
+
"data-privana": true,
|
|
4288
|
+
className: cn(
|
|
4289
|
+
"bg-card flex w-[560px] max-w-full flex-col gap-2 overflow-hidden rounded-2xl p-2 shadow-lg",
|
|
4290
|
+
className
|
|
4291
|
+
),
|
|
4292
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4293
|
+
ModalBody,
|
|
4294
|
+
{
|
|
4295
|
+
showLockedFunds,
|
|
4296
|
+
defaultTab,
|
|
4297
|
+
onDepositSuccess
|
|
4298
|
+
}
|
|
4299
|
+
)
|
|
4300
|
+
}
|
|
4301
|
+
);
|
|
4302
|
+
}
|
|
4303
|
+
function PrivanaButton({
|
|
4304
|
+
children,
|
|
4305
|
+
className,
|
|
4306
|
+
variant = "outline",
|
|
4307
|
+
size = "default",
|
|
4308
|
+
asChild = false,
|
|
4309
|
+
renderButton,
|
|
4310
|
+
hideWhenDisconnected = true,
|
|
4311
|
+
showLockedFunds = true,
|
|
4312
|
+
defaultTab,
|
|
4313
|
+
onDepositSuccess,
|
|
4314
|
+
...buttonProps
|
|
4315
|
+
}) {
|
|
4316
|
+
const [modalOpen, setModalOpen] = react.useState(false);
|
|
4317
|
+
const { isConnected } = wagmi.useAccount();
|
|
4318
|
+
if (hideWhenDisconnected && !isConnected) {
|
|
4319
|
+
return null;
|
|
4320
|
+
}
|
|
4321
|
+
const handleClick = () => setModalOpen(true);
|
|
4322
|
+
const buttonElement = renderButton ? renderButton({ onClick: handleClick, isOpen: modalOpen }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
4323
|
+
Button,
|
|
4324
|
+
{
|
|
4325
|
+
variant,
|
|
4326
|
+
size,
|
|
4327
|
+
asChild,
|
|
4328
|
+
className: cn(className),
|
|
4329
|
+
onClick: handleClick,
|
|
4330
|
+
disabled: !isConnected,
|
|
4331
|
+
...buttonProps,
|
|
4332
|
+
children: children ?? "Privana"
|
|
4333
|
+
}
|
|
4334
|
+
);
|
|
4335
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { "data-privana": true, className: "contents", children: [
|
|
4336
|
+
buttonElement,
|
|
4337
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4338
|
+
PrivanaModal,
|
|
4339
|
+
{
|
|
4340
|
+
open: modalOpen,
|
|
4341
|
+
onClose: () => setModalOpen(false),
|
|
4342
|
+
showLockedFunds,
|
|
4343
|
+
defaultTab,
|
|
4344
|
+
onDepositSuccess
|
|
4345
|
+
}
|
|
4346
|
+
)
|
|
4347
|
+
] });
|
|
4348
|
+
}
|
|
4349
|
+
function Skeleton({ className, ...props }) {
|
|
4350
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4351
|
+
"div",
|
|
4352
|
+
{
|
|
4353
|
+
"data-slot": "skeleton",
|
|
4354
|
+
className: cn("bg-accent animate-pulse rounded-md", className),
|
|
4355
|
+
...props
|
|
4356
|
+
}
|
|
4357
|
+
);
|
|
4358
|
+
}
|
|
4359
|
+
|
|
4360
|
+
exports.AccountingApiError = AccountingApiError;
|
|
4361
|
+
exports.Button = Button;
|
|
4362
|
+
exports.HOSTED_AUTH_CLOCK_SKEW_MS = HOSTED_AUTH_CLOCK_SKEW_MS;
|
|
4363
|
+
exports.HostedAuthError = HostedAuthError;
|
|
4364
|
+
exports.HostedAuthRequiredError = HostedAuthRequiredError;
|
|
4365
|
+
exports.HostedAuthStateMismatchError = HostedAuthStateMismatchError;
|
|
4366
|
+
exports.HttpClient = HttpClient;
|
|
4367
|
+
exports.LOCK_TYPES = LOCK_TYPES;
|
|
4368
|
+
exports.MODIFY_LOCK_TYPES = MODIFY_LOCK_TYPES;
|
|
4369
|
+
exports.NETWORK_CONFIG = NETWORK_CONFIG;
|
|
4370
|
+
exports.NetworkError = NetworkError;
|
|
4371
|
+
exports.PrivanaButton = PrivanaButton;
|
|
4372
|
+
exports.PrivanaClient = PrivanaClient;
|
|
4373
|
+
exports.PrivanaInlineModal = PrivanaInlineModal;
|
|
4374
|
+
exports.PrivanaModal = PrivanaModal;
|
|
4375
|
+
exports.PrivanaProvider = PrivanaProvider;
|
|
4376
|
+
exports.SUPPORTED_CHAINS = SUPPORTED_CHAINS;
|
|
4377
|
+
exports.Skeleton = Skeleton;
|
|
4378
|
+
exports.TRANSFER_LOCKED_TYPES = TRANSFER_LOCKED_TYPES;
|
|
4379
|
+
exports.TRANSFER_TYPES = TRANSFER_TYPES;
|
|
4380
|
+
exports.ValidationError = ValidationError;
|
|
4381
|
+
exports.WITHDRAW_FROM_LOCK_TYPES = WITHDRAW_FROM_LOCK_TYPES;
|
|
4382
|
+
exports.WITHDRAW_TYPES = WITHDRAW_TYPES;
|
|
4383
|
+
exports.applyRefreshResponse = applyRefreshResponse;
|
|
4384
|
+
exports.buildHostedAuthSession = buildHostedAuthSession;
|
|
4385
|
+
exports.buttonVariants = buttonVariants;
|
|
4386
|
+
exports.clearHostedAuthPendingTransaction = clearHostedAuthPendingTransaction;
|
|
4387
|
+
exports.createDomain = createDomain;
|
|
4388
|
+
exports.createHostedAuthPendingStorageKey = createHostedAuthPendingStorageKey;
|
|
4389
|
+
exports.createHostedAuthState = createHostedAuthState;
|
|
4390
|
+
exports.createHostedAuthStorageKey = createHostedAuthStorageKey;
|
|
4391
|
+
exports.createLockExpiry = createLockExpiry;
|
|
4392
|
+
exports.createPkceChallenge = createPkceChallenge;
|
|
4393
|
+
exports.createPkceVerifier = createPkceVerifier;
|
|
4394
|
+
exports.getAccountingContract = getAccountingContract;
|
|
4395
|
+
exports.getApiUrl = getApiUrl;
|
|
4396
|
+
exports.getChainById = getChainById;
|
|
4397
|
+
exports.getChainIcon = getChainIcon;
|
|
4398
|
+
exports.getChainId = getChainId;
|
|
4399
|
+
exports.getExplorerAddressUrl = getExplorerAddressUrl;
|
|
4400
|
+
exports.getTokenIcon = getTokenIcon;
|
|
4401
|
+
exports.isHostedAuthRefreshActive = isHostedAuthRefreshActive;
|
|
4402
|
+
exports.isHostedAuthSessionActive = isHostedAuthSessionActive;
|
|
4403
|
+
exports.normalizeAddress = normalizeAddress;
|
|
4404
|
+
exports.normalizeHex = normalizeHex;
|
|
4405
|
+
exports.parseHostedAuthCallback = parseHostedAuthCallback;
|
|
4406
|
+
exports.persistHostedAuthPendingTransaction = persistHostedAuthPendingTransaction;
|
|
4407
|
+
exports.readHostedAuthPendingTransaction = readHostedAuthPendingTransaction;
|
|
4408
|
+
exports.readStoredHostedAuthSession = readStoredHostedAuthSession;
|
|
4409
|
+
exports.signLockMessage = signLockMessage;
|
|
4410
|
+
exports.signModifyLockMessage = signModifyLockMessage;
|
|
4411
|
+
exports.signTransferLockedMessage = signTransferLockedMessage;
|
|
4412
|
+
exports.signTransferMessage = signTransferMessage;
|
|
4413
|
+
exports.signWithdrawFromLockMessage = signWithdrawFromLockMessage;
|
|
4414
|
+
exports.signWithdrawMessage = signWithdrawMessage;
|
|
4415
|
+
exports.stripHostedAuthCallbackParams = stripHostedAuthCallbackParams;
|
|
4416
|
+
exports.syncHostedAuthSessionToClient = syncHostedAuthSessionToClient;
|
|
4417
|
+
exports.useBalance = useBalance;
|
|
4418
|
+
exports.useBatchBalances = useBatchBalances;
|
|
4419
|
+
exports.useDeposit = useDeposit;
|
|
4420
|
+
exports.useExpiredLocks = useExpiredLocks;
|
|
4421
|
+
exports.useHistory = useHistory;
|
|
4422
|
+
exports.useHostedRedirectAuth = useHostedRedirectAuth;
|
|
4423
|
+
exports.useLockFunds = useLockFunds;
|
|
4424
|
+
exports.useLockedFunds = useLockedFunds;
|
|
4425
|
+
exports.useModifyLock = useModifyLock;
|
|
4426
|
+
exports.usePendingWithdrawals = usePendingWithdrawals;
|
|
4427
|
+
exports.usePrivanaClient = usePrivanaClient;
|
|
4428
|
+
exports.usePrivanaContext = usePrivanaContext;
|
|
4429
|
+
exports.useSafeAccount = useSafeAccount;
|
|
4430
|
+
exports.useSafePrivanaContext = useSafePrivanaContext;
|
|
4431
|
+
exports.useTokenInfo = useTokenInfo;
|
|
4432
|
+
exports.useTokenList = useTokenList;
|
|
4433
|
+
exports.useTotalLockedBalance = useTotalLockedBalance;
|
|
4434
|
+
exports.useTransfer = useTransfer;
|
|
4435
|
+
exports.useUnlockFunds = useUnlockFunds;
|
|
4436
|
+
exports.useWithdraw = useWithdraw;
|
|
4437
|
+
//# sourceMappingURL=index.cjs.map
|
|
4438
|
+
//# sourceMappingURL=index.cjs.map
|