WDK logoWDK documentation

API Reference

Complete API reference for @tetherto/wdk-react-native-core

This reference covers @tetherto/wdk-react-native-core@1.0.0-beta.21.

ExportTypeDescription
WdkAppProviderComponentRoot provider for WDK initialization
useWdkAppHookApp-level state (discriminated union)
useWalletManagerHookWallet lifecycle (create, restore, lock, unlock)
useAccountHookAccount operations (send, sign, verify, estimateFee)
useProtocolHookProtocol method calls through the active worklet account
useModuleHookNamed generic-module calls and event subscriptions
ModuleServiceClassLow-level static gateway for generic-module calls and events
useAddressesHookLoad and query wallet addresses
useBalanceHookSingle asset balance with TanStack Query
useBalancesForWalletHookBulk balance fetch for multiple assets
useBalancesForWalletsHookBulk balance fetch for multiple assets across account indices
useRefreshBalanceHookInvalidate and refetch balances
BaseAssetClassDefault IAsset implementation
validateMnemonicUtilityValidate BIP39 mnemonic phrases
balanceQueryKeysUtilityTanStack Query key factory
createSecureStorageFunctionAccess keychain-backed wallet credentials directly
DEFAULT_IDENTIFIERConstantReserved identifier for the predecessor's no-identifier storage slot
SecureStorageError and subclassesClassesStorage validation, timeout, and keychain failures
SecureStorageTypeDirect credential-storage interface
WdkAppProviderPropsTypeProps for WdkAppProvider
WdkAppContextValueTypeProvider context containing the lifecycle state
UseWdkAppResultTypeReturn type of useWdkApp, including manual reset methods
UseAccountParamsTypeParameters for useAccount
UseAccountReturnTypeReturn type of useAccount
UseProtocolParamsTypeParameters for useProtocol
UseModuleProxyTypeTyped asynchronous module proxy returned by useModule
ModuleEventListenerTypeListener for parsed module-event payloads
UseAddressesReturnTypeReturn type of useAddresses
UseWalletManagerResultTypeReturn type of useWalletManager

WdkAppProvider

The root provider component that orchestrates WDK initialization. Wraps your app with TanStack Query, Zustand stores, and the worklet runtime.

Must be placed at the root of your component tree. All hooks in this library require WdkAppProvider as an ancestor.

Props

PropTypeDefaultDescription
bundleBundleConfigrequiredWorklet bundle configuration
wdkConfigsWdkConfigsrequiredNetwork and protocol configurations
childrenReact.ReactNoderequiredChild components

The app owns wallet identity, background-locking behavior, and biometric or other authentication checks.

Example

import { WdkAppProvider } from '@tetherto/wdk-react-native-core'
import { bundle } from './.wdk'

const wdkConfigs = {
  networks: {
    ethereum: {
      blockchain: 'ethereum',
      config: {
        chainId: 1,
        provider: 'https://eth.drpc.org',
      },
    },
  },
}

export default function App() {
  return (
    <WdkAppProvider
      bundle={{ bundle }}
      wdkConfigs={wdkConfigs}
    >
      <Navigation />
    </WdkAppProvider>
  )
}

useWdkApp

Hook to access app-level state. Returns a discriminated union representing the current state of the WDK lifecycle.

Must be used within WdkAppProvider.

Returns

PropertyTypeDescription
stateWdkAppStateCurrent app state (discriminated union)
reinitializeWdk() => Promise<void>Request reinitialization of an already started and initialized worklet
resetWallets(networkKeys: string[]) => Promise<void>Reset wallet modules for exact keys from wdkConfigs.networks

Both methods can resolve without completing the requested reset. reinitializeWdk() returns early when lifecycle preconditions are not met and catches initialization failures. resetWallets() ignores an empty list or unknown network keys, returns early while loading, and catches underlying reset failures. Treat the resolved promise as request completion, not proof of a reset; observe the expected lifecycle transition and verify the resulting module state.

For example, with networks: { sepolia: { blockchain: 'ethereum', ... } }, call resetWallets(['sepolia']). Passing ['ethereum'] does nothing because ethereum is the blockchain field, not the configured network key.

WdkAppState

The state property is a discriminated union on the status field:

StatusAdditional FieldsDescription
'INITIALIZING'-Worklet is starting, or the library does not yet know enough to select a more specific state
'REINITIALIZING'-reinitializeWdk() is manually reinitializing the worklet
'NO_WALLET'-Worklet is ready, no wallet has been created
'LOCKED'walletId?: stringNo wallet is unlocked; the optional ID is a hint when a specific wallet is being targeted
'READY'walletId: stringThe active wallet identity matches the wallet loaded in the worklet
'ERROR'error: ErrorWorklet initialization or a wallet lifecycle operation failed

