@sodax/dapp-kit 2.0.0-rc.11 → 2.0.0-rc.13

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.
Files changed (35) hide show
  1. package/README.md +5 -5
  2. package/dist/index.d.ts +282 -8
  3. package/dist/index.mjs +290 -47
  4. package/package.json +2 -2
  5. package/src/hooks/_mutationContract.test.ts +6 -1
  6. package/src/hooks/bitcoin/resolveBtcReadAddress.test.ts +54 -0
  7. package/src/hooks/bitcoin/resolveBtcReadAddress.ts +21 -0
  8. package/src/hooks/bitcoin/useFundTradingWallet.ts +1 -1
  9. package/src/hooks/bitcoin/useRadfiAuth.ts +2 -2
  10. package/src/hooks/bitcoin/useRadfiWithdraw.ts +2 -2
  11. package/src/hooks/bitcoin/useRenewUtxos.ts +2 -2
  12. package/src/hooks/bitcoin/useTradingWallet.ts +1 -1
  13. package/src/hooks/index.ts +1 -0
  14. package/src/hooks/leverageYield/index.ts +9 -0
  15. package/src/hooks/leverageYield/useLeverageYieldDeposit.ts +40 -0
  16. package/src/hooks/leverageYield/useLeverageYieldEffectiveApr.ts +42 -0
  17. package/src/hooks/leverageYield/useLeverageYieldNotifySolver.ts +34 -0
  18. package/src/hooks/leverageYield/useLeverageYieldPosition.ts +40 -0
  19. package/src/hooks/leverageYield/useLeverageYieldPreviewRedeem.ts +43 -0
  20. package/src/hooks/leverageYield/useLeverageYieldShareBalances.ts +74 -0
  21. package/src/hooks/leverageYield/useLeverageYieldTotalAssets.ts +36 -0
  22. package/src/hooks/leverageYield/useLeverageYieldVaultSwap.ts +50 -0
  23. package/src/hooks/leverageYield/useLeverageYieldWithdraw.ts +40 -0
  24. package/src/hooks/mm/useATokensBalances.ts +7 -1
  25. package/src/hooks/mm/useUserFormattedSummary.ts +5 -1
  26. package/src/hooks/mm/useUserReservesData.ts +5 -1
  27. package/src/hooks/shared/index.ts +3 -0
  28. package/src/hooks/shared/useGetUserHubWalletAddress.ts +5 -1
  29. package/src/hooks/shared/useNearStorageCheck.ts +45 -0
  30. package/src/hooks/shared/useNearStorageGate.ts +60 -0
  31. package/src/hooks/shared/useRegisterNearStorage.ts +46 -0
  32. package/src/providers/SodaxProvider.tsx +2 -3
  33. package/src/utils/index.ts +1 -0
  34. package/src/utils/nearStorageGate.test.ts +46 -0
  35. package/src/utils/nearStorageGate.ts +39 -0
