Skip to content

Email OTP

Use email OTP as a server-verified challenge while keeping recovery and signing secrets on the wallet boundary.

Email OTP

Email OTP is a server-verified channel challenge and worker-owned secret flow.

Role in the model

Email OTP can:

  1. prove control of a verified email channel;
  2. authorize worker-owned Email OTP secret reconstruction;
  3. create or restore signing capabilities under Wallet Session policy;
  4. step up exhausted or expired Email OTP Wallet Session quotas;
  5. authorize export or recovery only through fresh operation-specific policy.

Secret-bearing Email OTP material belongs in the dedicated Email OTP worker or encrypted storage. App-origin code should not receive recovered Email OTP secrets or derived signing shares.

Start a login

Exchange a Google ID token for a typed login flow, then submit the code entered by the user. The registration branch is handled separately because it carries its own wallet selection and recovery-code backup ceremony.

Runnable TypeScript example
ts
import type {
  GoogleEmailOtpWalletAuthLoginFlow,
  GoogleEmailOtpWalletAuthSubmitSuccess,
  SeamsWeb,
} from '@seams/wallet';

export async function startGoogleEmailOtpLogin(
  seams: SeamsWeb,
  googleIdToken: string,
): Promise<GoogleEmailOtpWalletAuthLoginFlow> {
  const started = await seams.auth.beginGoogleEmailOtpWalletAuth({
    idToken: googleIdToken,
    mode: 'login',
    loginTarget: { kind: 'discoverable' },
  });
  if (!started.ok) {
    throw new Error(started.error.message);
  }
  if (started.value.mode !== 'login') {
    await started.value.cancel();
    throw new Error('This Google account needs wallet registration');
  }
  return started.value;
}

export async function submitGoogleEmailOtp(
  flow: GoogleEmailOtpWalletAuthLoginFlow,
  otpCode: string,
): Promise<GoogleEmailOtpWalletAuthSubmitSuccess> {
  const submitted = await flow.submit({ otpCode });
  if (!submitted.ok) {
    throw new Error(submitted.error.message);
  }
  return submitted.value;
}