From dfd250f58ddac4893be83af6813fe6f20b8cf4fe Mon Sep 17 00:00:00 2001 From: port dev <108868128+portdeveloper@users.noreply.github.com> Date: Thu, 24 Oct 2024 10:06:50 +0300 Subject: [PATCH 1/6] Add shell script for prod contract deployments + Modify Makefile accordingly --- packages/foundry/Makefile | 23 +++- packages/foundry/scripts/select_keystore.sh | 144 ++++++++++++++++++++ 2 files changed, 163 insertions(+), 4 deletions(-) create mode 100755 packages/foundry/scripts/select_keystore.sh diff --git a/packages/foundry/Makefile b/packages/foundry/Makefile index d973c2010..b716321c3 100644 --- a/packages/foundry/Makefile +++ b/packages/foundry/Makefile @@ -22,7 +22,14 @@ deploy: @if [ "$(RPC_URL)" = "localhost" ]; then \ forge script script/Deploy.s.sol --rpc-url localhost --password localhost --broadcast --legacy --ffi; \ else \ - forge script script/Deploy.s.sol --rpc-url $(RPC_URL) --broadcast --legacy --ffi; \ + RPC_URL=$$(grep "^$(RPC_URL) =" foundry.toml | sed 's/.*= "\(.*\)"/\1/'); \ + KEYSTORE=$$(bash scripts/select_keystore.sh "$$RPC_URL"); \ + if [ -n "$$KEYSTORE" ]; then \ + forge script script/Deploy.s.sol --rpc-url $$RPC_URL --broadcast --legacy --ffi --account ~/.foundry/keystores/"$$KEYSTORE"; \ + else \ + echo "Keystore selection failed. Aborting deployment."; \ + exit 1; \ + fi; \ fi # Build and deploy target @@ -46,7 +53,6 @@ account: # Generate a new account account-generate: @cast wallet import $(ACCOUNT_NAME) --private-key $$(cast wallet new | grep 'Private key:' | awk '{print $$3}') - @echo "Please update .env file with ETH_KEYSTORE_ACCOUNT=$(ACCOUNT_NAME)" # Import an existing account account-import: @@ -61,7 +67,13 @@ deploy-verify: @if [ "$(RPC_URL)" = "localhost" ]; then \ forge script script/Deploy.s.sol --rpc-url localhost --password localhost --broadcast --legacy --ffi --verify; \ else \ - forge script script/Deploy.s.sol --rpc-url $(RPC_URL) --broadcast --legacy --ffi --verify; \ + KEYSTORE=$$(bash scripts/select_keystore.sh); \ + if [ -n "$$KEYSTORE" ]; then \ + forge script script/Deploy.s.sol --rpc-url $(RPC_URL) --broadcast --legacy --ffi --verify --account ~/.foundry/keystores/"$$KEYSTORE"; \ + else \ + echo "Keystore selection failed. Aborting deployment."; \ + exit 1; \ + fi; \ fi node scripts-js/generateTsAbis.js @@ -77,9 +89,12 @@ format: lint: forge fmt --check && prettier --check ./script/**/*.js +# Run tests +test: + forge test + # Verify contracts verify: forge script script/VerifyAll.s.sol --ffi --rpc-url $(RPC_URL) build-and-verify: build verify - diff --git a/packages/foundry/scripts/select_keystore.sh b/packages/foundry/scripts/select_keystore.sh new file mode 100755 index 000000000..57fb1a761 --- /dev/null +++ b/packages/foundry/scripts/select_keystore.sh @@ -0,0 +1,144 @@ +#!/bin/bash + +set -e + +KEYSTORE_DIR="$HOME/.foundry/keystores" +RPC_URL=$1 + +# Function to check balance +check_balance() { + local address=$1 + local rpc_url=$2 + local result=$(curl -s -X POST -H "Content-Type: application/json" --data "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getBalance\",\"params\":[\"$address\", \"latest\"],\"id\":1}" $rpc_url) + local balance=$(echo $result | sed 's/.*"result":"\(0x[a-fA-F0-9]*\)".*/\1/') + echo $balance +} + +# Function to convert hex to decimal +hex_to_decimal() { + printf '%d\n' "$1" +} + +# Function to convert wei to ether (simplified, shows only up to 6 decimal places) +wei_to_ether() { + awk -v wei="$1" 'BEGIN {printf "%.6f\n", wei / 1000000000000000000}' +} + +# Function to display balance +display_balance() { + local address=$1 + local rpc_url=$2 + local balance_hex=$(check_balance $address $rpc_url) + local balance_wei=$(hex_to_decimal $balance_hex) + local balance_eth=$(wei_to_ether $balance_wei) + echo "Current balance: $balance_eth ETH" >&2 +} + +# Check if the keystore directory exists +if [ ! -d "$KEYSTORE_DIR" ]; then + echo "No keystores found in $KEYSTORE_DIR, generating a new account..." >&2 + read -p "Enter a name for your new keystore: " keystore_name + yarn account:generate --name "$keystore_name" + if [ $? -ne 0 ]; then + echo "Failed to generate a new account" >&2 + exit 1 + fi +fi + +# List all keystores +keystores=($(ls "$KEYSTORE_DIR")) + +if [ ${#keystores[@]} -eq 0 ]; then + echo "No keystores found in $KEYSTORE_DIR, generating a new account..." >&2 + read -p "Enter a name for your new keystore: " keystore_name + yarn account:generate --name "$keystore_name" + if [ $? -ne 0 ]; then + echo "Failed to generate a new account" >&2 + exit 1 + fi + keystores=($(ls "$KEYSTORE_DIR")) +fi + +echo "You can also create a new keystore with a custom name by running 'yarn account:generate --name '" >&2 +echo "Available options/keystores:" >&2 +echo "0. Create new keystore" >&2 +for i in "${!keystores[@]}"; do + echo "$((i+1)). ${keystores[$i]}" >&2 +done + +# Prompt user to select a keystore +read -p "Select a keystore by entering the number (0-${#keystores[@]}): " selection + +# Validate selection +if ! [[ "$selection" =~ ^[0-9]+$ ]] || [ "$selection" -lt 0 ] || [ "$selection" -gt "${#keystores[@]}" ]; then + echo "Invalid selection" >&2 + exit 1 +fi + +if [ "$selection" -eq 0 ]; then + read -p "Enter a name for your new keystore: " keystore_name + echo "Generating a new keystore with the name: $keystore_name" >&2 + output=$(yarn account:generate --name "$keystore_name" 2>&1) + if [ $? -ne 0 ]; then + echo "Failed to generate a new account" >&2 + echo "$output" >&2 + exit 1 + fi + # Extract the address from the output + address=$(echo "$output" | grep -o '0x[0-9a-fA-F]\{40\}') + if [ -z "$address" ]; then + echo "Failed to extract address from output" >&2 + echo "$output" >&2 + exit 1 + fi + selected_keystore="$keystore_name" + echo "New keystore '$keystore_name' created and selected." >&2 +else + # Get the selected keystore + selected_keystore="${keystores[$((selection-1))]}" +fi + +# Check if the selected keystore is scaffold-eth-default +# Maybe we don't let the user use this keystore for deployment? +if [ "$selected_keystore" == "scaffold-eth-default" ]; then + echo "Warning: scaffold-eth-default is only for local development purposes." >&2 + echo "You can create a new keystore with a custom name by running 'yarn account:generate --name '" >&2 + read -p "Are you sure you want to use this keystore? (y/n): " confirm + if [[ ! "$confirm" =~ ^[Yy]$ ]]; then + echo "Keystore selection cancelled." >&2 + exit 1 + fi +fi + +# After selecting or creating the keystore +if [ -n "$selected_keystore" ]; then + # Get the address from the keystore if not already set + if [ -z "$address" ]; then + address=$(cast wallet address --account $KEYSTORE_DIR/"$selected_keystore") + fi + echo "Selected address: $address" >&2 + + while true; do + display_balance $address $RPC_URL + + read -p "Do you want to proceed with deployment? (y/n/c for check balance again): " confirm + case $confirm in + [Yy]* ) + break + ;; + [Nn]* ) + echo "Deployment cancelled." >&2 + exit 1 + ;; + [Cc]* ) + continue + ;; + * ) + echo "Please answer y (yes), n (no), or c (check balance again)." >&2 + ;; + esac + done +fi + +# Output only the selected keystore filename +echo "$selected_keystore" From 839e2b9a48cb7cb6385748bc0bbf589ffd9891d4 Mon Sep 17 00:00:00 2001 From: port dev <108868128+portdeveloper@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:39:28 +0300 Subject: [PATCH 2/6] Make the bash script work. WIP --- packages/foundry/scripts-js/parseArgs.js | 58 +++---- packages/foundry/scripts/select_keystore.sh | 15 +- .../nextjs/contracts/deployedContracts.ts | 150 +++++++++++++++++- 3 files changed, 180 insertions(+), 43 deletions(-) diff --git a/packages/foundry/scripts-js/parseArgs.js b/packages/foundry/scripts-js/parseArgs.js index a9f2fea78..c38574693 100644 --- a/packages/foundry/scripts-js/parseArgs.js +++ b/packages/foundry/scripts-js/parseArgs.js @@ -13,6 +13,9 @@ const args = process.argv.slice(2); let fileName = "Deploy.s.sol"; let network = "localhost"; +//// Maybe also a flag for the keystore to use? +//// e.g. yarn deploy --network sepolia --keystore + // Show help message if --help is provided if (args.includes("--help") || args.includes("-h")) { console.log(` @@ -42,6 +45,7 @@ for (let i = 0; i < args.length; i++) { } // Check if the network exists in rpc_endpoints +let rpcUrl; try { const foundryTomlPath = join(__dirname, "..", "foundry.toml"); const tomlString = readFileSync(foundryTomlPath, "utf-8"); @@ -54,45 +58,35 @@ try { ); process.exit(1); } + + rpcUrl = parsedToml.rpc_endpoints[network].replace(/\${(\w+)}/g, (_, key) => { + const value = process.env[key]; + if (!value) { + console.log(`\n❌ Error: Environment variable ${key} is not set!`); + process.exit(1); + } + return value; + }); } catch (error) { console.error("\n❌ Error reading or parsing foundry.toml:", error); process.exit(1); } -// Check for default account on live network -if ( - process.env.ETH_KEYSTORE_ACCOUNT === "scaffold-eth-default" && - network !== "localhost" -) { - console.log(` -❌ Error: Cannot deploy to live network using default keystore account! - -To deploy to ${network}, please follow these steps: - -1. If you haven't generated a keystore account yet: - $ yarn generate - -2. Update your .env file: - ETH_KEYSTORE_ACCOUNT='scaffold-eth-custom' +let selectedKeystore = process.env.ETH_KEYSTORE_ACCOUNT; +if (network !== "localhost") { + const keystoreResult = spawnSync( + "bash", + [join(__dirname, "..", "scripts", "select_keystore.sh"), rpcUrl], + { stdio: ["inherit", "pipe", "inherit"], encoding: "utf-8" } + ); -The default account (scaffold-eth-default) can only be used for localhost deployments. -`); - process.exit(0); -} - -if ( - process.env.ETH_KEYSTORE_ACCOUNT !== "scaffold-eth-default" && - network === "localhost" -) { - console.log(` -⚠️ Warning: Using ${process.env.ETH_KEYSTORE_ACCOUNT} keystore account on localhost. + if (keystoreResult.status !== 0) { + console.error("\n❌ Error selecting keystore"); + process.exit(1); + } -You can either: -1. Enter the password for ${process.env.ETH_KEYSTORE_ACCOUNT} account - OR -2. Set the default keystore account in your .env and re-run the command to skip password prompt: - ETH_KEYSTORE_ACCOUNT='scaffold-eth-default' -`); + selectedKeystore = keystoreResult.stdout.trim(); + process.env.ETH_KEYSTORE_ACCOUNT = selectedKeystore; } // Set environment variables for the make command diff --git a/packages/foundry/scripts/select_keystore.sh b/packages/foundry/scripts/select_keystore.sh index 57fb1a761..dbb8004f3 100755 --- a/packages/foundry/scripts/select_keystore.sh +++ b/packages/foundry/scripts/select_keystore.sh @@ -14,7 +14,6 @@ check_balance() { echo $balance } -# Function to convert hex to decimal hex_to_decimal() { printf '%d\n' "$1" } @@ -28,9 +27,9 @@ wei_to_ether() { display_balance() { local address=$1 local rpc_url=$2 - local balance_hex=$(check_balance $address $rpc_url) - local balance_wei=$(hex_to_decimal $balance_hex) - local balance_eth=$(wei_to_ether $balance_wei) + local balance_hex=$(check_balance "$address" "$rpc_url") + local balance_wei=$(hex_to_decimal "$balance_hex") + local balance_eth=$(wei_to_ether "$balance_wei") echo "Current balance: $balance_eth ETH" >&2 } @@ -99,13 +98,9 @@ else fi # Check if the selected keystore is scaffold-eth-default -# Maybe we don't let the user use this keystore for deployment? if [ "$selected_keystore" == "scaffold-eth-default" ]; then - echo "Warning: scaffold-eth-default is only for local development purposes." >&2 - echo "You can create a new keystore with a custom name by running 'yarn account:generate --name '" >&2 - read -p "Are you sure you want to use this keystore? (y/n): " confirm - if [[ ! "$confirm" =~ ^[Yy]$ ]]; then - echo "Keystore selection cancelled." >&2 + if [ "$RPC_URL" != "http://127.0.0.1:8545" ] && [ "$RPC_URL" != "http://localhost:8545" ]; then + echo "❌ Error: Cannot use scaffold-eth-default keystore for non-localhost deployments!" >&2 exit 1 fi fi diff --git a/packages/nextjs/contracts/deployedContracts.ts b/packages/nextjs/contracts/deployedContracts.ts index 008d4eb06..dcd46c31e 100644 --- a/packages/nextjs/contracts/deployedContracts.ts +++ b/packages/nextjs/contracts/deployedContracts.ts @@ -4,6 +4,154 @@ */ import { GenericContractsDeclaration } from "~~/utils/scaffold-eth/contract"; -const deployedContracts = {} as const; +const deployedContracts = { + 31337: { + YourContract: { + address: "0xa15bb66138824a1c7167f5e85b957d04dd34e468", + abi: [ + { + type: "constructor", + inputs: [ + { + name: "_owner", + type: "address", + internalType: "address", + }, + ], + stateMutability: "nonpayable", + }, + { + type: "receive", + stateMutability: "payable", + }, + { + type: "function", + name: "greeting", + inputs: [], + outputs: [ + { + name: "", + type: "string", + internalType: "string", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "owner", + inputs: [], + outputs: [ + { + name: "", + type: "address", + internalType: "address", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "premium", + inputs: [], + outputs: [ + { + name: "", + type: "bool", + internalType: "bool", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "setGreeting", + inputs: [ + { + name: "_newGreeting", + type: "string", + internalType: "string", + }, + ], + outputs: [], + stateMutability: "payable", + }, + { + type: "function", + name: "totalCounter", + inputs: [], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "userGreetingCounter", + inputs: [ + { + name: "", + type: "address", + internalType: "address", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "withdraw", + inputs: [], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "event", + name: "GreetingChange", + inputs: [ + { + name: "greetingSetter", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "newGreeting", + type: "string", + indexed: false, + internalType: "string", + }, + { + name: "premium", + type: "bool", + indexed: false, + internalType: "bool", + }, + { + name: "value", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + }, + ], + inheritedFunctions: {}, + deploymentFile: "run-1734507468.json", + deploymentScript: "Deploy.s.sol", + }, + }, +} as const; export default deployedContracts satisfies GenericContractsDeclaration; From 76eaac0db03b06fb00a24472b6cdbd7b07051f88 Mon Sep 17 00:00:00 2001 From: port <108868128+portdeveloper@users.noreply.github.com> Date: Wed, 18 Dec 2024 12:11:50 +0300 Subject: [PATCH 3/6] Revert changes on deployedContracts file --- .../nextjs/contracts/deployedContracts.ts | 150 +----------------- 1 file changed, 1 insertion(+), 149 deletions(-) diff --git a/packages/nextjs/contracts/deployedContracts.ts b/packages/nextjs/contracts/deployedContracts.ts index dcd46c31e..008d4eb06 100644 --- a/packages/nextjs/contracts/deployedContracts.ts +++ b/packages/nextjs/contracts/deployedContracts.ts @@ -4,154 +4,6 @@ */ import { GenericContractsDeclaration } from "~~/utils/scaffold-eth/contract"; -const deployedContracts = { - 31337: { - YourContract: { - address: "0xa15bb66138824a1c7167f5e85b957d04dd34e468", - abi: [ - { - type: "constructor", - inputs: [ - { - name: "_owner", - type: "address", - internalType: "address", - }, - ], - stateMutability: "nonpayable", - }, - { - type: "receive", - stateMutability: "payable", - }, - { - type: "function", - name: "greeting", - inputs: [], - outputs: [ - { - name: "", - type: "string", - internalType: "string", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "owner", - inputs: [], - outputs: [ - { - name: "", - type: "address", - internalType: "address", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "premium", - inputs: [], - outputs: [ - { - name: "", - type: "bool", - internalType: "bool", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "setGreeting", - inputs: [ - { - name: "_newGreeting", - type: "string", - internalType: "string", - }, - ], - outputs: [], - stateMutability: "payable", - }, - { - type: "function", - name: "totalCounter", - inputs: [], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "userGreetingCounter", - inputs: [ - { - name: "", - type: "address", - internalType: "address", - }, - ], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "withdraw", - inputs: [], - outputs: [], - stateMutability: "nonpayable", - }, - { - type: "event", - name: "GreetingChange", - inputs: [ - { - name: "greetingSetter", - type: "address", - indexed: true, - internalType: "address", - }, - { - name: "newGreeting", - type: "string", - indexed: false, - internalType: "string", - }, - { - name: "premium", - type: "bool", - indexed: false, - internalType: "bool", - }, - { - name: "value", - type: "uint256", - indexed: false, - internalType: "uint256", - }, - ], - anonymous: false, - }, - ], - inheritedFunctions: {}, - deploymentFile: "run-1734507468.json", - deploymentScript: "Deploy.s.sol", - }, - }, -} as const; +const deployedContracts = {} as const; export default deployedContracts satisfies GenericContractsDeclaration; From 11020db0abc61d8860428b43eacea0f104b47ab1 Mon Sep 17 00:00:00 2001 From: port dev <108868128+portdeveloper@users.noreply.github.com> Date: Mon, 23 Dec 2024 20:07:55 +0300 Subject: [PATCH 4/6] Do not show scaffold-eth-default in the list of contracts --- packages/foundry/scripts/select_keystore.sh | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/packages/foundry/scripts/select_keystore.sh b/packages/foundry/scripts/select_keystore.sh index dbb8004f3..eb0736547 100755 --- a/packages/foundry/scripts/select_keystore.sh +++ b/packages/foundry/scripts/select_keystore.sh @@ -45,7 +45,7 @@ if [ ! -d "$KEYSTORE_DIR" ]; then fi # List all keystores -keystores=($(ls "$KEYSTORE_DIR")) +keystores=($(ls "$KEYSTORE_DIR" | grep -v "scaffold-eth-default")) if [ ${#keystores[@]} -eq 0 ]; then echo "No keystores found in $KEYSTORE_DIR, generating a new account..." >&2 @@ -97,14 +97,6 @@ else selected_keystore="${keystores[$((selection-1))]}" fi -# Check if the selected keystore is scaffold-eth-default -if [ "$selected_keystore" == "scaffold-eth-default" ]; then - if [ "$RPC_URL" != "http://127.0.0.1:8545" ] && [ "$RPC_URL" != "http://localhost:8545" ]; then - echo "❌ Error: Cannot use scaffold-eth-default keystore for non-localhost deployments!" >&2 - exit 1 - fi -fi - # After selecting or creating the keystore if [ -n "$selected_keystore" ]; then # Get the address from the keystore if not already set From 45e724a91e8914a7d7dd075329a77cde4aab2db3 Mon Sep 17 00:00:00 2001 From: port dev <108868128+portdeveloper@users.noreply.github.com> Date: Mon, 23 Dec 2024 20:31:32 +0300 Subject: [PATCH 5/6] Remove unused var from parseArgs.js --- packages/foundry/scripts-js/parseArgs.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/foundry/scripts-js/parseArgs.js b/packages/foundry/scripts-js/parseArgs.js index 13b228680..6a255ddb2 100644 --- a/packages/foundry/scripts-js/parseArgs.js +++ b/packages/foundry/scripts-js/parseArgs.js @@ -12,7 +12,6 @@ config(); const args = process.argv.slice(2); let fileName = "Deploy.s.sol"; let network = "localhost"; -let verify = false; //// Maybe also a flag for the keystore to use? //// e.g. yarn deploy --network sepolia --keystore From 13f4e159816e5cf0b792301ab4f862c943dee295 Mon Sep 17 00:00:00 2001 From: port dev <108868128+portdeveloper@users.noreply.github.com> Date: Mon, 23 Dec 2024 21:58:22 +0300 Subject: [PATCH 6/6] Able to pass in a --keystore flag --- packages/foundry/Makefile | 16 +++++--- packages/foundry/scripts-js/parseArgs.js | 50 ++++++++++++++++++------ 2 files changed, 48 insertions(+), 18 deletions(-) diff --git a/packages/foundry/Makefile b/packages/foundry/Makefile index 42151aeae..831632c76 100644 --- a/packages/foundry/Makefile +++ b/packages/foundry/Makefile @@ -29,13 +29,17 @@ deploy: forge script $(DEPLOY_SCRIPT) --rpc-url localhost --broadcast --legacy --ffi; \ fi \ else \ - RPC_URL=$$(grep "^$(RPC_URL) =" foundry.toml | sed 's/.*= "\(.*\)"/\1/'); \ - KEYSTORE=$$(bash scripts/select_keystore.sh "$$RPC_URL"); \ - if [ -n "$$KEYSTORE" ]; then \ - forge script $(DEPLOY_SCRIPT) --rpc-url $$RPC_URL --broadcast --legacy --ffi --account ~/.foundry/keystores/"$$KEYSTORE"; \ + if [ -n "$(ETH_KEYSTORE_ACCOUNT)" ]; then \ + echo "Using keystore: $(ETH_KEYSTORE_ACCOUNT)"; \ + forge script $(DEPLOY_SCRIPT) --rpc-url $$RPC_URL --broadcast --legacy --ffi --account ~/.foundry/keystores/"$(ETH_KEYSTORE_ACCOUNT)"; \ else \ - echo "Keystore selection failed. Aborting deployment."; \ - exit 1; \ + KEYSTORE=$$(bash scripts/select_keystore.sh "$$RPC_URL"); \ + if [ -n "$$KEYSTORE" ]; then \ + forge script $(DEPLOY_SCRIPT) --rpc-url $$RPC_URL --broadcast --legacy --ffi --account ~/.foundry/keystores/"$$KEYSTORE"; \ + else \ + echo "Keystore selection failed. Aborting deployment."; \ + exit 1; \ + fi; \ fi; \ fi diff --git a/packages/foundry/scripts-js/parseArgs.js b/packages/foundry/scripts-js/parseArgs.js index 6a255ddb2..6582be363 100644 --- a/packages/foundry/scripts-js/parseArgs.js +++ b/packages/foundry/scripts-js/parseArgs.js @@ -12,6 +12,7 @@ config(); const args = process.argv.slice(2); let fileName = "Deploy.s.sol"; let network = "localhost"; +let specifiedKeystore = null; //// Maybe also a flag for the keystore to use? //// e.g. yarn deploy --network sepolia --keystore @@ -23,10 +24,11 @@ Usage: yarn deploy [options] Options: --file Specify the deployment script file (default: Deploy.s.sol) --network Specify the network (default: localhost) + --keystore Specify the keystore to use (skips interactive selection) --help, -h Show this help message Examples: yarn deploy --file DeployYourContract.s.sol --network sepolia - yarn deploy --network sepolia + yarn deploy --network sepolia --keystore scaffold-eth-custom yarn deploy --file DeployYourContract.s.sol yarn deploy `); @@ -41,6 +43,9 @@ for (let i = 0; i < args.length; i++) { } else if (args[i] === "--file" && args[i + 1]) { fileName = args[i + 1]; i++; // Skip next arg since we used it + } else if (args[i] === "--keystore" && args[i + 1]) { + specifiedKeystore = args[i + 1]; + i++; // Skip next arg since we used it } } @@ -74,18 +79,38 @@ try { let selectedKeystore = process.env.ETH_KEYSTORE_ACCOUNT; if (network !== "localhost") { - const keystoreResult = spawnSync( - "bash", - [join(__dirname, "..", "scripts", "select_keystore.sh"), rpcUrl], - { stdio: ["inherit", "pipe", "inherit"], encoding: "utf-8" } - ); - - if (keystoreResult.status !== 0) { - console.error("\n❌ Error selecting keystore"); - process.exit(1); - } + if (specifiedKeystore) { + // If keystore is specified, verify it exists + const keystoreResult = spawnSync( + "test", + [ + "-f", + join(process.env.HOME, ".foundry", "keystores", specifiedKeystore), + ], + { stdio: "pipe" } + ); + + if (keystoreResult.status !== 0) { + console.error(`\n❌ Error: Keystore '${specifiedKeystore}' not found!`); + process.exit(1); + } - selectedKeystore = keystoreResult.stdout.trim(); + selectedKeystore = specifiedKeystore; + } else { + // Interactive keystore selection if not specified + const keystoreResult = spawnSync( + "bash", + [join(__dirname, "..", "scripts", "select_keystore.sh"), rpcUrl], + { stdio: ["inherit", "pipe", "inherit"], encoding: "utf-8" } + ); + + if (keystoreResult.status !== 0) { + console.error("\n❌ Error selecting keystore"); + process.exit(1); + } + + selectedKeystore = keystoreResult.stdout.trim(); + } process.env.ETH_KEYSTORE_ACCOUNT = selectedKeystore; } @@ -99,6 +124,7 @@ const result = spawnSync( "deploy-and-generate-abis", `DEPLOY_SCRIPT=${process.env.DEPLOY_SCRIPT}`, `RPC_URL=${process.env.RPC_URL}`, + `ETH_KEYSTORE_ACCOUNT=${process.env.ETH_KEYSTORE_ACCOUNT}`, ], { stdio: "inherit",