@faast/tron-payments
Version:
Library to assist in processing tron payments, such as deriving addresses and sweeping funds
226 lines • 8.91 kB
JavaScript
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