@mapprotocol/common-contracts 0.3.2 → 0.4.0

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.
package/README.md CHANGED
@@ -12,7 +12,7 @@ npm install @mapprotocol/common-contracts
12
12
 
13
13
  ### BaseImplementation
14
14
 
15
- Abstract base for all upgradeable protocol contracts. Combines UUPS proxy, pausable, and access control in one inheritance.
15
+ Abstract base for all upgradeable protocol contracts (UUPS + Pausable + AccessManaged).
16
16
 
17
17
  ```solidity
18
18
  import "@mapprotocol/common-contracts/contracts/base/BaseImplementation.sol";
@@ -24,12 +24,6 @@ contract MyContract is BaseImplementation {
24
24
  }
25
25
  ```
26
26
 
27
- **Includes:**
28
- - `UUPSUpgradeable` — proxy upgrade pattern
29
- - `PausableUpgradeable` — emergency pause via `trigger()`
30
- - `AccessManagedUpgradeable` — role-based function access
31
- - `getImplementation()` — read current implementation address
32
-
33
27
  ### AuthorityManager
34
28
 
35
29
  Extended `AccessManager` with enumerable role members. One instance per chain controls all protocol contracts.
@@ -38,82 +32,49 @@ Extended `AccessManager` with enumerable role members. One instance per chain co
38
32
  import "@mapprotocol/common-contracts/contracts/AuthorityManager.sol";
39
33
  ```
40
34
 
41
- **Key functions:**
42
- - `grantRole(roleId, account, delay)` — assign role
43
- - `revokeRole(roleId, account)` — remove role
44
- - `setTargetFunctionRole(target, selectors, roleId)` — restrict contract functions to a role
45
- - `getRoleMembers(roleId)` — list all members of a role
46
-
47
35
  ## TypeScript Utilities
48
36
 
49
- ### Deployment
50
-
51
37
  ```typescript
52
- import { getDeploymentByKey, saveDeployment, resolveDeploymentEnv } from "@mapprotocol/common-contracts/utils/deployment";
53
-
54
- // Read deployed address (defaults to <cwd>/deployments/deploy.json)
55
- const addr = await getDeploymentByKey("Bsc", "Gateway");
56
-
57
- // With custom suffix and path
58
- const addr = await getDeploymentByKey("Bsc", "Gateway", { suffix: "main", basePath: "./my-deployments" });
59
-
60
- // Save deployment
61
- await saveDeployment("Bsc", "Gateway", "0x...");
38
+ import { createDeployer } from "@mapprotocol/common-contracts/utils/deployer";
39
+ import { getDeploymentByKey, saveDeployment } from "@mapprotocol/common-contracts/utils/deployRecord";
40
+ import { TronClient, tronToHex, tronFromHex } from "@mapprotocol/common-contracts/utils/tronHelper";
41
+ import { verify } from "@mapprotocol/common-contracts/utils/verifier";
42
+ import { addressToHex } from "@mapprotocol/common-contracts/utils/addressCodec";
62
43
  ```
63
44
 
64
- ### Tron Interaction
45
+ ### Quick Start
65
46
 
66
47
  ```typescript
67
- import { createTronWeb, tronDeploy, getTronContract, tronToHex, tronFromHex } from "@mapprotocol/common-contracts/utils/tronHelper";
68
-
69
- // Address conversion (pure, no RPC needed)
70
- const hex = tronToHex("TXyz..."); // -> "0x..."
71
- const base58 = tronFromHex("0x..."); // -> "TXyz..."
72
-
73
- // Deploy contract on Tron
74
- const tronWeb = createTronWeb({ rpcUrl: "https://api.trongrid.io", privateKey: "..." });
75
- const addr = await tronDeploy(tronWeb, artifacts, "MyContract", [arg1, arg2]);
76
-
77
- // Read-only (no privateKey needed)
78
- const readOnly = createTronWeb({ rpcUrl: "https://api.trongrid.io" });
79
- const contract = await getTronContract(readOnly, artifacts, "MyContract", addr);
48
+ // Unified deployer auto-routes EVM / Tron
49
+ const deployer = createDeployer(hre, { autoVerify: true });
50
+ let result = await deployer.deploy("Gateway", [bridge, owner, wtoken]);
51
+ let proxy = await deployer.deployProxy("Gateway", [admin]);
52
+
53
+ // Deploy record
54
+ const addr = getDeploymentByKey("Bsc", "Gateway", { env: "prod" });
55
+ saveDeployment("Bsc", "Gateway", "0x...", { env: "prod" });
56
+
57
+ // Tron client
58
+ let client = TronClient.fromHre(hre);
59
+ let gw = await client.getContract(hre.artifacts, "Gateway", addr);
60
+ await gw.setWtoken(client.toHex(wtoken)).sendAndWait();
61
+ let val = await gw.wToken().call();
80
62
  ```
81
63
 
82
- ### Address Encoding
83
-
84
- ```typescript
85
- import { addressToHex, isTronAddress, isSolanaChain } from "@mapprotocol/common-contracts/utils/addressCodec";
86
-
87
- // Auto-detect format and convert to hex
88
- addressToHex("0xAbC..."); // EVM -> lowercase hex
89
- addressToHex("TXyz..."); // Tron -> extract 20-byte address
90
- addressToHex("So11111..."); // Solana -> full base58 decode
91
-
92
- // Type checks
93
- isTronAddress("TXyz..."); // true (validates 0x41 prefix byte)
94
- isSolanaChain("Sol"); // true
95
- ```
64
+ See JSDoc in each module's source for full API details and edge cases.
96
65
 
97
66
  ## Forge Script Base
98
67
 
99
- For monorepo projects, `script/Base.s.sol` provides deployment primitives:
100
-
101
68
  ```solidity
