Wallet sign-in needs v0.17.0 or later, and it is React-only for now: your backend requests and your wallet connection reach the SDK through
PassageProvider.How it works
- The browser asks your backend for a challenge. Your backend gets a Sign-In With Ethereum (SIWE) message from CoinList with
auth.walletChallenge. - The SDK asks the wallet to sign the message, then sends the signature to your backend.
- Your backend exchanges the signature with
auth.completeWalletSignIn, which stores the session as a CoinList login does. - The browser reads the token from your access-token endpoint. A browser holds one session at a time, so a wallet sign-in replaces a CoinList login.
What a wallet session can do
A wallet session has no CoinList account behind it, so it reaches only part of the SDK.- Works: Ondo buy and sell; Centrifuge buy, sell, and claim; and
requirements.forOffer. - Throws
NotAuthenticatedError:offers,wallets, Superstate, CoinList token sales, and every otherrequirementsmethod.
signInMethodFor applies this rule, so your sign-in button and the checkout agree:
Set up your OAuth client
Wallet sign-in requires these settings on your OAuth client. CoinList configures it, so contact us to set them:Add the backend endpoints
You need four endpoints. Two are new, and the other two are the ones a CoinList login uses. The samples build on thepassageServer factory and session store from Set up OAuth authentication.
Challenge
Setstatement on the server, never from the browser’s request: the wallet shows it to the user. It can hold up to 256 letters, digits, spaces, and URI punctuation, with no line breaks or double quotes. CoinList answers any other statement with a 422.
Serve message to the browser unchanged. The wallet signs it byte for byte, so any change fails the signature.
Complete
A challenge is single-use, so the SDK never retries this exchange.Access token
Use the access-token endpoint a CoinList login uses. It serves the token withsignInMethod, which says how the user signed in and, for a wallet session, which wallet the token acts for.
Sign-out
Use the logout endpoint you already have.auth.logout revokes the wallet session’s token like any other, as long as your OAuth client has the revoke grant type.
Protect the challenge and complete endpoints
These two endpoints are yours to secure. The samples above already call the helpers below.Block cross-site requests
Nothing in a signed grant says which browser asked for its challenge. Without a check, a page on another site can post a signature an attacker made for their own wallet, and sign your user into the attacker’s wallet session. So refuse requests from other sites on both endpoints, and accept a grant only from the browser that asked for that wallet’s challenge. AnOrigin check does the first, and a SameSite=Strict cookie does the second:
wallet-challenge-binding.ts
Rate-limit the challenge endpoint
The challenge endpoint is public, and each new challenge cancels the last one for that address. Without a limit, anyone can keep canceling a user’s challenge, so that user can never finish signing in. Limit requests per IP, with the rate limiter your platform provides. If you run several instances, count in a shared store, since a counter in one process sees only its own requests.Turn it on in the browser
Pass the walletSignIn prop
walletSignIn holds your three backend requests and your wallet connection. One prop holds both because a user who signs in with a wallet must be signed out when that wallet leaves, and connection is how the SDK finds out.
New functions and a new connection object on each render are fine. Only a changed status, address, or chain counts as a wallet change.
Providers inside your wallet library’s provider, so useAccount can read the connection.
Add a sign-in button
useWalletSignIn gives you signIn, which takes the wallet that signs, and state, which drives the button. The wallet must be the one connection reports as connected.
signIn resolves with how the sign-in ended:
Handle a sign-out
A wallet change, a network change, or a disconnect ends the session like a log-out. So does a checkout on another network. Each time, the SDK requestswalletSignIn.signOut and drops the token.
The SDK never navigates and never switches networks, so the rest is yours. Disconnect the wallet, as signOut does in the sample, and send the user back to the offer page to connect and sign in again: close any open checkout when usePassage().authState.type is logged-out.
Your own Sign out button only has to disconnect the wallet. The SDK sees the disconnect and does the rest.
Wallet behavior
- Map a wallet library’s “reconnecting” to
connecting. Otherwise every reload signs the user out, as the comment in the sample says. - Send the message hex-encoded.
personal_signmust receive the message in hex, or MetaMask shows a generic signature request and skips its wrong-site warning. viem’s and wagmi’ssignMessage({ message })do this for you. If you send the request to the provider yourself, encode the message with viem’sstringToHex. - Serve the sign-in page from your redirect URI’s host. The SDK takes the challenge’s domain from
redirectUri, and the wallet compares that domain with the page asking for the signature. On any other host, the wallet warns the user. - A session is for one address on one network. Sign the user in on the network of the offer they are buying. Pass
disconnectedas theconnectionfor a wallet on a network the SDK does not support, as the sample does. - Use the same wallet for
connectionand for the checkout’swallets.external. If you passwallets.externalwithoutwalletSignIn, nothing signs the user out when the wallet changes. - Checkout on a wallet session supports external wallets only. The checkout pays from
wallets.external. An embedded wallet can sign in, because the SDK accepts any signer, but the checkout then asks the user to connect their wallet, with no way forward. The Centrifuge claim card does accept an embedded wallet, so this limit is about checkout. - Smart accounts are untested. We have tested wallet sign-in with externally owned accounts only.
Next steps
Checkout
On a wallet session, the checkout opens on the session’s wallet, with no wallet step.
Wallets
Implement
EvmWallet, the interface a checkout pays through.