@@ -0,0 +1,40 @@
1
+ import { useSodaxContext } from '../shared/useSodaxContext.js';
2
+ import type { LeverageYieldSwapPayload, LeverageYieldSwapDepositParams } from '@sodax/sdk';
3
+ import type { MutationHookParams } from '../shared/types.js';
4
+ import { useSafeMutation, type SafeUseMutationResult } from '../shared/useSafeMutation.js';
5
+ import { unwrapResult } from '../shared/unwrapResult.js';
6
+
7
+ /** Mutation variables for {@link useLeverageYieldDeposit} — the swap-style deposit inputs. */
8
+ export type UseLeverageYieldDepositVars = LeverageYieldSwapDepositParams;
9
+
10
+ /**
11
+ * Builds the `LeverageYieldSwapPayload` for a leverage-yield deposit (any token → `lsoda*` shares,
12
+ * delivered to the user's hub wallet). Spread the returned payload into
13
+ * {@link useLeverageYieldVaultSwap}.
14
+ *
15
+ * This is a builder, not an executor — it derives the hub wallet and assembles the intent; the
16
+ * actual swap is relayed by `useLeverageYieldVaultSwap`. Throws on SDK failure so React Query's
17
+ * error model engages; returns the unwrapped `LeverageYieldSwapPayload` on success.
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * const { mutateAsyncSafe: buildDeposit } = useLeverageYieldDeposit();
22
+ * const built = await buildDeposit({ vault, srcChainKey, srcAddress, inputToken, inputAmount, minOutputAmount });
23
+ * if (built.ok) vaultSwap({ ...built.value, walletProvider });
24
+ * ```
25
+ */
26
+ export function useLeverageYieldDeposit({
27
+ mutationOptions,
28
+ }: MutationHookParams<LeverageYieldSwapPayload, UseLeverageYieldDepositVars> = {}): SafeUseMutationResult<
29
+ LeverageYieldSwapPayload,
30
+ Error,
31
+ UseLeverageYieldDepositVars
32
+ > {
33
+ const { sodax } = useSodaxContext();
34
+
35
+ return useSafeMutation<LeverageYieldSwapPayload, Error, UseLeverageYieldDepositVars>({
36
+ mutationKey: ['leverageYield', 'deposit'],
37
+ ...mutationOptions,
38
+ mutationFn: async vars => unwrapResult(await sodax.leverageYield.deposit(vars)),
39
+ });
40
+ }
@@ -0,0 +1,42 @@
1
+ import { useQuery, type UseQueryResult } from '@tanstack/react-query';
2
+ import { useSodaxContext } from '../shared/useSodaxContext.js';
3
+ import type { Address, LeverageYieldEffectiveApr } from '@sodax/sdk';
4
+ import type { ReadHookParams } from '../shared/types.js';
5
+
6
+ export type UseLeverageYieldEffectiveAprParams = ReadHookParams<
7
+ LeverageYieldEffectiveApr,
8
+ { vault: Address | undefined }
9
+ >;
10
+
11
+ /**
12
+ * Reads a leverage-yield vault's effective APR — AAVE supply/borrow rates plus the LSD
13
+ * staking yield, with the vault's leverage formula re-applied on the boosted supply side.
14
+ *
15
+ * The underlying SDK call does the on-chain reads (AAVE rates + `targetLTV`) and the
16
+ * off-chain LSD fetch in parallel. Rates drift slowly, so the default refresh is 60s.
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * const { data: apr } = useLeverageYieldEffectiveApr({ params: { vault: vault.vault } });
21
+ * ```
22
+ */
23
+ export function useLeverageYieldEffectiveApr({
24
+ params,
25
+ queryOptions,
26
+ }: UseLeverageYieldEffectiveAprParams = {}): UseQueryResult<LeverageYieldEffectiveApr, Error> {
27
+ const { sodax } = useSodaxContext();
28
+ const vault = params?.vault;
29
+
30
+ return useQuery<LeverageYieldEffectiveApr, Error>({
31
+ queryKey: ['leverageYield', 'effectiveApr', vault],
32
+ queryFn: async () => {
33
+ if (!vault) throw new Error('vault is required');
34
+ const result = await sodax.leverageYield.getEffectiveApr(vault);
35
+ if (!result.ok) throw result.error;
36
+ return result.value;
37
+ },
38
+ enabled: !!vault,
39
+ refetchInterval: 60_000,
40
+ ...queryOptions,
41
+ });
42
+ }
@@ -0,0 +1,34 @@
1
+ import { useSodaxContext } from '../shared/useSodaxContext.js';
2
+ import type { SolverExecutionRequest, SolverExecutionResponse } from '@sodax/sdk';
3
+ import type { MutationHookParams } from '../shared/types.js';
4
+ import { useSafeMutation, type SafeUseMutationResult } from '../shared/useSafeMutation.js';
5
+ import { unwrapResult } from '../shared/unwrapResult.js';
6
+
7
+ /** Mutation variables for {@link useLeverageYieldNotifySolver} — the hub-side intent tx hash. */
8
+ export type UseLeverageYieldNotifySolverVars = SolverExecutionRequest;
9
+
10
+ /**
11
+ * Notifies the solver that a leverage-yield vault intent landed on the hub, triggering the fill.
12
+ * The standalone notify step for the manual create → relay → notify flow: build the intent with
13
+ * `sodax.leverageYield.createVaultIntent`, relay it yourself, then call this with the hub-side
14
+ * intent tx hash. The end-to-end {@link useLeverageYieldVaultSwap} already calls notifySolver
15
+ * internally — only reach for this hook when driving the relay manually.
16
+ *
17
+ * Throws on SDK failure so React Query's error model engages; returns the unwrapped
18
+ * `SolverExecutionResponse` (`{ answer: 'OK', intent_hash }`) on success.
19
+ */
20
+ export function useLeverageYieldNotifySolver({
21
+ mutationOptions,
22
+ }: MutationHookParams<SolverExecutionResponse, UseLeverageYieldNotifySolverVars> = {}): SafeUseMutationResult<
23
+ SolverExecutionResponse,
24
+ Error,
25
+ UseLeverageYieldNotifySolverVars
26
+ > {
27
+ const { sodax } = useSodaxContext();
28
+
29
+ return useSafeMutation<SolverExecutionResponse, Error, UseLeverageYieldNotifySolverVars>({
30
+ mutationKey: ['leverageYield', 'notifySolver'],
31
+ ...mutationOptions,
32
+ mutationFn: async vars => unwrapResult(await sodax.leverageYield.notifySolver(vars)),
33
+ });
34
+ }
@@ -0,0 +1,40 @@
1
+ import { useQuery, type UseQueryResult } from '@tanstack/react-query';
2
+ import { useSodaxContext } from '../shared/useSodaxContext.js';
3
+ import type { Address, LeverageYieldPosition } from '@sodax/sdk';
4
+ import type { ReadHookParams } from '../shared/types.js';
5
+
6
+ export type UseLeverageYieldPositionParams = ReadHookParams<LeverageYieldPosition, { vault: Address | undefined }>;
7
+
8
+ /**
9
+ * Reads a leverage-yield vault's live position snapshot via the vault's `getPositionDetails`:
10
+ * collateral, debt, current LTV (drift vs `targetLTV`), health factor (∞ when no debt), and
11
+ * idle (not-yet-deployed) asset.
12
+ *
13
+ * LTV shifts with each rebalance/rate tick, so the default refresh (30s) is faster than the
14
+ * APR/stats reads.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const { data: position } = useLeverageYieldPosition({ params: { vault: vault.vault } });
19
+ * ```
20
+ */
21
+ export function useLeverageYieldPosition({
22
+ params,
23
+ queryOptions,
24
+ }: UseLeverageYieldPositionParams = {}): UseQueryResult<LeverageYieldPosition, Error> {
25
+ const { sodax } = useSodaxContext();
26
+ const vault = params?.vault;
27
+
28
+ return useQuery<LeverageYieldPosition, Error>({
29
+ queryKey: ['leverageYield', 'position', vault],
30
+ queryFn: async () => {
31
+ if (!vault) throw new Error('vault is required');
32
+ const result = await sodax.leverageYield.getPosition(vault);
33
+ if (!result.ok) throw result.error;
34
+ return result.value;
35
+ },
36
+ enabled: !!vault,
37
+ refetchInterval: 30_000,
38
+ ...queryOptions,
39
+ });
40
+ }
@@ -0,0 +1,43 @@
1
+ import { useQuery, type UseQueryResult } from '@tanstack/react-query';
2
+ import { useSodaxContext } from '../shared/useSodaxContext.js';
3
+ import type { Address } from '@sodax/sdk';
4
+ import type { ReadHookParams } from '../shared/types.js';
5
+
6
+ export type UseLeverageYieldPreviewRedeemParams = ReadHookParams<
7
+ bigint,
8
+ { vault: Address | undefined; shares: bigint | undefined }
9
+ >;
10
+
11
+ /**
12
+ * Reads the assets received for redeeming `shares` of a leverage-yield vault (ERC-4626
13
+ * `previewRedeem`). Passing `10n ** 18n` (one share) yields the per-share price that creeps
14
+ * up as the vault accrues interest. Moves slowly, so the default refresh is 60s.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const { data: sharePrice } = useLeverageYieldPreviewRedeem({
19
+ * params: { vault: vault.vault, shares: 10n ** 18n },
20
+ * });
21
+ * ```
22
+ */
23
+ export function useLeverageYieldPreviewRedeem({
24
+ params,
25
+ queryOptions,
26
+ }: UseLeverageYieldPreviewRedeemParams = {}): UseQueryResult<bigint, Error> {
27
+ const { sodax } = useSodaxContext();
28
+ const vault = params?.vault;
29
+ const shares = params?.shares;
30
+
31
+ return useQuery<bigint, Error>({
32
+ queryKey: ['leverageYield', 'previewRedeem', vault, shares?.toString()],
33
+ queryFn: async () => {
34
+ if (!vault || shares === undefined) throw new Error('vault and shares are required');
35
+ const result = await sodax.leverageYield.previewRedeem(vault, shares);
36
+ if (!result.ok) throw result.error;
37
+ return result.value;
38
+ },
39
+ enabled: !!vault && shares !== undefined,
40
+ refetchInterval: 60_000,
41
+ ...queryOptions,
42
+ });
43
+ }
@@ -0,0 +1,74 @@
1
+ import { useQueries, type UseQueryResult } from '@tanstack/react-query';
2
+ import { useSodaxContext } from '../shared/useSodaxContext.js';
3
+ import type { Address, SpokeChainKey } from '@sodax/sdk';
4
+ import type { ReadHookParams } from '../shared/types.js';
5
+
6
+ /** A single `(chain, hub-wallet holder, share balance)` row produced by {@link useLeverageYieldShareBalances}. */
7
+ export type LeverageYieldShareHolding = {
8
+ chainKey: SpokeChainKey;
9
+ holder: Address;
10
+ shares: bigint;
11
+ };
12
+
13
+ /** A `(chainKey, EOA address)` pair the user controls — one per spoke chain they may hold shares under. */
14
+ export type LeverageYieldShareHolder = {
15
+ chainKey: SpokeChainKey;
16
+ address: string;
17
+ };
18
+
19
+ export type UseLeverageYieldShareBalancesParams = ReadHookParams<
20
+ LeverageYieldShareHolding,
21
+ { vault: Address | undefined; holders: readonly LeverageYieldShareHolder[] | undefined }
22
+ >;
23
+
24
+ /**
25
+ * Reads a user's leverage-vault share (`lsoda*`) balances across every chain they may hold a
26
+ * position under — one query per holder, fanned out via `useQueries`.
27
+ *
28
+ * For each `(chainKey, address)` the holder is resolved to the address that actually owns the
29
+ * shares: the user's **derived hub wallet** on every chain, including the hub itself. Leverage
30
+ * deposits always deliver `lsoda*` to `getUserHubWalletAddress(srcAddress, srcChainKey)` (the
31
+ * CREATE3 user-router on Sonic for a hub-sourced deposit, the per-spoke hub wallet otherwise) —
32
+ * never the bare EOA — so a hub-chain deposit's shares live in the router, not the signing EOA.
33
+ * Resolving the holder the same way the deposit does is what makes a Sonic-sourced position show
34
+ * up here instead of reading a stale zero off the EOA.
35
+ *
36
+ * Returns the raw `useQueries` result array (15s refresh per query); callers aggregate as needed
37
+ * (e.g. sum `shares` for a headline total, or pick the row for the active chain).
38
+ *
39
+ * `useQueries` has no top-level options slot, so `queryOptions` is spread into every individual
40
+ * query config (and applies uniformly to each holder's query).
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * const balances = useLeverageYieldShareBalances({ params: { vault: vault.vault, holders } });
45
+ * const total = balances.reduce((acc, q) => acc + (q.data?.shares ?? 0n), 0n);
46
+ * ```
47
+ */
48
+ export function useLeverageYieldShareBalances({
49
+ params,
50
+ queryOptions,
51
+ }: UseLeverageYieldShareBalancesParams = {}): UseQueryResult<LeverageYieldShareHolding, Error>[] {
52
+ const { sodax } = useSodaxContext();
53
+ const vault = params?.vault;
54
+ const holders = params?.holders;
55
+
56
+ return useQueries({
57
+ queries: (holders ?? []).map(({ chainKey, address }) => ({
58
+ queryKey: ['leverageYield', 'shareBalance', vault, chainKey, address] as const,
59
+ refetchInterval: 15_000,
60
+ ...queryOptions,
61
+ enabled: !!vault,
62
+ queryFn: async (): Promise<LeverageYieldShareHolding> => {
63
+ if (!vault) throw new Error('vault is required');
64
+ // Always the derived hub wallet — on the hub chain too. A leverage deposit delivers
65
+ // shares to getUserHubWalletAddress(...), the CREATE3 router on Sonic, never the EOA,
66
+ // so reading balanceOf(EOA) for the hub chain would always come back zero.
67
+ const holder = await sodax.hubProvider.getUserHubWalletAddress(address, chainKey);
68
+ const result = await sodax.leverageYield.getShareBalance(vault, holder);
69
+ if (!result.ok) throw result.error;
70
+ return { chainKey, holder, shares: result.value };
71
+ },
72
+ })),
73
+ });
74
+ }
@@ -0,0 +1,36 @@
1
+ import { useQuery, type UseQueryResult } from '@tanstack/react-query';
2
+ import { useSodaxContext } from '../shared/useSodaxContext.js';
3
+ import type { Address } from '@sodax/sdk';
4
+ import type { ReadHookParams } from '../shared/types.js';
5
+
6
+ export type UseLeverageYieldTotalAssetsParams = ReadHookParams<bigint, { vault: Address | undefined }>;
7
+
8
+ /**
9
+ * Reads a leverage-yield vault's total underlying assets (vault-asset units, 18 decimals) —
10
+ * i.e. its TVL. Moves slowly, so the default refresh is 60s.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const { data: tvl } = useLeverageYieldTotalAssets({ params: { vault: vault.vault } });
15
+ * ```
16
+ */
17
+ export function useLeverageYieldTotalAssets({
18
+ params,
19
+ queryOptions,
20
+ }: UseLeverageYieldTotalAssetsParams = {}): UseQueryResult<bigint, Error> {
21
+ const { sodax } = useSodaxContext();
22
+ const vault = params?.vault;
23
+
24
+ return useQuery<bigint, Error>({
25
+ queryKey: ['leverageYield', 'totalAssets', vault],
26
+ queryFn: async () => {
27
+ if (!vault) throw new Error('vault is required');
28
+ const result = await sodax.leverageYield.getTotalAssets(vault);
29
+ if (!result.ok) throw result.error;
30
+ return result.value;
31
+ },
32
+ enabled: !!vault,
33
+ refetchInterval: 60_000,
34
+ ...queryOptions,
35
+ });
36
+ }
@@ -0,0 +1,50 @@
1
+ import { useSodaxContext } from '../shared/useSodaxContext.js';
2
+ import type { SpokeChainKey, VaultSwapActionParams, VaultSwapResponse } from '@sodax/sdk';
3
+ import { useQueryClient } from '@tanstack/react-query';
4
+ import type { MutationHookParams } from '../shared/types.js';
5
+ import { useSafeMutation, type SafeUseMutationResult } from '../shared/useSafeMutation.js';
6
+ import { unwrapResult } from '../shared/unwrapResult.js';
7
+
8
+ /**
9
+ * Mutation variables for {@link useLeverageYieldVaultSwap}. Generic over `K extends SpokeChainKey`
10
+ * (defaults to the full union). Sophisticated callers can lock K at the hook call site to narrow
11
+ * the `walletProvider` and `params.srcChainKey` types.
12
+ */
13
+ export type UseLeverageYieldVaultSwapVars<K extends SpokeChainKey = SpokeChainKey> = Omit<
14
+ VaultSwapActionParams<K, false>,
15
+ 'raw'
16
+ >;
17
+
18
+ /**
19
+ * React hook for executing an end-to-end leverage-yield vault swap (deposit or withdraw).
20
+ *
21
+ * Spread a `LeverageYieldSwapPayload` built by {@link useLeverageYieldDeposit} /
22
+ * {@link useLeverageYieldWithdraw} into `mutate` alongside the wallet provider:
23
+ * `vaultSwap({ ...payload, walletProvider })`. Runs `sodax.leverageYield.vaultSwap` —
24
+ * create intent → verify → relay → notify solver — so vault flows never touch the
25
+ * generic swap surface.
26
+ *
27
+ * Throws on SDK failure so React Query's native error model engages (`isError`, `error`,
28
+ * `onError`, `retry`). Returns the unwrapped `VaultSwapResponse` on success.
29
+ */
30
+ export function useLeverageYieldVaultSwap<K extends SpokeChainKey = SpokeChainKey>({
31
+ mutationOptions,
32
+ }: MutationHookParams<VaultSwapResponse, UseLeverageYieldVaultSwapVars<K>> = {}): SafeUseMutationResult<
33
+ VaultSwapResponse,
34
+ Error,
35
+ UseLeverageYieldVaultSwapVars<K>
36
+ > {
37
+ const { sodax } = useSodaxContext();
38
+ const queryClient = useQueryClient();
39
+
40
+ return useSafeMutation<VaultSwapResponse, Error, UseLeverageYieldVaultSwapVars<K>>({
41
+ mutationKey: ['leverageYield', 'vaultSwap'],
42
+ ...mutationOptions,
43
+ mutationFn: async vars => unwrapResult(await sodax.leverageYield.vaultSwap({ ...vars, raw: false })),
44
+ onSuccess: async (data, vars, ctx) => {
45
+ queryClient.invalidateQueries({ queryKey: ['shared', 'xBalances', vars.params.srcChainKey] });
46
+ queryClient.invalidateQueries({ queryKey: ['shared', 'xBalances', vars.params.dstChainKey] });
47
+ await mutationOptions?.onSuccess?.(data, vars, ctx);
48
+ },
49
+ });
50
+ }
@@ -0,0 +1,40 @@
1
+ import { useSodaxContext } from '../shared/useSodaxContext.js';
2
+ import type { LeverageYieldSwapPayload, LeverageYieldSwapWithdrawParams } from '@sodax/sdk';
3
+ import type { MutationHookParams } from '../shared/types.js';
4
+ import { useSafeMutation, type SafeUseMutationResult } from '../shared/useSafeMutation.js';
5
+ import { unwrapResult } from '../shared/unwrapResult.js';
6
+
7
+ /** Mutation variables for {@link useLeverageYieldWithdraw} — the swap-style withdraw inputs. */
8
+ export type UseLeverageYieldWithdrawVars = LeverageYieldSwapWithdrawParams;
9
+
10
+ /**
11
+ * Builds the `LeverageYieldSwapPayload` for a leverage-yield withdraw (`lsoda*` shares → any token).
12
+ * The returned payload carries `hubWalletSwap: true`; spread it into
13
+ * {@link useLeverageYieldVaultSwap}, which authorises the hub wallet to spend the shares via a
14
+ * `Connection.sendMessage`.
15
+ *
16
+ * This is a builder, not an executor. Throws on SDK failure so React Query's error model engages;
17
+ * returns the unwrapped `LeverageYieldSwapPayload` on success.
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * const { mutateAsyncSafe: buildWithdraw } = useLeverageYieldWithdraw();
22
+ * const built = await buildWithdraw({ vault, srcChainKey, srcAddress, dstChainKey, outputToken, inputAmount, minOutputAmount });
23
+ * if (built.ok) vaultSwap({ ...built.value, walletProvider });
24
+ * ```
25
+ */
26
+ export function useLeverageYieldWithdraw({
27
+ mutationOptions,
28
+ }: MutationHookParams<LeverageYieldSwapPayload, UseLeverageYieldWithdrawVars> = {}): SafeUseMutationResult<
29
+ LeverageYieldSwapPayload,
30
+ Error,
31
+ UseLeverageYieldWithdrawVars
32
+ > {
33
+ const { sodax } = useSodaxContext();
34
+
35
+ return useSafeMutation<LeverageYieldSwapPayload, Error, UseLeverageYieldWithdrawVars>({
36
+ mutationKey: ['leverageYield', 'withdraw'],
37
+ ...mutationOptions,
38
+ mutationFn: async vars => unwrapResult(await sodax.leverageYield.withdraw(vars)),
39
+ });
40
+ }
@@ -2,6 +2,7 @@ import type { SpokeChainKey } from '@sodax/sdk';
2
2
  import { useQuery, type UseQueryResult } from '@tanstack/react-query';
