diff --git a/README.md b/README.md index cf6a1ff..5936256 100644 --- a/README.md +++ b/README.md @@ -1 +1,30 @@ -# examples \ No newline at end of file +# Anboto Trading API examples + +Sample clients for the [Anboto Trading API](https://anbotolabs.github.io/anboto-api-docs/#anboto-trading-api). + +## Authentication (HMAC system-generated keys) + +1. Create keys in the Anboto UI (Settings → API). +2. Build the string to sign (no separators): + `timestamp + apiKey + recvWindow + queryString + jsonBody` + - `queryString`: sorted query parameters, e.g. `exchange=BINANCE&symbol=BTC/USDT` + - `jsonBody`: compact JSON for POST bodies (`separators=(',', ':')` in Python) +3. **Decode the secret** before HMAC (do not use the UTF-8 string as the key): + - Legacy keys (contain `+`, `/`, or end with `=`): standard Base64 decode + - New keys (URL-safe, no padding): URL-safe Base64 decode +4. `X-SIGN` = standard Base64(`HMAC-SHA256(decoded_secret, string_to_sign)`) + +Headers: `X-API-KEY`, `X-TIMESTAMP`, `X-RECV-WINDOW` (default `5000`), `X-SIGN`. + +## Base URLs + +| Environment | URL | +|-------------|-----| +| Mainnet | `https://api.trade.anboto.xyz` | +| Pro | `https://api.pro.anboto.xyz` | +| Testnet | `https://api.testnet.anboto.xyz` | + +## Projects + +- `anboto-python-example/client.py` — Python reference client +- `anboto-java-example/` — Java (OkHttp) reference client diff --git a/anboto-java-example/src/main/java/xyz/anboto/example/Client.java b/anboto-java-example/src/main/java/xyz/anboto/example/Client.java index 4c3afcd..ca4ff8e 100644 --- a/anboto-java-example/src/main/java/xyz/anboto/example/Client.java +++ b/anboto-java-example/src/main/java/xyz/anboto/example/Client.java @@ -16,7 +16,16 @@ public class Client { final static String API_KEY = "xxx"; - final static byte[] API_SECRET = Base64.getDecoder().decode("xxx"); + final static byte[] API_SECRET = decodeSignKey("xxx"); + + /** Match api-gw decodeSignKey: legacy standard Base64 or new URL-safe secrets. */ + static byte[] decodeSignKey(String keyEncoded) { + if (keyEncoded.contains("+") || keyEncoded.contains("/") || keyEncoded.endsWith("=")) { + return Base64.getDecoder().decode(keyEncoded); + } + int pad = (4 - (keyEncoded.length() % 4)) % 4; + return Base64.getUrlDecoder().decode(keyEncoded + "=".repeat(pad)); + } final static String TIMESTAMP = Long.toString(ZonedDateTime.now().toInstant().toEpochMilli()); final static String RECV_WINDOW = "5000"; final static String X_API_KEY = "X-API-KEY"; diff --git a/anboto-python-example/client.py b/anboto-python-example/client.py index aeb8554..349b7fd 100644 --- a/anboto-python-example/client.py +++ b/anboto-python-example/client.py @@ -83,12 +83,19 @@ def get_current_millis(): return str(int(current_time_seconds * 1000)) +def decode_sign_key(key_encoded: str) -> bytes: + """Match api-gw decodeSignKey: legacy standard Base64 or new URL-safe secrets.""" + if "+" in key_encoded or "/" in key_encoded or key_encoded.endswith("="): + return base64.b64decode(key_encoded) + pad = "=" * (-len(key_encoded) % 4) + return base64.urlsafe_b64decode(key_encoded + pad) + class AnbotoTradingAPI: def __init__(self, base_url, api_key, secret_key): self.base_url = base_url self.api_key = api_key - self.secret_key = base64.b64decode(secret_key) # Decode api_secret + self.secret_key = decode_sign_key(secret_key) def gen_signature(self, timestamp, querystring, body): recv_window = "5000"