Example

import { useWdkApp, useWalletManager } from '@tetherto/wdk-react-native-core'

function AppRouter({
  walletId,
  authenticate,
}: {
  walletId: string
  authenticate: () => Promise<boolean>
}) {
  const { state } = useWdkApp()
  const { createWallet, unlock } = useWalletManager()

  const create = async () => {
    if (await authenticate()) await createWallet(walletId)
  }

  const open = async () => {
    if (await authenticate()) await unlock(walletId)
  }

  switch (state.status) {
    case 'INITIALIZING':
    case 'REINITIALIZING':
      return <LoadingScreen />
    case 'NO_WALLET':
      return <Button title="Create Wallet" onPress={create} />
    case 'LOCKED':
      return <Button title="Unlock" onPress={open} />
    case 'READY':
      return <MainApp walletId={state.walletId} />
    case 'ERROR':
      return <ErrorScreen error={state.error} />
  }
}

useWalletManager

Hook for wallet lifecycle operations: create, restore, lock, unlock, delete, and manage wallets.

Returns

PropertyTypeDescription
activeWalletIdstring | nullCurrently active wallet identifier
status'LOCKED' | 'UNLOCKED' | 'NO_WALLET' | 'LOADING' | 'ERROR'Current wallet status
walletsWalletInfo[]List of wallets managed by the device
createWallet(walletId: string) => Promise<void>Create and activate a new wallet with the supplied identity
restoreWallet(mnemonic: string, walletId: string) => Promise<string>Restore a wallet from a seed phrase. Returns the wallet ID
deleteWallet(walletId: string) => Promise<void>Delete stored data, send a one-way full WDK disposal request, and reset local lifecycle state; the worklet and IPC keep running
lock() => Promise<void>When a wallet is active, send a one-way full WDK disposal request and clear local wallet state; the worklet and IPC keep running
unlock(walletId: string) => Promise<void>Explicitly decrypt and load the selected wallet; the ID has no implicit fallback
switchWallet(walletId: string) => Promise<void>Under one mutex, lock the current wallet and then unlock the selected wallet; a failed unlock leaves the prior wallet locked
generateMnemonic(wordCount?: 12 | 24) => Promise<string>Generate a new BIP39 mnemonic phrase
getMnemonic(walletId: string) => Promise<string | null>Reconstruct the plaintext mnemonic from the selected wallet's stored encrypted entropy and encryption key; returns null when either is missing
createTemporaryWallet(walletId: string, mnemonic?: string) => Promise<string>Create an in-memory wallet for previewing addresses. Returns the temporary wallet ID
clearTemporaryWallet() => voidClear the temporary wallet session. If it is active, also send a one-way full WDK disposal request and clear shared initialization and address state
clearCache() => voidClear cached balance data

Only one wallet can be active. Await lock() before creating or restoring a different identity. To select an existing wallet, use switchWallet() or lock and then unlock it explicitly. Switching locks the previous wallet first; a failed unlock leaves it locked.

createWallet(), restoreWallet(), deleteWallet(), lock(), unlock(), switchWallet(), and createTemporaryWallet() share a fail-fast mutex: overlapping calls reject instead of queuing, and a timeout does not cancel the operation. clearTemporaryWallet() is unguarded; serialize it with lifecycle calls because clearing an active temporary wallet also resets shared lifecycle state.

Core does not prompt for authentication. Authenticate in your app before lifecycle or key-access calls. When backgrounding, close the authenticated UI immediately. If an in-flight operation makes lock() reject, wait for it to settle and retry the lock before treating the SDK session as locked.

Deleting even an inactive wallet resets shared lifecycle state after deleting its credentials. A storage failure can leave partial deletion and skip that reset. Check the target's remaining credentials before retrying; a rejected deletion does not mean the wallet was left intact.

Locking an active wallet, successfully deleting a wallet, and clearing an active temporary wallet request full WDK disposal without waiting for worklet cleanup. The worklet and IPC keep running, so completion is not proof that every secret copy was erased. If wallet initialization fails, restart the app before retrying; a rejected initialization does not confirm cleanup.

Advanced Crypto Methods

These methods provide lower-level access to wallet cryptographic operations. Most apps won't need these directly.

