UNPKG

@qso-soft/shared

Version:

Shared library for QSO-soft

229 lines 8.87 kB
import { createHmac } from 'crypto'; import axios from 'axios'; import { okx } from 'ccxt'; import { AMOUNT_IS_TOO_LOW_ERROR, OKX_WL_ERROR } from '../../constants'; import { getRandomNumber, prepareProxy, retry, sleep } from '../../helpers'; import { OKX_FEE_NETWORK_MAP, OKX_NETWORK_MAP } from '../../modules/okx-withdraw/constants'; const ON_CHAIN = '4'; const INTERNAL_TRANSFER = '6'; const OKX_DOMAIN = 'https://www.okx.com'; export class Okx { static { this.instance = null; } constructor({ logger, random, amount, hideExtraLogs }) { this.logger = logger; this.secrets = this.getAccountOKX(); this.random = random; this.amount = amount; this.hideExtraLogs = hideExtraLogs || false; this.okxController = this.setOkxController(); } static getInstance(props) { if (!Okx.instance) { Okx.instance = new Okx(props); } return Okx.instance; } getChainName(token, network) { return `${token}-${network}`; } getWithdrawAmount() { if (this.amount) { return `${this.amount}`; } if (!this.random) { return ''; } const [randomFrom, randomTo] = this.random.withdrawToAmount; return getRandomNumber([randomFrom, randomTo]).toFixed(5); } setOkxController() { const okxController = new okx({ ...this.secrets, enableRateLimit: true, }); const proxy = QsoGlobal.OKX.proxy; if (proxy && QsoGlobal.settings.useProxy) { const proxyObject = prepareProxy({ proxy, proxy_type: 'HTTP', }); if (proxyObject) { okxController.https_proxy = proxyObject.url; } } return okxController; } async authGuard() { try { this.okxController.checkRequiredCredentials(); } catch (error) { const e = error; this.logger?.info(e.message, { action: 'authGuard', status: 'failed', }); throw new Error(e.message); } } async checkNetConnection(token, network, sleepTime = 300000) { let isAvailable = false; while (!isAvailable) { const networkInfo = await this.okxController.fetchCurrencies(); isAvailable = networkInfo?.[token]?.networks?.[network]?.info.canWd; if (!isAvailable) { this.logger?.error(`Withdraw is unavailable for this moment. Next attempt will be in ${sleepTime / 60} minutes`, { action: 'checkNetConnection', status: 'failed', }); await sleep(sleepTime); } } } async getWithdrawFee(token, network, inputNetwork) { try { const feesData = (await this.okxController.fetchDepositWithdrawFees([token])); const tokenNetworks = feesData[token]?.networks; const feeNetwork = OKX_FEE_NETWORK_MAP[inputNetwork]; const feeInfo = tokenNetworks?.[feeNetwork] || tokenNetworks?.[network] || tokenNetworks?.[token]; if (!feeInfo) { throw new Error('No fee info'); } return feeInfo.withdraw.fee; } catch (error) { const withdrawFees = this.random?.withdrawFees; if (!withdrawFees) { this.logger?.info(`${error}. withdrawFees can not be empty.`, { action: 'getWithdrawFee', status: 'failed', }); } this.logger?.info(`${error}. Script will use withdraw fee from config - ${withdrawFees}.`, { action: 'getWithdrawFee', status: 'in progress', }); return withdrawFees; } } async execWithdraw({ walletAddress, token, network, minAmount }) { const logTemplate = { action: 'execWithdraw', status: 'in progress', }; const lowAmountErrMessage = 'Withdrawal amount is lower than the lower limit'; try { await this.authGuard(); const okxNetwork = OKX_NETWORK_MAP[network]; const chainName = this.getChainName(token, okxNetwork); const withdrawFee = await this.getWithdrawFee(token, okxNetwork, network); const amount = `${+this.getWithdrawAmount() + withdrawFee}`; if (minAmount && +amount < minAmount) { throw new Error(lowAmountErrMessage); } this.logger?.info(`Withdrawing ${amount} ${token} in ${chainName}`, logTemplate); await retry({ callback: () => this.okxController.withdraw(token, amount, walletAddress, { toAddress: walletAddress, chainName, dest: ON_CHAIN, fee: withdrawFee, pwd: '-', amt: amount, network: okxNetwork, }), baseDelayMs: 5, maxAttempts: 1, }); this.logger?.success(`${amount} ${token} were send. We are waiting for the withdrawal from OKX, relax...`, { ...logTemplate, status: 'succeeded', }); } catch (e) { const errorMessage = e?.message ?? 'unknown error'; const whiteListError = errorMessage.includes('address is not allowlisted'); if (whiteListError) { throw new Error(OKX_WL_ERROR); } if (errorMessage.includes(lowAmountErrMessage)) { throw new Error(AMOUNT_IS_TOO_LOW_ERROR); } throw new Error(errorMessage); } } getAccountOKX() { const accountSecret = QsoGlobal.OKX.accounts[QsoGlobal.OKX.accountName]; if (!accountSecret) { throw new Error('OKX account was not found'); } return accountSecret; } getSignature({ timeStamp, method, requestPath, body = '' }) { const message = timeStamp + method.toUpperCase() + requestPath + body; const hmac = createHmac('sha256', this.secrets.secret); hmac.update(message); return hmac.digest('base64'); } getAuthHeaders({ requestPath, method = 'GET', body = '' }) { const timeStamp = new Date().toISOString(); return { 'OK-ACCESS-TIMESTAMP': timeStamp, 'OK-ACCESS-KEY': this.secrets.apiKey, 'OK-ACCESS-SIGN': this.getSignature({ timeStamp, method, requestPath, body }), 'OK-ACCESS-PASSPHRASE': this.secrets.password, }; } async transferBalanceFromSubToMain({ symbol, amount, subAccName }) { const requestPath = '/api/v5/asset/transfer'; const body = { ccy: symbol, amt: amount, from: INTERNAL_TRANSFER, to: INTERNAL_TRANSFER, type: '2', subAcct: subAccName, }; const headers = this.getAuthHeaders({ requestPath, method: 'POST', body: JSON.stringify(body) }); const response = await axios.post(`${OKX_DOMAIN}${requestPath}`, body, { headers, }); const { amt, ccy } = response.data.data[0]; this.logger?.success(`${amt} ${ccy} has been sent from sub-account [${subAccName}] to main account`, { status: 'succeeded', }); } async getSubAccountBalaces(subAccName) { const requestPath = `/api/v5/asset/subaccount/balances?subAcct=${subAccName}`; const response = await axios.get(`${OKX_DOMAIN}${requestPath}`, { headers: this.getAuthHeaders({ requestPath }), }); return response.data.data; } async transferFromSubAccs() { const subAccounts = await this.okxController.privateGetUsersSubaccountList(); if (!subAccounts.data) { throw new Error('We can not get sub accounts from OKX'); } if (subAccounts.data.length === 0) { return; } for (const { label: subAccName } of subAccounts.data) { try { const balances = await this.getSubAccountBalaces(subAccName); if (!balances.length) { continue; } for (const { availBal, ccy } of balances) { await this.transferBalanceFromSubToMain({ amount: availBal, subAccName, symbol: ccy }); await sleep(1); } } catch (error) { const err = error; this.logger?.error(`${err.message}`); } } } } //# sourceMappingURL=okx.js.map