From 8a0372a5272124f7e9396ef6e8437cf3aec1bbcb Mon Sep 17 00:00:00 2001 From: EjembiEmmanuel Date: Wed, 18 Dec 2024 22:27:18 +0100 Subject: [PATCH] feat: declare contract script --- configs/deployments.config.json | 12 ++ configs/scaffold.config.json | 3 +- package.json | 4 +- scaffold_scripts/declareContract.mjs | 168 +++++++++++++++++++++++++++ 4 files changed, 184 insertions(+), 3 deletions(-) create mode 100644 configs/deployments.config.json create mode 100644 scaffold_scripts/declareContract.mjs diff --git a/configs/deployments.config.json b/configs/deployments.config.json new file mode 100644 index 0000000..edb158e --- /dev/null +++ b/configs/deployments.config.json @@ -0,0 +1,12 @@ +{ + "devnet": {}, + "sepolia": { + "HelloStarknet": { + "contract": "HelloStarknet", + "classHash": "0x659beca87b6eb6621573f9155f15f970caf513d419ba46b545b29439e0045c6", + "constructorArgs": [], + "address": "" + } + }, + "mainnet": {} +} \ No newline at end of file diff --git a/configs/scaffold.config.json b/configs/scaffold.config.json index ce6d894..ae6c393 100644 --- a/configs/scaffold.config.json +++ b/configs/scaffold.config.json @@ -1,4 +1,5 @@ { + "network": "sepolia", "url": "https://free-rpc.nethermind.io/sepolia-juno/", "feeToken": "eth", "maxFee": 894843045483, @@ -6,7 +7,7 @@ "name": "scaffold", "profile": "scaffold" }, - "contract-names": [ + "contracts": [ "HelloStarknet" ] } \ No newline at end of file diff --git a/package.json b/package.json index 66ad34e..f43928c 100644 --- a/package.json +++ b/package.json @@ -6,15 +6,15 @@ "scripts": { "prepare-account": "node scaffold_scripts/prepare-account.js", "deploy-account": "node scaffold_scripts/deploy-account.js", - "get-contract-names": "node scaffold_scripts/get-contract-names.js", + "generate-contract-names": "node scaffold_scripts/generateContractNames.mjs", "build-contracts": "cd contracts && scarb build", "test-contracts": "cd contracts && snforge test", "format-contracts": "cd contracts && scarb fmt", + "declare-contract": "node scaffold_scripts/declareContract.mjs", "verify-contracts": "cd contracts && sncast verify --contract-address ${npm_config_contract_address} --contract-name ${npm_config_contract_name} --verifier walnut --network ${npm_config_network}", "contract-scripts": "cd contracts/scripts && sncast script run ${npm_config_script} --url ${npm_config_url}", "generate-interface": "cd contracts && src5_rs parse", "delete-account": "cd contracts && sncast --profile ${npm_config_profile} --accounts-file ${npm_config_accounts_file} account delete --name ${npm_config_name} --network ${npm_config_network}", - "declare-contract": "cd contracts && sncast --profile ${npm_config_profile} declare --contract-name ${npm_config_contract_name} --fee-token ${npm_config_fee_token}", "deploy-contract": "cd contracts && sncast --profile ${npm_config_profile} deploy --fee-token ${npm_config_fee_token} --class-hash ${npm_config_class_hash}", "initialize-dojo": "rm -rf contracts && mkdir contracts && cd contracts && sozo init ${npm_config_name}", "build-dojo": "cd contracts/${npm_config_name} && sozo build", diff --git a/scaffold_scripts/declareContract.mjs b/scaffold_scripts/declareContract.mjs new file mode 100644 index 0000000..3e09192 --- /dev/null +++ b/scaffold_scripts/declareContract.mjs @@ -0,0 +1,168 @@ +import cp, { execSync } from "child_process"; +import { readFileSync, existsSync, writeFileSync } from "fs"; +import { resolve } from "path"; +import { createInterface } from "readline"; +import { promisify } from "util"; +import ora from "ora"; + +// Resolve config path +const configPath = resolve(process.cwd(), "configs/scaffold.config.json"); +const deploymentsPath = resolve( + process.cwd(), + "configs/deployments.config.json" +); + +// convert libs to promises +const exec = promisify(cp.exec); + +// Check if the config file exists +if (!existsSync(configPath) || !existsSync(deploymentsPath)) { + console.error("Error: Config file not found. Ensure the correct setup."); + process.exit(1); +} + +// Load and parse the configuration file +let config; +try { + config = JSON.parse(readFileSync(configPath, "utf-8")); +} catch (error) { + console.error("Error reading or parsing the config file:", error.message); + process.exit(1); +} + +// Initialize readline interface +const rl = createInterface({ + input: process.stdin, + output: process.stdout, +}); + +/** + * Prompt the user with a question and return their response. + * @param {string} question - The question to ask. + * @returns {Promise} - User's input. + */ +const askQuestion = (question) => + new Promise((resolve) => rl.question(question, resolve)); + +/** + * Display contract names and prompt the user to select one. + * @returns {Promise} - The selected contract name. + */ +async function getContract() { + const contracts = config["contracts"] || []; + + if (!contracts.length) { + console.error( + "No contracts found. Please run 'npm run generate-contract-names' and try again." + ); + process.exit(1); + } + + console.log("Available Contracts:"); + contracts.forEach((contract, index) => + console.log(`${index + 1}. ${contract}`) + ); + + while (true) { + const choice = await askQuestion( + `Select the contract to declare (1-${contracts.length}): ` + ); + const selectedIndex = parseInt(choice, 10) - 1; + + if (selectedIndex >= 0 && selectedIndex < contracts.length) { + return contracts[selectedIndex]; + } + + console.log("Invalid choice. Please try again."); + } +} + +/** + * Validate if `sncast` is available in the environment. + */ +async function validateSncast() { + try { + await exec("sncast --version"); + } catch { + console.error("Error: `sncast` is not installed or not in PATH."); + process.exit(1); + } +} + +/** + * Reads a JSON configuration file and updates the deployment details. + * @param {string} network - The network of deployment. + * @param {string} contract - The name of the declared contract. + * @param {string} classHash - The declared class hash. + */ +function updateDeploymentConfig(network, contract, classHash) { + try { + const deployments = JSON.parse(readFileSync(deploymentsPath, "utf-8")); + + if (!deployments[network]) { + deployments[network] = {}; + } + + // Update and write the configuration file + deployments[network][contract] = { + contract, + classHash, + constructorArgs: [], + address: "", + }; + + // Save updated deployments + writeFileSync(deploymentsPath, JSON.stringify(deployments, null, 2)); + } catch (err) { + throw new Error(`Error updating deployments config file: ${err.message}`); + } +} + +(async () => { + const spinner = ora(); + + try { + // Validate `sncast` availability + validateSncast(); + + // Destructure config variables + const { + network, + feeToken, + account: { profile }, + } = config; + + // Get the selected contract name + const contract = await getContract(); + + // Build the command + const command = `cd contracts && sncast --profile ${profile} declare --contract-name ${contract} --fee-token ${feeToken}`; + + spinner.start("Declaring contract..."); + const output = await exec(command); + + if (output.stderr) { + throw new Error(output.stderr); + } + + const classHashMatch = output.stdout.match( + /class_hash:\s*(0x[0-9a-fA-F]+)/ + ); + const classHash = classHashMatch ? classHashMatch[1] : null; + + if (!classHash) { + throw new Error("class_hash not found in command output."); + } + + updateDeploymentConfig(network, contract, classHash); + + spinner.succeed("Contract declared successfully."); + console.log("Run 'npm deploy-contract' to deploy a contract."); + } catch (error) { + spinner.fail("Error during contract declaration."); + console.error("Error:", error.message); + process.exit(1); + } finally { + rl.close(); + } +})();