@d20dao/vrf-sdk 0.3.4 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/API.md CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  <!-- Generated by scripts/api-reference.mjs from abi/*.json and scripts/api-descriptions.mjs. Do not edit by hand. -->
4
4
 
5
- Every function, event and error of `D20VRFCoordinator` and `EpochEntropy`, generated from the ABIs of `@d20dao/vrf-sdk` 0.3.4. Regenerate with `npm run build && npm run api-reference`; `npm test` fails when this file is out of date.
5
+ Every function, event and error of `D20VRFCoordinator` and `EpochEntropy`, generated from the ABIs of `@d20dao/vrf-sdk` 0.4.0. Regenerate with `npm run build && npm run api-reference`; `npm test` fails when this file is out of date.
6
6
 
7
- - Package: `@d20dao/vrf-sdk` 0.3.4
8
- - Protocol source: commit `640b60cb992a7e3add1efe8e7b392341732ea004`, copied to [`protocol/contracts/`](protocol/contracts/) (hashes in `PROTOCOL-PROVENANCE.json`)
7
+ - Package: `@d20dao/vrf-sdk` 0.4.0
8
+ - Protocol source: commit `de5f82eb9fc749c80e83270f57cde9908ddcf1f3`, copied to [`protocol/contracts/`](protocol/contracts/) (hashes in `PROTOCOL-PROVENANCE.json`)
9
9
  - Compiler: solc 0.8.28+commit.7893614a.Emscripten.clang, EVM version `cancun`
10
10
  - `abi/D20VRFCoordinator.json` SHA-256: `4764ba62745e109f3b968b21ed23e88da739a4a26906fc2ed3192fa23b8d79c1`
11
- - `abi/EpochEntropy.json` SHA-256: `90d465fd2fbd5321a6ec87ab81850cf63b7b9c00b1b7d8aa00f30abad40e583a`
11
+ - `abi/EpochEntropy.json` SHA-256: `684ee3dfe0745f141831aee896db6436d490d5e9715e61661c86b044d1c3260e`
12
12
 
13
13
  This reference describes that source. A deployment runs it only while the implementation behind each proxy is the one the deployment manifest records for that commit: check when you integrate and whenever a proxy emits `Upgraded` (README [Security and trust](README.md#security-and-trust)).
14
14
 
@@ -42,6 +42,7 @@ This reference describes that source. A deployment runs it only while the implem
42
42
  - [Epoch state for consumers and verifiers](#registry-epoch-state-for-consumers-and-verifiers)
43
43
  - [Registry reads](#registry-registry-reads)
44
44
  - [Source selection and publication](#registry-source-selection-and-publication)
45
+ - [Recipes and catalogs](#registry-recipes-and-catalogs)
45
46
  - [Owner administration](#registry-owner-administration)
46
47
  - [Constants](#registry-constants)
47
48
  - [Events](#registry-events)
@@ -59,7 +60,7 @@ Requests, fulfillment, `storeBlockHash`, retries, `refundRequest` and withdrawal
59
60
 
60
61
  Returned by `getRequest`. It does not contain the escrowed fee or the refund ratio; read `requestFeePaid` and `requestRefundBps`.
61
62
 
62
- Source: `D20VRFCoordinator.sol` lines 58–76
63
+ Source: `D20VRFCoordinator.sol` lines 59–77
63
64
 
64
65
  | Field | Type | Meaning |
65
66
  | --- | --- | --- |
@@ -123,7 +124,7 @@ Requests must come from a contract and pay at least the fee computed in their ow
123
124
  function quoteFee(uint32 callbackGasLimit) external view returns (uint256)
124
125
  ```
125
126
 
126
- Selector `0xc9caa0c3` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 227–232
127
+ Selector `0xc9caa0c3` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 228–233
127
128
 
128
129
  Fee for a request with this `callbackGasLimit` priced at `block.basefee`, that is `quoteFeeAt(callbackGasLimit, block.basefee)`. Exact inside the requesting transaction, which is how `D20VRFRequests` helpers pay. Through `eth_call` the base fee is commonly reported as 0 (observed on Arc), so the answer collapses to `minFee` and a transaction sent with it reverts `IncorrectFee`. Off-chain, quote with `quoteFeeAt` and the latest header base fee plus a buffer, as `quoteRequestFee` does. It does not check the gas limit range.
129
130
 
@@ -135,7 +136,7 @@ Fee for a request with this `callbackGasLimit` priced at `block.basefee`, that i
135
136
  function quoteFeeAt(uint32 callbackGasLimit, uint256 baseFee) external view returns (uint256)
136
137
  ```
137
138
 
138
- Selector `0x26fa8481` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 219–226
139
+ Selector `0x26fa8481` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 220–227
139
140
 
140
141
  `max(minFee, feeMultiplier × baseFee × (fulfillGasOverhead + callbackGasLimit))` over the live pricing for a base fee in wei that you supply; with `feeMultiplier` 0 it returns `minFee`. A quote, not a reservation: pricing can change before your transaction. A `baseFee` large enough to overflow uint256 reverts with `Panic(0x11)` instead of `FeeOverflow`.
141
142
 
@@ -147,7 +148,7 @@ Selector `0x26fa8481` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol
147
148
  function requestRandomness(bytes32 clientSeed, uint32 callbackGasLimit, address _refundAddress) external payable returns (uint256 requestId)
148
149
  ```
149
150
 
150
- Selector `0x9849d1e5` · Caller: Any contract · Source: `D20VRFCoordinator.sol` lines 234–239, 247–286
151
+ Selector `0x9849d1e5` · Caller: Any contract · Source: `D20VRFCoordinator.sol` lines 235–240, 248–287
151
152
 
152
153
  Creates a raw request (spec all zero) with `msg.sender` as consumer and returns its ID. Needs `msg.value` at least the fee computed in this transaction; escrows exactly that fee and credits any excess to `_refundAddress` as refund credit. Fixes the request block, epoch, client seed, refund address, fee, refund ratio (`refundBps`) and a deadline of `block.timestamp + RESPONSE_TIMEOUT`. After acceptance the consumer receives `rawFulfillRandomness(requestId, randomness)` with exactly `callbackGasLimit` gas. Checks run in this order: caller has code, refund address non-zero, gas limit in range, fee, mapping, epoch started.
153
154
 
@@ -161,7 +162,7 @@ Creates a raw request (spec all zero) with `msg.sender` as consumer and returns
161
162
  function requestMappedRandomness(bytes32 clientSeed, uint32 callbackGasLimit, address _refundAddress, RandomnessMapping.Spec spec) external payable returns (uint256 requestId)
162
163
  ```
163
164
 
164
- Selector `0xe6b41a8c` · Caller: Any contract · Source: `D20VRFCoordinator.sol` lines 241–245, 247–286
165
+ Selector `0xe6b41a8c` · Caller: Any contract · Source: `D20VRFCoordinator.sol` lines 242–246, 248–287
165
166
 
166
167
  Same as `requestRandomness`, storing `spec` with the request (`getMapping`, `mappingHash`). The callback still carries the raw word; read the mapped values with `getMappedResult`. `D20VRFRequests` helpers call this function and pay `quoteFee` from the calling contract balance.
167
168
 
@@ -175,11 +176,11 @@ Views, callable by anyone. They describe requests created from now on; an existi
175
176
 
176
177
  | Function | Selector | Meaning | Source |
177
178
  | --- | --- | --- | --- |
178
- | <a id="coordinator-fn-pricing"></a>`pricing() returns (uint256, uint16, uint32)` | `0x7ce91411` | Live `(minFee, feeMultiplier, fulfillGasOverhead)`. The outputs are unnamed, so read them by position. | lines 210–212 |
179
- | <a id="coordinator-fn-minfee"></a>`minFee() returns (uint256)` | `0x24ec7590` | Minimum fee in wei, at most `MAX_MIN_FEE` (10 USDC). | line 43 |
180
- | <a id="coordinator-fn-feemultiplier"></a>`feeMultiplier() returns (uint16)` | `0xe5a70ef7` | Base-fee multiplier, 0 to `MAX_FEE_MULTIPLIER` (20); 0 makes every fee `minFee`. | lines 45–46 |
181
- | <a id="coordinator-fn-fulfillgasoverhead"></a>`fulfillGasOverhead() returns (uint32)` | `0x19d40839` | Gas added to `callbackGasLimit` in the fee formula, `MIN_FULFILL_GAS_OVERHEAD` to `MAX_FULFILL_GAS_OVERHEAD`. | line 47 |
182
- | <a id="coordinator-fn-refundbps"></a>`refundBps() returns (uint16)` | `0xec8c9a0b` | Current refund ratio in basis points (5000 to 10000), copied into each new request. Not the ratio of an existing request: use `requestRefundBps(requestId)`. | lines 48–49 |
179
+ | <a id="coordinator-fn-pricing"></a>`pricing() returns (uint256, uint16, uint32)` | `0x7ce91411` | Live `(minFee, feeMultiplier, fulfillGasOverhead)`. The outputs are unnamed, so read them by position. | lines 211–213 |
180
+ | <a id="coordinator-fn-minfee"></a>`minFee() returns (uint256)` | `0x24ec7590` | Minimum fee in wei, at most `MAX_MIN_FEE` (10 USDC). | line 44 |
181
+ | <a id="coordinator-fn-feemultiplier"></a>`feeMultiplier() returns (uint16)` | `0xe5a70ef7` | Base-fee multiplier, 0 to `MAX_FEE_MULTIPLIER` (20); 0 makes every fee `minFee`. | lines 46–47 |
182
+ | <a id="coordinator-fn-fulfillgasoverhead"></a>`fulfillGasOverhead() returns (uint32)` | `0x19d40839` | Gas added to `callbackGasLimit` in the fee formula, `MIN_FULFILL_GAS_OVERHEAD` to `MAX_FULFILL_GAS_OVERHEAD`. | line 48 |
183
+ | <a id="coordinator-fn-refundbps"></a>`refundBps() returns (uint16)` | `0xec8c9a0b` | Current refund ratio in basis points (5000 to 10000), copied into each new request. Not the ratio of an existing request: use `requestRefundBps(requestId)`. | lines 49–50 |
183
184
 
184
185
  ### <a id="coordinator-reading-request-state-and-results"></a>Reading request state and results
185
186
 
@@ -191,7 +192,7 @@ Views, callable by anyone, including from a callback. Request IDs start at 1 and
191
192
  function getRequest(uint256 requestId) external view returns (D20VRFCoordinator.Request result)
192
193
  ```
193
194
 
194
- Selector `0xc58343ef` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 288–307, 547–552
195
+ Selector `0xc58343ef` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 289–308, 558–563
195
196
 
196
197
  Full state of a request, see [`D20VRFCoordinator.Request`](#coordinator-type-d20vrfcoordinator-request). `targetBlock` and `epochHash` are resolved from the registry, so they become non-zero as soon as the epoch packet is published. `fulfilled` means the word is final; `delivered` only reports that a callback succeeded. A request that is not `fulfilled` in a block whose timestamp is after `deadline` has expired and can only be refunded. When polling, read the latest block before `getRequest`, so that a proof included up to that block is visible.
197
198
 
@@ -203,7 +204,7 @@ Full state of a request, see [`D20VRFCoordinator.Request`](#coordinator-type-d20
203
204
  function getMapping(uint256 requestId) external view returns (RandomnessMapping.Spec)
204
205
  ```
205
206
 
206
- Selector `0xede9ba8b` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 339–342
207
+ Selector `0xede9ba8b` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 340–343
207
208
 
208
209
  The stored [`RandomnessMapping.Spec`](#coordinator-type-randomnessmapping-spec); all fields zero (Raw) for `requestRandomness`.
209
210
 
@@ -215,7 +216,7 @@ The stored [`RandomnessMapping.Spec`](#coordinator-type-randomnessmapping-spec);
215
216
  function getMappedResult(uint256 requestId) external view returns (uint256[])
216
217
  ```
217
218
 
218
- Selector `0x8f09a3e6` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 344–348
219
+ Selector `0x8f09a3e6` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 345–349
219
220
 
220
221
  The accepted word mapped with the stored spec: `[uint256(word)]` for a raw request, otherwise the values in README [Randomness options](README.md#randomness-options). Part of `ID20VRF`. Reverts `NotFulfilled` until a proof is accepted, so an expired or refunded request never has a result. Gas grows with the mapping; a 256-item shuffle is expensive onchain.
221
222
 
@@ -227,7 +228,7 @@ The accepted word mapped with the stored spec: `[uint256(word)]` for a raw reque
227
228
  function mapRandomness(bytes32 randomness, RandomnessMapping.Spec spec) external pure returns (uint256[])
228
229
  ```
229
230
 
230
- Selector `0x41c2a199` · Caller: Anyone (pure) · Source: `D20VRFCoordinator.sol` lines 350–355
231
+ Selector `0x41c2a199` · Caller: Anyone (pure) · Source: `D20VRFCoordinator.sol` lines 351–356
231
232
 
232
233
  Maps any word with any valid spec, like the SDK `mapRandomness(word, spec)` off-chain. It does not show that a request was fulfilled.
233
234
 
@@ -239,7 +240,7 @@ Maps any word with any valid spec, like the SDK `mapRandomness(word, spec)` off-
239
240
  function requestFeePaid(uint256 requestId) external view returns (uint256)
240
241
  ```
241
242
 
242
- Selector `0xef7cc992` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 330–333
243
+ Selector `0xef7cc992` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 331–334
243
244
 
244
245
  Fee escrowed by the request (`feePaid` in `RandomnessRequested`), excluding any overpayment. The keeper share, the protocol share and the refund are computed from it.
245
246
 
@@ -251,7 +252,7 @@ Fee escrowed by the request (`feePaid` in `RandomnessRequested`), excluding any
251
252
  function requestRefundBps(uint256 requestId) external view returns (uint16)
252
253
  ```
253
254
 
254
- Selector `0x5d170fd7` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 334–337
255
+ Selector `0x5d170fd7` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 335–338
255
256
 
256
257
  Refund ratio the request copied from `refundBps` at creation. An expiry refund pays `requestFeePaid × requestRefundBps / 10000`; a later `setRefundBps` does not change it.
257
258
 
@@ -263,7 +264,7 @@ Refund ratio the request copied from `refundBps` at creation. An expiry refund p
263
264
  function refundCallbackDelivered(uint256) external view returns (bool)
264
265
  ```
265
266
 
266
- Selector `0x281d3157` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 102, 500
267
+ Selector `0x281d3157` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 103, 511
267
268
 
268
269
  True once an `onRefund` notification for the request succeeded, at `refundRequest` or `retryRefundCallback`. Returns false for unknown IDs instead of reverting.
269
270
 
@@ -273,7 +274,7 @@ True once an `onRefund` notification for the request succeeded, at `refundReques
273
274
  function nextRequestId() external view returns (uint256)
274
275
  ```
275
276
 
276
- Selector `0x6a84a985` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 50, 172, 260
277
+ Selector `0x6a84a985` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 51, 173, 261
277
278
 
278
279
  ID the next request will receive. Issued IDs are 1 to `nextRequestId() - 1`.
279
280
 
@@ -287,7 +288,7 @@ Recovery calls need no value or role. They forward gas to the consumer and rever
287
288
  function refundRequest(uint256 requestId) external
288
289
  ```
289
290
 
290
- Selector `0x7411484e` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 455–479, 491–502
291
+ Selector `0x7411484e` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 466–490, 502–513
291
292
 
292
293
  Refunds an unfulfilled request once a block timestamp is after its deadline. Marks it refunded, sends `feePaid × requestRefundBps / 10000` to the fixed refund address with a 30,000-gas transfer, or adds it to that address's refund credit if the transfer fails, and adds the rest of the fee to `earnedFees`. Then calls `onRefund(requestId)` on the consumer with 100,000 gas; a failed notification does not undo the refund. The caller receives nothing. Measured minimum transaction gas limit 302,558 to 357,517; use 400,000.
293
294
 
@@ -301,7 +302,7 @@ Refunds an unfulfilled request once a block timestamp is after its deadline. Mar
301
302
  function retryCallback(uint256 requestId, uint32 gasLimit) external
302
303
  ```
303
304
 
304
- Selector `0xdd11c275` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 445–453, 602–619
305
+ Selector `0xdd11c275` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 456–464, 613–630
305
306
 
306
307
  Calls `rawFulfillRandomness` again with the same accepted word after a failed callback, forwarding `gasLimit` (30,000 to 1,000,000 and not below the request's `callbackGasLimit`). Sets `delivered` on success. Pays nobody and never changes the word. Transaction gas limit: about `gasLimit + 250,000`.
307
308
 
@@ -315,7 +316,7 @@ Calls `rawFulfillRandomness` again with the same accepted word after a failed ca
315
316
  function retryRefundCallback(uint256 requestId, uint32 gasLimit) external
316
317
  ```
317
318
 
318
- Selector `0x054f6962` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 481–489, 491–502
319
+ Selector `0x054f6962` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 492–500, 502–513
319
320
 
320
321
  Repeats a failed `onRefund` notification for a refunded request with `gasLimit` (100,000 to 1,000,000). Never transfers funds again. Transaction gas limit: about `gasLimit + 150,000`.
321
322
 
@@ -329,7 +330,7 @@ Repeats a failed `onRefund` notification for a refunded request with `gasLimit`
329
330
  function withdrawRefundCredit(address recipient) external
330
331
  ```
331
332
 
332
- Selector `0x445071f2` · Caller: Refund-credit holder · Source: `D20VRFCoordinator.sol` lines 504–514
333
+ Selector `0x445071f2` · Caller: Refund-credit holder · Source: `D20VRFCoordinator.sol` lines 515–525
333
334
 
334
335
  Sends all of the caller's refund credit, `refundCredits(msg.sender)`, to `recipient` with all remaining gas. Credit comes from overpayment and from refund transfers that failed, and belongs to the request's refund address, so that address must make the call. If `recipient` rejects the transfer the call reverts and the credit stays.
335
336
 
@@ -343,7 +344,7 @@ Sends all of the caller's refund credit, `refundCredits(msg.sender)`, to `recipi
343
344
  function withdrawFees(address recipient) external
344
345
  ```
345
346
 
346
- Selector `0x164e68de` · Caller: Fee recipient · Source: `D20VRFCoordinator.sol` lines 516–525
347
+ Selector `0x164e68de` · Caller: Fee recipient · Source: `D20VRFCoordinator.sol` lines 527–536
347
348
 
348
349
  Sends all `earnedFees` to `recipient`. Fees accrue at acceptance (fee minus keeper share) and from the part of a refunded fee that is not returned; open escrow is never included. With nothing earned it sends zero without reverting.
349
350
 
@@ -357,7 +358,7 @@ Sends all `earnedFees` to `recipient`. Fees accrue at acceptance (fee minus keep
357
358
  function withdrawKeeperCredit(address recipient) external
358
359
  ```
359
360
 
360
- Selector `0xf62c546b` · Caller: Keeper-credit holder · Source: `D20VRFCoordinator.sol` lines 527–536
361
+ Selector `0xf62c546b` · Caller: Keeper-credit holder · Source: `D20VRFCoordinator.sol` lines 538–547
361
362
 
362
363
  Sends all of the caller's keeper credit (keeper-share transfers that failed) to `recipient`.
363
364
 
@@ -371,17 +372,17 @@ Views, callable by anyone. Amounts are in wei of native USDC.
371
372
 
372
373
  | Function | Selector | Meaning | Source |
373
374
  | --- | --- | --- | --- |
374
- | <a id="coordinator-fn-refundcredits"></a>`refundCredits(address) returns (uint256)` | `0x61137e40` | Refund credit that an address can withdraw with `withdrawRefundCredit`. | line 56 |
375
- | <a id="coordinator-fn-totalrefundcredits"></a>`totalRefundCredits() returns (uint256)` | `0x6e0842e1` | Sum of all refund credit held by the coordinator. | line 55 |
376
- | <a id="coordinator-fn-earnedfees"></a>`earnedFees() returns (uint256)` | `0xb1b3ffd9` | Protocol fees that the fee recipient can withdraw. | line 51 |
377
- | <a id="coordinator-fn-feerecipient"></a>`feeRecipient() returns (address)` | `0x46904840` | Address allowed to call `withdrawFees`; changed with `setFeeRecipient`. | line 39 |
378
- | <a id="coordinator-fn-keeperfeebps"></a>`keeperFeeBps() returns (uint16)` | `0x0eab7d63` | Keeper share of each accepted fee in basis points (0 to 10000). Read at acceptance, not snapshotted: a change applies to open requests accepted afterwards. It only splits the escrowed fee; what the consumer paid and can be refunded does not change. | lines 40, 422 |
379
- | <a id="coordinator-fn-keepercredits"></a>`keeperCredits(address) returns (uint256)` | `0xf5c764f6` | Keeper credit that an address can withdraw with `withdrawKeeperCredit`. | line 41 |
380
- | <a id="coordinator-fn-totalkeepercredits"></a>`totalKeeperCredits() returns (uint256)` | `0xc7281b7a` | Sum of all keeper credit held by the coordinator. | line 42 |
375
+ | <a id="coordinator-fn-refundcredits"></a>`refundCredits(address) returns (uint256)` | `0x61137e40` | Refund credit that an address can withdraw with `withdrawRefundCredit`. | line 57 |
376
+ | <a id="coordinator-fn-totalrefundcredits"></a>`totalRefundCredits() returns (uint256)` | `0x6e0842e1` | Sum of all refund credit held by the coordinator. | line 56 |
377
+ | <a id="coordinator-fn-earnedfees"></a>`earnedFees() returns (uint256)` | `0xb1b3ffd9` | Protocol fees that the fee recipient can withdraw. | line 52 |
378
+ | <a id="coordinator-fn-feerecipient"></a>`feeRecipient() returns (address)` | `0x46904840` | Address allowed to call `withdrawFees`; changed with `setFeeRecipient`. | line 40 |
379
+ | <a id="coordinator-fn-keeperfeebps"></a>`keeperFeeBps() returns (uint16)` | `0x0eab7d63` | Keeper share of each accepted fee in basis points (0 to 10000). Read at acceptance, not snapshotted: a change applies to open requests accepted afterwards. It only splits the escrowed fee; what the consumer paid and can be refunded does not change. | lines 41, 433 |
380
+ | <a id="coordinator-fn-keepercredits"></a>`keeperCredits(address) returns (uint256)` | `0xf5c764f6` | Keeper credit that an address can withdraw with `withdrawKeeperCredit`. | line 42 |
381
+ | <a id="coordinator-fn-totalkeepercredits"></a>`totalKeeperCredits() returns (uint256)` | `0xc7281b7a` | Sum of all keeper credit held by the coordinator. | line 43 |
381
382
 
382
383
  ### <a id="coordinator-keeper-and-proof-functions"></a>Keeper and proof functions
383
384
 
384
- Proof submission is permissionless: anyone holding a valid proof may submit it, and the keeper share always goes to the registry `committer()`. Consumers normally only read `getRequest`. Besides the custom errors listed, proof functions can revert with `Error(string)` messages from the vendored VRF verifier, such as `invalid proof`, which are not in the ABI.
385
+ Proof submission is permissionless: anyone holding a valid proof may submit it, and the keeper share goes to the submitting wallet when the registry authorizes it as its committer or a backup committer, and to `committer()` otherwise. Consumers normally only read `getRequest`. Besides the custom errors listed, proof functions can revert with `Error(string)` messages from the vendored VRF verifier, such as `invalid proof`, which are not in the ABI.
385
386
 
386
387
  #### <a id="coordinator-fn-fulfillrandomness"></a>`fulfillRandomness`
387
388
 
@@ -389,9 +390,9 @@ Proof submission is permissionless: anyone holding a valid proof may submit it,
389
390
  function fulfillRandomness(uint256 requestId, VRF.Proof proof) external
390
391
  ```
391
392
 
392
- Selector `0xef7c2b19` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 388–396, 412–437
393
+ Selector `0xef7c2b19` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 389–397, 413–448
393
394
 
394
- Accepts a proof for a request that is not fulfilled, not refunded and not past its deadline; acceptance in a block with timestamp equal to `deadline` is timely. Stores the target block hash if needed, verifies the proof against `requestSeed(requestId)`, stores the word, proof hash and transcript hash, sets `fulfilled`, adds `feePaid` minus the keeper share to `earnedFees` and calls the consumer with `callbackGasLimit` gas. It then sends the keeper share (`keeperFeeBps` of `feePaid`) to `committer()` with 30,000 gas, or records it as keeper credit. A failing callback does not revert the fulfillment.
395
+ Accepts a proof for a request that is not fulfilled, not refunded and not past its deadline; acceptance in a block with timestamp equal to `deadline` is timely. Stores the target block hash if needed, verifies the proof against `requestSeed(requestId)`, stores the word, proof hash and transcript hash, sets `fulfilled`, adds `feePaid` minus the keeper share to `earnedFees` and calls the consumer with `callbackGasLimit` gas. It then sends the keeper share (`keeperFeeBps` of `feePaid`) with 30,000 gas to the submitting wallet when the registry answers `isAuthorizedCommitter` true for it (the committer or an allowed backup committer) and to `committer()` otherwise, or records it as that wallet's keeper credit; a registry call that reverts also pays `committer()`. A failing callback does not revert the fulfillment.
395
396
 
396
397
  **Emits:** [`BlockHashStored`](#coordinator-event-blockhashstored), [`RequestServed`](#coordinator-event-requestserved), [`ProofVerified`](#coordinator-event-proofverified), [`RandomnessFulfilled`](#coordinator-event-randomnessfulfilled), [`FulfillmentEvidence`](#coordinator-event-fulfillmentevidence), [`CallbackAttempted`](#coordinator-event-callbackattempted), [`KeeperFeePaid`](#coordinator-event-keeperfeepaid).
397
398
 
@@ -403,7 +404,7 @@ Accepts a proof for a request that is not fulfilled, not refunded and not past i
403
404
  function fulfillRandomnessBatch(uint256[] ids, VRF.Proof[] proofs) external
404
405
  ```
405
406
 
406
- Selector `0x9497b180` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 398–410
407
+ Selector `0x9497b180` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 399–411
407
408
 
408
409
  Fulfills up to `MAX_FULFILL_BATCH` (16) requests, one proof each. Members already fulfilled, refunded or past their deadline, including an ID repeated in the batch, are skipped with `FulfillmentSkipped`; every other member runs exactly like `fulfillRandomness` and emits the same events, so an unknown ID, an unready member or an invalid proof reverts the whole batch.
409
410
 
@@ -417,7 +418,7 @@ Fulfills up to `MAX_FULFILL_BATCH` (16) requests, one proof each. Members alread
417
418
  function storeBlockHash(uint256 requestId) external returns (bytes32)
418
419
  ```
419
420
 
420
- Selector `0x262fd733` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 367–371, 553–568
421
+ Selector `0x262fd733` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 368–372, 564–579
421
422
 
422
423
  Resolves the target block from the published epoch, stores its hash if not stored yet and returns it. Fulfillment does this automatically; calling it earlier keeps a request provable after its target leaves the 256-block `BLOCKHASH` window. Needs `block.number` at least `targetBlock + confirmationBlocks`. Works on any request, whatever its status.
423
424
 
@@ -431,7 +432,7 @@ Resolves the target block from the published epoch, stores its hash if not store
431
432
  function verifyRequestProof(uint256 requestId, VRF.Proof proof) external view returns (bytes32)
432
433
  ```
433
434
 
434
- Selector `0x0846de99` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 357–365
435
+ Selector `0x0846de99` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 358–366
435
436
 
436
437
  Returns the word a proof yields for the request's seed, without changing state. A valid proof is not acceptance: check `getRequest(requestId).fulfilled`.
437
438
 
@@ -443,7 +444,7 @@ Returns the word a proof yields for the request's seed, without changing state.
443
444
  function requestSeed(uint256 requestId) external view returns (uint256)
444
445
  ```
445
446
 
446
- Selector `0xa9df851a` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 373–378, 570–578
447
+ Selector `0xa9df851a` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 374–379, 581–589
447
448
 
448
449
  Seed the proof must use: `keccak256(abi.encode(SEED_DOMAIN, chainId, coordinator, keyHash, requestId, consumer, clientSeed, mappingHash, requestBlock, targetBlock, blockHash, epochId, epochHash))` as uint256. Available only after publication and `confirmationBlocks` confirmations of the target block.
449
450
 
@@ -455,7 +456,7 @@ Seed the proof must use: `keccak256(abi.encode(SEED_DOMAIN, chainId, coordinator
455
456
  function getProofContext(uint256 requestId) external view returns (uint256 seed, uint64 deadline, bool fulfilled, bool refunded)
456
457
  ```
457
458
 
458
- Selector `0xcf14de9d` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 380–386
459
+ Selector `0xcf14de9d` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 381–387
459
460
 
460
461
  `requestSeed` together with `deadline`, `fulfilled` and `refunded`. It reverts `NotReady` like `requestSeed`, so it is not a status read for waiting requests; use `getRequest`.
461
462
 
@@ -467,7 +468,7 @@ Selector `0xcf14de9d` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol
467
468
  function getPendingRequestIds(uint256 fromId, uint256 limit) external view returns (uint256[] ids, uint256 nextCursor)
468
469
  ```
469
470
 
470
- Selector `0xfdfe72e6` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 309–328
471
+ Selector `0xfdfe72e6` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 310–329
471
472
 
472
473
  Scans `limit` (1 to 256) request IDs from `fromId` (at least 1) and returns those not fulfilled, not refunded and not past their deadline, with the ID to continue from. Continue with `nextCursor` until it equals `nextRequestId()`. The answer can be stale by the time a transaction lands.
473
474
 
@@ -479,17 +480,17 @@ Views, callable by anyone. Nothing here has a setter except through an upgrade.
479
480
 
480
481
  | Function | Selector | Meaning | Source |
481
482
  | --- | --- | --- | --- |
482
- | <a id="coordinator-fn-lastservedrequestid"></a>`lastServedRequestId() returns (uint256)` | `0xef54e226` | ID of the most recently accepted request; 0 before the first. | lines 52, 424 |
483
- | <a id="coordinator-fn-lastservedindex"></a>`lastServedIndex() returns (uint256)` | `0x7e176eed` | Number of accepted requests so far: the `serveIndex` of the latest `RequestServed`. | lines 53, 425 |
484
- | <a id="coordinator-fn-servedrequestat"></a>`servedRequestAt(uint256) returns (uint256)` | `0xf9a4acc6` | Request ID accepted at a serve index (from 1); 0 for an index not used yet. | lines 54, 425 |
485
- | <a id="coordinator-fn-keyhash"></a>`keyHash() returns (bytes32)` | `0x61728f39` | `keccak256(abi.encode(publicKey))` of the VRF key; indexed in `RandomnessRequested` and `ProofVerified`. | lines 36, 179 |
486
- | <a id="coordinator-fn-publickeyx"></a>`publicKeyX() returns (uint256)` | `0xfa6df55d` | x coordinate of the VRF public key. | line 34 |
487
- | <a id="coordinator-fn-publickeyy"></a>`publicKeyY() returns (uint256)` | `0xd7a6f6e8` | y coordinate of the VRF public key. | line 35 |
488
- | <a id="coordinator-fn-confirmationblocks"></a>`confirmationBlocks() returns (uint16)` | `0x460a58aa` | Blocks after the target block before the seed and proofs become available (1 to 64, set at initialization). | lines 44, 555 |
489
- | <a id="coordinator-fn-epochregistry"></a>`epochRegistry() returns (address)` | `0x2b12cb69` | The `EpochEntropy` proxy that supplies epochs and the keeper-share recipient. | line 32 |
490
- | <a id="coordinator-fn-protocolconfigurationhash"></a>`protocolConfigurationHash() returns (bytes32)` | `0x155cf49b` | Hash of the initialized configuration (public key, initial fee recipient, initial minimum fee, confirmations, registry, initial catalog hash, first epoch start, epoch length 200) under `CONFIG_DOMAIN`. Bound into every transcript hash. | lines 31, 189 |
491
- | <a id="coordinator-fn-initialfeerecipient"></a>`initialFeeRecipient() returns (address)` | `0x308c2d6b` | Fee recipient given to `initialize`, used by replay. The live payout address is `feeRecipient()`. | lines 38, 181 |
492
- | <a id="coordinator-fn-initialminfee"></a>`initialMinFee() returns (uint256)` | `0xb3839295` | Minimum fee given to `initialize`, used by replay. The live minimum is `minFee()`. | lines 104, 184 |
483
+ | <a id="coordinator-fn-lastservedrequestid"></a>`lastServedRequestId() returns (uint256)` | `0xef54e226` | ID of the most recently accepted request; 0 before the first. | lines 53, 435 |
484
+ | <a id="coordinator-fn-lastservedindex"></a>`lastServedIndex() returns (uint256)` | `0x7e176eed` | Number of accepted requests so far: the `serveIndex` of the latest `RequestServed`. | lines 54, 436 |
485
+ | <a id="coordinator-fn-servedrequestat"></a>`servedRequestAt(uint256) returns (uint256)` | `0xf9a4acc6` | Request ID accepted at a serve index (from 1); 0 for an index not used yet. | lines 55, 436 |
486
+ | <a id="coordinator-fn-keyhash"></a>`keyHash() returns (bytes32)` | `0x61728f39` | `keccak256(abi.encode(publicKey))` of the VRF key; indexed in `RandomnessRequested` and `ProofVerified`. | lines 37, 180 |
487
+ | <a id="coordinator-fn-publickeyx"></a>`publicKeyX() returns (uint256)` | `0xfa6df55d` | x coordinate of the VRF public key. | line 35 |
488
+ | <a id="coordinator-fn-publickeyy"></a>`publicKeyY() returns (uint256)` | `0xd7a6f6e8` | y coordinate of the VRF public key. | line 36 |
489
+ | <a id="coordinator-fn-confirmationblocks"></a>`confirmationBlocks() returns (uint16)` | `0x460a58aa` | Blocks after the target block before the seed and proofs become available (1 to 64, set at initialization). | lines 45, 566 |
490
+ | <a id="coordinator-fn-epochregistry"></a>`epochRegistry() returns (address)` | `0x2b12cb69` | The `EpochEntropy` proxy that supplies epochs and the keeper-share recipient. | line 33 |
491
+ | <a id="coordinator-fn-protocolconfigurationhash"></a>`protocolConfigurationHash() returns (bytes32)` | `0x155cf49b` | Hash of the initialized configuration (public key, initial fee recipient, initial minimum fee, confirmations, registry, initial catalog hash, first epoch start, epoch length 200) under `CONFIG_DOMAIN`. Bound into every transcript hash. | lines 32, 190 |
492
+ | <a id="coordinator-fn-initialfeerecipient"></a>`initialFeeRecipient() returns (address)` | `0x308c2d6b` | Fee recipient given to `initialize`, used by replay. The live payout address is `feeRecipient()`. | lines 39, 182 |
493
+ | <a id="coordinator-fn-initialminfee"></a>`initialMinFee() returns (uint256)` | `0xb3839295` | Minimum fee given to `initialize`, used by replay. The live minimum is `minFee()`. | lines 105, 185 |
493
494
 
494
495
  ### <a id="coordinator-owner-administration"></a>Owner administration
495
496
 
@@ -501,7 +502,7 @@ Owner-only functions revert `OwnableUnauthorizedAccount` for anyone else. No set
501
502
  function setPricing(uint256 nextMinFee, uint16 multiplier, uint32 overhead) external
502
503
  ```
503
504
 
504
- Selector `0x4c729ce6` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 204–209
505
+ Selector `0x4c729ce6` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 205–210
505
506
 
506
507
  Sets `minFee` (at most `MAX_MIN_FEE`, 10 USDC), `feeMultiplier` (at most `MAX_FEE_MULTIPLIER`, 20) and `fulfillGasOverhead` (`MIN_FULFILL_GAS_OVERHEAD` to `MAX_FULFILL_GAS_OVERHEAD`, 100,000 to 2,000,000 gas). Affects requests created afterwards; open requests keep their escrowed fee.
507
508
 
@@ -515,7 +516,7 @@ Sets `minFee` (at most `MAX_MIN_FEE`, 10 USDC), `feeMultiplier` (at most `MAX_FE
515
516
  function setRefundBps(uint16 next) external
516
517
  ```
517
518
 
518
- Selector `0x55a94d1b` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 213–217
519
+ Selector `0x55a94d1b` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 214–218
519
520
 
520
521
  Sets the refund ratio for requests created afterwards, `MIN_REFUND_BPS` (5000) to 10000.
521
522
 
@@ -529,7 +530,7 @@ Sets the refund ratio for requests created afterwards, `MIN_REFUND_BPS` (5000) t
529
530
  function setKeeperFeeBps(uint16 next) external
530
531
  ```
531
532
 
532
- Selector `0xe140f0ca` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 200–203
533
+ Selector `0xe140f0ca` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 201–204
533
534
 
534
535
  Sets the keeper share, 0 to 10000 basis points. Read at each acceptance, so it also applies to open requests accepted later.
535
536
 
@@ -543,7 +544,7 @@ Sets the keeper share, 0 to 10000 basis points. Read at each acceptance, so it a
543
544
  function setFeeRecipient(address next) external
544
545
  ```
545
546
 
546
- Selector `0xe74b981b` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 196–199
547
+ Selector `0xe74b981b` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 197–200
547
548
 
548
549
  Sets the address allowed to withdraw `earnedFees`, including fees earned before the change. The zero address is rejected.
549
550
 
@@ -605,7 +606,7 @@ Completes the transfer to the caller and clears the nomination.
605
606
  function renounceOwnership() external view
606
607
  ```
607
608
 
608
- Selector `0x715018a6` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 193–194
609
+ Selector `0x715018a6` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 194–195
609
610
 
610
611
  Disabled and declared `view`: the owner gets `RenounceDisabled` and anyone else `OwnableUnauthorizedAccount`, so the contract always has an owner and upgrade authority can only move through an accepted transfer.
611
612
 
@@ -643,7 +644,7 @@ ERC-1822 check used by `upgradeToAndCall`. Returns the ERC-1967 implementation s
643
644
  function initialize(uint256[2] publicKey, address initialOwner, address recipient, uint256 fee, uint16 confirmations, address registry, uint16 keeperBps) external
644
645
  ```
645
646
 
646
- Selector `0x56b95b47` · Caller: Once, by `D20Proxy` at deployment · Source: `D20VRFCoordinator.sol` lines 169–190
647
+ Selector `0x56b95b47` · Caller: Once, by `D20Proxy` at deployment · Source: `D20VRFCoordinator.sol` lines 170–191
647
648
 
648
649
  Sets owner, VRF public key, fee recipient, minimum fee, confirmations, registry and keeper share, with `feeMultiplier` 5, `fulfillGasOverhead` 300,000 and `refundBps` 10000. A public key that is not on the curve can also revert with an `Error(string)` from the verifier.
649
650
 
@@ -683,7 +684,7 @@ Views returning values fixed in the implementation code.
683
684
  event RandomnessRequested(uint256 indexed requestId, address indexed consumer, bytes32 indexed keyHash, bytes32 clientSeed, uint64 requestBlock, uint32 callbackGasLimit, uint256 feePaid, address refundAddress, uint64 deadline)
684
685
  ```
685
686
 
686
- Topic 0 `0xaf91b17376114a36689aa115062983bda7b43263a891fb8de0cc69d30d4240ad` · Emitted by: [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness) · Source: `D20VRFCoordinator.sol` lines 138–142, 283–284
687
+ Topic 0 `0xaf91b17376114a36689aa115062983bda7b43263a891fb8de0cc69d30d4240ad` · Emitted by: [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness) · Source: `D20VRFCoordinator.sol` lines 139–143, 284–285
687
688
 
688
689
  A request was created. `feePaid` is the escrowed fee, not `msg.value`; `deadline` is the block timestamp plus 60 seconds. Read `requestId` from this log in the request receipt, filtering by the coordinator address and event name: with an overpayment, `FeeOverpaymentCredited` comes first.
689
690
 
@@ -693,7 +694,7 @@ A request was created. `feePaid` is the escrowed fee, not `msg.value`; `deadline
693
694
  event MappingRequested(uint256 indexed requestId, bytes32 indexed mappingHash, RandomnessMapping.Spec spec)
694
695
  ```
695
696
 
696
- Topic 0 `0xbe1c93f40bd74ff9acd22dc40818e36d04e0b8a49b8238d0537c63219c2336dd` · Emitted by: [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness) · Source: `D20VRFCoordinator.sol` lines 157, 285
697
+ Topic 0 `0xbe1c93f40bd74ff9acd22dc40818e36d04e0b8a49b8238d0537c63219c2336dd` · Emitted by: [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness) · Source: `D20VRFCoordinator.sol` lines 158, 286
697
698
 
698
699
  Emitted right after `RandomnessRequested` with the stored spec (all zero for a raw request) and its hash.
699
700
 
@@ -703,7 +704,7 @@ Emitted right after `RandomnessRequested` with the stored spec (all zero for a r
703
704
  event FeeOverpaymentCredited(uint256 indexed requestId, address indexed refundAddress, uint256 amount)
704
705
  ```
705
706
 
706
- Topic 0 `0x8ae693db98f043f48e8f427375449ed5576aba97575e4f7f93ff2c1f6c75dcb5` · Emitted by: [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness) · Source: `D20VRFCoordinator.sol` lines 151, 277–282
707
+ Topic 0 `0x8ae693db98f043f48e8f427375449ed5576aba97575e4f7f93ff2c1f6c75dcb5` · Emitted by: [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness) · Source: `D20VRFCoordinator.sol` lines 152, 278–283
707
708
 
708
709
  `msg.value` exceeded the fee and `amount` was added to `refundCredits(refundAddress)`, independently of what happens to the request. Emitted before `RandomnessRequested`.
709
710
 
@@ -713,7 +714,7 @@ Topic 0 `0x8ae693db98f043f48e8f427375449ed5576aba97575e4f7f93ff2c1f6c75dcb5` ·
713
714
  event BlockHashStored(uint256 indexed requestId, uint64 targetBlock, bytes32 blockHash)
714
715
  ```
715
716
 
716
- Topic 0 `0x81bc3b4ec75af0fb9ed3521d7c766d8f995d04b0735c17d61ff9d468b0f04911` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch), [`storeBlockHash`](#coordinator-fn-storeblockhash) · Source: `D20VRFCoordinator.sol` lines 143, 561–568
717
+ Topic 0 `0x81bc3b4ec75af0fb9ed3521d7c766d8f995d04b0735c17d61ff9d468b0f04911` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch), [`storeBlockHash`](#coordinator-fn-storeblockhash) · Source: `D20VRFCoordinator.sol` lines 144, 572–579
717
718
 
718
719
  The target block hash of the request was stored. Emitted once per request: by `storeBlockHash`, or by fulfillment if the hash was not stored before.
719
720
 
@@ -723,7 +724,7 @@ The target block hash of the request was stored. Emitted once per request: by `s
723
724
  event RequestServed(uint256 indexed requestId, uint256 indexed serveIndex)
724
725
  ```
725
726
 
726
- Topic 0 `0x2012511e6cebd578bcabff1ef3346edb032cbab8622e9f23d9f15d7d1037267f` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 159, 424–426
727
+ Topic 0 `0x2012511e6cebd578bcabff1ef3346edb032cbab8622e9f23d9f15d7d1037267f` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 160, 435–437
727
728
 
728
729
  A proof was accepted. `serveIndex` counts accepted requests from 1 (`lastServedIndex`, `servedRequestAt`).
729
730
 
@@ -733,7 +734,7 @@ A proof was accepted. `serveIndex` counts accepted requests from 1 (`lastServedI
733
734
  event ProofVerified(uint256 indexed requestId, bytes32 indexed keyHash, uint256 seed, bytes32 proofHash)
734
735
  ```
735
736
 
736
- Topic 0 `0x55bb25be3ecd9f68ceae7cdabf4eabe2e0940bd8fc1c26c0d68ad5f7c5d08d22` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 158, 427
737
+ Topic 0 `0x55bb25be3ecd9f68ceae7cdabf4eabe2e0940bd8fc1c26c0d68ad5f7c5d08d22` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 159, 438
737
738
 
738
739
  Seed and hash of the accepted proof.
739
740
 
@@ -743,9 +744,9 @@ Seed and hash of the accepted proof.
743
744
  event RandomnessFulfilled(uint256 indexed requestId, bytes32 randomness, address indexed submitter)
744
745
  ```
745
746
 
746
- Topic 0 `0x9c82683ee7932041c254d206bcce4241d66a811d53ee7191799cc120777b2b87` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 144, 428
747
+ Topic 0 `0x9c82683ee7932041c254d206bcce4241d66a811d53ee7191799cc120777b2b87` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 145, 439
747
748
 
748
- A proof was accepted and `randomness` is final. `submitter` sent the transaction and is not paid for it.
749
+ A proof was accepted and `randomness` is final. `submitter` sent the transaction; `KeeperFeePaid` names the wallet that received the keeper share, which is `submitter` only when the registry authorizes it.
749
750
 
750
751
  #### <a id="coordinator-event-fulfillmentevidence"></a>`FulfillmentEvidence`
751
752
 
@@ -753,7 +754,7 @@ A proof was accepted and `randomness` is final. `submitter` sent the transaction
753
754
  event FulfillmentEvidence(uint256 indexed requestId, bytes32 indexed transcriptHash, bytes packet)
754
755
  ```
755
756
 
756
- Topic 0 `0xa121bbea897439460dfb08c3e6d6af064bc1f31e9477471828a87c5596b92e77` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 162–163, 439–443
757
+ Topic 0 `0xa121bbea897439460dfb08c3e6d6af064bc1f31e9477471828a87c5596b92e77` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 163–164, 450–454
757
758
 
758
759
  The accepted proof as a 416-byte ABI-encoded packet, indexed by `transcriptHash`. Decode it with `decodeEvidencePacket`; take evidence from this log, not from calldata, since a batch carries several proofs.
759
760
 
@@ -763,7 +764,7 @@ The accepted proof as a 416-byte ABI-encoded packet, indexed by `transcriptHash`
763
764
  event CallbackAttempted(uint256 indexed requestId, bool success, uint32 gasLimit)
764
765
  ```
765
766
 
766
- Topic 0 `0x70f64c0739e827900ae6f2e1317601653f4080bc857f423671fc58d5822f1f4a` · Emitted by: [`retryCallback`](#coordinator-fn-retrycallback), [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 145, 602–619
767
+ Topic 0 `0x70f64c0739e827900ae6f2e1317601653f4080bc857f423671fc58d5822f1f4a` · Emitted by: [`retryCallback`](#coordinator-fn-retrycallback), [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 146, 613–630
767
768
 
768
769
  Result of calling `rawFulfillRandomness` with `gasLimit` gas, at fulfillment and at each `retryCallback`. `success` false means the consumer reverted, ran out of gas or has no code; the word is accepted either way.
769
770
 
@@ -773,9 +774,9 @@ Result of calling `rawFulfillRandomness` with `gasLimit` gas, at fulfillment and
773
774
  event KeeperFeePaid(uint256 indexed requestId, address indexed keeper, uint256 amount, bool paid)
774
775
  ```
775
776
 
776
- Topic 0 `0x7605929b04963e0365f647d9ab12e7ac4aeba5474bb80f1e1554fccad0584683` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 152, 431–436
777
+ Topic 0 `0x7605929b04963e0365f647d9ab12e7ac4aeba5474bb80f1e1554fccad0584683` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 153, 442–447
777
778
 
778
- At acceptance, when the keeper share is non-zero: `amount` went to `keeper`, the registry committer, by a 30,000-gas transfer (`paid` true) or was added to `keeperCredits(keeper)` (`paid` false). Emitted after `CallbackAttempted`.
779
+ At acceptance, when the keeper share is non-zero: `amount` went to `keeper`, the submitter when the registry authorizes it and `committer()` otherwise, by a 30,000-gas transfer (`paid` true) or was added to `keeperCredits(keeper)` (`paid` false). Emitted after `CallbackAttempted`.
779
780
 
780
781
  #### <a id="coordinator-event-fulfillmentskipped"></a>`FulfillmentSkipped`
781
782
 
@@ -783,7 +784,7 @@ At acceptance, when the keeper share is non-zero: `amount` went to `keeper`, the
783
784
  event FulfillmentSkipped(uint256 indexed requestId, uint8 reason)
784
785
  ```
785
786
 
786
- Topic 0 `0x45d96bda73a91db41bdeab56114e5d7b9f42c9f38add9e2d2c6d6f5761203ca3` · Emitted by: [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 160–161, 406–407
787
+ Topic 0 `0x45d96bda73a91db41bdeab56114e5d7b9f42c9f38add9e2d2c6d6f5761203ca3` · Emitted by: [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 161–162, 407–408
787
788
 
788
789
  A batch member was left untouched: `reason` 1 already fulfilled, 2 refunded, 3 past its deadline.
789
790
 
@@ -793,7 +794,7 @@ A batch member was left untouched: `reason` 1 already fulfilled, 2 refunded, 3 p
793
794
  event RequestRefundedTo(uint256 indexed requestId, address indexed refundAddress, uint256 amount, bool paid)
794
795
  ```
795
796
 
796
- Topic 0 `0x0f6107d218fea62a20553f3700dba7c94dcf653bd2027c0bf1ebe0832f42a506` · Emitted by: [`refundRequest`](#coordinator-fn-refundrequest) · Source: `D20VRFCoordinator.sol` lines 154, 477
797
+ Topic 0 `0x0f6107d218fea62a20553f3700dba7c94dcf653bd2027c0bf1ebe0832f42a506` · Emitted by: [`refundRequest`](#coordinator-fn-refundrequest) · Source: `D20VRFCoordinator.sol` lines 155, 488
797
798
 
798
799
  An expired request was refunded: `amount` (`feePaid × requestRefundBps / 10000`) was sent to `refundAddress` (`paid` true) or added to its refund credit (`paid` false).
799
800
 
@@ -803,7 +804,7 @@ An expired request was refunded: `amount` (`feePaid × requestRefundBps / 10000`
803
804
  event RefundCallbackAttempted(uint256 indexed requestId, address indexed consumer, bool success, uint32 gasLimit)
804
805
  ```
805
806
 
806
- Topic 0 `0x88448c9fbcfc67f28f0266e82766e402ccd28115fbb84edb6e5b2597effe83d8` · Emitted by: [`refundRequest`](#coordinator-fn-refundrequest), [`retryRefundCallback`](#coordinator-fn-retryrefundcallback) · Source: `D20VRFCoordinator.sol` lines 156, 491–502
807
+ Topic 0 `0x88448c9fbcfc67f28f0266e82766e402ccd28115fbb84edb6e5b2597effe83d8` · Emitted by: [`refundRequest`](#coordinator-fn-refundrequest), [`retryRefundCallback`](#coordinator-fn-retryrefundcallback) · Source: `D20VRFCoordinator.sol` lines 157, 502–513
807
808
 
808
809
  Result of calling `onRefund(requestId)` on `consumer` with `gasLimit` gas: 100,000 at `refundRequest`, the caller's limit at `retryRefundCallback`.
809
810
 
@@ -815,7 +816,7 @@ Result of calling `onRefund(requestId)` on `consumer` with `gasLimit` gas: 100,0
815
816
  event RefundCreditWithdrawn(address indexed owner, address indexed recipient, uint256 amount)
816
817
  ```
817
818
 
818
- Topic 0 `0x9d520065b24fda0469128acd3f3078de7e43d70ab762aeea8c741bde25070192` · Emitted by: [`withdrawRefundCredit`](#coordinator-fn-withdrawrefundcredit) · Source: `D20VRFCoordinator.sol` lines 155, 513
819
+ Topic 0 `0x9d520065b24fda0469128acd3f3078de7e43d70ab762aeea8c741bde25070192` · Emitted by: [`withdrawRefundCredit`](#coordinator-fn-withdrawrefundcredit) · Source: `D20VRFCoordinator.sol` lines 156, 524
819
820
 
820
821
  `owner`, the credit holder (not the contract owner), withdrew `amount` of refund credit to `recipient`.
821
822
 
@@ -825,7 +826,7 @@ Topic 0 `0x9d520065b24fda0469128acd3f3078de7e43d70ab762aeea8c741bde25070192` ·
825
826
  event KeeperCreditWithdrawn(address indexed keeper, address indexed recipient, uint256 amount)
826
827
  ```
827
828
 
828
- Topic 0 `0x22f05c41968705c032a86a65f8fda7e64483ea5e6b6b27a920d1fbfab94267ba` · Emitted by: [`withdrawKeeperCredit`](#coordinator-fn-withdrawkeepercredit) · Source: `D20VRFCoordinator.sol` lines 153, 535
829
+ Topic 0 `0x22f05c41968705c032a86a65f8fda7e64483ea5e6b6b27a920d1fbfab94267ba` · Emitted by: [`withdrawKeeperCredit`](#coordinator-fn-withdrawkeepercredit) · Source: `D20VRFCoordinator.sol` lines 154, 546
829
830
 
830
831
  `keeper` withdrew `amount` of keeper credit to `recipient`.
831
832
 
@@ -835,7 +836,7 @@ Topic 0 `0x22f05c41968705c032a86a65f8fda7e64483ea5e6b6b27a920d1fbfab94267ba` ·
835
836
  event FeesWithdrawn(address indexed recipient, uint256 amount)
836
837
  ```
837
838
 
838
- Topic 0 `0xc0819c13be868895eb93e40eaceb96de976442fa1d404e5c55f14bb65a8c489a` · Emitted by: [`withdrawFees`](#coordinator-fn-withdrawfees) · Source: `D20VRFCoordinator.sol` lines 146, 524
839
+ Topic 0 `0xc0819c13be868895eb93e40eaceb96de976442fa1d404e5c55f14bb65a8c489a` · Emitted by: [`withdrawFees`](#coordinator-fn-withdrawfees) · Source: `D20VRFCoordinator.sol` lines 147, 535
839
840
 
840
841
  The fee recipient withdrew `amount` of earned fees to `recipient`.
841
842
 
@@ -847,7 +848,7 @@ The fee recipient withdrew `amount` of earned fees to `recipient`.
847
848
  event PricingChanged(uint256 minFee, uint16 feeMultiplier, uint32 fulfillGasOverhead)
848
849
  ```
849
850
 
850
- Topic 0 `0x32806eb5e21ac2f5fb7d11f898c2995e19fdf203c8a5aeed8b824506cd0d44ff` · Emitted by: [`setPricing`](#coordinator-fn-setpricing) · Source: `D20VRFCoordinator.sol` lines 149, 208
851
+ Topic 0 `0x32806eb5e21ac2f5fb7d11f898c2995e19fdf203c8a5aeed8b824506cd0d44ff` · Emitted by: [`setPricing`](#coordinator-fn-setpricing) · Source: `D20VRFCoordinator.sol` lines 150, 209
851
852
 
852
853
  New `minFee`, `feeMultiplier` and `fulfillGasOverhead` for requests created afterwards.
853
854
 
@@ -857,7 +858,7 @@ New `minFee`, `feeMultiplier` and `fulfillGasOverhead` for requests created afte
857
858
  event RefundBpsChanged(uint16 previousBps, uint16 newBps)
858
859
  ```
859
860
 
860
- Topic 0 `0x21e3c4cf3007c4ee385a3936593ff4cfa23fc17bca1175b6350c546f9810e0d8` · Emitted by: [`setRefundBps`](#coordinator-fn-setrefundbps) · Source: `D20VRFCoordinator.sol` lines 150, 216
861
+ Topic 0 `0x21e3c4cf3007c4ee385a3936593ff4cfa23fc17bca1175b6350c546f9810e0d8` · Emitted by: [`setRefundBps`](#coordinator-fn-setrefundbps) · Source: `D20VRFCoordinator.sol` lines 151, 217
861
862
 
862
863
  New refund ratio for requests created afterwards.
863
864
 
@@ -867,7 +868,7 @@ New refund ratio for requests created afterwards.
867
868
  event KeeperFeeBpsChanged(uint16 previousBps, uint16 newBps)
868
869
  ```
869
870
 
870
- Topic 0 `0xa648a60f1d22511c1cc898ca69b633d1a1114079e83734b9ab9a13e0e28c68b7` · Emitted by: [`setKeeperFeeBps`](#coordinator-fn-setkeeperfeebps) · Source: `D20VRFCoordinator.sol` lines 148, 202
871
+ Topic 0 `0xa648a60f1d22511c1cc898ca69b633d1a1114079e83734b9ab9a13e0e28c68b7` · Emitted by: [`setKeeperFeeBps`](#coordinator-fn-setkeeperfeebps) · Source: `D20VRFCoordinator.sol` lines 149, 203
871
872
 
872
873
  New keeper share, applied at later acceptances, including of requests already open.
873
874
 
@@ -877,7 +878,7 @@ New keeper share, applied at later acceptances, including of requests already op
877
878
  event FeeRecipientChanged(address indexed previousRecipient, address indexed newRecipient)
878
879
  ```
879
880
 
880
- Topic 0 `0x0bc21fe5c3ab742ff1d15b5c4477ffbacf1167e618228078fa625edebe7f331d` · Emitted by: [`setFeeRecipient`](#coordinator-fn-setfeerecipient) · Source: `D20VRFCoordinator.sol` lines 147, 198
881
+ Topic 0 `0x0bc21fe5c3ab742ff1d15b5c4477ffbacf1167e618228078fa625edebe7f331d` · Emitted by: [`setFeeRecipient`](#coordinator-fn-setfeerecipient) · Source: `D20VRFCoordinator.sol` lines 148, 199
881
882
 
882
883
  New address allowed to withdraw earned fees.
883
884
 
@@ -927,7 +928,7 @@ Topic 0 `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` ·
927
928
 
928
929
  #### <a id="coordinator-error-contractconsumerrequired"></a>`ContractConsumerRequired`
929
930
 
930
- `error ContractConsumerRequired()` · Selector `0x2b99db1e` · Source: `D20VRFCoordinator.sol` lines 110, 250
931
+ `error ContractConsumerRequired()` · Selector `0x2b99db1e` · Source: `D20VRFCoordinator.sol` lines 111, 251
931
932
 
932
933
  **Raised by:** [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness).
933
934
 
@@ -937,7 +938,7 @@ The caller of a request function has no code: an externally owned account, or a
937
938
 
938
939
  #### <a id="coordinator-error-invalidrefundaddress"></a>`InvalidRefundAddress`
939
940
 
940
- `error InvalidRefundAddress()` · Selector `0xe2fe2726` · Source: `D20VRFCoordinator.sol` lines 124, 251, 506
941
+ `error InvalidRefundAddress()` · Selector `0xe2fe2726` · Source: `D20VRFCoordinator.sol` lines 125, 252, 517
941
942
 
942
943
  **Raised by:** [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness), [`withdrawRefundCredit`](#coordinator-fn-withdrawrefundcredit).
943
944
 
@@ -947,7 +948,7 @@ A request named the zero address as refund address, or `withdrawRefundCredit` na
947
948
 
948
949
  #### <a id="coordinator-error-invalidcallbackgas"></a>`InvalidCallbackGas`
949
950
 
950
- `error InvalidCallbackGas()` · Selector `0x35883c54` · Source: `D20VRFCoordinator.sol` lines 112, 451, 487, 598–600
951
+ `error InvalidCallbackGas()` · Selector `0x35883c54` · Source: `D20VRFCoordinator.sol` lines 113, 462, 498, 609–611
951
952
 
952
953
  **Raised by:** [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness), [`retryCallback`](#coordinator-fn-retrycallback), [`retryRefundCallback`](#coordinator-fn-retryrefundcallback).
953
954
 
@@ -957,7 +958,7 @@ A gas limit is out of range: a request `callbackGasLimit` outside 30,000 to 1,00
957
958
 
958
959
  #### <a id="coordinator-error-incorrectfee"></a>`IncorrectFee`
959
960
 
960
- `error IncorrectFee(uint256 expected, uint256 actual)` · Selector `0xdcf6afcb` · Source: `D20VRFCoordinator.sol` lines 111, 254
961
+ `error IncorrectFee(uint256 expected, uint256 actual)` · Selector `0xdcf6afcb` · Source: `D20VRFCoordinator.sol` lines 112, 255
961
962
 
962
963
  **Raised by:** [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness).
963
964
 
@@ -967,7 +968,7 @@ A gas limit is out of range: a request `callbackGasLimit` outside 30,000 to 1,00
967
968
 
968
969
  #### <a id="coordinator-error-feeoverflow"></a>`FeeOverflow`
969
970
 
970
- `error FeeOverflow()` · Selector `0x8181adca` · Source: `D20VRFCoordinator.sol` lines 135, 224
971
+ `error FeeOverflow()` · Selector `0x8181adca` · Source: `D20VRFCoordinator.sol` lines 136, 225
971
972
 
972
973
  **Raised by:** [`quoteFee`](#coordinator-fn-quotefee), [`quoteFeeAt`](#coordinator-fn-quotefeeat), [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness).
973
974
 
@@ -987,7 +988,7 @@ The spec breaks the rules for its operation (README [Randomness options](README.
987
988
 
988
989
  #### <a id="coordinator-error-epochunavailable"></a>`EpochUnavailable`
989
990
 
990
- `error EpochUnavailable()` · Selector `0x0b3487b8` · Source: `D20VRFCoordinator.sol` lines 108, 258
991
+ `error EpochUnavailable()` · Selector `0x0b3487b8` · Source: `D20VRFCoordinator.sol` lines 109, 259
991
992
 
992
993
  **Raised by:** [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness).
993
994
 
@@ -999,7 +1000,7 @@ The request block is before the registry's first epoch: `epochForBlock(block.num
999
1000
 
1000
1001
  #### <a id="coordinator-error-unknownrequest"></a>`UnknownRequest`
1001
1002
 
1002
- `error UnknownRequest()` · Selector `0x6d080297` · Source: `D20VRFCoordinator.sol` lines 113, 538–541
1003
+ `error UnknownRequest()` · Selector `0x6d080297` · Source: `D20VRFCoordinator.sol` lines 114, 549–552
1003
1004
 
1004
1005
  **Raised by:** [`getRequest`](#coordinator-fn-getrequest), [`getMapping`](#coordinator-fn-getmapping), [`getMappedResult`](#coordinator-fn-getmappedresult), [`requestFeePaid`](#coordinator-fn-requestfeepaid), [`requestRefundBps`](#coordinator-fn-requestrefundbps), [`refundRequest`](#coordinator-fn-refundrequest), [`retryCallback`](#coordinator-fn-retrycallback), [`retryRefundCallback`](#coordinator-fn-retryrefundcallback), [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch), [`storeBlockHash`](#coordinator-fn-storeblockhash), [`verifyRequestProof`](#coordinator-fn-verifyrequestproof), [`requestSeed`](#coordinator-fn-requestseed), [`getProofContext`](#coordinator-fn-getproofcontext).
1005
1006
 
@@ -1009,7 +1010,7 @@ No request has this ID: 0, or not below `nextRequestId()`. An unknown ID also re
1009
1010
 
1010
1011
  #### <a id="coordinator-error-notfulfilled"></a>`NotFulfilled`
1011
1012
 
1012
- `error NotFulfilled()` · Selector `0x07bc6c3e` · Source: `D20VRFCoordinator.sol` lines 117, 346, 448
1013
+ `error NotFulfilled()` · Selector `0x07bc6c3e` · Source: `D20VRFCoordinator.sol` lines 118, 347, 459
1013
1014
 
1014
1015
  **Raised by:** [`getMappedResult`](#coordinator-fn-getmappedresult), [`retryCallback`](#coordinator-fn-retrycallback).
1015
1016
 
@@ -1019,7 +1020,7 @@ No request has this ID: 0, or not below `nextRequestId()`. An unknown ID also re
1019
1020
 
1020
1021
  #### <a id="coordinator-error-alreadydelivered"></a>`AlreadyDelivered`
1021
1022
 
1022
- `error AlreadyDelivered()` · Selector `0xb9f79653` · Source: `D20VRFCoordinator.sol` lines 118, 449
1023
+ `error AlreadyDelivered()` · Selector `0xb9f79653` · Source: `D20VRFCoordinator.sol` lines 119, 460
1023
1024
 
1024
1025
  **Raised by:** [`retryCallback`](#coordinator-fn-retrycallback).
1025
1026
 
@@ -1029,7 +1030,7 @@ No request has this ID: 0, or not below `nextRequestId()`. An unknown ID also re
1029
1030
 
1030
1031
  #### <a id="coordinator-error-refundnotavailable"></a>`RefundNotAvailable`
1031
1032
 
1032
- `error RefundNotAvailable()` · Selector `0x0b4d6981` · Source: `D20VRFCoordinator.sol` lines 127, 460
1033
+ `error RefundNotAvailable()` · Selector `0x0b4d6981` · Source: `D20VRFCoordinator.sol` lines 128, 471
1033
1034
 
1034
1035
  **Raised by:** [`refundRequest`](#coordinator-fn-refundrequest).
1035
1036
 
@@ -1039,7 +1040,7 @@ No request has this ID: 0, or not below `nextRequestId()`. An unknown ID also re
1039
1040
 
1040
1041
  #### <a id="coordinator-error-notrefunded"></a>`NotRefunded`
1041
1042
 
1042
- `error NotRefunded()` · Selector `0xfae7079c` · Source: `D20VRFCoordinator.sol` lines 130, 484
1043
+ `error NotRefunded()` · Selector `0xfae7079c` · Source: `D20VRFCoordinator.sol` lines 131, 495
1043
1044
 
1044
1045
  **Raised by:** [`retryRefundCallback`](#coordinator-fn-retryrefundcallback).
1045
1046
 
@@ -1049,7 +1050,7 @@ No request has this ID: 0, or not below `nextRequestId()`. An unknown ID also re
1049
1050
 
1050
1051
  #### <a id="coordinator-error-refundcallbackalreadydelivered"></a>`RefundCallbackAlreadyDelivered`
1051
1052
 
1052
- `error RefundCallbackAlreadyDelivered()` · Selector `0x6502f8ae` · Source: `D20VRFCoordinator.sol` lines 131, 485
1053
+ `error RefundCallbackAlreadyDelivered()` · Selector `0x6502f8ae` · Source: `D20VRFCoordinator.sol` lines 132, 496
1053
1054
 
1054
1055
  **Raised by:** [`retryRefundCallback`](#coordinator-fn-retryrefundcallback).
1055
1056
 
@@ -1059,7 +1060,7 @@ No request has this ID: 0, or not below `nextRequestId()`. An unknown ID also re
1059
1060
 
1060
1061
  #### <a id="coordinator-error-insufficientcallbackgas"></a>`InsufficientCallbackGas`
1061
1062
 
1062
- `error InsufficientCallbackGas()` · Selector `0xa2c23f0d` · Source: `D20VRFCoordinator.sol` lines 121, 469, 494, 610–611
1063
+ `error InsufficientCallbackGas()` · Selector `0xa2c23f0d` · Source: `D20VRFCoordinator.sol` lines 122, 480, 505, 621–622
1063
1064
 
1064
1065
  **Raised by:** [`refundRequest`](#coordinator-fn-refundrequest), [`retryCallback`](#coordinator-fn-retrycallback), [`retryRefundCallback`](#coordinator-fn-retryrefundcallback), [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch).
1065
1066
 
@@ -1071,7 +1072,7 @@ Too little gas remained to forward the full callback budget and keep the coordin
1071
1072
 
1072
1073
  #### <a id="coordinator-error-norefundcredit"></a>`NoRefundCredit`
1073
1074
 
1074
- `error NoRefundCredit()` · Selector `0x1d59da8e` · Source: `D20VRFCoordinator.sol` lines 128, 508
1075
+ `error NoRefundCredit()` · Selector `0x1d59da8e` · Source: `D20VRFCoordinator.sol` lines 129, 519
1075
1076
 
1076
1077
  **Raised by:** [`withdrawRefundCredit`](#coordinator-fn-withdrawrefundcredit).
1077
1078
 
@@ -1081,7 +1082,7 @@ Too little gas remained to forward the full callback budget and keep the coordin
1081
1082
 
1082
1083
  #### <a id="coordinator-error-transferfailed"></a>`TransferFailed`
1083
1084
 
1084
- `error TransferFailed()` · Selector `0x90b8ec18` · Source: `D20VRFCoordinator.sol` lines 123, 512, 523, 534
1085
+ `error TransferFailed()` · Selector `0x90b8ec18` · Source: `D20VRFCoordinator.sol` lines 124, 523, 534, 545
1085
1086
 
1086
1087
  **Raised by:** [`withdrawRefundCredit`](#coordinator-fn-withdrawrefundcredit), [`withdrawFees`](#coordinator-fn-withdrawfees), [`withdrawKeeperCredit`](#coordinator-fn-withdrawkeepercredit).
1087
1088
 
@@ -1091,7 +1092,7 @@ The `recipient` of `withdrawRefundCredit`, `withdrawFees` or `withdrawKeeperCred
1091
1092
 
1092
1093
  #### <a id="coordinator-error-onlyfeerecipient"></a>`OnlyFeeRecipient`
1093
1094
 
1094
- `error OnlyFeeRecipient()` · Selector `0x07d8ed3d` · Source: `D20VRFCoordinator.sol` lines 122, 518
1095
+ `error OnlyFeeRecipient()` · Selector `0x07d8ed3d` · Source: `D20VRFCoordinator.sol` lines 123, 529
1095
1096
 
1096
1097
  **Raised by:** [`withdrawFees`](#coordinator-fn-withdrawfees).
1097
1098
 
@@ -1101,7 +1102,7 @@ The `recipient` of `withdrawRefundCredit`, `withdrawFees` or `withdrawKeeperCred
1101
1102
 
1102
1103
  #### <a id="coordinator-error-nokeepercredit"></a>`NoKeeperCredit`
1103
1104
 
1104
- `error NoKeeperCredit()` · Selector `0x0d106640` · Source: `D20VRFCoordinator.sol` lines 129, 530
1105
+ `error NoKeeperCredit()` · Selector `0x0d106640` · Source: `D20VRFCoordinator.sol` lines 130, 541
1105
1106
 
1106
1107
  **Raised by:** [`withdrawKeeperCredit`](#coordinator-fn-withdrawkeepercredit).
1107
1108
 
@@ -1113,7 +1114,7 @@ The `recipient` of `withdrawRefundCredit`, `withdrawFees` or `withdrawKeeperCred
1113
1114
 
1114
1115
  #### <a id="coordinator-error-notready"></a>`NotReady`
1115
1116
 
1116
- `error NotReady()` · Selector `0x9488aaa6` · Source: `D20VRFCoordinator.sol` lines 114, 555
1117
+ `error NotReady()` · Selector `0x9488aaa6` · Source: `D20VRFCoordinator.sol` lines 115, 566
1117
1118
 
1118
1119
  **Raised by:** [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch), [`storeBlockHash`](#coordinator-fn-storeblockhash), [`verifyRequestProof`](#coordinator-fn-verifyrequestproof), [`requestSeed`](#coordinator-fn-requestseed), [`getProofContext`](#coordinator-fn-getproofcontext).
1119
1120
 
@@ -1123,7 +1124,7 @@ The request cannot be proven yet: its epoch packet is not published, or `block.n
1123
1124
 
1124
1125
  #### <a id="coordinator-error-blockhashunavailable"></a>`BlockHashUnavailable`
1125
1126
 
1126
- `error BlockHashUnavailable()` · Selector `0xbfc9f0d3` · Source: `D20VRFCoordinator.sol` lines 115, 558
1127
+ `error BlockHashUnavailable()` · Selector `0xbfc9f0d3` · Source: `D20VRFCoordinator.sol` lines 116, 569
1127
1128
 
1128
1129
  **Raised by:** [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch), [`storeBlockHash`](#coordinator-fn-storeblockhash), [`verifyRequestProof`](#coordinator-fn-verifyrequestproof), [`requestSeed`](#coordinator-fn-requestseed), [`getProofContext`](#coordinator-fn-getproofcontext).
1129
1130
 
@@ -1133,7 +1134,7 @@ The target block hash was never stored and is outside the 256-block `BLOCKHASH`
1133
1134
 
1134
1135
  #### <a id="coordinator-error-alreadyfulfilled"></a>`AlreadyFulfilled`
1135
1136
 
1136
- `error AlreadyFulfilled()` · Selector `0x4a4117f9` · Source: `D20VRFCoordinator.sol` lines 116, 392
1137
+ `error AlreadyFulfilled()` · Selector `0x4a4117f9` · Source: `D20VRFCoordinator.sol` lines 117, 393
1137
1138
 
1138
1139
  **Raised by:** [`fulfillRandomness`](#coordinator-fn-fulfillrandomness).
1139
1140
 
@@ -1143,7 +1144,7 @@ The target block hash was never stored and is outside the 256-block `BLOCKHASH`
1143
1144
 
1144
1145
  #### <a id="coordinator-error-requestrefunded"></a>`RequestRefunded`
1145
1146
 
1146
- `error RequestRefunded()` · Selector `0xe0dec416` · Source: `D20VRFCoordinator.sol` lines 126, 393
1147
+ `error RequestRefunded()` · Selector `0xe0dec416` · Source: `D20VRFCoordinator.sol` lines 127, 394
1147
1148
 
1148
1149
  **Raised by:** [`fulfillRandomness`](#coordinator-fn-fulfillrandomness).
1149
1150
 
@@ -1153,7 +1154,7 @@ The target block hash was never stored and is outside the 256-block `BLOCKHASH`
1153
1154
 
1154
1155
  #### <a id="coordinator-error-requestexpired"></a>`RequestExpired`
1155
1156
 
1156
- `error RequestExpired()` · Selector `0xfef01cd2` · Source: `D20VRFCoordinator.sol` lines 125, 394
1157
+ `error RequestExpired()` · Selector `0xfef01cd2` · Source: `D20VRFCoordinator.sol` lines 126, 395
1157
1158
 
1158
1159
  **Raised by:** [`fulfillRandomness`](#coordinator-fn-fulfillrandomness).
1159
1160
 
@@ -1163,7 +1164,7 @@ The target block hash was never stored and is outside the 256-block `BLOCKHASH`
1163
1164
 
1164
1165
  #### <a id="coordinator-error-wrongpublickey"></a>`WrongPublicKey`
1165
1166
 
1166
- `error WrongPublicKey()` · Selector `0x2b0bb68e` · Source: `D20VRFCoordinator.sol` lines 119, 583
1167
+ `error WrongPublicKey()` · Selector `0x2b0bb68e` · Source: `D20VRFCoordinator.sol` lines 120, 594
1167
1168
 
1168
1169
  **Raised by:** [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch), [`verifyRequestProof`](#coordinator-fn-verifyrequestproof).
1169
1170
 
@@ -1173,7 +1174,7 @@ The proof's `pk` is not the coordinator's VRF key.
1173
1174
 
1174
1175
  #### <a id="coordinator-error-wrongseed"></a>`WrongSeed`
1175
1176
 
1176
- `error WrongSeed()` · Selector `0xf36cbea4` · Source: `D20VRFCoordinator.sol` lines 120, 585
1177
+ `error WrongSeed()` · Selector `0xf36cbea4` · Source: `D20VRFCoordinator.sol` lines 121, 596
1177
1178
 
1178
1179
  **Raised by:** [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch), [`verifyRequestProof`](#coordinator-fn-verifyrequestproof).
1179
1180
 
@@ -1183,7 +1184,7 @@ The proof's `seed` differs from `requestSeed(requestId)`.
1183
1184
 
1184
1185
  #### <a id="coordinator-error-evidencepackettoolarge"></a>`EvidencePacketTooLarge`
1185
1186
 
1186
- `error EvidencePacketTooLarge()` · Selector `0xcfbc3ebf` · Source: `D20VRFCoordinator.sol` lines 133, 441
1187
+ `error EvidencePacketTooLarge()` · Selector `0xcfbc3ebf` · Source: `D20VRFCoordinator.sol` lines 134, 452
1187
1188
 
1188
1189
  **Raised by:** [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch).
1189
1190
 
@@ -1193,7 +1194,7 @@ The encoded proof exceeds `MAX_EVIDENCE_PACKET_BYTES`. A proof always encodes to
1193
1194
 
1194
1195
  #### <a id="coordinator-error-invalidbatch"></a>`InvalidBatch`
1195
1196
 
1196
- `error InvalidBatch()` · Selector `0x33b094a1` · Source: `D20VRFCoordinator.sol` lines 136, 403
1197
+ `error InvalidBatch()` · Selector `0x33b094a1` · Source: `D20VRFCoordinator.sol` lines 137, 404
1197
1198
 
1198
1199
  **Raised by:** [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch).
1199
1200
 
@@ -1203,7 +1204,7 @@ The encoded proof exceeds `MAX_EVIDENCE_PACKET_BYTES`. A proof always encodes to
1203
1204
 
1204
1205
  #### <a id="coordinator-error-invalidscan"></a>`InvalidScan`
1205
1206
 
1206
- `error InvalidScan()` · Selector `0x3e6249a2` · Source: `D20VRFCoordinator.sol` lines 132, 315
1207
+ `error InvalidScan()` · Selector `0x3e6249a2` · Source: `D20VRFCoordinator.sol` lines 133, 316
1207
1208
 
1208
1209
  **Raised by:** [`getPendingRequestIds`](#coordinator-fn-getpendingrequestids).
1209
1210
 
@@ -1215,7 +1216,7 @@ The encoded proof exceeds `MAX_EVIDENCE_PACKET_BYTES`. A proof always encodes to
1215
1216
 
1216
1217
  #### <a id="coordinator-error-invalidconfig"></a>`InvalidConfig`
1217
1218
 
1218
- `error InvalidConfig()` · Selector `0x35be3ac8` · Source: `D20VRFCoordinator.sol` lines 107, 173–174, 197, 201, 206, 215, 519, 528
1219
+ `error InvalidConfig()` · Selector `0x35be3ac8` · Source: `D20VRFCoordinator.sol` lines 108, 174–175, 198, 202, 207, 216, 530, 539
1219
1220
 
1220
1221
  **Raised by:** [`withdrawFees`](#coordinator-fn-withdrawfees), [`withdrawKeeperCredit`](#coordinator-fn-withdrawkeepercredit), [`setPricing`](#coordinator-fn-setpricing), [`setRefundBps`](#coordinator-fn-setrefundbps), [`setKeeperFeeBps`](#coordinator-fn-setkeeperfeebps), [`setFeeRecipient`](#coordinator-fn-setfeerecipient), [`initialize`](#coordinator-fn-initialize).
1221
1222
 
@@ -1225,7 +1226,7 @@ A value is out of bounds: in `initialize` (zero fee recipient, confirmations 0 o
1225
1226
 
1226
1227
  #### <a id="coordinator-error-invalidpublickey"></a>`InvalidPublicKey`
1227
1228
 
1228
- `error InvalidPublicKey()` · Selector `0xa2d0fee8` · Source: `D20VRFCoordinator.sol` lines 109, 176
1229
+ `error InvalidPublicKey()` · Selector `0xa2d0fee8` · Source: `D20VRFCoordinator.sol` lines 110, 177
1229
1230
 
1230
1231
  **Raised by:** [`initialize`](#coordinator-fn-initialize).
1231
1232
 
@@ -1265,7 +1266,7 @@ A `nonReentrant` coordinator function (a request, `storeBlockHash`, a fulfillmen
1265
1266
 
1266
1267
  #### <a id="coordinator-error-renouncedisabled"></a>`RenounceDisabled`
1267
1268
 
1268
- `error RenounceDisabled()` · Selector `0x89051165` · Source: `D20VRFCoordinator.sol` lines 134, 194
1269
+ `error RenounceDisabled()` · Selector `0x89051165` · Source: `D20VRFCoordinator.sol` lines 135, 195
1269
1270
 
1270
1271
  **Raised by:** [`renounceOwnership`](#coordinator-fn-renounceownership).
1271
1272
 
@@ -1357,21 +1358,23 @@ The initialization call made by `upgradeToAndCall` reverted without revert data.
1357
1358
 
1358
1359
  The epoch registry. A consumer never needs to call it: a request fixes its epoch automatically, and the coordinator reads the registry for targets and for the keeper-share recipient. The first group and the events matter to consumers and verifiers (README [Replay and verification](README.md#replay-and-verification)); publication and administration are listed for completeness.
1359
1360
 
1361
+ Epoch sources are recipes in an owner-managed, append-only registry: each has an id, a canonical request whose `keccak256` is the query hash its signer signs, a data template that fixes the exact signed bytes the registry accepts (README [Data templates](README.md#data-templates)) and the JSON body keepers post to the provider gateway. A catalog lists 1 to `MAX_SOURCES` registered recipes with one signer each; each epoch selects from the catalog in force for it (`catalogAt`).
1362
+
1360
1363
  ### <a id="registry-types"></a>Types
1361
1364
 
1362
1365
  #### <a id="registry-type-epochentropy-epoch"></a>`EpochEntropy.Epoch`
1363
1366
 
1364
1367
  Returned by `getEpoch`; all zero until the epoch is published.
1365
1368
 
1366
- Source: `EpochEntropy.sol` lines 30–33
1369
+ Source: `EpochEntropy.sol` lines 45–48
1367
1370
 
1368
1371
  | Field | Type | Meaning |
1369
1372
  | --- | --- | --- |
1370
1373
  | `epochHash` | `bytes32` | Epoch commitment: `keccak256(abi.encode(EPOCH_DOMAIN, chainId, registry, catalogHash, epochId, epochStart, anchorHash, source, queryHash, dataHash, attestationHash))`. |
1371
- | `catalogHash` | `bytes32` | Signer catalog in force for the epoch at publication. |
1374
+ | `catalogHash` | `bytes32` | Hash of the catalog in force for the epoch (`catalogAt`) at publication. |
1372
1375
  | `anchorHash` | `bytes32` | Hash of block `epochStart - 1`, which selects the source. |
1373
- | `source` | `uint8` | Committed source slot, 0 to 3: the selected slot or a fallback. |
1374
- | `queryHash` | `bytes32` | `keccak256` of the canonical request string of the slot. |
1376
+ | `source` | `uint8` | Committed slot in the epoch's catalog, 0 to `sourceCountAt(epochId) - 1`: the selected slot or a fallback. The recipe is the catalog's recipe at that slot. |
1377
+ | `queryHash` | `bytes32` | `keccak256` of the canonical request of the slot's recipe. |
1375
1378
  | `dataHash` | `bytes32` | `keccak256` of the signed response data. |
1376
1379
  | `attestationHash` | `bytes32` | `keccak256(abi.encode(queryHash, timestamp, dataHash, keccak256(signature)))`. |
1377
1380
  | `signedAt` | `uint256` | Attestation timestamp in Unix seconds. |
@@ -1381,26 +1384,27 @@ Source: `EpochEntropy.sol` lines 30–33
1381
1384
 
1382
1385
  Returned by `getEpochSelection` and `getEpochFallbackSelection`.
1383
1386
 
1384
- Source: `EpochEntropy.sol` lines 29, 121–133
1387
+ Source: `EpochEntropy.sol` lines 43–44, 262–273
1385
1388
 
1386
1389
  | Field | Type | Meaning |
1387
1390
  | --- | --- | --- |
1388
- | `source` | `uint8` | Source slot 0 to 3: Hyperliquid BTC volume, ANU, TickerLayer BTCUSD, TickerLayer ETHUSD. |
1391
+ | `source` | `uint8` | Slot in the epoch's catalog. |
1392
+ | `recipe` | `uint8` | Registered recipe id at that slot. |
1389
1393
  | `airnode` | `address` | Signer of that slot in the catalog in force for the epoch. |
1390
- | `selector` | `bytes32` | `keccak256(abi.encode(SELECT_DOMAIN, catalogHash, epochId, anchor))`; the slot is `(selector mod 4 + attempt) mod 4`. |
1394
+ | `selector` | `bytes32` | `keccak256(abi.encode(SELECT_DOMAIN, catalogHash, epochId, anchor))`; the slot is `(selector mod count + attempt) mod count` with `count = sourceCountAt(epochId)`. |
1391
1395
  | `queryHash` | `bytes32` | `keccak256` of `canonicalRequest`. |
1392
- | `canonicalRequest` | `string` | Fixed request string of the slot. |
1396
+ | `canonicalRequest` | `string` | Canonical request of the recipe, as `getRecipe` returns it. |
1393
1397
 
1394
1398
  #### <a id="registry-type-epochentropy-attestation"></a>`EpochEntropy.Attestation`
1395
1399
 
1396
1400
  Signed source response passed to `commitEpoch` and `commitEpochFallback`.
1397
1401
 
1398
- Source: `EpochEntropy.sol` lines 28, 140–159
1402
+ Source: `EpochEntropy.sol` lines 42, 280–300
1399
1403
 
1400
1404
  | Field | Type | Meaning |
1401
1405
  | --- | --- | --- |
1402
1406
  | `timestamp` | `uint256` | Signing time in Unix seconds; not in the future and at most `MAX_ATTESTATION_AGE` old at publication. |
1403
- | `data` | `bytes` | Signed response bytes, 1 to 128 bytes, in the fixed format of the slot. |
1407
+ | `data` | `bytes` | Signed response bytes, at most `MAX_DATA_BYTES` (128), matching the data template of the slot's recipe exactly. |
1404
1408
  | `signature` | `bytes` | Signature over `toEthSignedMessageHash(keccak256(abi.encodePacked(queryHash, timestamp, data)))`. |
1405
1409
 
1406
1410
  ### <a id="registry-epoch-state-for-consumers-and-verifiers"></a>Epoch state for consumers and verifiers
@@ -1413,7 +1417,7 @@ Views, callable by anyone. Epoch IDs start at 1; each epoch lasts `EPOCH_LENGTH`
1413
1417
  function epochForBlock(uint256 number) external view returns (uint64)
1414
1418
  ```
1415
1419
 
1416
- Selector `0x7018ebb1` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 91–93
1420
+ Selector `0x7018ebb1` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 235–237
1417
1421
 
1418
1422
  Epoch containing a block: 0 before `firstEpochStart`, otherwise `1 + (number - firstEpochStart) / 200`. A request belongs to `epochForBlock(requestBlock)`.
1419
1423
 
@@ -1423,7 +1427,7 @@ Epoch containing a block: 0 before `firstEpochStart`, otherwise `1 + (number - f
1423
1427
  function epochStart(uint64 epochId) external view returns (uint64)
1424
1428
  ```
1425
1429
 
1426
- Selector `0xa1587509` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 87–90
1430
+ Selector `0xa1587509` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 231–234
1427
1431
 
1428
1432
  First block of an epoch: `firstEpochStart + (epochId - 1) × 200`.
1429
1433
 
@@ -1435,29 +1439,63 @@ First block of an epoch: `firstEpochStart + (epochId - 1) × 200`.
1435
1439
  function getEpoch(uint64 epochId) external view returns (EpochEntropy.Epoch)
1436
1440
  ```
1437
1441
 
1438
- Selector `0x12a02c82` · Caller: Anyone (view) · Source: `EpochEntropy.sol` line 108
1442
+ Selector `0x12a02c82` · Caller: Anyone (view) · Source: `EpochEntropy.sol` line 252
1439
1443
 
1440
1444
  The published [`EpochEntropy.Epoch`](#registry-type-epochentropy-epoch) record, or all zero while unpublished; it never reverts. A non-zero `epochHash` means published.
1441
1445
 
1442
- #### <a id="registry-fn-cataloghashat"></a>`catalogHashAt`
1446
+ #### <a id="registry-fn-catalogat"></a>`catalogAt`
1443
1447
 
1444
1448
  ```solidity
1445
- function catalogHashAt(uint64 epochId) external view returns (bytes32 hash)
1449
+ function catalogAt(uint64 epochId) external view returns (bytes32 hash, uint8[] recipes, address[] signers)
1446
1450
  ```
1447
1451
 
1448
- Selector `0xab3ee735` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 78, 80–86
1452
+ Selector `0xec993599` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 212–220, 226–230
1453
+
1454
+ The catalog in force for an epoch: its hash and the recipe id and signer of each slot, in slot order. That is the latest scheduled version whose `fromEpoch` is at or below `epochId`, otherwise the initial catalog: recipes 0 to 3 with the initial signers and hash `catalogHash()`. Replay needs this catalog, not the initial signer getters.
1455
+
1456
+ #### <a id="registry-fn-sourcecountat"></a>`sourceCountAt`
1457
+
1458
+ ```solidity
1459
+ function sourceCountAt(uint64 epochId) external view returns (uint256)
1460
+ ```
1461
+
1462
+ Selector `0x3edc6b12` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 221–225, 226–230
1463
+
1464
+ Number of slots in the catalog in force for an epoch, and so the number of selection attempts, 0 to count - 1.
1465
+
1466
+ #### <a id="registry-fn-getrecipe"></a>`getRecipe`
1467
+
1468
+ ```solidity
1469
+ function getRecipe(uint8 recipe) external view returns (bytes32 queryHash, string canonicalRequest, bytes template, string body)
1470
+ ```
1471
+
1472
+ Selector `0xba01b103` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 132–136, 139–142
1473
+
1474
+ A registered recipe: `queryHash` (`keccak256` of `canonicalRequest`), the canonical request its signer signs, the data template its signed data must match and the JSON body keepers post to the provider gateway. Registered recipes never change. `readEpochRecipes` in `@d20dao/vrf-sdk/epoch` reads recipes with this view and checks each query hash.
1475
+
1476
+ **Errors:** [`InvalidConfig`](#registry-error-invalidconfig).
1477
+
1478
+ #### <a id="registry-fn-reciperequest"></a>`recipeRequest`
1479
+
1480
+ ```solidity
1481
+ function recipeRequest(uint8 recipe) external view returns (string)
1482
+ ```
1483
+
1484
+ Selector `0x7ce4b6e0` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 137–142
1485
+
1486
+ Canonical request of a registered recipe, the same string `getRecipe` returns.
1449
1487
 
1450
- Catalog hash in force for an epoch: the latest scheduled version whose `fromEpoch` is at or below `epochId`, otherwise the initial catalog.
1488
+ **Errors:** [`InvalidConfig`](#registry-error-invalidconfig).
1451
1489
 
1452
- #### <a id="registry-fn-signersat"></a>`signersAt`
1490
+ #### <a id="registry-fn-recipecount"></a>`recipeCount`
1453
1491
 
1454
1492
  ```solidity
1455
- function signersAt(uint64 epochId) external view returns (address[4] signers)
1493
+ function recipeCount() external view returns (uint256)
1456
1494
  ```
1457
1495
 
1458
- Selector `0xe788e413` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 79, 80–86
1496
+ Selector `0x69cfdf74` · Caller: Anyone (view) · Source: `EpochEntropy.sol` line 131
1459
1497
 
1460
- Signers in force for an epoch, in slot order. Replay needs these, not the initial slot getters.
1498
+ Number of registered recipes; ids run from 0 to `recipeCount() - 1`.
1461
1499
 
1462
1500
  ### <a id="registry-registry-reads"></a>Registry reads
1463
1501
 
@@ -1465,18 +1503,21 @@ Views, callable by anyone.
1465
1503
 
1466
1504
  | Function | Selector | Meaning | Source |
1467
1505
  | --- | --- | --- | --- |
1468
- | <a id="registry-fn-firstepochstart"></a>`firstEpochStart() returns (uint64)` | `0x219f2428` | First block of epoch 1: the initialization block plus 200. | lines 26, 55 |
1469
- | <a id="registry-fn-committer"></a>`committer() returns (address)` | `0x5bc8e8f9` | Address allowed to publish epochs. The coordinator also pays the keeper share to it at each acceptance. | lines 25, 141 |
1470
- | <a id="registry-fn-cataloghash"></a>`catalogHash() returns (bytes32)` | `0x830c083a` | Initial signer catalog hash, bound into `protocolConfigurationHash`. Never changes; `catalogHashAt` gives the catalog of an epoch. | lines 27, 56 |
1471
- | <a id="registry-fn-epochanchors"></a>`epochAnchors(uint64) returns (bytes32)` | `0x48a030fb` | Checkpointed anchor of an epoch (hash of block `epochStart - 1`); zero until a request, `checkpointEpoch` or publication stores it. | lines 36, 97–100 |
1472
- | <a id="registry-fn-hyperliquidsigner"></a>`hyperliquidSigner() returns (address)` | `0xf2a12563` | Initial signer of slot 0 (Hyperliquid BTC volume). Never changes; see `signersAt`. | line 21 |
1473
- | <a id="registry-fn-anusigner"></a>`anuSigner() returns (address)` | `0xc6dbfa46` | Initial signer of slot 1 (ANU). Never changes; see `signersAt`. | line 22 |
1474
- | <a id="registry-fn-btctradesigner"></a>`btcTradeSigner() returns (address)` | `0xb3b9cbb0` | Initial signer of slot 2 (TickerLayer BTCUSD). Never changes; see `signersAt`. | line 23 |
1475
- | <a id="registry-fn-ethtradesigner"></a>`ethTradeSigner() returns (address)` | `0x3a700168` | Initial signer of slot 3 (TickerLayer ETHUSD). Never changes; see `signersAt`. | line 24 |
1506
+ | <a id="registry-fn-firstepochstart"></a>`firstEpochStart() returns (uint64)` | `0x219f2428` | First block of epoch 1: the initialization block plus 200. | lines 40, 83 |
1507
+ | <a id="registry-fn-committer"></a>`committer() returns (address)` | `0x5bc8e8f9` | Primary publishing address. The coordinator pays it the keeper share of the requests it serves itself and of every request whose proof came from a wallet this registry does not authorize. | lines 39, 281 |
1508
+ | <a id="registry-fn-isbackupcommitter"></a>`isBackupCommitter(address account) returns (bool)` | `0x1d97e417` | Whether an address may publish epochs besides `committer()`. | line 118 |
1509
+ | <a id="registry-fn-isauthorizedcommitter"></a>`isAuthorizedCommitter(address account) returns (bool)` | `0x1579ab83` | Whether an address may publish epochs at all: `committer()` or an allowed backup committer. The coordinator reads it to decide whether a proof submitter earns the keeper share. | lines 119–121 |
1510
+ | <a id="registry-fn-backupcommittercount"></a>`backupCommitterCount() returns (uint256)` | `0xa815c5bf` | Number of allowed backup committers, at most `MAX_BACKUP_COMMITTERS`. | lines 63, 107–117 |
1511
+ | <a id="registry-fn-cataloghash"></a>`catalogHash() returns (bytes32)` | `0x830c083a` | Initial catalog hash, bound into `protocolConfigurationHash`. Never changes; `catalogAt` gives the catalog of an epoch. | lines 41, 84 |
1512
+ | <a id="registry-fn-epochanchors"></a>`epochAnchors(uint64) returns (bytes32)` | `0x48a030fb` | Checkpointed anchor of an epoch (hash of block `epochStart - 1`); zero until a request, `checkpointEpoch` or publication stores it. | lines 53, 241–244 |
1513
+ | <a id="registry-fn-hyperliquidsigner"></a>`hyperliquidSigner() returns (address)` | `0xf2a12563` | Initial-catalog signer of slot 0 (recipe 0, Hyperliquid BTC volume). Never changes; see `catalogAt`. | line 34 |
1514
+ | <a id="registry-fn-ethereumblocksigner"></a>`ethereumBlockSigner() returns (address)` | `0xd25eacfc` | Initial-catalog signer of slot 1 (recipe 1, Ethereum block hash). Never changes; see `catalogAt`. | lines 35–36 |
1515
+ | <a id="registry-fn-btctradesigner"></a>`btcTradeSigner() returns (address)` | `0xb3b9cbb0` | Initial-catalog signer of slot 2 (recipe 2, TickerLayer BTCUSD). Never changes; see `catalogAt`. | line 37 |
1516
+ | <a id="registry-fn-ethtradesigner"></a>`ethTradeSigner() returns (address)` | `0x3a700168` | Initial-catalog signer of slot 3 (recipe 3, TickerLayer ETHUSD). Never changes; see `catalogAt`. | line 38 |
1476
1517
 
1477
1518
  ### <a id="registry-source-selection-and-publication"></a>Source selection and publication
1478
1519
 
1479
- Used by the keeper. Publication is restricted to the committer; the selection views and `checkpointEpoch` are open to anyone.
1520
+ Used by keepers. Publication is restricted to the committer and backup committers; the selection views and `checkpointEpoch` are open to anyone.
1480
1521
 
1481
1522
  #### <a id="registry-fn-getepochselection"></a>`getEpochSelection`
1482
1523
 
@@ -1484,11 +1525,11 @@ Used by the keeper. Publication is restricted to the committer; the selection vi
1484
1525
  function getEpochSelection(uint64 epochId) external view returns (EpochEntropy.Selection s)
1485
1526
  ```
1486
1527
 
1487
- Selector `0xec4960ad` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 109, 121–133
1528
+ Selector `0xec4960ad` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 253, 262–273
1488
1529
 
1489
1530
  The selected source of an epoch, attempt 0, as an [`EpochEntropy.Selection`](#registry-type-epochentropy-selection).
1490
1531
 
1491
- **Errors:** [`InvalidEpoch`](#registry-error-invalidepoch), [`PreparationClosed`](#registry-error-preparationclosed), [`AnchorUnavailable`](#registry-error-anchorunavailable).
1532
+ **Errors:** [`InvalidEpoch`](#registry-error-invalidepoch), [`PreparationClosed`](#registry-error-preparationclosed), [`AnchorUnavailable`](#registry-error-anchorunavailable), [`InvalidConfig`](#registry-error-invalidconfig).
1492
1533
 
1493
1534
  #### <a id="registry-fn-getepochfallbackselection"></a>`getEpochFallbackSelection`
1494
1535
 
@@ -1496,11 +1537,11 @@ The selected source of an epoch, attempt 0, as an [`EpochEntropy.Selection`](#re
1496
1537
  function getEpochFallbackSelection(uint64 epochId, uint8 attempt) external view returns (EpochEntropy.Selection s)
1497
1538
  ```
1498
1539
 
1499
- Selector `0x0e5a0e02` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 110–114, 121–133
1540
+ Selector `0x0e5a0e02` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 254–255, 262–273
1500
1541
 
1501
- The source for attempt 0 to `MAX_FALLBACK_ATTEMPT` (3); attempt n uses the slot n positions after the selected one.
1542
+ The source for attempt 0 to `sourceCountAt(epochId) - 1`; attempt n uses the slot n positions after the selected one.
1502
1543
 
1503
- **Errors:** [`InvalidFallback`](#registry-error-invalidfallback), [`InvalidEpoch`](#registry-error-invalidepoch), [`PreparationClosed`](#registry-error-preparationclosed), [`AnchorUnavailable`](#registry-error-anchorunavailable).
1544
+ **Errors:** [`InvalidFallback`](#registry-error-invalidfallback), [`InvalidEpoch`](#registry-error-invalidepoch), [`PreparationClosed`](#registry-error-preparationclosed), [`AnchorUnavailable`](#registry-error-anchorunavailable), [`InvalidConfig`](#registry-error-invalidconfig).
1504
1545
 
1505
1546
  #### <a id="registry-fn-fallbackopensat"></a>`fallbackOpensAt`
1506
1547
 
@@ -1508,9 +1549,9 @@ The source for attempt 0 to `MAX_FALLBACK_ATTEMPT` (3); attempt n uses the slot
1508
1549
  function fallbackOpensAt(uint64 epochId, uint8 attempt) external view returns (uint64)
1509
1550
  ```
1510
1551
 
1511
- Selector `0x98208050` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 115–119
1552
+ Selector `0x98208050` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 256–260
1512
1553
 
1513
- First block at which an attempt may be published: `epochStart + attempt × FALLBACK_DELAY_BLOCKS` (20).
1554
+ First block at which an attempt may be published: `epochStart + attempt × FALLBACK_DELAY_BLOCKS` (20). With at most `MAX_SOURCES` (10) slots the last window opens 180 blocks into the epoch.
1514
1555
 
1515
1556
  **Errors:** [`InvalidFallback`](#registry-error-invalidfallback), [`InvalidEpoch`](#registry-error-invalidepoch).
1516
1557
 
@@ -1520,7 +1561,7 @@ First block at which an attempt may be published: `epochStart + attempt × FALLB
1520
1561
  function nextEpochToPrepare(uint256 number) external view returns (uint64)
1521
1562
  ```
1522
1563
 
1523
- Selector `0xc78fafe2` · Caller: Anyone (view) · Source: `EpochEntropy.sol` line 94
1564
+ Selector `0xc78fafe2` · Caller: Anyone (view) · Source: `EpochEntropy.sol` line 238
1524
1565
 
1525
1566
  Same value as `epochForBlock(number)`.
1526
1567
 
@@ -1530,7 +1571,7 @@ Same value as `epochForBlock(number)`.
1530
1571
  function checkpointEpoch(uint64 epochId) external returns (bytes32 anchor)
1531
1572
  ```
1532
1573
 
1533
- Selector `0x16de78cb` · Caller: Anyone · Source: `EpochEntropy.sol` lines 95–100, 101–107
1574
+ Selector `0x16de78cb` · Caller: Anyone · Source: `EpochEntropy.sol` lines 239–244, 245–251
1534
1575
 
1535
1576
  Stores the anchor of a started epoch (hash of block `epochStart - 1`) if not stored yet, and returns it. The coordinator calls it on every request, so the anchor of an epoch with requests survives the 256-block `BLOCKHASH` window.
1536
1577
 
@@ -1542,13 +1583,13 @@ Stores the anchor of a started epoch (hash of block `epochStart - 1`) if not sto
1542
1583
  function commitEpoch(uint64 epochId, EpochEntropy.Attestation a) external
1543
1584
  ```
1544
1585
 
1545
- Selector `0xb1580277` · Caller: Committer · Source: `EpochEntropy.sol` lines 134, 140–159
1586
+ Selector `0xb1580277` · Caller: Committer or backup committer · Source: `EpochEntropy.sol` lines 274, 280–300
1546
1587
 
1547
- Publishes the packet of the selected source once per epoch, from the epoch start: checks that the attestation is not future-dated and at most 240 seconds old, that its data has the fixed format of the slot, and that the slot's signer in the epoch's catalog signed it. Stores the record and emits the packet.
1588
+ Publishes the packet of the selected source once per epoch, from the epoch start: checks that the attestation is not future-dated and at most 240 seconds old, that its data matches the data template of the slot's recipe exactly, and that the slot's signer in the epoch's catalog signed it. Stores the record and emits the packet. Publishing earns nothing by itself: the keeper share of each request goes to the authorized wallet that submits its accepted proof, or to `committer()` when the submitter is not authorized.
1548
1589
 
1549
1590
  **Emits:** [`EpochCommitted`](#registry-event-epochcommitted).
1550
1591
 
1551
- **Errors:** [`OnlyCommitter`](#registry-error-onlycommitter), [`AlreadyCommitted`](#registry-error-alreadycommitted), [`InvalidEpoch`](#registry-error-invalidepoch), [`FallbackNotOpen`](#registry-error-fallbacknotopen), [`AnchorUnavailable`](#registry-error-anchorunavailable), [`InvalidTime`](#registry-error-invalidtime), [`InvalidData`](#registry-error-invaliddata), [`ECDSAInvalidSignatureLength`](#registry-error-ecdsainvalidsignaturelength), [`ECDSAInvalidSignatureS`](#registry-error-ecdsainvalidsignatures), [`ECDSAInvalidSignature`](#registry-error-ecdsainvalidsignature), [`InvalidSigner`](#registry-error-invalidsigner), [`PacketTooLarge`](#registry-error-packettoolarge).
1592
+ **Errors:** [`OnlyCommitter`](#registry-error-onlycommitter), [`AlreadyCommitted`](#registry-error-alreadycommitted), [`InvalidEpoch`](#registry-error-invalidepoch), [`FallbackNotOpen`](#registry-error-fallbacknotopen), [`AnchorUnavailable`](#registry-error-anchorunavailable), [`InvalidConfig`](#registry-error-invalidconfig), [`InvalidTime`](#registry-error-invalidtime), [`InvalidData`](#registry-error-invaliddata), [`ECDSAInvalidSignatureLength`](#registry-error-ecdsainvalidsignaturelength), [`ECDSAInvalidSignatureS`](#registry-error-ecdsainvalidsignatures), [`ECDSAInvalidSignature`](#registry-error-ecdsainvalidsignature), [`InvalidSigner`](#registry-error-invalidsigner), [`PacketTooLarge`](#registry-error-packettoolarge).
1552
1593
 
1553
1594
  #### <a id="registry-fn-commitepochfallback"></a>`commitEpochFallback`
1554
1595
 
@@ -1556,13 +1597,59 @@ Publishes the packet of the selected source once per epoch, from the epoch start
1556
1597
  function commitEpochFallback(uint64 epochId, uint8 attempt, EpochEntropy.Attestation a) external
1557
1598
  ```
1558
1599
 
1559
- Selector `0x5768d9a1` · Caller: Committer · Source: `EpochEntropy.sol` lines 135–139, 140–159
1600
+ Selector `0x5768d9a1` · Caller: Committer or backup committer · Source: `EpochEntropy.sol` lines 275–279, 280–300
1560
1601
 
1561
- Publishes fallback attempt 1 to 3, using the slot `attempt` positions after the selected source, once `fallbackOpensAt(epochId, attempt)` is reached. Same checks as `commitEpoch`.
1602
+ Publishes fallback attempt 1 to `sourceCountAt(epochId) - 1`, using the slot `attempt` positions after the selected source, once `fallbackOpensAt(epochId, attempt)` is reached. Same checks as `commitEpoch`.
1562
1603
 
1563
1604
  **Emits:** [`EpochCommitted`](#registry-event-epochcommitted).
1564
1605
 
1565
- **Errors:** [`InvalidFallback`](#registry-error-invalidfallback), [`OnlyCommitter`](#registry-error-onlycommitter), [`AlreadyCommitted`](#registry-error-alreadycommitted), [`InvalidEpoch`](#registry-error-invalidepoch), [`FallbackNotOpen`](#registry-error-fallbacknotopen), [`AnchorUnavailable`](#registry-error-anchorunavailable), [`InvalidTime`](#registry-error-invalidtime), [`InvalidData`](#registry-error-invaliddata), [`ECDSAInvalidSignatureLength`](#registry-error-ecdsainvalidsignaturelength), [`ECDSAInvalidSignatureS`](#registry-error-ecdsainvalidsignatures), [`ECDSAInvalidSignature`](#registry-error-ecdsainvalidsignature), [`InvalidSigner`](#registry-error-invalidsigner), [`PacketTooLarge`](#registry-error-packettoolarge).
1606
+ **Errors:** [`InvalidFallback`](#registry-error-invalidfallback), [`OnlyCommitter`](#registry-error-onlycommitter), [`AlreadyCommitted`](#registry-error-alreadycommitted), [`InvalidEpoch`](#registry-error-invalidepoch), [`FallbackNotOpen`](#registry-error-fallbacknotopen), [`AnchorUnavailable`](#registry-error-anchorunavailable), [`InvalidConfig`](#registry-error-invalidconfig), [`InvalidTime`](#registry-error-invalidtime), [`InvalidData`](#registry-error-invaliddata), [`ECDSAInvalidSignatureLength`](#registry-error-ecdsainvalidsignaturelength), [`ECDSAInvalidSignatureS`](#registry-error-ecdsainvalidsignatures), [`ECDSAInvalidSignature`](#registry-error-ecdsainvalidsignature), [`InvalidSigner`](#registry-error-invalidsigner), [`PacketTooLarge`](#registry-error-packettoolarge).
1607
+
1608
+ ### <a id="registry-recipes-and-catalogs"></a>Recipes and catalogs
1609
+
1610
+ Owner-only; on Arc Mainnet the owner is the DAO treasury Safe. A recipe or catalog never changes a published epoch.
1611
+
1612
+ #### <a id="registry-fn-registerrecipe"></a>`registerRecipe`
1613
+
1614
+ ```solidity
1615
+ function registerRecipe(string canonicalRequest, bytes template, string body) external returns (uint8 recipe)
1616
+ ```
1617
+
1618
+ Selector `0x5add5c50` · Caller: Owner · Source: `EpochEntropy.sol` lines 123–130, 143–152
1619
+
1620
+ Appends an immutable recipe and returns its id, the next index. The canonical request is 1 to `MAX_REQUEST_BYTES` (1024) bytes, the body 1 to `MAX_BODY_BYTES` (2048) bytes, and the template must be a well-formed data template of at most `MAX_TEMPLATE_BYTES` (256) bytes; at most `MAX_RECIPES` (256) recipes exist. The contract does not check that the body canonicalizes to the request; keepers refuse a recipe whose body does not. A changed listing is registered as a new id.
1621
+
1622
+ **Emits:** [`RecipeRegistered`](#registry-event-reciperegistered).
1623
+
1624
+ **Errors:** [`OwnableUnauthorizedAccount`](#registry-error-ownableunauthorizedaccount), [`InvalidRecipe`](#registry-error-invalidrecipe), [`InvalidTemplate`](#registry-error-invalidtemplate).
1625
+
1626
+ #### <a id="registry-fn-schedulecatalog"></a>`scheduleCatalog`
1627
+
1628
+ ```solidity
1629
+ function scheduleCatalog(uint8[] recipes, address[] signers, uint64 fromEpoch) external
1630
+ ```
1631
+
1632
+ Selector `0x42984450` · Caller: Owner · Source: `EpochEntropy.sol` lines 190–211
1633
+
1634
+ Schedules a catalog for epochs from `fromEpoch`, which must be at least two epochs after the current one: 1 to `MAX_SOURCES` (10) distinct registered recipe ids with one non-zero signer each, in slot order. Its hash is `keccak256(abi.encode(RECIPE_DOMAIN, recipes, signers))`. If the latest scheduled version has not taken effect yet (its `fromEpoch` is after the current epoch) it is replaced, so that version never applies; this can return the next epoch to the previous catalog. The current epoch keeps its catalog.
1635
+
1636
+ **Emits:** [`CatalogScheduled`](#registry-event-catalogscheduled).
1637
+
1638
+ **Errors:** [`OwnableUnauthorizedAccount`](#registry-error-ownableunauthorizedaccount), [`InvalidConfig`](#registry-error-invalidconfig), [`InvalidEpoch`](#registry-error-invalidepoch).
1639
+
1640
+ #### <a id="registry-fn-initializereciperegistry"></a>`initializeRecipeRegistry`
1641
+
1642
+ ```solidity
1643
+ function initializeRecipeRegistry() external
1644
+ ```
1645
+
1646
+ Selector `0x8700b456` · Caller: Owner, once per proxy, as the `upgradeToAndCall` data of the recipe-registry upgrade · Source: `EpochEntropy.sol` lines 87–95
1647
+
1648
+ Registers built-in recipes 0 to 5 on a registry initialized before the recipe registry, whose initial catalog selects recipes 0 to 3. It runs once per proxy (reinitializer version 2) and refuses a registry that already has recipes, which includes every registry initialized by this implementation, or a catalog scheduled under the earlier hardcoded recipe ids.
1649
+
1650
+ **Emits:** [`RecipeRegistered`](#registry-event-reciperegistered), [`Initialized`](#registry-event-initialized).
1651
+
1652
+ **Errors:** [`InvalidInitialization`](#registry-error-invalidinitialization), [`OwnableUnauthorizedAccount`](#registry-error-ownableunauthorizedaccount), [`InvalidConfig`](#registry-error-invalidconfig).
1566
1653
 
1567
1654
  ### <a id="registry-owner-administration"></a>Owner administration
1568
1655
 
@@ -1574,27 +1661,27 @@ Owner-only functions revert `OwnableUnauthorizedAccount` for anyone else. No set
1574
1661
  function setCommitter(address next) external
1575
1662
  ```
1576
1663
 
1577
- Selector `0xdd51ce22` · Caller: Owner · Source: `EpochEntropy.sol` lines 62–65
1664
+ Selector `0xdd51ce22` · Caller: Owner · Source: `EpochEntropy.sol` lines 100–103
1578
1665
 
1579
- Changes the publishing address, which is also the keeper-share recipient the coordinator reads at each acceptance.
1666
+ Changes the primary publishing address, which is also the keeper-share recipient the coordinator reads at each acceptance.
1580
1667
 
1581
1668
  **Emits:** [`CommitterChanged`](#registry-event-committerchanged).
1582
1669
 
1583
1670
  **Errors:** [`OwnableUnauthorizedAccount`](#registry-error-ownableunauthorizedaccount), [`InvalidConfig`](#registry-error-invalidconfig).
1584
1671
 
1585
- #### <a id="registry-fn-schedulecatalog"></a>`scheduleCatalog`
1672
+ #### <a id="registry-fn-setbackupcommitter"></a>`setBackupCommitter`
1586
1673
 
1587
1674
  ```solidity
1588
- function scheduleCatalog(address[4] signers, uint64 fromEpoch) external
1675
+ function setBackupCommitter(address account, bool allowed) external
1589
1676
  ```
1590
1677
 
1591
- Selector `0x7ad3b7ef` · Caller: Owner · Source: `EpochEntropy.sol` lines 66–77
1678
+ Selector `0xd870d0c6` · Caller: Owner · Source: `EpochEntropy.sol` lines 104–117
1592
1679
 
1593
- Schedules four signers for epochs from `fromEpoch`, which must be at least two epochs after the current one. If the latest scheduled version has not taken effect yet (its `fromEpoch` is after the current epoch) it is replaced, so that version never applies; this can return the next epoch to the previous catalog. The current epoch keeps its catalog.
1680
+ Allows or removes a backup committer: a separate wallet that may call `commitEpoch` and `commitEpochFallback` under exactly the committer's rules, for example a follower keeper that takes over while the primary keeper is down. It has no other role, and the coordinator pays it the keeper share of the requests whose accepted proofs it submits itself. Reverts for the zero address, for allowing the current committer, for a call that does not change the address's status, and for more than `MAX_BACKUP_COMMITTERS` (4).
1594
1681
 
1595
- **Emits:** [`CatalogScheduled`](#registry-event-catalogscheduled).
1682
+ **Emits:** [`BackupCommitterSet`](#registry-event-backupcommitterset).
1596
1683
 
1597
- **Errors:** [`OwnableUnauthorizedAccount`](#registry-error-ownableunauthorizedaccount), [`InvalidConfig`](#registry-error-invalidconfig), [`InvalidEpoch`](#registry-error-invalidepoch).
1684
+ **Errors:** [`OwnableUnauthorizedAccount`](#registry-error-ownableunauthorizedaccount), [`InvalidConfig`](#registry-error-invalidconfig).
1598
1685
 
1599
1686
  #### <a id="registry-fn-owner"></a>`owner`
1600
1687
 
@@ -1650,7 +1737,7 @@ Completes the transfer to the caller and clears the nomination.
1650
1737
  function renounceOwnership() external view
1651
1738
  ```
1652
1739
 
1653
- Selector `0x715018a6` · Caller: Owner · Source: `EpochEntropy.sol` lines 59–60
1740
+ Selector `0x715018a6` · Caller: Owner · Source: `EpochEntropy.sol` lines 97–98
1654
1741
 
1655
1742
  Disabled and declared `view`: the owner gets `RenounceDisabled` and anyone else `OwnableUnauthorizedAccount`, so the contract always has an owner and upgrade authority can only move through an accepted transfer.
1656
1743
 
@@ -1688,11 +1775,11 @@ ERC-1822 check used by `upgradeToAndCall`. Returns the ERC-1967 implementation s
1688
1775
  function initialize(address[4] signers, address initialOwner, address initialCommitter) external
1689
1776
  ```
1690
1777
 
1691
- Selector `0xfda9f5ca` · Caller: Once, by `D20Proxy` at deployment · Source: `EpochEntropy.sol` lines 50–57
1778
+ Selector `0xfda9f5ca` · Caller: Once, by `D20Proxy` at deployment · Source: `EpochEntropy.sol` lines 78–86
1692
1779
 
1693
- Sets the four initial signers, owner and committer. Epoch 1 starts 200 blocks after the initialization block.
1780
+ Sets the four initial-catalog signers of recipes 0 to 3, the owner and the committer, and registers built-in recipes 0 to 5. Epoch 1 starts 200 blocks after the initialization block.
1694
1781
 
1695
- **Emits:** [`OwnershipTransferred`](#registry-event-ownershiptransferred), [`Initialized`](#registry-event-initialized).
1782
+ **Emits:** [`OwnershipTransferred`](#registry-event-ownershiptransferred), [`RecipeRegistered`](#registry-event-reciperegistered), [`Initialized`](#registry-event-initialized).
1696
1783
 
1697
1784
  **Errors:** [`InvalidInitialization`](#registry-error-invalidinitialization), [`OwnableInvalidOwner`](#registry-error-ownableinvalidowner), [`InvalidConfig`](#registry-error-invalidconfig).
1698
1785
 
@@ -1706,7 +1793,13 @@ Views returning values fixed in the implementation code.
1706
1793
  | <a id="registry-fn-max_attestation_age"></a>`MAX_ATTESTATION_AGE` | `uint256` | `240 seconds` | `0xb9f7bb8d` | Oldest attestation accepted at publication, in seconds; also exported by `@d20dao/vrf-sdk/epoch`. |
1707
1794
  | <a id="registry-fn-max_packet_bytes"></a>`MAX_PACKET_BYTES` | `uint256` | `2048` | `0x48cad11b` | Upper bound on the `EpochCommitted` packet. |
1708
1795
  | <a id="registry-fn-fallback_delay_blocks"></a>`FALLBACK_DELAY_BLOCKS` | `uint64` | `20` | `0x0a48a95d` | Blocks between fallback windows. |
1709
- | <a id="registry-fn-max_fallback_attempt"></a>`MAX_FALLBACK_ATTEMPT` | `uint8` | `3` | `0x59981a9f` | Highest fallback attempt. |
1796
+ | <a id="registry-fn-max_sources"></a>`MAX_SOURCES` | `uint256` | `10` | `0x64aefc06` | Most slots in a catalog. |
1797
+ | <a id="registry-fn-max_recipes"></a>`MAX_RECIPES` | `uint256` | `256` | `0x0c148333` | Most registered recipes; ids are `uint8`. |
1798
+ | <a id="registry-fn-max_request_bytes"></a>`MAX_REQUEST_BYTES` | `uint256` | `1024` | `0x4b62e5b4` | Longest canonical request of a recipe. |
1799
+ | <a id="registry-fn-max_body_bytes"></a>`MAX_BODY_BYTES` | `uint256` | `2048` | `0x2ade18c2` | Longest gateway request body of a recipe. |
1800
+ | <a id="registry-fn-max_data_bytes"></a>`MAX_DATA_BYTES` | `uint256` | `128` | `0xf1d11a6c` | Longest signed data a template accepts. |
1801
+ | <a id="registry-fn-max_template_bytes"></a>`MAX_TEMPLATE_BYTES` | `uint256` | `256` | `0x30a3bee5` | Longest data template. |
1802
+ | <a id="registry-fn-max_backup_committers"></a>`MAX_BACKUP_COMMITTERS` | `uint256` | `4` | `0xc195af66` | Most backup committers allowed at once. |
1710
1803
  | <a id="registry-fn-recipe_domain"></a>`RECIPE_DOMAIN` | `bytes32` | `keccak256("D20_EPOCH_RECIPES")` | `0xacea73c8` | Domain tag of catalog hashes. |
1711
1804
  | <a id="registry-fn-select_domain"></a>`SELECT_DOMAIN` | `bytes32` | `keccak256("D20_EPOCH_SELECT")` | `0x10181587` | Domain tag of the source selector. |
1712
1805
  | <a id="registry-fn-epoch_domain"></a>`EPOCH_DOMAIN` | `bytes32` | `keccak256("D20_EPOCH")` | `0xbece738c` | Domain tag of the epoch commitment. |
@@ -1714,7 +1807,7 @@ Views returning values fixed in the implementation code.
1714
1807
 
1715
1808
  ### <a id="registry-events"></a>Events
1716
1809
 
1717
- **Epochs**
1810
+ **Epochs, recipes and catalogs**
1718
1811
 
1719
1812
  #### <a id="registry-event-epochcommitted"></a>`EpochCommitted`
1720
1813
 
@@ -1722,19 +1815,29 @@ Views returning values fixed in the implementation code.
1722
1815
  event EpochCommitted(uint64 indexed epochId, bytes32 indexed epochHash, bytes packet)
1723
1816
  ```
1724
1817
 
1725
- Topic 0 `0xc9db8d1389570196eda0f2c6c4e2f78429c2f812db30b6b1022b5e0b5162ef72` · Emitted by: [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback) · Source: `EpochEntropy.sol` lines 45, 156–158
1818
+ Topic 0 `0xc9db8d1389570196eda0f2c6c4e2f78429c2f812db30b6b1022b5e0b5162ef72` · Emitted by: [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback) · Source: `EpochEntropy.sol` lines 71, 297–299
1726
1819
 
1727
1820
  An epoch was published. `packet` is `abi.encode(canonicalRequest, attestation)`: decode it with `decodeEpochEvidencePacket` and verify with `replayEpochCommitment`. Requests of the epoch now have a target block.
1728
1821
 
1822
+ #### <a id="registry-event-reciperegistered"></a>`RecipeRegistered`
1823
+
1824
+ ```solidity
1825
+ event RecipeRegistered(uint8 indexed recipe, bytes32 indexed queryHash, string canonicalRequest, bytes template, string body)
1826
+ ```
1827
+
1828
+ Topic 0 `0xbc5c1e3f4647d7f37dc8b4fe5e26e7c58ae35b8c9fb1db0233ddedba0d7f5fd8` · Emitted by: [`registerRecipe`](#registry-fn-registerrecipe), [`initializeRecipeRegistry`](#registry-fn-initializereciperegistry), [`initialize`](#registry-fn-initialize) · Source: `EpochEntropy.sol` lines 74, 151
1829
+
1830
+ Recipe `recipe` was registered. The event carries the complete definition, so every recipe can be rebuilt from logs; `getRecipe` returns the same values.
1831
+
1729
1832
  #### <a id="registry-event-catalogscheduled"></a>`CatalogScheduled`
1730
1833
 
1731
1834
  ```solidity
1732
- event CatalogScheduled(uint64 indexed fromEpoch, bytes32 indexed catalogHash, address[4] signers)
1835
+ event CatalogScheduled(uint64 indexed fromEpoch, bytes32 indexed catalogHash, uint8[] recipes, address[] signers)
1733
1836
  ```
1734
1837
 
1735
- Topic 0 `0xcf1bfe9243f85512c4c62a40dac0bdb9ca6b01cf9bddd6b86f061d0e3685d26a` · Emitted by: [`scheduleCatalog`](#registry-fn-schedulecatalog) · Source: `EpochEntropy.sol` lines 47, 76
1838
+ Topic 0 `0xc91bcd562b1edaabdb2772ada76957511610c715ef892b9c9af1f35be93c3c4f` · Emitted by: [`scheduleCatalog`](#registry-fn-schedulecatalog) · Source: `EpochEntropy.sol` lines 73, 210
1736
1839
 
1737
- Signers scheduled for epochs from `fromEpoch`. A later `CatalogScheduled` emitted while this version has not taken effect replaces it, so when rebuilding catalogs from history drop replaced versions, or read `signersAt(epochId)`.
1840
+ A catalog was scheduled for epochs from `fromEpoch`: recipe ids and signers in slot order. A later `CatalogScheduled` emitted while this version has not taken effect replaces it, so when rebuilding catalogs from history drop replaced versions, or read `catalogAt(epochId)`.
1738
1841
 
1739
1842
  **Administration and upgrades**
1740
1843
 
@@ -1744,9 +1847,19 @@ Signers scheduled for epochs from `fromEpoch`. A later `CatalogScheduled` emitte
1744
1847
  event CommitterChanged(address indexed previousCommitter, address indexed newCommitter)
1745
1848
  ```
1746
1849
 
1747
- Topic 0 `0x3f67cc70f736070aaac75db90cef1ab4047521b73e8a38d02852e8bf1a91e7e0` · Emitted by: [`setCommitter`](#registry-fn-setcommitter) · Source: `EpochEntropy.sol` lines 46, 64
1850
+ Topic 0 `0x3f67cc70f736070aaac75db90cef1ab4047521b73e8a38d02852e8bf1a91e7e0` · Emitted by: [`setCommitter`](#registry-fn-setcommitter) · Source: `EpochEntropy.sol` lines 72, 102
1851
+
1852
+ New primary publishing address; it also receives the keeper share of proofs submitted by wallets the registry does not authorize.
1748
1853
 
1749
- New publishing address and keeper-share recipient.
1854
+ #### <a id="registry-event-backupcommitterset"></a>`BackupCommitterSet`
1855
+
1856
+ ```solidity
1857
+ event BackupCommitterSet(address indexed account, bool allowed)
1858
+ ```
1859
+
1860
+ Topic 0 `0x20380b8c17d904db7d905a51f1538057d280a6cecca38882832ba0261b39fa66` · Emitted by: [`setBackupCommitter`](#registry-fn-setbackupcommitter) · Source: `EpochEntropy.sol` lines 75, 116
1861
+
1862
+ `account` may now publish epochs (`allowed` true) or no longer may (`allowed` false).
1750
1863
 
1751
1864
  #### <a id="registry-event-ownershiptransferstarted"></a>`OwnershipTransferStarted`
1752
1865
 
@@ -1784,9 +1897,9 @@ The proxy now runs `implementation`. Emitted by the proxy at deployment and at e
1784
1897
  event Initialized(uint64 version)
1785
1898
  ```
1786
1899
 
1787
- Topic 0 `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` · Emitted by: [`initialize`](#registry-fn-initialize)
1900
+ Topic 0 `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` · Emitted by: [`initializeRecipeRegistry`](#registry-fn-initializereciperegistry), [`initialize`](#registry-fn-initialize)
1788
1901
 
1789
- `initialize` ran on the proxy (`version` 1). Each implementation contract also emitted it once at construction with `version` 2^64 − 1, which locks the implementation against initialization.
1902
+ `initialize` ran on the proxy (`version` 1) or `initializeRecipeRegistry` did (`version` 2). Each implementation contract also emitted it once at construction with `version` 2^64 − 1, which locks the implementation against initialization.
1790
1903
 
1791
1904
  ### <a id="registry-errors"></a>Errors
1792
1905
 
@@ -1794,7 +1907,7 @@ Topic 0 `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` ·
1794
1907
 
1795
1908
  #### <a id="registry-error-invalidepoch"></a>`InvalidEpoch`
1796
1909
 
1797
- `error InvalidEpoch()` · Selector `0xd5b25b63` · Source: `EpochEntropy.sol` lines 41, 71, 88
1910
+ `error InvalidEpoch()` · Selector `0xd5b25b63` · Source: `EpochEntropy.sol` lines 66, 204, 232
1798
1911
 
1799
1912
  **Raised by:** [`epochStart`](#registry-fn-epochstart), [`getEpochSelection`](#registry-fn-getepochselection), [`getEpochFallbackSelection`](#registry-fn-getepochfallbackselection), [`fallbackOpensAt`](#registry-fn-fallbackopensat), [`checkpointEpoch`](#registry-fn-checkpointepoch), [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback), [`scheduleCatalog`](#registry-fn-schedulecatalog).
1800
1913
 
@@ -1804,7 +1917,7 @@ Epoch 0 was passed to `epochStart`, `fallbackOpensAt`, a selection view, `checkp
1804
1917
 
1805
1918
  #### <a id="registry-error-preparationclosed"></a>`PreparationClosed`
1806
1919
 
1807
- `error PreparationClosed()` · Selector `0x8e2a3c7d` · Source: `EpochEntropy.sol` lines 41, 103
1920
+ `error PreparationClosed()` · Selector `0x8e2a3c7d` · Source: `EpochEntropy.sol` lines 66, 247
1808
1921
 
1809
1922
  **Raised by:** [`getEpochSelection`](#registry-fn-getepochselection), [`getEpochFallbackSelection`](#registry-fn-getepochfallbackselection), [`checkpointEpoch`](#registry-fn-checkpointepoch).
1810
1923
 
@@ -1814,7 +1927,7 @@ The epoch has not started (`block.number` is below `epochStart(epochId)`), so it
1814
1927
 
1815
1928
  #### <a id="registry-error-anchorunavailable"></a>`AnchorUnavailable`
1816
1929
 
1817
- `error AnchorUnavailable()` · Selector `0x60776ed3` · Source: `EpochEntropy.sol` lines 41, 106
1930
+ `error AnchorUnavailable()` · Selector `0x60776ed3` · Source: `EpochEntropy.sol` lines 66, 250
1818
1931
 
1819
1932
  **Raised by:** [`getEpochSelection`](#registry-fn-getepochselection), [`getEpochFallbackSelection`](#registry-fn-getepochfallbackselection), [`checkpointEpoch`](#registry-fn-checkpointepoch), [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback).
1820
1933
 
@@ -1826,17 +1939,17 @@ The anchor (hash of block `epochStart - 1`) was never checkpointed and is outsid
1826
1939
 
1827
1940
  #### <a id="registry-error-onlycommitter"></a>`OnlyCommitter`
1828
1941
 
1829
- `error OnlyCommitter()` · Selector `0xfffe5af3` · Source: `EpochEntropy.sol` lines 42, 141
1942
+ `error OnlyCommitter()` · Selector `0xfffe5af3` · Source: `EpochEntropy.sol` lines 67, 281
1830
1943
 
1831
1944
  **Raised by:** [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback).
1832
1945
 
1833
- A commit from an address other than `committer()`.
1946
+ A commit from an address that is neither `committer()` nor an allowed backup committer.
1834
1947
 
1835
- **What to do:** Only the committer publishes.
1948
+ **What to do:** Only the committer and backup committers publish; check `isBackupCommitter`.
1836
1949
 
1837
1950
  #### <a id="registry-error-alreadycommitted"></a>`AlreadyCommitted`
1838
1951
 
1839
- `error AlreadyCommitted()` · Selector `0xbfec5558` · Source: `EpochEntropy.sol` lines 42, 142
1952
+ `error AlreadyCommitted()` · Selector `0xbfec5558` · Source: `EpochEntropy.sol` lines 67, 282
1840
1953
 
1841
1954
  **Raised by:** [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback).
1842
1955
 
@@ -1846,7 +1959,7 @@ The epoch already has a published packet.
1846
1959
 
1847
1960
  #### <a id="registry-error-fallbacknotopen"></a>`FallbackNotOpen`
1848
1961
 
1849
- `error FallbackNotOpen()` · Selector `0xf8635228` · Source: `EpochEntropy.sol` lines 44, 143
1962
+ `error FallbackNotOpen()` · Selector `0xf8635228` · Source: `EpochEntropy.sol` lines 69, 283
1850
1963
 
1851
1964
  **Raised by:** [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback).
1852
1965
 
@@ -1856,17 +1969,17 @@ The epoch already has a published packet.
1856
1969
 
1857
1970
  #### <a id="registry-error-invalidfallback"></a>`InvalidFallback`
1858
1971
 
1859
- `error InvalidFallback()` · Selector `0x5a93724d` · Source: `EpochEntropy.sol` lines 44, 112, 117, 137
1972
+ `error InvalidFallback()` · Selector `0x5a93724d` · Source: `EpochEntropy.sol` lines 69, 258, 266, 277
1860
1973
 
1861
1974
  **Raised by:** [`getEpochFallbackSelection`](#registry-fn-getepochfallbackselection), [`fallbackOpensAt`](#registry-fn-fallbackopensat), [`commitEpochFallback`](#registry-fn-commitepochfallback).
1862
1975
 
1863
- An attempt above `MAX_FALLBACK_ATTEMPT`, or attempt 0 passed to `commitEpochFallback`.
1976
+ An attempt at or above `sourceCountAt(epochId)`, or attempt 0 passed to `commitEpochFallback`.
1864
1977
 
1865
- **What to do:** Use attempts 1 to 3 for fallbacks.
1978
+ **What to do:** Use attempts 1 to `sourceCountAt(epochId) - 1` for fallbacks.
1866
1979
 
1867
1980
  #### <a id="registry-error-invalidtime"></a>`InvalidTime`
1868
1981
 
1869
- `error InvalidTime()` · Selector `0x6f7eac26` · Source: `EpochEntropy.sol` lines 42, 145
1982
+ `error InvalidTime()` · Selector `0x6f7eac26` · Source: `EpochEntropy.sol` lines 67, 285
1870
1983
 
1871
1984
  **Raised by:** [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback).
1872
1985
 
@@ -1876,23 +1989,23 @@ The attestation timestamp is in the future or more than `MAX_ATTESTATION_AGE` (2
1876
1989
 
1877
1990
  #### <a id="registry-error-invaliddata"></a>`InvalidData`
1878
1991
 
1879
- `error InvalidData()` · Selector `0x5cb045db` · Source: `EpochEntropy.sol` lines 42, 160–204
1992
+ `error InvalidData()` · Selector `0x5cb045db` · Source: `EpochEntropy.sol` lines 67, 286–287
1880
1993
 
1881
1994
  **Raised by:** [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback).
1882
1995
 
1883
- The signed data is empty, longer than 128 bytes, or not in the fixed format of the slot.
1996
+ The signed data does not match the data template of the slot's recipe exactly, which includes data longer than `MAX_DATA_BYTES` (128).
1884
1997
 
1885
- **What to do:** Publish only a validated response for the selected slot.
1998
+ **What to do:** Publish only a response that matches the selected recipe's template, unmodified.
1886
1999
 
1887
2000
  #### <a id="registry-error-invalidsigner"></a>`InvalidSigner`
1888
2001
 
1889
- `error InvalidSigner()` · Selector `0x815e1d64` · Source: `EpochEntropy.sol` lines 42, 148
2002
+ `error InvalidSigner()` · Selector `0x815e1d64` · Source: `EpochEntropy.sol` lines 67, 289
1890
2003
 
1891
2004
  **Raised by:** [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback).
1892
2005
 
1893
2006
  The signature does not recover to the slot's signer in the epoch's catalog.
1894
2007
 
1895
- **What to do:** Use `signersAt(epochId)` for the expected signer.
2008
+ **What to do:** Use `catalogAt(epochId)` for the expected signer.
1896
2009
 
1897
2010
  #### <a id="registry-error-ecdsainvalidsignature"></a>`ECDSAInvalidSignature`
1898
2011
 
@@ -1926,31 +2039,51 @@ The signature has a high `s` value (OpenZeppelin `ECDSA`).
1926
2039
 
1927
2040
  #### <a id="registry-error-packettoolarge"></a>`PacketTooLarge`
1928
2041
 
1929
- `error PacketTooLarge()` · Selector `0xda85e8a5` · Source: `EpochEntropy.sol` lines 43, 157
2042
+ `error PacketTooLarge()` · Selector `0xda85e8a5` · Source: `EpochEntropy.sol` lines 68, 298
1930
2043
 
1931
2044
  **Raised by:** [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback).
1932
2045
 
1933
2046
  The encoded packet exceeds `MAX_PACKET_BYTES` (2048).
1934
2047
 
1935
- **What to do:** Not reachable with data of at most 128 bytes and a 65-byte signature.
2048
+ **What to do:** Not reachable: the recipe and data bounds keep every packet within `MAX_PACKET_BYTES`.
1936
2049
 
1937
- **Administration, initialization and upgrades**
2050
+ **Recipes, administration, initialization and upgrades**
2051
+
2052
+ #### <a id="registry-error-invalidrecipe"></a>`InvalidRecipe`
2053
+
2054
+ `error InvalidRecipe()` · Selector `0x7b776f4c` · Source: `EpochEntropy.sol` lines 70, 147
2055
+
2056
+ **Raised by:** [`registerRecipe`](#registry-fn-registerrecipe).
2057
+
2058
+ `registerRecipe` got an empty canonical request or body, a canonical request over `MAX_REQUEST_BYTES` or a body over `MAX_BODY_BYTES`, or `MAX_RECIPES` recipes are already registered.
2059
+
2060
+ **What to do:** Shorten the request or body. Recipe ids are never freed.
2061
+
2062
+ #### <a id="registry-error-invalidtemplate"></a>`InvalidTemplate`
2063
+
2064
+ `error InvalidTemplate()` · Selector `0xec55b8cd` · Source: `EpochEntropy.sol` lines 70, 148
2065
+
2066
+ **Raised by:** [`registerRecipe`](#registry-fn-registerrecipe).
2067
+
2068
+ `registerRecipe` got a data template that is not well formed (README [Data templates](README.md#data-templates)).
2069
+
2070
+ **What to do:** Build the template with `encodeDataTemplate`, which names the broken rule, before registering.
1938
2071
 
1939
2072
  #### <a id="registry-error-invalidconfig"></a>`InvalidConfig`
1940
2073
 
1941
- `error InvalidConfig()` · Selector `0x35be3ac8` · Source: `EpochEntropy.sol` lines 41, 53, 63, 69
2074
+ `error InvalidConfig()` · Selector `0x35be3ac8` · Source: `EpochEntropy.sol` lines 66, 81, 93, 101, 108, 110, 140, 196, 200
1942
2075
 
1943
- **Raised by:** [`setCommitter`](#registry-fn-setcommitter), [`scheduleCatalog`](#registry-fn-schedulecatalog), [`initialize`](#registry-fn-initialize).
2076
+ **Raised by:** [`getRecipe`](#registry-fn-getrecipe), [`recipeRequest`](#registry-fn-reciperequest), [`getEpochSelection`](#registry-fn-getepochselection), [`getEpochFallbackSelection`](#registry-fn-getepochfallbackselection), [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback), [`scheduleCatalog`](#registry-fn-schedulecatalog), [`initializeRecipeRegistry`](#registry-fn-initializereciperegistry), [`setCommitter`](#registry-fn-setcommitter), [`setBackupCommitter`](#registry-fn-setbackupcommitter), [`initialize`](#registry-fn-initialize).
1944
2077
 
1945
- A zero signer or committer in `initialize`, a zero address in `setCommitter`, or a zero signer in `scheduleCatalog`.
2078
+ A zero signer or committer in `initialize`; a zero address in `setCommitter`; in `setBackupCommitter` the zero address, allowing the committer, an unchanged status or a fifth backup committer; in `scheduleCatalog` an empty or oversized catalog, mismatched lengths, a repeated or unregistered recipe or a zero signer; `initializeRecipeRegistry` on a registry that already has recipes or a scheduled catalog; or a recipe id that is not registered, in `getRecipe` and `recipeRequest` or, on a registry upgraded without `initializeRecipeRegistry`, in selection and publication.
1946
2079
 
1947
- **What to do:** Use non-zero addresses.
2080
+ **What to do:** Correct the arguments; recipe ids run from 0 to `recipeCount() - 1`. A registry upgraded without the recipe step needs `initializeRecipeRegistry` from its owner.
1948
2081
 
1949
2082
  #### <a id="registry-error-ownableunauthorizedaccount"></a>`OwnableUnauthorizedAccount`
1950
2083
 
1951
2084
  `error OwnableUnauthorizedAccount(address account)` · Selector `0x118cdaa7`
1952
2085
 
1953
- **Raised by:** [`setCommitter`](#registry-fn-setcommitter), [`scheduleCatalog`](#registry-fn-schedulecatalog), [`transferOwnership`](#registry-fn-transferownership), [`acceptOwnership`](#registry-fn-acceptownership), [`renounceOwnership`](#registry-fn-renounceownership), [`upgradeToAndCall`](#registry-fn-upgradetoandcall).
2086
+ **Raised by:** [`registerRecipe`](#registry-fn-registerrecipe), [`scheduleCatalog`](#registry-fn-schedulecatalog), [`initializeRecipeRegistry`](#registry-fn-initializereciperegistry), [`setCommitter`](#registry-fn-setcommitter), [`setBackupCommitter`](#registry-fn-setbackupcommitter), [`transferOwnership`](#registry-fn-transferownership), [`acceptOwnership`](#registry-fn-acceptownership), [`renounceOwnership`](#registry-fn-renounceownership), [`upgradeToAndCall`](#registry-fn-upgradetoandcall).
1954
2087
 
1955
2088
  `account` is not the owner (owner-only functions) or not the pending owner (`acceptOwnership`).
1956
2089
 
@@ -1968,7 +2101,7 @@ A zero signer or committer in `initialize`, a zero address in `setCommitter`, or
1968
2101
 
1969
2102
  #### <a id="registry-error-renouncedisabled"></a>`RenounceDisabled`
1970
2103
 
1971
- `error RenounceDisabled()` · Selector `0x89051165` · Source: `EpochEntropy.sol` lines 43, 60
2104
+ `error RenounceDisabled()` · Selector `0x89051165` · Source: `EpochEntropy.sol` lines 68, 98
1972
2105
 
1973
2106
  **Raised by:** [`renounceOwnership`](#registry-fn-renounceownership).
1974
2107
 
@@ -1980,11 +2113,11 @@ The owner called `renounceOwnership`, which is disabled.
1980
2113
 
1981
2114
  `error InvalidInitialization()` · Selector `0xf92ee8a9`
1982
2115
 
1983
- **Raised by:** [`initialize`](#registry-fn-initialize).
2116
+ **Raised by:** [`initializeRecipeRegistry`](#registry-fn-initializereciperegistry), [`initialize`](#registry-fn-initialize).
1984
2117
 
1985
- `initialize` on a proxy that is already initialized, or on an implementation contract, whose initializers are disabled at construction.
2118
+ `initialize` on a proxy that is already initialized or on an implementation contract, whose initializers are disabled at construction, or `initializeRecipeRegistry` on a proxy that has already reached initializer version 2.
1986
2119
 
1987
- **What to do:** None: initialization happens once, atomically, when `D20Proxy` is deployed.
2120
+ **What to do:** None: `initialize` happens once when `D20Proxy` is deployed, and `initializeRecipeRegistry` once in the recipe-registry upgrade.
1988
2121
 
1989
2122
  #### <a id="registry-error-notinitializing"></a>`NotInitializing`
1990
2123