Skip to main content

Generate Request Signature

Orderly uses the ed25519 elliptic curve standard for request authentication via signature verification.
1

Orderly Account ID

Register your account and obtain your account ID. The registration steps are provided here. Add your account ID to the request header as orderly-account-id.
2

Orderly Key

Add your Orderly public key to the request header as orderly-key. To generate and add a new Orderly Key, see the documentation. You can also obtain Orderly keys from frontend builders like WOOFi Pro.
3

Timestamp

Take the current timestamp in milliseconds and add it as orderly-timestamp to the request header.
4

Normalize request content

Normalize the message to a string by concatenating the following:
  1. Current timestamp in milliseconds, e.g. 1649920583000
  2. HTTP method in uppercase, e.g. POST
  3. Request path including query parameters (without base URL), e.g. /v1/orders?symbol=PERP_BTC_USDC
  4. (Optional) If the request has a body, JSON stringify it and append
Example result:
1649920583000POST/v1/order{"symbol": "PERP_ETH_USDC", "order_type": "LIMIT", "order_price": 1521.03, "order_quantity": 2.11, "side": "BUY"}
5

Generate signature

Sign the normalized content using the ed25519 algorithm, encode the signature in base64 url-safe format, and add the result to the request header as orderly-signature.
6

Content type

Set the Content-Type header:
  • GET and DELETE: application/x-www-form-urlencoded
  • POST and PUT: application/json
7

Send the request

The final request should have the following headers:
HeaderDescription
Content-TypeRequest content type
orderly-account-idYour Orderly account ID
orderly-keyYour Orderly public key
orderly-signatureed25519 signature (base64url)
orderly-timestampRequest timestamp (ms)
The Orderly Key should be used without the ed25519: prefix when used in code samples below.

Minimal Example

This is a concise, single-file TypeScript example that demonstrates how to sign and send a request to the Orderly API using the @noble/curves library.
import { ed25519 } from "@noble/curves/ed25519";
import { base58 } from "@scure/base";

async function main() {
  const orderlyAccountId = "YOUR_ACCOUNT_ID";
  const orderlySecret = "YOUR_ORDERLY_SECRET"; // base58 encoded
  const baseUrl = "https://testnet-api.orderly.org";
  const url = new URL(`${baseUrl}/v1/order`);
  const method = "POST";
  const body = JSON.stringify({
    symbol: "PERP_ETH_USDC",
    order_type: "MARKET",
    order_quantity: 0.01,
    side: "BUY"
  });
  const timestamp = Date.now();

  const privateKey = base58.decode(orderlySecret);
  const message = `${timestamp}${method}${url.pathname}${url.search}${body}`;
  const signatureBytes = ed25519.sign(new TextEncoder().encode(message), privateKey);

  const publicKeyBytes = ed25519.getPublicKey(privateKey);

  const response = await fetch(url, {
    method,
    body,
    headers: {
      "Content-Type": "application/json",
      "orderly-account-id": orderlyAccountId,
      "orderly-key": `ed25519:${base58.encode(publicKeyBytes)}`,
      "orderly-timestamp": String(timestamp),
      "orderly-signature": Buffer.from(signatureBytes).toString("base64url")
    }
  });

  const data = await response.json();
  console.log(data);
}

main();

Full Example

import io.github.cdimascio.dotenv.Dotenv;
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

import org.bitcoinj.base.Base58;
import org.json.JSONObject;

public class AuthenticationExample {
public static void main(String[] args) throws Exception {
String baseUrl = "https://testnet-api.orderly.org";
String orderlyAccountId = "<orderly-account-id>";

      Dotenv dotenv = Dotenv.load();
      OkHttpClient client = new OkHttpClient();

      String key = dotenv.get("ORDERLY_SECRET");
      EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519);
      EdDSAPrivateKeySpec encoded = new EdDSAPrivateKeySpec(Base58.decode(key), spec);
      EdDSAPrivateKey orderlyKey = new EdDSAPrivateKey(encoded);

      Signer signer = new Signer(baseUrl, orderlyAccountId, orderlyKey);

