@d20dao/vrf-sdk 0.3.4 → 0.5.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,23 +2,24 @@
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`, `EpochEntropy` and `D20BeaconVerifier`, generated from the ABIs of `@d20dao/vrf-sdk` 0.5.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.5.0
8
+ - Protocol source: commit `98e537fb249dd0d3365b8d78e6040a9323a65a88`, 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: `74eff72ea9f7f434b628dd98ac921867e68e55cfac609f33d088f2e06c9cbc6c`
12
+ - `abi/D20BeaconVerifier.json` SHA-256: `06a0d6a046a5b790c49ed21cf545f3dfca36c7bac1ef262e927598c01292d6e5`
12
13
 
13
14
  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
15
 
15
16
  ## Conventions
16
17
 
17
- - **Addresses.** Call the proxies listed in README [Deployments](README.md#deployments). The ABIs are those of the implementations; `D20Proxy` adds no functions of its own.
18
+ - **Addresses.** Call the proxies listed in README [Deployments](README.md#deployments). The ABIs are those of the implementations; `D20Proxy` adds no functions of its own. `D20BeaconVerifier` is not behind a proxy: its address is in each beacon registration (`beaconOf`) and in README [Deployments](README.md#deployments).
18
19
  - **Units.** Fees and credits are wei of native USDC, which has 18 decimals (`1e18` is 1 USDC). Deadlines and timestamps are Unix seconds. Ratios are basis points (10000 is 100%).
19
20
  - **Callers.** "Anyone" means any account or contract; "Any contract" means `msg.sender` must have code. Owner-only functions revert `OwnableUnauthorizedAccount` for other callers.
20
21
  - **Selectors.** Functions and errors show their 4-byte selector, events their topic 0, so revert data and logs can be matched by hand.
21
- - **Decoding errors.** `coordinatorAbi` and `epochEntropyAbi` (`@d20dao/vrf-sdk/abi`) contain every custom error below. When your consumer calls the coordinator with an ordinary Solidity call, a coordinator revert is passed through unchanged, so a wallet sending a transaction to your consumer sees the coordinator's selector. Decode with `coordinator.interface.parseError(data)` in ethers or `decodeErrorResult({ abi: coordinatorAbi, data })` in viem, or build your consumer's contract object from its ABI plus the coordinator's error entries. Your consumer's own errors, such as `OnlyCoordinator` and `InvalidCoordinator` from `D20VRFConsumer`, are only in your consumer's ABI. Arithmetic overflow reverts with `Panic(uint256)`.
22
+ - **Decoding errors.** `coordinatorAbi`, `epochEntropyAbi` and `beaconVerifierAbi` (`@d20dao/vrf-sdk/abi`) contain every custom error below. When your consumer calls the coordinator with an ordinary Solidity call, a coordinator revert is passed through unchanged, so a wallet sending a transaction to your consumer sees the coordinator's selector. Decode with `coordinator.interface.parseError(data)` in ethers or `decodeErrorResult({ abi: coordinatorAbi, data })` in viem, or build your consumer's contract object from its ABI plus the coordinator's error entries. Your consumer's own errors, such as `OnlyCoordinator` and `InvalidCoordinator` from `D20VRFConsumer`, are only in your consumer's ABI. Arithmetic overflow reverts with `Panic(uint256)`.
22
23
  - **ethers v6 results.** Structs and multiple return values arrive as `Result` objects, which are arrays. A named value is also a property unless its name collides with an `Array` or `Result` member, such as `length`, `values`, `keys`, `map` or `filter`; read such a value with `result.getValue(name)`, by position, or from `result.toObject()`. Unnamed outputs, such as those of `pricing()`, are positional only.
23
24
  - **Consumer contracts.** `D20VRFConsumer` implements `rawFulfillRandomness(requestId, randomness)` and `onRefund(requestId)`; both revert `OnlyCoordinator` unless called by the coordinator proxy given to its constructor, which reverts `InvalidCoordinator` for an address without code.
24
25
 
@@ -42,10 +43,15 @@ This reference describes that source. A deployment runs it only while the implem
42
43
  - [Epoch state for consumers and verifiers](#registry-epoch-state-for-consumers-and-verifiers)
43
44
  - [Registry reads](#registry-registry-reads)
44
45
  - [Source selection and publication](#registry-source-selection-and-publication)
46
+ - [Recipes and catalogs](#registry-recipes-and-catalogs)
45
47
  - [Owner administration](#registry-owner-administration)
46
48
  - [Constants](#registry-constants)
47
49
  - [Events](#registry-events)
48
50
  - [Errors](#registry-errors)
51
+ - [D20BeaconVerifier](#verifier)
52
+ - [Verification](#verifier-verification)
53
+ - [Constants](#verifier-constants)
54
+ - [Errors](#verifier-errors)
49
55
 
50
56
  ## <a id="coordinator"></a>D20VRFCoordinator
51
57
 
@@ -59,7 +65,7 @@ Requests, fulfillment, `storeBlockHash`, retries, `refundRequest` and withdrawal
59
65
 
60
66
  Returned by `getRequest`. It does not contain the escrowed fee or the refund ratio; read `requestFeePaid` and `requestRefundBps`.
61
67
 
62
- Source: `D20VRFCoordinator.sol` lines 58–76
68
+ Source: `D20VRFCoordinator.sol` lines 61–79
63
69
 
64
70
  | Field | Type | Meaning |
65
71
  | --- | --- | --- |
@@ -123,7 +129,7 @@ Requests must come from a contract and pay at least the fee computed in their ow
123
129
  function quoteFee(uint32 callbackGasLimit) external view returns (uint256)
124
130
  ```
125
131
 
126
- Selector `0xc9caa0c3` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 227–232
132
+ Selector `0xc9caa0c3` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 232–237
127
133
 
128
134
  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
135
 
@@ -135,7 +141,7 @@ Fee for a request with this `callbackGasLimit` priced at `block.basefee`, that i
135
141
  function quoteFeeAt(uint32 callbackGasLimit, uint256 baseFee) external view returns (uint256)
136
142
  ```
137
143
 
138
- Selector `0x26fa8481` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 219–226
144
+ Selector `0x26fa8481` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 224–231
139
145
 
140
146
  `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
147
 
@@ -147,7 +153,7 @@ Selector `0x26fa8481` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol
147
153
  function requestRandomness(bytes32 clientSeed, uint32 callbackGasLimit, address _refundAddress) external payable returns (uint256 requestId)
148
154
  ```
149
155
 
150
- Selector `0x9849d1e5` · Caller: Any contract · Source: `D20VRFCoordinator.sol` lines 234–239, 247–286
156
+ Selector `0x9849d1e5` · Caller: Any contract · Source: `D20VRFCoordinator.sol` lines 239–244, 252–291
151
157
 
152
158
  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
159
 
@@ -161,7 +167,7 @@ Creates a raw request (spec all zero) with `msg.sender` as consumer and returns
161
167
  function requestMappedRandomness(bytes32 clientSeed, uint32 callbackGasLimit, address _refundAddress, RandomnessMapping.Spec spec) external payable returns (uint256 requestId)
162
168
  ```
163
169
 
164
- Selector `0xe6b41a8c` · Caller: Any contract · Source: `D20VRFCoordinator.sol` lines 241–245, 247–286
170
+ Selector `0xe6b41a8c` · Caller: Any contract · Source: `D20VRFCoordinator.sol` lines 246–250, 252–291
165
171
 
166
172
  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
173
 
@@ -175,11 +181,11 @@ Views, callable by anyone. They describe requests created from now on; an existi
175
181
 
176
182
  | Function | Selector | Meaning | Source |
177
183
  | --- | --- | --- | --- |
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 |
184
+ | <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 215–217 |
185
+ | <a id="coordinator-fn-minfee"></a>`minFee() returns (uint256)` | `0x24ec7590` | Minimum fee in wei, at most `MAX_MIN_FEE` (10 USDC). | line 46 |
186
+ | <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 48–49 |
187
+ | <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 50 |
188
+ | <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 51–52 |
183
189
 
184
190
  ### <a id="coordinator-reading-request-state-and-results"></a>Reading request state and results
185
191
 
@@ -191,7 +197,7 @@ Views, callable by anyone, including from a callback. Request IDs start at 1 and
191
197
  function getRequest(uint256 requestId) external view returns (D20VRFCoordinator.Request result)
192
198
  ```
193
199
 
194
- Selector `0xc58343ef` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 288–307, 547–552
200
+ Selector `0xc58343ef` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 293–312, 575–580
195
201
 
196
202
  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
203
 
@@ -203,7 +209,7 @@ Full state of a request, see [`D20VRFCoordinator.Request`](#coordinator-type-d20
203
209
  function getMapping(uint256 requestId) external view returns (RandomnessMapping.Spec)
204
210
  ```
205
211
 
206
- Selector `0xede9ba8b` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 339–342
212
+ Selector `0xede9ba8b` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 344–347
207
213
 
