Skip to main content
Certain Anchorage endpoints require an Ed25519 signature in the request headers alongside the API key. Signatures are optional unless explicitly required, but are encouraged for all requests for maximum security. The Api-Signature is valid for 60 seconds.

Signature input

Create the signature by concatenating these values:
timestamp + uppercase HTTP method + request path including query + request body
For requests without a body, omit the body from the signature input. For requests with a JSON body, sign the exact bytes sent over the wire — that is, JSON.stringify(body) with no extra whitespace. Any difference (stray spaces, trailing newlines, key reordering) will produce a signature the server rejects.
ValueNotes
timestampSeconds since the Unix Epoch in UTC. It must be within one minute of the API service time.
methodUppercase HTTP method, such as GET, POST, or DELETE.
request pathPath and query string, such as /v2/transfers?foo=bar&baz=bang.
bodyJSON.stringify(body) for requests that include one. Empty for GET.

Sign a request

package com.anchorage.api.client;

import okio.Buffer;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.Signer;
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
import org.bouncycastle.crypto.signers.Ed25519Signer;
import org.json.JSONObject;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import java.nio.charset.StandardCharsets;
import java.time.Instant;

/**
 * A sample java API client to connect to Anchorage API v2
 *
 * Required dependencies:
 *   org.json:json:20210307
 *   org.bouncycastle:bcpkix-jdk15on:1.69
 *   commons-codec:commons-codec:1.15
 *   com.squareup.okio:okio:1.9.0
 */
public class RestClientWithSigning {

    private RestTemplate restTemplate;

    public static void main(String[] args){
        RestClientWithSigning api = new RestClientWithSigning();
        api.init();
        api.apiCall();
    }

    private void init(){
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setConnectTimeout(2000);
        factory.setReadTimeout(2000);
        restTemplate = new RestTemplate(factory);
    }

    private void apiCall() {
        try {
            String url = "https://api.anchorage-staging.com/v2/trading/quote";
            String body = createRequestBody();

            HttpEntity<String> request = new HttpEntity<>(body, createHeaders("/v2/trading/quote", HttpMethod.POST, body));
            ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.POST, request, String.class);
            if (responseEntity != null && HttpStatus.CREATED == responseEntity.getStatusCode()) {
                System.out.println(String.format("Getting data from ( %s ) response: %s", url, responseEntity.getBody()));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private String createRequestBody(){
        JSONObject body = new JSONObject();
        body.put("currency", "ETH");
        body.put("quantity", "1.2");
        body.put("side", "BUY");
        body.put("tradingPair", "ETH-USD");
        return body.toString();
    }

    private HttpHeaders createHeaders(String requestPath, HttpMethod httpmethod, String body) throws DecoderException, CryptoException {
        String api_key = "your API Key";
        String signing_key_hex = "Your signing key";

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.set("Api-Access-Key", api_key);

        String timestamp = String.valueOf(Instant.now().getEpochSecond());
        byte[] toSign = decodeMessage(timestamp, httpmethod, requestPath, body);

        String signature = bc_sign(Hex.decodeHex(signing_key_hex), toSign);
        headers.add("Api-Signature", signature);
        headers.add("Api-Timestamp", timestamp);
        return headers;
    }

    private byte[] decodeMessage(String timestamp, HttpMethod httpmethod, String url_path, String body){
        Buffer buffer = new Buffer();
        buffer.write(timestamp.getBytes(StandardCharsets.UTF_8))
                .write(httpmethod.name().getBytes(StandardCharsets.UTF_8))
                .write(url_path.getBytes(StandardCharsets.UTF_8))
                .write(body.getBytes(StandardCharsets.UTF_8));
        return buffer.readByteArray();
    }

    private String bc_sign(byte[] signing_key, byte[] toSign) throws CryptoException {
        Ed25519PrivateKeyParameters privateKeyParameters = new Ed25519PrivateKeyParameters(signing_key);
        Signer signer = new Ed25519Signer();
        signer.init(true, privateKeyParameters);
        signer.update(toSign, 0, toSign.length);
        return Hex.encodeHexString(signer.generateSignature());
    }
}
JSON.stringify(body) produces the same compact bytes the server signs against, so you don’t need to pre-serialize the request manually. The live signer above applies the same canonicalization to anything you paste into the body field.