TriportRPC

POST /v1/auth/wallet/verify

POSThttps://api.triport.io/v1/auth/wallet/verify

Verify a wallet signature against a previously issued challenge nonce, then open an authenticated console session.

Ethereum (EVM) / Solana

This is the second and final step of the wallet sign-in flow. First call POST /v1/auth/wallet/challenge to obtain a single-use nonce and the exact human-readable message the wallet must sign. Have the user sign that message with their wallet, then post the nonce, address, chain, and resulting signature here.

The server reloads the challenge by nonce, confirms it was issued for the same address and chain, cryptographically verifies the signature, and — on success — links or creates the user account for that wallet identity and issues a console session. The session is delivered as cookies, not as a token: the response body contains only the user object.

Signature handling differs by chain:

  • EVM (chain: "evm") — a 65-byte secp256k1 signature in R‖S‖V order, supplied as hex (with or without a 0x prefix). V may be 27/28 or 0/1. The message is hashed with the EIP-191 personal_sign envelope (keccak256("\x19Ethereum Signed Message:\n" + len(msg) + msg)), the public key is recovered, and the derived address must match the submitted address (case-insensitive — EVM addresses are not EIP-55 checksum-validated).
  • Solana (chain: "solana") — a 64-byte ed25519 signature. It is accepted as hex, base58 (Phantom default), or base64 (standard or URL-safe, padded or unpadded). The address is the base58-encoded 32-byte public key, and the signature is verified directly against the challenge message.

Gotchas:

  • The nonce is single-use. It is burned on the first successful verify, so a retry with the same nonce returns invalid_nonce. Request a fresh challenge to try again.
  • The challenge expires 5 minutes after it is issued; an expired or unknown nonce returns invalid_nonce.
  • The address/chain you send here must match the values used in the challenge request, or you get address_mismatch.

Parameters

Request body (JSON). All fields are required.

noncestring (UUID)required
The single-use nonce returned by POST /v1/auth/wallet/challenge.
addressstringrequired
Wallet address. EVM: 0x + 40 hex chars. Solana: base58 of the 32-byte public key.
chainstringrequired
"evm" or "solana". Must match the chain used for the challenge.
signaturestringrequired
Signature over the challenge message. EVM: 65-byte hex. Solana: 64-byte hex/base58/base64.

Response

200 OK — the wallet was verified and a session was opened. The session and CSRF cookies are returned via Set-Cookie; the body carries the user record.

Response headers include:

Set-Cookie: nl_session=<opaque>; Path=/; HttpOnly; Secure; SameSite=Lax
Set-Cookie: nl_csrf=<opaque>; Path=/; Secure; SameSite=Lax

For wallet sign-ups, email is a synthetic placeholder of the form <address>@<chain>.wallet and display_name is the shortened address. Both can be updated later from the console.

user.idstring (UUID)
Stable user identifier.
user.emailstring
Account email. For wallet-only accounts this is a <address>@<chain>.wallet placeholder.
user.display_namestring
Display name; defaults to the shortened wallet address.
user.avatar_urlstring
Avatar URL, if set. Omitted when empty.
user.billing_customer_idstring
Billing customer reference, if any. Omitted when empty.
user.created_atstring (RFC 3339)
Account creation timestamp.
user.updated_atstring (RFC 3339)
Last update timestamp.

Errors

Errors use the shared envelope { "error": "<code>" }. See errors.md for the full envelope and conventions.

CodeHTTPWhen it happens
invalid_nonce400nonce is not a valid UUID, or no matching challenge exists (unknown, already consumed, or expired).
invalid_address400address is malformed for the given chain, or chain is not evm/solana.
address_mismatch400The address/chain submitted do not match the challenge the nonce was issued for.
invalid_signature400Signature is the wrong length/encoding, or it does not verify to the submitted address.
invalid_json400Request body is not valid JSON (or exceeds the 8 KiB limit).
session_issue_failed500Signature verified, but the server could not mint the session.
method_not_allowed405A method other than POST was used.
internal500Unexpected server-side failure (challenge lookup, nonce consume, or user upsert).

Examples

JavaScript (fetch)

async function walletVerify({ nonce, address, chain, signature }) {
  const res = await fetch("https://api.triport.io/v1/auth/wallet/verify", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    credentials: "include", // required so nl_session / nl_csrf cookies are stored
    body: JSON.stringify({ nonce, address, chain, signature }),
  });
  if (!res.ok) {
    const { error } = await res.json();
    throw new Error(error);
  }
  const { user } = await res.json();
  return user;
}

TypeScript SDK (@triport/sdk)

import { TriportConsole } from "@triport/sdk";


const console = new TriportConsole({ baseUrl: "https://api.triport.io" });


// 1. Request a challenge.
const { nonce, message } = await console.auth.wallet.challenge({
  address,
  chain: "solana",
});


// 2. Sign `message` with the connected wallet (e.g. Phantom).
const signature = await wallet.signMessage(message); // base58 / base64 / hex


// 3. Verify — session cookies are persisted by the SDK client.
const { user } = await console.auth.wallet.verify({
  nonce,
  address,
  chain: "solana",
  signature,
});

Python (triport-sdk)

from triport import ConsoleClient


client = ConsoleClient(base_url="https://api.triport.io")


# 1. Request a challenge.
challenge = client.auth.wallet.challenge(address=address, chain="evm")


# 2. Sign challenge.message with the user's wallet (65-byte secp256k1, hex).
signature = sign_personal_message(challenge.message, private_key)


# 3. Verify; the client session keeps the nl_session / nl_csrf cookies.
result = client.auth.wallet.verify(
    nonce=challenge.nonce,
    address=address,
    chain="evm",
    signature=signature,
)
print(result.user.id)

Notes

  • Two-step flow. Always pair this call with POST /v1/auth/wallet/challenge; the nonce and the message you sign both come from there.
  • Sign the exact message. The wallet must sign the challenge message byte-for-byte. Re-deriving or altering it (extra whitespace, different line endings) will fail signature verification.
  • Cookie session, not a bearer token. This endpoint returns no token. Reuse the nl_session cookie on subsequent requests, and echo the nl_csrf value in the X-CSRF-Token header on mutating calls. See the authentication overview.
  • Check who you are with GET /v1/auth/me, and end the session with POST /v1/auth/logout.