3
3
  import { type Address, isAddress } from 'viem';
4
4
  import { useSodaxContext } from '../shared/useSodaxContext.js';
5
+ import { resolveBtcReadAddress } from '../bitcoin/resolveBtcReadAddress.js';
5
6
  import type { ReadHookParams } from '../shared/types.js';
6
7
 
7
8
  export type UseATokensBalancesParams = ReadHookParams<
@@ -42,7 +43,12 @@ export function useATokensBalances({
42
43
  }
43
44
  }
44
45
 
45
- const hubWalletAddress = await sodax.hubProvider.getUserHubWalletAddress(userAddress, spokeChainKey);
46
+ // Bitcoin positions live under the trading-wallet-derived hub wallet; resolve it locally
47
+ // (no network) so a Bound Exchange outage never reads a real position as empty.
48
+ const hubWalletAddress = await sodax.hubProvider.getUserHubWalletAddress(
49
+ resolveBtcReadAddress(spokeChainKey, userAddress),
50
+ spokeChainKey,
51
+ );
46
52
  return sodax.moneyMarket.data.getATokensBalances(aTokens, hubWalletAddress);
47
53
  },
48
54
  enabled: aTokens.length > 0 && !!spokeChainKey && !!userAddress,
@@ -2,6 +2,7 @@ import type { FormatReserveUSDResponse, FormatUserSummaryResponse } from '@sodax
2
2
  import type { SpokeChainKey } from '@sodax/sdk';
