> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://accounts.tempo.xyz/api/mcp` to find what you need.

# `wallet_connect`

Requests to connect account(s) with optional capabilities.

Set `capabilities.showDeposit` to `true` or funding hints to show an optional
deposit prompt after an account connects. The deposit target is always the
connected account on the active connect chain.

## Request

`capabilities` is a discriminated union over `method` — `'register'` or
`'login'`. Both branches share the `digest`, `authorizeAccessKey`, `auth`,
`identity`, `personalSign`, and `showDeposit` fields.

`digest` is signed as a raw hash, so only adapters running in your own app
honor it. A remote wallet (the Tempo Wallet dialog, popup or other remote
adapters) rejects a `wallet_connect` that sets it with `-32602` (invalid
params), since an app could otherwise pass a transaction hash and have it
signed behind a sign-in prompt. Use `personalSign` to sign during connect.

```ts
type Request = {
  method: 'wallet_connect'
  params?: [{
    capabilities?: ConnectRegisterCapabilities | ConnectLoginCapabilities
    /** Chain ID to connect to (hex-encoded number on the wire). */
    chainId?: number
    /** Caller-supplied protocol version string. */
    version?: string
  }]
}

type ConnectRegisterCapabilities = {
  method: 'register'
  /** Optional digest to bind into the registration ceremony. Rejected by remote wallets: use `personalSign`. */
  digest?: `0x${string}`
  /** Authorize an access key during the same ceremony. */
  authorizeAccessKey?: AuthorizeAccessKeyCapability
  /** SIWE round-trip configuration. See `Auth` below. */
  auth?: Auth
  /** Request consented identity claims (e.g. a verified email). See `Identity` below. */
  identity?: Identity
  /** Display name to associate with the new account. */
  name?: string
  /** Sign an EIP-191 message in the same passkey ceremony. */
  personalSign?: { message: string }
  /** Show an optional deposit prompt after the account connects. */
  showDeposit?: ShowDeposit
  /** Stable user identifier passed to the WebAuthn credential. */
  userId?: string
}

type ConnectLoginCapabilities = {
  method?: 'login'
  /** Optional digest to bind into the login ceremony. Rejected by remote wallets: use `personalSign`. */
  digest?: `0x${string}`
  /** WebAuthn credential ID or IDs to authenticate with. */
  credentialId?: string | string[]
  /** Authorize an access key during the same ceremony. */
  authorizeAccessKey?: AuthorizeAccessKeyCapability
  /** SIWE round-trip configuration. See `Auth` below. */
  auth?: Auth
  /** Request consented identity claims (e.g. a verified email). See `Identity` below. */
  identity?: Identity
  /** Sign an EIP-191 message in the same passkey ceremony. */
  personalSign?: { message: string }
  /** Show an optional deposit prompt after the account connects. */
  showDeposit?: ShowDeposit
  /** Show an account picker before authenticating. */
  selectAccount?: boolean
}

type Identity = {
  /**
   * Request the account's verified email. Returned on the connected account
   * as `capabilities.identity.email` (a plain string, for client-side use)
   * and `capabilities.identity.idToken` (a signed token, for server-side
   * verification).
   *
   * - `true` — request the email; the wallet auto-issues the signed token,
   *   reusing the `auth` (SIWE) challenge nonce when present.
   * - `'user@example.com'` — require this exact email: the wallet forces the
   *   user to authenticate with the account owning it (verifying it first
   *   when needed) instead of a free-form entry.
   * - `{ address, nonce }` — same as above, with an explicit nonce baked into
   *   the token (for standalone use without the `auth` capability). Both
   *   fields are optional; `{ nonce }` alone requests any email.
   * - `{ domains }` — restrict email entry to these domains: the wallet
   *   rejects entering an email outside them. An account's already-verified
   *   email is still shared; verify server-side.
   */
  email?: boolean | string | { address?: string; domains?: string[]; nonce?: string }
}

type ShowDeposit = boolean | {
  /** Funding path to open. Use mach for MACH checkout. */
  intent?: 'credits' | 'crypto' | 'faucet' | 'mach' | 'referralCode' | 'x'
  /** Human-readable amount to pre-fill (e.g. `"50"`). */
  amount?: string
  /** Display name shown in the deposit UI (e.g. the app name). */
  displayName?: string
  /** Connect method that should show the deposit prompt. Defaults to both methods. */
  on?: 'login' | 'register'
  /** Token contract address or supported token symbol to pre-fill. */
  token?: `0x${string}` | string
}

type AuthorizeAccessKeyCapability = {
  address?: Address
  /** Hex on the wire, decoded to `bigint`. */
  chainId?: bigint
  expiry: number
  keyType?: 'secp256k1' | 'p256' | 'webAuthn'
  limits?: {
    token: Address
    /** Hex on the wire, decoded to `bigint`. */
    limit: bigint
    /** Window length in seconds. Omit for an absolute cap. */
    period?: number
  }[]
  publicKey?: Hex
  scopes?: {
    address: Address
    selector?: Hex | string
    recipients?: Address[]
  }[]
}

type Auth = string | {
  /**
   * Base URL. The SDK derives `${url}/challenge` and `${url}/logout`;
   * `${url}` itself is the verify endpoint.
   */
  url?: string
  /** Override individual endpoints. Either `url` or both `challenge` + `verify` must be set. */
  challenge?: string
  verify?: string
  logout?: string
  /**
   * Ask the verify endpoint to also return `{ token }` in the JSON body.
   * Default `false` — cookie mode relies on `Set-Cookie` only.
   */
  returnToken?: boolean
}
```

