@ledgerhq/coin-tron
Version:
Ledger Tron Coin integration
449 lines • 20.4 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.claimRewardTronTransaction = exports.getUnwithdrawnReward = exports.voteTronSuperRepresentatives = exports.getTronSuperRepresentativeData = exports.getNextVotingDate = exports.hydrateSuperRepresentatives = exports.getTronSuperRepresentatives = exports.getBrokerage = exports.getAccountName = exports.validateAddress = exports.getTronAccountNetwork = exports.fetchTronContract = exports.getContractUserEnergyRatioConsumption = exports.broadcastHexTron = exports.broadcastTron = exports.createTronTransaction = exports.legacyUnfreezeTronTransaction = exports.unDelegateResourceTransaction = exports.withdrawExpireUnfreezeTronTransaction = exports.unfreezeTronTransaction = exports.freezeTronTransaction = void 0;
exports.post = post;
exports.getDelegatedResource = getDelegatedResource;
exports.craftTrc20Transaction = craftTrc20Transaction;
exports.craftStandardTransaction = craftStandardTransaction;
exports.fetchTronAccount = fetchTronAccount;
exports.getLastBlock = getLastBlock;
exports.fetchTronTxDetail = fetchTronTxDetail;
exports.fetchTronAccountTxs = fetchTronAccountTxs;
const cache_1 = require("@ledgerhq/live-network/cache");
const live_network_1 = __importDefault(require("@ledgerhq/live-network"));
const live_promise_1 = require("@ledgerhq/live-promise");
const logs_1 = require("@ledgerhq/logs");
const bignumber_js_1 = require("bignumber.js");
const compact_1 = __importDefault(require("lodash/compact"));
const drop_1 = __importDefault(require("lodash/drop"));
const sumBy_1 = __importDefault(require("lodash/sumBy"));
const take_1 = __importDefault(require("lodash/take"));
const tronweb_1 = __importDefault(require("tronweb"));
const errors_1 = require("../types/errors");
const config_1 = __importDefault(require("../config"));
const utils_1 = require("./utils");
const format_1 = require("./format");
const types_1 = require("./types");
const querystring_1 = require("querystring");
const getBaseApiUrl = () => config_1.default.getCoinConfig().explorer.url;
async function post(endPoint, body) {
const { data } = await (0, live_network_1.default)({
method: "POST",
url: `${getBaseApiUrl()}${endPoint}`,
data: body,
});
// Ugly but trongrid send a 200 status event if there are errors
if ("Error" in data) {
(0, logs_1.log)("tron-error", (0, querystring_1.stringify)(data.Error), {
endPoint,
body,
});
throw new Error((0, querystring_1.stringify)(data.Error));
}
return data;
}
async function fetch(endPoint) {
return fetchWithBaseUrl(`${getBaseApiUrl()}${endPoint}`);
}
async function fetchWithBaseUrl(url) {
const { data } = await (0, live_network_1.default)({ url });
// Ugly but trongrid send a 200 status event if there are errors
if ("Error" in data) {
(0, logs_1.log)("tron-error", (0, querystring_1.stringify)(data.Error), {
url,
});
throw new Error((0, querystring_1.stringify)(data.Error));
}
return data;
}
const freezeTronTransaction = async (account, transaction) => {
const txData = {
frozen_balance: transaction.amount.toNumber(),
resource: transaction.resource,
owner_address: (0, format_1.decode58Check)(account.freshAddress),
};
const url = `/wallet/freezebalancev2`;
const result = await post(url, txData);
return result;
};
exports.freezeTronTransaction = freezeTronTransaction;
const unfreezeTronTransaction = async (account, transaction) => {
const txData = {
owner_address: (0, format_1.decode58Check)(account.freshAddress),
resource: transaction.resource,
unfreeze_balance: transaction.amount.toNumber(),
};
const url = `/wallet/unfreezebalancev2`;
const result = await post(url, txData);
return result;
};
exports.unfreezeTronTransaction = unfreezeTronTransaction;
const withdrawExpireUnfreezeTronTransaction = async (account, _transaction) => {
const txData = {
owner_address: (0, format_1.decode58Check)(account.freshAddress),
};
const url = `/wallet/withdrawexpireunfreeze`;
const result = await post(url, txData);
return result;
};
exports.withdrawExpireUnfreezeTronTransaction = withdrawExpireUnfreezeTronTransaction;
const unDelegateResourceTransaction = async (account, transaction) => {
const txData = {
balance: transaction.amount.toNumber(),
resource: transaction.resource,
owner_address: (0, format_1.decode58Check)(account.freshAddress),
receiver_address: (0, format_1.decode58Check)(transaction.recipient),
};
const url = `/wallet/undelegateresource`;
const result = await post(url, txData);
return result;
};
exports.unDelegateResourceTransaction = unDelegateResourceTransaction;
const legacyUnfreezeTronTransaction = async (account, transaction) => {
const txData = {
resource: transaction.resource,
owner_address: (0, format_1.decode58Check)(account.freshAddress),
receiver_address: transaction.recipient ? (0, format_1.decode58Check)(transaction.recipient) : undefined,
};
const url = `/wallet/unfreezebalance`;
const result = await post(url, txData);
return result;
};
exports.legacyUnfreezeTronTransaction = legacyUnfreezeTronTransaction;
async function getDelegatedResource(account, transaction, resource) {
const url = `/wallet/getdelegatedresourcev2`;
const { delegatedResource = [], } = await post(url, {
fromAddress: (0, format_1.decode58Check)(account.freshAddress),
toAddress: (0, format_1.decode58Check)(transaction.recipient),
});
const { frozen_balance_for_bandwidth, frozen_balance_for_energy } = delegatedResource.reduce((accum, cur) => {
if (cur.frozen_balance_for_bandwidth) {
accum.frozen_balance_for_bandwidth += cur.frozen_balance_for_bandwidth;
}
if (cur.frozen_balance_for_energy) {
accum.frozen_balance_for_energy += cur.frozen_balance_for_energy;
}
return accum;
}, { frozen_balance_for_bandwidth: 0, frozen_balance_for_energy: 0 });
const amount = resource === "BANDWIDTH" ? frozen_balance_for_bandwidth : frozen_balance_for_energy;
return new bignumber_js_1.BigNumber(amount);
}
async function craftTrc20Transaction(tokenAddress, recipientAddress, senderAddress, amount) {
const txData = {
function_selector: "transfer(address,uint256)",
fee_limit: 50000000,
call_value: 0,
contract_address: (0, format_1.decode58Check)(tokenAddress),
parameter: (0, utils_1.abiEncodeTrc20Transfer)(recipientAddress, new bignumber_js_1.BigNumber(amount.toString())),
owner_address: senderAddress,
};
const url = `/wallet/triggersmartcontract`;
const { transaction: preparedTransaction } = await post(url, txData);
return await extendTronTxExpirationTimeBy10mn(preparedTransaction);
}
async function craftStandardTransaction(tokenAddress, recipientAddress, senderAddress, amount, isTransferAsset) {
const url = isTransferAsset ? `/wallet/transferasset` : `/wallet/createtransaction`;
const txData = {
to_address: recipientAddress,
owner_address: senderAddress,
amount: Number(amount),
asset_name: tokenAddress && Buffer.from(tokenAddress).toString("hex"),
};
const preparedTransaction = await post(url, txData);
return await extendTronTxExpirationTimeBy10mn(preparedTransaction);
}
const getTokenInfo = (subAccount) => {
const tokenInfo = subAccount && subAccount.type === "TokenAccount"
? (0, drop_1.default)(subAccount.token.id.split("/"), 1)
: [undefined, undefined];
return tokenInfo;
};
// Send trx or trc10/trc20 tokens
const createTronTransaction = async (account, transaction, subAccount) => {
const [tokenType, tokenId] = getTokenInfo(subAccount);
const decodeRecipient = (0, format_1.decode58Check)(transaction.recipient);
const decodeSender = (0, format_1.decode58Check)(account.freshAddress);
// trc20
if (tokenType === "trc20" && tokenId) {
const tokenContractAddress = subAccount.token.contractAddress;
return craftTrc20Transaction(tokenContractAddress, decodeRecipient, decodeSender, transaction.amount);
}
else {
const isTransferAsset = subAccount ? true : false;
return craftStandardTransaction(tokenId, decodeRecipient, decodeSender, transaction.amount, isTransferAsset);
}
};
exports.createTronTransaction = createTronTransaction;
async function extendTronTxExpirationTimeBy10mn(preparedTransaction) {
const VAULT_EXPIRATION_TIME = 600;
const HttpProvider = tronweb_1.default.providers.HttpProvider;
const fullNode = new HttpProvider(getBaseApiUrl());
const solidityNode = new HttpProvider(getBaseApiUrl());
const eventServer = new HttpProvider(getBaseApiUrl());
const tronWeb = new tronweb_1.default(fullNode, solidityNode, eventServer);
//FIXME: test it and rewrite it
return tronWeb.transactionBuilder.extendExpiration(preparedTransaction, VAULT_EXPIRATION_TIME);
}
/**
* @see https://github.com/tronprotocol/java-tron/blob/develop/framework/src/main/java/org/tron/core/services/http/BroadcastServlet.java
* @param trxTransaction
* @returns Transaction ID
*/
const broadcastTron = async (trxTransaction) => {
const result = await post("/wallet/broadcasttransaction", trxTransaction);
if (result.result !== true) {
if (result.code === "TRANSACTION_EXPIRATION_ERROR") {
throw new errors_1.TronTransactionExpired();
}
else {
throw new Error(result.message);
}
}
return result.txid;
};
exports.broadcastTron = broadcastTron;
const broadcastHexTron = async (rawTransaction) => {
const result = await post(`/wallet/broadcasthex`, { transaction: rawTransaction });
if (!result.result) {
throw Error(`Broadcast failed due to ${result.code}`);
}
return result.txid;
};
exports.broadcastHexTron = broadcastHexTron;
/**
* {@link https://github.com/tronprotocol/java-tron/blob/develop/framework/src/main/java/org/tron/core/services/http/GetAccountServlet.java | Tron Framework}
*/
async function fetchTronAccount(addr) {
try {
const data = await fetch(`/v1/accounts/${addr}`);
return data.data;
}
catch (e) {
return [];
}
}
async function getLastBlock() {
const data = await fetch(`/wallet/getnowblock`);
return {
height: data.block_header.raw_data.number,
hash: data.blockID,
time: new Date(data.block_header.raw_data.timestamp),
};
}
// For the moment, fetching transaction info is the only way to get fees from a transaction
// Export for test purpose only
async function fetchTronTxDetail(txId) {
const { fee, blockNumber, withdraw_amount, unfreeze_amount } = await fetch(`/wallet/gettransactioninfobyid?value=${encodeURIComponent(txId)}`);
return {
fee,
blockNumber,
withdraw_amount,
unfreeze_amount,
};
}
async function getAllTransactions(initialUrl, shouldFetchMoreTxs, getTxs) {
let all = [];
let url = initialUrl;
while (url && shouldFetchMoreTxs(all)) {
const { nextUrl, results } = await getTxs(url);
url = nextUrl;
all = all.concat(results);
}
return all;
}
const getTransactions = (cacheTransactionInfoById) => async (url) => {
const transactions = await fetchWithBaseUrl(url);
const nextUrl = transactions.meta.links?.next?.replace(/https:\/\/api(\.[a-z]*)?.trongrid.io/, getBaseApiUrl());
const results = await (0, live_promise_1.promiseAllBatched)(3, transactions.data || [], async (tx) => {
if ((0, types_1.isMalformedTransactionTronAPI)(tx)) {
return tx;
}
const txID = tx.txID;
const detail = cacheTransactionInfoById[txID] || (await fetchTronTxDetail(txID));
cacheTransactionInfoById[txID] = detail;
return { ...tx, detail };
});
return {
results,
nextUrl,
};
};
const getTrc20 = async (url) => {
const transactions = await fetchWithBaseUrl(url);
return {
results: transactions.data,
nextUrl: transactions.meta.links?.next?.replace(/https:\/\/api(\.[a-z]*)?.trongrid.io/, getBaseApiUrl()),
};
};
async function fetchTronAccountTxs(addr, shouldFetchMoreTxs, cacheTransactionInfoById) {
const entireTxs = (await getAllTransactions(`${getBaseApiUrl()}/v1/accounts/${addr}/transactions?limit=100`, shouldFetchMoreTxs, getTransactions(cacheTransactionInfoById)))
.filter((tx) => (0, types_1.isTransactionTronAPI)(tx))
.filter(tx => {
// custom smart contract tx has internal txs
const hasInternalTxs = tx.txID && tx.internal_transactions && tx.internal_transactions.length > 0;
// and also a duplicated malformed tx that we have to ignore
const isDuplicated = tx.tx_id;
const type = tx.raw_data.contract[0].type;
if (hasInternalTxs) {
// log once
(0, logs_1.log)("tron-error", `unsupported transaction ${tx.txID}`);
}
return !isDuplicated && !hasInternalTxs && type !== "TriggerSmartContract";
})
.map(tx => (0, format_1.formatTrongridTxResponse)(tx));
// we need to fetch and filter trc20 transactions from another endpoint
const entireTrc20Txs = (await getAllTransactions(`${getBaseApiUrl()}/v1/accounts/${addr}/transactions/trc20?get_detail=true`, shouldFetchMoreTxs, getTrc20)).map(tx => (0, format_1.formatTrongridTrc20TxResponse)(tx));
const txInfos = (0, compact_1.default)(entireTxs.concat(entireTrc20Txs)).sort((a, b) => b.date.getTime() - a.date.getTime());
return txInfos;
}
const getContractUserEnergyRatioConsumption = async (address) => {
const result = await (0, exports.fetchTronContract)(address);
if (result) {
const { consume_user_resource_percent } = result;
return consume_user_resource_percent;
}
return 0;
};
exports.getContractUserEnergyRatioConsumption = getContractUserEnergyRatioConsumption;
const fetchTronContract = async (addr) => {
try {
const data = await post(`/wallet/getcontract`, {
value: (0, format_1.decode58Check)(addr),
});
return Object.keys(data).length !== 0 ? data : undefined;
}
catch (e) {
return undefined;
}
};
exports.fetchTronContract = fetchTronContract;
const getTronAccountNetwork = async (address) => {
const result = await fetch(`/wallet/getaccountresource?address=${encodeURIComponent((0, format_1.decode58Check)(address))}`);
const { freeNetUsed = 0, freeNetLimit = 0, NetUsed = 0, NetLimit = 0, EnergyUsed = 0, EnergyLimit = 0, } = result;
return {
family: "tron",
freeNetUsed: new bignumber_js_1.BigNumber(freeNetUsed),
freeNetLimit: new bignumber_js_1.BigNumber(freeNetLimit),
netUsed: new bignumber_js_1.BigNumber(NetUsed),
netLimit: new bignumber_js_1.BigNumber(NetLimit),
energyUsed: new bignumber_js_1.BigNumber(EnergyUsed),
energyLimit: new bignumber_js_1.BigNumber(EnergyLimit),
};
};
exports.getTronAccountNetwork = getTronAccountNetwork;
const validateAddress = async (address) => {
try {
const result = await post(`/wallet/validateaddress`, {
address: (0, format_1.decode58Check)(address),
});
return result.result || false;
}
catch (e) {
// FIXME we should not silent errors!
(0, logs_1.log)("tron-error", "validateAddress fails with " + e.message, {
address,
});
return false;
}
};
exports.validateAddress = validateAddress;
// cache for account names (name is unchanged over time)
const accountNamesCache = (0, cache_1.makeLRUCache)(async (addr) => (0, exports.getAccountName)(addr), (addr) => addr, (0, cache_1.hours)(3, 300));
// cache for super representative brokerages (brokerage is unchanged over time)
const srBrokeragesCache = (0, cache_1.makeLRUCache)(async (addr) => (0, exports.getBrokerage)(addr), (addr) => addr, (0, cache_1.hours)(3, 300));
const getAccountName = async (addr) => {
const tronAcc = await fetchTronAccount(addr);
const acc = tronAcc[0];
const accountName = acc && acc.account_name ? (0, utils_1.hexToAscii)(acc.account_name) : undefined;
accountNamesCache.hydrate(addr, accountName); // put it in cache
return accountName;
};
exports.getAccountName = getAccountName;
const getBrokerage = async (addr) => {
const { brokerage } = await fetch(`/wallet/getBrokerage?address=${encodeURIComponent(addr)}`);
srBrokeragesCache.hydrate(addr, brokerage); // put it in cache
return brokerage;
};
exports.getBrokerage = getBrokerage;
const superRepresentativesCache = (0, cache_1.makeLRUCache)(async () => {
const superRepresentatives = await fetchSuperRepresentatives();
(0, logs_1.log)("tron/superRepresentatives", "loaded " + superRepresentatives.length + " super representatives");
return superRepresentatives;
}, () => "", (0, cache_1.hours)(1, 300));
const getTronSuperRepresentatives = async () => {
return await superRepresentativesCache();
};
exports.getTronSuperRepresentatives = getTronSuperRepresentatives;
const hydrateSuperRepresentatives = (list) => {
(0, logs_1.log)("tron/superRepresentatives", "hydrate " + list.length + " super representatives");
superRepresentativesCache.hydrate("", list);
};
exports.hydrateSuperRepresentatives = hydrateSuperRepresentatives;
const fetchSuperRepresentatives = async () => {
const result = await fetch(`/wallet/listwitnesses`);
const sorted = result.witnesses.sort((a, b) => b.voteCount - a.voteCount);
const superRepresentatives = await (0, live_promise_1.promiseAllBatched)(3, sorted, async (w) => {
const encodedAddress = (0, format_1.encode58Check)(w.address);
const accountName = await accountNamesCache(encodedAddress);
const brokerage = await srBrokeragesCache(encodedAddress);
return {
...w,
address: encodedAddress,
name: accountName,
brokerage,
voteCount: w.voteCount || 0,
isJobs: w.isJobs || false,
};
});
(0, exports.hydrateSuperRepresentatives)(superRepresentatives); // put it in cache
return superRepresentatives;
};
const getNextVotingDate = async () => {
const { num } = await fetch(`/wallet/getnextmaintenancetime`);
return new Date(num);
};
exports.getNextVotingDate = getNextVotingDate;
const getTronSuperRepresentativeData = async (max) => {
const list = await (0, exports.getTronSuperRepresentatives)();
const nextVotingDate = await (0, exports.getNextVotingDate)();
return {
list: max ? (0, take_1.default)(list, max) : list,
totalVotes: (0, sumBy_1.default)(list, "voteCount"),
nextVotingDate,
};
};
exports.getTronSuperRepresentativeData = getTronSuperRepresentativeData;
const voteTronSuperRepresentatives = async (account, transaction) => {
const payload = {
owner_address: (0, format_1.decode58Check)(account.freshAddress),
votes: transaction.votes.map(v => ({
vote_address: (0, format_1.decode58Check)(v.address),
vote_count: v.voteCount,
})),
};
return await post(`/wallet/votewitnessaccount`, payload);
};
exports.voteTronSuperRepresentatives = voteTronSuperRepresentatives;
const getUnwithdrawnReward = async (addr) => {
try {
const { reward = 0 } = await fetch(`/wallet/getReward?address=${encodeURIComponent((0, format_1.decode58Check)(addr))}`);
return new bignumber_js_1.BigNumber(reward);
}
catch (e) {
return Promise.resolve(new bignumber_js_1.BigNumber(0));
}
};
exports.getUnwithdrawnReward = getUnwithdrawnReward;
const claimRewardTronTransaction = async (account) => {
const url = `/wallet/withdrawbalance`;
const data = {
owner_address: (0, format_1.decode58Check)(account.freshAddress),
};
const result = await post(url, data);
return result;
};
exports.claimRewardTronTransaction = claimRewardTronTransaction;
//# sourceMappingURL=index.js.map