UNPKG

@faast/tron-payments

Version:

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

226 lines 8.91 kB
import { cloneDeep } from 'lodash'; import { TransactionStatus, FeeLevel, FeeRateType, FeeOptionCustom, PaymentsError, PaymentsErrorCode, } from '@faast/payments-common'; import { isType } from '@faast/ts-common'; import { toBaseDenominationNumber, isValidAddress } from './helpers'; import { toError } from './utils'; import { MIN_BALANCE_SUN, MIN_BALANCE_TRX, DEFAULT_FEE_LEVEL, EXPIRATION_FUDGE_MS, TX_EXPIRATION_EXTENSION_SECONDS, } from './constants'; import { TronPaymentsUtils } from './TronPaymentsUtils'; export class BaseTronPayments extends TronPaymentsUtils { constructor(config) { super(config); } async init() { } async destroy() { } requiresBalanceMonitor() { return false; } async getBalance(resolveablePayport) { const payport = await this.resolvePayport(resolveablePayport); return this.getAddressBalance(payport.address); } 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 buildUnsignedTx(toAddress, amountSun, fromAddress) { let tx = await this.tronweb.transactionBuilder.sendTrx(toAddress, amountSun, fromAddress); tx = await this.tronweb.transactionBuilder.extendExpiration(tx, TX_EXPIRATION_EXTENSION_SECONDS); return tx; } async createServiceTransaction() { return null; } 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.canSweepBalanceSun(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.buildUnsignedTx(toAddress, amountSun, fromAddress); return { status: TransactionStatus.Unsigned, id: tx.txID, fromAddress, toAddress, toExtraId: null, fromIndex, toIndex, amount: amountTrx, fee: feeMain, targetFeeLevel, targetFeeRate, targetFeeRateType, sequenceNumber: null, 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.buildUnsignedTx(toAddress, amountSun, fromAddress); return { status: TransactionStatus.Unsigned, id: tx.txID, fromAddress, toAddress, toExtraId: null, fromIndex, toIndex, amount: amountTrx, fee: feeMain, targetFeeLevel, targetFeeRate, targetFeeRateType, sequenceNumber: null, 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._retryDced(() => this.tronweb.trx.sendRawTransaction(tx.data)); let success = false; let rebroadcast = false; if (status.result || status.code === 'SUCCESS') { success = true; } else { try { await this._retryDced(() => this.tronweb.trx.getTransaction(tx.id)); success = true; rebroadcast = true; } catch (e) { const expiration = tx.data && tx.data.raw_data.expiration; if (expiration && Date.now() > expiration + EXPIRATION_FUDGE_MS) { throw new PaymentsError(PaymentsErrorCode.TxExpired, 'Transaction has expired'); } } } if (success) { return { id: tx.id, rebroadcast, }; } else { let statusCode = status.code; if (statusCode === 'TRANSACTION_EXPIRATION_ERROR') { throw new PaymentsError(PaymentsErrorCode.TxExpired, `${statusCode} ${status.message || ''}`); } 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); } } isSweepableBalance(balanceTrx) { return this.isAddressBalanceSweepable(balanceTrx); } usesSequenceNumber() { return false; } async getNextSequenceNumber() { return null; } usesUtxos() { return false; } async getUtxos() { return []; } 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 (!this.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, }; } async createMultiOutputTransaction(from, to, options = {}) { return null; } } export default BaseTronPayments; //# sourceMappingURL=BaseTronPayments.js.map