Expo and React Native support is a new feature in v0.3 with specialized providers for mobile environments.
Overview
React Native and Expo applications require special handling for:
- Server-Sent Events (SSE): Standard EventSource doesn’t work in React Native
- Streaming: JSON streaming requires custom fetch implementation
- Cryptography:
crypto.getRandomValues()
polyfill is required
The v0.3 SDK provides Expo-compatible providers that handle these requirements automatically.
Installation
First, install the required dependencies:
npm install @arkade-os/sdk
npx expo install expo-crypto
Crypto Polyfill Setup
You must polyfill crypto.getRandomValues()
before importing the SDK. This is required for MuSig2 settlements and cryptographic operations.
Add this at the top of your app entry point (before any SDK imports):
// App.tsx or index.js - MUST be first import
import * as Crypto from 'expo-crypto'
if (!global.crypto) global.crypto = {} as any
global.crypto.getRandomValues = Crypto.getRandomValues
// Now import the SDK
import { Wallet, SingleKey } from '@arkade-os/sdk'
import { ExpoArkProvider, ExpoIndexerProvider } from '@arkade-os/sdk/adapters/expo'
Basic Setup
Create a wallet with Expo-compatible providers:
import { Wallet, SingleKey } from '@arkade-os/sdk'
import { ExpoArkProvider, ExpoIndexerProvider } from '@arkade-os/sdk/adapters/expo'
import { AsyncStorageAdapter } from '@arkade-os/sdk/adapters/asyncStorage'
// Setup storage
const storage = new AsyncStorageAdapter()
// Load or create identity
let privateKeyHex = await storage.getItem('private-key')
if (!privateKeyHex) {
const newIdentity = SingleKey.fromRandomBytes()
privateKeyHex = newIdentity.toHex()
await storage.setItem('private-key', privateKeyHex)
}
const identity = SingleKey.fromHex(privateKeyHex)
// Create wallet with Expo providers
const wallet = await Wallet.create({
identity,
esploraUrl: 'https://mutinynet.com/api',
arkProvider: new ExpoArkProvider('https://mutinynet.arkade.sh'),
indexerProvider: new ExpoIndexerProvider('https://mutinynet.arkade.sh'),
storage
})
// Use wallet normally
const address = await wallet.getAddress()
const balance = await wallet.getBalance()
Understanding Expo Providers
The SDK includes two specialized providers for Expo/React Native:
ExpoArkProvider
Handles settlement events and transaction streaming using expo/fetch
for Server-Sent Events:
import { ExpoArkProvider } from '@arkade-os/sdk/adapters/expo'
const arkProvider = new ExpoArkProvider('https://mutinynet.arkade.sh')
ExpoIndexerProvider
Handles address subscriptions and VTXO updates using expo/fetch
for JSON streaming:
import { ExpoIndexerProvider } from '@arkade-os/sdk/adapters/expo'
const indexerProvider = new ExpoIndexerProvider('https://mutinynet.arkade.sh')
Both providers follow the SDK’s modular architecture pattern, keeping the main bundle clean while providing opt-in functionality for specific environments.
Complete Example
Here’s a complete React Native component with wallet integration:
import React, { useEffect, useState } from 'react'
import { View, Text, Button } from 'react-native'
import { Wallet, SingleKey } from '@arkade-os/sdk'
import { ExpoArkProvider, ExpoIndexerProvider } from '@arkade-os/sdk/adapters/expo'
import { AsyncStorageAdapter } from '@arkade-os/sdk/adapters/asyncStorage'
export default function WalletScreen() {
const [wallet, setWallet] = useState(null)
const [address, setAddress] = useState('')
const [balance, setBalance] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
initWallet()
}, [])
async function initWallet() {
try {
const storage = new AsyncStorageAdapter()
// Load or create identity
let privateKeyHex = await storage.getItem('private-key')
if (!privateKeyHex) {
const newIdentity = SingleKey.fromRandomBytes()
privateKeyHex = newIdentity.toHex()
await storage.setItem('private-key', privateKeyHex)
}
const identity = SingleKey.fromHex(privateKeyHex)
// Create wallet
const newWallet = await Wallet.create({
identity,
esploraUrl: 'https://mutinynet.com/api',
arkProvider: new ExpoArkProvider('https://mutinynet.arkade.sh'),
indexerProvider: new ExpoIndexerProvider('https://mutinynet.arkade.sh'),
storage
})
setWallet(newWallet)
// Get address and balance
const addr = await newWallet.getAddress()
const bal = await newWallet.getBalance()
setAddress(addr)
setBalance(bal)
} catch (error) {
console.error('Failed to initialize wallet:', error)
} finally {
setLoading(false)
}
}
async function refreshBalance() {
if (!wallet) return
const bal = await wallet.getBalance()
setBalance(bal)
}
if (loading) {
return <Text>Loading wallet...</Text>
}
return (
<View style={{ padding: 20 }}>
<Text>Address: {address}</Text>
<Text>Balance: {balance?.total || 0} sats</Text>
<Button title="Refresh Balance" onPress={refreshBalance} />
</View>
)
}
Using AsyncStorage
For persistent storage in React Native, use AsyncStorageAdapter
:
import { AsyncStorageAdapter } from '@arkade-os/sdk/adapters/asyncStorage'
const storage = new AsyncStorageAdapter()
// Store identity
await storage.setItem('private-key', privateKeyHex)
// Load identity
const privateKeyHex = await storage.getItem('private-key')
Common Issues
Crypto Not Defined
Error: crypto is not defined
or crypto.getRandomValues is not a function
Solution: Ensure the crypto polyfill is set up before importing the SDK:
import * as Crypto from 'expo-crypto'
if (!global.crypto) global.crypto = {} as any
global.crypto.getRandomValues = Crypto.getRandomValues
EventSource Not Available
Error: EventSource is not defined
Solution: Use ExpoArkProvider
and ExpoIndexerProvider
instead of default providers:
import { ExpoArkProvider, ExpoIndexerProvider } from '@arkade-os/sdk/adapters/expo'
const wallet = await Wallet.create({
identity,
arkProvider: new ExpoArkProvider(arkUrl),
indexerProvider: new ExpoIndexerProvider(arkUrl)
})
AsyncStorage Errors
Error: AsyncStorage is null
Solution: Make sure you’re using AsyncStorageAdapter
from the SDK:
import { AsyncStorageAdapter } from '@arkade-os/sdk/adapters/asyncStorage'
const storage = new AsyncStorageAdapter()
Testing on Devices
When testing on physical devices or emulators:
- Make sure your device can reach the Arkade server URL
- Use a publicly accessible server (not localhost)
- Check network permissions in your app configuration