@layerzerolabs/hardhat-deploy
Version:
Hardhat Plugin For Replicable Deployments And Tests
350 lines • 18 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TronWeb3Provider = void 0;
const providers_1 = require("@ethersproject/providers");
const signer_1 = require("./signer");
const ethers_1 = require("ethers");
const utils_1 = require("./utils");
const utils_2 = require("ethers/lib/utils");
const tronweb_1 = __importDefault(require("tronweb"));
/**
* A provider for interacting with the TRON blockchain, extending the Web3Provider.
*
* `TronWeb3Provider` is designed to integrate TRON's blockchain functionalities with the Web3 interface.
* It extends the `Web3Provider` class, adapting it to work with the TRON network.
* This class manages a collection of `TronSigner` instances for transaction signing
* and provides methods for interacting with the TRON blockchain, such as sending transactions,
* estimating gas, and retrieving transaction details.
*
* Key Features:
* - Signer Management: Maintains a collection of `TronSigner` instances for different addresses.
* - Transaction Handling: Provides methods for sending transactions, estimating gas, and more.
* - TronWeb Integration: Utilizes TronWeb for direct interactions with the TRON network.
* - Configurable: Can be configured with custom network settings and HTTP headers.
*
* @extends Web3Provider
*
* @constructor
* @param {ExternalProvider | JsonRpcFetchFunc} provider - The underlying JSON-RPC provider.
* @param {HttpNetworkConfig} config - Configuration for the network, including HTTP headers and URL.
* @param {Networkish | undefined} [network] - The network configuration.
*/
class TronWeb3Provider extends providers_1.Web3Provider {
constructor(provider, config, network) {
super(provider, network);
this.signers = new Map();
this.gasPrice = { time: utils_1.Time.NOW };
this.FALLBACK_MAX_FEE_LIMIT = 15e9; // 15,000 TRX;
const { httpHeaders: headers, url, accounts } = config;
let fullHost = url;
// the address of the tron node has the jsonrpc path chopped off
fullHost = fullHost.replace(/\/jsonrpc\/?$/, '');
this.fullHost = fullHost;
this.headers = headers;
this.ro_tronweb = new tronweb_1.default({ fullHost, headers });
// instantiate Tron Signer
if (Array.isArray(accounts)) {
for (const pk of accounts) {
const addr = new ethers_1.Wallet(pk).address;
this.signers.set(addr, new signer_1.TronSigner(fullHost, headers, pk, this));
}
}
else if (typeof accounts !== 'string' && 'mnemonic' in accounts) {
const hdNode = utils_2.HDNode.fromMnemonic(accounts.mnemonic, accounts.passphrase);
const derivedNode = hdNode.derivePath(`${accounts.path}/${accounts.initialIndex}`);
this.signers.set(derivedNode.address, new signer_1.TronSigner(fullHost, headers, derivedNode.privateKey, this));
}
else {
throw new Error('unable to instantiate Tron Signer, unrecognized private key');
}
}
/**
* Adds a new signer to the signer collection.
*
* This method creates and adds a new `TronSigner` instance to the signer collection using the provided private key.
* If a signer already exists for the derived address, it returns the existing signer.
* Otherwise, it creates a new `TronSigner`, adds it to the collection, and returns it.
*
* @param pk - The private key to create a new signer.
* @returns The newly added or existing `TronSigner` instance.
*/
addSigner(pk) {
const addr = new ethers_1.Wallet(pk).address;
if (this.signers.has(addr))
return this.signers.get(addr);
const signer = new signer_1.TronSigner(this.fullHost, this.headers, pk, this);
this.signers.set(addr, signer);
return signer;
}
/**
* Retrieves the transaction count for an account.
*
* This method overrides the `getTransactionCount` method. Since the Tron protocol does not support
* the concept of nonces as in Ethereum, this method returns a dummy value.
*
* @returns A promise that resolves to the dummy transaction count.
*/
async getTransactionCount() {
console.log('getTransactionCount is not available in the Tron protocol, returning dummy value 1 ...');
return 1;
}
/**
* Retrieves a signer instance for a given address.
*
* This method overrides the `getSigner` method to return a signer instance
* associated with the provided address. If no signer is found for the given address, it throws an error.
*
* @template T - The type of signer to be returned, either `TronSigner` or `JsonRpcSigner`.
* @param address - The address to retrieve the signer for.
* @returns The signer instance corresponding to the given address.
* @throws Throws an error if no signer exists for the provided address.
*/
getSigner(address) {
const signer = this.signers.get(address);
if (!signer) {
throw new Error(`No Tron signer exists for this address ${address}`);
}
return signer;
}
/**
* Retrieves the current gas price with caching.
*
* This method overrides the `getGasPrice` method to include a caching mechanism with a 15-second TTL.
* If the cached value is recent (within 15 seconds), it returns the cached value. Otherwise, it fetches
* the current gas price from the network. If fetching fails, it defaults to a predefined energy price.
*
* @returns A promise that resolves to the current gas price as a BigNumber.
*/
async getGasPrice() {
const DEFAULT_ENERGY_PRICE = ethers_1.BigNumber.from('1000');
const { time, value } = this.gasPrice;
if (time > utils_1.Time.NOW - 15 * utils_1.Time.SECOND && value)
return value;
const gasPrice = (await super.getGasPrice()) ?? DEFAULT_ENERGY_PRICE;
this.gasPrice = { time: utils_1.Time.NOW, value: gasPrice };
return gasPrice;
}
/**
* Sends a signed transaction to the network.
*
* This method first checks if the signed transaction is a simple TRX transfer (send TRX transaction).
* If so, it handles the transaction through the `sendTrx` method.
*
* @param signedTransaction - The signed transaction or a promise that resolves to it.
* @returns A promise that resolves to the transaction response.
*/
async sendTransaction(signedTransaction) {
signedTransaction = await signedTransaction;
const deser = (0, utils_2.parseTransaction)(signedTransaction);
const { to, data, from, value } = deser;
// is this a send trx transaction?
if (this.isSendTRX(to, from, data)) {
return this.sendTrx(from, to, value);
}
// is this a smart contract transaction?
if (await this.isSmartContractCall(to, from, data)) {
throw new Error('direct smart contract call not yet implemented for Tron');
}
// otherwise don't alter behavior
return super.sendTransaction(signedTransaction);
}
/**
* Sends TRX from one account to another.
*
* This method handles the sending of TRX tokens by creating, signing, and sending a transaction.
* It accounts for the difference in decimal places between TRX (6 decimals) and ETH (18 decimals).
* If the value is extremely large (more than 1000 TRX), it scales down the value to prevent errors.
* After sending the transaction, it waits briefly for the transaction to be processed.
*
* @param from - The address to send TRX from.
* @param to - The address to send TRX to.
* @param value - The amount of TRX to send, as a BigNumber.
* @returns A promise that resolves to the transaction response.
* @throws Throws an error if the transaction fails.
*/
async sendTrx(from, to, value) {
if (value.gt(10 ** 9))
value = value.div(10 ** 12);
const unsignedTx = await this.ro_tronweb.transactionBuilder.sendTrx(this.ro_tronweb.address.toHex(to), Math.floor(value.toNumber()), this.ro_tronweb.address.toHex(from));
const signedTx = await this.getSigner(from).sign(unsignedTx);
return this.sendRawTransaction(signedTx);
}
/**
* Triggers a function call on a specified smart contract in the Tron network.
*
* This method constructs a transaction to call a function of a smart contract. It requires
* the sender's address, the contract address, the function signature, parameters for the function,
* and an options object which may include a gas limit and an optional value to send with the transaction.
* The fee limit for the transaction is determined using the sender's signer. The transaction
* is then signed and sent to the Tron network.
*
* @param from - The address of the sender initiating the contract call.
* @param contract - The address of the smart contract to interact with.
* @param funcSig - The function signature to call in the smart contract.
* @param params - An array of parameters for the function call, each with a type and value.
* @param options - An object containing optional parameters.
* @returns A promise that resolves to a `TransactionResponse` object representing the result of the transaction.
*/
async triggerSmartContract(from, contract, funcSig, params, options) {
const feeLimit = await this.getSigner(from).getFeeLimit({ to: contract }, options);
const { transaction } = await this.ro_tronweb.transactionBuilder.triggerSmartContract(this.ro_tronweb.address.toHex(contract), funcSig, { feeLimit, callValue: options.value?.toString() ?? 0 }, params, this.ro_tronweb.address.toHex(from));
const signedTx = await this.getSigner(from).sign(transaction);
return this.sendRawTransaction(signedTx);
}
/**
* Sends a raw transaction to the Tron network and returns the transaction response.
*
* This method accepts a raw transaction object, sends it to the Tron network, and waits
* for the transaction to be acknowledged. After the transaction is acknowledged, it retrieves
* and returns the transaction response. If the transaction fails at any stage, the method
* throws an error.
*
* @param transaction - The raw transaction object to be sent to the network.
* @returns A promise that resolves to a `TransactionResponse` object, which includes details of the processed transaction.
* @throws `TronWebError` - If the transaction fails to be sent or acknowledged by the network.
*
*/
async sendRawTransaction(transaction) {
const response = await this.ro_tronweb.trx.sendRawTransaction(transaction);
if (!('result' in response) || !response.result) {
throw new utils_1.TronWebError(response);
}
console.log('\nTron transaction broadcast, waiting for response...');
const txRes = await this.getTransactionWithRetry(response.txid);
txRes.wait = this._buildWait(txRes.confirmations, response.txid);
return txRes;
}
/**
* Creates a function that waits for a specified number of confirmations of a transaction.
*
* This method generates a function that, when called, will continuously check for the number of confirmations
* of a given transaction until it reaches the specified target. It checks the transaction status every second.
* If the transaction is found to have failed (status 0), a `TronTransactionFailedError` is thrown.
*
* @param initialConfirmations - The initial number of confirmations at the time of this method call.
* @param hash - The hash of the transaction to wait for.
* @returns A function that takes `targetConfirmations` and returns a promise that resolves to the transaction receipt.
*/
_buildWait(initialConfirmations, hash) {
return async (targetConfirmations) => {
let curr_conf = initialConfirmations;
while (targetConfirmations && curr_conf < targetConfirmations) {
await utils_1.Time.sleep(utils_1.Time.SECOND); // sleep 1 sec
const { confirmations: latest_conf } = await this.getTransactionWithRetry(hash, 3);
curr_conf = latest_conf;
}
const receipt = await this.getTransactionReceipt((0, utils_1.ensure0x)(hash));
const { status } = receipt;
if (status === 0) {
throw new utils_1.TronTransactionFailedError(receipt);
}
return receipt;
};
}
/**
* Attempts to retrieve a transaction response from the jsonrpc node using the hash, with a retry mechanism.
*
* This method tries to get a transaction by its hash. If the initial attempt fails, it retries
* the operation, up to a specified number of times. Between each retry, the method waits for
* a period that increases linearly, with an additional random jitter to avoid simultaneous
* retry spikes. This approach is useful for handling transient network issues, or the sync delay that can happen between
* a Tron fullNode and its rpc node
*
* @param hash The hash of the transaction to retrieve.
* @param retries The maximum number of attempts to retrieve the transaction. Defaults to 10.
* @returns A promise that resolves to the transaction response.
*/
async getTransactionWithRetry(hash, retries = 10) {
for (let i = 1; i < retries; i++) {
try {
const response = await this.getTransaction((0, utils_1.ensure0x)(hash)); // can return null!
if (response == null)
throw '';
return response;
}
catch (error) { }
// Linear backoff with jitter
const jitter = Math.floor(Math.random() * 300);
await utils_1.Time.sleep(utils_1.Time.SECOND + jitter);
}
return await this.getTransaction((0, utils_1.ensure0x)(hash));
}
/**
* Estimates the gas required for a transaction on the TRON network.
*
* This method overrides the `estimateGas` method to accommodate TRON's [specific requirements](https://developers.tron.network/reference/eth_estimategas).
* TRON does not support EIP-1559 transactions and nonces, so related fields are removed from the transaction object.
* It then calls the superclass's `estimateGas` method for the actual estimation.
*
* @param transaction - The transaction object to estimate gas for.
* @returns A promise that resolves to the estimated gas as a BigNumber.
*/
async estimateGas(transaction) {
const toDel = ['type', 'maxFeePerGas', 'maxPriorityFeePerGas', 'nonce'];
for (const field of toDel) {
delete transaction[field];
}
return super.estimateGas(transaction);
}
/**
* Checks if a given transaction is a smart contract call.
*
* This method examines the `to`, `from`, and `data` fields of a transaction
* to determine if it is likely a call to a smart contract. It considers a transaction
* as a smart contract call if all fields are defined, the addresses are valid,
* the data field has a significant length, and there is associated contract code.
*
* @param to - The recipient address of the transaction.
* @param from - The sender address of the transaction.
* @param data - The data payload of the transaction.
* @returns A promise that resolves to `true` if the transaction is a smart contract call, otherwise `false`.
*/
async isSmartContractCall(to, from, data) {
if ([to, from, data].some((f) => f == undefined))
return false;
if ([to, from].some((f) => (0, utils_2.isAddress)(f) == false))
return false;
if (data.length <= 2)
return false;
const contractCode = await this.getCode(to);
return contractCode != undefined && (0, utils_1.strip0x)(contractCode).length > 0;
}
/**
* Determines if a transaction is a TRX (transfer) operation.
*
* This method checks if the provided `to`, `from`, and `data` fields
* of a transaction suggest a TRX operation. It considers a transaction as
* a TRX operation if the `to` and `from` fields are defined and the `data` field
* is either not present or equals '0x'.
*
* @param to - The recipient address of the transaction.
* @param from - The sender address of the transaction.
* @param data - The data payload of the transaction.
* @returns `true` if the transaction is likely a TRX operation, otherwise `false`.
*/
isSendTRX(to, from, data) {
if ([to, from].some((f) => f == undefined))
return false;
return !data || data == '0x';
}
/**
* Asynchronously retrieves and caches the maximum fee limit from the chain parameters.
* If the parameter is not found, a predefined fallback value is used.
* The value is cached for future calls to this method.
*
* @returns {Promise<number>} A promise that resolves with the cached or newly retrieved maximum fee limit.
*/
async getMaxFeeLimit() {
if (this.maxFeeLimit == undefined) {
const params = await this.ro_tronweb.trx.getChainParameters();
const param = params.find(({ key }) => key === 'getMaxFeeLimit');
this.maxFeeLimit = param?.value ?? this.FALLBACK_MAX_FEE_LIMIT;
}
return this.maxFeeLimit;
}
}
exports.TronWeb3Provider = TronWeb3Provider;
//# sourceMappingURL=provider.js.map