@human.tech/waap-sdk-react-native
v1.0.5
Published
React Native implementation of WaaP SDK
Maintainers
Readme
WaaP SDK for React Native
React Native wallet integration that brings WaaP's security and ease of use to mobile apps.
Quick Start
Installation
npm install @human.tech/waap-sdk-react-native
# or
yarn add @human.tech/waap-sdk-react-native
# or
pnpm add @human.tech/waap-sdk-react-nativeRequired Dependencies
npm install @react-native-async-storage/async-storage \
@react-native-community/netinfo \
react-native-css-interop \
react-native-get-random-values \
react-native-keychain \
react-native-safe-area-context \
react-native-svg \
react-native-webviewPlus ONE of these browser libraries:
For Expo apps:
npx expo install expo-web-browserFor bare React Native apps:
npm install react-native-inappbrowser-rebornImportant Notes:
react-native-keychain: Required for secure storage of sensitive data like authentication tokens and private keys. Provides hardware-backed encryption via iOS Keychain and Android Keystore.- Browser library (choose one):
- Expo: Use
expo-web-browserwith thecreateExpoNativeBrowser()factory function- Bare React Native: Use
react-native-inappbrowser-rebornwith thecreateInAppNativeBrowser()factory function- You must pass the created browser function to
initWaapNative()for social authentication to workreact-native-safe-area-context: Your app MUST wrap its root component with<SafeAreaProvider>for the wallet UI to render correctlyThe SDK will behave unexpectedly if these critical dependencies are missing.
Most React Native apps already have several of these installed. Make sure to check your package.json.
Basic Usage
import { useEffect, useState } from 'react'
import { View, Button, Text } from 'react-native'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import * as WebBrowser from 'expo-web-browser'
import {
initWaapNative,
WalletComponent,
createExpoNativeBrowser
} from '@human.tech/waap-sdk-react-native'
function App() {
const [provider, setProvider] = useState(null)
const [account, setAccount] = useState(null)
useEffect(() => {
// Initialize with Expo browser
const provider = initWaapNative({
project: {
name: 'My App',
logo: 'data:image/png;base64,...'
},
nativeBrowser: createExpoNativeBrowser(WebBrowser)
})
setProvider(provider)
// Listen to events
provider.on('accountsChanged', (accounts) => {
setAccount(accounts[0] || null)
})
// Auto-connect
provider
.request({ method: 'eth_requestAccounts' })
.then((accounts) => setAccount(accounts[0]))
.catch(() => console.log('Not connected'))
}, [])
const connectWallet = async () => {
try {
await provider.login()
const accounts = await provider.request({
method: 'eth_requestAccounts'
})
setAccount(accounts[0])
} catch (error) {
console.error('Login failed:', error)
}
}
if (!provider) return <Text>Loading...</Text>
return (
<SafeAreaProvider>
<View>
{account ? (
<>
<Text>Connected: {account.slice(0, 6)}...{account.slice(-4)}</Text>
<Button title='Disconnect' onPress={() => provider.logout()} />
</>
) : (
<Button title='Connect Wallet' onPress={connectWallet} />
)}
<WalletComponent />
</View>
</SafeAreaProvider>
)
}import { useEffect, useState } from 'react'
import { View, Button, Text } from 'react-native'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import InAppBrowser from 'react-native-inappbrowser-reborn'
import {
initWaapNative,
WalletComponent,
createInAppNativeBrowser
} from '@human.tech/waap-sdk-react-native'
function App() {
const [provider, setProvider] = useState(null)
const [account, setAccount] = useState(null)
useEffect(() => {
// Initialize with InAppBrowser
const provider = initWaapNative({
project: {
name: 'My App',
logo: 'data:image/png;base64,...'
},
nativeBrowser: createInAppNativeBrowser(InAppBrowser)
})
setProvider(provider)
// Listen to events
provider.on('accountsChanged', (accounts) => {
setAccount(accounts[0] || null)
})
// Auto-connect
provider
.request({ method: 'eth_requestAccounts' })
.then((accounts) => setAccount(accounts[0]))
.catch(() => console.log('Not connected'))
}, [])
const connectWallet = async () => {
try {
await provider.login()
const accounts = await provider.request({
method: 'eth_requestAccounts'
})
setAccount(accounts[0])
} catch (error) {
console.error('Login failed:', error)
}
}
if (!provider) return <Text>Loading...</Text>
return (
<SafeAreaProvider>
<View>
{account ? (
<>
<Text>Connected: {account.slice(0, 6)}...{account.slice(-4)}</Text>
<Button title='Disconnect' onPress={() => provider.logout()} />
</>
) : (
<Button title='Connect Wallet' onPress={connectWallet} />
)}
<WalletComponent />
</View>
</SafeAreaProvider>
)
}Key points:
- MUST wrap your app with
<SafeAreaProvider>fromreact-native-safe-area-context - Use
createExpoNativeBrowser(WebBrowser)for Expo orcreateInAppNativeBrowser(InAppBrowser)for bare React Native - Pass the created browser function to
initWaapNative()via thenativeBrowseroption initWaapNative()returns only theproviderWalletComponenthandles its own visibility - callingprovider.login()shows it automatically
API Reference
Configuration
import * as WebBrowser from 'expo-web-browser'
import { createExpoNativeBrowser } from '@human.tech/waap-sdk-react-native'
initWaapNative({
// REQUIRED: Native browser for authentication flows
nativeBrowser: createExpoNativeBrowser(WebBrowser),
// Project details (required for native auth)
project: {
name: 'My App', // Display name
logo: 'data:image/png;base64,...', // Base64 logo
appId: 'com.myapp.mobile', // Bundle ID (iOS) or package name (Android)
entryTitle: 'Log in to My App', // Custom login screen title
projectId: 'YOUR_PROJECT_ID', // For gastank integration
termsOfServiceUrl: 'https://...', // Optional
privacyPolicyUrl: 'https://...' // Optional
},
// Optional: UI customization
customConfig: {
authenticationMethods: ['email', 'phone', 'social', 'wallet'],
allowedSocials: ['google', 'apple', 'twitter', 'bluesky', 'discord'],
styles: {
darkMode: true
},
showSecured: true
},
// Optional: WalletConnect (required if 'wallet' in authenticationMethods)
walletConnectProjectId: 'YOUR_PROJECT_ID', // Get from https://cloud.reown.com
// Optional: Human Wallet points referral
referralCode: 'YOUR_REFERRAL_CODE',
// Optional: Use staging environment
useStaging: false
})import InAppBrowser from 'react-native-inappbrowser-reborn'
import { createInAppNativeBrowser } from '@human.tech/waap-sdk-react-native'
initWaapNative({
// REQUIRED: Native browser for authentication flows
nativeBrowser: createInAppNativeBrowser(InAppBrowser),
// Project details (required for native auth)
project: {
name: 'My App', // Display name
logo: 'data:image/png;base64,...', // Base64 logo
appId: 'com.myapp.mobile', // Bundle ID (iOS) or package name (Android)
entryTitle: 'Log in to My App', // Custom login screen title
projectId: 'YOUR_PROJECT_ID', // For gastank integration
termsOfServiceUrl: 'https://...', // Optional
privacyPolicyUrl: 'https://...' // Optional
},
// Optional: UI customization
customConfig: {
authenticationMethods: ['email', 'phone', 'social', 'wallet'],
allowedSocials: ['google', 'apple', 'twitter', 'bluesky', 'discord'],
styles: {
darkMode: true
},
showSecured: true
},
// Optional: WalletConnect (required if 'wallet' in authenticationMethods)
walletConnectProjectId: 'YOUR_PROJECT_ID', // Get from https://cloud.reown.com
// Optional: Human Wallet points referral
referralCode: 'YOUR_REFERRAL_CODE',
// Optional: Use staging environment
useStaging: false
})→ Full configuration reference
Provider Methods
The provider implements EIP-1193 with additional methods:
// Get accounts (auto-connects if previously logged in)
const accounts = await provider.request({
method: 'eth_requestAccounts'
})
// Get balance
const balance = await provider.request({
method: 'eth_getBalance',
params: [address, 'latest']
})
// Send transaction
const txHash = await provider.request({
method: 'eth_sendTransaction',
params: [{ from, to, value, data }]
})
// Sign message
const signature = await provider.request({
method: 'personal_sign',
params: [message, account]
})
// Sign typed data (EIP-712)
const signature = await provider.request({
method: 'eth_signTypedData_v4',
params: [account, typedData]
})
// Logout
await provider.logout()Events
Listen to standard EIP-1193 events:
provider.on('connect', (connectInfo: { chainId: string }) => {
console.log('Connected to chain:', connectInfo.chainId)
})
provider.on('accountsChanged', (accounts: string[]) => {
console.log('Account changed:', accounts[0])
})
provider.on('chainChanged', (chainId: string) => {
console.log('Chain changed:', chainId)
})
provider.on('disconnect', (error) => {
console.log('Disconnected:', error)
})Platform Setup
Native App Authentication
WaaP uses Universal Links (iOS) and App Links (Android) to redirect users back to your app after authentication. You need to configure your app to handle these redirects.
iOS - Universal Links
Add to app.json:
{
"expo": {
"ios": {
"associatedDomains": ["applinks:waap.xyz"],
"bundleIdentifier": "com.company.myapp"
}
}
}Add to Info.plist:
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:waap.xyz</string>
</array>Android - App Links
Add to app.json:
{
"expo": {
"android": {
"package": "com.company.myapp"
},
"plugins": [
[
"expo-build-properties",
{
"android": {
"manifestQueries": {
"intent": [
{
"action": "android.intent.action.VIEW",
"data": {
"scheme": "https",
"host": "waap.xyz"
}
}
]
}
}
}
]
]
}
}Add to AndroidManifest.xml:
<activity android:name=".MainActivity" android:exported="true">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="waap.xyz"
android:pathPrefix="/auth" />
</intent-filter>
</activity>Important: The appId you pass to initWaapNative() must exactly match your bundle ID (iOS) or package name (Android).
→ Learn more about WaaP's security architecture
External Wallets (Optional)
To let users connect external mobile wallets like MetaMask or Rainbow via WalletConnect:
1. Enable in config:
initWaapNative({
walletConnectProjectId: 'YOUR_PROJECT_ID', // Get from https://cloud.reown.com
customConfig: {
authenticationMethods: ['email', 'phone', 'social', 'wallet'] // Include 'wallet'
}
})2. Configure wallet detection:
iOS
Add to app.json:
{
"expo": {
"ios": {
"infoPlist": {
"LSApplicationQueriesSchemes": [
"metamask",
"trust",
"rainbow",
"uniswap",
"zerion"
]
}
}
}
}Add to Info.plist:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>metamask</string>
<string>trust</string>
<string>rainbow</string>
<string>uniswap</string>
<string>zerion</string>
</array>Android
Create android-wallet-queries.js:
const {
AndroidConfig,
withAndroidManifest,
createRunOncePlugin
} = require('expo/config-plugins')
const queries = {
package: [
{ $: { 'android:name': 'io.metamask' } },
{ $: { 'android:name': 'com.wallet.crypto.trustapp' } },
{ $: { 'android:name': 'me.rainbow' } },
{ $: { 'android:name': 'com.uniswap.mobile' } },
{ $: { 'android:name': 'io.zerion.android' } }
]
}
const withAndroidWalletQueries = (config) => {
return withAndroidManifest(config, (config) => {
config.modResults.manifest = {
...config.modResults.manifest,
queries
}
return config
})
}
module.exports = createRunOncePlugin(
withAndroidWalletQueries,
'withAndroidWalletQueries',
'1.0.0'
)Add to app.json:
{
"expo": {
"plugins": ["./android-wallet-queries.js"]
}
}Add to AndroidManifest.xml:
<queries>
<package android:name="io.metamask"/>
<package android:name="com.wallet.crypto.trustapp"/>
<package android:name="me.rainbow"/>
<package android:name="com.uniswap.mobile"/>
<package android:name="io.zerion.android"/>
</queries>3. Configure redirect URLs:
import { createNativeWalletConnect } from '@human.tech/waap-sdk-react-native'
const walletConnect = createNativeWalletConnect({
redirect: {
native: 'myapp://', // Your custom URL scheme
universal: 'https://myapp.com' // Your universal link (optional)
}
})Don't forget to register your URL scheme in app.json (Expo) or native config files.
→ See WalletConnect integration example
Complete Example
import React, { useState, useEffect } from 'react'
import { View, Button, Text, StyleSheet, ActivityIndicator } from 'react-native'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import * as WebBrowser from 'expo-web-browser'
import {
initWaapNative,
WalletComponent,
createExpoNativeBrowser
} from '@human.tech/waap-sdk-react-native'
import InAppBrowser from 'react-native-inappbrowser-reborn'
export default function App() {
const [provider, setProvider] = useState(null)
const [account, setAccount] = useState(null)
const [chainId, setChainId] = useState(null)
const [loading, setLoading] = useState(false)
useEffect(() => {
const provider = initWaapNative({
project: {
name: 'My DApp',
appId: 'com.myapp.mobile',
logo: 'data:image/png;base64,...'
},
customConfig: {
styles: {
darkMode: true
}
},
nativeBrowser: createExpoNativeBrowser(WebBrowser)
})
setProvider(provider)
// Event listeners
provider.on('accountsChanged', (accounts) => {
setAccount(accounts[0] || null)
})
provider.on('chainChanged', (newChainId) => {
setChainId(newChainId)
})
provider.on('disconnect', () => {
setAccount(null)
setChainId(null)
})
// Try auto-connect
provider
.request({ method: 'eth_requestAccounts' })
.then((accounts) => setAccount(accounts[0]))
.catch(() => console.log('Not connected'))
return () => {
provider.removeAllListeners()
}
}, [])
const connectWallet = async () => {
setLoading(true)
try {
await provider.login()
const accounts = await provider.request({
method: 'eth_requestAccounts'
})
setAccount(accounts[0])
} catch (error) {
console.error('Failed to connect:', error)
} finally {
setLoading(false)
}
}
const sendTransaction = async () => {
setLoading(true)
try {
const txHash = await provider.request({
method: 'eth_sendTransaction',
params: [
{
from: account,
to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
value: '0x0'
}
]
})
console.log('Transaction sent:', txHash)
} catch (error) {
console.error('Transaction failed:', error)
} finally {
setLoading(false)
}
}
const disconnect = async () => {
setLoading(true)
try {
await provider.logout()
setAccount(null)
setChainId(null)
} catch (error) {
console.error('Failed to disconnect:', error)
} finally {
setLoading(false)
}
}
if (!provider) {
return (
<SafeAreaProvider>
<View style={styles.centerContainer}>
<ActivityIndicator size='large' />
<Text>Initializing...</Text>
</View>
</SafeAreaProvider>
)
}
return (
<SafeAreaProvider>
<View style={styles.container}>
<Text style={styles.title}>WaaP SDK Demo</Text>
{account ? (
<View style={styles.section}>
<Text style={styles.label}>Account:</Text>
<Text style={styles.value}>
{account.slice(0, 6)}...{account.slice(-4)}
</Text>
{chainId && (
<>
<Text style={styles.label}>Chain ID:</Text>
<Text style={styles.value}>{chainId}</Text>
</>
)}
<View style={styles.buttonSpacing}>
<Button
title='Send Transaction'
onPress={sendTransaction}
disabled={loading}
/>
</View>
<Button title='Disconnect' onPress={disconnect} disabled={loading} />
</View>
) : (
<Button
title='Connect Wallet'
onPress={connectWallet}
disabled={loading}
/>
)}
{loading && <ActivityIndicator style={{ marginTop: 20 }} />}
<WalletComponent />
</View>
</SafeAreaProvider>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20
},
centerContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20
},
section: {
width: '100%',
padding: 16,
backgroundColor: '#f5f5f5',
borderRadius: 8,
marginBottom: 20
},
label: {
fontSize: 14,
color: '#666',
marginTop: 8
},
value: {
fontSize: 16,
fontWeight: '500',
marginBottom: 8
},
buttonSpacing: {
marginBottom: 12
}
})import React, { useState, useEffect } from 'react'
import { View, Button, Text, StyleSheet, ActivityIndicator } from 'react-native'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import InAppBrowser from 'react-native-inappbrowser-reborn'
import {
initWaapNative,
WalletComponent,
createInAppNativeBrowser
} from '@human.tech/waap-sdk-react-native'
export default function App() {
const [provider, setProvider] = useState(null)
const [account, setAccount] = useState(null)
const [chainId, setChainId] = useState(null)
const [loading, setLoading] = useState(false)
useEffect(() => {
const provider = initWaapNative({
project: {
name: 'My DApp',
appId: 'com.myapp.mobile',
logo: 'data:image/png;base64,...'
},
customConfig: {
styles: {
darkMode: true
}
},
nativeBrowser: createInAppNativeBrowser(InAppBrowser)
})
setProvider(provider)
// Event listeners
provider.on('accountsChanged', (accounts) => {
setAccount(accounts[0] || null)
})
provider.on('chainChanged', (newChainId) => {
setChainId(newChainId)
})
provider.on('disconnect', () => {
setAccount(null)
setChainId(null)
})
// Try auto-connect
provider
.request({ method: 'eth_requestAccounts' })
.then((accounts) => setAccount(accounts[0]))
.catch(() => console.log('Not connected'))
return () => {
provider.removeAllListeners()
}
}, [])
const connectWallet = async () => {
setLoading(true)
try {
await provider.login()
const accounts = await provider.request({
method: 'eth_requestAccounts'
})
setAccount(accounts[0])
} catch (error) {
console.error('Failed to connect:', error)
} finally {
setLoading(false)
}
}
const sendTransaction = async () => {
setLoading(true)
try {
const txHash = await provider.request({
method: 'eth_sendTransaction',
params: [
{
from: account,
to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
value: '0x0'
}
]
})
console.log('Transaction sent:', txHash)
} catch (error) {
console.error('Transaction failed:', error)
} finally {
setLoading(false)
}
}
const disconnect = async () => {
setLoading(true)
try {
await provider.logout()
setAccount(null)
setChainId(null)
} catch (error) {
console.error('Failed to disconnect:', error)
} finally {
setLoading(false)
}
}
if (!provider) {
return (
<SafeAreaProvider>
<View style={styles.centerContainer}>
<ActivityIndicator size='large' />
<Text>Initializing...</Text>
</View>
</SafeAreaProvider>
)
}
return (
<SafeAreaProvider>
<View style={styles.container}>
<Text style={styles.title}>WaaP SDK Demo</Text>
{account ? (
<View style={styles.section}>
<Text style={styles.label}>Account:</Text>
<Text style={styles.value}>
{account.slice(0, 6)}...{account.slice(-4)}
</Text>
{chainId && (
<>
<Text style={styles.label}>Chain ID:</Text>
<Text style={styles.value}>{chainId}</Text>
</>
)}
<View style={styles.buttonSpacing}>
<Button
title='Send Transaction'
onPress={sendTransaction}
disabled={loading}
/>
</View>
<Button title='Disconnect' onPress={disconnect} disabled={loading} />
</View>
) : (
<Button
title='Connect Wallet'
onPress={connectWallet}
disabled={loading}
/>
)}
{loading && <ActivityIndicator style={{ marginTop: 20 }} />}
<WalletComponent />
</View>
</SafeAreaProvider>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20
},
centerContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20
},
section: {
width: '100%',
padding: 16,
backgroundColor: '#f5f5f5',
borderRadius: 8,
marginBottom: 20
},
label: {
fontSize: 14,
color: '#666',
marginTop: 8
},
value: {
fontSize: 16,
fontWeight: '500',
marginBottom: 8
},
buttonSpacing: {
marginBottom: 12
}
})Troubleshooting
Metro bundler errors
If you see module resolution errors for viem or ox:
// metro.config.js
const { getDefaultConfig } = require('expo/metro-config')
const {
withWaapMetroConfig
} = require('@human.tech/waap-sdk-react-native/metro-plugin')
const config = getDefaultConfig(__dirname)
module.exports = withWaapMetroConfig(config)Authentication not working
Missing SafeAreaProvider: Ensure your app is wrapped with
<SafeAreaProvider>:import { SafeAreaProvider } from 'react-native-safe-area-context' return ( <SafeAreaProvider> <YourApp /> </SafeAreaProvider> )Missing native browser: Ensure you create and pass the browser function correctly:
For Expo:
import * as WebBrowser from 'expo-web-browser' import { createExpoNativeBrowser } from '@human.tech/waap-sdk-react-native' const provider = initWaapNative({ nativeBrowser: createExpoNativeBrowser(WebBrowser), // ... other options })For Bare React Native:
import InAppBrowser from 'react-native-inappbrowser-reborn' import { createInAppNativeBrowser } from '@human.tech/waap-sdk-react-native' const provider = initWaapNative({ nativeBrowser: createInAppNativeBrowser(InAppBrowser), // ... other options })iOS: Verify
associatedDomainsincludesapplinks:waap.xyzand bundle ID matchesproject.appIdAndroid: Check
android:autoVerify="true"is set and package name matchesproject.appIdTest on real devices (simulators have limited Universal Link support)
Confirm
project.appIdexactly matches your bundle ID/package name
Resources
- 📘 Full Documentation
- 🎮 Interactive Playground
- 💡 Examples & Templates
- 🔐 Security & Architecture
- 💬 Developer Telegram
- 💼 GitHub Issues
License
MIT
