# Audit report

> Independently assess the focused 0.12.2 changes against baseline 2b8752232847ce53c6a4a89304ae24a775ffb102 in this repository. That baseline preserves the core implementation reviewed in completed job 2bb2335f-3d83-4171-b1cd-b7153912baaf. Read its accepted judge findings at https://api.imdnotdead.fun/jobs/2bb2335f-3d83-4171-b1cd-b7153912baaf/submissions and report.md. Its two medium findings blocked public B and proposed C. Reuse unchanged assessments; review this delta, its regressions and remaining material findings. Report defects honestly.
>
> Scope B: public sponsored IMD reviews, bounded Sepolia noncustodial readonly module registration, genuine Claus decisions, execution and contract request/callback handling. Scope C: preparation only of the same noncustodial Ethereum mainnet profile, separately requiring exact human approval. No token launches, user custody or public inference. No Solidity or financial ceilings changed. Public B is closed; this request is not activation authority.
>
> Changes: relayed registration pending state omits an absent transaction hash; worker commit promises are awaited inside error handlers; recovery starts no later than the module deployment receipt and pins finalized reads to the same scanned head. A matching direct relay must contain the exact signed registerProject calldata, not merely a matching event. Finalized conflicting registrations and complete empty recovery for an already registered project block explicitly. A missing registration expires only after finalized chain time passes its deadline. Latest-only matching relays remain recoverable after local signature expiry. A finalized recovery hash conflict blocks the affected job while retaining journals. Existing cancellation, reservations, nonce/hash and signatures remain intact. Automatic recovery of nested contract relays is explicitly unsupported.
>
> Dispatch without an existing transaction distinguishes subsidy expiry from actual request expiry. An external callback racing a locally cancelled callback waits for finalized delivery. Existing signed transactions remain bound to the original subsidy; no allocation or transaction is silently replaced. Verify those exact limits, including any cancelled-transaction edge case.
>
> Tests: registration-recovery exercises expiry/finality/mismatched signed inputs and persisted job states; chain-recovery-guards preserves the original cursor on hash conflict; contract-delivery covers withdrawal, external callback finality, subsidy expiry and restart. Real local EVM integration runs chain IDs 11155111 and 1 with CLAUS_TEST_RELAY=1. Sepolia checks the persisted wait before finality. Source gate: 411 pass, 10 optional skips; both separate real local chain runs pass. These are operator tests, not independent acceptance.
>
> Retained, documented limits from findings 4,7,8,9: an admitted requester can delay peers behind the shared 32-event scan page, no fair-scheduling guarantee; 16 lifetime registration intents include expired/blocked records; proposed Mainnet bootstrap cost 0.0046 ETH and max 30M gas permit fees only up to cap/paddedGas (about 0.189 gwei at a 19.5M estimate); proposed Sepolia dispatch cap is 0.00005 test ETH per transaction and per project, shared by resolution and callback (1M-gas callbacks require fees below about 0.04 gwei before other costs). Callbacks are best effort and the canonical result remains readable. Assess whether any of these is blocking for this bounded launch. State B and C conclusions separately. Acceptance never implies Mainnet authorization or universal safety.

| | |
|---|---|
| Repository | https://github.com/contractclaus/claus-network |
| Commit | `1f6f6d7ebb30dab9e14edda5a34da20f74e565e8` |
| Job | `d80d33c1-afdd-48fc-96bc-88570c9d7a48` |
| Judged | 2026-10-12 16:45 UTC |
| Findings | 2 medium · 5 low · 1 info |

Four agents audited the code as it is at `1f6f6d7`, each in one area (math, permissions, economics, control flow),
and a judge reproduced, merged and ranked what they found, then read the code once more itself. Nothing in the repository was changed or deployed.

## Findings

### 1. Medium: Byte-exact relay calldata check lets any mempool observer permanently block a project's registration by mining the same signed intent with padded calldata

`adapters/chain-executor.mjs:92`

