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
JSON.stringify(body) with no extra whitespace. Any difference
(stray spaces, trailing newlines, key reordering) will produce a signature
the server rejects.
| Value | Notes |
|---|---|
timestamp | Seconds since the Unix Epoch in UTC. It must be within one minute of the API service time. |
method | Uppercase HTTP method, such as GET, POST, or DELETE. |
request path | Path and query string, such as /v2/transfers?foo=bar&baz=bang. |
body | JSON.stringify(body) for requests that include one. Empty for GET. |
Sign a request
- Java
- C#
- Ruby
- Python
- Node.js
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());
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Sodium;
// Dependencies: Sodium.Core 1.3.1
// Replace privateKey, publicKey, and Api-Access-Key with your keys
namespace WebAPIClient
{
class Program
{
private static readonly HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
await ProcessAnchorageAPISign();
}
private static async Task<Object> ProcessAnchorageAPISign()
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
string privateKey = "your private key here";
string publicKey = "your public key here";
string signingKey = $"{privateKey}{publicKey}";
string timestamp = DateTimeOffset.Now.ToUnixTimeSeconds().ToString();
string path = "/v2/trading/quote/accept";
string method = "POST";
string payload = "{\"quoteID\":\"1b1748b0-a62a-46c7-802c-7c61ac222424\",\"side\":\"BUY\"}";
string msgToSign = $"{timestamp}{method}{path}{payload}";
var signature = ComputeSignature(Utilities.HexToBinary(signingKey), Encoding.UTF8.GetBytes(msgToSign));
client.DefaultRequestHeaders.Add("Api-Access-Key", "your api access key");
client.DefaultRequestHeaders.Add("Api-Timestamp", timestamp);
client.DefaultRequestHeaders.Add("Api-Signature", signature);
Uri u = new Uri("https://api.anchorage-staging.com/v2/trading/quote/accept");
HttpContent c = new StringContent(payload, Encoding.UTF8, "application/json");
HttpResponseMessage result = await client.PostAsync(u, c);
var response = await result.Content.ReadAsStringAsync();
Console.WriteLine(response);
return response;
}
static string ComputeSignature(byte[] privateKey, byte[] toSign)
{
byte[] signature = PublicKeyAuth.SignDetached(toSign, privateKey);
return Utilities.BinaryToHex(signature);
}
}
}
require "ed25519"
require "net/http"
require "time"
def hex_to_bin(s)
[s].pack('H*')
end
def bin_to_hex(s)
s.unpack('H*').first
end
private_key_seed_hex = '0101010101010101010101010101010101010101010101010101010101010101'
public_key_hex = '8a88e3dd7409f195fd52db2d3cba5d72ca6709bf1d94121bf3748801b40f6f5c'
key_pair_hex = private_key_seed_hex + public_key_hex
key_pair = hex_to_bin(key_pair_hex)
signing_key = Ed25519::SigningKey.from_keypair(key_pair)
timestamp = '1577880000' # Time.now.to_i.to_s
req = Net::HTTP::Post.new('/v2/transfers?foo=bar&baz=bang')
req.body = '{"source": {"id": "1c920f4241b78a1d483a29f3c24b6c4c", "type": "VAULT"},
"assetType": "ETH", "destination": {"id": "55e89d4a644d736b01533a2ea9b32a20", "type": "VAULT"}, "amount": "1000.00000000"}'
signature = signing_key.sign(timestamp + req.method + req.path + req.body)
req['Api-Access-Key'] = 'YOUR_ACCESS_KEY'
req['Api-Timestamp'] = timestamp
req['Api-Signature'] = bin_to_hex(signature)
puts bin_to_hex(signature)
import time
import requests
from nacl import signing
class AnchorageAuth(requests.auth.AuthBase):
ACCESS_KEY_HEADER = "Api-Access-Key"
SIGNATURE_HEADER = "Api-Signature"
TIMESTAMP_HEADER = "Api-Timestamp"
def __init__(self, access_key: str, signing_key_seed: bytes):
self.access_key = access_key
self.signing_key = signing.SigningKey(signing_key_seed)
def __call__(self, request: requests.PreparedRequest):
request.headers[self.ACCESS_KEY_HEADER] = self.access_key
timestamp = str(int(time.time()))
method = request.method.upper() if request.method else "GET"
body = request.body or b""
if isinstance(body, str):
body = body.encode("utf-8")
message = b"".join([
timestamp.encode("utf-8"),
method.encode("utf-8"),
request.path_url.encode("utf-8"),
body,
])
request.headers[self.SIGNATURE_HEADER] = self.signing_key.sign(message).signature.hex()
request.headers[self.TIMESTAMP_HEADER] = timestamp
return request
const { sign } = require("@noble/ed25519");
const axios = require("axios");
class AnchorageClient {
constructor(accessKey, signingKeySeed) {
this.accessKey = accessKey;
this.signingKeySeed = signingKeySeed; // 32-byte hex seed
this.basePath = "https://api.anchorage-staging.com";
}
async sendSignedRequest(method, endpoint, body) {
const timestamp = Math.floor(Date.now() / 1000);
const serializedBody = body ? JSON.stringify(body) : "";
const signatureInput = `${timestamp}${method}${endpoint}${serializedBody}`;
const messageHex = Buffer.from(signatureInput, "utf8").toString("hex");
const signature = await sign(messageHex, this.signingKeySeed);
const signatureHex = Buffer.from(signature).toString("hex");
return axios({
method,
url: this.basePath + endpoint,
data: body,
headers: {
"Api-Access-Key": this.accessKey,
"Api-Signature": signatureHex,
"Api-Timestamp": String(timestamp),
"Content-Type": "application/json",
},
}).then((res) => res.data);
}
}
// Example: request and accept a quote
const client = new AnchorageClient("YOUR_ACCESS_KEY", "YOUR_SIGNING_KEY_SEED");
const quote = await client.sendSignedRequest("POST", "/v2/trading/quote", {
tradingPair: "BTC-USD",
quantity: "1",
currency: "BTC",
side: "BUY",
});
await client.sendSignedRequest("POST", "/v2/trading/quote/accept", {
quoteID: quote.data.quoteID,
side: "BUY",
allowedSlippage: "0.001",
});
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.