APIPerps Auth

StandX Perps Authentication

⚠️ This document is under construction.

This document explains how to obtain JWT access tokens for the StandX Perps API through wallet signatures.

Prerequisites

  • Valid wallet address and corresponding private key
  • Development environment with ed25519 algorithm support

Authentication Flow

1. Prepare Wallet and Temporary ed25519 Key Pair

  1. Prepare Wallet: Ensure you have a blockchain wallet with its address and private key.
  2. Generate Temporary ed25519 Key Pair and requestId

2. Get Signature Data

Request signature data from the server:

Note: Code examples provided below are for reference purposes only and demonstrate the general implementation approach. Adapt them to your specific production environment.

Using curl

curl 'https://api.standx.com/v1/offchain/prepare-signin?chain=<chain>' \
  -H 'Content-Type: application/json' \
  --data-raw '{
    "address": "<your_wallet_address>",
    "requestId": "<base58_encoded_public_key>"
  }'

TypeScript/ES6 Implementation Reference

import axios from "axios";
 
const chain = "bsc"; // or "solana"
const walletAddress = "<your_wallet_address>";
const url = `https://api.standx.com/v1/offchain/prepare-signin?chain=${chain}`;
 
const data = {
  address: walletAddress,
  requestId: requestId, // requestId from previous step
};
 
try {
  const response = await axios.post(url, data, {
    headers: { "Content-Type": "application/json" },
  });
 
  if (response.data.success) {
    const signedData = response.data.signedData;
    // Use signedData for next step
  }
} catch (error) {
  console.error("Request failed:", error.message);
}

Request Parameters

ParameterTypeRequiredDescription
chainstringYesBlockchain network: bsc or solana
addressstringYesWallet address
requestIdstringYesBase58-encoded ed25519 public key from step 1

Success Response

{
  "success": true,
  "signedData": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9..."
}

3. Parse and Verify Signature Data

signedData is a JWT string that must be verified using StandX’s public key.

Get Verification Public Key

# Using curl
curl 'https://api.standx.com/v1/offchain/certs'

Example signedData Payload

{
  "domain": "standx.com",
  "uri": "https://standx.com",
  "statement": "Sign in with Ethereum to access more StandX features...",
  "version": "1",
  "chainId": 56,
  "nonce": "74Gd7Plf3a1TMVElc",
  "address": "0x...",
  "requestId": "<requestId>",
  "issuedAt": "2025-10-12T17:46:44.731Z",
  "message": "standx.com wants you to sign in with your Ethereum account:\n...",
  "exp": 1760291384,
  "iat": 1760291204
}

4. Sign the Message

Sign payload.message with your wallet private key to generate the signature.

BSC (EVM) Implementation Reference

import { ethers } from "ethers";
 
const provider = new ethers.JsonRpcProvider(
  "https://bsc-dataseed.binance.org/"
);
const privateKey = "<your_wallet_private_key>"; // Keep secure; use environment variables
const wallet = new ethers.Wallet(privateKey, provider);
 
// Sign using the message from the parsed payload
const signature = await wallet.signMessage(payload.message);

Solana Implementation Reference

import bs58 from "bs58";
import { ed25519 } from "@noble/curves/ed25519";
import { Keypair } from "@solana/web3.js";
 
const privateKey = "<your_base58_encoded_private_key>"; // Keep secure; use environment variables
const walletKeypair = Keypair.fromSecretKey(bs58.decode(privateKey));
 
// Sign using the message from the parsed payload
const messageBytes = new TextEncoder().encode(payload.message);
const signatureBytes = ed25519.sign(
  messageBytes,
  walletKeypair.secretKey.slice(0, 32) // First 32 bytes are the private key
);
 
// Solana requires a specific signature format
const signature = Buffer.from(
  JSON.stringify({
    input: payload,
    output: {
      signedMessage: Array.from(messageBytes),
      signature: Array.from(signatureBytes),
      account: {
        publicKey: Array.from(walletKeypair.publicKey.toBytes()),
      },
    },
  })
).toString("base64");

5. Get Access Token

Submit the signature and original signedData to the login endpoint.

Optional Parameter:

  • expiresSeconds (number): Token expiration time in seconds. Defaults to 604800 (7 days) if not specified. This controls how long the JWT access token remains valid before requiring re-authentication.

