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 uses a one-time password to check access to an email address. The server verifies the challenge. A dedicated worker handles the secret material.

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 the user entered. 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.startGoogleSignIn({
    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;
}