Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: scripts optimization #160

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions configs/deployments.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"devnet": {},
"sepolia": {
"HelloStarknet": {
"contract": "HelloStarknet",
"classHash": "0x659beca87b6eb6621573f9155f15f970caf513d419ba46b545b29439e0045c6",
"constructorArgs": [],
"address": ""
}
},
"mainnet": {}
}
13 changes: 13 additions & 0 deletions configs/scaffold.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"network": "sepolia",
"url": "https://free-rpc.nethermind.io/sepolia-juno/",
"feeToken": "eth",
"maxFee": 894843045483,
"account": {
"name": "scaffold",
"profile": "scaffold"
},
"contracts": [
"HelloStarknet"
]
}
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
{
"name": "create-starknet-app",
"version": "0.6.0",
"name": "test-create-starknet-app",
"version": "0.5.8",
"description": "An open-source starknet development stack",
"bin": "./bin/cli.mjs",
"scripts": {
"prepare-account": "node scaffold_scripts/prepare-account.js",
"deploy-account": "node scaffold_scripts/deploy-account.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",
"prepare-account": "cd contracts && sncast account create --url ${npm_config_url} --name ${npm_config_name} --add-profile",
"deploy-account": "cd contracts && sncast --profile ${npm_config_profile} account deploy --name ${npm_config_name} --fee-token ${npm_config_fee_token} --max-fee ${npm_config_max_fee}",
"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",
Expand Down
168 changes: 168 additions & 0 deletions scaffold_scripts/declareContract.mjs
Original file line number Diff line number Diff line change
@@ -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<string>} - 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<string>} - 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();
}
})();
26 changes: 26 additions & 0 deletions scaffold_scripts/deploy-account.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const { execSync } = require("child_process");
const fs = require("fs");
const path = require("path");

// Load configuration
const configPath = path.resolve(__dirname, "../configs/scaffold.config.json");
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));

// Destructure variables
const {
feeToken,
maxFee,
account: { name, profile },
} = config;

// Build the command
const command = `cd contracts && sncast --profile ${profile} account deploy --name ${name} --fee-token ${feeToken} --max-fee ${maxFee}`;

try {
console.log("Running deploy-account command...");
execSync(command, { stdio: "inherit" });
console.log("Account deployed successfully!");
} catch (error) {
console.error("Error deploying account:", error.message);
process.exit(1);
}
63 changes: 63 additions & 0 deletions scaffold_scripts/generateContractNames.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { readdirSync, readFileSync, writeFileSync } from "fs";
import { resolve, join } from "path";

const configPath = resolve(process.cwd(), "configs/scaffold.config.json");
const contractsDir = resolve(process.cwd(), "contracts/src");

/**
* Scans the contracts directory and returns an array of contract names.
* @returns {string[]} An array of contract names.
*/
function getContractNames() {
try {
// Read all files in the contracts directory with .cairo extension
const cairoFiles = readdirSync(contractsDir).filter((file) =>
file.endsWith(".cairo")
);

// Extract contract names using regex
return cairoFiles.flatMap((file) => {
const filePath = join(contractsDir, file);
const content = readFileSync(filePath, "utf-8");
const contractRegex = /#\[\s*starknet::contract\s*]\s*mod\s+(\w+)/g;
const matches = [...content.matchAll(contractRegex)];
return matches.map((match) => match[1]); // Extract the contract name from each match
});
} catch (err) {
throw new Error(`Failed to read contract files: ${err.message}`);
}
}

/**
* Reads a JSON configuration file and updates the contract names array
* @param {string[]} contractNames - The array of contract names for updating.
*/
function updateConfigFileWithContractNames(contractNames) {
try {
const config = JSON.parse(readFileSync(configPath, "utf-8"));

// Update and write the configuration file
config["contract-names"] = contractNames;
writeFileSync(configPath, JSON.stringify(config, null, 2));

console.log("Contract names added to config file successfully!");
} catch (err) {
throw new Error(`Error updating config file: ${err.message}`);
}
}

/**
* Main function to scan contracts and update the configuration file.
*/
function main() {
try {
const contractNames = getContractNames();
console.log("Contracts found:", contractNames);

updateConfigFileWithContractNames(contractNames);
} catch (err) {
console.error(err.message);
}
}

main();
25 changes: 25 additions & 0 deletions scaffold_scripts/prepare-account.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const { execSync } = require("child_process");
const fs = require("fs");
const path = require("path");

// Load configuration
const configPath = path.resolve(__dirname, "../configs/scaffold.config.json");
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));

// Destructure variables
const {
url,
account: { name, profile },
} = config;

// Build the command
const command = `cd contracts && sncast account create --url ${url} --name ${name} --add-profile ${profile}`;

try {
console.log("Running prepare-account command...");
execSync(command, { stdio: "inherit" });
console.log("Account prepared successfully!");
} catch (error) {
console.error("Error preparing account:", error.message);
process.exit(1);
}
Loading