@cryptodevops/n8n-nodes-binance
Version:
n8n node for Binance API
685 lines (684 loc) • 31.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Binance = void 0;
const n8n_workflow_1 = require("n8n-workflow");
const crypto_1 = require("crypto");
class Binance {
constructor() {
this.description = {
displayName: 'Binance',
name: 'binance',
icon: 'file:binance.svg',
group: ['transform'],
version: 1,
subtitle: '={{$parameter["operation"]}}',
description: 'Interact with Binance API',
defaults: {
name: 'Binance',
},
inputs: ["main" /* NodeConnectionType.Main */],
outputs: ["main" /* NodeConnectionType.Main */],
credentials: [
{
name: 'binance',
required: false,
},
],
properties: [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Get 24hr Ticker Statistics',
value: 'get24hrTicker',
description: 'Get 24hr ticker price change statistics',
action: 'Get 24hr ticker statistics',
},
{
name: 'Get Account Information',
value: 'getAccountInfo',
description: 'Get current account information',
action: 'Get account information',
},
{
name: 'Get All Orders',
value: 'getAllOrders',
description: 'Get all account orders; active, canceled, or filled',
action: 'Get all orders',
},
{
name: 'Get Asset Detail',
value: 'getAssetDetail',
description: 'Fetch details of assets supported on Binance',
action: 'Get asset detail',
},
{
name: 'Get Asset Dividend Record',
value: 'getAssetDividendRecord',
description: 'Query asset dividend record',
action: 'Get asset dividend record',
},
{
name: 'Get Convert History',
value: 'getConvertHistory',
description: 'Get convert trade history',
action: 'Get convert history',
},
{
name: 'Get Deposit Address',
value: 'getDepositAddress',
description: 'Fetch deposit address with network',
action: 'Get deposit address',
},
{
name: 'Get Deposit History',
value: 'getDepositHistory',
description: 'Fetch deposit history',
action: 'Get deposit history',
},
{
name: 'Get Dust Log',
value: 'getDustLog',
action: 'Get dust log',
},
{
name: 'Get Exchange Information',
value: 'getExchangeInfo',
description: 'Get current exchange trading rules and symbol information',
action: 'Get exchange information',
},
{
name: 'Get Kline/Candlestick Data',
value: 'getKlines',
description: 'Get kline/candlestick bars for a symbol',
action: 'Get kline candlestick data',
},
{
name: 'Get Open Orders',
value: 'getOpenOrders',
description: 'Get all open orders on a symbol',
action: 'Get open orders',
},
{
name: 'Get Order Book',
value: 'getOrderBook',
description: 'Get order book for a symbol',
action: 'Get order book',
},
{
name: 'Get Simple Earn Positions',
value: 'getSimpleEarnPositions',
description: 'Get Simple Earn flexible product position',
action: 'Get simple earn positions',
},
{
name: 'Get Sub-Account List',
value: 'getSubAccountList',
description: 'Query sub-account list',
action: 'Get sub account list',
},
{
name: 'Get Ticker Price',
value: 'getTickerPrice',
description: 'Get latest price for a symbol',
action: 'Get ticker price',
},
{
name: 'Get Trade Fee',
value: 'getTradeFee',
action: 'Get trade fee',
},
{
name: 'Get Trade List',
value: 'getMyTrades',
description: 'Get trades for a specific account and symbol',
action: 'Get trade list',
},
{
name: 'Get Withdraw History',
value: 'getWithdrawHistory',
description: 'Fetch withdraw history',
action: 'Get withdraw history',
},
{
name: 'Withdraw',
value: 'withdraw',
description: 'Submit a withdraw request',
action: 'Withdraw',
},
],
default: 'getTickerPrice',
},
{
displayName: 'Symbol',
name: 'symbol',
type: 'string',
default: 'BTCUSDT',
placeholder: 'BTCUSDT',
description: 'Trading pair symbol',
displayOptions: {
show: {
operation: [
'getTickerPrice',
'get24hrTicker',
'getOrderBook',
'getKlines',
'getAllOrders',
'getOpenOrders',
'getMyTrades',
'getTradeFee',
],
},
},
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
typeOptions: {
minValue: 1,
},
default: 50,
description: 'Max number of results to return',
displayOptions: {
show: {
operation: ['getOrderBook', 'getKlines', 'getAllOrders', 'getMyTrades', 'getConvertHistory'],
},
},
},
{
displayName: 'Interval',
name: 'interval',
type: 'options',
options: [
{ name: '1 Day', value: '1d' },
{ name: '1 Hour', value: '1h' },
{ name: '1 Minute', value: '1m' },
{ name: '1 Month', value: '1M' },
{ name: '1 Week', value: '1w' },
{ name: '12 Hours', value: '12h' },
{ name: '15 Minutes', value: '15m' },
{ name: '2 Hours', value: '2h' },
{ name: '3 Days', value: '3d' },
{ name: '3 Minutes', value: '3m' },
{ name: '30 Minutes', value: '30m' },
{ name: '4 Hours', value: '4h' },
{ name: '5 Minutes', value: '5m' },
{ name: '6 Hours', value: '6h' },
{ name: '8 Hours', value: '8h' },
],
default: '1h',
description: 'Kline interval',
displayOptions: {
show: {
operation: ['getKlines'],
},
},
},
{
displayName: 'Coin',
name: 'coin',
type: 'string',
default: '',
placeholder: 'BTC',
description: 'Coin name',
displayOptions: {
show: {
operation: ['getDepositHistory', 'getWithdrawHistory', 'getDepositAddress', 'withdraw'],
},
},
},
{
displayName: 'Address',
name: 'address',
type: 'string',
default: '',
placeholder: 'Withdrawal address',
description: 'Withdrawal address',
displayOptions: {
show: {
operation: ['withdraw'],
},
},
},
{
displayName: 'Network',
name: 'network',
type: 'string',
default: '',
placeholder: 'ETH',
description: 'Network name',
displayOptions: {
show: {
operation: ['getDepositAddress', 'withdraw'],
},
},
},
{
displayName: 'Address Tag',
name: 'addressTag',
type: 'string',
default: '',
placeholder: 'Address tag (optional)',
description: 'Secondary address identifier for coins like XRP, XMR etc',
displayOptions: {
show: {
operation: ['withdraw'],
},
},
},
{
displayName: 'Asset',
name: 'asset',
type: 'string',
default: '',
placeholder: 'BTC',
description: 'Asset name',
displayOptions: {
show: {
operation: ['getAssetDetail', 'getAssetDividendRecord', 'getSimpleEarnPositions'],
},
},
},
{
displayName: 'Transfer Type',
name: 'transferType',
type: 'options',
options: [
{ name: 'Internal Transfer', value: 0 },
{ name: 'External Transfer', value: 1 },
],
default: 0,
description: 'Transfer type for deposit history',
displayOptions: {
show: {
operation: ['getDepositHistory'],
},
},
},
{
displayName: 'Start Time',
name: 'startTime',
type: 'dateTime',
default: '',
description: 'Start time for the query',
displayOptions: {
show: {
operation: ['getDustLog', 'getAssetDividendRecord', 'getConvertHistory'],
},
},
},
{
displayName: 'End Time',
name: 'endTime',
type: 'dateTime',
default: '',
description: 'End time for the query',
displayOptions: {
show: {
operation: ['getDustLog', 'getAssetDividendRecord', 'getConvertHistory'],
},
},
},
{
displayName: 'Account Type',
name: 'accountType',
type: 'options',
options: [
{ name: 'Spot', value: 'SPOT' },
{ name: 'Margin', value: 'MARGIN' },
],
default: 'SPOT',
description: 'Account type for dust log',
displayOptions: {
show: {
operation: ['getDustLog'],
},
},
},
{
displayName: 'Product ID',
name: 'productId',
type: 'string',
default: '',
description: 'Product ID for Simple Earn positions',
displayOptions: {
show: {
operation: ['getSimpleEarnPositions'],
},
},
},
],
};
}
async execute() {
const items = this.getInputData();
const returnData = [];
for (let i = 0; i < items.length; i++) {
try {
const operation = this.getNodeParameter('operation', i);
let endpoint = '';
let method = 'GET';
let requiresAuth = false;
const qs = {};
switch (operation) {
case 'getTickerPrice':
endpoint = '/api/v3/ticker/price';
const tickerSymbol = this.getNodeParameter('symbol', i);
if (tickerSymbol) {
qs.symbol = tickerSymbol.toUpperCase();
}
break;
case 'get24hrTicker':
endpoint = '/api/v3/ticker/24hr';
const ticker24Symbol = this.getNodeParameter('symbol', i);
if (ticker24Symbol) {
qs.symbol = ticker24Symbol.toUpperCase();
}
break;
case 'getOrderBook':
endpoint = '/api/v3/depth';
const orderBookSymbol = this.getNodeParameter('symbol', i);
const orderBookLimit = this.getNodeParameter('limit', i);
qs.symbol = orderBookSymbol.toUpperCase();
if (orderBookLimit) {
qs.limit = orderBookLimit;
}
break;
case 'getKlines':
endpoint = '/api/v3/klines';
const klinesSymbol = this.getNodeParameter('symbol', i);
const klinesInterval = this.getNodeParameter('interval', i);
const klinesLimit = this.getNodeParameter('limit', i);
qs.symbol = klinesSymbol.toUpperCase();
qs.interval = klinesInterval;
if (klinesLimit) {
qs.limit = klinesLimit;
}
break;
case 'getExchangeInfo':
endpoint = '/api/v3/exchangeInfo';
break;
case 'getAccountInfo':
endpoint = '/api/v3/account';
requiresAuth = true;
break;
case 'getAllOrders':
endpoint = '/api/v3/allOrders';
requiresAuth = true;
const allOrdersSymbol = this.getNodeParameter('symbol', i);
const allOrdersLimit = this.getNodeParameter('limit', i);
qs.symbol = allOrdersSymbol.toUpperCase();
if (allOrdersLimit) {
qs.limit = allOrdersLimit;
}
break;
case 'getOpenOrders':
endpoint = '/api/v3/openOrders';
requiresAuth = true;
const openOrdersSymbol = this.getNodeParameter('symbol', i);
if (openOrdersSymbol) {
qs.symbol = openOrdersSymbol.toUpperCase();
}
break;
case 'getMyTrades':
endpoint = '/api/v3/myTrades';
requiresAuth = true;
const myTradesSymbol = this.getNodeParameter('symbol', i);
const myTradesLimit = this.getNodeParameter('limit', i);
qs.symbol = myTradesSymbol.toUpperCase();
if (myTradesLimit) {
qs.limit = myTradesLimit;
}
break;
case 'getDepositHistory':
endpoint = '/sapi/v1/capital/deposit/hisrec';
requiresAuth = true;
const depositCoin = this.getNodeParameter('coin', i);
const transferType = this.getNodeParameter('transferType', i);
if (depositCoin) {
qs.coin = depositCoin.toUpperCase();
}
if (transferType !== undefined) {
qs.status = transferType;
}
break;
case 'getWithdrawHistory':
endpoint = '/sapi/v1/capital/withdraw/history';
requiresAuth = true;
const withdrawCoin = this.getNodeParameter('coin', i);
if (withdrawCoin) {
qs.coin = withdrawCoin.toUpperCase();
}
break;
case 'getDepositAddress':
endpoint = '/sapi/v1/capital/deposit/address';
requiresAuth = true;
const depositAddressCoin = this.getNodeParameter('coin', i);
const depositNetwork = this.getNodeParameter('network', i);
qs.coin = depositAddressCoin.toUpperCase();
if (depositNetwork) {
qs.network = depositNetwork.toUpperCase();
}
break;
case 'withdraw':
endpoint = '/sapi/v1/capital/withdraw/apply';
method = 'POST';
requiresAuth = true;
const withdrawalCoin = this.getNodeParameter('coin', i);
const withdrawalAddress = this.getNodeParameter('address', i);
const withdrawalNetwork = this.getNodeParameter('network', i);
const withdrawalAddressTag = this.getNodeParameter('addressTag', i);
qs.coin = withdrawalCoin.toUpperCase();
qs.address = withdrawalAddress;
if (withdrawalNetwork) {
qs.network = withdrawalNetwork.toUpperCase();
}
if (withdrawalAddressTag) {
qs.addressTag = withdrawalAddressTag;
}
break;
case 'getAssetDetail':
endpoint = '/sapi/v1/asset/assetDetail';
requiresAuth = true;
const assetDetailAsset = this.getNodeParameter('asset', i);
if (assetDetailAsset) {
qs.asset = assetDetailAsset.toUpperCase();
}
break;
case 'getDustLog':
endpoint = '/sapi/v1/asset/dribblet';
requiresAuth = true;
const dustStartTime = this.getNodeParameter('startTime', i);
const dustEndTime = this.getNodeParameter('endTime', i);
const dustAccountType = this.getNodeParameter('accountType', i);
if (dustStartTime) {
qs.startTime = new Date(dustStartTime).getTime();
}
if (dustEndTime) {
qs.endTime = new Date(dustEndTime).getTime();
}
if (dustAccountType) {
qs.accountType = dustAccountType;
}
break;
case 'getAssetDividendRecord':
endpoint = '/sapi/v1/asset/assetDividend';
requiresAuth = true;
const dividendAsset = this.getNodeParameter('asset', i);
const dividendStartTime = this.getNodeParameter('startTime', i);
const dividendEndTime = this.getNodeParameter('endTime', i);
if (dividendAsset) {
qs.asset = dividendAsset.toUpperCase();
}
if (dividendStartTime) {
qs.startTime = new Date(dividendStartTime).getTime();
}
if (dividendEndTime) {
qs.endTime = new Date(dividendEndTime).getTime();
}
break;
case 'getTradeFee':
endpoint = '/sapi/v1/asset/tradeFee';
requiresAuth = true;
const tradeFeeSymbol = this.getNodeParameter('symbol', i);
if (tradeFeeSymbol) {
qs.symbol = tradeFeeSymbol.toUpperCase();
}
break;
case 'getSimpleEarnPositions':
endpoint = '/sapi/v1/simple-earn/flexible/position';
requiresAuth = true;
const earnAsset = this.getNodeParameter('asset', i);
const earnProductId = this.getNodeParameter('productId', i);
if (earnAsset) {
qs.asset = earnAsset.toUpperCase();
}
if (earnProductId) {
qs.productId = earnProductId;
}
break;
case 'getConvertHistory':
endpoint = '/sapi/v1/convert/tradeFlow';
requiresAuth = true;
const convertStartTime = this.getNodeParameter('startTime', i);
const convertEndTime = this.getNodeParameter('endTime', i);
const convertLimit = this.getNodeParameter('limit', i);
if (convertStartTime) {
qs.startTime = new Date(convertStartTime).getTime();
}
if (convertEndTime) {
qs.endTime = new Date(convertEndTime).getTime();
}
if (convertLimit) {
qs.limit = Math.min(convertLimit, 1000);
}
break;
case 'getSubAccountList':
endpoint = '/sapi/v1/sub-account/list';
requiresAuth = true;
break;
default:
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `The operation "${operation}" is not known!`);
}
let responseData;
if (requiresAuth) {
const credentials = await this.getCredentials('binance');
const apiKey = credentials.apiKey;
const apiSecret = credentials.apiSecret;
const testnet = credentials.testnet;
const baseURL = testnet ? 'https://testnet.binance.vision' : 'https://api.binance.com';
// Add timestamp for authenticated requests
qs.timestamp = Date.now();
// Add recvWindow to avoid timing issues
if (!qs.recvWindow) {
qs.recvWindow = 5000;
}
// Create signature - preserve parameter order (DO NOT SORT)
const queryString = Object.keys(qs)
.filter(key => qs[key] !== undefined && qs[key] !== null && qs[key] !== '')
.map(key => {
const value = qs[key];
// Handle arrays and objects properly
if (Array.isArray(value)) {
return `${key}=${value.join(',')}`;
}
return `${key}=${String(value)}`;
})
.join('&');
const signature = (0, crypto_1.createHmac)('sha256', apiSecret)
.update(queryString)
.digest('hex');
// Debug logs
console.log('🔍 BINANCE DEBUG:');
console.log(`Endpoint: ${endpoint}`);
console.log(`Method: ${method}`);
console.log(`Base URL: ${baseURL}`);
console.log(`Parameters: ${JSON.stringify(qs)}`);
console.log(`Query String: ${queryString}`);
console.log(`Signature: ${signature}`);
let options;
if (method === 'POST') {
// For POST: put parameters in request body
const bodyData = `${queryString}&signature=${signature}`;
options = {
method,
url: `${baseURL}${endpoint}`,
headers: {
'X-MBX-APIKEY': apiKey,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: bodyData,
json: false, // Don't parse as JSON since we're sending form data
};
console.log(`POST Body: ${bodyData}`);
console.log(`POST Headers: ${JSON.stringify(options.headers)}`);
}
else {
// For GET: put parameters in query string (current behavior)
const finalUrl = `${baseURL}${endpoint}?${queryString}&signature=${signature}`;
options = {
method,
url: finalUrl,
headers: {
'X-MBX-APIKEY': apiKey,
},
json: true,
};
console.log(`GET URL: ${finalUrl}`);
console.log(`GET Headers: ${JSON.stringify(options.headers)}`);
}
responseData = await this.helpers.request(options);
// For POST requests, parse JSON response manually if needed
if (method === 'POST' && typeof responseData === 'string') {
try {
responseData = JSON.parse(responseData);
}
catch (parseError) {
console.log('Failed to parse POST response as JSON:', responseData);
// Keep as string if parsing fails
}
}
}
else {
const baseURL = 'https://api.binance.com';
const options = {
method,
url: `${baseURL}${endpoint}`,
qs,
json: true,
};
responseData = await this.helpers.request(options);
}
// Ensure responseData is properly formatted for n8n
let formattedData;
if (Array.isArray(responseData)) {
formattedData = responseData;
}
else if (responseData && typeof responseData === 'object') {
formattedData = [responseData];
}
else {
// Handle primitive values or unexpected formats
formattedData = [{ data: responseData }];
}
const executionData = this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(formattedData), { itemData: { item: i } });
returnData.push(...executionData);
}
catch (error) {
console.log('🚨 BINANCE ERROR:', error);
if (this.continueOnFail()) {
const executionErrorData = this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray([{ error: error.message }]), { itemData: { item: i } });
returnData.push(...executionErrorData);
continue;
}
throw error;
}
}
return [returnData];
}
}
exports.Binance = Binance;