@mapprotocol/common-contracts 0.3.1 → 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.
@@ -0,0 +1,229 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.verify = verify;
4
+ /**
5
+ * Contract verification — supports Etherscan, Blockscout (Mapo), and TronScan.
6
+ * Auto-routes based on network name.
7
+ */
8
+ const tronHelper_1 = require("./tronHelper");
9
+ // TronScan API endpoints by chainId
10
+ const TRONSCAN_API = {
11
+ 728126428: "https://apilist.tronscan.org/api/solidity/contract/verify", // mainnet
12
+ 3448148188: "https://nile.tronscan.org/api/solidity/contract/verify", // nile testnet
13
+ };
14
+ /**
15
+ * Unified contract verification — auto-routes to EVM (hardhat verify) or Tron (TronScan API)
16
+ */
17
+ async function verify(hre, opts) {
18
+ const network = hre.network.name;
19
+ if ((0, tronHelper_1.isTronNetwork)(network)) {
20
+ await verifyTron(hre, opts);
21
+ }
22
+ else {
23
+ await verifyEvm(hre, opts);
24
+ }
25
+ }
26
+ // ============================================================
27
+ // EVM verification via hardhat-verify
28
+ // ============================================================
29
+ async function verifyEvm(hre, opts) {
30
+ const contractPath = opts.contractPath || `contracts/${opts.contractName}.sol:${opts.contractName}`;
31
+ console.log(`verifying ${opts.contractName} at ${opts.address} ...`);
32
+ try {
33
+ await hre.run("verify:verify", {
34
+ contract: contractPath,
35
+ address: opts.address,
36
+ constructorArguments: opts.constructorArgs || [],
37
+ });
38
+ console.log(`${opts.contractName} verified`);
39
+ }
40
+ catch (e) {
41
+ if (e.message?.includes("Already Verified")) {
42
+ console.log(`${opts.contractName} already verified`);
43
+ }
44
+ else {
45
+ console.log(`verification failed: ${e.message || e}`);
46
+ // Print manual command as fallback
47
+ const args = (opts.constructorArgs || []).map((a) => typeof a === "string" ? `'${a}'` : a).join(" ");
48
+ console.log(`manual: npx hardhat verify --network ${hre.network.name} --contract ${contractPath} ${opts.address} ${args}`);
49
+ }
50
+ }
51
+ }
52
+ // ============================================================
53
+ // Tron verification via TronScan API
54
+ // ============================================================
55
+ async function verifyTron(hre, opts) {
56
+ const chainId = hre.network.config.chainId;
57
+ const fs = require("fs");
58
+ const path = require("path");
59
+ // Compiler settings: opts > build-info > hardhat config (no hardcoded defaults)
60
+ const solcConfig = hre.config?.solidity?.compilers?.[0] || hre.config?.solidity || {};
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
+ // Convert address to Tron format
67
+ let address = opts.address;
68
+ if (address.startsWith("0x")) {
69
+ address = (0, tronHelper_1.tronFromHex)(address);
70
+ }
71
+ // Generate flattened source
72
+ const outputDir = path.join(process.cwd(), "verify-output");
73
+ const flattenPath = path.join(outputDir, `${opts.contractName}_flatten.sol`);
74
+ if (fs.existsSync(flattenPath)) {
75
+ console.log(`using existing flatten: ${flattenPath}`);
76
+ }
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}`);
89
+ }
90
+ catch (e) {
91
+ console.log(`flatten failed, generate manually: forge flatten contracts/${opts.contractName}.sol`);
92
+ return;
93
+ }
94
+ }
95
+ // Submit to TronScan API
96
+ const apiUrl = TRONSCAN_API[chainId];
97
+ if (!apiUrl) {
98
+ console.log(`no TronScan API for chainId ${chainId}, printing manual instructions instead`);
99
+ printTronVerifyInfo(address, opts.contractName, compiler, optimizer, optimizerRuns, evmVersion, flattenPath, chainId);
100
+ return;
101
+ }
102
+ console.log(`submitting verification to TronScan for ${address}...`);
103
+ try {
104
+ const FormData = require("form-data");
105
+ const form = new FormData();
106
+ form.append("contractAddress", address);
107
+ form.append("contractName", opts.contractName);
108
+ // Read compiler settings from build-info that contains this contract
109
+ let fullCompiler = `v${compiler}`;
110
+ try {
111
+ const buildInfoDir = path.join(process.cwd(), "artifacts/build-info");
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;
134
+ }
135
+ }
136
+ }
137
+ catch (e) {
138
+ console.log(`[warn] could not read build-info: ${e.message}`);
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
+ }
145
+ form.append("compiler", fullCompiler);
146
+ form.append("license", "3"); // MIT
147
+ form.append("optimizer", optimizer ? "1" : "0");
148
+ form.append("runs", String(optimizerRuns ?? 200));
149
+ form.append("viaIR", viaIR);
150
+ form.append("evmVersion", evmVersion || "london");
151
+ // Encode constructor params from ABI if not pre-encoded
152
+ let constructorParams = opts.constructorParams || "";
153
+ if (!constructorParams && opts.constructorArgs && opts.constructorArgs.length > 0) {
154
+ const { Interface } = require("ethers");
155
+ const artifact = await hre.artifacts.readArtifact(opts.contractName);
156
+ const iface = new Interface(artifact.abi);
157
+ const encoded = iface.encodeDeploy(opts.constructorArgs);
158
+ constructorParams = encoded.slice(2); // remove 0x
159
+ }
160
+ form.append("constructorParams", constructorParams);
161
+ form.append("files", fs.createReadStream(flattenPath), {
162
+ filename: `${opts.contractName}.flat.tron.sol`,
163
+ contentType: "application/octet-stream",
164
+ });
165
+ const result = await new Promise((resolve, reject) => {
166
+ const url = new URL(apiUrl);
167
+ const https = require("https");
168
+ const req = https.request({
169
+ hostname: url.hostname,
170
+ path: url.pathname,
171
+ method: "POST",
172
+ headers: form.getHeaders(),
173
+ }, (res) => {
174
+ let data = "";
175
+ res.on("data", (chunk) => data += chunk);
176
+ res.on("end", () => {
177
+ try {
178
+ resolve(JSON.parse(data));
179
+ }
180
+ catch {
181
+ resolve({ code: -1, errmsg: data });
182
+ }
183
+ });
184
+ });
185
+ req.on("error", reject);
186
+ form.pipe(req);
187
+ });
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`);
192
+ }
193
+ else {
194
+ console.log(`verification failed (status: ${status}): ${result.data?.message || "unknown"}`);
195
+ console.log(` compiler: ${fullCompiler}, evmVersion: ${evmVersion}, optimizer: ${optimizer}, runs: ${optimizerRuns}, viaIR: ${viaIR}`);
196
+ printTronVerifyInfo(address, opts.contractName, fullCompiler, optimizer, optimizerRuns, evmVersion, flattenPath, chainId);
197
+ }
198
+ }
199
+ catch (e) {
200
+ console.log(`TronScan API error: ${e.message || e}`);
201
+ printTronVerifyInfo(address, opts.contractName, `v${compiler}`, optimizer, optimizerRuns, evmVersion, flattenPath, chainId);
202
+ }
203
+ }
204
+ function printTronVerifyInfo(address, contractName, compiler, optimizer, runs, evmVersion, flattenPath, chainId) {
205
+ const isMainnet = chainId === 728126428;
206
+ const tronscanUrl = isMainnet
207
+ ? `https://tronscan.org/#/contract/${address}/code`
208
+ : `https://nile.tronscan.org/#/contract/${address}/code`;
209
+ console.log(`\n=== Verify manually on TronScan ===`);
210
+ console.log(`Address: ${address}`);
211
+ console.log(`Contract: ${contractName}`);
212
+ console.log(`Compiler: ${compiler}`);
213
+ console.log(`Optimizer: ${optimizer ? "enabled" : "disabled"}, runs: ${runs}`);
214
+ console.log(`EVM: ${evmVersion}`);
215
+ console.log(`Flatten: ${flattenPath}`);
216
+ console.log(`URL: ${tronscanUrl}`);
217
+ console.log(`=====================================\n`);
218
+ }
219
+ function removeDuplicateSPDX(source) {
220
+ let found = false;
221
+ return source.split("\n").filter(line => {
222
+ if (line.includes("SPDX-License-Identifier")) {
223
+ if (found)
224
+ return false;
225
+ found = true;
226
+ }
227
+ return true;
228
+ }).join("\n");
229
+ }
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) {