UNPKG

@faast/tron-payments

Version:

Library to assist in processing tron payments, such as deriving addresses and sweeping funds

274 lines 11.1 kB
import TronWeb from 'tronweb'; import { pick, get, cloneDeep } from 'lodash'; import { TransactionStatus, FeeLevel, FeeRateType, FeeOptionCustom, } from '@faast/payments-common'; import { isType, DelegateLogger } from '@faast/ts-common'; import { toBaseDenominationNumber, isValidAddress, isValidPayport } from './helpers'; import { toError } from './utils'; import { DEFAULT_FULL_NODE, DEFAULT_EVENT_SERVER, DEFAULT_SOLIDITY_NODE, MIN_BALANCE_SUN, MIN_BALANCE_TRX, PACKAGE_NAME, DEFAULT_FEE_LEVEL, } from './constants'; import { TronPaymentsUtils } from './TronPaymentsUtils'; export class BaseTronPayments extends TronPaymentsUtils { constructor(config) { super(config); this.fullNode = config.fullNode || DEFAULT_FULL_NODE; this.solidityNode = config.solidityNode || DEFAULT_SOLIDITY_NODE; this.eventServer = config.eventServer || DEFAULT_EVENT_SERVER; this.logger = new DelegateLogger(config.logger, PACKAGE_NAME); this.tronweb = new TronWeb(this.fullNode, this.solidityNode, this.eventServer); } requiresBalanceMonitor() { return false; } async getBalance(resolveablePayport) { try { const payport = await this.resolvePayport(resolveablePayport); const balanceSun = await this.tronweb.trx.getBalance(payport.address); this.logger.debug(`trx.getBalance(${payport.address}) -> ${balanceSun}`); const sweepable = this.canSweepBalance(balanceSun); return { confirmedBalance: this.toMainDenomination(balanceSun).toString(), unconfirmedBalance: '0', sweepable, }; } catch (e) { throw toError(e); } } async resolveFeeOption(feeOption) { let targetFeeLevel; if (isType(FeeOptionCustom, feeOption)) { if (feeOption.feeRate !== '0') { throw new Error('tron-payments custom fees are unsupported'); } targetFeeLevel = FeeLevel.Custom; } else { targetFeeLevel = feeOption.feeLevel || DEFAULT_FEE_LEVEL; } return { targetFeeLevel, targetFeeRate: '0', targetFeeRateType: FeeRateType.Base, feeBase: '0', feeMain: '0', }; } async createSweepTransaction(from, to, options = {}) { this.logger.debug('createSweepTransaction', from, to); try { const { fromAddress, fromIndex, fromPayport, toAddress, toIndex } = await this.resolveFromTo(from, to); const { targetFeeLevel, targetFeeRate, targetFeeRateType, feeBase, feeMain } = await this.resolveFeeOption(options); const feeSun = Number.parseInt(feeBase); const { confirmedBalance: balanceTrx } = await this.getBalance(fromPayport); const balanceSun = toBaseDenominationNumber(balanceTrx); if (!this.canSweepBalance(balanceSun)) { throw new Error(`Insufficient balance (${balanceTrx}) to sweep with fee of ${feeMain} ` + `while maintaining a minimum required balance of ${MIN_BALANCE_TRX}`); } const amountSun = balanceSun - feeSun - MIN_BALANCE_SUN; const amountTrx = this.toMainDenomination(amountSun); const tx = await this.tronweb.transactionBuilder.sendTrx(toAddress, amountSun, fromAddress); return { id: tx.txID, fromAddress, toAddress, toExtraId: null, fromIndex, toIndex, amount: amountTrx, fee: feeMain, targetFeeLevel, targetFeeRate, targetFeeRateType, status: TransactionStatus.Unsigned, data: tx, }; } catch (e) { throw toError(e); } } async createTransaction(from, to, amountTrx, options = {}) { this.logger.debug('createTransaction', from, to, amountTrx); try { const { fromAddress, fromIndex, fromPayport, toAddress, toIndex } = await this.resolveFromTo(from, to); const { targetFeeLevel, targetFeeRate, targetFeeRateType, feeBase, feeMain } = await this.resolveFeeOption(options); const feeSun = Number.parseInt(feeBase); const { confirmedBalance: balanceTrx } = await this.getBalance(fromPayport); const balanceSun = toBaseDenominationNumber(balanceTrx); const amountSun = toBaseDenominationNumber(amountTrx); if (balanceSun - feeSun - MIN_BALANCE_SUN < amountSun) { throw new Error(`Insufficient balance (${balanceTrx}) to send ${amountTrx} including fee of ${feeMain} ` + `while maintaining a minimum required balance of ${MIN_BALANCE_TRX}`); } const tx = await this.tronweb.transactionBuilder.sendTrx(toAddress, amountSun, fromAddress); return { id: tx.txID, fromAddress, toAddress, toExtraId: null, fromIndex, toIndex, amount: amountTrx, fee: feeMain, targetFeeLevel, targetFeeRate, targetFeeRateType, status: TransactionStatus.Unsigned, data: tx, }; } catch (e) { throw toError(e); } } async signTransaction(unsignedTx) { try { const fromPrivateKey = await this.getPrivateKey(unsignedTx.fromIndex); const unsignedRaw = cloneDeep(unsignedTx.data); const signedTx = await this.tronweb.trx.sign(unsignedRaw, fromPrivateKey); return { ...unsignedTx, status: TransactionStatus.Signed, data: signedTx, }; } catch (e) { throw toError(e); } } async broadcastTransaction(tx) { try { const status = await this.tronweb.trx.sendRawTransaction(tx.data); let success = false; let rebroadcast = false; if (status.result || status.code === 'SUCCESS') { success = true; } else { try { await this.tronweb.trx.getTransaction(tx.id); success = true; rebroadcast = true; } catch (e) { } } if (success) { return { id: tx.id, rebroadcast, }; } else { let statusCode = status.code; if (statusCode === 'DUP_TRANSACTION_ERROR') { statusCode = 'DUP_TX_BUT_TX_NOT_FOUND_SO_PROBABLY_INVALID_TX_ERROR'; } this.logger.warn(`Tron broadcast tx unsuccessful ${tx.id}`, status); throw new Error(`Failed to broadcast transaction: ${statusCode} ${status.message}`); } } catch (e) { throw toError(e); } } async getTransactionInfo(txid) { try { const [tx, txInfo, currentBlock] = await Promise.all([ this.tronweb.trx.getTransaction(txid), this.tronweb.trx.getTransactionInfo(txid), this.tronweb.trx.getCurrentBlock(), ]); const { amountTrx, fromAddress, toAddress } = this.extractTxFields(tx); const contractRet = get(tx, 'ret[0].contractRet'); const isExecuted = contractRet === 'SUCCESS'; const block = txInfo.blockNumber || null; const feeTrx = this.toMainDenomination(txInfo.fee || 0); const currentBlockNumber = get(currentBlock, 'block_header.raw_data.number', 0); const confirmations = currentBlockNumber && block ? currentBlockNumber - block : 0; const isConfirmed = confirmations > 0; const confirmationTimestamp = txInfo.blockTimeStamp ? new Date(txInfo.blockTimeStamp) : null; let status = TransactionStatus.Pending; if (isConfirmed) { if (!isExecuted) { status = TransactionStatus.Failed; } status = TransactionStatus.Confirmed; } return { id: tx.txID, amount: amountTrx, toAddress, fromAddress, toExtraId: null, fromIndex: null, toIndex: null, fee: feeTrx, isExecuted, isConfirmed, confirmations, confirmationId: block ? String(block) : null, confirmationTimestamp, status, data: { ...tx, ...txInfo, currentBlock: pick(currentBlock, 'block_header', 'blockID'), }, }; } catch (e) { throw toError(e); } } canSweepBalance(balanceSun) { return balanceSun > MIN_BALANCE_SUN; } extractTxFields(tx) { const contractParam = get(tx, 'raw_data.contract[0].parameter.value'); if (!(contractParam && typeof contractParam.amount === 'number')) { throw new Error('Unable to get transaction'); } const amountSun = contractParam.amount || 0; const amountTrx = this.toMainDenomination(amountSun); const toAddress = this.tronweb.address.fromHex(contractParam.to_address); const fromAddress = this.tronweb.address.fromHex(contractParam.owner_address); return { amountTrx, amountSun, toAddress, fromAddress, }; } async resolvePayport(payport) { if (typeof payport === 'number') { return this.getPayport(payport); } else if (typeof payport === 'string') { if (!isValidAddress(payport)) { throw new Error(`Invalid TRON address: ${payport}`); } return { address: payport }; } if (!isValidPayport(payport)) { throw new Error(`Invalid TRON payport: ${JSON.stringify(payport)}`); } return payport; } async resolveFromTo(from, to) { const fromPayport = await this.getPayport(from); const toPayport = await this.resolvePayport(to); return { fromAddress: fromPayport.address, fromIndex: from, fromExtraId: fromPayport.extraId, fromPayport, toAddress: toPayport.address, toIndex: typeof to === 'number' ? to : null, toExtraId: toPayport.extraId, toPayport, }; } } export default BaseTronPayments; //# sourceMappingURL=BaseTronPayments.js.map