Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
6a32003
v2: contracts (AgentJob, ValidatorBond, RefundArbiter, Registry, Sett…
predge-ai Oct 19, 2026
b1cb234
v2: deploy tooling (--set v2), fork e2e with attack sequences, per-ch…
predge-ai Oct 19, 2026
1cf9374
v2: re-check provider at scoring, explicit challenge deadline, ports …
predge-ai Oct 19, 2026
94a8f49
v2: fork records (Arc and Base full e2e with attack sequences, all ch…
predge-ai Oct 19, 2026
4b20167
v2 tests: realistic timestamps and bond accounting asserts (kills Gam…
predge-ai Oct 19, 2026
edd1c46
v2 tests: ruling record assertions (kills remaining Gambit survivors)
predge-ai Oct 19, 2026
372b7ea
v2 config: minimal bonds (bondBps = arbiterBondBps = 0), floors refre…
predge-ai Oct 20, 2026
820cac5
v2 config: new v2 deployer and operator addresses
predge-ai Oct 20, 2026
ff101c7
deploy: v2 key guard matches the v1 directory segment only, not .pred…
predge-ai Oct 20, 2026
849d1ff
deploy: v2 live on Arc (5042) and Base (8453), temporary owner EOA, S…
predge-ai Oct 20, 2026
c47ed42
docs: README deployments, v2 live on Arc and Base, v1 marked legacy
predge-ai Oct 20, 2026
78c6142
fix(v2): Olympix BugPoCer findings on c47ed42
predge-ai Oct 23, 2026
fd78551
test(v2): kill the surviving Olympix mutants
predge-ai Oct 23, 2026
c27b836
chore(v2): fork e2e and README for the Olympix fixes
predge-ai Oct 23, 2026
f38c4b6
feat(v2.1): recordScore refuses a verdict that contradicts on-chain e…
predge-ai Oct 23, 2026
013ad4c
Merge pull request #5 from predgeAI/fix/olympix-2026-10-23
predge-ai Oct 23, 2026
8756d95
deployments: v2.1 live on Arc and Base (Olympix fixes, false verdicts…
predge-ai Oct 23, 2026
65a0d9b
Merge pull request #6 from predgeAI/deploy/v2.1-arc-base
predge-ai Oct 23, 2026
cd4e2c6
deployments: v2.1 live on Arbitrum, Avalanche, BNB Chain, Celo, Optim…
predge-ai Oct 26, 2026
18f7548
feat(v2): Circle Refund Protocol adapter for PredgeRefundArbiterV2 (#8)
predge-ai Nov 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "foundry-lib/forge-std"]
path = foundry-lib/forge-std
url = https://github.com/foundry-rs/forge-std
67 changes: 65 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,34 @@ node deploy.mjs --chain base --fork # full deploy + smoke + end-to-end on a
CONFIRM_MAINNET=base node deploy.mjs --chain base --broadcast # the real deploy
```

## v2 (self-audit fixes, live on 9 chains)

`contracts/v2/` is the redesigned set that fixes the 20.10.2026 self-audit findings (H-01..H-03, M-01..M-05, L-01..L-07). v2.1 is deployed on Arc, Base, Arbitrum, Avalanche, BNB Chain, Celo, Optimism, Sonic and Unichain (see [Deployments](#deployments)). The v1 set stays deployed as legacy on its 16 chains and is untouched. Design, finding-by-finding mapping and owner actions: `SELF-AUDIT-2026-10-20/v2-design.md` (Downloads).

**v2.1 (23.10.2026, set `2026-10-23-v2.1`)** adds the fixes for the Olympix BugPoCer scan of `c47ed42` and the owner's option (a)
for the validator bond: `PredgeValidatorBondV2.recordScore` refuses any verdict that contradicts the on-chain evidence and reverts
`VerdictContradictsEvidence`. A pass (100) needs the provider to have submitted the output bytes (hashed on-chain by the job) with a
hash equal to the `expected` the client committed in the job's specHash; a fail (0) needs the opposite. A false verdict therefore
cannot be recorded at any bond size, including `bondBps = 0`. `challenge`, `wouldSlash`, `reclaim` and the dispute window are gone
(nothing is left to challenge); the bond is credited back to the staker with the verdict and only backs liveness: an operator that
stays silent on a submitted job forfeits it to the provider (`release`). `verdictFor(jobId, expected)` returns the only score
`recordScore` will accept. Constructor: `(owner, operator, job, minBond, bondBps)`; `setParams(minBond, bondBps)`.
The same deployer creates v2.1 at nonces 6..11 (`nonceBase` in `config/set.v2.json`), again the same addresses on Arc and Base.

```
FOUNDRY_CONFIG=foundry-v2.toml forge test # unit + fuzz + invariant + PoC tests (v1 attacks run against v1 and v2)
FOUNDRY_CONFIG=foundry-v2.toml forge coverage --no-match-coverage "(test/|foundry-lib|contracts/[A-Za-z]+\.sol)"
node --test test/ # plan, v1 bytecode match (network), v2 plan
node deploy.mjs --chain base --set v2 --fork # anvil fork of Base: deploy + honest path + every v1 attack sequence (must revert)
node scripts/fork-all.mjs --set v2 --no-e2e # gas / cost on every chain
node scripts/mutate-v2.mjs # mutation smoke run
node scripts/v2-params.mjs 5 # regenerate per-chain bond floors (about $5 each) from live prices
node scripts/v2-post-deploy.mjs --chains arc,base # after a v2.1 broadcast: archive v2.0 records (*.v2-0.json), verify leftovers, README
```

Real v2 runs (`--set v2` without `--fork`) refuse to start until the owner fills `config/v2.config.json` (new deployer address, the Safe, the ops key)
and, to broadcast, pass `--key-file <the NEW v2 deployer json>`. v2 never reads `~/.predge-deployer`. `--fork` uses throwaway accounts only.

## The set

Order is the deployer's nonce. Contract *i* is created at nonce *i*.
Expand Down Expand Up @@ -59,7 +87,7 @@ The tool enforces this:

**Never use the deployer key for anything else.** Not even a plain transfer out until every chain has the whole set.

Canonical addresses for deployer `0x5d016C1B4d7A42eB7c982da1Ae7fFdB958CFCAe9`:
Canonical v1 addresses for deployer `0x5d016C1B4d7A42eB7c982da1Ae7fFdB958CFCAe9`:

| Contract | Address (every chain) |
|---|---|
Expand All @@ -72,7 +100,42 @@ Canonical addresses for deployer `0x5d016C1B4d7A42eB7c982da1Ae7fFdB958CFCAe9`:

## Deployments

Live on **16 mainnets** since 18 Oct 2026, all six contracts at the canonical addresses above. Checked the same day with `eth_getCode` on every chain, and every contract is an exact match on [Sourcify](https://sourcify.dev). Full receipts (block, gas, constructor args, source hash) are in `deployments/<chain>.json`.
### v2 (current, v2.1): Arc (5042), Base (8453), Arbitrum (42161), Avalanche (43114), BNB Chain (56), Celo (42220), Optimism (10), Sonic (146), Unichain (130)

Deployed 2026-10-23 from set `2026-10-23-v2.1` (deployer nonces 6..11). The same six addresses on both chains. All six are verified on Sourcify. Receipts: [`deployments/v2/arc.json`](deployments/v2/arc.json), [`deployments/v2/base.json`](deployments/v2/base.json).

| Contract | Address (all nine chains) | Sourcify | Explorer |
|---|---|---|---|
| PredgeAgentValidatorV2 | `0xE4CFdE5dbD9afD518b9c59136939607505bF643D` | [Sourcify](https://sourcify.dev/#/lookup/0xE4CFdE5dbD9afD518b9c59136939607505bF643D) | [Arc](https://explorer.arc.io/address/0xE4CFdE5dbD9afD518b9c59136939607505bF643D), [Base](https://basescan.org/address/0xE4CFdE5dbD9afD518b9c59136939607505bF643D) |
| AgentJobV2 | `0x5C673c6aBF3c60C3F6Ce3f1509f5d99C08F8B13F` | [Sourcify](https://sourcify.dev/#/lookup/0x5C673c6aBF3c60C3F6Ce3f1509f5d99C08F8B13F) | [Arc](https://explorer.arc.io/address/0x5C673c6aBF3c60C3F6Ce3f1509f5d99C08F8B13F), [Base](https://basescan.org/address/0x5C673c6aBF3c60C3F6Ce3f1509f5d99C08F8B13F) |
| PredgeValidatorBondV2 | `0x97E960Fe3D7759af8f51A95319859E60Df554329` | [Sourcify](https://sourcify.dev/#/lookup/0x97E960Fe3D7759af8f51A95319859E60Df554329) | [Arc](https://explorer.arc.io/address/0x97E960Fe3D7759af8f51A95319859E60Df554329), [Base](https://basescan.org/address/0x97E960Fe3D7759af8f51A95319859E60Df554329) |
| PredgeSettlementV2 | `0x6fac55c5187A9cdb519F77163C7e3fD9E87F6f24` | [Sourcify](https://sourcify.dev/#/lookup/0x6fac55c5187A9cdb519F77163C7e3fD9E87F6f24) | [Arc](https://explorer.arc.io/address/0x6fac55c5187A9cdb519F77163C7e3fD9E87F6f24), [Base](https://basescan.org/address/0x6fac55c5187A9cdb519F77163C7e3fD9E87F6f24) |
| MockRefundProtocolV2 (demo refund protocol) | `0x50041FdAb10f8f56E24FA9CA806f082CcAeb05E9` | [Sourcify](https://sourcify.dev/#/lookup/0x50041FdAb10f8f56E24FA9CA806f082CcAeb05E9) | [Arc](https://explorer.arc.io/address/0x50041FdAb10f8f56E24FA9CA806f082CcAeb05E9), [Base](https://basescan.org/address/0x50041FdAb10f8f56E24FA9CA806f082CcAeb05E9) |
| PredgeRefundArbiterV2 | `0xf404F908cb1ba47C01AB8526B725aAA940557eA9` | [Sourcify](https://sourcify.dev/#/lookup/0xf404F908cb1ba47C01AB8526B725aAA940557eA9) | [Arc](https://explorer.arc.io/address/0xf404F908cb1ba47C01AB8526B725aAA940557eA9), [Base](https://basescan.org/address/0xf404F908cb1ba47C01AB8526B725aAA940557eA9) |

- **26 Oct 2026, seven more chains:** Arbitrum, Avalanche, BNB Chain, Celo, Optimism, Sonic and Unichain, the same six addresses as above. The v2 deployer was fresh on these chains, so `scripts/v2-burn-nonces.mjs` first sent six 0-value self-transactions (nonces 0..5) and the set then landed at nonces 6..11, exactly as on Arc and Base. 30 read-only smoke checks passed on every chain and all 42 contracts are exact matches on Sourcify. Receipts: [`deployments/v2/optimism.json`](deployments/v2/optimism.json), [`deployments/v2/unichain.json`](deployments/v2/unichain.json), [`deployments/v2/arbitrum.json`](deployments/v2/arbitrum.json), [`deployments/v2/bnb.json`](deployments/v2/bnb.json), [`deployments/v2/avalanche.json`](deployments/v2/avalanche.json), [`deployments/v2/sonic.json`](deployments/v2/sonic.json), [`deployments/v2/celo.json`](deployments/v2/celo.json).
- **Owner:** `0x9084f5000E07C7133D6dA5eE4f271AB6D1821144` (TEMPORARY owner = Amir EOA 0x9084…1144 by owner decision; transferOwnership→acceptOwnership to the Safe once created.). **Operator** (hot ops key, rotatable by the owner): `0x8ffFB76c4329DDC8ee4a859DDF55AB48ba25e292`.
- **Bond floor:** minimal (bps = 0), per-chain floor from `config/v2.chains.json`. Since v2.1 the validator bond backs liveness only: `recordScore` refuses any verdict that contradicts the on-chain evidence (pass needs the output bytes hashed on-chain and equal to the client-committed `expected`; fail needs the opposite), so a false verdict cannot be recorded at any bond size and there is no challenge window.
- **Status:** v2.1 contains the fixes for the Olympix BugPoCer scan of `c47ed42` (23 Oct 2026) and the option (a) verdict check. The signed verdict API (`api.predge.io`) is unchanged and remains the product.

### v2.0 (DEPRECATED, 20-21 Oct 2026): do not use

The first v2 set (`2026-10-20-v2`, commit `c47ed42`) is still on chain at the addresses below, because the contracts are not upgradeable. It is affected by the confirmed Olympix findings and held no funds when it was superseded. Nothing in Predge points at it. Records: [`deployments/v2/arc.v2-0.json`](deployments/v2/arc.v2-0.json), [`deployments/v2/base.v2-0.json`](deployments/v2/base.v2-0.json).

| Contract | Deprecated address (Arc and Base) |
|---|---|
| PredgeAgentValidatorV2 | ~~`0xACC29BF9Ae35e51d36792C3B20D0Ac65f86Dd495`~~ |
| AgentJobV2 | ~~`0x550b360840C6614aD70E91CC6673FF3AB2dFd98C`~~ |
| PredgeValidatorBondV2 | ~~`0xa4BB68E55398c4EDD958DD6B32460D2018F78881`~~ |
| PredgeSettlementV2 | ~~`0x60991fE6a7afDcC79bDc43b754acE5c16158edb3`~~ |
| MockRefundProtocolV2 | ~~`0x2768dBC17728403BAE6D8838e4AeC63bE21a9dA7`~~ |
| PredgeRefundArbiterV2 | ~~`0x72FAEb8eE70816Dbf66722657cc4Bea314EB5FE3`~~ |

### v1 (legacy): 16 mainnets

**Legacy, known issues, holds no funds.** Kept deployed as a reference only. The v2 set above fixes the self-review findings in it.

The v1 set went live on **16 mainnets** on 18 Oct 2026, all six contracts at the canonical addresses above. Checked the same day with `eth_getCode` on every chain, and every contract is an exact match on [Sourcify](https://sourcify.dev). Full receipts (block, gas, constructor args, source hash) are in `deployments/<chain>.json`.

Creation transactions, in nonce order:

Expand Down
20 changes: 20 additions & 0 deletions config/set.v2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"_comment": "The Predge v2 set (contracts/v2). Same rules as set.json: contract i is created at deployer nonce nonceBase + i, so a fresh v2 deployer + this order gives the same addresses on every chain. Never reorder; append only. $owner is the Safe, $operator the hot ops key, $deployer holds NO role (it only pays gas). Chain-specific values (minBond, arbiterMinBond) come from config/v2.chains.json and do not change the addresses. Owner decision 20.10.2026: bonds MINIMAL, bondBps = arbiterBondBps = 0, so every bond is exactly the per-chain floor (~$5); the bps mechanism stays in the contracts and the Safe can raise it later with setParams (a paid bonded-verdict tier). 23.10.2026 (v2.1, Olympix fixes + option a): the v2 deployer already used nonces 0..5 on Arc and Base for the 20.10 set, so v2.1 is created at nonces 6..11 (nonceBase 6), again the same addresses on both chains; the validator bond has no dispute window any more (recordScore refuses a false verdict, so nothing is left to challenge).",
"version": "2026-10-23-v2.1",
"nonceBase": 6,
"params": {
"challengeWindow": "86400",
"evaluationWindow": "259200",
"bondBps": "0",
"arbiterBondBps": "0",
"identityRegistry": "0x0000000000000000000000000000000000000000"
},
"plan": [
{ "name": "PredgeAgentValidatorV2", "file": "v2/PredgeAgentValidatorV2", "args": ["$owner", "$operator", "$identityRegistry"], "role": "ERC-8004 validation registry, request-before-response, domain-separated request hashes" },
{ "name": "AgentJobV2", "file": "v2/AgentJobV2", "args": ["$evaluationWindow"], "role": "ERC-8183-style job escrow with expiry and pull payments; no owner" },
{ "name": "PredgeValidatorBondV2", "file": "v2/PredgeValidatorBondV2", "args": ["$owner", "$operator", "$AgentJobV2", "$minBond", "$bondBps"], "role": "commit-reveal validator bond; the evaluator of AgentJobV2; records only verdicts that match the on-chain evidence" },
{ "name": "PredgeSettlementV2", "file": "v2/PredgeSettlementV2", "args": ["$owner"], "role": "pay-per-route receipts in the native token" },
{ "name": "MockRefundProtocolV2", "file": "v2/mocks/MockRefundProtocolV2", "args": ["$PredgeRefundArbiterV2"], "role": "DEMO ONLY stand-in Refund Protocol (moves no money); out of audit scope" },
{ "name": "PredgeRefundArbiterV2", "file": "v2/PredgeRefundArbiterV2", "args": ["$owner", "$operator", "$MockRefundProtocolV2", "$challengeWindow", "$arbiterMinBond", "$arbiterBondBps"], "role": "optimistic bonded refund arbiter; Safe is the court of last resort" }
]
}
127 changes: 127 additions & 0 deletions config/v2.chains.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
{
"_comment": "Generated by scripts/v2-params.mjs. minBond / arbiterMinBond in native wei (18 decimals), ~usdTarget dollars each, 1 significant digit.",
"generatedAt": "2026-10-20T13:28:11.380Z",
"usdTarget": 5,
"chains": {
"ethereum": {
"native": "ETH",
"priceUsd": 2392.23,
"minBond": "2000000000000000",
"arbiterMinBond": "2000000000000000"
},
"base": {
"native": "ETH",
"priceUsd": 2392.23,
"minBond": "2000000000000000",
"arbiterMinBond": "2000000000000000"
},
"bnb": {
"native": "BNB",
"priceUsd": 730.57,
"minBond": "6000000000000000",
"arbiterMinBond": "6000000000000000"
},
"celo": {
"native": "CELO",
"priceUsd": 0.081817,
"minBond": "60000000000000000000",
"arbiterMinBond": "60000000000000000000"
},
"polygon": {
"native": "POL",
"priceUsd": 0.092243,
"minBond": "50000000000000000000",
"arbiterMinBond": "50000000000000000000"
},
"optimism": {
"native": "ETH",
"priceUsd": 2392.23,
"minBond": "2000000000000000",
"arbiterMinBond": "2000000000000000"
},
"arbitrum": {
"native": "ETH",
"priceUsd": 2392.23,
"minBond": "2000000000000000",
"arbiterMinBond": "2000000000000000"
},
"avalanche": {
"native": "AVAX",
"priceUsd": 9.46,
"minBond": "500000000000000000",
"arbiterMinBond": "500000000000000000"
},
"gnosis": {
"native": "xDAI",
"priceUsd": 1,
"minBond": "5000000000000000000",
"arbiterMinBond": "5000000000000000000"
},
"linea": {
"native": "ETH",
"priceUsd": 2392.23,
"minBond": "2000000000000000",
"arbiterMinBond": "2000000000000000"
},
"scroll": {
"native": "ETH",
"priceUsd": 2392.23,
"minBond": "2000000000000000",
"arbiterMinBond": "2000000000000000"
},
"unichain": {
"native": "ETH",
"priceUsd": 2392.23,
"minBond": "2000000000000000",
"arbiterMinBond": "2000000000000000"
},
"sonic": {
"native": "S",
"priceUsd": 0.056326,
"minBond": "80000000000000000000",
"arbiterMinBond": "80000000000000000000"
},
"mantle": {
"native": "MNT",
"priceUsd": 0.527794,
"minBond": "9000000000000000000",
"arbiterMinBond": "9000000000000000000"
},
"arc": {
"native": "USDC",
"priceUsd": 0.999869,
"minBond": "5000000000000000000",
"arbiterMinBond": "5000000000000000000"
},
"monad": {
"native": "MON",
"priceUsd": 0.02064362,
"minBond": "200000000000000000000",
"arbiterMinBond": "200000000000000000000"
},
"robinhood": {
"native": "ETH",
"priceUsd": 2392.23,
"minBond": "2000000000000000",
"arbiterMinBond": "2000000000000000"
},
"rootstock": {
"native": "RBTC",
"priceUsd": 81371,
"minBond": "60000000000000",
"arbiterMinBond": "60000000000000"
},
"plasma": {
"native": "XPL",
"priceUsd": 0.075746,
"minBond": "60000000000000000000",
"arbiterMinBond": "60000000000000000000"
},
"stable": {
"native": "USDT0",
"priceUsd": 0.99895,
"minBond": "5000000000000000000",
"arbiterMinBond": "5000000000000000000"
}
}
}
7 changes: 7 additions & 0 deletions config/v2.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"_comment": "Owner-supplied v2 identities. 20.10.2026: deployer and operator generated locally (keys in ~/.predge-deployer-v2/, 0600, never committed), nonce 0 on Arc and Base at creation; owner stays null until the Safe exists. `deployer` = a NEW key created by the owner (pays gas only, holds no role, nonce 0 on every chain). `owner` = the Safe multisig (on EVERY chain you deploy to, at the same address if you want the same wiring everywhere). `operator` = the hot ops key (rotatable by the owner). dry-run and --broadcast refuse to run while any of these is null. --fork ignores this file and uses throwaway anvil accounts.",
"deployer": "0xbB7A0c24677339045434273FFC2266B99E1e1301",
"owner": "0x9084f5000e07c7133d6da5ee4f271ab6d1821144",
"operator": "0x8ffFB76c4329DDC8ee4a859DDF55AB48ba25e292",
"_owner_note": "20.10.2026: TEMPORARY owner = Amir EOA 0x9084\u20261144 by owner decision; transferOwnership\u2192acceptOwnership to the Safe once created."
}
Loading
Loading