@sodax/dapp-kit 2.0.0-rc.10 → 2.0.0-rc.12

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/README.md CHANGED
@@ -10,7 +10,7 @@ High-level React hooks library for dApp developers. Wraps `@sodax/sdk` with Reac
10
10
  - **Staking** — `useStake`, `useUnstake`, `useInstantUnstake`, `useClaim`, `useCancelUnstake`, approval hooks, info/config/ratio queries
11
11
  - **DEX** — `useDexDeposit`, `useDexWithdraw`, `useSupplyLiquidity`, `useDecreaseLiquidity`, `useClaimRewards`, pool/position queries, param builders
12
12
  - **Migration** — `useMigrateIcxToSoda`, `useRevertMigrateSodaToIcx`, `useMigratebnUSD`, `useMigrateBaln`, `useMigrationApprove`, `useMigrationAllowance`
13
- - **Bitcoin (Radfi)** — `useRadfiAuth`, `useRadfiSession`, `useTradingWallet`, `useTradingWalletBalance`, `useBitcoinBalance`, `useFundTradingWallet`, `useRadfiWithdraw`, `useExpiredUtxos`, `useRenewUtxos`
13
+ - **Bitcoin (Bound Exchange)** — `useRadfiAuth`, `useRadfiSession`, `useTradingWallet`, `useTradingWalletBalance`, `useBitcoinBalance`, `useFundTradingWallet`, `useRadfiWithdraw`, `useExpiredUtxos`, `useRenewUtxos`
14
14
  - **Partner** — `useFetchAssetsBalances`, `useGetAutoSwapPreferences`, `useIsTokenApproved`, `useApproveToken`, `useSetSwapPreference`, `useFeeClaimSwap`
15
15
  - **Recovery** — `useHubAssetBalances`, `useWithdrawHubAsset`
16
16
  - **Backend Queries** — Intent tracking, orderbook, money market position queries