102
- import {BaseScript} from "../../common/script/Base.s.sol";
69
+ import {BaseScript} from "@mapprotocol/common-contracts/script/base/Base.s.sol";
103
70
 
104
71
  contract MyDeploy is BaseScript {
105
72
  function run() public broadcast {
106
- // CREATE2 factory deployment (deterministic address)
73
+ (address proxy, address impl) = deployProxy(type(MyContract).creationCode, initData);
107
74
  address addr = deployByFactory("my_salt", type(MyContract).creationCode, abi.encode(arg));
108
-
109
- // Check factory availability
110
- require(isFactoryAvailable(), "no factory on this chain");
111
-
112
- // UUPS proxy upgrade
113
- upgradeProxy(proxyAddr, newImplAddr);
114
-
115
- // Deploy new impl + upgrade in one step
116
75
  deployAndUpgrade(proxyAddr, type(MyContractV2).creationCode);
76
+ address relay = readDeployment("Relay");
77
+ saveDeployment("Gateway", addr);
117
78
  }
118
79
  }
119
80
  ```
@@ -121,43 +82,12 @@ contract MyDeploy is BaseScript {
121
82
  ## Development
122
83
 
123
84
  ```bash
124
- # Build
125
- forge build # Foundry
126
- npm run build:hardhat # Hardhat + TypeChain
127
-
128
- # Test
129
- forge test
130
- forge test --gas-report
131
-
132
- # Format
133
- forge fmt
134
-
135
- # Publish
136
- npm run prepublishOnly # clean + build + typecheck
137
- npm publish
85
+ forge build && forge test # Solidity
86
+ npm run build:hardhat # Hardhat + TypeChain
87
+ npm run build:utils # TypeScript utilities
88
+ npm run prepublishOnly && npm publish
138
89
  ```
139
90
 
140
- ## Package Contents
141
-
142
- | Path | Description |
143
- |------|-------------|
144
- | `contracts/**/*.sol` | Solidity source files |
145
- | `artifacts/**/*.json` | Compiled ABI + bytecode |
146
- | `typechain-types/**/*` | TypeChain generated types |
147
- | `utils/*.ts` | Shared TypeScript utilities |
148
-
149
- ## Requirements
150
-
151
- - Solidity ^0.8.20
152
- - Node.js >= 18
153
- - OpenZeppelin Contracts 5.4.0
154
-
155
91
  ## License
156
92
 
157
93
  MIT
158
-
159
- ## Links
160
-
161
- - [Repository](https://github.com/mapprotocol/mapo-contracts-v2/tree/main/common)
162
- - [Issues](https://github.com/mapprotocol/mapo-contracts-v2/issues)
163
- - [MAP Protocol](https://mapprotocol.io)
package/package.json CHANGED
@@ -1,9 +1,21 @@
1
1
  {
2
2
  "name": "@mapprotocol/common-contracts",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Common contracts for MAP Protocol",
5
5
  "main": "typechain-types/index.js",
6
6
  "types": "typechain-types/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./typechain-types/index.d.ts",
10
+ "default": "./typechain-types/index.js"
11
+ },
12
+ "./utils/*": {
13
+ "types": "./utils/dist/*.d.ts",
14
+ "default": "./utils/dist/*.js"
15
+ },
16
+ "./contracts/*": "./contracts/*",
17
+ "./script/*": "./script/*"
18
+ },
7
19
  "files": [
8
20
  "contracts/**/*.sol",
9
21
  "artifacts/contracts/**/*.json",
@@ -11,6 +23,7 @@
11
23
  "typechain-types/**/*",
12
24
  "utils/**/*.ts",
13
25
  "utils/dist/**/*",
26
+ "script/base/**/*.sol",
14
27
  "README.md"
15
28
  ],
16
29
  "repository": {
@@ -0,0 +1,231 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.20;
3
+
4
+ import {Script, stdJson, console} from "forge-std/Script.sol";
5
+ import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
6
+ import {IFactory} from "./interfaces/IFactory.sol";
7
+
8
+ /**
9
+ * @title BaseScript
10
+ * @notice Base contract for deployment scripts with full lifecycle support
11
+ * @dev Inherit this in your deployment scripts.
12
+ *
13
+ * Features:
14
+ * - Private key management (mainnet / testnet)
15
+ * - Direct deployment and CREATE2 factory deployment
16
+ * - Proxy deployment (implementation + ERC1967Proxy)
17
+ * - UUPS proxy upgrade
18
+ * - deploy.json record read/write
19
+ *
20
+ * Usage:
21
+ * contract MyDeploy is BaseScript {
22
+ * function run() public broadcast {
23
+ * // Direct deploy
24
+ * address impl = deployDirect(type(MyContract).creationCode, abi.encode(arg));
25
+ *
26
+ * // Factory deploy (deterministic address)
27
+ * address addr = deployByFactory("my_salt", type(MyContract).creationCode, abi.encode(arg));
28
+ *
29
+ * // Proxy deploy (impl + proxy)
30
+ * (address proxy, address impl) = deployProxy(type(MyContract).creationCode, initData);
31
+ * (address proxy, address impl) = deployProxyByFactory("my_salt", type(MyContract).creationCode, initData);
32
+ *
33
+ * // Upgrade
34
+ * upgradeProxy(proxy, newImplAddr);
35
+ * address newImpl = deployAndUpgrade(proxy, type(MyContractV2).creationCode);
36
+ *
37
+ * // Deploy record
38
+ * address relay = readDeployment("Relay");
39
+ * saveDeployment("Gateway", addr);
40
+ * }
41
+ * }
42
+ */
43
+ abstract contract BaseScript is Script {
44
+ IFactory constant private FACTORY = IFactory(0x6258e4d2950757A749a4d4683A7342261ce12471);
45
+ using stdJson for string;
46
+
47
+ address internal broadcaster;
48
+ uint256 private broadcasterPK;
49
+
50
+ constructor() {
51
+ uint256 privateKey;
52
+ if (block.chainid == 212) {
53
+ privateKey = vm.envUint("TESTNET_PRIVATE_KEY");
54
+ } else {
55
+ privateKey = vm.envUint("PRIVATE_KEY");
56
+ }
57
+ broadcaster = vm.addr(privateKey);
58
+ broadcasterPK = privateKey;
59
+ }
60
+
61
+ modifier broadcast() {
62
+ vm.startBroadcast(broadcasterPK);
63
+ _;
64
+ vm.stopBroadcast();
65
+ }
66
+
67
+ // ============================================================
68
+ // Factory (CREATE2) deployment
69
+ // ============================================================
70
+
71
+ /// @notice Deploy contract via CREATE2 factory with deterministic address
72
+ function deployByFactory(
73
+ string memory salt,
74
+ bytes memory creationCode,
75
+ bytes memory constructorArgs
76
+ ) internal returns (address addr) {
77
+ require(address(FACTORY).code.length > 0, "factory not deployed on this chain");
78
+ bytes32 saltHash = keccak256(bytes(salt));
79
+ addr = FACTORY.getAddress(saltHash);
80
+ if (addr.code.length > 0) revert("already deployed");
81
+ bytes memory code = abi.encodePacked(creationCode, constructorArgs);
82
+ FACTORY.deploy(saltHash, code, 0);
83
+ }
84
+
85
+ /// @notice Deploy implementation + proxy via CREATE2 factory
86
+ function deployProxyByFactory(
87
+ string memory salt,
88
+ bytes memory implCreationCode,
89
+ bytes memory initData
90
+ ) internal returns (address proxy, address impl) {
91
+ impl = deployDirect(implCreationCode, bytes(""));
92
+ proxy = deployByFactory(salt, type(ERC1967Proxy).creationCode, abi.encode(impl, initData));
93
+ console.log("Proxy:", proxy);
94
+ console.log("Implementation:", impl);
95
+ }
96
+
97
+ /// @notice Check if the CREATE2 factory is available on this chain
98
+ function isFactoryAvailable() internal view returns (bool) {
99
+ return address(FACTORY).code.length > 0;
100
+ }
101
+
102
+ /// @notice Get the predicted address for a given salt
103
+ function getFactoryAddress(string memory salt) internal view returns (address) {
104
+ require(isFactoryAvailable(), "factory not deployed on this chain");
105
+ return FACTORY.getAddress(keccak256(bytes(salt)));
106
+ }
107
+
108
+ /// @notice Check if a contract is already deployed at the factory address
109
+ function isFactoryDeployed(string memory salt) internal view returns (bool) {
110
+ if (!isFactoryAvailable()) return false;
111
+ return FACTORY.getAddress(keccak256(bytes(salt))).code.length > 0;
112
+ }
113
+
114
+ // ============================================================
115
+ // Direct deployment
116
+ // ============================================================
117
+
118
+ /// @notice Deploy a new contract directly (non-deterministic address)
119
+ function deployDirect(
120
+ bytes memory creationCode,
121
+ bytes memory constructorArgs
122
+ ) internal returns (address addr) {
123
+ bytes memory code = abi.encodePacked(creationCode, constructorArgs);
124
+ assembly {
125
+ addr := create(0, add(code, 0x20), mload(code))
126
+ }
127
+ require(addr != address(0), "deploy failed");
128
+ }
129
+
130
+ /// @notice Deploy implementation + proxy directly
131
+ function deployProxy(
132
+ bytes memory implCreationCode,
133
+ bytes memory initData
134
+ ) internal returns (address proxy, address impl) {
135
+ impl = deployDirect(implCreationCode, bytes(""));
136
+ ERC1967Proxy p = new ERC1967Proxy(impl, initData);
137
+ proxy = address(p);
138
+ console.log("Proxy:", proxy);
139
+ console.log("Implementation:", impl);
140
+ }
141
+
142
+ // ============================================================
143
+ // Upgrade
144
+ // ============================================================
145
+
146
+ /// @notice Upgrade a UUPS proxy to a new implementation
147
+ function upgradeProxy(address proxy, address newImpl) internal {
148
+ address oldImpl = _getImplementation(proxy);
149
+ (bool success,) = proxy.call(
150
+ abi.encodeWithSignature("upgradeToAndCall(address,bytes)", newImpl, bytes(""))
151
+ );
152
+ require(success, "upgrade failed");
153
+ console.log("Upgraded:", oldImpl, "->", newImpl);
154
+ }
155
+
156
+ /// @notice Deploy new implementation and upgrade proxy in one step
157
+ function deployAndUpgrade(address proxy, bytes memory implCreationCode) internal returns (address newImpl) {
158
+ newImpl = deployDirect(implCreationCode, bytes(""));
159
+ upgradeProxy(proxy, newImpl);
160
+ }
161
+
162
+ // ============================================================
163
+ // Deploy record (deploy.json) read/write
164
+ // ============================================================
165
+
166
+ /// @notice Read a deployed address from deployments/deploy.json
167
+ function readDeployment(string memory key) internal view returns (address) {
168
+ (string memory env, string memory chain) = _resolveDeploymentPath();
169
+ return _readDeploymentByPath(env, chain, key);
170
+ }
171
+
172
+ /// @notice Read a deployed address for a specific env and chain
173
+ function _readDeploymentByPath(string memory env, string memory chain, string memory key) internal view returns (address addr) {
174
+ string memory filePath = "deployments/deploy.json";
175
+ if (!vm.exists(filePath)) {
176
+ revert(string(abi.encodePacked("deploy.json not found: ", filePath)));
177
+ }
178
+ string memory json = vm.readFile(filePath);
179
+ // JSON path: .prod.Bsc.Gateway
180
+ string memory jsonPath = string(abi.encodePacked(".", env, ".", chain, ".", key));
181
+ addr = json.readAddress(jsonPath);
182
+ }
183
+
184
+ /// @notice Save a deployed address to deployments/deploy.json
185
+ function saveDeployment(string memory key, address addr) internal {
186
+ (string memory env, string memory chain) = _resolveDeploymentPath();
187
+ string memory filePath = "deployments/deploy.json";
188
+ // JSON path: .prod.Bsc.Gateway
189
+ string memory jsonPath = string(abi.encodePacked(".", env, ".", chain, ".", key));
190
+ string memory json = vm.readFile(filePath);
191
+ bool exists = vm.keyExistsJson(json, jsonPath);
192
+ if (!exists) {
193
+ revert(string(abi.encodePacked("key not found: ", env, ".", chain, ".", key)));
194
+ }
195
+ vm.writeJson(vm.toString(addr), filePath, jsonPath);
196
+ }
197
+
198
+ // ============================================================
199
+ // Internal helpers
200
+ // ============================================================
201
+
202
+ /// @notice Resolve deployment path (env, chain) based on chainId and NETWORK_ENV
203
+ /// @return env "prod", "main", or "test"
204
+ /// @return chain "Mapo", "Bsc", etc.
205
+ function _resolveDeploymentPath() internal view returns (string memory env, string memory chain) {
206
+ uint256 chainId = block.chainid;
207
+ env = vm.envString("NETWORK_ENV"); // required: test/prod/main
208
+
209
+ if (chainId == 212 || chainId == 22776) chain = "Mapo";
210
+ else if (chainId == 11155111 || chainId == 1) chain = "Eth";
211
+ else if (chainId == 97 || chainId == 56) chain = "Bsc";
212
+ else if (chainId == 8453) chain = "Base";
213
+ else if (chainId == 42161) chain = "Arb";
214
+ else if (chainId == 10) chain = "Op";
215
+ else if (chainId == 130) chain = "Uni";
216
+ else if (chainId == 137) chain = "Pol";
217
+ else if (chainId == 196) chain = "Xlayer";
218
+ else revert("unknown chain");
219
+ }
220
+
221
+ /// @notice Read current implementation address from a UUPS proxy
222
+ function _getImplementation(address proxy) internal view returns (address) {
223
+ (bool success, bytes memory data) = proxy.staticcall(
224
+ abi.encodeWithSignature("getImplementation()")
225
+ );
226
+ if (success && data.length == 32) {
227
+ return abi.decode(data, (address));
228
+ }
229
+ return address(0);
230
+ }
231
+ }
@@ -0,0 +1,7 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.20;
3
+
4
+ interface IFactory {
5
+ function getAddress(bytes32 salt) external view returns (address);
6
+ function deploy(bytes32 salt, bytes memory creationCode, uint256 value) external;
7
+ }
package/utils/deployer.ts CHANGED
@@ -13,12 +13,17 @@ import { verify as _verify } from "./verifier";
13
13
  */
14
14
 
15
15
  export interface DeployerOptions {
16
- autoVerify?: boolean; // auto verify after deploy/upgrade, defaults to false
16
+ /**
17
+ * Auto-verify contracts after deploy/upgrade.
18
+ * Failures are logged as warnings, never thrown — call deployer.verify() for explicit error handling.
19
+ * Defaults to false.
20
+ */
21
+ autoVerify?: boolean;
17
22
  }
18
23
 
19
24
  export interface DeployResult {
20
- address: string; // primary address (0x hex for EVM, base58 for Tron)
21
- hex?: string; // 0x hex (Tron only, EVM same as address)
25
+ address: string; // EVM: 0x hex address. Tron: base58 (T...) address.
26
+ hex?: string; // Tron only: 0x hex address. Undefined on EVM.
22
27
  }
23
28
 
24
29
  export interface DeployProxyResult {
@@ -29,9 +34,16 @@ export interface DeployProxyResult {
29
34
  }
30
35
 
31
36
  export interface Deployer {
37
+ /**
38
+ * Deploy a contract.
39
+ * @param contractName - artifact name
40
+ * @param args - constructor arguments as raw values
41
+ * @param salt - "" or omitted = direct deploy; non-empty string = CREATE2 factory (deterministic address)
42
+ */
32
43
  deploy(contractName: string, args?: any[], salt?: string): Promise<DeployResult>;
33
44
  deployProxy(contractName: string, initArgs?: any[], salt?: string): Promise<DeployProxyResult>;
34
45
  upgrade(contractName: string, proxyAddr: string): Promise<DeployResult>;
46
+ /** Verify explicitly (throws on failure, unlike autoVerify which only warns). */
35
47
  verify(contractName: string, address: string, constructorArgs?: any[], contractPath?: string): Promise<void>;
36
48
  isTron: boolean;
37
49
  network: string;
@@ -8,6 +8,11 @@
8
8
  * let newImpl = await deployer.upgrade("Gateway", proxyAddr);
9
9
  */
10
10
  export interface DeployerOptions {
11
+ /**
12
+ * Auto-verify contracts after deploy/upgrade.
13
+ * Failures are logged as warnings, never thrown — call deployer.verify() for explicit error handling.
14
+ * Defaults to false.
15
+ */
11
16
  autoVerify?: boolean;
12
17
  }
13
18
  export interface DeployResult {
@@ -21,9 +26,16 @@ export interface DeployProxyResult {
21
26
  implementationHex?: string;
22
27
  }
23
28
  export interface Deployer {
29
+ /**
30
+ * Deploy a contract.
31
+ * @param contractName - artifact name
32
+ * @param args - constructor arguments as raw values
33
+ * @param salt - "" or omitted = direct deploy; non-empty string = CREATE2 factory (deterministic address)
34
+ */
24
35
  deploy(contractName: string, args?: any[], salt?: string): Promise<DeployResult>;
25
36
  deployProxy(contractName: string, initArgs?: any[], salt?: string): Promise<DeployProxyResult>;
26
37
  upgrade(contractName: string, proxyAddr: string): Promise<DeployResult>;
38
+ /** Verify explicitly (throws on failure, unlike autoVerify which only warns). */
27
39
  verify(contractName: string, address: string, constructorArgs?: any[], contractPath?: string): Promise<void>;
28
40
  isTron: boolean;
29
41
  network: string;
@@ -2,7 +2,17 @@ export interface VerifyOptions {
2
2
  address: string;
3
3
  contractName: string;
4
4
  contractPath?: string;
5
+ /**
6
+ * Constructor arguments as raw values — auto-encoded via ethers `encodeDeploy`.
7
+ * Example: ["0xAbC123...", "0xDeF456...", 200]
8
+ * Use this for most cases. Mutually exclusive with constructorParams.
9
+ */
5
10
  constructorArgs?: any[];
11
+ /**
12
+ * Pre-encoded constructor params as hex string (without 0x prefix).
13
+ * Use this when you already have the ABI-encoded bytes, e.g. from `cast abi-encode`.
14
+ * Takes priority over constructorArgs if both are provided.
15
+ */
6
16
  constructorParams?: string;
7
17
  compiler?: string;
8
18
  optimizer?: boolean;
@@ -56,13 +56,13 @@ async function verifyTron(hre, opts) {
56
56
  const chainId = hre.network.config.chainId;
57
57
  const fs = require("fs");
58
58
  const path = require("path");
59
- // Auto-read compiler settings from hardhat config
59
+ // Compiler settings: opts > build-info > hardhat config (no hardcoded defaults)
60
60
  const solcConfig = hre.config?.solidity?.compilers?.[0] || hre.config?.solidity || {};
61
- const compiler = opts.compiler || solcConfig.version || "0.8.25";
62
- const optimizer = opts.optimizer ?? solcConfig.settings?.optimizer?.enabled ?? true;
63
- const optimizerRuns = opts.optimizerRuns ?? solcConfig.settings?.optimizer?.runs ?? 200;
64
- const evmVersion = solcConfig.settings?.evmVersion || "london";
65
- const viaIR = solcConfig.settings?.viaIR ? "1" : "0";
61
+ let compiler = opts.compiler || solcConfig.version || "";
62
+ let optimizer = opts.optimizer ?? solcConfig.settings?.optimizer?.enabled;
63
+ let optimizerRuns = opts.optimizerRuns ?? solcConfig.settings?.optimizer?.runs;
64
+ let evmVersion = solcConfig.settings?.evmVersion || "";
65
+ let viaIR = solcConfig.settings?.viaIR ? "1" : "0";
66
66
  // Convert address to Tron format
67
67
  let address = opts.address;
68
68
  if (address.startsWith("0x")) {
@@ -71,24 +71,23 @@ async function verifyTron(hre, opts) {
71
71
  // Generate flattened source
72
72
  const outputDir = path.join(process.cwd(), "verify-output");
73
73
  const flattenPath = path.join(outputDir, `${opts.contractName}_flatten.sol`);
74
- console.log(`generating flatten for ${opts.contractName}...`);
75
- try {
76
- // Find actual source path from artifact
77
- const artifact = await hre.artifacts.readArtifact(opts.contractName);
78
- const sourcePath = artifact.sourceName; // e.g. "contracts/factory/Create2Factory.sol"
79
- let flattenedSource = await hre.run("flatten:get-flattened-sources", {
80
- files: [sourcePath],
81
- });
82
- flattenedSource = removeDuplicateSPDX(flattenedSource);
83
- fs.mkdirSync(outputDir, { recursive: true });
84
- fs.writeFileSync(flattenPath, flattenedSource);
85
- console.log(`flatten saved to: ${flattenPath}`);
74
+ if (fs.existsSync(flattenPath)) {
75
+ console.log(`using existing flatten: ${flattenPath}`);
86
76
  }
87
- catch (e) {
88
- if (fs.existsSync(flattenPath)) {
89
- console.log(`using existing flatten: ${flattenPath}`);
77
+ else {
78
+ console.log(`generating flatten for ${opts.contractName}...`);
79
+ try {
80
+ const artifact = await hre.artifacts.readArtifact(opts.contractName);
81
+ const sourcePath = artifact.sourceName;
82
+ let flattenedSource = await hre.run("flatten:get-flattened-sources", {
83
+ files: [sourcePath],
84
+ });
85
+ flattenedSource = removeDuplicateSPDX(flattenedSource);
86
+ fs.mkdirSync(outputDir, { recursive: true });
87
+ fs.writeFileSync(flattenPath, flattenedSource);
88
+ console.log(`flatten saved to: ${flattenPath}`);
90
89
  }
91
- else {
90
+ catch (e) {
92
91
  console.log(`flatten failed, generate manually: forge flatten contracts/${opts.contractName}.sol`);
93
92
  return;
94
93
  }
@@ -106,27 +105,49 @@ async function verifyTron(hre, opts) {
106
105
  const form = new FormData();
107
106
  form.append("contractAddress", address);
108
107
  form.append("contractName", opts.contractName);
109
- // Read full compiler version from build-info (includes commit hash)
108
+ // Read compiler settings from build-info that contains this contract
110
109
  let fullCompiler = `v${compiler}`;
111
110
  try {
112
111
  const buildInfoDir = path.join(process.cwd(), "artifacts/build-info");
113
- const buildInfoFiles = fs.readdirSync(buildInfoDir);
114
- if (buildInfoFiles.length > 0) {
115
- const buildInfo = JSON.parse(fs.readFileSync(path.join(buildInfoDir, buildInfoFiles[0]), "utf-8"));
116
- if (buildInfo.solcLongVersion) {
117
- fullCompiler = `v${buildInfo.solcLongVersion}`;
112
+ const buildInfoFiles = fs.readdirSync(buildInfoDir).filter((f) => f.endsWith(".json"));
113
+ const artifact = await hre.artifacts.readArtifact(opts.contractName);
114
+ const sourceName = artifact.sourceName;
115
+ for (const file of buildInfoFiles) {
116
+ const buildInfo = JSON.parse(fs.readFileSync(path.join(buildInfoDir, file), "utf-8"));
117
+ if (buildInfo.output?.contracts?.[sourceName]?.[opts.contractName]) {
118
+ if (buildInfo.solcLongVersion) {
119
+ fullCompiler = `v${buildInfo.solcLongVersion}`;
120
+ }
121
+ // Override settings from actual build-info input
122
+ const settings = buildInfo.input?.settings;
123
+ if (settings) {
124
+ if (settings.evmVersion)
125
+ evmVersion = settings.evmVersion;
126
+ if (settings.optimizer != null) {
127
+ optimizer = settings.optimizer.enabled ?? optimizer;
128
+ optimizerRuns = settings.optimizer.runs ?? optimizerRuns;
129
+ }
130
+ if (settings.viaIR != null)
131
+ viaIR = settings.viaIR ? "1" : "0";
132
+ }
133
+ break;
118
134
  }
119
135
  }
120
136
  }
121
137
  catch (e) {
122
138
  console.log(`[warn] could not read build-info: ${e.message}`);
123
139
  }
140
+ if (!fullCompiler.includes("+commit.")) {
141
+ console.log(`[error] could not determine full compiler version (got: ${fullCompiler}). Run 'npx hardhat compile' to generate build-info.`);
142
+ printTronVerifyInfo(address, opts.contractName, fullCompiler, !!optimizer, optimizerRuns || 200, evmVersion || "london", flattenPath, chainId);
143
+ return;
144
+ }
124
145
  form.append("compiler", fullCompiler);
125
146
  form.append("license", "3"); // MIT
126
147
  form.append("optimizer", optimizer ? "1" : "0");
127
- form.append("runs", String(optimizerRuns));
148
+ form.append("runs", String(optimizerRuns ?? 200));
128
149
  form.append("viaIR", viaIR);
129
- form.append("evmVersion", evmVersion);
150
+ form.append("evmVersion", evmVersion || "london");
130
151
  // Encode constructor params from ABI if not pre-encoded
131
152
  let constructorParams = opts.constructorParams || "";
132
153
  if (!constructorParams && opts.constructorArgs && opts.constructorArgs.length > 0) {
@@ -164,11 +185,14 @@ async function verifyTron(hre, opts) {
164
185
  req.on("error", reject);
165
186
  form.pipe(req);
166
187
  });
167
- if (result.code === 200 || result.success) {
168
- console.log(`${opts.contractName} verified on TronScan: ${result.data?.message || "success"}`);
188
+ console.log(`TronScan raw response:`, JSON.stringify(result, null, 2));
189
+ const status = result.data?.status;
190
+ if ((result.code === 200 && (status === 200 || status === 2006)) || result.success) {
191
+ console.log(`${opts.contractName} verified on TronScan`);
169
192
  }
170
193
  else {
171
- console.log(`TronScan response:`, JSON.stringify(result, null, 2));
194
+ console.log(`verification failed (status: ${status}): ${result.data?.message || "unknown"}`);
195
+ console.log(` compiler: ${fullCompiler}, evmVersion: ${evmVersion}, optimizer: ${optimizer}, runs: ${optimizerRuns}, viaIR: ${viaIR}`);
172
196
  printTronVerifyInfo(address, opts.contractName, fullCompiler, optimizer, optimizerRuns, evmVersion, flattenPath, chainId);
173
197
  }
174
198
  }
package/utils/verifier.ts CHANGED
@@ -11,14 +11,24 @@ const TRONSCAN_API: Record<number, string> = {
11
11
  };
12
12
 
13
13
  export interface VerifyOptions {
14
- address: string; // contract address
14
+ address: string; // contract address (0x hex or Tron base58)
15
15
  contractName: string; // e.g. "AuthorityManager"
16
16
  contractPath?: string; // e.g. "contracts/AuthorityManager.sol:AuthorityManager"
17
- constructorArgs?: any[]; // constructor arguments (raw values)
18
- constructorParams?: string; // pre-encoded constructor params hex (without 0x), overrides constructorArgs
19
- compiler?: string; // solc version, defaults to "0.8.25"
20
- optimizer?: boolean; // defaults to true
21
- optimizerRuns?: number; // defaults to 200
17
+ /**
18
+ * Constructor arguments as raw values auto-encoded via ethers `encodeDeploy`.
19
+ * Example: ["0xAbC123...", "0xDeF456...", 200]
20
+ * Use this for most cases. Mutually exclusive with constructorParams.
21
+ */
22
+ constructorArgs?: any[];
23
+ /**
24
+ * Pre-encoded constructor params as hex string (without 0x prefix).
25
+ * Use this when you already have the ABI-encoded bytes, e.g. from `cast abi-encode`.
26
+ * Takes priority over constructorArgs if both are provided.
27
+ */
28
+ constructorParams?: string;
29
+ compiler?: string; // override solc version (auto-read from build-info if omitted)
30
+ optimizer?: boolean; // override optimizer enabled (auto-read from build-info if omitted)
31
+ optimizerRuns?: number; // override optimizer runs (auto-read from build-info if omitted)
22
32
  }
23
33
 
24
34
  /**
@@ -71,13 +81,13 @@ async function verifyTron(hre: any, opts: VerifyOptions): Promise<void> {
71
81
  const fs = require("fs");
72
82
  const path = require("path");
73
83
 
74
- // Auto-read compiler settings from hardhat config
84
+ // Compiler settings: opts > build-info > hardhat config (no hardcoded defaults)
75
85
  const solcConfig = hre.config?.solidity?.compilers?.[0] || hre.config?.solidity || {};
76
- const compiler = opts.compiler || solcConfig.version || "0.8.25";
77
- const optimizer = opts.optimizer ?? solcConfig.settings?.optimizer?.enabled ?? true;
78
- const optimizerRuns = opts.optimizerRuns ?? solcConfig.settings?.optimizer?.runs ?? 200;
79
- const evmVersion = solcConfig.settings?.evmVersion || "london";
80
- const viaIR = solcConfig.settings?.viaIR ? "1" : "0";
86
+ let compiler = opts.compiler || solcConfig.version || "";
87
+ let optimizer = opts.optimizer ?? solcConfig.settings?.optimizer?.enabled;
88
+ let optimizerRuns = opts.optimizerRuns ?? solcConfig.settings?.optimizer?.runs;
89
+ let evmVersion = solcConfig.settings?.evmVersion || "";
90
+ let viaIR = solcConfig.settings?.viaIR ? "1" : "0";
81
91
 
82
92
  // Convert address to Tron format
83
93
  let address = opts.address;
@@ -89,22 +99,21 @@ async function verifyTron(hre: any, opts: VerifyOptions): Promise<void> {
89
99
  const outputDir = path.join(process.cwd(), "verify-output");
90
100
  const flattenPath = path.join(outputDir, `${opts.contractName}_flatten.sol`);
91
101
 
92
- console.log(`generating flatten for ${opts.contractName}...`);
93
- try {
94
- // Find actual source path from artifact
95
- const artifact = await hre.artifacts.readArtifact(opts.contractName);
96
- const sourcePath = artifact.sourceName; // e.g. "contracts/factory/Create2Factory.sol"
97
- let flattenedSource = await hre.run("flatten:get-flattened-sources", {
98
- files: [sourcePath],
99
- });
100
- flattenedSource = removeDuplicateSPDX(flattenedSource);
101
- fs.mkdirSync(outputDir, { recursive: true });
102
- fs.writeFileSync(flattenPath, flattenedSource);
103
- console.log(`flatten saved to: ${flattenPath}`);
104
- } catch (e) {
105
- if (fs.existsSync(flattenPath)) {
106
- console.log(`using existing flatten: ${flattenPath}`);
107
- } else {
102
+ if (fs.existsSync(flattenPath)) {
103
+ console.log(`using existing flatten: ${flattenPath}`);
104
+ } else {
105
+ console.log(`generating flatten for ${opts.contractName}...`);
106
+ try {
107
+ const artifact = await hre.artifacts.readArtifact(opts.contractName);
108
+ const sourcePath = artifact.sourceName;
109
+ let flattenedSource = await hre.run("flatten:get-flattened-sources", {
110
+ files: [sourcePath],
111
+ });
112
+ flattenedSource = removeDuplicateSPDX(flattenedSource);
113
+ fs.mkdirSync(outputDir, { recursive: true });
114
+ fs.writeFileSync(flattenPath, flattenedSource);
115
+ console.log(`flatten saved to: ${flattenPath}`);
116
+ } catch (e) {
108
117
  console.log(`flatten failed, generate manually: forge flatten contracts/${opts.contractName}.sol`);
109
118
  return;
110
119
  }
@@ -124,28 +133,47 @@ async function verifyTron(hre: any, opts: VerifyOptions): Promise<void> {
124
133
  const form = new FormData();
125
134
  form.append("contractAddress", address);
126
135
  form.append("contractName", opts.contractName);
127
- // Read full compiler version from build-info (includes commit hash)
136
+ // Read compiler settings from build-info that contains this contract
128
137
  let fullCompiler = `v${compiler}`;
129
138
  try {
130
139
  const buildInfoDir = path.join(process.cwd(), "artifacts/build-info");
131
- const buildInfoFiles = fs.readdirSync(buildInfoDir);
132
- if (buildInfoFiles.length > 0) {
133
- const buildInfo = JSON.parse(fs.readFileSync(
134
- path.join(buildInfoDir, buildInfoFiles[0]), "utf-8"
135
- ));
136
- if (buildInfo.solcLongVersion) {
137
- fullCompiler = `v${buildInfo.solcLongVersion}`;
140
+ const buildInfoFiles = fs.readdirSync(buildInfoDir).filter((f: string) => f.endsWith(".json"));
141
+ const artifact = await hre.artifacts.readArtifact(opts.contractName);
142
+ const sourceName = artifact.sourceName;
143
+
144
+ for (const file of buildInfoFiles) {
145
+ const buildInfo = JSON.parse(fs.readFileSync(path.join(buildInfoDir, file), "utf-8"));
146
+ if (buildInfo.output?.contracts?.[sourceName]?.[opts.contractName]) {
147
+ if (buildInfo.solcLongVersion) {
148
+ fullCompiler = `v${buildInfo.solcLongVersion}`;
149
+ }
150
+ // Override settings from actual build-info input
151
+ const settings = buildInfo.input?.settings;
152
+ if (settings) {
153
+ if (settings.evmVersion) evmVersion = settings.evmVersion;
154
+ if (settings.optimizer != null) {
155
+ optimizer = settings.optimizer.enabled ?? optimizer;
156
+ optimizerRuns = settings.optimizer.runs ?? optimizerRuns;
157
+ }
158
+ if (settings.viaIR != null) viaIR = settings.viaIR ? "1" : "0";
159
+ }
160
+ break;
138
161
  }
139
162
  }
140
163
  } catch (e: any) {
141
164
  console.log(`[warn] could not read build-info: ${e.message}`);
142
165
  }
166
+ if (!fullCompiler.includes("+commit.")) {
167
+ console.log(`[error] could not determine full compiler version (got: ${fullCompiler}). Run 'npx hardhat compile' to generate build-info.`);
168
+ printTronVerifyInfo(address, opts.contractName, fullCompiler, !!optimizer, optimizerRuns || 200, evmVersion || "london", flattenPath, chainId);
169
+ return;
170
+ }
143
171
  form.append("compiler", fullCompiler);
144
172
  form.append("license", "3"); // MIT
145
173
  form.append("optimizer", optimizer ? "1" : "0");
146
- form.append("runs", String(optimizerRuns));
174
+ form.append("runs", String(optimizerRuns ?? 200));
147
175
  form.append("viaIR", viaIR);
148
- form.append("evmVersion", evmVersion);
176
+ form.append("evmVersion", evmVersion || "london");
149
177
  // Encode constructor params from ABI if not pre-encoded
150
178
  let constructorParams = opts.constructorParams || "";
151
179
  if (!constructorParams && opts.constructorArgs && opts.constructorArgs.length > 0) {
@@ -180,10 +208,13 @@ async function verifyTron(hre: any, opts: VerifyOptions): Promise<void> {
180
208
  form.pipe(req);
181
209
  });
182
210
 
183
- if (result.code === 200 || result.success) {
184
- console.log(`${opts.contractName} verified on TronScan: ${result.data?.message || "success"}`);
211
+ console.log(`TronScan raw response:`, JSON.stringify(result, null, 2));
212
+ const status = result.data?.status;
213
+ if ((result.code === 200 && (status === 200 || status === 2006)) || result.success) {
214
+ console.log(`${opts.contractName} verified on TronScan`);
185
215
  } else {
186
- console.log(`TronScan response:`, JSON.stringify(result, null, 2));
216
+ console.log(`verification failed (status: ${status}): ${result.data?.message || "unknown"}`);
217
+ console.log(` compiler: ${fullCompiler}, evmVersion: ${evmVersion}, optimizer: ${optimizer}, runs: ${optimizerRuns}, viaIR: ${viaIR}`);
187
218
  printTronVerifyInfo(address, opts.contractName, fullCompiler, optimizer, optimizerRuns, evmVersion, flattenPath, chainId);
188
219
  }
189
220
  } catch (e: any) {