UNPKG

@faast/tron-payments

Version:

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

242 lines (206 loc) 7.24 kB
import { PaymentsUtils, NetworkType, Payport, AutoFeeLevels, FeeRate, FeeRateType, BalanceResult, TransactionStatus } from '@faast/payments-common' import { Logger, DelegateLogger, isNil, assertType, Numeric } from '@faast/ts-common' import TronWeb, { Transaction as TronTransaction } from 'tronweb' import { toMainDenominationString, toBaseDenominationString, isValidXprv, isValidXpub, isValidAddress, isValidExtraId, isValidPrivateKey, privateKeyToAddress, toMainDenominationBigNumber, } from './helpers' import { COIN_NAME, COIN_SYMBOL, DECIMAL_PLACES, DEFAULT_EVENT_SERVER, DEFAULT_FULL_NODE, DEFAULT_SOLIDITY_NODE, MIN_BALANCE_SUN, MIN_BALANCE_TRX, PACKAGE_NAME, } from './constants' import { BaseTronPaymentsConfig, TronTransactionInfo } from './types' import { retryIfDisconnected, toError } from './utils' import BigNumber from 'bignumber.js' import { pick } from 'lodash' export class TronPaymentsUtils implements PaymentsUtils { readonly coinSymbol = COIN_SYMBOL readonly coinName = COIN_NAME readonly coinDecimals = DECIMAL_PLACES readonly networkType: NetworkType logger: Logger fullNode: string solidityNode: string eventServer: string tronweb: TronWeb constructor(config: BaseTronPaymentsConfig = {}) { assertType(BaseTronPaymentsConfig, config) this.networkType = config.network || NetworkType.Mainnet this.logger = new DelegateLogger(config.logger, PACKAGE_NAME) this.fullNode = config.fullNode || DEFAULT_FULL_NODE this.solidityNode = config.solidityNode || DEFAULT_SOLIDITY_NODE this.eventServer = config.eventServer || DEFAULT_EVENT_SERVER this.tronweb = new TronWeb(this.fullNode, this.solidityNode, this.eventServer) } async init() {} async destroy() {} isValidExtraId(extraId: string): boolean { return isValidExtraId(extraId) } isValidAddress(address: string): boolean { return isValidAddress(address) } standardizeAddress(address: string): string | null { if (!isValidAddress(address)) { return null } return address } private async _getPayportValidationMessage(payport: Payport): Promise<string | undefined> { const { address, extraId } = payport if (!isValidAddress(address)) { return 'Invalid payport address' } if (!isNil(extraId) && !isValidExtraId(extraId)) { return 'Invalid payport extraId' } } async getPayportValidationMessage(payport: Payport): Promise<string | undefined> { try { payport = assertType(Payport, payport, 'payport') } catch (e) { return e.message } return this._getPayportValidationMessage(payport) } async validatePayport(payport: Payport): Promise<void> { payport = assertType(Payport, payport, 'payport') const message = await this._getPayportValidationMessage(payport) if (message) { throw new Error(message) } } async isValidPayport(payport: Payport): Promise<boolean> { return Payport.is(payport) && !(await this._getPayportValidationMessage(payport)) } toMainDenomination(amount: string | number): string { return toMainDenominationString(amount) } toBaseDenomination(amount: string | number): string { return toBaseDenominationString(amount) } isValidXprv = isValidXprv isValidXpub = isValidXpub isValidPrivateKey = isValidPrivateKey privateKeyToAddress = privateKeyToAddress getFeeRateRecommendation(level: AutoFeeLevels): FeeRate { return { feeRate: '0', feeRateType: FeeRateType.Base } } async _retryDced<T>(fn: () => Promise<T>): Promise<T> { return retryIfDisconnected(fn, this.logger) } getCurrentBlockNumber() { return this._retryDced(async () => (await this.tronweb.trx.getCurrentBlock()).block_header.raw_data.number) } async getAddressUtxos() { return [] } async getAddressNextSequenceNumber() { return null } protected canSweepBalanceSun(balanceSun: number): boolean { return balanceSun > MIN_BALANCE_SUN } isAddressBalanceSweepable(balanceTrx: Numeric): boolean { return new BigNumber(balanceTrx).gt(MIN_BALANCE_TRX) } async getAddressBalance(address: string): Promise<BalanceResult> { try { const balanceSun = await this._retryDced(() => this.tronweb.trx.getBalance(address)) const sweepable = this.canSweepBalanceSun(balanceSun) const confirmedBalance = toMainDenominationBigNumber(balanceSun) const spendableBalance = BigNumber.max(0, confirmedBalance.minus(MIN_BALANCE_TRX)) return { confirmedBalance: confirmedBalance.toString(), unconfirmedBalance: '0', spendableBalance: spendableBalance.toString(), sweepable, requiresActivation: false, minimumBalance: String(MIN_BALANCE_TRX), } } catch (e) { throw toError(e) } } private extractTxFields(tx: TronTransaction) { const contractParam = tx.raw_data?.contract?.[0]?.parameter?.value ?? null 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 getTransactionInfo(txid: string): Promise<TronTransactionInfo> { try { const [tx, txInfo, currentBlock] = await Promise.all([ this._retryDced(() => this.tronweb.trx.getTransaction(txid)), this._retryDced(() => this.tronweb.trx.getTransactionInfo(txid)), this._retryDced(() => this.tronweb.trx.getCurrentBlock()), ]) const { amountTrx, fromAddress, toAddress } = this.extractTxFields(tx) const contractRet = tx.ret?.[0]?.contractRet const isExecuted = contractRet === 'SUCCESS' const block = txInfo.blockNumber || null const feeTrx = this.toMainDenomination(txInfo.fee || 0) const currentBlockNumber = 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 = 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, sequenceNumber: null, isExecuted, isConfirmed, confirmations, confirmationId: block ? String(block) : null, confirmationTimestamp, status, data: { ...tx, ...txInfo, currentBlock: pick(currentBlock, 'block_header', 'blockID'), }, } } catch (e) { throw toError(e) } } }