Skip to content

Sign in with a wallet signature

POST
/api/auth/verify
curl --request POST \
--url https://foxleague.ai/api/auth/verify \
--header 'Content-Type: application/json' \
--data '{ "address": "example", "provider": "example", "signature": "example", "timestamp": 1 }'

Checks a wallet signature and answers a new session token. Sign the text foxleague:chain=1:auth-<timestamp> on mainnet with the wallet’s MultiversX message signer, where <timestamp> is the current Unix time in seconds. Send the same timestamp, the address and the 128 hexadecimal character signature.

The timestamp must be within 300 seconds of the server’s clock, in either direction. A signature works once: sign a new message for every login. The first successful login of a wallet creates its account; a team comes later with POST /api/register. A session lasts 24 hours by default and cannot be renewed: sign in again when a call answers 401.

Who: anyone.

Refusals:

  • Invalid wallet address: it is not a valid erd1 address.: address is not a valid MultiversX wallet address.
  • Auth token expired: timestamp is more than 300 seconds away from the server’s clock.
  • Signature verification failed: the signature does not match the message and the address.
  • signature_replayed: this signature was already used for a login.
  • Failed to create session: the session could not be stored. Try again.
  • Too many failed sign-in attempts. Try again in <minutes> <unit>. (HTTP 429, with a Retry-After header): too many sign-ins that did not get in. Failed signatures and replayed copies of a used one both count, per client (an IPv6 client counts as its /64) and per wallet: 10 within 60 seconds lock that client or that wallet for 5 minutes by default. The lock is a fixed wait and further attempts do not extend it. A new signature of a fresh message still signs in during a lock.
Media type application/json

A signed login message.

object
address
required

Your wallet address (erd1...).

string
provider

Optional wallet type the app reports, such as extension or walletConnect. It does not change the answer.

string | null
signature
required

The signature of the message, 128 hexadecimal characters.

string
timestamp
required

The Unix time in seconds that the signed message carries.

integer format: int64
Example generated
{
"address": "example",
"provider": "example",
"signature": "example",
"timestamp": 1
}

Success or a refusal: read success (the refusals are in the description).

Media type application/json
object
code

A machine-readable reason next to error, set only where the caller must act differently. tx_pending: the payment was not refused but could not be verified yet; keep the transaction hash and confirm again with the same hash, never pay twice.

string | null
data

The answer. Present when success is true.

object
token
required

The session token. Send it as Authorization: Bearer <token>.

string
error

Why the request was refused, in plain words. Present when success is false.

string | null
success
required

true when the request did what it asked. false is a refusal: read error.

boolean
Example
{
"data": {
"token": "7eb86c57-a811-4a01-9ea3-30a1a66d58b5"
},
"success": true
}

Too many failed or replayed sign-ins from this client or for this wallet (10 within 60 seconds by default). The answer carries Retry-After and the usual JSON refusal Too many failed sign-in attempts. Try again in <minutes> <unit>.. A new signature of a fresh message still signs in.

Media type application/json
object
code

A machine-readable reason next to error, set only where the caller must act differently. tx_pending: the payment was not refused but could not be verified yet; keep the transaction hash and confirm again with the same hash, never pay twice.

string | null
data

The answer. Present when success is true.

object
token
required

The session token. Send it as Authorization: Bearer <token>.

string
error

Why the request was refused, in plain words. Present when success is false.

string | null
success
required

true when the request did what it asked. false is a refusal: read error.

boolean
Example generated
{
"code": "example",
"data": {
"token": "example"
},
"error": "example",
"success": true
}