3
3
  import { useQuery, type UseQueryResult } from '@tanstack/react-query';
4
4
  import { useSodaxContext } from '../shared/useSodaxContext.js';
5
+ import { resolveBtcReadAddress } from '../bitcoin/resolveBtcReadAddress.js';
5
6
  import type { ReadHookParams } from '../shared/types.js';
6
7
 
7
8
  export type UseUserFormattedSummaryParams = ReadHookParams<
@@ -36,7 +37,10 @@ export function useUserFormattedSummary({
36
37
 
37
38
  const [reserves, userReserves] = await Promise.all([
38
39
  sodax.moneyMarket.data.getReservesHumanized(),
39
- sodax.moneyMarket.data.getUserReservesHumanized(spokeChainKey, userAddress),
40
+ sodax.moneyMarket.data.getUserReservesHumanized(
41
+ spokeChainKey,
42
+ resolveBtcReadAddress(spokeChainKey, userAddress),
43
+ ),
40
44
  ]);
41
45
  const formattedReserves = sodax.moneyMarket.data.formatReservesUSD(
42
46
  sodax.moneyMarket.data.buildReserveDataWithPrice(reserves),
@@ -2,6 +2,7 @@ import type { UserReserveData } from '@sodax/sdk';
2
2
  import type { SpokeChainKey } from '@sodax/sdk';
3
3
  import { useQuery, type UseQueryResult } from '@tanstack/react-query';
4
4
  import { useSodaxContext } from '../shared/useSodaxContext.js';
5
+ import { resolveBtcReadAddress } from '../bitcoin/resolveBtcReadAddress.js';
5
6
  import type { ReadHookParams } from '../shared/types.js';
6
7
 
7
8
  export type UseUserReservesDataParams = ReadHookParams<
@@ -30,7 +31,10 @@ export function useUserReservesData({
30
31
  if (!spokeChainKey || !userAddress) {
31
32
  throw new Error('spokeChainKey and userAddress are required');
32
33
  }
33
- return sodax.moneyMarket.data.getUserReservesData(spokeChainKey, userAddress);
34
+ return sodax.moneyMarket.data.getUserReservesData(
35
+ spokeChainKey,
36
+ resolveBtcReadAddress(spokeChainKey, userAddress),
37
+ );
34
38
  },
35
39
  enabled: !!spokeChainKey && !!userAddress,
36
40
  refetchInterval: 5000,
@@ -6,5 +6,8 @@ export * from './useEstimateGas.js';
6
6
  export * from './useDeriveUserWalletAddress.js';
7
7
  export * from './useStellarTrustlineCheck.js';
8
8
  export * from './useRequestTrustline.js';
9
+ export * from './useNearStorageCheck.js';
10
+ export * from './useRegisterNearStorage.js';
11
+ export * from './useNearStorageGate.js';
9
12
  export * from './useGetUserHubWalletAddress.js';
10
13
  export * from './useXBalances.js';
@@ -1,6 +1,7 @@
1
1
  import type { SpokeChainKey } from '@sodax/sdk';
2
2
  import { useQuery, type UseQueryResult } from '@tanstack/react-query';
3
3
  import { useSodaxContext } from './useSodaxContext.js';
4
+ import { resolveBtcReadAddress } from '../bitcoin/resolveBtcReadAddress.js';
4
5
  import type { Address } from 'viem';
5
6
  import type { ReadHookParams } from './types.js';
6
7
 
@@ -44,7 +45,10 @@ export function useGetUserHubWalletAddress({
44
45
  if (!spokeChainId || !spokeAddress) {
45
46
  throw new Error('Spoke chain id and address are required');
46
47
  }
47
- return await sodax.hubProvider.getUserHubWalletAddress(spokeAddress, spokeChainId);
48
+ return await sodax.hubProvider.getUserHubWalletAddress(
49
+ resolveBtcReadAddress(spokeChainId, spokeAddress),
50
+ spokeChainId,
51
+ );
48
52
  },
49
53
  enabled: !!spokeChainId && !!spokeAddress,
50
54
  refetchInterval: false,
@@ -0,0 +1,45 @@
1
+ import { ChainKeys, type SpokeChainKey } from '@sodax/sdk';
2
+ import { useQuery, type UseQueryResult } from '@tanstack/react-query';
3
+ import { useSodaxContext } from './useSodaxContext.js';
4
+ import type { ReadHookParams } from './types.js';
5
+
6
+ export type UseNearStorageCheckParams = ReadHookParams<
7
+ boolean,
8
+ {
9
+ token: string | undefined;
10
+ accountId: string | undefined;
11
+ chainId: SpokeChainKey | undefined;
12
+ }
13
+ >;
14
+
15
+ /**
16
+ * Whether `accountId` is NEP-141 storage-registered for `token` on NEAR — the receive-side
17
+ * prerequisite for any leg that delivers a token to the user on NEAR (swap output on NEAR, bridge
18
+ * into NEAR, money-market borrow/withdraw to NEAR). NEAR's analogue of a Stellar trustline.
19
+ *
20
+ * Returns `true` (no gate) when `chainId` is not NEAR, mirroring how the Stellar trustline check
21
+ * short-circuits off-chain. Native NEAR is not a NEP-141 token, so the SDK reports it as registered.
22
+ *
23
+ * Pair with {@link useRegisterNearStorage} to perform the one-time `storage_deposit` when this
24
+ * resolves to `false`.
25
+ */
26
+ export function useNearStorageCheck({
27
+ params,
28
+ queryOptions,
29
+ }: UseNearStorageCheckParams = {}): UseQueryResult<boolean, Error> {
30
+ const { sodax } = useSodaxContext();
31
+ const token = params?.token;
32
+ const accountId = params?.accountId;
33
+ const chainId = params?.chainId;
34
+
35
+ return useQuery<boolean, Error>({
36
+ queryKey: ['shared', 'nearStorageCheck', chainId, token, accountId],
37
+ queryFn: async () => {
38
+ if (chainId !== ChainKeys.NEAR_MAINNET) return true;
39
+ if (!token || !accountId) return false;
40
+ return sodax.spoke.near.isStorageRegistered(token, accountId);
41
+ },
42
+ enabled: !!token && !!accountId,
43
+ ...queryOptions,
44
+ });
45
+ }
@@ -0,0 +1,60 @@
1
+ import type { GetWalletProviderType, INearWalletProvider, Result, SpokeChainKey } from '@sodax/sdk';
2
+ import { resolveNearStorageGate } from '../../utils/nearStorageGate.js';
3
+ import { useNearStorageCheck } from './useNearStorageCheck.js';
4
+ import { useRegisterNearStorage } from './useRegisterNearStorage.js';
5
+
6
+ interface UseNearStorageGateParams {
7
+ /** Destination chain the token is delivered on. */
8
+ dstChainKey: SpokeChainKey;
9
+ /** Destination token address the recipient must be registered for. */
10
+ token: string | undefined;
11
+ /** Recipient NEAR account id. */
12
+ accountId: string | undefined;
13
+ /** Destination wallet provider; only consumed when it is the NEAR provider. */
14
+ walletProvider: GetWalletProviderType<SpokeChainKey> | undefined;
15
+ }
16
+
17
+ interface NearStorageGate {
18
+ /** Destination is NEAR. */
19
+ isNear: boolean;
20
+ /** Destination is NEAR and the recipient is not yet storage-registered for `token`. */
21
+ needsRegistration: boolean;
22
+ /** The downstream action must stay disabled while the gate is unresolved or unmet. */
23
+ blocksAction: boolean;
24
+ /** The registration-status query is actively fetching. */
25
+ isChecking: boolean;
26
+ /** A `storage_deposit` tx is in flight. */
27
+ isRegistering: boolean;
28
+ /** Submit the `storage_deposit`; resolves to the SDK `Result` or `undefined` if inputs are missing. */
29
+ registerStorage: () => Promise<Result<string> | undefined>;
30
+ }
31
+
32
+ /**
33
+ * NEP-141 storage-registration gate for flows that deliver a token to a user on NEAR.
34
+ */
35
+ export function useNearStorageGate({
36
+ dstChainKey,
37
+ token,
38
+ accountId,
39
+ walletProvider,
40
+ }: UseNearStorageGateParams): NearStorageGate {
41
+ const nearWalletProvider = walletProvider?.chainType === 'NEAR' ? (walletProvider as INearWalletProvider) : undefined;
42
+
43
+ const storageCheck = useNearStorageCheck({ params: { token, accountId, chainId: dstChainKey } });
44
+ const { mutateAsyncSafe: register, isPending: isRegistering } = useRegisterNearStorage();
45
+ const { isNear, needsRegistration, blocksAction } = resolveNearStorageGate(dstChainKey, storageCheck);
46
+
47
+ const registerStorage = async (): Promise<Result<string> | undefined> => {
48
+ if (!nearWalletProvider || !token || !accountId) return undefined;
49
+ return register({ token, accountId, walletProvider: nearWalletProvider });
50
+ };
51
+
52
+ return {
53
+ isNear,
54
+ needsRegistration,
55
+ blocksAction,
56
+ isChecking: storageCheck.isLoading,
57
+ isRegistering,
58
+ registerStorage,
59
+ };
60
+ }
@@ -0,0 +1,46 @@
1
+ import { ChainKeys, type INearWalletProvider } from '@sodax/sdk';
2
+ import { useQueryClient } from '@tanstack/react-query';
3
+ import { useSodaxContext } from './useSodaxContext.js';
4
+ import type { MutationHookParams } from './types.js';
5
+ import { useSafeMutation, type SafeUseMutationResult } from './useSafeMutation.js';
6
+
7
+ export type UseRegisterNearStorageVars = {
8
+ token: string;
9
+ accountId: string;
10
+ walletProvider: INearWalletProvider;
11
+ /** Storage bond override; defaults to the SDK's `NEAR_STORAGE_DEPOSIT` (0.00125 NEAR). */
12
+ deposit?: bigint;
13
+ };
14
+
15
+ /**
16
+ * Submit a NEP-141 `storage_deposit` so `accountId` can receive `token` on NEAR. NEAR's analogue of
17
+ * requesting a Stellar trustline; pair with {@link useNearStorageCheck} and call this when the check
18
+ * resolves to `false`. The recipient's NEAR wallet signs the registration tx.
19
+ *
20
+ * Pure mutation: pass `{ token, accountId, walletProvider }` to `mutate(...)`. The hook itself only
21
+ * takes the structural `mutationOptions` slot. `registerStorage` throws natively (no `Result<T>`),
22
+ * so this hook is registered as `nativeThrow` in the mutation contract. On success it invalidates
23
+ * the matching {@link useNearStorageCheck} query.
24
+ */
25
+ export function useRegisterNearStorage({
26
+ mutationOptions,
27
+ }: MutationHookParams<string, UseRegisterNearStorageVars> = {}): SafeUseMutationResult<
28
+ string,
29
+ Error,
30
+ UseRegisterNearStorageVars
31
+ > {
32
+ const { sodax } = useSodaxContext();
33
+ const queryClient = useQueryClient();
34
+ return useSafeMutation<string, Error, UseRegisterNearStorageVars>({
35
+ mutationKey: ['shared', 'registerNearStorage'],
36
+ ...mutationOptions,
37
+ mutationFn: async ({ token, accountId, walletProvider, deposit }) =>
38
+ sodax.spoke.near.registerStorage({ token, accountId, walletProvider, deposit, raw: false }),
39
+ onSuccess: async (data, vars, ctx) => {
40
+ queryClient.invalidateQueries({
41
+ queryKey: ['shared', 'nearStorageCheck', ChainKeys.NEAR_MAINNET, vars.token, vars.accountId],
42
+ });
43
+ await mutationOptions?.onSuccess?.(data, vars, ctx);
44
+ },
45
+ });
46
+ }