PropertyTypeDescription
getEncryptionKey(walletId: string) => Promise<string | null>Get the selected wallet's base64 encryption key
getEncryptedSeed(walletId: string) => Promise<string | null>Get the base64 encrypted seed from storage
getEncryptedEntropy(walletId: string) => Promise<string | null>Get the base64 encrypted entropy from storage
generateEntropyAndEncrypt(wordCount?: 12 | 24) => Promise<{ encryptionKey: string, encryptedSeedBuffer: string, encryptedEntropyBuffer: string }>Generate and encrypt entropy for wallet creation; all returned strings are base64
getMnemonicFromEntropy(encryptedEntropy: string, encryptionKey: string) => Promise<{ mnemonic: string }>Derive a mnemonic from base64 encrypted entropy and key strings
getSeedAndEntropyFromMnemonic(mnemonic: string) => Promise<{ encryptionKey: string, encryptedSeedBuffer: string, encryptedEntropyBuffer: string }>Get encrypted seed and entropy from a mnemonic phrase; all returned strings are base64

Returned secret values are immutable JavaScript strings. Discard references when finished; method completion does not confirm that every copy was erased.

Example

import { useWalletManager } from '@tetherto/wdk-react-native-core'

function OnboardingScreen({
  walletId,
  authenticate,
  presentMnemonicForBackup,
}: {
  walletId: string
  authenticate: () => Promise<boolean>
  presentMnemonicForBackup: (mnemonic: string) => Promise<void>
}) {
  const { createWallet, restoreWallet, generateMnemonic } = useWalletManager()

  const handleCreate = async () => {
    if (!(await authenticate())) return
    await createWallet(walletId)
  }

  const handleRestore = async (mnemonic: string) => {
    if (!(await authenticate())) return
    await restoreWallet(mnemonic, walletId)
  }

  const handlePreview = async () => {
    if (!(await authenticate())) return
    const mnemonic = await generateMnemonic(12)
    await presentMnemonicForBackup(mnemonic)
  }

  return (
    <View>
      <Button title="Create Wallet" onPress={handleCreate} />
      <Button title="Generate Mnemonic" onPress={handlePreview} />
    </View>
  )
}

useAccount

Hook to interact with a specific blockchain account. Always returns an object - check address or account for readiness.

Parameters

ParameterTypeDescription
params.networkstringNetwork identifier (e.g., 'ethereum', 'bitcoin', 'tron')
params.accountIndexnumberAccount index (0-based, following BIP-44 derivation)

Returns UseAccountReturn<T>

PropertyTypeDescription
addressstring | nullThe derived public address (null if not loaded yet)
isLoadingbooleantrue if the account address is being derived
errorError | nullError if address derivation failed
account{ accountIndex, network, walletId } | nullAccount identifier (null if no active wallet or address not loaded)
getBalance(tokens: IAsset[]) => Promise<BalanceFetchResult[]>Fetch balances directly from the network (no cache)
send(params: TransactionParams) => Promise<TransactionResult>Send a transaction (native or token transfer)
sign(message: string) => Promise<UseAccountResponse & { signature: string }>Sign a UTF-8 message with the account's private key
verify(message: string, signature: string) => Promise<UseAccountResponse & { verified: boolean }>Verify a signature
estimateFee(params: TransactionParams) => Promise<Omit<TransactionResult, 'hash'>>Estimate the fee for a transaction
extension() => TAccess chain-specific methods not in the core API

UseAccountResponse

Base response type for account operations:

interface UseAccountResponse {
  success: boolean
  error?: string
}

TransactionParams

FieldTypeDescription
tostringRecipient address
assetIAssetAsset to send
amountstringAmount in smallest denomination (e.g., wei, satoshi)

TransactionResult

Extends UseAccountResponse:

FieldTypeDescription
successbooleanWhether the transaction succeeded
hashstringTransaction hash
feestringFee paid
errorstring?Error message (only if success is false)

Example

This example uses Ethereum mainnet. Configure the ethereum wallet for that chain and pass your intended, validated destination as recipientAddress before quoting or sending.

import { Button, Text, View } from 'react-native'
import { useAccount, BaseAsset } from '@tetherto/wdk-react-native-core'

const usdt = new BaseAsset({
  id: 'usdt-ethereum',
  network: 'ethereum',
  symbol: 'USDT',
  name: 'Tether USD',
  decimals: 6,
  isNative: false,
  address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
})

function SendScreen({ recipientAddress }: { recipientAddress: string }) {
  const { address, isLoading, send, estimateFee } = useAccount({ network: 'ethereum', accountIndex: 0 })

  if (isLoading) return <Text>Deriving address...</Text>
  if (!address) return <Text>No account available</Text>

  const handleSend = async () => {
    const txParams = {
      to: recipientAddress,
      asset: usdt,
      amount: '1000000', // 1 USDt (6 decimals)
    }

    // Estimate fee first
    const estimate = await estimateFee(txParams)
    console.log('Estimated fee:', estimate.fee)

    // Send the transaction
    const result = await send(txParams)
    if (result.success) {
      console.log('TX hash:', result.hash)
    } else {
      console.error('TX failed:', result.error)
    }
  }

  return (
    <View>
      <Text>Address: {address}</Text>
      <Button title="Send 1 USDt" onPress={handleSend} />
    </View>
  )
}

