SoroPass

Guides

The full SoroPass flow on Stellar testnet. Create a passkey wallet, sign a payment, watch a wrong key get rejected on-chain, add a backup device, and recover on another device.

The complete flow, end to end, on Stellar testnet: the same run you can try live at demo.soropass.dev, every transaction on Stellar Expert. It uses the headless Path A API throughout; the config (cfg, deployer, indexer) is the one from the Quickstart.

1 · Create a wallet

A passkey becomes the first signer of a freshly deployed smart-account contract. No seed phrase, nothing to write down. The private key never leaves the device.

import { createPasskey } from '@soropass/core/create';

const account = await createPasskey({ ...cfg, userName: 'alice', deployer });
// Touch ID / Windows Hello → ES256 passkey → v1 smart-wallet deployed + funded.
// Persist account.credentialId (and account.contractId) for fast reconnects.

2 · Sign a payment

The wallet sends value; the passkey signs the exact Soroban auth entry (challenge bound to the auth preimage, low-S normalized) that the contract's __check_auth re-derives.

import { sendSmartWalletTx, browserPasskeySigner } from '@soropass/core/sign';
import { Contract, Address, nativeToScVal } from '@stellar/stellar-sdk';

// A native-asset (SAC) transfer FROM the smart wallet.
const transfer = new Contract(NATIVE_SAC).call(
  'transfer',
  Address.fromString(account.contractId).toScVal(),
  Address.fromString(destination).toScVal(),
  nativeToScVal(amount, { type: 'i128' }),
);

const res = await sendSmartWalletTx({
  operation: transfer,
  rpcUrl: cfg.rpcUrl,
  networkPassphrase: cfg.networkPassphrase,
  sourceSecret: FEE_SOURCE_SECRET,
  sign: browserPasskeySigner({ rpId: cfg.rpId, allowCredentials: [account.credentialId] }),
});
// res.status === 'SUCCESS' · res.hash → on Stellar Expert

3 · Reject a wrong key

The safety proof: the contract itself rejects a signature from the wrong key, on-chain, not the UI. sendSmartWalletTx re-simulates enforcing __check_auth, which runs secp256r1_verify against the stored signer and fails.

// Same call, but `sign` is a random software key that is NOT a registered signer.
try {
  await sendSmartWalletTx({ operation: transfer, ...cfg, sourceSecret, sign: wrongKeySigner });
} catch (err) {
  // Rejected on-chain during the enforcing re-simulation, exactly as intended. No funds move.
}

4 · Add a backup device

Register a second passkey as an additional signer with an on-chain add_signer, authorized by the first passkey, so a lost device never locks you out.

import { registerPasskey } from '@soropass/core/create';
import { addSigner } from '@soropass/core/recover';

const backup = await registerPasskey({ ...cfg, userName: 'backup' }); // register WITHOUT deploying
await addSigner({
  walletContractId: account.contractId,
  newSigner: backup,
  rpcUrl: cfg.rpcUrl,
  networkPassphrase: cfg.networkPassphrase,
  sourceSecret: FEE_SOURCE_SECRET,
  sign: browserPasskeySigner({ rpId: cfg.rpId, allowCredentials: [account.credentialId] }),
});
// The wallet is now multi-signer: both passkeys work.

5 · Recover on another device

On a fresh device with nothing stored, a discoverable passkey sign-in plus the on-chain indexer resolves the wallet(s) that passkey controls. No seed phrase, no local state.

import { recover } from '@soropass/core/recover';

const wallets = await recover({ rpId: cfg.rpId, indexer });
// wallets: { contractId, credentialId }[], resolved from on-chain signer_added events.

Surfaces & the OS sheet

The same components adapt across surfaces. The native passkey sheet (Face ID / Touch ID) is the OS. The card stays calm behind it, never a competing spinner.

Interactive sign preview

The real component runs in mock mode on the demo. Open the demo →

Calm opaque panel + pulsing glyph behind the native sheet.

Interactive create preview

The real component runs in mock mode on the demo. Open the demo →

Works from 360px; ≥44–48px touch targets; a bottom-edge OS-sheet hint bar; safe-area aware.

Desktop initiates, the phone that holds the passkey approves (hybrid / caBLE / QR). hybrid_transport is a documented, sourced surface (see the matrix row).

Previews run in mock mode (no network, no authenticator) because browsers restrict WebAuthn inside cross-origin iframes. The real ceremonies run at demo.soropass.dev, every transaction linked to Stellar Expert.

On this page