docs
Get startedTroubleshootingPaymentsWebhooksAgent paymentsSwop MCPOviIdentity (swop.id)SwivyRPC (preview)API reference

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-crypto

2. 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.

On this page