Extension API

Use extension() to access chain-specific methods that aren't part of the core interface. The returned proxy waits for wallet initialization automatically:

// Type parameter provides type-safe access to chain-specific methods
const { extension } = useAccount<{ getTransfers: () => Promise<Transfer[]> }>({
  network: 'bitcoin',
  accountIndex: 0,
})

// Safe to call at any time - waits for wallet initialization internally
const btcApi = extension()
const transfers = await btcApi.getTransfers()

Account extension calls preserve a legitimate null result from the underlying account method. Handle nullable outcomes according to that module's return contract; malformed JSON still rejects.

useProtocol

Hook to call configured protocol modules through the active WDK worklet account. It returns a typed proxy, so each method you access is forwarded to the protocol registered for the requested account, network, type, and name.

Parameters

ParameterTypeDescription
params.networkstringNetwork identifier configured in wdkConfigs
params.accountIndexnumberAccount index to use for the protocol call
params.protocolType'bridge' | 'swap' | 'swidge' | 'lending' | 'fiat'Protocol category
params.protocolNamestringRegistered protocol name

Returns

Returns T, a proxy for the protocol methods you type with the hook generic. Each method returns a promise from the worklet call.

If no active account is available, protocol method calls log an error and return undefined. A successfully called protocol method can return null when that is its underlying contract.

The swidge protocol type requires a non-empty protocolName and forwards calls through the matching Swidge protocol wrapper on the selected account.

Example

import { useProtocol } from '@tetherto/wdk-react-native-core'

type SwapProtocol = {
  quoteSwap: (params: {
    tokenIn: string
    tokenOut: string
    tokenInAmount: string
  }) => Promise<{ tokenOutAmount: string; fee: string }>
}

function SwapQuoteScreen() {
  const swap = useProtocol<SwapProtocol>({
    network: 'ethereum',
    accountIndex: 0,
    protocolType: 'swap',
    protocolName: 'velora',
  })

  const loadQuote = async () => {
    const quote = await swap.quoteSwap({
      tokenIn: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
      tokenOut: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
      tokenInAmount: '1000000',
    })

    console.log('Quoted output:', quote?.tokenOutAmount)
  }

  return <Button title="Quote" onPress={loadQuote} />
}

useModule

Hook that creates a typed proxy for one named generic module in the active HRPC worklet. Every method access is forwarded through ModuleService.callModule(). The proxy also exposes on(event, listener) for module events.

Parameters

ParameterTypeDescription
moduleNamestringName shared by the bundler's build-time modules map and runtime wdkConfigs.modules map

Returns

Returns UseModuleProxy<T>. Define module methods as asynchronous in T because every proxy call crosses the worklet boundary and returns a promise.

UseModuleProxy
type UseModuleProxy<T extends object> = T & {
  on: (event: string, listener: ModuleEventListener) => () => void
}

type ModuleEventListener = (payload: unknown) => void

Example

Call A Named Module
import { useEffect } from 'react'
import { useModule } from '@tetherto/wdk-react-native-core'

type PreferencesModule = {
  getTheme: () => Promise<'light' | 'dark'>
  setTheme: (theme: 'light' | 'dark') => Promise<void>
}

function PreferencesScreen() {
  const preferences = useModule<PreferencesModule>('preferences')

  useEffect(() => {
    return preferences.on('changed', payload => {
      console.log('Preferences changed:', payload)
    })
  }, [preferences])

  // Module calls are asynchronous.
  // const theme = await preferences.getTheme()
}

Subscriptions are removed automatically on unmount or when moduleName changes. Calling the returned unsubscribe function removes a subscription earlier.


ModuleService

Low-level static service used by useModule(). Use it when a hook is not appropriate but the React Native worklet has already initialized.

MethodSignatureDescription
callModule()callModule(moduleName: string, method: string, ...args: unknown[]): Promise<unknown>Serialize positional arguments, call the named module method, and parse the JSON response
onModuleEvent()onModuleEvent(moduleName: string, event: string, listener: ModuleEventListener): () => voidSubscribe to one named event and return an unsubscribe function

