@sodax/dapp-kit 1.3.1-beta-rc1 → 1.3.1-beta-rc3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -143,6 +143,21 @@ function useSpokeProvider(spokeChainId, walletProvider) {
143
143
  if (!spokeChainId) return void 0;
144
144
  if (!xChainType) return void 0;
145
145
  if (!rpcConfig) return void 0;
146
+ if (xChainType === "BITCOIN") {
147
+ const bitcoinConfig = sdk.spokeChainConfig[spokeChainId];
148
+ const btcRpcOverride = rpcConfig[spokeChainId];
149
+ return new sdk.BitcoinSpokeProvider(
150
+ walletProvider,
151
+ bitcoinConfig,
152
+ {
153
+ url: btcRpcOverride?.radfiApiUrl || bitcoinConfig.radfiApiUrl,
154
+ apiKey: bitcoinConfig.radfiApiKey,
155
+ umsUrl: btcRpcOverride?.radfiUmsUrl || bitcoinConfig.radfiUmsUrl
156
+ },
157
+ "TRADING",
158
+ btcRpcOverride?.rpcUrl || bitcoinConfig.rpcUrl
159
+ );
160
+ }
146
161
  if (xChainType === "EVM") {
147
162
  if (spokeChainId === sdk.SONIC_MAINNET_CHAIN_ID) {
148
163
  return new sdk.SonicSpokeProvider(
@@ -205,6 +220,258 @@ function useSpokeProvider(spokeChainId, walletProvider) {
205
220
  }, [spokeChainId, xChainType, walletProvider, rpcConfig]);
206
221
  return spokeProvider;
207
222
  }
223
+
224
+ // src/hooks/bitcoin/radfiConstants.ts
225
+ var ACCESS_TOKEN_TTL = 10 * 60 * 1e3;
226
+ var REFRESH_TOKEN_TTL = 7 * 24 * 60 * 60 * 1e3;
227
+
228
+ // src/hooks/bitcoin/useRadfiAuth.ts
229
+ var SESSION_KEY = (address) => `radfi_session_${address}`;
230
+ function saveRadfiSession(address, session) {
231
+ try {
232
+ localStorage.setItem(SESSION_KEY(address), JSON.stringify(session));
233
+ } catch {
234
+ }
235
+ }
236
+ function loadRadfiSession(address) {
237
+ try {
238
+ const raw = localStorage.getItem(SESSION_KEY(address));
239
+ return raw ? JSON.parse(raw) : null;
240
+ } catch {
241
+ return null;
242
+ }
243
+ }
244
+ function clearRadfiSession(address) {
245
+ try {
246
+ localStorage.removeItem(SESSION_KEY(address));
247
+ } catch {
248
+ }
249
+ }
250
+ function isAccessTokenExpired(address) {
251
+ const session = loadRadfiSession(address);
252
+ if (!session) return true;
253
+ return Date.now() >= session.accessTokenExpiry;
254
+ }
255
+ function isRefreshTokenExpired(address) {
256
+ const session = loadRadfiSession(address);
257
+ if (!session) return true;
258
+ return Date.now() >= session.refreshTokenExpiry;
259
+ }
260
+ function useRadfiAuth(spokeProvider) {
261
+ return reactQuery.useMutation({
262
+ mutationFn: async () => {
263
+ if (!spokeProvider) {
264
+ throw new Error("Bitcoin spoke provider not found");
265
+ }
266
+ const walletAddress = await spokeProvider.walletProvider.getWalletAddress();
267
+ const existingSession = loadRadfiSession(walletAddress);
268
+ const cachedPublicKey = existingSession?.publicKey;
269
+ try {
270
+ const { accessToken, refreshToken, tradingAddress, publicKey } = await spokeProvider.authenticateWithWallet(cachedPublicKey);
271
+ const session = {
272
+ accessToken,
273
+ refreshToken,
274
+ tradingAddress,
275
+ publicKey,
276
+ accessTokenExpiry: Date.now() + ACCESS_TOKEN_TTL,
277
+ refreshTokenExpiry: Date.now() + REFRESH_TOKEN_TTL
278
+ };
279
+ saveRadfiSession(walletAddress, session);
280
+ return { accessToken, refreshToken, tradingAddress };
281
+ } catch (err) {
282
+ const isAlreadyRegistered = err instanceof Error && (err.message.includes("duplicatedPubKey") || err.message.includes("4008"));
283
+ if (isAlreadyRegistered) {
284
+ if (existingSession && !isRefreshTokenExpired(walletAddress)) {
285
+ const refreshed = await spokeProvider.radfi.refreshAccessToken(existingSession.refreshToken);
286
+ const session = {
287
+ ...existingSession,
288
+ accessToken: refreshed.accessToken,
289
+ refreshToken: refreshed.refreshToken,
290
+ accessTokenExpiry: Date.now() + ACCESS_TOKEN_TTL,
291
+ refreshTokenExpiry: Date.now() + REFRESH_TOKEN_TTL
292
+ };
293
+ spokeProvider.setRadfiAccessToken(refreshed.accessToken);
294
+ saveRadfiSession(walletAddress, session);
295
+ return { accessToken: refreshed.accessToken, refreshToken: refreshed.refreshToken, tradingAddress: existingSession.tradingAddress };
296
+ }
297
+ throw new Error(
298
+ "This wallet is already registered with Radfi from another session. Please clear your browser storage for this site and try again, or wait for the previous session to expire."
299
+ );
300
+ }
301
+ throw err;
302
+ }
303
+ }
304
+ });
305
+ }
306
+ var POLL_INTERVAL = 3e4;
307
+ function useRadfiSession(spokeProvider) {
308
+ const [walletAddress, setWalletAddress] = React.useState();
309
+ const [isAuthed, setIsAuthed] = React.useState(false);
310
+ const [tradingAddress, setTradingAddress] = React.useState();
311
+ const isRefreshingRef = React.useRef(false);
312
+ const silentRefresh = React.useCallback(async (address) => {
313
+ if (!spokeProvider || isRefreshingRef.current) return;
314
+ isRefreshingRef.current = true;
315
+ try {
316
+ const session = loadRadfiSession(address);
317
+ if (!session?.refreshToken) {
318
+ setIsAuthed(false);
319
+ return;
320
+ }
321
+ const { accessToken, refreshToken } = await spokeProvider.radfi.refreshAccessToken(session.refreshToken);
322
+ const updated = {
323
+ ...session,
324
+ accessToken,
325
+ refreshToken,
326
+ accessTokenExpiry: Date.now() + ACCESS_TOKEN_TTL
327
+ // Keep the original refreshTokenExpiry — don't roll it forward on every silent refresh
328
+ };
329
+ saveRadfiSession(address, updated);
330
+ spokeProvider.setRadfiAccessToken(accessToken);
331
+ setIsAuthed(true);
332
+ setTradingAddress(updated.tradingAddress || void 0);
333
+ } catch {
334
+ clearRadfiSession(address);
335
+ spokeProvider.setRadfiAccessToken("");
336
+ setIsAuthed(false);
337
+ setTradingAddress(void 0);
338
+ } finally {
339
+ isRefreshingRef.current = false;
340
+ }
341
+ }, [spokeProvider]);
342
+ React.useEffect(() => {
343
+ if (!spokeProvider) return;
344
+ const fetchAndRestore = () => {
345
+ spokeProvider.walletProvider.getWalletAddress().then((addr) => {
346
+ setWalletAddress(addr);
347
+ const session = loadRadfiSession(addr);
348
+ if (!session || isRefreshTokenExpired(addr)) return;
349
+ if (!isAccessTokenExpired(addr)) {
350
+ spokeProvider.setRadfiAccessToken(session.accessToken);
351
+ setIsAuthed(true);
352
+ setTradingAddress(session.tradingAddress || void 0);
353
+ } else {
354
+ silentRefresh(addr);
355
+ }
356
+ }).catch(() => {
357
+ });
358
+ };
359
+ fetchAndRestore();
360
+ const id = setInterval(fetchAndRestore, 3e3);
361
+ return () => clearInterval(id);
362
+ }, [spokeProvider, silentRefresh]);
363
+ React.useEffect(() => {
364
+ if (!walletAddress || !spokeProvider) return;
365
+ const id = setInterval(() => {
366
+ if (isRefreshTokenExpired(walletAddress)) {
367
+ clearRadfiSession(walletAddress);
368
+ spokeProvider.setRadfiAccessToken("");
369
+ setIsAuthed(false);
370
+ setTradingAddress(void 0);
371
+ return;
372
+ }
373
+ if (isAccessTokenExpired(walletAddress)) {
374
+ silentRefresh(walletAddress);
375
+ }
376
+ }, POLL_INTERVAL);
377
+ return () => clearInterval(id);
378
+ }, [walletAddress, spokeProvider, silentRefresh]);
379
+ const { mutateAsync: loginMutate, isPending: isLoginPending } = useRadfiAuth(spokeProvider);
380
+ const login = React.useCallback(async () => {
381
+ const result = await loginMutate();
382
+ setIsAuthed(true);
383
+ setTradingAddress(result.tradingAddress || void 0);
384
+ }, [loginMutate]);
385
+ return { walletAddress, isAuthed, tradingAddress, login, isLoginPending };
386
+ }
387
+ function useFundTradingWallet(spokeProvider) {
388
+ const queryClient = reactQuery.useQueryClient();
389
+ return reactQuery.useMutation({
390
+ mutationFn: async (amount) => {
391
+ if (!spokeProvider) {
392
+ throw new Error("Bitcoin spoke provider not found");
393
+ }
394
+ return sdk.BitcoinSpokeService.fundTradingWallet(amount, spokeProvider);
395
+ },
396
+ onSuccess: () => {
397
+ queryClient.invalidateQueries({ queryKey: ["btc-balance"] });
398
+ queryClient.invalidateQueries({ queryKey: ["xBalances"] });
399
+ }
400
+ });
401
+ }
402
+ function useBitcoinBalance(address, rpcUrl = "https://mempool.space/api") {
403
+ return reactQuery.useQuery({
404
+ queryKey: ["btc-balance", address],
405
+ queryFn: async () => {
406
+ if (!address) return 0n;
407
+ const response = await fetch(`${rpcUrl}/address/${address}/utxo`);
408
+ if (!response.ok) return 0n;
409
+ const utxos = await response.json();
410
+ return BigInt(utxos.reduce((sum, utxo) => sum + utxo.value, 0));
411
+ },
412
+ enabled: !!address
413
+ });
414
+ }
415
+ function useTradingWalletBalance(spokeProvider, tradingAddress) {
416
+ return reactQuery.useQuery({
417
+ queryKey: ["trading-wallet-balance", tradingAddress],
418
+ queryFn: () => {
419
+ if (!spokeProvider || !tradingAddress) {
420
+ throw new Error("spokeProvider and tradingAddress are required");
421
+ }
422
+ return spokeProvider.radfi.getBalance(tradingAddress);
423
+ },
424
+ enabled: !!spokeProvider && !!tradingAddress
425
+ });
426
+ }
427
+ function useExpiredUtxos(spokeProvider, tradingAddress) {
428
+ return reactQuery.useQuery({
429
+ queryKey: ["expired-utxos", tradingAddress],
430
+ queryFn: async () => {
431
+ if (!spokeProvider || !tradingAddress) {
432
+ throw new Error("spokeProvider and tradingAddress are required");
433
+ }
434
+ const result = await spokeProvider.radfi.getExpiredUtxos(tradingAddress);
435
+ return result.data;
436
+ },
437
+ enabled: !!spokeProvider && !!tradingAddress,
438
+ refetchInterval: 6e4
439
+ // refetch every minute
440
+ });
441
+ }
442
+ function useRenewUtxos(spokeProvider) {
443
+ const queryClient = reactQuery.useQueryClient();
444
+ return reactQuery.useMutation({
445
+ mutationFn: async ({ txIdVouts }) => {
446
+ if (!spokeProvider) {
447
+ throw new Error("Bitcoin spoke provider not found");
448
+ }
449
+ const userAddress = await spokeProvider.walletProvider.getWalletAddress();
450
+ const session = loadRadfiSession(userAddress);
451
+ const accessToken = session?.accessToken || spokeProvider.radfiAccessToken;
452
+ if (!accessToken) {
453
+ throw new Error("Radfi authentication required. Please login first.");
454
+ }
455
+ const buildResult = await spokeProvider.radfi.buildRenewUtxoTransaction(
456
+ { userAddress, txIdVouts },
457
+ accessToken
458
+ );
459
+ const signedTx = await spokeProvider.walletProvider.signTransaction(
460
+ buildResult.base64Psbt,
461
+ false
462
+ );
463
+ const signedBase64Tx = sdk.normalizePsbtToBase64(signedTx);
464
+ return spokeProvider.radfi.signAndBroadcastRenewUtxo(
465
+ { userAddress, signedBase64Tx },
466
+ accessToken
467
+ );
468
+ },
469
+ onSuccess: () => {
470
+ queryClient.invalidateQueries({ queryKey: ["expired-utxos"] });
471
+ queryClient.invalidateQueries({ queryKey: ["trading-wallet-balance"] });
472
+ }
473
+ });
474
+ }
208
475
  function useBorrow() {
209
476
  const { sodax } = useSodaxContext();
210
477
  return reactQuery.useMutation({
@@ -724,6 +991,54 @@ var useBackendUserIntents = ({
724
991
  }
725
992
  });
726
993
  };
994
+ var useBackendSubmitSwapTx = (params) => {
995
+ const { sodax } = useSodaxContext();
996
+ const defaultMutationOptions = {
997
+ retry: 3
998
+ };
999
+ const mutationOptions = {
1000
+ ...defaultMutationOptions,
1001
+ ...params?.mutationOptions
1002
+ };
1003
+ return reactQuery.useMutation({
1004
+ ...mutationOptions,
1005
+ mutationFn: async (request) => {
1006
+ return sodax.backendApi.submitSwapTx(request, params?.apiConfig);
1007
+ }
1008
+ });
1009
+ };
1010
+ var useBackendSubmitSwapTxStatus = (params) => {
1011
+ const { sodax } = useSodaxContext();
1012
+ const defaultQueryOptions = {
1013
+ queryKey: ["api", "swaps", "submit-tx", "status", params?.params?.txHash, params?.params?.srcChainId],
1014
+ enabled: !!params?.params?.txHash && params.params.txHash.length > 0,
1015
+ retry: 3,
1016
+ refetchInterval: (query) => {
1017
+ const status = query.state.data?.data?.status;
1018
+ if (status === "executed" || status === "failed") return false;
1019
+ return 1e3;
1020
+ }
1021
+ };
1022
+ const queryOptions = {
1023
+ ...defaultQueryOptions,
1024
+ ...params?.queryOptions
1025
+ };
1026
+ return reactQuery.useQuery({
1027
+ ...queryOptions,
1028
+ queryFn: async () => {
1029
+ if (!params?.params?.txHash) {
1030
+ return void 0;
1031
+ }
1032
+ return sodax.backendApi.getSubmitSwapTxStatus(
1033
+ {
1034
+ txHash: params.params.txHash,
1035
+ srcChainId: params.params.srcChainId
1036
+ },
1037
+ params.apiConfig
1038
+ );
1039
+ }
1040
+ });
1041
+ };
727
1042
  var useBackendOrderbook = (params) => {
728
1043
  const { sodax } = useSodaxContext();
729
1044
  const defaultQueryOptions = {
@@ -1521,6 +1836,11 @@ var SodaxProvider = ({ children, testnet = false, config, rpcConfig }) => {
1521
1836
  exports.MIGRATION_MODE_BNUSD = MIGRATION_MODE_BNUSD;
1522
1837
  exports.MIGRATION_MODE_ICX_SODA = MIGRATION_MODE_ICX_SODA;
1523
1838
  exports.SodaxProvider = SodaxProvider;
1839
+ exports.clearRadfiSession = clearRadfiSession;
1840
+ exports.isAccessTokenExpired = isAccessTokenExpired;
1841
+ exports.isRefreshTokenExpired = isRefreshTokenExpired;
1842
+ exports.loadRadfiSession = loadRadfiSession;
1843
+ exports.saveRadfiSession = saveRadfiSession;
1524
1844
  exports.useAToken = useAToken;
1525
1845
  exports.useATokensBalances = useATokensBalances;
1526
1846
  exports.useBackendAllMoneyMarketAssets = useBackendAllMoneyMarketAssets;
@@ -1532,7 +1852,10 @@ exports.useBackendMoneyMarketAssetBorrowers = useBackendMoneyMarketAssetBorrower
1532
1852
  exports.useBackendMoneyMarketAssetSuppliers = useBackendMoneyMarketAssetSuppliers;
1533
1853
  exports.useBackendMoneyMarketPosition = useBackendMoneyMarketPosition;
1534
1854
  exports.useBackendOrderbook = useBackendOrderbook;
1855
+ exports.useBackendSubmitSwapTx = useBackendSubmitSwapTx;
1856
+ exports.useBackendSubmitSwapTxStatus = useBackendSubmitSwapTxStatus;
1535
1857
  exports.useBackendUserIntents = useBackendUserIntents;
1858
+ exports.useBitcoinBalance = useBitcoinBalance;
1536
1859
  exports.useBorrow = useBorrow;
1537
1860
  exports.useBridge = useBridge;
1538
1861
  exports.useBridgeAllowance = useBridgeAllowance;
@@ -1545,6 +1868,8 @@ exports.useConvertedAssets = useConvertedAssets;
1545
1868
  exports.useCreateLimitOrder = useCreateLimitOrder;
1546
1869
  exports.useDeriveUserWalletAddress = useDeriveUserWalletAddress;
1547
1870
  exports.useEstimateGas = useEstimateGas;
1871
+ exports.useExpiredUtxos = useExpiredUtxos;
1872
+ exports.useFundTradingWallet = useFundTradingWallet;
1548
1873
  exports.useGetBridgeableAmount = useGetBridgeableAmount;
1549
1874
  exports.useGetBridgeableTokens = useGetBridgeableTokens;
1550
1875
  exports.useGetUserHubWalletAddress = useGetUserHubWalletAddress;
@@ -1559,6 +1884,9 @@ exports.useMigrate = useMigrate;
1559
1884
  exports.useMigrationAllowance = useMigrationAllowance;
1560
1885
  exports.useMigrationApprove = useMigrationApprove;
1561
1886
  exports.useQuote = useQuote;
1887
+ exports.useRadfiAuth = useRadfiAuth;
1888
+ exports.useRadfiSession = useRadfiSession;
1889
+ exports.useRenewUtxos = useRenewUtxos;
1562
1890
  exports.useRepay = useRepay;
1563
1891
  exports.useRequestTrustline = useRequestTrustline;
1564
1892
  exports.useReservesData = useReservesData;
@@ -1577,6 +1905,7 @@ exports.useSupply = useSupply;
1577
1905
  exports.useSwap = useSwap;
1578
1906
  exports.useSwapAllowance = useSwapAllowance;
1579
1907
  exports.useSwapApprove = useSwapApprove;
1908
+ exports.useTradingWalletBalance = useTradingWalletBalance;
1580
1909
  exports.useUnstake = useUnstake;
1581
1910
  exports.useUnstakeAllowance = useUnstakeAllowance;
1582
1911
  exports.useUnstakeApprove = useUnstakeApprove;