#!/usr/bin/env bash set -euo pipefail repository_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P) guard="$repository_root/tools/require-base-config.sh" terminal_url=https://terminal.invalid/rpc public_url=https://public.invalid/rpc account=demo-account sender=0x1111111111111111111111111111111111111111 recipient=0x2222222222222222222222222222222222222222 run_guard() { local mode=${1-deploy} shift || true env -i PATH="$PATH" \ BASE_SEPOLIA_RPC_URL="$terminal_url" \ BASE_SEPOLIA_PUBLIC_RPC_URL="$public_url" \ BASE_SEPOLIA_ACCOUNT="$account" \ BASE_SEPOLIA_SENDER="$sender" \ BASE_SEPOLIA_RECIPIENT="$recipient" \ "$@" bash "$guard" "$mode" } assert_no_config_values() { local output=$1 shift local value for value in "$terminal_url" "$public_url" "$account" "$sender" "$recipient" "$@"; do [[ -n $value ]] || continue if [[ $output == *"$value"* ]]; then echo "configuration guard leaked a configured value" >&2 exit 1 fi done } expect_rejected() { local assignment=$1 local output if output=$(run_guard deploy "$assignment" 2>&1); then echo "configuration guard accepted invalid fixture: ${assignment%%=*}" >&2 exit 1 fi assert_no_config_values "$output" "${assignment#*=}" } for mode in deploy upgrade transfer; do output=$(run_guard "$mode" 2>&1) [[ -z $output ]] || { echo "configuration guard produced output for valid fixtures" >&2; exit 1; } done for assignment in \ BASE_SEPOLIA_RPC_URL= \ BASE_SEPOLIA_PUBLIC_RPC_URL= \ BASE_SEPOLIA_ACCOUNT= \ BASE_SEPOLIA_SENDER= \ BASE_SEPOLIA_RECIPIENT= \ BASE_SEPOLIA_ACCOUNT='bad account' \ BASE_SEPOLIA_SENDER=0x1234 \ BASE_SEPOLIA_RECIPIENT=0x0000000000000000000000000000000000000000 \ BASE_SEPOLIA_RECIPIENT="$sender" \ BASE_SEPOLIA_RPC_URL=http://terminal.invalid \ BASE_SEPOLIA_PUBLIC_RPC_URL=http://public.invalid \ BASE_SEPOLIA_PUBLIC_RPC_URL=https://user@public.invalid \ BASE_SEPOLIA_PUBLIC_RPC_URL='https://public.invalid/?api_key=fixture' \ BASE_SEPOLIA_PUBLIC_RPC_URL='https://public.invalid/?token=fixture'; do expect_rejected "$assignment" done private_key_name=PRIVATE_$(printf KEY) mnemonic_name=MNEM$(printf ONIC) fake_key=0x$(printf 'a%.0s' {1..64}) output=$( env -i PATH="$PATH" "$private_key_name=$fake_key" "$mnemonic_name=fixture words only" \ bash "$guard" deploy 2>&1 || true ) if [[ $output != *BASE_SEPOLIA_RPC_URL* ]]; then echo "configuration guard unexpectedly accepted a raw-key or mnemonic fallback" >&2 exit 1 fi assert_no_config_values "$output" dry_run=$( make -n -C "$repository_root" deploy-base-sepolia \ BASE_SEPOLIA_RPC_URL=https://terminal.invalid \ BASE_SEPOLIA_PUBLIC_RPC_URL=https://public.invalid \ BASE_SEPOLIA_ACCOUNT=demo \ BASE_SEPOLIA_SENDER=0x1111111111111111111111111111111111111111 \ BASE_SEPOLIA_RECIPIENT=0x2222222222222222222222222222222222222222 ) [[ $dry_run == *'--account "demo"'* ]] || { echo "Base dry run omitted --account" >&2; exit 1; } [[ $dry_run == *'--sender "0x1111111111111111111111111111111111111111"'* ]] || \ { echo "Base dry run omitted --sender" >&2; exit 1; } [[ $dry_run == *'--slow'* ]] || { echo "Base dry run omitted --slow" >&2; exit 1; } if [[ ${dry_run,,} == *private-key* || ${dry_run,,} == *mnemonic* ]]; then echo "Base dry run exposed a raw signing option" >&2 exit 1 fi node --input-type=module - "$repository_root" <<'NODE' import assert from "node:assert/strict"; import { access, mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { pathToFileURL } from "node:url"; const repositoryRoot = process.argv[2]; const { archiveManifest, selectManifest } = await import(pathToFileURL(join(repositoryRoot, "tools/select-manifest.mjs"))); const { publishManifest } = await import(pathToFileURL(join(repositoryRoot, "tools/publish-web-manifest.mjs"))); const root = await mkdtemp(join(tmpdir(), "uups-base-config-")); const deployments = join(root, "deployments"); const webManifest = join(root, "web/public/deployment.json"); const owner = "0x1111111111111111111111111111111111111111"; const recipient = "0x2222222222222222222222222222222222222222"; const base = { schemaVersion: 1, network: "baseSepolia", chainId: 84532, deploymentBlock: 99, rpcUrl: "https://public.invalid/rpc", explorerBaseUrl: "https://sepolia.basescan.org", token: "0x3333333333333333333333333333333333333333", proxy: "0x4444444444444444444444444444444444444444", implementation: "0x5555555555555555555555555555555555555555", owner, actors: [ { label: "Presenter", address: owner }, { label: "Recipient", address: recipient }, ], }; const anvil = { ...base, network: "anvil", chainId: 31337, rpcUrl: "http://127.0.0.1:8545", explorerBaseUrl: undefined, actors: [ { label: "owner", address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" }, { label: "Alice", address: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" }, { label: "Bob", address: "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC" }, ], owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", }; delete anvil.explorerBaseUrl; try { await mkdir(deployments, { recursive: true }); await writeFile(join(deployments, "base-sepolia.json"), `${JSON.stringify(base)}\n`); await writeFile(join(deployments, "anvil.json"), `${JSON.stringify(anvil)}\n`); await selectManifest({ root, network: "baseSepolia" }); const activeBefore = await readFile(join(deployments, "active.json")); const anvilBefore = await readFile(join(deployments, "anvil.json")); const published = await publishManifest({ activePath: join(deployments, "active.json"), outputPath: webManifest, }); assert.equal(published.rpcUrl, "https://public.invalid/rpc"); assert.equal(published.explorerBaseUrl, "https://sepolia.basescan.org"); assert.equal((await readFile(webManifest, "utf8")).includes("terminal.invalid"), false); for (const invalid of [ { ...base, rpcUrl: "http://public.invalid/rpc" }, { ...base, explorerBaseUrl: "https://example.invalid" }, ]) { await writeFile(join(deployments, "active.json"), `${JSON.stringify(invalid)}\n`); await assert.rejects( () => publishManifest({ activePath: join(deployments, "active.json"), outputPath: webManifest }), /HTTPS|BaseScan/, ); } await writeFile(join(deployments, "active.json"), activeBefore); const archived = await archiveManifest({ root, network: "baseSepolia", now: new Date("2026-08-21T12:34:56.789Z"), }); assert.equal(archived.path, join(deployments, "base-sepolia.20260821T123456789Z.json")); await access(archived.path); await assert.rejects(() => access(join(deployments, "base-sepolia.json"))); assert.deepEqual(await readFile(join(deployments, "active.json")), activeBefore); assert.deepEqual(await readFile(join(deployments, "anvil.json")), anvilBefore); await assert.rejects(() => archiveManifest({ root, network: "anvil" }), /baseSepolia/); } finally { await rm(root, { recursive: true, force: true }); } NODE echo "Base configuration and manifest filesystem tests passed"