callModule() requires non-empty module and method names plus an initialized worklet. It returns undefined when the worklet response is absent or empty. Arguments and results must be safely JSON-serializable; bigint arguments are serialized as strings, while circular and non-standard-prototype objects are rejected.

onModuleEvent() parses JSON event payloads when possible and otherwise delivers the raw string. Listener failures do not stop delivery to other listeners.


useAddresses

Hook to load and query wallet addresses across all networks.

Returns

PropertyTypeDescription
dataAddressInfo[] | undefinedFlattened array of all loaded addresses for the active wallet
isLoadingbooleantrue if any address is currently being loaded
loadAddresses(accountIndices: number[], networks?: string[]) => Promise<AddressInfoResult[]>Fetch addresses for given account indices. Returns results for each address load attempt
getAddressesForNetwork(network: string) => Array<{ address: string, accountIndex: number }>Filter addresses by network
getAccountInfoFromAddress(address: string) => AddressInfo | undefinedReverse-lookup: resolve an address string to its account info (case-insensitive)

AddressInfo

FieldTypeDescription
addressstringThe public address
networkstringNetwork identifier
accountIndexnumberAccount index

AddressInfoResult

Discriminated union indicating success or failure for each address load:

type AddressInfoResult =
  | { network: string; accountIndex: number; success: true; address: string }
  | { network: string; accountIndex: number; success: false; reason: Error }

Example

import { useAddresses } from '@tetherto/wdk-react-native-core'

function AddressesScreen() {
  const { data, isLoading, loadAddresses, getAddressesForNetwork } = useAddresses()

  useEffect(() => {
    loadAddresses([0]).then((results) => {
      const failed = results.filter((r) => !r.success)
      if (failed.length > 0) {
        console.warn('Some addresses failed to load:', failed)
      }
    })
  }, [])

  const ethAddresses = getAddressesForNetwork('ethereum')

  if (isLoading) return <Text>Loading addresses...</Text>

  return (
    <View>
      {ethAddresses.map(({ address, accountIndex }) => (
        <Text key={address}>Account {accountIndex}: {address}</Text>
      ))}
    </View>
  )
}

useBalance

Hook to fetch a single asset balance using TanStack Query. The network is derived from the asset's getNetwork() method. Automatically reads cached data from Zustand on first render, then fetches fresh data from the network.

Parameters

ParameterTypeDescription
accountIndexnumberAccount index
assetIAssetAsset to fetch balance for (network is derived from asset.getNetwork())
optionsBalanceQueryOptionsOptional query configuration

Returns UseBalanceResult

A composite result combining address loading state with TanStack Query state:

type UseBalanceResult = Omit<UseQueryResult<BalanceFetchResult | undefined, Error>, 'isLoading' | 'error'> & {
  isLoading: boolean
  error: Error | null
}
PropertyTypeDescription
dataBalanceFetchResult | undefinedBalance result
isLoadingbooleantrue while address is loading or first balance fetch is in progress
isFetchingbooleanAny fetch in progress (including refetch)
isErrorbooleanQuery encountered an error
errorError | nullError from address loading or balance query
refetch() => Promise<...>Manually trigger refetch

Example

import { useBalance, BaseAsset } from '@tetherto/wdk-react-native-core'

const eth = new BaseAsset({
  id: 'eth',
  network: 'ethereum',
  symbol: 'ETH',
  name: 'Ether',
  decimals: 18,
  isNative: true,
})

function BalanceDisplay() {
  const { data: balance, isLoading } = useBalance(0, eth, {
    refetchInterval: 30000, // Refresh every 30s
  })

  if (isLoading) return <Text>Loading...</Text>

  if (balance?.success) {
    return <Text>ETH Balance: {balance.balance}</Text>
  }

  return <Text>Failed to fetch balance</Text>
}

useBalancesForWallet

Hook to fetch balances for multiple assets in a single query. Automatically loads addresses for all required networks before fetching balances. For non-native assets, the hook tries the wallet module's batch token balance method first, then falls back to individual token balance calls when batch fetching is not available.

Parameters

ParameterTypeDescription
accountIndexnumberAccount index
assetConfigsIAsset[]Array of assets to fetch balances for
optionsBalanceQueryOptionsOptional query configuration

Returns UseBalancesForWalletResult

Same shape as UseBalanceResult but data is BalanceFetchResult[].

type UseBalancesForWalletResult = Omit<UseQueryResult<BalanceFetchResult[], Error>, 'isLoading' | 'error'> & {
  isLoading: boolean
  error: Error | null
}

Example

import { useBalancesForWallet, BaseAsset } from '@tetherto/wdk-react-native-core'