```
    requireThat(transaction.to?.toLowerCase() === value.executor.toLowerCase() &&
      transaction.input.toLowerCase() === data.toLowerCase(), 'registered_project_mismatch');
```

Merged from audit_permissions (reproduced). 0.12.2 adopts a relayed registration only when the mined transaction's input is byte-identical to the executor's own encodeFunctionData output (lines 92-93). ClausApprovalExecutor.registerProject is permissionless and Solidity's ABI decoder accepts trailing calldata, so the exact signed Registration/configuration/signature can be mined by anyone with extra bytes appended (verified with Foundry: address(executor).call(bytes.concat(exact, new bytes(32))) succeeds, projects(id).target is set, registrationNonces[owner][nonce] is consumed, and the exact call then reverts). The signed calldata is public the moment Claus broadcasts register:<id>, so a front-runner needs no secret, only a higher tip. Afterwards: Claus's own transaction reverts, deployment.step falls into relayedRegistration, finds the single ProjectRegistered log with matching owner/mandateHash, compares getTransaction().input with data, and throws registered_project_mismatch; src/chain-deployments/index.mjs:181 now maps that code to a terminal 'blocked' job. Because projectKey = keccak(owner, keccak(projectId)) is fixed per owner and project (adapters/chain-read.mjs:94-95) and projects[projectKey].target is now non-zero, every later intent for that project also ends in registered_project_mismatch (complete empty recovery with a registered project), each one consuming one of the 16 lifetime intents. The registration onchain is exactly what the owner signed, so no authority was bypassed; the defect is that a valid relay is treated as a conflicting one and the outcome is irreversible without operator database intervention (docs/operations.md: journals are never cleared). Under baseline 2b87522 the event-only match (owner + mandateHash) would have adopted this relay, so this is a regression of the delta. The documented 'nested contract relays unsupported' limit does not cover it: the attacking transaction is a direct call to the executor (transaction.to matches). Applies identically to chain ids 11155111 (B) and 1 (C); on C the public pool also pays Claus's reverted transaction. Fix: decode the relayed transaction's input with decodeFunctionData and compare the decoded (message, configuration, signature) with the signed values instead of comparing bytes, or identify the intent by its onchain effect (registrationNonces[owner][nonce] consumed plus the finalized projects() tuple and the target's executor()/projectId()), which only the owner's exact signature can produce. Keep 'nested relay' as the only unsupported case.

**Reproduction**

Onchain: contracts/test/scratch/PaddedRelay.t.sol (forge test --match-path test/scratch/PaddedRelay.t.sol, passes): exact = abi.encodeCall(registerProject,(r,'cfg',sig)); padded = exact || 32 zero bytes; executor.call(padded) returns true and registers owner/target/mandate; executor.call(exact) then returns false (target already set). Offchain: test/scratch/registration-edges.test.mjs test A (fixture copied from test/registration-recovery.test.mjs: module receipt block 80, intent observedBlock 100, ProjectRegistered log at block 101, finalized head 110) with client.getTransaction returning { to: executor, input: data + '00'.repeat(32) }. decodeFunctionData on both inputs yields identical arguments. Expected: deployment.step returns 'verified' with a binding to the registered target. Actual: step rejects with code registered_project_mismatch, which runOne finishes as state 'blocked' (see the existing test 'a different registered project permanently blocks its original queued registration'). Any new intent for the same project then fails the same way because projects(projectKey).target is non-zero.

### 2. Medium: registered_project_mismatch is terminal for every throw site, so one stale latest-state read after the executor's own confirmed registration permanently blocks the project

`src/chain-deployments/index.mjs:181`

```
        if (['registered_project_mismatch', 'registration_receipt_mismatch', 'recovery_block_changed', 'recovery_event_mismatch'].includes(error.code) ||
```

From audit_flow (reproduced). The 0.12.2 terminal mapping is keyed on the error code, not on whether the evidence was finalized. The same code is thrown by adapters/chain-executor.mjs:140-146 after the executor's OWN register transaction is already confirmed (3 confirmations on Sepolia, finalized on chain 1): read(value.executor,'projects',...) at the default 'latest' tag must already show the registered owner/mandateHash/policyVersion/minDelaySeconds and the target must answer executor()/projectId(). Those reads are not pinned to the receipt's block. The Sepolia executor worker uses a single load-balanced public endpoint with retryCount 0 (runtime/chain-worker.mjs:28), where eth_call can be served by a backend lagging the one that answered eth_getTransactionReceipt; a Mainnet endpoint configured by URL has the same property. One such read returns the pre-registration struct (zero owner) and requireThat throws registered_project_mismatch. On baseline 2b87522 that was a 60 s 'waiting' retry that self-healed on the next tick; on 1f6f6d7 runOne finishes the job 'blocked', writes chain-registrations state 'blocked', and the job is never claimed again. The project is then registered onchain by this exact transaction (gas paid from the public pool) with no chain-bindings row; every later prepareRegistration/submitRegistration creates a new intent whose relayedRegistration finds the first ProjectRegistered event and fails the exact-calldata check at chain-executor.mjs:92-93 (different nonce/expiresAt), blocking again. No authorization is bypassed and no extra funds are spent beyond the one registration; the defect is an irreversible wrong terminal state on a transient read. Fix: throw a distinct, non-terminal code from the post-confirmation state checks at chain-executor.mjs:140-146 (or pin those reads to the confirmed receipt's blockNumber so they cannot lag), and keep the terminal mapping only for the finalized-evidence throw sites (lines 81, 87-88, 92-93).

**Reproduction**

test/scratch/registration-edges.test.mjs test B (node --test test/scratch/registration-edges.test.mjs). State: chain-transactions 'register:registration' state 'confirmed', sender = executor signer, receipt.blockNumber '105'; network-capabilities 'chain' enabled for the executor; chain-modules 'module' receipt block '80'; chain-registrations 'registration' queued with a project-register job enqueued; review pilot enabled. Mock client: getChainId 11155111, getBlockNumber 110n, readContract('projects') returns [0x0..,0x0..,0x0..,0x0..,0n] on the first tick and the registered struct afterwards. Expected (baseline behaviour): runOne returns state 'waiting' with retryAt now+60000 and the next tick returns 'verified'. Actual on 1f6f6d7: runOne returns state 'blocked', result.error 'registered_project_mismatch', chain-registrations 'registration' state 'blocked'; a direct deployment.step after the read settles returns 'verified' (proving the block was premature); the next runOne is idle and chain-bindings stays empty.

### 3. Low: A signed-but-unsent delivery transaction cancelled because its subsidy expired or dispatch was paused is reported as terminal 'closed' while the Inbox request is still open

`adapters/contract-delivery.mjs:200`

```
        return answer(transaction.state === 'cancelled' ? 'closed' : 'failed');
```

Merged from audit_math, audit_economics and audit_permissions (three reports of the same mechanism, reproduced). This is the cancelled-transaction edge case the task asked to verify. 0.12.2 distinguishes subsidy expiry from request expiry only on the path with no existing transaction (lines 187-188). When a resolve row already exists in state 'prepared' (signed, nonce reserved, never broadcast) its intent.expiresAt = min(resolution.expiresAt, budget.expiresAt) (line 154). On the next step transactions.submit() in adapters/sepolia-transactions.mjs:99-107 cancels the row whenever intent.expiresAt <= now() (so the budget deadline alone triggers it) or whenever authorize() returns false, which includes allowSubmit = false (the worker passes active, false during any admission pause) and active() false for an expired/revoked budget. The cancelled result reaches line 200 and the step answers 'closed'. runtime/contract-intake-worker.mjs:29 skips every request whose delivery.state is 'closed' forever, and createContractDelivery (lines 53-62) rebinds the request to the original budget through the cancelled resolve row, so no later allocation can deliver it either. The Inbox request is still Pending and resolvable until its own deadline, so docs/contract-intake.md ('an expired subsidy alone does not close an otherwise valid onchain request') and the reversible-pause description in docs/mainnet.md are both violated for this state. The same mapping answers 'closed' for a prepared callback cancelled by budget expiry although its resolve is finalized onchain. The window is real but narrow: a 'prepared' row outlives a tick only when submit() fails after prepare() committed (its first call is client.getChainId(); an RPC failure there propagates out of the worker tick with no retryAt) or when the process dies between the two commits; the next successful tick must then fall after budget.expiresAt or inside a pause. Not cancelling the signed transaction is correct (it must stay bound to its allocation); the defect is the terminal answer. No funds are spent and no duplicate transaction is created. Fix: in the cancelled branch for action 'resolve', answer 'closed' only if chain.deliveryState(request).closed or resolution.expiresAt <= now(), otherwise answer 'pending' (or a distinct non-terminal 'unfunded'/'paused' state with a retryAt); for a cancelled callback whose resolve is finalized, answer 'pending'. Note that the no-replacement rule means the cancelled row can never be re-prepared under the same id, so a later allocation would need an explicit decision on whether it may prepare a new transaction under a new id.

**Reproduction**

test/scratch/delivery-edges.test.mjs tests D and D2 (node --test test/scratch/delivery-edges.test.mjs; fixture copied from test/contract-delivery.test.mjs with the request deadline two days after budget.expiresAt). D: client.getChainId throws once on the call that follows a successful prepare(); step() rejects 'rpc down'; chain-transactions holds one row in state 'prepared', 0 broadcasts, reserved 135000000000. Advance now to budget.expiresAt + 1 (deadline still open, deliveryState.pending true), step(): row becomes 'cancelled', reservation released, outcome.state is 'closed'. Expected: 'pending'. D2: same prepared row, step({allowSubmit:false}) with a valid budget: outcome 'closed'. Expected: 'pending'. The existing test 'an expired subsidy leaves an open request pending' covers only the no-transaction path.

### 4. Low: A callback whose resolve ran under a now-lapsed subsidy stays 'pending' forever: no terminal state, no retryAt, re-stepped on every worker tick, and a later allocation is never consulted

`adapters/contract-delivery.mjs:188`

```
        if (expiresAt <= now()) return answer('pending');
```

Merged from audit_math, audit_economics, audit_permissions and audit_flow (four reports of one mechanism, reproduced). For action 'callback' expiresAt is budget.expiresAt alone (line 154); ClausRequestInbox.deliverCallback has no deadline and the resolve-only closure checks (lines 177, 187) do not apply. Once the resolve is finalized and the original budget expires before a callback transaction exists (callback preparation deferred by transaction_cost_limit, resolve finality outlasting the budget, or simply a short remaining window), line 188 answers 'pending' on every step. runtime/contract-intake-worker.mjs:60-67 persists {state:'pending', resultHash, observedAt} without retryAt for a returned outcome (retryAt is set only in the catch path), so the request is re-stepped every tick with chain.profile, chain.resolution and two deliveryState reads, for the life of the store. A newly admitted allocation never changes this: createContractDelivery (lines 53-62) rebinds the request to the budget recorded on its resolve row, and revoking the old budget does not help because line 188 precedes the active() check. 0.12.1 answered 'closed' here (the prior accepted finding 3); 0.12.2 replaced a wrong terminal state with an unbounded non-terminal one. The documented rule (existing signed transactions keep their allocation; nothing is replaced) is preserved, the canonical result stays readable onchain, and nothing is spent, so this is a liveness, state-accuracy and polling-cost defect bounded by the number of resolved requests with callbackGas > 0. Fix: either let a callback that has no transaction yet use the currently selected active allocation (only the resolve row is an existing signed transaction), or answer a distinct terminal state (e.g. 'failed' with reason 'callback_subsidy_expired') when the bound allocation is expired or revoked and no callback transaction exists, and in either case persist a retryAt for pending answers that cannot progress.

**Reproduction**

test/scratch/delivery-edges.test.mjs test E (node --test test/scratch/delivery-edges.test.mjs; fixture from test/contract-delivery.test.mjs with callback = true, observation.chainId set so the worker processes the row). step() broadcasts the resolve under 'fixture-budget'; confirm() finalizes it with the callback not delivered; advance now to budget.expiresAt + 1; three step() calls answer 'pending', broadcasts stay 1, one chain-transactions row. Five createContractIntakeWorker.tick() calls persist contract-requests.delivery = {state:'pending', resultHash, observedAt} with retryAt undefined and perform at least 10 deliveryState reads. Admit 'next-budget' (same inbox/dispatcher/codeHash, expiring 5 days later), revoke 'fixture-budget', step with the new budget: still 'pending', broadcasts 1, next-budget reserved '0'; 365 days later still 'pending'. Expected: a callback signed under the new allocation, or a terminal state, or at least a persisted retryAt.

### 5. Low: One empty eth_getLogs answer for the finalized page that holds the exact relay advances the recovery cursor past it, and the finalized state read then blocks the registration permanently

`adapters/chain-executor.mjs:81`

```
          requireThat(/^0x0{40}$/i.test(finalized[1]), 'registered_project_mismatch');
```

Merged from audit_economics and audit_flow (same mechanism, reproduced). relayedRegistration treats found.complete with zero logs plus a finalized projects() read showing a non-zero target as proof of a conflicting registration (lines 78-81). recoverChainEvent reaches 'complete' on the strength of one eth_getLogs response for [start, finalizedHead] and persists nextBlock = end+1 before the finalized read runs (adapters/chain-event-recovery.mjs:16-21); nothing cross-checks an empty page against contract state before the cursor moves. If that single page comes back empty while the event sits inside it (a load-balanced backend that has not imported the newest finalized blocks answering eth_getLogs with [] while eth_call at head.number is served by a synced backend; the Sepolia worker uses one public endpoint with retryCount 0, runtime/chain-worker.mjs:28), the finalized read correctly shows the project registered and line 81 throws. On every later tick start = nextBlock > head (or a later empty range), so the event block is never queried again, and with src/chain-deployments/index.mjs:181 the first throw already finishes the job 'blocked' with the error documented as 'a different registration for that project'. The exact signed registration IS the one onchain, so the project ends in the same permanent no-binding state as the padded-relay finding and every new intent blocks on the calldata check. Baseline 2b87522 left this input in an endless 'registering' loop (the prior medium finding on missing terminal outcomes); 0.12.2 converts it into a terminal conflict with no rescan, which is no better for the project and is mislabelled. Fix: before concluding registered_project_mismatch from a finalized state read with zero logs, discard the cursor and rescan [fromBlock, head] once (or query getLogs by the page-end block hash) and block only when the rescan also finds no event; or verify an empty page against projects(projectId).target at block end before persisting it and throw a retryable error on inconsistency.

**Reproduction**

test/scratch/registration-edges.test.mjs test C (node --test test/scratch/registration-edges.test.mjs; fixture from test/registration-recovery.test.mjs: module receipt block 80, intent observedBlock 100, exact-calldata relay in block 101, finalized head 110). getLogs returns [] once for the page [80,110], then answers correctly. Tick 1: step() rejects with code registered_project_mismatch and chain-event-recovery stores nextBlock '111'. Ticks 2 and 3 with a healthy RPC: step() rejects with registered_project_mismatch again; getLogs was called exactly once in total, never again for block 101. Expected: the relay at block 101 (owner, mandateHash and calldata all match) is adopted and the step returns 'verified'.

### 6. Low: Registration expiry is decided by the local wall clock when the project is still unregistered at latest, so a direct relay mined in the final seconds before the deadline is orphaned

`adapters/chain-executor.mjs:23`

```
    if (!status && expiresAt <= now()) return { state: 'cancelled' };
```

From audit_math (reproduced). 0.12.2 states that a missing registration expires only after finalized chain time passes its deadline and implements that at relayedRegistration line 83 (head.timestamp >= expiresAt on the finalized block). That branch is only reached when projects(projectId).target is already non-zero at latest (line 64). When the latest read is still zero, relayedRegistration returns null and deployment.step falls through to transact(), whose unsent-action cut-off at line 23 compares value.message.expiresAt * 1000 with the local clock. The contract accepts registerProject while block.timestamp < expiresAt (contracts/src/ClausApprovalExecutor.sol:133), so a relay broadcast by the owner in the last slot before expiry can be mined at chain time < expiresAt while the executor tick already sees now() >= expiresAt and the block is not yet visible at latest (one slot of propagation plus any clock skew). The tick returns cancelled, terminal() maps it to {state:'blocked', error:'authority_or_window_closed'} and runOne persists state blocked with retryAt null. The relay then lands: the project is registered onchain, chain-bindings stays empty, and because prepareRegistration only checks chain-bindings (src/chain-deployments/index.mjs:98) the owner can prepare and submit a new intent, which relayedRegistration finds but rejects on calldata mismatch (different nonce/expiresAt) with registered_project_mismatch, consuming lifetime intents. The window is seconds wide, so the likelihood is low; the inconsistency is that the two branches of one decision use two clocks, the local one being the earlier. No funds are spent and the onchain registration is valid. Fix: in deployment.step, when transact() answers cancelled for an unsent registration with no chain-transactions row, require the finalized head timestamp to be >= value.message.expiresAt before returning terminal (the rule relayedRegistration already applies), otherwise return {state:'registering'}.

**Reproduction**

test/scratch/registration-edges.test.mjs test F (node --test test/scratch/registration-edges.test.mjs). Intent observedBlock '100', message.expiresAt 2000, module receipt block 80, no chain-transactions row; readContract(projects) at latest returns a zero target; getBlock(finalized) = {number: 90, timestamp: 1900}; executor created with now: () => 2000500 (local clock 0.5 s past the deadline). Expected under the stated 0.12.2 rule: {state:'registering'} because finalized chain time 1900 < 2000. Actual: deployment.step returns {state:'blocked', error:'authority_or_window_closed'} with no chain-transactions row. Then make the relay visible (projects non-zero at latest, ProjectRegistered log at block 101 with the exact calldata, finalized head 160 at timestamp 1990): a fresh step() on the same inputs returns 'verified', proving the registration was recoverable, while the persisted job would be terminal blocked.

### 7. Low: An exact direct relay of a prepared intent that the owner mines before the local one-hour window ends can never be queued for recovery: submitRegistration rejects the expired intent and every later in

`src/chain-deployments/index.mjs:129`

```
        requireThat(prepared.message.expiresAt * 1000 > now(), 'registration_expired');
```

From audit_economics (reproduced). docs/testnet.md (0.12.2) advertises that registration recovery accepts a direct transaction to the pinned executor with the exact signed calldata. Recovery is only reached by the worker for a queued intent, and queuing requires submitRegistration(), which refuses any intent whose message.expiresAt has passed (line 129) regardless of whether that exact registration is already mined; the signature is stored only at submit (line 134), so the worker has nothing to match. The onchain executor accepts registerProject while block.timestamp < expiresAt, so an owner who signs the prepared intent and relays it directly, then submits the signature to the API after the hour (or whose API submission is delayed past the hour by an outage), leaves the project registered onchain under the first intent with no chain-binding. prepareRegistration then creates a new intent (new nonce and expiresAt, so different calldata) which the 0.12.2 recovery rules block with registered_project_mismatch, consuming one of the 16 lifetime intents each time; projectKey is fixed per project (adapters/chain-read.mjs:94-95), so no operation adopts the mined registration. This needs owner misordering, so it is a low-likelihood liveness defect, but its outcome is the same permanent lock as the other registration findings. Fix: let submitRegistration accept an expired intent when no other intent is active (the worker already terminates an expired unsent intent as blocked without spending, and 0.12.2 shows a queued intent remains recoverable after signature expiry), so an existing exact relay is recovered and a missing one is blocked explicitly.

**Reproduction**

test/scratch/submit-expired.test.mjs (node --test test/scratch/submit-expired.test.mjs; fixture from test/registration-delay.test.mjs). prepareRegistration at T returns intent I with message.expiresAt = T + 3600 s. The owner relays registerProject(I.message, configuration, signature) directly and it is mined at chain time < expiresAt (the chain state is irrelevant to the rejection). At T + 3601 s submitRegistration(I.id, signature) rejects with code registration_expired, no job is enqueued and no chain-registration-signatures row exists; prepareRegistration then returns a new intent with a different id and expiresAt (different calldata). Expected: a way to queue I so the worker's relayedRegistration can adopt the exact mined relay. Actual: no call on this tree can queue I afterwards.

### 8. Info: Retained limits from the prior review (page starvation, 16 lifetime intents, C bootstrap fee ceiling, B callback gas ceiling) are now documented and remain non-blocking for the bounded launch

`docs/contract-intake.md:106`

```
The proposed public dispatcher allocation caps each transaction and each project's cumulative dispatch gas at 0.00005 Sepolia ETH. Resolution and callback share that project cap. A 1,000,000-gas callback needs a fee below roughly 0.04 gwei even before resolution costs and other overhead; a 200,000-gas estimate fits a fresh transaction cap only below roughly 0.19 gwei. These are capacity examples, not current fee quotes or delivery guarantees. Prefer a small receiver or `callbackGas: 0` and read the canonical result. Insufficient gas allowance leaves delivery pending; an expired subsidy alone does not close an otherwise valid onchain request. Existing signed transactions retain their original allocation and deadline. No allocation is automatically renewed.
```

Reused assessment from completed job 2bb2335f (findings 4, 7, 8, 9); the underlying code is unchanged in the delta and the four limits are now stated in docs/contract-intake.md, docs/testnet.md and docs/mainnet.md. (4) An admitted requester can delay peers behind the shared 32-event scan page; no fair scheduling, but no spend and no authority effect. (7) 16 lifetime registration intents include expired and blocked records; finite by design, and the registration findings above make it more likely that intents are consumed by blocked recoveries, so fixing those matters more than raising the cap. (8) C bootstrap: 0.0046 ETH over a 24,385,000 padded gas limit tolerates about 0.189 gwei (0.153 gwei at the 30M cap); fails closed above that, so it is a readiness number for the human approval, not a defect. (9) B dispatch: 0.00005 test ETH per transaction and per project, shared by resolve and callback, means a 1,000,000-gas callback is deliverable only below about 0.04 gwei; callbacks are best effort and the canonical result stays readable. None of these is blocking for scope B or the preparation-only scope C. Nothing in this delta changes a Solidity contract or a financial ceiling.

**Reproduction**

Arithmetic re-checked against adapters/sepolia-transactions.mjs:63-66: gas = estimate*125/100 + 10000; 19,500,000 -> 24,385,000; 4.6e15 / 24,385,000 = 188,640,557 wei (0.189 gwei); 4.6e15 / 30,000,000 = 153,333,333 wei. 5e13 / 1,260,000 = 39,682,539 wei (0.040 gwei). Page size and intent cap: adapters/contract-intake.mjs:71-72 (logs.slice(0, 32)) and src/chain-deployments/index.mjs:101-111 (list limit 17, registrations.length < 16). The repository's own docs now carry the same figures (docs/contract-intake.md:104-106, docs/testnet.md:43-45, docs/mainnet.md:29).

---

Judge's submission `91bcbfd06002021ed842a315dd44c5d59055ad78d6c87b948422e8d045aa9f1b`, accepted on the IdentityMD network. Acceptance means the report met the job's checks;
it is not a guarantee that the code has no other defects.
