tronweb
Version:
JavaScript SDK that encapsulates the TRON HTTP API
1,438 lines (1,331 loc) • 80.9 kB
text/typescript
import { TronWeb } from '../../tronweb.js';
import { AbiCoder, keccak256 } from '../../utils/ethersUtils.js';
import { ADDRESS_PREFIX_REGEX, toHex } from '../../utils/address.js';
import { encodeParamsV2ByABI } from '../../utils/abi.js';
import { CreateSmartContractTransaction, SignedTransaction, Transaction, TransactionWrapper } from '../../types/Transaction.js';
import { Validator } from '../../paramValidator/index.js';
import { GetSignWeightResponse } from '../../types/APIResponse.js';
import { isArray, isInteger, isNotNullOrUndefined, isObject, isString } from '../../utils/validations.js';
import {
AccountCreateContract,
AccountPermissionUpdateContract,
AccountUpdateContract,
AssetIssueContract,
CancelFreezeBalanceV2Contract,
ClearABIContract,
ContractParamter,
ContractType,
CreateSmartContract,
DelegateResourceContract,
DeployConstantContract,
ExchangeCreateContract,
ExchangeInjectContract,
ExchangeTransactionContract,
ExchangeWithdrawContract,
FreezeBalanceContract,
FreezeBalanceV2Contract,
ParticipateAssetIssueContract,
Permission,
ProposalCreateContract,
ProposalDeleteContract,
SetAccountIdContract,
TransferAssetContract,
TransferContract,
TriggerSmartContract,
UnDelegateResourceContract,
UnfreezeBalanceContract,
UnfreezeBalanceV2Contract,
UpdateAssetContract,
UpdateBrokerageContract,
UpdateEnergyLimitContract,
UpdateSettingContract,
VoteProposalContract,
VoteWitnessContract,
WithdrawBalanceContract,
WithdrawExpireUnfreezeContract,
WitnessCreateContract,
} from '../../types/Contract.js';
import {
createTransaction,
deepCopyJson,
fromUtf8,
genContractAddress,
resultManager,
resultManagerTriggerSmartContract,
getTransactionOptions,
} from './helper.js';
import {
AlterTransactionOptions,
CreateSmartContractOptions,
CreateTokenOptions,
DeployConstantContractOptions,
TriggerConstantContractOptions,
TransactionCommonOptions,
Resource,
ContractFunctionParameter,
TriggerSmartContractOptions,
TxLocal,
UpdateTokenOptions,
VoteInfo,
} from '../../types/TransactionBuilder.js';
import { Address } from '../../types/Trx.js';
import { ConstructorFragment, ContractAbiInterface, FunctionFragment } from '../../types/ABI.js';
interface IArgs extends TriggerSmartContract {
function_selector?: string;
parameter?: string;
fee_limit?: number;
Permission_id?: number;
}
export class TransactionBuilder {
private tronWeb: TronWeb;
private validator: Validator;
constructor(tronWeb?: TronWeb) {
if (!tronWeb || !(tronWeb instanceof TronWeb)) {
throw new Error('Expected instance of TronWeb');
}
this.tronWeb = tronWeb;
this.validator = new Validator();
}
async sendTrx(
to: string,
amount = 0,
from: string = this.tronWeb.defaultAddress.hex as string,
options: TransactionCommonOptions = {}
): Promise<Transaction<TransferContract>> {
// accept amounts passed as strings
amount = parseInt(amount);
this.validator.notValid([
{
name: 'recipient',
type: 'address',
value: to,
},
{
name: 'origin',
type: 'address',
value: from as string,
},
{
names: ['recipient', 'origin'],
type: 'notEqual',
msg: 'Cannot transfer TRX to the same account',
},
{
name: 'amount',
type: 'integer',
gt: 0,
value: amount,
},
]);
const data: TransferContract = {
to_address: toHex(to),
owner_address: toHex(from as string),
amount: amount,
};
const transactionOptions = getTransactionOptions(options);
return createTransaction<TransferContract>(this.tronWeb, ContractType.TransferContract, data, options?.permissionId, transactionOptions);
}
async sendToken(
to: string,
amount = 0,
tokenId: string,
from: string = this.tronWeb.defaultAddress.hex as string,
options: TransactionCommonOptions = {}
): Promise<Transaction<TransferAssetContract>> {
amount = parseInt(amount);
this.validator.notValid([
{
name: 'recipient',
type: 'address',
value: to,
},
{
name: 'origin',
type: 'address',
value: from as string,
},
{
names: ['recipient', 'origin'],
type: 'notEqual',
msg: 'Cannot transfer tokens to the same account',
},
{
name: 'amount',
type: 'integer',
gt: 0,
value: amount,
},
{
name: 'token ID',
type: 'tokenId',
value: tokenId,
},
]);
const data: TransferAssetContract = {
to_address: toHex(to),
owner_address: toHex(from as string),
asset_name: fromUtf8(tokenId as string),
amount,
};
const transactionOptions = getTransactionOptions(options);
return createTransaction<TransferAssetContract>(this.tronWeb, ContractType.TransferAssetContract, data, options?.permissionId, transactionOptions);
}
async purchaseToken(
issuerAddress: string,
tokenId: string,
amount = 0,
buyer: string = this.tronWeb.defaultAddress.hex as string,
options: TransactionCommonOptions = {}
): Promise<Transaction<ParticipateAssetIssueContract>> {
this.validator.notValid([
{
name: 'buyer',
type: 'address',
value: buyer as string,
},
{
name: 'issuer',
type: 'address',
value: issuerAddress,
},
{
names: ['buyer', 'issuer'],
type: 'notEqual',
msg: 'Cannot purchase tokens from same account',
},
{
name: 'amount',
type: 'integer',
gt: 0,
value: amount,
},
{
name: 'token ID',
type: 'tokenId',
value: tokenId,
},
]);
const data: ParticipateAssetIssueContract = {
to_address: toHex(issuerAddress),
owner_address: toHex(buyer as string),
asset_name: fromUtf8(tokenId as string),
amount: parseInt(amount),
};
const transactionOptions = getTransactionOptions(options);
return createTransaction<ParticipateAssetIssueContract>(this.tronWeb, ContractType.ParticipateAssetIssueContract, data, options?.permissionId, transactionOptions);
}
async freezeBalance(
amount = 0,
duration = 3,
resource: Resource = 'BANDWIDTH',
ownerAddress: string = this.tronWeb.defaultAddress.hex as string,
receiverAddress?: string,
options: TransactionCommonOptions = {}
): Promise<Transaction<FreezeBalanceContract>> {
this.validator.notValid([
{
name: 'origin',
type: 'address',
value: ownerAddress as string,
},
{
name: 'receiver',
type: 'address',
value: receiverAddress as string,
optional: true,
},
{
name: 'amount',
type: 'integer',
gt: 0,
value: amount,
},
{
name: 'duration',
type: 'integer',
gte: 3,
value: duration,
},
{
name: 'resource',
type: 'resource',
value: resource,
msg: 'Invalid resource provided: Expected "BANDWIDTH" or "ENERGY"',
},
]);
const data: FreezeBalanceContract = {
owner_address: toHex(ownerAddress as string),
frozen_balance: parseInt(amount),
frozen_duration: parseInt(String(duration)),
};
if (resource !== 'BANDWIDTH') {
data.resource = resource as Resource;
}
if (isNotNullOrUndefined(receiverAddress) && toHex(receiverAddress as string) !== toHex(ownerAddress as string)) {
data.receiver_address = toHex(receiverAddress as string);
}
const transactionOptions = getTransactionOptions(options);
return createTransaction<FreezeBalanceContract>(this.tronWeb, ContractType.FreezeBalanceContract, data, options?.permissionId, transactionOptions);
}
async unfreezeBalance(
resource: Resource = 'BANDWIDTH',
address: string = this.tronWeb.defaultAddress.hex as string,
receiverAddress?: string,
options: TransactionCommonOptions = {}
): Promise<Transaction<UnfreezeBalanceContract>> {
this.validator.notValid([
{
name: 'origin',
type: 'address',
value: address as string,
},
{
name: 'receiver',
type: 'address',
value: receiverAddress as string,
optional: true,
},
{
name: 'resource',
type: 'resource',
value: resource,
msg: 'Invalid resource provided: Expected "BANDWIDTH" or "ENERGY"',
},
]);
const data: Partial<UnfreezeBalanceContract> = {
owner_address: toHex(address as string),
};
if (resource !== 'BANDWIDTH') {
data.resource = resource as Resource;
}
if (isNotNullOrUndefined(receiverAddress) && toHex(receiverAddress as string) !== toHex(address as string)) {
data.receiver_address = toHex(receiverAddress as string);
}
const transactionOptions = getTransactionOptions(options);
return createTransaction<UnfreezeBalanceContract>(this.tronWeb, ContractType.UnfreezeBalanceContract, data as UnfreezeBalanceContract, options?.permissionId, transactionOptions);
}
async freezeBalanceV2(
amount = 0,
resource: Resource = 'BANDWIDTH',
address: string = this.tronWeb.defaultAddress.hex as string,
options: TransactionCommonOptions = {}
): Promise<Transaction<FreezeBalanceV2Contract>> {
this.validator.notValid([
{
name: 'origin',
type: 'address',
value: address as string,
},
{
name: 'amount',
type: 'integer',
gt: 0,
value: amount,
},
{
name: 'resource',
type: 'resource',
value: resource as string,
msg: 'Invalid resource provided: Expected "BANDWIDTH" or "ENERGY"',
},
]);
const data: FreezeBalanceV2Contract = {
owner_address: toHex(address as string),
frozen_balance: parseInt(amount),
};
if (resource !== 'BANDWIDTH') {
data.resource = resource as Resource;
}
const transactionOptions = getTransactionOptions(options);
return createTransaction<FreezeBalanceV2Contract>(this.tronWeb, ContractType.FreezeBalanceV2Contract, data, options?.permissionId, transactionOptions);
}
async unfreezeBalanceV2(
amount = 0,
resource: Resource = 'BANDWIDTH',
address: string = this.tronWeb.defaultAddress.hex as string,
options: TransactionCommonOptions = {}
): Promise<Transaction<UnfreezeBalanceV2Contract>> {
this.validator.notValid([
{
name: 'origin',
type: 'address',
value: address as string,
},
{
name: 'amount',
type: 'integer',
gt: 0,
value: amount,
},
{
name: 'resource',
type: 'resource',
value: resource as string,
msg: 'Invalid resource provided: Expected "BANDWIDTH" or "ENERGY"',
},
]);
const data: UnfreezeBalanceV2Contract = {
owner_address: toHex(address as string),
unfreeze_balance: parseInt(amount),
};
if (resource !== 'BANDWIDTH') {
data.resource = resource as Resource;
}
const transactionOptions = getTransactionOptions(options);
return createTransaction<UnfreezeBalanceV2Contract>(this.tronWeb, ContractType.UnfreezeBalanceV2Contract, data, options?.permissionId, transactionOptions);
}
async cancelUnfreezeBalanceV2(
address: string = this.tronWeb.defaultAddress.hex as string,
options: TransactionCommonOptions = {}
): Promise<Transaction<CancelFreezeBalanceV2Contract>> {
this.validator.notValid([
{
name: 'origin',
type: 'address',
value: address as string,
},
]);
const data: CancelFreezeBalanceV2Contract = {
owner_address: toHex(address as string),
};
const transactionOptions = getTransactionOptions(options);
return createTransaction<CancelFreezeBalanceV2Contract>(this.tronWeb, ContractType.CancelAllUnfreezeV2Contract, data, options?.permissionId, transactionOptions);
}
async delegateResource(
amount = 0,
receiverAddress: string,
resource: Resource = 'BANDWIDTH',
address: string = this.tronWeb.defaultAddress.hex as string,
lock = false,
lockPeriod?: number,
options: TransactionCommonOptions = {}
): Promise<Transaction<DelegateResourceContract>> {
this.validator.notValid([
{
name: 'amount',
type: 'integer',
gt: 0,
value: amount,
},
{
name: 'resource',
type: 'resource',
value: resource as string,
msg: 'Invalid resource provided: Expected "BANDWIDTH" or "ENERGY"',
},
{
name: 'receiver',
type: 'address',
value: receiverAddress,
},
{
name: 'origin',
type: 'address',
value: address as string,
},
{
name: 'lock',
type: 'boolean',
value: lock as boolean,
},
{
name: 'lock period',
type: 'integer',
gte: 0,
value: lockPeriod as number,
optional: true,
},
]);
if (toHex(receiverAddress) === toHex(address as string)) {
throw new Error('Receiver address must not be the same as owner address');
}
const data: DelegateResourceContract = {
owner_address: toHex(address as string),
receiver_address: toHex(receiverAddress),
balance: parseInt(amount),
};
if (resource !== 'BANDWIDTH') {
data.resource = resource as Resource;
}
if (lock) {
data.lock = lock as boolean;
if (isNotNullOrUndefined(lockPeriod)) {
data.lock_period = lockPeriod as number;
}
}
const transactionOptions = getTransactionOptions(options);
return createTransaction<DelegateResourceContract>(this.tronWeb, ContractType.DelegateResourceContract, data, options?.permissionId, transactionOptions);
}
async undelegateResource(
amount = 0,
receiverAddress: string,
resource: Resource = 'BANDWIDTH',
address: string = this.tronWeb.defaultAddress.hex as string,
options: TransactionCommonOptions = {}
): Promise<Transaction<UnDelegateResourceContract>> {
this.validator.notValid([
{
name: 'origin',
type: 'address',
value: address as string,
},
{
name: 'receiver',
type: 'address',
value: receiverAddress,
},
{
name: 'amount',
type: 'integer',
gt: 0,
value: amount,
},
{
name: 'resource',
type: 'resource',
value: resource as string,
msg: 'Invalid resource provided: Expected "BANDWIDTH" or "ENERGY"',
},
]);
if (toHex(receiverAddress) === toHex(address as string)) {
throw new Error('Receiver address must not be the same as owner address');
}
const data: UnDelegateResourceContract = {
owner_address: toHex(address as string),
receiver_address: toHex(receiverAddress),
balance: parseInt(amount),
};
if (resource !== 'BANDWIDTH') {
data.resource = resource as Resource;
}
const transactionOptions = getTransactionOptions(options);
return createTransaction<UnDelegateResourceContract>(this.tronWeb, ContractType.UnDelegateResourceContract, data, options?.permissionId, transactionOptions);
}
async withdrawExpireUnfreeze(
address: string = this.tronWeb.defaultAddress.hex as string,
options: TransactionCommonOptions = {}
): Promise<Transaction<WithdrawExpireUnfreezeContract>> {
this.validator.notValid([
{
name: 'origin',
type: 'address',
value: address,
},
]);
const data: WithdrawExpireUnfreezeContract = {
owner_address: toHex(address),
};
const transactionOptions = getTransactionOptions(options);
return createTransaction<WithdrawExpireUnfreezeContract>(this.tronWeb, ContractType.WithdrawExpireUnfreezeContract, data, options?.permissionId, transactionOptions);
}
async withdrawBlockRewards(
address: string = this.tronWeb.defaultAddress.hex as string,
options: TransactionCommonOptions = {}
): Promise<Transaction<WithdrawBalanceContract>> {
this.validator.notValid([
{
name: 'origin',
type: 'address',
value: address as string,
},
]);
const data: WithdrawBalanceContract = {
owner_address: toHex(address as string),
};
const transactionOptions = getTransactionOptions(options);
return createTransaction<WithdrawBalanceContract>(this.tronWeb, ContractType.WithdrawBalanceContract, data, options?.permissionId, transactionOptions);
}
async applyForSR(
address: string = this.tronWeb.defaultAddress.hex as string,
url = '',
options: TransactionCommonOptions = {}
): Promise<Transaction<WitnessCreateContract>> {
this.validator.notValid([
{
name: 'origin',
type: 'address',
value: address,
},
{
name: 'url',
type: 'url',
value: url as string,
msg: 'Invalid url provided',
},
{
name: 'url',
type: 'string',
value: url as string,
lte: 256,
msg: 'Invalid url provided',
},
]);
const data: WitnessCreateContract = {
owner_address: toHex(address as string),
url: fromUtf8(url as string),
};
const transactionOptions = getTransactionOptions(options);
return createTransaction<WitnessCreateContract>(this.tronWeb, ContractType.WitnessCreateContract, data, options?.permissionId, transactionOptions);
}
async vote(
votes: VoteInfo = {},
voterAddress: string = this.tronWeb.defaultAddress.hex as string,
options: TransactionCommonOptions = {}
): Promise<Transaction<VoteWitnessContract>> {
this.validator.notValid([
{
name: 'voter',
type: 'address',
value: voterAddress as string,
},
{
name: 'votes',
type: 'notEmptyObject',
value: votes as VoteInfo,
},
]);
const entries = Object.entries(votes);
for (const [srAddress, voteCount] of entries) {
this.validator.notValid([
{
name: 'SR',
type: 'address',
value: srAddress,
},
{
name: 'vote count',
type: 'integer',
gt: 0,
value: voteCount,
msg: 'Invalid vote count provided for SR: ' + srAddress,
},
]);
}
const voteList = entries.map(([srAddress, voteCount]) => {
return {
vote_address: toHex(srAddress),
vote_count: parseInt(voteCount),
};
});
const data: VoteWitnessContract = {
owner_address: toHex(voterAddress as string),
votes: voteList,
};
const transactionOptions = getTransactionOptions(options);
return createTransaction<VoteWitnessContract>(this.tronWeb, ContractType.VoteWitnessContract, data, options?.permissionId, transactionOptions);
}
async createSmartContract(
options: CreateSmartContractOptions = {} as CreateSmartContractOptions,
issuerAddress: string = this.tronWeb.defaultAddress.hex as string
): Promise<CreateSmartContractTransaction> {
const feeLimit = options.feeLimit || this.tronWeb.feeLimit;
let userFeePercentage = options.userFeePercentage;
if (typeof userFeePercentage !== 'number' && !userFeePercentage) {
userFeePercentage = 100;
}
const originEnergyLimit = options.originEnergyLimit || 10_000_000;
const callValue = options.callValue || 0;
const tokenValue = options.tokenValue;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const tokenId = options.tokenId || options.token_id;
let { abi } = options;
const { parameters = [] } = options;
let parameter = '';
const { bytecode = false, name = '' } = options;
if (abi && isString(abi)) {
try {
abi = JSON.parse(abi);
} catch {
throw new Error('Invalid options.abi provided');
}
}
const newAbi = abi as { entrys: ContractAbiInterface } | ContractAbiInterface;
let entries: ContractAbiInterface | null = newAbi as ContractAbiInterface;
if ((newAbi as { entrys: ContractAbiInterface }).entrys) {
entries = (newAbi as { entrys: ContractAbiInterface }).entrys;
}
if (!isArray(entries)) throw new Error('Invalid options.abi provided');
const payable = entries.some((func) => {
return func.type === 'constructor' && 'payable' === (func as ConstructorFragment).stateMutability.toLowerCase();
});
this.validator.notValid([
{
name: 'bytecode',
type: 'hex',
value: bytecode,
},
{
name: 'feeLimit',
type: 'integer',
value: feeLimit,
gt: 0,
},
{
name: 'callValue',
type: 'integer',
value: callValue,
gte: 0,
},
{
name: 'userFeePercentage',
type: 'integer',
value: userFeePercentage,
gte: 0,
lte: 100,
},
{
name: 'originEnergyLimit',
type: 'integer',
value: originEnergyLimit,
gte: 0,
lte: 10_000_000,
},
{
name: 'parameters',
type: 'array',
value: parameters,
},
{
name: 'issuer',
type: 'address',
value: issuerAddress,
},
{
name: 'tokenValue',
type: 'integer',
value: tokenValue,
gte: 0,
optional: true,
},
{
name: 'tokenId',
type: 'integer',
value: tokenId,
gte: 0,
optional: true,
},
]);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (!payable && (callValue > 0 || tokenValue > 0))
throw new Error('When contract is not payable, options.callValue and options.tokenValue must be 0');
const { rawParameter, funcABIV2, parametersV2 } = options as any;
if (rawParameter && isString(rawParameter)) {
parameter = rawParameter.replace(/^(0x)/, '');
} else if (funcABIV2) {
parameter = encodeParamsV2ByABI(funcABIV2, parametersV2).replace(/^(0x)/, '');
} else {
let constructorParams: any = entries.find((it: any) => {
return it.type === 'constructor';
});
if (typeof constructorParams !== 'undefined' && constructorParams) {
const abiCoder = new AbiCoder();
const types = [];
const values = [];
constructorParams = constructorParams.inputs;
if (parameters.length != constructorParams.length)
throw new Error(`constructor needs ${constructorParams.length} but ${parameters.length} provided`);
for (let i = 0; i < parameters.length; i++) {
let type = constructorParams[i].type;
let value: any = parameters[i];
if (!type || !isString(type) || !type.length) throw new Error('Invalid parameter type provided: ' + type);
const replaceAddressPrefix = (value: unknown): any => {
if (isArray(value)) {
return value.map((v) => replaceAddressPrefix(v));
}
return toHex(value as string).replace(ADDRESS_PREFIX_REGEX, '0x');
};
if (type === 'address') value = replaceAddressPrefix(value);
else if (type.match(/^([^\x5b]*)(\x5b|$)/)?.[0] === 'address[') value = replaceAddressPrefix(value);
else if (/trcToken/.test(type)) {
type = type.replace(/trcToken/, 'uint256');
}
types.push(type);
values.push(value);
}
try {
parameter = abiCoder.encode(types, values).replace(/^(0x)/, '');
} catch (ex) {
throw new Error(ex as string);
}
} else {
parameter = '';
}
}
const args: any = {
owner_address: toHex(issuerAddress),
fee_limit: parseInt(feeLimit),
call_value: parseInt(callValue),
consume_user_resource_percent: userFeePercentage,
origin_energy_limit: originEnergyLimit,
abi: JSON.stringify(entries),
bytecode,
parameter,
name,
};
// tokenValue and tokenId can cause errors if provided when the trx10 proposal has not been approved yet. So we set them only if they are passed to the method.
if (isNotNullOrUndefined(tokenValue)) {
args.call_token_value = parseInt(tokenValue as number);
}
if (isNotNullOrUndefined(tokenId)) {
args.token_id = parseInt(tokenId);
}
const contract: CreateSmartContract = {} as CreateSmartContract;
contract.owner_address = args.owner_address;
if (isNotNullOrUndefined(args.call_token_value)) {
contract.call_token_value = args.call_token_value;
}
if (isNotNullOrUndefined(args.token_id)) {
contract.token_id = args.token_id;
}
const new_contract = (contract.new_contract = {} as CreateSmartContract['new_contract']);
if (args.abi) {
new_contract.abi = {
entrys: JSON.parse(args.abi),
};
} else {
new_contract.abi = {};
}
if (args.call_value) {
new_contract.call_value = args.call_value;
}
new_contract.consume_user_resource_percent = args.consume_user_resource_percent;
new_contract.origin_energy_limit = args.origin_energy_limit;
new_contract.origin_address = args.origin_address ?? args.owner_address;
if (args.bytecode + args.parameter) {
new_contract.bytecode = (args.bytecode + args.parameter).replace(/^0x/, '');
}
if (isNotNullOrUndefined(args.name)) {
new_contract.name = args.name;
}
const transactionOptions = getTransactionOptions(options);
const tx = (await createTransaction(this.tronWeb, ContractType.CreateSmartContract, contract, options?.permissionId, {
...transactionOptions,
fee_limit: args.fee_limit,
})) as CreateSmartContractTransaction;
tx.contract_address = genContractAddress(args.owner_address, tx.txID);
return tx;
}
async triggerSmartContract(
contractAddress: string,
functionSelector: string,
options?: TriggerSmartContractOptions,
parameters?: ContractFunctionParameter[],
issuerAddress?: string
): Promise<TransactionWrapper> {
const params: Parameters<typeof this._triggerSmartContractLocal> = [
contractAddress,
functionSelector,
options,
parameters,
issuerAddress,
];
if (typeof params[2] !== 'object') {
params[2] = {
feeLimit: params[2],
callValue: params[3] as unknown as number,
};
params.splice(3, 1);
}
if (params[2]?.txLocal) {
return this._triggerSmartContractLocal(...params);
}
return this._triggerSmartContract(...params);
}
async triggerConstantContract(
contractAddress: string,
functionSelector: string,
options: TriggerConstantContractOptions = {},
parameters: ContractFunctionParameter[] = [],
issuerAddress: string = this.tronWeb.defaultAddress.hex as string
): Promise<TransactionWrapper> {
options._isConstant = true;
return this._triggerSmartContract(contractAddress, functionSelector, options, parameters, issuerAddress);
}
async triggerConfirmedConstantContract(
contractAddress: string,
functionSelector: string,
options: TriggerConstantContractOptions = {},
parameters: ContractFunctionParameter[] = [],
issuerAddress: string = this.tronWeb.defaultAddress.hex as string
): Promise<TransactionWrapper> {
options._isConstant = true;
options.confirmed = true;
return this._triggerSmartContract(contractAddress, functionSelector, options, parameters, issuerAddress);
}
async estimateEnergy(
contractAddress: string,
functionSelector: string,
options: TriggerConstantContractOptions = {},
parameters: ContractFunctionParameter[] = [],
issuerAddress: string = this.tronWeb.defaultAddress.hex as string
): Promise<{ result: { result: boolean }; energy_required: number }> {
options.estimateEnergy = true;
const result = await this._triggerSmartContract(contractAddress, functionSelector, options, parameters, issuerAddress);
return result as { result: { result: boolean }; energy_required: number };
}
async deployConstantContract(options: DeployConstantContractOptions = { input: '', ownerAddress: '' }) {
const { input, ownerAddress, tokenId, tokenValue, callValue = 0 } = options;
this.validator.notValid([
{
name: 'input',
type: 'not-empty-string',
value: input,
},
{
name: 'callValue',
type: 'integer',
value: callValue,
gte: 0,
},
{
name: 'owner',
type: 'address',
value: ownerAddress,
},
{
name: 'tokenValue',
type: 'integer',
value: tokenValue,
gte: 0,
optional: true,
},
{
name: 'tokenId',
type: 'integer',
value: tokenId,
gte: 0,
optional: true,
},
]);
const args: DeployConstantContract = {
data: input,
owner_address: toHex(ownerAddress),
call_value: callValue,
};
if (tokenId) {
args.token_id = tokenId;
}
if (tokenValue) {
args.call_token_value = tokenValue;
}
const pathInfo = `wallet${options.confirmed ? 'solidity' : ''}/estimateenergy`;
const transaction: TransactionWrapper = await this.tronWeb[options.confirmed ? 'solidityNode' : 'fullNode'].request(
pathInfo,
args,
'post'
);
if (transaction.Error) throw new Error(transaction.Error);
if (transaction.result && transaction.result.message) {
throw new Error(this.tronWeb.toUtf8(transaction.result.message));
}
return transaction as { result: { result: boolean }; energy_required: number };
}
_getTriggerSmartContractArgs(
contractAddress: string,
functionSelector: string,
options: TriggerConstantContractOptions,
parameters: ContractFunctionParameter[],
issuerAddress: string,
tokenValue?: number,
tokenId?: string,
callValue?: number,
feeLimit?: number
) {
const args: IArgs = {
contract_address: toHex(contractAddress),
owner_address: toHex(issuerAddress),
};
if (functionSelector && isString(functionSelector)) {
functionSelector = functionSelector.replace(/\s*/g, '');
let parameterStr;
if (parameters.length) {
const abiCoder = new AbiCoder();
let types = [];
const values = [];
for (let i = 0; i < parameters.length; i++) {
let { value } = parameters[i];
const { type } = parameters[i];
if (!type || !isString(type) || !type.length) throw new Error('Invalid parameter type provided: ' + type);
const replaceAddressPrefix = (value: unknown): any => {
if (isArray(value)) {
return value.map((v) => replaceAddressPrefix(v));
}
return toHex(value as string).replace(ADDRESS_PREFIX_REGEX, '0x');
};
if (type === 'address') value = replaceAddressPrefix(value);
else if (type.match(/^([^\x5b]*)(\x5b|$)/)?.[0] === 'address[') value = replaceAddressPrefix(value);
types.push(type);
values.push(value);
}
try {
// workaround for unsupported trcToken type
types = types.map((type) => {
if (/trcToken/.test(type)) {
type = type.replace(/trcToken/, 'uint256');
}
return type;
});
parameterStr = abiCoder.encode(types, values).replace(/^(0x)/, '');
} catch (ex) {
throw new Error(ex as string);
}
} else parameterStr = '';
// work for abiv2 if passed the function abi in options
if (options.funcABIV2) {
parameterStr = encodeParamsV2ByABI(
options.funcABIV2 as FunctionFragment,
options.parametersV2 as unknown[]
).replace(/^(0x)/, '');
}
if (options.shieldedParameter && isString(options.shieldedParameter)) {
parameterStr = options.shieldedParameter.replace(/^(0x)/, '');
}
if (options.rawParameter && isString(options.rawParameter)) {
parameterStr = options.rawParameter.replace(/^(0x)/, '');
}
args.function_selector = functionSelector;
args.parameter = parameterStr;
} else if (options.input) {
args.data = options.input;
}
args.call_value = parseInt(callValue as number);
if (isNotNullOrUndefined(tokenValue)) args.call_token_value = parseInt(tokenValue as number);
if (isNotNullOrUndefined(tokenId)) args.token_id = parseInt(tokenId as string);
if (!(options._isConstant || options.estimateEnergy)) {
args.fee_limit = parseInt(feeLimit as number);
}
if (options.permissionId) {
args.Permission_id = options.permissionId;
}
return args;
}
async _triggerSmartContractLocal(
contractAddress: string,
functionSelector: string,
options: TriggerConstantContractOptions = {},
parameters: ContractFunctionParameter[] = [],
issuerAddress = this.tronWeb.defaultAddress.hex as string
) {
const { tokenValue, tokenId, callValue, feeLimit } = Object.assign(
{
callValue: 0,
feeLimit: this.tronWeb.feeLimit,
},
options
);
this.validator.notValid([
{
name: 'feeLimit',
type: 'integer',
value: feeLimit,
gt: 0,
},
{
name: 'callValue',
type: 'integer',
value: callValue,
gte: 0,
},
{
name: 'parameters',
type: 'array',
value: parameters,
},
{
name: 'contract',
type: 'address',
value: contractAddress,
},
{
name: 'issuer',
type: 'address',
value: issuerAddress,
optional: true,
},
{
name: 'tokenValue',
type: 'integer',
value: tokenValue,
gte: 0,
optional: true,
},
{
name: 'tokenId',
type: 'integer',
value: tokenId,
gte: 0,
optional: true,
},
]);
const args = this._getTriggerSmartContractArgs(
contractAddress,
functionSelector,
options,
parameters,
issuerAddress,
tokenValue,
tokenId,
callValue,
feeLimit
);
if (args.function_selector) {
args.data = keccak256(Buffer.from(args.function_selector, 'utf-8')).toString().substring(2, 10) + args.parameter;
}
const value: TriggerSmartContract = {
data: args.data,
owner_address: args.owner_address,
contract_address: args.contract_address,
};
if (args.call_value) {
value.call_value = args.call_value;
}
if (args.call_token_value) {
value.call_token_value = args.call_token_value;
}
if (args.token_id) {
value.token_id = args.token_id;
}
const transactionOptions = getTransactionOptions(options);
const transaction = await createTransaction<TriggerSmartContract>(
this.tronWeb,
ContractType.TriggerSmartContract,
value,
options.permissionId,
{
...transactionOptions,
fee_limit: args.fee_limit,
}
);
return {
result: {
result: true,
},
transaction,
};
}
async _triggerSmartContract(
contractAddress: string,
functionSelector: string,
options: TriggerConstantContractOptions = {},
parameters: ContractFunctionParameter[] = [],
issuerAddress: string = this.tronWeb.defaultAddress.hex as string
) {
const { tokenValue, tokenId, callValue, feeLimit } = Object.assign(
{
callValue: 0,
feeLimit: this.tronWeb.feeLimit,
},
options
);
this.validator.notValid([
{
name: 'feeLimit',
type: 'integer',
value: feeLimit,
gt: 0,
},
{
name: 'callValue',
type: 'integer',
value: callValue,
gte: 0,
},
{
name: 'parameters',
type: 'array',
value: parameters,
},
{
name: 'contract',
type: 'address',
value: contractAddress,
},
{
name: 'issuer',
type: 'address',
value: issuerAddress,
optional: true,
},
{
name: 'tokenValue',
type: 'integer',
value: tokenValue,
gte: 0,
optional: true,
},
{
name: 'tokenId',
type: 'integer',
value: tokenId,
gte: 0,
optional: true,
},
]);
const args = this._getTriggerSmartContractArgs(
contractAddress,
functionSelector,
options,
parameters,
issuerAddress,
tokenValue,
tokenId,
callValue,
feeLimit
);
let pathInfo = 'triggersmartcontract';
if (options._isConstant) {
pathInfo = 'triggerconstantcontract';
} else if (options.estimateEnergy) {
pathInfo = 'estimateenergy';
}
pathInfo = `wallet${options.confirmed ? 'solidity' : ''}/${pathInfo}`;
const transaction: TransactionWrapper = await this.tronWeb[options.confirmed ? 'solidityNode' : 'fullNode'].request(
pathInfo,
args,
'post'
);
return resultManagerTriggerSmartContract(transaction, args, options);
}
async clearABI(
contractAddress: string,
ownerAddress: string = this.tronWeb.defaultAddress.hex as string,
options: TransactionCommonOptions = {}
): Promise<Transaction<ClearABIContract>> {
if (!TronWeb.isAddress(contractAddress)) throw new Error('Invalid contract address provided');
if (!TronWeb.isAddress(ownerAddress)) throw new Error('Invalid owner address provided');
const data: ClearABIContract = {
contract_address: toHex(contractAddress),
owner_address: toHex(ownerAddress as string),
};
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (this.tronWeb.trx.cache.contracts[contractAddress]) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete this.tronWeb.trx.cache.contracts[contractAddress];
}
const transactionOptions = getTransactionOptions(options);
return createTransaction<ClearABIContract>(this.tronWeb, ContractType.ClearABIContract, data, options?.permissionId, transactionOptions);
}
async updateBrokerage(
brokerage: number,
ownerAddress: string = this.tronWeb.defaultAddress.hex as string,
options: TransactionCommonOptions = {}
): Promise<Transaction<UpdateBrokerageContract>> {
if (!isNotNullOrUndefined(brokerage)) throw new Error('Invalid brokerage provided');
if (!isInteger(brokerage) || brokerage < 0 || brokerage > 100)
throw new Error('Brokerage must be an integer between 0 and 100');
if (!TronWeb.isAddress(ownerAddress)) throw new Error('Invalid owner address provided');
const data: UpdateBrokerageContract = {
brokerage: parseInt(brokerage),
owner_address: toHex(ownerAddress as string),
};
const transactionOptions = getTransactionOptions(options);
return createTransaction<UpdateBrokerageContract>(this.tronWeb, ContractType.UpdateBrokerageContract, data, options?.permissionId, transactionOptions);
}
async createToken(
options: CreateTokenOptions = {} as CreateTokenOptions,
issuerAddress: string = this.tronWeb.defaultAddress.hex as string
): Promise<Transaction<AssetIssueContract>> {
const {
name = false,
abbreviation = false,
description = '',
url = false,
totalSupply = 0,
trxRatio = 1, // How much TRX will `tokenRatio` cost
tokenRatio = 1, // How many tokens will `trxRatio` afford
saleStart = Date.now(),
saleEnd = false,
freeBandwidth = 0, // The creator's "donated" bandwidth for use by token holders
freeBandwidthLimit = 0, // Out of `totalFreeBandwidth`, the amount each token holder get
frozenAmount = 0,
frozenDuration = 0,
// for now there is no default for the following values
voteScore,
precision,
} = options;
this.validator.notValid([
{
name: 'Supply amount',
type: 'positive-integer',
value: totalSupply,
},
{
name: 'TRX ratio',
type: 'positive-integer',
value: trxRatio,
},
{
name: 'Token ratio',
type: 'positive-integer',
value: tokenRatio,
},
{
name: 'token abbreviation',
type: 'string',
value: abbreviation,
lte: 32,
gt: 0,
},
{
name: 'token name',
type: 'not-empty-string',
value: name,
},
{
name: 'token description',
type: 'string',
value: description,
lte: 200,
},
{
name: 'token url',
type: 'url',
value: url,
},
{
name: 'token url',
type: 'string',
value: url,
lte: 256,
},
{
name: 'issuer',
type: 'address',
value: issuerAddress,
},
{
name: 'sale start timestamp',
type: 'integer',
value: saleStart,
gte: Date.now(),
},
{
name: 'sale end timestamp',
type: 'integer',
value: saleEnd,
gt: saleStart,
},
{
name: 'Frozen supply',
type: 'integer',
value: frozenAmount,
gte: 0,
},
{
name: 'Frozen duration',
type: 'integer',
value: frozenDuration,
gte: 0,
},
]);
if (isNotNullOrUndefined(voteScore) && (!isInteger(voteScore) || voteScore <= 0))
throw new Error('voteScore must be a positive integer greater than 0');
if (isNotNullOrUndefined(precision) && (!isInteger(precision) || precision < 0 || precision > 6))
throw new Error('precision must be a positive integer >= 0 and <= 6');
const data: Partial<AssetIssueContract> = {
owner_address: toHex(issuerAddress),
name: fromUtf8(name as s