#!/usr/bin/env bash # ============================================================================= # Singularity3 DualSpace — Mainnet Deployment Script # ============================================================================= # Deploys Singularity3DualSpace.sol to Ethereum mainnet. # Private key is entered interactively (never stored, never logged). # ============================================================================= set -euo pipefail # ── Anchor to script directory so paths work from anywhere ────────────────── SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" cd "$SCRIPT_DIR" # ── Configuration ──────────────────────────────────────────────────────────── EXPECTED_DEPLOYER="0xe673621B36984Cb1F74a876b4ba26C4F6cA4e25F" CONTRACT_PATH="contracts/Singularity3_DualSpace.sol" CONTRACT_NAME="Singularity3DualSpace" CHAIN_ID=1 # Public RPC endpoints (tried in order) RPCS=( "https://ethereum-rpc.publicnode.com" "https://eth.drpc.org" "https://rpc.ankr.com/eth" "https://eth.llamarpc.com" "https://cloudflare-eth.com" "https://ethereum.blockpi.network/v1/rpc/public" "https://eth.merkle.io" ) # ── Colors ─────────────────────────────────────────────────────────────────── RED=$'\033[0;31m' GRN=$'\033[0;32m' YLW=$'\033[1;33m' CYN=$'\033[0;36m' BLD=$'\033[1m' NC=$'\033[0m' log() { printf "%s\n" "$*"; } info() { printf "${CYN}[i]${NC} %s\n" "$*"; } ok() { printf "${GRN}[✓]${NC} %s\n" "$*"; } warn() { printf "${YLW}[!]${NC} %s\n" "$*" >&2; } err() { printf "${RED}[x]${NC} %s\n" "$*" >&2; } hdr() { printf "\n${BLD}━━━ %s ━━━${NC}\n" "$*"; } # ── Pre-flight ─────────────────────────────────────────────────────────────── preflight() { hdr "Pre-flight" command -v cast >/dev/null || { err "cast not found (install Foundry)"; exit 1; } command -v forge >/dev/null || { err "forge not found (install Foundry)"; exit 1; } [[ -f "$CONTRACT_PATH" ]] || { err "Contract not found: $CONTRACT_PATH"; exit 1; } ok "Foundry: $(cast --version | head -1)" ok "Contract found: $CONTRACT_PATH" } # ── RPC selection (find a working endpoint) ────────────────────────────────── select_rpc() { hdr "RPC selection" for rpc in "${RPCS[@]}"; do info "Testing $rpc ..." if cid=$(timeout 8 cast chain-id --rpc-url "$rpc" 2>/dev/null) && [[ "$cid" == "$CHAIN_ID" ]]; then RPC_URL="$rpc" ok "Selected RPC: $RPC_URL (chain id $cid)" return 0 fi done err "No public RPC reachable. Set RPC_URL env var manually and re-run." exit 1 } # ── Build (compile bytecode locally) ───────────────────────────────────────── build() { hdr "Compile" forge build --sizes ok "Bytecode compiled to ./out/" } # ── Wallet checks ──────────────────────────────────────────────────────────── check_balance_and_gas() { hdr "Network state" local bal_wei bal_eth gas_wei gas_gwei block bal_wei=$(cast balance "$EXPECTED_DEPLOYER" --rpc-url "$RPC_URL") bal_eth=$(cast from-wei "$bal_wei" ether) gas_wei=$(cast gas-price --rpc-url "$RPC_URL") gas_gwei=$(cast from-wei "$gas_wei" gwei) block=$(cast block-number --rpc-url "$RPC_URL") info "Block: $block" info "Deployer: $EXPECTED_DEPLOYER" info "Balance: $bal_eth ETH" info "Gas price: $gas_gwei gwei" # Estimate cost: contract initcode is ~3.9KB; rough deploy = ~1.2M gas local est_gas=1200000 local est_wei=$(( gas_wei * est_gas )) local est_eth est_eth=$(cast from-wei "$est_wei" ether) info "Est. deploy: ~${est_gas} gas → ~${est_eth} ETH" # Bail if balance < 1.5x estimate if (( bal_wei < est_wei * 3 / 2 )); then warn "Balance may be insufficient (need ~$(cast from-wei $((est_wei*3/2)) ether) ETH headroom)." read -r -p "Continue anyway? [y/N] " ans [[ "$ans" =~ ^[yY]$ ]] || { log "Aborted."; exit 1; } fi } # ── Read & verify private key ──────────────────────────────────────────────── # Honors $PRIVATE_KEY from the environment if already set; otherwise prompts. read_key() { hdr "Private key" if [[ -n "${PRIVATE_KEY:-}" ]]; then info "Using PRIVATE_KEY from environment." PRIVKEY="$PRIVATE_KEY" # Don't leave it lingering in the env for child processes. unset PRIVATE_KEY else warn "The key is read silently. It is NEVER written to disk or logs." printf "Paste private key (with or without 0x prefix), then press Enter: " IFS= read -rs PRIVKEY printf "\n" fi # Normalize: strip whitespace, ensure 0x prefix PRIVKEY="${PRIVKEY//[[:space:]]/}" [[ "$PRIVKEY" =~ ^0x ]] || PRIVKEY="0x$PRIVKEY" # Length check (0x + 64 hex) if [[ ! "$PRIVKEY" =~ ^0x[0-9a-fA-F]{64}$ ]]; then err "Invalid private key format (must be 32 bytes hex)." unset PRIVKEY exit 1 fi # Derive address and verify it matches expected deployer local derived derived=$(cast wallet address --private-key "$PRIVKEY" 2>/dev/null) || { err "Could not derive address from key."; unset PRIVKEY; exit 1; } local derived_lc expected_lc derived_lc=$(printf "%s" "$derived" | tr '[:upper:]' '[:lower:]') expected_lc=$(printf "%s" "$EXPECTED_DEPLOYER" | tr '[:upper:]' '[:lower:]') if [[ "$derived_lc" != "$expected_lc" ]]; then err "Key does NOT match expected deployer." err " expected: $EXPECTED_DEPLOYER" err " derived: $derived" unset PRIVKEY exit 1 fi ok "Key verified → derives $derived" } # ── Final confirmation ─────────────────────────────────────────────────────── confirm() { hdr "Confirm" log " Network: Ethereum mainnet (chain id 1)" log " RPC: $RPC_URL" log " Deployer: $EXPECTED_DEPLOYER" log " Contract: $CONTRACT_NAME" log " Source: $CONTRACT_PATH" log " Args: (none)" printf "\n${YLW}Type 'DEPLOY' to broadcast: ${NC}" read -r answer [[ "$answer" == "DEPLOY" ]] || { log "Aborted."; unset PRIVKEY; exit 1; } } # ── Deploy ─────────────────────────────────────────────────────────────────── deploy() { hdr "Broadcasting" local ts log_file ts=$(date -u +%Y%m%dT%H%M%SZ) log_file="deployment_${ts}.log" # forge create handles nonce, EIP-1559 fees, signing, broadcast. # --broadcast is required to actually send (otherwise it's a dry run). if forge create \ --rpc-url "$RPC_URL" \ --private-key "$PRIVKEY" \ --broadcast \ "${CONTRACT_PATH}:${CONTRACT_NAME}" \ 2>&1 | tee "$log_file" then ok "Broadcast complete. Log: $log_file" else err "Deployment failed. See $log_file for details." unset PRIVKEY exit 1 fi # Clear key from memory ASAP unset PRIVKEY # Extract address + tx local addr tx addr=$(grep -oE 'Deployed to: 0x[0-9a-fA-F]{40}' "$log_file" | awk '{print $3}' || true) tx=$(grep -oE 'Transaction hash: 0x[0-9a-fA-F]{64}' "$log_file" | awk '{print $3}' || true) if [[ -n "$addr" && -n "$tx" ]]; then hdr "Result" ok "Contract: $addr" ok "Tx: $tx" ok "Etherscan: https://etherscan.io/address/$addr" ok "Tx detail: https://etherscan.io/tx/$tx" # Run on-chain activation/verification activate "$addr" "$tx" "$ts" else warn "Could not parse address/tx from output. Inspect $log_file manually." fi } # ── Activation / verification ──────────────────────────────────────────────── # The contract self-activates in its constructor (sets immutable origin, # initializes the 9 sections, emits ZeroPointPulse). This step verifies # all of that fired correctly and the contract is responding to view calls. activate() { local addr="$1" local tx="$2" local ts="$3" hdr "Activation / verification" # 1. Wait for receipt (forge create already waits, but re-poll for safety) info "Fetching transaction receipt..." local receipt status block_num receipt=$(cast receipt "$tx" --rpc-url "$RPC_URL" --json 2>/dev/null || echo "{}") status=$(printf "%s" "$receipt" | grep -oE '"status":"0x[01]"' | head -1) block_num=$(printf "%s" "$receipt" | grep -oE '"blockNumber":"0x[0-9a-fA-F]+"' | head -1 | grep -oE '0x[0-9a-fA-F]+') if [[ "$status" != *"0x1"* ]]; then err "Transaction status is not success. Receipt: $receipt" return 1 fi ok "Tx mined in block $(cast --to-dec "$block_num" 2>/dev/null || echo "$block_num"). Status: success." # 2. Detect ZeroPointPulse activation event in the receipt logs local pulse_topic pulse_topic=$(cast keccak "ZeroPointPulse(address,uint256,bytes32)") if printf "%s" "$receipt" | grep -qi "${pulse_topic#0x}"; then ok "ZeroPointPulse event detected → contract is ACTIVE." PULSE_DETECTED="true" else warn "ZeroPointPulse event not found in receipt logs (constructor may still be valid)." PULSE_DETECTED="false" fi # 3. Live view-function probes info "Probing contract state via eth_call..." local origin resonance formula pol_axis pol_doubling dual_axis dual_neg sweep_ok origin=$(cast call "$addr" "singularityOrigin()(address)" --rpc-url "$RPC_URL" 2>/dev/null || echo "ERR") resonance=$(cast call "$addr" "resonanceCheck()(bool)" --rpc-url "$RPC_URL" 2>/dev/null || echo "ERR") formula=$(cast call "$addr" "sacredFormula()(string)" --rpc-url "$RPC_URL" 2>/dev/null || echo "ERR") pol_axis=$(cast call "$addr" "polarityOf(uint8)(string)" 2 --rpc-url "$RPC_URL" 2>/dev/null || echo "ERR") pol_doubling=$(cast call "$addr" "polarityOf(uint8)(string)" 0 --rpc-url "$RPC_URL" 2>/dev/null || echo "ERR") ok " singularityOrigin : $origin" ok " resonanceCheck : $resonance" ok " sacredFormula : $formula" ok " polarityOf(2) : $pol_axis (offset 3, axis → expected POSITIVE_SPACE)" ok " polarityOf(0) : $pol_doubling (offset 1, doubling → expected NEGATIVE_SPACE)" # 4. dualCall probes (one positive-space, one negative-space) info "Probing dualCall (both halves)..." dual_axis=$(cast call "$addr" "dualCall(uint8,uint8)(bool,uint256,string)" 2 1 --rpc-url "$RPC_URL" 2>/dev/null || echo "ERR") dual_neg=$(cast call "$addr" "dualCall(uint8,uint8)(bool,uint256,string)" 0 1 --rpc-url "$RPC_URL" 2>/dev/null || echo "ERR") ok " dualCall(2,1) : $(printf "%s" "$dual_axis" | tr '\n' ' ')" ok " dualCall(0,1) : $(printf "%s" "$dual_neg" | tr '\n' ' ')" # 5. Sanity assertions (non-fatal, just reported) local activation_ok="true" if [[ -n "$origin" ]] && \ [ "$(printf "%s" "$origin" | tr '[:upper:]' '[:lower:]')" \ = "$(printf "%s" "$EXPECTED_DEPLOYER" | tr '[:upper:]' '[:lower:]')" ]; then ok " origin matches expected deployer." else warn " origin does NOT match expected deployer (got $origin)." activation_ok="false" fi [[ "$resonance" == "true" ]] && ok " resonanceCheck = true (3-6-9 sums divisible by 9)." \ || { warn " resonanceCheck != true."; activation_ok="false"; } [[ "$pol_axis" == *"POSITIVE_SPACE"* ]] || activation_ok="false" [[ "$pol_doubling" == *"NEGATIVE_SPACE"* ]] || activation_ok="false" # 6. Persist deployment + activation record cat > "deployment_${ts}.json" <