@sodax/dapp-kit 1.3.1-beta-rc1 → 1.3.1-beta-rc2
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.d.mts +105 -2
- package/dist/index.d.ts +105 -2
- package/dist/index.js +279 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +269 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/hooks/bitcoin/index.ts +7 -0
- package/src/hooks/bitcoin/radfiConstants.ts +2 -0
- package/src/hooks/bitcoin/useBitcoinBalance.ts +27 -0
- package/src/hooks/bitcoin/useExpiredUtxos.ts +25 -0
- package/src/hooks/bitcoin/useFundTradingWallet.ts +39 -0
- package/src/hooks/bitcoin/useRadfiAuth.ts +123 -0
- package/src/hooks/bitcoin/useRadfiSession.ts +133 -0
- package/src/hooks/bitcoin/useRenewUtxos.ts +72 -0
- package/src/hooks/bitcoin/useTradingWallet.ts +15 -0
- package/src/hooks/bitcoin/useTradingWalletBalance.ts +22 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/provider/useSpokeProvider.ts +20 -0
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { createContext, useContext, useState, useCallback, useMemo, useRef, useEffect } from 'react';
|
|
2
|
-
import { SpokeService, deriveUserWalletAddress, STELLAR_MAINNET_CHAIN_ID, StellarSpokeProvider, StellarSpokeService, HubService, spokeChainConfig, SONIC_MAINNET_CHAIN_ID, SonicSpokeProvider, EvmSpokeProvider, SuiSpokeProvider, IconSpokeProvider, InjectiveSpokeProvider, SolanaSpokeProvider, NearSpokeProvider, isLegacybnUSDToken, Sodax } from '@sodax/sdk';
|
|
2
|
+
import { SpokeService, deriveUserWalletAddress, STELLAR_MAINNET_CHAIN_ID, StellarSpokeProvider, StellarSpokeService, HubService, spokeChainConfig, BitcoinSpokeProvider, SONIC_MAINNET_CHAIN_ID, SonicSpokeProvider, EvmSpokeProvider, SuiSpokeProvider, IconSpokeProvider, InjectiveSpokeProvider, SolanaSpokeProvider, NearSpokeProvider, BitcoinSpokeService, normalizePsbtToBase64, isLegacybnUSDToken, Sodax } from '@sodax/sdk';
|
|
3
3
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
4
4
|
import { isAddress, parseUnits } from 'viem';
|
|
5
5
|
import { ICON_MAINNET_CHAIN_ID } from '@sodax/types';
|
|
@@ -137,6 +137,21 @@ function useSpokeProvider(spokeChainId, walletProvider) {
|
|
|
137
137
|
if (!spokeChainId) return void 0;
|
|
138
138
|
if (!xChainType) return void 0;
|
|
139
139
|
if (!rpcConfig) return void 0;
|
|
140
|
+
if (xChainType === "BITCOIN") {
|
|
141
|
+
const bitcoinConfig = spokeChainConfig[spokeChainId];
|
|
142
|
+
const btcRpcOverride = rpcConfig[spokeChainId];
|
|
143
|
+
return new BitcoinSpokeProvider(
|
|
144
|
+
walletProvider,
|
|
145
|
+
bitcoinConfig,
|
|
146
|
+
{
|
|
147
|
+
url: btcRpcOverride?.radfiApiUrl || bitcoinConfig.radfiApiUrl,
|
|
148
|
+
apiKey: bitcoinConfig.radfiApiKey,
|
|
149
|
+
umsUrl: btcRpcOverride?.radfiUmsUrl || bitcoinConfig.radfiUmsUrl
|
|
150
|
+
},
|
|
151
|
+
"TRADING",
|
|
152
|
+
btcRpcOverride?.rpcUrl || bitcoinConfig.rpcUrl
|
|
153
|
+
);
|
|
154
|
+
}
|
|
140
155
|
if (xChainType === "EVM") {
|
|
141
156
|
if (spokeChainId === SONIC_MAINNET_CHAIN_ID) {
|
|
142
157
|
return new SonicSpokeProvider(
|
|
@@ -199,6 +214,258 @@ function useSpokeProvider(spokeChainId, walletProvider) {
|
|
|
199
214
|
}, [spokeChainId, xChainType, walletProvider, rpcConfig]);
|
|
200
215
|
return spokeProvider;
|
|
201
216
|
}
|
|
217
|
+
|
|
218
|
+
// src/hooks/bitcoin/radfiConstants.ts
|
|
219
|
+
var ACCESS_TOKEN_TTL = 10 * 60 * 1e3;
|
|
220
|
+
var REFRESH_TOKEN_TTL = 7 * 24 * 60 * 60 * 1e3;
|
|
221
|
+
|
|
222
|
+
// src/hooks/bitcoin/useRadfiAuth.ts
|
|
223
|
+
var SESSION_KEY = (address) => `radfi_session_${address}`;
|
|
224
|
+
function saveRadfiSession(address, session) {
|
|
225
|
+
try {
|
|
226
|
+
localStorage.setItem(SESSION_KEY(address), JSON.stringify(session));
|
|
227
|
+
} catch {
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function loadRadfiSession(address) {
|
|
231
|
+
try {
|
|
232
|
+
const raw = localStorage.getItem(SESSION_KEY(address));
|
|
233
|
+
return raw ? JSON.parse(raw) : null;
|
|
234
|
+
} catch {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function clearRadfiSession(address) {
|
|
239
|
+
try {
|
|
240
|
+
localStorage.removeItem(SESSION_KEY(address));
|
|
241
|
+
} catch {
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function isAccessTokenExpired(address) {
|
|
245
|
+
const session = loadRadfiSession(address);
|
|
246
|
+
if (!session) return true;
|
|
247
|
+
return Date.now() >= session.accessTokenExpiry;
|
|
248
|
+
}
|
|
249
|
+
function isRefreshTokenExpired(address) {
|
|
250
|
+
const session = loadRadfiSession(address);
|
|
251
|
+
if (!session) return true;
|
|
252
|
+
return Date.now() >= session.refreshTokenExpiry;
|
|
253
|
+
}
|
|
254
|
+
function useRadfiAuth(spokeProvider) {
|
|
255
|
+
return useMutation({
|
|
256
|
+
mutationFn: async () => {
|
|
257
|
+
if (!spokeProvider) {
|
|
258
|
+
throw new Error("Bitcoin spoke provider not found");
|
|
259
|
+
}
|
|
260
|
+
const walletAddress = await spokeProvider.walletProvider.getWalletAddress();
|
|
261
|
+
const existingSession = loadRadfiSession(walletAddress);
|
|
262
|
+
const cachedPublicKey = existingSession?.publicKey;
|
|
263
|
+
try {
|
|
264
|
+
const { accessToken, refreshToken, tradingAddress, publicKey } = await spokeProvider.authenticateWithWallet(cachedPublicKey);
|
|
265
|
+
const session = {
|
|
266
|
+
accessToken,
|
|
267
|
+
refreshToken,
|
|
268
|
+
tradingAddress,
|
|
269
|
+
publicKey,
|
|
270
|
+
accessTokenExpiry: Date.now() + ACCESS_TOKEN_TTL,
|
|
271
|
+
refreshTokenExpiry: Date.now() + REFRESH_TOKEN_TTL
|
|
272
|
+
};
|
|
273
|
+
saveRadfiSession(walletAddress, session);
|
|
274
|
+
return { accessToken, refreshToken, tradingAddress };
|
|
275
|
+
} catch (err) {
|
|
276
|
+
const isAlreadyRegistered = err instanceof Error && (err.message.includes("duplicatedPubKey") || err.message.includes("4008"));
|
|
277
|
+
if (isAlreadyRegistered) {
|
|
278
|
+
if (existingSession && !isRefreshTokenExpired(walletAddress)) {
|
|
279
|
+
const refreshed = await spokeProvider.radfi.refreshAccessToken(existingSession.refreshToken);
|
|
280
|
+
const session = {
|
|
281
|
+
...existingSession,
|
|
282
|
+
accessToken: refreshed.accessToken,
|
|
283
|
+
refreshToken: refreshed.refreshToken,
|
|
284
|
+
accessTokenExpiry: Date.now() + ACCESS_TOKEN_TTL,
|
|
285
|
+
refreshTokenExpiry: Date.now() + REFRESH_TOKEN_TTL
|
|
286
|
+
};
|
|
287
|
+
spokeProvider.setRadfiAccessToken(refreshed.accessToken);
|
|
288
|
+
saveRadfiSession(walletAddress, session);
|
|
289
|
+
return { accessToken: refreshed.accessToken, refreshToken: refreshed.refreshToken, tradingAddress: existingSession.tradingAddress };
|
|
290
|
+
}
|
|
291
|
+
throw new Error(
|
|
292
|
+
"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."
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
throw err;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
var POLL_INTERVAL = 3e4;
|
|
301
|
+
function useRadfiSession(spokeProvider) {
|
|
302
|
+
const [walletAddress, setWalletAddress] = useState();
|
|
303
|
+
const [isAuthed, setIsAuthed] = useState(false);
|
|
304
|
+
const [tradingAddress, setTradingAddress] = useState();
|
|
305
|
+
const isRefreshingRef = useRef(false);
|
|
306
|
+
const silentRefresh = useCallback(async (address) => {
|
|
307
|
+
if (!spokeProvider || isRefreshingRef.current) return;
|
|
308
|
+
isRefreshingRef.current = true;
|
|
309
|
+
try {
|
|
310
|
+
const session = loadRadfiSession(address);
|
|
311
|
+
if (!session?.refreshToken) {
|
|
312
|
+
setIsAuthed(false);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const { accessToken, refreshToken } = await spokeProvider.radfi.refreshAccessToken(session.refreshToken);
|
|
316
|
+
const updated = {
|
|
317
|
+
...session,
|
|
318
|
+
accessToken,
|
|
319
|
+
refreshToken,
|
|
320
|
+
accessTokenExpiry: Date.now() + ACCESS_TOKEN_TTL
|
|
321
|
+
// Keep the original refreshTokenExpiry — don't roll it forward on every silent refresh
|
|
322
|
+
};
|
|
323
|
+
saveRadfiSession(address, updated);
|
|
324
|
+
spokeProvider.setRadfiAccessToken(accessToken);
|
|
325
|
+
setIsAuthed(true);
|
|
326
|
+
setTradingAddress(updated.tradingAddress || void 0);
|
|
327
|
+
} catch {
|
|
328
|
+
clearRadfiSession(address);
|
|
329
|
+
spokeProvider.setRadfiAccessToken("");
|
|
330
|
+
setIsAuthed(false);
|
|
331
|
+
setTradingAddress(void 0);
|
|
332
|
+
} finally {
|
|
333
|
+
isRefreshingRef.current = false;
|
|
334
|
+
}
|
|
335
|
+
}, [spokeProvider]);
|
|
336
|
+
useEffect(() => {
|
|
337
|
+
if (!spokeProvider) return;
|
|
338
|
+
const fetchAndRestore = () => {
|
|
339
|
+
spokeProvider.walletProvider.getWalletAddress().then((addr) => {
|
|
340
|
+
setWalletAddress(addr);
|
|
341
|
+
const session = loadRadfiSession(addr);
|
|
342
|
+
if (!session || isRefreshTokenExpired(addr)) return;
|
|
343
|
+
if (!isAccessTokenExpired(addr)) {
|
|
344
|
+
spokeProvider.setRadfiAccessToken(session.accessToken);
|
|
345
|
+
setIsAuthed(true);
|
|
346
|
+
setTradingAddress(session.tradingAddress || void 0);
|
|
347
|
+
} else {
|
|
348
|
+
silentRefresh(addr);
|
|
349
|
+
}
|
|
350
|
+
}).catch(() => {
|
|
351
|
+
});
|
|
352
|
+
};
|
|
353
|
+
fetchAndRestore();
|
|
354
|
+
const id = setInterval(fetchAndRestore, 3e3);
|
|
355
|
+
return () => clearInterval(id);
|
|
356
|
+
}, [spokeProvider, silentRefresh]);
|
|
357
|
+
useEffect(() => {
|
|
358
|
+
if (!walletAddress || !spokeProvider) return;
|
|
359
|
+
const id = setInterval(() => {
|
|
360
|
+
if (isRefreshTokenExpired(walletAddress)) {
|
|
361
|
+
clearRadfiSession(walletAddress);
|
|
362
|
+
spokeProvider.setRadfiAccessToken("");
|
|
363
|
+
setIsAuthed(false);
|
|
364
|
+
setTradingAddress(void 0);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
if (isAccessTokenExpired(walletAddress)) {
|
|
368
|
+
silentRefresh(walletAddress);
|
|
369
|
+
}
|
|
370
|
+
}, POLL_INTERVAL);
|
|
371
|
+
return () => clearInterval(id);
|
|
372
|
+
}, [walletAddress, spokeProvider, silentRefresh]);
|
|
373
|
+
const { mutateAsync: loginMutate, isPending: isLoginPending } = useRadfiAuth(spokeProvider);
|
|
374
|
+
const login = useCallback(async () => {
|
|
375
|
+
const result = await loginMutate();
|
|
376
|
+
setIsAuthed(true);
|
|
377
|
+
setTradingAddress(result.tradingAddress || void 0);
|
|
378
|
+
}, [loginMutate]);
|
|
379
|
+
return { walletAddress, isAuthed, tradingAddress, login, isLoginPending };
|
|
380
|
+
}
|
|
381
|
+
function useFundTradingWallet(spokeProvider) {
|
|
382
|
+
const queryClient = useQueryClient();
|
|
383
|
+
return useMutation({
|
|
384
|
+
mutationFn: async (amount) => {
|
|
385
|
+
if (!spokeProvider) {
|
|
386
|
+
throw new Error("Bitcoin spoke provider not found");
|
|
387
|
+
}
|
|
388
|
+
return BitcoinSpokeService.fundTradingWallet(amount, spokeProvider);
|
|
389
|
+
},
|
|
390
|
+
onSuccess: () => {
|
|
391
|
+
queryClient.invalidateQueries({ queryKey: ["btc-balance"] });
|
|
392
|
+
queryClient.invalidateQueries({ queryKey: ["xBalances"] });
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
function useBitcoinBalance(address, rpcUrl = "https://mempool.space/api") {
|
|
397
|
+
return useQuery({
|
|
398
|
+
queryKey: ["btc-balance", address],
|
|
399
|
+
queryFn: async () => {
|
|
400
|
+
if (!address) return 0n;
|
|
401
|
+
const response = await fetch(`${rpcUrl}/address/${address}/utxo`);
|
|
402
|
+
if (!response.ok) return 0n;
|
|
403
|
+
const utxos = await response.json();
|
|
404
|
+
return BigInt(utxos.reduce((sum, utxo) => sum + utxo.value, 0));
|
|
405
|
+
},
|
|
406
|
+
enabled: !!address
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
function useTradingWalletBalance(spokeProvider, tradingAddress) {
|
|
410
|
+
return useQuery({
|
|
411
|
+
queryKey: ["trading-wallet-balance", tradingAddress],
|
|
412
|
+
queryFn: () => {
|
|
413
|
+
if (!spokeProvider || !tradingAddress) {
|
|
414
|
+
throw new Error("spokeProvider and tradingAddress are required");
|
|
415
|
+
}
|
|
416
|
+
return spokeProvider.radfi.getBalance(tradingAddress);
|
|
417
|
+
},
|
|
418
|
+
enabled: !!spokeProvider && !!tradingAddress
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
function useExpiredUtxos(spokeProvider, tradingAddress) {
|
|
422
|
+
return useQuery({
|
|
423
|
+
queryKey: ["expired-utxos", tradingAddress],
|
|
424
|
+
queryFn: async () => {
|
|
425
|
+
if (!spokeProvider || !tradingAddress) {
|
|
426
|
+
throw new Error("spokeProvider and tradingAddress are required");
|
|
427
|
+
}
|
|
428
|
+
const result = await spokeProvider.radfi.getExpiredUtxos(tradingAddress);
|
|
429
|
+
return result.data;
|
|
430
|
+
},
|
|
431
|
+
enabled: !!spokeProvider && !!tradingAddress,
|
|
432
|
+
refetchInterval: 6e4
|
|
433
|
+
// refetch every minute
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
function useRenewUtxos(spokeProvider) {
|
|
437
|
+
const queryClient = useQueryClient();
|
|
438
|
+
return useMutation({
|
|
439
|
+
mutationFn: async ({ txIdVouts }) => {
|
|
440
|
+
if (!spokeProvider) {
|
|
441
|
+
throw new Error("Bitcoin spoke provider not found");
|
|
442
|
+
}
|
|
443
|
+
const userAddress = await spokeProvider.walletProvider.getWalletAddress();
|
|
444
|
+
const session = loadRadfiSession(userAddress);
|
|
445
|
+
const accessToken = session?.accessToken || spokeProvider.radfiAccessToken;
|
|
446
|
+
if (!accessToken) {
|
|
447
|
+
throw new Error("Radfi authentication required. Please login first.");
|
|
448
|
+
}
|
|
449
|
+
const buildResult = await spokeProvider.radfi.buildRenewUtxoTransaction(
|
|
450
|
+
{ userAddress, txIdVouts },
|
|
451
|
+
accessToken
|
|
452
|
+
);
|
|
453
|
+
const signedTx = await spokeProvider.walletProvider.signTransaction(
|
|
454
|
+
buildResult.base64Psbt,
|
|
455
|
+
false
|
|
456
|
+
);
|
|
457
|
+
const signedBase64Tx = normalizePsbtToBase64(signedTx);
|
|
458
|
+
return spokeProvider.radfi.signAndBroadcastRenewUtxo(
|
|
459
|
+
{ userAddress, signedBase64Tx },
|
|
460
|
+
accessToken
|
|
461
|
+
);
|
|
462
|
+
},
|
|
463
|
+
onSuccess: () => {
|
|
464
|
+
queryClient.invalidateQueries({ queryKey: ["expired-utxos"] });
|
|
465
|
+
queryClient.invalidateQueries({ queryKey: ["trading-wallet-balance"] });
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
}
|
|
202
469
|
function useBorrow() {
|
|
203
470
|
const { sodax } = useSodaxContext();
|
|
204
471
|
return useMutation({
|
|
@@ -1512,6 +1779,6 @@ var SodaxProvider = ({ children, testnet = false, config, rpcConfig }) => {
|
|
|
1512
1779
|
return /* @__PURE__ */ React.createElement(SodaxContext.Provider, { value: { sodax, testnet, rpcConfig } }, children);
|
|
1513
1780
|
};
|
|
1514
1781
|
|
|
1515
|
-
export { MIGRATION_MODE_BNUSD, MIGRATION_MODE_ICX_SODA, SodaxProvider, useAToken, useATokensBalances, useBackendAllMoneyMarketAssets, useBackendAllMoneyMarketBorrowers, useBackendIntentByHash, useBackendIntentByTxHash, useBackendMoneyMarketAsset, useBackendMoneyMarketAssetBorrowers, useBackendMoneyMarketAssetSuppliers, useBackendMoneyMarketPosition, useBackendOrderbook, useBackendUserIntents, useBorrow, useBridge, useBridgeAllowance, useBridgeApprove, useCancelLimitOrder, useCancelSwap, useCancelUnstake, useClaim, useConvertedAssets, useCreateLimitOrder, useDeriveUserWalletAddress, useEstimateGas, useGetBridgeableAmount, useGetBridgeableTokens, useGetUserHubWalletAddress, useHubProvider, useInstantUnstake, useInstantUnstakeAllowance, useInstantUnstakeApprove, useInstantUnstakeRatio, useMMAllowance, useMMApprove, useMigrate, useMigrationAllowance, useMigrationApprove, useQuote, useRepay, useRequestTrustline, useReservesData, useReservesUsdFormat, useSodaxContext, useSpokeProvider, useStake, useStakeAllowance, useStakeApprove, useStakeRatio, useStakingConfig, useStakingInfo, useStatus, useStellarTrustlineCheck, useSupply, useSwap, useSwapAllowance, useSwapApprove, useUnstake, useUnstakeAllowance, useUnstakeApprove, useUnstakingInfo, useUnstakingInfoWithPenalty, useUserFormattedSummary, useUserReservesData, useWithdraw };
|
|
1782
|
+
export { MIGRATION_MODE_BNUSD, MIGRATION_MODE_ICX_SODA, SodaxProvider, clearRadfiSession, isAccessTokenExpired, isRefreshTokenExpired, loadRadfiSession, saveRadfiSession, useAToken, useATokensBalances, useBackendAllMoneyMarketAssets, useBackendAllMoneyMarketBorrowers, useBackendIntentByHash, useBackendIntentByTxHash, useBackendMoneyMarketAsset, useBackendMoneyMarketAssetBorrowers, useBackendMoneyMarketAssetSuppliers, useBackendMoneyMarketPosition, useBackendOrderbook, useBackendUserIntents, useBitcoinBalance, useBorrow, useBridge, useBridgeAllowance, useBridgeApprove, useCancelLimitOrder, useCancelSwap, useCancelUnstake, useClaim, useConvertedAssets, useCreateLimitOrder, useDeriveUserWalletAddress, useEstimateGas, useExpiredUtxos, useFundTradingWallet, useGetBridgeableAmount, useGetBridgeableTokens, useGetUserHubWalletAddress, useHubProvider, useInstantUnstake, useInstantUnstakeAllowance, useInstantUnstakeApprove, useInstantUnstakeRatio, useMMAllowance, useMMApprove, useMigrate, useMigrationAllowance, useMigrationApprove, useQuote, useRadfiAuth, useRadfiSession, useRenewUtxos, useRepay, useRequestTrustline, useReservesData, useReservesUsdFormat, useSodaxContext, useSpokeProvider, useStake, useStakeAllowance, useStakeApprove, useStakeRatio, useStakingConfig, useStakingInfo, useStatus, useStellarTrustlineCheck, useSupply, useSwap, useSwapAllowance, useSwapApprove, useTradingWalletBalance, useUnstake, useUnstakeAllowance, useUnstakeApprove, useUnstakingInfo, useUnstakingInfoWithPenalty, useUserFormattedSummary, useUserReservesData, useWithdraw };
|
|
1516
1783
|
//# sourceMappingURL=index.mjs.map
|
|
1517
1784
|
//# sourceMappingURL=index.mjs.map
|