208
214
  The stored [`RandomnessMapping.Spec`](#coordinator-type-randomnessmapping-spec); all fields zero (Raw) for `requestRandomness`.
209
215
 
@@ -215,7 +221,7 @@ The stored [`RandomnessMapping.Spec`](#coordinator-type-randomnessmapping-spec);
215
221
  function getMappedResult(uint256 requestId) external view returns (uint256[])
216
222
  ```
217
223
 
218
- Selector `0x8f09a3e6` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 344–348
224
+ Selector `0x8f09a3e6` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 349–353
219
225
 
220
226
  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
227
 
@@ -227,7 +233,7 @@ The accepted word mapped with the stored spec: `[uint256(word)]` for a raw reque
227
233
  function mapRandomness(bytes32 randomness, RandomnessMapping.Spec spec) external pure returns (uint256[])
228
234
  ```
229
235
 
230
- Selector `0x41c2a199` · Caller: Anyone (pure) · Source: `D20VRFCoordinator.sol` lines 350–355
236
+ Selector `0x41c2a199` · Caller: Anyone (pure) · Source: `D20VRFCoordinator.sol` lines 355–360
231
237
 
232
238
  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
239
 
@@ -239,7 +245,7 @@ Maps any word with any valid spec, like the SDK `mapRandomness(word, spec)` off-
239
245
  function requestFeePaid(uint256 requestId) external view returns (uint256)
240
246
  ```
241
247
 
242
- Selector `0xef7cc992` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 330–333
248
+ Selector `0xef7cc992` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 335–338
243
249
 
244
250
  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
251
 
@@ -251,7 +257,7 @@ Fee escrowed by the request (`feePaid` in `RandomnessRequested`), excluding any
251
257
  function requestRefundBps(uint256 requestId) external view returns (uint16)
252
258
  ```
253
259
 
254
- Selector `0x5d170fd7` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 334–337
260
+ Selector `0x5d170fd7` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 339–342
255
261
 
256
262
  Refund ratio the request copied from `refundBps` at creation. An expiry refund pays `requestFeePaid × requestRefundBps / 10000`; a later `setRefundBps` does not change it.
257
263
 
@@ -263,7 +269,7 @@ Refund ratio the request copied from `refundBps` at creation. An expiry refund p
263
269
  function refundCallbackDelivered(uint256) external view returns (bool)
264
270
  ```
265
271
 
266
- Selector `0x281d3157` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 102, 500
272
+ Selector `0x281d3157` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 105, 528
267
273
 
268
274
  True once an `onRefund` notification for the request succeeded, at `refundRequest` or `retryRefundCallback`. Returns false for unknown IDs instead of reverting.
269
275
 
@@ -273,7 +279,7 @@ True once an `onRefund` notification for the request succeeded, at `refundReques
273
279
  function nextRequestId() external view returns (uint256)
274
280
  ```
275
281
 
276
- Selector `0x6a84a985` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 50, 172, 260
282
+ Selector `0x6a84a985` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 53, 175, 265
277
283
 
278
284
  ID the next request will receive. Issued IDs are 1 to `nextRequestId() - 1`.
279
285
 
@@ -287,7 +293,7 @@ Recovery calls need no value or role. They forward gas to the consumer and rever
287
293
  function refundRequest(uint256 requestId) external
288
294
  ```
289
295
 
290
- Selector `0x7411484e` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 455–479, 491–502
296
+ Selector `0x7411484e` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 483–507, 519–530
291
297
 
292
298
  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
299
 
@@ -301,7 +307,7 @@ Refunds an unfulfilled request once a block timestamp is after its deadline. Mar
301
307
  function retryCallback(uint256 requestId, uint32 gasLimit) external
302
308
  ```
303
309
 
304
- Selector `0xdd11c275` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 445–453, 602–619
310
+ Selector `0xdd11c275` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 473–481, 630–647
305
311
 
306
312
  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
313
 
@@ -315,7 +321,7 @@ Calls `rawFulfillRandomness` again with the same accepted word after a failed ca
315
321
  function retryRefundCallback(uint256 requestId, uint32 gasLimit) external
316
322
  ```
317
323
 
318
- Selector `0x054f6962` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 481–489, 491–502
324
+ Selector `0x054f6962` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 509–517, 519–530
319
325
 
320
326
  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
327
 
@@ -329,7 +335,7 @@ Repeats a failed `onRefund` notification for a refunded request with `gasLimit`
329
335
  function withdrawRefundCredit(address recipient) external
330
336
  ```
331
337
 
332
- Selector `0x445071f2` · Caller: Refund-credit holder · Source: `D20VRFCoordinator.sol` lines 504–514
338
+ Selector `0x445071f2` · Caller: Refund-credit holder · Source: `D20VRFCoordinator.sol` lines 532–542
333
339
 
334
340
  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
341
 
@@ -343,7 +349,7 @@ Sends all of the caller's refund credit, `refundCredits(msg.sender)`, to `recipi
343
349
  function withdrawFees(address recipient) external
344
350
  ```
345
351
 
346
- Selector `0x164e68de` · Caller: Fee recipient · Source: `D20VRFCoordinator.sol` lines 516–525
352
+ Selector `0x164e68de` · Caller: Fee recipient · Source: `D20VRFCoordinator.sol` lines 544–553
347
353
 
348
354
  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
355
 
@@ -357,7 +363,7 @@ Sends all `earnedFees` to `recipient`. Fees accrue at acceptance (fee minus keep
357
363
  function withdrawKeeperCredit(address recipient) external
358
364
  ```
359
365
 
360
- Selector `0xf62c546b` · Caller: Keeper-credit holder · Source: `D20VRFCoordinator.sol` lines 527–536
366
+ Selector `0xf62c546b` · Caller: Keeper-credit holder · Source: `D20VRFCoordinator.sol` lines 555–564
361
367
 
362
368
  Sends all of the caller's keeper credit (keeper-share transfers that failed) to `recipient`.
363
369
 
@@ -371,17 +377,17 @@ Views, callable by anyone. Amounts are in wei of native USDC.
371
377
 
372
378
  | Function | Selector | Meaning | Source |
373
379
  | --- | --- | --- | --- |
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 |
380
+ | <a id="coordinator-fn-refundcredits"></a>`refundCredits(address) returns (uint256)` | `0x61137e40` | Refund credit that an address can withdraw with `withdrawRefundCredit`. | line 59 |
381
+ | <a id="coordinator-fn-totalrefundcredits"></a>`totalRefundCredits() returns (uint256)` | `0x6e0842e1` | Sum of all refund credit held by the coordinator. | line 58 |
382
+ | <a id="coordinator-fn-earnedfees"></a>`earnedFees() returns (uint256)` | `0xb1b3ffd9` | Protocol fees that the fee recipient can withdraw. | line 54 |
383
+ | <a id="coordinator-fn-feerecipient"></a>`feeRecipient() returns (address)` | `0x46904840` | Address allowed to call `withdrawFees`; changed with `setFeeRecipient`. | line 42 |
384
+ | <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 43, 450 |
385
+ | <a id="coordinator-fn-keepercredits"></a>`keeperCredits(address) returns (uint256)` | `0xf5c764f6` | Keeper credit that an address can withdraw with `withdrawKeeperCredit`. | line 44 |
386
+ | <a id="coordinator-fn-totalkeepercredits"></a>`totalKeeperCredits() returns (uint256)` | `0xc7281b7a` | Sum of all keeper credit held by the coordinator. | line 45 |
381
387
 
382
388
  ### <a id="coordinator-keeper-and-proof-functions"></a>Keeper and proof functions
383
389
 
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.
390
+ 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
391
 
386
392
  #### <a id="coordinator-fn-fulfillrandomness"></a>`fulfillRandomness`
387
393
 
@@ -389,9 +395,9 @@ Proof submission is permissionless: anyone holding a valid proof may submit it,
389
395
  function fulfillRandomness(uint256 requestId, VRF.Proof proof) external
390
396
  ```
391
397
 
392
- Selector `0xef7c2b19` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 388–396, 412–437
398
+ Selector `0xef7c2b19` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 393–401, 430–465
393
399
 
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.
400
+ 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
401
 
396
402
  **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
403
 
@@ -403,9 +409,9 @@ Accepts a proof for a request that is not fulfilled, not refunded and not past i
403
409
  function fulfillRandomnessBatch(uint256[] ids, VRF.Proof[] proofs) external
404
410
  ```
405
411
 
406
- Selector `0x9497b180` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 398–410
412
+ Selector `0x9497b180` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 403–428
407
413
 
408
- 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.
414
+ 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. Before any result is revealed it checks that the gas left covers every member that will be served, `140,000 + Σ(callbackGasLimit + callbackGasLimit / 63 + 400,000)`, and reverts `InsufficientCallbackGas` otherwise, so no callback can starve a later member and revert results already revealed.
409
415
 
410
416
  **Emits:** [`FulfillmentSkipped`](#coordinator-event-fulfillmentskipped), [`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).
411
417
 
@@ -417,7 +423,7 @@ Fulfills up to `MAX_FULFILL_BATCH` (16) requests, one proof each. Members alread
417
423
  function storeBlockHash(uint256 requestId) external returns (bytes32)
418
424
  ```
419
425
 
420
- Selector `0x262fd733` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 367–371, 553–568
426
+ Selector `0x262fd733` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 372–376, 581–596
421
427
 
422
428
  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
429
 
@@ -431,7 +437,7 @@ Resolves the target block from the published epoch, stores its hash if not store
431
437
  function verifyRequestProof(uint256 requestId, VRF.Proof proof) external view returns (bytes32)
432
438
  ```
433
439
 
434
- Selector `0x0846de99` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 357–365
440
+ Selector `0x0846de99` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 362–370
435
441
 
436
442
  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
443
 
@@ -443,7 +449,7 @@ Returns the word a proof yields for the request's seed, without changing state.
443
449
  function requestSeed(uint256 requestId) external view returns (uint256)
444
450
  ```
445
451
 
446
- Selector `0xa9df851a` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 373–378, 570–578
452
+ Selector `0xa9df851a` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 378–383, 598–606
447
453
 
448
454
  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
455
 
@@ -455,7 +461,7 @@ Seed the proof must use: `keccak256(abi.encode(SEED_DOMAIN, chainId, coordinator
455
461
  function getProofContext(uint256 requestId) external view returns (uint256 seed, uint64 deadline, bool fulfilled, bool refunded)
456
462
  ```
457
463
 
458
- Selector `0xcf14de9d` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 380–386
464
+ Selector `0xcf14de9d` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 385–391
459
465
 
460
466
  `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
467
 
@@ -467,7 +473,7 @@ Selector `0xcf14de9d` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol
467
473
  function getPendingRequestIds(uint256 fromId, uint256 limit) external view returns (uint256[] ids, uint256 nextCursor)
468
474
  ```
469
475
 
470
- Selector `0xfdfe72e6` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 309–328
476
+ Selector `0xfdfe72e6` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 314–333
471
477
 
472
478
  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
479
 
@@ -479,17 +485,17 @@ Views, callable by anyone. Nothing here has a setter except through an upgrade.
479
485
 
480
486
  | Function | Selector | Meaning | Source |
481
487
  | --- | --- | --- | --- |
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 |
488
+ | <a id="coordinator-fn-lastservedrequestid"></a>`lastServedRequestId() returns (uint256)` | `0xef54e226` | ID of the most recently accepted request; 0 before the first. | lines 55, 452 |
489
+ | <a id="coordinator-fn-lastservedindex"></a>`lastServedIndex() returns (uint256)` | `0x7e176eed` | Number of accepted requests so far: the `serveIndex` of the latest `RequestServed`. | lines 56, 453 |
490
+ | <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 57, 453 |
491
+ | <a id="coordinator-fn-keyhash"></a>`keyHash() returns (bytes32)` | `0x61728f39` | `keccak256(abi.encode(publicKey))` of the VRF key; indexed in `RandomnessRequested` and `ProofVerified`. | lines 39, 182 |
492
+ | <a id="coordinator-fn-publickeyx"></a>`publicKeyX() returns (uint256)` | `0xfa6df55d` | x coordinate of the VRF public key. | line 37 |
493
+ | <a id="coordinator-fn-publickeyy"></a>`publicKeyY() returns (uint256)` | `0xd7a6f6e8` | y coordinate of the VRF public key. | line 38 |
494
+ | <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 47, 583 |
495
+ | <a id="coordinator-fn-epochregistry"></a>`epochRegistry() returns (address)` | `0x2b12cb69` | The `EpochEntropy` proxy that supplies epochs and the keeper-share recipient. | line 35 |
496
+ | <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 34, 192 |
497
+ | <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 41, 184 |
498
+ | <a id="coordinator-fn-initialminfee"></a>`initialMinFee() returns (uint256)` | `0xb3839295` | Minimum fee given to `initialize`, used by replay. The live minimum is `minFee()`. | lines 107, 187 |
493
499
 
494
500
  ### <a id="coordinator-owner-administration"></a>Owner administration
495
501
 
@@ -501,9 +507,9 @@ Owner-only functions revert `OwnableUnauthorizedAccount` for anyone else. No set
501
507
  function setPricing(uint256 nextMinFee, uint16 multiplier, uint32 overhead) external
502
508
  ```
503
509
 
504
- Selector `0x4c729ce6` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 204–209
510
+ Selector `0x4c729ce6` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 207–214
505
511
 
506
- 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.
512
+ 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). `minFee` and `feeMultiplier` cannot both be zero, so a request is never free. Affects requests created afterwards; open requests keep their escrowed fee.
507
513
 
508
514
  **Emits:** [`PricingChanged`](#coordinator-event-pricingchanged).
509
515
 
@@ -515,7 +521,7 @@ Sets `minFee` (at most `MAX_MIN_FEE`, 10 USDC), `feeMultiplier` (at most `MAX_FE
515
521
  function setRefundBps(uint16 next) external
516
522
  ```
517
523
 
518
- Selector `0x55a94d1b` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 213–217
524
+ Selector `0x55a94d1b` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 218–222
519
525
 
520
526
  Sets the refund ratio for requests created afterwards, `MIN_REFUND_BPS` (5000) to 10000.
521
527
 
@@ -529,7 +535,7 @@ Sets the refund ratio for requests created afterwards, `MIN_REFUND_BPS` (5000) t
529
535
  function setKeeperFeeBps(uint16 next) external
530
536
  ```
531
537
 
532
- Selector `0xe140f0ca` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 200–203
538
+ Selector `0xe140f0ca` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 203–206
533
539
 
534
540
  Sets the keeper share, 0 to 10000 basis points. Read at each acceptance, so it also applies to open requests accepted later.
535
541
 
@@ -543,7 +549,7 @@ Sets the keeper share, 0 to 10000 basis points. Read at each acceptance, so it a
543
549
  function setFeeRecipient(address next) external
544
550
  ```
545
551
 
546
- Selector `0xe74b981b` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 196–199
552
+ Selector `0xe74b981b` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 199–202
547
553
 
548
554
  Sets the address allowed to withdraw `earnedFees`, including fees earned before the change. The zero address is rejected.
549
555
 
@@ -605,7 +611,7 @@ Completes the transfer to the caller and clears the nomination.
605
611
  function renounceOwnership() external view
606
612
  ```
607
613
 
608
- Selector `0x715018a6` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 193–194
614
+ Selector `0x715018a6` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 196–197
609
615
 
610
616
  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
617
 
@@ -643,7 +649,7 @@ ERC-1822 check used by `upgradeToAndCall`. Returns the ERC-1967 implementation s
643
649
  function initialize(uint256[2] publicKey, address initialOwner, address recipient, uint256 fee, uint16 confirmations, address registry, uint16 keeperBps) external
644
650
  ```
645
651
 
646
- Selector `0x56b95b47` · Caller: Once, by `D20Proxy` at deployment · Source: `D20VRFCoordinator.sol` lines 169–190
652
+ Selector `0x56b95b47` · Caller: Once, by `D20Proxy` at deployment · Source: `D20VRFCoordinator.sol` lines 172–193
647
653
 
648
654
  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
655
 
@@ -683,7 +689,7 @@ Views returning values fixed in the implementation code.
683
689
  event RandomnessRequested(uint256 indexed requestId, address indexed consumer, bytes32 indexed keyHash, bytes32 clientSeed, uint64 requestBlock, uint32 callbackGasLimit, uint256 feePaid, address refundAddress, uint64 deadline)
684
690
  ```
685
691
 
686
- Topic 0 `0xaf91b17376114a36689aa115062983bda7b43263a891fb8de0cc69d30d4240ad` · Emitted by: [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness) · Source: `D20VRFCoordinator.sol` lines 138–142, 283–284
692
+ Topic 0 `0xaf91b17376114a36689aa115062983bda7b43263a891fb8de0cc69d30d4240ad` · Emitted by: [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness) · Source: `D20VRFCoordinator.sol` lines 141–145, 288–289
687
693
 
688
694
  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
695
 
@@ -693,7 +699,7 @@ A request was created. `feePaid` is the escrowed fee, not `msg.value`; `deadline
693
699
  event MappingRequested(uint256 indexed requestId, bytes32 indexed mappingHash, RandomnessMapping.Spec spec)
694
700
  ```
695
701
 
696
- Topic 0 `0xbe1c93f40bd74ff9acd22dc40818e36d04e0b8a49b8238d0537c63219c2336dd` · Emitted by: [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness) · Source: `D20VRFCoordinator.sol` lines 157, 285
702
+ Topic 0 `0xbe1c93f40bd74ff9acd22dc40818e36d04e0b8a49b8238d0537c63219c2336dd` · Emitted by: [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness) · Source: `D20VRFCoordinator.sol` lines 160, 290
697
703
 
698
704
  Emitted right after `RandomnessRequested` with the stored spec (all zero for a raw request) and its hash.
699
705
 
@@ -703,7 +709,7 @@ Emitted right after `RandomnessRequested` with the stored spec (all zero for a r
703
709
  event FeeOverpaymentCredited(uint256 indexed requestId, address indexed refundAddress, uint256 amount)
704
710
  ```
705
711
 
706
- Topic 0 `0x8ae693db98f043f48e8f427375449ed5576aba97575e4f7f93ff2c1f6c75dcb5` · Emitted by: [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness) · Source: `D20VRFCoordinator.sol` lines 151, 277–282
712
+ Topic 0 `0x8ae693db98f043f48e8f427375449ed5576aba97575e4f7f93ff2c1f6c75dcb5` · Emitted by: [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness) · Source: `D20VRFCoordinator.sol` lines 154, 282–287
707
713
 
708
714
  `msg.value` exceeded the fee and `amount` was added to `refundCredits(refundAddress)`, independently of what happens to the request. Emitted before `RandomnessRequested`.
709
715
 
@@ -713,7 +719,7 @@ Topic 0 `0x8ae693db98f043f48e8f427375449ed5576aba97575e4f7f93ff2c1f6c75dcb5` ·
713
719
  event BlockHashStored(uint256 indexed requestId, uint64 targetBlock, bytes32 blockHash)
714
720
  ```
715
721
 
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
722
+ Topic 0 `0x81bc3b4ec75af0fb9ed3521d7c766d8f995d04b0735c17d61ff9d468b0f04911` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch), [`storeBlockHash`](#coordinator-fn-storeblockhash) · Source: `D20VRFCoordinator.sol` lines 146, 589–596
717
723
 
718
724
  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
725
 
@@ -723,7 +729,7 @@ The target block hash of the request was stored. Emitted once per request: by `s
723
729
  event RequestServed(uint256 indexed requestId, uint256 indexed serveIndex)
724
730
  ```
725
731
 
726
- Topic 0 `0x2012511e6cebd578bcabff1ef3346edb032cbab8622e9f23d9f15d7d1037267f` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 159, 424–426
732
+ Topic 0 `0x2012511e6cebd578bcabff1ef3346edb032cbab8622e9f23d9f15d7d1037267f` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 162, 452–454
727
733
 
728
734
  A proof was accepted. `serveIndex` counts accepted requests from 1 (`lastServedIndex`, `servedRequestAt`).
729
735
 
@@ -733,7 +739,7 @@ A proof was accepted. `serveIndex` counts accepted requests from 1 (`lastServedI
733
739
  event ProofVerified(uint256 indexed requestId, bytes32 indexed keyHash, uint256 seed, bytes32 proofHash)
734
740
  ```
735
741
 
736
- Topic 0 `0x55bb25be3ecd9f68ceae7cdabf4eabe2e0940bd8fc1c26c0d68ad5f7c5d08d22` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 158, 427
742
+ Topic 0 `0x55bb25be3ecd9f68ceae7cdabf4eabe2e0940bd8fc1c26c0d68ad5f7c5d08d22` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 161, 455
737
743
 
738
744
  Seed and hash of the accepted proof.
739
745
 
@@ -743,9 +749,9 @@ Seed and hash of the accepted proof.
743
749
  event RandomnessFulfilled(uint256 indexed requestId, bytes32 randomness, address indexed submitter)
744
750
  ```
745
751
 
746
- Topic 0 `0x9c82683ee7932041c254d206bcce4241d66a811d53ee7191799cc120777b2b87` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 144, 428
752
+ Topic 0 `0x9c82683ee7932041c254d206bcce4241d66a811d53ee7191799cc120777b2b87` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 147, 456
747
753
 
748
- A proof was accepted and `randomness` is final. `submitter` sent the transaction and is not paid for it.
754
+ 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
755
 
750
756
  #### <a id="coordinator-event-fulfillmentevidence"></a>`FulfillmentEvidence`
751
757
 
@@ -753,7 +759,7 @@ A proof was accepted and `randomness` is final. `submitter` sent the transaction
753
759
  event FulfillmentEvidence(uint256 indexed requestId, bytes32 indexed transcriptHash, bytes packet)
754
760
  ```
755
761
 
756
- Topic 0 `0xa121bbea897439460dfb08c3e6d6af064bc1f31e9477471828a87c5596b92e77` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 162–163, 439–443
762
+ Topic 0 `0xa121bbea897439460dfb08c3e6d6af064bc1f31e9477471828a87c5596b92e77` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 165–166, 467–471
757
763
 
758
764
  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
765
 
@@ -763,7 +769,7 @@ The accepted proof as a 416-byte ABI-encoded packet, indexed by `transcriptHash`
763
769
  event CallbackAttempted(uint256 indexed requestId, bool success, uint32 gasLimit)
764
770
  ```
765
771
 
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
772
+ Topic 0 `0x70f64c0739e827900ae6f2e1317601653f4080bc857f423671fc58d5822f1f4a` · Emitted by: [`retryCallback`](#coordinator-fn-retrycallback), [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 148, 630–647
767
773
 
768
774
  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
775
 
@@ -773,9 +779,9 @@ Result of calling `rawFulfillRandomness` with `gasLimit` gas, at fulfillment and
773
779
  event KeeperFeePaid(uint256 indexed requestId, address indexed keeper, uint256 amount, bool paid)
774
780
  ```
775
781
 
776
- Topic 0 `0x7605929b04963e0365f647d9ab12e7ac4aeba5474bb80f1e1554fccad0584683` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 152, 431–436
782
+ Topic 0 `0x7605929b04963e0365f647d9ab12e7ac4aeba5474bb80f1e1554fccad0584683` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 155, 459–464
777
783
 
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`.
784
+ 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
785
 
780
786
  #### <a id="coordinator-event-fulfillmentskipped"></a>`FulfillmentSkipped`
781
787
 
@@ -783,7 +789,7 @@ At acceptance, when the keeper share is non-zero: `amount` went to `keeper`, the
783
789
  event FulfillmentSkipped(uint256 indexed requestId, uint8 reason)
784
790
  ```
785
791
 
786
- Topic 0 `0x45d96bda73a91db41bdeab56114e5d7b9f42c9f38add9e2d2c6d6f5761203ca3` · Emitted by: [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 160–161, 406–407
792
+ Topic 0 `0x45d96bda73a91db41bdeab56114e5d7b9f42c9f38add9e2d2c6d6f5761203ca3` · Emitted by: [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 163–164, 424–425
787
793
 
788
794
  A batch member was left untouched: `reason` 1 already fulfilled, 2 refunded, 3 past its deadline.
789
795
 
@@ -793,7 +799,7 @@ A batch member was left untouched: `reason` 1 already fulfilled, 2 refunded, 3 p
793
799
  event RequestRefundedTo(uint256 indexed requestId, address indexed refundAddress, uint256 amount, bool paid)
794
800
  ```
795
801
 
796
- Topic 0 `0x0f6107d218fea62a20553f3700dba7c94dcf653bd2027c0bf1ebe0832f42a506` · Emitted by: [`refundRequest`](#coordinator-fn-refundrequest) · Source: `D20VRFCoordinator.sol` lines 154, 477
802
+ Topic 0 `0x0f6107d218fea62a20553f3700dba7c94dcf653bd2027c0bf1ebe0832f42a506` · Emitted by: [`refundRequest`](#coordinator-fn-refundrequest) · Source: `D20VRFCoordinator.sol` lines 157, 505
797
803
 
798
804
  An expired request was refunded: `amount` (`feePaid × requestRefundBps / 10000`) was sent to `refundAddress` (`paid` true) or added to its refund credit (`paid` false).
799
805
 
@@ -803,7 +809,7 @@ An expired request was refunded: `amount` (`feePaid × requestRefundBps / 10000`
803
809
  event RefundCallbackAttempted(uint256 indexed requestId, address indexed consumer, bool success, uint32 gasLimit)
804
810
  ```
805
811
 
806
- Topic 0 `0x88448c9fbcfc67f28f0266e82766e402ccd28115fbb84edb6e5b2597effe83d8` · Emitted by: [`refundRequest`](#coordinator-fn-refundrequest), [`retryRefundCallback`](#coordinator-fn-retryrefundcallback) · Source: `D20VRFCoordinator.sol` lines 156, 491–502
812
+ Topic 0 `0x88448c9fbcfc67f28f0266e82766e402ccd28115fbb84edb6e5b2597effe83d8` · Emitted by: [`refundRequest`](#coordinator-fn-refundrequest), [`retryRefundCallback`](#coordinator-fn-retryrefundcallback) · Source: `D20VRFCoordinator.sol` lines 159, 519–530
807
813
 
808
814
  Result of calling `onRefund(requestId)` on `consumer` with `gasLimit` gas: 100,000 at `refundRequest`, the caller's limit at `retryRefundCallback`.
809
815
 
@@ -815,7 +821,7 @@ Result of calling `onRefund(requestId)` on `consumer` with `gasLimit` gas: 100,0
815
821
  event RefundCreditWithdrawn(address indexed owner, address indexed recipient, uint256 amount)
816
822
  ```
817
823
 
818
- Topic 0 `0x9d520065b24fda0469128acd3f3078de7e43d70ab762aeea8c741bde25070192` · Emitted by: [`withdrawRefundCredit`](#coordinator-fn-withdrawrefundcredit) · Source: `D20VRFCoordinator.sol` lines 155, 513
824
+ Topic 0 `0x9d520065b24fda0469128acd3f3078de7e43d70ab762aeea8c741bde25070192` · Emitted by: [`withdrawRefundCredit`](#coordinator-fn-withdrawrefundcredit) · Source: `D20VRFCoordinator.sol` lines 158, 541
819
825
 
820
826
  `owner`, the credit holder (not the contract owner), withdrew `amount` of refund credit to `recipient`.
821
827
 
@@ -825,7 +831,7 @@ Topic 0 `0x9d520065b24fda0469128acd3f3078de7e43d70ab762aeea8c741bde25070192` ·
825
831
  event KeeperCreditWithdrawn(address indexed keeper, address indexed recipient, uint256 amount)
826
832
  ```
827
833
 
828
- Topic 0 `0x22f05c41968705c032a86a65f8fda7e64483ea5e6b6b27a920d1fbfab94267ba` · Emitted by: [`withdrawKeeperCredit`](#coordinator-fn-withdrawkeepercredit) · Source: `D20VRFCoordinator.sol` lines 153, 535
834
+ Topic 0 `0x22f05c41968705c032a86a65f8fda7e64483ea5e6b6b27a920d1fbfab94267ba` · Emitted by: [`withdrawKeeperCredit`](#coordinator-fn-withdrawkeepercredit) · Source: `D20VRFCoordinator.sol` lines 156, 563
829
835
 
830
836
  `keeper` withdrew `amount` of keeper credit to `recipient`.
831
837
 
@@ -835,7 +841,7 @@ Topic 0 `0x22f05c41968705c032a86a65f8fda7e64483ea5e6b6b27a920d1fbfab94267ba` ·
835
841
  event FeesWithdrawn(address indexed recipient, uint256 amount)
836
842
  ```
837
843
 
838
- Topic 0 `0xc0819c13be868895eb93e40eaceb96de976442fa1d404e5c55f14bb65a8c489a` · Emitted by: [`withdrawFees`](#coordinator-fn-withdrawfees) · Source: `D20VRFCoordinator.sol` lines 146, 524
844
+ Topic 0 `0xc0819c13be868895eb93e40eaceb96de976442fa1d404e5c55f14bb65a8c489a` · Emitted by: [`withdrawFees`](#coordinator-fn-withdrawfees) · Source: `D20VRFCoordinator.sol` lines 149, 552
839
845
 
840
846
  The fee recipient withdrew `amount` of earned fees to `recipient`.
841
847
 
@@ -847,7 +853,7 @@ The fee recipient withdrew `amount` of earned fees to `recipient`.
847
853
  event PricingChanged(uint256 minFee, uint16 feeMultiplier, uint32 fulfillGasOverhead)
848
854
  ```
849
855
 
850
- Topic 0 `0x32806eb5e21ac2f5fb7d11f898c2995e19fdf203c8a5aeed8b824506cd0d44ff` · Emitted by: [`setPricing`](#coordinator-fn-setpricing) · Source: `D20VRFCoordinator.sol` lines 149, 208
856
+ Topic 0 `0x32806eb5e21ac2f5fb7d11f898c2995e19fdf203c8a5aeed8b824506cd0d44ff` · Emitted by: [`setPricing`](#coordinator-fn-setpricing) · Source: `D20VRFCoordinator.sol` lines 152, 213
851
857
 
852
858
  New `minFee`, `feeMultiplier` and `fulfillGasOverhead` for requests created afterwards.
853
859
 
@@ -857,7 +863,7 @@ New `minFee`, `feeMultiplier` and `fulfillGasOverhead` for requests created afte
857
863
  event RefundBpsChanged(uint16 previousBps, uint16 newBps)
858
864
  ```
859
865
 
860
- Topic 0 `0x21e3c4cf3007c4ee385a3936593ff4cfa23fc17bca1175b6350c546f9810e0d8` · Emitted by: [`setRefundBps`](#coordinator-fn-setrefundbps) · Source: `D20VRFCoordinator.sol` lines 150, 216
866
+ Topic 0 `0x21e3c4cf3007c4ee385a3936593ff4cfa23fc17bca1175b6350c546f9810e0d8` · Emitted by: [`setRefundBps`](#coordinator-fn-setrefundbps) · Source: `D20VRFCoordinator.sol` lines 153, 221
861
867
 
862
868
  New refund ratio for requests created afterwards.
863
869
 
@@ -867,7 +873,7 @@ New refund ratio for requests created afterwards.
867
873
  event KeeperFeeBpsChanged(uint16 previousBps, uint16 newBps)
868
874
  ```
869
875
 
870
- Topic 0 `0xa648a60f1d22511c1cc898ca69b633d1a1114079e83734b9ab9a13e0e28c68b7` · Emitted by: [`setKeeperFeeBps`](#coordinator-fn-setkeeperfeebps) · Source: `D20VRFCoordinator.sol` lines 148, 202
876
+ Topic 0 `0xa648a60f1d22511c1cc898ca69b633d1a1114079e83734b9ab9a13e0e28c68b7` · Emitted by: [`setKeeperFeeBps`](#coordinator-fn-setkeeperfeebps) · Source: `D20VRFCoordinator.sol` lines 151, 205
871
877
 
872
878
  New keeper share, applied at later acceptances, including of requests already open.
873
879
 
@@ -877,7 +883,7 @@ New keeper share, applied at later acceptances, including of requests already op
877
883
  event FeeRecipientChanged(address indexed previousRecipient, address indexed newRecipient)
878
884
  ```
879
885
 
880
- Topic 0 `0x0bc21fe5c3ab742ff1d15b5c4477ffbacf1167e618228078fa625edebe7f331d` · Emitted by: [`setFeeRecipient`](#coordinator-fn-setfeerecipient) · Source: `D20VRFCoordinator.sol` lines 147, 198
886
+ Topic 0 `0x0bc21fe5c3ab742ff1d15b5c4477ffbacf1167e618228078fa625edebe7f331d` · Emitted by: [`setFeeRecipient`](#coordinator-fn-setfeerecipient) · Source: `D20VRFCoordinator.sol` lines 150, 201
881
887
 
882
888
  New address allowed to withdraw earned fees.
883
889
 
@@ -927,7 +933,7 @@ Topic 0 `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` ·
927
933
 
928
934
  #### <a id="coordinator-error-contractconsumerrequired"></a>`ContractConsumerRequired`
929
935
 
930
- `error ContractConsumerRequired()` · Selector `0x2b99db1e` · Source: `D20VRFCoordinator.sol` lines 110, 250
936
+ `error ContractConsumerRequired()` · Selector `0x2b99db1e` · Source: `D20VRFCoordinator.sol` lines 113, 255
931
937
 
932
938
  **Raised by:** [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness).
933
939
 
@@ -937,7 +943,7 @@ The caller of a request function has no code: an externally owned account, or a
937
943
 
938
944
  #### <a id="coordinator-error-invalidrefundaddress"></a>`InvalidRefundAddress`
939
945
 
940
- `error InvalidRefundAddress()` · Selector `0xe2fe2726` · Source: `D20VRFCoordinator.sol` lines 124, 251, 506
946
+ `error InvalidRefundAddress()` · Selector `0xe2fe2726` · Source: `D20VRFCoordinator.sol` lines 127, 256, 534
941
947
 
942
948
  **Raised by:** [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness), [`withdrawRefundCredit`](#coordinator-fn-withdrawrefundcredit).
943
949
 
@@ -947,7 +953,7 @@ A request named the zero address as refund address, or `withdrawRefundCredit` na
947
953
 
948
954
  #### <a id="coordinator-error-invalidcallbackgas"></a>`InvalidCallbackGas`
949
955
 
950
- `error InvalidCallbackGas()` · Selector `0x35883c54` · Source: `D20VRFCoordinator.sol` lines 112, 451, 487, 598–600
956
+ `error InvalidCallbackGas()` · Selector `0x35883c54` · Source: `D20VRFCoordinator.sol` lines 115, 479, 515, 626–628
951
957
 
952
958
  **Raised by:** [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness), [`retryCallback`](#coordinator-fn-retrycallback), [`retryRefundCallback`](#coordinator-fn-retryrefundcallback).
953
959
 
@@ -957,7 +963,7 @@ A gas limit is out of range: a request `callbackGasLimit` outside 30,000 to 1,00
957
963
 
958
964
  #### <a id="coordinator-error-incorrectfee"></a>`IncorrectFee`
959
965
 
960
- `error IncorrectFee(uint256 expected, uint256 actual)` · Selector `0xdcf6afcb` · Source: `D20VRFCoordinator.sol` lines 111, 254
966
+ `error IncorrectFee(uint256 expected, uint256 actual)` · Selector `0xdcf6afcb` · Source: `D20VRFCoordinator.sol` lines 114, 259
961
967
 
962
968
  **Raised by:** [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness).
963
969
 
@@ -967,7 +973,7 @@ A gas limit is out of range: a request `callbackGasLimit` outside 30,000 to 1,00
967
973
 
968
974
  #### <a id="coordinator-error-feeoverflow"></a>`FeeOverflow`
969
975
 
970
- `error FeeOverflow()` · Selector `0x8181adca` · Source: `D20VRFCoordinator.sol` lines 135, 224
976
+ `error FeeOverflow()` · Selector `0x8181adca` · Source: `D20VRFCoordinator.sol` lines 138, 229
971
977
 
972
978
  **Raised by:** [`quoteFee`](#coordinator-fn-quotefee), [`quoteFeeAt`](#coordinator-fn-quotefeeat), [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness).
973
979
 
@@ -987,7 +993,7 @@ The spec breaks the rules for its operation (README [Randomness options](README.
987
993
 
988
994
  #### <a id="coordinator-error-epochunavailable"></a>`EpochUnavailable`
989
995
 
990
- `error EpochUnavailable()` · Selector `0x0b3487b8` · Source: `D20VRFCoordinator.sol` lines 108, 258
996
+ `error EpochUnavailable()` · Selector `0x0b3487b8` · Source: `D20VRFCoordinator.sol` lines 111, 263
991
997
 
992
998
  **Raised by:** [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness).
993
999
 
@@ -999,7 +1005,7 @@ The request block is before the registry's first epoch: `epochForBlock(block.num
999
1005
 
1000
1006
  #### <a id="coordinator-error-unknownrequest"></a>`UnknownRequest`
1001
1007
 
1002
- `error UnknownRequest()` · Selector `0x6d080297` · Source: `D20VRFCoordinator.sol` lines 113, 538–541
1008
+ `error UnknownRequest()` · Selector `0x6d080297` · Source: `D20VRFCoordinator.sol` lines 116, 566–569
1003
1009
 
1004
1010
  **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
1011
 
@@ -1009,7 +1015,7 @@ No request has this ID: 0, or not below `nextRequestId()`. An unknown ID also re
1009
1015
 
1010
1016
  #### <a id="coordinator-error-notfulfilled"></a>`NotFulfilled`
1011
1017
 
1012
- `error NotFulfilled()` · Selector `0x07bc6c3e` · Source: `D20VRFCoordinator.sol` lines 117, 346, 448
1018
+ `error NotFulfilled()` · Selector `0x07bc6c3e` · Source: `D20VRFCoordinator.sol` lines 120, 351, 476
1013
1019
 
1014
1020
  **Raised by:** [`getMappedResult`](#coordinator-fn-getmappedresult), [`retryCallback`](#coordinator-fn-retrycallback).
1015
1021
 
@@ -1019,7 +1025,7 @@ No request has this ID: 0, or not below `nextRequestId()`. An unknown ID also re
1019
1025
 
1020
1026
  #### <a id="coordinator-error-alreadydelivered"></a>`AlreadyDelivered`
1021
1027
 
1022
- `error AlreadyDelivered()` · Selector `0xb9f79653` · Source: `D20VRFCoordinator.sol` lines 118, 449
1028
+ `error AlreadyDelivered()` · Selector `0xb9f79653` · Source: `D20VRFCoordinator.sol` lines 121, 477
1023
1029
 
1024
1030
  **Raised by:** [`retryCallback`](#coordinator-fn-retrycallback).
1025
1031
 
@@ -1029,7 +1035,7 @@ No request has this ID: 0, or not below `nextRequestId()`. An unknown ID also re
1029
1035
 
1030
1036
  #### <a id="coordinator-error-refundnotavailable"></a>`RefundNotAvailable`
1031
1037
 
1032
- `error RefundNotAvailable()` · Selector `0x0b4d6981` · Source: `D20VRFCoordinator.sol` lines 127, 460
1038
+ `error RefundNotAvailable()` · Selector `0x0b4d6981` · Source: `D20VRFCoordinator.sol` lines 130, 488
1033
1039
 
1034
1040
  **Raised by:** [`refundRequest`](#coordinator-fn-refundrequest).
1035
1041
 
@@ -1039,7 +1045,7 @@ No request has this ID: 0, or not below `nextRequestId()`. An unknown ID also re
1039
1045
 
1040
1046
  #### <a id="coordinator-error-notrefunded"></a>`NotRefunded`
1041
1047
 
1042
- `error NotRefunded()` · Selector `0xfae7079c` · Source: `D20VRFCoordinator.sol` lines 130, 484
1048
+ `error NotRefunded()` · Selector `0xfae7079c` · Source: `D20VRFCoordinator.sol` lines 133, 512
1043
1049
 
1044
1050
  **Raised by:** [`retryRefundCallback`](#coordinator-fn-retryrefundcallback).
1045
1051
 
@@ -1049,7 +1055,7 @@ No request has this ID: 0, or not below `nextRequestId()`. An unknown ID also re
1049
1055
 
1050
1056
  #### <a id="coordinator-error-refundcallbackalreadydelivered"></a>`RefundCallbackAlreadyDelivered`
1051
1057
 
1052
- `error RefundCallbackAlreadyDelivered()` · Selector `0x6502f8ae` · Source: `D20VRFCoordinator.sol` lines 131, 485
1058
+ `error RefundCallbackAlreadyDelivered()` · Selector `0x6502f8ae` · Source: `D20VRFCoordinator.sol` lines 134, 513
1053
1059
 
1054
1060
  **Raised by:** [`retryRefundCallback`](#coordinator-fn-retryrefundcallback).
1055
1061
 
@@ -1059,19 +1065,19 @@ No request has this ID: 0, or not below `nextRequestId()`. An unknown ID also re
1059
1065
 
1060
1066
  #### <a id="coordinator-error-insufficientcallbackgas"></a>`InsufficientCallbackGas`
1061
1067
 
1062
- `error InsufficientCallbackGas()` · Selector `0xa2c23f0d` · Source: `D20VRFCoordinator.sol` lines 121, 469, 494, 610–611
1068
+ `error InsufficientCallbackGas()` · Selector `0xa2c23f0d` · Source: `D20VRFCoordinator.sol` lines 124, 421, 497, 522, 638–639
1063
1069
 
1064
1070
  **Raised by:** [`refundRequest`](#coordinator-fn-refundrequest), [`retryCallback`](#coordinator-fn-retrycallback), [`retryRefundCallback`](#coordinator-fn-retryrefundcallback), [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch).
1065
1071
 
1066
- Too little gas remained to forward the full callback budget and keep the coordinator's reserve: `gasLimit + gasLimit/63 + 140,000` before a fulfillment callback, `100,000 + 100,000/63 + 140,000` after refund settlement, `gasLimit + gasLimit/63 + 50,000` before a refund notification. The coordinator reverts instead of forwarding less.
1072
+ Too little gas remained to forward the full callback budget and keep the coordinator's reserve: `gasLimit + gasLimit/63 + 140,000` before a fulfillment callback, `100,000 + 100,000/63 + 140,000` after refund settlement, `gasLimit + gasLimit/63 + 50,000` before a refund notification, and `140,000 + Σ(callbackGasLimit + callbackGasLimit / 63 + 400,000)` over the served members before `fulfillRandomnessBatch` reveals any result. The coordinator reverts instead of forwarding less.
1067
1073
 
1068
- **What to do:** Raise the transaction gas limit: 400,000 for `refundRequest`, `gasLimit + 250,000` for `retryCallback`, `gasLimit + 150,000` for `retryRefundCallback` (README [Gas for refund and retry calls](README.md#gas-for-refund-and-retry-calls)). `eth_estimateGas` finds the minimum.
1074
+ **What to do:** Raise the transaction gas limit: 400,000 for `refundRequest`, `gasLimit + 250,000` for `retryCallback`, `gasLimit + 150,000` for `retryRefundCallback` (README [Gas for refund and retry calls](README.md#gas-for-refund-and-retry-calls)); a keeper sizes a batch to the sum above. `eth_estimateGas` finds the minimum.
1069
1075
 
1070
1076
  **Credits and withdrawals**
1071
1077
 
1072
1078
  #### <a id="coordinator-error-norefundcredit"></a>`NoRefundCredit`
1073
1079
 
1074
- `error NoRefundCredit()` · Selector `0x1d59da8e` · Source: `D20VRFCoordinator.sol` lines 128, 508
1080
+ `error NoRefundCredit()` · Selector `0x1d59da8e` · Source: `D20VRFCoordinator.sol` lines 131, 536
1075
1081
 
1076
1082
  **Raised by:** [`withdrawRefundCredit`](#coordinator-fn-withdrawrefundcredit).
1077
1083
 
@@ -1081,7 +1087,7 @@ Too little gas remained to forward the full callback budget and keep the coordin
1081
1087
 
1082
1088
  #### <a id="coordinator-error-transferfailed"></a>`TransferFailed`
1083
1089
 
1084
- `error TransferFailed()` · Selector `0x90b8ec18` · Source: `D20VRFCoordinator.sol` lines 123, 512, 523, 534
1090
+ `error TransferFailed()` · Selector `0x90b8ec18` · Source: `D20VRFCoordinator.sol` lines 126, 540, 551, 562
1085
1091
 
1086
1092
  **Raised by:** [`withdrawRefundCredit`](#coordinator-fn-withdrawrefundcredit), [`withdrawFees`](#coordinator-fn-withdrawfees), [`withdrawKeeperCredit`](#coordinator-fn-withdrawkeepercredit).
1087
1093
 
@@ -1091,7 +1097,7 @@ The `recipient` of `withdrawRefundCredit`, `withdrawFees` or `withdrawKeeperCred
1091
1097
 
1092
1098
  #### <a id="coordinator-error-onlyfeerecipient"></a>`OnlyFeeRecipient`
1093
1099
 
1094
- `error OnlyFeeRecipient()` · Selector `0x07d8ed3d` · Source: `D20VRFCoordinator.sol` lines 122, 518
1100
+ `error OnlyFeeRecipient()` · Selector `0x07d8ed3d` · Source: `D20VRFCoordinator.sol` lines 125, 546
1095
1101
 
1096
1102
  **Raised by:** [`withdrawFees`](#coordinator-fn-withdrawfees).
1097
1103
 
@@ -1101,7 +1107,7 @@ The `recipient` of `withdrawRefundCredit`, `withdrawFees` or `withdrawKeeperCred
1101
1107
 
1102
1108
  #### <a id="coordinator-error-nokeepercredit"></a>`NoKeeperCredit`
1103
1109
 
1104
- `error NoKeeperCredit()` · Selector `0x0d106640` · Source: `D20VRFCoordinator.sol` lines 129, 530
1110
+ `error NoKeeperCredit()` · Selector `0x0d106640` · Source: `D20VRFCoordinator.sol` lines 132, 558
1105
1111
 
1106
1112
  **Raised by:** [`withdrawKeeperCredit`](#coordinator-fn-withdrawkeepercredit).
1107
1113
 
@@ -1113,7 +1119,7 @@ The `recipient` of `withdrawRefundCredit`, `withdrawFees` or `withdrawKeeperCred
1113
1119
 
1114
1120
  #### <a id="coordinator-error-notready"></a>`NotReady`
1115
1121
 
1116
- `error NotReady()` · Selector `0x9488aaa6` · Source: `D20VRFCoordinator.sol` lines 114, 555
1122
+ `error NotReady()` · Selector `0x9488aaa6` · Source: `D20VRFCoordinator.sol` lines 117, 583
1117
1123
 
1118
1124
  **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
1125
 
@@ -1123,7 +1129,7 @@ The request cannot be proven yet: its epoch packet is not published, or `block.n
1123
1129
 
1124
1130
  #### <a id="coordinator-error-blockhashunavailable"></a>`BlockHashUnavailable`
1125
1131
 
1126
- `error BlockHashUnavailable()` · Selector `0xbfc9f0d3` · Source: `D20VRFCoordinator.sol` lines 115, 558
1132
+ `error BlockHashUnavailable()` · Selector `0xbfc9f0d3` · Source: `D20VRFCoordinator.sol` lines 118, 586
1127
1133
 
1128
1134
  **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
1135
 
@@ -1133,7 +1139,7 @@ The target block hash was never stored and is outside the 256-block `BLOCKHASH`
1133
1139
 
1134
1140
  #### <a id="coordinator-error-alreadyfulfilled"></a>`AlreadyFulfilled`
1135
1141
 
1136
- `error AlreadyFulfilled()` · Selector `0x4a4117f9` · Source: `D20VRFCoordinator.sol` lines 116, 392
1142
+ `error AlreadyFulfilled()` · Selector `0x4a4117f9` · Source: `D20VRFCoordinator.sol` lines 119, 397
1137
1143
 
1138
1144
  **Raised by:** [`fulfillRandomness`](#coordinator-fn-fulfillrandomness).
1139
1145
 
@@ -1143,7 +1149,7 @@ The target block hash was never stored and is outside the 256-block `BLOCKHASH`
1143
1149
 
1144
1150
  #### <a id="coordinator-error-requestrefunded"></a>`RequestRefunded`
1145
1151
 
1146
- `error RequestRefunded()` · Selector `0xe0dec416` · Source: `D20VRFCoordinator.sol` lines 126, 393
1152
+ `error RequestRefunded()` · Selector `0xe0dec416` · Source: `D20VRFCoordinator.sol` lines 129, 398
1147
1153
 
1148
1154
  **Raised by:** [`fulfillRandomness`](#coordinator-fn-fulfillrandomness).
1149
1155
 
@@ -1153,7 +1159,7 @@ The target block hash was never stored and is outside the 256-block `BLOCKHASH`
1153
1159
 
1154
1160
  #### <a id="coordinator-error-requestexpired"></a>`RequestExpired`
1155
1161
 
1156
- `error RequestExpired()` · Selector `0xfef01cd2` · Source: `D20VRFCoordinator.sol` lines 125, 394
1162
+ `error RequestExpired()` · Selector `0xfef01cd2` · Source: `D20VRFCoordinator.sol` lines 128, 399
1157
1163
 
1158
1164
  **Raised by:** [`fulfillRandomness`](#coordinator-fn-fulfillrandomness).
1159
1165
 
@@ -1163,7 +1169,7 @@ The target block hash was never stored and is outside the 256-block `BLOCKHASH`
1163
1169
 
1164
1170
  #### <a id="coordinator-error-wrongpublickey"></a>`WrongPublicKey`
1165
1171
 
1166
- `error WrongPublicKey()` · Selector `0x2b0bb68e` · Source: `D20VRFCoordinator.sol` lines 119, 583
1172
+ `error WrongPublicKey()` · Selector `0x2b0bb68e` · Source: `D20VRFCoordinator.sol` lines 122, 611
1167
1173
 
1168
1174
  **Raised by:** [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch), [`verifyRequestProof`](#coordinator-fn-verifyrequestproof).
1169
1175
 
@@ -1173,7 +1179,7 @@ The proof's `pk` is not the coordinator's VRF key.
1173
1179
 
1174
1180
  #### <a id="coordinator-error-wrongseed"></a>`WrongSeed`
1175
1181
 
1176
- `error WrongSeed()` · Selector `0xf36cbea4` · Source: `D20VRFCoordinator.sol` lines 120, 585
1182
+ `error WrongSeed()` · Selector `0xf36cbea4` · Source: `D20VRFCoordinator.sol` lines 123, 613
1177
1183
 
1178
1184
  **Raised by:** [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch), [`verifyRequestProof`](#coordinator-fn-verifyrequestproof).
1179
1185
 
@@ -1183,7 +1189,7 @@ The proof's `seed` differs from `requestSeed(requestId)`.
1183
1189
 
1184
1190
  #### <a id="coordinator-error-evidencepackettoolarge"></a>`EvidencePacketTooLarge`
1185
1191
 
1186
- `error EvidencePacketTooLarge()` · Selector `0xcfbc3ebf` · Source: `D20VRFCoordinator.sol` lines 133, 441
1192
+ `error EvidencePacketTooLarge()` · Selector `0xcfbc3ebf` · Source: `D20VRFCoordinator.sol` lines 136, 469
1187
1193
 
1188
1194
  **Raised by:** [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch).
1189
1195
 
@@ -1193,7 +1199,7 @@ The encoded proof exceeds `MAX_EVIDENCE_PACKET_BYTES`. A proof always encodes to
1193
1199
 
1194
1200
  #### <a id="coordinator-error-invalidbatch"></a>`InvalidBatch`
1195
1201
 
1196
- `error InvalidBatch()` · Selector `0x33b094a1` · Source: `D20VRFCoordinator.sol` lines 136, 403
1202
+ `error InvalidBatch()` · Selector `0x33b094a1` · Source: `D20VRFCoordinator.sol` lines 139, 410
1197
1203
 
1198
1204
  **Raised by:** [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch).
1199
1205
 
@@ -1203,7 +1209,7 @@ The encoded proof exceeds `MAX_EVIDENCE_PACKET_BYTES`. A proof always encodes to
1203
1209
 
1204
1210
  #### <a id="coordinator-error-invalidscan"></a>`InvalidScan`
1205
1211
 
1206
- `error InvalidScan()` · Selector `0x3e6249a2` · Source: `D20VRFCoordinator.sol` lines 132, 315
1212
+ `error InvalidScan()` · Selector `0x3e6249a2` · Source: `D20VRFCoordinator.sol` lines 135, 320
1207
1213
 
1208
1214
  **Raised by:** [`getPendingRequestIds`](#coordinator-fn-getpendingrequestids).
1209
1215
 
@@ -1215,17 +1221,17 @@ The encoded proof exceeds `MAX_EVIDENCE_PACKET_BYTES`. A proof always encodes to
1215
1221
 
1216
1222
  #### <a id="coordinator-error-invalidconfig"></a>`InvalidConfig`
1217
1223
 
1218
- `error InvalidConfig()` · Selector `0x35be3ac8` · Source: `D20VRFCoordinator.sol` lines 107, 173–174, 197, 201, 206, 215, 519, 528
1224
+ `error InvalidConfig()` · Selector `0x35be3ac8` · Source: `D20VRFCoordinator.sol` lines 110, 176–177, 200, 204, 209, 211, 220, 547, 556
1219
1225
 
1220
1226
  **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
1227
 
1222
- A value is out of bounds: in `initialize` (zero fee recipient, confirmations 0 or above 64, keeper share above 10000, minimum fee above 10 USDC, registry without code), `setFeeRecipient` with zero, `setKeeperFeeBps` above 10000, `setPricing` outside its bounds, `setRefundBps` outside 5000 to 10000, or a zero `recipient` for `withdrawFees` or `withdrawKeeperCredit`.
1228
+ A value is out of bounds: in `initialize` (zero fee recipient, confirmations 0 or above 64, keeper share above 10000, minimum fee above 10 USDC, registry without code), `setFeeRecipient` with zero, `setKeeperFeeBps` above 10000, `setPricing` outside its bounds or with `minFee` and `feeMultiplier` both zero, `setRefundBps` outside 5000 to 10000, or a zero `recipient` for `withdrawFees` or `withdrawKeeperCredit`.
1223
1229
 
1224
1230
  **What to do:** Use values within the bounds given for each function.
1225
1231
 
1226
1232
  #### <a id="coordinator-error-invalidpublickey"></a>`InvalidPublicKey`
1227
1233
 
1228
- `error InvalidPublicKey()` · Selector `0xa2d0fee8` · Source: `D20VRFCoordinator.sol` lines 109, 176
1234
+ `error InvalidPublicKey()` · Selector `0xa2d0fee8` · Source: `D20VRFCoordinator.sol` lines 112, 179
1229
1235
 
1230
1236
  **Raised by:** [`initialize`](#coordinator-fn-initialize).
1231
1237
 
@@ -1265,7 +1271,7 @@ A `nonReentrant` coordinator function (a request, `storeBlockHash`, a fulfillmen
1265
1271
 
1266
1272
  #### <a id="coordinator-error-renouncedisabled"></a>`RenounceDisabled`
1267
1273
 
1268
- `error RenounceDisabled()` · Selector `0x89051165` · Source: `D20VRFCoordinator.sol` lines 134, 194
1274
+ `error RenounceDisabled()` · Selector `0x89051165` · Source: `D20VRFCoordinator.sol` lines 137, 197
1269
1275
 
1270
1276
  **Raised by:** [`renounceOwnership`](#coordinator-fn-renounceownership).
1271
1277
 
@@ -1357,21 +1363,25 @@ The initialization call made by `upgradeToAndCall` reverted without revert data.
1357
1363
 
1358
1364
  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
1365
 
1366
+ 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 body keepers send: the JSON they post to the provider gateway, or a beacon's canonical request. A catalog lists 1 to `MAX_SOURCES` registered recipes with one signer each; each epoch selects from the catalog in force for it (`catalogAt`).
1367
+
1368
+ A recipe is one of two kinds. A signed API recipe (`registerRecipe`) commits a provider's signed record: the signature is an EIP-191 signature of the recipe's signer over the query hash, timestamp and data. A beacon recipe (`registerBeacon`) commits one round of a public randomness beacon such as drand: the data is the round number, the timestamp the round's scheduled time and the signature the beacon's, which the verifier recorded in its registration (`beaconOf`) checks. Its catalog signer is `slotSigner(recipe)`, an identity derived from the registration. Replay needs the registration of every beacon recipe an epoch used (README [Beacon epochs](README.md#beacon-epochs)).
1369
+
1360
1370
  ### <a id="registry-types"></a>Types
1361
1371
 
1362
1372
  #### <a id="registry-type-epochentropy-epoch"></a>`EpochEntropy.Epoch`
1363
1373
 
1364
1374
  Returned by `getEpoch`; all zero until the epoch is published.
1365
1375
 
1366
- Source: `EpochEntropy.sol` lines 30–33
1376
+ Source: `EpochEntropy.sol` lines 57–60
1367
1377
 
1368
1378
  | Field | Type | Meaning |
1369
1379
  | --- | --- | --- |
1370
1380
  | `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. |
1381
+ | `catalogHash` | `bytes32` | Hash of the catalog in force for the epoch (`catalogAt`) at publication. |
1372
1382
  | `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. |
1383
+ | `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. |
1384
+ | `queryHash` | `bytes32` | `keccak256` of the canonical request of the slot's recipe. |
1375
1385
  | `dataHash` | `bytes32` | `keccak256` of the signed response data. |
1376
1386
  | `attestationHash` | `bytes32` | `keccak256(abi.encode(queryHash, timestamp, dataHash, keccak256(signature)))`. |
1377
1387
  | `signedAt` | `uint256` | Attestation timestamp in Unix seconds. |
@@ -1381,27 +1391,42 @@ Source: `EpochEntropy.sol` lines 30–33
1381
1391
 
1382
1392
  Returned by `getEpochSelection` and `getEpochFallbackSelection`.
1383
1393
 
1384
- Source: `EpochEntropy.sol` lines 29, 121–133
1394
+ Source: `EpochEntropy.sol` lines 55–56, 345–356
1385
1395
 
1386
1396
  | Field | Type | Meaning |
1387
1397
  | --- | --- | --- |
1388
- | `source` | `uint8` | Source slot 0 to 3: Hyperliquid BTC volume, ANU, TickerLayer BTCUSD, TickerLayer ETHUSD. |
1389
- | `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`. |
1398
+ | `source` | `uint8` | Slot in the epoch's catalog. |
1399
+ | `recipe` | `uint8` | Registered recipe id at that slot. |
1400
+ | `airnode` | `address` | Signer of that slot in the catalog in force for the epoch; for a beacon recipe its `slotSigner`. |
1401
+ | `selector` | `bytes32` | `keccak256(abi.encode(SELECT_DOMAIN, catalogHash, epochId, anchor))`; the slot is `(selector mod count + attempt) mod count` with `count = sourceCountAt(epochId)`. |
1391
1402
  | `queryHash` | `bytes32` | `keccak256` of `canonicalRequest`. |
1392
- | `canonicalRequest` | `string` | Fixed request string of the slot. |
1403
+ | `canonicalRequest` | `string` | Canonical request of the recipe, as `getRecipe` returns it. |
1393
1404
 
1394
1405
  #### <a id="registry-type-epochentropy-attestation"></a>`EpochEntropy.Attestation`
1395
1406
 
1396
- Signed source response passed to `commitEpoch` and `commitEpochFallback`.
1407
+ Source response passed to `commitEpoch` and `commitEpochFallback`: a signed API record or a beacon round.
1397
1408
 
1398
- Source: `EpochEntropy.sol` lines 28, 140–159
1409
+ Source: `EpochEntropy.sol` lines 54, 363–391
1399
1410
 
1400
1411
  | Field | Type | Meaning |
1401
1412
  | --- | --- | --- |
1402
- | `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. |
1404
- | `signature` | `bytes` | Signature over `toEthSignedMessageHash(keccak256(abi.encodePacked(queryHash, timestamp, data)))`. |
1413
+ | `timestamp` | `uint256` | Signing time in Unix seconds, or for a beacon round its scheduled time `genesis + (round - 1) × period`; not in the future and at most `MAX_ATTESTATION_AGE` old at publication. |
1414
+ | `data` | `bytes` | Signed response bytes, or for a beacon the round number in decimal, at most `MAX_DATA_BYTES` (128), matching the data template of the slot's recipe exactly. |
1415
+ | `signature` | `bytes` | For a signed API recipe, the 65-byte signature over `toEthSignedMessageHash(keccak256(abi.encodePacked(queryHash, timestamp, data)))`. For a beacon recipe, the beacon's 64-byte signature of the round, which the registered verifier checks. |
1416
+
1417
+ #### <a id="registry-type-epochentropy-beacon"></a>`EpochEntropy.Beacon`
1418
+
1419
+ Returned by `beaconOf`: the registration of a beacon recipe. Fixed when `registerBeacon` appends the recipe. All zero for a signed API recipe.
1420
+
1421
+ Source: `EpochEntropy.sol` lines 64–66
1422
+
1423
+ | Field | Type | Meaning |
1424
+ | --- | --- | --- |
1425
+ | `verifier` | `address` | Contract that checks a round's signature under `publicKey` (`IBeaconVerifier`, for drand's evmnet `D20BeaconVerifier`). Zero marks a signed API recipe. |
1426
+ | `genesis` | `uint64` | Scheduled time of round 1, Unix seconds. Round `r` is scheduled at `genesis + (r - 1) × period`. |
1427
+ | `period` | `uint64` | Seconds between rounds. |
1428
+ | `chainHash` | `bytes32` | Identifier of the beacon network (for drand, the chain hash), 32 bytes. The recipe's canonical request is `["drand","<chainHash>"]`. |
1429
+ | `publicKey` | `bytes` | The beacon's group public key as the verifier reads it; for `D20BeaconVerifier` 128 bytes, `x_im ‖ x_re ‖ y_im ‖ y_re` of a BN254 G2 point. |
1405
1430
 
1406
1431
  ### <a id="registry-epoch-state-for-consumers-and-verifiers"></a>Epoch state for consumers and verifiers
1407
1432
 
@@ -1413,7 +1438,7 @@ Views, callable by anyone. Epoch IDs start at 1; each epoch lasts `EPOCH_LENGTH`
1413
1438
  function epochForBlock(uint256 number) external view returns (uint64)
1414
1439
  ```
1415
1440
 
1416
- Selector `0x7018ebb1` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 91–93
1441
+ Selector `0x7018ebb1` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 318–320
1417
1442
 
1418
1443
  Epoch containing a block: 0 before `firstEpochStart`, otherwise `1 + (number - firstEpochStart) / 200`. A request belongs to `epochForBlock(requestBlock)`.
1419
1444
 
@@ -1423,7 +1448,7 @@ Epoch containing a block: 0 before `firstEpochStart`, otherwise `1 + (number - f
1423
1448
  function epochStart(uint64 epochId) external view returns (uint64)
1424
1449
  ```
1425
1450
 
1426
- Selector `0xa1587509` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 87–90
1451
+ Selector `0xa1587509` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 314–317
1427
1452
 
1428
1453
  First block of an epoch: `firstEpochStart + (epochId - 1) × 200`.
1429
1454
 
@@ -1435,29 +1460,97 @@ First block of an epoch: `firstEpochStart + (epochId - 1) × 200`.
1435
1460
  function getEpoch(uint64 epochId) external view returns (EpochEntropy.Epoch)
1436
1461
  ```
1437
1462
 
1438
- Selector `0x12a02c82` · Caller: Anyone (view) · Source: `EpochEntropy.sol` line 108
1463
+ Selector `0x12a02c82` · Caller: Anyone (view) · Source: `EpochEntropy.sol` line 335
1439
1464
 
1440
1465
  The published [`EpochEntropy.Epoch`](#registry-type-epochentropy-epoch) record, or all zero while unpublished; it never reverts. A non-zero `epochHash` means published.
1441
1466
 
1442
- #### <a id="registry-fn-cataloghashat"></a>`catalogHashAt`
1467
+ #### <a id="registry-fn-catalogat"></a>`catalogAt`
1468
+
1469
+ ```solidity
1470
+ function catalogAt(uint64 epochId) external view returns (bytes32 hash, uint8[] recipes, address[] signers)
1471
+ ```
1472
+
1473
+ Selector `0xec993599` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 295–303, 309–313
1474
+
1475
+ 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.
1476
+
1477
+ #### <a id="registry-fn-sourcecountat"></a>`sourceCountAt`
1478
+
1479
+ ```solidity
1480
+ function sourceCountAt(uint64 epochId) external view returns (uint256)
1481
+ ```
1482
+
1483
+ Selector `0x3edc6b12` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 304–308, 309–313
1484
+
1485
+ Number of slots in the catalog in force for an epoch, and so the number of selection attempts, 0 to count - 1.
1486
+
1487
+ #### <a id="registry-fn-getrecipe"></a>`getRecipe`
1488
+
1489
+ ```solidity
1490
+ function getRecipe(uint8 recipe) external view returns (bytes32 queryHash, string canonicalRequest, bytes template, string body)
1491
+ ```
1492
+
1493
+ Selector `0xba01b103` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 150–154, 157–160
1494
+
1495
+ A registered recipe: `queryHash` (`keccak256` of `canonicalRequest`), the canonical request its signer signs, the data template its signed data must match and the body keepers send: the JSON they post to the provider gateway, or for a beacon recipe its canonical request. Registered recipes never change. `readEpochRecipes` in `@d20dao/vrf-sdk/epoch` reads recipes with this view, checks each query hash and adds the registration of a beacon recipe from `beaconOf`.
1496
+
1497
+ **Errors:** [`InvalidConfig`](#registry-error-invalidconfig).
1498
+
1499
+ #### <a id="registry-fn-beaconof"></a>`beaconOf`
1443
1500
 
1444
1501
  ```solidity
1445
- function catalogHashAt(uint64 epochId) external view returns (bytes32 hash)
1502
+ function beaconOf(uint8 recipe) external view returns (EpochEntropy.Beacon)
1446
1503
  ```
1447
1504
 
1448
- Selector `0xab3ee735` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 78, 80–86
1505
+ Selector `0x87533a48` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 188–192
1449
1506
 
1450
- Catalog hash in force for an epoch: the latest scheduled version whose `fromEpoch` is at or below `epochId`, otherwise the initial catalog.
1507
+ The registration of a recipe as an [`EpochEntropy.Beacon`](#registry-type-epochentropy-beacon): verifier, genesis, period, chain hash and public key. A signed API recipe returns all zero. Replay of a beacon epoch needs this registration, and `readEpochRecipes` reads it for every recipe whose canonical request names drand.
1451
1508
 
1452
- #### <a id="registry-fn-signersat"></a>`signersAt`
1509
+ **Errors:** [`InvalidConfig`](#registry-error-invalidconfig).
1510
+
1511
+ #### <a id="registry-fn-slotsigner"></a>`slotSigner`
1453
1512
 
1454
1513
  ```solidity
1455
- function signersAt(uint64 epochId) external view returns (address[4] signers)
1514
+ function slotSigner(uint8 recipe) external view returns (address)
1456
1515
  ```
1457
1516
 
1458
- Selector `0xe788e413` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 79, 80–86
1517
+ Selector `0xb42be3c1` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 193–199
1518
+
1519
+ The signer a catalog lists for a beacon recipe: the low 160 bits of `keccak256(abi.encode(BEACON_DOMAIN, verifier, chainHash, keccak256(publicKey), genesis, period))`, an identity derived from the registration and not a key. Zero for a signed API recipe and for an id that is not registered. `scheduleCatalog` requires it as the signer of a beacon slot; `beaconSlotSigner` in `@d20dao/vrf-sdk` computes it off-chain.
1459
1520
 
1460
- Signers in force for an epoch, in slot order. Replay needs these, not the initial slot getters.
1521
+ #### <a id="registry-fn-verifybeacon"></a>`verifyBeacon`
1522
+
1523
+ ```solidity
1524
+ function verifyBeacon(uint8 recipe, uint64 round, bytes signature) external view returns (bool)
1525
+ ```
1526
+
1527
+ Selector `0x0ccd9ab2` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 200–209
1528
+
1529
+ Whether `signature` is the valid signature of `round` for a beacon recipe, checked the way a publication checks it: the recipe's verifier is called with a fixed allowance of `BEACON_VERIFY_GAS` and only an exact `true` counts. False for a signed API recipe and for an id that is not registered. For a beacon recipe it reverts `BeaconGasTooLow`, and never answers false, when the gas of the call cannot give the verifier its whole allowance; an `eth_call` needs about 461,000 gas.
1530
+
1531
+ **Errors:** [`BeaconGasTooLow`](#registry-error-beacongastoolow).
1532
+
1533
+ #### <a id="registry-fn-reciperequest"></a>`recipeRequest`
1534
+
1535
+ ```solidity
1536
+ function recipeRequest(uint8 recipe) external view returns (string)
1537
+ ```
1538
+
1539
+ Selector `0x7ce4b6e0` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 155–160
1540
+
1541
+ Canonical request of a registered recipe, the same string `getRecipe` returns.
1542
+
1543
+ **Errors:** [`InvalidConfig`](#registry-error-invalidconfig).
1544
+
1545
+ #### <a id="registry-fn-recipecount"></a>`recipeCount`
1546
+
1547
+ ```solidity
1548
+ function recipeCount() external view returns (uint256)
1549
+ ```
1550
+
1551
+ Selector `0x69cfdf74` · Caller: Anyone (view) · Source: `EpochEntropy.sol` line 149
1552
+
1553
+ Number of registered recipes; ids run from 0 to `recipeCount() - 1`.
1461
1554
 
1462
1555
  ### <a id="registry-registry-reads"></a>Registry reads
1463
1556
 
@@ -1465,18 +1558,21 @@ Views, callable by anyone.
1465
1558
 
1466
1559
  | Function | Selector | Meaning | Source |
1467
1560
  | --- | --- | --- | --- |
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 |
1561
+ | <a id="registry-fn-firstepochstart"></a>`firstEpochStart() returns (uint64)` | `0x219f2428` | First block of epoch 1: the initialization block plus 200. | lines 52, 101 |
1562
+ | <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 51, 364 |
1563
+ | <a id="registry-fn-isbackupcommitter"></a>`isBackupCommitter(address account) returns (bool)` | `0x1d97e417` | Whether an address may publish epochs besides `committer()`. | line 136 |
1564
+ | <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 137–139 |
1565
+ | <a id="registry-fn-backupcommittercount"></a>`backupCommitterCount() returns (uint256)` | `0xa815c5bf` | Number of allowed backup committers, at most `MAX_BACKUP_COMMITTERS`. | lines 78, 125–135 |
1566
+ | <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 53, 102 |
1567
+ | <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 68, 324–327 |
1568
+ | <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 46 |
1569
+ | <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 47–48 |
1570
+ | <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 49 |
1571
+ | <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 50 |
1476
1572
 
1477
1573
  ### <a id="registry-source-selection-and-publication"></a>Source selection and publication
1478
1574
 
1479
- Used by the keeper. Publication is restricted to the committer; the selection views and `checkpointEpoch` are open to anyone.
1575
+ Used by keepers. Publication is restricted to the committer and backup committers; the selection views and `checkpointEpoch` are open to anyone.
1480
1576
 
1481
1577
  #### <a id="registry-fn-getepochselection"></a>`getEpochSelection`
1482
1578
 
@@ -1484,11 +1580,11 @@ Used by the keeper. Publication is restricted to the committer; the selection vi
1484
1580
  function getEpochSelection(uint64 epochId) external view returns (EpochEntropy.Selection s)
1485
1581
  ```
1486
1582
 
1487
- Selector `0xec4960ad` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 109, 121–133
1583
+ Selector `0xec4960ad` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 336, 345–356
1488
1584
 
1489
1585
  The selected source of an epoch, attempt 0, as an [`EpochEntropy.Selection`](#registry-type-epochentropy-selection).
1490
1586
 
1491
- **Errors:** [`InvalidEpoch`](#registry-error-invalidepoch), [`PreparationClosed`](#registry-error-preparationclosed), [`AnchorUnavailable`](#registry-error-anchorunavailable).
1587
+ **Errors:** [`InvalidEpoch`](#registry-error-invalidepoch), [`PreparationClosed`](#registry-error-preparationclosed), [`AnchorUnavailable`](#registry-error-anchorunavailable), [`InvalidConfig`](#registry-error-invalidconfig).
1492
1588
 
1493
1589
  #### <a id="registry-fn-getepochfallbackselection"></a>`getEpochFallbackSelection`
1494
1590
 
@@ -1496,11 +1592,11 @@ The selected source of an epoch, attempt 0, as an [`EpochEntropy.Selection`](#re
1496
1592
  function getEpochFallbackSelection(uint64 epochId, uint8 attempt) external view returns (EpochEntropy.Selection s)
1497
1593
  ```
1498
1594
 
1499
- Selector `0x0e5a0e02` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 110–114, 121–133
1595
+ Selector `0x0e5a0e02` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 337–338, 345–356
1500
1596
 
1501
- The source for attempt 0 to `MAX_FALLBACK_ATTEMPT` (3); attempt n uses the slot n positions after the selected one.
1597
+ The source for attempt 0 to `sourceCountAt(epochId) - 1`; attempt n uses the slot n positions after the selected one.
1502
1598
 
1503
- **Errors:** [`InvalidFallback`](#registry-error-invalidfallback), [`InvalidEpoch`](#registry-error-invalidepoch), [`PreparationClosed`](#registry-error-preparationclosed), [`AnchorUnavailable`](#registry-error-anchorunavailable).
1599
+ **Errors:** [`InvalidFallback`](#registry-error-invalidfallback), [`InvalidEpoch`](#registry-error-invalidepoch), [`PreparationClosed`](#registry-error-preparationclosed), [`AnchorUnavailable`](#registry-error-anchorunavailable), [`InvalidConfig`](#registry-error-invalidconfig).
1504
1600
 
1505
1601
  #### <a id="registry-fn-fallbackopensat"></a>`fallbackOpensAt`
1506
1602
 
@@ -1508,9 +1604,9 @@ The source for attempt 0 to `MAX_FALLBACK_ATTEMPT` (3); attempt n uses the slot
1508
1604
  function fallbackOpensAt(uint64 epochId, uint8 attempt) external view returns (uint64)
1509
1605
  ```
1510
1606
 
1511
- Selector `0x98208050` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 115–119
1607
+ Selector `0x98208050` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 339–343
1512
1608
 
1513
- First block at which an attempt may be published: `epochStart + attempt × FALLBACK_DELAY_BLOCKS` (20).
1609
+ 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
1610
 
1515
1611
  **Errors:** [`InvalidFallback`](#registry-error-invalidfallback), [`InvalidEpoch`](#registry-error-invalidepoch).
1516
1612
 
@@ -1520,7 +1616,7 @@ First block at which an attempt may be published: `epochStart + attempt × FALLB
1520
1616
  function nextEpochToPrepare(uint256 number) external view returns (uint64)
1521
1617
  ```
1522
1618
 
1523
- Selector `0xc78fafe2` · Caller: Anyone (view) · Source: `EpochEntropy.sol` line 94
1619
+ Selector `0xc78fafe2` · Caller: Anyone (view) · Source: `EpochEntropy.sol` line 321
1524
1620
 
1525
1621
  Same value as `epochForBlock(number)`.
1526
1622
 
@@ -1530,7 +1626,7 @@ Same value as `epochForBlock(number)`.
1530
1626
  function checkpointEpoch(uint64 epochId) external returns (bytes32 anchor)
1531
1627
  ```
1532
1628
 
1533
- Selector `0x16de78cb` · Caller: Anyone · Source: `EpochEntropy.sol` lines 95–100, 101–107
1629
+ Selector `0x16de78cb` · Caller: Anyone · Source: `EpochEntropy.sol` lines 322–327, 328–334
1534
1630
 
1535
1631
  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
1632
 
@@ -1542,13 +1638,13 @@ Stores the anchor of a started epoch (hash of block `epochStart - 1`) if not sto
1542
1638
  function commitEpoch(uint64 epochId, EpochEntropy.Attestation a) external
1543
1639
  ```
1544
1640
 
1545
- Selector `0xb1580277` · Caller: Committer · Source: `EpochEntropy.sol` lines 134, 140–159
1641
+ Selector `0xb1580277` · Caller: Committer or backup committer · Source: `EpochEntropy.sol` lines 357, 363–391
1546
1642
 
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.
1643
+ 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 and that its data matches the data template of the slot's recipe exactly, then checks its signature. For a signed API recipe the slot's signer in the epoch's catalog must have signed it. For a beacon recipe the timestamp must be the scheduled time of the round in the data, and the verifier of its registration must accept the signature of that round within `BEACON_VERIFY_GAS`; the call reverts `BeaconGasTooLow` when the gas left cannot give the verifier that allowance. 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
1644
 
1549
1645
  **Emits:** [`EpochCommitted`](#registry-event-epochcommitted).
1550
1646
 
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).
1647
+ **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), [`BeaconGasTooLow`](#registry-error-beacongastoolow), [`PacketTooLarge`](#registry-error-packettoolarge).
1552
1648
 
1553
1649
  #### <a id="registry-fn-commitepochfallback"></a>`commitEpochFallback`
1554
1650
 
@@ -1556,13 +1652,73 @@ Publishes the packet of the selected source once per epoch, from the epoch start
1556
1652
  function commitEpochFallback(uint64 epochId, uint8 attempt, EpochEntropy.Attestation a) external
1557
1653
  ```
1558
1654
 
1559
- Selector `0x5768d9a1` · Caller: Committer · Source: `EpochEntropy.sol` lines 135–139, 140–159
1655
+ Selector `0x5768d9a1` · Caller: Committer or backup committer · Source: `EpochEntropy.sol` lines 358–362, 363–391
1560
1656
 
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`.
1657
+ 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
1658
 
1563
1659
  **Emits:** [`EpochCommitted`](#registry-event-epochcommitted).
1564
1660
 
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).
1661
+ **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), [`BeaconGasTooLow`](#registry-error-beacongastoolow), [`PacketTooLarge`](#registry-error-packettoolarge).
1662
+
1663
+ ### <a id="registry-recipes-and-catalogs"></a>Recipes and catalogs
1664
+
1665
+ Owner-only; on Arc Mainnet the owner is the DAO treasury Safe. A recipe or catalog never changes a published epoch.
1666
+
1667
+ #### <a id="registry-fn-registerrecipe"></a>`registerRecipe`
1668
+
1669
+ ```solidity
1670
+ function registerRecipe(string canonicalRequest, bytes template, string body) external returns (uint8 recipe)
1671
+ ```
1672
+
1673
+ Selector `0x5add5c50` · Caller: Owner · Source: `EpochEntropy.sol` lines 141–148, 161–170
1674
+
1675
+ 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.
1676
+
1677
+ **Emits:** [`RecipeRegistered`](#registry-event-reciperegistered).
1678
+
1679
+ **Errors:** [`OwnableUnauthorizedAccount`](#registry-error-ownableunauthorizedaccount), [`InvalidRecipe`](#registry-error-invalidrecipe), [`InvalidTemplate`](#registry-error-invalidtemplate).
1680
+
1681
+ #### <a id="registry-fn-registerbeacon"></a>`registerBeacon`
1682
+
1683
+ ```solidity
1684
+ function registerBeacon(address verifier, bytes32 chainHash, bytes publicKey, uint64 genesis, uint64 period, uint64 sampleRound, bytes sampleSignature) external returns (uint8 recipe)
1685
+ ```
1686
+
1687
+ Selector `0x65afb221` · Caller: Owner · Source: `EpochEntropy.sol` lines 171–187
1688
+
1689
+ Appends a public randomness beacon as an immutable recipe and returns its id. Its canonical request, which is also its body, is `["drand","<chainHash>"]` with the hash in lowercase hex, and its template accepts one round number: 1 to 19 decimal digits without a leading zero. An epoch it serves commits round `r` as the data, the round's scheduled time `genesis + (r - 1) × period` as the timestamp and the beacon's signature of the round, which `verifier` checks under `publicKey`. Reverts `InvalidConfig` for a verifier without code, a zero `chainHash`, `genesis`, `period` or `sampleRound`, a `sampleRound` not yet scheduled, a key the verifier does not accept, or a `sampleSignature` of `sampleRound` it rejects, so that a malformed key or a verifier that misbehaves or needs more gas cannot register. The registration, like the recipe, never changes.
1690
+
1691
+ **Emits:** [`RecipeRegistered`](#registry-event-reciperegistered), [`BeaconRegistered`](#registry-event-beaconregistered).
1692
+
1693
+ **Errors:** [`OwnableUnauthorizedAccount`](#registry-error-ownableunauthorizedaccount), [`InvalidConfig`](#registry-error-invalidconfig), [`BeaconGasTooLow`](#registry-error-beacongastoolow), [`InvalidRecipe`](#registry-error-invalidrecipe).
1694
+
1695
+ #### <a id="registry-fn-schedulecatalog"></a>`scheduleCatalog`
1696
+
1697
+ ```solidity
1698
+ function scheduleCatalog(uint8[] recipes, address[] signers, uint64 fromEpoch) external
1699
+ ```
1700
+
1701
+ Selector `0x42984450` · Caller: Owner · Source: `EpochEntropy.sol` lines 268–294
1702
+
1703
+ 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; the signer of a beacon recipe must be its `slotSigner`. Its hash is `keccak256(abi.encode(RECIPE_DOMAIN, recipes, signers))`. A pending version, one whose `fromEpoch` is two or more epochs after the current one, is replaced, so that version never applies. The version that takes effect at the next epoch is kept, as is every active one, because the next epoch's catalog is already fixed: its snapshot may be prepared and its requests open. The current epoch keeps its catalog.
1704
+
1705
+ **Emits:** [`CatalogScheduled`](#registry-event-catalogscheduled).
1706
+
1707
+ **Errors:** [`OwnableUnauthorizedAccount`](#registry-error-ownableunauthorizedaccount), [`InvalidConfig`](#registry-error-invalidconfig), [`InvalidEpoch`](#registry-error-invalidepoch).
1708
+
1709
+ #### <a id="registry-fn-initializereciperegistry"></a>`initializeRecipeRegistry`
1710
+
1711
+ ```solidity
1712
+ function initializeRecipeRegistry() external
1713
+ ```
1714
+
1715
+ Selector `0x8700b456` · Caller: Owner, once per proxy, as the `upgradeToAndCall` data of the recipe-registry upgrade · Source: `EpochEntropy.sol` lines 105–113
1716
+
1717
+ 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.
1718
+
1719
+ **Emits:** [`RecipeRegistered`](#registry-event-reciperegistered), [`Initialized`](#registry-event-initialized).
1720
+
1721
+ **Errors:** [`InvalidInitialization`](#registry-error-invalidinitialization), [`OwnableUnauthorizedAccount`](#registry-error-ownableunauthorizedaccount), [`InvalidConfig`](#registry-error-invalidconfig).
1566
1722
 
1567
1723
  ### <a id="registry-owner-administration"></a>Owner administration
1568
1724
 
@@ -1574,27 +1730,27 @@ Owner-only functions revert `OwnableUnauthorizedAccount` for anyone else. No set
1574
1730
  function setCommitter(address next) external
1575
1731
  ```
1576
1732
 
1577
- Selector `0xdd51ce22` · Caller: Owner · Source: `EpochEntropy.sol` lines 62–65
1733
+ Selector `0xdd51ce22` · Caller: Owner · Source: `EpochEntropy.sol` lines 118–121
1578
1734
 
1579
- Changes the publishing address, which is also the keeper-share recipient the coordinator reads at each acceptance.
1735
+ Changes the primary publishing address, which is also the keeper-share recipient the coordinator reads at each acceptance.
1580
1736
 
1581
1737
  **Emits:** [`CommitterChanged`](#registry-event-committerchanged).
1582
1738
 
1583
1739
  **Errors:** [`OwnableUnauthorizedAccount`](#registry-error-ownableunauthorizedaccount), [`InvalidConfig`](#registry-error-invalidconfig).
1584
1740
 
1585
- #### <a id="registry-fn-schedulecatalog"></a>`scheduleCatalog`
1741
+ #### <a id="registry-fn-setbackupcommitter"></a>`setBackupCommitter`
1586
1742
 
1587
1743
  ```solidity
1588
- function scheduleCatalog(address[4] signers, uint64 fromEpoch) external
1744
+ function setBackupCommitter(address account, bool allowed) external
1589
1745
  ```
1590
1746
 
1591
- Selector `0x7ad3b7ef` · Caller: Owner · Source: `EpochEntropy.sol` lines 66–77
1747
+ Selector `0xd870d0c6` · Caller: Owner · Source: `EpochEntropy.sol` lines 122–135
1592
1748
 
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.
1749
+ 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
1750
 
1595
- **Emits:** [`CatalogScheduled`](#registry-event-catalogscheduled).
1751
+ **Emits:** [`BackupCommitterSet`](#registry-event-backupcommitterset).
1596
1752
 
1597
- **Errors:** [`OwnableUnauthorizedAccount`](#registry-error-ownableunauthorizedaccount), [`InvalidConfig`](#registry-error-invalidconfig), [`InvalidEpoch`](#registry-error-invalidepoch).
1753
+ **Errors:** [`OwnableUnauthorizedAccount`](#registry-error-ownableunauthorizedaccount), [`InvalidConfig`](#registry-error-invalidconfig).
1598
1754
 
1599
1755
  #### <a id="registry-fn-owner"></a>`owner`
1600
1756
 
@@ -1650,7 +1806,7 @@ Completes the transfer to the caller and clears the nomination.
1650
1806
  function renounceOwnership() external view
1651
1807
  ```
1652
1808
 
1653
- Selector `0x715018a6` · Caller: Owner · Source: `EpochEntropy.sol` lines 59–60
1809
+ Selector `0x715018a6` · Caller: Owner · Source: `EpochEntropy.sol` lines 115–116
1654
1810
 
1655
1811
  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
1812
 
@@ -1688,11 +1844,11 @@ ERC-1822 check used by `upgradeToAndCall`. Returns the ERC-1967 implementation s
1688
1844
  function initialize(address[4] signers, address initialOwner, address initialCommitter) external
1689
1845
  ```
1690
1846
 
1691
- Selector `0xfda9f5ca` · Caller: Once, by `D20Proxy` at deployment · Source: `EpochEntropy.sol` lines 50–57
1847
+ Selector `0xfda9f5ca` · Caller: Once, by `D20Proxy` at deployment · Source: `EpochEntropy.sol` lines 96–104
1692
1848
 
1693
- Sets the four initial signers, owner and committer. Epoch 1 starts 200 blocks after the initialization block.
1849
+ 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
1850
 
1695
- **Emits:** [`OwnershipTransferred`](#registry-event-ownershiptransferred), [`Initialized`](#registry-event-initialized).
1851
+ **Emits:** [`OwnershipTransferred`](#registry-event-ownershiptransferred), [`RecipeRegistered`](#registry-event-reciperegistered), [`Initialized`](#registry-event-initialized).
1696
1852
 
1697
1853
  **Errors:** [`InvalidInitialization`](#registry-error-invalidinitialization), [`OwnableInvalidOwner`](#registry-error-ownableinvalidowner), [`InvalidConfig`](#registry-error-invalidconfig).
1698
1854
 
@@ -1706,15 +1862,23 @@ Views returning values fixed in the implementation code.
1706
1862
  | <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
1863
  | <a id="registry-fn-max_packet_bytes"></a>`MAX_PACKET_BYTES` | `uint256` | `2048` | `0x48cad11b` | Upper bound on the `EpochCommitted` packet. |
1708
1864
  | <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. |
1865
+ | <a id="registry-fn-max_sources"></a>`MAX_SOURCES` | `uint256` | `10` | `0x64aefc06` | Most slots in a catalog. |
1866
+ | <a id="registry-fn-max_recipes"></a>`MAX_RECIPES` | `uint256` | `256` | `0x0c148333` | Most registered recipes; ids are `uint8`. |
1867
+ | <a id="registry-fn-max_request_bytes"></a>`MAX_REQUEST_BYTES` | `uint256` | `1024` | `0x4b62e5b4` | Longest canonical request of a recipe. |
1868
+ | <a id="registry-fn-max_body_bytes"></a>`MAX_BODY_BYTES` | `uint256` | `2048` | `0x2ade18c2` | Longest gateway request body of a recipe. |
1869
+ | <a id="registry-fn-max_data_bytes"></a>`MAX_DATA_BYTES` | `uint256` | `128` | `0xf1d11a6c` | Longest signed data a template accepts. |
1870
+ | <a id="registry-fn-max_template_bytes"></a>`MAX_TEMPLATE_BYTES` | `uint256` | `256` | `0x30a3bee5` | Longest data template. |
1871
+ | <a id="registry-fn-max_backup_committers"></a>`MAX_BACKUP_COMMITTERS` | `uint256` | `4` | `0xc195af66` | Most backup committers allowed at once. |
1710
1872
  | <a id="registry-fn-recipe_domain"></a>`RECIPE_DOMAIN` | `bytes32` | `keccak256("D20_EPOCH_RECIPES")` | `0xacea73c8` | Domain tag of catalog hashes. |
1711
1873
  | <a id="registry-fn-select_domain"></a>`SELECT_DOMAIN` | `bytes32` | `keccak256("D20_EPOCH_SELECT")` | `0x10181587` | Domain tag of the source selector. |
1712
1874
  | <a id="registry-fn-epoch_domain"></a>`EPOCH_DOMAIN` | `bytes32` | `keccak256("D20_EPOCH")` | `0xbece738c` | Domain tag of the epoch commitment. |
1875
+ | <a id="registry-fn-beacon_domain"></a>`BEACON_DOMAIN` | `bytes32` | `keccak256("D20_EPOCH_BEACON")` | `0x11083512` | Domain tag of `slotSigner`. |
1876
+ | <a id="registry-fn-beacon_verify_gas"></a>`BEACON_VERIFY_GAS` | `uint256` | `400_000` | `0x92c32ece` | Gas a beacon verifier gets for one round; `D20BeaconVerifier` uses about 175,000 of it. A call that cannot leave the verifier this much reverts `BeaconGasTooLow`. |
1713
1877
  | <a id="registry-fn-upgrade_interface_version"></a>`UPGRADE_INTERFACE_VERSION` | `string` | `"5.0.0"` | `0xad3cb1cc` | OpenZeppelin UUPS interface version: upgrades go through `upgradeToAndCall` only. |
1714
1878
 
1715
1879
  ### <a id="registry-events"></a>Events
1716
1880
 
1717
- **Epochs**
1881
+ **Epochs, recipes and catalogs**
1718
1882
 
1719
1883
  #### <a id="registry-event-epochcommitted"></a>`EpochCommitted`
1720
1884
 
@@ -1722,19 +1886,39 @@ Views returning values fixed in the implementation code.
1722
1886
  event EpochCommitted(uint64 indexed epochId, bytes32 indexed epochHash, bytes packet)
1723
1887
  ```
1724
1888
 
1725
- Topic 0 `0xc9db8d1389570196eda0f2c6c4e2f78429c2f812db30b6b1022b5e0b5162ef72` · Emitted by: [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback) · Source: `EpochEntropy.sol` lines 45, 156–158
1889
+ Topic 0 `0xc9db8d1389570196eda0f2c6c4e2f78429c2f812db30b6b1022b5e0b5162ef72` · Emitted by: [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback) · Source: `EpochEntropy.sol` lines 88, 388–390
1890
+
1891
+ An epoch was published. `packet` is `abi.encode(canonicalRequest, attestation)`: decode it with `decodeEpochEvidencePacket` and verify with `replayEpochCommitment`, which needs the registration of a beacon recipe as well. Requests of the epoch now have a target block.
1892
+
1893
+ #### <a id="registry-event-reciperegistered"></a>`RecipeRegistered`
1894
+
1895
+ ```solidity
1896
+ event RecipeRegistered(uint8 indexed recipe, bytes32 indexed queryHash, string canonicalRequest, bytes template, string body)
1897
+ ```
1898
+
1899
+ Topic 0 `0xbc5c1e3f4647d7f37dc8b4fe5e26e7c58ae35b8c9fb1db0233ddedba0d7f5fd8` · Emitted by: [`registerRecipe`](#registry-fn-registerrecipe), [`registerBeacon`](#registry-fn-registerbeacon), [`initializeRecipeRegistry`](#registry-fn-initializereciperegistry), [`initialize`](#registry-fn-initialize) · Source: `EpochEntropy.sol` lines 91, 169
1900
+
1901
+ Recipe `recipe` was registered, by `registerRecipe` or by `registerBeacon`. The event carries the complete definition, so every recipe can be rebuilt from logs; `getRecipe` returns the same values.
1902
+
1903
+ #### <a id="registry-event-beaconregistered"></a>`BeaconRegistered`
1904
+
1905
+ ```solidity
1906
+ event BeaconRegistered(uint8 indexed recipe, address indexed verifier, bytes32 indexed chainHash, bytes publicKey, uint64 genesis, uint64 period)
1907
+ ```
1908
+
1909
+ Topic 0 `0x293a9d9da76ea51abf287fd8574e03634d02d6156cf7df7e901320c2de39bf96` · Emitted by: [`registerBeacon`](#registry-fn-registerbeacon) · Source: `EpochEntropy.sol` lines 93, 186
1726
1910
 
1727
- 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.
1911
+ Recipe `recipe` is a beacon recipe: emitted right after its `RecipeRegistered` with the registration `beaconOf` returns (`verifier`, `chainHash`, `publicKey`, `genesis`, `period`).
1728
1912
 
1729
1913
  #### <a id="registry-event-catalogscheduled"></a>`CatalogScheduled`
1730
1914
 
1731
1915
  ```solidity
1732
- event CatalogScheduled(uint64 indexed fromEpoch, bytes32 indexed catalogHash, address[4] signers)
1916
+ event CatalogScheduled(uint64 indexed fromEpoch, bytes32 indexed catalogHash, uint8[] recipes, address[] signers)
1733
1917
  ```
1734
1918
 
1735
- Topic 0 `0xcf1bfe9243f85512c4c62a40dac0bdb9ca6b01cf9bddd6b86f061d0e3685d26a` · Emitted by: [`scheduleCatalog`](#registry-fn-schedulecatalog) · Source: `EpochEntropy.sol` lines 47, 76
1919
+ Topic 0 `0xc91bcd562b1edaabdb2772ada76957511610c715ef892b9c9af1f35be93c3c4f` · Emitted by: [`scheduleCatalog`](#registry-fn-schedulecatalog) · Source: `EpochEntropy.sol` lines 90, 293
1736
1920
 
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)`.
1921
+ A catalog was scheduled for epochs from `fromEpoch`: recipe ids and signers in slot order. A later `CatalogScheduled` emitted while this version is still pending, two or more epochs ahead, replaces it, so when rebuilding catalogs from history drop replaced versions, or read `catalogAt(epochId)`.
1738
1922
 
1739
1923
  **Administration and upgrades**
1740
1924
 
@@ -1744,9 +1928,19 @@ Signers scheduled for epochs from `fromEpoch`. A later `CatalogScheduled` emitte
1744
1928
  event CommitterChanged(address indexed previousCommitter, address indexed newCommitter)
1745
1929
  ```
1746
1930
 
1747
- Topic 0 `0x3f67cc70f736070aaac75db90cef1ab4047521b73e8a38d02852e8bf1a91e7e0` · Emitted by: [`setCommitter`](#registry-fn-setcommitter) · Source: `EpochEntropy.sol` lines 46, 64
1931
+ Topic 0 `0x3f67cc70f736070aaac75db90cef1ab4047521b73e8a38d02852e8bf1a91e7e0` · Emitted by: [`setCommitter`](#registry-fn-setcommitter) · Source: `EpochEntropy.sol` lines 89, 120
1932
+
1933
+ New primary publishing address; it also receives the keeper share of proofs submitted by wallets the registry does not authorize.
1934
+
1935
+ #### <a id="registry-event-backupcommitterset"></a>`BackupCommitterSet`
1936
+
1937
+ ```solidity
1938
+ event BackupCommitterSet(address indexed account, bool allowed)
1939
+ ```
1748
1940
 
1749
- New publishing address and keeper-share recipient.
1941
+ Topic 0 `0x20380b8c17d904db7d905a51f1538057d280a6cecca38882832ba0261b39fa66` · Emitted by: [`setBackupCommitter`](#registry-fn-setbackupcommitter) · Source: `EpochEntropy.sol` lines 92, 134
1942
+
1943
+ `account` may now publish epochs (`allowed` true) or no longer may (`allowed` false).
1750
1944
 
1751
1945
  #### <a id="registry-event-ownershiptransferstarted"></a>`OwnershipTransferStarted`
1752
1946
 
@@ -1784,9 +1978,9 @@ The proxy now runs `implementation`. Emitted by the proxy at deployment and at e
1784
1978
  event Initialized(uint64 version)
1785
1979
  ```
1786
1980
 
1787
- Topic 0 `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` · Emitted by: [`initialize`](#registry-fn-initialize)
1981
+ Topic 0 `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` · Emitted by: [`initializeRecipeRegistry`](#registry-fn-initializereciperegistry), [`initialize`](#registry-fn-initialize)
1788
1982
 
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.
1983
+ `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
1984
 
1791
1985
  ### <a id="registry-errors"></a>Errors
1792
1986
 
@@ -1794,7 +1988,7 @@ Topic 0 `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` ·
1794
1988
 
1795
1989
  #### <a id="registry-error-invalidepoch"></a>`InvalidEpoch`
1796
1990
 
1797
- `error InvalidEpoch()` · Selector `0xd5b25b63` · Source: `EpochEntropy.sol` lines 41, 71, 88
1991
+ `error InvalidEpoch()` · Selector `0xd5b25b63` · Source: `EpochEntropy.sol` lines 83, 284, 315
1798
1992
 
1799
1993
  **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
1994
 
@@ -1804,7 +1998,7 @@ Epoch 0 was passed to `epochStart`, `fallbackOpensAt`, a selection view, `checkp
1804
1998
 
1805
1999
  #### <a id="registry-error-preparationclosed"></a>`PreparationClosed`
1806
2000
 
1807
- `error PreparationClosed()` · Selector `0x8e2a3c7d` · Source: `EpochEntropy.sol` lines 41, 103
2001
+ `error PreparationClosed()` · Selector `0x8e2a3c7d` · Source: `EpochEntropy.sol` lines 83, 330
1808
2002
 
1809
2003
  **Raised by:** [`getEpochSelection`](#registry-fn-getepochselection), [`getEpochFallbackSelection`](#registry-fn-getepochfallbackselection), [`checkpointEpoch`](#registry-fn-checkpointepoch).
1810
2004
 
@@ -1814,7 +2008,7 @@ The epoch has not started (`block.number` is below `epochStart(epochId)`), so it
1814
2008
 
1815
2009
  #### <a id="registry-error-anchorunavailable"></a>`AnchorUnavailable`
1816
2010
 
1817
- `error AnchorUnavailable()` · Selector `0x60776ed3` · Source: `EpochEntropy.sol` lines 41, 106
2011
+ `error AnchorUnavailable()` · Selector `0x60776ed3` · Source: `EpochEntropy.sol` lines 83, 333
1818
2012
 
1819
2013
  **Raised by:** [`getEpochSelection`](#registry-fn-getepochselection), [`getEpochFallbackSelection`](#registry-fn-getepochfallbackselection), [`checkpointEpoch`](#registry-fn-checkpointepoch), [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback).
1820
2014
 
@@ -1826,17 +2020,17 @@ The anchor (hash of block `epochStart - 1`) was never checkpointed and is outsid
1826
2020
 
1827
2021
  #### <a id="registry-error-onlycommitter"></a>`OnlyCommitter`
1828
2022
 
1829
- `error OnlyCommitter()` · Selector `0xfffe5af3` · Source: `EpochEntropy.sol` lines 42, 141
2023
+ `error OnlyCommitter()` · Selector `0xfffe5af3` · Source: `EpochEntropy.sol` lines 84, 364
1830
2024
 
1831
2025
  **Raised by:** [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback).
1832
2026
 
1833
- A commit from an address other than `committer()`.
2027
+ A commit from an address that is neither `committer()` nor an allowed backup committer.
1834
2028
 
1835
- **What to do:** Only the committer publishes.
2029
+ **What to do:** Only the committer and backup committers publish; check `isBackupCommitter`.
1836
2030
 
1837
2031
  #### <a id="registry-error-alreadycommitted"></a>`AlreadyCommitted`
1838
2032
 
1839
- `error AlreadyCommitted()` · Selector `0xbfec5558` · Source: `EpochEntropy.sol` lines 42, 142
2033
+ `error AlreadyCommitted()` · Selector `0xbfec5558` · Source: `EpochEntropy.sol` lines 84, 365
1840
2034
 
1841
2035
  **Raised by:** [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback).
1842
2036
 
@@ -1846,7 +2040,7 @@ The epoch already has a published packet.
1846
2040
 
1847
2041
  #### <a id="registry-error-fallbacknotopen"></a>`FallbackNotOpen`
1848
2042
 
1849
- `error FallbackNotOpen()` · Selector `0xf8635228` · Source: `EpochEntropy.sol` lines 44, 143
2043
+ `error FallbackNotOpen()` · Selector `0xf8635228` · Source: `EpochEntropy.sol` lines 86, 366
1850
2044
 
1851
2045
  **Raised by:** [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback).
1852
2046
 
@@ -1856,43 +2050,53 @@ The epoch already has a published packet.
1856
2050
 
1857
2051
  #### <a id="registry-error-invalidfallback"></a>`InvalidFallback`
1858
2052
 
1859
- `error InvalidFallback()` · Selector `0x5a93724d` · Source: `EpochEntropy.sol` lines 44, 112, 117, 137
2053
+ `error InvalidFallback()` · Selector `0x5a93724d` · Source: `EpochEntropy.sol` lines 86, 341, 349, 360
1860
2054
 
1861
2055
  **Raised by:** [`getEpochFallbackSelection`](#registry-fn-getepochfallbackselection), [`fallbackOpensAt`](#registry-fn-fallbackopensat), [`commitEpochFallback`](#registry-fn-commitepochfallback).
1862
2056
 
1863
- An attempt above `MAX_FALLBACK_ATTEMPT`, or attempt 0 passed to `commitEpochFallback`.
2057
+ An attempt at or above `sourceCountAt(epochId)`, or attempt 0 passed to `commitEpochFallback`.
1864
2058
 
1865
- **What to do:** Use attempts 1 to 3 for fallbacks.
2059
+ **What to do:** Use attempts 1 to `sourceCountAt(epochId) - 1` for fallbacks.
1866
2060
 
1867
2061
  #### <a id="registry-error-invalidtime"></a>`InvalidTime`
1868
2062
 
1869
- `error InvalidTime()` · Selector `0x6f7eac26` · Source: `EpochEntropy.sol` lines 42, 145
2063
+ `error InvalidTime()` · Selector `0x6f7eac26` · Source: `EpochEntropy.sol` lines 84, 368, 378
1870
2064
 
1871
2065
  **Raised by:** [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback).
1872
2066
 
1873
- The attestation timestamp is in the future or more than `MAX_ATTESTATION_AGE` (240 seconds) before the publication block.
2067
+ The attestation timestamp is in the future or more than `MAX_ATTESTATION_AGE` (240 seconds) before the publication block, or, for a beacon recipe, is not the scheduled time of the round in the data.
1874
2068
 
1875
2069
  **What to do:** A saved packet is never refreshed; its requests expire and are refunded.
1876
2070
 
1877
2071
  #### <a id="registry-error-invaliddata"></a>`InvalidData`
1878
2072
 
1879
- `error InvalidData()` · Selector `0x5cb045db` · Source: `EpochEntropy.sol` lines 42, 160–204
2073
+ `error InvalidData()` · Selector `0x5cb045db` · Source: `EpochEntropy.sol` lines 84, 369–370
1880
2074
 
1881
2075
  **Raised by:** [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback).
1882
2076
 
1883
- The signed data is empty, longer than 128 bytes, or not in the fixed format of the slot.
2077
+ 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
2078
 
1885
- **What to do:** Publish only a validated response for the selected slot.
2079
+ **What to do:** Publish only a response that matches the selected recipe's template, unmodified.
1886
2080
 
1887
2081
  #### <a id="registry-error-invalidsigner"></a>`InvalidSigner`
1888
2082
 
1889
- `error InvalidSigner()` · Selector `0x815e1d64` · Source: `EpochEntropy.sol` lines 42, 148
2083
+ `error InvalidSigner()` · Selector `0x815e1d64` · Source: `EpochEntropy.sol` lines 84, 374, 379
1890
2084
 
1891
2085
  **Raised by:** [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback).
1892
2086
 
1893
- The signature does not recover to the slot's signer in the epoch's catalog.
2087
+ The signature does not recover to the slot's signer in the epoch's catalog, or, for a beacon recipe, the verifier of its registration did not accept it as the beacon's signature of the round in the data.
2088
+
2089
+ **What to do:** Use `catalogAt(epochId)` for the expected signer; for a beacon recipe check the round with `verifyBeacon`.
2090
+
2091
+ #### <a id="registry-error-beacongastoolow"></a>`BeaconGasTooLow`
2092
+
2093
+ `error BeaconGasTooLow()` · Selector `0x919ddbf6` · Source: `EpochEntropy.sol` lines 87, 220
2094
+
2095
+ **Raised by:** [`verifyBeacon`](#registry-fn-verifybeacon), [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback), [`registerBeacon`](#registry-fn-registerbeacon).
1894
2096
 
1895
- **What to do:** Use `signersAt(epochId)` for the expected signer.
2097
+ Too little gas was left to give the beacon verifier its whole `BEACON_VERIFY_GAS` allowance, in `registerBeacon`, `verifyBeacon` or a publication of a beacon epoch. It is raised instead of answering false, so a valid round is never read as invalid because the sender's gas limit was low.
2098
+
2099
+ **What to do:** Send the transaction with a higher gas limit; an `eth_call` of `verifyBeacon` needs about 461,000 gas.
1896
2100
 
1897
2101
  #### <a id="registry-error-ecdsainvalidsignature"></a>`ECDSAInvalidSignature`
1898
2102
 
@@ -1926,31 +2130,61 @@ The signature has a high `s` value (OpenZeppelin `ECDSA`).
1926
2130
 
1927
2131
  #### <a id="registry-error-packettoolarge"></a>`PacketTooLarge`
1928
2132
 
1929
- `error PacketTooLarge()` · Selector `0xda85e8a5` · Source: `EpochEntropy.sol` lines 43, 157
2133
+ `error PacketTooLarge()` · Selector `0xda85e8a5` · Source: `EpochEntropy.sol` lines 85, 389
1930
2134
 
1931
2135
  **Raised by:** [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback).
1932
2136
 
1933
2137
  The encoded packet exceeds `MAX_PACKET_BYTES` (2048).
1934
2138
 
1935
- **What to do:** Not reachable with data of at most 128 bytes and a 65-byte signature.
2139
+ **What to do:** Not reachable: the recipe and data bounds keep every packet within `MAX_PACKET_BYTES`.
1936
2140
 
1937
- **Administration, initialization and upgrades**
2141
+ **Recipes, administration, initialization and upgrades**
2142
+
2143
+ #### <a id="registry-error-invalidrecipe"></a>`InvalidRecipe`
2144
+
2145
+ `error InvalidRecipe()` · Selector `0x7b776f4c` · Source: `EpochEntropy.sol` lines 87, 165
2146
+
2147
+ **Raised by:** [`registerRecipe`](#registry-fn-registerrecipe), [`registerBeacon`](#registry-fn-registerbeacon).
2148
+
2149
+ `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.
2150
+
2151
+ **What to do:** Shorten the request or body. Recipe ids are never freed.
2152
+
2153
+ #### <a id="registry-error-invalidtemplate"></a>`InvalidTemplate`
2154
+
2155
+ `error InvalidTemplate()` · Selector `0xec55b8cd` · Source: `EpochEntropy.sol` lines 87, 166
2156
+
2157
+ **Raised by:** [`registerRecipe`](#registry-fn-registerrecipe).
2158
+
2159
+ `registerRecipe` got a data template that is not well formed (README [Data templates](README.md#data-templates)).
2160
+
2161
+ **What to do:** Build the template with `encodeDataTemplate`, which names the broken rule, before registering.
2162
+
2163
+ #### <a id="registry-error-stringsinsufficienthexlength"></a>`StringsInsufficientHexLength`
2164
+
2165
+ `error StringsInsufficientHexLength(uint256 value, uint256 length)` · Selector `0xe22e27eb`
2166
+
2167
+ **Raised by:** no public function (declared by OpenZeppelin `Strings`).
2168
+
2169
+ Declared by OpenZeppelin `Strings`, which `registerBeacon` uses to write the chain hash as 32 bytes of hex. A 32-byte value always fits, so it cannot be raised.
2170
+
2171
+ **What to do:** None.
1938
2172
 
1939
2173
  #### <a id="registry-error-invalidconfig"></a>`InvalidConfig`
1940
2174
 
1941
- `error InvalidConfig()` · Selector `0x35be3ac8` · Source: `EpochEntropy.sol` lines 41, 53, 63, 69
2175
+ `error InvalidConfig()` · Selector `0x35be3ac8` · Source: `EpochEntropy.sol` lines 83, 99, 111, 119, 126, 128, 158, 180–182, 275, 279, 280
1942
2176
 
1943
- **Raised by:** [`setCommitter`](#registry-fn-setcommitter), [`scheduleCatalog`](#registry-fn-schedulecatalog), [`initialize`](#registry-fn-initialize).
2177
+ **Raised by:** [`getRecipe`](#registry-fn-getrecipe), [`beaconOf`](#registry-fn-beaconof), [`recipeRequest`](#registry-fn-reciperequest), [`getEpochSelection`](#registry-fn-getepochselection), [`getEpochFallbackSelection`](#registry-fn-getepochfallbackselection), [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback), [`registerBeacon`](#registry-fn-registerbeacon), [`scheduleCatalog`](#registry-fn-schedulecatalog), [`initializeRecipeRegistry`](#registry-fn-initializereciperegistry), [`setCommitter`](#registry-fn-setcommitter), [`setBackupCommitter`](#registry-fn-setbackupcommitter), [`initialize`](#registry-fn-initialize).
1944
2178
 
1945
- A zero signer or committer in `initialize`, a zero address in `setCommitter`, or a zero signer in `scheduleCatalog`.
2179
+ 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 `registerBeacon` a verifier without code, a zero chain hash, genesis, period or sample round, a sample round not yet scheduled, or a key or sample signature the verifier rejects; in `scheduleCatalog` an empty or oversized catalog, mismatched lengths, a repeated or unregistered recipe, a zero signer or a beacon recipe listed with a signer other than its `slotSigner`; `initializeRecipeRegistry` on a registry that already has recipes or a scheduled catalog; or a recipe id that is not registered, in `getRecipe`, `recipeRequest` and `beaconOf` or, on a registry upgraded without `initializeRecipeRegistry`, in selection and publication.
1946
2180
 
1947
- **What to do:** Use non-zero addresses.
2181
+ **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
2182
 
1949
2183
  #### <a id="registry-error-ownableunauthorizedaccount"></a>`OwnableUnauthorizedAccount`
1950
2184
 
1951
2185
  `error OwnableUnauthorizedAccount(address account)` · Selector `0x118cdaa7`
1952
2186
 
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).
2187
+ **Raised by:** [`registerRecipe`](#registry-fn-registerrecipe), [`registerBeacon`](#registry-fn-registerbeacon), [`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
2188
 
1955
2189
  `account` is not the owner (owner-only functions) or not the pending owner (`acceptOwnership`).
1956
2190
 
@@ -1968,7 +2202,7 @@ A zero signer or committer in `initialize`, a zero address in `setCommitter`, or
1968
2202
 
1969
2203
  #### <a id="registry-error-renouncedisabled"></a>`RenounceDisabled`
1970
2204
 
1971
- `error RenounceDisabled()` · Selector `0x89051165` · Source: `EpochEntropy.sol` lines 43, 60
2205
+ `error RenounceDisabled()` · Selector `0x89051165` · Source: `EpochEntropy.sol` lines 85, 116
1972
2206
 
1973
2207
  **Raised by:** [`renounceOwnership`](#registry-fn-renounceownership).
1974
2208
 
@@ -1980,11 +2214,11 @@ The owner called `renounceOwnership`, which is disabled.
1980
2214
 
1981
2215
  `error InvalidInitialization()` · Selector `0xf92ee8a9`
1982
2216
 
1983
- **Raised by:** [`initialize`](#registry-fn-initialize).
2217
+ **Raised by:** [`initializeRecipeRegistry`](#registry-fn-initializereciperegistry), [`initialize`](#registry-fn-initialize).
1984
2218
 
1985
- `initialize` on a proxy that is already initialized, or on an implementation contract, whose initializers are disabled at construction.
2219
+ `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
2220
 
1987
- **What to do:** None: initialization happens once, atomically, when `D20Proxy` is deployed.
2221
+ **What to do:** None: `initialize` happens once when `D20Proxy` is deployed, and `initializeRecipeRegistry` once in the recipe-registry upgrade.
1988
2222
 
1989
2223
  #### <a id="registry-error-notinitializing"></a>`NotInitializing`
1990
2224
 
@@ -2055,3 +2289,120 @@ Declared by OpenZeppelin `Address` for the delegatecall in `upgradeToAndCall`. N
2055
2289
  The initialization call made by `upgradeToAndCall` reverted without revert data.
2056
2290
 
2057
2291
  **What to do:** Owner upgrade procedure only.
2292
+
2293
+ ## <a id="verifier"></a>D20BeaconVerifier
2294
+
2295
+ The verifier of the registry's drand beacon recipe: stateless and deployed directly, not behind a proxy. `EpochEntropy` records its address in a recipe's registration (`beaconOf`) and calls `isValidPublicKey` and `verifyRound` under a fixed gas allowance. It checks the bls-bn254-unchained-on-g1 scheme of drand's evmnet: the group public key is a BN254 G2 point, round `r`'s signature is a G1 point on the RFC 9380 hash-to-curve of `keccak256` of `r` as 8 big-endian bytes, and keys and signatures use drand's serialization of 32-byte big-endian words (`x ‖ y` for G1, `x_im ‖ x_re ‖ y_im ‖ y_re` for G2). It is built on the unmodified kevincharm/bls-bn254 library recorded in `notices/PROVENANCE.md`.
2296
+
2297
+ Anyone can call it, for example to check a drand round inside another contract. `beaconVerifierAbi` from `@d20dao/vrf-sdk/abi` carries its ABI, and `verifyBeaconRound` in `@d20dao/vrf-sdk` computes the same check off-chain.
2298
+
2299
+ ### <a id="verifier-verification"></a>Verification
2300
+
2301
+ Views, callable by anyone.
2302
+
2303
+ #### <a id="verifier-fn-isvalidpublickey"></a>`isValidPublicKey`
2304
+
2305
+ ```solidity
2306
+ function isValidPublicKey(bytes publicKey) external pure returns (bool)
2307
+ ```
2308
+
2309
+ Selector `0xc4c1cf54` · Caller: Anyone (view) · Source: `D20BeaconVerifier.sol` lines 28–34
2310
+
2311
+ Whether `publicKey` is 128 bytes, `x_im ‖ x_re ‖ y_im ‖ y_re`, encoding a point on the BN254 G2 curve with every coordinate below the field order. Subgroup membership is not checked here: the pairing precompile refuses a key outside the subgroup, so a verified signature under the key establishes it. `EpochEntropy` calls it when a beacon is registered.
2312
+
2313
+ #### <a id="verifier-fn-roundmessage"></a>`roundMessage`
2314
+
2315
+ ```solidity
2316
+ function roundMessage(uint64 round) external view returns (uint256[2])
2317
+ ```
2318
+
2319
+ Selector `0xfd5a7458` · Caller: Anyone (view) · Source: `D20BeaconVerifier.sol` lines 36–39
2320
+
2321
+ The G1 point that the signature of `round` signs: the RFC 9380 hash-to-curve of `keccak256` of the round as 8 big-endian bytes under `DST` (`expand_message_xmd` with `keccak256`, the Shallue-van de Woestijne map and the sum of two mapped field elements). `beaconRoundMessage` in `@d20dao/vrf-sdk` computes the same point off-chain.
2322
+
2323
+ **Errors:** [`BNAddFailed`](#verifier-error-bnaddfailed), [`ModExpFailed`](#verifier-error-modexpfailed).
2324
+
2325
+ #### <a id="verifier-fn-verifyround"></a>`verifyRound`
2326
+
2327
+ ```solidity
2328
+ function verifyRound(bytes publicKey, uint64 round, bytes signature) external view returns (bool)
2329
+ ```
2330
+
2331
+ Selector `0x3a1bc7c9` · Caller: Anyone (view) · Source: `D20BeaconVerifier.sol` lines 41–60
2332
+
2333
+ Whether `signature` (64 bytes, `x ‖ y`) is the beacon's signature of `round` under `publicKey`: the pairing of `signature` with the G2 generator equals the pairing of `roundMessage(round)` with `publicKey`. It returns false, without reverting, for a signature that is not 64 bytes, a key that fails `isValidPublicKey` and a signature whose coordinates are not below the field order or not on the curve. Otherwise it runs the pairing precompile with a fixed allowance of `PAIRING_GAS`, and a direct call needs about 285,000 gas. It reverts `InsufficientGas`, and never answers false, when too little gas is left to give the precompile that allowance, so a valid signature is not read as invalid for want of gas.
2334
+
2335
+ **Errors:** [`InsufficientGas`](#verifier-error-insufficientgas), [`BNAddFailed`](#verifier-error-bnaddfailed), [`ModExpFailed`](#verifier-error-modexpfailed).
2336
+
2337
+ ### <a id="verifier-constants"></a>Constants
2338
+
2339
+ Views returning values fixed in the code.
2340
+
2341
+ | Constant | Returns | Value | Selector | Meaning |
2342
+ | --- | --- | --- | --- | --- |
2343
+ | <a id="verifier-fn-dst"></a>`DST` | `bytes` | `"BLS_SIG_BN254G1_XMD:KECCAK-256_SVDW_RO_NUL_"` | `0x5f7c7522` | RFC 9380 domain separation tag of the scheme's hash-to-curve. |
2344
+ | <a id="verifier-fn-pairing_gas"></a>`PAIRING_GAS` | `uint256` | `200_000` | `0x1848e02a` | Gas given to the pairing precompile (address 8). A two-pair check costs 113,000 under EIP-1108. |
2345
+
2346
+ ### <a id="verifier-errors"></a>Errors
2347
+
2348
+ **Verification**
2349
+
2350
+ #### <a id="verifier-error-insufficientgas"></a>`InsufficientGas`
2351
+
2352
+ `error InsufficientGas()` · Selector `0x1c26714c` · Source: `D20BeaconVerifier.sol` lines 17, 57
2353
+
2354
+ **Raised by:** [`verifyRound`](#verifier-fn-verifyround).
2355
+
2356
+ Too little gas was left in `verifyRound` for the pairing precompile to get its whole `PAIRING_GAS` allowance.
2357
+
2358
+ **What to do:** Send the call with a higher gas limit; a direct call needs about 285,000 gas.
2359
+
2360
+ #### <a id="verifier-error-modexpfailed"></a>`ModExpFailed`
2361
+
2362
+ `error ModExpFailed(uint256 base, uint256 exponent, uint256 modulus)` · Selector `0xc6daf7ab` · Source: `vendor/bls-bn254/BLS.sol` lines 56, 399
2363
+
2364
+ **Raised by:** [`roundMessage`](#verifier-fn-roundmessage), [`verifyRound`](#verifier-fn-verifyround).
2365
+
2366
+ The modexp precompile (address 5), which the hash-to-curve calls with all remaining gas, failed: in practice the call ran out of gas.
2367
+
2368
+ **What to do:** Send the call with a higher gas limit.
2369
+
2370
+ #### <a id="verifier-error-bnaddfailed"></a>`BNAddFailed`
2371
+
2372
+ `error BNAddFailed(uint256[4] input)` · Selector `0x128e3f08` · Source: `vendor/bls-bn254/BLS.sol` lines 52, 113
2373
+
2374
+ **Raised by:** [`roundMessage`](#verifier-fn-roundmessage), [`verifyRound`](#verifier-fn-verifyround).
2375
+
2376
+ The bn256 addition precompile (address 6), which adds the two mapped points of the hash-to-curve, failed: in practice the call ran out of gas.
2377
+
2378
+ **What to do:** Send the call with a higher gas limit.
2379
+
2380
+ #### <a id="verifier-error-maptopointfailed"></a>`MapToPointFailed`
2381
+
2382
+ `error MapToPointFailed(uint256 noSqrt)` · Selector `0x396ec771` · Source: `vendor/bls-bn254/BLS.sol` lines 54, 329, 334, 339, 365
2383
+
2384
+ **Raised by:** no input known (check inside the vendored library).
2385
+
2386
+ Raised by the vendored library when its map of a field element to the curve finds no square root or a Legendre exponentiation returns an unexpected value. The Shallue-van de Woestijne map always yields a point, so no input should reach it.
2387
+
2388
+ **What to do:** None.
2389
+
2390
+ #### <a id="verifier-error-invalidfieldelement"></a>`InvalidFieldElement`
2391
+
2392
+ `error InvalidFieldElement(uint256 x)` · Selector `0xd53e9415` · Source: `vendor/bls-bn254/BLS.sol` lines 53, 310
2393
+
2394
+ **Raised by:** no input of this contract (check inside the vendored library).
2395
+
2396
+ The vendored library was asked to map a value at or above the field order. The hash-to-curve reduces its field elements below it first, so this contract cannot reach it.
2397
+
2398
+ **What to do:** None.
2399
+
2400
+ #### <a id="verifier-error-invaliddstlength"></a>`InvalidDSTLength`
2401
+
2402
+ `error InvalidDSTLength(bytes dst)` · Selector `0x26e4f9ba` · Source: `vendor/bls-bn254/BLS.sol` lines 55, 260
2403
+
2404
+ **Raised by:** no input of this contract (check inside the vendored library).
2405
+
2406
+ The domain separation tag is longer than 255 bytes. `DST` is 43 bytes, so this contract cannot reach it.
2407
+
2408
+ **What to do:** None.