## Response

```ts
type Response = {
  accounts: {
    address: Address
    capabilities: {
      /**
       * Consented identity claims, present only when requested via
       * `capabilities.identity` AND approved by the user. Omitted entirely
       * otherwise. Only set on the authenticated account.
       */
      identity?: {
        /** Verified email address, if requested and approved. May be `null`. */
        email?: string | null
        /**
         * Signed identity token (JWT) asserting the verified email, bound to
         * the requesting app origin (`aud`) and the connected account (`sub`).
         * Verify it server-side against the wallet's JWKS. Present only when
         * `identity.email` was requested and a verified email is available.
         */
        idToken?: string
      }
      /** Authorized access key, if `authorizeAccessKey` was requested. */
      keyAuthorization?: KeyAuthorization
      /**
       * Echo of the `personalSign` request, present iff the caller set
       * `capabilities.personalSign` (or `capabilities.auth` folded a SIWE
       * message into the same slot). The signature itself lives on
       * `capabilities.signature`.
       */
      personalSign?: { message: string }
      /** Signature for `personalSign` / SIWE message, if requested. */
      signature?: Hex
      /** Authenticated username, if available. May be `null`. */
      username?: string | null
      /**
       * SIWE round-trip output, populated when the request `auth`
       * capability was set. `token` is present in JWT mode or when the
       * request set `returnToken: true`. Cookie-mode default = `{}`.
       */
      auth?: { token?: string }
    }
  }[]
}
```

## Example

```ts
import { Provider } from 'accounts'

const provider = Provider.create()

const { accounts } = await provider.request({
  method: 'wallet_connect',
})
```

### Requesting a verified email

Set `capabilities.identity.email` to `true` to ask for the account's verified
email. The wallet runs an in-flow approval after the connect ceremony: if the
account already has a verified email it shows a consent prompt; otherwise it
collects and verifies an email (one-time code) before sharing it. The user can
decline — `wallet_connect` still succeeds, just without `identity`.

```ts
const { accounts } = await provider.request({
  method: 'wallet_connect',
  params: [{ capabilities: { identity: { email: true } } }],
})

// Present only if the user approved sharing; set on the authenticated account.
const email = accounts[0]?.capabilities?.identity?.email
```

The response also carries a signed `idToken` alongside the plain `email`. The
plain string is convenient for client-side display, but it can be set by anyone.
Forward the `idToken` to your backend and verify it against the wallet's JWKS to
trust the email server-side.

```ts
const identity = accounts[0]?.capabilities?.identity
// `email` for display; `idToken` to prove the email to your backend.
const { email, idToken } = identity ?? {}
```

The token is a standard OIDC ID token (JWT) bound to your app origin (`aud`) and
the connected account address (`sub`). Verify it with any OIDC-aware library, or
with [`Identity.verify`](/docs/server/identity.verify).

### Requiring a specific email

Set `capabilities.identity.email` to an email address to require that exact
email. The wallet pre-fills and locks the sign-in entry to it: the user must
authenticate with the account that owns the email (verifying it with a one-time
code first when needed) and cannot substitute another one. Connecting an
account whose verified email differs is rejected. Use this when your app
already knows who is signing in — an invite link, a pre-auth session, or a
server-side allowlist.

```ts
const { accounts } = await provider.request({
  method: 'wallet_connect',
  params: [{ capabilities: { identity: { email: 'user@example.com' } } }],
})
```

Use the object form to also bind an explicit nonce into the issued token:

```ts
const { accounts } = await provider.request({
  method: 'wallet_connect',
  params: [
    {
      capabilities: {
        identity: { email: { address: 'user@example.com', nonce } },
      },
    },
  ],
})
```

### Restricting email domains

Set `capabilities.identity.email` to `{ domains }` to restrict email entry to
an allowlist of domains. The wallet rejects entering an email outside them
(matched case-insensitively) in both the sign-in entry and the in-flow email
prompt. Use this when your server restricts sign-ups to approved domains, so
users get immediate feedback instead of a rejection after authenticating.

```ts
const { accounts } = await provider.request({
  method: 'wallet_connect',
  params: [
    {
      capabilities: {
        identity: { email: { domains: ['tempo.xyz'] } },
      },
    },
  ],
})
```

The restriction applies to email *entry* only: an account whose email is
already verified still shares it, since your server may accept existing or
invited users beyond the allowlist. Always enforce the policy server-side by
verifying the `idToken`. An exact `address` takes precedence over `domains`.
