a manifold extension to port fully onchain an existing collection minted with offchain content
clgsms/scripts/verify.js
JavaScript · 118 lines · 4 KBRAWDOWNLOAD
/**
* Verify every deployed contract on Etherscan, from the recorded addresses.
*
* npx hardhat run scripts/verify.js --network sepolia
* npx hardhat run scripts/verify.js --network mainnet
*
* Reads deployments/<network>.json and verifies the storage, the extension and
* the renderer with the exact constructor arguments they were deployed with —
* no hand-copying addresses or remembering that the renderer's third argument
* is `preEncoded`.
*
* Needs ETHERSCAN_API_KEY in .env. Safe to re-run: a contract Etherscan has
* already verified is reported and skipped, not treated as a failure.
*
* Env:
* STORAGE / EXTENSION / RENDERER override the recorded addresses
* FORCE=1 re-verify even if already verified
*
* Run it a minute or so after deploying — Etherscan has to have indexed the
* deployment transaction before it can verify the code.
*/
import hre from "hardhat";
import { verifyContract } from "@nomicfoundation/hardhat-verify/verify";
import { connect, main, readDeployment } from "./_lib.js";
main(async () => {
const { ethers, net, chainId } = await connect();
if (chainId === 31337n) {
throw new Error("nothing to verify on a local chain — use --network sepolia or mainnet");
}
if (!process.env.ETHERSCAN_API_KEY) {
throw new Error("set ETHERSCAN_API_KEY in .env (one V2 key covers every chain)");
}
const d = readDeployment(net);
const pick = (k) => process.env[k.toUpperCase()] ?? d[k];
const targets = [
{
name: "ColorgasmStorage",
address: pick("storage"),
constructorArgs: [],
},
{
name: "ColorgasmRedeemExtension",
address: pick("extension"),
constructorArgs: [pick("creator"), d.firstId ?? 1, d.edition ?? 100],
},
{
name: "ColorgasmRendererV3",
address: pick("renderer"),
constructorArgs: [pick("extension"), pick("storage"), d.preEncoded ?? false],
},
];
const missing = targets.filter((t) => !t.address);
if (missing.length > 0) {
throw new Error(
`no address for ${missing.map((t) => t.name).join(", ")} on "${net}" — ` +
`deploy first, or pass ${missing.map((t) => t.name.replace("Colorgasm", "").toUpperCase()).join("/")}=0x…`,
);
}
console.log(
`verifying ${targets.length} contracts on ${net}\n` +
` artwork stored ${d.preEncoded ? "pre-encoded (base64)" : "raw"} — the renderer's ` +
`constructor argument must match this or the bytecode will not\n`,
);
const results = [];
for (const t of targets) {
const code = await ethers.provider.getCode(t.address);
if (code === "0x") {
results.push({ ...t, status: "NO CONTRACT" });
console.log(`✗ ${t.name.padEnd(26)} ${t.address} nothing deployed at this address`);
continue;
}
try {
await verifyContract(
{
address: t.address,
constructorArgs: t.constructorArgs,
force: process.env.FORCE === "1",
},
hre,
);
results.push({ ...t, status: "verified" });
console.log(`✓ ${t.name.padEnd(26)} ${t.address}`);
} catch (e) {
const already = /already verified|already been verified/i.test(e.message ?? "");
results.push({ ...t, status: already ? "already verified" : "FAILED" });
console.log(
`${already ? "•" : "✗"} ${t.name.padEnd(26)} ${t.address} ` +
(already ? "already verified" : (e.message ?? "").split("\n")[0]),
);
}
}
// The SSTORE2 pointers that hold the artwork are not verifiable and do not
// need to be: they have no source, only a STOP byte followed by raw data.
// Anyone can read them with eth_getCode and compare against the files —
// which is exactly what scripts/inspect.js does.
console.log(
`\nthe artwork chunks are SSTORE2 pointers — data, not code, so there is no\n` +
`source to verify. Check them with:\n` +
` FROM_ID=1 TO_ID=100 npx hardhat run scripts/inspect.js --network ${net}`,
);
const bad = results.filter((r) => r.status === "FAILED" || r.status === "NO CONTRACT");
if (bad.length > 0) {
console.log(`\n${bad.length} contract(s) not verified — see above`);
process.exitCode = 1;
} else {
console.log(`\nall ${results.length} contracts verified on ${net}`);
}
});