Security Note: For security best practices, avoid setting excessively long expiration times. Shorter token lifetimes reduce the risk of unauthorized access if a token is compromised. Consider your security requirements when configuring this value.

Using curl

curl 'https://api.standx.com/v1/offchain/login?chain=<chain>' \
  -H 'Content-Type: application/json' \
  --data-raw '{
    "signature": "0x...",
    "signedData": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9...",
    "expiresSeconds": 604800
  }'

TypeScript/ES6 Implementation Reference

const loginUrl = `https://api.standx.com/v1/offchain/login?chain=${chain}`;
 
try {
  const loginResponse = await axios.post(
    loginUrl,
    {
      signature,
      signedData,
      expiresSeconds, // default: seconds for 7days
    },
    {
      headers: { "Content-Type": "application/json" },
    }
  );
 
  const { token, address, chain } = loginResponse.data;
  // Store token for subsequent API requests
} catch (error) {
  console.error("Login failed:", error.message);
}

Success Response

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "address": "0x...",
  "alias": "user123",
  "chain": "bsc",
  "perpsAlpha": true
}

6. Use Access Token

Use the obtained token for subsequent API requests by adding Authorization: Bearer <token> to the request headers.

Body Signature Flow

Basic Flow

  1. Prepare a key pair
  2. Build message: {version},{id},{timestamp},{payload}
  3. Sign with private key
  4. Base64 encode signature
  5. Attach signature to request headers
{
    ...
    "authorization": "Bearer <token>",
    "x-request-sign-version": "v1",
    "x-request-id": "uuid",
    "x-request-timestamp": "timestamp",
    "x-request-signature": "signature",
    ...
}

Code example (only for reference):

import { ed25519 } from "@noble/curves/ed25519";
import { base58 } from "@scure/base";
import { v4 as uuidv4 } from "uuid";
 
/**
 * Sign request and return Base64-encoded signature.
 */
function encodeRequestSignature(
  xRequestVersion: string,
  xRequestId: string,
  xRequestTimestamp: number,
  payload: string,
  signingKey: Uint8Array
): string {
  // Build message to sign: "{version},{id},{timestamp},{payload}"
  const signMsg = `${xRequestVersion},${xRequestId},${xRequestTimestamp},${payload}`;
 
  // Sign message with Ed25519 private key
  const messageBytes = Buffer.from(signMsg, "utf-8");
  const signature = ed25519.sign(messageBytes, signingKey);
 
  // Base64 encode the signature
  return Buffer.from(signature).toString("base64");
}
 
// --- Example Usage ---
 
// Generate Ed25519 key pair
const privateKey = ed25519.utils.randomSecretKey();
const publicKey = ed25519.getPublicKey(privateKey);
 
// Generate requestId (base58-encoded public key)
const requestId = base58.encode(publicKey);
 
// Prepare request parameters
const xRequestVersion = "v1";
const xRequestId = uuidv4();
const xRequestTimestamp = Date.now();
 
const payloadDict = {
  user_id: 12345,
  data: "some important information",
};
const payloadStr = JSON.stringify(payloadDict);
 
// Generate signature
const signature = encodeRequestSignature(
  xRequestVersion,
  xRequestId,
  xRequestTimestamp,
  payloadStr,
  privateKey
);
 
// Verify signature (optional)
try {
  const verifyMsg = `v1,${xRequestId},${xRequestTimestamp},${payloadStr}`;
  const signatureBytes = Buffer.from(signature, "base64");
  const messageBytes = Buffer.from(verifyMsg, "utf-8");
 
  const isValid = ed25519.verify(signatureBytes, messageBytes, publicKey);
  if (!isValid) throw new Error("Verification failed");
} catch (error) {
  console.error("Signature verification error:", error.message);
}
 
// Send Request with Body Signature
fetch("/api/request_need_body_signature", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    authorization: `Bearer ${token}`,
    "x-request-sign-version": "v1",
    "x-request-id": xRequestId,
    "x-request-timestamp": xRequestTimestamp.toString(),
    "x-request-signature": signature,
  },
  body: payloadStr,
});

Complete Authentication Examples

For complete, runnable implementations, see the chain-specific examples: