Quickstart
Prerequisites
npm install @noble/ed255192
Configure your environment
# Mainnet (defaults shown; omit to use them)
export PERPL_API_URL="https://app.perpl.xyz/api"
export PERPL_WS_URL="wss://app.perpl.xyz" # WebSocket URL has NO /api prefix
export PERPL_CHAIN_ID="143"
# Testnet
# export PERPL_API_URL="https://testnet.perpl.xyz/api"
# export PERPL_WS_URL="wss://testnet.perpl.xyz"
# export PERPL_CHAIN_ID="10143"
# Your enrolled key (from Step 1)
export PERPL_API_KEY="<your X-API-Key token>"
export PERPL_API_KEY_SECRET="<hex of your 32-byte Ed25519 private key>"Variable
Purpose
Mainnet default
3
Sign and send your first request
<chain_id>
<HTTP_METHOD> e.g. GET, POST
<request-target> path + query string exactly as sent, e.g. /v1/trading/fills?count=1
<timestamp_ms> unix epoch milliseconds, decimal
<nonce> client-random, base64url (no padding)
<sha256(body) hex> hex of SHA-256 over the raw body ("" body → sha256 of empty string)Header
Value
import { createHash, randomBytes } from 'crypto';
import * as ed from '@noble/ed25519';
const API_URL = process.env.PERPL_API_URL || 'https://app.perpl.xyz/api';
const CHAIN_ID = Number(process.env.PERPL_CHAIN_ID) || 143;
const API_KEY = process.env.PERPL_API_KEY!;
const privateKey = Buffer.from(
(process.env.PERPL_API_KEY_SECRET ?? '').replace(/^0x/, ''),
'hex',
);
// `target` is the path + query string exactly as sent.
async function signedFetch(method: string, target: string, body = '') {
const timestamp = Date.now().toString();
const nonce = randomBytes(16).toString('base64url');
const bodyHash = createHash('sha256').update(body).digest('hex');
const canonical = [CHAIN_ID, method, target, timestamp, nonce, bodyHash].join('\n');
const sig = await ed.signAsync(Buffer.from(canonical), privateKey);
return fetch(`${API_URL}${target}`, {
method,
headers: {
'X-API-Key': API_KEY,
'X-API-Timestamp': timestamp,
'X-API-Nonce': nonce,
'X-API-Signature': Buffer.from(sig).toString('base64url'),
...(body ? { 'Content-Type': 'application/json' } : {}),
},
...(body ? { body } : {}),
});
}
// Example: read your most recent fill.
const res = await signedFetch('GET', '/v1/trading/fills?count=1');
console.log(res.status, await res.json());Signature validity
If it fails
Status
Meaning
Fix
Next steps
Last updated