Expo quickstart
Email login, wallets, passkey approval and Sign in with Swop in an Expo or React Native app.
Preview: Swivy is not generally available
Swivy is in staging. It is not in production and Swop's own apps don't run on it yet. The SDK packages aren't published to npm; preview partners get access along with their credentials. Everything on this page can change. Request early access.
The mobile SDK, @swivy/expo, covers email login, session storage on the device, wallets, passkey approval through a native adapter, intents and Sign in with Swop.
Mobile limits today
split_custody local signing isn't at browser parity on mobile yet. Use the default managed_tee custody on iOS and Android for now.
1. Install
npm install @swivy/expo
npx expo install expo-auth-session expo-secure-store expo-crypto2. Keep the session in secure storage
import * as SecureStore from 'expo-secure-store';
import { createAsyncStorageSessionPersistence } from '@swivy/expo';
export const sessionPersistence = createAsyncStorageSessionPersistence({
getItem: (key) => SecureStore.getItemAsync(key),
setItem: (key, value) => SecureStore.setItemAsync(key, value),
removeItem: (key) => SecureStore.deleteItemAsync(key),
});3. Plug in your passkey library
Swivy doesn't force a passkey package. You provide an adapter that returns the WebAuthn JSON Swivy expects.
import type { SwivyExpoPasskeyAdapter } from '@swivy/expo';
export const passkeyAdapter: SwivyExpoPasskeyAdapter = {
async isSupported() { return true; },
async register(options, context) { return nativePasskeyRegister(options, context); },
async authenticate(options, context) { return nativePasskeyAuthenticate(options, context); },
};4. Create the client
import { SwivyExpoClient } from '@swivy/expo';
export const swivy = await SwivyExpoClient.create({
baseUrl: process.env.EXPO_PUBLIC_SWIVY_BASE_URL!,
appId: process.env.EXPO_PUBLIC_SWIVY_APP_ID!,
publishableKey: process.env.EXPO_PUBLIC_SWIVY_PUBLISHABLE_KEY!,
sessionPersistence,
passkeyAdapter,
});5. Sign in and load wallets
await swivy.requestEmailCode('alice@example.com');
await swivy.verifyEmailCode('alice@example.com', '123456');
const evm = await swivy.ensureEmbeddedWallet('evm', { label: 'Primary wallet' });
const sol = await swivy.ensureEmbeddedWallet('solana', { label: 'Primary wallet' });6. Approve and send
const passkey = await swivy.ensurePasskey('Primary passkey');
const approval = await swivy.approveWithPasskey(passkey.authMethod.id, 'intent_approval');
const intent = await swivy.createTransactionIntentAndWait({
accountId: evm.account.id,
chain: 'evm',
challengeId: approval.challenge.challengeId,
payload: {
network: 'base-sepolia',
transaction: { to: '0x1111111111111111111111111111111111111111', value: '0x0', data: '0x' },
},
});7. Sign in with Swop from a mobile app
Run the hosted authorize flow with expo-auth-session, then turn the token into a session:
const discovery = await swivy.getOidcDiscovery('app_swop_...');
// …run the authorization-code + PKCE flow against discovery.authorization_endpoint…
const session = await swivy.exchangeOidcToken(idToken);See Sign in with Swop for the full flow.