const assets = [
  new BaseAsset({ id: 'eth', network: 'ethereum', symbol: 'ETH', name: 'Ether', decimals: 18, isNative: true }),
  new BaseAsset({ id: 'usdt-eth', network: 'ethereum', symbol: 'USDT', name: 'Tether USD', decimals: 6, isNative: false, address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' }),
]

function PortfolioScreen() {
  const { data: balances, isLoading } = useBalancesForWallet(0, assets, {
    refetchInterval: 60000,
  })

  if (isLoading) return <Text>Loading balances...</Text>

  return (
    <View>
      {balances?.map((b) => (
        <Text key={b.assetId}>
          {b.assetId}: {b.success ? b.balance : 'Error'}
        </Text>
      ))}
    </View>
  )
}

useBalancesForWallets

Hook to fetch balances for multiple assets across multiple account indices in a single TanStack Query. The hook loads addresses for every required account/network pair before fetching balances, reuses cached balance data as initial query data, and returns per-account token failures instead of failing the entire query when one account fetch fails.

Parameters

ParameterTypeDescription
accountIndicesnumber[]Account indices to fetch balances for
assetConfigsIAsset[]Array of assets to fetch for each account index
optionsBalanceQueryOptions & { concurrencyLimit?: number }Optional query configuration and per-account fetch concurrency. Defaults to 2 concurrent account fetches

Returns UseBalancesForWalletsResult

Same shape as UseBalancesForWalletResult; data is a flattened BalanceFetchResult[] for all requested account indices and assets.

type UseBalancesForWalletsResult = Omit<UseQueryResult<BalanceFetchResult[], Error>, 'isLoading' | 'error'> & {
  isLoading: boolean
  error: Error | null
}

Example

import { useBalancesForWallets, BaseAsset } from '@tetherto/wdk-react-native-core'

const assets = [
  new BaseAsset({ id: 'eth', network: 'ethereum', symbol: 'ETH', name: 'Ether', decimals: 18, isNative: true }),
  new BaseAsset({ id: 'usdt-eth', network: 'ethereum', symbol: 'USDT', name: 'Tether USD', decimals: 6, isNative: false, address: '0xdAC17F958D2ee523a2206206994597C13D831ec7' }),
]

function AccountsPortfolioScreen() {
  const { data: balances, isLoading } = useBalancesForWallets([0, 1, 2], assets, {
    concurrencyLimit: 2,
    refetchInterval: 60000,
  })

  if (isLoading) return <Text>Loading balances...</Text>

  return (
    <View>
      {balances?.map((b) => (
        <Text key={`${b.accountIndex}-${b.assetId}`}>
          Account {b.accountIndex} {b.assetId}: {b.success ? b.balance : 'Error'}
        </Text>
      ))}
    </View>
  )
}

useRefreshBalance

Hook that returns a TanStack Query mutation for invalidating and refetching balances.

Returns

Standard TanStack Query mutation result (UseMutationResult).

Call mutate(params) with:

FieldTypeDescription
accountIndexnumberAccount index
networkstring?Network (required for 'token' and 'network' types)
assetIdstring?Asset ID (required for 'token' type)
type'token' | 'wallet' | 'network' | 'all'?Refresh scope (default: 'token')
walletIdstring?Wallet ID override (defaults to active wallet)

Example

import { useRefreshBalance } from '@tetherto/wdk-react-native-core'

function RefreshButton() {
  const { mutate: refreshBalance, isPending } = useRefreshBalance()

  return (
    <Button
      title="Refresh All Balances"
      disabled={isPending}
      onPress={() => refreshBalance({ accountIndex: 0, type: 'wallet' })}
    />
  )
}

BaseAsset

Default implementation of the IAsset interface. Wraps an AssetConfig object.

Constructor

new BaseAsset(config: AssetConfig)

Methods

MethodReturnsDescription
getId()stringUnique asset identifier
getNetwork()stringNetwork this asset belongs to
getSymbol()stringTicker symbol (e.g., 'USDT')
getName()stringHuman-readable name
getDecimals()numberDecimal places
isNative()booleantrue for native chain currency (ETH, BTC, etc.)
getContractAddress()string | nullToken contract address (null for native assets)

Example

import { BaseAsset } from '@tetherto/wdk-react-native-core'

const usdt = new BaseAsset({
  id: 'usdt-ethereum',
  network: 'ethereum',
  symbol: 'USDT',
  name: 'Tether USD',
  decimals: 6,
  isNative: false,
  address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
})

console.log(usdt.getSymbol())          // 'USDT'
console.log(usdt.isNative())           // false
console.log(usdt.getContractAddress()) // '0xdAC17F958D2ee523a2206206994597C13D831ec7'

IAsset

Interface that all assets must implement. BaseAsset provides a default implementation, but you can create custom asset classes for advanced use cases.

interface IAsset {
  getId(): string
  getNetwork(): string
  getSymbol(): string
  getName(): string
  getDecimals(): number
  isNative(): boolean
  getContractAddress(): string | null
}

Types

WdkConfigs

Root configuration object passed to WdkAppProvider. Defines network and protocol configurations for the WDK worklet. Extends WdkWorkletConfig from @tetherto/pear-wrk-wdk.

interface WdkConfigs<TNetwork = Record<string, unknown>, TProtocol = Record<string, unknown>> extends WdkWorkletConfig {
  networks: {
    [blockchain: string]: WdkNetworkConfig<TNetwork>
  }
  protocols?: {
    [protocolName: string]: WdkProtocolConfig<TProtocol>
  }
  modules?: {
    [moduleName: string]: Record<string, unknown>
  }
}

modules contains runtime configuration for generic modules. It is distinct from the Worklet Bundler's build-time modules map, which selects package names, factories, and forwarded events. Use the same module name in both maps.

WdkNetworkConfig

Wrapper around NetworkConfig with typed config field.

interface WdkNetworkConfig<T = Record<string, unknown>> extends NetworkConfig {
  blockchain: string
  config: T
}

WdkProtocolConfig

Wrapper around ProtocolConfig with typed config field.

interface WdkProtocolConfig<T = Record<string, unknown>> extends ProtocolConfig {
  blockchain: string
  protocolName: string
  config: T
}

AssetConfig

Raw configuration object for defining an asset. Pass this to the BaseAsset constructor.

type AssetConfig<T = Record<string, unknown>> = T & {
  id: string           // Unique identifier
  network: string      // Network this asset belongs to
  symbol: string       // Ticker symbol
  name: string         // Human-readable name
  decimals: number     // Decimal places
  isNative: boolean    // true for native chain currency
  address?: string | null  // Token contract address (null for native)
}

BalanceFetchResult

Result of a balance fetch operation.

interface BalanceFetchResult {
  success: boolean        // Whether the fetch succeeded
  network: string         // Network name
  accountIndex: number    // Account index
  assetId: string         // Asset identifier
  balance: string | null  // Balance as string (null if failed)
  error?: string          // Error message (only if success is false)
}

BundleConfig

Configuration for the worklet bundle.

interface BundleConfig {
  bundle: string  // Compiled JavaScript bundle for the worklet runtime
}

BalanceQueryOptions

Options for balance query hooks.

interface BalanceQueryOptions {
  enabled?: boolean              // Whether the query is enabled (default: true)
  refetchInterval?: number | false  // Refetch interval in ms (false to disable)
  staleTime?: number             // Stale time in ms
}

WalletInfo

Information about a wallet stored on the device.

interface WalletInfo {
  identifier: string  // Wallet identifier (e.g., user email)
  exists: boolean     // Whether wallet exists in secure storage
}

AccountInfo

Information about a wallet account.

interface AccountInfo {
  accountIndex: number              // Account index (0-based)
  addresses: Record<string, string> // Address for each network
}

Direct Credential Storage

Most apps should use useWalletManager for wallet lifecycle operations. The direct API supports existing keychain integrations and migrations; it does not update the wallet inventory or initialize, lock, or dispose a worklet.

createSecureStorage

createSecureStorage(): SecureStorage returns the same config-free singleton on every call. It has no biometric, cache, or timeout configuration arguments. Authenticate in your app before reading or changing wallet credentials. No WdkAppProvider is needed for this direct API, but the native keychain and Expo Crypto runtime must be available.

Import runtime values from the package root before any separate type-only imports:

Access direct storage
import { createSecureStorage } from '@tetherto/wdk-react-native-core'
import type { SecureStorage } from '@tetherto/wdk-react-native-core'

const storage: SecureStorage = createSecureStorage()

Each credential method requires a wallet identifier. Normal identifiers are trimmed and lowercased before hashing into the keychain service name, so case or surrounding whitespace does not create a separate slot. Use a stable unique identifier of at most 256 characters containing letters, digits, ., -, _, +, or the supported email format. Values must be non-empty strings of at most 10,240 characters.

MethodReturnPurpose
setEncryptionKey(key, identifier)Promise<void>Store an encryption-key string
getEncryptionKey(identifier)Promise<string | null>Read the encryption key
setEncryptedSeed(encryptedSeed, identifier)Promise<void>Store an encrypted-seed string
getEncryptedSeed(identifier)Promise<string | null>Read the encrypted seed
setEncryptedEntropy(encryptedEntropy, identifier)Promise<void>Store an encrypted-entropy string
getEncryptedEntropy(identifier)Promise<string | null>Read the encrypted entropy
getAllEncrypted(identifier)Promise<{ encryptedSeed: string | null, encryptedEntropy: string | null, encryptionKey: string | null }>Read all three values
hasWallet(identifier)Promise<boolean>Check for the encrypted-seed entry
deleteWallet(identifier)Promise<void>Delete the three credential entries
cleanup()voidNo operation; does not delete stored credentials or clear returned values

All setter values and identifiers above are string. The storage layer does not encrypt an input seed for you or verify its encoding. Core's wallet hooks store base64 strings here; preserve that format when interoperating with them.

  • Keychain accessibility: encryption keys use WHEN_UNLOCKED. On iOS, encrypted seeds and entropy use WHEN_UNLOCKED_THIS_DEVICE_ONLY, so those entries do not transfer to another device through an encrypted backup restore.
  • Reading credentials: getAllEncrypted() includes the raw encryption key; do not log it or treat the object as an encrypted backup. Its separate reads are not an atomic snapshot, so serialize credential mutations when consistency matters. hasWallet() checks only for an encrypted-seed entry; it does not prove that the other credentials exist or recovery will succeed.
  • Deleting credentials: deletion is not atomic and can leave partial data. Check remaining credentials after a failure before retrying. Direct deletion does not update Core's wallet inventory or reset its worklet.

Storage Errors

Catch these runtime classes from the package root:

ExportcodeMeaning
SecureStorageErrorOperation-specific stringBase storage error; failed deletion uses WALLET_DELETE_ERROR
KeychainReadErrorKEYCHAIN_READ_ERRORKeychain read or existence-check failure
KeychainWriteErrorKEYCHAIN_WRITE_ERRORKeychain write failure
ValidationErrorVALIDATION_ERRORInvalid identifier or stored value
TimeoutErrorTIMEOUT_ERRORA keychain operation exceeded 30 seconds

Errors expose message, code, and optional cause. The timeout rejects the caller without cancelling the native operation; a timed-out write or delete can still finish later. Recheck state before retrying or continuing a migration.

Legacy Storage Compatibility

When migrating from @tetherto/wdk-react-native-secure-storage, existing normalized named-identifier slots are compatible with Core and need no credential copy. Update imports to Core and provide app-owned authentication. Core requires an explicit identifier and accepts no biometric or cache configuration options; do not forward the standalone package's options.

DEFAULT_IDENTIFIER is the reserved string __default__. It selects the predecessor's bare keychain service names used when no identifier was supplied. Use it only to inspect or migrate that legacy slot, never as the identifier for a new user's wallet.

After authenticating in your app, check for the legacy slot without reading its secrets:

Check the legacy storage slot
import {
  createSecureStorage,
  DEFAULT_IDENTIFIER,
} from '@tetherto/wdk-react-native-core'

const storage = createSecureStorage()
const hasLegacyWallet = await storage.hasWallet(DEFAULT_IDENTIFIER)

Copy credentials only when moving the legacy default slot or intentionally changing identifiers. Serialize writes and preserve every available credential field. Verify recovery from the destination before deleting the source; a timeout or partial write must stop the migration. Moving keychain values does not populate Core's wallet inventory.


Utilities

validateMnemonic

Validates a BIP39 mnemonic phrase. Checks that it contains exactly 12 or 24 non-empty words.

function validateMnemonic(mnemonic: string): boolean

Example

import { validateMnemonic } from '@tetherto/wdk-react-native-core'

validateMnemonic('word1 word2 word3 word4 word5 word6 word7 word8 word9 word10 word11 word12')
// true

validateMnemonic('only three words')
// false

balanceQueryKeys

Factory for constructing TanStack Query keys for balance queries. Useful for manual cache invalidation.

const balanceQueryKeys = {
  all: ['balances'],
  byWallet: (walletId: string, accountIndex: number) => [...],
  byNetwork: (network: string) => [...],
  byWalletAndNetwork: (walletId: string, accountIndex: number, network: string) => [...],
  byToken: (walletId: string, accountIndex: number, network: string, assetId: string) => [...],
}

Example

import { balanceQueryKeys } from '@tetherto/wdk-react-native-core'
import { useQueryClient } from '@tanstack/react-query'

const queryClient = useQueryClient()

// Invalidate all balances for a specific wallet
queryClient.invalidateQueries({
  queryKey: balanceQueryKeys.byWallet('user@example.com', 0),
})

Need Help?

On this page