Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/rsa sha256 #626

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,25 @@ const client2 = Binance({
client.time().then(time => console.log(time))
```

Can also use an RSA api key. You can create [here](https://www.binance.com/en/support/faq/how-to-generate-an-rsa-key-pair-to-send-api-requests-on-binance-2b79728f331e43079b27440d9d15c5db)

```js
import Binance from 'binance-api-node'

const client = Binance()

// Authenticated client, can make signed calls
const client2 = Binance({
apiKey: 'xxx',
apiSecret: 'xxx',
getTime: xxx,
privateKey: 'path of .pem file',
privateKeyPassphrase: 'password .pem'
})

client.time().then(time => console.log(time))
```

If you do not have an appropriate babel config, you will need to use the basic commonjs requires.

```js
Expand Down
40 changes: 21 additions & 19 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
// tslint:disable:interface-name
declare module 'binance-api-node' {
export default function(options?: {
export default function (options?: {
apiKey?: string
apiSecret?: string
getTime?: () => number | Promise<number>
httpBase?: string
httpFutures?: string
wsBase?: string
wsFutures?: string
proxy?: string
proxy?: string,
privateKey?: Buffer | string,
privateKeyPassphrase?: string
}): Binance

export type ErrorCodes_LT =
Expand Down Expand Up @@ -339,11 +341,11 @@ declare module 'binance-api-node' {
export type CancelOrderOptions =
| { symbol: string; orderId: number; useServerTime?: boolean; newClientOrderId?: string }
| {
symbol: string
origClientOrderId: string
useServerTime?: boolean
newClientOrderId?: string
}
symbol: string
origClientOrderId: string
useServerTime?: boolean
newClientOrderId?: string
}

export type GetOrderOcoOptions =
| { orderListId: number; useServerTime?: boolean }
Expand All @@ -352,11 +354,11 @@ declare module 'binance-api-node' {
export type CancelOrderOcoOptions =
| { symbol: string; orderListId: number; useServerTime?: boolean; newClientOrderId?: string }
| {
symbol: string
listClientOrderId: string
useServerTime?: boolean
newClientOrderId?: string
}
symbol: string
listClientOrderId: string
useServerTime?: boolean
newClientOrderId?: string
}

export type cancelOpenOrdersOptions = {
symbol: string
Expand Down Expand Up @@ -599,10 +601,10 @@ declare module 'binance-api-node' {
recvWindow?: number
}): Promise<DepositHistoryResponse>
dustLog(options: {
startTime?: number
endTime?: number
recvWindow?: number
timestamp: number
startTime?: number
endTime?: number
recvWindow?: number
timestamp: number
}): DustLog
universalTransfer(options: UniversalTransfer): Promise<{ tranId: number }>
universalTransferHistory(
Expand Down Expand Up @@ -1631,9 +1633,9 @@ declare module 'binance-api-node' {
listClientOrderId: string
transactionTime: number
orders: Array<{
symbol: string
orderId: number
clientOrderId: string
symbol: string
orderId: number
clientOrderId: string
}>
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "binance-api-node",
"version": "0.12.4",
"version": "0.12.5-RC1",
"description": "A node API wrapper for Binance",
"main": "dist",
"files": [
Expand Down
75 changes: 50 additions & 25 deletions src/http-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,21 @@ const info = {
/**
* Build query string for uri encoded url based on json object
*/
const makeQueryString = q =>
q
? `?${Object.keys(q)
.map(k => `${encodeURIComponent(k)}=${encodeURIComponent(q[k])}`)
.join('&')}`
: ''
const makeQueryString = params => {
if (!params) return ''
return Object.entries(params)
.map(stringifyKeyValuePair)
.join('&')
}

/**
* NOTE: The array conversion logic is different from usual query string.
* E.g. symbols=["BTCUSDT","BNBBTC"] instead of symbols[]=BTCUSDT&symbols[]=BNBBTC
*/
const stringifyKeyValuePair = ([key, value]) => {
const valueString = Array.isArray(value) ? `["${value.join('","')}"]` : value
return `${key}=${encodeURIComponent(valueString)}`
}

/**
* Get API limits info from headers
Expand All @@ -47,8 +56,8 @@ const responseHandler = res => {
const marketName = res.url.includes(FUTURES)
? 'futures'
: res.url.includes(COIN_FUTURES)
? 'delivery'
: 'spot'
? 'delivery'
: 'spot'

Object.keys(headersMapping).forEach(key => {
const outKey = headersMapping[key]
Expand Down Expand Up @@ -122,13 +131,12 @@ const checkParams = (name, payload, requires = []) => {
const publicCall = ({ proxy, endpoints }) => (path, data, method = 'GET', headers = {}) => {
return sendResult(
fetch(
`${
path.includes('/fapi') || path.includes('/futures')
? endpoints.futures
: path.includes('/dapi')
`${path.includes('/fapi') || path.includes('/futures')
? endpoints.futures
: path.includes('/dapi')
? endpoints.delivery
: endpoints.base
}${path}${makeQueryString(data)}`,
}${path}?${makeQueryString(data)}`,
{
method,
json: true,
Expand Down Expand Up @@ -173,8 +181,10 @@ const privateCall = ({
endpoints,
getTime = defaultGetTime,
pubCall,
privateKey,
privateKeyPassphrase
}) => (path, data = {}, method = 'GET', noData, noExtra) => {
if (!apiKey || !apiSecret) {
if ((!apiKey || !apiSecret) && (!apiKey || !privateKey || !privateKeyPassphrase)) {
throw new Error('You need to pass an API key and secret to make authenticated calls.')
}

Expand All @@ -186,22 +196,37 @@ const privateCall = ({
delete data.useServerTime
}

const signature = crypto
.createHmac('sha256', apiSecret)
.update(makeQueryString({ ...data, timestamp }).substr(1))
.digest('hex')

const newData = noExtra ? data : { ...data, timestamp, signature }
let signature;
let newData;
let queryString;
if (!privateKey) {
signature = crypto
.createHmac('sha256', apiSecret)
.update(makeQueryString({ ...data, timestamp }))
.digest('hex')
newData = noExtra ? data : { ...data, timestamp, signature }
queryString = makeQueryString(newData)
} else {
signature = crypto
.createSign('RSA-SHA256')
.update(makeQueryString({ ...data, timestamp }))
.sign({
key: privateKey,
passphrase: privateKeyPassphrase
}, 'base64')
signature = encodeURIComponent(signature)
newData = noExtra ? makeQueryString(data) : `${makeQueryString({ ...data, timestamp })}&signature=${signature}`;
queryString = newData;
}

return sendResult(
fetch(
`${
path.includes('/fapi') || path.includes('/futures')
? endpoints.futures
: path.includes('/dapi')
`${path.includes('/fapi') || path.includes('/futures')
? endpoints.futures
: path.includes('/dapi')
? endpoints.delivery
: endpoints.base
}${path}${noData ? '' : makeQueryString(newData)}`,
}${path}${noData ? '' : `?${queryString}`} `,
{
method,
headers: { 'X-MBX-APIKEY': apiKey },
Expand Down