zonder
Version:
Ergonomic multi-chain indexing framework with dual runtime support for Ponder and Envio.
113 lines (112 loc) ⢠4.39 kB
JavaScript
import * as fs from 'fs';
import * as path from 'path';
import { safeWriteFileSync } from '../utils/safeWrite.js';
export function takeAbiFromFoundryOutput(foundryJsonPath) {
try {
const content = fs.readFileSync(foundryJsonPath, 'utf8');
const foundryOutput = JSON.parse(content);
return foundryOutput.abi;
}
catch (error) {
console.error(`Error reading ${foundryJsonPath}:`, error.message);
return null;
}
}
export function generateTypeScriptAbiFile(abi) {
const formattedAbi = JSON.stringify(abi, null, 2);
return `export default ${formattedAbi} as const;\n`;
}
function findContractFile(dirPath, targetContract) {
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
const found = findContractFile(fullPath, targetContract);
if (found)
return found;
}
else if (entry.name === `${targetContract}.json`) {
return fullPath;
}
}
return null;
}
function findAllContractFiles(dirPath) {
const contracts = [];
if (!fs.existsSync(dirPath)) {
return contracts;
}
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
contracts.push(...findAllContractFiles(fullPath));
}
else if (entry.name.endsWith('.json')) {
const contractName = entry.name.replace('.json', '');
contracts.push(contractName);
}
}
return contracts;
}
export function extractSpecificContract(foundryOutDir, contractName, outputDir, overwrite = false) {
if (!fs.existsSync(foundryOutDir)) {
console.error(`Foundry output directory not found: ${foundryOutDir}`);
return false;
}
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
const contractJsonPath = findContractFile(foundryOutDir, contractName);
if (!contractJsonPath) {
console.error(`Contract ${contractName}.json not found in ${foundryOutDir}`);
// Show available contracts
const availableContracts = findAllContractFiles(foundryOutDir);
if (availableContracts.length > 0) {
const sortedContracts = [...new Set(availableContracts)].sort();
console.log('\nš” Available contracts:');
sortedContracts.forEach((contract) => {
console.log(` - ${contract}`);
});
console.log(`\nš” Usage: pnpm zonder take-abi ${foundryOutDir} ${sortedContracts.slice(0, 3).join(' ')}`);
}
return false;
}
const abi = takeAbiFromFoundryOutput(contractJsonPath);
if (!abi || abi.length === 0) {
console.error(`No ABI found for ${contractName}`);
return false;
}
const tsContent = generateTypeScriptAbiFile(abi);
const outputPath = path.join(outputDir, `${contractName}.ts`);
safeWriteFileSync(outputPath, tsContent, { overwrite });
return true;
}
export function extractMultipleContracts(foundryOutDir, contractNames, outputDir = './abis', overwrite = false) {
const foundryOutDirResolved = path.resolve(foundryOutDir);
const outputDirResolved = path.resolve(outputDir);
// Check if foundry out directory exists
if (!fs.existsSync(foundryOutDirResolved)) {
console.error(`ā Foundry output directory not found: ${foundryOutDirResolved}`);
console.log('š” Make sure you have run "forge build" in your Foundry project first.');
process.exit(1);
}
let successCount = 0;
let failCount = 0;
for (const contractName of contractNames) {
const success = extractSpecificContract(foundryOutDirResolved, contractName, outputDirResolved, overwrite);
if (success) {
successCount++;
}
else {
failCount++;
}
}
console.log(`\nExtraction completed: ${successCount} succeeded, ${failCount} failed`);
if (failCount > 0) {
process.exit(1);
}
}
export async function takeAbi(outDir, contracts, overwrite = false) {
return extractMultipleContracts(outDir, contracts, './abis', overwrite);
}