Skip to content

Commit

Permalink
3.0.0rc1 release (#80)
Browse files Browse the repository at this point in the history
  • Loading branch information
aisling-2 authored May 12, 2023
1 parent 030d31e commit b522c22
Show file tree
Hide file tree
Showing 542 changed files with 1,541 additions and 816 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## 3.0.0rc1 - 2023-05-12

### Added
- Support for generating signatures using Ed25519 keys has been added.

### Changed
- Upgraded `org.json` dependency to version `20230227`.
- If `x-sapi-used-uid-weight-1m` exists, the response will list it when `setShowLimitUsage(true)` is set.
- Added `setShowLimitUsage`, `setProxy`, and `unsetProxy` methods to the `SpotClient` interface.

## 2.0.0 - 2023-05-01

### Changed
Expand Down
147 changes: 86 additions & 61 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,15 @@ to call its respective endpoints.

#### Market Endpoint: Exchange Information
```java
SpotClientImpl client = new SpotClientImpl();
SpotClient client = new SpotClientImpl();
String result = client.createMarket().exchangeInfo();
```

#### Trade Endpoint: Testing a new order
```java
LinkedHashMap<String,Object> parameters = new LinkedHashMap<String,Object>();

SpotClientImpl client = new SpotClientImpl(PrivateConfig.API_KEY, PrivateConfig.SECRET_KEY);
SpotClient client = new SpotClientImpl(PrivateConfig.API_KEY, PrivateConfig.SECRET_KEY);

parameters.put("symbol","BTCUSDT");
parameters.put("side", "SELL");
Expand All @@ -91,6 +91,61 @@ parameters.put("price", 9500);
String result = client.createTrade().testNewOrder(parameters);
```

### WebSocket Stream

```java
WebSocketStreamClient wsStreamClient = new WebSocketStreamClientImpl(); // defaults to live exchange unless stated.

// Single stream
int streamID1 = wsStreamClient.aggTradeStream("btcusdt",((event) -> {
System.out.println(event);
}));

// Combined streams
ArrayList<String> streams = new ArrayList<>();
streams.add("btcusdt@trade");
streams.add("bnbusdt@trade");

int streamID2 = wsStreamClient.combineStreams(streams, ((event) -> {
System.out.println(event);
}));

// Close single stream
wsStreamClient.closeConnection(streamID1); //closes aggTradeStream-btcusdt

// Close all streams
wsStreamClient.closeAllConnections();
```

More examples are available at `test/examples/websocketstream` folder

### WebSocket API

```java
RsaSignatureGenerator signatureGenerator = new RsaSignatureGenerator("PRIVATE_KEY_PATH");
WebSocketApiClient wsApiClient = new WebSocketApiClientImpl("API_KEY", signatureGenerator); // defaults to live exchange unless stated.

// Open connection with a callback as parameter
wsApiClient.connect(((message) -> {
System.out.println(message);
}));

JSONObject optionalParams = new JSONObject();
optionalParams.put("requestId", "request123");
optionalParams.put("quantity", 1);

wsApiClient.trade().testNewOrder("BTCUSDT", "BUY", "MARKET", optionalParams);

Thread.sleep(3000);

// Close connection
wsApiClient.close();
```

If `requestId` is empty (`""`), `null` or not sent, this library will generate a `UUID` string for it.

More examples are available at `test/examples/websocketapi` folder

### Testnet

While `/sapi/*` endpoints don't have testnet environment yet, `/api/*` endpoints can be tested in
Expand All @@ -99,7 +154,7 @@ While `/sapi/*` endpoints don't have testnet environment yet, `/api/*` endpoints
```java
LinkedHashMap<String,Object> parameters = new LinkedHashMap<>();

SpotClientImpl client = new SpotClientImpl(PrivateConfig.TESTNET_API_KEY, PrivateConfig.TESTNET_SECRET_KEY, PrivateConfig.TESTNET_URL);
SpotClient client = new SpotClientImpl(PrivateConfig.TESTNET_API_KEY, PrivateConfig.TESTNET_SECRET_KEY, PrivateConfig.TESTNET_URL);
String result = client.createMarket().time();
```

Expand Down Expand Up @@ -132,7 +187,7 @@ parameters.put("price", 9500);
The Binance API server provides weight usages in the headers of each response. This value can be return by
calling `setShowLimitUsage` and setting it to `true`.
```java
SpotClientImpl client = new SpotClientImpl();
SpotClient client = new SpotClientImpl();
client.setShowLimitUsage(true);
String result = client.createMarket().time();
logger.info(result);
Expand All @@ -148,7 +203,7 @@ HTTP Proxy is supported.
To set it up, call `setProxy()` with `ProxyAuth` and before submitting requests to binance:

```java
SpotClientImpl client = new SpotClientImpl();
SpotClient client = new SpotClientImpl();
Proxy proxyConn = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8080));
ProxyAuth proxy = new ProxyAuth(proxyConn, null);

