@tatumio/tron-connector
Version:
Tron Connector for Tatum API
322 lines (291 loc) • 13.6 kB
text/typescript
import {PinoLogger} from 'nestjs-pino';
import axios from 'axios';
import {
convertAddressFromHex,
CreateTronTrc10,
CreateTronTrc20,
Currency,
FreezeTron,
generateAddressFromXPub,
generatePrivateKeyFromMnemonic,
generateWallet,
prepareTronCreateTrc10SignedKMSTransaction,
prepareTronCreateTrc10SignedTransaction,
prepareTronCreateTrc20SignedKMSTransaction,
prepareTronCreateTrc20SignedTransaction,
prepareTronFreezeKMSTransaction,
prepareTronFreezeTransaction,
prepareTronSignedKMSTransaction,
prepareTronSignedTransaction,
prepareTronTrc10SignedKMSTransaction,
prepareTronTrc10SignedTransaction,
prepareTronTrc20SignedKMSTransaction,
prepareTronTrc20SignedTransaction,
TransferTron,
TransferTronTrc10,
TransferTronTrc20,
TronAccount,
TronBlock,
TronTransaction,
TronTrc10
} from '@tatumio/tatum';
import {Trc20Tx} from './dto/Trc20Tx';
import {TronError} from './TronError';
export abstract class TronService {
private static mapTransaction(t: any): TronTransaction {
if (t.internal_tx_id) {
return {...t, fromAddressBase58: convertAddressFromHex(t.from_address), toAddressBase58: convertAddressFromHex(t.to_address)}
}
const rawData = {...t.raw_data};
rawData.contract = rawData.contract?.map(c => ({
...c,
parameter: {
...c.parameter,
value: {
...c.parameter.value,
ownerAddressBase58: convertAddressFromHex(c.parameter.value.owner_address),
toAddressBase58: convertAddressFromHex(c.parameter.value.to_address),
contractAddressBase58: convertAddressFromHex(c.parameter.value.contract_address),
}
}
})) || [];
return {
ret: t.ret,
signature: t.signature,
blockNumber: t.blockNumber,
txID: t.txID,
netFee: t.net_fee,
netUsage: t.net_usage,
energyFee: t.energy_fee,
energyUsage: t.energy_usage,
energyUsageTotal: t.energy_usage_total,
internalTransactions: t.internal_transactions,
rawData: rawData,
log: t.log,
};
}
private static mapTransaction20(t: any): Trc20Tx {
return {
txID: t.transaction_id,
tokenInfo: t.token_info,
from: t.from,
to: t.to,
type: t.type,
value: t.value,
};
}
private async getNodeAddress(): Promise<string> {
return (await this.isTestnet()) ? 'https://api.shasta.trongrid.io' : 'https://api.trongrid.io';
}
protected constructor(protected readonly logger: PinoLogger) {
}
protected abstract isTestnet(): Promise<boolean>;
protected abstract getNodesUrl(testnet: boolean): Promise<string[]>;
protected abstract getApiKey(): string;
protected abstract getScanningApiKey(): string;
protected abstract storeKMSTransaction(txData: string, currency: string, signatureId: string[], index?: number): Promise<string>;
protected abstract completeKMSTransaction(txId: string, signatureId: string): Promise<void>;
public async broadcast(txData: string, signatureId?: string) {
const transaction = JSON.parse(txData);
if (Date.now() > transaction?.raw_data?.expiration) {
throw new TronError('Transaction expired', 'tron.error');
}
const url = (await this.getNodesUrl(await this.isTestnet()))[0];
const broadcast = (await axios.post(`${url}/wallet/broadcasttransaction`, transaction, {headers: {'TRON-PRO-API-KEY': this.getApiKey()}})).data;
if (broadcast.result) {
if (signatureId) {
try {
await this.completeKMSTransaction(broadcast.txid, signatureId);
} catch (e) {
this.logger.error(e);
return {txId: broadcast.txid, failed: true};
}
}
return {txId: broadcast.txid};
}
let msg = broadcast.message;
try {
msg = Buffer.from(msg, 'hex').toString();
} catch (e) {
}
throw new TronError(`Broadcast failed due to ${msg}`, 'tron.error');
}
public async getBlockChainInfo(testnet?: boolean): Promise<{ testnet: boolean, hash: string, blockNumber: number }> {
let t: boolean;
let apiKey: string;
if (testnet !== undefined) {
t = testnet;
apiKey = this.getScanningApiKey();
} else {
t = await this.isTestnet();
apiKey = this.getApiKey();
}
const urls = await this.getNodesUrl(t);
const block = (await axios.post(urls[0] + '/wallet/getnowblock', undefined, {headers: {'TRON-PRO-API-KEY': apiKey}})).data;
return {testnet: t, hash: block.blockID, blockNumber: block.block_header.raw_data.number};
}
public async getBlock(hashOrHeight: string, testnet?: boolean): Promise<TronBlock> {
let t: boolean;
let apiKey: string;
if (testnet !== undefined) {
t = testnet;
apiKey = this.getScanningApiKey();
} else {
t = await this.isTestnet();
apiKey = this.getApiKey();
}
const url = (await this.getNodesUrl(t))[0];
let block;
if (hashOrHeight.length > 32) {
block = (await axios.post(`${url}/wallet/getblockbyid`, {value: hashOrHeight}, {headers: {'TRON-PRO-API-KEY': apiKey}})).data;
} else {
block = (await axios.post(`${url}/wallet/getblockbynum`, {num: parseInt(hashOrHeight)}, {headers: {'TRON-PRO-API-KEY': apiKey}})).data;
}
return {
blockNumber: block.block_header.raw_data.number,
hash: block.blockID,
parentHash: block.block_header.raw_data.parentHash,
timestamp: block.block_header.raw_data.timestamp,
witnessAddress: block.block_header.raw_data.witness_address,
witnessSignature: block.block_header.witness_signature,
transactions: block.transactions?.map(TronService.mapTransaction) || [],
};
}
public async getTransaction(txId: string, testnet?: boolean): Promise<TronTransaction> {
const url = (await this.getNodesUrl(testnet !== undefined ? testnet : await this.isTestnet()))[0];
let apiKey1, apiKey2: string;
if (testnet !== undefined) {
apiKey1 = this.getScanningApiKey();
apiKey2 = this.getScanningApiKey();
} else {
apiKey1 = this.getApiKey();
apiKey2 = this.getApiKey();
}
const [{data: tx}, {data: info}] = await Promise.all([axios.post(`${url}/wallet/gettransactionbyid`, {value: txId}, {headers: {'TRON-PRO-API-KEY': apiKey1}}),
axios.post(`${url}/wallet/gettransactioninfobyid`, {value: txId}, {headers: {'TRON-PRO-API-KEY': apiKey2}})]);
return TronService.mapTransaction({...tx, ...info.receipt, log: info.log, blockNumber: info.blockNumber});
}
public async getAccount(address: string): Promise<TronAccount> {
const url = await this.getNodeAddress();
const {data} = (await axios.get(`${url}/v1/accounts/${address}`, {headers: {'TRON-PRO-API-KEY': this.getApiKey()}})).data;
if (!data?.length) {
throw new TronError('no such account.', 'tron.error');
}
const account = data[0];
return {
address: account.add,
assetIssuedId: account.asset_issued_ID,
assetIssuedName: account.asset_issued_name,
balance: account.balance,
createTime: account.create_time,
freeNetUsage: account.free_net_usage,
trc10: account.assetV2,
trc20: account.trc20,
};
}
public async getTransactionsByAccount(address: string, next?: string): Promise<{ transactions: TronTransaction[], internalTransactions: TronTransaction[], next?: string }> {
const url = await this.getNodeAddress();
let u = `${url}/v1/accounts/${address}/transactions?limit=200`;
if (next) {
u += '&fingerprint=' + next;
}
const result = (await axios.get(u, {headers: {'TRON-PRO-API-KEY': this.getApiKey()}})).data;
return {
transactions: result.data.map(TronService.mapTransaction).filter(t => !t.internal_tx_id),
internalTransactions: result.data.map(TronService.mapTransaction).filter(t => t.internal_tx_id),
next: result.meta?.fingerprint
};
}
public async getTrc20TransactionsByAccount(address: string, next?: string): Promise<{ transactions: Trc20Tx[], next?: string }> {
const url = await this.getNodeAddress();
let u = `${url}/v1/accounts/${address}/transactions/trc20?limit=200`;
if (next) {
u += '&fingerprint=' + next;
}
const result = (await axios.get(u, {headers: {'TRON-PRO-API-KEY': this.getApiKey()}})).data;
return {
transactions: result.data.map(TronService.mapTransaction20),
next: result.meta?.fingerprint
};
}
public async generateWallet(mnem?: string) {
return generateWallet(Currency.TRON, await this.isTestnet(), mnem);
}
public async generateAddress(xpub: string, i: number) {
return {address: await generateAddressFromXPub(Currency.TRON, await this.isTestnet(), xpub, i)};
}
public async generatePrivateKey(mnemonic: string, i: number) {
return {key: await generatePrivateKeyFromMnemonic(Currency.TRON, await this.isTestnet(), mnemonic, i)};
}
public async sendTransaction(body: TransferTron) {
const t = await this.isTestnet();
if (body.signatureId) {
const txData = await prepareTronSignedKMSTransaction(t, body, (await this.getNodesUrl(t))[0]);
return {signatureId: await this.storeKMSTransaction(txData, Currency.TRON, [body.signatureId], body.index)};
} else {
return this.broadcast(await prepareTronSignedTransaction(t, body, (await this.getNodesUrl(t))[0]));
}
}
public async sendTrc10Transaction(body: TransferTronTrc10) {
const t = await this.isTestnet();
if (body.signatureId) {
const txData = await prepareTronTrc10SignedKMSTransaction(t, body, undefined, (await this.getNodesUrl(t))[0]);
return {signatureId: await this.storeKMSTransaction(txData, Currency.TRON, [body.signatureId], body.index)};
} else {
return this.broadcast(await prepareTronTrc10SignedTransaction(t, body, undefined, (await this.getNodesUrl(t))[0]));
}
}
public async sendTrc20Transaction(body: TransferTronTrc20) {
const t = await this.isTestnet();
if (body.signatureId) {
const txData = await prepareTronTrc20SignedKMSTransaction(t, body, (await this.getNodesUrl(t))[0]);
return {signatureId: await this.storeKMSTransaction(txData, Currency.TRON, [body.signatureId], body.index)};
} else {
return this.broadcast(await prepareTronTrc20SignedTransaction(t, body, (await this.getNodesUrl(t))[0]));
}
}
public async createTrc10Transaction(body: CreateTronTrc10) {
const t = await this.isTestnet();
if (body.signatureId) {
const txData = await prepareTronCreateTrc10SignedKMSTransaction(t, body, (await this.getNodesUrl(t))[0]);
return {signatureId: await this.storeKMSTransaction(txData, Currency.TRON, [body.signatureId], body.index)};
} else {
return this.broadcast(await prepareTronCreateTrc10SignedTransaction(t, body, (await this.getNodesUrl(t))[0]));
}
}
public async createTrc20Transaction(body: CreateTronTrc20) {
const t = await this.isTestnet();
if (body.signatureId) {
const txData = await prepareTronCreateTrc20SignedKMSTransaction(t, body, (await this.getNodesUrl(t))[0]);
return {signatureId: await this.storeKMSTransaction(txData, Currency.TRON, [body.signatureId], body.index)};
} else {
return this.broadcast(await prepareTronCreateTrc20SignedTransaction(t, body, (await this.getNodesUrl(t))[0]));
}
}
public async freezeBalance(body: FreezeTron) {
const t = await this.isTestnet();
if (body.signatureId) {
const txData = await prepareTronFreezeKMSTransaction(t, body, (await this.getNodesUrl(t))[0]);
return {signatureId: await this.storeKMSTransaction(txData, Currency.TRON, [body.signatureId], body.index)};
} else {
return this.broadcast(await prepareTronFreezeTransaction(t, body, (await this.getNodesUrl(t))[0]));
}
}
public async getTrc10Detail(id: string): Promise<TronTrc10> {
const url = `${(await this.getNodeAddress())}/v1/assets/${id}`;
const {data} = (await axios.get(url, {headers: {'TRON-PRO-API-KEY': this.getApiKey()}})).data;
if (!data?.length) {
throw new TronError('No such asset.', 'tron.error');
}
return {
abbr: data[0].abbr,
description: data[0].description,
id: data[0].id,
name: data[0].name,
ownerAddress: data[0].owner_address,
precision: data[0].precision,
totalSupply: data[0].total_supply,
url: data[0].url,
};
}
}