Skip to main content
You sign in by proving you own a wallet: the relayer makes a challenge, your wallet signs it, and the relayer answers with an access token and a refresh token. An EVM wallet signs a Sign-In with Ethereum (SIWE) message. A Stellar wallet co-signs a SEP-10 challenge transaction. A user holds at most one EVM wallet and one Stellar wallet. The first wallet you sign in with creates the user; you attach the other with POST /v1/auth/link while signed in. Signing in later with either wallet lands on the same user.

Endpoints

Challenge, login and link use the strict rate limit (10 a minute per IP). Refresh and logout allow 10 a minute per session.

Tokens and sessions

  • A refresh token works once. Each refresh spends the current token and hands you the next. Sending a spent token again within 30 seconds returns the same next pair (a retry after a lost answer, or a second tab). After that, the relayer treats it as a stolen copy, ends the whole session and answers 401 refresh-reused.
  • Refresh once, share the result. If several calls hit a 401 at the same time, run one refresh and let every waiting call reuse it. Better, refresh a little before the access token’s exp, at a random point so clients signed in together do not refresh together.
  • Logout ends the refresh tokens at once. An access token already issued keeps working until its own 15 minutes run out, so delete it on your side.

Browser or server client

The relayer decides where the refresh token travels by looking at the request:
  • Browser (the request carries Origin or Sec-Fetch-Site, which page scripts cannot remove): the refresh token is set as an HttpOnly cookie, pb_refresh on Path=/v1/auth, and left out of the JSON body. Login, refresh and logout must come from the app’s origin with an X-Requested-With header, or they are refused with 403 csrf-refused. Send them with credentials (credentials: "include" in fetch, withCredentials: true in axios), or the browser drops the cookie. A browser refresh or logout sends {} as the body.
  • Server client (a bot, a script, an agent; Node’s fetch counts): both tokens come back in the body, and you send the refresh token in the body of refresh and logout. No cookie is read or set.

Get a challenge

POST /v1/auth/challenge
EVM response. Build the SIWE message from domain, uri, nonce and the checksummed address:
Stellar response. The wallet signs transaction exactly as it is, with networkPassphrase. Do not add operations or change anything:
The SEP-10 transaction has sequence number 0, so it can never be submitted to the network.

Log in

POST /v1/auth/login. Send it within 5 minutes of the challenge.
Response 201:
What the relayer checks:
  • EVM. The message parses as SIWE with a checksummed address, its domain and URI match the challenge’s exactly, and the signature verifies as a plain wallet (EOA) signature. Smart-contract wallets (EIP-1271) cannot sign in. The nonce must belong to that address, be unused, and be no more than 60 seconds past its 5 minutes.
  • Stellar. The transaction is this relayer’s challenge for this network, unchanged, and carries a signature by the account’s own master key. An account whose master key was replaced by other signers cannot sign in. Each signed challenge works once.

Refresh

POST /v1/auth/refresh A server client sends its refresh token in the body:
A browser sends {} with credentials; the cookie carries the token:
Response 200. Store the new pair; the old refresh token is now spent. A browser gets access only and a new pb_refresh cookie.
On any 401 here, sign the user out and start again at the challenge. A 429 or a 5xx is not a sign-out: keep the tokens and try again later. A 401 also clears a browser’s cookie.

Log out

POST /v1/auth/logout. Takes the same body as refresh (a server client’s refresh token, or {} from a browser). An expired refresh token still works, so you can log out after the access token has run out. Response 204 with no body. Every refresh token of that sign-in stops working; your other sign-ins (another browser, another bot) are not touched. Logging out a session that has already ended still answers 204. A browser’s cookie is cleared even when the token is refused. POST /v1/auth/link, with your access token. Get a fresh challenge for the wallet you are adding, sign it with that wallet, and send the same body as login. Instead of signing you in, the relayer attaches the wallet to your user. No new tokens are issued.
Response 201:
The challenge is spent before the link rules run, so after a 409 get a new challenge before you try another wallet. There is no route to unlink or replace a wallet. Linked smart accounts (a Stellar custody account, or an EVM custody account) are a separate thing; see Accounts.

Who am I

GET /v1/user/me
Use it to decide whether to prompt “Link your Stellar wallet”. Many write routes answer 403 No STELLAR wallet linked — connect and link the matching wallet to continue (or EVM) when you lack the wallet kind they need.

JavaScript example

A server-side client signing in with an EVM key, calling a guarded route, refreshing and logging out. In a browser, add credentials: "include" and an X-Requested-With header to the auth calls and do not store the refresh token yourself: the pb_refresh cookie carries it.
Treat the refresh token like a password. In a browser, let the HttpOnly cookie hold it and keep the access token in memory.