feat: bridge chain artifacts to typed web reads
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
|
||||
const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const bankRequirements = {
|
||||
functions: ["contractVersion", "paused", "asset", "owner", "totalLiabilities", "balanceOf"],
|
||||
events: ["Deposited", "Withdrawn", "Paused", "Unpaused", "OwnershipTransferred", "Upgraded"],
|
||||
};
|
||||
const tokenRequirements = { functions: ["balanceOf"], events: [] };
|
||||
|
||||
export function extractAbi(artifact, contractName) {
|
||||
if (!artifact || typeof artifact !== "object" || !Array.isArray(artifact.abi)) {
|
||||
throw new Error(`${contractName} artifact must contain an ABI`);
|
||||
}
|
||||
const requirements = contractName === "BankV1" ? bankRequirements : contractName === "MockUSDC" ? tokenRequirements : null;
|
||||
if (!requirements) throw new Error(`unsupported contract artifact ${contractName}`);
|
||||
for (const [type, names] of Object.entries(requirements)) {
|
||||
for (const name of names) {
|
||||
if (!artifact.abi.some((entry) => entry && entry.type === type.slice(0, -1) && entry.name === name)) {
|
||||
throw new Error(`${contractName} ABI is missing required ${type.slice(0, -1)} ${name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return artifact.abi;
|
||||
}
|
||||
|
||||
export function renderContractsModule(bankV1Abi, mockUsdcAbi) {
|
||||
return `/* This file is generated by tools/sync-web-artifacts.mjs. Do not edit. */\n\nexport const bankV1Abi = ${JSON.stringify(bankV1Abi, null, 2)} as const;\n\nexport const mockUsdcAbi = ${JSON.stringify(mockUsdcAbi, null, 2)} as const;\n`;
|
||||
}
|
||||
|
||||
export async function syncArtifacts({
|
||||
bankArtifactPath = resolve(repositoryRoot, "out/BankV1.sol/BankV1.json"),
|
||||
tokenArtifactPath = resolve(repositoryRoot, "out/MockUSDC.sol/MockUSDC.json"),
|
||||
outputPath = resolve(repositoryRoot, "web/src/generated/contracts.ts"),
|
||||
check = false,
|
||||
} = {}) {
|
||||
const [bankArtifact, tokenArtifact] = await Promise.all([
|
||||
readArtifact(bankArtifactPath, "BankV1"),
|
||||
readArtifact(tokenArtifactPath, "MockUSDC"),
|
||||
]);
|
||||
const contents = renderContractsModule(extractAbi(bankArtifact, "BankV1"), extractAbi(tokenArtifact, "MockUSDC"));
|
||||
if (check) {
|
||||
let existing;
|
||||
try { existing = await readFile(outputPath, "utf8"); } catch { throw new Error("generated contracts module is stale or missing"); }
|
||||
if (existing !== contents) throw new Error("generated contracts module is stale");
|
||||
return contents;
|
||||
}
|
||||
await mkdir(dirname(outputPath), { recursive: true });
|
||||
await writeFile(outputPath, contents);
|
||||
return contents;
|
||||
}
|
||||
|
||||
async function readArtifact(path, contractName) {
|
||||
let contents;
|
||||
try { contents = await readFile(path, "utf8"); } catch { throw new Error(`${contractName} artifact is missing at ${path}`); }
|
||||
try { return JSON.parse(contents); } catch { throw new Error(`${contractName} artifact contains invalid JSON`); }
|
||||
}
|
||||
|
||||
if (process.argv[1] && pathToFileURL(resolve(process.argv[1])).href === import.meta.url) {
|
||||
const check = process.argv.slice(2).every((argument) => argument === "--check") && process.argv.includes("--check");
|
||||
if (process.argv.slice(2).some((argument) => argument !== "--check")) {
|
||||
console.error("usage: sync-web-artifacts.mjs [--check]");
|
||||
process.exitCode = 1;
|
||||
} else {
|
||||
syncArtifacts({ check }).catch((error) => { console.error(error.message); process.exitCode = 1; });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user