目录
1、币安API接入资料
注意:币安的API需要开代理才能调,就是国内调不通,要用国外的IP才能调,还不是所有国家都可以,所以没有这个条件的可以不用往下看了,有代理就是能翻墙,才可以继续走下去。
币安API文档位置
币安提供了详细的API文档,其中包含了各种可用的API端点和参数。您可以根据自己的需求选择合适的API端点来执行自动划转。右上角有简体中文切换。
java代码
这是binance官方提供的代码,可以用于生成签名,下面需要用到,如果是其它语言,也可以在上面文档中:
Introduction -> API Library
需求:一键划转现货账户和资金账户的某个币种的所有资金
建议1:从头开始阅读该文档,不要漏掉开头,因为很多参数和专业术语的介绍都在前面,如果你直接去找API,里面的参数可能你可能并不理解。公共参数看完后,后面的参数都是类似的,所以多花点时间阅读前面的文档。
建议2:先使用工具进行测试,比如我是使用ApiPost工具,将参数设置好后发送请求,看看调用结果,最后再编写代码自动运行,从简单到复杂。
2、文档部分专业词汇摘录
账户
SPOT:现货账户
MARGIN:杠杆账户
FUTURES:期货账户
接口鉴权
NONE 不需要鉴权的接口
TRADE 需要有效的 API-Key 和签名
MARGIN 需要有效的 API-Key 和签名
USER_DATA 需要有效的 API-Key 和签名
USER_STREAM 需要有效的 API-Key
MARKET_DATA 需要有效的 API-Key
API-Key密钥
登录到您的币安账户,进入“API管理”页面,然后创建一个新的API密钥。确保选择适当的权限和限制,以便只允许进行必要的操作。
(主页自己的头像-》APIManagement,里面可以添加密钥;注意:普通查询不用添加IP地址限制,但是转账这些操作,需要添加IP地址,防止资金被盗)
接口鉴权中需要有效的 API-Key
就是上面在币安账户页面生成的一个API秘钥串,应当在HTTP头中以 X-MBX-APIKEY字段传递。
我用的是ApiPost,本地测试时可以在文件夹里设置公共的header {X-MBX-APIKEY : “xxxxxAPI-Keyxxxxx”}
签名(重点,这里很容易出错)
文档中有对前面单独介绍:基本信息-》SIGNE
下载代码后,搜索HmacSignatureGenerator.java类,里面有一个 getSignature 方法。
注意:签名是对你传入的 所有参数+值+&拼接的字符串进行签名,且顺序最好不要改变,你就按照文档的顺序,传值,拼接,因为本来我是用map来传的,后来总是不通,后来换成List,添加参数的顺序和我前面时参数的顺序一致,就通了。
3、编码实践
1、首先,使用不需要鉴权,也不需要代理的API:如:测试服务连通性,返回服务时间
// 测试服务连通性,返回服务时间
https://data-api.binance.vision/api/v3/time
// 测试服务连通性,返回{}
https://data-api.binance.vision/api/v3/ping
2、使用查询类的API,需要鉴权+代理
base URL-摘录文档(基本信息-》API基本信息)
https://api.binance.com
https://api-gcp.binance.com
https://api1.binance.com
https://api2.binance.com
https://api3.binance.com
https://api4.binance.com
上述列表的最后4个接口 (api1-api4) 可能会提供更好的性能,但其稳定性略为逊色。因此,请务必使用最适合您现有配置的那款。所有接口的响应都是 JSON 格式。
// 这里以查询接口为例,进行账户参数解释
USER_DATA:表示这个接口需要有效的 API-Key 和签名
API-key:需要放到header中,以 X-MBX-APIKEY 为参数名传递
type:账户类型
startTime,endTime为查询资产快照起始时间,可以不填,默认七天。
limit:天数
recvWindow:这个比较重要,是以你传入的timestamp为起点,计算是否超过有效期,最大设置为60000,就是一分钟内有效
timestamp:签名需要这个参数,且要和 recvWindow 一起判断,有没有超过现在时间点,如果 timestamp+recvWindow<now(),就表示过期了。
signature:注意参数顺序,调用币安的签名函数,对data进行签名。
String data = "type=LIMIT&recvWindow=60000×tamp=1499827319559";
// ApiPost调用信息
https://api.binance.com/sapi/v1/accountSnapshot?type=SPOT&recvWindow=60000×tamp=1499827319559&signature=e335b4e7a2d7cede3246ed0f1915766d3fb8175ae183b09cc3a886aeb5ac256b
3、提币API(重点)
这里需要打开 API-Key的权限,且需要添加IP地址,否则提币功能是调不通的。
难点:币安需要开代理才能调通,开了代理还要查询自己的公网IP,最好还是静态外网IP,否则下次再用,又要去币安登记IP地址,不过亲测也快,三分钟就搞定。注意不是局域网IP,192.168.x.x这是局域网IP地址,注意区分。
Windows查看外网IP地址:curl ipinfo.io
MAC 查看外网IP地址:curl ifconfig.me
package com.binance.connector.client.utils.signaturegenerator;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.codec.binary.Hex;
import com.binance.connector.client.utils.ParameterChecker;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.net.URI;
import java.util.*;
public final class HmacSignatureGenerator implements SignatureGenerator {
private static final String apiKey = "this is my apiKey";
private static final String HMAC_SHA256 = "HmacSHA256";
private String apiSecret = "this is my secretKey"; // 和 apiKey一起生成的
public HmacSignatureGenerator() {
}
public HmacSignatureGenerator(String apiSecret) {
ParameterChecker.checkParameterType(apiSecret, String.class, "apiSecret");
this.apiSecret = apiSecret;
}
public String getSignature(String data) {
byte[] hmacSha256;
try {
SecretKeySpec secretKeySpec = new SecretKeySpec(apiSecret.getBytes(), HMAC_SHA256);
Mac mac = Mac.getInstance(HMAC_SHA256);
mac.init(secretKeySpec);
hmacSha256 = mac.doFinal(data.getBytes());
} catch (Exception e) {
throw new RuntimeException("Failed to calculate hmac-sha256", e);
}
return Hex.encodeHexString(hmacSha256);
}
public static void main(String[] args) {
transferSpotAndFund();
}
/**
* 正式环境:一键划转现货账户和资金账户的某个币种的所有资金
*/
public static void transferSpotAndFund() {
// 1、获取现货账户资金余额
String spotAccountTokenInfo = spotAccountInfo();
// 2、获取资金账户的余额
String fundAccountTokenInfo = fundAccountInfo();
// 3、解析现货账户需要划转的币种的余额
String tokenName = "USDT";
String spotTokenFree = jsonParseSpotAccount(spotAccountTokenInfo, tokenName).split("\\.")[0];
System.out.println(String.format("现货账户转出的币种为:%s, 转出的金额为:%s", tokenName, spotTokenFree));
// 4、解析资金账户需要划转的币种的余额
String fundTokenFree = jsonParseFundAccount(fundAccountTokenInfo, tokenName).split("\\.")[0];
System.out.println(String.format("资金账户转出的币种为:%s, 转出的金额为:%s", tokenName, fundTokenFree));
// 5、对此金额进行划转(0现货账户,1资金账户)
// withdraw(tokenName, spotTokenFree, "0");
// withdraw(tokenName, fundTokenFree, "1");
}
/**
* 解析现货账户的JSON,获取指定币种的余额,没查到则返回 0
*/
public static String jsonParseSpotAccount(String jsonStr, String tokenName) {
String tokenNameKey = "asset"; // 币安返回币种列表的名称key
String tokenFreeKey = "free"; // 币安返回币种列表的余额key
JSONObject jsonObject = JSON.parseObject(jsonStr);
JSONArray balances = jsonObject.getJSONArray("balances");
for (int i = 0; i < balances.size(); i++) {
JSONObject balance = balances.getJSONObject(i);
if (tokenName.equals(balance.getString(tokenNameKey))) {
return balance.getString(tokenFreeKey);
}
}
return "0";
}
/**
* 解析资金账户的JSON,获取指定币种的余额,没查到则返回 0
*/
public static String jsonParseFundAccount(String jsonStr, String tokenName) {
String tokenNameKey = "asset"; // 币安返回币种列表的名称key
String tokenFreeKey = "free"; // 币安返回币种列表的余额key
JSONArray balances = JSON.parseArray(jsonStr);
for (int i = 0; i < balances.size(); i++) {
JSONObject balance = balances.getJSONObject(i);
if (tokenName.equals(balance.getString(tokenNameKey))) {
return balance.getString(tokenFreeKey);
}
}
return "0";
}
/**
* 正式环境:查询每日资产快照 (USER_DATA)
*/
public static void accountSnapshot() {
// 1、构造参数和签名
String type = "SPOT";
String startTime = String.valueOf(System.currentTimeMillis());
String limit = "1";
String recvWindow = "60000";
String timestamp = String.valueOf(System.currentTimeMillis());
String signature;
//TODO 注意:如果修改了上面的参数,需要修改签名,增加此参数,否则签名验证会失效
String data = String.format("type=%s&limit=%s&recvWindow=%s×tamp=%s", type, limit, recvWindow, timestamp);
HmacSignatureGenerator generator = new HmacSignatureGenerator();
signature = generator.getSignature(data);
System.out.println("需要签名的数据:" + data);
System.out.println("时间戳:" + timestamp);
System.out.println("签名:" + signature);
// 2、发送请求
// 构造参数的顺序必须一定,否则会导致签名验证失败
List<NameValuePair> paramList = new ArrayList<>();
paramList.add(new BasicNameValuePair("type", type));
paramList.add(new BasicNameValuePair("limit", limit));
paramList.add(new BasicNameValuePair("recvWindow", recvWindow));
paramList.add(new BasicNameValuePair("timestamp", timestamp));
paramList.add(new BasicNameValuePair("signature", signature));
String url = "https://api.binance.com/sapi/v1/accountSnapshot";
doGet(url, paramList);
}
/**
* 正式环境:提币 (USER_DATA)
*/
public static void withdraw(String coin, String amount, String walletType) {
// 1、构造参数和签名
String network = "TRX";
String address = "this is someone receiveAddress";
String transactionFeeFlag = "true"; // 当站内转账时免手续费, true: 手续费归资金转入方; false: 手续费归资金转出方; . 默认 false.
String recvWindow = "60000";
String timestamp = String.valueOf(System.currentTimeMillis());
String signature;
String data = String.format("coin=%s&network=%s&address=%s&amount=%s&transactionFeeFlag=%s&walletType=%s" +
"&recvWindow=%s×tamp=%s",
coin, network, address, amount, transactionFeeFlag, walletType, recvWindow, timestamp);
HmacSignatureGenerator generator = new HmacSignatureGenerator();
signature = generator.getSignature(data);
// System.out.println("需要签名的数据:" + data);
// System.out.println("时间戳:" + timestamp);
// System.out.println("签名:" + signature);
// 2、发送请求
// 构造参数的顺序必须一定,否则会导致签名验证失败
List<NameValuePair> paramList = new ArrayList<>();
paramList.add(new BasicNameValuePair("coin", coin));
paramList.add(new BasicNameValuePair("network", network));
paramList.add(new BasicNameValuePair("address", address));
paramList.add(new BasicNameValuePair("amount", amount));
paramList.add(new BasicNameValuePair("transactionFeeFlag", transactionFeeFlag));
paramList.add(new BasicNameValuePair("walletType", walletType));
paramList.add(new BasicNameValuePair("recvWindow", recvWindow));
paramList.add(new BasicNameValuePair("timestamp", timestamp));
paramList.add(new BasicNameValuePair("signature", signature));
String url = "https://api.binance.com/sapi/v1/capital/withdraw/apply";
doPost(url, paramList);
}
/**
* 正式环境:现货账户信息 (USER_DATA)
*/
public static String spotAccountInfo() {
// 1、构造参数和签名
String recvWindow = "60000";
String timestamp = String.valueOf(System.currentTimeMillis());
String signature;
//TODO 注意:如果修改了上面的参数,需要修改签名,增加此参数,否则签名验证会失效
String data = String.format("recvWindow=%s×tamp=%s", recvWindow, timestamp);
HmacSignatureGenerator generator = new HmacSignatureGenerator();
signature = generator.getSignature(data);
// System.out.println("需要签名的数据:" + data);
// System.out.println("时间戳:" + timestamp);
// System.out.println("签名:" + signature);
// 2、发送请求
// 构造参数的顺序必须一定,否则会导致签名验证失败
List<NameValuePair> paramList = new ArrayList<>();
paramList.add(new BasicNameValuePair("recvWindow", recvWindow));
paramList.add(new BasicNameValuePair("timestamp", timestamp));
paramList.add(new BasicNameValuePair("signature", signature));
String url = "https://api.binance.com/api/v3/account";
return doGet(url, paramList);
}
/**
* 正式环境:资金账户信息 (USER_DATA)
*/
public static String fundAccountInfo() {
// 1、构造参数和签名
String recvWindow = "60000";
String timestamp = String.valueOf(System.currentTimeMillis());
String signature;
//TODO 注意:如果修改了上面的参数,需要修改签名,增加此参数,否则签名验证会失效
String data = String.format("recvWindow=%s×tamp=%s", recvWindow, timestamp);
HmacSignatureGenerator generator = new HmacSignatureGenerator();
signature = generator.getSignature(data);
// 2、发送请求
// 构造参数的顺序必须一定,否则会导致签名验证失败
List<NameValuePair> paramList = new ArrayList<>();
paramList.add(new BasicNameValuePair("recvWindow", recvWindow));
paramList.add(new BasicNameValuePair("timestamp", timestamp));
paramList.add(new BasicNameValuePair("signature", signature));
String url = "https://api.binance.com/sapi/v1/asset/get-funding-asset";
return doPost(url, paramList);
}
/**
* POST请求
* 执行流程:
*/
public static String doPost(String url, List<NameValuePair> paramList) {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("X-MBX-APIKEY", apiKey);
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded");
// 模拟表单
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);
httpPost.setEntity(entity);
// 执行http请求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "utf-8");
System.out.println("执行结果:" + resultString);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
/**
* GET请求
* 流程:
* 1、URL
* 2、转换为 URIBuilder 用来加参数
* 3、转换为 URI
* 4、构造 HTTPGET 对象并放入 URI
* 5、设置 header 并发送
*/
public static String doGet(String url, List<NameValuePair> paramList) {
// 创建Httpclient对象
CloseableHttpClient httpclient = HttpClients.createDefault();
String resultString = "";
CloseableHttpResponse response = null;
try {
// 2、转换为 URIBuilder 用来加参数
URIBuilder builder = new URIBuilder(url);
builder.addParameters(paramList);
// 3、转换为 URI
URI uri = builder.build();
// 4、构造 HTTPGET 对象并放入 URI
HttpGet httpGet = new HttpGet(uri);
httpGet.setHeader("X-MBX-APIKEY", apiKey);
// 5、执行请求
response = httpclient.execute(httpGet);
// 解析返回状态
if (response != null) {
resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
System.out.println("查询结果:" + resultString);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (response != null) {
response.close();
}
httpclient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
}
4、图片