Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions docs/pages/wallets/auth/email-otp.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Email OTP Authentication [Authenticate with a one-time code sent by email]

:::danger[IMPORTANT]
**USE FOR INTERNAL TESTING PURPOSES ONLY.** You may use these features solely for internal evaluation purposes on supported testnets. DO NOT use for production use or share with your users. Wallets created during this preview ("Alpha Wallets") will be discontinued. Any tokens remaining within Alpha Wallets will be permanently lost upon discontinuance. Any mainnet tokens sent to an Alpha Wallet will not be deposited and will be permanently lost when discontinued. We are unable to help recover any lost funds from Alpha Wallets. We provide all previews on an "as is" basis without warranty of any kind, and we may terminate or suspend the availability of any preview at any time.
:::

Email OTP is a two-step flow: send a verification code to the user's email, then verify the code they enter.

## Hooks

- [`useSendOTP`](/wallets/hooks/use-send-otp) — Send a one-time code to an email address
- [`useVerifyOTP`](/wallets/hooks/use-verify-otp) — Verify the code and authenticate

## Example

```tsx
import { useState } from 'react'
import { useAccount, useDisconnect } from 'wagmi'
import { useSendOTP, useVerifyOTP } from '@zerodev/wallet-react'

function EmailOTPAuth() {
const [email, setEmail] = useState('')
const [code, setCode] = useState('')
const [otpId, setOtpId] = useState<string | null>(null)

const { address, isConnected } = useAccount()
const { disconnectAsync } = useDisconnect()
const sendOTP = useSendOTP()
const verifyOTP = useVerifyOTP()

if (isConnected) {
return (
<div>
<p>Connected: {address}</p>
<button onClick={() => disconnectAsync()}>Logout</button>
</div>
)
}

if (!otpId) {
return (
<div>
<input
type="email"
placeholder="Enter your email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button
onClick={async () => {
const result = await sendOTP.mutateAsync({ email })
setOtpId(result.otpId)
}}
disabled={sendOTP.isPending || !email}
>
{sendOTP.isPending ? 'Sending...' : 'Send Code'}
</button>
{sendOTP.isError && <p>Error: {sendOTP.error.message}</p>}
</div>
)
}

return (
<div>
<p>Code sent to {email}</p>
<input
type="text"
placeholder="Enter verification code"
value={code}
onChange={(e) => setCode(e.target.value)}
/>
<button
onClick={() => verifyOTP.mutateAsync({ code, otpId })}
disabled={verifyOTP.isPending || !code}
>
{verifyOTP.isPending ? 'Verifying...' : 'Verify Code'}
</button>
{verifyOTP.isError && <p>Error: {verifyOTP.error.message}</p>}
</div>
)
}
```

## How it works

1. **Send code**: `useSendOTP` sends a one-time verification code to the provided email address. It returns an `otpId` that identifies this verification attempt.

2. **Verify code**: `useVerifyOTP` takes the `otpId` and the code the user entered. If the code is valid, the SDK authenticates and creates a session.

After verification, the Wagmi connector is connected and the user's address is available via `useAccount`.
85 changes: 85 additions & 0 deletions docs/pages/wallets/auth/google-oauth.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Google OAuth [Authenticate with Google sign-in]

:::danger[IMPORTANT]
**USE FOR INTERNAL TESTING PURPOSES ONLY.** You may use these features solely for internal evaluation purposes on supported testnets. DO NOT use for production use or share with your users. Wallets created during this preview ("Alpha Wallets") will be discontinued. Any tokens remaining within Alpha Wallets will be permanently lost upon discontinuance. Any mainnet tokens sent to an Alpha Wallet will not be deposited and will be permanently lost when discontinued. We are unable to help recover any lost funds from Alpha Wallets. We provide all previews on an "as is" basis without warranty of any kind, and we may terminate or suspend the availability of any preview at any time.
:::

Google OAuth lets users sign in with their Google account using a popup-based flow. The SDK handles the popup, redirect, and session creation automatically.

## Hook

- [`useAuthenticateOAuth`](/wallets/hooks/use-authenticate-oauth) — Trigger the OAuth flow

## Example