      JSONObject json = new JSONObject();
      json.put("symbol", "PERP_ETH_USDC");
      json.put("order_type", "MARKET");
      json.put("order_quantity", 0.01);
      json.put("side", "BUY");
      Request req = signer.createSignedRequest("/v1/order", "POST", json);
      String res;
      try (Response response = client.newCall(req).execute()) {
         res = response.body().string();
      }
      JSONObject obj = new JSONObject(res);

}
}

import java.io.UnsupportedEncodingException;
import java.security.*;
import java.time.Instant;
import java.util.Base64;

import org.json.JSONObject;

import net.i2p.crypto.eddsa.EdDSAEngine;
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;

public class Signer {
   public final String baseUrl;

   private String accountId;
   private EdDSAPrivateKey privateKey;

   public Signer(String baseUrl, String accountId, EdDSAPrivateKey privateKey) {
      this.baseUrl = baseUrl;
      this.accountId = accountId;
      this.privateKey = privateKey;
   }

   public Request createSignedRequest(String url)
         throws SignatureException, UnsupportedEncodingException, InvalidKeyException, OrderlyClientException {
      checkIsInitialized();
      return createSignedRequest(url, "GET", null);
   }

   public Request createSignedRequest(String url, String method, JSONObject json)
         throws OrderlyClientException, InvalidKeyException, SignatureException, UnsupportedEncodingException {
      checkIsInitialized();
      if (method == "GET" || method == "DELETE") {
         return createSignedFormRequest(url, method);
      } else if ((method == "POST" || method == "PUT") && json != null) {
         return createSignedJsonRequest(url, method, json);
      } else {
         throw new IllegalArgumentException();
      }
   }

   private void checkIsInitialized() throws OrderlyClientException {
      if (accountId == null) {
         throw OrderlyClientExceptionReason.ACCOUNT_UNINITIALIZED.asException();
      }
      if (privateKey == null) {
         throw OrderlyClientExceptionReason.KEYPAIR_UNINITIALIZED.asException();
      }
   }

   private Request createSignedFormRequest(String url, String method)
         throws SignatureException, UnsupportedEncodingException, InvalidKeyException {
      long timestamp = Instant.now().toEpochMilli();
      String message = "" + timestamp + method + url;

      EdDSAEngine signature = new EdDSAEngine();
      signature.initSign(privateKey);
      byte[] orderlySignature = signature.signOneShot(message.getBytes("UTF-8"));

      return new Request.Builder()
            .url(baseUrl + url)
            .addHeader("Content-Type", "x-www-form-urlencoded")
            .addHeader("orderly-timestamp", "" + timestamp)
            .addHeader("orderly-account-id", accountId)
            .addHeader("orderly-key", Util.encodePublicKey(privateKey))
            .addHeader("orderly-signature", Base64.getUrlEncoder().encodeToString(orderlySignature))
            .get()
            .build();
   }

   private Request createSignedJsonRequest(String url, String method, JSONObject json)
         throws SignatureException, UnsupportedEncodingException, InvalidKeyException {
      RequestBody body = RequestBody.create(json.toString(), MediaType.parse("application/json"));

      long timestamp = Instant.now().toEpochMilli();
      String message = "" + timestamp + method + url + json.toString();

      EdDSAEngine signature = new EdDSAEngine();
      signature.initSign(privateKey);
      byte[] orderlySignature = signature.signOneShot(message.getBytes("UTF-8"));

      return new Request.Builder()
            .url(baseUrl + url)
            .addHeader("Content-Type", "application/json")
            .addHeader("orderly-timestamp", "" + timestamp)
            .addHeader("orderly-account-id", accountId)
            .addHeader("orderly-key", Util.encodePublicKey(privateKey))
            .addHeader("orderly-signature", Base64.getUrlEncoder().encodeToString(orderlySignature))
            .post(body)
            .build();
   }
}

Security

Orderly validates every request through three checks. A request must pass all three to be accepted.

Request Timestamp

The request is rejected if the orderly-timestamp header differs from the API server time by more than 300 seconds.

Signature Verification

The orderly-signature header must be a valid ed25519 signature generated from the normalized request content and signed with your Orderly secret key.

Orderly Key Validity

The orderly-key header must reference a key that has been added to the network, is associated with the account, and has not expired.