Files
ritmex-bot/docs/standx/auth.md
T

396 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 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
#### Request Parameters
| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| chain | string | Yes | Blockchain network: `bsc` or `solana` |
| address | string | Yes | Wallet address |
| requestId | string | Yes | Base58-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 StandXs public key.
#### Get Verification Public Key
```
# Using curl
curl 'https://api.standx.com/v1/offchain/certs'
```
#### Example signedData Payload
### 4\. Sign the Message
Sign `payload.message` with your wallet private key to generate the `signature`.
#### TypeScript/ES6 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);
```
### 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
#### TypeScript/ES6 Implementation Reference
#### 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 Class Example
Heres a complete implementation using a class-based approach:
```
import { ed25519 } from "@noble/curves/ed25519";
import { base58 } from "@scure/base";
export type Chain = "bsc" | "solana";
export interface SignedData {
domain: string;
uri: string;
statement: string;
version: string;
chainId: number;
nonce: string;
address: string;
requestId: string;
issuedAt: string;
message: string;
exp: number;
iat: number;
}
export interface LoginResponse {
token: string;
address: string;
alias: string;
chain: string;
perpsAlpha: boolean;
}
export interface RequestSignatureHeaders {
"x-request-sign-version": string;
"x-request-id": string;
"x-request-timestamp": string;
"x-request-signature": string;
}
export class StandXAuth {
private ed25519PrivateKey: Uint8Array;
private ed25519PublicKey: Uint8Array;
private requestId: string;
private baseUrl = "https://api.standx.com";
constructor() {
const privateKey = ed25519.utils.randomSecretKey();
this.ed25519PrivateKey = privateKey;
this.ed25519PublicKey = ed25519.getPublicKey(privateKey);
this.requestId = base58.encode(this.ed25519PublicKey);
}
async authenticate(
chain: Chain,
walletAddress: string,
signMessage: (msg: string) => Promise<string>
): Promise<LoginResponse> {
const signedDataJwt = await this.prepareSignIn(chain, walletAddress);
const payload = this.parseJwt<SignedData>(signedDataJwt);
const signature = await signMessage(payload.message);
return this.login(chain, signature, signedDataJwt);
}
private async prepareSignIn(chain: Chain, address: string): Promise<string> {
const res = await fetch(
\`${this.baseUrl}/v1/offchain/prepare-signin?chain=${chain}\`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ address, requestId: this.requestId }),
}
);
const data = await res.json();
if (!data.success) throw new Error("Failed to prepare sign-in");
return data.signedData;
}
private async login(
chain: Chain,
signature: string,
signedData: string,
expiresSeconds: number = 604800 // default: 7 days
): Promise<LoginResponse> {
const res = await fetch(
\`${this.baseUrl}/v1/offchain/login?chain=${chain}\`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ signature, signedData, expiresSeconds }),
}
);
return res.json();
}
signRequest(
payload: string,
requestId: string,
timestamp: number
): RequestSignatureHeaders {
const version = "v1";
const message = \`${version},${requestId},${timestamp},${payload}\`;
const signature = ed25519.sign(
Buffer.from(message, "utf-8"),
this.ed25519PrivateKey
);
return {
"x-request-sign-version": version,
"x-request-id": requestId,
"x-request-timestamp": timestamp.toString(),
"x-request-signature": Buffer.from(signature).toString("base64"),
};
}
private parseJwt<T>(token: string): T {
const base64Url = token.split(".")[1];
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
return JSON.parse(Buffer.from(base64, "base64").toString("utf-8"));
}
}
// Usage Example
import { ethers } from "ethers";
async function example() {
// Initialize auth
const auth = new StandXAuth();
// Setup wallet
const provider = new ethers.JsonRpcProvider(
"https://bsc-dataseed.binance.org/"
);
const privateKey = process.env.WALLET_PRIVATE_KEY!;
const wallet = new ethers.Wallet(privateKey, provider);
// Authenticate
const loginResponse = await auth.authenticate(
"bsc",
wallet.address,
async (message) => wallet.signMessage(message)
);
console.log("Access Token:", loginResponse.token);
// Sign a request
const payload = JSON.stringify({
symbol: "BTC-USD",
side: "buy",
order_type: "limit",
qty: "0.1",
price: "50000",
time_in_force: "gtc",
reduce_only: false,
});
const headers = auth.signRequest(payload, crypto.randomUUID(), Date.now());
// Make authenticated request
await fetch("https://perps.standx.com/api/new_order", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: \`Bearer ${loginResponse.token}\`,
...headers,
},
body: payload,
});
}
```
Last updated on
[About StandX API](https://docs.standx.com/standx-api/standx-api "About StandX API") [Perps HTTP API](https://docs.standx.com/standx-api/perps-http "Perps HTTP API")