Expo SDK
@zypp-labs/expo-sdk brings offline-first Web3 intents to React Native (Expo) applications — mobile wallets, social apps, in-person payments, and any mobile use case.
Installation
npx expo install @zypp-labs/expo-sdk
iOS
Add to ios/Podfile:
pod 'ZyppSdk', :path => '../node_modules/@zypp-labs/expo-sdk/ios'
Then run pod install.
Android
The JNI bindings are auto-linked via the Expo module system. Ensure CMakeLists.txt is picked up by your Gradle build.
Initialization
Basic Setup
import { createIntent, markProvisional, getQueue } from '@zypp-labs/expo-sdk';
// Create and enqueue an intent
const { intentId } = createIntent('move_player', JSON.stringify({ x: 10, y: 20 }));
markProvisional(intentId);
Encrypted Storage
Initialize with a 32-byte key before creating any intents:
import { initWithSecureStorage } from '@zypp-labs/expo-sdk';
// Key from iOS Keychain / Android Keystore
initWithSecureStorage(platformKey);
initWithSecureStorage and initWithEncryption are identical at runtime — the difference is naming. initWithSecureStorage signals the key comes from platform secure storage; initWithEncryption is the low-level equivalent for custom key management.
API Reference
| Function | Description |
|---|---|
createIntent(kind, payload, trustTier?) | Create and enqueue an intent |
createPaymentIntent(amount, asset, recipient) | Create a Payment intent |
markProvisional(intentId) | Transition Pending → SyncedProvisional |
finalize(intentId) | Transition SyncedProvisional → SyncedFinalized |
attachSignature(intentId, sig, pubKey, nonce) | Attach cryptographic envelope |
attachIssuerSignature(intentId, sig, nonce) | Attach issuer signature (Ticket) |
attachHolderSignature(intentId, sig) | Attach holder signature (Ticket) |
getQueue() | Get all queued entries |
queueLength() | Get queue length |
settle(intentId, exitUrl, exitType) | Settle an intent |
settleWithStrategy(primaryUrl, primaryType, degradedUrl, degradedType) | Settle with degraded routing |
reportConnectivity(quality) | Report network quality |
createTransferPacket(intentId) | Create offline transfer packet |
decodeTransferPacket(packetB64) | Decode a transfer packet |
initWithEncryption(key) | Initialize encrypted persistence |
initWithSecureStorage(key) | Initialize with platform key |
registerKind(descriptor) | Register kind for validation |
lint() | Audit trust tier config |
All functions throw ZyppError on failure.
Common Patterns
Background Sync
Use expo-background-fetch for periodic sync in the background:
import * as BackgroundFetch from 'expo-background-fetch';
import * as TaskManager from 'expo-task-manager';
const BACKGROUND_SYNC_TASK = 'zypp-background-sync';
TaskManager.defineTask(BACKGROUND_SYNC_TASK, async () => {
const queue = getQueue();
for (const entry of queue) {
if (entry.status === 'Pending') {
settle(entry.intentId, 'https://api.mainnet-beta.solana.com', 0);
}
}
return BackgroundFetch.BackgroundFetchResult.NewData;
});
Push Notification on Finalization
import { getQueue } from '@zypp-labs/expo-sdk';
import * as Notifications from 'expo-notifications';
// Poll or listen for state changes
setInterval(() => {
const queue = getQueue();
for (const entry of queue) {
if (entry.status === 'SyncedFinalized') {
Notifications.scheduleNotificationAsync({
content: { title: 'Intent settled!', body: `Intent ${entry.intentId} finalized` },
trigger: null,
});
}
}
}, 5000);
Offline Transfer via QR
import { createTransferPacket, decodeTransferPacket } from '@zypp-labs/expo-sdk';
// Sender: create QR data
const { packetB64 } = createTransferPacket(intentId);
// Render packetB64 as a QR code...
// Receiver: decode
const decoded = decodeTransferPacket(scannedPacket);
BLE Transfer
import { BleAdapter } from '@zypp-labs/expo-sdk';
const ble = new BleAdapter(512); // BLE 5.0 MTU
// Send with chunking
const chunks = ble.chunkPacket(packet);
for (const chunk of chunks) {
await ble.writeChunk(chunk);
}
// Receive with reassembly
ble.onConnected();
for (const chunk of receivedChunks) {
const result = ble.feedChunk(chunk);
if (result) processPacket(result);
}
Error Handling
import { createIntent, ZyppError } from '@zypp-labs/expo-sdk';
try {
const intent = createIntent('action', JSON.stringify(payload));
markProvisional(intent.intentId);
} catch (error) {
if (error instanceof ZyppError) {
console.error('SDK error:', error.message);
}
}
Building from Source
./scripts/build-rust.sh # all targets
./scripts/build-rust.sh ios # iOS only
./scripts/build-rust.sh android # Android only
Requires rustup with aarch64-apple-ios and aarch64-linux-android targets.
Next Steps
- Build a Payment flow with the Expo SDK
- Set up encrypted storage for production
- See the Payments App example for a full mobile app reference