@mapprotocol/common-contracts 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.evmDeployByFactory = evmDeployByFactory;
4
+ exports.evmGetFactoryAddress = evmGetFactoryAddress;
5
+ exports.tronDeployByFactory = tronDeployByFactory;
6
+ exports.tronGetFactoryAddress = tronGetFactoryAddress;
7
+ /**
8
+ * Factory deployment — deterministic contract addresses across chains.
9
+ * EVM: CREATE3 factory at 0x6258e4d2950757A749a4d4683A7342261ce12471
10
+ * Tron: CREATE2 factory at TJAwD5VfMYMGWwdPi2aVcoQwVBfsuw5wQt
11
+ */
12
+ const tronHelper_1 = require("./tronHelper");
13
+ // Factory contract addresses
14
+ const EVM_FACTORY = "0x6258e4d2950757A749a4d4683A7342261ce12471";
15
+ const TRON_FACTORY = "TJAwD5VfMYMGWwdPi2aVcoQwVBfsuw5wQt";
16
+ // EVM factory ABI (CREATE3 — getAddress only needs salt)
17
+ // function deploy(bytes32 salt, bytes creationCode, uint256 value)
18
+ // function getAddress(bytes32 salt) view returns (address)
19
+ const EVM_FACTORY_ABI = [
20
+ {
21
+ "inputs": [{ "name": "salt", "type": "bytes32" }, { "name": "creationCode", "type": "bytes" }, { "name": "value", "type": "uint256" }],
22
+ "name": "deploy",
23
+ "outputs": [],
24
+ "stateMutability": "nonpayable",
25
+ "type": "function"
26
+ },
27
+ {
28
+ "inputs": [{ "name": "salt", "type": "bytes32" }],
29
+ "name": "getAddress",
30
+ "outputs": [{ "name": "", "type": "address" }],
31
+ "stateMutability": "view",
32
+ "type": "function"
33
+ }
34
+ ];
35
+ // Tron factory ABI (CREATE2 — getAddress needs salt + codeHash)
36
+ // function deploy(bytes32 salt, bytes creationCode, uint256 value) returns (address)
37
+ // function getAddress(bytes32 salt, bytes32 codeHash) view returns (address)
38
+ // function getAddressTron(bytes32 salt, bytes32 codeHash) view returns (address)
39
+ const TRON_FACTORY_ABI = [
40
+ {
41
+ "inputs": [{ "name": "salt", "type": "bytes32" }, { "name": "creationCode", "type": "bytes" }, { "name": "value", "type": "uint256" }],
42
+ "name": "deploy",
43
+ "outputs": [{ "name": "addr", "type": "address" }],
44
+ "stateMutability": "nonpayable",
45
+ "type": "function"
46
+ },
47
+ {
48
+ "inputs": [{ "name": "salt", "type": "bytes32" }, { "name": "codeHash", "type": "bytes32" }],
49
+ "name": "getAddressTron",
50
+ "outputs": [{ "name": "", "type": "address" }],
51
+ "stateMutability": "view",
52
+ "type": "function"
53
+ }
54
+ ];
55
+ // ============================================================
56
+ // EVM Factory (CREATE3, ethers.js)
57
+ // ============================================================
58
+ /**
59
+ * Deploy contract via CREATE3 factory on EVM chains.
60
+ * Address only depends on salt, not bytecode.
61
+ * @param ethers - ethers from hardhat runtime (hre.ethers)
62
+ * @param salt - human-readable salt string
63
+ * @param bytecode - contract bytecode (artifact.bytecode)
64
+ * @param constructorArgs - ABI-encoded constructor arguments
65
+ * @returns deployed contract address
66
+ */
67
+ async function evmDeployByFactory(ethers, salt, bytecode, constructorArgs = "0x") {
68
+ const [signer] = await ethers.getSigners();
69
+ const factory = new ethers.Contract(EVM_FACTORY, EVM_FACTORY_ABI, signer);
70
+ const code = await ethers.provider.getCode(EVM_FACTORY);
71
+ if (code === "0x")
72
+ throw new Error("factory not deployed on this chain");
73
+ const saltHash = ethers.keccak256(ethers.toUtf8Bytes(salt));
74
+ const predicted = await factory.getAddress(saltHash);
75
+ const existingCode = await ethers.provider.getCode(predicted);
76
+ if (existingCode !== "0x") {
77
+ console.log(`already deployed at ${predicted}`);
78
+ return predicted;
79
+ }
80
+ const fullBytecode = constructorArgs === "0x"
81
+ ? bytecode
82
+ : ethers.concat([bytecode, constructorArgs]);
83
+ const tx = await factory.deploy(saltHash, fullBytecode, 0);
84
+ await tx.wait();
85
+ // Verify contract was actually deployed
86
+ const deployedCode = await ethers.provider.getCode(predicted);
87
+ if (deployedCode === "0x") {
88
+ throw new Error(`factory deploy failed: no contract at predicted address ${predicted}`);
89
+ }
90
+ console.log(`deployed via factory at ${predicted}`);
91
+ return predicted;
92
+ }
93
+ /**
94
+ * Get predicted factory address for a salt on EVM (CREATE3 — only needs salt).
95
+ * @param ethers - ethers from hardhat runtime
96
+ * @param salt - human-readable salt string
97
+ */
98
+ async function evmGetFactoryAddress(ethers, salt) {
99
+ const factory = new ethers.Contract(EVM_FACTORY, EVM_FACTORY_ABI, await ethers.provider);
100
+ const saltHash = ethers.keccak256(ethers.toUtf8Bytes(salt));
101
+ return factory.getAddress(saltHash);
102
+ }
103
+ // ============================================================
104
+ // Tron Factory (CREATE2, tronweb)
105
+ // ============================================================
106
+ /**
107
+ * Deploy contract via CREATE2 factory on Tron.
108
+ * Address depends on salt + creationCode (bytecode + constructor args).
109
+ * @param tronWeb - initialized tronweb instance
110
+ * @param artifacts - hardhat artifacts
111
+ * @param contractName - contract name to deploy
112
+ * @param salt - human-readable salt string
113
+ * @param args - constructor arguments array
114
+ * @param feeLimit - tron fee limit
115
+ * @returns deployed contract address (0x-prefixed hex)
116
+ */
117
+ async function tronDeployByFactory(tronWeb, artifacts, contractName, salt, args = [], feeLimit = 15000000000) {
118
+ const factory = await tronWeb.contract(TRON_FACTORY_ABI, TRON_FACTORY);
119
+ const saltHash = tronWeb.sha3(salt);
120
+ // Build creation code with constructor args
121
+ const artifact = await artifacts.readArtifact(contractName);
122
+ let creationCode = artifact.bytecode;
123
+ if (args.length > 0) {
124
+ const iface = new (require("ethers").Interface)(artifact.abi);
125
+ const encoded = iface.encodeDeploy(args);
126
+ creationCode = creationCode + encoded.slice(2);
127
+ }
128
+ // Check if already deployed
129
+ const codeHash = tronWeb.sha3(creationCode);
130
+ const predicted = await factory.getAddressTron(saltHash, codeHash).call();
131
+ const predictedHex = predicted.replace(/^41/, "0x");
132
+ try {
133
+ const existing = await tronWeb.trx.getContract(tronWeb.address.fromHex(predicted));
134
+ if (existing && existing.bytecode) {
135
+ console.log(`already deployed at ${(0, tronHelper_1.tronFromHex)(predicted)}`);
136
+ return predictedHex;
137
+ }
138
+ }
139
+ catch { }
140
+ // Deploy
141
+ console.log(`deploying ${contractName} via factory with salt "${salt}"...`);
142
+ const { sendAndWait } = require("./tronHelper");
143
+ const txResult = await sendAndWait(factory.deploy(saltHash, creationCode, 0), tronWeb, { feeLimit });
144
+ // Read actual address from Deployed event
145
+ let addrHex = predictedHex; // fallback to predicted
146
+ if (txResult.log && txResult.log.length > 0) {
147
+ // Deployed(address indexed addr, bytes32 indexed salt)
148
+ // First topic = event sig, second topic = addr
149
+ const addrTopic = txResult.log[0].topics?.[1];
150
+ if (addrTopic) {
151
+ addrHex = "0x" + addrTopic.slice(24); // last 20 bytes
152
+ }
153
+ }
154
+ // Verify contract was actually deployed
155
+ const addrBase58 = (0, tronHelper_1.tronFromHex)(addrHex);
156
+ try {
157
+ const deployed = await tronWeb.trx.getContract(addrBase58);
158
+ if (!deployed || !deployed.bytecode) {
159
+ throw new Error(`factory deploy failed: no contract at ${addrBase58}`);
160
+ }
161
+ }
162
+ catch (e) {
163
+ if (e.message?.includes("factory deploy failed"))
164
+ throw e;
165
+ // Contract might not be indexed yet, wait and retry
166
+ await new Promise(r => setTimeout(r, 3000));
167
+ const deployed = await tronWeb.trx.getContract(addrBase58);
168
+ if (!deployed || !deployed.bytecode) {
169
+ throw new Error(`factory deploy failed: no contract at ${addrBase58}`);
170
+ }
171
+ }
172
+ console.log(`${contractName} deployed: ${addrBase58} (${addrHex})`);
173
+ return addrHex;
174
+ }
175
+ /**
176
+ * Get predicted factory address for a salt on Tron (CREATE2 — needs salt + codeHash).
177
+ * @param tronWeb - initialized tronweb instance
178
+ * @param salt - human-readable salt string
179
+ * @param codeHash - keccak256 of creationCode (bytecode + constructor args)
180
+ * @returns address in base58 format
181
+ */
182
+ async function tronGetFactoryAddress(tronWeb, salt, codeHash) {
183
+ const factory = await tronWeb.contract(TRON_FACTORY_ABI, TRON_FACTORY);
184
+ const saltHash = tronWeb.sha3(salt);
185
+ const addr = await factory.getAddressTron(saltHash, codeHash).call();
186
+ return (0, tronHelper_1.tronFromHex)(addr);
187
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @mapprotocol/common-contracts/utils
3
+ *
4
+ * Shared TypeScript utilities for MAP Protocol contract operations.
5
+ *
6
+ * Quick start:
7
+ * const deployer = createDeployer(hre, { autoVerify: true });
8
+ * await deployer.deploy("MyContract", [arg1], "optional_salt");
9
+ * await deployer.deployProxy("MyContract", [initArg], "optional_salt");
10
+ * await deployer.upgrade("MyContract", proxyAddr);
11
+ */
12
+ export { createDeployer, type Deployer, type DeployResult, type DeployProxyResult, type DeployerOptions } from "./deployer";
13
+ export { resolveDeploymentPath, getDeploymentByKey, hasDeployment, saveDeployment, type DeploymentPath, type DeploymentOptions } from "./deployRecord";
14
+ export { verify, type VerifyOptions } from "./verifier";
15
+ export { getCodeHash, getProxyCodeHash, getCustomProxyCodeHash } from "./codeHash";
16
+ export { addressToHex, isBase58, isTronAddress, isSolanaChain } from "./addressCodec";
17
+ export { TronClient, tronFromHex, tronToHex, isTronNetwork, sendAndWait, waitForTx, type TronAddress } from "./tronHelper";
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ /**
3
+ * @mapprotocol/common-contracts/utils
4
+ *
5
+ * Shared TypeScript utilities for MAP Protocol contract operations.
6
+ *
7
+ * Quick start:
8
+ * const deployer = createDeployer(hre, { autoVerify: true });
9
+ * await deployer.deploy("MyContract", [arg1], "optional_salt");
10
+ * await deployer.deployProxy("MyContract", [initArg], "optional_salt");
11
+ * await deployer.upgrade("MyContract", proxyAddr);
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.waitForTx = exports.sendAndWait = exports.isTronNetwork = exports.tronToHex = exports.tronFromHex = exports.TronClient = exports.isSolanaChain = exports.isTronAddress = exports.isBase58 = exports.addressToHex = exports.getCustomProxyCodeHash = exports.getProxyCodeHash = exports.getCodeHash = exports.verify = exports.saveDeployment = exports.hasDeployment = exports.getDeploymentByKey = exports.resolveDeploymentPath = exports.createDeployer = void 0;
15
+ // Unified deployer — recommended entry point for deploy/upgrade/verify
16
+ var deployer_1 = require("./deployer");
17
+ Object.defineProperty(exports, "createDeployer", { enumerable: true, get: function () { return deployer_1.createDeployer; } });
18
+ // Deploy record — deploy.json address management
19
+ var deployRecord_1 = require("./deployRecord");
20
+ Object.defineProperty(exports, "resolveDeploymentPath", { enumerable: true, get: function () { return deployRecord_1.resolveDeploymentPath; } });
21
+ Object.defineProperty(exports, "getDeploymentByKey", { enumerable: true, get: function () { return deployRecord_1.getDeploymentByKey; } });
22
+ Object.defineProperty(exports, "hasDeployment", { enumerable: true, get: function () { return deployRecord_1.hasDeployment; } });
23
+ Object.defineProperty(exports, "saveDeployment", { enumerable: true, get: function () { return deployRecord_1.saveDeployment; } });
24
+ // Contract verification
25
+ var verifier_1 = require("./verifier");
26
+ Object.defineProperty(exports, "verify", { enumerable: true, get: function () { return verifier_1.verify; } });
27
+ // Code hash for CREATE2 address prediction
28
+ var codeHash_1 = require("./codeHash");
29
+ Object.defineProperty(exports, "getCodeHash", { enumerable: true, get: function () { return codeHash_1.getCodeHash; } });
30
+ Object.defineProperty(exports, "getProxyCodeHash", { enumerable: true, get: function () { return codeHash_1.getProxyCodeHash; } });
31
+ Object.defineProperty(exports, "getCustomProxyCodeHash", { enumerable: true, get: function () { return codeHash_1.getCustomProxyCodeHash; } });
32
+ // Address encoding
33
+ var addressCodec_1 = require("./addressCodec");
34
+ Object.defineProperty(exports, "addressToHex", { enumerable: true, get: function () { return addressCodec_1.addressToHex; } });
35
+ Object.defineProperty(exports, "isBase58", { enumerable: true, get: function () { return addressCodec_1.isBase58; } });
36
+ Object.defineProperty(exports, "isTronAddress", { enumerable: true, get: function () { return addressCodec_1.isTronAddress; } });
37
+ Object.defineProperty(exports, "isSolanaChain", { enumerable: true, get: function () { return addressCodec_1.isSolanaChain; } });
38
+ // Tron utilities
39
+ var tronHelper_1 = require("./tronHelper");
40
+ Object.defineProperty(exports, "TronClient", { enumerable: true, get: function () { return tronHelper_1.TronClient; } });
41
+ Object.defineProperty(exports, "tronFromHex", { enumerable: true, get: function () { return tronHelper_1.tronFromHex; } });
42
+ Object.defineProperty(exports, "tronToHex", { enumerable: true, get: function () { return tronHelper_1.tronToHex; } });
43
+ Object.defineProperty(exports, "isTronNetwork", { enumerable: true, get: function () { return tronHelper_1.isTronNetwork; } });
44
+ Object.defineProperty(exports, "sendAndWait", { enumerable: true, get: function () { return tronHelper_1.sendAndWait; } });
45
+ Object.defineProperty(exports, "waitForTx", { enumerable: true, get: function () { return tronHelper_1.waitForTx; } });
@@ -0,0 +1,127 @@
1
+ /** Convert hex address to Tron base58 format. Returns as-is if already base58. */
2
+ export declare function tronFromHex(hex: string): string;
3
+ /** Convert Tron base58 address to 0x-prefixed hex. Returns as-is if already hex. */
4
+ export declare function tronToHex(addr: string): string;
5
+ /** Check if a network name or chainId belongs to Tron (case-insensitive). */
6
+ export declare function isTronNetwork(networkOrChainId: string | number): boolean;
7
+ export interface TronAddress {
8
+ hex: string;
9
+ base58: string;
10
+ }
11
+ /**
12
+ * TronClient — encapsulates tronWeb instance, provides high-level contract operations.
13
+ *
14
+ * Usage:
15
+ * // From hardhat runtime (recommended in tasks)
16
+ * let client = TronClient.fromHre(hre);
17
+ *
18
+ * // Manual — with rpc and private key
19
+ * let client = new TronClient("https://api.trongrid.io", "your_private_key");
20
+ *
21
+ * // Read-only — no private key
22
+ * let client = new TronClient("https://api.trongrid.io");
23
+ *
24
+ * // Contract interaction
25
+ * let gw = await client.getContract(artifacts, "Gateway", addr);
26
+ * let wtoken = await gw.wToken().call(); // read
27
+ * await gw.setWtoken(client.toHex(wtoken)).sendAndWait(); // write + wait
28
+ *
29
+ * // Deploy
30
+ * let { hex, base58 } = await client.deploy(artifacts, "Gateway");
31
+ * let { proxy, implementation } = await client.deployProxy(artifacts, "Gateway", [admin]);
32
+ *
33
+ * // Upgrade
34
+ * await client.upgradeProxy(artifacts, "Gateway", proxyAddr);
35
+ */
36
+ export declare class TronClient {
37
+ private tronWeb;
38
+ private connected;
39
+ /**
40
+ * Create a TronClient with explicit rpcUrl.
41
+ * @param rpcUrl - Tron full host URL (e.g. "https://api.trongrid.io")
42
+ * @param privateKey - optional, omit for read-only operations
43
+ */
44
+ constructor(rpcUrl: string, privateKey?: string);
45
+ /**
46
+ * Create TronClient from hardhat runtime environment.
47
+ * Reads rpcUrl and privateKey from hre.network.config. Verifies RPC connectivity.
48
+ */
49
+ static fromHre(hre: any): TronClient;
50
+ /** Verify RPC is reachable. Uses getNowBlock which is widely supported. */
51
+ private _checkConnection;
52
+ /** Lazy connection check — called before first contract operation. */
53
+ private _ensureConnected;
54
+ /** Get the default operator address in base58 format. */
55
+ get defaultAddress(): string;
56
+ /** Convert Tron base58 address to 0x hex. */
57
+ toHex(addr: string): string;
58
+ /** Convert 0x hex to Tron base58 address. */
59
+ fromHex(hex: string): string;
60
+ /**
61
+ * Get a contract instance. Write methods have .sendAndWait() attached.
62
+ * @param artifacts - hardhat artifacts (hre.artifacts)
63
+ * @param contractName - contract name (e.g. "Gateway")
64
+ * @param addr - contract address (base58 or hex)
65
+ */
66
+ getContract(artifacts: any, contractName: string, addr: string): Promise<any>;
67
+ /**
68
+ * Deploy contract. If salt is provided, uses CREATE2 factory.
69
+ * @param artifacts - hardhat artifacts
70
+ * @param contractName - contract name to deploy
71
+ * @param args - constructor arguments
72
+ * @param salt - optional CREATE2 salt for deterministic address
73
+ * @param feeLimit - Tron fee limit (default 15 TRX)
74
+ */
75
+ deploy(artifacts: any, contractName: string, args?: any[], salt?: string, feeLimit?: number): Promise<TronAddress>;
76
+ /**
77
+ * Deploy implementation + ERC1967 proxy. If salt is provided, proxy uses factory.
78
+ * @param artifacts - hardhat artifacts
79
+ * @param contractName - implementation contract name
80
+ * @param initArgs - initialize() function arguments
81
+ * @param salt - optional CREATE2 salt for proxy
82
+ * @param feeLimit - Tron fee limit
83
+ */
84
+ deployProxy(artifacts: any, contractName: string, initArgs?: any[], salt?: string, feeLimit?: number): Promise<{
85
+ proxy: TronAddress;
86
+ implementation: TronAddress;
87
+ }>;
88
+ /**
89
+ * Upgrade a UUPS proxy to a new implementation.
90
+ * @param artifacts - hardhat artifacts
91
+ * @param contractName - new implementation contract name
92
+ * @param proxyAddr - proxy address to upgrade
93
+ * @param feeLimit - Tron fee limit
94
+ */
95
+ upgradeProxy(artifacts: any, contractName: string, proxyAddr: string, feeLimit?: number): Promise<TronAddress>;
96
+ /**
97
+ * Wait for a transaction to be confirmed on-chain.
98
+ * @param txId - transaction hash from .send()
99
+ */
100
+ waitForTx(txId: string, retries?: number, interval?: number): Promise<any>;
101
+ /** Wrap contract methods — adds .sendAndWait() to write methods. */
102
+ private _wrapContract;
103
+ }
104
+ /** @internal Deploy contract on Tron. If salt is provided, uses CREATE2 factory. */
105
+ export declare function tronDeploy(tronWeb: any, artifacts: any, contractName: string, args?: any[], salt?: string, feeLimit?: number): Promise<TronAddress>;
106
+ /** @internal Deploy implementation + ERC1967 proxy on Tron. */
107
+ export declare function tronDeployProxy(tronWeb: any, artifacts: any, contractName: string, initArgs?: any[], salt?: string, feeLimit?: number): Promise<{
108
+ proxy: TronAddress;
109
+ implementation: TronAddress;
110
+ }>;
111
+ /** @internal Upgrade a UUPS proxy to a new implementation on Tron. */
112
+ export declare function tronUpgradeProxy(tronWeb: any, artifacts: any, contractName: string, proxyAddr: string, feeLimit?: number): Promise<TronAddress>;
113
+ /**
114
+ * Send a tron contract call and wait for on-chain confirmation.
115
+ * @param methodCall - tronweb contract method call (e.g. contract.setWtoken(addr))
116
+ * @param tronWeb - tronweb instance for querying tx status
117
+ * @param opts - send options (feeLimit, callValue, etc.)
118
+ */
119
+ export declare function sendAndWait(methodCall: any, tronWeb: any, opts?: Record<string, any>): Promise<any>;
120
+ /**
121
+ * Wait for a Tron transaction to be confirmed on-chain.
122
+ * @param tronWeb - tronweb instance
123
+ * @param txId - transaction hash returned by .send()
124
+ * @param retries - max poll attempts (default 20)
125
+ * @param interval - poll interval in ms (default 3000)
126
+ */
127
+ export declare function waitForTx(tronWeb: any, txId: string, retries?: number, interval?: number): Promise<any>;
@@ -0,0 +1,291 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TronClient = void 0;
4
+ exports.tronFromHex = tronFromHex;
5
+ exports.tronToHex = tronToHex;
6
+ exports.isTronNetwork = isTronNetwork;
7
+ exports.tronDeploy = tronDeploy;
8
+ exports.tronDeployProxy = tronDeployProxy;
9
+ exports.tronUpgradeProxy = tronUpgradeProxy;
10
+ exports.sendAndWait = sendAndWait;
11
+ exports.waitForTx = waitForTx;
12
+ let TronWeb = require("tronweb");
13
+ // ============================================================
14
+ // Pure address conversion — no RPC, no private key needed
15
+ // ============================================================
16
+ /** Convert hex address to Tron base58 format. Returns as-is if already base58. */
17
+ function tronFromHex(hex) {
18
+ if (hex.startsWith("T") && hex.length === 34)
19
+ return hex;
20
+ return TronWeb.address.fromHex(hex);
21
+ }
22
+ /** Convert Tron base58 address to 0x-prefixed hex. Returns as-is if already hex. */
23
+ function tronToHex(addr) {
24
+ if (addr.startsWith("0x") && addr.length === 42)
25
+ return addr;
26
+ return TronWeb.address.toHex(addr).replace(/^(41)/, "0x");
27
+ }
28
+ // Tron chainIds: mainnet 728126428, nile testnet 3448148188
29
+ const TRON_CHAIN_IDS = [728126428, 3448148188];
30
+ /** Check if a network name or chainId belongs to Tron (case-insensitive). */
31
+ function isTronNetwork(networkOrChainId) {
32
+ if (typeof networkOrChainId === "number") {
33
+ return TRON_CHAIN_IDS.includes(networkOrChainId);
34
+ }
35
+ return networkOrChainId.toLowerCase().startsWith("tron");
36
+ }
37
+ // ============================================================
38
+ // TronClient
39
+ // ============================================================
40
+ /**
41
+ * TronClient — encapsulates tronWeb instance, provides high-level contract operations.
42
+ *
43
+ * Usage:
44
+ * // From hardhat runtime (recommended in tasks)
45
+ * let client = TronClient.fromHre(hre);
46
+ *
47
+ * // Manual — with rpc and private key
48
+ * let client = new TronClient("https://api.trongrid.io", "your_private_key");
49
+ *
50
+ * // Read-only — no private key
51
+ * let client = new TronClient("https://api.trongrid.io");
52
+ *
53
+ * // Contract interaction
54
+ * let gw = await client.getContract(artifacts, "Gateway", addr);
55
+ * let wtoken = await gw.wToken().call(); // read
56
+ * await gw.setWtoken(client.toHex(wtoken)).sendAndWait(); // write + wait
57
+ *
58
+ * // Deploy
59
+ * let { hex, base58 } = await client.deploy(artifacts, "Gateway");
60
+ * let { proxy, implementation } = await client.deployProxy(artifacts, "Gateway", [admin]);
61
+ *
62
+ * // Upgrade
63
+ * await client.upgradeProxy(artifacts, "Gateway", proxyAddr);
64
+ */
65
+ class TronClient {
66
+ /**
67
+ * Create a TronClient with explicit rpcUrl.
68
+ * @param rpcUrl - Tron full host URL (e.g. "https://api.trongrid.io")
69
+ * @param privateKey - optional, omit for read-only operations
70
+ */
71
+ constructor(rpcUrl, privateKey) {
72
+ this.connected = false;
73
+ const opts = { fullHost: rpcUrl };
74
+ if (privateKey) {
75
+ // TronWeb doesn't accept 0x prefix on private keys
76
+ opts.privateKey = privateKey.startsWith("0x") ? privateKey.slice(2) : privateKey;
77
+ }
78
+ this.tronWeb = new TronWeb(opts);
79
+ }
80
+ /**
81
+ * Create TronClient from hardhat runtime environment.
82
+ * Reads rpcUrl and privateKey from hre.network.config. Verifies RPC connectivity.
83
+ */
84
+ static fromHre(hre) {
85
+ const config = hre.network.config;
86
+ let rpcUrl = config.url;
87
+ let privateKey = Array.isArray(config.accounts) ? config.accounts[0] : undefined;
88
+ if (!rpcUrl)
89
+ throw new Error(`no rpc url configured for network ${hre.network.name}`);
90
+ // Hardhat config uses JSON-RPC path (e.g. /jsonrpc), TronWeb needs base URL
91
+ rpcUrl = rpcUrl.replace(/\/(jsonrpc|wallet|solidity)\/?$/i, "");
92
+ // TronWeb doesn't accept 0x prefix on private keys
93
+ if (privateKey && privateKey.startsWith("0x")) {
94
+ privateKey = privateKey.slice(2);
95
+ }
96
+ return new TronClient(rpcUrl, privateKey);
97
+ }
98
+ /** Verify RPC is reachable. Uses getNowBlock which is widely supported. */
99
+ async _checkConnection() {
100
+ if (this.connected)
101
+ return;
102
+ try {
103
+ // getNowBlock calls /wallet/getnowblock — more universally supported than getNodeInfo
104
+ await this.tronWeb.trx.getCurrentBlock();
105
+ this.connected = true;
106
+ }
107
+ catch (e) {
108
+ const url = this.tronWeb.fullNode?.host || "unknown";
109
+ throw new Error(`TronClient: cannot connect to ${url}. ` +
110
+ `Ensure the URL is a valid TronWeb full host (e.g. "https://api.trongrid.io") and is reachable. ` +
111
+ `Error: ${e.message || e}`);
112
+ }
113
+ }
114
+ /** Lazy connection check — called before first contract operation. */
115
+ async _ensureConnected() {
116
+ if (!this.connected)
117
+ await this._checkConnection();
118
+ }
119
+ /** Get the default operator address in base58 format. */
120
+ get defaultAddress() {
121
+ return this.tronWeb.defaultAddress.base58;
122
+ }
123
+ /** Convert Tron base58 address to 0x hex. */
124
+ toHex(addr) {
125
+ return tronToHex(addr);
126
+ }
127
+ /** Convert 0x hex to Tron base58 address. */
128
+ fromHex(hex) {
129
+ return tronFromHex(hex);
130
+ }
131
+ /**
132
+ * Get a contract instance. Write methods have .sendAndWait() attached.
133
+ * @param artifacts - hardhat artifacts (hre.artifacts)
134
+ * @param contractName - contract name (e.g. "Gateway")
135
+ * @param addr - contract address (base58 or hex)
136
+ */
137
+ async getContract(artifacts, contractName, addr) {
138
+ await this._ensureConnected();
139
+ console.log("operator address is:", this.defaultAddress);
140
+ const artifact = await artifacts.readArtifact(contractName);
141
+ const contract = await this.tronWeb.contract(artifact.abi, addr);
142
+ return this._wrapContract(contract);
143
+ }
144
+ /**
145
+ * Deploy contract. If salt is provided, uses CREATE2 factory.
146
+ * @param artifacts - hardhat artifacts
147
+ * @param contractName - contract name to deploy
148
+ * @param args - constructor arguments
149
+ * @param salt - optional CREATE2 salt for deterministic address
150
+ * @param feeLimit - Tron fee limit (default 15 TRX)
151
+ */
152
+ async deploy(artifacts, contractName, args = [], salt = "", feeLimit = 15000000000) {
153
+ await this._ensureConnected();
154
+ return tronDeploy(this.tronWeb, artifacts, contractName, args, salt, feeLimit);
155
+ }
156
+ /**
157
+ * Deploy implementation + ERC1967 proxy. If salt is provided, proxy uses factory.
158
+ * @param artifacts - hardhat artifacts
159
+ * @param contractName - implementation contract name
160
+ * @param initArgs - initialize() function arguments
161
+ * @param salt - optional CREATE2 salt for proxy
162
+ * @param feeLimit - Tron fee limit
163
+ */
164
+ async deployProxy(artifacts, contractName, initArgs = [], salt = "", feeLimit = 15000000000) {
165
+ await this._ensureConnected();
166
+ return tronDeployProxy(this.tronWeb, artifacts, contractName, initArgs, salt, feeLimit);
167
+ }
168
+ /**
169
+ * Upgrade a UUPS proxy to a new implementation.
170
+ * @param artifacts - hardhat artifacts
171
+ * @param contractName - new implementation contract name
172
+ * @param proxyAddr - proxy address to upgrade
173
+ * @param feeLimit - Tron fee limit
174
+ */
175
+ async upgradeProxy(artifacts, contractName, proxyAddr, feeLimit = 15000000000) {
176
+ await this._ensureConnected();
177
+ return tronUpgradeProxy(this.tronWeb, artifacts, contractName, proxyAddr, feeLimit);
178
+ }
179
+ /**
180
+ * Wait for a transaction to be confirmed on-chain.
181
+ * @param txId - transaction hash from .send()
182
+ */
183
+ async waitForTx(txId, retries = 20, interval = 3000) {
184
+ return waitForTx(this.tronWeb, txId, retries, interval);
185
+ }
186
+ /** Wrap contract methods — adds .sendAndWait() to write methods. */
187
+ _wrapContract(contract) {
188
+ const tronWeb = this.tronWeb;
189
+ return new Proxy(contract, {
190
+ get(target, prop) {
191
+ const original = target[prop];
192
+ if (typeof original !== "function")
193
+ return original;
194
+ return (...args) => {
195
+ const methodCall = original.apply(target, args);
196
+ if (methodCall && typeof methodCall.send === "function") {
197
+ methodCall.sendAndWait = (opts) => sendAndWait(methodCall, tronWeb, opts);
198
+ }
199
+ return methodCall;
200
+ };
201
+ }
202
+ });
203
+ }
204
+ }
205
+ exports.TronClient = TronClient;
206
+ // ============================================================
207
+ // Low-level functions (used internally by TronClient)
208
+ // ============================================================
209
+ /** @internal Deploy contract on Tron. If salt is provided, uses CREATE2 factory. */
210
+ async function tronDeploy(tronWeb, artifacts, contractName, args = [], salt = "", feeLimit = 15000000000) {
211
+ if (salt) {
212
+ const { tronDeployByFactory } = require("./factory");
213
+ const hex = await tronDeployByFactory(tronWeb, artifacts, contractName, salt, args, feeLimit);
214
+ return { hex, base58: tronFromHex(hex) };
215
+ }
216
+ const artifact = await artifacts.readArtifact(contractName);
217
+ console.log("deploy address is:", tronWeb.defaultAddress.base58);
218
+ const instance = await tronWeb.contract().new({
219
+ abi: artifact.abi,
220
+ bytecode: artifact.bytecode,
221
+ feeLimit,
222
+ callValue: 0,
223
+ parameters: args,
224
+ });
225
+ const rawAddress = instance.address; // 41-prefixed hex
226
+ const hex = rawAddress.replace(/^41/, "0x");
227
+ const base58 = tronWeb.address.fromHex(rawAddress);
228
+ console.log(`${contractName} deployed: ${base58} (${hex})`);
229
+ return { hex, base58 };
230
+ }
231
+ /** @internal Deploy implementation + ERC1967 proxy on Tron. */
232
+ async function tronDeployProxy(tronWeb, artifacts, contractName, initArgs = [], salt = "", feeLimit = 15000000000) {
233
+ const implementation = await tronDeploy(tronWeb, artifacts, contractName, [], "", feeLimit);
234
+ console.log(`${contractName} implementation: ${implementation.base58} (${implementation.hex})`);
235
+ // Wait for implementation to be fully confirmed before deploying proxy
236
+ if (salt) {
237
+ console.log("waiting for implementation to be indexed...");
238
+ await new Promise(r => setTimeout(r, 5000));
239
+ }
240
+ const artifact = await artifacts.readArtifact(contractName);
241
+ const iface = new (require("ethers").Interface)(artifact.abi);
242
+ const initData = iface.encodeFunctionData("initialize", initArgs);
243
+ const proxy = await tronDeploy(tronWeb, artifacts, "ERC1967Proxy", [implementation.hex, initData], salt, feeLimit);
244
+ console.log(`${contractName} proxy: ${proxy.base58} (${proxy.hex})`);
245
+ return { proxy, implementation };
246
+ }
247
+ /** @internal Upgrade a UUPS proxy to a new implementation on Tron. */
248
+ async function tronUpgradeProxy(tronWeb, artifacts, contractName, proxyAddr, feeLimit = 15000000000) {
249
+ const impl = await tronDeploy(tronWeb, artifacts, contractName, [], "", feeLimit);
250
+ console.log(`${contractName} new implementation: ${impl.base58} (${impl.hex})`);
251
+ const artifact = await artifacts.readArtifact("BaseImplementation");
252
+ const proxyContract = await tronWeb.contract(artifact.abi, proxyAddr);
253
+ const oldImpl = await proxyContract.getImplementation().call();
254
+ console.log(`old implementation: ${tronFromHex(oldImpl)}`);
255
+ await sendAndWait(proxyContract.upgradeToAndCall(impl.hex, "0x"), tronWeb, { feeLimit });
256
+ const newImpl = await proxyContract.getImplementation().call();
257
+ console.log(`${contractName} upgraded: ${tronFromHex(oldImpl)} -> ${tronFromHex(newImpl)}`);
258
+ return impl;
259
+ }
260
+ /**
261
+ * Send a tron contract call and wait for on-chain confirmation.
262
+ * @param methodCall - tronweb contract method call (e.g. contract.setWtoken(addr))
263
+ * @param tronWeb - tronweb instance for querying tx status
264
+ * @param opts - send options (feeLimit, callValue, etc.)
265
+ */
266
+ async function sendAndWait(methodCall, tronWeb, opts = {}) {
267
+ const txId = await methodCall.send(opts);
268
+ return waitForTx(tronWeb, txId);
269
+ }
270
+ /**
271
+ * Wait for a Tron transaction to be confirmed on-chain.
272
+ * @param tronWeb - tronweb instance
273
+ * @param txId - transaction hash returned by .send()
274
+ * @param retries - max poll attempts (default 20)
275
+ * @param interval - poll interval in ms (default 3000)
276
+ */
277
+ async function waitForTx(tronWeb, txId, retries = 20, interval = 3000) {
278
+ console.log(`waiting for tx ${txId}...`);
279
+ for (let i = 0; i < retries; i++) {
280
+ const result = await tronWeb.trx.getTransactionInfo(txId);
281
+ if (result && result.id) {
282
+ if (result.receipt?.result === "SUCCESS") {
283
+ console.log(`tx confirmed in block ${result.blockNumber}`);
284
+ return result;
285
+ }
286
+ throw new Error(`tx failed: ${result.receipt?.result || "UNKNOWN"}`);
287
+ }
288
+ await new Promise(r => setTimeout(r, interval));
289
+ }
290
+ throw new Error(`tx not confirmed after ${retries} retries: ${txId}`);
291
+ }