```tsx
import { useAccount, useDisconnect } from 'wagmi'
import {
useAuthenticateOAuth,
OAUTH_PROVIDERS,
} from '@zerodev/wallet-react'

function GoogleAuth() {
const { address, isConnected } = useAccount()
const { disconnectAsync } = useDisconnect()
const authenticateOAuth = useAuthenticateOAuth()

if (isConnected) {
return (
<div>
<p>Connected: {address}</p>
<button onClick={() => disconnectAsync()}>Logout</button>
</div>
)
}

return (
<div>
<button
onClick={() =>
authenticateOAuth.mutateAsync({
provider: OAUTH_PROVIDERS.GOOGLE,
})
}
disabled={authenticateOAuth.isPending}
>
{authenticateOAuth.isPending
? 'Signing in...'
: 'Sign in with Google'}
</button>

{authenticateOAuth.isError && (
<p>Error: {authenticateOAuth.error.message}</p>
)}
</div>
)
}
```

## How it works

1. **Open popup**: `useAuthenticateOAuth` opens a popup window to the KMS backend's OAuth endpoint.

2. **Google sign-in**: The backend initiates the Google OAuth flow with PKCE. The user signs in with their Google account.

3. **Backend callback**: Google redirects back to the backend, which exchanges the auth code for tokens and sets a session cookie.

4. **Popup redirect**: The backend redirects the popup to your app with `?oauth_success=true`.

5. **Auto-detect**: The SDK detects the success parameter. If running in a popup, it sends a `postMessage` to the opener window and closes. The main window then calls the backend's auth endpoint (reading the session cookie) to get a session.

After the flow completes, the Wagmi connector is connected and the user's address is available via `useAccount`.

## Backend requirements

Google OAuth requires a KMS proxy backend that handles the OAuth flow. Set the `proxyBaseUrl` connector option to point to your backend:

```tsx
zeroDevWallet({
projectId: 'YOUR_PROJECT_ID',
proxyBaseUrl: 'https://your-backend.com',
chains: [sepolia],
})
```

The backend must have Google OAuth credentials configured (`GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET`).
107 changes: 107 additions & 0 deletions docs/pages/wallets/auth/magic-link.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Magic Link Authentication [Authenticate with a link sent by email]

:::danger[IMPORTANT]
**USE FOR INTERNAL TESTING PURPOSES ONLY.** You may use these features solely for internal evaluation purposes on supported testnets. DO NOT use for production use or share with your users. Wallets created during this preview ("Alpha Wallets") will be discontinued. Any tokens remaining within Alpha Wallets will be permanently lost upon discontinuance. Any mainnet tokens sent to an Alpha Wallet will not be deposited and will be permanently lost when discontinued. We are unable to help recover any lost funds from Alpha Wallets. We provide all previews on an "as is" basis without warranty of any kind, and we may terminate or suspend the availability of any preview at any time.
:::

Magic links let users authenticate by clicking a link in their email. The link redirects to your app where the SDK completes the verification.

## Hooks

- [`useSendMagicLink`](/wallets/hooks/use-send-magic-link) — Send a magic link to an email address
- [`useVerifyMagicLink`](/wallets/hooks/use-verify-magic-link) — Verify the magic link and authenticate

## Example

### Login page — send the magic link

```tsx
import { useState } from 'react'
import { useSendMagicLink } from '@zerodev/wallet-react'

function MagicLinkLogin() {
const [email, setEmail] = useState('')
const sendMagicLink = useSendMagicLink()

return (
<div>
<input
type="email"
placeholder="Enter your email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button
onClick={async () => {
const result = await sendMagicLink.mutateAsync({
email,
redirectURL: `${window.location.origin}/verify`,
})
// Store otpId for the verify page
sessionStorage.setItem('magicLinkOtpId', result.otpId)
}}
disabled={sendMagicLink.isPending || !email}
>
{sendMagicLink.isPending ? 'Sending...' : 'Send Magic Link'}
</button>

{sendMagicLink.isSuccess && (
<p>Check your email for the magic link!</p>
)}
{sendMagicLink.isError && (
<p>Error: {sendMagicLink.error.message}</p>
)}
</div>
)
}
```

### Verify page — handle the redirect

When the user clicks the magic link, they're redirected to your `redirectURL` with query parameters. The verify page reads these and completes authentication:

```tsx
import { useEffect } from 'react'
import { useSearchParams } from 'next/navigation'
import { useAccount } from 'wagmi'
import { useVerifyMagicLink } from '@zerodev/wallet-react'

function VerifyMagicLink() {
const searchParams = useSearchParams()
const { isConnected, address } = useAccount()
const verifyMagicLink = useVerifyMagicLink()

useEffect(() => {
const code = searchParams.get('code')
const otpId = sessionStorage.getItem('magicLinkOtpId')

if (code && otpId && !isConnected) {
verifyMagicLink.mutateAsync({ otpId, code })
}
}, [searchParams])

if (isConnected) {
return <p>Connected: {address}</p>
}

if (verifyMagicLink.isPending) {
return <p>Verifying...</p>
}

if (verifyMagicLink.isError) {
return <p>Error: {verifyMagicLink.error.message}</p>
}

return <p>Waiting for verification...</p>
}
```

## How it works

1. **Send link**: `useSendMagicLink` sends an email with a magic link pointing to your `redirectURL`. It returns an `otpId` that you need for verification.

2. **User clicks link**: The user clicks the link in their email, which redirects them to your app with a `code` query parameter.

3. **Verify**: Your verify page calls `useVerifyMagicLink` with the `otpId` and `code`. If valid, the SDK authenticates and creates a session.

After verification, the Wagmi connector is connected and the user's address is available via `useAccount`.
86 changes: 86 additions & 0 deletions docs/pages/wallets/auth/passkeys.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Passkey Authentication [Register and login with WebAuthn passkeys]

:::danger[IMPORTANT]
**USE FOR INTERNAL TESTING PURPOSES ONLY.** You may use these features solely for internal evaluation purposes on supported testnets. DO NOT use for production use or share with your users. Wallets created during this preview ("Alpha Wallets") will be discontinued. Any tokens remaining within Alpha Wallets will be permanently lost upon discontinuance. Any mainnet tokens sent to an Alpha Wallet will not be deposited and will be permanently lost when discontinued. We are unable to help recover any lost funds from Alpha Wallets. We provide all previews on an "as is" basis without warranty of any kind, and we may terminate or suspend the availability of any preview at any time.
:::

Passkeys use the [WebAuthn](https://webauthn.guide/) standard for passwordless authentication. Users authenticate with biometrics (Face ID, Touch ID, fingerprint) or a hardware security key.

## Configure RP ID (optional)

Set `rpId` on the connector if you need a custom [relying party identifier](https://www.w3.org/TR/webauthn-2/#relying-party-identifier) (defaults to the current hostname):

```ts
zeroDevWallet({
projectId: process.env.NEXT_PUBLIC_ZERODEV_PROJECT_ID!,
chains: [sepolia],
rpId: "example.com",
});
```

## Hooks

- [`useRegisterPasskey`](/wallets/hooks/use-register-passkey) — Create a new wallet with a passkey
- [`useLoginPasskey`](/wallets/hooks/use-login-passkey) — Sign in to an existing wallet

## Example

```tsx
import { useAccount, useDisconnect } from 'wagmi'
import { useRegisterPasskey, useLoginPasskey } from '@zerodev/wallet-react'

function PasskeyAuth() {
const { address, isConnected } = useAccount()
const { disconnectAsync } = useDisconnect()
const registerPasskey = useRegisterPasskey()
const loginPasskey = useLoginPasskey()

if (isConnected) {
return (
<div>
<p>Connected: {address}</p>
<button onClick={() => disconnectAsync()}>Logout</button>
</div>
)
}

return (
<div>
<button
onClick={() => registerPasskey.mutateAsync()}
disabled={registerPasskey.isPending}
>
{registerPasskey.isPending ? 'Registering...' : 'Create Wallet'}
</button>

<button
onClick={() => loginPasskey.mutateAsync()}
disabled={loginPasskey.isPending}
>
{loginPasskey.isPending ? 'Logging in...' : 'Login'}
</button>

{registerPasskey.isError && (
<p>Registration error: {registerPasskey.error.message}</p>
)}
{loginPasskey.isError && (
<p>Login error: {loginPasskey.error.message}</p>
)}
</div>
)
}
```

## How it works

1. **Register**: `useRegisterPasskey` triggers the browser's WebAuthn prompt. The user creates a passkey stored on their device. A new wallet is created and linked to the passkey.

2. **Login**: `useLoginPasskey` triggers the WebAuthn prompt. The user selects their existing passkey. The SDK authenticates and creates a session.

After either flow, the Wagmi connector is connected and the user's address is available via `useAccount`.

## Notes

- No email is required for passkey auth — the passkey itself is the credential
- Passkeys are supported in all major browsers (Chrome, Safari, Firefox, Edge)
- Passkeys can sync across devices via iCloud Keychain, Google Password Manager, etc.
Loading