PHP WebShell
Текущая директория: /opt/BitGoJS/modules/sdk-coin-trx/src/lib
Просмотр файла: transaction.ts
import { createHash } from 'crypto';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import BigNumber from 'bignumber.js';
import { protocol } from '../../resources/protobuf/tron';
import {
BaseKey,
BaseTransaction,
ExtendTransactionError,
ParseTransactionError,
TransactionType,
} from '@bitgo/sdk-core';
import { ContractType } from './enum';
import {
decodeTransaction,
decodeDataParams,
getBase58AddressFromHex,
tokenMainnetContractAddresses,
tokenTestnetContractAddresses,
} from './utils';
import { ContractEntry, RawData, TransactionReceipt, TransferContract, TriggerSmartContract } from './iface';
/**
* Tron transaction model.
*/
export class Transaction extends BaseTransaction {
// Tron specific fields
protected _validFrom: number;
protected _validTo: number;
protected _inputs: ContractEntry[];
protected _outputs: ContractEntry[];
private _decodedRawDataHex: RawData;
private _transaction?: TransactionReceipt;
/**
* Public constructor.
*
* @param coinConfig
* @param rawTransaction
*/
constructor(coinConfig: Readonly<CoinConfig>, rawTransaction?: TransactionReceipt) {
super(coinConfig);
if (rawTransaction) {
if (!rawTransaction.txID) {
throw new ParseTransactionError('Transaction has no id');
}
this._id = rawTransaction.txID;
this._transaction = rawTransaction;
this._decodedRawDataHex = decodeTransaction(rawTransaction.raw_data_hex);
// Destination depends on the contract type
this.recordRawDataFields(this._decodedRawDataHex);
}
}
/**
* Parse the transaction raw data and record the most important fields.
*
* @param rawData Object from a tron transaction
*/
private recordRawDataFields(rawData: RawData) {
// Contract-agnostic fields
this._validFrom = rawData.timestamp;
this._validTo = rawData.expiration;
let output: ContractEntry, input: ContractEntry;
// Contract-specific fields
switch (rawData.contractType) {
case ContractType.Transfer:
this._type = TransactionType.Send;
const value = new BigNumber((rawData.contract[0] as TransferContract).parameter.value.amount).toFixed(0);
output = {
address: (rawData.contract[0] as TransferContract).parameter.value.to_address,
value,
};
input = {
address: (rawData.contract[0] as TransferContract).parameter.value.owner_address,
value,
};
break;
case ContractType.AccountPermissionUpdate:
this._type = TransactionType.WalletInitialization;
output = {
address: (rawData.contract as any).owner_address,
value: '0',
};
input = {
address: (rawData.contract as any).owner_address,
value: '0',
};
break;
case ContractType.TriggerSmartContract:
this._type = TransactionType.ContractCall;
const contractCallValues = (rawData.contract[0] as TriggerSmartContract).parameter.value;
const contractAddress = contractCallValues.contract_address;
if (
tokenMainnetContractAddresses.includes(contractAddress) ||
tokenTestnetContractAddresses.includes(contractAddress)
) {
// this is then a token smart contract transaction and the data must be decoded
const types = ['address', 'uint256'];
const data = Buffer.from(contractCallValues.data, 'base64').toString('hex');
const decodedData = decodeDataParams(types, data);
const recipient_address = getBase58AddressFromHex(decodedData[0]);
const value = decodedData[1].toString();
output = {
address: recipient_address,
value,
};
input = {
address: contractCallValues.owner_address,
contractAddress,
data,
value,
};
break;
}
output = {
address: contractCallValues.owner_address,
value: '0',
};
input = {
address: contractCallValues.owner_address,
contractAddress,
data: contractCallValues.data,
value: '0',
};
break;
default:
throw new ParseTransactionError('Unsupported contract type');
}
this._inputs = [input];
this._outputs = [output];
}
/**
* Recalculate and update the transaction id. This should be done after changing any transaction
* field since the the id is a hash of the transaction body.
*/
private updateId(): void {
if (!this._transaction) {
throw new ParseTransactionError('Empty transaction');
}
const hexBuffer = Buffer.from(this._transaction.raw_data_hex, 'hex');
const newTxid = createHash('sha256').update(hexBuffer).digest('hex');
this._transaction.txID = newTxid;
this._id = newTxid;
}
/**
* Extend the expiration date by the given number of milliseconds.
*
* @param extensionMs The number of milliseconds to extend the expiration by
*/
extendExpiration(extensionMs: number): void {
if (extensionMs < 0) {
throw new ExtendTransactionError('Invalid extension range. Must be positive a integer');
}
if (!this._transaction) {
throw new ExtendTransactionError('Empty transaction');
}
if (this._transaction.signature && this._transaction.signature.length > 0) {
throw new ExtendTransactionError('Cannot extend a signed transaction');
}
const rawDataHex = this._transaction.raw_data_hex;
const bytes = Buffer.from(rawDataHex, 'hex');
let raw;
try {
raw = protocol.Transaction.raw.decode(bytes);
const newExpiration = new BigNumber(raw.expiration).plus(extensionMs).toNumber();
raw.expiration = newExpiration;
const newRawDataHex = Buffer.from(protocol.Transaction.raw.encode(raw).finish()).toString('hex');
// Set the internal variables to account for the new expiration date
this._transaction.raw_data_hex = newRawDataHex;
this._transaction.raw_data.expiration = newExpiration;
this._decodedRawDataHex = decodeTransaction(newRawDataHex);
this.recordRawDataFields(this._decodedRawDataHex);
this.updateId();
} catch (e) {
throw new ExtendTransactionError('There was an error decoding the initial raw_data_hex from the serialized tx.');
}
}
/**
* Get the signatures associated with this transaction.
*/
get signature(): string[] {
if (this._transaction && this._transaction.signature) {
return this._transaction.signature;
}
return [];
}
/**
* Get the time in milliseconds this transaction becomes valid and can be broadcasted to the
* network.
*/
get validFrom(): number {
return this._validFrom;
}
/**
* Get the expiration time in milliseconds.
*/
get validTo(): number {
return this._validTo;
}
/** @inheritdoc */
get outputs(): ContractEntry[] {
return this._outputs;
}
/** @inheritdoc */
get inputs(): ContractEntry[] {
return this._inputs;
}
/** @inheritdoc */
canSign(key: BaseKey): boolean {
// Tron transaction do not contain the owners account address so it is not possible to check the
// private key with any but the account main address. This is not enough to fail this check, so
// it is a no-op.
return true;
}
/**
* Sets this transaction
*
* @param {Transaction} tx transaction
*/
setTransactionReceipt(tx: TransactionReceipt) {
this._transaction = tx;
this.updateId();
}
/**
* Set the transaction type
*
* @param {TransactionType} transactionType The transaction type to be set
*/
setTransactionType(transactionType: TransactionType): void {
this._type = transactionType;
}
/** @inheritdoc */
toJson(): TransactionReceipt {
if (!this._transaction) {
throw new ParseTransactionError('Empty transaction');
}
return this._transaction;
}
/** @inheritdoc */
toBroadcastFormat(): any {
return JSON.stringify(this.toJson());
}
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!