A React Native library providing wallet context and WDK (Wallet Development Kit) service integration for building secure, multi-chain cryptocurrency wallets.
- Multi-chain Support: Bitcoin, Ethereum, Polygon, Arbitrum, TON, Solana, and Tron
- Multi-asset Management: BTC, USDT, XAUT, and more
- Secure Seed Management: Encrypted seed phrase storage using native keychain
- React Context API: Easy-to-use wallet context provider and hooks
- Account Management: Create, import, and unlock wallets
- Balance & Transactions: Real-time balance updates and transaction history
- Send & Quote: Transaction sending and fee estimation
- TypeScript Support: Full type definitions included
- Android minSdkVersion: 29 or higher
- iOS Deployment Target: 15.1 or higher
- React Native: 0.81.0+
npm install @tetherto/wdk-react-native-providerThe library requires Android minSdkVersion 29 to properly run react-native-bare-kit.
For Expo projects with prebuild:
Add to your app.json or app.config.js:
{
"expo": {
"plugins": [
[
"expo-build-properties",
{
"android": {
"minSdkVersion": 29
}
}
]
]
}
}For bare React Native projects:
Update android/build.gradle:
buildscript {
ext {
minSdkVersion = 29
// ... other config
}
}This library requires several peer dependencies. Install them using:
npm install \
@craftzdog/react-native-buffer \
@react-native-async-storage/async-storage \
@tetherto/pear-wrk-wdk \
@tetherto/wdk-secret-manager \
b4a \
bip39 \
browserify-zlib \
decimal.js \
events \
http2-wrapper \
https-browserify \
nice-grpc-web \
path-browserify \
process \
querystring-es3 \
react-native-bare-kit \
react-native-crypto \
react-native-device-info \
react-native-get-random-values \
react-native-keychain \
react-native-tcp-socket \
react-native-url-polyfill \
sodium-javascript \
stream-browserify \
stream-httpThe library requires Node.js core module polyfills for React Native. Configure your Metro bundler using the provided helper function.
For Expo projects:
Update your metro.config.js:
const { getDefaultConfig } = require('expo/metro-config');
const { configureMetroForWDK } = require('@tetherto/wdk-react-native-provider/metro-polyfills');
const config = getDefaultConfig(__dirname);
const configWdk = configureMetroForWDK(config);
module.exports = configWdk;Manual configuration (if needed):
If you prefer to manually configure the polyfills:
const { getDefaultConfig } = require('expo/metro-config');
const { getMetroPolyfills } = require('@tetherto/wdk-react-native-provider/metro-polyfills');
const config = getDefaultConfig(__dirname);
config.resolver.extraNodeModules = {
...config.resolver.extraNodeModules,
...getMetroPolyfills(),
};
module.exports = config;Note: Runtime polyfills for Buffer, process, and crypto are automatically initialized when you import from @tetherto/wdk-react-native-provider. No additional setup is required.
Wrap your app with the WalletProvider and provide the required configuration.
For Expo Router projects:
Add the provider to your app/_layout.tsx file to make the useWallet hook accessible throughout your app:
// app/_layout.tsx
import { WalletProvider } from '@tetherto/wdk-react-native-provider';
import { Stack } from 'expo-router';
// Define your chain configurations
const CHAINS_CONFIG = {
ethereum: {
chainId: 1,
blockchain: 'ethereum',
provider: 'https://mainnet.gateway.tenderly.co/YOUR_KEY',
bundlerUrl: 'https://api.candide.dev/public/v3/ethereum',
paymasterUrl: 'https://api.candide.dev/public/v3/ethereum',
paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba',
entrypointAddress: '0x0000000071727De22E5E9d8BAf0edAc6f37da032',
transferMaxFee: 5000000,
swapMaxFee: 5000000,
bridgeMaxFee: 5000000,
paymasterToken: {
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
},
},
bitcoin: {
host: 'api.ordimint.com',
port: 50001,
},
// ... add other chains as needed
};
export default function RootLayout() {
return (
<WalletProvider
config={{
indexer: {
apiKey: 'your-api-key-here',
url: 'https://your-indexer-url.com',
},
chains: CHAINS_CONFIG,
enableCaching: true, // Optional: enable caching for better performance
}}
>
<Stack />
</WalletProvider>
);
}For standard React Native projects:
Wrap your root component:
// App.tsx
import { WalletProvider } from '@tetherto/wdk-react-native-provider';
const CHAINS_CONFIG = {
ethereum: {
chainId: 1,
blockchain: 'ethereum',
provider: 'https://mainnet.gateway.tenderly.co/YOUR_KEY',
bundlerUrl: 'https://api.candide.dev/public/v3/ethereum',
paymasterUrl: 'https://api.candide.dev/public/v3/ethereum',
paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba',
entrypointAddress: '0x0000000071727De22E5E9d8BAf0edAc6f37da032',
transferMaxFee: 5000000,
swapMaxFee: 5000000,
bridgeMaxFee: 5000000,
paymasterToken: {
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
},
},
bitcoin: {
host: 'api.ordimint.com',
port: 50001,
},
// ... other chains
};
function App() {
return (
<WalletProvider
config={{
indexer: {
apiKey: 'your-api-key-here',
url: 'https://your-indexer-url.com',
},
chains: CHAINS_CONFIG,
enableCaching: true, // Optional: enable caching for balances and transactions
}}
>
<YourApp />
</WalletProvider>
);
}Access wallet functionality using the useWallet hook:
import { useWallet } from '@tetherto/wdk-react-native-provider';
function WalletScreen() {
const {
wallet,
balances,
transactions,
isLoading,
isInitialized,
isUnlocked,
createWallet,
unlockWallet,
refreshWalletBalance,
refreshTransactions,
} = useWallet();
// Create a new wallet
const handleCreateWallet = async () => {
try {
const wallet = await createWallet({
name: 'Imported Wallet',
mnemonic: 'your twelve word seed phrase goes here',
});
console.log('Wallet created:', wallet);
} catch (error) {
console.error('Failed to create wallet:', error);
}
};
// Unlock wallet
const handleUnlockWallet = async () => {
try {
await unlockWallet();
console.log('Wallet unlocked');
} catch (error) {
console.error('Failed to unlock wallet:', error);
}
};
if (!isInitialized) {
return <Text>Initializing...</Text>;
}
if (!wallet) {
return (
<View>
<Button title="Create Wallet" onPress={handleCreateWallet} />
<Button title="Import Wallet" onPress={handleImportWallet} />
</View>
);
}
if (!isUnlocked) {
return <Button title="Unlock Wallet" onPress={handleUnlockWallet} />;
}
return (
<View>
<Text>Wallet Name: {wallet.name}</Text>
<Button title="Refresh Balance" onPress={refreshWalletBalance} />
<Button title="Refresh Transactions" onPress={refreshTransactions} />
</View>
);
}The main provider component that manages wallet state.
Props:
config.indexer(object, required): Indexer service configurationconfig.indexer.apiKey(string, required): API key for the indexer serviceconfig.indexer.url(string, required): URL of the indexer serviceconfig.indexer.version(string, optional): API version (defaults to 'v1')
config.chains(ChainsConfig, required): Chain configuration object containing network-specific settingsconfig.enableCaching(boolean, optional): Enable caching for balances and transactions to improve performance
See Chain Configuration for detailed configuration options.
Hook to access wallet context and functionality.
Returns:
{
// State
wallet?: Wallet | null;
addresses?: AddressMap;
balances: {
list: Amount[];
map: BalanceMap;
isLoading: boolean;
};
transactions: {
list: Transaction[];
map: TransactionMap;
isLoading: boolean;
};
isLoading: boolean;
error: string | null;
isInitialized: boolean;
isUnlocked: boolean;
// Actions
createWallet: (params: { name: string; mnemonic?: string }) => Promise<Wallet | null>;
clearWallet: () => Promise<void>;
clearError: () => void;
refreshWalletBalance: () => Promise<void>;
refreshTransactions: () => Promise<void>;
unlockWallet: () => Promise<boolean | undefined>;
}Low-level service for direct wallet operations. Available as a singleton.
import { WDKService } from '@tetherto/wdk-react-native-provider';
// Initialize WDK
await WDKService.initialize();
// Create seed
const seed = await WDKService.createSeed({ prf: 'passkey' });
// Import seed phrase
await WDKService.importSeedPhrase({
prf: 'passkey',
seedPhrase: 'your mnemonic here',
});
// Create wallet
const wallet = await WDKService.createWallet({
walletName: 'My Wallet',
prf: 'passkey',
});
// Get balances
const balances = await WDKService.resolveWalletBalances(
enabledAssets,
addressMap
);
// Send transaction
const result = await WDKService.sendByNetwork(
NetworkType.ETHEREUM,
0, // account index
100, // amount
'0x...', // recipient address
AssetTicker.USDT
);The library supports multiple blockchain networks, each with its own configuration structure.
The chains configuration object supports the following blockchain networks:
const chains = {
ethereum?: EVMChainConfig;
arbitrum?: EVMChainConfig;
polygon?: EVMChainConfig;
ton?: TONChainConfig;
bitcoin?: BitcoinChainConfig;
tron?: TronChainConfig;
}For Ethereum, Polygon, and Arbitrum:
const ethereumConfig = {
chainId: 1,
blockchain: 'ethereum',
provider: 'https://mainnet.gateway.tenderly.co/YOUR_KEY',
bundlerUrl: 'https://api.candide.dev/public/v3/ethereum',
paymasterUrl: 'https://api.candide.dev/public/v3/ethereum',
paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba',
entrypointAddress: '0x0000000071727De22E5E9d8BAf0edAc6f37da032',
transferMaxFee: 5000000,
swapMaxFee: 5000000,
bridgeMaxFee: 5000000,
paymasterToken: {
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
},
safeModulesVersion: '0.3.0', // Optional, for Polygon
};const tonConfig = {
tonApiClient: {
url: 'https://tonapi.io',
},
tonClient: {
url: 'https://toncenter.com/api/v2/jsonRPC',
},
paymasterToken: {
address: 'EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs',
},
transferMaxFee: 1000000000,
};const bitcoinConfig = {
host: 'api.ordimint.com',
port: 50001,
};const tronConfig = {
chainId: 3448148188,
provider: 'https://trongrid.io',
gasFreeProvider: 'https://gasfree.io',
apiKey: 'your-api-key',
apiSecret: 'your-api-secret',
serviceProvider: 'TKtWbdzEq5ss9vTS9kwRhBp5mXmBfBns3E',
verifyingContract: 'THQGuFzL87ZqhxkgqYEryRAd7gqFqL5rdc',
transferMaxFee: 10000000,
swapMaxFee: 1000000,
bridgeMaxFee: 1000000,
paymasterToken: {
address: 'TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf',
},
};import { WalletProvider } from '@tetherto/wdk-react-native-provider';
const chains = {
ethereum: {
chainId: 1,
blockchain: 'ethereum',
provider: 'https://mainnet.gateway.tenderly.co/YOUR_KEY',
bundlerUrl: 'https://api.candide.dev/public/v3/ethereum',
paymasterUrl: 'https://api.candide.dev/public/v3/ethereum',
paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba',
entrypointAddress: '0x0000000071727De22E5E9d8BAf0edAc6f37da032',
transferMaxFee: 5000000,
swapMaxFee: 5000000,
bridgeMaxFee: 5000000,
paymasterToken: {
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
},
},
polygon: {
chainId: 137,
blockchain: 'polygon',
provider: 'https://polygon.gateway.tenderly.co/YOUR_KEY',
bundlerUrl: 'https://api.candide.dev/public/v3/polygon',
paymasterUrl: 'https://api.candide.dev/public/v3/polygon',
paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba',
entrypointAddress: '0x0000000071727De22E5E9d8BAf0edAc6f37da032',
transferMaxFee: 5000000,
swapMaxFee: 5000000,
bridgeMaxFee: 5000000,
paymasterToken: {
address: '0xc2132d05d31c914a87c6611c10748aeb04b58e8f',
},
safeModulesVersion: '0.3.0',
},
arbitrum: {
chainId: 42161,
blockchain: 'arbitrum',
provider: 'https://arbitrum.gateway.tenderly.co/YOUR_KEY',
bundlerUrl: 'https://api.candide.dev/public/v3/arbitrum',
paymasterUrl: 'https://api.candide.dev/public/v3/arbitrum',
paymasterAddress: '0x8b1f6cb5d062aa2ce8d581942bbb960420d875ba',
entrypointAddress: '0x0000000071727De22E5E9d8BAf0edAc6f37da032',
transferMaxFee: 5000000,
swapMaxFee: 5000000,
bridgeMaxFee: 5000000,
paymasterToken: {
address: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9',
},
},
ton: {
tonApiClient: {
url: 'https://tonapi.io',
},
tonClient: {
url: 'https://toncenter.com/api/v2/jsonRPC',
},
paymasterToken: {
address: 'EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs',
},
transferMaxFee: 1000000000,
},
bitcoin: {
host: 'api.ordimint.com',
port: 50001,
},
tron: {
chainId: 3448148188,
provider: 'https://trongrid.io',
gasFreeProvider: 'https://gasfree.io',
apiKey: 'your-api-key',
apiSecret: 'your-api-secret',
serviceProvider: 'TKtWbdzEq5ss9vTS9kwRhBp5mXmBfBns3E',
verifyingContract: 'THQGuFzL87ZqhxkgqYEryRAd7gqFqL5rdc',
transferMaxFee: 10000000,
swapMaxFee: 1000000,
bridgeMaxFee: 1000000,
paymasterToken: {
address: 'TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf',
},
},
};
function App() {
return (
<WalletProvider
config={{
indexer: {
apiKey: 'your-indexer-api-key',
url: 'https://your-indexer-url.com',
},
chains,
enableCaching: true, // Optional: enable caching for better performance
}}
>
<YourApp />
</WalletProvider>
);
}const { wallet, addresses, balances, transactions } = useWallet();
if (wallet) {
// Addresses
if (addresses) {
Object.entries(addresses).forEach(([ticker, addressList]) => {
console.log(`${ticker}: ${addressList[0]?.value}`);
});
}
// Balances - available as both list and map
balances.list.forEach(balance => {
console.log(`${balance.denomination}: ${balance.value}`);
});
// Or access by ticker from the map
const usdtBalance = balances.map.USDT?.[0];
console.log('USDT Balance:', usdtBalance?.value);
// Check loading state
if (balances.isLoading) {
console.log('Loading balances...');
}
// Transactions - available as both list and map
transactions.list.forEach(tx => {
console.log('Transaction:', tx);
});
// Or access by ticker from the map
const usdtTransactions = transactions.map.USDT;
console.log('USDT Transactions:', usdtTransactions);
// Check loading state
if (transactions.isLoading) {
console.log('Loading transactions...');
}
}import { NetworkType } from '@tetherto/wdk-react-native-provider';
// Available networks:
NetworkType.SEGWIT // Bitcoin
NetworkType.ETHEREUM // Ethereum
NetworkType.POLYGON // Polygon
NetworkType.ARBITRUM // Arbitrum
NetworkType.TON // TON
NetworkType.SOLANA // Solana
NetworkType.TRON // Tronimport { AssetTicker } from '@tetherto/wdk-react-native-provider';
// Available assets:
AssetTicker.BTC // Bitcoin
AssetTicker.USDT // Tether USD
AssetTicker.XAUT // Tether GoldThis library is written in TypeScript and includes complete type definitions. Import types as needed:
import type {
// Provider configuration
WalletProviderConfig,
// Wallet types
Amount,
Transaction,
Wallet,
// Enums (also available as values)
AssetTicker,
NetworkType,
} from '@tetherto/wdk-react-native-provider';Note: Chain configuration types (ChainsConfig, EVMChainConfig, TONChainConfig, etc.) are defined in the underlying @tetherto/pear-wrk-wdk package. TypeScript will infer these types when you use them in the WalletProviderConfig, so explicit imports are typically not needed.
- Seed Phrase Storage: Seed phrases are encrypted and stored securely using device-specific encryption
- Passkey/PRF: Uses device unique ID by default. In production, integrate with biometric authentication
- Never Log Seeds: Never log or display seed phrases in production code
- Secure Communication: All API calls use HTTPS and require API keys
See CONTRIBUTING.md for development workflow and guidelines.
npm run preparenpm run typechecknpm run lintnpm testIf you're experiencing issues, verify you've completed all setup steps:
For Expo projects:
- ✅ Install the package:
npm install @tetherto/wdk-react-native-provider - ✅ Install all peer dependencies (see Peer Dependencies)
- ✅ Configure Android minSdkVersion to 29 in
app.json:{ "expo": { "plugins": [ ["expo-build-properties", { "android": { "minSdkVersion": 29 } }] ] } } - ✅ Configure Metro polyfills in
metro.config.js:const { configureMetroForWDK } = require('@tetherto/wdk-react-native-provider/metro-polyfills'); const config = getDefaultConfig(__dirname); const configWdk = configureMetroForWDK(config); module.exports = configWdk;
- ✅ Add
WalletProvidertoapp/_layout.tsxwith proper config - ✅ Use
useWallet()hook in your components - ✅ Check
isInitializedbefore creating wallets
For bare React Native projects:
- ✅ Install the package and peer dependencies
- ✅ Set minSdkVersion to 29 in
android/build.gradle - ✅ Configure Metro polyfills in
metro.config.js - ✅ Wrap your root component with
WalletProvider - ✅ Rebuild native code:
npx react-native run-androidornpx react-native run-ios
The WDK service is initialized automatically when the WalletProvider mounts. If you see this error, ensure:
- Your component is wrapped with
WalletProvider - The provider's config is properly set
- You're checking
isInitializedbefore performing wallet operations:
const { isInitialized, createWallet } = useWallet();
if (isInitialized) {
await createWallet({ name: 'My Wallet' });
}Ensure a wallet has been created or imported before attempting transactions:
const { wallet, createWallet } = useWallet();
if (!wallet) {
await createWallet({ name: 'My Wallet' });
}If you see errors like Unable to resolve "stream" or other Node.js core modules:
- Ensure you've configured Metro polyfills correctly (see Metro Configuration)
- Clear Metro cache:
npx expo start --clearornpx react-native start --reset-cache - Delete
node_modulesand reinstall:rm -rf node_modules && npm install - For Expo: Run prebuild if using custom native modules:
npx expo prebuild --clean
This usually means minSdkVersion is too low. Ensure you've set minSdkVersion to 29:
For Expo:
{
"expo": {
"plugins": [
["expo-build-properties", { "android": { "minSdkVersion": 29 } }]
]
}
}Then rebuild: npx expo run:android or npx expo prebuild --clean && npx expo run:android
Some peer dependencies may not have type definitions. You can ignore these by adding to your tsconfig.json:
{
"compilerOptions": {
"skipLibCheck": true
}
}See the contributing guide to learn how to contribute to the repository and the development workflow.
Apache-2.0
Made with ❤️ by Tether