UNPKG

@cryptodevops/n8n-nodes-binance

Version:
685 lines (684 loc) 31.9 kB
"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;