recover & connect
Bring a returning SoroPass user back to their Stellar smart account — silent connect() on a known device, discoverable recover() for new devices.
Two ways to bring a returning user back to their smart account. connect() is the silent happy path on a known device; recover() is the new-device / cleared-storage path. Both map a passkey credential to its C-address through an IndexerAdapter.
Overview
The two functions ship from dedicated subpaths so a wallet only pulls in what it uses:
import { connect } from '@soropass/core/connect';
import { recover } from '@soropass/core/recover';connect() first. When it returns null there is no stored credential or no account — fall through to recover(), which performs a discoverable get and resolves every account controlled by the credential.@stellar/stellar-sdk is a peer dependency — it is never bundled into @soropass/core. The account resolution that turns a credential into a C-address is delegated entirely to your IndexerAdapter.Highlights
Silent reconnect
Discoverable recovery
One and many accounts
Degrades gracefully
connect()
Silent reconnect using the stored credential id. Where conditional mediation is available, does a best-effort mediation:'silent' liveness check; the C-address is resolved via the IndexerAdapter regardless, so connect degrades gracefully where silent mediation is unsupported. Returns null when there is no stored credential (the caller should recover) or no account.
connect(options: ConnectOptions): Promise<ConnectResult | null>ConnectOptions
| Field | Type | Description |
|---|---|---|
rpId (required) | string | Relying Party ID for the WebAuthn ceremony. |
indexer (required) | IndexerAdapter | Resolves the credential to its C-address(es). |
webauthn | WebAuthnClient | Optional WebAuthn client override (defaults to the platform navigator.credentials). |
storage | CredentialStorage | Where the stored credential id is read from. |
silentMediationSupported | boolean | Override the isConditionalMediationAvailable probe. |
ConnectResult
| Field | Type | Description |
|---|---|---|
contractId | string | The resolved smart-account C-address. |
credentialId | string | The passkey credential id that controls it. |
Returns — Promise<ConnectResult | null>. null means no stored credential or no account; the caller should fall through to recover().
Usage
import { connect } from '@soropass/core/connect';
const session = await connect({
rpId: 'wallet.example.com',
indexer,
storage,
});
if (session) {
// known device — silent reconnect succeeded
use(session.contractId, session.credentialId);
} else {
// no stored credential or no account — fall through to recover()
}recover()
The lost-localStorage / new-device path. Performs a discoverable-credential get() (no allowCredentials), then resolves every smart account controlled by that credential via the IndexerAdapter.
recover(options: RecoverOptions): Promise<RecoverResult[]>RecoverOptions
| Field | Type | Description |
|---|---|---|
rpId (required) | string | Relying Party ID for the WebAuthn ceremony. |
indexer (required) | IndexerAdapter | Resolves the recovered credential to its C-address(es). |
webauthn | WebAuthnClient | Optional WebAuthn client override (defaults to the platform navigator.credentials). |
challenge | Uint8Array | Optional challenge bytes for the get() ceremony. |
userActivation | { isActive: boolean } | Caller-supplied user-gesture state for the WebAuthn call. |
RecoverResult
| Field | Type | Description |
|---|---|---|
contractId | string | A smart-account C-address controlled by the credential. |
credentialId | string | The discoverable passkey credential id that was used. |
Returns — Promise<RecoverResult[]>. One credential can control several accounts; recover() handles both 1 and many — present a picker when the array has more than one entry.
Usage
import { recover } from '@soropass/core/recover';
const accounts = await recover({
rpId: 'wallet.example.com',
indexer,
userActivation: { isActive: true },
});
if (accounts.length === 1) {
open(accounts[0].contractId);
} else if (accounts.length > 1) {
// one credential, several smart accounts — let the user choose
showPicker(accounts);
}IndexerAdapter
Both functions take an IndexerAdapter to map a credential to its C-address(es). It exposes a single method, resolveByCredential, which returns the accounts a credential controls. See Adapters for the bundled events indexer and how to write your own.
interface IndexerAdapter {
resolveByCredential(credentialId: string): Promise<ResolvedAccount[]>;
}
// ResolvedAccount = { contractId: string }| Field | Type | Description |
|---|---|---|
resolveByCredential | (credentialId: string) => Promise<ResolvedAccount[]> | Returns every account whose signer is the given credential. |
Connect-then-recover
connect() is the happy path on a known device — a null result falls through to recover(), the new-device / cleared-storage path. This is the idiomatic startup sequence:
import { connect } from '@soropass/core/connect';
import { recover } from '@soropass/core/recover';
async function restore(rpId, indexer, storage) {
const session = await connect({ rpId, indexer, storage });
if (session) return [session];
// null -> no stored credential / no account: recover
return recover({ rpId, indexer, userActivation: { isActive: true } });
}signTransaction
Sign Soroban transactions and authorization entries with a passkey — WebAuthn assertion, low-S normalization, and contract-verifiable signature assembly.
Adapters
Two small pluggable seams for submission and indexing, with a zero-infra default of direct submission plus an on-chain events indexer.