|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +Guidance for working in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +`bitmart-java-sdk-api` is the official BitMart Exchange Java client for the |
| 8 | +BitMart Cloud API. It wraps BitMart's REST endpoints and WebSocket streams |
| 9 | +described in the online docs at <https://developer.bitmart.com/spot> (changelog: |
| 10 | +<https://developer-pro.bitmart.com/en/spot/#change-log>). |
| 11 | + |
| 12 | +- **GroupId/ArtifactId:** `io.github.bitmartexchange:bitmart-java-sdk-api` |
| 13 | +- **Java:** compiled for Java 8 (`maven.compiler.source/target = 8`) |
| 14 | +- **Published to:** Maven Central via the Sonatype Central Portal (`release` profile) |
| 15 | +- Current version is the `<version>` in [pom.xml](pom.xml) and the `USER_AGENT` |
| 16 | + constant in [Call.java](src/main/java/com/bitmart/api/Call.java). **Keep these |
| 17 | + two in sync when bumping the version.** |
| 18 | + |
| 19 | +## Build & Test |
| 20 | + |
| 21 | +```bash |
| 22 | +mvn install # build + install to local repo |
| 23 | +mvn test # run JUnit 5 tests |
| 24 | +mvn package # build the jar |
| 25 | +mvn -Prelease deploy # sign (GPG) + publish to Central (release only) |
| 26 | +``` |
| 27 | + |
| 28 | +Tests live under `src/test/java`: |
| 29 | +- `com.bitmart.unit.*` — JUnit 5 unit tests grouped by domain (`api`, `websocket`, `data`). |
| 30 | +- `com.bitmart.examples.*` — runnable `main()` examples mirroring the README, one |
| 31 | + class per endpoint/stream. These double as living usage documentation. |
| 32 | + |
| 33 | +Many tests/examples need real API credentials (key/secret/memo). They hit live |
| 34 | +endpoints, so treat them as integration samples, not pure unit tests. |
| 35 | + |
| 36 | +## Architecture |
| 37 | + |
| 38 | +Two independent subsystems share the auth/key/common code: |
| 39 | + |
| 40 | +### 1. REST client (`com.bitmart.api`) |
| 41 | + |
| 42 | +The flow is **build a Request object → pass it to `Call.callCloud()` → get a `CloudResponse`**. |
| 43 | + |
| 44 | +- [Call.java](src/main/java/com/bitmart/api/Call.java) — the HTTP engine (OkHttp). |
| 45 | + Dispatches GET vs POST based on `request.getMethod()`, builds headers, and |
| 46 | + wraps the result in `CloudResponse` (raw JSON body + HTTP status + rate-limit |
| 47 | + headers `X-BM-RateLimit-*`). |
| 48 | +- [CloudContext.java](src/main/java/com/bitmart/api/CloudContext.java) — holds |
| 49 | + the base URL, timeouts, custom headers, and the `CloudKey`. Default URL is |
| 50 | + `GlobalConst.CLOUD_URL`. Construct with a `CloudKey` for authenticated calls, |
| 51 | + or no-arg for public-only. |
| 52 | +- [CloudRequest.java](src/main/java/com/bitmart/api/request/CloudRequest.java) — |
| 53 | + base class for **every** request. Subclasses set `path`, `method`, and `auth` |
| 54 | + in their constructor via `super(...)`, and declare parameter fields. |
| 55 | +- [Method.java](src/main/java/com/bitmart/api/request/Method.java) — `GET`/`POST`. |
| 56 | +- [Auth.java](src/main/java/com/bitmart/api/request/Auth.java) — `NONE` (public), |
| 57 | + `KEYED` (sends `X-BM-KEY` only), `SIGNED` (adds `X-BM-TIMESTAMP` + `X-BM-SIGN`). |
| 58 | + |
| 59 | +### Request parameter mechanism (`@ParamKey` + reflection) |
| 60 | + |
| 61 | +Request classes use Lombok (`@Data @Accessors(chain = true)`) and annotate each |
| 62 | +parameter field with |
| 63 | +[`@ParamKey(value="...", required=...)`](src/main/java/com/bitmart/api/annotations/ParamKey.java). |
| 64 | +At call time, |
| 65 | +[`CommonUtils.genRequestMap()`](src/main/java/com/bitmart/api/common/CommonUtils.java) |
| 66 | +reflects over the **declared fields of the leaf class only** (it intentionally |
| 67 | +does NOT walk superclass fields), builds a sorted `TreeMap` of `key → value`, |
| 68 | +skips nulls, and throws `CloudException` if a `required` field is null. Because |
| 69 | +only leaf fields are scanned, request subclasses must declare all of their own |
| 70 | +params directly — don't push shared params up into a base class expecting them |
| 71 | +to be serialized. |
| 72 | + |
| 73 | +- GET: the map becomes a `key=value&...` query string. |
| 74 | +- POST: the map is serialized to a JSON body. |
| 75 | + |
| 76 | +The same query/body string is what gets HMAC-signed. |
| 77 | + |
| 78 | +### Signing (`SIGNED` endpoints) |
| 79 | + |
| 80 | +[CloudSignature.java](src/main/java/com/bitmart/api/key/CloudSignature.java) |
| 81 | +produces `X-BM-SIGN = HMAC-SHA256(secret, "{timestamp}#{memo}#{payload}")` |
| 82 | +(hex-encoded), where `payload` is the query string (GET) or JSON body (POST), |
| 83 | +and sets `X-BM-TIMESTAMP` to `System.currentTimeMillis()`. |
| 84 | + |
| 85 | +### Adding a new REST endpoint |
| 86 | + |
| 87 | +1. Create a `*Request` class in the correct package under |
| 88 | + `com.bitmart.api.request` (see layout below). |
| 89 | +2. Extend `CloudRequest`; in the constructor call |
| 90 | + `super("/the/api/path", Method.GET_or_POST, Auth.LEVEL)`. |
| 91 | +3. Add fields annotated with `@ParamKey`, plus Lombok annotations |
| 92 | + (`@EqualsAndHashCode(callSuper=true) @Data @ToString @Accessors(chain=true)`). |
| 93 | +4. Add an example under `src/test/java/com/bitmart/examples/...` and/or a unit test. |
| 94 | + |
| 95 | +### Request package layout (`src/main/java/com/bitmart/api/request`) |
| 96 | + |
| 97 | +Convention: `<domain>/<pub|prv>[/<version>]`, where `pub` = public/unauthenticated |
| 98 | +and `prv` = private/authenticated. |
| 99 | + |
| 100 | +- `spot/pub/market` — spot market data, V3 (`V3TickerRequest`, `V3DepthRequest`, `V3HistoryKlineRequest`, …) |
| 101 | +- `spot/prv` — spot trading (`SubmitOrderRequest`, `BatchOrdersRequest`, `CancelOrderRequest`, margin orders, …) |
| 102 | +- `spot/prv/v4` — V4 signed query endpoints (`V4QueryOpenOrdersRequest`, `V4QueryHistoryOrdersRequest`, trades, …) |
| 103 | +- `contract/pub` — futures market data (`DepthRequest`, `KlineRequest`, `FundingRateRequest`, `LeverageBracketRequest`, …) |
| 104 | +- `contract/prv` — futures trading/account (orders, plan/tp-sl/trail orders, positions, leverage, transfers, …) |
| 105 | +- `account/pub` — `AccountCurrenciesRequest` |
| 106 | +- `account/prv` — wallet, deposit/withdraw, withdraw address, isolated margin transfer, trade-fee |
| 107 | +- `margin_loan/prv` — isolated margin borrow/repay + records and pairs |
| 108 | +- `system/pub` — `SystemServiceRequest`, `SystemTimeRequest` |
| 109 | +- `broker` — `BrokerRebateRequest` |
| 110 | + |
| 111 | +### 2. WebSocket client (`com.bitmart.websocket`) |
| 112 | + |
| 113 | +Netty-based WS client supporting four endpoints defined in |
| 114 | +[GlobalConst.java](src/main/java/com/bitmart/api/common/GlobalConst.java): |
| 115 | +spot public/private and futures public/private (`CLOUD_*_WS_*_URL`). |
| 116 | + |
| 117 | +- [WebSocketClient.java](src/main/java/com/bitmart/websocket/WebSocketClient.java) / |
| 118 | + [ContractWebSocket.java](src/main/java/com/bitmart/websocket/ContractWebSocket.java) — |
| 119 | + connect, auto-reconnect (replaying subscribed channels + re-login), keepalive |
| 120 | + (`ping` every 10s), and `login()` for private streams. |
| 121 | +- Login signs the literal string `"bitmart.WebSocket"` and sends |
| 122 | + `{"op":"login","args":[apiKey, timestamp, sign]}`. |
| 123 | +- Subscribe via [OpParam](src/main/java/com/bitmart/websocket/OpParam.java): |
| 124 | + `send(new OpParam().setOp("subscribe").setArgs(ImmutableList.of("spot/ticker:BTC_USDT")))`. |
| 125 | +- Implement [WebSocketCallBack](src/main/java/com/bitmart/websocket/WebSocketCallBack.java) |
| 126 | + `onMessage(String)` to receive frames. |
| 127 | +- Channel name constants: |
| 128 | + [spot/WebSocketConstant](src/main/java/com/bitmart/websocket/spot/WebSocketConstant.java), |
| 129 | + [contract/ContractWebSocketConstant](src/main/java/com/bitmart/websocket/contract/ContractWebSocketConstant.java). |
| 130 | + Use `createChannel(channel, symbol)` to build `"channel:symbol"` args. |
| 131 | + |
| 132 | +## Conventions |
| 133 | + |
| 134 | +- **Lombok everywhere.** Request/response/data classes rely on `@Data`, |
| 135 | + `@Accessors(chain = true)` (fluent setters return `this`), `@ToString`, |
| 136 | + `@EqualsAndHashCode(callSuper = true)`. Setters are chainable by design. |
| 137 | +- **No business logic in request classes** — they are pure parameter holders. |
| 138 | + All serialization/signing is centralized in `Call` + `CommonUtils` + `CloudSignature`. |
| 139 | +- Responses are returned as **raw JSON strings** (`CloudResponse.getResponseContent()`); |
| 140 | + the SDK does not deserialize into typed response models. |
| 141 | +- Errors surface as `CloudException` (checked). |
| 142 | +- Class naming mirrors the API doc endpoint name, with a version prefix |
| 143 | + (`V3`/`V4`) where the API is versioned. |
| 144 | + |
| 145 | +## Key dependencies |
| 146 | + |
| 147 | +OkHttp (REST transport), Netty (WebSocket), Gson (JSON), Guava, Apache Commons |
| 148 | +(codec/lang3/collections4), SLF4J (logging API), Lombok (provided). Tests: |
| 149 | +JUnit Jupiter 5, Logback. |
0 commit comments