BuildBaseBuildBase

Wallet sign-in

Let people sign in and sign up by signing a message with an Ethereum or Solana wallet in their browser.

For an app whose customers already live in a crypto wallet, a password is one more thing to make up. Wallet sign-in lets them sign in by signing a message with the wallet in their browser: Sign-In with Ethereum (EIP-4361), or its Solana counterpart. A wallet nobody has seen before signs up with a name and an email, as a new phone number does. It all runs on your hosted sign-in page.

Step by step, with screenshots: Set up wallet sign-in.

Before you start

Wallet sign-in needs a plan that includes it. Without one, the console shows the method locked and adding it is refused with a 402.

Setting it up

  1. Open User Management → Access → Authentication → Methods, choose Add auth method, then Wallet.
  2. Choose the Ethereum networks a wallet may sign in from: Ethereum, Polygon, Base, Arbitrum One and Optimism are listed by name, and Other chain IDs takes any other EIP-155 chain ID. Only Ethereum mainnet (chain ID 1) is allowed until you change it.
  3. Turn Solana on if Solana wallets may sign in. It is off by default.
  4. Tick the method on your client in the Settings tab.

A method has to allow at least one Ethereum network or Solana, or no wallet could sign in, and saving it is refused.

Which wallets work

Injected wallets with plain keys. For Ethereum, the EIP-1193 provider a browser wallet injects as window.ethereum; for Solana, a Phantom-style window.solana. The address is the key that signs.

Two kinds of wallet do not work yet, on purpose:

  • WalletConnect, the QR code that connects a phone wallet to a desktop page. It needs a project ID registered by each app owner.
  • Smart-contract wallets (ERC-1271). Checking their signatures needs a call to the chain, and BuildBase checks every signature offline.

What people see

The sign-in page offers the wallet, and asks it to sign a message the page builds from a one-time nonce. Nothing is sent to the chain and nothing costs gas. A wallet already linked to an account signs straight in.

  • A new wallet is asked for a name and an email after signing. The email starts unverified: a verification code goes to it, as after any sign-up, so wallet sign-up needs a connected email sender. An address that already has an account here is refused, with the same sentence whether it is taken or not allowed. Linking the wallet there would hand the account to whoever holds the wallet; its owner can connect the wallet from their own Security screen instead.
  • Sign-up rules apply. Closed sign-up, who can sign up, disposable addresses and your plan's end-user limit all hold for a new wallet.
  • The second step still applies. A wallet sign-in is one factor, swk in the token's amr, so a person with an authenticator app is asked for its code after signing, as after a password.

What is checked

A signed message is believed only when all of these hold:

  • it names your sign-in page's domain and address, so a message signed for another site is refused
  • its nonce is one BuildBase issued for this sign-in, less than 5 minutes ago, and it is spent on first use
  • its network is one the method allows. Solana has no network setting: a message naming mainnet, devnet or testnet is accepted
  • its times hold: issued recently, not expired, not dated in the future
  • the signature belongs to the address in the message, recovered with secp256k1 for Ethereum and checked with ed25519 for Solana

A wallet is then a connected account: provider ethereum with the EIP-55 checksum address, or solana with the base58 address.

Connecting a wallet from your app

A signed-in person can add a wallet to the account they already have. While your client offers wallet sign-in, the Connected accounts section of the SDK's Security screen shows Connect an Ethereum wallet and Connect a Solana wallet. Connected wallets are listed with a short address, count as a way in, and emit user.account_linked. Hide only the connect buttons with ui.settings.security.wallets: false.

Without the screen, connectWallet runs the whole exchange: it gets a nonce, asks an Ethereum wallet on another network to switch to an allowed one, has the wallet sign, and links the result.

import { connectWallet, WalletFlowError } from '@buildbase/sdk/react';

try {
  const account = await connectWallet('ethereum', { api }); // api: a WorkspaceApi
  console.log(account.detail); // the short address
} catch (error) {
  // no_wallet, rejected, wallet_busy, wrong_network, no_account, wallet_failed
  if (error instanceof WalletFlowError) console.warn(error.code);
  else throw error;
}

Connect from your app's own page. A wallet shows the person which site is asking for the signature, and warns when the message names a different one. So a link's message names your app, not the sign-in page, and the request has to come from one of your allowed app origins: the origin of one of your clients' redirect URLs. From anywhere else it is refused with 403 origin_not_allowed.

RefusalWhen
409 link_conflictThe wallet is connected to another account here
409 guest_accountA guest creates an account before connecting a wallet
401 nonce_expiredThe nonce lapsed or was used. Start again. The SDK does not sign anyone out for it
400 chain_not_allowedThe method does not take that kind of wallet, or that network
400 invalid_messageThe message was not made for this page, or its times are off
400 invalid_signatureThe signature is not the address's
403 origin_not_allowedThe request did not come from one of your allowed app origins
403 method_unavailableYour client offers no wallet sign-in

If you call the auth API yourself

The hosted page does this for you. Its three steps, with the sign-in page's state header:

RouteBodyAnswers
POST /api/v1/auth/wallet/nonce{ chain }, ethereum or solana{ nonce, domain, uri, issuedAt, chainIds }
POST /api/v1/auth/wallet/verify{ chain, message, signature }A sign-in, or { signupRequired, signupToken } for a new wallet
POST /api/v1/auth/wallet/register{ signupToken, name, email, turnstileToken? }A sign-in, as phone sign-up answers

/verify refuses with 400 invalid_signature, invalid_message or chain_not_allowed, and 401 nonce_expired. /register answers as phone sign-up does, from 409 email_unavailable to 401 signup_expired.

Next steps