@@ -213,13 +213,13 @@ function SwapButton({ intentParams }: { intentParams: CreateIntentParams }) {
213
213
  - [`useMigrationApprove()`](src/hooks/migrate/useMigrationApprove.ts) — Approve token spending before migration
214
214
  - [`useMigrationAllowance()`](src/hooks/migrate/useMigrationAllowance.ts) — Check if approval is needed
215
215
 
216
- ### Bitcoin (Radfi) Hooks
216
+ ### Bitcoin (Bound Exchange) Hooks
217
217
 
218
- - [`useRadfiSession()`](src/hooks/bitcoin/useRadfiSession.ts) — Manage Radfi session (login, auto-refresh)
219
- - [`useRadfiAuth()`](src/hooks/bitcoin/useRadfiAuth.ts) — Authenticate with Radfi via BIP322 signing
218
+ - [`useRadfiSession()`](src/hooks/bitcoin/useRadfiSession.ts) — Manage Bound Exchange session (login, auto-refresh)
219
+ - [`useRadfiAuth()`](src/hooks/bitcoin/useRadfiAuth.ts) — Authenticate with Bound Exchange via BIP322 signing
220
220
  - [`useTradingWallet()`](src/hooks/bitcoin/useTradingWallet.ts) — Get trading wallet address from persisted session
221
221
  - [`useBitcoinBalance()`](src/hooks/bitcoin/useBitcoinBalance.ts) — BTC balance for any address
222
- - [`useTradingWalletBalance()`](src/hooks/bitcoin/useTradingWalletBalance.ts) — Trading wallet balance from Radfi API
222
+ - [`useTradingWalletBalance()`](src/hooks/bitcoin/useTradingWalletBalance.ts) — Trading wallet balance from Bound Exchange API
223
223
  - [`useFundTradingWallet()`](src/hooks/bitcoin/useFundTradingWallet.ts) — Fund trading wallet from personal wallet
224
224
  - [`useRadfiWithdraw()`](src/hooks/bitcoin/useRadfiWithdraw.ts) — Withdraw from trading wallet
225
225
  - [`useExpiredUtxos()`](src/hooks/bitcoin/useExpiredUtxos.ts) — Fetch expired UTXOs (polls every 60s)
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { UseMutationOptions, UseQueryOptions, UseMutationResult, MutateOptions, UseQueryResult, QueryClientConfig, QueryClient } from '@tanstack/react-query';
2
- import { Result, Sodax, SpokeChainKey, GetEstimateGasReturnType, EstimateGasParams, IStellarWalletProvider, StellarChainKey, IXServiceBase, XToken, EvmHubProvider, IBitcoinWalletProvider, RadfiWalletBalance, RadfiUtxo, MoneyMarketBorrowActionParams, TxHashPair, MoneyMarketRepayActionParams, MoneyMarketSupplyActionParams, MoneyMarketWithdrawActionParams, UserReserveData, AggregatedReserveData, BaseCurrencyInfo, ReservesDataHumanized, Address as Address$1, MoneyMarketParams, MoneyMarketApproveActionParams, TxReturnType, Erc20Token, ChainKey, ReserveDataHumanized, FormatReserveUSDResponse, FormatUserSummaryResponse, SolverIntentQuoteResponse, SolverErrorResponse, SolverIntentQuoteRequest, SwapActionParams, SwapResponse, SolverIntentStatusResponse, Hex, CreateIntentParams, CreateLimitOrderParams, GetWalletProviderType, Intent, LimitOrderActionParams, IntentResponse, UserIntentsResponse, SubmitSwapTxResponse, SubmitSwapTxRequest, RequestOverrideConfig, SubmitSwapTxStatusResponse, OrderbookResponse, MoneyMarketPosition, MoneyMarketAsset, MoneyMarketAssetBorrowers, MoneyMarketAssetSuppliers, MoneyMarketBorrowers, BridgeParams, CreateBridgeIntentParams, BridgeLimit, StakeAction, StakeParams, UnstakeAction, ClaimAction, CancelUnstakeAction, StakingInfo, UnstakingInfo, UnstakeRequestWithPenalty, StakingConfig, InstantUnstakeAction, UnstakeParams, InstantUnstakeParams, PartnerFeeClaimAssetBalance, AutoSwapPreferences, FeeTokenApproveParams, FeeTokenApproveAction, HubChainKey, SetSwapPreferenceAction, PartnerFeeClaimSwapAction, IntentAutoSwapResult, HubAssetBalance, WithdrawHubAssetAction, IcxMigrateAction, IcxRevertMigrationAction, UnifiedBnUSDMigrateAction, BalnMigrateAction, MigrationAction, IcxMigrateParams, IcxCreateRevertMigrationParams, UnifiedBnUSDMigrateParams, BalnMigrateParams, PoolKey, PoolData, ClPositionInfo, AssetDepositAction, AssetWithdrawAction, CreateAssetDepositParams, ClSupplyParams, ClIncreaseLiquidityParams, ClLiquidityDecreaseLiquidityAction, ClDecreaseLiquidityParams, CreateAssetWithdrawParams, PoolSpokeAssets, DestinationParamsType, ClLiquidityClaimRewardsAction, DeepPartial, SodaxConfig } from '@sodax/sdk';
2
+ import { Result, Sodax, SpokeChainKey, GetEstimateGasReturnType, EstimateGasParams, IStellarWalletProvider, StellarChainKey, INearWalletProvider, GetWalletProviderType, IXServiceBase, XToken, EvmHubProvider, IBitcoinWalletProvider, RadfiWalletBalance, RadfiUtxo, MoneyMarketBorrowActionParams, TxHashPair, MoneyMarketRepayActionParams, MoneyMarketSupplyActionParams, MoneyMarketWithdrawActionParams, UserReserveData, AggregatedReserveData, BaseCurrencyInfo, ReservesDataHumanized, Address as Address$1, MoneyMarketParams, MoneyMarketApproveActionParams, TxReturnType, Erc20Token, ChainKey, ReserveDataHumanized, FormatReserveUSDResponse, FormatUserSummaryResponse, SolverIntentQuoteResponse, SolverErrorResponse, SolverIntentQuoteRequest, SwapActionParams, SwapResponse, SolverIntentStatusResponse, Hex, CreateIntentParams, CreateLimitOrderParams, Intent, LimitOrderActionParams, IntentResponse, UserIntentsResponse, SubmitSwapTxResponse, SubmitSwapTxRequest, RequestOverrideConfig, SubmitSwapTxStatusResponse, OrderbookResponse, MoneyMarketPosition, MoneyMarketAsset, MoneyMarketAssetBorrowers, MoneyMarketAssetSuppliers, MoneyMarketBorrowers, BridgeParams, CreateBridgeIntentParams, BridgeLimit, StakeAction, StakeParams, UnstakeAction, ClaimAction, CancelUnstakeAction, StakingInfo, UnstakingInfo, UnstakeRequestWithPenalty, StakingConfig, InstantUnstakeAction, UnstakeParams, InstantUnstakeParams, PartnerFeeClaimAssetBalance, AutoSwapPreferences, FeeTokenApproveParams, FeeTokenApproveAction, HubChainKey, SetSwapPreferenceAction, PartnerFeeClaimSwapAction, IntentAutoSwapResult, HubAssetBalance, WithdrawHubAssetAction, IcxMigrateAction, IcxRevertMigrationAction, UnifiedBnUSDMigrateAction, BalnMigrateAction, MigrationAction, IcxMigrateParams, IcxCreateRevertMigrationParams, UnifiedBnUSDMigrateParams, BalnMigrateParams, PoolKey, PoolData, ClPositionInfo, AssetDepositAction, AssetWithdrawAction, CreateAssetDepositParams, ClSupplyParams, ClIncreaseLiquidityParams, ClLiquidityDecreaseLiquidityAction, ClDecreaseLiquidityParams, CreateAssetWithdrawParams, PoolSpokeAssets, DestinationParamsType, ClLiquidityClaimRewardsAction, SodaxOptions } from '@sodax/sdk';
3
3
  export * from '@sodax/sdk';
4
4
  import { Address } from 'viem';
5
5
  import { ReactNode, ReactElement } from 'react';
@@ -151,6 +151,72 @@ declare function useRequestTrustline(token: string | undefined): {
151
151
  data: string | null;
152
152
  };
153
153
 
154
+ type UseNearStorageCheckParams = ReadHookParams<boolean, {
155
+ token: string | undefined;
156
+ accountId: string | undefined;
157
+ chainId: SpokeChainKey | undefined;
158
+ }>;
159
+ /**
160
+ * Whether `accountId` is NEP-141 storage-registered for `token` on NEAR — the receive-side
161
+ * prerequisite for any leg that delivers a token to the user on NEAR (swap output on NEAR, bridge
162
+ * into NEAR, money-market borrow/withdraw to NEAR). NEAR's analogue of a Stellar trustline.
163
+ *
164
+ * Returns `true` (no gate) when `chainId` is not NEAR, mirroring how the Stellar trustline check
165
+ * short-circuits off-chain. Native NEAR is not a NEP-141 token, so the SDK reports it as registered.
166
+ *
167
+ * Pair with {@link useRegisterNearStorage} to perform the one-time `storage_deposit` when this
168
+ * resolves to `false`.
169
+ */
170
+ declare function useNearStorageCheck({ params, queryOptions, }?: UseNearStorageCheckParams): UseQueryResult<boolean, Error>;
171
+
172
+ type UseRegisterNearStorageVars = {
173
+ token: string;
174
+ accountId: string;
175
+ walletProvider: INearWalletProvider;
176
+ /** Storage bond override; defaults to the SDK's `NEAR_STORAGE_DEPOSIT` (0.00125 NEAR). */
177
+ deposit?: bigint;
178
+ };
179
+ /**
180
+ * Submit a NEP-141 `storage_deposit` so `accountId` can receive `token` on NEAR. NEAR's analogue of
181
+ * requesting a Stellar trustline; pair with {@link useNearStorageCheck} and call this when the check
182
+ * resolves to `false`. The recipient's NEAR wallet signs the registration tx.
183
+ *
184
+ * Pure mutation: pass `{ token, accountId, walletProvider }` to `mutate(...)`. The hook itself only
185
+ * takes the structural `mutationOptions` slot. `registerStorage` throws natively (no `Result<T>`),
186
+ * so this hook is registered as `nativeThrow` in the mutation contract. On success it invalidates
187
+ * the matching {@link useNearStorageCheck} query.
188
+ */
189
+ declare function useRegisterNearStorage({ mutationOptions, }?: MutationHookParams<string, UseRegisterNearStorageVars>): SafeUseMutationResult<string, Error, UseRegisterNearStorageVars>;
190
+
191
+ interface UseNearStorageGateParams {
192
+ /** Destination chain the token is delivered on. */
193
+ dstChainKey: SpokeChainKey;
194
+ /** Destination token address the recipient must be registered for. */
195
+ token: string | undefined;
196
+ /** Recipient NEAR account id. */
197
+ accountId: string | undefined;
198
+ /** Destination wallet provider; only consumed when it is the NEAR provider. */
199
+ walletProvider: GetWalletProviderType<SpokeChainKey> | undefined;
200
+ }
201
+ interface NearStorageGate {
202
+ /** Destination is NEAR. */
203
+ isNear: boolean;
204
+ /** Destination is NEAR and the recipient is not yet storage-registered for `token`. */
205
+ needsRegistration: boolean;
206
+ /** The downstream action must stay disabled while the gate is unresolved or unmet. */
207
+ blocksAction: boolean;
208
+ /** The registration-status query is actively fetching. */
209
+ isChecking: boolean;
210
+ /** A `storage_deposit` tx is in flight. */
211
+ isRegistering: boolean;
212
+ /** Submit the `storage_deposit`; resolves to the SDK `Result` or `undefined` if inputs are missing. */
213
+ registerStorage: () => Promise<Result<string> | undefined>;
214
+ }
215
+ /**
216
+ * NEP-141 storage-registration gate for flows that deliver a token to a user on NEAR.
217
+ */
218
+ declare function useNearStorageGate({ dstChainKey, token, accountId, walletProvider, }: UseNearStorageGateParams): NearStorageGate;
219
+
154
220
  type UseGetUserHubWalletAddressParams = ReadHookParams<Address, {
155
221
  spokeChainId?: SpokeChainKey;
156
222
  spokeAddress?: string;
@@ -228,7 +294,7 @@ declare function saveRadfiSession(address: string, session: RadfiSession): void;
228
294
  declare function loadRadfiSession(address: string): RadfiSession | null;
229
295
  declare function clearRadfiSession(address: string): void;
230
296
  /**
231
- * React hook for authenticating with Radfi via BIP322-signed message. Pure mutation: pass
297
+ * React hook for authenticating with Bound Exchange via BIP322-signed message. Pure mutation: pass
232
298
  * `{ walletProvider }` to `mutate({...})`. The hook itself takes no arguments other than the
233
299
  * structural `mutationOptions` slot.
234
300
  */
@@ -248,7 +314,7 @@ type UseFundTradingWalletVars = {
248
314
  walletProvider: IBitcoinWalletProvider;
249
315
  };
250
316
  /**
251
- * React hook for funding the user's Radfi trading wallet from their personal Bitcoin wallet.
317
+ * React hook for funding the user's Bound Exchange trading wallet from their personal Bitcoin wallet.
252
318
  * Pure mutation: pass `{ amount, walletProvider }` to `mutate({...})`. Returns the broadcast tx
253
319
  * id on success.
254
320
  */
@@ -271,7 +337,7 @@ type UseTradingWalletReturn = {
271
337
  tradingAddress: string | undefined;
272
338
  };
273
339
  /**
274
- * Returns the Radfi trading wallet address from the persisted session.
340
+ * Returns the Bound Exchange trading wallet address from the persisted session.
275
341
  * Trading wallet is created automatically during authentication — no API call needed.
276
342
  */
277
343
  declare function useTradingWallet(walletAddress: string | undefined): UseTradingWalletReturn;
@@ -293,7 +359,7 @@ type UseRenewUtxosVars = {
293
359
  walletProvider: IBitcoinWalletProvider;
294
360
  };
295
361
  /**
296
- * React hook for renewing expired UTXOs in the user's Radfi trading wallet. Pure mutation: pass
362
+ * React hook for renewing expired UTXOs in the user's Bound Exchange trading wallet. Pure mutation: pass
297
363
  * `{ txIdVouts, walletProvider }` to `mutate({...})`.
298
364
  */
299
365
  declare function useRenewUtxos({ mutationOptions, }?: MutationHookParams<string, UseRenewUtxosVars>): SafeUseMutationResult<string, Error, UseRenewUtxosVars>;
@@ -309,7 +375,7 @@ type WithdrawResult = {
309
375
  fee: number;
310
376
  };
311
377
  /**
312
- * React hook for withdrawing BTC from the user's Radfi trading wallet back to their personal
378
+ * React hook for withdrawing BTC from the user's Bound Exchange trading wallet back to their personal
313
379
  * Bitcoin wallet. Pure mutation: pass all inputs (including the wallet provider) to
314
380
  * `mutate({...})`.
315
381
  */
@@ -1489,7 +1555,7 @@ interface SodaxProviderProps {
1489
1555
  * <SodaxProvider config={config}>...</SodaxProvider>
1490
1556
  * ```
1491
1557
  */
1492
- config?: DeepPartial<SodaxConfig>;
1558
+ config?: SodaxOptions;
1493
1559
  }
1494
1560
  /** Root provider for `@sodax/dapp-kit`. Must be paired with `QueryClientProvider`. */
1495
1561
  declare const SodaxProvider: ({ children, config }: SodaxProviderProps) => ReactElement;
@@ -1554,4 +1620,30 @@ type CreateSodaxQueryClientOptions = {
1554
1620
  */
1555
1621
  declare function createSodaxQueryClient({ onMutationError, config, }?: CreateSodaxQueryClientOptions): QueryClient;
1556
1622
 
1557
- export { type ATokenData, type BackendPaginationParams, type CreateSodaxQueryClientOptions, type DecreaseLiquidityParamsCore, type DepositParamsCore, type MutationHookOptions, type MutationHookParams, type RadfiSession, type ReadHookParams, type ReadQueryOptions, type ReserveUsdFormat, type SafeUseMutationResult, SodaxProvider, type UseATokenParams, type UseATokensBalancesParams, type UseApproveTokenVars, type UseBitcoinBalanceParams, type UseBorrowVars, type UseBridgeAllowanceParams, type UseBridgeApproveVars, type UseBridgeVars, type UseClaimRewardsVars, type UseCreateDecreaseLiquidityParamsProps, type UseCreateDepositParamsProps, type UseCreateLimitOrderVars, type UseCreateSupplyLiquidityParamsProps, type UseCreateSupplyLiquidityParamsResult, type UseCreateWithdrawParamsProps, type UseDecreaseLiquidityVars, type UseDeriveUserWalletAddressParams, type UseDexAllowanceParams, type UseDexApproveVars, type UseDexDepositVars, type UseDexWithdrawVars, type UseExpiredUtxosParams, type UseFeeClaimSwapVars, type UseFetchAssetsBalancesParams, type UseFundTradingWalletVars, type UseGetAutoSwapPreferencesParams, type UseGetBridgeableAmountParams, type UseGetBridgeableTokensParams, type UseGetUserHubWalletAddressParams, type UseHubAssetBalancesParams, type UseIsTokenApprovedParams, type UseLiquidityAmountsResult, type UseMMAllowanceParams, type UseMMApproveVars, type UseMigrateBalnVars, type UseMigrateIcxToSodaVars, type UseMigratebnUSDVars, type UseMigrationAllowanceInputs, type UseMigrationAllowanceParams, type UseMigrationApproveVars, type UsePoolBalancesParams, type UsePoolBalancesResponse, type UsePoolDataParams, type UsePoolsParams, type UsePositionInfoParams, type UsePositionInfoResponse, type UseQuoteParams, type UseRadfiAuthVars, type UseRadfiSessionReturn, type UseRadfiWithdrawVars, type UseRenewUtxosVars, type UseRepayVars, type UseReservesDataParams, type UseReservesHumanizedParams, type UseReservesListParams, type UseReservesUsdFormatParams, type UseRevertMigrateSodaToIcxVars, type UseSetSwapPreferenceVars, type UseSetSwapPreferenceVarsHub, type UseStatusParams, type UseStellarTrustlineCheckParams, type UseSupplyLiquidityVars, type UseSupplyVars, type UseSwapAllowanceParams, type UseSwapApproveVars, type UseSwapVars, type UseTradingWalletBalanceParams, type UseUserFormattedSummaryParams, type UseUserReservesDataParams, type UseWithdrawHubAssetVars, type UseWithdrawVars, type UseXBalancesParams, type WithdrawParamsCore, type XBalancesInputs, clearRadfiSession, createDecreaseLiquidityParamsProps, createDepositParamsProps, createSodaxQueryClient, createSupplyLiquidityParamsProps, createWithdrawParamsProps, getXBalancesQueryOptions, loadRadfiSession, saveRadfiSession, toResult, unwrapResult, useAToken, useATokensBalances, useApproveToken, useBackendAllMoneyMarketAssets, useBackendAllMoneyMarketBorrowers, useBackendIntentByHash, useBackendIntentByTxHash, useBackendMoneyMarketAsset, useBackendMoneyMarketAssetBorrowers, useBackendMoneyMarketAssetSuppliers, useBackendMoneyMarketPosition, useBackendOrderbook, useBackendSubmitSwapTx, useBackendSubmitSwapTxStatus, useBackendUserIntents, useBitcoinBalance, useBorrow, useBridge, useBridgeAllowance, useBridgeApprove, useCancelLimitOrder, useCancelSwap, useCancelUnstake, useClaim, useClaimRewards, useConvertedAssets, useCreateDecreaseLiquidityParams, useCreateDepositParams, useCreateLimitOrder, useCreateSupplyLiquidityParams, useCreateWithdrawParams, useDecreaseLiquidity, useDeriveUserWalletAddress, useDexAllowance, useDexApprove, useDexDeposit, useDexWithdraw, useEstimateGas, useExpiredUtxos, useFeeClaimSwap, useFetchAssetsBalances, useFundTradingWallet, useGetAutoSwapPreferences, useGetBridgeableAmount, useGetBridgeableTokens, useGetUserHubWalletAddress, useHubAssetBalances, useHubProvider, useInstantUnstake, useInstantUnstakeAllowance, useInstantUnstakeApprove, useInstantUnstakeRatio, useIsTokenApproved, useLiquidityAmounts, useMMAllowance, useMMApprove, useMigrateBaln, useMigrateIcxToSoda, useMigratebnUSD, useMigrationAllowance, useMigrationApprove, usePoolBalances, usePoolData, usePools, usePositionInfo, useQuote, useRadfiAuth, useRadfiSession, useRadfiWithdraw, useRenewUtxos, useRepay, useRequestTrustline, useReservesData, useReservesHumanized, useReservesList, useReservesUsdFormat, useRevertMigrateSodaToIcx, useSafeMutation, useSetSwapPreference, useSodaxContext, useStake, useStakeAllowance, useStakeApprove, useStakeRatio, useStakingConfig, useStakingInfo, useStatus, useStellarTrustlineCheck, useSupply, useSupplyLiquidity, useSwap, useSwapAllowance, useSwapApprove, useTradingWallet, useTradingWalletBalance, useUnstake, useUnstakeAllowance, useUnstakeApprove, useUnstakingInfo, useUnstakingInfoWithPenalty, useUserFormattedSummary, useUserReservesData, useWithdraw, useWithdrawHubAsset, useXBalances };
1623
+ /** The subset of a `useNearStorageCheck` result the gate reads. A full `UseQueryResult<boolean>` satisfies it. */
1624
+ type NearStorageCheckResult = Pick<UseQueryResult<boolean>, 'isLoading' | 'data'>;
1625
+ /** UI gate state for NEP-141 storage registration on NEAR. */
1626
+ interface NearStorageGateState {
1627
+ /** Destination is NEAR — the only chain with a NEP-141 storage-registration prerequisite. */
1628
+ isNear: boolean;
1629
+ /** Show the "register storage" action: the check resolved and the recipient is not registered. */
1630
+ needsRegistration: boolean;
1631
+ /**
1632
+ * Keep the downstream action (swap/bridge/borrow/withdraw) disabled: the NEAR gate is unresolved
1633
+ * (still checking) or unmet (needs registration).
1634
+ */
1635
+ blocksAction: boolean;
1636
+ }
1637
+ /**
1638
+ * Derives the NEP-141 storage-registration UI gate state for a flow that delivers a token to a user
1639
+ * on NEAR — NEAR's analogue of the Stellar trustline gate. Unwrapped (no hook): pass the destination
1640
+ * `chainKey` and the `useNearStorageCheck` result; the util owns the `=== NEAR` test and reads
1641
+ * `isLoading` (NOT `isPending`, which stays `true` for a disabled query and would block forever).
1642
+ *
1643
+ * `blocksAction` deliberately also covers the in-flight check window so the action can't be
1644
+ * triggered before registration status is known; `needsRegistration` only flips once the check has
1645
+ * resolved (so the "register" button isn't shown speculatively while still checking).
1646
+ */
1647
+ declare function resolveNearStorageGate(chainKey: SpokeChainKey, check: NearStorageCheckResult): NearStorageGateState;
1648
+
1649
+ export { type ATokenData, type BackendPaginationParams, type CreateSodaxQueryClientOptions, type DecreaseLiquidityParamsCore, type DepositParamsCore, type MutationHookOptions, type MutationHookParams, type NearStorageCheckResult, type NearStorageGateState, type RadfiSession, type ReadHookParams, type ReadQueryOptions, type ReserveUsdFormat, type SafeUseMutationResult, SodaxProvider, type UseATokenParams, type UseATokensBalancesParams, type UseApproveTokenVars, type UseBitcoinBalanceParams, type UseBorrowVars, type UseBridgeAllowanceParams, type UseBridgeApproveVars, type UseBridgeVars, type UseClaimRewardsVars, type UseCreateDecreaseLiquidityParamsProps, type UseCreateDepositParamsProps, type UseCreateLimitOrderVars, type UseCreateSupplyLiquidityParamsProps, type UseCreateSupplyLiquidityParamsResult, type UseCreateWithdrawParamsProps, type UseDecreaseLiquidityVars, type UseDeriveUserWalletAddressParams, type UseDexAllowanceParams, type UseDexApproveVars, type UseDexDepositVars, type UseDexWithdrawVars, type UseExpiredUtxosParams, type UseFeeClaimSwapVars, type UseFetchAssetsBalancesParams, type UseFundTradingWalletVars, type UseGetAutoSwapPreferencesParams, type UseGetBridgeableAmountParams, type UseGetBridgeableTokensParams, type UseGetUserHubWalletAddressParams, type UseHubAssetBalancesParams, type UseIsTokenApprovedParams, type UseLiquidityAmountsResult, type UseMMAllowanceParams, type UseMMApproveVars, type UseMigrateBalnVars, type UseMigrateIcxToSodaVars, type UseMigratebnUSDVars, type UseMigrationAllowanceInputs, type UseMigrationAllowanceParams, type UseMigrationApproveVars, type UseNearStorageCheckParams, type UsePoolBalancesParams, type UsePoolBalancesResponse, type UsePoolDataParams, type UsePoolsParams, type UsePositionInfoParams, type UsePositionInfoResponse, type UseQuoteParams, type UseRadfiAuthVars, type UseRadfiSessionReturn, type UseRadfiWithdrawVars, type UseRegisterNearStorageVars, type UseRenewUtxosVars, type UseRepayVars, type UseReservesDataParams, type UseReservesHumanizedParams, type UseReservesListParams, type UseReservesUsdFormatParams, type UseRevertMigrateSodaToIcxVars, type UseSetSwapPreferenceVars, type UseSetSwapPreferenceVarsHub, type UseStatusParams, type UseStellarTrustlineCheckParams, type UseSupplyLiquidityVars, type UseSupplyVars, type UseSwapAllowanceParams, type UseSwapApproveVars, type UseSwapVars, type UseTradingWalletBalanceParams, type UseUserFormattedSummaryParams, type UseUserReservesDataParams, type UseWithdrawHubAssetVars, type UseWithdrawVars, type UseXBalancesParams, type WithdrawParamsCore, type XBalancesInputs, clearRadfiSession, createDecreaseLiquidityParamsProps, createDepositParamsProps, createSodaxQueryClient, createSupplyLiquidityParamsProps, createWithdrawParamsProps, getXBalancesQueryOptions, loadRadfiSession, resolveNearStorageGate, saveRadfiSession, toResult, unwrapResult, useAToken, useATokensBalances, useApproveToken, useBackendAllMoneyMarketAssets, useBackendAllMoneyMarketBorrowers, useBackendIntentByHash, useBackendIntentByTxHash, useBackendMoneyMarketAsset, useBackendMoneyMarketAssetBorrowers, useBackendMoneyMarketAssetSuppliers, useBackendMoneyMarketPosition, useBackendOrderbook, useBackendSubmitSwapTx, useBackendSubmitSwapTxStatus, useBackendUserIntents, useBitcoinBalance, useBorrow, useBridge, useBridgeAllowance, useBridgeApprove, useCancelLimitOrder, useCancelSwap, useCancelUnstake, useClaim, useClaimRewards, useConvertedAssets, useCreateDecreaseLiquidityParams, useCreateDepositParams, useCreateLimitOrder, useCreateSupplyLiquidityParams, useCreateWithdrawParams, useDecreaseLiquidity, useDeriveUserWalletAddress, useDexAllowance, useDexApprove, useDexDeposit, useDexWithdraw, useEstimateGas, useExpiredUtxos, useFeeClaimSwap, useFetchAssetsBalances, useFundTradingWallet, useGetAutoSwapPreferences, useGetBridgeableAmount, useGetBridgeableTokens, useGetUserHubWalletAddress, useHubAssetBalances, useHubProvider, useInstantUnstake, useInstantUnstakeAllowance, useInstantUnstakeApprove, useInstantUnstakeRatio, useIsTokenApproved, useLiquidityAmounts, useMMAllowance, useMMApprove, useMigrateBaln, useMigrateIcxToSoda, useMigratebnUSD, useMigrationAllowance, useMigrationApprove, useNearStorageCheck, useNearStorageGate, usePoolBalances, usePoolData, usePools, usePositionInfo, useQuote, useRadfiAuth, useRadfiSession, useRadfiWithdraw, useRegisterNearStorage, useRenewUtxos, useRepay, useRequestTrustline, useReservesData, useReservesHumanized, useReservesList, useReservesUsdFormat, useRevertMigrateSodaToIcx, useSafeMutation, useSetSwapPreference, useSodaxContext, useStake, useStakeAllowance, useStakeApprove, useStakeRatio, useStakingConfig, useStakingInfo, useStatus, useStellarTrustlineCheck, useSupply, useSupplyLiquidity, useSwap, useSwapAllowance, useSwapApprove, useTradingWallet, useTradingWalletBalance, useUnstake, useUnstakeAllowance, useUnstakeApprove, useUnstakingInfo, useUnstakingInfoWithPenalty, useUserFormattedSummary, useUserReservesData, useWithdraw, useWithdrawHubAsset, useXBalances };
package/dist/index.mjs CHANGED
@@ -140,59 +140,76 @@ function useRequestTrustline(token) {
140
140
  );
141
141
  return { requestTrustline, isLoading, isRequested, error, data };
142
142
  }
143
- function useGetUserHubWalletAddress({
143
+ function useNearStorageCheck({
144
144
  params,
145
145
  queryOptions
146
146
  } = {}) {
147
147
  const { sodax } = useSodaxContext();
148
- const spokeChainId = params?.spokeChainId;
149
- const spokeAddress = params?.spokeAddress;
148
+ const token = params?.token;
149
+ const accountId = params?.accountId;
150
+ const chainId = params?.chainId;
150
151
  return useQuery({
151
- queryKey: ["shared", "userHubWalletAddress", spokeChainId, spokeAddress],
152
+ queryKey: ["shared", "nearStorageCheck", chainId, token, accountId],
152
153
  queryFn: async () => {
153
- if (!spokeChainId || !spokeAddress) {
154
- throw new Error("Spoke chain id and address are required");
155
- }
156
- return await sodax.hubProvider.getUserHubWalletAddress(spokeAddress, spokeChainId);
154
+ if (chainId !== ChainKeys.NEAR_MAINNET) return true;
155
+ if (!token || !accountId) return false;
156
+ return sodax.spoke.near.isStorageRegistered(token, accountId);
157
157
  },
158
- enabled: !!spokeChainId && !!spokeAddress,
159
- refetchInterval: false,
158
+ enabled: !!token && !!accountId,
160
159
  ...queryOptions
161
160
  });
162
161
  }
163
- var REFETCH_INTERVAL_MS = 5e3;
164
- function getXBalancesQueryOptions({ xService, xChainId, xTokens, address }) {
165
- return {
166
- // Pair symbol + address: readable in devtools, unique on-chain (symbol alone
167
- // can collide — e.g. scam tokens copying legitimate ticker).
168
- queryKey: ["shared", "xBalances", xChainId, xTokens.map((x) => [x.symbol, x.address]), address],
169
- queryFn: async () => {
170
- if (!xService) return {};
171
- return xService.getBalances(address, xTokens);
172
- },
173
- enabled: !!xService && !!address && xTokens.length > 0,
174
- refetchInterval: REFETCH_INTERVAL_MS
175
- };
176
- }
177
- function useXBalances({
178
- params,
179
- queryOptions
162
+ function useRegisterNearStorage({
163
+ mutationOptions
180
164
  } = {}) {
181
- return useQuery({
182
- ...getXBalancesQueryOptions({
183
- xService: params?.xService,
184
- xChainId: params?.xChainId,
185
- xTokens: params?.xTokens ?? [],
186
- address: params?.address
187
- }),
188
- ...queryOptions
165
+ const { sodax } = useSodaxContext();
166
+ const queryClient = useQueryClient();
167
+ return useSafeMutation({
168
+ mutationKey: ["shared", "registerNearStorage"],
169
+ ...mutationOptions,
170
+ mutationFn: async ({ token, accountId, walletProvider, deposit }) => sodax.spoke.near.registerStorage({ token, accountId, walletProvider, deposit, raw: false }),
171
+ onSuccess: async (data, vars, ctx) => {
172
+ queryClient.invalidateQueries({
173
+ queryKey: ["shared", "nearStorageCheck", ChainKeys.NEAR_MAINNET, vars.token, vars.accountId]
174
+ });
175
+ await mutationOptions?.onSuccess?.(data, vars, ctx);
176
+ }
189
177
  });
190
178
  }
179
+ function resolveNearStorageGate(chainKey, check) {
180
+ const isNear = chainKey === ChainKeys.NEAR_MAINNET;
181
+ const isChecking = check.isLoading;
182
+ const isRegistered = check.data;
183
+ return {
184
+ isNear,
185
+ needsRegistration: isNear && !isChecking && isRegistered === false,
186
+ blocksAction: isNear && (isChecking || isRegistered === false)
187
+ };
188
+ }
191
189
 
192
- // src/hooks/provider/useHubProvider.ts
193
- function useHubProvider() {
194
- const { sodax } = useSodaxContext();
195
- return sodax.hubProvider;
190
+ // src/hooks/shared/useNearStorageGate.ts
191
+ function useNearStorageGate({
192
+ dstChainKey,
193
+ token,
194
+ accountId,
195
+ walletProvider
196
+ }) {
197
+ const nearWalletProvider = walletProvider?.chainType === "NEAR" ? walletProvider : void 0;
198
+ const storageCheck = useNearStorageCheck({ params: { token, accountId, chainId: dstChainKey } });
199
+ const { mutateAsyncSafe: register, isPending: isRegistering } = useRegisterNearStorage();
200
+ const { isNear, needsRegistration, blocksAction } = resolveNearStorageGate(dstChainKey, storageCheck);
201
+ const registerStorage = async () => {
202
+ if (!nearWalletProvider || !token || !accountId) return void 0;
203
+ return register({ token, accountId, walletProvider: nearWalletProvider });
204
+ };
205
+ return {
206
+ isNear,
207
+ needsRegistration,
208
+ blocksAction,
209
+ isChecking: storageCheck.isLoading,
210
+ isRegistering,
211
+ registerStorage
212
+ };
196
213
  }
197
214
  var SESSION_KEY = (address) => `radfi_session_${address}`;
198
215
  function saveRadfiSession(address, session) {
@@ -254,7 +271,7 @@ function useRadfiAuth({
254
271
  clearRadfiSession(walletAddress);
255
272
  }
256
273
  throw new Error(
257
- "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."
274
+ "This wallet is already registered with Bound Exchange from another session. Please clear your browser storage for this site and try again, or wait for the previous session to expire."
258
275
  );
259
276
  }
260
277
  throw err;
@@ -262,6 +279,71 @@ function useRadfiAuth({
262
279
  }
263
280
  });
264
281
  }
282
+
283
+ // src/hooks/bitcoin/resolveBtcReadAddress.ts
284
+ function resolveBtcReadAddress(chainKey, address) {
285
+ if (chainKey !== ChainKeys.BITCOIN_MAINNET) return address;
286
+ return loadRadfiSession(address)?.tradingAddress ?? address;
287
+ }
288
+
289
+ // src/hooks/shared/useGetUserHubWalletAddress.ts
290
+ function useGetUserHubWalletAddress({
291
+ params,
292
+ queryOptions
293
+ } = {}) {
294
+ const { sodax } = useSodaxContext();
295
+ const spokeChainId = params?.spokeChainId;
296
+ const spokeAddress = params?.spokeAddress;
297
+ return useQuery({
298
+ queryKey: ["shared", "userHubWalletAddress", spokeChainId, spokeAddress],
299
+ queryFn: async () => {
300
+ if (!spokeChainId || !spokeAddress) {
301
+ throw new Error("Spoke chain id and address are required");
302
+ }
303
+ return await sodax.hubProvider.getUserHubWalletAddress(
304
+ resolveBtcReadAddress(spokeChainId, spokeAddress),
305
+ spokeChainId
306
+ );
307
+ },
308
+ enabled: !!spokeChainId && !!spokeAddress,
309
+ refetchInterval: false,
310
+ ...queryOptions
311
+ });
312
+ }
313
+ var REFETCH_INTERVAL_MS = 5e3;
314
+ function getXBalancesQueryOptions({ xService, xChainId, xTokens, address }) {
315
+ return {
316
+ // Pair symbol + address: readable in devtools, unique on-chain (symbol alone
317
+ // can collide — e.g. scam tokens copying legitimate ticker).
318
+ queryKey: ["shared", "xBalances", xChainId, xTokens.map((x) => [x.symbol, x.address]), address],
319
+ queryFn: async () => {
320
+ if (!xService) return {};
321
+ return xService.getBalances(address, xTokens);
322
+ },
323
+ enabled: !!xService && !!address && xTokens.length > 0,
324
+ refetchInterval: REFETCH_INTERVAL_MS
325
+ };
326
+ }
327
+ function useXBalances({
328
+ params,
329
+ queryOptions
330
+ } = {}) {
331
+ return useQuery({
332
+ ...getXBalancesQueryOptions({
333
+ xService: params?.xService,
334
+ xChainId: params?.xChainId,
335
+ xTokens: params?.xTokens ?? [],
336
+ address: params?.address
337
+ }),
338
+ ...queryOptions
339
+ });
340
+ }
341
+
342
+ // src/hooks/provider/useHubProvider.ts
343
+ function useHubProvider() {
344
+ const { sodax } = useSodaxContext();
345
+ return sodax.hubProvider;
346
+ }
265
347
  var REFRESH_INTERVAL = 5 * 60 * 1e3;
266
348
  function useRadfiSession(walletProvider) {
267
349
  const { sodax } = useSodaxContext();
@@ -432,7 +514,7 @@ function useRenewUtxos({
432
514
  const session = loadRadfiSession(userAddress);
433
515
  const accessToken = session?.accessToken || radfi.accessToken;
434
516
  if (!accessToken) {
435
- throw new Error("Radfi authentication required. Please login first.");
517
+ throw new Error("Bound Exchange authentication required. Please login first.");
436
518
  }
437
519
  const buildResult = await radfi.buildRenewUtxoTransaction({ userAddress, txIdVouts }, accessToken);
438
520
  const signedTx = await walletProvider.signTransaction(buildResult.base64Psbt, false);
@@ -460,7 +542,7 @@ function useRadfiWithdraw({
460
542
  const session = loadRadfiSession(userAddress);
461
543
  const accessToken = session?.accessToken || radfi.accessToken;
462
544
  if (!accessToken) {
463
- throw new Error("Radfi authentication required. Please login first.");
545
+ throw new Error("Bound Exchange authentication required. Please login first.");
464
546
  }
465
547
  const buildResult = await radfi.withdrawToUser({ userAddress, amount, tokenId, withdrawTo }, accessToken);
466
548
  const signedTx = await walletProvider.signTransaction(buildResult.base64Psbt, false);
@@ -581,7 +663,10 @@ function useUserReservesData({
581
663
  if (!spokeChainKey || !userAddress) {
582
664
  throw new Error("spokeChainKey and userAddress are required");
583
665
  }
584
- return sodax.moneyMarket.data.getUserReservesData(spokeChainKey, userAddress);
666
+ return sodax.moneyMarket.data.getUserReservesData(
667
+ spokeChainKey,
668
+ resolveBtcReadAddress(spokeChainKey, userAddress)
669
+ );
585
670
  },
586
671
  enabled: !!spokeChainKey && !!userAddress,
587
672
  refetchInterval: 5e3,
@@ -707,7 +792,10 @@ function useATokensBalances({
707
792
  throw new Error(`Invalid aToken address: ${aToken}`);
708
793
  }
709
794
  }
710
- const hubWalletAddress = await sodax.hubProvider.getUserHubWalletAddress(userAddress, spokeChainKey);
795
+ const hubWalletAddress = await sodax.hubProvider.getUserHubWalletAddress(
796
+ resolveBtcReadAddress(spokeChainKey, userAddress),
797
+ spokeChainKey
798
+ );
711
799
  return sodax.moneyMarket.data.getATokensBalances(aTokens, hubWalletAddress);
712
800
  },
713
801
  enabled: aTokens.length > 0 && !!spokeChainKey && !!userAddress,
@@ -742,7 +830,10 @@ function useUserFormattedSummary({
742
830
  }
743
831
  const [reserves, userReserves] = await Promise.all([
744
832
  sodax.moneyMarket.data.getReservesHumanized(),
745
- sodax.moneyMarket.data.getUserReservesHumanized(spokeChainKey, userAddress)
833
+ sodax.moneyMarket.data.getUserReservesHumanized(
834
+ spokeChainKey,
835
+ resolveBtcReadAddress(spokeChainKey, userAddress)
836
+ )
746
837
  ]);
747
838
  const formattedReserves = sodax.moneyMarket.data.formatReservesUSD(
748
839
  sodax.moneyMarket.data.buildReserveDataWithPrice(reserves)
@@ -2513,4 +2604,4 @@ function createSodaxQueryClient({
2513
2604
  });
2514
2605
  }
2515
2606
 
2516
- export { SodaxProvider, clearRadfiSession, createDecreaseLiquidityParamsProps, createDepositParamsProps, createSodaxQueryClient, createSupplyLiquidityParamsProps, createWithdrawParamsProps, getXBalancesQueryOptions, loadRadfiSession, saveRadfiSession, toResult, unwrapResult, useAToken, useATokensBalances, useApproveToken, useBackendAllMoneyMarketAssets, useBackendAllMoneyMarketBorrowers, useBackendIntentByHash, useBackendIntentByTxHash, useBackendMoneyMarketAsset, useBackendMoneyMarketAssetBorrowers, useBackendMoneyMarketAssetSuppliers, useBackendMoneyMarketPosition, useBackendOrderbook, useBackendSubmitSwapTx, useBackendSubmitSwapTxStatus, useBackendUserIntents, useBitcoinBalance, useBorrow, useBridge, useBridgeAllowance, useBridgeApprove, useCancelLimitOrder, useCancelSwap, useCancelUnstake, useClaim, useClaimRewards, useConvertedAssets, useCreateDecreaseLiquidityParams, useCreateDepositParams, useCreateLimitOrder, useCreateSupplyLiquidityParams, useCreateWithdrawParams, useDecreaseLiquidity, useDeriveUserWalletAddress, useDexAllowance, useDexApprove, useDexDeposit, useDexWithdraw, useEstimateGas, useExpiredUtxos, useFeeClaimSwap, useFetchAssetsBalances, useFundTradingWallet, useGetAutoSwapPreferences, useGetBridgeableAmount, useGetBridgeableTokens, useGetUserHubWalletAddress, useHubAssetBalances, useHubProvider, useInstantUnstake, useInstantUnstakeAllowance, useInstantUnstakeApprove, useInstantUnstakeRatio, useIsTokenApproved, useLiquidityAmounts, useMMAllowance, useMMApprove, useMigrateBaln, useMigrateIcxToSoda, useMigratebnUSD, useMigrationAllowance, useMigrationApprove, usePoolBalances, usePoolData, usePools, usePositionInfo, useQuote, useRadfiAuth, useRadfiSession, useRadfiWithdraw, useRenewUtxos, useRepay, useRequestTrustline, useReservesData, useReservesHumanized, useReservesList, useReservesUsdFormat, useRevertMigrateSodaToIcx, useSafeMutation, useSetSwapPreference, useSodaxContext, useStake, useStakeAllowance, useStakeApprove, useStakeRatio, useStakingConfig, useStakingInfo, useStatus, useStellarTrustlineCheck, useSupply, useSupplyLiquidity, useSwap, useSwapAllowance, useSwapApprove, useTradingWallet, useTradingWalletBalance, useUnstake, useUnstakeAllowance, useUnstakeApprove, useUnstakingInfo, useUnstakingInfoWithPenalty, useUserFormattedSummary, useUserReservesData, useWithdraw, useWithdrawHubAsset, useXBalances };
2607
+ export { SodaxProvider, clearRadfiSession, createDecreaseLiquidityParamsProps, createDepositParamsProps, createSodaxQueryClient, createSupplyLiquidityParamsProps, createWithdrawParamsProps, getXBalancesQueryOptions, loadRadfiSession, resolveNearStorageGate, saveRadfiSession, toResult, unwrapResult, useAToken, useATokensBalances, useApproveToken, useBackendAllMoneyMarketAssets, useBackendAllMoneyMarketBorrowers, useBackendIntentByHash, useBackendIntentByTxHash, useBackendMoneyMarketAsset, useBackendMoneyMarketAssetBorrowers, useBackendMoneyMarketAssetSuppliers, useBackendMoneyMarketPosition, useBackendOrderbook, useBackendSubmitSwapTx, useBackendSubmitSwapTxStatus, useBackendUserIntents, useBitcoinBalance, useBorrow, useBridge, useBridgeAllowance, useBridgeApprove, useCancelLimitOrder, useCancelSwap, useCancelUnstake, useClaim, useClaimRewards, useConvertedAssets, useCreateDecreaseLiquidityParams, useCreateDepositParams, useCreateLimitOrder, useCreateSupplyLiquidityParams, useCreateWithdrawParams, useDecreaseLiquidity, useDeriveUserWalletAddress, useDexAllowance, useDexApprove, useDexDeposit, useDexWithdraw, useEstimateGas, useExpiredUtxos, useFeeClaimSwap, useFetchAssetsBalances, useFundTradingWallet, useGetAutoSwapPreferences, useGetBridgeableAmount, useGetBridgeableTokens, useGetUserHubWalletAddress, useHubAssetBalances, useHubProvider, useInstantUnstake, useInstantUnstakeAllowance, useInstantUnstakeApprove, useInstantUnstakeRatio, useIsTokenApproved, useLiquidityAmounts, useMMAllowance, useMMApprove, useMigrateBaln, useMigrateIcxToSoda, useMigratebnUSD, useMigrationAllowance, useMigrationApprove, useNearStorageCheck, useNearStorageGate, usePoolBalances, usePoolData, usePools, usePositionInfo, useQuote, useRadfiAuth, useRadfiSession, useRadfiWithdraw, useRegisterNearStorage, useRenewUtxos, useRepay, useRequestTrustline, useReservesData, useReservesHumanized, useReservesList, useReservesUsdFormat, useRevertMigrateSodaToIcx, useSafeMutation, useSetSwapPreference, useSodaxContext, useStake, useStakeAllowance, useStakeApprove, useStakeRatio, useStakingConfig, useStakingInfo, useStatus, useStellarTrustlineCheck, useSupply, useSupplyLiquidity, useSwap, useSwapAllowance, useSwapApprove, useTradingWallet, useTradingWalletBalance, useUnstake, useUnstakeAllowance, useUnstakeApprove, useUnstakingInfo, useUnstakingInfoWithPenalty, useUserFormattedSummary, useUserReservesData, useWithdraw, useWithdrawHubAsset, useXBalances };
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "access": "public"
6
6
  },
7
7
  "license": "MIT",
8
- "version": "2.0.0-rc.10",
8
+ "version": "2.0.0-rc.12",
9
9
  "description": "React hooks for building dApps on the SODAX cross-chain DeFi platform",
10
10
  "keywords": [
11
11
  "sodax",
@@ -34,7 +34,7 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "viem": "2.29.2",
37
- "@sodax/sdk": "2.0.0-rc.10"
37
+ "@sodax/sdk": "2.0.0-rc.12"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@arethetypeswrong/cli": "0.17.4",
@@ -7,7 +7,7 @@ const HOOKS_DIR = resolve(fileURLToPath(import.meta.url), '..');
7
7
 
8
8
  /**
9
9
  * Manifest of every mutation hook. `nativeThrow: true` marks hooks whose underlying SDK methods
10
- * throw natively (Radfi APIs) and so don't need `unwrapResult`.
10
+ * throw natively (Bound Exchange APIs) and so don't need `unwrapResult`.
11
11
  *
12
12
  * To add a new mutation hook, add its path here. The friction is intentional — it forces explicit
13
13
  * registration so the contract is enforced from day one.
@@ -41,6 +41,7 @@ const HOOKS: Array<{ path: string; nativeThrow?: true }> = [
41
41
  { path: 'partner/useSetSwapPreference.ts' },
42
42
  { path: 'recovery/useWithdrawHubAsset.ts' },
43
43
  { path: 'shared/useEstimateGas.ts' },
44
+ { path: 'shared/useRegisterNearStorage.ts', nativeThrow: true },
44
45
  { path: 'staking/useCancelUnstake.ts' },
45
46
  { path: 'staking/useClaim.ts' },
46
47
  { path: 'staking/useInstantUnstake.ts' },
@@ -0,0 +1,54 @@
1
+ import { ChainKeys } from '@sodax/sdk';
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import type { RadfiSession } from './useRadfiAuth.js';
4
+ import { loadRadfiSession } from './useRadfiAuth.js';
5
+ import { resolveBtcReadAddress } from './resolveBtcReadAddress.js';
6
+
7
+ // resolveBtcReadAddress reads the Bound Exchange session via loadRadfiSession (localStorage-backed).
8
+ // The dapp-kit test env is `node` (no localStorage), so mock the session lookup to drive each branch.
9
+ vi.mock('./useRadfiAuth.js', () => ({
10
+ loadRadfiSession: vi.fn(),
11
+ }));
12
+
13
+ const mockLoadRadfiSession = vi.mocked(loadRadfiSession);
14
+
15
+ const PERSONAL_ADDRESS = 'bc1qpersonal00000000000000000000000000000';
16
+ const TRADING_ADDRESS = 'bc1qtrading000000000000000000000000000000';
17
+
18
+ const session = (tradingAddress: string): RadfiSession => ({
19
+ accessToken: 'access',
20
+ refreshToken: 'refresh',
21
+ tradingAddress,
22
+ publicKey: 'pub',
23
+ });
24
+
25
+ describe('resolveBtcReadAddress', () => {
26
+ beforeEach(() => {
27
+ mockLoadRadfiSession.mockReset();
28
+ });
29
+
30
+ it('passes the address through unchanged for non-Bitcoin chains (no session lookup)', () => {
31
+ const result = resolveBtcReadAddress(ChainKeys.ARBITRUM_MAINNET, PERSONAL_ADDRESS);
32
+
33
+ expect(result).toBe(PERSONAL_ADDRESS);
34
+ expect(mockLoadRadfiSession).not.toHaveBeenCalled();
35
+ });
36
+
37
+ it('returns the trading address from the local session for Bitcoin', () => {
38
+ mockLoadRadfiSession.mockReturnValue(session(TRADING_ADDRESS));
39
+
40
+ const result = resolveBtcReadAddress(ChainKeys.BITCOIN_MAINNET, PERSONAL_ADDRESS);
41
+
42
+ expect(result).toBe(TRADING_ADDRESS);
43
+ // The session is keyed by the personal address.
44
+ expect(mockLoadRadfiSession).toHaveBeenCalledWith(PERSONAL_ADDRESS);
45
+ });
46
+
47
+ it('falls back to the personal address for Bitcoin when there is no session', () => {
48
+ mockLoadRadfiSession.mockReturnValue(null);
49
+
50
+ const result = resolveBtcReadAddress(ChainKeys.BITCOIN_MAINNET, PERSONAL_ADDRESS);
51
+
52
+ expect(result).toBe(PERSONAL_ADDRESS);
53
+ });
54
+ });
@@ -0,0 +1,21 @@
1
+ import { ChainKeys, type SpokeChainKey } from '@sodax/sdk';
2
+ import { loadRadfiSession } from './useRadfiAuth.js';
3
+
4
+ /**
5
+ * Resolve a spoke address for READ queries (positions, balances, hub-wallet display).
6
+ *
7
+ * Bitcoin positions live under the Bound Exchange trading-wallet-derived hub wallet, so for Bitcoin we
8
+ * resolve to the trading address from the locally-persisted Bound Exchange session. This is intentionally
9
+ * a LOCAL lookup (no network call): a transient Bound Exchange API outage can never make a real position
10
+ * read as empty, and reads never throw "Trading wallet not found". When there is no session, the
11
+ * user has no Bitcoin position to show, so the original (personal) address is returned — deriving
12
+ * an empty hub wallet, which is correct. Non-Bitcoin chains pass through unchanged.
13
+ *
14
+ * The WRITE path resolves the trading address authoritatively in the SDK
15
+ * (`getEffectiveWalletAddress`, called from the money-market create*Intent flows); reads
16
+ * deliberately use this lighter, fail-safe path.
17
+ */
18
+ export function resolveBtcReadAddress(chainKey: SpokeChainKey, address: string): string {
19
+ if (chainKey !== ChainKeys.BITCOIN_MAINNET) return address;
20
+ return loadRadfiSession(address)?.tradingAddress ?? address;
21
+ }
@@ -11,7 +11,7 @@ export type UseFundTradingWalletVars = {
11
11
  };
12
12
 
13
13
  /**
14
- * React hook for funding the user's Radfi trading wallet from their personal Bitcoin wallet.
14
+ * React hook for funding the user's Bound Exchange trading wallet from their personal Bitcoin wallet.
15
15
  * Pure mutation: pass `{ amount, walletProvider }` to `mutate({...})`. Returns the broadcast tx
16
16
  * id on success.
17
17
  */
@@ -45,7 +45,7 @@ export function clearRadfiSession(address: string): void {
45
45
  }
46
46
 
47
47
  /**
48
- * React hook for authenticating with Radfi via BIP322-signed message. Pure mutation: pass
48
+ * React hook for authenticating with Bound Exchange via BIP322-signed message. Pure mutation: pass
49
49
  * `{ walletProvider }` to `mutate({...})`. The hook itself takes no arguments other than the
50
50
  * structural `mutationOptions` slot.
51
51
  */
@@ -95,7 +95,7 @@ export function useRadfiAuth({
95
95
  }
96
96
 
97
97
  throw new Error(
98
- 'This wallet is already registered with Radfi from another session. ' +
98
+ 'This wallet is already registered with Bound Exchange from another session. ' +
99
99
  'Please clear your browser storage for this site and try again, ' +
100
100
  'or wait for the previous session to expire.',
101
101
  );
@@ -19,7 +19,7 @@ type WithdrawResult = {
19
19
  };
20
20
 
21
21
  /**
22
- * React hook for withdrawing BTC from the user's Radfi trading wallet back to their personal
22
+ * React hook for withdrawing BTC from the user's Bound Exchange trading wallet back to their personal
23
23
  * Bitcoin wallet. Pure mutation: pass all inputs (including the wallet provider) to
24
24
  * `mutate({...})`.
25
25
  */
@@ -44,7 +44,7 @@ export function useRadfiWithdraw({
44
44
  const accessToken = session?.accessToken || radfi.accessToken;
45
45
 
46
46
  if (!accessToken) {
47
- throw new Error('Radfi authentication required. Please login first.');
47
+ throw new Error('Bound Exchange authentication required. Please login first.');
48
48
  }
49
49
 
50
50
  const buildResult = await radfi.withdrawToUser({ userAddress, amount, tokenId, withdrawTo }, accessToken);
@@ -12,7 +12,7 @@ export type UseRenewUtxosVars = {
12
12
  };
13
13
 
14
14
  /**
15
- * React hook for renewing expired UTXOs in the user's Radfi trading wallet. Pure mutation: pass
15
+ * React hook for renewing expired UTXOs in the user's Bound Exchange trading wallet. Pure mutation: pass
16
16
  * `{ txIdVouts, walletProvider }` to `mutate({...})`.
17
17
  */
18
18
  export function useRenewUtxos({
@@ -32,7 +32,7 @@ export function useRenewUtxos({
32
32
  const accessToken = session?.accessToken || radfi.accessToken;
33
33
 
34
34
  if (!accessToken) {
35
- throw new Error('Radfi authentication required. Please login first.');
35
+ throw new Error('Bound Exchange authentication required. Please login first.');
36
36
  }
37
37
 
38
38
  const buildResult = await radfi.buildRenewUtxoTransaction({ userAddress, txIdVouts }, accessToken);
@@ -5,7 +5,7 @@ type UseTradingWalletReturn = {
5
5
  };
6
6
 
7
7
  /**
8
- * Returns the Radfi trading wallet address from the persisted session.
8
+ * Returns the Bound Exchange trading wallet address from the persisted session.
9
9
  * Trading wallet is created automatically during authentication — no API call needed.
10
10
  */
11
11
  export function useTradingWallet(walletAddress: string | undefined): UseTradingWalletReturn {
@@ -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
+ }
@@ -1,7 +1,6 @@
1
1
  import { useMemo, type ReactNode, type ReactElement } from 'react';
2
- import { Sodax, type SodaxConfig } from '@sodax/sdk';
2
+ import { Sodax, type SodaxOptions } from '@sodax/sdk';
3
3
  import { SodaxContext } from '@/contexts/index.js';
4
- import type { DeepPartial } from '@sodax/sdk';
5
4
 
6
5
  interface SodaxProviderProps {
7
6
  children: ReactNode;
@@ -17,7 +16,7 @@ interface SodaxProviderProps {
17
16
  * <SodaxProvider config={config}>...</SodaxProvider>
18
17
  * ```
19
18
  */
20
- config?: DeepPartial<SodaxConfig>;
19
+ config?: SodaxOptions;
21
20
  }
22
21
 
23
22
  /** Root provider for `@sodax/dapp-kit`. Must be paired with `QueryClientProvider`. */
@@ -1 +1,2 @@
1
1
  export * from './dex-utils.js';
2
+ export * from './nearStorageGate.js';
@@ -0,0 +1,46 @@
1
+ import { ChainKeys } from '@sodax/sdk';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { resolveNearStorageGate } from './nearStorageGate.js';
4
+
5
+ describe('resolveNearStorageGate', () => {
6
+ it('destination not NEAR → never gates', () => {
7
+ expect(resolveNearStorageGate(ChainKeys.POLYGON_MAINNET, { isLoading: false, data: false })).toEqual({
8
+ isNear: false,
9
+ needsRegistration: false,
10
+ blocksAction: false,
11
+ });
12
+ });
13
+
14
+ it('NEAR, check in flight → blocks action but does not yet prompt registration', () => {
15
+ expect(resolveNearStorageGate(ChainKeys.NEAR_MAINNET, { isLoading: true, data: undefined })).toEqual({
16
+ isNear: true,
17
+ needsRegistration: false,
18
+ blocksAction: true,
19
+ });
20
+ });
21
+
22
+ it('NEAR, check resolved, not registered → prompt registration and block action', () => {
23
+ expect(resolveNearStorageGate(ChainKeys.NEAR_MAINNET, { isLoading: false, data: false })).toEqual({
24
+ isNear: true,
25
+ needsRegistration: true,
26
+ blocksAction: true,
27
+ });
28
+ });
29
+
30
+ it('NEAR, check resolved, registered → no gate', () => {
31
+ expect(resolveNearStorageGate(ChainKeys.NEAR_MAINNET, { isLoading: false, data: true })).toEqual({
32
+ isNear: true,
33
+ needsRegistration: false,
34
+ blocksAction: false,
35
+ });
36
+ });
37
+
38
+ it('NEAR, query disabled (unresolved, not checking) → does not block', () => {
39
+ // isLoading is false for a disabled query; data stays undefined → action must not be blocked.
40
+ expect(resolveNearStorageGate(ChainKeys.NEAR_MAINNET, { isLoading: false, data: undefined })).toEqual({
41
+ isNear: true,
42
+ needsRegistration: false,
43
+ blocksAction: false,
44
+ });
45
+ });
46
+ });
@@ -0,0 +1,39 @@
1
+ import { ChainKeys, type SpokeChainKey } from '@sodax/sdk';
2
+ import type { UseQueryResult } from '@tanstack/react-query';
3
+
4
+ /** The subset of a `useNearStorageCheck` result the gate reads. A full `UseQueryResult<boolean>` satisfies it. */
5
+ export type NearStorageCheckResult = Pick<UseQueryResult<boolean>, 'isLoading' | 'data'>;
6
+
7
+ /** UI gate state for NEP-141 storage registration on NEAR. */
8
+ export interface NearStorageGateState {
9
+ /** Destination is NEAR — the only chain with a NEP-141 storage-registration prerequisite. */
10
+ isNear: boolean;
11
+ /** Show the "register storage" action: the check resolved and the recipient is not registered. */
12
+ needsRegistration: boolean;
13
+ /**
14
+ * Keep the downstream action (swap/bridge/borrow/withdraw) disabled: the NEAR gate is unresolved
15
+ * (still checking) or unmet (needs registration).
16
+ */
17
+ blocksAction: boolean;
18
+ }
19
+
20
+ /**
21
+ * Derives the NEP-141 storage-registration UI gate state for a flow that delivers a token to a user
22
+ * on NEAR — NEAR's analogue of the Stellar trustline gate. Unwrapped (no hook): pass the destination
23
+ * `chainKey` and the `useNearStorageCheck` result; the util owns the `=== NEAR` test and reads
24
+ * `isLoading` (NOT `isPending`, which stays `true` for a disabled query and would block forever).
25
+ *
26
+ * `blocksAction` deliberately also covers the in-flight check window so the action can't be
27
+ * triggered before registration status is known; `needsRegistration` only flips once the check has
28
+ * resolved (so the "register" button isn't shown speculatively while still checking).
29
+ */
30
+ export function resolveNearStorageGate(chainKey: SpokeChainKey, check: NearStorageCheckResult): NearStorageGateState {
31
+ const isNear = chainKey === ChainKeys.NEAR_MAINNET;
32
+ const isChecking = check.isLoading;
33
+ const isRegistered = check.data;
34
+ return {
35
+ isNear,
36
+ needsRegistration: isNear && !isChecking && isRegistered === false,
37
+ blocksAction: isNear && (isChecking || isRegistered === false),
38
+ };
39
+ }