@d20dao/vrf-sdk 0.4.0 → 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.4.0. 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.4.0
8
- - Protocol source: commit `de5f82eb9fc749c80e83270f57cde9908ddcf1f3`, 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: `684ee3dfe0745f141831aee896db6436d490d5e9715e61661c86b044d1c3260e`
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
 
@@ -47,6 +48,10 @@ This reference describes that source. A deployment runs it only while the implem
47
48
  - [Constants](#registry-constants)
48
49
  - [Events](#registry-events)
49
50
  - [Errors](#registry-errors)
51
+ - [D20BeaconVerifier](#verifier)
52
+ - [Verification](#verifier-verification)
53
+ - [Constants](#verifier-constants)
54
+ - [Errors](#verifier-errors)
50
55
 
51
56
  ## <a id="coordinator"></a>D20VRFCoordinator
52
57
 
@@ -60,7 +65,7 @@ Requests, fulfillment, `storeBlockHash`, retries, `refundRequest` and withdrawal
60
65
 
61
66
  Returned by `getRequest`. It does not contain the escrowed fee or the refund ratio; read `requestFeePaid` and `requestRefundBps`.
62
67
 
63
- Source: `D20VRFCoordinator.sol` lines 59–77
68
+ Source: `D20VRFCoordinator.sol` lines 61–79
64
69
 
65
70
  | Field | Type | Meaning |
66
71
  | --- | --- | --- |
@@ -124,7 +129,7 @@ Requests must come from a contract and pay at least the fee computed in their ow
124
129
  function quoteFee(uint32 callbackGasLimit) external view returns (uint256)
125
130
  ```
126
131
 
127
- Selector `0xc9caa0c3` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 228–233
132
+ Selector `0xc9caa0c3` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 232–237
128
133
 
129
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.
130
135
 
@@ -136,7 +141,7 @@ Fee for a request with this `callbackGasLimit` priced at `block.basefee`, that i
136
141
  function quoteFeeAt(uint32 callbackGasLimit, uint256 baseFee) external view returns (uint256)
137
142
  ```
138
143
 
139
- Selector `0x26fa8481` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 220–227
144
+ Selector `0x26fa8481` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 224–231
140
145
 
141
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`.
142
147
 
@@ -148,7 +153,7 @@ Selector `0x26fa8481` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol
148
153
  function requestRandomness(bytes32 clientSeed, uint32 callbackGasLimit, address _refundAddress) external payable returns (uint256 requestId)
149
154
  ```
150
155
 
151
- Selector `0x9849d1e5` · Caller: Any contract · Source: `D20VRFCoordinator.sol` lines 235–240, 248–287
156
+ Selector `0x9849d1e5` · Caller: Any contract · Source: `D20VRFCoordinator.sol` lines 239–244, 252–291
152
157
 
153
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.
154
159
 
@@ -162,7 +167,7 @@ Creates a raw request (spec all zero) with `msg.sender` as consumer and returns
162
167
  function requestMappedRandomness(bytes32 clientSeed, uint32 callbackGasLimit, address _refundAddress, RandomnessMapping.Spec spec) external payable returns (uint256 requestId)
163
168
  ```
164
169
 
165
- Selector `0xe6b41a8c` · Caller: Any contract · Source: `D20VRFCoordinator.sol` lines 242–246, 248–287
170
+ Selector `0xe6b41a8c` · Caller: Any contract · Source: `D20VRFCoordinator.sol` lines 246–250, 252–291
166
171
 
167
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.
168
173
 
@@ -176,11 +181,11 @@ Views, callable by anyone. They describe requests created from now on; an existi
176
181
 
177
182
  | Function | Selector | Meaning | Source |
178
183
  | --- | --- | --- | --- |
179
- | <a id="coordinator-fn-pricing"></a>`pricing() returns (uint256, uint16, uint32)` | `0x7ce91411` | Live `(minFee, feeMultiplier, fulfillGasOverhead)`. The outputs are unnamed, so read them by position. | lines 211–213 |
180
- | <a id="coordinator-fn-minfee"></a>`minFee() returns (uint256)` | `0x24ec7590` | Minimum fee in wei, at most `MAX_MIN_FEE` (10 USDC). | line 44 |
181
- | <a id="coordinator-fn-feemultiplier"></a>`feeMultiplier() returns (uint16)` | `0xe5a70ef7` | Base-fee multiplier, 0 to `MAX_FEE_MULTIPLIER` (20); 0 makes every fee `minFee`. | lines 46–47 |
182
- | <a id="coordinator-fn-fulfillgasoverhead"></a>`fulfillGasOverhead() returns (uint32)` | `0x19d40839` | Gas added to `callbackGasLimit` in the fee formula, `MIN_FULFILL_GAS_OVERHEAD` to `MAX_FULFILL_GAS_OVERHEAD`. | line 48 |
183
- | <a id="coordinator-fn-refundbps"></a>`refundBps() returns (uint16)` | `0xec8c9a0b` | Current refund ratio in basis points (5000 to 10000), copied into each new request. Not the ratio of an existing request: use `requestRefundBps(requestId)`. | lines 49–50 |
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 |
184
189
 
185
190
  ### <a id="coordinator-reading-request-state-and-results"></a>Reading request state and results
186
191
 
@@ -192,7 +197,7 @@ Views, callable by anyone, including from a callback. Request IDs start at 1 and
192
197
  function getRequest(uint256 requestId) external view returns (D20VRFCoordinator.Request result)
193
198
  ```
194
199
 
195
- Selector `0xc58343ef` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 289–308, 558–563
200
+ Selector `0xc58343ef` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 293–312, 575–580
196
201
 
197
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.
198
203
 
@@ -204,7 +209,7 @@ Full state of a request, see [`D20VRFCoordinator.Request`](#coordinator-type-d20
204
209
  function getMapping(uint256 requestId) external view returns (RandomnessMapping.Spec)
205
210
  ```
206
211
 
207
- Selector `0xede9ba8b` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 340–343
212
+ Selector `0xede9ba8b` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 344–347
208
213
 
209
214
  The stored [`RandomnessMapping.Spec`](#coordinator-type-randomnessmapping-spec); all fields zero (Raw) for `requestRandomness`.
210
215
 
@@ -216,7 +221,7 @@ The stored [`RandomnessMapping.Spec`](#coordinator-type-randomnessmapping-spec);
216
221
  function getMappedResult(uint256 requestId) external view returns (uint256[])
217
222
  ```
218
223
 
219
- Selector `0x8f09a3e6` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 345–349
224
+ Selector `0x8f09a3e6` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 349–353
220
225
 
221
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.
222
227
 
@@ -228,7 +233,7 @@ The accepted word mapped with the stored spec: `[uint256(word)]` for a raw reque
228
233
  function mapRandomness(bytes32 randomness, RandomnessMapping.Spec spec) external pure returns (uint256[])
229
234
  ```
230
235
 
231
- Selector `0x41c2a199` · Caller: Anyone (pure) · Source: `D20VRFCoordinator.sol` lines 351–356
236
+ Selector `0x41c2a199` · Caller: Anyone (pure) · Source: `D20VRFCoordinator.sol` lines 355–360
232
237
 
233
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.
234
239
 
@@ -240,7 +245,7 @@ Maps any word with any valid spec, like the SDK `mapRandomness(word, spec)` off-
240
245
  function requestFeePaid(uint256 requestId) external view returns (uint256)
241
246
  ```
242
247
 
243
- Selector `0xef7cc992` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 331–334
248
+ Selector `0xef7cc992` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 335–338
244
249
 
245
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.
246
251
 
@@ -252,7 +257,7 @@ Fee escrowed by the request (`feePaid` in `RandomnessRequested`), excluding any
252
257
  function requestRefundBps(uint256 requestId) external view returns (uint16)
253
258
  ```
254
259
 
255
- Selector `0x5d170fd7` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 335–338
260
+ Selector `0x5d170fd7` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 339–342
256
261
 
257
262
  Refund ratio the request copied from `refundBps` at creation. An expiry refund pays `requestFeePaid × requestRefundBps / 10000`; a later `setRefundBps` does not change it.
258
263
 
@@ -264,7 +269,7 @@ Refund ratio the request copied from `refundBps` at creation. An expiry refund p
264
269
  function refundCallbackDelivered(uint256) external view returns (bool)
265
270
  ```
266
271
 
267
- Selector `0x281d3157` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 103, 511
272
+ Selector `0x281d3157` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 105, 528
268
273
 
269
274
  True once an `onRefund` notification for the request succeeded, at `refundRequest` or `retryRefundCallback`. Returns false for unknown IDs instead of reverting.
270
275
 
@@ -274,7 +279,7 @@ True once an `onRefund` notification for the request succeeded, at `refundReques
274
279
  function nextRequestId() external view returns (uint256)
275
280
  ```
276
281
 
277
- Selector `0x6a84a985` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 51, 173, 261
282
+ Selector `0x6a84a985` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 53, 175, 265
278
283
 
279
284
  ID the next request will receive. Issued IDs are 1 to `nextRequestId() - 1`.
280
285
 
@@ -288,7 +293,7 @@ Recovery calls need no value or role. They forward gas to the consumer and rever
288
293
  function refundRequest(uint256 requestId) external
289
294
  ```
290
295
 
291
- Selector `0x7411484e` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 466–490, 502–513
296
+ Selector `0x7411484e` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 483–507, 519–530
292
297
 
293
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.
294
299
 
@@ -302,7 +307,7 @@ Refunds an unfulfilled request once a block timestamp is after its deadline. Mar
302
307
  function retryCallback(uint256 requestId, uint32 gasLimit) external
303
308
  ```
304
309
 
305
- Selector `0xdd11c275` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 456–464, 613–630
310
+ Selector `0xdd11c275` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 473–481, 630–647
306
311
 
307
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`.
308
313
 
@@ -316,7 +321,7 @@ Calls `rawFulfillRandomness` again with the same accepted word after a failed ca
316
321
  function retryRefundCallback(uint256 requestId, uint32 gasLimit) external
317
322
  ```
318
323
 
319
- Selector `0x054f6962` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 492–500, 502–513
324
+ Selector `0x054f6962` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 509–517, 519–530
320
325
 
321
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`.
322
327
 
@@ -330,7 +335,7 @@ Repeats a failed `onRefund` notification for a refunded request with `gasLimit`
330
335
  function withdrawRefundCredit(address recipient) external
331
336
  ```
332
337
 
333
- Selector `0x445071f2` · Caller: Refund-credit holder · Source: `D20VRFCoordinator.sol` lines 515–525
338
+ Selector `0x445071f2` · Caller: Refund-credit holder · Source: `D20VRFCoordinator.sol` lines 532–542
334
339
 
335
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.
336
341
 
@@ -344,7 +349,7 @@ Sends all of the caller's refund credit, `refundCredits(msg.sender)`, to `recipi
344
349
  function withdrawFees(address recipient) external
345
350
  ```
346
351
 
347
- Selector `0x164e68de` · Caller: Fee recipient · Source: `D20VRFCoordinator.sol` lines 527–536
352
+ Selector `0x164e68de` · Caller: Fee recipient · Source: `D20VRFCoordinator.sol` lines 544–553
348
353
 
349
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.
350
355
 
@@ -358,7 +363,7 @@ Sends all `earnedFees` to `recipient`. Fees accrue at acceptance (fee minus keep
358
363
  function withdrawKeeperCredit(address recipient) external
359
364
  ```
360
365
 
361
- Selector `0xf62c546b` · Caller: Keeper-credit holder · Source: `D20VRFCoordinator.sol` lines 538–547
366
+ Selector `0xf62c546b` · Caller: Keeper-credit holder · Source: `D20VRFCoordinator.sol` lines 555–564
362
367
 
363
368
  Sends all of the caller's keeper credit (keeper-share transfers that failed) to `recipient`.
364
369
 
@@ -372,13 +377,13 @@ Views, callable by anyone. Amounts are in wei of native USDC.
372
377
 
373
378
  | Function | Selector | Meaning | Source |
374
379
  | --- | --- | --- | --- |
375
- | <a id="coordinator-fn-refundcredits"></a>`refundCredits(address) returns (uint256)` | `0x61137e40` | Refund credit that an address can withdraw with `withdrawRefundCredit`. | line 57 |
376
- | <a id="coordinator-fn-totalrefundcredits"></a>`totalRefundCredits() returns (uint256)` | `0x6e0842e1` | Sum of all refund credit held by the coordinator. | line 56 |
377
- | <a id="coordinator-fn-earnedfees"></a>`earnedFees() returns (uint256)` | `0xb1b3ffd9` | Protocol fees that the fee recipient can withdraw. | line 52 |
378
- | <a id="coordinator-fn-feerecipient"></a>`feeRecipient() returns (address)` | `0x46904840` | Address allowed to call `withdrawFees`; changed with `setFeeRecipient`. | line 40 |
379
- | <a id="coordinator-fn-keeperfeebps"></a>`keeperFeeBps() returns (uint16)` | `0x0eab7d63` | Keeper share of each accepted fee in basis points (0 to 10000). Read at acceptance, not snapshotted: a change applies to open requests accepted afterwards. It only splits the escrowed fee; what the consumer paid and can be refunded does not change. | lines 41, 433 |
380
- | <a id="coordinator-fn-keepercredits"></a>`keeperCredits(address) returns (uint256)` | `0xf5c764f6` | Keeper credit that an address can withdraw with `withdrawKeeperCredit`. | line 42 |
381
- | <a id="coordinator-fn-totalkeepercredits"></a>`totalKeeperCredits() returns (uint256)` | `0xc7281b7a` | Sum of all keeper credit held by the coordinator. | line 43 |
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 |
382
387
 
383
388
  ### <a id="coordinator-keeper-and-proof-functions"></a>Keeper and proof functions
384
389
 
@@ -390,7 +395,7 @@ Proof submission is permissionless: anyone holding a valid proof may submit it,
390
395
  function fulfillRandomness(uint256 requestId, VRF.Proof proof) external
391
396
  ```
392
397
 
393
- Selector `0xef7c2b19` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 389–397, 413–448
398
+ Selector `0xef7c2b19` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 393–401, 430–465
394
399
 
395
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.
396
401
 
@@ -404,9 +409,9 @@ Accepts a proof for a request that is not fulfilled, not refunded and not past i
404
409
  function fulfillRandomnessBatch(uint256[] ids, VRF.Proof[] proofs) external
405
410
  ```
406
411
 
407
- Selector `0x9497b180` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 399–411
412
+ Selector `0x9497b180` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 403–428
408
413
 
409
- Fulfills up to `MAX_FULFILL_BATCH` (16) requests, one proof each. Members already fulfilled, refunded or past their deadline, including an ID repeated in the batch, are skipped with `FulfillmentSkipped`; every other member runs exactly like `fulfillRandomness` and emits the same events, so an unknown ID, an unready member or an invalid proof reverts the whole batch.
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.
410
415
 
411
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).
412
417
 
@@ -418,7 +423,7 @@ Fulfills up to `MAX_FULFILL_BATCH` (16) requests, one proof each. Members alread
418
423
  function storeBlockHash(uint256 requestId) external returns (bytes32)
419
424
  ```
420
425
 
421
- Selector `0x262fd733` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 368–372, 564–579
426
+ Selector `0x262fd733` · Caller: Anyone · Source: `D20VRFCoordinator.sol` lines 372–376, 581–596
422
427
 
423
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.
424
429
 
@@ -432,7 +437,7 @@ Resolves the target block from the published epoch, stores its hash if not store
432
437
  function verifyRequestProof(uint256 requestId, VRF.Proof proof) external view returns (bytes32)
433
438
  ```
434
439
 
435
- Selector `0x0846de99` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 358–366
440
+ Selector `0x0846de99` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 362–370
436
441
 
437
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`.
438
443
 
@@ -444,7 +449,7 @@ Returns the word a proof yields for the request's seed, without changing state.
444
449
  function requestSeed(uint256 requestId) external view returns (uint256)
445
450
  ```
446
451
 
447
- Selector `0xa9df851a` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 374–379, 581–589
452
+ Selector `0xa9df851a` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 378–383, 598–606
448
453
 
449
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.
450
455
 
@@ -456,7 +461,7 @@ Seed the proof must use: `keccak256(abi.encode(SEED_DOMAIN, chainId, coordinator
456
461
  function getProofContext(uint256 requestId) external view returns (uint256 seed, uint64 deadline, bool fulfilled, bool refunded)
457
462
  ```
458
463
 
459
- Selector `0xcf14de9d` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 381–387
464
+ Selector `0xcf14de9d` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 385–391
460
465
 
461
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`.
462
467
 
@@ -468,7 +473,7 @@ Selector `0xcf14de9d` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol
468
473
  function getPendingRequestIds(uint256 fromId, uint256 limit) external view returns (uint256[] ids, uint256 nextCursor)
469
474
  ```
470
475
 
471
- Selector `0xfdfe72e6` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 310–329
476
+ Selector `0xfdfe72e6` · Caller: Anyone (view) · Source: `D20VRFCoordinator.sol` lines 314–333
472
477
 
473
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.
474
479
 
@@ -480,17 +485,17 @@ Views, callable by anyone. Nothing here has a setter except through an upgrade.
480
485
 
481
486
  | Function | Selector | Meaning | Source |
482
487
  | --- | --- | --- | --- |
483
- | <a id="coordinator-fn-lastservedrequestid"></a>`lastServedRequestId() returns (uint256)` | `0xef54e226` | ID of the most recently accepted request; 0 before the first. | lines 53, 435 |
484
- | <a id="coordinator-fn-lastservedindex"></a>`lastServedIndex() returns (uint256)` | `0x7e176eed` | Number of accepted requests so far: the `serveIndex` of the latest `RequestServed`. | lines 54, 436 |
485
- | <a id="coordinator-fn-servedrequestat"></a>`servedRequestAt(uint256) returns (uint256)` | `0xf9a4acc6` | Request ID accepted at a serve index (from 1); 0 for an index not used yet. | lines 55, 436 |
486
- | <a id="coordinator-fn-keyhash"></a>`keyHash() returns (bytes32)` | `0x61728f39` | `keccak256(abi.encode(publicKey))` of the VRF key; indexed in `RandomnessRequested` and `ProofVerified`. | lines 37, 180 |
487
- | <a id="coordinator-fn-publickeyx"></a>`publicKeyX() returns (uint256)` | `0xfa6df55d` | x coordinate of the VRF public key. | line 35 |
488
- | <a id="coordinator-fn-publickeyy"></a>`publicKeyY() returns (uint256)` | `0xd7a6f6e8` | y coordinate of the VRF public key. | line 36 |
489
- | <a id="coordinator-fn-confirmationblocks"></a>`confirmationBlocks() returns (uint16)` | `0x460a58aa` | Blocks after the target block before the seed and proofs become available (1 to 64, set at initialization). | lines 45, 566 |
490
- | <a id="coordinator-fn-epochregistry"></a>`epochRegistry() returns (address)` | `0x2b12cb69` | The `EpochEntropy` proxy that supplies epochs and the keeper-share recipient. | line 33 |
491
- | <a id="coordinator-fn-protocolconfigurationhash"></a>`protocolConfigurationHash() returns (bytes32)` | `0x155cf49b` | Hash of the initialized configuration (public key, initial fee recipient, initial minimum fee, confirmations, registry, initial catalog hash, first epoch start, epoch length 200) under `CONFIG_DOMAIN`. Bound into every transcript hash. | lines 32, 190 |
492
- | <a id="coordinator-fn-initialfeerecipient"></a>`initialFeeRecipient() returns (address)` | `0x308c2d6b` | Fee recipient given to `initialize`, used by replay. The live payout address is `feeRecipient()`. | lines 39, 182 |
493
- | <a id="coordinator-fn-initialminfee"></a>`initialMinFee() returns (uint256)` | `0xb3839295` | Minimum fee given to `initialize`, used by replay. The live minimum is `minFee()`. | lines 105, 185 |
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 |
494
499
 
495
500
  ### <a id="coordinator-owner-administration"></a>Owner administration
496
501
 
@@ -502,9 +507,9 @@ Owner-only functions revert `OwnableUnauthorizedAccount` for anyone else. No set
502
507
  function setPricing(uint256 nextMinFee, uint16 multiplier, uint32 overhead) external
503
508
  ```
504
509
 
505
- Selector `0x4c729ce6` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 205–210
510
+ Selector `0x4c729ce6` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 207–214
506
511
 
507
- Sets `minFee` (at most `MAX_MIN_FEE`, 10 USDC), `feeMultiplier` (at most `MAX_FEE_MULTIPLIER`, 20) and `fulfillGasOverhead` (`MIN_FULFILL_GAS_OVERHEAD` to `MAX_FULFILL_GAS_OVERHEAD`, 100,000 to 2,000,000 gas). Affects requests created afterwards; open requests keep their escrowed fee.
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.
508
513
 
509
514
  **Emits:** [`PricingChanged`](#coordinator-event-pricingchanged).
510
515
 
@@ -516,7 +521,7 @@ Sets `minFee` (at most `MAX_MIN_FEE`, 10 USDC), `feeMultiplier` (at most `MAX_FE
516
521
  function setRefundBps(uint16 next) external
517
522
  ```
518
523
 
519
- Selector `0x55a94d1b` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 214–218
524
+ Selector `0x55a94d1b` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 218–222
520
525
 
521
526
  Sets the refund ratio for requests created afterwards, `MIN_REFUND_BPS` (5000) to 10000.
522
527
 
@@ -530,7 +535,7 @@ Sets the refund ratio for requests created afterwards, `MIN_REFUND_BPS` (5000) t
530
535
  function setKeeperFeeBps(uint16 next) external
531
536
  ```
532
537
 
533
- Selector `0xe140f0ca` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 201–204
538
+ Selector `0xe140f0ca` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 203–206
534
539
 
535
540
  Sets the keeper share, 0 to 10000 basis points. Read at each acceptance, so it also applies to open requests accepted later.
536
541
 
@@ -544,7 +549,7 @@ Sets the keeper share, 0 to 10000 basis points. Read at each acceptance, so it a
544
549
  function setFeeRecipient(address next) external
545
550
  ```
546
551
 
547
- Selector `0xe74b981b` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 197–200
552
+ Selector `0xe74b981b` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 199–202
548
553
 
549
554
  Sets the address allowed to withdraw `earnedFees`, including fees earned before the change. The zero address is rejected.
550
555
 
@@ -606,7 +611,7 @@ Completes the transfer to the caller and clears the nomination.
606
611
  function renounceOwnership() external view
607
612
  ```
608
613
 
609
- Selector `0x715018a6` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 194–195
614
+ Selector `0x715018a6` · Caller: Owner · Source: `D20VRFCoordinator.sol` lines 196–197
610
615
 
611
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.
612
617
 
@@ -644,7 +649,7 @@ ERC-1822 check used by `upgradeToAndCall`. Returns the ERC-1967 implementation s
644
649
  function initialize(uint256[2] publicKey, address initialOwner, address recipient, uint256 fee, uint16 confirmations, address registry, uint16 keeperBps) external
645
650
  ```
646
651
 
647
- Selector `0x56b95b47` · Caller: Once, by `D20Proxy` at deployment · Source: `D20VRFCoordinator.sol` lines 170–191
652
+ Selector `0x56b95b47` · Caller: Once, by `D20Proxy` at deployment · Source: `D20VRFCoordinator.sol` lines 172–193
648
653
 
649
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.
650
655
 
@@ -684,7 +689,7 @@ Views returning values fixed in the implementation code.
684
689
  event RandomnessRequested(uint256 indexed requestId, address indexed consumer, bytes32 indexed keyHash, bytes32 clientSeed, uint64 requestBlock, uint32 callbackGasLimit, uint256 feePaid, address refundAddress, uint64 deadline)
685
690
  ```
686
691
 
687
- Topic 0 `0xaf91b17376114a36689aa115062983bda7b43263a891fb8de0cc69d30d4240ad` · Emitted by: [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness) · Source: `D20VRFCoordinator.sol` lines 139–143, 284–285
692
+ Topic 0 `0xaf91b17376114a36689aa115062983bda7b43263a891fb8de0cc69d30d4240ad` · Emitted by: [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness) · Source: `D20VRFCoordinator.sol` lines 141–145, 288–289
688
693
 
689
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.
690
695
 
@@ -694,7 +699,7 @@ A request was created. `feePaid` is the escrowed fee, not `msg.value`; `deadline
694
699
  event MappingRequested(uint256 indexed requestId, bytes32 indexed mappingHash, RandomnessMapping.Spec spec)
695
700
  ```
696
701
 
697
- Topic 0 `0xbe1c93f40bd74ff9acd22dc40818e36d04e0b8a49b8238d0537c63219c2336dd` · Emitted by: [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness) · Source: `D20VRFCoordinator.sol` lines 158, 286
702
+ Topic 0 `0xbe1c93f40bd74ff9acd22dc40818e36d04e0b8a49b8238d0537c63219c2336dd` · Emitted by: [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness) · Source: `D20VRFCoordinator.sol` lines 160, 290
698
703
 
699
704
  Emitted right after `RandomnessRequested` with the stored spec (all zero for a raw request) and its hash.
700
705
 
@@ -704,7 +709,7 @@ Emitted right after `RandomnessRequested` with the stored spec (all zero for a r
704
709
  event FeeOverpaymentCredited(uint256 indexed requestId, address indexed refundAddress, uint256 amount)
705
710
  ```
706
711
 
707
- Topic 0 `0x8ae693db98f043f48e8f427375449ed5576aba97575e4f7f93ff2c1f6c75dcb5` · Emitted by: [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness) · Source: `D20VRFCoordinator.sol` lines 152, 278–283
712
+ Topic 0 `0x8ae693db98f043f48e8f427375449ed5576aba97575e4f7f93ff2c1f6c75dcb5` · Emitted by: [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness) · Source: `D20VRFCoordinator.sol` lines 154, 282–287
708
713
 
709
714
  `msg.value` exceeded the fee and `amount` was added to `refundCredits(refundAddress)`, independently of what happens to the request. Emitted before `RandomnessRequested`.
710
715
 
@@ -714,7 +719,7 @@ Topic 0 `0x8ae693db98f043f48e8f427375449ed5576aba97575e4f7f93ff2c1f6c75dcb5` ·
714
719
  event BlockHashStored(uint256 indexed requestId, uint64 targetBlock, bytes32 blockHash)
715
720
  ```
716
721
 
717
- Topic 0 `0x81bc3b4ec75af0fb9ed3521d7c766d8f995d04b0735c17d61ff9d468b0f04911` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch), [`storeBlockHash`](#coordinator-fn-storeblockhash) · Source: `D20VRFCoordinator.sol` lines 144, 572–579
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
718
723
 
719
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.
720
725
 
@@ -724,7 +729,7 @@ The target block hash of the request was stored. Emitted once per request: by `s
724
729
  event RequestServed(uint256 indexed requestId, uint256 indexed serveIndex)
725
730
  ```
726
731
 
727
- Topic 0 `0x2012511e6cebd578bcabff1ef3346edb032cbab8622e9f23d9f15d7d1037267f` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 160, 435–437
732
+ Topic 0 `0x2012511e6cebd578bcabff1ef3346edb032cbab8622e9f23d9f15d7d1037267f` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 162, 452–454
728
733
 
729
734
  A proof was accepted. `serveIndex` counts accepted requests from 1 (`lastServedIndex`, `servedRequestAt`).
730
735
 
@@ -734,7 +739,7 @@ A proof was accepted. `serveIndex` counts accepted requests from 1 (`lastServedI
734
739
  event ProofVerified(uint256 indexed requestId, bytes32 indexed keyHash, uint256 seed, bytes32 proofHash)
735
740
  ```
736
741
 
737
- Topic 0 `0x55bb25be3ecd9f68ceae7cdabf4eabe2e0940bd8fc1c26c0d68ad5f7c5d08d22` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 159, 438
742
+ Topic 0 `0x55bb25be3ecd9f68ceae7cdabf4eabe2e0940bd8fc1c26c0d68ad5f7c5d08d22` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 161, 455
738
743
 
739
744
  Seed and hash of the accepted proof.
740
745
 
@@ -744,7 +749,7 @@ Seed and hash of the accepted proof.
744
749
  event RandomnessFulfilled(uint256 indexed requestId, bytes32 randomness, address indexed submitter)
745
750
  ```
746
751
 
747
- Topic 0 `0x9c82683ee7932041c254d206bcce4241d66a811d53ee7191799cc120777b2b87` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 145, 439
752
+ Topic 0 `0x9c82683ee7932041c254d206bcce4241d66a811d53ee7191799cc120777b2b87` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 147, 456
748
753
 
749
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.
750
755
 
@@ -754,7 +759,7 @@ A proof was accepted and `randomness` is final. `submitter` sent the transaction
754
759
  event FulfillmentEvidence(uint256 indexed requestId, bytes32 indexed transcriptHash, bytes packet)
755
760
  ```
756
761
 
757
- Topic 0 `0xa121bbea897439460dfb08c3e6d6af064bc1f31e9477471828a87c5596b92e77` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 163–164, 450–454
762
+ Topic 0 `0xa121bbea897439460dfb08c3e6d6af064bc1f31e9477471828a87c5596b92e77` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 165–166, 467–471
758
763
 
759
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.
760
765
 
@@ -764,7 +769,7 @@ The accepted proof as a 416-byte ABI-encoded packet, indexed by `transcriptHash`
764
769
  event CallbackAttempted(uint256 indexed requestId, bool success, uint32 gasLimit)
765
770
  ```
766
771
 
767
- Topic 0 `0x70f64c0739e827900ae6f2e1317601653f4080bc857f423671fc58d5822f1f4a` · Emitted by: [`retryCallback`](#coordinator-fn-retrycallback), [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 146, 613–630
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
768
773
 
769
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.
770
775
 
@@ -774,7 +779,7 @@ Result of calling `rawFulfillRandomness` with `gasLimit` gas, at fulfillment and
774
779
  event KeeperFeePaid(uint256 indexed requestId, address indexed keeper, uint256 amount, bool paid)
775
780
  ```
776
781
 
777
- Topic 0 `0x7605929b04963e0365f647d9ab12e7ac4aeba5474bb80f1e1554fccad0584683` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 153, 442–447
782
+ Topic 0 `0x7605929b04963e0365f647d9ab12e7ac4aeba5474bb80f1e1554fccad0584683` · Emitted by: [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 155, 459–464
778
783
 
779
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`.
780
785
 
@@ -784,7 +789,7 @@ At acceptance, when the keeper share is non-zero: `amount` went to `keeper`, the
784
789
  event FulfillmentSkipped(uint256 indexed requestId, uint8 reason)
785
790
  ```
786
791
 
787
- Topic 0 `0x45d96bda73a91db41bdeab56114e5d7b9f42c9f38add9e2d2c6d6f5761203ca3` · Emitted by: [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 161–162, 407–408
792
+ Topic 0 `0x45d96bda73a91db41bdeab56114e5d7b9f42c9f38add9e2d2c6d6f5761203ca3` · Emitted by: [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch) · Source: `D20VRFCoordinator.sol` lines 163–164, 424–425
788
793
 
789
794
  A batch member was left untouched: `reason` 1 already fulfilled, 2 refunded, 3 past its deadline.
790
795
 
@@ -794,7 +799,7 @@ A batch member was left untouched: `reason` 1 already fulfilled, 2 refunded, 3 p
794
799
  event RequestRefundedTo(uint256 indexed requestId, address indexed refundAddress, uint256 amount, bool paid)
795
800
  ```
796
801
 
797
- Topic 0 `0x0f6107d218fea62a20553f3700dba7c94dcf653bd2027c0bf1ebe0832f42a506` · Emitted by: [`refundRequest`](#coordinator-fn-refundrequest) · Source: `D20VRFCoordinator.sol` lines 155, 488
802
+ Topic 0 `0x0f6107d218fea62a20553f3700dba7c94dcf653bd2027c0bf1ebe0832f42a506` · Emitted by: [`refundRequest`](#coordinator-fn-refundrequest) · Source: `D20VRFCoordinator.sol` lines 157, 505
798
803
 
799
804
  An expired request was refunded: `amount` (`feePaid × requestRefundBps / 10000`) was sent to `refundAddress` (`paid` true) or added to its refund credit (`paid` false).
800
805
 
@@ -804,7 +809,7 @@ An expired request was refunded: `amount` (`feePaid × requestRefundBps / 10000`
804
809
  event RefundCallbackAttempted(uint256 indexed requestId, address indexed consumer, bool success, uint32 gasLimit)
805
810
  ```
806
811
 
807
- Topic 0 `0x88448c9fbcfc67f28f0266e82766e402ccd28115fbb84edb6e5b2597effe83d8` · Emitted by: [`refundRequest`](#coordinator-fn-refundrequest), [`retryRefundCallback`](#coordinator-fn-retryrefundcallback) · Source: `D20VRFCoordinator.sol` lines 157, 502–513
812
+ Topic 0 `0x88448c9fbcfc67f28f0266e82766e402ccd28115fbb84edb6e5b2597effe83d8` · Emitted by: [`refundRequest`](#coordinator-fn-refundrequest), [`retryRefundCallback`](#coordinator-fn-retryrefundcallback) · Source: `D20VRFCoordinator.sol` lines 159, 519–530
808
813
 
809
814
  Result of calling `onRefund(requestId)` on `consumer` with `gasLimit` gas: 100,000 at `refundRequest`, the caller's limit at `retryRefundCallback`.
810
815
 
@@ -816,7 +821,7 @@ Result of calling `onRefund(requestId)` on `consumer` with `gasLimit` gas: 100,0
816
821
  event RefundCreditWithdrawn(address indexed owner, address indexed recipient, uint256 amount)
817
822
  ```
818
823
 
819
- Topic 0 `0x9d520065b24fda0469128acd3f3078de7e43d70ab762aeea8c741bde25070192` · Emitted by: [`withdrawRefundCredit`](#coordinator-fn-withdrawrefundcredit) · Source: `D20VRFCoordinator.sol` lines 156, 524
824
+ Topic 0 `0x9d520065b24fda0469128acd3f3078de7e43d70ab762aeea8c741bde25070192` · Emitted by: [`withdrawRefundCredit`](#coordinator-fn-withdrawrefundcredit) · Source: `D20VRFCoordinator.sol` lines 158, 541
820
825
 
821
826
  `owner`, the credit holder (not the contract owner), withdrew `amount` of refund credit to `recipient`.
822
827
 
@@ -826,7 +831,7 @@ Topic 0 `0x9d520065b24fda0469128acd3f3078de7e43d70ab762aeea8c741bde25070192` ·
826
831
  event KeeperCreditWithdrawn(address indexed keeper, address indexed recipient, uint256 amount)
827
832
  ```
828
833
 
829
- Topic 0 `0x22f05c41968705c032a86a65f8fda7e64483ea5e6b6b27a920d1fbfab94267ba` · Emitted by: [`withdrawKeeperCredit`](#coordinator-fn-withdrawkeepercredit) · Source: `D20VRFCoordinator.sol` lines 154, 546
834
+ Topic 0 `0x22f05c41968705c032a86a65f8fda7e64483ea5e6b6b27a920d1fbfab94267ba` · Emitted by: [`withdrawKeeperCredit`](#coordinator-fn-withdrawkeepercredit) · Source: `D20VRFCoordinator.sol` lines 156, 563
830
835
 
831
836
  `keeper` withdrew `amount` of keeper credit to `recipient`.
832
837
 
@@ -836,7 +841,7 @@ Topic 0 `0x22f05c41968705c032a86a65f8fda7e64483ea5e6b6b27a920d1fbfab94267ba` ·
836
841
  event FeesWithdrawn(address indexed recipient, uint256 amount)
837
842
  ```
838
843
 
839
- Topic 0 `0xc0819c13be868895eb93e40eaceb96de976442fa1d404e5c55f14bb65a8c489a` · Emitted by: [`withdrawFees`](#coordinator-fn-withdrawfees) · Source: `D20VRFCoordinator.sol` lines 147, 535
844
+ Topic 0 `0xc0819c13be868895eb93e40eaceb96de976442fa1d404e5c55f14bb65a8c489a` · Emitted by: [`withdrawFees`](#coordinator-fn-withdrawfees) · Source: `D20VRFCoordinator.sol` lines 149, 552
840
845
 
841
846
  The fee recipient withdrew `amount` of earned fees to `recipient`.
842
847
 
@@ -848,7 +853,7 @@ The fee recipient withdrew `amount` of earned fees to `recipient`.
848
853
  event PricingChanged(uint256 minFee, uint16 feeMultiplier, uint32 fulfillGasOverhead)
849
854
  ```
850
855
 
851
- Topic 0 `0x32806eb5e21ac2f5fb7d11f898c2995e19fdf203c8a5aeed8b824506cd0d44ff` · Emitted by: [`setPricing`](#coordinator-fn-setpricing) · Source: `D20VRFCoordinator.sol` lines 150, 209
856
+ Topic 0 `0x32806eb5e21ac2f5fb7d11f898c2995e19fdf203c8a5aeed8b824506cd0d44ff` · Emitted by: [`setPricing`](#coordinator-fn-setpricing) · Source: `D20VRFCoordinator.sol` lines 152, 213
852
857
 
853
858
  New `minFee`, `feeMultiplier` and `fulfillGasOverhead` for requests created afterwards.
854
859
 
@@ -858,7 +863,7 @@ New `minFee`, `feeMultiplier` and `fulfillGasOverhead` for requests created afte
858
863
  event RefundBpsChanged(uint16 previousBps, uint16 newBps)
859
864
  ```
860
865
 
861
- Topic 0 `0x21e3c4cf3007c4ee385a3936593ff4cfa23fc17bca1175b6350c546f9810e0d8` · Emitted by: [`setRefundBps`](#coordinator-fn-setrefundbps) · Source: `D20VRFCoordinator.sol` lines 151, 217
866
+ Topic 0 `0x21e3c4cf3007c4ee385a3936593ff4cfa23fc17bca1175b6350c546f9810e0d8` · Emitted by: [`setRefundBps`](#coordinator-fn-setrefundbps) · Source: `D20VRFCoordinator.sol` lines 153, 221
862
867
 
863
868
  New refund ratio for requests created afterwards.
864
869
 
@@ -868,7 +873,7 @@ New refund ratio for requests created afterwards.
868
873
  event KeeperFeeBpsChanged(uint16 previousBps, uint16 newBps)
869
874
  ```
870
875
 
871
- Topic 0 `0xa648a60f1d22511c1cc898ca69b633d1a1114079e83734b9ab9a13e0e28c68b7` · Emitted by: [`setKeeperFeeBps`](#coordinator-fn-setkeeperfeebps) · Source: `D20VRFCoordinator.sol` lines 149, 203
876
+ Topic 0 `0xa648a60f1d22511c1cc898ca69b633d1a1114079e83734b9ab9a13e0e28c68b7` · Emitted by: [`setKeeperFeeBps`](#coordinator-fn-setkeeperfeebps) · Source: `D20VRFCoordinator.sol` lines 151, 205
872
877
 
873
878
  New keeper share, applied at later acceptances, including of requests already open.
874
879
 
@@ -878,7 +883,7 @@ New keeper share, applied at later acceptances, including of requests already op
878
883
  event FeeRecipientChanged(address indexed previousRecipient, address indexed newRecipient)
879
884
  ```
880
885
 
881
- Topic 0 `0x0bc21fe5c3ab742ff1d15b5c4477ffbacf1167e618228078fa625edebe7f331d` · Emitted by: [`setFeeRecipient`](#coordinator-fn-setfeerecipient) · Source: `D20VRFCoordinator.sol` lines 148, 199
886
+ Topic 0 `0x0bc21fe5c3ab742ff1d15b5c4477ffbacf1167e618228078fa625edebe7f331d` · Emitted by: [`setFeeRecipient`](#coordinator-fn-setfeerecipient) · Source: `D20VRFCoordinator.sol` lines 150, 201
882
887
 
883
888
  New address allowed to withdraw earned fees.
884
889
 
@@ -928,7 +933,7 @@ Topic 0 `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` ·
928
933
 
929
934
  #### <a id="coordinator-error-contractconsumerrequired"></a>`ContractConsumerRequired`
930
935
 
931
- `error ContractConsumerRequired()` · Selector `0x2b99db1e` · Source: `D20VRFCoordinator.sol` lines 111, 251
936
+ `error ContractConsumerRequired()` · Selector `0x2b99db1e` · Source: `D20VRFCoordinator.sol` lines 113, 255
932
937
 
933
938
  **Raised by:** [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness).
934
939
 
@@ -938,7 +943,7 @@ The caller of a request function has no code: an externally owned account, or a
938
943
 
939
944
  #### <a id="coordinator-error-invalidrefundaddress"></a>`InvalidRefundAddress`
940
945
 
941
- `error InvalidRefundAddress()` · Selector `0xe2fe2726` · Source: `D20VRFCoordinator.sol` lines 125, 252, 517
946
+ `error InvalidRefundAddress()` · Selector `0xe2fe2726` · Source: `D20VRFCoordinator.sol` lines 127, 256, 534
942
947
 
943
948
  **Raised by:** [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness), [`withdrawRefundCredit`](#coordinator-fn-withdrawrefundcredit).
944
949
 
@@ -948,7 +953,7 @@ A request named the zero address as refund address, or `withdrawRefundCredit` na
948
953
 
949
954
  #### <a id="coordinator-error-invalidcallbackgas"></a>`InvalidCallbackGas`
950
955
 
951
- `error InvalidCallbackGas()` · Selector `0x35883c54` · Source: `D20VRFCoordinator.sol` lines 113, 462, 498, 609–611
956
+ `error InvalidCallbackGas()` · Selector `0x35883c54` · Source: `D20VRFCoordinator.sol` lines 115, 479, 515, 626–628
952
957
 
953
958
  **Raised by:** [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness), [`retryCallback`](#coordinator-fn-retrycallback), [`retryRefundCallback`](#coordinator-fn-retryrefundcallback).
954
959
 
@@ -958,7 +963,7 @@ A gas limit is out of range: a request `callbackGasLimit` outside 30,000 to 1,00
958
963
 
959
964
  #### <a id="coordinator-error-incorrectfee"></a>`IncorrectFee`
960
965
 
961
- `error IncorrectFee(uint256 expected, uint256 actual)` · Selector `0xdcf6afcb` · Source: `D20VRFCoordinator.sol` lines 112, 255
966
+ `error IncorrectFee(uint256 expected, uint256 actual)` · Selector `0xdcf6afcb` · Source: `D20VRFCoordinator.sol` lines 114, 259
962
967
 
963
968
  **Raised by:** [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness).
964
969
 
@@ -968,7 +973,7 @@ A gas limit is out of range: a request `callbackGasLimit` outside 30,000 to 1,00
968
973
 
969
974
  #### <a id="coordinator-error-feeoverflow"></a>`FeeOverflow`
970
975
 
971
- `error FeeOverflow()` · Selector `0x8181adca` · Source: `D20VRFCoordinator.sol` lines 136, 225
976
+ `error FeeOverflow()` · Selector `0x8181adca` · Source: `D20VRFCoordinator.sol` lines 138, 229
972
977
 
973
978
  **Raised by:** [`quoteFee`](#coordinator-fn-quotefee), [`quoteFeeAt`](#coordinator-fn-quotefeeat), [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness).
974
979
 
@@ -988,7 +993,7 @@ The spec breaks the rules for its operation (README [Randomness options](README.
988
993
 
989
994
  #### <a id="coordinator-error-epochunavailable"></a>`EpochUnavailable`
990
995
 
991
- `error EpochUnavailable()` · Selector `0x0b3487b8` · Source: `D20VRFCoordinator.sol` lines 109, 259
996
+ `error EpochUnavailable()` · Selector `0x0b3487b8` · Source: `D20VRFCoordinator.sol` lines 111, 263
992
997
 
993
998
  **Raised by:** [`requestRandomness`](#coordinator-fn-requestrandomness), [`requestMappedRandomness`](#coordinator-fn-requestmappedrandomness).
994
999
 
@@ -1000,7 +1005,7 @@ The request block is before the registry's first epoch: `epochForBlock(block.num
1000
1005
 
1001
1006
  #### <a id="coordinator-error-unknownrequest"></a>`UnknownRequest`
1002
1007
 
1003
- `error UnknownRequest()` · Selector `0x6d080297` · Source: `D20VRFCoordinator.sol` lines 114, 549–552
1008
+ `error UnknownRequest()` · Selector `0x6d080297` · Source: `D20VRFCoordinator.sol` lines 116, 566–569
1004
1009
 
1005
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).
1006
1011
 
@@ -1010,7 +1015,7 @@ No request has this ID: 0, or not below `nextRequestId()`. An unknown ID also re
1010
1015
 
1011
1016
  #### <a id="coordinator-error-notfulfilled"></a>`NotFulfilled`
1012
1017
 
1013
- `error NotFulfilled()` · Selector `0x07bc6c3e` · Source: `D20VRFCoordinator.sol` lines 118, 347, 459
1018
+ `error NotFulfilled()` · Selector `0x07bc6c3e` · Source: `D20VRFCoordinator.sol` lines 120, 351, 476
1014
1019
 
1015
1020
  **Raised by:** [`getMappedResult`](#coordinator-fn-getmappedresult), [`retryCallback`](#coordinator-fn-retrycallback).
1016
1021
 
@@ -1020,7 +1025,7 @@ No request has this ID: 0, or not below `nextRequestId()`. An unknown ID also re
1020
1025
 
1021
1026
  #### <a id="coordinator-error-alreadydelivered"></a>`AlreadyDelivered`
1022
1027
 
1023
- `error AlreadyDelivered()` · Selector `0xb9f79653` · Source: `D20VRFCoordinator.sol` lines 119, 460
1028
+ `error AlreadyDelivered()` · Selector `0xb9f79653` · Source: `D20VRFCoordinator.sol` lines 121, 477
1024
1029
 
1025
1030
  **Raised by:** [`retryCallback`](#coordinator-fn-retrycallback).
1026
1031
 
@@ -1030,7 +1035,7 @@ No request has this ID: 0, or not below `nextRequestId()`. An unknown ID also re
1030
1035
 
1031
1036
  #### <a id="coordinator-error-refundnotavailable"></a>`RefundNotAvailable`
1032
1037
 
1033
- `error RefundNotAvailable()` · Selector `0x0b4d6981` · Source: `D20VRFCoordinator.sol` lines 128, 471
1038
+ `error RefundNotAvailable()` · Selector `0x0b4d6981` · Source: `D20VRFCoordinator.sol` lines 130, 488
1034
1039
 
1035
1040
  **Raised by:** [`refundRequest`](#coordinator-fn-refundrequest).
1036
1041
 
@@ -1040,7 +1045,7 @@ No request has this ID: 0, or not below `nextRequestId()`. An unknown ID also re
1040
1045
 
1041
1046
  #### <a id="coordinator-error-notrefunded"></a>`NotRefunded`
1042
1047
 
1043
- `error NotRefunded()` · Selector `0xfae7079c` · Source: `D20VRFCoordinator.sol` lines 131, 495
1048
+ `error NotRefunded()` · Selector `0xfae7079c` · Source: `D20VRFCoordinator.sol` lines 133, 512
1044
1049
 
1045
1050
  **Raised by:** [`retryRefundCallback`](#coordinator-fn-retryrefundcallback).
1046
1051
 
@@ -1050,7 +1055,7 @@ No request has this ID: 0, or not below `nextRequestId()`. An unknown ID also re
1050
1055
 
1051
1056
  #### <a id="coordinator-error-refundcallbackalreadydelivered"></a>`RefundCallbackAlreadyDelivered`
1052
1057
 
1053
- `error RefundCallbackAlreadyDelivered()` · Selector `0x6502f8ae` · Source: `D20VRFCoordinator.sol` lines 132, 496
1058
+ `error RefundCallbackAlreadyDelivered()` · Selector `0x6502f8ae` · Source: `D20VRFCoordinator.sol` lines 134, 513
1054
1059
 
1055
1060
  **Raised by:** [`retryRefundCallback`](#coordinator-fn-retryrefundcallback).
1056
1061
 
@@ -1060,19 +1065,19 @@ No request has this ID: 0, or not below `nextRequestId()`. An unknown ID also re
1060
1065
 
1061
1066
  #### <a id="coordinator-error-insufficientcallbackgas"></a>`InsufficientCallbackGas`
1062
1067
 
1063
- `error InsufficientCallbackGas()` · Selector `0xa2c23f0d` · Source: `D20VRFCoordinator.sol` lines 122, 480, 505, 621–622
1068
+ `error InsufficientCallbackGas()` · Selector `0xa2c23f0d` · Source: `D20VRFCoordinator.sol` lines 124, 421, 497, 522, 638–639
1064
1069
 
1065
1070
  **Raised by:** [`refundRequest`](#coordinator-fn-refundrequest), [`retryCallback`](#coordinator-fn-retrycallback), [`retryRefundCallback`](#coordinator-fn-retryrefundcallback), [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch).
1066
1071
 
1067
- 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.
1068
1073
 
1069
- **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.
1070
1075
 
1071
1076
  **Credits and withdrawals**
1072
1077
 
1073
1078
  #### <a id="coordinator-error-norefundcredit"></a>`NoRefundCredit`
1074
1079
 
1075
- `error NoRefundCredit()` · Selector `0x1d59da8e` · Source: `D20VRFCoordinator.sol` lines 129, 519
1080
+ `error NoRefundCredit()` · Selector `0x1d59da8e` · Source: `D20VRFCoordinator.sol` lines 131, 536
1076
1081
 
1077
1082
  **Raised by:** [`withdrawRefundCredit`](#coordinator-fn-withdrawrefundcredit).
1078
1083
 
@@ -1082,7 +1087,7 @@ Too little gas remained to forward the full callback budget and keep the coordin
1082
1087
 
1083
1088
  #### <a id="coordinator-error-transferfailed"></a>`TransferFailed`
1084
1089
 
1085
- `error TransferFailed()` · Selector `0x90b8ec18` · Source: `D20VRFCoordinator.sol` lines 124, 523, 534, 545
1090
+ `error TransferFailed()` · Selector `0x90b8ec18` · Source: `D20VRFCoordinator.sol` lines 126, 540, 551, 562
1086
1091
 
1087
1092
  **Raised by:** [`withdrawRefundCredit`](#coordinator-fn-withdrawrefundcredit), [`withdrawFees`](#coordinator-fn-withdrawfees), [`withdrawKeeperCredit`](#coordinator-fn-withdrawkeepercredit).
1088
1093
 
@@ -1092,7 +1097,7 @@ The `recipient` of `withdrawRefundCredit`, `withdrawFees` or `withdrawKeeperCred
1092
1097
 
1093
1098
  #### <a id="coordinator-error-onlyfeerecipient"></a>`OnlyFeeRecipient`
1094
1099
 
1095
- `error OnlyFeeRecipient()` · Selector `0x07d8ed3d` · Source: `D20VRFCoordinator.sol` lines 123, 529
1100
+ `error OnlyFeeRecipient()` · Selector `0x07d8ed3d` · Source: `D20VRFCoordinator.sol` lines 125, 546
1096
1101
 
1097
1102
  **Raised by:** [`withdrawFees`](#coordinator-fn-withdrawfees).
1098
1103
 
@@ -1102,7 +1107,7 @@ The `recipient` of `withdrawRefundCredit`, `withdrawFees` or `withdrawKeeperCred
1102
1107
 
1103
1108
  #### <a id="coordinator-error-nokeepercredit"></a>`NoKeeperCredit`
1104
1109
 
1105
- `error NoKeeperCredit()` · Selector `0x0d106640` · Source: `D20VRFCoordinator.sol` lines 130, 541
1110
+ `error NoKeeperCredit()` · Selector `0x0d106640` · Source: `D20VRFCoordinator.sol` lines 132, 558
1106
1111
 
1107
1112
  **Raised by:** [`withdrawKeeperCredit`](#coordinator-fn-withdrawkeepercredit).
1108
1113
 
@@ -1114,7 +1119,7 @@ The `recipient` of `withdrawRefundCredit`, `withdrawFees` or `withdrawKeeperCred
1114
1119
 
1115
1120
  #### <a id="coordinator-error-notready"></a>`NotReady`
1116
1121
 
1117
- `error NotReady()` · Selector `0x9488aaa6` · Source: `D20VRFCoordinator.sol` lines 115, 566
1122
+ `error NotReady()` · Selector `0x9488aaa6` · Source: `D20VRFCoordinator.sol` lines 117, 583
1118
1123
 
1119
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).
1120
1125
 
@@ -1124,7 +1129,7 @@ The request cannot be proven yet: its epoch packet is not published, or `block.n
1124
1129
 
1125
1130
  #### <a id="coordinator-error-blockhashunavailable"></a>`BlockHashUnavailable`
1126
1131
 
1127
- `error BlockHashUnavailable()` · Selector `0xbfc9f0d3` · Source: `D20VRFCoordinator.sol` lines 116, 569
1132
+ `error BlockHashUnavailable()` · Selector `0xbfc9f0d3` · Source: `D20VRFCoordinator.sol` lines 118, 586
1128
1133
 
1129
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).
1130
1135
 
@@ -1134,7 +1139,7 @@ The target block hash was never stored and is outside the 256-block `BLOCKHASH`
1134
1139
 
1135
1140
  #### <a id="coordinator-error-alreadyfulfilled"></a>`AlreadyFulfilled`
1136
1141
 
1137
- `error AlreadyFulfilled()` · Selector `0x4a4117f9` · Source: `D20VRFCoordinator.sol` lines 117, 393
1142
+ `error AlreadyFulfilled()` · Selector `0x4a4117f9` · Source: `D20VRFCoordinator.sol` lines 119, 397
1138
1143
 
1139
1144
  **Raised by:** [`fulfillRandomness`](#coordinator-fn-fulfillrandomness).
1140
1145
 
@@ -1144,7 +1149,7 @@ The target block hash was never stored and is outside the 256-block `BLOCKHASH`
1144
1149
 
1145
1150
  #### <a id="coordinator-error-requestrefunded"></a>`RequestRefunded`
1146
1151
 
1147
- `error RequestRefunded()` · Selector `0xe0dec416` · Source: `D20VRFCoordinator.sol` lines 127, 394
1152
+ `error RequestRefunded()` · Selector `0xe0dec416` · Source: `D20VRFCoordinator.sol` lines 129, 398
1148
1153
 
1149
1154
  **Raised by:** [`fulfillRandomness`](#coordinator-fn-fulfillrandomness).
1150
1155
 
@@ -1154,7 +1159,7 @@ The target block hash was never stored and is outside the 256-block `BLOCKHASH`
1154
1159
 
1155
1160
  #### <a id="coordinator-error-requestexpired"></a>`RequestExpired`
1156
1161
 
1157
- `error RequestExpired()` · Selector `0xfef01cd2` · Source: `D20VRFCoordinator.sol` lines 126, 395
1162
+ `error RequestExpired()` · Selector `0xfef01cd2` · Source: `D20VRFCoordinator.sol` lines 128, 399
1158
1163
 
1159
1164
  **Raised by:** [`fulfillRandomness`](#coordinator-fn-fulfillrandomness).
1160
1165
 
@@ -1164,7 +1169,7 @@ The target block hash was never stored and is outside the 256-block `BLOCKHASH`
1164
1169
 
1165
1170
  #### <a id="coordinator-error-wrongpublickey"></a>`WrongPublicKey`
1166
1171
 
1167
- `error WrongPublicKey()` · Selector `0x2b0bb68e` · Source: `D20VRFCoordinator.sol` lines 120, 594
1172
+ `error WrongPublicKey()` · Selector `0x2b0bb68e` · Source: `D20VRFCoordinator.sol` lines 122, 611
1168
1173
 
1169
1174
  **Raised by:** [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch), [`verifyRequestProof`](#coordinator-fn-verifyrequestproof).
1170
1175
 
@@ -1174,7 +1179,7 @@ The proof's `pk` is not the coordinator's VRF key.
1174
1179
 
1175
1180
  #### <a id="coordinator-error-wrongseed"></a>`WrongSeed`
1176
1181
 
1177
- `error WrongSeed()` · Selector `0xf36cbea4` · Source: `D20VRFCoordinator.sol` lines 121, 596
1182
+ `error WrongSeed()` · Selector `0xf36cbea4` · Source: `D20VRFCoordinator.sol` lines 123, 613
1178
1183
 
1179
1184
  **Raised by:** [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch), [`verifyRequestProof`](#coordinator-fn-verifyrequestproof).
1180
1185
 
@@ -1184,7 +1189,7 @@ The proof's `seed` differs from `requestSeed(requestId)`.
1184
1189
 
1185
1190
  #### <a id="coordinator-error-evidencepackettoolarge"></a>`EvidencePacketTooLarge`
1186
1191
 
1187
- `error EvidencePacketTooLarge()` · Selector `0xcfbc3ebf` · Source: `D20VRFCoordinator.sol` lines 134, 452
1192
+ `error EvidencePacketTooLarge()` · Selector `0xcfbc3ebf` · Source: `D20VRFCoordinator.sol` lines 136, 469
1188
1193
 
1189
1194
  **Raised by:** [`fulfillRandomness`](#coordinator-fn-fulfillrandomness), [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch).
1190
1195
 
@@ -1194,7 +1199,7 @@ The encoded proof exceeds `MAX_EVIDENCE_PACKET_BYTES`. A proof always encodes to
1194
1199
 
1195
1200
  #### <a id="coordinator-error-invalidbatch"></a>`InvalidBatch`
1196
1201
 
1197
- `error InvalidBatch()` · Selector `0x33b094a1` · Source: `D20VRFCoordinator.sol` lines 137, 404
1202
+ `error InvalidBatch()` · Selector `0x33b094a1` · Source: `D20VRFCoordinator.sol` lines 139, 410
1198
1203
 
1199
1204
  **Raised by:** [`fulfillRandomnessBatch`](#coordinator-fn-fulfillrandomnessbatch).
1200
1205
 
@@ -1204,7 +1209,7 @@ The encoded proof exceeds `MAX_EVIDENCE_PACKET_BYTES`. A proof always encodes to
1204
1209
 
1205
1210
  #### <a id="coordinator-error-invalidscan"></a>`InvalidScan`
1206
1211
 
1207
- `error InvalidScan()` · Selector `0x3e6249a2` · Source: `D20VRFCoordinator.sol` lines 133, 316
1212
+ `error InvalidScan()` · Selector `0x3e6249a2` · Source: `D20VRFCoordinator.sol` lines 135, 320
1208
1213
 
1209
1214
  **Raised by:** [`getPendingRequestIds`](#coordinator-fn-getpendingrequestids).
1210
1215
 
@@ -1216,17 +1221,17 @@ The encoded proof exceeds `MAX_EVIDENCE_PACKET_BYTES`. A proof always encodes to
1216
1221
 
1217
1222
  #### <a id="coordinator-error-invalidconfig"></a>`InvalidConfig`
1218
1223
 
1219
- `error InvalidConfig()` · Selector `0x35be3ac8` · Source: `D20VRFCoordinator.sol` lines 108, 174–175, 198, 202, 207, 216, 530, 539
1224
+ `error InvalidConfig()` · Selector `0x35be3ac8` · Source: `D20VRFCoordinator.sol` lines 110, 176–177, 200, 204, 209, 211, 220, 547, 556
1220
1225
 
1221
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).
1222
1227
 
1223
- 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`.
1224
1229
 
1225
1230
  **What to do:** Use values within the bounds given for each function.
1226
1231
 
1227
1232
  #### <a id="coordinator-error-invalidpublickey"></a>`InvalidPublicKey`
1228
1233
 
1229
- `error InvalidPublicKey()` · Selector `0xa2d0fee8` · Source: `D20VRFCoordinator.sol` lines 110, 177
1234
+ `error InvalidPublicKey()` · Selector `0xa2d0fee8` · Source: `D20VRFCoordinator.sol` lines 112, 179
1230
1235
 
1231
1236
  **Raised by:** [`initialize`](#coordinator-fn-initialize).
1232
1237
 
@@ -1266,7 +1271,7 @@ A `nonReentrant` coordinator function (a request, `storeBlockHash`, a fulfillmen
1266
1271
 
1267
1272
  #### <a id="coordinator-error-renouncedisabled"></a>`RenounceDisabled`
1268
1273
 
1269
- `error RenounceDisabled()` · Selector `0x89051165` · Source: `D20VRFCoordinator.sol` lines 135, 195
1274
+ `error RenounceDisabled()` · Selector `0x89051165` · Source: `D20VRFCoordinator.sol` lines 137, 197
1270
1275
 
1271
1276
  **Raised by:** [`renounceOwnership`](#coordinator-fn-renounceownership).
1272
1277
 
@@ -1358,7 +1363,9 @@ The initialization call made by `upgradeToAndCall` reverted without revert data.
1358
1363
 
1359
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.
1360
1365
 
1361
- Epoch sources are recipes in an owner-managed, append-only registry: each has an id, a canonical request whose `keccak256` is the query hash its signer signs, a data template that fixes the exact signed bytes the registry accepts (README [Data templates](README.md#data-templates)) and the JSON body keepers post to the provider gateway. A catalog lists 1 to `MAX_SOURCES` registered recipes with one signer each; each epoch selects from the catalog in force for it (`catalogAt`).
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)).
1362
1369
 
1363
1370
  ### <a id="registry-types"></a>Types
1364
1371
 
@@ -1366,7 +1373,7 @@ Epoch sources are recipes in an owner-managed, append-only registry: each has an
1366
1373
 
1367
1374
  Returned by `getEpoch`; all zero until the epoch is published.
1368
1375
 
1369
- Source: `EpochEntropy.sol` lines 45–48
1376
+ Source: `EpochEntropy.sol` lines 57–60
1370
1377
 
1371
1378
  | Field | Type | Meaning |
1372
1379
  | --- | --- | --- |
@@ -1384,28 +1391,42 @@ Source: `EpochEntropy.sol` lines 45–48
1384
1391
 
1385
1392
  Returned by `getEpochSelection` and `getEpochFallbackSelection`.
1386
1393
 
1387
- Source: `EpochEntropy.sol` lines 43–44, 262–273
1394
+ Source: `EpochEntropy.sol` lines 55–56, 345–356
1388
1395
 
1389
1396
  | Field | Type | Meaning |
1390
1397
  | --- | --- | --- |
1391
1398
  | `source` | `uint8` | Slot in the epoch's catalog. |
1392
1399
  | `recipe` | `uint8` | Registered recipe id at that slot. |
1393
- | `airnode` | `address` | Signer of that slot in the catalog in force for the epoch. |
1400
+ | `airnode` | `address` | Signer of that slot in the catalog in force for the epoch; for a beacon recipe its `slotSigner`. |
1394
1401
  | `selector` | `bytes32` | `keccak256(abi.encode(SELECT_DOMAIN, catalogHash, epochId, anchor))`; the slot is `(selector mod count + attempt) mod count` with `count = sourceCountAt(epochId)`. |
1395
1402
  | `queryHash` | `bytes32` | `keccak256` of `canonicalRequest`. |
1396
1403
  | `canonicalRequest` | `string` | Canonical request of the recipe, as `getRecipe` returns it. |
1397
1404
 
1398
1405
  #### <a id="registry-type-epochentropy-attestation"></a>`EpochEntropy.Attestation`
1399
1406
 
1400
- Signed source response passed to `commitEpoch` and `commitEpochFallback`.
1407
+ Source response passed to `commitEpoch` and `commitEpochFallback`: a signed API record or a beacon round.
1408
+
1409
+ Source: `EpochEntropy.sol` lines 54, 363–391
1410
+
1411
+ | Field | Type | Meaning |
1412
+ | --- | --- | --- |
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`
1401
1418
 
1402
- Source: `EpochEntropy.sol` lines 42, 280–300
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
1403
1422
 
1404
1423
  | Field | Type | Meaning |
1405
1424
  | --- | --- | --- |
1406
- | `timestamp` | `uint256` | Signing time in Unix seconds; not in the future and at most `MAX_ATTESTATION_AGE` old at publication. |
1407
- | `data` | `bytes` | Signed response bytes, at most `MAX_DATA_BYTES` (128), matching the data template of the slot's recipe exactly. |
1408
- | `signature` | `bytes` | Signature over `toEthSignedMessageHash(keccak256(abi.encodePacked(queryHash, timestamp, data)))`. |
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. |
1409
1430
 
1410
1431
  ### <a id="registry-epoch-state-for-consumers-and-verifiers"></a>Epoch state for consumers and verifiers
1411
1432
 
@@ -1417,7 +1438,7 @@ Views, callable by anyone. Epoch IDs start at 1; each epoch lasts `EPOCH_LENGTH`
1417
1438
  function epochForBlock(uint256 number) external view returns (uint64)
1418
1439
  ```
1419
1440
 
1420
- Selector `0x7018ebb1` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 235–237
1441
+ Selector `0x7018ebb1` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 318–320
1421
1442
 
1422
1443
  Epoch containing a block: 0 before `firstEpochStart`, otherwise `1 + (number - firstEpochStart) / 200`. A request belongs to `epochForBlock(requestBlock)`.
1423
1444
 
@@ -1427,7 +1448,7 @@ Epoch containing a block: 0 before `firstEpochStart`, otherwise `1 + (number - f
1427
1448
  function epochStart(uint64 epochId) external view returns (uint64)
1428
1449
  ```
1429
1450
 
1430
- Selector `0xa1587509` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 231–234
1451
+ Selector `0xa1587509` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 314–317
1431
1452
 
1432
1453
  First block of an epoch: `firstEpochStart + (epochId - 1) × 200`.
1433
1454
 
@@ -1439,7 +1460,7 @@ First block of an epoch: `firstEpochStart + (epochId - 1) × 200`.
1439
1460
  function getEpoch(uint64 epochId) external view returns (EpochEntropy.Epoch)
1440
1461
  ```
1441
1462
 
1442
- Selector `0x12a02c82` · Caller: Anyone (view) · Source: `EpochEntropy.sol` line 252
1463
+ Selector `0x12a02c82` · Caller: Anyone (view) · Source: `EpochEntropy.sol` line 335
1443
1464
 
1444
1465
  The published [`EpochEntropy.Epoch`](#registry-type-epochentropy-epoch) record, or all zero while unpublished; it never reverts. A non-zero `epochHash` means published.
1445
1466
 
@@ -1449,7 +1470,7 @@ The published [`EpochEntropy.Epoch`](#registry-type-epochentropy-epoch) record,
1449
1470
  function catalogAt(uint64 epochId) external view returns (bytes32 hash, uint8[] recipes, address[] signers)
1450
1471
  ```
1451
1472
 
1452
- Selector `0xec993599` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 212–220, 226–230
1473
+ Selector `0xec993599` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 295–303, 309–313
1453
1474
 
1454
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.
1455
1476
 
@@ -1459,7 +1480,7 @@ The catalog in force for an epoch: its hash and the recipe id and signer of each
1459
1480
  function sourceCountAt(uint64 epochId) external view returns (uint256)
1460
1481
  ```
1461
1482
 
1462
- Selector `0x3edc6b12` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 221–225, 226–230
1483
+ Selector `0x3edc6b12` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 304–308, 309–313
1463
1484
 
1464
1485
  Number of slots in the catalog in force for an epoch, and so the number of selection attempts, 0 to count - 1.
1465
1486
 
@@ -1469,19 +1490,53 @@ Number of slots in the catalog in force for an epoch, and so the number of selec
1469
1490
  function getRecipe(uint8 recipe) external view returns (bytes32 queryHash, string canonicalRequest, bytes template, string body)
1470
1491
  ```
1471
1492
 
1472
- Selector `0xba01b103` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 132–136, 139–142
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`
1500
+
1501
+ ```solidity
1502
+ function beaconOf(uint8 recipe) external view returns (EpochEntropy.Beacon)
1503
+ ```
1504
+
1505
+ Selector `0x87533a48` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 188–192
1473
1506
 
1474
- A registered recipe: `queryHash` (`keccak256` of `canonicalRequest`), the canonical request its signer signs, the data template its signed data must match and the JSON body keepers post to the provider gateway. Registered recipes never change. `readEpochRecipes` in `@d20dao/vrf-sdk/epoch` reads recipes with this view and checks each query hash.
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.
1475
1508
 
1476
1509
  **Errors:** [`InvalidConfig`](#registry-error-invalidconfig).
1477
1510
 
1511
+ #### <a id="registry-fn-slotsigner"></a>`slotSigner`
1512
+
1513
+ ```solidity
1514
+ function slotSigner(uint8 recipe) external view returns (address)
1515
+ ```
1516
+
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.
1520
+
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
+
1478
1533
  #### <a id="registry-fn-reciperequest"></a>`recipeRequest`
1479
1534
 
1480
1535
  ```solidity
1481
1536
  function recipeRequest(uint8 recipe) external view returns (string)
1482
1537
  ```
1483
1538
 
1484
- Selector `0x7ce4b6e0` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 137–142
1539
+ Selector `0x7ce4b6e0` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 155–160
1485
1540
 
1486
1541
  Canonical request of a registered recipe, the same string `getRecipe` returns.
1487
1542
 
@@ -1493,7 +1548,7 @@ Canonical request of a registered recipe, the same string `getRecipe` returns.
1493
1548
  function recipeCount() external view returns (uint256)
1494
1549
  ```
1495
1550
 
1496
- Selector `0x69cfdf74` · Caller: Anyone (view) · Source: `EpochEntropy.sol` line 131
1551
+ Selector `0x69cfdf74` · Caller: Anyone (view) · Source: `EpochEntropy.sol` line 149
1497
1552
 
1498
1553
  Number of registered recipes; ids run from 0 to `recipeCount() - 1`.
1499
1554
 
@@ -1503,17 +1558,17 @@ Views, callable by anyone.
1503
1558
 
1504
1559
  | Function | Selector | Meaning | Source |
1505
1560
  | --- | --- | --- | --- |
1506
- | <a id="registry-fn-firstepochstart"></a>`firstEpochStart() returns (uint64)` | `0x219f2428` | First block of epoch 1: the initialization block plus 200. | lines 40, 83 |
1507
- | <a id="registry-fn-committer"></a>`committer() returns (address)` | `0x5bc8e8f9` | Primary publishing address. The coordinator pays it the keeper share of the requests it serves itself and of every request whose proof came from a wallet this registry does not authorize. | lines 39, 281 |
1508
- | <a id="registry-fn-isbackupcommitter"></a>`isBackupCommitter(address account) returns (bool)` | `0x1d97e417` | Whether an address may publish epochs besides `committer()`. | line 118 |
1509
- | <a id="registry-fn-isauthorizedcommitter"></a>`isAuthorizedCommitter(address account) returns (bool)` | `0x1579ab83` | Whether an address may publish epochs at all: `committer()` or an allowed backup committer. The coordinator reads it to decide whether a proof submitter earns the keeper share. | lines 119–121 |
1510
- | <a id="registry-fn-backupcommittercount"></a>`backupCommitterCount() returns (uint256)` | `0xa815c5bf` | Number of allowed backup committers, at most `MAX_BACKUP_COMMITTERS`. | lines 63, 107–117 |
1511
- | <a id="registry-fn-cataloghash"></a>`catalogHash() returns (bytes32)` | `0x830c083a` | Initial catalog hash, bound into `protocolConfigurationHash`. Never changes; `catalogAt` gives the catalog of an epoch. | lines 41, 84 |
1512
- | <a id="registry-fn-epochanchors"></a>`epochAnchors(uint64) returns (bytes32)` | `0x48a030fb` | Checkpointed anchor of an epoch (hash of block `epochStart - 1`); zero until a request, `checkpointEpoch` or publication stores it. | lines 53, 241–244 |
1513
- | <a id="registry-fn-hyperliquidsigner"></a>`hyperliquidSigner() returns (address)` | `0xf2a12563` | Initial-catalog signer of slot 0 (recipe 0, Hyperliquid BTC volume). Never changes; see `catalogAt`. | line 34 |
1514
- | <a id="registry-fn-ethereumblocksigner"></a>`ethereumBlockSigner() returns (address)` | `0xd25eacfc` | Initial-catalog signer of slot 1 (recipe 1, Ethereum block hash). Never changes; see `catalogAt`. | lines 35–36 |
1515
- | <a id="registry-fn-btctradesigner"></a>`btcTradeSigner() returns (address)` | `0xb3b9cbb0` | Initial-catalog signer of slot 2 (recipe 2, TickerLayer BTCUSD). Never changes; see `catalogAt`. | line 37 |
1516
- | <a id="registry-fn-ethtradesigner"></a>`ethTradeSigner() returns (address)` | `0x3a700168` | Initial-catalog signer of slot 3 (recipe 3, TickerLayer ETHUSD). Never changes; see `catalogAt`. | line 38 |
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 |
1517
1572
 
1518
1573
  ### <a id="registry-source-selection-and-publication"></a>Source selection and publication
1519
1574
 
@@ -1525,7 +1580,7 @@ Used by keepers. Publication is restricted to the committer and backup committer
1525
1580
  function getEpochSelection(uint64 epochId) external view returns (EpochEntropy.Selection s)
1526
1581
  ```
1527
1582
 
1528
- Selector `0xec4960ad` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 253, 262–273
1583
+ Selector `0xec4960ad` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 336, 345–356
1529
1584
 
1530
1585
  The selected source of an epoch, attempt 0, as an [`EpochEntropy.Selection`](#registry-type-epochentropy-selection).
1531
1586
 
@@ -1537,7 +1592,7 @@ The selected source of an epoch, attempt 0, as an [`EpochEntropy.Selection`](#re
1537
1592
  function getEpochFallbackSelection(uint64 epochId, uint8 attempt) external view returns (EpochEntropy.Selection s)
1538
1593
  ```
1539
1594
 
1540
- Selector `0x0e5a0e02` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 254–255, 262–273
1595
+ Selector `0x0e5a0e02` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 337–338, 345–356
1541
1596
 
1542
1597
  The source for attempt 0 to `sourceCountAt(epochId) - 1`; attempt n uses the slot n positions after the selected one.
1543
1598
 
@@ -1549,7 +1604,7 @@ The source for attempt 0 to `sourceCountAt(epochId) - 1`; attempt n uses the slo
1549
1604
  function fallbackOpensAt(uint64 epochId, uint8 attempt) external view returns (uint64)
1550
1605
  ```
1551
1606
 
1552
- Selector `0x98208050` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 256–260
1607
+ Selector `0x98208050` · Caller: Anyone (view) · Source: `EpochEntropy.sol` lines 339–343
1553
1608
 
1554
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.
1555
1610
 
@@ -1561,7 +1616,7 @@ First block at which an attempt may be published: `epochStart + attempt × FALLB
1561
1616
  function nextEpochToPrepare(uint256 number) external view returns (uint64)
1562
1617
  ```
1563
1618
 
1564
- Selector `0xc78fafe2` · Caller: Anyone (view) · Source: `EpochEntropy.sol` line 238
1619
+ Selector `0xc78fafe2` · Caller: Anyone (view) · Source: `EpochEntropy.sol` line 321
1565
1620
 
1566
1621
  Same value as `epochForBlock(number)`.
1567
1622
 
@@ -1571,7 +1626,7 @@ Same value as `epochForBlock(number)`.
1571
1626
  function checkpointEpoch(uint64 epochId) external returns (bytes32 anchor)
1572
1627
  ```
1573
1628
 
1574
- Selector `0x16de78cb` · Caller: Anyone · Source: `EpochEntropy.sol` lines 239–244, 245–251
1629
+ Selector `0x16de78cb` · Caller: Anyone · Source: `EpochEntropy.sol` lines 322–327, 328–334
1575
1630
 
1576
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.
1577
1632
 
@@ -1583,13 +1638,13 @@ Stores the anchor of a started epoch (hash of block `epochStart - 1`) if not sto
1583
1638
  function commitEpoch(uint64 epochId, EpochEntropy.Attestation a) external
1584
1639
  ```
1585
1640
 
1586
- Selector `0xb1580277` · Caller: Committer or backup committer · Source: `EpochEntropy.sol` lines 274, 280–300
1641
+ Selector `0xb1580277` · Caller: Committer or backup committer · Source: `EpochEntropy.sol` lines 357, 363–391
1587
1642
 
1588
- Publishes the packet of the selected source once per epoch, from the epoch start: checks that the attestation is not future-dated and at most 240 seconds old, that its data matches the data template of the slot's recipe exactly, and that the slot's signer in the epoch's catalog signed it. Stores the record and emits the packet. Publishing earns nothing by itself: the keeper share of each request goes to the authorized wallet that submits its accepted proof, or to `committer()` when the submitter is not authorized.
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.
1589
1644
 
1590
1645
  **Emits:** [`EpochCommitted`](#registry-event-epochcommitted).
1591
1646
 
1592
- **Errors:** [`OnlyCommitter`](#registry-error-onlycommitter), [`AlreadyCommitted`](#registry-error-alreadycommitted), [`InvalidEpoch`](#registry-error-invalidepoch), [`FallbackNotOpen`](#registry-error-fallbacknotopen), [`AnchorUnavailable`](#registry-error-anchorunavailable), [`InvalidConfig`](#registry-error-invalidconfig), [`InvalidTime`](#registry-error-invalidtime), [`InvalidData`](#registry-error-invaliddata), [`ECDSAInvalidSignatureLength`](#registry-error-ecdsainvalidsignaturelength), [`ECDSAInvalidSignatureS`](#registry-error-ecdsainvalidsignatures), [`ECDSAInvalidSignature`](#registry-error-ecdsainvalidsignature), [`InvalidSigner`](#registry-error-invalidsigner), [`PacketTooLarge`](#registry-error-packettoolarge).
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).
1593
1648
 
1594
1649
  #### <a id="registry-fn-commitepochfallback"></a>`commitEpochFallback`
1595
1650
 
@@ -1597,13 +1652,13 @@ Publishes the packet of the selected source once per epoch, from the epoch start
1597
1652
  function commitEpochFallback(uint64 epochId, uint8 attempt, EpochEntropy.Attestation a) external
1598
1653
  ```
1599
1654
 
1600
- Selector `0x5768d9a1` · Caller: Committer or backup committer · Source: `EpochEntropy.sol` lines 275–279, 280–300
1655
+ Selector `0x5768d9a1` · Caller: Committer or backup committer · Source: `EpochEntropy.sol` lines 358–362, 363–391
1601
1656
 
1602
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`.
1603
1658
 
1604
1659
  **Emits:** [`EpochCommitted`](#registry-event-epochcommitted).
1605
1660
 
1606
- **Errors:** [`InvalidFallback`](#registry-error-invalidfallback), [`OnlyCommitter`](#registry-error-onlycommitter), [`AlreadyCommitted`](#registry-error-alreadycommitted), [`InvalidEpoch`](#registry-error-invalidepoch), [`FallbackNotOpen`](#registry-error-fallbacknotopen), [`AnchorUnavailable`](#registry-error-anchorunavailable), [`InvalidConfig`](#registry-error-invalidconfig), [`InvalidTime`](#registry-error-invalidtime), [`InvalidData`](#registry-error-invaliddata), [`ECDSAInvalidSignatureLength`](#registry-error-ecdsainvalidsignaturelength), [`ECDSAInvalidSignatureS`](#registry-error-ecdsainvalidsignatures), [`ECDSAInvalidSignature`](#registry-error-ecdsainvalidsignature), [`InvalidSigner`](#registry-error-invalidsigner), [`PacketTooLarge`](#registry-error-packettoolarge).
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).
1607
1662
 
1608
1663
  ### <a id="registry-recipes-and-catalogs"></a>Recipes and catalogs
1609
1664
 
@@ -1615,7 +1670,7 @@ Owner-only; on Arc Mainnet the owner is the DAO treasury Safe. A recipe or catal
1615
1670
  function registerRecipe(string canonicalRequest, bytes template, string body) external returns (uint8 recipe)
1616
1671
  ```
1617
1672
 
1618
- Selector `0x5add5c50` · Caller: Owner · Source: `EpochEntropy.sol` lines 123–130, 143–152
1673
+ Selector `0x5add5c50` · Caller: Owner · Source: `EpochEntropy.sol` lines 141–148, 161–170
1619
1674
 
1620
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.
1621
1676
 
@@ -1623,15 +1678,29 @@ Appends an immutable recipe and returns its id, the next index. The canonical re
1623
1678
 
1624
1679
  **Errors:** [`OwnableUnauthorizedAccount`](#registry-error-ownableunauthorizedaccount), [`InvalidRecipe`](#registry-error-invalidrecipe), [`InvalidTemplate`](#registry-error-invalidtemplate).
1625
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
+
1626
1695
  #### <a id="registry-fn-schedulecatalog"></a>`scheduleCatalog`
1627
1696
 
1628
1697
  ```solidity
1629
1698
  function scheduleCatalog(uint8[] recipes, address[] signers, uint64 fromEpoch) external
1630
1699
  ```
1631
1700
 
1632
- Selector `0x42984450` · Caller: Owner · Source: `EpochEntropy.sol` lines 190–211
1701
+ Selector `0x42984450` · Caller: Owner · Source: `EpochEntropy.sol` lines 268–294
1633
1702
 
1634
- Schedules a catalog for epochs from `fromEpoch`, which must be at least two epochs after the current one: 1 to `MAX_SOURCES` (10) distinct registered recipe ids with one non-zero signer each, in slot order. Its hash is `keccak256(abi.encode(RECIPE_DOMAIN, recipes, signers))`. If the latest scheduled version has not taken effect yet (its `fromEpoch` is after the current epoch) it is replaced, so that version never applies; this can return the next epoch to the previous catalog. The current epoch keeps its catalog.
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.
1635
1704
 
1636
1705
  **Emits:** [`CatalogScheduled`](#registry-event-catalogscheduled).
1637
1706
 
@@ -1643,7 +1712,7 @@ Schedules a catalog for epochs from `fromEpoch`, which must be at least two epoc
1643
1712
  function initializeRecipeRegistry() external
1644
1713
  ```
1645
1714
 
1646
- Selector `0x8700b456` · Caller: Owner, once per proxy, as the `upgradeToAndCall` data of the recipe-registry upgrade · Source: `EpochEntropy.sol` lines 87–95
1715
+ Selector `0x8700b456` · Caller: Owner, once per proxy, as the `upgradeToAndCall` data of the recipe-registry upgrade · Source: `EpochEntropy.sol` lines 105–113
1647
1716
 
1648
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.
1649
1718
 
@@ -1661,7 +1730,7 @@ Owner-only functions revert `OwnableUnauthorizedAccount` for anyone else. No set
1661
1730
  function setCommitter(address next) external
1662
1731
  ```
1663
1732
 
1664
- Selector `0xdd51ce22` · Caller: Owner · Source: `EpochEntropy.sol` lines 100–103
1733
+ Selector `0xdd51ce22` · Caller: Owner · Source: `EpochEntropy.sol` lines 118–121
1665
1734
 
1666
1735
  Changes the primary publishing address, which is also the keeper-share recipient the coordinator reads at each acceptance.
1667
1736
 
@@ -1675,7 +1744,7 @@ Changes the primary publishing address, which is also the keeper-share recipient
1675
1744
  function setBackupCommitter(address account, bool allowed) external
1676
1745
  ```
1677
1746
 
1678
- Selector `0xd870d0c6` · Caller: Owner · Source: `EpochEntropy.sol` lines 104–117
1747
+ Selector `0xd870d0c6` · Caller: Owner · Source: `EpochEntropy.sol` lines 122–135
1679
1748
 
1680
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).
1681
1750
 
@@ -1737,7 +1806,7 @@ Completes the transfer to the caller and clears the nomination.
1737
1806
  function renounceOwnership() external view
1738
1807
  ```
1739
1808
 
1740
- Selector `0x715018a6` · Caller: Owner · Source: `EpochEntropy.sol` lines 97–98
1809
+ Selector `0x715018a6` · Caller: Owner · Source: `EpochEntropy.sol` lines 115–116
1741
1810
 
1742
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.
1743
1812
 
@@ -1775,7 +1844,7 @@ ERC-1822 check used by `upgradeToAndCall`. Returns the ERC-1967 implementation s
1775
1844
  function initialize(address[4] signers, address initialOwner, address initialCommitter) external
1776
1845
  ```
1777
1846
 
1778
- Selector `0xfda9f5ca` · Caller: Once, by `D20Proxy` at deployment · Source: `EpochEntropy.sol` lines 78–86
1847
+ Selector `0xfda9f5ca` · Caller: Once, by `D20Proxy` at deployment · Source: `EpochEntropy.sol` lines 96–104
1779
1848
 
1780
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.
1781
1850
 
@@ -1803,6 +1872,8 @@ Views returning values fixed in the implementation code.
1803
1872
  | <a id="registry-fn-recipe_domain"></a>`RECIPE_DOMAIN` | `bytes32` | `keccak256("D20_EPOCH_RECIPES")` | `0xacea73c8` | Domain tag of catalog hashes. |
1804
1873
  | <a id="registry-fn-select_domain"></a>`SELECT_DOMAIN` | `bytes32` | `keccak256("D20_EPOCH_SELECT")` | `0x10181587` | Domain tag of the source selector. |
1805
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`. |
1806
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. |
1807
1878
 
1808
1879
  ### <a id="registry-events"></a>Events
@@ -1815,9 +1886,9 @@ Views returning values fixed in the implementation code.
1815
1886
  event EpochCommitted(uint64 indexed epochId, bytes32 indexed epochHash, bytes packet)
1816
1887
  ```
1817
1888
 
1818
- Topic 0 `0xc9db8d1389570196eda0f2c6c4e2f78429c2f812db30b6b1022b5e0b5162ef72` · Emitted by: [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback) · Source: `EpochEntropy.sol` lines 71, 297–299
1889
+ Topic 0 `0xc9db8d1389570196eda0f2c6c4e2f78429c2f812db30b6b1022b5e0b5162ef72` · Emitted by: [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback) · Source: `EpochEntropy.sol` lines 88, 388–390
1819
1890
 
1820
- An epoch was published. `packet` is `abi.encode(canonicalRequest, attestation)`: decode it with `decodeEpochEvidencePacket` and verify with `replayEpochCommitment`. Requests of the epoch now have a target block.
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.
1821
1892
 
1822
1893
  #### <a id="registry-event-reciperegistered"></a>`RecipeRegistered`
1823
1894
 
@@ -1825,9 +1896,19 @@ An epoch was published. `packet` is `abi.encode(canonicalRequest, attestation)`:
1825
1896
  event RecipeRegistered(uint8 indexed recipe, bytes32 indexed queryHash, string canonicalRequest, bytes template, string body)
1826
1897
  ```
1827
1898
 
1828
- Topic 0 `0xbc5c1e3f4647d7f37dc8b4fe5e26e7c58ae35b8c9fb1db0233ddedba0d7f5fd8` · Emitted by: [`registerRecipe`](#registry-fn-registerrecipe), [`initializeRecipeRegistry`](#registry-fn-initializereciperegistry), [`initialize`](#registry-fn-initialize) · Source: `EpochEntropy.sol` lines 74, 151
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
1829
1900
 
1830
- Recipe `recipe` was registered. The event carries the complete definition, so every recipe can be rebuilt from logs; `getRecipe` returns the same values.
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
1910
+
1911
+ Recipe `recipe` is a beacon recipe: emitted right after its `RecipeRegistered` with the registration `beaconOf` returns (`verifier`, `chainHash`, `publicKey`, `genesis`, `period`).
1831
1912
 
1832
1913
  #### <a id="registry-event-catalogscheduled"></a>`CatalogScheduled`
1833
1914
 
@@ -1835,9 +1916,9 @@ Recipe `recipe` was registered. The event carries the complete definition, so ev
1835
1916
  event CatalogScheduled(uint64 indexed fromEpoch, bytes32 indexed catalogHash, uint8[] recipes, address[] signers)
1836
1917
  ```
1837
1918
 
1838
- Topic 0 `0xc91bcd562b1edaabdb2772ada76957511610c715ef892b9c9af1f35be93c3c4f` · Emitted by: [`scheduleCatalog`](#registry-fn-schedulecatalog) · Source: `EpochEntropy.sol` lines 73, 210
1919
+ Topic 0 `0xc91bcd562b1edaabdb2772ada76957511610c715ef892b9c9af1f35be93c3c4f` · Emitted by: [`scheduleCatalog`](#registry-fn-schedulecatalog) · Source: `EpochEntropy.sol` lines 90, 293
1839
1920
 
1840
- A catalog was scheduled for epochs from `fromEpoch`: recipe ids and signers in slot order. A later `CatalogScheduled` emitted while this version has not taken effect replaces it, so when rebuilding catalogs from history drop replaced versions, or read `catalogAt(epochId)`.
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)`.
1841
1922
 
1842
1923
  **Administration and upgrades**
1843
1924
 
@@ -1847,7 +1928,7 @@ A catalog was scheduled for epochs from `fromEpoch`: recipe ids and signers in s
1847
1928
  event CommitterChanged(address indexed previousCommitter, address indexed newCommitter)
1848
1929
  ```
1849
1930
 
1850
- Topic 0 `0x3f67cc70f736070aaac75db90cef1ab4047521b73e8a38d02852e8bf1a91e7e0` · Emitted by: [`setCommitter`](#registry-fn-setcommitter) · Source: `EpochEntropy.sol` lines 72, 102
1931
+ Topic 0 `0x3f67cc70f736070aaac75db90cef1ab4047521b73e8a38d02852e8bf1a91e7e0` · Emitted by: [`setCommitter`](#registry-fn-setcommitter) · Source: `EpochEntropy.sol` lines 89, 120
1851
1932
 
1852
1933
  New primary publishing address; it also receives the keeper share of proofs submitted by wallets the registry does not authorize.
1853
1934
 
@@ -1857,7 +1938,7 @@ New primary publishing address; it also receives the keeper share of proofs subm
1857
1938
  event BackupCommitterSet(address indexed account, bool allowed)
1858
1939
  ```
1859
1940
 
1860
- Topic 0 `0x20380b8c17d904db7d905a51f1538057d280a6cecca38882832ba0261b39fa66` · Emitted by: [`setBackupCommitter`](#registry-fn-setbackupcommitter) · Source: `EpochEntropy.sol` lines 75, 116
1941
+ Topic 0 `0x20380b8c17d904db7d905a51f1538057d280a6cecca38882832ba0261b39fa66` · Emitted by: [`setBackupCommitter`](#registry-fn-setbackupcommitter) · Source: `EpochEntropy.sol` lines 92, 134
1861
1942
 
1862
1943
  `account` may now publish epochs (`allowed` true) or no longer may (`allowed` false).
1863
1944
 
@@ -1907,7 +1988,7 @@ Topic 0 `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` ·
1907
1988
 
1908
1989
  #### <a id="registry-error-invalidepoch"></a>`InvalidEpoch`
1909
1990
 
1910
- `error InvalidEpoch()` · Selector `0xd5b25b63` · Source: `EpochEntropy.sol` lines 66, 204, 232
1991
+ `error InvalidEpoch()` · Selector `0xd5b25b63` · Source: `EpochEntropy.sol` lines 83, 284, 315
1911
1992
 
1912
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).
1913
1994
 
@@ -1917,7 +1998,7 @@ Epoch 0 was passed to `epochStart`, `fallbackOpensAt`, a selection view, `checkp
1917
1998
 
1918
1999
  #### <a id="registry-error-preparationclosed"></a>`PreparationClosed`
1919
2000
 
1920
- `error PreparationClosed()` · Selector `0x8e2a3c7d` · Source: `EpochEntropy.sol` lines 66, 247
2001
+ `error PreparationClosed()` · Selector `0x8e2a3c7d` · Source: `EpochEntropy.sol` lines 83, 330
1921
2002
 
1922
2003
  **Raised by:** [`getEpochSelection`](#registry-fn-getepochselection), [`getEpochFallbackSelection`](#registry-fn-getepochfallbackselection), [`checkpointEpoch`](#registry-fn-checkpointepoch).
1923
2004
 
@@ -1927,7 +2008,7 @@ The epoch has not started (`block.number` is below `epochStart(epochId)`), so it
1927
2008
 
1928
2009
  #### <a id="registry-error-anchorunavailable"></a>`AnchorUnavailable`
1929
2010
 
1930
- `error AnchorUnavailable()` · Selector `0x60776ed3` · Source: `EpochEntropy.sol` lines 66, 250
2011
+ `error AnchorUnavailable()` · Selector `0x60776ed3` · Source: `EpochEntropy.sol` lines 83, 333
1931
2012
 
1932
2013
  **Raised by:** [`getEpochSelection`](#registry-fn-getepochselection), [`getEpochFallbackSelection`](#registry-fn-getepochfallbackselection), [`checkpointEpoch`](#registry-fn-checkpointepoch), [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback).
1933
2014
 
@@ -1939,7 +2020,7 @@ The anchor (hash of block `epochStart - 1`) was never checkpointed and is outsid
1939
2020
 
1940
2021
  #### <a id="registry-error-onlycommitter"></a>`OnlyCommitter`
1941
2022
 
1942
- `error OnlyCommitter()` · Selector `0xfffe5af3` · Source: `EpochEntropy.sol` lines 67, 281
2023
+ `error OnlyCommitter()` · Selector `0xfffe5af3` · Source: `EpochEntropy.sol` lines 84, 364
1943
2024
 
1944
2025
  **Raised by:** [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback).
1945
2026
 
@@ -1949,7 +2030,7 @@ A commit from an address that is neither `committer()` nor an allowed backup com
1949
2030
 
1950
2031
  #### <a id="registry-error-alreadycommitted"></a>`AlreadyCommitted`
1951
2032
 
1952
- `error AlreadyCommitted()` · Selector `0xbfec5558` · Source: `EpochEntropy.sol` lines 67, 282
2033
+ `error AlreadyCommitted()` · Selector `0xbfec5558` · Source: `EpochEntropy.sol` lines 84, 365
1953
2034
 
1954
2035
  **Raised by:** [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback).
1955
2036
 
@@ -1959,7 +2040,7 @@ The epoch already has a published packet.
1959
2040
 
1960
2041
  #### <a id="registry-error-fallbacknotopen"></a>`FallbackNotOpen`
1961
2042
 
1962
- `error FallbackNotOpen()` · Selector `0xf8635228` · Source: `EpochEntropy.sol` lines 69, 283
2043
+ `error FallbackNotOpen()` · Selector `0xf8635228` · Source: `EpochEntropy.sol` lines 86, 366
1963
2044
 
1964
2045
  **Raised by:** [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback).
1965
2046
 
@@ -1969,7 +2050,7 @@ The epoch already has a published packet.
1969
2050
 
1970
2051
  #### <a id="registry-error-invalidfallback"></a>`InvalidFallback`
1971
2052
 
1972
- `error InvalidFallback()` · Selector `0x5a93724d` · Source: `EpochEntropy.sol` lines 69, 258, 266, 277
2053
+ `error InvalidFallback()` · Selector `0x5a93724d` · Source: `EpochEntropy.sol` lines 86, 341, 349, 360
1973
2054
 
1974
2055
  **Raised by:** [`getEpochFallbackSelection`](#registry-fn-getepochfallbackselection), [`fallbackOpensAt`](#registry-fn-fallbackopensat), [`commitEpochFallback`](#registry-fn-commitepochfallback).
1975
2056
 
@@ -1979,17 +2060,17 @@ An attempt at or above `sourceCountAt(epochId)`, or attempt 0 passed to `commitE
1979
2060
 
1980
2061
  #### <a id="registry-error-invalidtime"></a>`InvalidTime`
1981
2062
 
1982
- `error InvalidTime()` · Selector `0x6f7eac26` · Source: `EpochEntropy.sol` lines 67, 285
2063
+ `error InvalidTime()` · Selector `0x6f7eac26` · Source: `EpochEntropy.sol` lines 84, 368, 378
1983
2064
 
1984
2065
  **Raised by:** [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback).
1985
2066
 
1986
- 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.
1987
2068
 
1988
2069
  **What to do:** A saved packet is never refreshed; its requests expire and are refunded.
1989
2070
 
1990
2071
  #### <a id="registry-error-invaliddata"></a>`InvalidData`
1991
2072
 
1992
- `error InvalidData()` · Selector `0x5cb045db` · Source: `EpochEntropy.sol` lines 67, 286–287
2073
+ `error InvalidData()` · Selector `0x5cb045db` · Source: `EpochEntropy.sol` lines 84, 369–370
1993
2074
 
1994
2075
  **Raised by:** [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback).
1995
2076
 
@@ -1999,13 +2080,23 @@ The signed data does not match the data template of the slot's recipe exactly, w
1999
2080
 
2000
2081
  #### <a id="registry-error-invalidsigner"></a>`InvalidSigner`
2001
2082
 
2002
- `error InvalidSigner()` · Selector `0x815e1d64` · Source: `EpochEntropy.sol` lines 67, 289
2083
+ `error InvalidSigner()` · Selector `0x815e1d64` · Source: `EpochEntropy.sol` lines 84, 374, 379
2003
2084
 
2004
2085
  **Raised by:** [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback).
2005
2086
 
2006
- 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
2007
2094
 
2008
- **What to do:** Use `catalogAt(epochId)` for the expected signer.
2095
+ **Raised by:** [`verifyBeacon`](#registry-fn-verifybeacon), [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback), [`registerBeacon`](#registry-fn-registerbeacon).
2096
+
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.
2009
2100
 
2010
2101
  #### <a id="registry-error-ecdsainvalidsignature"></a>`ECDSAInvalidSignature`
2011
2102
 
@@ -2039,7 +2130,7 @@ The signature has a high `s` value (OpenZeppelin `ECDSA`).
2039
2130
 
2040
2131
  #### <a id="registry-error-packettoolarge"></a>`PacketTooLarge`
2041
2132
 
2042
- `error PacketTooLarge()` · Selector `0xda85e8a5` · Source: `EpochEntropy.sol` lines 68, 298
2133
+ `error PacketTooLarge()` · Selector `0xda85e8a5` · Source: `EpochEntropy.sol` lines 85, 389
2043
2134
 
2044
2135
  **Raised by:** [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback).
2045
2136
 
@@ -2051,9 +2142,9 @@ The encoded packet exceeds `MAX_PACKET_BYTES` (2048).
2051
2142
 
2052
2143
  #### <a id="registry-error-invalidrecipe"></a>`InvalidRecipe`
2053
2144
 
2054
- `error InvalidRecipe()` · Selector `0x7b776f4c` · Source: `EpochEntropy.sol` lines 70, 147
2145
+ `error InvalidRecipe()` · Selector `0x7b776f4c` · Source: `EpochEntropy.sol` lines 87, 165
2055
2146
 
2056
- **Raised by:** [`registerRecipe`](#registry-fn-registerrecipe).
2147
+ **Raised by:** [`registerRecipe`](#registry-fn-registerrecipe), [`registerBeacon`](#registry-fn-registerbeacon).
2057
2148
 
2058
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.
2059
2150
 
@@ -2061,7 +2152,7 @@ The encoded packet exceeds `MAX_PACKET_BYTES` (2048).
2061
2152
 
2062
2153
  #### <a id="registry-error-invalidtemplate"></a>`InvalidTemplate`
2063
2154
 
2064
- `error InvalidTemplate()` · Selector `0xec55b8cd` · Source: `EpochEntropy.sol` lines 70, 148
2155
+ `error InvalidTemplate()` · Selector `0xec55b8cd` · Source: `EpochEntropy.sol` lines 87, 166
2065
2156
 
2066
2157
  **Raised by:** [`registerRecipe`](#registry-fn-registerrecipe).
2067
2158
 
@@ -2069,13 +2160,23 @@ The encoded packet exceeds `MAX_PACKET_BYTES` (2048).
2069
2160
 
2070
2161
  **What to do:** Build the template with `encodeDataTemplate`, which names the broken rule, before registering.
2071
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.
2172
+
2072
2173
  #### <a id="registry-error-invalidconfig"></a>`InvalidConfig`
2073
2174
 
2074
- `error InvalidConfig()` · Selector `0x35be3ac8` · Source: `EpochEntropy.sol` lines 66, 81, 93, 101, 108, 110, 140, 196, 200
2175
+ `error InvalidConfig()` · Selector `0x35be3ac8` · Source: `EpochEntropy.sol` lines 83, 99, 111, 119, 126, 128, 158, 180–182, 275, 279, 280
2075
2176
 
2076
- **Raised by:** [`getRecipe`](#registry-fn-getrecipe), [`recipeRequest`](#registry-fn-reciperequest), [`getEpochSelection`](#registry-fn-getepochselection), [`getEpochFallbackSelection`](#registry-fn-getepochfallbackselection), [`commitEpoch`](#registry-fn-commitepoch), [`commitEpochFallback`](#registry-fn-commitepochfallback), [`scheduleCatalog`](#registry-fn-schedulecatalog), [`initializeRecipeRegistry`](#registry-fn-initializereciperegistry), [`setCommitter`](#registry-fn-setcommitter), [`setBackupCommitter`](#registry-fn-setbackupcommitter), [`initialize`](#registry-fn-initialize).
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).
2077
2178
 
2078
- A zero signer or committer in `initialize`; a zero address in `setCommitter`; in `setBackupCommitter` the zero address, allowing the committer, an unchanged status or a fifth backup committer; in `scheduleCatalog` an empty or oversized catalog, mismatched lengths, a repeated or unregistered recipe or a zero signer; `initializeRecipeRegistry` on a registry that already has recipes or a scheduled catalog; or a recipe id that is not registered, in `getRecipe` and `recipeRequest` or, on a registry upgraded without `initializeRecipeRegistry`, in selection and publication.
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.
2079
2180
 
2080
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.
2081
2182
 
@@ -2083,7 +2184,7 @@ A zero signer or committer in `initialize`; a zero address in `setCommitter`; in
2083
2184
 
2084
2185
  `error OwnableUnauthorizedAccount(address account)` · Selector `0x118cdaa7`
2085
2186
 
2086
- **Raised by:** [`registerRecipe`](#registry-fn-registerrecipe), [`scheduleCatalog`](#registry-fn-schedulecatalog), [`initializeRecipeRegistry`](#registry-fn-initializereciperegistry), [`setCommitter`](#registry-fn-setcommitter), [`setBackupCommitter`](#registry-fn-setbackupcommitter), [`transferOwnership`](#registry-fn-transferownership), [`acceptOwnership`](#registry-fn-acceptownership), [`renounceOwnership`](#registry-fn-renounceownership), [`upgradeToAndCall`](#registry-fn-upgradetoandcall).
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).
2087
2188
 
2088
2189
  `account` is not the owner (owner-only functions) or not the pending owner (`acceptOwnership`).
2089
2190
 
@@ -2101,7 +2202,7 @@ A zero signer or committer in `initialize`; a zero address in `setCommitter`; in
2101
2202
 
2102
2203
  #### <a id="registry-error-renouncedisabled"></a>`RenounceDisabled`
2103
2204
 
2104
- `error RenounceDisabled()` · Selector `0x89051165` · Source: `EpochEntropy.sol` lines 68, 98
2205
+ `error RenounceDisabled()` · Selector `0x89051165` · Source: `EpochEntropy.sol` lines 85, 116
2105
2206
 
2106
2207
  **Raised by:** [`renounceOwnership`](#registry-fn-renounceownership).
2107
2208
 
@@ -2188,3 +2289,120 @@ Declared by OpenZeppelin `Address` for the delegatecall in `upgradeToAndCall`. N
2188
2289
  The initialization call made by `upgradeToAndCall` reverted without revert data.
2189
2290
 
2190
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.