Skip to main content

Overview

Proteus uses wallet-based JWT authentication with two supported methods:
  1. MetaMask/External Wallet - Sign a message with your wallet
  2. Coinbase Embedded Wallet - Email OTP authentication
All authenticated requests include:
Authorization: Bearer <jwt_token>

MetaMask Authentication Flow

Step 1: Get Nonce

Request a unique nonce for your wallet address.
cURL
curl https://proteus-production-6213.up.railway.app/auth/nonce/0x1234567890abcdef1234567890abcdef12345678
address
string
required
Your Ethereum wallet address (checksummed format recommended)
nonce
string
Unique 32-character hex string for this authentication attempt
message
string
Complete message to sign with your wallet

Response

{
  "nonce": "abc123def456...",
  "message": "Sign this message to authenticate with Proteus: abc123def456..."
}

Step 2: Sign Message

Sign the message with your wallet (client-side):
JavaScript
const signature = await ethereum.request({
  method: 'personal_sign',
  params: [message, address]
});

Step 3: Verify Signature

Submit the signature to receive a JWT token.
cURL
curl -X POST https://proteus-production-6213.up.railway.app/auth/verify \
  -H "Content-Type: application/json" \
  -d '{
    "address": "0x1234567890abcdef1234567890abcdef12345678",
    "signature": "0xabcdef...",
    "message": "Sign this message to authenticate with Proteus: abc123def456..."
  }'
address
string
required
Your Ethereum wallet address
signature
string
required
Hex-encoded signature from personal_sign
message
string
required
The exact message that was signed
token
string
JWT token for authenticated requests (expires in 24 hours)
address
string
Verified wallet address
expires_in
number
Token expiry time in seconds (86400 for 24 hours)

Response

{
  "success": true,
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "address": "0x1234567890abcdef1234567890abcdef12345678",
    "expires_in": 86400
  }
}

Coinbase Embedded Wallet Flow

Step 1: Request OTP

Send an OTP code to the user’s email.
cURL
curl -X POST https://proteus-production-6213.up.railway.app/api/embedded/auth/send-otp \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]"
  }'
email
string
required
User’s email address

Response

{
  "success": true,
  "message": "OTP sent successfully"
}

Step 2: Verify OTP

Submit the 6-digit OTP code to authenticate.
cURL
curl -X POST https://proteus-production-6213.up.railway.app/api/embedded/auth/verify-otp \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "otp": "123456"
  }'
email
string
required
User’s email address
otp
string
required
6-digit OTP code from email
email
string
Verified email address
wallet_address
string
Generated deterministic wallet address for this email

Response

{
  "success": true,
  "data": {
    "email": "[email protected]",
    "wallet_address": "0x9876543210fedcba9876543210fedcba98765432"
  },
  "message": "Successfully authenticated"
}

Token Management

Refresh Token

Refresh your JWT token before it expires.
cURL
curl -X POST https://proteus-production-6213.up.railway.app/auth/refresh \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Response

{
  "success": true,
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.new_token...",
    "address": "0x1234567890abcdef1234567890abcdef12345678",
    "expires_in": 86400
  }
}

Check Auth Status (JWT)

cURL
curl https://proteus-production-6213.up.railway.app/auth/jwt-status \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Response

{
  "authenticated": true,
  "address": "0x1234567890abcdef1234567890abcdef12345678"
}

Check Auth Status (Session)

For Coinbase Embedded Wallet users with session cookies:
cURL
curl https://proteus-production-6213.up.railway.app/api/embedded/auth/status \
  --cookie "session=..."

Response

{
  "authenticated": true,
  "email": "[email protected]",
  "wallet_address": "0x9876543210fedcba9876543210fedcba98765432",
  "wallet_type": "coinbase"
}

Logout

cURL
curl -X POST https://proteus-production-6213.up.railway.app/auth/logout \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
For JWT-based auth, the client should discard the token on logout. The server logs the event but does not invalidate the token (stateless design).

Rate Limiting

OTP Request Limits

  • 5 OTP requests per email per hour
  • Returns 429 Too Many Requests if exceeded

OTP Verification Limits

  • 5 failed attempts per email per hour
  • Account locked for 1 hour after 5 failures

Error Response

{
  "success": false,
  "error": {
    "code": "RATE_LIMITED",
    "message": "Too many OTP requests. Please try again later."
  }
}

Security Notes

  • Never share your JWT token or signature
  • Tokens expire after 24 hours for security
  • Nonces are single-use only (5 minute TTL)
  • OTP codes expire after 10 minutes
  • Always use HTTPS in production

Implementation Example

JavaScript
// MetaMask authentication
async function authenticateWithMetaMask() {
  // 1. Get wallet address
  const accounts = await ethereum.request({ method: 'eth_requestAccounts' });
  const address = accounts[0];
  
  // 2. Request nonce
  const nonceRes = await fetch(`/auth/nonce/${address}`);
  const { nonce, message } = await nonceRes.json();
  
  // 3. Sign message
  const signature = await ethereum.request({
    method: 'personal_sign',
    params: [message, address]
  });
  
  // 4. Verify signature
  const verifyRes = await fetch('/auth/verify', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ address, signature, message })
  });
  
  const { data } = await verifyRes.json();
  return data.token; // Store this token for API requests
}

// Use the token
async function fetchMarketsAuthenticated(token) {
  const res = await fetch('/api/chain/markets', {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });
  return res.json();
}

Build docs developers (and LLMs) love