@oasisprotocol/privana-sdk 0.4.4 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-4IW4V7YJ.cjs +3805 -0
- package/dist/chunk-4IW4V7YJ.cjs.map +1 -0
- package/dist/chunk-SAJ7K5WT.js +3716 -0
- package/dist/chunk-SAJ7K5WT.js.map +1 -0
- package/dist/index.cjs +2528 -636
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +2 -2
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +177 -334
- package/dist/index.d.ts +177 -334
- package/dist/index.js +2152 -359
- package/dist/index.js.map +1 -1
- package/dist/on-ramp.cjs +10 -974
- package/dist/on-ramp.cjs.map +1 -1
- package/dist/on-ramp.d.cts +50 -4
- package/dist/on-ramp.d.ts +50 -4
- package/dist/on-ramp.js +1 -975
- package/dist/on-ramp.js.map +1 -1
- package/dist/pending-lock-BdkuMjmY.d.cts +600 -0
- package/dist/pending-lock-BdkuMjmY.d.ts +600 -0
- package/package.json +3 -2
- package/dist/chunk-NMRIKUDN.cjs +0 -1775
- package/dist/chunk-NMRIKUDN.cjs.map +0 -1
- package/dist/chunk-WV6CGHEF.js +0 -1720
- package/dist/chunk-WV6CGHEF.js.map +0 -1
- package/dist/on-ramp-CdXqJjmm.d.cts +0 -115
- package/dist/on-ramp-CdXqJjmm.d.ts +0 -115
|
@@ -0,0 +1,3805 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var wagmi = require('wagmi');
|
|
6
|
+
var actions = require('wagmi/actions');
|
|
7
|
+
var siwe = require('viem/siwe');
|
|
8
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
9
|
+
var viem = require('viem');
|
|
10
|
+
var reactQuery = require('@tanstack/react-query');
|
|
11
|
+
var reactSlot = require('@radix-ui/react-slot');
|
|
12
|
+
var classVarianceAuthority = require('class-variance-authority');
|
|
13
|
+
var clsx = require('clsx');
|
|
14
|
+
var tailwindMerge = require('tailwind-merge');
|
|
15
|
+
var utils = require('viem/utils');
|
|
16
|
+
var actions$1 = require('viem/actions');
|
|
17
|
+
var lucideReact = require('lucide-react');
|
|
18
|
+
var moonpayReact = require('@moonpay/moonpay-react');
|
|
19
|
+
|
|
20
|
+
// ../../shared/config.json
|
|
21
|
+
var config_default = {
|
|
22
|
+
chains: [
|
|
23
|
+
{
|
|
24
|
+
id: 84532,
|
|
25
|
+
name: "Base Sepolia",
|
|
26
|
+
explorerUrl: "https://sepolia.basescan.org",
|
|
27
|
+
explorerName: "BaseScan"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: 11155111,
|
|
31
|
+
name: "Ethereum Sepolia",
|
|
32
|
+
explorerUrl: "https://sepolia.etherscan.io",
|
|
33
|
+
explorerName: "Etherscan"
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
networks: {
|
|
37
|
+
testnet: {
|
|
38
|
+
chainId: 23295,
|
|
39
|
+
name: "Sapphire Testnet",
|
|
40
|
+
accountingContract: "0xad3C76e4E621C0cfF7540479Ee9B0A945723A642",
|
|
41
|
+
apiUrl: "https://api.testnet.privana.finance"
|
|
42
|
+
},
|
|
43
|
+
mainnet: {
|
|
44
|
+
chainId: 23294,
|
|
45
|
+
name: "Sapphire Mainnet",
|
|
46
|
+
accountingContract: "0x0000000000000000000000000000000000000000",
|
|
47
|
+
apiUrl: ""
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// src/sdk/types/common.ts
|
|
53
|
+
var NETWORK_CONFIG = {
|
|
54
|
+
testnet: {
|
|
55
|
+
...config_default.networks.testnet,
|
|
56
|
+
accountingContract: config_default.networks.testnet.accountingContract
|
|
57
|
+
},
|
|
58
|
+
mainnet: {
|
|
59
|
+
...config_default.networks.mainnet,
|
|
60
|
+
accountingContract: config_default.networks.mainnet.accountingContract
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
function getChainId(network) {
|
|
64
|
+
return NETWORK_CONFIG[network].chainId;
|
|
65
|
+
}
|
|
66
|
+
function getAccountingContract(network) {
|
|
67
|
+
return NETWORK_CONFIG[network].accountingContract;
|
|
68
|
+
}
|
|
69
|
+
function getApiUrl(network) {
|
|
70
|
+
return NETWORK_CONFIG[network].apiUrl;
|
|
71
|
+
}
|
|
72
|
+
function normalizeHex(value) {
|
|
73
|
+
const normalized = value.trim().toLowerCase();
|
|
74
|
+
return normalized.startsWith("0x") ? normalized : `0x${normalized}`;
|
|
75
|
+
}
|
|
76
|
+
function normalizeAddress(value) {
|
|
77
|
+
return normalizeHex(value);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/sdk/types/chains.ts
|
|
81
|
+
var SUPPORTED_CHAINS = config_default.chains.map((chain) => ({
|
|
82
|
+
id: chain.id,
|
|
83
|
+
name: chain.name,
|
|
84
|
+
explorerUrl: chain.explorerUrl,
|
|
85
|
+
explorerName: chain.explorerName
|
|
86
|
+
}));
|
|
87
|
+
function getChainById(chainId) {
|
|
88
|
+
return SUPPORTED_CHAINS.find((c) => c.id === chainId);
|
|
89
|
+
}
|
|
90
|
+
function getExplorerLabel(chainId) {
|
|
91
|
+
const chain = getChainById(chainId);
|
|
92
|
+
return `View on ${chain?.explorerName ?? "Explorer"}`;
|
|
93
|
+
}
|
|
94
|
+
function getExplorerAddressUrl(chainId, address) {
|
|
95
|
+
const chain = getChainById(chainId);
|
|
96
|
+
if (!chain) return void 0;
|
|
97
|
+
return `${chain.explorerUrl}/address/${address}#tokentxns`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/sdk/auth/hosted-auth.ts
|
|
101
|
+
var HOSTED_AUTH_CLOCK_SKEW_MS = 3e4;
|
|
102
|
+
var PKCE_CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
103
|
+
var DEFAULT_RANDOM_LENGTH = 64;
|
|
104
|
+
var HOSTED_AUTH_CALLBACK_QUERY_KEYS = ["code", "error", "error_description", "state"];
|
|
105
|
+
function randomString(length) {
|
|
106
|
+
const values = new Uint8Array(length);
|
|
107
|
+
crypto.getRandomValues(values);
|
|
108
|
+
let output = "";
|
|
109
|
+
for (const value of values) {
|
|
110
|
+
output += PKCE_CHARSET[value % PKCE_CHARSET.length];
|
|
111
|
+
}
|
|
112
|
+
return output;
|
|
113
|
+
}
|
|
114
|
+
function toBase64Url(bytes) {
|
|
115
|
+
let binary = "";
|
|
116
|
+
bytes.forEach((byte) => {
|
|
117
|
+
binary += String.fromCharCode(byte);
|
|
118
|
+
});
|
|
119
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
120
|
+
}
|
|
121
|
+
function createPkceVerifier(length = DEFAULT_RANDOM_LENGTH) {
|
|
122
|
+
return randomString(length);
|
|
123
|
+
}
|
|
124
|
+
async function createPkceChallenge(verifier) {
|
|
125
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
126
|
+
return toBase64Url(new Uint8Array(digest));
|
|
127
|
+
}
|
|
128
|
+
function createHostedAuthState(length = DEFAULT_RANDOM_LENGTH) {
|
|
129
|
+
return randomString(length);
|
|
130
|
+
}
|
|
131
|
+
function createHostedAuthStorageKey(apiUrl, config) {
|
|
132
|
+
const normalizedApiUrl = apiUrl.replace(/\/$/, "");
|
|
133
|
+
return [
|
|
134
|
+
"privana",
|
|
135
|
+
"hosted-auth",
|
|
136
|
+
normalizedApiUrl,
|
|
137
|
+
config.clientId.trim(),
|
|
138
|
+
config.redirectUri.trim()
|
|
139
|
+
].join(":");
|
|
140
|
+
}
|
|
141
|
+
function createHostedAuthPendingStorageKey(apiUrl, config) {
|
|
142
|
+
return `${createHostedAuthStorageKey(apiUrl, config)}:pending`;
|
|
143
|
+
}
|
|
144
|
+
function persistHostedAuthPendingTransaction(storage, key, transaction) {
|
|
145
|
+
storage.setItem(key, JSON.stringify(transaction));
|
|
146
|
+
}
|
|
147
|
+
function readHostedAuthPendingTransaction(storage, key) {
|
|
148
|
+
const raw = storage.getItem(key);
|
|
149
|
+
if (!raw) return null;
|
|
150
|
+
try {
|
|
151
|
+
const parsed = JSON.parse(raw);
|
|
152
|
+
if (typeof parsed.codeVerifier !== "string" || typeof parsed.state !== "string") {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
codeVerifier: parsed.codeVerifier,
|
|
157
|
+
state: parsed.state
|
|
158
|
+
};
|
|
159
|
+
} catch {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function clearHostedAuthPendingTransaction(storage, key) {
|
|
164
|
+
storage.removeItem(key);
|
|
165
|
+
}
|
|
166
|
+
function parseHostedAuthCallback(url, redirectUri) {
|
|
167
|
+
const expectedUrl = new URL(redirectUri);
|
|
168
|
+
if (url.origin !== expectedUrl.origin || url.pathname !== expectedUrl.pathname) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
const code = url.searchParams.get("code");
|
|
172
|
+
if (code) {
|
|
173
|
+
return {
|
|
174
|
+
code,
|
|
175
|
+
state: url.searchParams.get("state")
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const error = url.searchParams.get("error");
|
|
179
|
+
if (error) {
|
|
180
|
+
return {
|
|
181
|
+
error,
|
|
182
|
+
errorDescription: url.searchParams.get("error_description") ?? void 0,
|
|
183
|
+
state: url.searchParams.get("state")
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
function stripHostedAuthCallbackParams(url) {
|
|
189
|
+
const nextUrl = new URL(url.toString());
|
|
190
|
+
HOSTED_AUTH_CALLBACK_QUERY_KEYS.forEach((key) => {
|
|
191
|
+
nextUrl.searchParams.delete(key);
|
|
192
|
+
});
|
|
193
|
+
return `${nextUrl.pathname}${nextUrl.search}${nextUrl.hash}`;
|
|
194
|
+
}
|
|
195
|
+
function buildHostedAuthSession(response, config, now = Date.now()) {
|
|
196
|
+
return {
|
|
197
|
+
accessToken: response.access_token,
|
|
198
|
+
refreshToken: response.refresh_token,
|
|
199
|
+
idToken: response.id_token,
|
|
200
|
+
tokenType: response.token_type,
|
|
201
|
+
address: response.address,
|
|
202
|
+
clientId: config.clientId,
|
|
203
|
+
redirectUri: config.redirectUri,
|
|
204
|
+
expiresAt: now + response.expires_in * 1e3,
|
|
205
|
+
refreshExpiresAt: now + response.refresh_expires_in * 1e3
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function applyRefreshResponse(session, response, now = Date.now()) {
|
|
209
|
+
return {
|
|
210
|
+
...session,
|
|
211
|
+
accessToken: response.token,
|
|
212
|
+
refreshToken: response.refresh_token,
|
|
213
|
+
expiresAt: now + response.expires_in * 1e3,
|
|
214
|
+
refreshExpiresAt: now + response.refresh_expires_in * 1e3
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function isHostedAuthSessionActive(session, now = Date.now(), skewMs = HOSTED_AUTH_CLOCK_SKEW_MS) {
|
|
218
|
+
return session.expiresAt > now + skewMs;
|
|
219
|
+
}
|
|
220
|
+
function isHostedAuthRefreshActive(session, now = Date.now(), skewMs = HOSTED_AUTH_CLOCK_SKEW_MS) {
|
|
221
|
+
return session.refreshExpiresAt > now + skewMs;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/sdk/auth/siwe.ts
|
|
225
|
+
function buildSiweStatement(chainId) {
|
|
226
|
+
return `Sign in to Privana on chain ${chainId}`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/sdk/client/errors.ts
|
|
230
|
+
var AccountingApiError = class _AccountingApiError extends Error {
|
|
231
|
+
constructor(message, statusCode, detail) {
|
|
232
|
+
super(message);
|
|
233
|
+
this.statusCode = statusCode;
|
|
234
|
+
this.detail = detail;
|
|
235
|
+
this.name = "AccountingApiError";
|
|
236
|
+
Object.setPrototypeOf(this, _AccountingApiError.prototype);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
var NetworkError = class _NetworkError extends Error {
|
|
240
|
+
constructor(message, cause) {
|
|
241
|
+
super(message);
|
|
242
|
+
this.cause = cause;
|
|
243
|
+
this.name = "NetworkError";
|
|
244
|
+
Object.setPrototypeOf(this, _NetworkError.prototype);
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
var ValidationError = class _ValidationError extends Error {
|
|
248
|
+
constructor(message, field) {
|
|
249
|
+
super(message);
|
|
250
|
+
this.field = field;
|
|
251
|
+
this.name = "ValidationError";
|
|
252
|
+
Object.setPrototypeOf(this, _ValidationError.prototype);
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
var HostedAuthError = class _HostedAuthError extends Error {
|
|
256
|
+
constructor(message) {
|
|
257
|
+
super(message);
|
|
258
|
+
this.name = "HostedAuthError";
|
|
259
|
+
Object.setPrototypeOf(this, _HostedAuthError.prototype);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
var HostedAuthRequiredError = class _HostedAuthRequiredError extends HostedAuthError {
|
|
263
|
+
constructor(message = "Hosted redirect authentication is required. Start login with useHostedRedirectAuth().") {
|
|
264
|
+
super(message);
|
|
265
|
+
this.name = "HostedAuthRequiredError";
|
|
266
|
+
Object.setPrototypeOf(this, _HostedAuthRequiredError.prototype);
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
var HostedAuthStateMismatchError = class _HostedAuthStateMismatchError extends HostedAuthError {
|
|
270
|
+
constructor(message = "Hosted authentication returned an invalid state value.") {
|
|
271
|
+
super(message);
|
|
272
|
+
this.name = "HostedAuthStateMismatchError";
|
|
273
|
+
Object.setPrototypeOf(this, _HostedAuthStateMismatchError.prototype);
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// src/sdk/client/http-client.ts
|
|
278
|
+
var HttpClient = class {
|
|
279
|
+
constructor(config) {
|
|
280
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
281
|
+
this.timeout = config.timeout ?? 3e4;
|
|
282
|
+
this.headers = {
|
|
283
|
+
"Content-Type": "application/json",
|
|
284
|
+
...config.headers
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
async get(path) {
|
|
288
|
+
return this.request("GET", path);
|
|
289
|
+
}
|
|
290
|
+
async post(path, body) {
|
|
291
|
+
return this.request("POST", path, body);
|
|
292
|
+
}
|
|
293
|
+
getBaseUrl() {
|
|
294
|
+
return this.baseUrl;
|
|
295
|
+
}
|
|
296
|
+
setHeader(name, value) {
|
|
297
|
+
this.headers[name] = value;
|
|
298
|
+
}
|
|
299
|
+
removeHeader(name) {
|
|
300
|
+
delete this.headers[name];
|
|
301
|
+
}
|
|
302
|
+
getHeader(name) {
|
|
303
|
+
return this.headers[name];
|
|
304
|
+
}
|
|
305
|
+
async request(method, path, body) {
|
|
306
|
+
const url = `${this.baseUrl}${path}`;
|
|
307
|
+
const controller = new AbortController();
|
|
308
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
309
|
+
try {
|
|
310
|
+
const response = await fetch(url, {
|
|
311
|
+
method,
|
|
312
|
+
headers: this.headers,
|
|
313
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
314
|
+
signal: controller.signal
|
|
315
|
+
});
|
|
316
|
+
clearTimeout(timeoutId);
|
|
317
|
+
if (!response.ok) {
|
|
318
|
+
let detail;
|
|
319
|
+
try {
|
|
320
|
+
const errorBody = await response.json();
|
|
321
|
+
detail = errorBody.detail || errorBody.error_description || errorBody.message;
|
|
322
|
+
} catch {
|
|
323
|
+
detail = await response.text().catch(() => void 0);
|
|
324
|
+
}
|
|
325
|
+
throw new AccountingApiError(
|
|
326
|
+
`API request failed: ${response.status} ${response.statusText}`,
|
|
327
|
+
response.status,
|
|
328
|
+
detail
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
return response.json();
|
|
332
|
+
} catch (error) {
|
|
333
|
+
clearTimeout(timeoutId);
|
|
334
|
+
if (error instanceof AccountingApiError) {
|
|
335
|
+
throw error;
|
|
336
|
+
}
|
|
337
|
+
if (error instanceof Error) {
|
|
338
|
+
if (error.name === "AbortError") {
|
|
339
|
+
throw new NetworkError(`Request timeout after ${this.timeout}ms`);
|
|
340
|
+
}
|
|
341
|
+
throw new NetworkError(`Network request failed: ${error.message}`, error);
|
|
342
|
+
}
|
|
343
|
+
throw new NetworkError("Unknown network error occurred");
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// src/sdk/client/privana-client.ts
|
|
349
|
+
var PRIVATE_READ_TOKEN_HEADER = "X-SIWE-Token";
|
|
350
|
+
var MAX_BATCH_BALANCE_TOKEN_IDS = 100;
|
|
351
|
+
var MAX_HISTORY_PAGE_SIZE = 100;
|
|
352
|
+
var PrivanaClient = class {
|
|
353
|
+
constructor(config) {
|
|
354
|
+
this.http = new HttpClient(config);
|
|
355
|
+
}
|
|
356
|
+
getBaseUrl() {
|
|
357
|
+
return this.http.getBaseUrl();
|
|
358
|
+
}
|
|
359
|
+
async getDepositAddress(request = {}) {
|
|
360
|
+
return this.http.post("/v1/accounting/deposits/address", {
|
|
361
|
+
chain_type: request.chain_type ?? "evm",
|
|
362
|
+
version: request.version ?? 0
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
async checkDeposit(request) {
|
|
366
|
+
return this.http.post("/v1/accounting/deposits/check", {
|
|
367
|
+
chain_type: request.chain_type ?? "evm",
|
|
368
|
+
chain_id: request.chain_id,
|
|
369
|
+
tx_hash: normalizeHex(request.tx_hash),
|
|
370
|
+
amount: String(request.amount),
|
|
371
|
+
log_index: request.log_index ?? 0,
|
|
372
|
+
version: request.version ?? 0
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
async getDepositStatus(depositId) {
|
|
376
|
+
return this.http.get(`/v1/accounting/deposits/status/${depositId}`);
|
|
377
|
+
}
|
|
378
|
+
async getPendingDeposits(request) {
|
|
379
|
+
const params = new URLSearchParams({ chain_id: String(request.chain_id) });
|
|
380
|
+
if (request.version !== void 0) params.set("version", String(request.version));
|
|
381
|
+
if (request.token_address !== void 0) {
|
|
382
|
+
params.set("token_address", normalizeAddress(request.token_address));
|
|
383
|
+
}
|
|
384
|
+
if (request.lookback_blocks !== void 0) {
|
|
385
|
+
params.set("lookback_blocks", String(request.lookback_blocks));
|
|
386
|
+
}
|
|
387
|
+
return this.http.get(
|
|
388
|
+
`/v1/accounting/deposits/pending?${params.toString()}`
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
async getBalance(tokenId) {
|
|
392
|
+
const token = normalizeHex(tokenId);
|
|
393
|
+
return this.http.get(`/v1/accounting/balances/${token}`);
|
|
394
|
+
}
|
|
395
|
+
async getBatchBalances(request) {
|
|
396
|
+
if (request.token_ids.length > MAX_BATCH_BALANCE_TOKEN_IDS) {
|
|
397
|
+
throw new Error(
|
|
398
|
+
`Batch balance requests support at most ${MAX_BATCH_BALANCE_TOKEN_IDS} token IDs`
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
return this.http.post("/v1/accounting/balances/batch", {
|
|
402
|
+
token_ids: request.token_ids.map((id) => normalizeHex(id))
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
async getHistory(request = {}) {
|
|
406
|
+
const offset = request.offset ?? -1;
|
|
407
|
+
const limit = request.limit ?? 50;
|
|
408
|
+
if (!Number.isSafeInteger(offset)) {
|
|
409
|
+
throw new Error("History offset must be an integer");
|
|
410
|
+
}
|
|
411
|
+
if (!Number.isSafeInteger(limit)) {
|
|
412
|
+
throw new Error("History limit must be an integer");
|
|
413
|
+
}
|
|
414
|
+
if (limit < 0 || limit > MAX_HISTORY_PAGE_SIZE) {
|
|
415
|
+
throw new Error(`History requests support between 0 and ${MAX_HISTORY_PAGE_SIZE} entries`);
|
|
416
|
+
}
|
|
417
|
+
const params = new URLSearchParams({
|
|
418
|
+
offset: String(offset),
|
|
419
|
+
limit: String(limit)
|
|
420
|
+
});
|
|
421
|
+
return this.http.get(`/v1/accounting/history?${params.toString()}`);
|
|
422
|
+
}
|
|
423
|
+
async listTokens() {
|
|
424
|
+
return this.http.get("/v1/accounting/tokens");
|
|
425
|
+
}
|
|
426
|
+
async getTokenInfo(tokenId) {
|
|
427
|
+
const token = normalizeHex(tokenId);
|
|
428
|
+
return this.http.get(`/v1/accounting/tokens/${token}`);
|
|
429
|
+
}
|
|
430
|
+
async lockFunds(request) {
|
|
431
|
+
return this.http.post("/v1/accounting/funds/lock", {
|
|
432
|
+
service_address: normalizeAddress(request.service_address),
|
|
433
|
+
token_id: normalizeHex(request.token_id),
|
|
434
|
+
amount: String(request.amount),
|
|
435
|
+
expiry: String(request.expiry),
|
|
436
|
+
nonce: String(request.nonce),
|
|
437
|
+
signature: normalizeHex(request.signature)
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
async modifyLock(request) {
|
|
441
|
+
return this.http.post("/v1/accounting/funds/modify-lock", {
|
|
442
|
+
lock_id: request.lock_id,
|
|
443
|
+
amount: String(request.amount),
|
|
444
|
+
new_expiry: String(request.new_expiry),
|
|
445
|
+
nonce: String(request.nonce),
|
|
446
|
+
signature: normalizeHex(request.signature)
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
async unlockFunds(request) {
|
|
450
|
+
return this.http.post("/v1/accounting/funds/unlock", {
|
|
451
|
+
user_address: normalizeAddress(request.user_address),
|
|
452
|
+
lock_id: request.lock_id
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
async unlockAllExpired(request) {
|
|
456
|
+
return this.http.post(
|
|
457
|
+
"/v1/accounting/funds/unlock-all-expired",
|
|
458
|
+
{
|
|
459
|
+
user_address: normalizeAddress(request.user_address)
|
|
460
|
+
}
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
async getLockedFunds(serviceAddress) {
|
|
464
|
+
const queryParams = serviceAddress ? `?service_address=${normalizeAddress(serviceAddress)}` : "";
|
|
465
|
+
return this.http.get(`/v1/accounting/funds/locked${queryParams}`);
|
|
466
|
+
}
|
|
467
|
+
async getTotalLockedBalance(tokenId) {
|
|
468
|
+
const token = normalizeHex(tokenId);
|
|
469
|
+
return this.http.get(`/v1/accounting/funds/locked/total/${token}`);
|
|
470
|
+
}
|
|
471
|
+
async getExpiredLocks() {
|
|
472
|
+
return this.http.get("/v1/accounting/funds/expired");
|
|
473
|
+
}
|
|
474
|
+
async transferFunds(request) {
|
|
475
|
+
return this.http.post("/v1/accounting/funds/transfer", {
|
|
476
|
+
to_address: normalizeAddress(request.to_address),
|
|
477
|
+
token_id: normalizeHex(request.token_id),
|
|
478
|
+
amount: String(request.amount),
|
|
479
|
+
nonce: String(request.nonce),
|
|
480
|
+
signature: normalizeHex(request.signature)
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
async getTransferNonce(userAddress) {
|
|
484
|
+
const user = normalizeAddress(userAddress);
|
|
485
|
+
return this.http.get(`/v1/accounting/funds/transfer/nonce/${user}`);
|
|
486
|
+
}
|
|
487
|
+
async getLockNonce(userAddress) {
|
|
488
|
+
const user = normalizeAddress(userAddress);
|
|
489
|
+
return this.http.get(`/v1/accounting/funds/lock/nonce/${user}`);
|
|
490
|
+
}
|
|
491
|
+
async getModifyLockNonce(userAddress) {
|
|
492
|
+
const user = normalizeAddress(userAddress);
|
|
493
|
+
return this.http.get(`/v1/accounting/funds/modify-lock/nonce/${user}`);
|
|
494
|
+
}
|
|
495
|
+
async transferLockedFunds(request) {
|
|
496
|
+
return this.http.post("/v1/accounting/funds/transfer-locked", {
|
|
497
|
+
user_address: normalizeAddress(request.user_address),
|
|
498
|
+
lock_id: request.lock_id,
|
|
499
|
+
to_address: normalizeAddress(request.to_address),
|
|
500
|
+
amount: String(request.amount),
|
|
501
|
+
service_address: normalizeAddress(request.service_address),
|
|
502
|
+
nonce: String(request.nonce),
|
|
503
|
+
signature: normalizeHex(request.signature)
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
async withdrawFromLock(request) {
|
|
507
|
+
return this.http.post(
|
|
508
|
+
"/v1/accounting/funds/withdraw-from-lock",
|
|
509
|
+
{
|
|
510
|
+
to_address: normalizeAddress(request.to_address),
|
|
511
|
+
lock_id: request.lock_id,
|
|
512
|
+
amount: String(request.amount),
|
|
513
|
+
nonce: String(request.nonce),
|
|
514
|
+
signature: normalizeHex(request.signature)
|
|
515
|
+
}
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
async requestWithdrawal(request) {
|
|
519
|
+
return this.http.post("/v1/accounting/withdraw", {
|
|
520
|
+
token_id: normalizeHex(request.token_id),
|
|
521
|
+
amount: String(request.amount),
|
|
522
|
+
nonce: String(request.nonce),
|
|
523
|
+
signature: normalizeHex(request.signature)
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
async getWithdrawalNonce(userAddress) {
|
|
527
|
+
const user = normalizeAddress(userAddress);
|
|
528
|
+
return this.http.get(`/v1/accounting/withdraw/nonce/${user}`);
|
|
529
|
+
}
|
|
530
|
+
async getTransferLockedNonce(serviceAddress) {
|
|
531
|
+
const service = normalizeAddress(serviceAddress);
|
|
532
|
+
return this.http.get(
|
|
533
|
+
`/v1/accounting/funds/transfer-locked/nonce/${service}`
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
async getPendingWithdrawals(userAddress) {
|
|
537
|
+
const user = normalizeAddress(userAddress);
|
|
538
|
+
return this.http.get(`/v1/accounting/withdraw/pending/${user}`);
|
|
539
|
+
}
|
|
540
|
+
async getWithdrawalInfo(index) {
|
|
541
|
+
return this.http.get(`/v1/accounting/withdraw/${index}`);
|
|
542
|
+
}
|
|
543
|
+
async getSiweDomain() {
|
|
544
|
+
return this.http.get("/v1/accounting/auth/domain");
|
|
545
|
+
}
|
|
546
|
+
async getSiweNonce(userAddress) {
|
|
547
|
+
const user = normalizeAddress(userAddress);
|
|
548
|
+
return this.http.get(`/v1/accounting/auth/nonce?address=${user}`);
|
|
549
|
+
}
|
|
550
|
+
async loginWithSiwe(request) {
|
|
551
|
+
return this.http.post("/v1/accounting/auth/login", {
|
|
552
|
+
siwe_message: request.siwe_message,
|
|
553
|
+
signature: normalizeHex(request.signature)
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
getHostedAuthAuthorizeUrl(request) {
|
|
557
|
+
const url = new URL(
|
|
558
|
+
"v1/accounting/auth/authorize",
|
|
559
|
+
`${this.http.getBaseUrl().replace(/\/$/, "")}/`
|
|
560
|
+
);
|
|
561
|
+
url.searchParams.set("client_id", request.client_id);
|
|
562
|
+
url.searchParams.set("redirect_uri", request.redirect_uri);
|
|
563
|
+
url.searchParams.set("code_challenge", request.code_challenge);
|
|
564
|
+
url.searchParams.set("state", request.state);
|
|
565
|
+
url.searchParams.set("chain_id", String(request.chain_id));
|
|
566
|
+
url.searchParams.set("response_mode", request.response_mode ?? "redirect");
|
|
567
|
+
url.searchParams.set("code_challenge_method", request.code_challenge_method ?? "S256");
|
|
568
|
+
return url.toString();
|
|
569
|
+
}
|
|
570
|
+
async exchangeHostedAuthCode(request) {
|
|
571
|
+
return this.http.post("/v1/accounting/auth/token", {
|
|
572
|
+
grant_type: request.grant_type ?? "authorization_code",
|
|
573
|
+
code: request.code,
|
|
574
|
+
code_verifier: request.code_verifier,
|
|
575
|
+
client_id: request.client_id,
|
|
576
|
+
redirect_uri: request.redirect_uri
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
async refreshJwtSession(request) {
|
|
580
|
+
return this.http.post("/v1/accounting/auth/jwt/refresh", {
|
|
581
|
+
refresh_token: request.refresh_token
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
async logoutJwtSession(request = {}) {
|
|
585
|
+
return this.http.post("/v1/accounting/auth/jwt/logout", {
|
|
586
|
+
refresh_token: request.refresh_token,
|
|
587
|
+
revoke_all: request.revoke_all ?? false
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
async signOnRampUrl(request) {
|
|
591
|
+
return this.http.post("/v1/accounting/onramp/sign-url", {
|
|
592
|
+
url: request.url
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
async createOnRampIntent(request) {
|
|
596
|
+
return this.http.post("/v1/accounting/onramp/intent", {
|
|
597
|
+
wallet_address: request.wallet_address ? normalizeAddress(request.wallet_address) : void 0,
|
|
598
|
+
token_id: normalizeHex(request.token_id),
|
|
599
|
+
chain_id: request.chain_id,
|
|
600
|
+
moonpay_currency_code: request.moonpay_currency_code,
|
|
601
|
+
base_currency_code: request.base_currency_code,
|
|
602
|
+
base_currency_amount: request.base_currency_amount
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
async updateOnRamp(transactionId, request) {
|
|
606
|
+
return this.http.post(
|
|
607
|
+
`/v1/accounting/onramp/${encodeURIComponent(transactionId)}`,
|
|
608
|
+
{
|
|
609
|
+
wallet_address: request.wallet_address ? normalizeAddress(request.wallet_address) : void 0,
|
|
610
|
+
token_id: request.token_id ? normalizeHex(request.token_id) : void 0,
|
|
611
|
+
chain_id: request.chain_id,
|
|
612
|
+
moonpay_transaction_id: request.moonpay_transaction_id,
|
|
613
|
+
base_currency_code: request.base_currency_code,
|
|
614
|
+
base_currency_amount: request.base_currency_amount,
|
|
615
|
+
quote_currency_amount: request.quote_currency_amount,
|
|
616
|
+
on_chain_tx_hash: request.on_chain_tx_hash ? normalizeHex(request.on_chain_tx_hash) : void 0,
|
|
617
|
+
deposit_tx_hash: request.deposit_tx_hash ? normalizeHex(request.deposit_tx_hash) : void 0
|
|
618
|
+
}
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
async getPendingOnRamps() {
|
|
622
|
+
return this.http.get("/v1/accounting/onramp/pending");
|
|
623
|
+
}
|
|
624
|
+
setPrivateReadToken(token) {
|
|
625
|
+
this.http.removeHeader("Authorization");
|
|
626
|
+
this.http.setHeader(PRIVATE_READ_TOKEN_HEADER, token);
|
|
627
|
+
}
|
|
628
|
+
getPrivateReadToken() {
|
|
629
|
+
return this.http.getHeader(PRIVATE_READ_TOKEN_HEADER);
|
|
630
|
+
}
|
|
631
|
+
clearPrivateReadToken() {
|
|
632
|
+
this.http.removeHeader(PRIVATE_READ_TOKEN_HEADER);
|
|
633
|
+
}
|
|
634
|
+
setBearerToken(token) {
|
|
635
|
+
this.http.removeHeader(PRIVATE_READ_TOKEN_HEADER);
|
|
636
|
+
this.http.setHeader("Authorization", `Bearer ${token}`);
|
|
637
|
+
}
|
|
638
|
+
clearBearerToken() {
|
|
639
|
+
this.http.removeHeader("Authorization");
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
// src/sdk/signatures/eip712-types.ts
|
|
644
|
+
function createDomain(chainId, verifyingContract) {
|
|
645
|
+
return {
|
|
646
|
+
name: "AccountingModule",
|
|
647
|
+
version: "1",
|
|
648
|
+
chainId,
|
|
649
|
+
verifyingContract
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
var LOCK_TYPES = {
|
|
653
|
+
Lock: [
|
|
654
|
+
{ name: "serviceAddress", type: "address" },
|
|
655
|
+
{ name: "tokenId", type: "bytes32" },
|
|
656
|
+
{ name: "amount", type: "uint256" },
|
|
657
|
+
{ name: "expiry", type: "uint256" },
|
|
658
|
+
{ name: "nonce", type: "uint256" }
|
|
659
|
+
]
|
|
660
|
+
};
|
|
661
|
+
var TRANSFER_TYPES = {
|
|
662
|
+
Transfer: [
|
|
663
|
+
{ name: "toAddress", type: "address" },
|
|
664
|
+
{ name: "tokenId", type: "bytes32" },
|
|
665
|
+
{ name: "amount", type: "uint256" },
|
|
666
|
+
{ name: "nonce", type: "uint256" }
|
|
667
|
+
]
|
|
668
|
+
};
|
|
669
|
+
var TRANSFER_LOCKED_TYPES = {
|
|
670
|
+
TransferLocked: [
|
|
671
|
+
{ name: "userAddress", type: "address" },
|
|
672
|
+
{ name: "toAddress", type: "address" },
|
|
673
|
+
{ name: "lockId", type: "uint256" },
|
|
674
|
+
{ name: "amount", type: "uint256" },
|
|
675
|
+
{ name: "nonce", type: "uint256" },
|
|
676
|
+
{ name: "serviceAddress", type: "address" }
|
|
677
|
+
]
|
|
678
|
+
};
|
|
679
|
+
var WITHDRAW_TYPES = {
|
|
680
|
+
Withdraw: [
|
|
681
|
+
{ name: "tokenId", type: "bytes32" },
|
|
682
|
+
{ name: "amount", type: "uint256" },
|
|
683
|
+
{ name: "nonce", type: "uint256" }
|
|
684
|
+
]
|
|
685
|
+
};
|
|
686
|
+
var MODIFY_LOCK_TYPES = {
|
|
687
|
+
ModifyLock: [
|
|
688
|
+
{ name: "lockId", type: "uint256" },
|
|
689
|
+
{ name: "amount", type: "uint256" },
|
|
690
|
+
{ name: "newExpiry", type: "uint256" },
|
|
691
|
+
{ name: "nonce", type: "uint256" }
|
|
692
|
+
]
|
|
693
|
+
};
|
|
694
|
+
var WITHDRAW_FROM_LOCK_TYPES = {
|
|
695
|
+
WithdrawFromLock: [
|
|
696
|
+
{ name: "userAddress", type: "address" },
|
|
697
|
+
{ name: "toAddress", type: "address" },
|
|
698
|
+
{ name: "lockId", type: "uint256" },
|
|
699
|
+
{ name: "amount", type: "uint256" },
|
|
700
|
+
{ name: "nonce", type: "uint256" }
|
|
701
|
+
]
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
// src/sdk/signatures/sign-lock.ts
|
|
705
|
+
async function signLockMessage({
|
|
706
|
+
walletClient,
|
|
707
|
+
chainId,
|
|
708
|
+
verifyingContract,
|
|
709
|
+
message
|
|
710
|
+
}) {
|
|
711
|
+
const account = walletClient.account;
|
|
712
|
+
if (!account) {
|
|
713
|
+
throw new Error("No account connected to wallet client");
|
|
714
|
+
}
|
|
715
|
+
const domain = createDomain(chainId, verifyingContract);
|
|
716
|
+
const signature = await walletClient.signTypedData({
|
|
717
|
+
account,
|
|
718
|
+
domain,
|
|
719
|
+
types: LOCK_TYPES,
|
|
720
|
+
primaryType: "Lock",
|
|
721
|
+
message
|
|
722
|
+
});
|
|
723
|
+
return signature;
|
|
724
|
+
}
|
|
725
|
+
function createLockExpiry(minutesFromNow = 60) {
|
|
726
|
+
return BigInt(Math.floor(Date.now() / 1e3) + minutesFromNow * 60);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// src/sdk/signatures/sign-modify-lock.ts
|
|
730
|
+
async function signModifyLockMessage({
|
|
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: MODIFY_LOCK_TYPES,
|
|
745
|
+
primaryType: "ModifyLock",
|
|
746
|
+
message
|
|
747
|
+
});
|
|
748
|
+
return signature;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// src/sdk/signatures/sign-transfer.ts
|
|
752
|
+
async function signTransferMessage({
|
|
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: TRANSFER_TYPES,
|
|
767
|
+
primaryType: "Transfer",
|
|
768
|
+
message
|
|
769
|
+
});
|
|
770
|
+
return signature;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// src/sdk/signatures/sign-transfer-locked.ts
|
|
774
|
+
async function signTransferLockedMessage({
|
|
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: TRANSFER_LOCKED_TYPES,
|
|
789
|
+
primaryType: "TransferLocked",
|
|
790
|
+
message
|
|
791
|
+
});
|
|
792
|
+
return signature;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// src/sdk/signatures/sign-withdraw.ts
|
|
796
|
+
async function signWithdrawMessage({
|
|
797
|
+
walletClient,
|
|
798
|
+
chainId,
|
|
799
|
+
verifyingContract,
|
|
800
|
+
message
|
|
801
|
+
}) {
|
|
802
|
+
const account = walletClient.account;
|
|
803
|
+
if (!account) {
|
|
804
|
+
throw new Error("No account connected to wallet client");
|
|
805
|
+
}
|
|
806
|
+
const domain = createDomain(chainId, verifyingContract);
|
|
807
|
+
const signature = await walletClient.signTypedData({
|
|
808
|
+
account,
|
|
809
|
+
domain,
|
|
810
|
+
types: WITHDRAW_TYPES,
|
|
811
|
+
primaryType: "Withdraw",
|
|
812
|
+
message
|
|
813
|
+
});
|
|
814
|
+
return signature;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// src/sdk/signatures/sign-withdraw-from-lock.ts
|
|
818
|
+
async function signWithdrawFromLockMessage({
|
|
819
|
+
walletClient,
|
|
820
|
+
chainId,
|
|
821
|
+
verifyingContract,
|
|
822
|
+
message
|
|
823
|
+
}) {
|
|
824
|
+
const account = walletClient.account;
|
|
825
|
+
if (!account) {
|
|
826
|
+
throw new Error("No account connected to wallet client");
|
|
827
|
+
}
|
|
828
|
+
const domain = createDomain(chainId, verifyingContract);
|
|
829
|
+
const signature = await walletClient.signTypedData({
|
|
830
|
+
account,
|
|
831
|
+
domain,
|
|
832
|
+
types: WITHDRAW_FROM_LOCK_TYPES,
|
|
833
|
+
primaryType: "WithdrawFromLock",
|
|
834
|
+
message
|
|
835
|
+
});
|
|
836
|
+
return signature;
|
|
837
|
+
}
|
|
838
|
+
var defaultResult = {
|
|
839
|
+
address: void 0,
|
|
840
|
+
isConnected: false,
|
|
841
|
+
status: "disconnected"
|
|
842
|
+
};
|
|
843
|
+
function useSafeAccount() {
|
|
844
|
+
const context = react.useContext(wagmi.WagmiContext);
|
|
845
|
+
const cacheRef = react.useRef(defaultResult);
|
|
846
|
+
const subscribe = react.useCallback(
|
|
847
|
+
(onChange) => {
|
|
848
|
+
if (!context) return () => {
|
|
849
|
+
};
|
|
850
|
+
return actions.watchAccount(context, { onChange });
|
|
851
|
+
},
|
|
852
|
+
[context]
|
|
853
|
+
);
|
|
854
|
+
const getSnapshot = react.useCallback(() => {
|
|
855
|
+
if (!context) return defaultResult;
|
|
856
|
+
const account = actions.getAccount(context);
|
|
857
|
+
if (cacheRef.current.address !== account.address || cacheRef.current.isConnected !== account.isConnected || cacheRef.current.status !== account.status) {
|
|
858
|
+
cacheRef.current = {
|
|
859
|
+
address: account.address,
|
|
860
|
+
isConnected: account.isConnected,
|
|
861
|
+
status: account.status
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
return cacheRef.current;
|
|
865
|
+
}, [context]);
|
|
866
|
+
const getServerSnapshot = react.useCallback(() => defaultResult, []);
|
|
867
|
+
return react.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// src/sdk/hooks/private-read-token-store.ts
|
|
871
|
+
var AUTH_CLOCK_SKEW_MS = 3e4;
|
|
872
|
+
var cache = /* @__PURE__ */ new Map();
|
|
873
|
+
function createScopeKey(apiUrl, chainId, address) {
|
|
874
|
+
return `${apiUrl.replace(/\/$/, "")}:${chainId}:${address.toLowerCase()}`;
|
|
875
|
+
}
|
|
876
|
+
function getCachedPrivateReadToken(scopeKey) {
|
|
877
|
+
const cached = cache.get(scopeKey);
|
|
878
|
+
if (!cached) return null;
|
|
879
|
+
if (cached.expiresAt <= Date.now() + AUTH_CLOCK_SKEW_MS) {
|
|
880
|
+
cache.delete(scopeKey);
|
|
881
|
+
return null;
|
|
882
|
+
}
|
|
883
|
+
return cached.token;
|
|
884
|
+
}
|
|
885
|
+
function setCachedPrivateReadToken(scopeKey, token, expiresAt) {
|
|
886
|
+
cache.set(scopeKey, { token, expiresAt });
|
|
887
|
+
}
|
|
888
|
+
function deleteCachedPrivateReadToken(scopeKey) {
|
|
889
|
+
cache.delete(scopeKey);
|
|
890
|
+
}
|
|
891
|
+
var DEFAULT_SIWE_VALIDITY_MS = 24 * 60 * 60 * 1e3;
|
|
892
|
+
var AUTH_REFRESH_SKEW_MS = 3e4;
|
|
893
|
+
var SiweAuthContext = react.createContext(null);
|
|
894
|
+
function SiweAuthProvider({
|
|
895
|
+
children,
|
|
896
|
+
client,
|
|
897
|
+
networkConfig,
|
|
898
|
+
autoLogin = true
|
|
899
|
+
}) {
|
|
900
|
+
const wagmiContext = react.useContext(wagmi.WagmiContext);
|
|
901
|
+
const { address, isConnected, status } = useSafeAccount();
|
|
902
|
+
const [session, setSession] = react.useState(null);
|
|
903
|
+
const [tokens, setTokens] = react.useState(null);
|
|
904
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
905
|
+
const [error, setError] = react.useState(null);
|
|
906
|
+
const [accessTokenExpiresAt, setAccessTokenExpiresAt] = react.useState(null);
|
|
907
|
+
const loginInFlight = react.useRef(false);
|
|
908
|
+
const autoAttemptedAddress = react.useRef(null);
|
|
909
|
+
const refreshInFlight = react.useRef(false);
|
|
910
|
+
const refreshDataRef = react.useRef(null);
|
|
911
|
+
const clearSession = react.useCallback(() => {
|
|
912
|
+
refreshDataRef.current = null;
|
|
913
|
+
setAccessTokenExpiresAt(null);
|
|
914
|
+
client.clearPrivateReadToken();
|
|
915
|
+
client.clearBearerToken();
|
|
916
|
+
setSession(null);
|
|
917
|
+
setTokens(null);
|
|
918
|
+
setError(null);
|
|
919
|
+
autoAttemptedAddress.current = null;
|
|
920
|
+
}, [client]);
|
|
921
|
+
const logout = react.useCallback(async () => {
|
|
922
|
+
setError(null);
|
|
923
|
+
const refreshToken = refreshDataRef.current?.refreshToken;
|
|
924
|
+
try {
|
|
925
|
+
if (refreshToken) {
|
|
926
|
+
await client.logoutJwtSession({ refresh_token: refreshToken });
|
|
927
|
+
}
|
|
928
|
+
} finally {
|
|
929
|
+
clearSession();
|
|
930
|
+
autoAttemptedAddress.current = address ?? null;
|
|
931
|
+
}
|
|
932
|
+
}, [clearSession, client, address]);
|
|
933
|
+
const login = react.useCallback(async () => {
|
|
934
|
+
if (!wagmiContext) throw new Error("WagmiProvider is required for SIWE auth");
|
|
935
|
+
if (!address) throw new Error("No wallet connected");
|
|
936
|
+
if (loginInFlight.current) return;
|
|
937
|
+
loginInFlight.current = true;
|
|
938
|
+
setIsLoading(true);
|
|
939
|
+
setError(null);
|
|
940
|
+
try {
|
|
941
|
+
const walletClient = await actions.getWalletClient(wagmiContext);
|
|
942
|
+
if (!walletClient) throw new Error("No wallet client available");
|
|
943
|
+
const [{ domain }, nonceRes] = await Promise.all([
|
|
944
|
+
client.getSiweDomain(),
|
|
945
|
+
client.getSiweNonce(address)
|
|
946
|
+
]);
|
|
947
|
+
const issuedAt = /* @__PURE__ */ new Date();
|
|
948
|
+
const expirationTime = new Date(issuedAt.getTime() + DEFAULT_SIWE_VALIDITY_MS);
|
|
949
|
+
const uri = typeof window !== "undefined" && window.location.origin ? window.location.origin : networkConfig.apiUrl;
|
|
950
|
+
const message = siwe.createSiweMessage({
|
|
951
|
+
address,
|
|
952
|
+
chainId: networkConfig.chainId,
|
|
953
|
+
domain,
|
|
954
|
+
uri,
|
|
955
|
+
version: "1",
|
|
956
|
+
nonce: nonceRes.nonce,
|
|
957
|
+
statement: buildSiweStatement(networkConfig.chainId),
|
|
958
|
+
issuedAt,
|
|
959
|
+
expirationTime
|
|
960
|
+
});
|
|
961
|
+
const signature = await walletClient.signMessage({
|
|
962
|
+
account: walletClient.account ?? address,
|
|
963
|
+
message
|
|
964
|
+
});
|
|
965
|
+
const res = await client.loginWithSiwe({ siwe_message: message, signature });
|
|
966
|
+
const loggedInAt = Date.now();
|
|
967
|
+
client.setPrivateReadToken(res.siwe_token);
|
|
968
|
+
client.setBearerToken(res.jwt_access_token);
|
|
969
|
+
refreshDataRef.current = {
|
|
970
|
+
refreshToken: res.jwt_refresh_token,
|
|
971
|
+
refreshExpiresAt: loggedInAt + res.jwt_refresh_expires_in * 1e3
|
|
972
|
+
};
|
|
973
|
+
setCachedPrivateReadToken(
|
|
974
|
+
createScopeKey(networkConfig.apiUrl, networkConfig.chainId, address),
|
|
975
|
+
res.siwe_token,
|
|
976
|
+
expirationTime.getTime()
|
|
977
|
+
);
|
|
978
|
+
setSession({ address: res.address });
|
|
979
|
+
setTokens({
|
|
980
|
+
siwe_token: res.siwe_token,
|
|
981
|
+
jwt_access_token: res.jwt_access_token,
|
|
982
|
+
jwt_refresh_token: res.jwt_refresh_token,
|
|
983
|
+
address: res.address
|
|
984
|
+
});
|
|
985
|
+
setAccessTokenExpiresAt(loggedInAt + res.jwt_expires_in * 1e3);
|
|
986
|
+
} catch (err) {
|
|
987
|
+
setError(err instanceof Error ? err : new Error("Sign-in failed"));
|
|
988
|
+
throw err;
|
|
989
|
+
} finally {
|
|
990
|
+
setIsLoading(false);
|
|
991
|
+
loginInFlight.current = false;
|
|
992
|
+
}
|
|
993
|
+
}, [wagmiContext, address, client, networkConfig.chainId, networkConfig.apiUrl]);
|
|
994
|
+
const refreshAccessToken = react.useCallback(async () => {
|
|
995
|
+
const data = refreshDataRef.current;
|
|
996
|
+
if (!data || refreshInFlight.current) return;
|
|
997
|
+
if (Date.now() >= data.refreshExpiresAt - AUTH_REFRESH_SKEW_MS) {
|
|
998
|
+
clearSession();
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
refreshInFlight.current = true;
|
|
1002
|
+
try {
|
|
1003
|
+
const res = await client.refreshJwtSession({ refresh_token: data.refreshToken });
|
|
1004
|
+
const refreshedAt = Date.now();
|
|
1005
|
+
client.setBearerToken(res.token);
|
|
1006
|
+
refreshDataRef.current = {
|
|
1007
|
+
refreshToken: res.refresh_token,
|
|
1008
|
+
refreshExpiresAt: refreshedAt + res.refresh_expires_in * 1e3
|
|
1009
|
+
};
|
|
1010
|
+
setTokens(
|
|
1011
|
+
(prev) => prev ? { ...prev, jwt_access_token: res.token, jwt_refresh_token: res.refresh_token } : prev
|
|
1012
|
+
);
|
|
1013
|
+
setAccessTokenExpiresAt(refreshedAt + res.expires_in * 1e3);
|
|
1014
|
+
} catch {
|
|
1015
|
+
clearSession();
|
|
1016
|
+
} finally {
|
|
1017
|
+
refreshInFlight.current = false;
|
|
1018
|
+
}
|
|
1019
|
+
}, [client, clearSession]);
|
|
1020
|
+
react.useEffect(() => {
|
|
1021
|
+
if (accessTokenExpiresAt == null) return;
|
|
1022
|
+
const delay = Math.max(accessTokenExpiresAt - AUTH_REFRESH_SKEW_MS - Date.now(), 0);
|
|
1023
|
+
const timer = setTimeout(() => {
|
|
1024
|
+
void refreshAccessToken();
|
|
1025
|
+
}, delay);
|
|
1026
|
+
return () => clearTimeout(timer);
|
|
1027
|
+
}, [accessTokenExpiresAt, refreshAccessToken]);
|
|
1028
|
+
react.useEffect(() => {
|
|
1029
|
+
if (!autoLogin) return;
|
|
1030
|
+
if (status === "connecting" || status === "reconnecting") return;
|
|
1031
|
+
if (!isConnected && session) {
|
|
1032
|
+
clearSession();
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
if (isConnected && address && session && address.toLowerCase() !== session.address.toLowerCase()) {
|
|
1036
|
+
clearSession();
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
if (isConnected && address && !session && !isLoading && autoAttemptedAddress.current !== address) {
|
|
1040
|
+
autoAttemptedAddress.current = address;
|
|
1041
|
+
void login().catch(() => {
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
}, [autoLogin, status, isConnected, address, session, isLoading, login, clearSession]);
|
|
1045
|
+
const value = react.useMemo(
|
|
1046
|
+
() => ({
|
|
1047
|
+
isAuthenticated: !!session,
|
|
1048
|
+
isLoading,
|
|
1049
|
+
error,
|
|
1050
|
+
session,
|
|
1051
|
+
accessToken: tokens?.jwt_access_token,
|
|
1052
|
+
tokens,
|
|
1053
|
+
login,
|
|
1054
|
+
logout
|
|
1055
|
+
}),
|
|
1056
|
+
[session, isLoading, error, tokens, login, logout]
|
|
1057
|
+
);
|
|
1058
|
+
return /* @__PURE__ */ jsxRuntime.jsx(SiweAuthContext.Provider, { value, children });
|
|
1059
|
+
}
|
|
1060
|
+
function useSiweAuth() {
|
|
1061
|
+
const ctx = react.useContext(SiweAuthContext);
|
|
1062
|
+
if (!ctx) throw new Error("useSiweAuth must be used within SiweAuthProvider");
|
|
1063
|
+
return ctx;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// src/sdk/moonpay-currency-codes.ts
|
|
1067
|
+
var MOONPAY_CURRENCY_CODE_BY_TOKEN_ID = {
|
|
1068
|
+
// Sandbox MPT test token (Ethereum Sepolia).
|
|
1069
|
+
"0xbd3a41ffd21be1cfcdca7a4e7755842a5b78c9443fb7ea008e6a7314f0caea87": "usdc"
|
|
1070
|
+
};
|
|
1071
|
+
function resolveMoonpayCurrencyCode(tokenId) {
|
|
1072
|
+
return MOONPAY_CURRENCY_CODE_BY_TOKEN_ID[tokenId.toLowerCase()];
|
|
1073
|
+
}
|
|
1074
|
+
var PrivanaContext = react.createContext(null);
|
|
1075
|
+
function readStoredHostedAuthSession(storage, hostedAuthStorageKey, now = Date.now()) {
|
|
1076
|
+
const raw = storage.getItem(hostedAuthStorageKey);
|
|
1077
|
+
if (!raw) {
|
|
1078
|
+
return null;
|
|
1079
|
+
}
|
|
1080
|
+
try {
|
|
1081
|
+
const parsed = JSON.parse(raw);
|
|
1082
|
+
if (!isHostedAuthRefreshActive(parsed, now, 0)) {
|
|
1083
|
+
storage.removeItem(hostedAuthStorageKey);
|
|
1084
|
+
return null;
|
|
1085
|
+
}
|
|
1086
|
+
return parsed;
|
|
1087
|
+
} catch {
|
|
1088
|
+
storage.removeItem(hostedAuthStorageKey);
|
|
1089
|
+
return null;
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
function syncHostedAuthSessionToClient(client, hostedAuthConfig, hostedAuthSession) {
|
|
1093
|
+
if (!hostedAuthConfig) {
|
|
1094
|
+
client.clearBearerToken();
|
|
1095
|
+
client.clearPrivateReadToken();
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
if (hostedAuthSession && isHostedAuthSessionActive(hostedAuthSession)) {
|
|
1099
|
+
client.setBearerToken(hostedAuthSession.accessToken);
|
|
1100
|
+
client.clearPrivateReadToken();
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
client.clearBearerToken();
|
|
1104
|
+
client.clearPrivateReadToken();
|
|
1105
|
+
}
|
|
1106
|
+
var DEFAULT_NETWORK_CONFIG = NETWORK_CONFIG.testnet;
|
|
1107
|
+
function PrivanaProvider({
|
|
1108
|
+
children,
|
|
1109
|
+
networkConfig: networkConfigOverride,
|
|
1110
|
+
tokens,
|
|
1111
|
+
chains,
|
|
1112
|
+
pollingInterval = 1e4,
|
|
1113
|
+
serviceAddress,
|
|
1114
|
+
serviceName,
|
|
1115
|
+
serviceIcon,
|
|
1116
|
+
hostedAuth,
|
|
1117
|
+
siweAuth
|
|
1118
|
+
}) {
|
|
1119
|
+
if (hostedAuth && siweAuth) {
|
|
1120
|
+
throw new Error(
|
|
1121
|
+
"PrivanaProvider: `hostedAuth` and `siweAuth` are mutually exclusive - provide only one. When both are set, private reads use hosted auth and the in-app SIWE login is ignored."
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
1124
|
+
const networkConfig = react.useMemo(() => {
|
|
1125
|
+
const config = {
|
|
1126
|
+
...DEFAULT_NETWORK_CONFIG,
|
|
1127
|
+
...networkConfigOverride
|
|
1128
|
+
};
|
|
1129
|
+
if (!config.chainId || config.chainId <= 0) {
|
|
1130
|
+
throw new Error("PrivanaProvider: networkConfig.chainId must be a positive number");
|
|
1131
|
+
}
|
|
1132
|
+
if (!config.accountingContract || !config.accountingContract.startsWith("0x")) {
|
|
1133
|
+
throw new Error("PrivanaProvider: networkConfig.accountingContract must be a valid address");
|
|
1134
|
+
}
|
|
1135
|
+
if (!config.apiUrl) {
|
|
1136
|
+
throw new Error("PrivanaProvider: networkConfig.apiUrl must be provided");
|
|
1137
|
+
}
|
|
1138
|
+
return config;
|
|
1139
|
+
}, [
|
|
1140
|
+
networkConfigOverride?.chainId,
|
|
1141
|
+
networkConfigOverride?.name,
|
|
1142
|
+
networkConfigOverride?.accountingContract,
|
|
1143
|
+
networkConfigOverride?.apiUrl
|
|
1144
|
+
]);
|
|
1145
|
+
const resolvedChains = react.useMemo(() => {
|
|
1146
|
+
if (chains && chains.length > 0) return chains;
|
|
1147
|
+
return SUPPORTED_CHAINS;
|
|
1148
|
+
}, [chains]);
|
|
1149
|
+
const client = react.useMemo(
|
|
1150
|
+
() => new PrivanaClient({ baseUrl: networkConfig.apiUrl }),
|
|
1151
|
+
[networkConfig.apiUrl]
|
|
1152
|
+
);
|
|
1153
|
+
const [allTokens, setAllTokens] = react.useState([]);
|
|
1154
|
+
const [tokensStatus, setTokensStatus] = react.useState("loading");
|
|
1155
|
+
const [tokensError, setTokensError] = react.useState();
|
|
1156
|
+
react.useEffect(() => {
|
|
1157
|
+
setTokensStatus("loading");
|
|
1158
|
+
setTokensError(void 0);
|
|
1159
|
+
client.listTokens().then(({ tokens: list }) => {
|
|
1160
|
+
setAllTokens(
|
|
1161
|
+
list.map((t) => ({
|
|
1162
|
+
id: t.token_id,
|
|
1163
|
+
symbol: t.symbol,
|
|
1164
|
+
decimals: t.decimals,
|
|
1165
|
+
contract: t.token_address ?? viem.zeroAddress,
|
|
1166
|
+
name: t.name,
|
|
1167
|
+
chainId: t.chain_id,
|
|
1168
|
+
moonpayCurrencyCode: resolveMoonpayCurrencyCode(t.token_id)
|
|
1169
|
+
}))
|
|
1170
|
+
);
|
|
1171
|
+
setTokensStatus("ready");
|
|
1172
|
+
}).catch((err) => {
|
|
1173
|
+
setTokensError(err instanceof Error ? err : new Error(String(err)));
|
|
1174
|
+
setTokensStatus("error");
|
|
1175
|
+
});
|
|
1176
|
+
}, [client]);
|
|
1177
|
+
const enabledTokens = react.useMemo(() => {
|
|
1178
|
+
if (tokens && tokens.length > 0) {
|
|
1179
|
+
const allowed = new Set(tokens.map((id) => id.toLowerCase()));
|
|
1180
|
+
return allTokens.filter((t) => allowed.has(t.id.toLowerCase()));
|
|
1181
|
+
}
|
|
1182
|
+
return allTokens;
|
|
1183
|
+
}, [allTokens, tokens]);
|
|
1184
|
+
const tokenById = react.useMemo(
|
|
1185
|
+
() => Object.fromEntries(enabledTokens.map((t) => [t.id.toLowerCase(), t])),
|
|
1186
|
+
[enabledTokens]
|
|
1187
|
+
);
|
|
1188
|
+
const getTokenById = react.useMemo(() => (id) => tokenById[id.toLowerCase()], [tokenById]);
|
|
1189
|
+
const chainById = react.useMemo(
|
|
1190
|
+
() => Object.fromEntries(resolvedChains.map((c) => [c.id, c])),
|
|
1191
|
+
[resolvedChains]
|
|
1192
|
+
);
|
|
1193
|
+
const getChainById2 = react.useMemo(() => (id) => chainById[id], [chainById]);
|
|
1194
|
+
const hostedAuthConfig = react.useMemo(() => {
|
|
1195
|
+
if (!hostedAuth) return null;
|
|
1196
|
+
const clientId = hostedAuth.clientId.trim();
|
|
1197
|
+
const redirectUri = hostedAuth.redirectUri.trim();
|
|
1198
|
+
if (!clientId) {
|
|
1199
|
+
throw new Error(
|
|
1200
|
+
"PrivanaProvider: hostedAuth.clientId must be provided when hostedAuth is enabled"
|
|
1201
|
+
);
|
|
1202
|
+
}
|
|
1203
|
+
if (!redirectUri) {
|
|
1204
|
+
throw new Error(
|
|
1205
|
+
"PrivanaProvider: hostedAuth.redirectUri must be provided when hostedAuth is enabled"
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
return {
|
|
1209
|
+
clientId,
|
|
1210
|
+
redirectUri
|
|
1211
|
+
};
|
|
1212
|
+
}, [hostedAuth]);
|
|
1213
|
+
const hostedAuthStorageKey = react.useMemo(
|
|
1214
|
+
() => hostedAuthConfig ? createHostedAuthStorageKey(networkConfig.apiUrl, hostedAuthConfig) : null,
|
|
1215
|
+
[hostedAuthConfig, networkConfig.apiUrl]
|
|
1216
|
+
);
|
|
1217
|
+
const [hostedAuthSession, setHostedAuthSessionState] = react.useState(null);
|
|
1218
|
+
const hostedAuthSessionRef = react.useRef(null);
|
|
1219
|
+
const hostedAuthStateVersionRef = react.useRef(0);
|
|
1220
|
+
const hostedAuthRefreshInflight = react.useRef(null);
|
|
1221
|
+
const clearHostedAuthSession = react.useCallback(() => {
|
|
1222
|
+
hostedAuthStateVersionRef.current += 1;
|
|
1223
|
+
hostedAuthSessionRef.current = null;
|
|
1224
|
+
hostedAuthRefreshInflight.current = null;
|
|
1225
|
+
setHostedAuthSessionState(null);
|
|
1226
|
+
client.clearBearerToken();
|
|
1227
|
+
client.clearPrivateReadToken();
|
|
1228
|
+
if (hostedAuthStorageKey && typeof window !== "undefined") {
|
|
1229
|
+
window.sessionStorage.removeItem(hostedAuthStorageKey);
|
|
1230
|
+
}
|
|
1231
|
+
}, [client, hostedAuthStorageKey]);
|
|
1232
|
+
const setHostedAuthSession = react.useCallback(
|
|
1233
|
+
(session) => {
|
|
1234
|
+
if (!session) {
|
|
1235
|
+
clearHostedAuthSession();
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
hostedAuthStateVersionRef.current += 1;
|
|
1239
|
+
hostedAuthSessionRef.current = session;
|
|
1240
|
+
setHostedAuthSessionState(session);
|
|
1241
|
+
if (isHostedAuthSessionActive(session)) {
|
|
1242
|
+
client.setBearerToken(session.accessToken);
|
|
1243
|
+
} else {
|
|
1244
|
+
client.clearBearerToken();
|
|
1245
|
+
}
|
|
1246
|
+
client.clearPrivateReadToken();
|
|
1247
|
+
if (hostedAuthStorageKey && typeof window !== "undefined") {
|
|
1248
|
+
window.sessionStorage.setItem(hostedAuthStorageKey, JSON.stringify(session));
|
|
1249
|
+
}
|
|
1250
|
+
},
|
|
1251
|
+
[clearHostedAuthSession, client, hostedAuthStorageKey]
|
|
1252
|
+
);
|
|
1253
|
+
const refreshHostedAuthSession = react.useCallback(async () => {
|
|
1254
|
+
if (!hostedAuthConfig) {
|
|
1255
|
+
throw new HostedAuthRequiredError(
|
|
1256
|
+
"Hosted redirect authentication is not configured for this provider."
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
const currentSession = hostedAuthSessionRef.current;
|
|
1260
|
+
if (!currentSession) {
|
|
1261
|
+
throw new HostedAuthRequiredError();
|
|
1262
|
+
}
|
|
1263
|
+
if (!isHostedAuthRefreshActive(currentSession)) {
|
|
1264
|
+
clearHostedAuthSession();
|
|
1265
|
+
throw new HostedAuthRequiredError(
|
|
1266
|
+
"Hosted redirect authentication has expired. Start login again."
|
|
1267
|
+
);
|
|
1268
|
+
}
|
|
1269
|
+
if (hostedAuthRefreshInflight.current) {
|
|
1270
|
+
return hostedAuthRefreshInflight.current;
|
|
1271
|
+
}
|
|
1272
|
+
const refreshPromise = (async () => {
|
|
1273
|
+
const refreshVersion = hostedAuthStateVersionRef.current;
|
|
1274
|
+
try {
|
|
1275
|
+
const response = await client.refreshJwtSession({
|
|
1276
|
+
refresh_token: currentSession.refreshToken
|
|
1277
|
+
});
|
|
1278
|
+
if (refreshVersion !== hostedAuthStateVersionRef.current) {
|
|
1279
|
+
const latestSession = hostedAuthSessionRef.current;
|
|
1280
|
+
if (latestSession) {
|
|
1281
|
+
return latestSession;
|
|
1282
|
+
}
|
|
1283
|
+
throw new HostedAuthRequiredError(
|
|
1284
|
+
"Hosted redirect authentication has changed. Start login again."
|
|
1285
|
+
);
|
|
1286
|
+
}
|
|
1287
|
+
const nextSession = applyRefreshResponse(currentSession, response);
|
|
1288
|
+
setHostedAuthSession(nextSession);
|
|
1289
|
+
return nextSession;
|
|
1290
|
+
} catch (error) {
|
|
1291
|
+
if (refreshVersion === hostedAuthStateVersionRef.current) {
|
|
1292
|
+
clearHostedAuthSession();
|
|
1293
|
+
}
|
|
1294
|
+
throw error;
|
|
1295
|
+
} finally {
|
|
1296
|
+
hostedAuthRefreshInflight.current = null;
|
|
1297
|
+
}
|
|
1298
|
+
})();
|
|
1299
|
+
hostedAuthRefreshInflight.current = refreshPromise;
|
|
1300
|
+
return refreshPromise;
|
|
1301
|
+
}, [clearHostedAuthSession, client, hostedAuthConfig, setHostedAuthSession]);
|
|
1302
|
+
react.useEffect(() => {
|
|
1303
|
+
hostedAuthSessionRef.current = hostedAuthSession;
|
|
1304
|
+
}, [hostedAuthSession]);
|
|
1305
|
+
react.useEffect(() => {
|
|
1306
|
+
if (!hostedAuthStorageKey || typeof window === "undefined") {
|
|
1307
|
+
hostedAuthStateVersionRef.current += 1;
|
|
1308
|
+
hostedAuthSessionRef.current = null;
|
|
1309
|
+
hostedAuthRefreshInflight.current = null;
|
|
1310
|
+
setHostedAuthSessionState(null);
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
const restoredSession = readStoredHostedAuthSession(window.sessionStorage, hostedAuthStorageKey);
|
|
1314
|
+
hostedAuthStateVersionRef.current += 1;
|
|
1315
|
+
hostedAuthSessionRef.current = restoredSession;
|
|
1316
|
+
hostedAuthRefreshInflight.current = null;
|
|
1317
|
+
setHostedAuthSessionState(restoredSession);
|
|
1318
|
+
}, [hostedAuthStorageKey]);
|
|
1319
|
+
react.useEffect(() => {
|
|
1320
|
+
syncHostedAuthSessionToClient(client, hostedAuthConfig, hostedAuthSession);
|
|
1321
|
+
}, [client, hostedAuthConfig, hostedAuthSession]);
|
|
1322
|
+
const value = react.useMemo(
|
|
1323
|
+
() => ({
|
|
1324
|
+
client,
|
|
1325
|
+
networkConfig,
|
|
1326
|
+
enabledTokens,
|
|
1327
|
+
defaultToken: enabledTokens[0],
|
|
1328
|
+
getTokenById,
|
|
1329
|
+
getChainById: getChainById2,
|
|
1330
|
+
chains: resolvedChains,
|
|
1331
|
+
tokensStatus,
|
|
1332
|
+
tokensError,
|
|
1333
|
+
pollingInterval,
|
|
1334
|
+
serviceAddress,
|
|
1335
|
+
serviceName,
|
|
1336
|
+
serviceIcon,
|
|
1337
|
+
hostedAuthConfig,
|
|
1338
|
+
hostedAuthSession,
|
|
1339
|
+
setHostedAuthSession,
|
|
1340
|
+
clearHostedAuthSession,
|
|
1341
|
+
refreshHostedAuthSession
|
|
1342
|
+
}),
|
|
1343
|
+
[
|
|
1344
|
+
client,
|
|
1345
|
+
networkConfig,
|
|
1346
|
+
enabledTokens,
|
|
1347
|
+
getTokenById,
|
|
1348
|
+
getChainById2,
|
|
1349
|
+
resolvedChains,
|
|
1350
|
+
tokensStatus,
|
|
1351
|
+
tokensError,
|
|
1352
|
+
pollingInterval,
|
|
1353
|
+
serviceAddress,
|
|
1354
|
+
serviceName,
|
|
1355
|
+
serviceIcon,
|
|
1356
|
+
hostedAuthConfig,
|
|
1357
|
+
hostedAuthSession,
|
|
1358
|
+
setHostedAuthSession,
|
|
1359
|
+
clearHostedAuthSession,
|
|
1360
|
+
refreshHostedAuthSession
|
|
1361
|
+
]
|
|
1362
|
+
);
|
|
1363
|
+
return /* @__PURE__ */ jsxRuntime.jsx(PrivanaContext.Provider, { value, children: siweAuth ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1364
|
+
SiweAuthProvider,
|
|
1365
|
+
{
|
|
1366
|
+
client,
|
|
1367
|
+
networkConfig,
|
|
1368
|
+
autoLogin: typeof siweAuth === "object" ? siweAuth.autoLogin : void 0,
|
|
1369
|
+
children
|
|
1370
|
+
}
|
|
1371
|
+
) : children });
|
|
1372
|
+
}
|
|
1373
|
+
function usePrivanaContext() {
|
|
1374
|
+
const context = react.useContext(PrivanaContext);
|
|
1375
|
+
if (!context) {
|
|
1376
|
+
throw new Error("usePrivanaContext must be used within a PrivanaProvider");
|
|
1377
|
+
}
|
|
1378
|
+
return context;
|
|
1379
|
+
}
|
|
1380
|
+
function useSafePrivanaContext() {
|
|
1381
|
+
return react.useContext(PrivanaContext);
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
// src/sdk/hooks/browser-storage.ts
|
|
1385
|
+
function storageCandidate(name) {
|
|
1386
|
+
try {
|
|
1387
|
+
if (typeof window === "undefined") return void 0;
|
|
1388
|
+
return window[name] ?? void 0;
|
|
1389
|
+
} catch {
|
|
1390
|
+
return void 0;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
function storageCandidates() {
|
|
1394
|
+
return [storageCandidate("localStorage"), storageCandidate("sessionStorage")].filter(
|
|
1395
|
+
(storage) => storage !== void 0
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
1398
|
+
function canUseBrowserStorage() {
|
|
1399
|
+
const probeKey = "privana:storage-probe";
|
|
1400
|
+
for (const storage of storageCandidates()) {
|
|
1401
|
+
try {
|
|
1402
|
+
storage.setItem(probeKey, "1");
|
|
1403
|
+
storage.removeItem(probeKey);
|
|
1404
|
+
return true;
|
|
1405
|
+
} catch {
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
return false;
|
|
1409
|
+
}
|
|
1410
|
+
function setBrowserStorageItem(key, value) {
|
|
1411
|
+
let stored = false;
|
|
1412
|
+
for (const storage of storageCandidates()) {
|
|
1413
|
+
try {
|
|
1414
|
+
storage.setItem(key, value);
|
|
1415
|
+
stored = true;
|
|
1416
|
+
} catch {
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
return stored;
|
|
1420
|
+
}
|
|
1421
|
+
function getBrowserStorageItem(key) {
|
|
1422
|
+
for (const storage of storageCandidates()) {
|
|
1423
|
+
try {
|
|
1424
|
+
const value = storage.getItem(key);
|
|
1425
|
+
if (value !== null) return value;
|
|
1426
|
+
} catch {
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
return null;
|
|
1430
|
+
}
|
|
1431
|
+
function removeBrowserStorageItem(key) {
|
|
1432
|
+
for (const storage of storageCandidates()) {
|
|
1433
|
+
try {
|
|
1434
|
+
storage.removeItem(key);
|
|
1435
|
+
} catch {
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// src/sdk/hooks/pending-lock.ts
|
|
1441
|
+
var DEFAULT_LOCK_DURATION_SECONDS = 259200;
|
|
1442
|
+
var DEFAULT_ONRAMP_LOCK_BUFFER = 0.02;
|
|
1443
|
+
var BUFFER_SCALE = 1000000n;
|
|
1444
|
+
function applyLockBuffer(amount, buffer = DEFAULT_ONRAMP_LOCK_BUFFER) {
|
|
1445
|
+
if (!Number.isFinite(buffer) || buffer < 0 || buffer >= 1) {
|
|
1446
|
+
throw new Error(`Lock buffer must be in [0, 1), got ${buffer}`);
|
|
1447
|
+
}
|
|
1448
|
+
const shave = BigInt(Math.max(0, Math.ceil(buffer * Number(BUFFER_SCALE) - 1e-6)));
|
|
1449
|
+
return amount * (BUFFER_SCALE - shave) / BUFFER_SCALE;
|
|
1450
|
+
}
|
|
1451
|
+
function clampLockAmount(amount, maxAmount) {
|
|
1452
|
+
return maxAmount !== void 0 && maxAmount < amount ? maxAmount : amount;
|
|
1453
|
+
}
|
|
1454
|
+
async function createSignedLockRequest({
|
|
1455
|
+
client,
|
|
1456
|
+
walletClient,
|
|
1457
|
+
userAddress,
|
|
1458
|
+
networkConfig,
|
|
1459
|
+
serviceAddress,
|
|
1460
|
+
tokenId,
|
|
1461
|
+
amount,
|
|
1462
|
+
lockDuration = DEFAULT_LOCK_DURATION_SECONDS
|
|
1463
|
+
}) {
|
|
1464
|
+
if (amount <= 0n) {
|
|
1465
|
+
throw new Error("Lock amount must be positive");
|
|
1466
|
+
}
|
|
1467
|
+
const expiry = BigInt(Math.floor(Date.now() / 1e3) + lockDuration);
|
|
1468
|
+
const { nonce } = await client.getLockNonce(userAddress);
|
|
1469
|
+
const signature = await signLockMessage({
|
|
1470
|
+
walletClient,
|
|
1471
|
+
chainId: networkConfig.chainId,
|
|
1472
|
+
verifyingContract: networkConfig.accountingContract,
|
|
1473
|
+
message: {
|
|
1474
|
+
serviceAddress,
|
|
1475
|
+
tokenId,
|
|
1476
|
+
amount,
|
|
1477
|
+
expiry,
|
|
1478
|
+
nonce: BigInt(nonce)
|
|
1479
|
+
}
|
|
1480
|
+
});
|
|
1481
|
+
return {
|
|
1482
|
+
service_address: serviceAddress,
|
|
1483
|
+
token_id: tokenId,
|
|
1484
|
+
amount: amount.toString(),
|
|
1485
|
+
expiry: expiry.toString(),
|
|
1486
|
+
nonce: String(nonce),
|
|
1487
|
+
signature
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
var EXPIRY_SLACK_SECONDS = 60;
|
|
1491
|
+
function isSignedLockUsable(payload) {
|
|
1492
|
+
const expiry = Number(payload.expiry);
|
|
1493
|
+
if (!Number.isFinite(expiry)) return false;
|
|
1494
|
+
return expiry > Math.floor(Date.now() / 1e3) + EXPIRY_SLACK_SECONDS;
|
|
1495
|
+
}
|
|
1496
|
+
var PostDepositLockError = class _PostDepositLockError extends Error {
|
|
1497
|
+
constructor(message, reason, signedAmount, creditedAmount, options) {
|
|
1498
|
+
super(message, options);
|
|
1499
|
+
this.reason = reason;
|
|
1500
|
+
this.signedAmount = signedAmount;
|
|
1501
|
+
this.creditedAmount = creditedAmount;
|
|
1502
|
+
this.name = "PostDepositLockError";
|
|
1503
|
+
Object.setPrototypeOf(this, _PostDepositLockError.prototype);
|
|
1504
|
+
}
|
|
1505
|
+
};
|
|
1506
|
+
async function submitPendingLock({
|
|
1507
|
+
client,
|
|
1508
|
+
payload,
|
|
1509
|
+
creditedAmount
|
|
1510
|
+
}) {
|
|
1511
|
+
let signedAmount;
|
|
1512
|
+
try {
|
|
1513
|
+
signedAmount = BigInt(payload.amount);
|
|
1514
|
+
} catch (err) {
|
|
1515
|
+
throw new PostDepositLockError(
|
|
1516
|
+
"Stored signed lock payload is malformed",
|
|
1517
|
+
"submission-failed",
|
|
1518
|
+
void 0,
|
|
1519
|
+
creditedAmount,
|
|
1520
|
+
{ cause: err }
|
|
1521
|
+
);
|
|
1522
|
+
}
|
|
1523
|
+
if (!isSignedLockUsable(payload)) {
|
|
1524
|
+
throw new PostDepositLockError(
|
|
1525
|
+
"Signed lock expired before the deposit was credited",
|
|
1526
|
+
"expired",
|
|
1527
|
+
signedAmount,
|
|
1528
|
+
creditedAmount
|
|
1529
|
+
);
|
|
1530
|
+
}
|
|
1531
|
+
if (creditedAmount !== void 0 && creditedAmount < signedAmount) {
|
|
1532
|
+
throw new PostDepositLockError(
|
|
1533
|
+
`Credited amount (${creditedAmount}) is below the signed lock amount (${signedAmount})`,
|
|
1534
|
+
"credited-below-signed",
|
|
1535
|
+
signedAmount,
|
|
1536
|
+
creditedAmount
|
|
1537
|
+
);
|
|
1538
|
+
}
|
|
1539
|
+
try {
|
|
1540
|
+
return await client.lockFunds(payload);
|
|
1541
|
+
} catch (err) {
|
|
1542
|
+
throw new PostDepositLockError(
|
|
1543
|
+
err instanceof Error ? err.message : "Lock submission failed",
|
|
1544
|
+
"submission-failed",
|
|
1545
|
+
signedAmount,
|
|
1546
|
+
creditedAmount,
|
|
1547
|
+
{ cause: err }
|
|
1548
|
+
);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
function pendingLockKey(userAddress, correlationId) {
|
|
1552
|
+
return `privana:pending-lock:${userAddress.toLowerCase()}:${correlationId}`;
|
|
1553
|
+
}
|
|
1554
|
+
function savePendingLock(userAddress, correlationId, payload) {
|
|
1555
|
+
const record = { payload, savedAt: Date.now() };
|
|
1556
|
+
const stored = setBrowserStorageItem(
|
|
1557
|
+
pendingLockKey(userAddress, correlationId),
|
|
1558
|
+
JSON.stringify(record)
|
|
1559
|
+
);
|
|
1560
|
+
if (!stored) {
|
|
1561
|
+
throw new Error("Unable to persist signed lock for recovery");
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
function loadPendingLock(userAddress, correlationId) {
|
|
1565
|
+
const key = pendingLockKey(userAddress, correlationId);
|
|
1566
|
+
try {
|
|
1567
|
+
const raw = getBrowserStorageItem(key);
|
|
1568
|
+
if (!raw) return void 0;
|
|
1569
|
+
const record = JSON.parse(raw);
|
|
1570
|
+
if (!record?.payload?.signature) {
|
|
1571
|
+
removeBrowserStorageItem(key);
|
|
1572
|
+
return void 0;
|
|
1573
|
+
}
|
|
1574
|
+
return record.payload;
|
|
1575
|
+
} catch {
|
|
1576
|
+
return void 0;
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
function clearPendingLock(userAddress, correlationId) {
|
|
1580
|
+
removeBrowserStorageItem(pendingLockKey(userAddress, correlationId));
|
|
1581
|
+
}
|
|
1582
|
+
var INITIAL_AUTH_BACKOFF_MS = 5e3;
|
|
1583
|
+
var MAX_AUTH_BACKOFF_MS = 6e4;
|
|
1584
|
+
var DEFAULT_SIWE_AUTH_VALIDITY_MS = 24 * 60 * 60 * 1e3;
|
|
1585
|
+
var privateReadFailureCache = /* @__PURE__ */ new Map();
|
|
1586
|
+
var privateReadInflight = /* @__PURE__ */ new Map();
|
|
1587
|
+
async function executeHostedAuthPrivateReadRequest({
|
|
1588
|
+
client,
|
|
1589
|
+
hostedAuthSession,
|
|
1590
|
+
refreshHostedAuthSession,
|
|
1591
|
+
request
|
|
1592
|
+
}) {
|
|
1593
|
+
const ensureHostedAuth = async (forceRefresh) => {
|
|
1594
|
+
if (!hostedAuthSession) {
|
|
1595
|
+
throw new HostedAuthRequiredError();
|
|
1596
|
+
}
|
|
1597
|
+
if (!forceRefresh && isHostedAuthSessionActive(hostedAuthSession)) {
|
|
1598
|
+
client.clearPrivateReadToken();
|
|
1599
|
+
client.setBearerToken(hostedAuthSession.accessToken);
|
|
1600
|
+
return hostedAuthSession.accessToken;
|
|
1601
|
+
}
|
|
1602
|
+
const refreshed = await refreshHostedAuthSession();
|
|
1603
|
+
client.clearPrivateReadToken();
|
|
1604
|
+
client.setBearerToken(refreshed.accessToken);
|
|
1605
|
+
return refreshed.accessToken;
|
|
1606
|
+
};
|
|
1607
|
+
await ensureHostedAuth(false);
|
|
1608
|
+
try {
|
|
1609
|
+
return await request();
|
|
1610
|
+
} catch (error) {
|
|
1611
|
+
if (!(error instanceof AccountingApiError) || error.statusCode !== 401) {
|
|
1612
|
+
throw error;
|
|
1613
|
+
}
|
|
1614
|
+
await ensureHostedAuth(true);
|
|
1615
|
+
return request();
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
function clearPrivateReadScope(scopeKey, client) {
|
|
1619
|
+
deleteCachedPrivateReadToken(scopeKey);
|
|
1620
|
+
privateReadFailureCache.delete(scopeKey);
|
|
1621
|
+
client.clearPrivateReadToken();
|
|
1622
|
+
}
|
|
1623
|
+
function recordPrivateReadFailure(scopeKey) {
|
|
1624
|
+
const previous = privateReadFailureCache.get(scopeKey);
|
|
1625
|
+
const backoffMs = Math.min(
|
|
1626
|
+
previous ? previous.backoffMs * 2 : INITIAL_AUTH_BACKOFF_MS,
|
|
1627
|
+
MAX_AUTH_BACKOFF_MS
|
|
1628
|
+
);
|
|
1629
|
+
privateReadFailureCache.set(scopeKey, {
|
|
1630
|
+
backoffMs,
|
|
1631
|
+
retryAt: Date.now() + backoffMs
|
|
1632
|
+
});
|
|
1633
|
+
}
|
|
1634
|
+
function ensureFailureBackoff(scopeKey) {
|
|
1635
|
+
const failure = privateReadFailureCache.get(scopeKey);
|
|
1636
|
+
if (!failure) return;
|
|
1637
|
+
if (failure.retryAt <= Date.now()) {
|
|
1638
|
+
privateReadFailureCache.delete(scopeKey);
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1641
|
+
throw new Error(
|
|
1642
|
+
`Private-read authentication is temporarily paused after a recent failure. Retry in ${Math.ceil(
|
|
1643
|
+
(failure.retryAt - Date.now()) / 1e3
|
|
1644
|
+
)}s.`
|
|
1645
|
+
);
|
|
1646
|
+
}
|
|
1647
|
+
function usePrivateReadRequest() {
|
|
1648
|
+
const wagmiContext = react.useContext(wagmi.WagmiContext);
|
|
1649
|
+
const { client, networkConfig, hostedAuthConfig, hostedAuthSession, refreshHostedAuthSession } = usePrivanaContext();
|
|
1650
|
+
const { address: walletAddress } = useSafeAccount();
|
|
1651
|
+
const privateReadAddress = hostedAuthConfig ? hostedAuthSession?.address ?? null : walletAddress ?? null;
|
|
1652
|
+
const privateReadReady = hostedAuthConfig ? !!hostedAuthSession : !!walletAddress;
|
|
1653
|
+
const executePrivateRead = react.useCallback(
|
|
1654
|
+
async (request) => {
|
|
1655
|
+
if (hostedAuthConfig) {
|
|
1656
|
+
return executeHostedAuthPrivateReadRequest({
|
|
1657
|
+
client,
|
|
1658
|
+
hostedAuthSession,
|
|
1659
|
+
refreshHostedAuthSession,
|
|
1660
|
+
request
|
|
1661
|
+
});
|
|
1662
|
+
}
|
|
1663
|
+
if (!wagmiContext) {
|
|
1664
|
+
throw new Error("WagmiProvider is required for authenticated private reads");
|
|
1665
|
+
}
|
|
1666
|
+
if (!walletAddress) {
|
|
1667
|
+
throw new Error("No wallet connected");
|
|
1668
|
+
}
|
|
1669
|
+
const walletClient = await actions.getWalletClient(wagmiContext);
|
|
1670
|
+
if (!walletClient) {
|
|
1671
|
+
throw new Error("No wallet client available");
|
|
1672
|
+
}
|
|
1673
|
+
const apiUrl = networkConfig.apiUrl;
|
|
1674
|
+
const scopeKey = createScopeKey(apiUrl, networkConfig.chainId, walletAddress);
|
|
1675
|
+
const getToken = async (forceRefresh) => {
|
|
1676
|
+
const inflight = privateReadInflight.get(scopeKey);
|
|
1677
|
+
if (inflight) {
|
|
1678
|
+
const token = await inflight;
|
|
1679
|
+
client.setPrivateReadToken(token);
|
|
1680
|
+
return token;
|
|
1681
|
+
}
|
|
1682
|
+
if (!forceRefresh) {
|
|
1683
|
+
const cached = getCachedPrivateReadToken(scopeKey);
|
|
1684
|
+
if (cached) {
|
|
1685
|
+
client.setPrivateReadToken(cached);
|
|
1686
|
+
return cached;
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
ensureFailureBackoff(scopeKey);
|
|
1690
|
+
const authPromise = (async () => {
|
|
1691
|
+
try {
|
|
1692
|
+
const [{ domain }, nonceResponse] = await Promise.all([
|
|
1693
|
+
client.getSiweDomain(),
|
|
1694
|
+
client.getSiweNonce(walletAddress)
|
|
1695
|
+
]);
|
|
1696
|
+
const issuedAt = /* @__PURE__ */ new Date();
|
|
1697
|
+
const expirationTime = new Date(issuedAt.getTime() + DEFAULT_SIWE_AUTH_VALIDITY_MS);
|
|
1698
|
+
const uri = typeof window !== "undefined" && window.location.origin ? window.location.origin : apiUrl;
|
|
1699
|
+
const message = siwe.createSiweMessage({
|
|
1700
|
+
address: walletAddress,
|
|
1701
|
+
chainId: networkConfig.chainId,
|
|
1702
|
+
domain,
|
|
1703
|
+
expirationTime,
|
|
1704
|
+
issuedAt,
|
|
1705
|
+
nonce: nonceResponse.nonce,
|
|
1706
|
+
statement: buildSiweStatement(networkConfig.chainId),
|
|
1707
|
+
uri,
|
|
1708
|
+
version: "1"
|
|
1709
|
+
});
|
|
1710
|
+
const signature = await walletClient.signMessage({
|
|
1711
|
+
account: walletClient.account ?? walletAddress,
|
|
1712
|
+
message
|
|
1713
|
+
});
|
|
1714
|
+
const login = await client.loginWithSiwe({
|
|
1715
|
+
siwe_message: message,
|
|
1716
|
+
signature
|
|
1717
|
+
});
|
|
1718
|
+
setCachedPrivateReadToken(scopeKey, login.siwe_token, expirationTime.getTime());
|
|
1719
|
+
privateReadFailureCache.delete(scopeKey);
|
|
1720
|
+
client.setPrivateReadToken(login.siwe_token);
|
|
1721
|
+
return login.siwe_token;
|
|
1722
|
+
} catch (error) {
|
|
1723
|
+
const authError = error instanceof Error ? error : new Error("Failed to authenticate private reads");
|
|
1724
|
+
clearPrivateReadScope(scopeKey, client);
|
|
1725
|
+
recordPrivateReadFailure(scopeKey);
|
|
1726
|
+
throw authError;
|
|
1727
|
+
} finally {
|
|
1728
|
+
privateReadInflight.delete(scopeKey);
|
|
1729
|
+
}
|
|
1730
|
+
})();
|
|
1731
|
+
privateReadInflight.set(scopeKey, authPromise);
|
|
1732
|
+
return authPromise;
|
|
1733
|
+
};
|
|
1734
|
+
await getToken(false);
|
|
1735
|
+
try {
|
|
1736
|
+
return await request();
|
|
1737
|
+
} catch (error) {
|
|
1738
|
+
if (!(error instanceof AccountingApiError) || error.statusCode !== 401) {
|
|
1739
|
+
throw error;
|
|
1740
|
+
}
|
|
1741
|
+
clearPrivateReadScope(scopeKey, client);
|
|
1742
|
+
await getToken(true);
|
|
1743
|
+
return request();
|
|
1744
|
+
}
|
|
1745
|
+
},
|
|
1746
|
+
[
|
|
1747
|
+
client,
|
|
1748
|
+
hostedAuthConfig,
|
|
1749
|
+
hostedAuthSession,
|
|
1750
|
+
networkConfig.apiUrl,
|
|
1751
|
+
networkConfig.chainId,
|
|
1752
|
+
refreshHostedAuthSession,
|
|
1753
|
+
walletAddress,
|
|
1754
|
+
wagmiContext
|
|
1755
|
+
]
|
|
1756
|
+
);
|
|
1757
|
+
const privateReadQueryScope = react.useMemo(
|
|
1758
|
+
() => [networkConfig.apiUrl, networkConfig.chainId, privateReadAddress],
|
|
1759
|
+
[networkConfig.apiUrl, networkConfig.chainId, privateReadAddress]
|
|
1760
|
+
);
|
|
1761
|
+
return {
|
|
1762
|
+
executePrivateRead,
|
|
1763
|
+
privateReadAddress,
|
|
1764
|
+
privateReadReady,
|
|
1765
|
+
privateReadQueryScope
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
// src/sdk/hooks/use-deposit-verification.ts
|
|
1770
|
+
function useDepositVerification(options = {}) {
|
|
1771
|
+
const { client } = usePrivanaContext();
|
|
1772
|
+
const queryClient = reactQuery.useQueryClient();
|
|
1773
|
+
const { executePrivateRead } = usePrivateReadRequest();
|
|
1774
|
+
const pollInterval = options.pollInterval ?? 5e3;
|
|
1775
|
+
const finalityRetryInterval = options.finalityRetryInterval ?? pollInterval;
|
|
1776
|
+
const pollTimeout = options.pollTimeout ?? 18e4;
|
|
1777
|
+
const [isVerifying, setIsVerifying] = react.useState(false);
|
|
1778
|
+
const [didTimeout, setDidTimeout] = react.useState(false);
|
|
1779
|
+
const [verificationFailed, setVerificationFailed] = react.useState(false);
|
|
1780
|
+
const [error, setError] = react.useState(null);
|
|
1781
|
+
const [txHash, setTxHash] = react.useState();
|
|
1782
|
+
const generationRef = react.useRef(0);
|
|
1783
|
+
const pollIntervalRef = react.useRef(null);
|
|
1784
|
+
const verificationContextRef = react.useRef(null);
|
|
1785
|
+
const onCreditedRef = react.useRef(options.onCredited);
|
|
1786
|
+
const onCheckTimeoutRef = react.useRef(options.onCheckTimeout);
|
|
1787
|
+
const onErrorRef = react.useRef(options.onError);
|
|
1788
|
+
const onCheckRetryRef = react.useRef(options.onCheckRetry);
|
|
1789
|
+
react.useEffect(() => {
|
|
1790
|
+
onCreditedRef.current = options.onCredited;
|
|
1791
|
+
onCheckTimeoutRef.current = options.onCheckTimeout;
|
|
1792
|
+
onErrorRef.current = options.onError;
|
|
1793
|
+
onCheckRetryRef.current = options.onCheckRetry;
|
|
1794
|
+
}, [options.onCredited, options.onCheckTimeout, options.onError, options.onCheckRetry]);
|
|
1795
|
+
const stopPolling = react.useCallback(() => {
|
|
1796
|
+
if (pollIntervalRef.current) {
|
|
1797
|
+
clearTimeout(pollIntervalRef.current);
|
|
1798
|
+
pollIntervalRef.current = null;
|
|
1799
|
+
}
|
|
1800
|
+
}, []);
|
|
1801
|
+
const invalidateGeneration = react.useCallback(() => {
|
|
1802
|
+
generationRef.current++;
|
|
1803
|
+
}, []);
|
|
1804
|
+
react.useEffect(() => {
|
|
1805
|
+
return () => {
|
|
1806
|
+
invalidateGeneration();
|
|
1807
|
+
stopPolling();
|
|
1808
|
+
};
|
|
1809
|
+
}, [invalidateGeneration, stopPolling]);
|
|
1810
|
+
const runVerification = react.useCallback(
|
|
1811
|
+
async (ctx, generation) => {
|
|
1812
|
+
const isStale = () => generation !== generationRef.current;
|
|
1813
|
+
const { hash, chainId, amount, logIndex } = ctx;
|
|
1814
|
+
setVerificationFailed(false);
|
|
1815
|
+
setError(null);
|
|
1816
|
+
setDidTimeout(false);
|
|
1817
|
+
setIsVerifying(true);
|
|
1818
|
+
const pollStartTime = Date.now();
|
|
1819
|
+
const markVerificationFailed = (err) => {
|
|
1820
|
+
setIsVerifying(false);
|
|
1821
|
+
setError(err);
|
|
1822
|
+
setVerificationFailed(true);
|
|
1823
|
+
onErrorRef.current?.(err);
|
|
1824
|
+
};
|
|
1825
|
+
const markVerificationTimedOut = () => {
|
|
1826
|
+
stopPolling();
|
|
1827
|
+
setIsVerifying(false);
|
|
1828
|
+
setDidTimeout(true);
|
|
1829
|
+
onCheckTimeoutRef.current?.(hash);
|
|
1830
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
|
|
1831
|
+
};
|
|
1832
|
+
try {
|
|
1833
|
+
let triggerResult;
|
|
1834
|
+
while (!triggerResult) {
|
|
1835
|
+
if (isStale()) return;
|
|
1836
|
+
try {
|
|
1837
|
+
const result = await executePrivateRead(
|
|
1838
|
+
() => client.checkDeposit({
|
|
1839
|
+
chain_id: chainId,
|
|
1840
|
+
tx_hash: hash,
|
|
1841
|
+
amount: amount.toString(),
|
|
1842
|
+
log_index: logIndex
|
|
1843
|
+
})
|
|
1844
|
+
);
|
|
1845
|
+
if (result.status === "error" && isInsufficientFinalityMessage(result.detail)) {
|
|
1846
|
+
if (result.detail) onCheckRetryRef.current?.(result.detail);
|
|
1847
|
+
if (Date.now() - pollStartTime > pollTimeout) {
|
|
1848
|
+
markVerificationTimedOut();
|
|
1849
|
+
return;
|
|
1850
|
+
}
|
|
1851
|
+
await sleep(finalityRetryInterval);
|
|
1852
|
+
if (isStale()) return;
|
|
1853
|
+
continue;
|
|
1854
|
+
}
|
|
1855
|
+
triggerResult = result;
|
|
1856
|
+
} catch (err) {
|
|
1857
|
+
if (isStale()) return;
|
|
1858
|
+
if (!isInsufficientFinalityError(err)) {
|
|
1859
|
+
throw err;
|
|
1860
|
+
}
|
|
1861
|
+
const message = err instanceof AccountingApiError && err.detail ? err.detail : err instanceof Error ? err.message : String(err);
|
|
1862
|
+
onCheckRetryRef.current?.(message);
|
|
1863
|
+
if (Date.now() - pollStartTime > pollTimeout) {
|
|
1864
|
+
markVerificationTimedOut();
|
|
1865
|
+
return;
|
|
1866
|
+
}
|
|
1867
|
+
await sleep(finalityRetryInterval);
|
|
1868
|
+
if (isStale()) return;
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
if (isStale()) return;
|
|
1872
|
+
if (triggerResult.status === "credited") {
|
|
1873
|
+
setIsVerifying(false);
|
|
1874
|
+
verificationContextRef.current = null;
|
|
1875
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
|
|
1876
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
|
|
1877
|
+
onCreditedRef.current?.(hash, triggerResult);
|
|
1878
|
+
return;
|
|
1879
|
+
}
|
|
1880
|
+
if (triggerResult.status === "error") {
|
|
1881
|
+
markVerificationFailed(new Error(triggerResult.detail ?? "Deposit verification failed"));
|
|
1882
|
+
return;
|
|
1883
|
+
}
|
|
1884
|
+
const depositId = triggerResult.deposit_id;
|
|
1885
|
+
if (!depositId) {
|
|
1886
|
+
markVerificationFailed(new Error("Deposit check did not return a deposit id"));
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
let consecutiveFailures = 0;
|
|
1890
|
+
const checkStatus = async () => {
|
|
1891
|
+
if (isStale()) return true;
|
|
1892
|
+
if (Date.now() - pollStartTime > pollTimeout) {
|
|
1893
|
+
markVerificationTimedOut();
|
|
1894
|
+
return true;
|
|
1895
|
+
}
|
|
1896
|
+
try {
|
|
1897
|
+
const result = await executePrivateRead(() => client.getDepositStatus(depositId));
|
|
1898
|
+
if (isStale()) return true;
|
|
1899
|
+
consecutiveFailures = 0;
|
|
1900
|
+
if (result.status === "credited") {
|
|
1901
|
+
stopPolling();
|
|
1902
|
+
setIsVerifying(false);
|
|
1903
|
+
verificationContextRef.current = null;
|
|
1904
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
|
|
1905
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
|
|
1906
|
+
onCreditedRef.current?.(hash, result);
|
|
1907
|
+
return true;
|
|
1908
|
+
}
|
|
1909
|
+
if (result.status === "error") {
|
|
1910
|
+
stopPolling();
|
|
1911
|
+
markVerificationFailed(new Error(result.detail ?? "Deposit verification failed"));
|
|
1912
|
+
return true;
|
|
1913
|
+
}
|
|
1914
|
+
} catch (err) {
|
|
1915
|
+
if (isStale()) return true;
|
|
1916
|
+
consecutiveFailures++;
|
|
1917
|
+
console.warn("Error polling deposit status:", err);
|
|
1918
|
+
if (consecutiveFailures >= 3) {
|
|
1919
|
+
stopPolling();
|
|
1920
|
+
markVerificationFailed(
|
|
1921
|
+
err instanceof Error ? err : new Error("Deposit status polling failed")
|
|
1922
|
+
);
|
|
1923
|
+
return true;
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
return false;
|
|
1927
|
+
};
|
|
1928
|
+
const pollLoop = async () => {
|
|
1929
|
+
const done = await checkStatus();
|
|
1930
|
+
if (!done && !isStale() && pollIntervalRef.current !== null) {
|
|
1931
|
+
pollIntervalRef.current = setTimeout(pollLoop, pollInterval);
|
|
1932
|
+
}
|
|
1933
|
+
};
|
|
1934
|
+
pollIntervalRef.current = setTimeout(pollLoop, pollInterval);
|
|
1935
|
+
} catch (err) {
|
|
1936
|
+
if (isStale()) return;
|
|
1937
|
+
stopPolling();
|
|
1938
|
+
markVerificationFailed(
|
|
1939
|
+
err instanceof Error ? err : new Error("Deposit verification failed")
|
|
1940
|
+
);
|
|
1941
|
+
}
|
|
1942
|
+
},
|
|
1943
|
+
[
|
|
1944
|
+
client,
|
|
1945
|
+
executePrivateRead,
|
|
1946
|
+
finalityRetryInterval,
|
|
1947
|
+
pollInterval,
|
|
1948
|
+
pollTimeout,
|
|
1949
|
+
queryClient,
|
|
1950
|
+
stopPolling
|
|
1951
|
+
]
|
|
1952
|
+
);
|
|
1953
|
+
const verify = react.useCallback(
|
|
1954
|
+
async (ctx) => {
|
|
1955
|
+
generationRef.current++;
|
|
1956
|
+
stopPolling();
|
|
1957
|
+
verificationContextRef.current = ctx;
|
|
1958
|
+
setTxHash(ctx.hash);
|
|
1959
|
+
const generation = generationRef.current;
|
|
1960
|
+
await runVerification(ctx, generation);
|
|
1961
|
+
},
|
|
1962
|
+
[runVerification, stopPolling]
|
|
1963
|
+
);
|
|
1964
|
+
const retryVerification = react.useCallback(async () => {
|
|
1965
|
+
const ctx = verificationContextRef.current;
|
|
1966
|
+
if (!ctx) {
|
|
1967
|
+
throw new Error("No pending verification to retry");
|
|
1968
|
+
}
|
|
1969
|
+
generationRef.current++;
|
|
1970
|
+
stopPolling();
|
|
1971
|
+
const generation = generationRef.current;
|
|
1972
|
+
await runVerification(ctx, generation);
|
|
1973
|
+
}, [runVerification, stopPolling]);
|
|
1974
|
+
const reset = react.useCallback(() => {
|
|
1975
|
+
generationRef.current++;
|
|
1976
|
+
stopPolling();
|
|
1977
|
+
verificationContextRef.current = null;
|
|
1978
|
+
setTxHash(void 0);
|
|
1979
|
+
setIsVerifying(false);
|
|
1980
|
+
setDidTimeout(false);
|
|
1981
|
+
setVerificationFailed(false);
|
|
1982
|
+
setError(null);
|
|
1983
|
+
}, [stopPolling]);
|
|
1984
|
+
return {
|
|
1985
|
+
isVerifying,
|
|
1986
|
+
didTimeout,
|
|
1987
|
+
verificationFailed,
|
|
1988
|
+
error,
|
|
1989
|
+
txHash,
|
|
1990
|
+
verify,
|
|
1991
|
+
retryVerification,
|
|
1992
|
+
reset
|
|
1993
|
+
};
|
|
1994
|
+
}
|
|
1995
|
+
function sleep(ms) {
|
|
1996
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1997
|
+
}
|
|
1998
|
+
function isInsufficientFinalityError(error) {
|
|
1999
|
+
if (error instanceof AccountingApiError) {
|
|
2000
|
+
return isInsufficientFinalityMessage(error.detail) || isInsufficientFinalityMessage(error.message);
|
|
2001
|
+
}
|
|
2002
|
+
return error instanceof Error && isInsufficientFinalityMessage(error.message);
|
|
2003
|
+
}
|
|
2004
|
+
function isInsufficientFinalityMessage(message) {
|
|
2005
|
+
return message?.includes("Insufficient finality") ?? false;
|
|
2006
|
+
}
|
|
2007
|
+
function cn(...inputs) {
|
|
2008
|
+
return tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
2009
|
+
}
|
|
2010
|
+
function formatTokenAmount(amount, decimals = 18) {
|
|
2011
|
+
const value = typeof amount === "string" ? BigInt(amount) : amount;
|
|
2012
|
+
const divisor = BigInt(10 ** decimals);
|
|
2013
|
+
const integerPart = value / divisor;
|
|
2014
|
+
const fractionalPart = value % divisor;
|
|
2015
|
+
const fractionalStr = fractionalPart.toString().padStart(decimals, "0");
|
|
2016
|
+
const twoDecimals = fractionalStr.slice(0, 2).padEnd(2, "0");
|
|
2017
|
+
const integerWithSpaces = integerPart.toString().replace(/\B(?=(\d{3})+(?!\d))/g, "\u2009");
|
|
2018
|
+
return `${integerWithSpaces}.${twoDecimals}`;
|
|
2019
|
+
}
|
|
2020
|
+
function parseTokenAmount(amount, decimals = 18) {
|
|
2021
|
+
const sanitized = amount.replace(/[\s\u2009]/g, "").replace(/,/g, ".");
|
|
2022
|
+
const lastDot = sanitized.lastIndexOf(".");
|
|
2023
|
+
const integerPart = lastDot === -1 ? sanitized : sanitized.slice(0, lastDot).replace(/\./g, "");
|
|
2024
|
+
const fractionalPart = lastDot === -1 ? "" : sanitized.slice(lastDot + 1);
|
|
2025
|
+
const paddedFractional = fractionalPart.padEnd(decimals, "0").slice(0, decimals);
|
|
2026
|
+
return BigInt(integerPart + paddedFractional);
|
|
2027
|
+
}
|
|
2028
|
+
function shortenAddress(address, chars = 4) {
|
|
2029
|
+
if (!address || address.length < chars * 2 + 2) return address;
|
|
2030
|
+
return `${address.slice(0, chars + 2)}...${address.slice(-chars)}`;
|
|
2031
|
+
}
|
|
2032
|
+
function formatTimeRemaining(expiryTimestamp) {
|
|
2033
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
2034
|
+
const diff = expiryTimestamp - now;
|
|
2035
|
+
if (diff <= 0) return "Expired";
|
|
2036
|
+
const days = Math.floor(diff / 86400);
|
|
2037
|
+
const hours = Math.floor(diff % 86400 / 3600);
|
|
2038
|
+
const minutes = Math.floor(diff % 3600 / 60);
|
|
2039
|
+
if (days > 0) {
|
|
2040
|
+
return hours > 0 ? `${days}d ${hours}h left` : `${days}d left`;
|
|
2041
|
+
}
|
|
2042
|
+
if (hours > 0) {
|
|
2043
|
+
return minutes > 0 ? `${hours}h ${minutes}m left` : `${hours}h left`;
|
|
2044
|
+
}
|
|
2045
|
+
return `${minutes}m left`;
|
|
2046
|
+
}
|
|
2047
|
+
function formatCountdown(secondsLeft) {
|
|
2048
|
+
const clamped = Math.max(0, secondsLeft);
|
|
2049
|
+
const minutes = Math.floor(clamped / 60);
|
|
2050
|
+
const seconds = clamped % 60;
|
|
2051
|
+
return `${minutes}m:${String(seconds).padStart(2, "0")}s`;
|
|
2052
|
+
}
|
|
2053
|
+
var buttonVariants = classVarianceAuthority.cva(
|
|
2054
|
+
"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",
|
|
2055
|
+
{
|
|
2056
|
+
variants: {
|
|
2057
|
+
variant: {
|
|
2058
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
2059
|
+
destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
2060
|
+
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",
|
|
2061
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
2062
|
+
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
|
2063
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
2064
|
+
},
|
|
2065
|
+
size: {
|
|
2066
|
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
2067
|
+
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
|
2068
|
+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
2069
|
+
icon: "size-9",
|
|
2070
|
+
"icon-sm": "size-8",
|
|
2071
|
+
"icon-lg": "size-10"
|
|
2072
|
+
}
|
|
2073
|
+
},
|
|
2074
|
+
defaultVariants: {
|
|
2075
|
+
variant: "default",
|
|
2076
|
+
size: "default"
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
);
|
|
2080
|
+
function Button({
|
|
2081
|
+
className,
|
|
2082
|
+
variant = "default",
|
|
2083
|
+
size = "default",
|
|
2084
|
+
asChild = false,
|
|
2085
|
+
...props
|
|
2086
|
+
}) {
|
|
2087
|
+
const Comp = asChild ? reactSlot.Slot : "button";
|
|
2088
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2089
|
+
Comp,
|
|
2090
|
+
{
|
|
2091
|
+
"data-slot": "button",
|
|
2092
|
+
"data-variant": variant,
|
|
2093
|
+
"data-size": size,
|
|
2094
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
2095
|
+
...props
|
|
2096
|
+
}
|
|
2097
|
+
);
|
|
2098
|
+
}
|
|
2099
|
+
function Skeleton({ className, ...props }) {
|
|
2100
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2101
|
+
"div",
|
|
2102
|
+
{
|
|
2103
|
+
"data-slot": "skeleton",
|
|
2104
|
+
className: cn("bg-secondary animate-pulse rounded-md", className),
|
|
2105
|
+
...props
|
|
2106
|
+
}
|
|
2107
|
+
);
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
// ../../node_modules/@wagmi/core/dist/esm/utils/getAction.js
|
|
2111
|
+
function getAction(client, actionFn, name) {
|
|
2112
|
+
const action_implicit = client[actionFn.name];
|
|
2113
|
+
if (typeof action_implicit === "function")
|
|
2114
|
+
return action_implicit;
|
|
2115
|
+
const action_explicit = client[name];
|
|
2116
|
+
if (typeof action_explicit === "function")
|
|
2117
|
+
return action_explicit;
|
|
2118
|
+
return (params) => actionFn(client, params);
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2121
|
+
// ../../node_modules/@wagmi/core/dist/esm/version.js
|
|
2122
|
+
var version = "3.3.1";
|
|
2123
|
+
|
|
2124
|
+
// ../../node_modules/@wagmi/core/dist/esm/utils/getVersion.js
|
|
2125
|
+
var getVersion = () => `@wagmi/core@${version}`;
|
|
2126
|
+
|
|
2127
|
+
// ../../node_modules/@wagmi/core/dist/esm/errors/base.js
|
|
2128
|
+
var __classPrivateFieldGet = function(receiver, state, kind, f) {
|
|
2129
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
2130
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
2131
|
+
};
|
|
2132
|
+
var _BaseError_instances;
|
|
2133
|
+
var _BaseError_walk;
|
|
2134
|
+
var BaseError = class _BaseError extends Error {
|
|
2135
|
+
get docsBaseUrl() {
|
|
2136
|
+
return "https://wagmi.sh/core";
|
|
2137
|
+
}
|
|
2138
|
+
get version() {
|
|
2139
|
+
return getVersion();
|
|
2140
|
+
}
|
|
2141
|
+
constructor(shortMessage, options = {}) {
|
|
2142
|
+
super();
|
|
2143
|
+
_BaseError_instances.add(this);
|
|
2144
|
+
Object.defineProperty(this, "details", {
|
|
2145
|
+
enumerable: true,
|
|
2146
|
+
configurable: true,
|
|
2147
|
+
writable: true,
|
|
2148
|
+
value: void 0
|
|
2149
|
+
});
|
|
2150
|
+
Object.defineProperty(this, "docsPath", {
|
|
2151
|
+
enumerable: true,
|
|
2152
|
+
configurable: true,
|
|
2153
|
+
writable: true,
|
|
2154
|
+
value: void 0
|
|
2155
|
+
});
|
|
2156
|
+
Object.defineProperty(this, "metaMessages", {
|
|
2157
|
+
enumerable: true,
|
|
2158
|
+
configurable: true,
|
|
2159
|
+
writable: true,
|
|
2160
|
+
value: void 0
|
|
2161
|
+
});
|
|
2162
|
+
Object.defineProperty(this, "shortMessage", {
|
|
2163
|
+
enumerable: true,
|
|
2164
|
+
configurable: true,
|
|
2165
|
+
writable: true,
|
|
2166
|
+
value: void 0
|
|
2167
|
+
});
|
|
2168
|
+
Object.defineProperty(this, "name", {
|
|
2169
|
+
enumerable: true,
|
|
2170
|
+
configurable: true,
|
|
2171
|
+
writable: true,
|
|
2172
|
+
value: "WagmiCoreError"
|
|
2173
|
+
});
|
|
2174
|
+
const details = options.cause instanceof _BaseError ? options.cause.details : options.cause?.message ? options.cause.message : options.details;
|
|
2175
|
+
const docsPath = options.cause instanceof _BaseError ? options.cause.docsPath || options.docsPath : options.docsPath;
|
|
2176
|
+
this.message = [
|
|
2177
|
+
shortMessage || "An error occurred.",
|
|
2178
|
+
"",
|
|
2179
|
+
...options.metaMessages ? [...options.metaMessages, ""] : [],
|
|
2180
|
+
...docsPath ? [
|
|
2181
|
+
`Docs: ${this.docsBaseUrl}${docsPath}.html${options.docsSlug ? `#${options.docsSlug}` : ""}`
|
|
2182
|
+
] : [],
|
|
2183
|
+
...details ? [`Details: ${details}`] : [],
|
|
2184
|
+
`Version: ${this.version}`
|
|
2185
|
+
].join("\n");
|
|
2186
|
+
if (options.cause)
|
|
2187
|
+
this.cause = options.cause;
|
|
2188
|
+
this.details = details;
|
|
2189
|
+
this.docsPath = docsPath;
|
|
2190
|
+
this.metaMessages = options.metaMessages;
|
|
2191
|
+
this.shortMessage = shortMessage;
|
|
2192
|
+
}
|
|
2193
|
+
walk(fn) {
|
|
2194
|
+
return __classPrivateFieldGet(this, _BaseError_instances, "m", _BaseError_walk).call(this, this, fn);
|
|
2195
|
+
}
|
|
2196
|
+
};
|
|
2197
|
+
_BaseError_instances = /* @__PURE__ */ new WeakSet(), _BaseError_walk = function _BaseError_walk2(err, fn) {
|
|
2198
|
+
if (fn?.(err))
|
|
2199
|
+
return err;
|
|
2200
|
+
if (err.cause)
|
|
2201
|
+
return __classPrivateFieldGet(this, _BaseError_instances, "m", _BaseError_walk2).call(this, err.cause, fn);
|
|
2202
|
+
return err;
|
|
2203
|
+
};
|
|
2204
|
+
|
|
2205
|
+
// ../../node_modules/@wagmi/core/dist/esm/errors/config.js
|
|
2206
|
+
var ConnectorNotConnectedError = class extends BaseError {
|
|
2207
|
+
constructor() {
|
|
2208
|
+
super("Connector not connected.");
|
|
2209
|
+
Object.defineProperty(this, "name", {
|
|
2210
|
+
enumerable: true,
|
|
2211
|
+
configurable: true,
|
|
2212
|
+
writable: true,
|
|
2213
|
+
value: "ConnectorNotConnectedError"
|
|
2214
|
+
});
|
|
2215
|
+
}
|
|
2216
|
+
};
|
|
2217
|
+
var ConnectorAccountNotFoundError = class extends BaseError {
|
|
2218
|
+
constructor({ address, connector }) {
|
|
2219
|
+
super(`Account "${address}" not found for connector "${connector.name}".`);
|
|
2220
|
+
Object.defineProperty(this, "name", {
|
|
2221
|
+
enumerable: true,
|
|
2222
|
+
configurable: true,
|
|
2223
|
+
writable: true,
|
|
2224
|
+
value: "ConnectorAccountNotFoundError"
|
|
2225
|
+
});
|
|
2226
|
+
}
|
|
2227
|
+
};
|
|
2228
|
+
var ConnectorChainMismatchError = class extends BaseError {
|
|
2229
|
+
constructor({ connectionChainId, connectorChainId }) {
|
|
2230
|
+
super(`The current chain of the connector (id: ${connectorChainId}) does not match the connection's chain (id: ${connectionChainId}).`, {
|
|
2231
|
+
metaMessages: [
|
|
2232
|
+
`Current Chain ID: ${connectorChainId}`,
|
|
2233
|
+
`Expected Chain ID: ${connectionChainId}`
|
|
2234
|
+
]
|
|
2235
|
+
});
|
|
2236
|
+
Object.defineProperty(this, "name", {
|
|
2237
|
+
enumerable: true,
|
|
2238
|
+
configurable: true,
|
|
2239
|
+
writable: true,
|
|
2240
|
+
value: "ConnectorChainMismatchError"
|
|
2241
|
+
});
|
|
2242
|
+
}
|
|
2243
|
+
};
|
|
2244
|
+
var ConnectorUnavailableReconnectingError = class extends BaseError {
|
|
2245
|
+
constructor({ connector }) {
|
|
2246
|
+
super(`Connector "${connector.name}" unavailable while reconnecting.`, {
|
|
2247
|
+
details: [
|
|
2248
|
+
"During the reconnection step, the only connector methods guaranteed to be available are: `id`, `name`, `type`, `uid`.",
|
|
2249
|
+
"All other methods are not guaranteed to be available until reconnection completes and connectors are fully restored.",
|
|
2250
|
+
"This error commonly occurs for connectors that asynchronously inject after reconnection has already started."
|
|
2251
|
+
].join(" ")
|
|
2252
|
+
});
|
|
2253
|
+
Object.defineProperty(this, "name", {
|
|
2254
|
+
enumerable: true,
|
|
2255
|
+
configurable: true,
|
|
2256
|
+
writable: true,
|
|
2257
|
+
value: "ConnectorUnavailableReconnectingError"
|
|
2258
|
+
});
|
|
2259
|
+
}
|
|
2260
|
+
};
|
|
2261
|
+
async function getConnectorClient(config, parameters = {}) {
|
|
2262
|
+
const { assertChainId = true } = parameters;
|
|
2263
|
+
let connection;
|
|
2264
|
+
if (parameters.connector) {
|
|
2265
|
+
const { connector: connector2 } = parameters;
|
|
2266
|
+
if (config.state.status === "reconnecting" && !connector2.getAccounts && !connector2.getChainId)
|
|
2267
|
+
throw new ConnectorUnavailableReconnectingError({ connector: connector2 });
|
|
2268
|
+
const [accounts, chainId2] = await Promise.all([
|
|
2269
|
+
connector2.getAccounts().catch((e) => {
|
|
2270
|
+
if (parameters.account === null)
|
|
2271
|
+
return [];
|
|
2272
|
+
throw e;
|
|
2273
|
+
}),
|
|
2274
|
+
connector2.getChainId()
|
|
2275
|
+
]);
|
|
2276
|
+
connection = {
|
|
2277
|
+
accounts,
|
|
2278
|
+
chainId: chainId2,
|
|
2279
|
+
connector: connector2
|
|
2280
|
+
};
|
|
2281
|
+
} else
|
|
2282
|
+
connection = config.state.connections.get(config.state.current);
|
|
2283
|
+
if (!connection)
|
|
2284
|
+
throw new ConnectorNotConnectedError();
|
|
2285
|
+
const chainId = parameters.chainId ?? connection.chainId;
|
|
2286
|
+
const connectorChainId = await connection.connector.getChainId();
|
|
2287
|
+
if (assertChainId && connectorChainId !== chainId)
|
|
2288
|
+
throw new ConnectorChainMismatchError({
|
|
2289
|
+
connectionChainId: chainId,
|
|
2290
|
+
connectorChainId
|
|
2291
|
+
});
|
|
2292
|
+
const connector = connection.connector;
|
|
2293
|
+
if (connector.getClient)
|
|
2294
|
+
return connector.getClient({ chainId });
|
|
2295
|
+
const account = utils.parseAccount(parameters.account ?? connection.accounts[0]);
|
|
2296
|
+
if (account)
|
|
2297
|
+
account.address = utils.getAddress(account.address);
|
|
2298
|
+
if (parameters.account && !connection.accounts.some((x) => x.toLowerCase() === account.address.toLowerCase()))
|
|
2299
|
+
throw new ConnectorAccountNotFoundError({
|
|
2300
|
+
address: account.address,
|
|
2301
|
+
connector
|
|
2302
|
+
});
|
|
2303
|
+
const chain = config.chains.find((chain2) => chain2.id === chainId);
|
|
2304
|
+
const provider = await connection.connector.getProvider({ chainId });
|
|
2305
|
+
return viem.createClient({
|
|
2306
|
+
account,
|
|
2307
|
+
chain,
|
|
2308
|
+
name: "Connector Client",
|
|
2309
|
+
transport: (opts) => viem.custom(provider)({ ...opts, retryCount: 0 })
|
|
2310
|
+
});
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
// ../../node_modules/@wagmi/core/dist/esm/actions/getChainId.js
|
|
2314
|
+
function getChainId2(config) {
|
|
2315
|
+
return config.state.chainId;
|
|
2316
|
+
}
|
|
2317
|
+
async function getTransactionReceipt(config, parameters) {
|
|
2318
|
+
const { chainId, ...rest } = parameters;
|
|
2319
|
+
const client = config.getClient({ chainId });
|
|
2320
|
+
const action = getAction(client, actions$1.getTransactionReceipt, "getTransactionReceipt");
|
|
2321
|
+
return action(rest);
|
|
2322
|
+
}
|
|
2323
|
+
async function getWalletClient3(config, parameters = {}) {
|
|
2324
|
+
const client = await getConnectorClient(config, parameters);
|
|
2325
|
+
return client.extend(viem.walletActions);
|
|
2326
|
+
}
|
|
2327
|
+
async function waitForTransactionReceipt(config, parameters) {
|
|
2328
|
+
const { chainId, timeout = 0, ...rest } = parameters;
|
|
2329
|
+
const client = config.getClient({ chainId });
|
|
2330
|
+
const action = getAction(client, actions$1.waitForTransactionReceipt, "waitForTransactionReceipt");
|
|
2331
|
+
const receipt = await action({ ...rest, timeout });
|
|
2332
|
+
if (receipt.status === "reverted") {
|
|
2333
|
+
const action_getTransaction = getAction(client, actions$1.getTransaction, "getTransaction");
|
|
2334
|
+
const { from: account, ...txn } = await action_getTransaction({
|
|
2335
|
+
hash: receipt.transactionHash
|
|
2336
|
+
});
|
|
2337
|
+
const action_call = getAction(client, actions$1.call, "call");
|
|
2338
|
+
const code = await action_call({
|
|
2339
|
+
...txn,
|
|
2340
|
+
account,
|
|
2341
|
+
data: txn.input,
|
|
2342
|
+
gasPrice: txn.type !== "eip1559" ? txn.gasPrice : void 0,
|
|
2343
|
+
maxFeePerGas: txn.type === "eip1559" ? txn.maxFeePerGas : void 0,
|
|
2344
|
+
maxPriorityFeePerGas: txn.type === "eip1559" ? txn.maxPriorityFeePerGas : void 0
|
|
2345
|
+
});
|
|
2346
|
+
const reason = code?.data ? viem.hexToString(`0x${code.data.substring(138)}`) : "unknown reason";
|
|
2347
|
+
throw new Error(reason);
|
|
2348
|
+
}
|
|
2349
|
+
return {
|
|
2350
|
+
...receipt,
|
|
2351
|
+
chainId: client.chain.id
|
|
2352
|
+
};
|
|
2353
|
+
}
|
|
2354
|
+
function useEnsureCorrectChain() {
|
|
2355
|
+
const config = wagmi.useConfig();
|
|
2356
|
+
const chainId = wagmi.useChainId();
|
|
2357
|
+
const { switchChainAsync } = wagmi.useSwitchChain();
|
|
2358
|
+
const waitUntilOnChain = react.useCallback(
|
|
2359
|
+
async (expectedChainId, timeoutMs, pollIntervalMs = 250) => {
|
|
2360
|
+
const startedAt = Date.now();
|
|
2361
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
2362
|
+
const currentChainId = getChainId2(config);
|
|
2363
|
+
if (currentChainId === expectedChainId) return true;
|
|
2364
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
2365
|
+
}
|
|
2366
|
+
return false;
|
|
2367
|
+
},
|
|
2368
|
+
[config]
|
|
2369
|
+
);
|
|
2370
|
+
const ensureCorrectChain = react.useCallback(
|
|
2371
|
+
async (targetChainId) => {
|
|
2372
|
+
const currentChainId = getChainId2(config);
|
|
2373
|
+
if (currentChainId === targetChainId) return false;
|
|
2374
|
+
let switchErrorMessage;
|
|
2375
|
+
try {
|
|
2376
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
2377
|
+
setTimeout(() => reject(new Error("Chain switch timeout")), 3e3);
|
|
2378
|
+
});
|
|
2379
|
+
await Promise.race([switchChainAsync({ chainId: targetChainId }), timeoutPromise]);
|
|
2380
|
+
} catch (error) {
|
|
2381
|
+
if (error instanceof Error && error.message.includes("Unsupported Chain")) {
|
|
2382
|
+
console.warn("Got 'Unsupported Chain' error, chain may have switched anyway.");
|
|
2383
|
+
} else {
|
|
2384
|
+
switchErrorMessage = error instanceof Error ? error.message : "Unknown chain switch error";
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
const settled = await waitUntilOnChain(targetChainId, 2e4);
|
|
2388
|
+
if (settled) return true;
|
|
2389
|
+
if (switchErrorMessage) {
|
|
2390
|
+
throw new Error(
|
|
2391
|
+
`Failed to switch to chain (Chain ID: ${targetChainId}): ${switchErrorMessage}`
|
|
2392
|
+
);
|
|
2393
|
+
}
|
|
2394
|
+
throw new Error(`Chain switch did not settle in time (expected ${targetChainId}).`);
|
|
2395
|
+
},
|
|
2396
|
+
[config, switchChainAsync, waitUntilOnChain]
|
|
2397
|
+
);
|
|
2398
|
+
const isOnChain = react.useCallback((targetChainId) => chainId === targetChainId, [chainId]);
|
|
2399
|
+
return {
|
|
2400
|
+
chainId,
|
|
2401
|
+
ensureCorrectChain,
|
|
2402
|
+
isOnChain
|
|
2403
|
+
};
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
// src/sdk/hooks/use-fiat-on-ramp.ts
|
|
2407
|
+
var DEFAULT_DELIVERY_TIMEOUT_MS = 12e4;
|
|
2408
|
+
var DEFAULT_VERIFICATION_TIMEOUT_MS = 10 * 6e4;
|
|
2409
|
+
var DEFAULT_FINALITY_RETRY_INTERVAL_MS = 15e3;
|
|
2410
|
+
var ERC20_TRANSFER_EVENT = viem.parseAbiItem(
|
|
2411
|
+
"event Transfer(address indexed from, address indexed to, uint256 value)"
|
|
2412
|
+
);
|
|
2413
|
+
function useFiatOnRamp(options) {
|
|
2414
|
+
const {
|
|
2415
|
+
tokenId,
|
|
2416
|
+
postDepositLock,
|
|
2417
|
+
onCredited,
|
|
2418
|
+
onLockSubmitted,
|
|
2419
|
+
onLockFailed,
|
|
2420
|
+
onError,
|
|
2421
|
+
onDebugEvent
|
|
2422
|
+
} = options;
|
|
2423
|
+
const deliveryTimeout = options.deliveryTimeout ?? DEFAULT_DELIVERY_TIMEOUT_MS;
|
|
2424
|
+
const deliveryPollInterval = options.deliveryPollInterval ?? 3e3;
|
|
2425
|
+
const verificationTimeout = options.verificationTimeout ?? DEFAULT_VERIFICATION_TIMEOUT_MS;
|
|
2426
|
+
const finalityRetryInterval = options.finalityRetryInterval ?? DEFAULT_FINALITY_RETRY_INTERVAL_MS;
|
|
2427
|
+
const { address } = wagmi.useAccount();
|
|
2428
|
+
const { data: walletClient } = wagmi.useWalletClient();
|
|
2429
|
+
const { client, enabledTokens, networkConfig, serviceAddress } = usePrivanaContext();
|
|
2430
|
+
const { executePrivateRead, privateReadReady } = usePrivateReadRequest();
|
|
2431
|
+
const { ensureCorrectChain } = useEnsureCorrectChain();
|
|
2432
|
+
const wagmiConfig = wagmi.useConfig();
|
|
2433
|
+
const queryClient = reactQuery.useQueryClient();
|
|
2434
|
+
const selectedToken = enabledTokens.find((t) => t.id.toLowerCase() === tokenId.toLowerCase());
|
|
2435
|
+
const [status, setStatus] = react.useState("idle");
|
|
2436
|
+
const [pending, setPending] = react.useState([]);
|
|
2437
|
+
const [error, setError] = react.useState(null);
|
|
2438
|
+
const [depositAddress, setDepositAddress] = react.useState();
|
|
2439
|
+
const [minDepositBaseUnits, setMinDepositBaseUnits] = react.useState();
|
|
2440
|
+
const [activeIntentId, setActiveIntentId] = react.useState(null);
|
|
2441
|
+
const [activeVerificationId, setActiveVerificationId] = react.useState(null);
|
|
2442
|
+
const [finalityProgress, setFinalityProgress] = react.useState({});
|
|
2443
|
+
const onCreditedRef = react.useRef(onCredited);
|
|
2444
|
+
const onLockSubmittedRef = react.useRef(onLockSubmitted);
|
|
2445
|
+
const onLockFailedRef = react.useRef(onLockFailed);
|
|
2446
|
+
const onErrorRef = react.useRef(onError);
|
|
2447
|
+
const onDebugEventRef = react.useRef(onDebugEvent);
|
|
2448
|
+
const statusRef = react.useRef(status);
|
|
2449
|
+
const activeIntentIdRef = react.useRef(null);
|
|
2450
|
+
const activeVerificationRecordRef = react.useRef(null);
|
|
2451
|
+
const activeVerificationKeyRef = react.useRef(null);
|
|
2452
|
+
const activeVerificationAmountRef = react.useRef(null);
|
|
2453
|
+
const lockOwnerRef = react.useRef(null);
|
|
2454
|
+
const triggeredVerificationKeysRef = react.useRef(/* @__PURE__ */ new Set());
|
|
2455
|
+
const activeVerificationDoneRef = react.useRef(null);
|
|
2456
|
+
const closeReconcilePromiseRef = react.useRef(null);
|
|
2457
|
+
const purchaseInitiatedRef = react.useRef(false);
|
|
2458
|
+
react.useEffect(() => {
|
|
2459
|
+
onCreditedRef.current = onCredited;
|
|
2460
|
+
onLockSubmittedRef.current = onLockSubmitted;
|
|
2461
|
+
onLockFailedRef.current = onLockFailed;
|
|
2462
|
+
onErrorRef.current = onError;
|
|
2463
|
+
onDebugEventRef.current = onDebugEvent;
|
|
2464
|
+
}, [onCredited, onLockSubmitted, onLockFailed, onError, onDebugEvent]);
|
|
2465
|
+
react.useEffect(() => {
|
|
2466
|
+
statusRef.current = status;
|
|
2467
|
+
}, [status]);
|
|
2468
|
+
react.useEffect(() => {
|
|
2469
|
+
activeIntentIdRef.current = activeIntentId;
|
|
2470
|
+
}, [activeIntentId]);
|
|
2471
|
+
react.useEffect(() => {
|
|
2472
|
+
activeIntentIdRef.current = null;
|
|
2473
|
+
setActiveIntentId(null);
|
|
2474
|
+
}, [tokenId]);
|
|
2475
|
+
const emitDebug = react.useCallback(
|
|
2476
|
+
(event, payload) => {
|
|
2477
|
+
onDebugEventRef.current?.({
|
|
2478
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2479
|
+
event,
|
|
2480
|
+
status: statusRef.current,
|
|
2481
|
+
tokenId,
|
|
2482
|
+
payload
|
|
2483
|
+
});
|
|
2484
|
+
},
|
|
2485
|
+
[tokenId]
|
|
2486
|
+
);
|
|
2487
|
+
react.useEffect(() => {
|
|
2488
|
+
emitDebug("private-read-state", { privateReadReady });
|
|
2489
|
+
}, [emitDebug, privateReadReady]);
|
|
2490
|
+
react.useEffect(() => {
|
|
2491
|
+
if (!privateReadReady) {
|
|
2492
|
+
emitDebug("deposit-address:skip", { reason: "private-read-not-ready" });
|
|
2493
|
+
setDepositAddress(void 0);
|
|
2494
|
+
setMinDepositBaseUnits(void 0);
|
|
2495
|
+
return;
|
|
2496
|
+
}
|
|
2497
|
+
let cancelled = false;
|
|
2498
|
+
void (async () => {
|
|
2499
|
+
try {
|
|
2500
|
+
emitDebug("deposit-address:request");
|
|
2501
|
+
const resp = await executePrivateRead(() => client.getDepositAddress());
|
|
2502
|
+
if (cancelled) return;
|
|
2503
|
+
setDepositAddress(resp.deposit_address);
|
|
2504
|
+
const token = enabledTokens.find((t) => t.id.toLowerCase() === tokenId.toLowerCase());
|
|
2505
|
+
const mins = token ? resp.min_deposit?.[String(token.chainId)] : void 0;
|
|
2506
|
+
if (mins?.erc20) setMinDepositBaseUnits(BigInt(mins.erc20));
|
|
2507
|
+
emitDebug("deposit-address:success", {
|
|
2508
|
+
depositAddress: resp.deposit_address,
|
|
2509
|
+
selectedToken: token ? summariseToken(token) : null,
|
|
2510
|
+
minDepositBaseUnits: mins?.erc20 ?? null
|
|
2511
|
+
});
|
|
2512
|
+
} catch (err) {
|
|
2513
|
+
if (!cancelled) {
|
|
2514
|
+
emitDebug("deposit-address:error", errorPayload(err));
|
|
2515
|
+
console.warn("Failed to fetch Privana deposit address:", err);
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
})();
|
|
2519
|
+
return () => {
|
|
2520
|
+
cancelled = true;
|
|
2521
|
+
};
|
|
2522
|
+
}, [client, emitDebug, enabledTokens, executePrivateRead, privateReadReady, tokenId]);
|
|
2523
|
+
const refreshPending = react.useCallback(async () => {
|
|
2524
|
+
if (!privateReadReady) {
|
|
2525
|
+
emitDebug("pending:skip", { reason: "private-read-not-ready" });
|
|
2526
|
+
setPending([]);
|
|
2527
|
+
return;
|
|
2528
|
+
}
|
|
2529
|
+
try {
|
|
2530
|
+
emitDebug("pending:request");
|
|
2531
|
+
const { pending: rows } = await executePrivateRead(() => client.getPendingOnRamps());
|
|
2532
|
+
setPending(rows);
|
|
2533
|
+
emitDebug("pending:success", {
|
|
2534
|
+
count: rows.length,
|
|
2535
|
+
rows: rows.map(summariseOnRampRecord)
|
|
2536
|
+
});
|
|
2537
|
+
} catch (err) {
|
|
2538
|
+
emitDebug("pending:error", errorPayload(err));
|
|
2539
|
+
console.warn("Failed to load pending on-ramps:", err);
|
|
2540
|
+
}
|
|
2541
|
+
}, [client, emitDebug, executePrivateRead, privateReadReady]);
|
|
2542
|
+
react.useEffect(() => {
|
|
2543
|
+
refreshPending();
|
|
2544
|
+
}, [refreshPending]);
|
|
2545
|
+
const clearActiveVerification = react.useCallback((expectedKey) => {
|
|
2546
|
+
const key = activeVerificationKeyRef.current;
|
|
2547
|
+
if (expectedKey != null && key !== null && key !== expectedKey) return;
|
|
2548
|
+
if (key) triggeredVerificationKeysRef.current.delete(key);
|
|
2549
|
+
activeVerificationKeyRef.current = null;
|
|
2550
|
+
activeVerificationRecordRef.current = null;
|
|
2551
|
+
activeVerificationAmountRef.current = null;
|
|
2552
|
+
setActiveVerificationId(null);
|
|
2553
|
+
activeVerificationDoneRef.current?.();
|
|
2554
|
+
activeVerificationDoneRef.current = null;
|
|
2555
|
+
}, []);
|
|
2556
|
+
const submitPendingLockAfterCredit = react.useCallback(
|
|
2557
|
+
async (transactionId, userAddress) => {
|
|
2558
|
+
const signedLock = loadPendingLock(userAddress, transactionId);
|
|
2559
|
+
if (!signedLock) {
|
|
2560
|
+
clearPendingLock(userAddress, transactionId);
|
|
2561
|
+
if (!postDepositLock || transactionId !== activeIntentIdRef.current) return;
|
|
2562
|
+
const error2 = new PostDepositLockError(
|
|
2563
|
+
"No persisted signed lock found for this on-ramp",
|
|
2564
|
+
"not-found"
|
|
2565
|
+
);
|
|
2566
|
+
emitDebug("lock:not-found", { transactionId });
|
|
2567
|
+
(onLockFailedRef.current ?? onErrorRef.current)?.(error2);
|
|
2568
|
+
return;
|
|
2569
|
+
}
|
|
2570
|
+
const creditedAmount = activeVerificationAmountRef.current ?? void 0;
|
|
2571
|
+
try {
|
|
2572
|
+
const result = await submitPendingLock({ client, payload: signedLock, creditedAmount });
|
|
2573
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
|
|
2574
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-locked-funds"] });
|
|
2575
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-total-locked-balance"] });
|
|
2576
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
|
|
2577
|
+
emitDebug("lock:submitted", {
|
|
2578
|
+
transactionId,
|
|
2579
|
+
amount: signedLock.amount,
|
|
2580
|
+
submissionId: result.submission_id
|
|
2581
|
+
});
|
|
2582
|
+
onLockSubmittedRef.current?.(result);
|
|
2583
|
+
} catch (err) {
|
|
2584
|
+
const error2 = err instanceof PostDepositLockError ? err : new PostDepositLockError(
|
|
2585
|
+
err instanceof Error ? err.message : "Lock submission failed",
|
|
2586
|
+
"submission-failed",
|
|
2587
|
+
BigInt(signedLock.amount),
|
|
2588
|
+
creditedAmount,
|
|
2589
|
+
{ cause: err }
|
|
2590
|
+
);
|
|
2591
|
+
emitDebug("lock:failed", {
|
|
2592
|
+
transactionId,
|
|
2593
|
+
reason: error2.reason,
|
|
2594
|
+
message: error2.message
|
|
2595
|
+
});
|
|
2596
|
+
(onLockFailedRef.current ?? onErrorRef.current)?.(error2);
|
|
2597
|
+
} finally {
|
|
2598
|
+
clearPendingLock(userAddress, transactionId);
|
|
2599
|
+
}
|
|
2600
|
+
},
|
|
2601
|
+
[client, emitDebug, postDepositLock, queryClient]
|
|
2602
|
+
);
|
|
2603
|
+
const { verify } = useDepositVerification({
|
|
2604
|
+
pollTimeout: verificationTimeout,
|
|
2605
|
+
pollInterval: options.verificationPollInterval,
|
|
2606
|
+
finalityRetryInterval,
|
|
2607
|
+
onCheckRetry: (message) => {
|
|
2608
|
+
const record = activeVerificationRecordRef.current;
|
|
2609
|
+
if (!record) return;
|
|
2610
|
+
emitDebug("verification:check-retry", {
|
|
2611
|
+
message,
|
|
2612
|
+
record: summariseOnRampRecord(record)
|
|
2613
|
+
});
|
|
2614
|
+
setFinalityProgress((prev) => ({ ...prev, [record.transaction_id]: message }));
|
|
2615
|
+
},
|
|
2616
|
+
onCredited: (depositTxHash) => {
|
|
2617
|
+
const record = activeVerificationRecordRef.current;
|
|
2618
|
+
const verificationKey = record ? getOnRampVerificationKey(record) : null;
|
|
2619
|
+
emitDebug("verification:credited", {
|
|
2620
|
+
depositTxHash,
|
|
2621
|
+
record: record ? summariseOnRampRecord(record) : null
|
|
2622
|
+
});
|
|
2623
|
+
if (record && activeIntentIdRef.current === record.transaction_id) {
|
|
2624
|
+
setStatus("credited");
|
|
2625
|
+
}
|
|
2626
|
+
if (record) {
|
|
2627
|
+
setFinalityProgress((prev) => {
|
|
2628
|
+
if (!(record.transaction_id in prev)) return prev;
|
|
2629
|
+
const next = { ...prev };
|
|
2630
|
+
delete next[record.transaction_id];
|
|
2631
|
+
return next;
|
|
2632
|
+
});
|
|
2633
|
+
const lockOwner = lockOwnerRef.current ?? address;
|
|
2634
|
+
if (lockOwner) {
|
|
2635
|
+
void submitPendingLockAfterCredit(record.transaction_id, lockOwner);
|
|
2636
|
+
} else if (postDepositLock) {
|
|
2637
|
+
emitDebug("lock:owner-unavailable", { transactionId: record.transaction_id });
|
|
2638
|
+
(onLockFailedRef.current ?? onErrorRef.current)?.(
|
|
2639
|
+
new PostDepositLockError(
|
|
2640
|
+
"No wallet address available to look up the signed lock for this on-ramp",
|
|
2641
|
+
"not-found"
|
|
2642
|
+
)
|
|
2643
|
+
);
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
void (async () => {
|
|
2647
|
+
try {
|
|
2648
|
+
if (record && depositTxHash.startsWith("0x")) {
|
|
2649
|
+
emitDebug("onramp:mark-deposit-triggered-request", {
|
|
2650
|
+
transactionId: record.transaction_id,
|
|
2651
|
+
depositTxHash
|
|
2652
|
+
});
|
|
2653
|
+
const updated = await executePrivateRead(
|
|
2654
|
+
() => client.updateOnRamp(record.transaction_id, {
|
|
2655
|
+
deposit_tx_hash: depositTxHash
|
|
2656
|
+
})
|
|
2657
|
+
);
|
|
2658
|
+
emitDebug("onramp:mark-deposit-triggered-success", {
|
|
2659
|
+
record: summariseOnRampRecord(updated)
|
|
2660
|
+
});
|
|
2661
|
+
}
|
|
2662
|
+
} catch (err) {
|
|
2663
|
+
emitDebug("onramp:mark-deposit-triggered-error", errorPayload(err));
|
|
2664
|
+
console.warn("Failed to mark on-ramp row complete:", err);
|
|
2665
|
+
} finally {
|
|
2666
|
+
await refreshPending();
|
|
2667
|
+
clearActiveVerification(verificationKey);
|
|
2668
|
+
if (record && activeIntentIdRef.current === record.transaction_id) {
|
|
2669
|
+
activeIntentIdRef.current = null;
|
|
2670
|
+
setActiveIntentId(null);
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
})();
|
|
2674
|
+
onCreditedRef.current?.(depositTxHash);
|
|
2675
|
+
},
|
|
2676
|
+
onCheckTimeout: (depositTxHash) => {
|
|
2677
|
+
const record = activeVerificationRecordRef.current;
|
|
2678
|
+
const err = new Error(
|
|
2679
|
+
"Privana verification is still pending. Retry from the pending on-ramp list if it does not complete."
|
|
2680
|
+
);
|
|
2681
|
+
emitDebug("verification:timeout", { depositTxHash, message: err.message });
|
|
2682
|
+
clearActiveVerification();
|
|
2683
|
+
if (!record || activeIntentIdRef.current === record.transaction_id) {
|
|
2684
|
+
setStatus("failed");
|
|
2685
|
+
setError(err);
|
|
2686
|
+
}
|
|
2687
|
+
void refreshPending();
|
|
2688
|
+
onErrorRef.current?.(err);
|
|
2689
|
+
},
|
|
2690
|
+
onError: (err) => {
|
|
2691
|
+
const record = activeVerificationRecordRef.current;
|
|
2692
|
+
emitDebug("verification:error", errorPayload(err));
|
|
2693
|
+
clearActiveVerification();
|
|
2694
|
+
if (!record || activeIntentIdRef.current === record.transaction_id) {
|
|
2695
|
+
setStatus("failed");
|
|
2696
|
+
setError(err);
|
|
2697
|
+
}
|
|
2698
|
+
onErrorRef.current?.(err);
|
|
2699
|
+
}
|
|
2700
|
+
});
|
|
2701
|
+
const prepareOnRampIntent = react.useCallback(
|
|
2702
|
+
async ({
|
|
2703
|
+
currencyCode,
|
|
2704
|
+
baseCurrencyCode,
|
|
2705
|
+
baseCurrencyAmount,
|
|
2706
|
+
quoteCurrencyAmount
|
|
2707
|
+
}) => {
|
|
2708
|
+
try {
|
|
2709
|
+
setError(null);
|
|
2710
|
+
purchaseInitiatedRef.current = false;
|
|
2711
|
+
const token = enabledTokens.find((t) => t.id.toLowerCase() === tokenId.toLowerCase());
|
|
2712
|
+
if (!token) throw new Error(`Unknown token: ${tokenId}`);
|
|
2713
|
+
if (!depositAddress) throw new Error("Privana deposit address is not ready");
|
|
2714
|
+
let lockAmount;
|
|
2715
|
+
if (postDepositLock) {
|
|
2716
|
+
if (!address || !walletClient) throw new Error("Wallet not connected");
|
|
2717
|
+
if (!quoteCurrencyAmount) {
|
|
2718
|
+
throw new Error(
|
|
2719
|
+
"postDepositLock requires quoteCurrencyAmount to derive the lock amount"
|
|
2720
|
+
);
|
|
2721
|
+
}
|
|
2722
|
+
if (!canUseBrowserStorage()) {
|
|
2723
|
+
throw new Error("Browser storage is required for locked on-ramp recovery");
|
|
2724
|
+
}
|
|
2725
|
+
const buffered = applyLockBuffer(
|
|
2726
|
+
viem.parseUnits(quoteCurrencyAmount, token.decimals),
|
|
2727
|
+
postDepositLock.buffer
|
|
2728
|
+
);
|
|
2729
|
+
lockAmount = clampLockAmount(buffered, postDepositLock.maxAmount);
|
|
2730
|
+
if (lockAmount <= 0n) {
|
|
2731
|
+
throw new Error(`Post-deposit lock amount must be positive, got ${lockAmount}`);
|
|
2732
|
+
}
|
|
2733
|
+
await ensureCorrectChain(networkConfig.chainId);
|
|
2734
|
+
}
|
|
2735
|
+
emitDebug("intent:create-request", {
|
|
2736
|
+
tokenId,
|
|
2737
|
+
chainId: token.chainId,
|
|
2738
|
+
currencyCode,
|
|
2739
|
+
baseCurrencyCode: baseCurrencyCode ?? null,
|
|
2740
|
+
baseCurrencyAmount: baseCurrencyAmount ?? null,
|
|
2741
|
+
quoteCurrencyAmount: quoteCurrencyAmount ?? null,
|
|
2742
|
+
depositAddress
|
|
2743
|
+
});
|
|
2744
|
+
const record = await executePrivateRead(
|
|
2745
|
+
() => client.createOnRampIntent({
|
|
2746
|
+
wallet_address: depositAddress,
|
|
2747
|
+
token_id: tokenId,
|
|
2748
|
+
chain_id: token.chainId,
|
|
2749
|
+
moonpay_currency_code: currencyCode,
|
|
2750
|
+
base_currency_code: baseCurrencyCode,
|
|
2751
|
+
base_currency_amount: baseCurrencyAmount
|
|
2752
|
+
})
|
|
2753
|
+
);
|
|
2754
|
+
if (postDepositLock && address && walletClient && lockAmount !== void 0) {
|
|
2755
|
+
const signingWalletClient = await getWalletClient3(wagmiConfig, {
|
|
2756
|
+
chainId: networkConfig.chainId
|
|
2757
|
+
});
|
|
2758
|
+
const signedLock = await createSignedLockRequest({
|
|
2759
|
+
client,
|
|
2760
|
+
walletClient: signingWalletClient,
|
|
2761
|
+
userAddress: address,
|
|
2762
|
+
networkConfig,
|
|
2763
|
+
serviceAddress: requireServiceAddress(postDepositLock.serviceAddress ?? serviceAddress),
|
|
2764
|
+
tokenId,
|
|
2765
|
+
amount: lockAmount,
|
|
2766
|
+
lockDuration: postDepositLock.lockDuration
|
|
2767
|
+
});
|
|
2768
|
+
savePendingLock(address, record.transaction_id, signedLock);
|
|
2769
|
+
lockOwnerRef.current = address;
|
|
2770
|
+
emitDebug("intent:lock-signed", {
|
|
2771
|
+
transactionId: record.transaction_id,
|
|
2772
|
+
amount: signedLock.amount,
|
|
2773
|
+
expiry: signedLock.expiry
|
|
2774
|
+
});
|
|
2775
|
+
}
|
|
2776
|
+
activeIntentIdRef.current = record.transaction_id;
|
|
2777
|
+
setActiveIntentId(record.transaction_id);
|
|
2778
|
+
emitDebug("intent:create-success", {
|
|
2779
|
+
record: summariseOnRampRecord(record)
|
|
2780
|
+
});
|
|
2781
|
+
return record;
|
|
2782
|
+
} catch (err) {
|
|
2783
|
+
const e = err instanceof Error ? err : new Error("Failed to create on-ramp intent");
|
|
2784
|
+
setStatus("failed");
|
|
2785
|
+
setError(e);
|
|
2786
|
+
emitDebug("intent:create-error", errorPayload(e));
|
|
2787
|
+
onErrorRef.current?.(e);
|
|
2788
|
+
throw e;
|
|
2789
|
+
}
|
|
2790
|
+
},
|
|
2791
|
+
[
|
|
2792
|
+
address,
|
|
2793
|
+
client,
|
|
2794
|
+
depositAddress,
|
|
2795
|
+
emitDebug,
|
|
2796
|
+
enabledTokens,
|
|
2797
|
+
ensureCorrectChain,
|
|
2798
|
+
executePrivateRead,
|
|
2799
|
+
networkConfig,
|
|
2800
|
+
postDepositLock,
|
|
2801
|
+
serviceAddress,
|
|
2802
|
+
tokenId,
|
|
2803
|
+
wagmiConfig,
|
|
2804
|
+
walletClient
|
|
2805
|
+
]
|
|
2806
|
+
);
|
|
2807
|
+
const registerOnRampTokenMapping = react.useCallback(
|
|
2808
|
+
async (moonpayTransactionId) => {
|
|
2809
|
+
const token = enabledTokens.find((t) => t.id.toLowerCase() === tokenId.toLowerCase());
|
|
2810
|
+
if (!token) {
|
|
2811
|
+
emitDebug("register-token-mapping:skip", {
|
|
2812
|
+
moonpayTransactionId,
|
|
2813
|
+
reason: "selected-token-not-found"
|
|
2814
|
+
});
|
|
2815
|
+
return;
|
|
2816
|
+
}
|
|
2817
|
+
const transactionId = activeIntentIdRef.current ?? moonpayTransactionId;
|
|
2818
|
+
try {
|
|
2819
|
+
emitDebug("register-token-mapping:request", {
|
|
2820
|
+
transactionId,
|
|
2821
|
+
moonpayTransactionId,
|
|
2822
|
+
tokenId,
|
|
2823
|
+
chainId: token.chainId
|
|
2824
|
+
});
|
|
2825
|
+
const record = await executePrivateRead(
|
|
2826
|
+
() => client.updateOnRamp(transactionId, {
|
|
2827
|
+
token_id: tokenId,
|
|
2828
|
+
chain_id: token.chainId,
|
|
2829
|
+
moonpay_transaction_id: transactionId === moonpayTransactionId ? void 0 : moonpayTransactionId
|
|
2830
|
+
})
|
|
2831
|
+
);
|
|
2832
|
+
emitDebug("register-token-mapping:success", {
|
|
2833
|
+
transactionId,
|
|
2834
|
+
moonpayTransactionId,
|
|
2835
|
+
record: summariseOnRampRecord(record)
|
|
2836
|
+
});
|
|
2837
|
+
} catch (err) {
|
|
2838
|
+
emitDebug("register-token-mapping:error", {
|
|
2839
|
+
transactionId,
|
|
2840
|
+
moonpayTransactionId,
|
|
2841
|
+
...errorPayload(err)
|
|
2842
|
+
});
|
|
2843
|
+
console.warn("Failed to register on-ramp token mapping:", err);
|
|
2844
|
+
}
|
|
2845
|
+
},
|
|
2846
|
+
[client, emitDebug, enabledTokens, executePrivateRead, tokenId]
|
|
2847
|
+
);
|
|
2848
|
+
const handleTransactionCreated = react.useCallback(
|
|
2849
|
+
async (props) => {
|
|
2850
|
+
emitDebug("moonpay:onTransactionCreated", summariseMoonPayEventProps(props));
|
|
2851
|
+
purchaseInitiatedRef.current = true;
|
|
2852
|
+
await registerOnRampTokenMapping(props.id);
|
|
2853
|
+
},
|
|
2854
|
+
[emitDebug, registerOnRampTokenMapping]
|
|
2855
|
+
);
|
|
2856
|
+
const signUrl = react.useCallback(
|
|
2857
|
+
async (url) => {
|
|
2858
|
+
setError(null);
|
|
2859
|
+
try {
|
|
2860
|
+
emitDebug("moonpay:onUrlSignatureRequested", summariseMoonPayUrl(url));
|
|
2861
|
+
const { signature } = await executePrivateRead(() => client.signOnRampUrl({ url }));
|
|
2862
|
+
setStatus("awaiting-purchase");
|
|
2863
|
+
emitDebug("sign-url:success", {
|
|
2864
|
+
signatureLength: signature.length
|
|
2865
|
+
});
|
|
2866
|
+
return signature;
|
|
2867
|
+
} catch (err) {
|
|
2868
|
+
const e = err instanceof Error ? err : new Error("Failed to sign on-ramp URL");
|
|
2869
|
+
setStatus("failed");
|
|
2870
|
+
setError(e);
|
|
2871
|
+
emitDebug("sign-url:error", errorPayload(e));
|
|
2872
|
+
onErrorRef.current?.(e);
|
|
2873
|
+
throw err;
|
|
2874
|
+
}
|
|
2875
|
+
},
|
|
2876
|
+
[client, emitDebug, executePrivateRead]
|
|
2877
|
+
);
|
|
2878
|
+
const waitForOnChainHash = react.useCallback(
|
|
2879
|
+
async (transactionId) => {
|
|
2880
|
+
const startTime = Date.now();
|
|
2881
|
+
emitDebug("delivery-poll:start", {
|
|
2882
|
+
transactionId,
|
|
2883
|
+
deliveryTimeout,
|
|
2884
|
+
deliveryPollInterval
|
|
2885
|
+
});
|
|
2886
|
+
while (Date.now() - startTime < deliveryTimeout) {
|
|
2887
|
+
try {
|
|
2888
|
+
const { pending: rows } = await executePrivateRead(() => client.getPendingOnRamps());
|
|
2889
|
+
setPending(rows);
|
|
2890
|
+
const record = rows.find((r) => matchesOnRampTransaction(r, transactionId));
|
|
2891
|
+
emitDebug("delivery-poll:tick", {
|
|
2892
|
+
transactionId,
|
|
2893
|
+
count: rows.length,
|
|
2894
|
+
matchingRecord: record ? summariseOnRampRecord(record) : null
|
|
2895
|
+
});
|
|
2896
|
+
if (record?.on_chain_tx_hash && record.quote_currency_amount) {
|
|
2897
|
+
emitDebug("delivery-poll:success", {
|
|
2898
|
+
transactionId,
|
|
2899
|
+
record: summariseOnRampRecord(record)
|
|
2900
|
+
});
|
|
2901
|
+
return record;
|
|
2902
|
+
}
|
|
2903
|
+
} catch (err) {
|
|
2904
|
+
emitDebug("delivery-poll:error", {
|
|
2905
|
+
transactionId,
|
|
2906
|
+
...errorPayload(err)
|
|
2907
|
+
});
|
|
2908
|
+
console.warn("Polling pending on-ramps failed:", err);
|
|
2909
|
+
}
|
|
2910
|
+
await new Promise((r) => setTimeout(r, deliveryPollInterval));
|
|
2911
|
+
}
|
|
2912
|
+
emitDebug("delivery-poll:timeout", { transactionId });
|
|
2913
|
+
return null;
|
|
2914
|
+
},
|
|
2915
|
+
[client, deliveryPollInterval, deliveryTimeout, emitDebug, executePrivateRead]
|
|
2916
|
+
);
|
|
2917
|
+
const triggerVerification = react.useCallback(
|
|
2918
|
+
async (record) => {
|
|
2919
|
+
const verificationKey = getOnRampVerificationKey(record);
|
|
2920
|
+
if (triggeredVerificationKeysRef.current.has(verificationKey)) {
|
|
2921
|
+
emitDebug("verification:skip-duplicate", {
|
|
2922
|
+
verificationKey,
|
|
2923
|
+
record: summariseOnRampRecord(record)
|
|
2924
|
+
});
|
|
2925
|
+
return;
|
|
2926
|
+
}
|
|
2927
|
+
const supersededKey = activeVerificationKeyRef.current;
|
|
2928
|
+
if (supersededKey && supersededKey !== verificationKey) {
|
|
2929
|
+
triggeredVerificationKeysRef.current.delete(supersededKey);
|
|
2930
|
+
}
|
|
2931
|
+
activeVerificationDoneRef.current?.();
|
|
2932
|
+
activeVerificationDoneRef.current = null;
|
|
2933
|
+
triggeredVerificationKeysRef.current.add(verificationKey);
|
|
2934
|
+
activeVerificationKeyRef.current = verificationKey;
|
|
2935
|
+
activeVerificationRecordRef.current = record;
|
|
2936
|
+
setActiveVerificationId(record.transaction_id);
|
|
2937
|
+
setFinalityProgress((prev) => {
|
|
2938
|
+
if (!(record.transaction_id in prev)) return prev;
|
|
2939
|
+
const next = { ...prev };
|
|
2940
|
+
delete next[record.transaction_id];
|
|
2941
|
+
return next;
|
|
2942
|
+
});
|
|
2943
|
+
emitDebug("verification:start", {
|
|
2944
|
+
verificationKey,
|
|
2945
|
+
record: summariseOnRampRecord(record)
|
|
2946
|
+
});
|
|
2947
|
+
try {
|
|
2948
|
+
if (!record.on_chain_tx_hash || !record.quote_currency_amount) {
|
|
2949
|
+
throw new Error("On-ramp record missing on-chain tx hash or delivered amount");
|
|
2950
|
+
}
|
|
2951
|
+
if (record.chain_id === void 0 || !record.wallet_address) {
|
|
2952
|
+
throw new Error("On-ramp record missing chain id or wallet address");
|
|
2953
|
+
}
|
|
2954
|
+
const recordTokenId = record.token_id;
|
|
2955
|
+
if (!recordTokenId) {
|
|
2956
|
+
throw new Error("On-ramp record missing token id");
|
|
2957
|
+
}
|
|
2958
|
+
const token = enabledTokens.find((t) => t.id.toLowerCase() === recordTokenId.toLowerCase());
|
|
2959
|
+
if (!token) throw new Error(`Unknown token: ${recordTokenId}`);
|
|
2960
|
+
if (token.chainId !== record.chain_id) {
|
|
2961
|
+
throw new Error(
|
|
2962
|
+
`Token ${recordTokenId} is on chain ${token.chainId} but record is on chain ${record.chain_id}`
|
|
2963
|
+
);
|
|
2964
|
+
}
|
|
2965
|
+
const amount = await resolveDeliveredAmount({
|
|
2966
|
+
onChainTxHash: record.on_chain_tx_hash,
|
|
2967
|
+
chainId: record.chain_id,
|
|
2968
|
+
walletAddress: record.wallet_address,
|
|
2969
|
+
token,
|
|
2970
|
+
fallbackAmount: record.quote_currency_amount,
|
|
2971
|
+
wagmiConfig,
|
|
2972
|
+
emitDebug
|
|
2973
|
+
});
|
|
2974
|
+
if (minDepositBaseUnits !== void 0 && amount < minDepositBaseUnits) {
|
|
2975
|
+
emitDebug("verification:below-minimum", {
|
|
2976
|
+
quoteCurrencyAmount: record.quote_currency_amount,
|
|
2977
|
+
minDepositBaseUnits: String(minDepositBaseUnits)
|
|
2978
|
+
});
|
|
2979
|
+
throw new Error(
|
|
2980
|
+
`Delivered amount (${record.quote_currency_amount}) is below the minimum deposit.`
|
|
2981
|
+
);
|
|
2982
|
+
}
|
|
2983
|
+
activeVerificationAmountRef.current = amount;
|
|
2984
|
+
if (activeIntentIdRef.current === record.transaction_id) {
|
|
2985
|
+
setStatus("verifying");
|
|
2986
|
+
}
|
|
2987
|
+
emitDebug("verification:check-deposit-request", {
|
|
2988
|
+
hash: record.on_chain_tx_hash,
|
|
2989
|
+
chainId: record.chain_id,
|
|
2990
|
+
amount: amount.toString()
|
|
2991
|
+
});
|
|
2992
|
+
await verify({
|
|
2993
|
+
hash: record.on_chain_tx_hash,
|
|
2994
|
+
chainId: record.chain_id,
|
|
2995
|
+
amount
|
|
2996
|
+
});
|
|
2997
|
+
} catch (err) {
|
|
2998
|
+
triggeredVerificationKeysRef.current.delete(verificationKey);
|
|
2999
|
+
if (activeVerificationKeyRef.current === verificationKey) {
|
|
3000
|
+
activeVerificationKeyRef.current = null;
|
|
3001
|
+
activeVerificationRecordRef.current = null;
|
|
3002
|
+
activeVerificationAmountRef.current = null;
|
|
3003
|
+
setActiveVerificationId(null);
|
|
3004
|
+
}
|
|
3005
|
+
throw err;
|
|
3006
|
+
}
|
|
3007
|
+
},
|
|
3008
|
+
[emitDebug, enabledTokens, minDepositBaseUnits, verify, wagmiConfig]
|
|
3009
|
+
);
|
|
3010
|
+
const handleTransactionCompleted = react.useCallback(
|
|
3011
|
+
async (props) => {
|
|
3012
|
+
emitDebug("moonpay:onTransactionCompleted", summariseMoonPayEventProps(props));
|
|
3013
|
+
try {
|
|
3014
|
+
setStatus("awaiting-delivery");
|
|
3015
|
+
await registerOnRampTokenMapping(props.id);
|
|
3016
|
+
const transactionId = activeIntentIdRef.current ?? props.id;
|
|
3017
|
+
const record = await waitForOnChainHash(transactionId);
|
|
3018
|
+
if (!record) {
|
|
3019
|
+
const err = new Error(
|
|
3020
|
+
"Backend has not yet confirmed delivery. You can finish from the pending list."
|
|
3021
|
+
);
|
|
3022
|
+
emitDebug("moonpay:completed-without-backend-row", {
|
|
3023
|
+
transactionId,
|
|
3024
|
+
moonpayTransactionId: props.id,
|
|
3025
|
+
message: err.message
|
|
3026
|
+
});
|
|
3027
|
+
setStatus("failed");
|
|
3028
|
+
setError(err);
|
|
3029
|
+
onErrorRef.current?.(err);
|
|
3030
|
+
return;
|
|
3031
|
+
}
|
|
3032
|
+
await triggerVerification(record);
|
|
3033
|
+
} catch (err) {
|
|
3034
|
+
const e = err instanceof Error ? err : new Error("Verification failed");
|
|
3035
|
+
emitDebug("moonpay:onTransactionCompleted-error", errorPayload(e));
|
|
3036
|
+
setStatus("failed");
|
|
3037
|
+
setError(e);
|
|
3038
|
+
onErrorRef.current?.(e);
|
|
3039
|
+
}
|
|
3040
|
+
},
|
|
3041
|
+
[emitDebug, registerOnRampTokenMapping, triggerVerification, waitForOnChainHash]
|
|
3042
|
+
);
|
|
3043
|
+
const handleWidgetClosed = react.useCallback(async () => {
|
|
3044
|
+
if (closeReconcilePromiseRef.current) return closeReconcilePromiseRef.current;
|
|
3045
|
+
closeReconcilePromiseRef.current = (async () => {
|
|
3046
|
+
const previousStatus = statusRef.current;
|
|
3047
|
+
const transactionId = activeIntentIdRef.current;
|
|
3048
|
+
emitDebug("moonpay:widget-closed-reconcile", {
|
|
3049
|
+
previousStatus,
|
|
3050
|
+
transactionId
|
|
3051
|
+
});
|
|
3052
|
+
if (!transactionId) {
|
|
3053
|
+
await refreshPending();
|
|
3054
|
+
return;
|
|
3055
|
+
}
|
|
3056
|
+
if (!purchaseInitiatedRef.current) {
|
|
3057
|
+
emitDebug("moonpay:widget-closed-without-purchase", {
|
|
3058
|
+
previousStatus,
|
|
3059
|
+
transactionId
|
|
3060
|
+
});
|
|
3061
|
+
if (address) clearPendingLock(address, transactionId);
|
|
3062
|
+
await refreshPending();
|
|
3063
|
+
if (previousStatus === "awaiting-purchase" || previousStatus === "awaiting-delivery") {
|
|
3064
|
+
setStatus("idle");
|
|
3065
|
+
}
|
|
3066
|
+
return;
|
|
3067
|
+
}
|
|
3068
|
+
try {
|
|
3069
|
+
setStatus("awaiting-delivery");
|
|
3070
|
+
const record = await waitForOnChainHash(transactionId);
|
|
3071
|
+
if (record) {
|
|
3072
|
+
await triggerVerification(record);
|
|
3073
|
+
return;
|
|
3074
|
+
}
|
|
3075
|
+
await refreshPending();
|
|
3076
|
+
if (previousStatus === "awaiting-purchase" || previousStatus === "awaiting-delivery") {
|
|
3077
|
+
setStatus("idle");
|
|
3078
|
+
}
|
|
3079
|
+
} catch (err) {
|
|
3080
|
+
const e = err instanceof Error ? err : new Error("On-ramp reconciliation failed");
|
|
3081
|
+
emitDebug("moonpay:widget-closed-reconcile-error", errorPayload(e));
|
|
3082
|
+
setStatus("failed");
|
|
3083
|
+
setError(e);
|
|
3084
|
+
onErrorRef.current?.(e);
|
|
3085
|
+
}
|
|
3086
|
+
})();
|
|
3087
|
+
try {
|
|
3088
|
+
await closeReconcilePromiseRef.current;
|
|
3089
|
+
} finally {
|
|
3090
|
+
closeReconcilePromiseRef.current = null;
|
|
3091
|
+
}
|
|
3092
|
+
}, [address, emitDebug, refreshPending, triggerVerification, waitForOnChainHash]);
|
|
3093
|
+
const finishPendingVerification = react.useCallback(
|
|
3094
|
+
async (record) => {
|
|
3095
|
+
try {
|
|
3096
|
+
emitDebug("pending:finish-verification", {
|
|
3097
|
+
record: summariseOnRampRecord(record)
|
|
3098
|
+
});
|
|
3099
|
+
await triggerVerification(record);
|
|
3100
|
+
} catch (err) {
|
|
3101
|
+
const e = err instanceof Error ? err : new Error("Verification failed");
|
|
3102
|
+
emitDebug("pending:finish-verification-error", errorPayload(e));
|
|
3103
|
+
setStatus("failed");
|
|
3104
|
+
setError(e);
|
|
3105
|
+
onErrorRef.current?.(e);
|
|
3106
|
+
throw e;
|
|
3107
|
+
}
|
|
3108
|
+
},
|
|
3109
|
+
[emitDebug, triggerVerification]
|
|
3110
|
+
);
|
|
3111
|
+
const triggerVerificationRef = react.useRef(triggerVerification);
|
|
3112
|
+
react.useEffect(() => {
|
|
3113
|
+
triggerVerificationRef.current = triggerVerification;
|
|
3114
|
+
}, [triggerVerification]);
|
|
3115
|
+
react.useEffect(() => {
|
|
3116
|
+
let cancelled = false;
|
|
3117
|
+
void (async () => {
|
|
3118
|
+
for (const record of pending) {
|
|
3119
|
+
if (cancelled) break;
|
|
3120
|
+
if (!record.on_chain_tx_hash || !record.quote_currency_amount) continue;
|
|
3121
|
+
const key = getOnRampVerificationKey(record);
|
|
3122
|
+
if (triggeredVerificationKeysRef.current.has(key)) continue;
|
|
3123
|
+
try {
|
|
3124
|
+
await triggerVerificationRef.current(record);
|
|
3125
|
+
} catch {
|
|
3126
|
+
continue;
|
|
3127
|
+
}
|
|
3128
|
+
if (activeVerificationKeyRef.current === key) {
|
|
3129
|
+
await new Promise((resolve) => {
|
|
3130
|
+
activeVerificationDoneRef.current = resolve;
|
|
3131
|
+
});
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
})();
|
|
3135
|
+
return () => {
|
|
3136
|
+
cancelled = true;
|
|
3137
|
+
};
|
|
3138
|
+
}, [pending]);
|
|
3139
|
+
return {
|
|
3140
|
+
status,
|
|
3141
|
+
activeIntentId,
|
|
3142
|
+
pending,
|
|
3143
|
+
activeVerificationId,
|
|
3144
|
+
error,
|
|
3145
|
+
finalityProgress,
|
|
3146
|
+
depositAddress,
|
|
3147
|
+
minDepositBaseUnits,
|
|
3148
|
+
selectedToken,
|
|
3149
|
+
prepareOnRampIntent,
|
|
3150
|
+
signUrl,
|
|
3151
|
+
handleTransactionCreated,
|
|
3152
|
+
handleTransactionCompleted,
|
|
3153
|
+
finishPendingVerification,
|
|
3154
|
+
handleWidgetClosed,
|
|
3155
|
+
refreshPending
|
|
3156
|
+
};
|
|
3157
|
+
}
|
|
3158
|
+
function summariseToken(token) {
|
|
3159
|
+
return {
|
|
3160
|
+
tokenId: token.id,
|
|
3161
|
+
chainId: token.chainId,
|
|
3162
|
+
symbol: token.symbol ?? null,
|
|
3163
|
+
decimals: token.decimals ?? null
|
|
3164
|
+
};
|
|
3165
|
+
}
|
|
3166
|
+
function summariseOnRampRecord(record) {
|
|
3167
|
+
return {
|
|
3168
|
+
transaction_id: record.transaction_id,
|
|
3169
|
+
external_transaction_id: record.external_transaction_id ?? null,
|
|
3170
|
+
moonpay_transaction_id: record.moonpay_transaction_id ?? null,
|
|
3171
|
+
status: record.status,
|
|
3172
|
+
wallet_address: record.wallet_address,
|
|
3173
|
+
token_id: record.token_id,
|
|
3174
|
+
chain_id: record.chain_id,
|
|
3175
|
+
moonpay_currency_code: record.moonpay_currency_code ?? null,
|
|
3176
|
+
quote_currency_amount: record.quote_currency_amount ?? null,
|
|
3177
|
+
on_chain_tx_hash: record.on_chain_tx_hash ?? null,
|
|
3178
|
+
deposit_tx_hash: record.deposit_tx_hash ?? null,
|
|
3179
|
+
deposit_triggered_at: record.deposit_triggered_at ?? null,
|
|
3180
|
+
credited_at: record.credited_at ?? null
|
|
3181
|
+
};
|
|
3182
|
+
}
|
|
3183
|
+
async function resolveDeliveredAmount({
|
|
3184
|
+
onChainTxHash,
|
|
3185
|
+
chainId,
|
|
3186
|
+
walletAddress,
|
|
3187
|
+
token,
|
|
3188
|
+
fallbackAmount,
|
|
3189
|
+
wagmiConfig,
|
|
3190
|
+
emitDebug
|
|
3191
|
+
}) {
|
|
3192
|
+
if (token.contract === viem.zeroAddress) {
|
|
3193
|
+
return viem.parseUnits(fallbackAmount, token.decimals);
|
|
3194
|
+
}
|
|
3195
|
+
let receiptError;
|
|
3196
|
+
try {
|
|
3197
|
+
const receipt = await waitForTransactionReceipt(wagmiConfig, {
|
|
3198
|
+
hash: onChainTxHash,
|
|
3199
|
+
chainId,
|
|
3200
|
+
timeout: 6e4,
|
|
3201
|
+
pollingInterval: 4e3
|
|
3202
|
+
});
|
|
3203
|
+
let delivered = 0n;
|
|
3204
|
+
for (const log of receipt.logs) {
|
|
3205
|
+
if (log.address.toLowerCase() !== token.contract.toLowerCase()) continue;
|
|
3206
|
+
try {
|
|
3207
|
+
const decoded = viem.decodeEventLog({
|
|
3208
|
+
abi: [ERC20_TRANSFER_EVENT],
|
|
3209
|
+
data: log.data,
|
|
3210
|
+
topics: log.topics
|
|
3211
|
+
});
|
|
3212
|
+
if (decoded.eventName !== "Transfer") continue;
|
|
3213
|
+
const to = decoded.args.to.toLowerCase();
|
|
3214
|
+
if (to !== walletAddress.toLowerCase()) continue;
|
|
3215
|
+
delivered += decoded.args.value;
|
|
3216
|
+
} catch {
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
if (delivered > 0n) {
|
|
3220
|
+
emitDebug("verification:amount-from-receipt", {
|
|
3221
|
+
amount: delivered.toString(),
|
|
3222
|
+
tokenAddress: token.contract,
|
|
3223
|
+
walletAddress,
|
|
3224
|
+
moonpayQuoteCurrencyAmount: fallbackAmount
|
|
3225
|
+
});
|
|
3226
|
+
return delivered;
|
|
3227
|
+
}
|
|
3228
|
+
emitDebug("verification:amount-from-receipt-missing", {
|
|
3229
|
+
tokenAddress: token.contract,
|
|
3230
|
+
walletAddress,
|
|
3231
|
+
moonpayQuoteCurrencyAmount: fallbackAmount
|
|
3232
|
+
});
|
|
3233
|
+
} catch (err) {
|
|
3234
|
+
emitDebug("verification:amount-from-receipt-error", errorPayload(err));
|
|
3235
|
+
receiptError = err;
|
|
3236
|
+
}
|
|
3237
|
+
const errorDetail = receiptError instanceof Error ? receiptError.message : receiptError === void 0 ? `no ${token.symbol} Transfer to ${walletAddress} found` : String(receiptError);
|
|
3238
|
+
throw new Error(
|
|
3239
|
+
`Unable to derive delivered ${token.symbol} amount from ${onChainTxHash}: ${errorDetail}`
|
|
3240
|
+
);
|
|
3241
|
+
}
|
|
3242
|
+
function matchesOnRampTransaction(record, transactionId) {
|
|
3243
|
+
return record.transaction_id === transactionId || record.external_transaction_id === transactionId || record.moonpay_transaction_id === transactionId;
|
|
3244
|
+
}
|
|
3245
|
+
function getOnRampVerificationKey(record) {
|
|
3246
|
+
return record.on_chain_tx_hash ?? record.transaction_id;
|
|
3247
|
+
}
|
|
3248
|
+
function requireServiceAddress(serviceAddress) {
|
|
3249
|
+
if (!serviceAddress) {
|
|
3250
|
+
throw new Error("Service address not configured");
|
|
3251
|
+
}
|
|
3252
|
+
return serviceAddress;
|
|
3253
|
+
}
|
|
3254
|
+
function summariseMoonPayEventProps(props) {
|
|
3255
|
+
return {
|
|
3256
|
+
id: props.id,
|
|
3257
|
+
externalTransactionId: props.externalTransactionId,
|
|
3258
|
+
status: props.status,
|
|
3259
|
+
walletAddress: props.walletAddress,
|
|
3260
|
+
walletAddressTag: props.walletAddressTag,
|
|
3261
|
+
baseCurrencyAmount: props.baseCurrencyAmount,
|
|
3262
|
+
quoteCurrencyAmount: props.quoteCurrencyAmount,
|
|
3263
|
+
baseCurrency: props.baseCurrency,
|
|
3264
|
+
quoteCurrency: props.quoteCurrency,
|
|
3265
|
+
createdAt: props.createdAt
|
|
3266
|
+
};
|
|
3267
|
+
}
|
|
3268
|
+
function summariseMoonPayUrl(url) {
|
|
3269
|
+
try {
|
|
3270
|
+
const parsed = new URL(url);
|
|
3271
|
+
const params = parsed.searchParams;
|
|
3272
|
+
return {
|
|
3273
|
+
origin: parsed.origin,
|
|
3274
|
+
pathname: parsed.pathname,
|
|
3275
|
+
apiKeyPrefix: params.get("apiKey")?.slice(0, 8) ?? null,
|
|
3276
|
+
currencyCode: params.get("currencyCode"),
|
|
3277
|
+
baseCurrencyCode: params.get("baseCurrencyCode"),
|
|
3278
|
+
baseCurrencyAmount: params.get("baseCurrencyAmount"),
|
|
3279
|
+
walletAddress: params.get("walletAddress"),
|
|
3280
|
+
externalCustomerId: params.get("externalCustomerId"),
|
|
3281
|
+
externalTransactionId: params.get("externalTransactionId"),
|
|
3282
|
+
redirectURL: params.get("redirectURL"),
|
|
3283
|
+
signaturePresent: params.has("signature")
|
|
3284
|
+
};
|
|
3285
|
+
} catch {
|
|
3286
|
+
return { parseError: true, length: url.length };
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
function errorPayload(err) {
|
|
3290
|
+
if (err instanceof Error) {
|
|
3291
|
+
return {
|
|
3292
|
+
name: err.name,
|
|
3293
|
+
message: err.message,
|
|
3294
|
+
stack: err.stack?.split("\n").slice(0, 4).join("\n")
|
|
3295
|
+
};
|
|
3296
|
+
}
|
|
3297
|
+
return { message: String(err) };
|
|
3298
|
+
}
|
|
3299
|
+
function useMoonPayBuyWidget({
|
|
3300
|
+
variant,
|
|
3301
|
+
visible,
|
|
3302
|
+
autoStart,
|
|
3303
|
+
canBuy,
|
|
3304
|
+
openWidget,
|
|
3305
|
+
refreshPending,
|
|
3306
|
+
theme,
|
|
3307
|
+
themeId,
|
|
3308
|
+
colorCode,
|
|
3309
|
+
baseCurrencyCode,
|
|
3310
|
+
baseCurrencyAmount,
|
|
3311
|
+
quoteCurrencyAmount,
|
|
3312
|
+
lockAmount,
|
|
3313
|
+
paymentMethod,
|
|
3314
|
+
currencyCode,
|
|
3315
|
+
depositAddress,
|
|
3316
|
+
externalCustomerId,
|
|
3317
|
+
externalTransactionId,
|
|
3318
|
+
onClose,
|
|
3319
|
+
onCloseOverlay,
|
|
3320
|
+
onReady,
|
|
3321
|
+
onUrlSignatureRequested,
|
|
3322
|
+
onTransactionCreated,
|
|
3323
|
+
onTransactionCompleted
|
|
3324
|
+
}) {
|
|
3325
|
+
const autoStartedRef = react.useRef(false);
|
|
3326
|
+
react.useEffect(() => {
|
|
3327
|
+
if (!autoStart || autoStartedRef.current || !canBuy) return;
|
|
3328
|
+
autoStartedRef.current = true;
|
|
3329
|
+
void openWidget();
|
|
3330
|
+
}, [autoStart, canBuy, openWidget]);
|
|
3331
|
+
react.useEffect(() => {
|
|
3332
|
+
if (variant !== "embedded" || !visible) return;
|
|
3333
|
+
const id = setInterval(() => void refreshPending(), 5e3);
|
|
3334
|
+
return () => clearInterval(id);
|
|
3335
|
+
}, [variant, visible, refreshPending]);
|
|
3336
|
+
const callbacksRef = react.useRef({
|
|
3337
|
+
onClose,
|
|
3338
|
+
onCloseOverlay,
|
|
3339
|
+
onReady,
|
|
3340
|
+
onUrlSignatureRequested,
|
|
3341
|
+
onTransactionCreated,
|
|
3342
|
+
onTransactionCompleted
|
|
3343
|
+
});
|
|
3344
|
+
react.useEffect(() => {
|
|
3345
|
+
callbacksRef.current = {
|
|
3346
|
+
onClose,
|
|
3347
|
+
onCloseOverlay,
|
|
3348
|
+
onReady,
|
|
3349
|
+
onUrlSignatureRequested,
|
|
3350
|
+
onTransactionCreated,
|
|
3351
|
+
onTransactionCompleted
|
|
3352
|
+
};
|
|
3353
|
+
});
|
|
3354
|
+
const overlayNode = react.useMemo(
|
|
3355
|
+
() => variant === "overlay" ? buildOverlayNode() : void 0,
|
|
3356
|
+
[variant]
|
|
3357
|
+
);
|
|
3358
|
+
return react.useMemo(() => {
|
|
3359
|
+
if (!visible || !depositAddress || !externalTransactionId) return null;
|
|
3360
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3361
|
+
moonpayReact.MoonPayBuyWidget,
|
|
3362
|
+
{
|
|
3363
|
+
variant,
|
|
3364
|
+
visible: true,
|
|
3365
|
+
theme,
|
|
3366
|
+
themeId,
|
|
3367
|
+
colorCode,
|
|
3368
|
+
overlayNode,
|
|
3369
|
+
baseCurrencyCode,
|
|
3370
|
+
baseCurrencyAmount,
|
|
3371
|
+
quoteCurrencyAmount,
|
|
3372
|
+
lockAmount: lockAmount ? "true" : void 0,
|
|
3373
|
+
paymentMethod,
|
|
3374
|
+
currencyCode,
|
|
3375
|
+
walletAddress: depositAddress,
|
|
3376
|
+
externalCustomerId,
|
|
3377
|
+
externalTransactionId,
|
|
3378
|
+
onClose: () => callbacksRef.current.onClose(),
|
|
3379
|
+
onCloseOverlay: () => callbacksRef.current.onCloseOverlay(),
|
|
3380
|
+
onReady: () => callbacksRef.current.onReady(),
|
|
3381
|
+
onUrlSignatureRequested: (url) => callbacksRef.current.onUrlSignatureRequested(url),
|
|
3382
|
+
onTransactionCreated: (props) => callbacksRef.current.onTransactionCreated(props),
|
|
3383
|
+
onTransactionCompleted: (props) => callbacksRef.current.onTransactionCompleted(props)
|
|
3384
|
+
}
|
|
3385
|
+
);
|
|
3386
|
+
}, [
|
|
3387
|
+
visible,
|
|
3388
|
+
depositAddress,
|
|
3389
|
+
externalTransactionId,
|
|
3390
|
+
variant,
|
|
3391
|
+
theme,
|
|
3392
|
+
themeId,
|
|
3393
|
+
colorCode,
|
|
3394
|
+
overlayNode,
|
|
3395
|
+
baseCurrencyCode,
|
|
3396
|
+
baseCurrencyAmount,
|
|
3397
|
+
quoteCurrencyAmount,
|
|
3398
|
+
lockAmount,
|
|
3399
|
+
paymentMethod,
|
|
3400
|
+
currencyCode,
|
|
3401
|
+
externalCustomerId
|
|
3402
|
+
]);
|
|
3403
|
+
}
|
|
3404
|
+
function buildOverlayNode() {
|
|
3405
|
+
if (typeof document === "undefined") return void 0;
|
|
3406
|
+
const wrap = document.createElement("div");
|
|
3407
|
+
wrap.style.cssText = "display:flex;flex-direction:column;align-items:center;gap:12px";
|
|
3408
|
+
wrap.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="139" height="20.61" viewBox="0 0 139 20.61"><path d="M15.08,20.61L10.49,20.61L10.49,0.00L17.96,0.00L18.81,0.02L19.62,0.10L20.38,0.22L21.11,0.39L21.79,0.60L22.43,0.87L23.03,1.18L23.58,1.53L24.08,1.92L24.53,2.35L24.93,2.82L25.29,3.33L25.59,3.88L25.84,4.47L26.03,5.09L26.17,5.75L26.25,6.44L26.28,7.17L26.28,7.17L26.28,7.62L26.25,8.33L26.17,9.01L26.03,9.66L25.84,10.27L25.59,10.86L25.29,11.41L24.93,11.93L24.53,12.40L24.08,12.83L23.58,13.23L23.03,13.58L22.43,13.89L21.79,14.15L21.11,14.37L20.38,14.54L19.62,14.66L18.81,14.73L17.96,14.76L17.96,14.76L15.08,14.76L15.08,20.61ZM18.19,3.98L15.08,3.98L15.08,10.78L18.19,10.78L18.53,10.77L18.87,10.73L19.18,10.68L19.48,10.59L19.76,10.49L20.02,10.36L20.27,10.21L20.50,10.04L20.70,9.85L20.89,9.64L21.06,9.42L21.21,9.17L21.34,8.91L21.45,8.63L21.53,8.34L21.59,8.04L21.62,7.72L21.63,7.39L21.63,7.39L21.62,7.05L21.59,6.72L21.53,6.40L21.45,6.10L21.34,5.82L21.21,5.56L21.06,5.31L20.89,5.09L20.70,4.88L20.50,4.69L20.27,4.53L20.02,4.38L19.76,4.26L19.48,4.16L19.18,4.08L18.87,4.02L18.53,3.99L18.19,3.98L18.19,3.98ZM31.76,20.61L27.16,20.61L27.16,0.00L35.20,0.00L36.03,0.02L36.82,0.09L37.58,0.19L38.30,0.34L38.98,0.53L39.62,0.77L40.23,1.05L40.78,1.37L41.28,1.73L41.74,2.13L42.15,2.57L42.51,3.05L42.82,3.58L43.07,4.15L43.26,4.76L43.40,5.42L43.49,6.12L43.52,6.86L43.52,6.86L43.52,7.31L43.49,8.04L43.40,8.73L43.26,9.38L43.07,9.98L42.82,10.54L42.51,11.06L42.51,11.06L42.15,11.53L41.75,11.97L41.30,12.36L40.80,12.72L40.26,13.03L39.68,13.30L39.68,13.30L44.89,20.61L39.57,20.61L35.12,14.06L31.76,14.06L31.76,20.61ZM35.56,3.89L31.76,3.89L31.76,10.44L35.56,10.44L35.89,10.43L36.21,10.40L36.51,10.34L36.79,10.26L37.06,10.16L37.31,10.04L37.55,9.89L37.77,9.73L37.97,9.55L38.15,9.35L38.32,9.13L38.46,8.89L38.59,8.64L38.69,8.37L38.77,8.09L38.82,7.79L38.86,7.49L38.87,7.17L38.86,6.85L38.82,6.54L38.77,6.25L38.69,5.97L38.59,5.70L38.46,5.45L38.32,5.21L38.15,4.99L37.97,4.79L37.77,4.61L37.55,4.44L37.31,4.30L37.06,4.17L36.79,4.07L36.51,3.99L36.21,3.94L35.89,3.90L35.56,3.89L35.56,3.89ZM50.25,20.61L45.66,20.61L45.66,0.17L50.25,0.17L50.25,20.61ZM64.88,20.61L57.41,20.61L51.19,0.17L55.92,0.17L60.71,16.88L61.66,16.88L66.09,0.17L70.68,0.17L64.88,20.61ZM71.44,20.61L66.85,20.61L73.60,0.17L81.02,0.17L88.02,20.61L83.26,20.61L81.61,15.54L73.07,15.54L71.44,20.61ZM74.27,11.73L80.35,11.73L77.80,3.92L76.76,3.92L74.27,11.73ZM92.94,20.61L88.68,20.61L88.68,0.17L96.21,0.17L103.91,16.88L104.30,16.88L104.30,0.17L108.62,0.17L108.62,20.61L101.03,20.61L93.33,3.89L92.94,3.89L92.94,20.61ZM113.87,20.61L109.28,20.61L116.02,0.17L123.44,0.17L130.44,20.61L125.68,20.61L124.03,15.54L115.49,15.54L113.87,20.61ZM116.70,11.73L122.77,11.73L120.22,3.92L119.19,3.92L116.70,11.73Z" fill="#ffffff"/></svg><span>Your secure checkout is loading</span>`;
|
|
3409
|
+
return wrap;
|
|
3410
|
+
}
|
|
3411
|
+
function FiatOnRampForm({
|
|
3412
|
+
tokenId,
|
|
3413
|
+
currencyCode,
|
|
3414
|
+
baseCurrencyCode = "usd",
|
|
3415
|
+
defaultBaseCurrencyAmount = "100",
|
|
3416
|
+
quoteCurrencyAmount,
|
|
3417
|
+
tokenSymbol,
|
|
3418
|
+
theme,
|
|
3419
|
+
themeId,
|
|
3420
|
+
colorCode,
|
|
3421
|
+
variant = "overlay",
|
|
3422
|
+
autoStart = false,
|
|
3423
|
+
lockAmount,
|
|
3424
|
+
paymentMethod,
|
|
3425
|
+
postDepositLock,
|
|
3426
|
+
onCredited,
|
|
3427
|
+
onLockSubmitted,
|
|
3428
|
+
onLockFailed,
|
|
3429
|
+
onError,
|
|
3430
|
+
onDebugEvent
|
|
3431
|
+
}) {
|
|
3432
|
+
const { address } = wagmi.useAccount();
|
|
3433
|
+
const [visible, setVisible] = react.useState(false);
|
|
3434
|
+
const [isPreparing, setIsPreparing] = react.useState(false);
|
|
3435
|
+
const [rowError, setRowError] = react.useState(null);
|
|
3436
|
+
const [lockError, setLockError] = react.useState(null);
|
|
3437
|
+
const [lockSettled, setLockSettled] = react.useState(false);
|
|
3438
|
+
const {
|
|
3439
|
+
status,
|
|
3440
|
+
activeIntentId,
|
|
3441
|
+
pending,
|
|
3442
|
+
activeVerificationId,
|
|
3443
|
+
error,
|
|
3444
|
+
finalityProgress,
|
|
3445
|
+
depositAddress,
|
|
3446
|
+
minDepositBaseUnits,
|
|
3447
|
+
selectedToken,
|
|
3448
|
+
prepareOnRampIntent,
|
|
3449
|
+
signUrl,
|
|
3450
|
+
handleTransactionCreated,
|
|
3451
|
+
handleTransactionCompleted,
|
|
3452
|
+
finishPendingVerification,
|
|
3453
|
+
handleWidgetClosed,
|
|
3454
|
+
refreshPending
|
|
3455
|
+
} = useFiatOnRamp({
|
|
3456
|
+
tokenId,
|
|
3457
|
+
postDepositLock,
|
|
3458
|
+
onCredited,
|
|
3459
|
+
// Lock callbacks aren't intent-keyed, so a resumed background row's lock
|
|
3460
|
+
// can settle these flags while a newer purchase is still locking — a
|
|
3461
|
+
// transient overpromise that the newer lock's own outcome then corrects.
|
|
3462
|
+
onLockSubmitted: (response) => {
|
|
3463
|
+
setLockError(null);
|
|
3464
|
+
setLockSettled(true);
|
|
3465
|
+
onLockSubmitted?.(response);
|
|
3466
|
+
},
|
|
3467
|
+
onLockFailed: (err) => {
|
|
3468
|
+
setLockError(err.message);
|
|
3469
|
+
onLockFailed?.(err);
|
|
3470
|
+
},
|
|
3471
|
+
onError,
|
|
3472
|
+
onDebugEvent
|
|
3473
|
+
});
|
|
3474
|
+
const decimals = selectedToken?.decimals;
|
|
3475
|
+
const displaySymbol = tokenSymbol ?? selectedToken?.symbol ?? currencyCode.toUpperCase();
|
|
3476
|
+
const emitFormDebug = react.useCallback(
|
|
3477
|
+
(event, payload) => {
|
|
3478
|
+
onDebugEvent?.({
|
|
3479
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3480
|
+
event,
|
|
3481
|
+
status,
|
|
3482
|
+
tokenId,
|
|
3483
|
+
payload
|
|
3484
|
+
});
|
|
3485
|
+
},
|
|
3486
|
+
[onDebugEvent, status, tokenId]
|
|
3487
|
+
);
|
|
3488
|
+
const minFiatGate = minDepositBaseUnits !== void 0 && decimals !== void 0 ? Number(viem.formatUnits(minDepositBaseUnits, decimals)) * 1.05 : void 0;
|
|
3489
|
+
const { units: quoteBaseUnits, failed: quoteParseFailed } = (() => {
|
|
3490
|
+
if (!quoteCurrencyAmount || decimals === void 0) return { units: void 0, failed: false };
|
|
3491
|
+
try {
|
|
3492
|
+
return { units: viem.parseUnits(quoteCurrencyAmount, decimals), failed: false };
|
|
3493
|
+
} catch {
|
|
3494
|
+
return { units: void 0, failed: true };
|
|
3495
|
+
}
|
|
3496
|
+
})();
|
|
3497
|
+
const isBelowMin = quoteBaseUnits !== void 0 && minDepositBaseUnits !== void 0 ? quoteBaseUnits < minDepositBaseUnits : minFiatGate !== void 0 && Number(defaultBaseCurrencyAmount) < minFiatGate;
|
|
3498
|
+
const isBusy = isPreparing || status === "awaiting-purchase";
|
|
3499
|
+
const lockPending = !!postDepositLock && status === "credited" && !lockSettled && !lockError;
|
|
3500
|
+
const isInitializing = !!address && !depositAddress;
|
|
3501
|
+
const isPrePurchase = status === "idle" || status === "awaiting-purchase";
|
|
3502
|
+
const isVerifying = status === "awaiting-delivery" || status === "verifying";
|
|
3503
|
+
const blockReasons = react.useMemo(
|
|
3504
|
+
() => [
|
|
3505
|
+
!address ? "wallet-not-connected" : null,
|
|
3506
|
+
!depositAddress ? "deposit-address-not-loaded" : null,
|
|
3507
|
+
isBusy ? `busy:${isPreparing ? "preparing" : status}` : null,
|
|
3508
|
+
visible ? "widget-open" : null,
|
|
3509
|
+
isBelowMin ? "below-minimum" : null,
|
|
3510
|
+
quoteParseFailed ? "invalid-quote-amount" : null
|
|
3511
|
+
].filter((reason) => Boolean(reason)),
|
|
3512
|
+
[address, depositAddress, isBelowMin, isBusy, isPreparing, quoteParseFailed, status, visible]
|
|
3513
|
+
);
|
|
3514
|
+
const canBuy = blockReasons.length === 0;
|
|
3515
|
+
const handleOpen = react.useCallback(async () => {
|
|
3516
|
+
if (!canBuy) {
|
|
3517
|
+
emitFormDebug("form:open-blocked", {
|
|
3518
|
+
reasons: blockReasons,
|
|
3519
|
+
currencyCode,
|
|
3520
|
+
tokenSymbol: displaySymbol,
|
|
3521
|
+
tokenDecimals: decimals ?? null,
|
|
3522
|
+
baseCurrencyCode,
|
|
3523
|
+
defaultBaseCurrencyAmount,
|
|
3524
|
+
depositAddress: depositAddress ?? null,
|
|
3525
|
+
walletAddress: address ?? null,
|
|
3526
|
+
status
|
|
3527
|
+
});
|
|
3528
|
+
return;
|
|
3529
|
+
}
|
|
3530
|
+
setIsPreparing(true);
|
|
3531
|
+
setLockError(null);
|
|
3532
|
+
setLockSettled(false);
|
|
3533
|
+
emitFormDebug("form:open-click", {
|
|
3534
|
+
currencyCode,
|
|
3535
|
+
tokenSymbol: displaySymbol,
|
|
3536
|
+
tokenDecimals: decimals ?? null,
|
|
3537
|
+
baseCurrencyCode,
|
|
3538
|
+
defaultBaseCurrencyAmount,
|
|
3539
|
+
depositAddress: depositAddress ?? null,
|
|
3540
|
+
walletConnected: Boolean(address)
|
|
3541
|
+
});
|
|
3542
|
+
try {
|
|
3543
|
+
const intent = await prepareOnRampIntent({
|
|
3544
|
+
currencyCode,
|
|
3545
|
+
baseCurrencyCode,
|
|
3546
|
+
baseCurrencyAmount: defaultBaseCurrencyAmount,
|
|
3547
|
+
quoteCurrencyAmount
|
|
3548
|
+
});
|
|
3549
|
+
emitFormDebug("form:intent-ready", {
|
|
3550
|
+
transactionId: intent.transaction_id,
|
|
3551
|
+
externalTransactionId: intent.external_transaction_id ?? null
|
|
3552
|
+
});
|
|
3553
|
+
setVisible(true);
|
|
3554
|
+
} catch (err) {
|
|
3555
|
+
const error2 = err instanceof Error ? err : new Error("Failed to prepare MoonPay on-ramp");
|
|
3556
|
+
emitFormDebug("form:intent-error", {
|
|
3557
|
+
name: error2.name,
|
|
3558
|
+
message: error2.message
|
|
3559
|
+
});
|
|
3560
|
+
} finally {
|
|
3561
|
+
setIsPreparing(false);
|
|
3562
|
+
}
|
|
3563
|
+
}, [
|
|
3564
|
+
address,
|
|
3565
|
+
baseCurrencyCode,
|
|
3566
|
+
blockReasons,
|
|
3567
|
+
canBuy,
|
|
3568
|
+
currencyCode,
|
|
3569
|
+
decimals,
|
|
3570
|
+
displaySymbol,
|
|
3571
|
+
defaultBaseCurrencyAmount,
|
|
3572
|
+
quoteCurrencyAmount,
|
|
3573
|
+
depositAddress,
|
|
3574
|
+
emitFormDebug,
|
|
3575
|
+
prepareOnRampIntent,
|
|
3576
|
+
status
|
|
3577
|
+
]);
|
|
3578
|
+
const handleClose = react.useCallback(async () => {
|
|
3579
|
+
emitFormDebug("moonpay:onClose");
|
|
3580
|
+
setVisible(false);
|
|
3581
|
+
await handleWidgetClosed();
|
|
3582
|
+
}, [emitFormDebug, handleWidgetClosed]);
|
|
3583
|
+
const handleCloseOverlay = react.useCallback(async () => {
|
|
3584
|
+
emitFormDebug("moonpay:onCloseOverlay");
|
|
3585
|
+
setVisible(false);
|
|
3586
|
+
await handleWidgetClosed();
|
|
3587
|
+
}, [emitFormDebug, handleWidgetClosed]);
|
|
3588
|
+
const handleReady = react.useCallback(async () => {
|
|
3589
|
+
emitFormDebug("moonpay:onReady");
|
|
3590
|
+
}, [emitFormDebug]);
|
|
3591
|
+
const widgetElement = useMoonPayBuyWidget({
|
|
3592
|
+
variant,
|
|
3593
|
+
visible,
|
|
3594
|
+
autoStart,
|
|
3595
|
+
canBuy,
|
|
3596
|
+
openWidget: handleOpen,
|
|
3597
|
+
refreshPending,
|
|
3598
|
+
theme,
|
|
3599
|
+
themeId,
|
|
3600
|
+
colorCode,
|
|
3601
|
+
baseCurrencyCode,
|
|
3602
|
+
baseCurrencyAmount: defaultBaseCurrencyAmount,
|
|
3603
|
+
quoteCurrencyAmount,
|
|
3604
|
+
lockAmount,
|
|
3605
|
+
paymentMethod,
|
|
3606
|
+
currencyCode,
|
|
3607
|
+
depositAddress,
|
|
3608
|
+
externalCustomerId: address?.toLowerCase(),
|
|
3609
|
+
externalTransactionId: activeIntentId,
|
|
3610
|
+
onClose: handleClose,
|
|
3611
|
+
onCloseOverlay: handleCloseOverlay,
|
|
3612
|
+
onReady: handleReady,
|
|
3613
|
+
onUrlSignatureRequested: signUrl,
|
|
3614
|
+
onTransactionCreated: handleTransactionCreated,
|
|
3615
|
+
onTransactionCompleted: handleTransactionCompleted
|
|
3616
|
+
});
|
|
3617
|
+
react.useEffect(() => {
|
|
3618
|
+
if (variant !== "embedded" || !visible) return;
|
|
3619
|
+
if (isVerifying || status === "credited") {
|
|
3620
|
+
setVisible(false);
|
|
3621
|
+
void refreshPending();
|
|
3622
|
+
}
|
|
3623
|
+
}, [variant, visible, isVerifying, status, refreshPending]);
|
|
3624
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-privana": true, className: "flex flex-col gap-4", children: [
|
|
3625
|
+
pending.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
3626
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-foreground text-sm font-medium", children: "Validating purchases" }),
|
|
3627
|
+
pending.map((record) => {
|
|
3628
|
+
const progress = parseFinalityProgress(finalityProgress[record.transaction_id]);
|
|
3629
|
+
const hasProgress = !!finalityProgress[record.transaction_id];
|
|
3630
|
+
const isStalled = !hasProgress && Date.now() / 1e3 - (record.updated_at ?? 0) > 60;
|
|
3631
|
+
const isActivelyVerifying = record.transaction_id === activeVerificationId;
|
|
3632
|
+
const showRetry = rowError?.id === record.transaction_id || isStalled && !isActivelyVerifying;
|
|
3633
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3634
|
+
"div",
|
|
3635
|
+
{
|
|
3636
|
+
className: "border-border flex flex-col gap-1 rounded-md border p-2",
|
|
3637
|
+
children: [
|
|
3638
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
|
|
3639
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-muted-foreground flex items-center gap-1 text-xs", children: [
|
|
3640
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-3 animate-spin", "aria-hidden": true }),
|
|
3641
|
+
progress ?? "Verifying\u2026"
|
|
3642
|
+
] }),
|
|
3643
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-muted-foreground text-xs", children: [
|
|
3644
|
+
record.quote_currency_amount ?? "?",
|
|
3645
|
+
" ",
|
|
3646
|
+
displaySymbol
|
|
3647
|
+
] })
|
|
3648
|
+
] }),
|
|
3649
|
+
rowError?.id === record.transaction_id && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-destructive text-xs", children: rowError.message }),
|
|
3650
|
+
showRetry && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3651
|
+
Button,
|
|
3652
|
+
{
|
|
3653
|
+
type: "button",
|
|
3654
|
+
variant: "outline",
|
|
3655
|
+
size: "sm",
|
|
3656
|
+
onClick: async () => {
|
|
3657
|
+
setRowError(null);
|
|
3658
|
+
try {
|
|
3659
|
+
await finishPendingVerification(record);
|
|
3660
|
+
} catch (err) {
|
|
3661
|
+
setRowError({
|
|
3662
|
+
id: record.transaction_id,
|
|
3663
|
+
message: err instanceof Error ? err.message : "Verification failed"
|
|
3664
|
+
});
|
|
3665
|
+
}
|
|
3666
|
+
},
|
|
3667
|
+
children: "Retry"
|
|
3668
|
+
}
|
|
3669
|
+
)
|
|
3670
|
+
]
|
|
3671
|
+
},
|
|
3672
|
+
record.transaction_id
|
|
3673
|
+
);
|
|
3674
|
+
})
|
|
3675
|
+
] }),
|
|
3676
|
+
status === "credited" && !lockPending && !lockError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-2 py-8 text-center", children: [
|
|
3677
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CircleCheckIcon, { className: "text-primary size-8", "aria-hidden": true }),
|
|
3678
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-foreground text-sm font-medium", children: "Purchase credited" }),
|
|
3679
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-muted-foreground text-sm", children: [
|
|
3680
|
+
"Your ",
|
|
3681
|
+
displaySymbol,
|
|
3682
|
+
" deposit is now available in your balance."
|
|
3683
|
+
] })
|
|
3684
|
+
] }),
|
|
3685
|
+
lockPending && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-muted-foreground flex items-center gap-2 text-sm", children: [
|
|
3686
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-4 animate-spin", "aria-hidden": true }),
|
|
3687
|
+
"Purchase credited \u2014 locking your funds\u2026"
|
|
3688
|
+
] }),
|
|
3689
|
+
autoStart ? !visible && isPrePurchase && /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { className: "h-[656px] w-full rounded-md" }) : isInitializing ? /* @__PURE__ */ jsxRuntime.jsx(Skeleton, { className: "h-9 w-full rounded-md" }) : /* @__PURE__ */ jsxRuntime.jsxs(Button, { type: "button", onClick: handleOpen, disabled: !canBuy, children: [
|
|
3690
|
+
(isBusy || visible) && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "animate-spin", "aria-hidden": true }),
|
|
3691
|
+
"Buy"
|
|
3692
|
+
] }),
|
|
3693
|
+
widgetElement,
|
|
3694
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-destructive text-sm", role: "alert", children: error.message }),
|
|
3695
|
+
lockError && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-destructive text-sm", role: "alert", children: [
|
|
3696
|
+
"Purchase credited to your account, but locking the funds to a service failed: ",
|
|
3697
|
+
lockError
|
|
3698
|
+
] }),
|
|
3699
|
+
isBelowMin && minFiatGate !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-destructive text-sm", role: "alert", children: [
|
|
3700
|
+
"Minimum purchase is ~$",
|
|
3701
|
+
minFiatGate.toFixed(2),
|
|
3702
|
+
"."
|
|
3703
|
+
] }),
|
|
3704
|
+
isVerifying && pending.length === 0 && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-muted-foreground flex items-center gap-2 text-sm", children: [
|
|
3705
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-4 animate-spin", "aria-hidden": true }),
|
|
3706
|
+
"Verifying your purchase\u2026"
|
|
3707
|
+
] })
|
|
3708
|
+
] });
|
|
3709
|
+
}
|
|
3710
|
+
function parseFinalityProgress(message) {
|
|
3711
|
+
if (!message) return null;
|
|
3712
|
+
const match = message.match(/(\d+\/\d+)\s+confirmations/i);
|
|
3713
|
+
return match ? `${match[1]} confirmations` : null;
|
|
3714
|
+
}
|
|
3715
|
+
|
|
3716
|
+
exports.AccountingApiError = AccountingApiError;
|
|
3717
|
+
exports.Button = Button;
|
|
3718
|
+
exports.DEFAULT_LOCK_DURATION_SECONDS = DEFAULT_LOCK_DURATION_SECONDS;
|
|
3719
|
+
exports.DEFAULT_ONRAMP_LOCK_BUFFER = DEFAULT_ONRAMP_LOCK_BUFFER;
|
|
3720
|
+
exports.FiatOnRampForm = FiatOnRampForm;
|
|
3721
|
+
exports.HOSTED_AUTH_CLOCK_SKEW_MS = HOSTED_AUTH_CLOCK_SKEW_MS;
|
|
3722
|
+
exports.HostedAuthError = HostedAuthError;
|
|
3723
|
+
exports.HostedAuthRequiredError = HostedAuthRequiredError;
|
|
3724
|
+
exports.HostedAuthStateMismatchError = HostedAuthStateMismatchError;
|
|
3725
|
+
exports.HttpClient = HttpClient;
|
|
3726
|
+
exports.LOCK_TYPES = LOCK_TYPES;
|
|
3727
|
+
exports.MODIFY_LOCK_TYPES = MODIFY_LOCK_TYPES;
|
|
3728
|
+
exports.NETWORK_CONFIG = NETWORK_CONFIG;
|
|
3729
|
+
exports.NetworkError = NetworkError;
|
|
3730
|
+
exports.PostDepositLockError = PostDepositLockError;
|
|
3731
|
+
exports.PrivanaClient = PrivanaClient;
|
|
3732
|
+
exports.PrivanaProvider = PrivanaProvider;
|
|
3733
|
+
exports.SUPPORTED_CHAINS = SUPPORTED_CHAINS;
|
|
3734
|
+
exports.SiweAuthProvider = SiweAuthProvider;
|
|
3735
|
+
exports.Skeleton = Skeleton;
|
|
3736
|
+
exports.TRANSFER_LOCKED_TYPES = TRANSFER_LOCKED_TYPES;
|
|
3737
|
+
exports.TRANSFER_TYPES = TRANSFER_TYPES;
|
|
3738
|
+
exports.ValidationError = ValidationError;
|
|
3739
|
+
exports.WITHDRAW_FROM_LOCK_TYPES = WITHDRAW_FROM_LOCK_TYPES;
|
|
3740
|
+
exports.WITHDRAW_TYPES = WITHDRAW_TYPES;
|
|
3741
|
+
exports.applyLockBuffer = applyLockBuffer;
|
|
3742
|
+
exports.applyRefreshResponse = applyRefreshResponse;
|
|
3743
|
+
exports.buildHostedAuthSession = buildHostedAuthSession;
|
|
3744
|
+
exports.buildSiweStatement = buildSiweStatement;
|
|
3745
|
+
exports.buttonVariants = buttonVariants;
|
|
3746
|
+
exports.canUseBrowserStorage = canUseBrowserStorage;
|
|
3747
|
+
exports.clampLockAmount = clampLockAmount;
|
|
3748
|
+
exports.clearHostedAuthPendingTransaction = clearHostedAuthPendingTransaction;
|
|
3749
|
+
exports.clearPendingLock = clearPendingLock;
|
|
3750
|
+
exports.cn = cn;
|
|
3751
|
+
exports.createDomain = createDomain;
|
|
3752
|
+
exports.createHostedAuthPendingStorageKey = createHostedAuthPendingStorageKey;
|
|
3753
|
+
exports.createHostedAuthState = createHostedAuthState;
|
|
3754
|
+
exports.createHostedAuthStorageKey = createHostedAuthStorageKey;
|
|
3755
|
+
exports.createLockExpiry = createLockExpiry;
|
|
3756
|
+
exports.createPkceChallenge = createPkceChallenge;
|
|
3757
|
+
exports.createPkceVerifier = createPkceVerifier;
|
|
3758
|
+
exports.createSignedLockRequest = createSignedLockRequest;
|
|
3759
|
+
exports.formatCountdown = formatCountdown;
|
|
3760
|
+
exports.formatTimeRemaining = formatTimeRemaining;
|
|
3761
|
+
exports.formatTokenAmount = formatTokenAmount;
|
|
3762
|
+
exports.getAccountingContract = getAccountingContract;
|
|
3763
|
+
exports.getApiUrl = getApiUrl;
|
|
3764
|
+
exports.getBrowserStorageItem = getBrowserStorageItem;
|
|
3765
|
+
exports.getChainById = getChainById;
|
|
3766
|
+
exports.getChainId = getChainId;
|
|
3767
|
+
exports.getExplorerAddressUrl = getExplorerAddressUrl;
|
|
3768
|
+
exports.getExplorerLabel = getExplorerLabel;
|
|
3769
|
+
exports.getTransactionReceipt = getTransactionReceipt;
|
|
3770
|
+
exports.getWalletClient = getWalletClient3;
|
|
3771
|
+
exports.isHostedAuthRefreshActive = isHostedAuthRefreshActive;
|
|
3772
|
+
exports.isHostedAuthSessionActive = isHostedAuthSessionActive;
|
|
3773
|
+
exports.isSignedLockUsable = isSignedLockUsable;
|
|
3774
|
+
exports.loadPendingLock = loadPendingLock;
|
|
3775
|
+
exports.normalizeAddress = normalizeAddress;
|
|
3776
|
+
exports.normalizeHex = normalizeHex;
|
|
3777
|
+
exports.parseHostedAuthCallback = parseHostedAuthCallback;
|
|
3778
|
+
exports.parseTokenAmount = parseTokenAmount;
|
|
3779
|
+
exports.persistHostedAuthPendingTransaction = persistHostedAuthPendingTransaction;
|
|
3780
|
+
exports.readHostedAuthPendingTransaction = readHostedAuthPendingTransaction;
|
|
3781
|
+
exports.readStoredHostedAuthSession = readStoredHostedAuthSession;
|
|
3782
|
+
exports.removeBrowserStorageItem = removeBrowserStorageItem;
|
|
3783
|
+
exports.savePendingLock = savePendingLock;
|
|
3784
|
+
exports.setBrowserStorageItem = setBrowserStorageItem;
|
|
3785
|
+
exports.shortenAddress = shortenAddress;
|
|
3786
|
+
exports.signLockMessage = signLockMessage;
|
|
3787
|
+
exports.signModifyLockMessage = signModifyLockMessage;
|
|
3788
|
+
exports.signTransferLockedMessage = signTransferLockedMessage;
|
|
3789
|
+
exports.signTransferMessage = signTransferMessage;
|
|
3790
|
+
exports.signWithdrawFromLockMessage = signWithdrawFromLockMessage;
|
|
3791
|
+
exports.signWithdrawMessage = signWithdrawMessage;
|
|
3792
|
+
exports.stripHostedAuthCallbackParams = stripHostedAuthCallbackParams;
|
|
3793
|
+
exports.submitPendingLock = submitPendingLock;
|
|
3794
|
+
exports.syncHostedAuthSessionToClient = syncHostedAuthSessionToClient;
|
|
3795
|
+
exports.useDepositVerification = useDepositVerification;
|
|
3796
|
+
exports.useEnsureCorrectChain = useEnsureCorrectChain;
|
|
3797
|
+
exports.useFiatOnRamp = useFiatOnRamp;
|
|
3798
|
+
exports.usePrivanaContext = usePrivanaContext;
|
|
3799
|
+
exports.usePrivateReadRequest = usePrivateReadRequest;
|
|
3800
|
+
exports.useSafeAccount = useSafeAccount;
|
|
3801
|
+
exports.useSafePrivanaContext = useSafePrivanaContext;
|
|
3802
|
+
exports.useSiweAuth = useSiweAuth;
|
|
3803
|
+
exports.waitForTransactionReceipt = waitForTransactionReceipt;
|
|
3804
|
+
//# sourceMappingURL=chunk-4IW4V7YJ.cjs.map
|
|
3805
|
+
//# sourceMappingURL=chunk-4IW4V7YJ.cjs.map
|