From cb2b8a257800f860bd31c690f92e5d4ce45f6517 Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Thu, 8 Oct 2026 10:24:07 +0000 Subject: [PATCH] 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 --- .forgejo/issue_template/grant.md | 26 ++ docs/build/README.md | 16 + docs/build/build.md | 88 +++++ docs/build/faucet.md | 36 ++ docs/build/first-contract-test.sh | 49 +++ docs/build/first-contract.md | 91 +++++ docs/build/grants.md | 46 +++ docs/build/rpc.md | 79 ++++ docs/build/verify-a-block.md | 34 ++ infra/build-server/faucet/faucet.mjs | 171 +++++++++ infra/build-server/faucet/faucet.sh | 62 +++ .../build-server/faucet/igneum-faucet.service | 22 ++ site/404.html | 19 +- site/address.html | 19 +- site/app.html | 19 +- site/bench.html | 41 +- site/block.html | 19 +- site/build.html | 356 ++++++++++++++++++ site/build.mjs | 16 + site/claims.html | 19 +- site/dev-fee.html | 19 +- site/download.html | 27 +- site/evidence.html | 19 +- site/explorer.html | 19 +- site/faucet.html | 68 ++-- site/grants.html | 333 ++++++++++++++++ site/income.html | 19 +- site/index.html | 19 +- site/journey.html | 29 +- site/journey.json | 22 +- site/ledger.html | 25 +- site/litepaper.html | 19 +- site/live.html | 19 +- site/metamask.html | 19 +- site/miner.html | 25 +- site/miners.html | 19 +- site/partials/footer.html | 4 +- site/partials/nav.html | 15 +- site/provenance.html | 19 +- site/randomx.html | 19 +- site/scenes.html | 19 +- site/sitemap.xml | 4 +- site/wallet.html | 19 +- tools/ci/site-nav-check.mjs | 7 +- 44 files changed, 1904 insertions(+), 130 deletions(-) create mode 100644 .forgejo/issue_template/grant.md create mode 100644 docs/build/README.md create mode 100644 docs/build/build.md create mode 100644 docs/build/faucet.md create mode 100755 docs/build/first-contract-test.sh create mode 100644 docs/build/first-contract.md create mode 100644 docs/build/grants.md create mode 100644 docs/build/rpc.md create mode 100644 docs/build/verify-a-block.md create mode 100644 infra/build-server/faucet/faucet.mjs create mode 100755 infra/build-server/faucet/faucet.sh create mode 100644 infra/build-server/faucet/igneum-faucet.service create mode 100644 site/build.html create mode 100644 site/grants.html diff --git a/.forgejo/issue_template/grant.md b/.forgejo/issue_template/grant.md new file mode 100644 index 000000000..59ae0a533 --- /dev/null +++ b/.forgejo/issue_template/grant.md @@ -0,0 +1,26 @@ +--- +name: Grant application +about: Apply for an Igneum grant: tooling, a reference app, infrastructure or research, paid in IGN on delivery +title: "Grant: " +labels: grant +--- + + + +## What you will build + + + +## What done looks like + + + +## Your public work + + + +## How to reach you + + + + diff --git a/docs/build/README.md b/docs/build/README.md new file mode 100644 index 000000000..da070f7a1 --- /dev/null +++ b/docs/build/README.md @@ -0,0 +1,16 @@ +# docs/build: the builder programme + +8 October 2026. These files are the source of the developer pages on igneum.network; `site/build.mjs` renders two of them as pages and the rest are read here. + +| File | Serves | +|---|---| +| `build.md` | [igneum.network/build](https://igneum.network/build): what is different, the chain ids and endpoints, the wallet, the explorer, the faucet, the five-minute contract, the browser verifier | +| `grants.md` | [igneum.network/grants](https://igneum.network/grants): the tiers, what a grant is, how to apply, the review, the honest lines | +| `first-contract.md`, `first-contract-test.sh` | the walkthrough and the script that runs it end to end on Devnet 3 from a build box; the PASS line is the test record | +| `rpc.md` | the JSON-RPC method list as the node serves it, read from a Devnet 3 node, with the probe | +| `faucet.md` | the Devnet 3 faucet: rules, where it runs, how it is funded | +| `verify-a-block.md` | the in-browser checkpoint verifier, twelve lines | + +The faucet service is `infra/build-server/faucet/`. The grant application template is `.forgejo/issue_template/grant.md`. + +Served-text rules (the site audit reads every page before it lands): no em dashes, short sentences, one to three points per block, nothing that reads as an offer, a price or a promise of value; devnet and testnet coins have no value; no amount, price or date is promised. diff --git a/docs/build/build.md b/docs/build/build.md new file mode 100644 index 000000000..5e9e7fd7a --- /dev/null +++ b/docs/build/build.md @@ -0,0 +1,88 @@ +# Build on Igneum + +This file is the source of [igneum.network/build](https://igneum.network/build). The site build renders it as the page. The other files in this directory are the walkthrough, the RPC list, the faucet, the verifier and the grants. + +## What is different + +**Every block is proven.** A chain block carries a zero-knowledge proof of its execution. The miners are the provers: the same cards that find blocks prove them, in shards, and the proof follows the tip by tens of seconds. Your contract runs on a chain whose state is proven, not assumed. + +**A lock in minutes, not an hour of confirmations.** Miners sign a checkpoint every 30 seconds. When two thirds of the mining weight of the last 30 days have signed, the checkpoint is locked. Weight is blocks mined, nothing else. The lock lands in about two minutes. The [litepaper](/litepaper) has the rule. + +**Nothing to stake.** No validator set, no delegation, no slashing, no bonded class above the miners. Finality comes from mining weight alone. Nobody holds a key that the chain depends on. + +**The EVM you already know.** Solidity deploys unchanged. Standard JSON-RPC, EIP-1559 transactions, the chain id in the signature, `cancun` as the EVM version. Gas has two dimensions on Igneum, execution and proving, and the node folds the second into the price it quotes, so `eth_estimateGas` and `eth_gasPrice` work as they do on Ethereum. + +## Networks + +| | Devnet 3 | Testnet | Mainnet | +|---|---|---|---| +| Chain id | 4463 (`0x116f`) | 4462 (`0x116e`) | 4461 (`0x116d`) | +| Network id | `igneum-devnet-3` | `igneum-testnet-1` | not started | +| RPC | `https://rpc.devnet.igneum.network` (a node you run serves `http://127.0.0.1:26790`) | `https://rpc.testnet.igneum.network` (answers; nothing mines there yet, so a transaction waits) | none | +| Coins | no value, resets without notice | no value, resets with notice | not started | +| Symbol, decimals | IGN, 18 | IGN, 18 | IGN, 18 | + +Devnet 3 is where you build today. It is a developer network: it resets without notice and its coins have no value. Its public RPC takes the read methods, `eth_sendRawTransaction` and a wRPC websocket at `/ws`, at 20 requests a second per address. The public testnet exists, its RPC answers, and no blocks are being produced on it until it opens. Mainnet has no date. Devnet 3 answers chain id 4463 until its class v5 floor, then 4464. + +## Endpoints and tools + +- **Wallet.** Any Ethereum wallet. [Add Igneum to MetaMask](/metamask) with one click, or the [Igneum wallet](/wallet). +- **Faucet.** [10 IGN of Devnet 3 coin](/faucet) per address per day. No account. +- **Explorer.** [The explorer](/explorer) shows blocks, the DAG, miners and addresses. The EVM views, transactions and contracts, are being built and link from the same page as they land. +- **Swap.** A swap on Devnet 3 is being built. This page links it when it serves. +- **Source.** [git.igneum.network/igneum-network/igneum](https://git.igneum.network/igneum-network/igneum): the node, the miner, the site, and these pages under `docs/build/`. + +## Your first contract in five minutes + +With [Foundry](https://getfoundry.sh). Tested end to end on Devnet 3 on a build box; the full walkthrough with the expected output is [docs/build/first-contract.md](https://git.igneum.network/igneum-network/igneum/src/branch/master/docs/build/first-contract.md). + +``` +curl -L https://foundry.paradigm.xyz | bash && foundryup +export RPC=https://rpc.devnet.igneum.network +cast wallet new # an address and a private key, for the devnet only +``` + +Paste the address into [the faucet](/faucet). Then: + +``` +export PK=0x... # the private key cast printed +cast balance $(cast wallet address $PK) --rpc-url $RPC --ether +forge init counter && cd counter +forge create src/Counter.sol:Counter --rpc-url $RPC --private-key $PK --broadcast +``` + +`forge create` prints `Deployed to: 0x...`. Call it: + +``` +cast send 0xDEPLOYED "increment()" --rpc-url $RPC --private-key $PK +cast call 0xDEPLOYED "number()(uint256)" --rpc-url $RPC +``` + +The call answers `1`. Paste the transaction hash into [the explorer](/explorer). Hardhat, ethers and viem work the same way: chain id 4463, the RPC above. + +## The RPC the node serves + +Read from a Devnet 3 node, not from a spec. `web3_clientVersion` answers `igneumd/2.1.0/execution-layer-v3`. The full list, served and not served, with the probe that produced it: [docs/build/rpc.md](https://git.igneum.network/igneum-network/igneum/src/branch/master/docs/build/rpc.md). + +- **Served.** `eth_chainId`, `eth_blockNumber`, `eth_getBalance`, `eth_getTransactionCount`, `eth_getCode`, `eth_getStorageAt`, `eth_getBlockByNumber`, `eth_getBlockByHash`, `eth_getTransactionByHash`, `eth_getTransactionReceipt`, `eth_getBlockReceipts`, `eth_getLogs`, `eth_call`, `eth_estimateGas`, `eth_gasPrice`, `eth_maxPriorityFeePerGas`, `eth_feeHistory`, `eth_sendRawTransaction`, `eth_syncing`, `eth_accounts`, `net_version`, `net_peerCount`, `net_listening`, `web3_clientVersion`. +- **Igneum's own.** `igneum_getExecStatus`, `igneum_getProvingStatus`, `igneum_getShardPlan`, `igneum_getProofRecords`, `igneum_getTransactionStatus`, `igneum_estimateGas`, `igneum_getBudgets` and the segment and proof calls. `igneum_estimateGas` returns both gas dimensions and whether a call exceeds the proving limit. +- **Not served.** Filters and subscriptions (`eth_newFilter`, `eth_subscribe`), `txpool_*`, `debug_*`, `trace_*`, `eth_sendTransaction`, `eth_sign`, `eth_getProof`, the uncle calls. Poll `eth_blockNumber` and `eth_getLogs`. Sign on your side. + +## Verify a block in your browser + +The site ships a light client. Your browser fetches the latest certified checkpoint from `/api/checkpoint` and verifies the BLS aggregate signature, the voter weights and the header chain itself, in the tab. Nothing is trusted from the server but the data. The example, in twelve lines, is [docs/build/verify-a-block.md](https://git.igneum.network/igneum-network/igneum/src/branch/master/docs/build/verify-a-block.md); the live run is [verify/test.html](/verify/test.html), genuine and tampered cases side by side. + +``` +import { fetchCheckpoint, verify } from 'https://igneum.network/verify/verify.js'; +const data = await fetchCheckpoint('https://igneum.network/api/checkpoint'); +const r = verify(data); +console.log(r.verified, r.signers, 'of', r.voters, 'voters', r.ms, 'ms'); +``` + +## Where to ask + +- An issue on [the repository](https://git.igneum.network/igneum-network/igneum/issues). +- [Discord](https://discord.gg/igneum), the builders channel. +- [Grants](/grants) for tooling, reference apps, infrastructure and research. + +Devnet and testnet coins have no value. Nothing on this page is an offer to sell anything. diff --git a/docs/build/faucet.md b/docs/build/faucet.md new file mode 100644 index 000000000..0d0120718 --- /dev/null +++ b/docs/build/faucet.md @@ -0,0 +1,36 @@ +# The Devnet 3 faucet + +[igneum.network/faucet](https://igneum.network/faucet) sends 10 IGN of Devnet 3 coin to an address. Once per address per day, once per connection per day. No account. + +## What it is + +- A small Node service on a build box, `infra/build-server/faucet/faucet.mjs`, behind Caddy at `faucet.igneum.network`. The page on igneum.network posts to it. +- It signs a plain transfer with its own key and sends it to the Devnet 3 node on the same box by address (`FAUCET_RPC`), never chosen by chain id: Devnet 3 answers the same chain id as the shared devnet. +- The key lives only in the service's environment file on that box. It is not in the repository and not on the site. + +## The rules, as the code enforces them + +| Rule | Value | +|---|---| +| Amount | 10 IGN per send; the code refuses to start with an amount over the 100 IGN cap | +| Per address | one send per 24 hours | +| Per connection | one send per 24 hours | +| Per day, in total | 500 sends, then "come back tomorrow" | +| What is stored | the address, the connection's IP, the transaction hash and the time, in a file on the box, for the 24-hour window | + +The faucet's balance is refilled from the devnet's funder as it runs down. When it is empty the page says so and sends nothing. + +## Devnet 3 coin + +No value. Not for sale, not redeemable, not a claim on anything. The chain resets without notice and balances do not carry over. Mainnet starts from an empty genesis. + +## Running it + +``` +infra/build-server/faucet/faucet.sh install # dry run: what would be copied and started +infra/build-server/faucet/faucet.sh install --go # copy the service, the unit and the Caddy block; start it +infra/build-server/faucet/faucet.sh status # the unit, the balance, the sends in the last 24 hours +node infra/build-server/faucet/faucet.mjs --self-test # the limiter: known-failed cases first +``` + +The public testnet faucet is a separate thing: `site/api/faucet.mjs` is prepared for it and answers "not open yet" until the testnet starts. diff --git a/docs/build/first-contract-test.sh b/docs/build/first-contract-test.sh new file mode 100755 index 000000000..ee529e092 --- /dev/null +++ b/docs/build/first-contract-test.sh @@ -0,0 +1,49 @@ +#!/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" diff --git a/docs/build/first-contract.md b/docs/build/first-contract.md new file mode 100644 index 000000000..66322567c --- /dev/null +++ b/docs/build/first-contract.md @@ -0,0 +1,91 @@ +# Your first contract on Devnet 3, in five minutes + +With Foundry. Tested end to end on Devnet 3 from a build box on 8 October 2026; the pass line and the exact output are at the end. Hardhat, ethers and viem work the same way: chain id 4463 and the RPC below. + +## 0. What you need + +- Foundry: `curl -L https://foundry.paradigm.xyz | bash && foundryup`. About a minute. +- The RPC. Devnet 3's public endpoint is `https://rpc.devnet.igneum.network`. A node you run yourself serves `http://127.0.0.1:26790`. +- Nothing else. No account, no sign-up. + +``` +export RPC=https://rpc.devnet.igneum.network +cast chain-id --rpc-url $RPC # 4463 +``` + +## 1. A key, for the devnet only + +``` +cast wallet new +``` + +It prints an address and a private key. Keep the key for this walkthrough and nothing else: devnet coins have no value and the chain resets without notice. + +``` +export PK=0x... # the private key cast printed +export ME=$(cast wallet address $PK) +``` + +## 2. Coins from the faucet + +Paste `$ME` into [igneum.network/faucet](https://igneum.network/faucet). It sends 10 IGN and shows the transaction hash. Once per address per day. + +``` +cast balance $ME --rpc-url $RPC --ether # 10.000000000000000000 +``` + +## 3. Deploy + +Foundry's template is a counter. Initialise it and deploy it as it is. + +``` +forge init counter && cd counter +forge create src/Counter.sol:Counter --rpc-url $RPC --private-key $PK --broadcast +``` + +Expected: + +``` +Deployer: 0x... +Deployed to: 0x... +Transaction hash: 0x... +``` + +``` +export C=0x... # the Deployed to address +``` + +## 4. Call it + +``` +cast send $C "increment()" --rpc-url $RPC --private-key $PK +cast call $C "number()(uint256)" --rpc-url $RPC # 1 +cast send $C "setNumber(uint256)" 41 --rpc-url $RPC --private-key $PK +cast call $C "number()(uint256)" --rpc-url $RPC # 41 +``` + +`cast send` waits for the receipt. A block takes about a second. + +## 5. See it + +Paste the transaction hash into [igneum.network/explorer](https://igneum.network/explorer). The receipt is also one call away: + +``` +cast receipt 0xTXHASH --rpc-url $RPC +``` + +## What is different under the hood + +- Gas has two dimensions, execution and proving. The node folds the second into the price it quotes, so `forge` and `cast` estimate and pay as on Ethereum. `igneum_estimateGas` shows both numbers and whether a call exceeds the proving limit. +- Your transaction is in a block within about a second, proven tens of seconds later, and locked by miner signatures in about two minutes. `igneum_getTransactionStatus` reports each stage. +- A transaction signed for another chain id is refused. EIP-155, EIP-1559 and legacy envelopes are accepted. + +## The test record + +Run on a build box against its own Devnet 3 node (the box that also fronts the public RPC and runs the faucet), with Foundry 1.8.5 and a fresh key, through `docs/build/first-contract-test.sh`. The pass line is appended below by the run. + +``` +PASS 10:17:23Z: Devnet 3 chain id 4463, faucet drip 0x8620e9d894484e824e4ac33002946a00e2f75823ea6db0da0ad29f1af142e8f2, deployed Counter at 0x6345265C6Db0b3713eb6645d795d0B52FE0a4201 (tx 0xcf77b4822d51f3c60ecdf6d7dbbe67524d42071f06c76e1cf9b0511d64783574), increment then setNumber(41) read back 41, receipt status 1 (success), block 27055, 24 s end to end, Foundry 1.8.5 +``` + +Through the public endpoints, `https://rpc.devnet.igneum.network` and `https://faucet.igneum.network`, from the box: the first faucet drip and the first reader-path deploy on Devnet 3. diff --git a/docs/build/grants.md b/docs/build/grants.md new file mode 100644 index 000000000..de138be38 --- /dev/null +++ b/docs/build/grants.md @@ -0,0 +1,46 @@ +# Grants + +This file is the source of [igneum.network/grants](https://igneum.network/grants). + +## What a grant is + +Work paid in IGN on delivery. You propose a piece of work, it is accepted with a written definition of done, you build it, it runs on the public testnet, and it is paid when it does. Not a salary, not an advance, not an investment. + +The money is the dev fee fund: the IGN that the Ember dev fee collects. Ember, the official miner, takes an optional 1% of a miner's rewards, in full view and off with one flag; that fee goes to Igneum Labs, the company that ships the software. The protocol itself carries no fee, no fund and no allocation, and no grant is paid from emission. [The dev fee](/dev-fee) has the mechanism. + +## The tiers + +| Tier | What fits | +|---|---| +| Tooling | SDK bindings, indexers, a Hardhat or Foundry plugin for the two gas dimensions, verification tooling, a light client in another language | +| Reference apps | A token, a swap, a registry, a wallet integration, an app that uses `igneum_getTransactionStatus` and the lock the way it is meant to be used | +| Infrastructure | RPC providers, archive nodes, seed nodes in new regions, pool software, an explorer back end | +| Research | The lottery hash, the finality rule, the proving layer, the fee model; attacks on the public test vectors; measurements the project does not have | + +A grant is sized by the work in front of it, not by a table. Two grants in the same tier can differ. + +## How to apply + +Open an issue on [git.igneum.network/igneum-network/igneum](https://git.igneum.network/igneum-network/igneum/issues/new?template=.forgejo%2Fissue_template%2Fgrant.md) and pick the Grant template. It asks for: + +1. What you will build, in a paragraph, and which tier it is. +2. What done looks like: what runs where, what a reviewer can click or call to see it working on the public testnet. +3. Your public work: a repository, a package, a paper, a deployed thing. +4. How you want to be reached. + +A short issue is fine. The definition of done is the part that matters. + +## The review + +- Every Friday. Each open application gets one of three answers inside seven days: accepted with a definition of done, questions, or no with the reason. +- Accepted work is tracked in its issue. The reviewer and the builder agree the definition of done there before any work starts. +- Delivery is reviewed on the public testnet first. Payment follows the review, in the issue, in the open. + +## The honest lines + +- Devnet and testnet coins have no value. A grant delivered before mainnet is paid when mainnet IGN exists, not in devnet or testnet coin. +- No amount, price or date is promised. The fund is what the dev fee collects, and it is zero until there is mining with value. +- A grant is for work delivered, never for a user count, a listing, a post or a referral. +- Igneum Labs may decline any application and may close the programme; what has been accepted and delivered is still paid. + +Not legal advice. diff --git a/docs/build/rpc.md b/docs/build/rpc.md new file mode 100644 index 000000000..8f46d1ef1 --- /dev/null +++ b/docs/build/rpc.md @@ -0,0 +1,79 @@ +# The JSON-RPC a Devnet 3 node serves + +Read from the node, not from a spec: every method below was sent to a Devnet 3 node on 8 October 2026 (`web3_clientVersion` answered `igneumd/2.1.0/execution-layer-v3`, `eth_chainId` `0x116f`, block 26,666) and sorted by what came back. A method the node does not know answers JSON-RPC error `-32601`. The probe is at the end; run it against your own node to refresh this file. + +Transport: HTTP POST, JSON-RPC 2.0, one request per call, no batches tested. The public endpoint is `https://rpc.devnet.igneum.network`; a node you run serves `http://127.0.0.1:26790`. The DAG side of the node (blocks, the mempool, the vote table) speaks the Kaspa-style gRPC and wRPC on their own ports; the explorer and the observer use those, a contract developer needs only what is here. + +## Served: the Ethereum set + +| Method | Note | +|---|---| +| `web3_clientVersion` | `igneumd//execution-layer-v3` | +| `net_version` | the chain id as a decimal string, `"4463"` | +| `net_listening`, `net_peerCount` | | +| `eth_chainId` | `0x116f` (4463) on Devnet 3; `0x1170` after its class v5 floor | +| `eth_blockNumber` | the executed chain tip | +| `eth_syncing` | `false` when the executor is at the tip | +| `eth_mining` | `false` from the RPC's point of view: mining is the miner's, not the node's | +| `eth_accounts` | always `[]`: the node holds no keys | +| `eth_protocolVersion` | `0x41` | +| `eth_gasPrice`, `eth_maxPriorityFeePerGas`, `eth_feeHistory` | EIP-1559 pricing; the proving dimension is folded into the quoted price | +| `eth_getBalance`, `eth_getTransactionCount`, `eth_getCode`, `eth_getStorageAt` | by block tag or number | +| `eth_getBlockByNumber`, `eth_getBlockByHash` | the EVM view of a chain block; `null` for an unknown hash | +| `eth_getBlockTransactionCountByNumber` | | +| `eth_getTransactionByHash`, `eth_getTransactionByBlockNumberAndIndex` | | +| `eth_getTransactionReceipt`, `eth_getBlockReceipts` | | +| `eth_getLogs` | by block range, address and topics | +| `eth_call`, `eth_estimateGas` | as on Ethereum | +| `eth_sendRawTransaction` | a signed EIP-155, EIP-1559 or legacy envelope; the chain id must match | + +## Served: Igneum's own + +| Method | What the answer carries (from the probe) | +|---|---| +| `igneum_getExecStatus` | the executed tip, a continuity break if any, what the executor is blocked on | +| `igneum_getProvingStatus` | whether proving is active, its activation height, assignees per shard, the dust rule | +| `igneum_getShardPlan` | a block's shard plan: the DAA score, eligible keys, the epoch seed, the assignments | +| `igneum_getProofRecords` | the proof records a block carries and claims | +| `igneum_getTransactionStatus` | per transaction: in the mempool, included in, executed, locked | +| `igneum_estimateGas` | both gas dimensions, the base fee, and `exceedsProvingLimit` | +| `igneum_getBudgets` | the proving budgets: backlog depth, the base fee, chain blocks | +| `igneum_getAssignedShards` | the shards assigned to this node's prover | +| `igneum_getSegment`, `igneum_getSegmentRecords`, `igneum_getSegmentStatement` | a segment's executed blocks, records and statement | +| `igneum_getSegmentProofBytes`, `igneum_getProofBytes` | the proof bytes for a key hash (`keyHash` is required) | +| `igneum_exportSegments` | a range of segments with their accounts | +| `igneum_exportExecSnapshot` | writes a snapshot to a path on the node's own disk | +| `igneum_submitProofRecord`, `igneum_submitSegmentRecord` | the prover's submissions; a record is required | + +## Not served + +Each of these answered `-32601` on 8 October 2026. Where a tool wants one, the note says what to do instead. + +| Method | Instead | +|---|---| +| `eth_newFilter`, `eth_newBlockFilter`, `eth_newPendingTransactionFilter`, `eth_getFilterChanges`, `eth_getFilterLogs`, `eth_uninstallFilter` | poll `eth_blockNumber` and `eth_getLogs` | +| `eth_subscribe`, `eth_unsubscribe` | no WebSocket subscriptions; poll | +| `eth_sendTransaction`, `eth_sign`, `eth_signTransaction` | the node holds no keys; sign on your side and use `eth_sendRawTransaction` | +| `eth_getProof` | not yet | +| `eth_getTransactionByBlockHashAndIndex`, `eth_getBlockTransactionCountByHash` | use the by-number forms | +| `eth_getUncleByBlockHashAndIndex`, `eth_getUncleByBlockNumberAndIndex`, `eth_getUncleCountByBlockHash`, `eth_getUncleCountByBlockNumber` | there are no uncles; the DAG's merged blocks are not EVM blocks | +| `eth_createAccessList`, `eth_simulateV1`, `eth_blobBaseFee` | not yet; no blobs | +| `eth_coinbase`, `eth_hashrate` | the miner's, not the node's | +| `txpool_content`, `txpool_status` | `igneum_getTransactionStatus` for one transaction | +| `debug_traceTransaction`, `debug_traceBlockByNumber`, `trace_block` | no tracing yet | +| `web3_sha3` | hash on your side | + +## The probe + +Every method with a harmless argument; the verdict is `served`, `not-found` (`-32601`) or `error` (the node knows the method and refused the argument, which counts as served). + +``` +URL=http://127.0.0.1:26790 +call() { curl -s -X POST -H 'content-type: application/json' \ + --data "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"$1\",\"params\":${2:-[]}}" "$URL"; } +for m in web3_clientVersion eth_chainId eth_blockNumber eth_newFilter eth_subscribe; do + printf '%-32s %s\n' "$m" "$(call $m | cut -c1-80)" +done +``` + +Devnet 3 resets without notice and its coins have no value. diff --git a/docs/build/verify-a-block.md b/docs/build/verify-a-block.md new file mode 100644 index 000000000..fcb1ac997 --- /dev/null +++ b/docs/build/verify-a-block.md @@ -0,0 +1,34 @@ +# Verify a block in your browser + +Igneum's finality is a certificate: miners sign a checkpoint every 30 seconds, and when two thirds of the 30-day mining weight have signed, it is locked. A browser can check that certificate by itself. The site ships the verifier: `site/verify/core.js` does the work, `site/verify/verify.js` fetches the data and calls it. + +## Twelve lines + +``` + +``` + +## What is checked, in the tab + +1. The header chain from the previous locked checkpoint to this one: each header's hash is recomputed and each links to its parent. +2. The voter table: each voter's key hash, and the weights the bitmap selects. +3. The BLS aggregate signature over the checkpoint message, against the aggregated public keys of the signers, and that the signed weight reaches two thirds of the total. + +Nothing is trusted from the server but the data itself. Change one bit of the signature, drop one signer from the bitmap, alter a header's nonce, and `verify` says `false` with the reason. The live run of exactly those cases is [igneum.network/verify/test.html](https://igneum.network/verify/test.html). + +## The data + +`GET /api/checkpoint` returns the latest certified checkpoint: the certificate as carried in a block, the voter list with public keys and weights, and the selected-chain headers from the previous lock. `?source=test` returns the finality test network's instead. Libraries, pinned: BLAKE2b from `@noble/hashes` 2.4.0 and BLS12-381 from `@noble/curves` 2.4.0, pure JavaScript. + +## From a node + +The same certificate is in the block that carries it, and `igneum_getProofRecords` returns a block's records. A light client that reads blocks from a node instead of the site's API verifies the same way; `core.js` has no dependency on the site. diff --git a/infra/build-server/faucet/faucet.mjs b/infra/build-server/faucet/faucet.mjs new file mode 100644 index 000000000..332f84393 --- /dev/null +++ b/infra/build-server/faucet/faucet.mjs @@ -0,0 +1,171 @@ +// The Devnet 3 faucet (the builder programme, 8 October 2026). A Node service behind Caddy at faucet.igneum.network on a build box: +// POST /api/faucet {address} sends AMOUNT_IGN of Devnet 3 coin to the address and answers {ok, tx, amount, chainId, to} +// GET /api/faucet the state: network, chain id, amount, the faucet's balance in IGN, sends in the last 24 hours +// Rules, enforced here and nowhere else: one send per address per WINDOW_HOURS, one per connection (IP) per WINDOW_HOURS, +// DAILY_MAX sends per WINDOW_HOURS in total, and AMOUNT_IGN under the HARD_CAP_IGN the fleet lane set on the faucet's own sends +// (the service refuses to start otherwise). Sends are serialised so the nonce is read once per send. The record of sends is an +// append-only JSONL file next to the service (FAUCET_STATE, default ./grants.jsonl); the window is read from it at start. +// Environment (EnvironmentFile=/srv/faucet/env on the box, mode 600; the key is nowhere else): FAUCET_KEY (0x + 64 hex), +// FAUCET_RPC (the Devnet 3 node by address, never chosen by chain id: Devnet 3 answers the same id as the shared devnet), +// FAUCET_CHAIN_ID (4463), FAUCET_PORT (8790), FAUCET_ORIGIN (the page's origin for CORS, https://igneum.network). +// Signing: site/lib/eth.mjs, copied next to this file by faucet.sh (zero dependencies). +// node faucet.mjs the service +// node faucet.mjs --self-test the limiter and the handler against a fake node: known-failed cases first +import { createServer } from 'node:http'; +import { readFileSync, appendFileSync, existsSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const here = dirname(fileURLToPath(import.meta.url)); +const { signTransaction, addressOf, isAddress, toWei } = await import(join(here, 'eth.mjs')); + +export const AMOUNT_IGN = 10; +export const HARD_CAP_IGN = 100; // the fleet lane's cap on the faucet's own sends (main's rule, 8 October 2026) +export const WINDOW_HOURS = 24; +export const DAILY_MAX = 500; +const WINDOW_MS = WINDOW_HOURS * 3600 * 1000; + +export function limiter(now = () => Date.now()) { + // in memory, fed from the JSONL at start; each row {t, address, ip, tx} + const rows = []; + const prune = () => { const cut = now() - WINDOW_MS; while (rows.length && rows[0].t < cut) rows.shift(); }; + return { + load(row) { rows.push(row); }, + check(address, ip) { + prune(); + if (rows.some(r => r.address === address)) return `This address had its ${AMOUNT_IGN} IGN in the last ${WINDOW_HOURS} hours. Come back tomorrow.`; + if (rows.some(r => r.ip === ip)) return `This connection had its ${AMOUNT_IGN} IGN in the last ${WINDOW_HOURS} hours. Come back tomorrow.`; + if (rows.length >= DAILY_MAX) return `The faucet has sent its ${DAILY_MAX} for today. Come back tomorrow.`; + return null; + }, + add(row) { rows.push(row); }, + drop(row) { const i = rows.indexOf(row); if (i >= 0) rows.splice(i, 1); }, + count() { prune(); return rows.length; }, + }; +} + +export function rpcClient(url, fetchImpl = fetch) { + let id = 0; + return async (method, params = []) => { + const r = await fetchImpl(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: ++id, method, params }) }); + const j = await r.json(); + if (j.error) throw new Error(`${method}: ${j.error.message || JSON.stringify(j.error)}`); + return j.result; + }; +} + +const ignOf = wei => (Number(BigInt(wei) / 10n ** 12n) / 1e6).toFixed(6); + +export function createHandler({ env = process.env, rpc, lim, record = () => {}, now = () => Date.now() } = {}) { + const key = env.FAUCET_KEY || '', rpcUrl = env.FAUCET_RPC || ''; + const chainId = Number(env.FAUCET_CHAIN_ID || 4463); + const origin = env.FAUCET_ORIGIN || 'https://igneum.network'; + if (AMOUNT_IGN > HARD_CAP_IGN) throw new Error(`AMOUNT_IGN ${AMOUNT_IGN} is over the ${HARD_CAP_IGN} IGN cap`); + if (!/^0x[0-9a-fA-F]{64}$/.test(key) || !rpcUrl) throw new Error('FAUCET_KEY (0x + 64 hex) and FAUCET_RPC are required'); + const from = addressOf(key); + rpc = rpc || rpcClient(rpcUrl); + lim = lim || limiter(now); + let chain = Promise.resolve(); // sends one after another: one nonce read per send + const json = (res, status, body) => { res.writeHead(status, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' }); res.end(JSON.stringify(body)); }; + const cors = (req, res) => { + const o = req.headers.origin || ''; + const allowed = o === origin || /^http:\/\/(127\.0\.0\.1|localhost):\d+$/.test(o); + res.setHeader('Access-Control-Allow-Origin', allowed ? o : origin); + res.setHeader('Vary', 'Origin'); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); + }; + const clientIp = req => { const fwd = req.headers['x-forwarded-for']; const first = (Array.isArray(fwd) ? fwd[0] : fwd || '').split(',')[0].trim(); return first || String(req.headers['x-real-ip'] || req.socket?.remoteAddress || '').trim() || 'unknown'; }; + const readBody = req => new Promise((resolve, reject) => { const c = []; let n = 0; req.on('data', d => { n += d.length; if (n > 4096) { reject(new Error('too large')); req.destroy(); } else c.push(d); }); req.on('end', () => { try { resolve(JSON.parse(Buffer.concat(c).toString('utf8') || '{}')); } catch (e) { reject(e); } }); req.on('error', reject); }); + return async function handler(req, res) { + cors(req, res); + const path = (req.url || '/').split('?')[0]; + if (req.method === 'OPTIONS') { res.writeHead(204); return res.end(); } + if (path !== '/api/faucet' && path !== '/api/faucet/') return json(res, 404, { ok: false, error: 'not found' }); + if (req.method === 'GET') { + let balance = null; + try { balance = ignOf(await rpc('eth_getBalance', [from, 'latest'])); } catch { /* the node is the status */ } + return json(res, 200, { ok: true, network: 'igneum-devnet-3', chainId, amount: String(AMOUNT_IGN), window_hours: WINDOW_HOURS, daily_max: DAILY_MAX, address: from, balance_ign: balance, sends_24h: lim.count(), funded: balance !== null && Number(balance) >= AMOUNT_IGN }); + } + if (req.method !== 'POST') { res.setHeader('Allow', 'GET, POST, OPTIONS'); return json(res, 405, { ok: false, error: 'method not allowed' }); } + let body; + try { body = await readBody(req); } catch { return json(res, 400, { ok: false, error: 'bad json' }); } + const address = String(body.address || '').trim(); + if (!isAddress(address)) return json(res, 400, { ok: false, error: 'That is not an address. It is 0x followed by 40 hex characters.' }); + const to = address.toLowerCase(), ip = clientIp(req); + const refused = lim.check(to, ip); + if (refused) return json(res, 429, { ok: false, error: refused }); + const row = { t: now(), address: to, ip, tx: null }; + lim.add(row); // reserved before the send, so two asks in the same second cannot both pass + const result = chain.then(async () => { + const [nonceHex, block, tipHex, balHex] = await Promise.all([ + rpc('eth_getTransactionCount', [from, 'pending']), + rpc('eth_getBlockByNumber', ['latest', false]), + rpc('eth_maxPriorityFeePerGas').catch(() => '0x3b9aca00'), + rpc('eth_getBalance', [from, 'latest']), + ]); + const value = toWei(AMOUNT_IGN); + if (BigInt(balHex) < value * 2n) throw Object.assign(new Error('The faucet is empty right now. It is being refilled; try again in an hour.'), { status: 503 }); + const baseFee = BigInt(block?.baseFeePerGas || '0x3b9aca00'), tip = BigInt(tipHex || '0x3b9aca00'); + let gas = 21000n; + try { gas = BigInt(await rpc('eth_estimateGas', [{ from, to, value: '0x' + value.toString(16) }])); } catch { /* the plain-transfer default */ } + const tx = { chainId, nonce: BigInt(nonceHex), maxPriorityFeePerGas: tip, maxFeePerGas: baseFee * 2n + tip, gas, to, value, data: '0x' }; + const signed = signTransaction(tx, key); + return rpc('eth_sendRawTransaction', [signed.raw]); + }); + chain = result.catch(() => {}); + try { + const hash = await result; + row.tx = hash; record(row); + return json(res, 200, { ok: true, tx: hash, amount: String(AMOUNT_IGN), chainId, to }); + } catch (e) { + lim.drop(row); + return json(res, e.status || 502, { ok: false, error: e.status ? e.message : `The node did not take the transaction: ${String(e.message || e).slice(0, 200)}` }); + } + }; +} + +if (process.argv.includes('--self-test')) { + // known-failed first: a fake node that answers every read and refuses every send must leave the limiter empty and answer 502 + let t = 1_000_000; const now = () => t; + const fakeNode = (sendOk = true) => async (m, p) => ({ + eth_getTransactionCount: '0x1', eth_getBlockByNumber: { baseFeePerGas: '0x174876e800' }, eth_maxPriorityFeePerGas: '0x3b9aca00', + eth_getBalance: '0x' + (10n ** 21n).toString(16), eth_estimateGas: '0x5208', + eth_sendRawTransaction: sendOk ? '0x' + 'ab'.repeat(32) : (() => { throw new Error('refused'); })(), + })[m]; + const call = async (h, method, body, ip = '1.1.1.1') => new Promise(resolve => { + const chunks = body === undefined ? [] : [Buffer.from(JSON.stringify(body))]; + const req = { method, url: '/api/faucet', headers: { 'x-forwarded-for': ip, origin: 'https://igneum.network' }, socket: {}, on(ev, cb) { if (ev === 'data') for (const c of chunks) cb(c); if (ev === 'end') cb(); return this; } }; + const res = { h: {}, setHeader(k, v) { this.h[k] = v; }, writeHead(s, hh) { this.status = s; Object.assign(this.h, hh || {}); }, end(b) { resolve({ status: this.status, body: b ? JSON.parse(b) : null }); } }; + h(req, res); + }); + const env = { FAUCET_KEY: '0x' + '11'.repeat(32), FAUCET_RPC: 'http://fake', FAUCET_CHAIN_ID: '4463' }; + const A = '0x' + 'a1'.repeat(20), B = '0x' + 'b2'.repeat(20); + let fails = 0; const expect = (c, msg) => { if (!c) { console.error('self-test failed: ' + msg); fails++; } }; + { const lim = limiter(now); const h = createHandler({ env, rpc: fakeNode(false), lim, now }); const r = await call(h, 'POST', { address: A }); expect(r.status === 502 && lim.count() === 0, `a refused send must answer 502 and reserve nothing (got ${r.status}, ${lim.count()})`); } + { const lim = limiter(now); const h = createHandler({ env, rpc: fakeNode(), lim, now }); const r = await call(h, 'POST', { address: 'not-an-address' }); expect(r.status === 400, 'a bad address must answer 400'); } + { const lim = limiter(now); const recorded = []; const h = createHandler({ env, rpc: fakeNode(), lim, record: r => recorded.push(r), now }); + const r1 = await call(h, 'POST', { address: A }); expect(r1.status === 200 && r1.body.ok && r1.body.tx.startsWith('0x'), 'the first ask sends'); + expect(recorded.length === 1 && recorded[0].tx === r1.body.tx, 'the send is recorded with its hash'); + const r2 = await call(h, 'POST', { address: A }); expect(r2.status === 429 && /address/.test(r2.body.error), 'the same address inside the window is refused'); + const r3 = await call(h, 'POST', { address: B }); expect(r3.status === 429 && /connection/.test(r3.body.error), 'the same connection inside the window is refused'); + const r4 = await call(h, 'POST', { address: B }, '2.2.2.2'); expect(r4.status === 200, 'another address from another connection sends'); + t += WINDOW_MS + 1; const r5 = await call(h, 'POST', { address: A }); expect(r5.status === 200, 'after the window the address sends again'); + const s = await call(h, 'GET'); expect(s.status === 200 && s.body.network === 'igneum-devnet-3' && s.body.sends_24h === 1 && s.body.funded === true, `the status carries the network and the window count (got ${JSON.stringify(s.body)})`); + for (let i = 0; i < DAILY_MAX - 1; i++) lim.add({ t: now(), address: '0x' + String(i).padStart(40, '0'), ip: 'x' + i, tx: '0x' }); + const r6 = await call(h, 'POST', { address: '0x' + 'c3'.repeat(20) }, '3.3.3.3'); expect(r6.status === 429 && /today/.test(r6.body.error), 'the daily total refuses the next ask'); } + { let threw = false; try { createHandler({ env: { ...env, FAUCET_KEY: 'bad' }, rpc: fakeNode(), now }); } catch { threw = true; } expect(threw, 'a bad key must refuse to start'); } + expect(AMOUNT_IGN <= HARD_CAP_IGN, 'the amount is under the cap'); + if (fails) process.exit(1); + console.log(`self-test passed: a refused send reserves nothing (502), a bad address 400, the same address and the same connection are refused inside ${WINDOW_HOURS} h (429), another pair sends, the window expires, the status counts, the ${DAILY_MAX}-a-day total refuses, a bad key refuses to start, ${AMOUNT_IGN} IGN is under the ${HARD_CAP_IGN} IGN cap`); + process.exit(0); +} + +if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1] && !process.argv.includes('--self-test')) { + const STATE = process.env.FAUCET_STATE || join(here, 'grants.jsonl'); + const lim = limiter(); + if (existsSync(STATE)) { const cut = Date.now() - WINDOW_MS; for (const l of readFileSync(STATE, 'utf8').split('\n')) { if (!l) continue; try { const r = JSON.parse(l); if (r.t >= cut && r.tx) lim.load(r); } catch { /* a torn line */ } } } + const handler = createHandler({ lim, record: row => appendFileSync(STATE, JSON.stringify(row) + '\n') }); + const port = Number(process.env.FAUCET_PORT || 8790); + createServer((req, res) => { handler(req, res).catch(e => { try { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ ok: false, error: String(e.message || e).slice(0, 200) })); } catch { /* closed */ } }); }).listen(port, '127.0.0.1', () => console.log(`${new Date().toISOString()} faucet on 127.0.0.1:${port}, ${AMOUNT_IGN} IGN per send, ${lim.count()} sends in the window, from ${addressOf(process.env.FAUCET_KEY)}`)); +} diff --git a/infra/build-server/faucet/faucet.sh b/infra/build-server/faucet/faucet.sh new file mode 100755 index 000000000..bd162e6bc --- /dev/null +++ b/infra/build-server/faucet/faucet.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# The Devnet 3 faucet on a build box (the builder programme, 8 October 2026). Dry run by default, --go executes. +# infra/build-server/faucet/faucet.sh install [--go] copy faucet.mjs and site/lib/eth.mjs to /srv/faucet (the build user's), the unit +# to /etc/systemd/system, the Caddy block for faucet.igneum.network (validate, reload), +# enable and start the unit, read back GET /api/faucet through the loopback +# infra/build-server/faucet/faucet.sh status the unit, the loopback status (balance, sends in the window), the Caddy host +# infra/build-server/faucet/faucet.sh self-test node faucet.mjs --self-test on the box +# /srv/faucet/env (mode 600, the build user's) is written by hand on the box and never by this script: FAUCET_KEY, FAUCET_RPC +# (the Devnet 3 node by address), FAUCET_CHAIN_ID, FAUCET_PORT, FAUCET_ORIGIN. The key is nowhere else. +set -euo pipefail +HERE=$(cd "$(dirname "$0")" && pwd); ROOT=$(cd "$HERE/../../.." && pwd) +KEY="${IGNEUM_BUILD_KEY:-$HOME/.ssh/igneum_ed25519}"; HOST=$(head -1 "${IGNEUM_BUILD_HOST_FILE:-$HOME/.config/igneum/build-server}" | tr -d '[:space:]') +IP="${HOST#*@}" +SSHB=(ssh -i "$KEY" -o BatchMode=yes -o ConnectTimeout=15 "$HOST") +SSHR=(ssh -i "$KEY" -o BatchMode=yes -o ConnectTimeout=15 "root@$IP") +say() { echo "$(TZ=UTC date '+%H:%M:%SZ') faucet: $*" >&2; } +mode="${1:-}"; shift || true; GO=0; [ "${1:-}" = --go ] && GO=1 +FAUCET_HOST="${FAUCET_HOST:-faucet.igneum.network}"; PORT="${FAUCET_PORT:-8790}" +caddy_block() { cat < $HOST:/srv/faucet/; igneum-faucet.service -> root@$IP:/etc/systemd/system/; append to /etc/caddy/Caddyfile unless $FAUCET_HOST is there:"; caddy_block; say "DRY: systemctl daemon-reload, enable --now igneum-faucet; curl 127.0.0.1:$PORT/api/faucet"; exit 0 + fi + "${SSHB[@]}" 'test -s /srv/faucet/env' || { say "/srv/faucet/env is missing on the box: write it by hand first (FAUCET_KEY, FAUCET_RPC, FAUCET_CHAIN_ID, FAUCET_PORT, FAUCET_ORIGIN)"; exit 1; } + scp -q -i "$KEY" "$HERE/faucet.mjs" "$ROOT/site/lib/eth.mjs" "$HOST:/srv/faucet/" + "${SSHB[@]}" 'cd /srv/faucet && node faucet.mjs --self-test' || { say "the self-test failed on the box; nothing started"; exit 1; } + scp -q -i "$KEY" "$HERE/igneum-faucet.service" "root@$IP:/etc/systemd/system/igneum-faucet.service" + "${SSHR[@]}" bash -s -- "$FAUCET_HOST" "$(caddy_block | base64 | tr -d '\n')" <<'REMOTE' +set -euo pipefail; host="$1"; block="$(printf '%s' "$2" | base64 -d)" +if ! grep -q "^$host {" /etc/caddy/Caddyfile; then + cp /etc/caddy/Caddyfile "/etc/caddy/Caddyfile.bak-$(date -u +%Y%m%d%H%M%S)" + printf '%s\n' "$block" >> /etc/caddy/Caddyfile + caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile >/dev/null + systemctl reload caddy; echo "caddy: $host block appended and reloaded" +else echo "caddy: $host block already present"; fi +systemctl daemon-reload; systemctl enable igneum-faucet >/dev/null 2>&1; systemctl restart igneum-faucet; sleep 3; systemctl is-active igneum-faucet; journalctl -u igneum-faucet --since "-30 s" --no-pager | tail -2 | cut -c1-160 +REMOTE + "${SSHB[@]}" "curl -s -m 10 http://127.0.0.1:$PORT/api/faucet; echo" + ;; + status) + "${SSHR[@]}" "systemctl is-active igneum-faucet; journalctl -u igneum-faucet --since '-10 min' --no-pager | tail -5 | cut -c1-160; grep -c '^$FAUCET_HOST {' /etc/caddy/Caddyfile" + "${SSHB[@]}" "curl -s -m 10 http://127.0.0.1:$PORT/api/faucet; echo; wc -l /srv/faucet/grants.jsonl 2>/dev/null || echo 'no sends yet'" + ;; + self-test) "${SSHB[@]}" 'cd /srv/faucet && node faucet.mjs --self-test' ;; + *) sed -n '2,10p' "$0" | sed 's/^# \{0,1\}//'; exit 2 ;; +esac diff --git a/infra/build-server/faucet/igneum-faucet.service b/infra/build-server/faucet/igneum-faucet.service new file mode 100644 index 000000000..c7adf50b8 --- /dev/null +++ b/infra/build-server/faucet/igneum-faucet.service @@ -0,0 +1,22 @@ +[Unit] +Description=Igneum Devnet 3 faucet (infra/build-server/faucet/faucet.mjs), 127.0.0.1:8790 behind Caddy at faucet.igneum.network +After=network-online.target +Wants=network-online.target + +[Service] +User=build +Group=build +WorkingDirectory=/srv/faucet +EnvironmentFile=/srv/faucet/env +Environment=FAUCET_STATE=/srv/faucet/grants.jsonl +ExecStart=/usr/local/bin/node /srv/faucet/faucet.mjs +Restart=always +RestartSec=5 +Nice=5 +NoNewPrivileges=true +ProtectSystem=strict +ReadWritePaths=/srv/faucet +PrivateTmp=true + +[Install] +WantedBy=multi-user.target diff --git a/site/404.html b/site/404.html index 4020c5c81..6cb434aff 100644 --- a/site/404.html +++ b/site/404.html @@ -60,6 +60,7 @@ main{flex:1} + @@ -100,6 +100,13 @@ main{flex:1} Built on the shouldersEvery borrowed part, credited. + + +
Network
@@ -177,6 +183,11 @@ main{padding-bottom:100px}.card{background:var(--row);border:1px solid var(--lin Igneum vs RandomXWhat was kept and what was rebuilt for GPUs. Built on the shouldersEvery borrowed part, credited.
+
Download @@ -273,7 +284,7 @@ main{padding-bottom:100px}.card{background:var(--row);border:1px solid var(--lin The wallet GPU bench table The dev fee - Testnet faucet + Devnet faucet Add Igneum to MetaMask
+ + + +
Download @@ -213,7 +224,7 @@ table{min-width:560px}
-
96 entries, newest at the bottom
+
98 entries, newest at the bottom

Engineering log

Every measurement the project has made, newest at the bottom, written by the people and agents who ran it, with the commands and hardware. Prototype numbers are not mining numbers and say so.

@@ -221,7 +232,7 @@ table{min-width:560px}
- +

Igneum bench log

Append-only. Every number here was measured on the machine named, on the date given.

@@ -962,7 +973,23 @@ table{min-width:560px}

Rented Vast instance 54613164, Intel Core i7-9700K at 4,170 MHz as read, one core, the box-built Linux igneum-pow (sha256 6d286783...), bench --seed igneum-genesis --day 2026-10-03 --warps 50. Ms per warp, cold max / average of 50: v2 1.582 / 1.280; mx8 5.394 / 5.267; mx8+sh256x27 (class v4) 6.334 / 6.006; dr368 5.540 / 5.426; dr736 10.290 / 10.042 (fails the 10 ms gate, the known-fail). Cache fill 276 ms. Log docs/analysis/attack-pass/o114-i7-9700K-2026-10-07.log; record docs/analysis/attack-pass-2026-10.md row F6. Class v4 passes a real 2019-class core with 3.7 ms to spare; the half-core proxy (8.23 ms) stays the standing pessimistic rule for the ladder's ceiling.

F6: the verifier's worst case over 10^5 class v4 programs (attack pass, 7 October 2026, 13:5x UTC)

-

igneum-build-1, cores 40 (one-core proxy) and 88 (the half-core proxy, both SMT siblings busy) under the per-core lease, core 40 at a median 3,799.9 MHz. 100,000 programs ranked by exact op counts; 50,000 timed cold on core 40 (min / median / p99 / max 4.610 / 4.948 / 5.606 / 6.194 ms per warp); the worst 200 re-timed at 10 cold reps on both proxies and the worst 1,000 on the half-core at 2 reps, the worst 10 at 20: worst half-core cold max 8.708 ms (attack-f6/87142), then 8.629 (attack-f6/88521), 8.414 (attack-f6/15781); the genesis program 8.624. Gate 10 ms: PASS by 1.29 ms. Record docs/analysis/attack-pass/f6-verifier.md; logs /srv/builds/igneum-wt-attack/target-attack-f6/phase2b.log, phase2c.log.

+

igneum-build-1, cores 40 (one-core proxy) and 88 (the half-core proxy, both SMT siblings busy) under the per-core lease, core 40 at a median 3,799.9 MHz. 100,000 programs ranked by exact op counts; 50,000 timed cold on core 40 (min / median / p99 / max 4.610 / 4.948 / 5.606 / 6.194 ms per warp); the worst 200 re-timed at 10 cold reps on both proxies and the worst 1,000 on the half-core at 2 reps, the worst 10 at 20: worst half-core cold max 8.708 ms (attack-f6/87142), then 8.629 (attack-f6/88521), 8.414 (attack-f6/15781); the genesis program 8.624. Gate 10 ms: PASS by 1.29 ms. Record docs/analysis/attack-pass/f6-verifier.md; logs /srv/builds/igneum-wt-attack/target-attack-f6/phase2b.log, phase2c.log.

+
+

7 to 8 October 2026, the class v4 efficiency passes: the core clock lock on the RTX 5090 and the RTX 5080 (branch ca3-v4-amend, the hash lane)

+

Main's question of 7 October ("profitable mining is all about efficiency, is there a solution?"): the latency-shadow work hides in the memory wait, so the core clock, and the voltage along the driver's V/F curve, can drop until the work just fits the wait, with no rate loss and the class v4 premium coming back. Measured on the three-card Windows rig (RTX 5090, RTX 4070, RX 9070 XT) (machine ae432dc7, driver 617.14, app 0.3.20) with tools/ca3-v4-amend/pc1-v4-efficiency.ps1: the installed CUDA worker's --bench on the class v4 pack v4-devnet-epoch0 (id a785001687d8688a) and the class v3 control mx8-devnet-epoch0 of the same seed (the two differ by the shadow block alone), the card alone through the runner's card switch, 60 s per step (the batch count sized from a 5-batch probe), the clock lock through the installed app's Power Helper task (nvidia-smi -lgc 0,<MHz>, a cap; no prompt), nvidia-smi at 1 Hz with the mean from 8 s in (power.draw, power.draw.instant and power.draw.average agree within 0.2 W on every row on 617.14), the 2^24 fingerprint at base 0 checked against the Apple M5 Max on every step (every step matched), the clocks reset and read back at the end. Jobs: run-ca3-pc1-v4-eff-5090-20261007-b (18:40 to 19:16 UTC, 2,850 to 1,400 MHz), run-ca3-pc1-v4-eff-5090-floor-20261007 (20:21 to 20:41 UTC, 1,400 to 1,100; the steps below were lost to the helper's sequence rule, see the fud ledger), run-ca3-pc1-v4-eff-5080-20261007-d (8 October, 00:45 to 01:54 UTC, unlocked to 1,000 MHz plus 900 on v4; the 75 min budget ended it there). The memory clock stayed at the driver's default throughout (13,801 MHz on the 5090, 14,801 on the 5080).

+

RTX 5090 (lock MHz: class v4 MH/s / W / MH/W ; class v3 MH/s / W / MH/W; the SM clock read):

+
lockv4 MH/sv4 Wv4 MH/Wv3 MH/sv3 Wv3 MH/Wsm MHz
unlocked136.84475.50.288136.59330.20.4142,838 / 2,850
2850136.89478.90.286136.61330.30.4142,833 / 2,842
2781136.87457.60.299136.62317.90.4302,767
2700136.85443.20.309136.54312.40.4372,692
2550136.77402.90.339136.43288.50.4732,542
2472136.62391.20.349136.38276.00.4942,460
2400136.67382.80.357136.29269.80.5052,392
2250136.43366.50.372136.07255.00.5342,242
2163136.33361.30.377136.11252.40.5392,152
2100136.25359.30.379136.04251.40.5412,092
1950135.99350.10.388135.78244.60.5551,942
1854135.85341.40.398135.56239.60.5661,845
1800135.75337.10.403135.48237.40.5711,792
1650135.37328.10.413135.10235.10.5751,642
1500135.22320.50.422134.90232.10.5811,492
1400134.98316.30.427134.68228.00.5911,387
1300134.76312.50.431134.62223.30.6031,290
1200133.80305.10.439129.54215.70.6011,192
1100122.43287.30.426118.70209.40.5671,087
+

Reading (5090): the rate is memory-bound on the whole grid and holds within 1.5 percent of unlocked down to 1,300 MHz on both classes; it falls past 5 percent at 1,200 on class v3 and past 10 percent at 1,100 on class v4, so the knee is 1,300 MHz (42 percent of the 3,090 MHz maximum). Best MH per watt: class v4 at 1,200 MHz (133.80 MH/s at 305.1 W, 0.439 MH/W; 168.6 W recovered for 2.2 percent rate), class v3 at 1,300 MHz (134.62 at 223.3 W, 0.603; 106.6 W for 1.4 percent). The class v4 premium over class v3 is 145.3 W at the unlocked clock and 81.8 W at the best points: 63 W of the premium is the clock and comes back with the lock, 82 W is the shadow's ALU work and stays. The throttle reason reads the power governor (0x400) from unlocked to 2,163 and the clock lock itself (0x4) below. Consequence per tier: a 5090 owner on class v4 locked at 1,200 to 1,300 MHz draws 305 to 313 W instead of 474 for 1.5 to 2.2 percent less rate (MH per watt up 49 to 52 percent).

+

RTX 5080 (the same shape; 8 October 2026, 00:45 to 01:54 UTC):

+
lockv4 MH/sv4 Wv4 MH/Wv3 MH/sv3 Wv3 MH/Wsm MHz
unlocked71.41253.10.28271.28169.70.4202,963 / 2,977
285071.41229.50.31171.29155.30.4592,842
278171.41223.90.31971.29150.10.4752,767
270071.41209.60.34171.29149.20.4782,692
255071.41193.00.37071.29134.40.5312,542
247271.41182.20.39271.28133.70.5332,460
240071.41176.00.40671.29128.70.5542,392
225071.41165.60.43171.28117.30.6082,242
216371.39161.50.44271.28114.10.6252,147 / 2,152
210071.38157.70.45371.28112.30.6352,085
195071.37154.00.46371.26111.60.6391,942
185471.37153.00.46771.25111.40.6401,845
180071.35150.30.47571.24113.40.6281,777 / 1,785
165071.33151.40.47171.22109.70.6491,635 / 1,642
150071.30149.20.47871.19110.60.6441,492
140071.27149.60.47671.16107.00.6651,387
130071.24147.90.48271.14107.70.6611,275 / 1,282
120071.20149.40.47771.12104.40.6811,192
110071.20146.60.48671.11105.60.6731,087
100071.19149.00.47871.11103.70.686990
90067.66137.80.491not taken (the budget)893
+

Reading (5080): the rate holds within 0.3 percent of unlocked down to 1,000 MHz on both classes and falls 5.2 percent at 900 MHz on class v4, so the knee is between 1,000 and 900 MHz, a third of the 2,963 MHz boost and lower than the 5090's (the 5080's 84 SMs have more compute headroom per unit of its memory bandwidth, so the memory wait hides the shadow down to a lower clock). Best MH per watt within the 1 percent rate tolerance: class v4 at 1,100 MHz (71.20 MH/s at 146.6 W, 0.486 MH/W; 106.5 W recovered for 0.29 percent rate), class v3 at 1,000 MHz (71.11 at 103.7 W, 0.686; 66.0 W for 0.25 percent). The class v4 premium is 83.4 W unlocked and 41 W at the best points (146.6 W against 105.6 W at 1,100). The draw floors from 1,500 MHz down (about 147 W on v4, 104 W on v3) with the power governor as the only throttle reason say the clock lever is spent by 1,500 MHz on this card. Consequence per tier: a 5080 owner on class v4 locked near 1,100 MHz pays 147 W instead of 253 for 0.3 percent less rate (MH per watt up 72 percent).

+

What the lever is and is not: nvidia-smi exposes no voltage offset (that is NVAPI's); the clock lock walks the driver's V/F curve, which is where the watts come from; the Apple M5 Max has no lever (no clock cap on Apple silicon); AMD has the ADLX tune line through igneum-gpu-telemetry or nothing. The knob goes into Ember Tune for 0.3.24 (src/ember.rs: the clock ladder continues below 45 percent of the maximum in 100 MHz steps to a 20 percent floor, the search stops at the first row more than the tolerance under the cap point's rate or on a faulted row, the best MH per watt within tolerance is the point, the fingerprint checked on every step, the result stored per card as the lock_* fields). The 5080 stock rows against the rented 5080 of 7 October (71.16 MH/s at 145 W on driver 580): the rate agrees to 0.4 percent, the watts do not (253 W here); the three-card Windows rig's three power fields agree, so the difference sits with the rented card's sampler or its cap, the fleet lane's re-measure owed.

+
+

8 October 2026, the hot-table packs on the RTX 5090: ld.global.cs against the plain load, unlocked and at the 1,300 MHz lock (branch ca3-v4-amend, the hash lane, job run-ca4-pc1-hot-ldcs-5090-20261008b)

+

the three-card Windows rig (RTX 5090, RTX 4070, RX 9070 XT), RTX 5090 (170 SMs, driver 13.4, NVRTC 12.8), igneum-worker-cuda 1.0 (4 October 2026). The eight 5 October hot-table packs (proto-cuda/packs-ca2-hot) and the mx8-genesis control (proto-cuda/packs-ca2-mixer), each run twice per state: variant base (plain loads) and variant ldcs (ld.global.cs on the hot-table loads). 16,777,216 nonces per run, nvidia-smi 1 Hz sampler (power.draw; instant and average agreed within 0.5 W on every row), 19 to 30 samples per row. Every one of the 36 rows PASS on its pinned fingerprint. Unlocked 06:34 to 06:45 UTC at 2,865 MHz; lock1300 06:45 to 06:56 UTC at 1,290 MHz through the helper; clocks reset (rgc) at the end. The 5090 was switched off in the app by the runner for the job and back on after.

+
packunlocked base MH/sunlocked base Wunlocked ldcs MH/slock1300 base MH/slock1300 base Wlock1300 base MH/Wlock1300 ldcs MH/slock1300 ldcs MH/W
mx8-genesis (control)137.7312.0137.7127.3211.40.602127.30.603
hot32k4147.4318.9147.4144.4215.30.671144.40.672
hot32k4a118.9320.1118.9116.6214.90.542116.60.542
hot64k2137.7318.1137.7135.0214.30.630135.00.630
hot64k4141.0318.8141.0138.2214.40.645138.20.644
hot64k4a115.7319.6115.8113.4214.80.528113.40.528
hot64k8164.2326.4164.2160.9219.20.734160.80.733
hot96k4139.0318.3139.0136.2214.50.635136.30.635
hot96k4a114.6318.8114.5112.3214.80.523112.30.523
+

What the rows say.

+
  1. The ldcs variant changes nothing: every pack reads the same rate and the same watts as its base run within 0.1 MH/s and 1 W, both states. The streaming hint on the hot-table loads is dead as a lever; the table's cache behaviour is already what the hardware gives. No further ldcs rows are owed.
  2. The hot packs hold their rate under the lock far better than mx8: the 1,300 MHz lock costs mx8 7.5 percent of rate (137.7 to 127.3) and costs the hot packs 2 percent (hot32k4 147.4 to 144.4, hot64k8 164.2 to 160.9). The hot packs are bound by the table's latency, not by the core; the lock takes a third of the watts off every pack (319 to 215 W) and the hot packs pay almost no rate for it.
  3. Per watt at the lock the hot family runs 0.52 to 0.73 MH/W against the control's 0.60: hot64k8 is 22 percent cheaper per hash than mx8 on this card, hot32k4 11 percent cheaper, the "a" packs 10 to 13 percent dearer. Whether a cheaper hash on the GPU is a gain or a loss for resistance is the research lane's call: it is a gain only if the saving comes from the memory path an ASIC would have to buy too.
  4. The 5090's locked class v4 reading from the efficiency pass (1,300 MHz: 134.6 MH/s at 223 W, 0.60 MH/W) sits level with the mx8 control here (0.602), so the two passes agree on the control and the hot rows are comparable to the v4 grid.

Generated from the repository at build time. Times are UTC. Machine names are model names.

@@ -989,7 +1016,7 @@ table{min-width:560px} The wallet GPU bench table The dev fee - Testnet faucet + Devnet faucet Add Igneum to MetaMask +
Network
@@ -177,6 +183,11 @@ main{padding-bottom:100px}.card{background:var(--row);border:1px solid var(--lin Igneum vs RandomXWhat was kept and what was rebuilt for GPUs. Built on the shouldersEvery borrowed part, credited.
+
Download @@ -274,7 +285,7 @@ main{padding-bottom:100px}.card{background:var(--row);border:1px solid var(--lin The wallet GPU bench table The dev fee - Testnet faucet + Devnet faucet Add Igneum to MetaMask
+ + + + + +
Download @@ -235,16 +246,16 @@

Ember for Linux

one tarball, the miner and the node inside

Unpack it, run igneum with your payout address. The same signed manifest as the desktop apps. NVIDIA through CUDA, AMD through OpenCL.

- Download the tarball v0.3.23 · 28.8 MB -
sha256 0d716ed92560b6b2eccb63ca1bd7a4d9eb64c9b334e8370e1263f0344cee294e
+ Download the tarball v0.3.22 · 28.8 MB +
sha256 8ad6dcef9edc57dcd33e8d5a97cbef5cdda0e384a6e56d3f62f8903029c4c764

Ember on HiveOS

for the rig people, the same tarball
-
Flight Sheet. Miner: Custom. Installation URL: https://dl.igneum.network/dl/public/igneum-hive-0.3.23.tar.gz. Miner name igneum, wallet and worker 0x<your 40-hex payout address>.%WORKER_NAME%. Hive itself is untested on our side: tell us what breaks.
- Download the tarball v0.3.23 · 28.8 MB +
Flight Sheet. Miner: Custom. Installation URL: https://dl.igneum.network/dl/public/igneum-hive-0.3.22.tar.gz. Miner name igneum, wallet and worker 0x<your 40-hex payout address>.%WORKER_NAME%. Hive itself is untested on our side: tell us what breaks.
+ Download the tarball v0.3.22 · 28.8 MB
@@ -308,7 +319,7 @@ The wallet GPU bench table The dev fee - Testnet faucet + Devnet faucet Add Igneum to MetaMask +
Network
@@ -166,6 +172,11 @@ td.mono{font-family:var(--f-mono);font-size:12.5px;min-width:180px}td.iv{color:v Igneum vs RandomXWhat was kept and what was rebuilt for GPUs. Built on the shouldersEvery borrowed part, credited.
+
Download @@ -303,7 +314,7 @@ td.mono{font-family:var(--f-mono);font-size:12.5px;min-width:180px}td.iv{color:v The wallet GPU bench table The dev fee - Testnet faucet + Devnet faucet Add Igneum to MetaMask
+
Network
@@ -176,6 +182,11 @@ main{padding-bottom:100px}.card{background:var(--row);border:1px solid var(--lin Igneum vs RandomXWhat was kept and what was rebuilt for GPUs. Built on the shouldersEvery borrowed part, credited.
+
Download @@ -295,7 +306,7 @@ main{padding-bottom:100px}.card{background:var(--row);border:1px solid var(--lin The wallet GPU bench table The dev fee - Testnet faucet + Devnet faucet Add Igneum to MetaMask
Network
@@ -158,6 +164,11 @@ dt{color:var(--ash)}dd{margin:0;font-family:var(--f-mono);font-size:14px;overflo Igneum vs RandomXWhat was kept and what was rebuilt for GPUs. Built on the shouldersEvery borrowed part, credited.
+
Download @@ -213,16 +224,16 @@ dt{color:var(--ash)}dd{margin:0;font-family:var(--f-mono);font-size:14px;overflo
- -
Testnet faucet
-

10 IGN a day, for testing.

-

Paste an address. The faucet sends 10 testnet IGN and shows you the transaction hash. Once per address per day, once per connection per day. No account, no sign-in, nothing to install.

-

Public testnet: not yet open. The faucet is prepared on the devnet and answers "not open yet" until the testnet starts.

+ +
Devnet 3 faucet
+

10 IGN a day, for building.

+

Paste an address. The faucet sends 10 IGN of Devnet 3 coin and shows you the transaction hash. Once per address per day, once per connection per day. No account, no sign-in, nothing to install.

+

Devnet 3, chain id 4463. The coins have no value and the chain resets without notice.

Ask for 10 IGN

-

An Ethereum-style address: 0x and 40 hex characters. The one the miner made for you, or any wallet you have pointed at the Igneum testnet.

+

An Ethereum-style address: 0x and 40 hex characters. The one cast wallet new printed, the one the miner made for you, or any wallet you have pointed at Devnet 3.

@@ -233,15 +244,17 @@ dt{color:var(--ash)}dd{margin:0;font-family:var(--f-mono);font-size:14px;overflo
@@ -268,7 +281,7 @@ dt{color:var(--ash)}dd{margin:0;font-family:var(--f-mono);font-size:14px;overflo The wallet GPU bench table The dev fee - Testnet faucet + Devnet faucet Add Igneum to MetaMask
+ + + + + +
Download @@ -212,7 +223,7 @@
-
Six phases · updated 7 Oct 2026
+
Six phases · updated 8 Oct 2026

The journey.

Six phases from the specification to a fair launch. Each closes at its gate, a measurement published whether it passes or fails, so there is no date to slip. Below the phases, the latest entries of the engineering log.

@@ -226,7 +237,8 @@
The log, newest first

Lately, in the log.

-
log

The first 16 GB card

The first 16 GB card: an RTX 5060 Ti in a Thunderbolt enclosure on the RTX 5090 Windows rig

+
log

The hot-table packs on the RTX 5090

The hot-table packs on the RTX 5090: ld.global.cs against the plain load, unlocked and at the 1,300 MHz lock

+
log

The first 16 GB card

The first 16 GB card: an RTX 5060 Ti in a Thunderbolt enclosure on the RTX 5090 Windows rig

log

Counter ASIC 3.0

Counter ASIC 3.0: the class v4 rehearsal

log

16:01Z: Ember run 6 on the three-card Windows rig

log

Counter ASIC 3.0 item 2

Counter ASIC 3.0 item 2: the per-day derivation

@@ -259,13 +271,12 @@
log

Live devnet: real transactions, the first non-empty shard proven and…

Live devnet: real transactions, the first non-empty shard proven and paid, and the exporter's block structure fixed

log

The program id split

The program id split: why the Apple M5 Max rejected the RTX 5090 Windows rig's proofs, and the verifier at 114 s

log

Live devnet: the first shards proven, verified and paid

+
log

Devnet v4 live: generator v2, two-thirds floor, fresh chain

Devnet v4 cut-over: generator v2, 2/3 floor, three nodes and a seed on a fresh chain

log

One-click Windows workers

One-click Windows workers: what the Apple M5 Max could measure

log

First live hourly swap: no pause on Mac, NVIDIA or AMD

First hourly program swap on the live devnet: compile-ahead, no pause, two cards

log

Proving: devnet v4 shards on the Apple M5 Max CPU, loaded machine

log

First live finality lock: 77.4% of weight, 17 voters

First finality lock on the live devnet: checkpoint 242 at 77.4% of all weight, two hours after genesis

-
log

The gfx1036 worker fault and what the Apple M5 Max could and could…

The gfx1036 worker fault and what the Apple M5 Max could and could not reproduce

-
log

A node 60 s behind the clock is silently dead

-
log

First machine on the one-click app: a 5090 at 118 MH/s

First machine on the Igneum Miner app: the RTX 5090 Windows rig's RTX 5090 at 118 MH/s, via Setup.exe

+
log

The gfx1036 worker fault and what the Apple M5 Max could and could…

The gfx1036 worker fault and what the Apple M5 Max could and could not reproduce

Every entry is a dated heading of the engineering log, where the commands and the hardware are. The phases and their gates are the litepaper’s roadmap.

@@ -294,7 +305,7 @@ The wallet GPU bench table The dev fee - Testnet faucet + Devnet faucet Add Igneum to MetaMask
+ + +
Network
@@ -296,6 +302,11 @@ details.tablebar summary{display:flex;align-items:center} Igneum vs RandomXWhat was kept and what was rebuilt for GPUs. Built on the shouldersEvery borrowed part, credited.
+
Download @@ -510,7 +521,7 @@ details.tablebar summary{display:flex;align-items:center} The wallet GPU bench table The dev fee - Testnet faucet + Devnet faucet Add Igneum to MetaMask
+
Network
@@ -157,6 +163,11 @@ ol{margin:0 0 14px;padding-left:22px}li{margin-bottom:6px} Igneum vs RandomXWhat was kept and what was rebuilt for GPUs. Built on the shouldersEvery borrowed part, credited.
+
Download @@ -301,7 +312,7 @@ ol{margin:0 0 14px;padding-left:22px}li{margin-bottom:6px} The wallet GPU bench table The dev fee - Testnet faucet + Devnet faucet Add Igneum to MetaMask
+
Network
@@ -152,6 +158,11 @@ pre b{color:var(--molten-text);font-weight:500} Igneum vs RandomXWhat was kept and what was rebuilt for GPUs. Built on the shouldersEvery borrowed part, credited.
+
Download @@ -268,9 +279,9 @@ pre b{color:var(--molten-text);font-weight:500}
@@ -453,7 +464,7 @@ pre b{color:var(--molten-text);font-weight:500} The wallet GPU bench table The dev fee - Testnet faucet + Devnet faucet Add Igneum to MetaMask
+ +
Download @@ -331,7 +342,7 @@ table.bench2 tr.detail .d{display:block;margin:0 0 4px}table.bench2 tr.detail b{ The wallet GPU bench table The dev fee - Testnet faucet + Devnet faucet Add Igneum to MetaMask
+
Download diff --git a/site/provenance.html b/site/provenance.html index c303deb48..91ccb4a13 100644 --- a/site/provenance.html +++ b/site/provenance.html @@ -87,6 +87,7 @@ table{min-width:560px} +
@@ -127,6 +127,13 @@ table{min-width:560px} Built on the shouldersEvery borrowed part, credited. + + + + + + + +