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

Further development on the Margin Trading API #226

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
99 changes: 74 additions & 25 deletions src/http-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ const defaultGetTime = () => Date.now()
* Build query string for uri encoded url based on json object
*/
const makeQueryString = q =>
q
? `?${Object.keys(q)
q ?
`?${Object.keys(q)
.map(k => `${encodeURIComponent(k)}=${encodeURIComponent(q[k])}`)
.join('&')}`
: ''
.join('&')}` :
''

/**
* Finalize API response
Expand Down Expand Up @@ -74,7 +74,9 @@ const checkParams = (name, payload, requires = []) => {
* @param {object} headers
* @returns {object} The api response
*/
const publicCall = ({ endpoints }) => (path, data, method = 'GET', headers = {}) =>
const publicCall = ({
endpoints
}) => (path, data, method = 'GET', headers = {}) =>
sendResult(
fetch(`${!path.includes('/fapi') ? endpoints.base : endpoints.futures}${path}${makeQueryString(data)}`, {
method,
Expand All @@ -91,7 +93,10 @@ const publicCall = ({ endpoints }) => (path, data, method = 'GET', headers = {})
* @param {string} method HTTB VERB, GET by default
* @returns {object} The api response
*/
const keyCall = ({ apiKey, pubCall }) => (path, data, method = 'GET') => {
const keyCall = ({
apiKey,
pubCall
}) => (path, data, method = 'GET') => {
if (!apiKey) {
throw new Error('You need to pass an API key to make this call.')
}
Expand All @@ -110,7 +115,13 @@ const keyCall = ({ apiKey, pubCall }) => (path, data, method = 'GET') => {
* @param {object} headers
* @returns {object} The api response
*/
const privateCall = ({ apiKey, apiSecret, endpoints, getTime = defaultGetTime, pubCall }) => (
const privateCall = ({
apiKey,
apiSecret,
endpoints,
getTime = defaultGetTime,
pubCall
}) => (
path,
data = {},
method = 'GET',
Expand All @@ -121,29 +132,37 @@ const privateCall = ({ apiKey, apiSecret, endpoints, getTime = defaultGetTime, p
throw new Error('You need to pass an API key and secret to make authenticated calls.')
}

return (data && data.useServerTime
? pubCall('/api/v1/time').then(r => r.serverTime)
: Promise.resolve(getTime())
return (data && data.useServerTime ?
pubCall('/api/v1/time').then(r => r.serverTime) :
Promise.resolve(getTime())
).then(timestamp => {
if (data) {
delete data.useServerTime
}

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

const newData = noExtra ? data : { ...data, timestamp, signature }
const newData = noExtra ? data : {
...data,
timestamp,
signature
}

return sendResult(
fetch(
`${!path.includes('/fapi') ? endpoints.base : endpoints.futures}${path}${noData
? ''
: makeQueryString(newData)}`,
{
: makeQueryString(newData)}`, {
method,
headers: { 'X-MBX-APIKEY': apiKey },
headers: {
'X-MBX-APIKEY': apiKey
},
json: true,
},
),
Expand Down Expand Up @@ -171,22 +190,29 @@ export const candleFields = [
*/
const candles = (pubCall, payload) =>
checkParams('candles', payload, ['symbol']) &&
pubCall('/api/v1/klines', { interval: '5m', ...payload }).then(candles =>
pubCall('/api/v1/klines', {
interval: '5m',
...payload
}).then(candles =>
candles.map(candle => zip(candleFields, candle)),
)

/**
* Create a new order wrapper for market order simplicity
*/
const order = (privCall, payload = {}, url) => {
const newPayload =
['LIMIT', 'STOP_LOSS_LIMIT', 'TAKE_PROFIT_LIMIT'].includes(payload.type) || !payload.type
? { timeInForce: 'GTC', ...payload }
: payload
const newPayload = ['LIMIT', 'STOP_LOSS_LIMIT', 'TAKE_PROFIT_LIMIT'].includes(payload.type) || !payload.type ? {
timeInForce: 'GTC',
...payload
} :
payload

return (
checkParams('order', newPayload, ['symbol', 'side', 'quantity']) &&
privCall(url, { type: 'LIMIT', ...newPayload }, 'POST')
privCall(url, {
type: 'LIMIT',
...newPayload
}, 'POST')
)
}

Expand All @@ -195,7 +221,11 @@ const order = (privCall, payload = {}, url) => {
*/
const book = (pubCall, payload) =>
checkParams('book', payload, ['symbol']) &&
pubCall('/api/v1/depth', payload).then(({ lastUpdateId, asks, bids }) => ({
pubCall('/api/v1/depth', payload).then(({
lastUpdateId,
asks,
bids
}) => ({
lastUpdateId,
asks: asks.map(a => zip(['price', 'quantity'], a)),
bids: bids.map(b => zip(['price', 'quantity'], b)),
Expand All @@ -222,9 +252,19 @@ export default opts => {
'futures': opts && opts.httpFutures || FUTURES,
}

const pubCall = publicCall({ ...opts, endpoints })
const privCall = privateCall({ ...opts, endpoints, pubCall })
const kCall = keyCall({ ...opts, pubCall })
const pubCall = publicCall({
...opts,
endpoints
})
const privCall = privateCall({
...opts,
endpoints,
pubCall
})
const kCall = keyCall({
...opts,
pubCall
})

return {
ping: () => pubCall('/api/v1/ping').then(() => true),
Expand Down Expand Up @@ -297,7 +337,16 @@ export default opts => {
marginOrder: payload => order(privCall, payload, '/sapi/v1/margin/order'),
marginCancelOrder: payload => privCall('/sapi/v1/margin/order', payload, 'DELETE'),
marginOpenOrders: payload => privCall('/sapi/v1/margin/openOrders', payload),
marginOrderHistory: payload => privCall('/sapi/v1/margin/order', payload),
marginAccountInfo: payload => privCall('/sapi/v1/margin/account', payload),
marginMyTrades: payload => privCall('/sapi/v1/margin/myTrades', payload),
marginTransfer: payload => privCall('/sapi/v1/margin/transfer', payload, 'POST'),
marginBorrow: payload => privCall('/sapi/v1/margin/loan', payload, 'POST'),
marginMaxBorrowable: payload => privCall('/sapi/v1/margin/maxBorrowable', payload),
marginRepay: payload => privCall('/sapi/v1/margin/repay', payload, 'POST'),
marginRepayRecord: payload => privCall('/sapi/v1/margin/repay', payload),
marginAssets: payload => privCall('/sapi/v1/margin/allAssets', payload),
marginAsset: payload => privCall('/sapi/v1/margin/asset', payload),
maxTransferable: payload => privCall('/sapi/v1/margin/maxTransferable', payload),
}
}