Expand All @@ -159,7 +214,7 @@ logger.info(client.createMarket().time());
For authenticated `Proxy`, define `ProxyAuth` with [`Authenticator` from `okhttp3`](https://square.github.io/okhttp/3.x/okhttp/index.html?okhttp3/Authenticator.html):

```java
SpotClientImpl client = new SpotClientImpl();
SpotClient client = new SpotClientImpl();
Proxy proxyConn = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8080));
Authenticator auth = new Authenticator() {
public Request authenticate(Route route, Response response) throws IOException {
Expand Down Expand Up @@ -199,8 +254,33 @@ SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
```

In case you want to use our custom `logback-classic`, it's available at [`binance-logback`](https://central.sonatype.com/artifact/io.github.binance/binance-logback).

If you prefer to not use a logger and suppress the `SLF4J` messages instead, you can refer to `slf4j-nop`.

### Types of Signature Generator
When creating `SpotClient`, `WebSocketStreamClient` or `WebSocketApiClient`, you use one of the following types of Signature Generator to create signatures (for SIGNED endpoints) based on your security preference:

- `HmacSignatureGenerator` - Use of API Key and Secret Key.
```java
HmacSignatureGenerator signGenerator = new HmacSignatureGenerator("SecretKey");
SpotClient client = new SpotClientImpl("ApiKey", signGenerator);
```

- `RsaSignatureGenerator` - Use of API Key and RSA algorithm keys.
```java
RsaSignatureGenerator signGenerator = new RsaSignatureGenerator("PathToPrivateKey");
// or if Private Key is protected
// RsaSignatureGenerator signGenerator = new RsaSignatureGenerator("PathToPrivateKey", "PrivateKeyPassword")
SpotClient client = new SpotClientImpl("ApiKey", signGenerator);
```

- `Ed25519SignatureGenerator` - Use of API Key and Ed25519 algorithm keys.
```java
Ed25519SignatureGenerator signGenerator = new Ed25519SignatureGenerator("PathToPrivateKey");
SpotClient client = new SpotClientImpl("ApiKey", signGenerator);
```

### Error

There are 3 types of error which may be thrown by this library.
Expand All @@ -227,61 +307,6 @@ try {
}
```

### WebSocket Stream

```java
WebSocketStreamClientImpl wsStreamClient = new WebSocketStreamClientImpl(); // defaults to live exchange unless stated.

// Single stream
int streamID1 = wsStreamClient.aggTradeStream("btcusdt",((event) -> {
System.out.println(event);
}));

// Combined streams
ArrayList<String> streams = new ArrayList<>();
streams.add("btcusdt@trade");
streams.add("bnbusdt@trade");

int streamID2 = wsStreamClient.combineStreams(streams, ((event) -> {
System.out.println(event);
}));

// Close single stream
wsStreamClient.closeConnection(streamID1); //closes aggTradeStream-btcusdt

// Close all streams
wsStreamClient.closeAllConnections();
```

More examples are available at `test/examples/websocketstream` folder

### WebSocket API

```java
RsaSignatureGenerator signatureGenerator = new RsaSignatureGenerator("PRIVATE_KEY_PATH");
WebSocketApiClientImpl wsApiClient = new WebSocketApiClientImpl("API_KEY", signatureGenerator); // defaults to live exchange unless stated.

// Open connection with a callback as parameter
wsApiClient.connect(((message) -> {
System.out.println(message);
}));

JSONObject optionalParams = new JSONObject();
optionalParams.put("requestId", "request123");
optionalParams.put("quantity", 1);

wsApiClient.trade().testNewOrder("BTCUSDT", "BUY", "MARKET", optionalParams);

Thread.sleep(3000);

// Close connection
wsApiClient.close();

```
If `requestId` is empty (`""`), `null` or not sent, this library will generate a `UUID` string for it.

More examples are available at `test/examples/websocketapi` folder

### Test
`mvn clean test`

Expand Down
11 changes: 8 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>io.github.binance</groupId>
<artifactId>binance-connector-java</artifactId>
<version>2.0.0</version>
<version>3.0.0rc1</version>
<packaging>jar</packaging>
<name>${project.groupId}:${project.artifactId}</name>
<description>lightweight connector to API</description>
Expand Down Expand Up @@ -171,7 +171,7 @@
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20210307</version>
<version>20230227</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
Expand All @@ -190,5 +190,10 @@
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.73</version>
</dependency>
</dependencies>
</project>
4 changes: 4 additions & 0 deletions src/main/java/com/binance/connector/client/SpotClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@
import com.binance.connector.client.impl.spot.Trade;
import com.binance.connector.client.impl.spot.UserData;
import com.binance.connector.client.impl.spot.Wallet;
import com.binance.connector.client.utils.ProxyAuth;


public interface SpotClient {
void setShowLimitUsage(boolean showLimitUsage);
void setProxy(ProxyAuth proxy);
void unsetProxy();
Blvt createBlvt();
BSwap createBswap();
C2C createC2C();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,17 @@ public SpotClientImpl(String apiKey, SignatureGenerator signatureGenerator, Stri
this.baseUrl = baseUrl;
}

@Override
public void setShowLimitUsage(boolean showLimitUsage) {
this.showLimitUsage = showLimitUsage;
}

@Override
public void setProxy(ProxyAuth proxy) {
this.proxy = proxy;
}


@Override
public void unsetProxy() {
this.proxy = null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public void connect(WebSocketCallback onMessageCallback) {
connect(noopCallback, onMessageCallback, noopCallback, noopCallback);
}

@Override
public void connect(WebSocketCallback onOpenCallback, WebSocketCallback onMessageCallback, WebSocketCallback onClosingCallback, WebSocketCallback onFailureCallback) {
Request request = RequestBuilder.buildWebSocketRequest(baseUrl);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.binance.connector.client.enums.HttpMethod;
import com.binance.connector.client.enums.RequestType;
import com.binance.connector.client.exceptions.BinanceConnectorException;
import com.binance.connector.client.utils.signaturegenerator.Ed25519SignatureGenerator;
import com.binance.connector.client.utils.signaturegenerator.HmacSignatureGenerator;
import com.binance.connector.client.utils.signaturegenerator.RsaSignatureGenerator;
import com.binance.connector.client.utils.signaturegenerator.SignatureGenerator;
Expand Down Expand Up @@ -76,15 +77,14 @@ public String sendWithApiKeyRequest(String baseUrl, String urlPath, LinkedHashMa
public String sendSignedRequest(String baseUrl, String urlPath, LinkedHashMap<String, Object> parameters,
HttpMethod httpMethod, boolean showLimitUsage) {

if (signatureGenerator.getClass() == HmacSignatureGenerator.class && null == apiKey || apiKey.isEmpty()) {
if (signatureGenerator.getClass() == HmacSignatureGenerator.class && (null == apiKey || apiKey.isEmpty())) {
throw new BinanceConnectorException("[RequestHandler] Secret key/API key cannot be null or empty!");
}
if (signatureGenerator.getClass() == RsaSignatureGenerator.class && null == apiKey || apiKey.isEmpty()) {
if ((signatureGenerator.getClass() == RsaSignatureGenerator.class || signatureGenerator.getClass() == Ed25519SignatureGenerator.class) && (null == apiKey || apiKey.isEmpty())) {
throw new BinanceConnectorException("[RequestHandler] Private key/API key cannot be null or empty!");
}
parameters.put("timestamp", UrlBuilder.buildTimestamp());
String queryString = UrlBuilder.joinQueryParameters(parameters);
String signature = this.signatureGenerator.getSignature(queryString);
String signature = this.signatureGenerator.getSignature(UrlBuilder.joinQueryParameters(parameters));
return sendApiRequest(baseUrl, urlPath, signature, parameters, httpMethod, RequestType.SIGNED, showLimitUsage);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public static String handleResponse(Request request, boolean showLimitUsage, Pro
}

if (showLimitUsage) {
return getlimitUsage(response, responseAsString);
return getLimitUsage(response, responseAsString);
} else {
return responseAsString;
}
Expand All @@ -59,9 +59,10 @@ public static String handleResponse(Request request, boolean showLimitUsage, Pro
}
}

private static String getlimitUsage(Response response, String resposeBodyAsString) {
private static String getLimitUsage(Response response, String resposeBodyAsString) {
JSONObject json = new JSONObject();
json.put("x-sapi-used-ip-weight-1m", response.header("X-SAPI-USED-IP-WEIGHT-1M"));
json.put("x-sapi-used-uid-weight-1m", response.header("X-SAPI-USED-UID-WEIGHT-1M"));
json.put("x-mbx-used-weight", response.header("x-mbx-used-weight"));
json.put("x-mbx-used-weight-1m", response.header("x-mbx-used-weight-1m"));
json.put("data", resposeBodyAsString);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.binance.connector.client.utils.signaturegenerator;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.util.Base64;

import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
import org.bouncycastle.crypto.signers.Ed25519Signer;
import org.bouncycastle.crypto.util.PrivateKeyFactory;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;

import com.binance.connector.client.utils.ParameterChecker;

public final class Ed25519SignatureGenerator implements SignatureGenerator {

private Ed25519PrivateKeyParameters privateKey;
private final int offset = 0;

public Ed25519SignatureGenerator(String privateKey) throws FileNotFoundException, IOException {

ParameterChecker.checkParameterType(privateKey, String.class, "privateKey");

Security.addProvider(new BouncyCastleProvider());
PemReader pemReader = new PemReader(new FileReader(privateKey));
PemObject pemObject = pemReader.readPemObject();
byte[] privateKeyBytes = pemObject.getContent();
this.privateKey = (Ed25519PrivateKeyParameters) PrivateKeyFactory.createKey(privateKeyBytes);
pemReader.close();
}

public String getSignature(String data) {

byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);

Ed25519Signer signer = new Ed25519Signer();
signer.init(true, this.privateKey);
signer.update(dataBytes, offset, dataBytes.length);
byte[] signatureBytes = signer.generateSignature();
return Base64.getEncoder().encodeToString(signatureBytes);

}
}
Loading

0 comments on commit b522c22

Please sign in to comment.