React quickstart
Email login, an embedded wallet, a passkey and a first transaction in a React 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.
By the end of this page your React app will sign a user in with an email code, give them an EVM wallet, enroll a passkey and send a transaction approved by that passkey.
1. Install
npm install @swivy/react @swivy/web2. Configure
Your browser app needs three values from your Swivy environment. They're safe to ship to the browser; the secret key never is.
NEXT_PUBLIC_SWIVY_BASE_URL=https://YOUR_SWIVY_BASE_URL
NEXT_PUBLIC_SWIVY_APP_ID=app_...
NEXT_PUBLIC_SWIVY_PUBLISHABLE_KEY=pk_swivy_...3. Mount the provider
import { SwivyProvider } from '@swivy/react';
export function AppProviders({ children }: { children: React.ReactNode }) {
return (
<SwivyProvider
config={{
baseUrl: process.env.NEXT_PUBLIC_SWIVY_BASE_URL!,
appId: process.env.NEXT_PUBLIC_SWIVY_APP_ID!,
publishableKey: process.env.NEXT_PUBLIC_SWIVY_PUBLISHABLE_KEY!,
sessionPersistence: 'localStorage', // keeps users signed in across reloads
}}
>
{children}
</SwivyProvider>
);
}4. Sign in with an email code
import { useState } from 'react';
import { useSwivySession } from '@swivy/react';
export function Login() {
const auth = useSwivySession();
const [email, setEmail] = useState('');
const [code, setCode] = useState('');
const [sent, setSent] = useState(false);
if (auth.isLoading) return <p>Loading…</p>;
if (auth.isAuthenticated) return <p>Signed in.</p>;
return sent ? (
<>
<input value={code} onChange={(e) => setCode(e.target.value)} placeholder="6-digit code" />
<button onClick={() => auth.signIn(email, code)}>Verify</button>
</>
) : (
<>
<input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="you@example.com" />
<button onClick={async () => { await auth.requestEmailCode(email); setSent(true); }}>Send code</button>
</>
);
}On success Swivy creates any missing wallets your environment policy allows (by default one EVM and one Solana wallet) and returns them in the session as managedAccounts.
Other ways in: auth.signInWithOidcToken(idToken) after Sign in with Swop, or auth.requestWalletLogin() + auth.signInWithWallet() for an existing EVM wallet or Phantom.
5. Load the wallet
import { useEmbeddedWallets } from '@swivy/react';
export function WalletAddress() {
const wallets = useEmbeddedWallets('evm');
const wallet = wallets.wallets[0];
return <code>{wallet?.address ?? 'No wallet yet'}</code>;
}Call wallets.ensureManagedAccount('evm') to create one explicitly. Use 'solana' for Solana.
6. Enroll a passkey
import { useSwivyPasskeys } from '@swivy/react';
export function PasskeySetup() {
const passkeys = useSwivyPasskeys();
return <button onClick={() => passkeys.ensurePasskey('Primary passkey')}>Add a passkey</button>;
}7. Send a transaction the user approves
import { useEmbeddedWallets, useSwivyIntents, useSwivyPasskeys } from '@swivy/react';
export function Send({ passkeyId }: { passkeyId: string }) {
const wallets = useEmbeddedWallets('evm');
const passkeys = useSwivyPasskeys();
const intents = useSwivyIntents();
async function send() {
const wallet = wallets.wallets[0] ?? (await wallets.ensureManagedAccount('evm')).account;
const approval = await passkeys.approveWithPasskey(passkeyId, 'intent_approval');
const intent = await intents.createTransactionIntentAndWait({
accountId: wallet.id,
chain: 'evm',
challengeId: approval.challenge.challengeId,
payload: {
network: 'base-sepolia',
transaction: { to: '0x1111111111111111111111111111111111111111', value: '0x0', data: '0x' },
},
});
console.log(intent.status, intent.result);
}
return <button onClick={send}>Send</button>;
}Handling passkey errors
Catch Swivy's stable error codes instead of raw browser exceptions:
import { isSwivyWebAuthnError } from '@swivy/web';
try {
await passkeys.approveWithPasskey(passkeyId, 'intent_approval');
} catch (error) {
if (isSwivyWebAuthnError(error)) console.log(error.code, error.recoveryHint);
throw error;
}| Code | Usually means |
|---|---|
webauthn_wrong_origin | The page's origin isn't allowed for this environment's passkeys. |
webauthn_stale_challenge | The challenge expired. Ask for a new one. |
webauthn_missing_credential | This device has no passkey for the user. Enroll one. |
webauthn_canceled | The user dismissed the prompt. |