igneum/docs/build/first-contract-test.sh
igneum-labs cb2b8a2578 Builder programme (8 October 2026): /build, /grants, the Devnet 3 faucet on build-1, docs/build as the source
- site/build.html and site/grants.html rendered by site/build.mjs from docs/build/build.md and docs/build/grants.md (the gate's site-only copy keeps the committed pages, as for /bench); site-relative links in the markdown renderer
- the nav's fourth group, Build (/build, /faucet, /grants), in the partial, the sheet, the footer, the sitemap and tools/ci/site-nav-check.mjs; the faucet leaves the Mine panel
- site/faucet.html is the Devnet 3 faucet: 10 IGN per address per day, 500 a day, chain id 4463, no value, resets without notice; the page posts to https://faucet.igneum.network/api/faucet and shows the balance
- infra/build-server/faucet: faucet.mjs (zero dependencies, signs with site/lib/eth.mjs, limiter self-test known-failed first, 100 IGN cap on the amount), the unit, faucet.sh install/status; live on build-1 behind Caddy since 10:16Z, funded 2,000 IGN by the fleet's funder at 10:01Z; first drip 0x8620e9d8 at 10:17Z
- docs/build: README, first-contract.md with its test script and the PASS line (24 s end to end through the public RPC and faucet, Foundry 1.8.5, Counter deployed at block 27055), rpc.md read from the node (75 methods probed, served and not served), faucet.md, verify-a-block.md, grants.md
- .forgejo/issue_template/grant.md: the grant application template on git.igneum.network

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-08 10:24:07 +00:00

49 lines
3.9 KiB
Bash
Executable file

#!/usr/bin/env bash
# The five-minute walkthrough (docs/build/first-contract.md), run end to end on a build box against a Devnet 3 node, so the
# page is tested and not described. Runs where Foundry is installed (~/.foundry/bin); asks the faucet for the coins exactly
# as a reader would, deploys Foundry's Counter, calls it, reads the receipt, prints one PASS line with the numbers.
# RPC=http://127.0.0.1:27810 FAUCET=http://127.0.0.1:8790/api/faucet bash docs/build/first-contract-test.sh
# Known-failed first: with RPC pointed at a closed port the script must print FAIL and exit 1 (--self-test).
set -uo pipefail
export PATH="$HOME/.foundry/bin:$PATH"
RPC="${RPC:-https://rpc.devnet.igneum.network}"
FAUCET="${FAUCET:-https://faucet.igneum.network/api/faucet}"
t0=$(date +%s); now() { date -u +%H:%M:%SZ; }
fail() { echo "FAIL $(now): $*"; exit 1; }
if [ "${1:-}" = --self-test ]; then
out=$(RPC=http://127.0.0.1:9 FAUCET=http://127.0.0.1:9/x bash "$0" 2>&1); rc=$?
[ "$rc" = 1 ] && case "$out" in *FAIL*) echo "self-test passed: a closed RPC port reads FAIL, exit 1"; exit 0 ;; esac
echo "self-test failed: rc=$rc out=$out"; exit 1
fi
command -v forge >/dev/null || fail "forge is not installed"
cid=$(cast chain-id --rpc-url "$RPC" 2>/dev/null) || fail "no chain id from $RPC"
[ "$cid" = 4463 ] || [ "$cid" = 4464 ] || fail "chain id $cid is not Devnet 3"
work=$(mktemp -d); cd "$work" || fail "no scratch dir"
# step 1: a key for the devnet only (never printed)
w=$(cast wallet new 2>&1 | sed 's/\x1b\[[0-9;]*m//g') || fail "cast wallet new"
PK=$(printf '%s\n' "$w" | sed -n 's/^Private key: *//p' | head -1); ME=$(printf '%s\n' "$w" | sed -n 's/^Address: *//p' | head -1)
[ -n "$PK" ] && [ -n "$ME" ] || fail "cast wallet new printed no key"
# step 2: the faucet, as a reader would
f=$(curl -s -m 60 -X POST -H 'content-type: application/json' --data "{\"address\":\"$ME\"}" "$FAUCET") || fail "the faucet did not answer"
case "$f" in *'"ok":true'*) ;; *) fail "the faucet refused: $f" ;; esac
drip=$(printf '%s' "$f" | sed -n 's/.*"tx":"\(0x[0-9a-f]*\)".*/\1/p')
for i in $(seq 1 60); do bal=$(cast balance "$ME" --rpc-url "$RPC" --ether 2>/dev/null); case "$bal" in 10.*|9.*) break ;; esac; sleep 1; done
case "$bal" in 10.*|9.*) ;; *) fail "balance after the faucet: ${bal:-none}" ;; esac
# step 3: deploy Foundry's template counter
forge init counter --no-git >/dev/null 2>&1 || forge init counter >/dev/null 2>&1 || fail "forge init"
cd counter || fail "no counter dir"
out=$(forge create src/Counter.sol:Counter --rpc-url "$RPC" --private-key "$PK" --broadcast 2>&1) || fail "forge create: $(printf '%s' "$out" | tail -3)"
C=$(printf '%s\n' "$out" | sed -n 's/^Deployed to: *//p' | head -1); deployTx=$(printf '%s\n' "$out" | sed -n 's/^Transaction hash: *//p' | head -1)
[ -n "$C" ] || fail "no Deployed to line: $out"
# step 4: call it
cast send "$C" "increment()" --rpc-url "$RPC" --private-key "$PK" >/dev/null 2>&1 || fail "cast send increment"
n=$(cast call "$C" "number()(uint256)" --rpc-url "$RPC" 2>/dev/null); [ "$n" = 1 ] || fail "number() after increment: $n"
sendOut=$(cast send "$C" "setNumber(uint256)" 41 --rpc-url "$RPC" --private-key "$PK" 2>&1) || fail "cast send setNumber"
txh=$(printf '%s\n' "$sendOut" | sed -n 's/^transactionHash *//p' | head -1)
n=$(cast call "$C" "number()(uint256)" --rpc-url "$RPC" 2>/dev/null); [ "$n" = 41 ] || fail "number() after setNumber: $n"
# step 5: the receipt
st=$(cast receipt "${txh:-$deployTx}" --rpc-url "$RPC" 2>/dev/null | sed -n 's/^status *//p' | head -1)
case "$st" in 1*|*success*) ;; *) fail "receipt status: $st" ;; esac
blk=$(cast block-number --rpc-url "$RPC" 2>/dev/null)
echo "PASS $(now): Devnet 3 chain id $cid, faucet drip $drip, deployed Counter at $C (tx $deployTx), increment then setNumber(41) read back 41, receipt status $st, block $blk, $(( $(date +%s) - t0 )) s end to end, Foundry $(forge --version 2>/dev/null | sed -n 's/^forge Version: *//p' | head -1)"
cd / && rm -rf "$work"