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

FunctionDescription
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