@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.
@@ -2,32 +2,38 @@
2
2
  "compiler": "0.8.28+commit.7893614a.Emscripten.clang",
3
3
  "protocol": {
4
4
  "sourceRepository": "https://github.com/d20dao/keeper",
5
- "sourceCommit": "de5f82eb9fc749c80e83270f57cde9908ddcf1f3",
6
- "note": "Current public d20dao protocol copied byte-for-byte from committed Git blobs. Operator backends, keys, test fixtures and proof generation are excluded from the package.",
5
+ "sourceCommit": "98e537fb249dd0d3365b8d78e6040a9323a65a88",
6
+ "note": "Protocol copied byte-for-byte from the committed Git blobs of the keeper source at sourceCommit, which is ahead of the latest release of the public repository named in sourceRepository. Operator backends, keys, test fixtures and proof generation are excluded from the package.",
7
7
  "files": {
8
8
  "LICENSE": "bfdc94eef4cfbec12a9ed47bb406c8125cf4c306facf7b8d92db58f133b1a703",
9
9
  "contracts/D20VRFConsumer.sol": "9200fcd8825756ba6978e42d94b52e82e6dea9b1ba35dc506652490194e16162",
10
- "contracts/D20VRFCoordinator.sol": "6d5dc7713d0313f8496eae1dddd6fc5626dc5b8a082fc42da6a87ce2b5d4de4f",
10
+ "contracts/D20VRFCoordinator.sol": "fa3ea7d7995c5dd8f6a9cd50140f0cb8d35e441a6ecda513f6e82fbbacea9eaf",
11
11
  "contracts/interfaces/ID20VRF.sol": "0361aee07644cbf7c2bcde8ffd9188825d5be633c4a1520169a7702a38c1bdbc",
12
12
  "contracts/libraries/D20VRFRequests.sol": "f87e66f7eb0350d9b8740f120b97085957793b826b81a0c7c80d58a3fcd92b76",
13
13
  "contracts/libraries/RandomnessMapping.sol": "00bce68c1a6d89903d72d1c3f9ee5e1a56392188ed29b6aa32ab7451ef180c91",
14
14
  "contracts/vendor/CHAINLINK-LICENSE": "25a502f8fd39602de8a98cc4835a4bf81851b095d6f2c5c130bf0863afac6c99",
15
- "contracts/vendor/PROVENANCE.md": "e6da392378851c47574ef808c9c7e17dea50054ffd0cae7a9070f76f8eed60f2",
15
+ "contracts/vendor/PROVENANCE.md": "56d0e087c3e3fffca95aa8a371979c32239c97a066ca02bb36ae2f0d2af46ffe",
16
16
  "contracts/vendor/VRF.sol": "98e0345fd2dd42178cad4ad16dd8b18621112078d63d0c64839c8bd01c539350",
17
17
  "src/evidence.ts": "81b6a1446b9700ec545e794b08d356411608aa655e6030cdfabd1dfa531ef457",
18
- "src/index.ts": "0a791dc1d12d8f02e503f4990b057f89838f16f93bbdafc4fb3effa6917daf80",
18
+ "src/index.ts": "a120030d296f518925159422873b796614ab83aef53fbe2acd89ea517d10f7d9",
19
19
  "src/mapping.ts": "76aa6093d55831052b56e177fc394ef6ef14183a8ab8ca6fcc2c9382efb6b83f",
20
20
  "src/replay.ts": "c97a5081e785e6a7f27987e2d4c4d9861fe8fa8cbc775b0152e337141d3d48d7",
21
- "src/sources.ts": "8e8b50adf76f4b17d3936ffa4bb4d13e3891f6c7c40483cc9854fc28d0e88a1d",
21
+ "src/sources.ts": "93dad72dd9b5e54a7781b642a0715317bdd8d23d107d53ce64a7e62ea960bdde",
22
22
  "src/verification.ts": "754e39554c0c432cf1404741503f66b4199b84f39003d8e42f300a7256ffaa6a",
23
- "src/epoch.ts": "91bb0d818315dfb5841e1a0450c4b8839d6992ae7d573b33bda1f8797f071aa0",
24
- "contracts/EpochEntropy.sol": "ece8798c2a608d20e1863debaab993a108266bd10b2f45bd762fab9f1db8243f",
23
+ "src/epoch.ts": "226f0c12f138fa4b45c0d073a5aceac4344882ecbdf9593053535ecd4e9a772a",
24
+ "contracts/EpochEntropy.sol": "100785392042af99e710af2e13855fd44d9c1018e33220d2601f75a3cb72f441",
25
25
  "contracts/D20Proxy.sol": "83dcef3b72e2a0a4809c2d8df0d083d2a0f659fe2b3b01fadb1b2eaf8afb7286",
26
26
  "contracts/libraries/DataTemplate.sol": "f0d93c2ec3bcb8cfb625001506fe24f938e6e2cfbae85632f2980d23595ed961",
27
- "src/templates.ts": "7f74968e746461e168a863769736399d74bdab29041ab8dd1cdc0d197b927e61"
27
+ "src/templates.ts": "7f74968e746461e168a863769736399d74bdab29041ab8dd1cdc0d197b927e61",
28
+ "contracts/D20BeaconVerifier.sol": "f2eb0917c2d996f4c3e4969d37e32366163532605cb9ae8e7e97e86935c7b068",
29
+ "contracts/interfaces/IBeaconVerifier.sol": "75782d629af93a368e216272cd1ab25191907ab65e79510294e8679a0f7c6f74",
30
+ "contracts/vendor/bls-bn254/BLS.sol": "887fd94553b9d6d0a247900bdb05523d3b9ef3b8ca049e2a0524a0b1a9d37e69",
31
+ "contracts/vendor/bls-bn254/ModExp.sol": "fd91ae9291511668914b05fece03fb9bf6e2b57f7784d2edddc82456af12b495",
32
+ "contracts/vendor/bls-bn254/LICENSE": "af36460fa628a7aca8c5b1f1b6b3615376f00212284fa0fd61a013ee982a3666",
33
+ "src/beacon.ts": "ab94d0702d7174405be085ac4ed4139a71c9d62929e05b93dc3f9e09f6d0a776"
28
34
  }
29
35
  },
30
- "packageLockSha256": "39cd5746f13f589dd9145f304d2ad68c64a25fcd1a746baa7f94d52fe41238c2",
36
+ "packageLockSha256": "8bd5bd3d10790ea1466c5e4851d92330ea54034ef0c65622fd8ae4f3280fce06",
31
37
  "buildDependencies": {
32
38
  "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol": "41a3040398d53999dea3251ff8906e11ec1a699362a1d8f4a55bfc7709cc00f3",
33
39
  "@openzeppelin/contracts/utils/ReentrancyGuard.sol": "94e409e8f6e3184236651a6cc2b1a6a3ea0f0a25eb85b71e524ad5791bb2fbc8",
@@ -35,46 +41,52 @@
35
41
  "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol": "b5af3bfd79a32c6da2d09b0be38d9b63dcd0d159a1a4a0198431fd00acc1fd52",
36
42
  "@openzeppelin/contracts/utils/cryptography/ECDSA.sol": "ba7c2d314fcd61c9783e0b3f0c664d004b76b408fda6a84edc8267d17c5012d6",
37
43
  "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol": "538a0d7c4f0ec5a8562f58561890f7b49d14580674ecf58bc5f505b45c73df65",
44
+ "@openzeppelin/contracts/utils/Strings.sol": "e318709bbe73a7831d64c1351e8bdc43780b245f25191234e361e6ab9b98c67b",
38
45
  "@openzeppelin/contracts/proxy/Proxy.sol": "fa8aae37b2939371fcbce48b814c0d5c7e57e3e267f348459f29941937746c98",
39
46
  "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol": "a89ba2032ab25959c6fde1e508ebf8f966ff632703d3ef79684f578a8fa8033f",
40
47
  "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol": "d20bb6bb32dd1fdb895914012ad08dc1e4bbd66df2e75a02c1ca97d884df70ed",
41
48
  "@openzeppelin/contracts/proxy/utils/Initializable.sol": "f527a063813c2bf60c153fb08e38539578935402894fcc36fac42324ca325d3b",
42
49
  "@openzeppelin/contracts/interfaces/draft-IERC1822.sol": "73653312bc2eda0ec231553a5295242eaef2d4f46a023532d45fe76cbd0f565f",
43
50
  "@openzeppelin/contracts/utils/StorageSlot.sol": "75704538dcb223239280c6726d9a31cf769a7816718517c997fc7d63bdb70778",
44
- "@openzeppelin/contracts/utils/Strings.sol": "e318709bbe73a7831d64c1351e8bdc43780b245f25191234e361e6ab9b98c67b",
45
- "@openzeppelin/contracts/proxy/beacon/IBeacon.sol": "c1c9726bbb0ec4c540c7c98059dcded69a8586b46c168036672381033ee76239",
46
- "@openzeppelin/contracts/interfaces/IERC1967.sol": "c4e901318ab6d4963582c62c62ce988a34c68dbae71846b67decacde79e9a317",
47
- "@openzeppelin/contracts/utils/Address.sol": "73e54e15285455f0e01e136c3663732641537614723ae5dbd820eb3cb036b1fd",
48
- "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol": "caf06b4c93bbc1d54eb772e76e949bd4a2520c535cc591d07e57cbafaf90b2fd",
49
51
  "@openzeppelin/contracts/utils/math/Math.sol": "bdfdbe133991c0c78042957ff5cd97167926fcaf6d15664f3835a076cb066457",
50
52
  "@openzeppelin/contracts/utils/math/SafeCast.sol": "5779bc848bde39f1ad7bc02b4f708a0040888e0083b1a33f119cd94639350134",
51
53
  "@openzeppelin/contracts/utils/math/SignedMath.sol": "1ed50b1056af886752f0fb48a0165d381e69bb4a4b18b893b066dc144a7e08d7",
52
54
  "@openzeppelin/contracts/utils/Bytes.sol": "3ce9120a27934c75a074cb785d3a88868069ef6529dbe4dc6b845af0836f3c70",
55
+ "@openzeppelin/contracts/proxy/beacon/IBeacon.sol": "c1c9726bbb0ec4c540c7c98059dcded69a8586b46c168036672381033ee76239",
56
+ "@openzeppelin/contracts/interfaces/IERC1967.sol": "c4e901318ab6d4963582c62c62ce988a34c68dbae71846b67decacde79e9a317",
57
+ "@openzeppelin/contracts/utils/Address.sol": "73e54e15285455f0e01e136c3663732641537614723ae5dbd820eb3cb036b1fd",
58
+ "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol": "caf06b4c93bbc1d54eb772e76e949bd4a2520c535cc591d07e57cbafaf90b2fd",
59
+ "@openzeppelin/contracts/utils/Panic.sol": "270fc8401c1a13fae6a7a4a2dd6e381b95d658896701e51f0d3e2688acab3dec",
53
60
  "@openzeppelin/contracts/utils/Errors.sol": "0704b9d6c032cca8512a3bc3f30f49f86f1f03102d2896a3d23e794b82efea66",
54
- "@openzeppelin/contracts/utils/LowLevelCall.sol": "e128cbe9c6c406d5a42c26e4079c0a95b369ce552f2d0c3dfd2fcb836c5708f2",
55
- "@openzeppelin/contracts/utils/Panic.sol": "270fc8401c1a13fae6a7a4a2dd6e381b95d658896701e51f0d3e2688acab3dec"
61
+ "@openzeppelin/contracts/utils/LowLevelCall.sol": "e128cbe9c6c406d5a42c26e4079c0a95b369ce552f2d0c3dfd2fcb836c5708f2"
56
62
  },
57
63
  "sources": {
58
64
  "LICENSE": "bfdc94eef4cfbec12a9ed47bb406c8125cf4c306facf7b8d92db58f133b1a703",
59
65
  "contracts/D20VRFConsumer.sol": "9200fcd8825756ba6978e42d94b52e82e6dea9b1ba35dc506652490194e16162",
60
- "contracts/D20VRFCoordinator.sol": "6d5dc7713d0313f8496eae1dddd6fc5626dc5b8a082fc42da6a87ce2b5d4de4f",
66
+ "contracts/D20VRFCoordinator.sol": "fa3ea7d7995c5dd8f6a9cd50140f0cb8d35e441a6ecda513f6e82fbbacea9eaf",
61
67
  "contracts/interfaces/ID20VRF.sol": "0361aee07644cbf7c2bcde8ffd9188825d5be633c4a1520169a7702a38c1bdbc",
62
68
  "contracts/libraries/D20VRFRequests.sol": "f87e66f7eb0350d9b8740f120b97085957793b826b81a0c7c80d58a3fcd92b76",
63
69
  "contracts/libraries/RandomnessMapping.sol": "00bce68c1a6d89903d72d1c3f9ee5e1a56392188ed29b6aa32ab7451ef180c91",
64
70
  "contracts/vendor/CHAINLINK-LICENSE": "25a502f8fd39602de8a98cc4835a4bf81851b095d6f2c5c130bf0863afac6c99",
65
- "contracts/vendor/PROVENANCE.md": "e6da392378851c47574ef808c9c7e17dea50054ffd0cae7a9070f76f8eed60f2",
71
+ "contracts/vendor/PROVENANCE.md": "56d0e087c3e3fffca95aa8a371979c32239c97a066ca02bb36ae2f0d2af46ffe",
66
72
  "contracts/vendor/VRF.sol": "98e0345fd2dd42178cad4ad16dd8b18621112078d63d0c64839c8bd01c539350",
67
73
  "src/evidence.ts": "81b6a1446b9700ec545e794b08d356411608aa655e6030cdfabd1dfa531ef457",
68
- "src/index.ts": "0a791dc1d12d8f02e503f4990b057f89838f16f93bbdafc4fb3effa6917daf80",
74
+ "src/index.ts": "a120030d296f518925159422873b796614ab83aef53fbe2acd89ea517d10f7d9",
69
75
  "src/mapping.ts": "76aa6093d55831052b56e177fc394ef6ef14183a8ab8ca6fcc2c9382efb6b83f",
70
76
  "src/replay.ts": "c97a5081e785e6a7f27987e2d4c4d9861fe8fa8cbc775b0152e337141d3d48d7",
71
- "src/sources.ts": "8e8b50adf76f4b17d3936ffa4bb4d13e3891f6c7c40483cc9854fc28d0e88a1d",
77
+ "src/sources.ts": "93dad72dd9b5e54a7781b642a0715317bdd8d23d107d53ce64a7e62ea960bdde",
72
78
  "src/verification.ts": "754e39554c0c432cf1404741503f66b4199b84f39003d8e42f300a7256ffaa6a",
73
- "src/epoch.ts": "91bb0d818315dfb5841e1a0450c4b8839d6992ae7d573b33bda1f8797f071aa0",
74
- "contracts/EpochEntropy.sol": "ece8798c2a608d20e1863debaab993a108266bd10b2f45bd762fab9f1db8243f",
79
+ "src/epoch.ts": "226f0c12f138fa4b45c0d073a5aceac4344882ecbdf9593053535ecd4e9a772a",
80
+ "contracts/EpochEntropy.sol": "100785392042af99e710af2e13855fd44d9c1018e33220d2601f75a3cb72f441",
75
81
  "contracts/D20Proxy.sol": "83dcef3b72e2a0a4809c2d8df0d083d2a0f659fe2b3b01fadb1b2eaf8afb7286",
76
82
  "contracts/libraries/DataTemplate.sol": "f0d93c2ec3bcb8cfb625001506fe24f938e6e2cfbae85632f2980d23595ed961",
77
- "src/templates.ts": "7f74968e746461e168a863769736399d74bdab29041ab8dd1cdc0d197b927e61"
83
+ "src/templates.ts": "7f74968e746461e168a863769736399d74bdab29041ab8dd1cdc0d197b927e61",
84
+ "contracts/D20BeaconVerifier.sol": "f2eb0917c2d996f4c3e4969d37e32366163532605cb9ae8e7e97e86935c7b068",
85
+ "contracts/interfaces/IBeaconVerifier.sol": "75782d629af93a368e216272cd1ab25191907ab65e79510294e8679a0f7c6f74",
86
+ "contracts/vendor/bls-bn254/BLS.sol": "887fd94553b9d6d0a247900bdb05523d3b9ef3b8ca049e2a0524a0b1a9d37e69",
87
+ "contracts/vendor/bls-bn254/ModExp.sol": "fd91ae9291511668914b05fece03fb9bf6e2b57f7784d2edddc82456af12b495",
88
+ "contracts/vendor/bls-bn254/LICENSE": "af36460fa628a7aca8c5b1f1b6b3615376f00212284fa0fd61a013ee982a3666",
89
+ "src/beacon.ts": "ab94d0702d7174405be085ac4ed4139a71c9d62929e05b93dc3f9e09f6d0a776"
78
90
  },
79
91
  "packageSources": {
80
92
  "src/fees.ts": "836f6a6e14d638876a4ceca8f3369577edb5d242096b420606fb69c74bb7dcae"
package/CHANGELOG.md CHANGED
@@ -1,5 +1,58 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0
4
+
5
+ Replay covers drand beacon epochs. Arc Testnet draws its epochs from drand since epoch 11319 (2026-09-30) and Arc
6
+ Mainnet since epoch 12448 (2026-10-01). The proxy addresses are unchanged and the interface a consumer calls is the
7
+ same as in 0.4.0, so a consumer needs no change. Replay tools do: 0.4.0 rejects a beacon epoch's 64-byte signature
8
+ with `Expected canonical 65-byte low-s EIP-191 signature`.
9
+
10
+ ### Protocol
11
+
12
+ - **The registry has beacon recipes.** `registerBeacon` appends a public randomness beacon as an immutable recipe;
13
+ `beaconOf`, `slotSigner`, `verifyBeacon`, `BEACON_VERIFY_GAS` and `BEACON_DOMAIN` read it, and `BeaconRegistered`
14
+ and `BeaconGasTooLow` are the new event and error. An epoch it serves commits one drand round: the round number as
15
+ data, the round's scheduled time as timestamp and the beacon's 64-byte BLS signature, which the stateless
16
+ `D20BeaconVerifier` (BLS on BN254, built on the unmodified kevincharm/bls-bn254 library) checks on chain. The
17
+ catalog's signer for a beacon slot is `slotSigner(recipe)`.
18
+ - `scheduleCatalog` keeps the version that takes effect at the next epoch and replaces only a version two or more
19
+ epochs ahead. A beacon recipe must be listed with its `slotSigner`.
20
+ - New registry implementation `0xD20dA0853a6f894c0cdc9018fD4F8F67Eac15704` and verifier
21
+ `0xd20dA01Aa16AeD6b77Cd8DDb869151802599100a` on both chains, behind the unchanged proxies: Arc Testnet from block
22
+ 64712965, Arc Mainnet from block 23724929 (transaction
23
+ `0x5a7a2fa8f15eefccee99f6bd363ce7717e65c5571c8c76396337fd8a1261a7cb`).
24
+ `protocol/` now also carries the source of the coordinator implementation `0xD20da000125643B4db5A6A36A3b853c17745DF44`
25
+ live since 2026-09-22: the batch gas guard and the zero minimum fee check.
26
+ - Catalogs: Arc Testnet `[0,1,2,4,5]` from epoch 966, `[6,7,8,9,10]` from 10108 and `[11]` from 11319; Arc Mainnet
27
+ `[0,1,2,4,5]` from epoch 848, `[6,7,8,9,10]` from 10070 and `[11]` from 12448.
28
+
29
+ ### SDK
30
+
31
+ - `replayEpochCommitment` and `replayCoordinator` verify both record types. A beacon epoch needs the registration in
32
+ the recipe book: round, scheduled time, BLS signature and slot signer are checked. Signed-record epochs replay as
33
+ before: nine Arc Mainnet and three Arc Testnet signed-record requests give the same result in 0.4.0 and 0.5.0.
34
+ - `readEpochRecipes` reads `beaconOf` for a recipe that names drand and accepts `{ blockTag }`. `EpochRecipe` has an
35
+ optional `beacon` registration.
36
+ - New exports: `DRAND_EVMNET`, `BEACON_TEMPLATE`, `BEACON_DST`, `BEACON_DOMAIN`, `beaconRoundTime`, `beaconRoundAt`,
37
+ `encodeBeaconRound`, `decodeBeaconRound`, `beaconCanonicalRequest`, `beaconSlotSigner`, `beaconRoundMessage`,
38
+ `verifyBeaconRound` and `BeaconRegistration`. The passthrough-recipe helpers of the protocol source are exported too:
39
+ `PASSTHROUGH`, `PASSTHROUGH_EPOCH_REQUESTS`, `passthroughEpochRecipe`, `canonicalPassthroughRequest`,
40
+ `parsePassthroughRequest` and `passthroughUrl`; `canonicalRequestOfBody` accepts a passthrough body.
41
+ - `beaconVerifierAbi` and `abi/D20BeaconVerifier.json` are the verifier's ABI, and `epochEntropyAbi` covers the beacon
42
+ functions. `API.md` documents them and the changed coordinator source.
43
+ - The BN254 curve comes from `@noble/curves` 1.9.7, the version already in use. It is read only inside the functions
44
+ that verify a round, so a bundler leaves it out of a bundle that reaches none of them; importing the package in Node
45
+ loads it, which adds about 45 ms.
46
+ - Tests replay real Arc requests recorded from the public RPCs, including the first Arc Mainnet drand epoch (12448),
47
+ with tampered-signature, wrong-round, wrong-signer and missing-registration cases, and real drand rounds checked
48
+ against another library's hash-to-curve points.
49
+ - README and `AGENTS.md`: verification covers both record types, the catalog history replaces the single five-source
50
+ catalog, the implementation tables list the registry upgrade and the verifier, and `protocol/` is described as a
51
+ byte-for-byte copy of the keeper source at the pinned commit, checked by its SHA-256 list; that commit is ahead of the
52
+ latest release of the public keeper repository.
53
+ - Vendored protocol re-pinned; `PROTOCOL-PROVENANCE.json` names the commit and the SHA-256 of every file, including
54
+ the vendored bls-bn254 library, whose license ships as `notices/BLS-BN254-LICENSE`.
55
+
3
56
  ## 0.4.0
4
57
 
5
58
  Both Arc networks run the upgraded contracts. The proxy addresses are unchanged, so nothing in an existing
@@ -1,27 +1,33 @@
1
1
  {
2
2
  "sourceRepository": "https://github.com/d20dao/keeper",
3
- "sourceCommit": "de5f82eb9fc749c80e83270f57cde9908ddcf1f3",
4
- "note": "Current public d20dao protocol copied byte-for-byte from committed Git blobs. Operator backends, keys, test fixtures and proof generation are excluded from the package.",
3
+ "sourceCommit": "98e537fb249dd0d3365b8d78e6040a9323a65a88",
4
+ "note": "Protocol copied byte-for-byte from the committed Git blobs of the keeper source at sourceCommit, which is ahead of the latest release of the public repository named in sourceRepository. Operator backends, keys, test fixtures and proof generation are excluded from the package.",
5
5
  "files": {
6
6
  "LICENSE": "bfdc94eef4cfbec12a9ed47bb406c8125cf4c306facf7b8d92db58f133b1a703",
7
7
  "contracts/D20VRFConsumer.sol": "9200fcd8825756ba6978e42d94b52e82e6dea9b1ba35dc506652490194e16162",
8
- "contracts/D20VRFCoordinator.sol": "6d5dc7713d0313f8496eae1dddd6fc5626dc5b8a082fc42da6a87ce2b5d4de4f",
8
+ "contracts/D20VRFCoordinator.sol": "fa3ea7d7995c5dd8f6a9cd50140f0cb8d35e441a6ecda513f6e82fbbacea9eaf",
9
9
  "contracts/interfaces/ID20VRF.sol": "0361aee07644cbf7c2bcde8ffd9188825d5be633c4a1520169a7702a38c1bdbc",
10
10
  "contracts/libraries/D20VRFRequests.sol": "f87e66f7eb0350d9b8740f120b97085957793b826b81a0c7c80d58a3fcd92b76",
11
11
  "contracts/libraries/RandomnessMapping.sol": "00bce68c1a6d89903d72d1c3f9ee5e1a56392188ed29b6aa32ab7451ef180c91",
12
12
  "contracts/vendor/CHAINLINK-LICENSE": "25a502f8fd39602de8a98cc4835a4bf81851b095d6f2c5c130bf0863afac6c99",
13
- "contracts/vendor/PROVENANCE.md": "e6da392378851c47574ef808c9c7e17dea50054ffd0cae7a9070f76f8eed60f2",
13
+ "contracts/vendor/PROVENANCE.md": "56d0e087c3e3fffca95aa8a371979c32239c97a066ca02bb36ae2f0d2af46ffe",
14
14
  "contracts/vendor/VRF.sol": "98e0345fd2dd42178cad4ad16dd8b18621112078d63d0c64839c8bd01c539350",
15
15
  "src/evidence.ts": "81b6a1446b9700ec545e794b08d356411608aa655e6030cdfabd1dfa531ef457",
16
- "src/index.ts": "0a791dc1d12d8f02e503f4990b057f89838f16f93bbdafc4fb3effa6917daf80",
16
+ "src/index.ts": "a120030d296f518925159422873b796614ab83aef53fbe2acd89ea517d10f7d9",
17
17
  "src/mapping.ts": "76aa6093d55831052b56e177fc394ef6ef14183a8ab8ca6fcc2c9382efb6b83f",
18
18
  "src/replay.ts": "c97a5081e785e6a7f27987e2d4c4d9861fe8fa8cbc775b0152e337141d3d48d7",
19
- "src/sources.ts": "8e8b50adf76f4b17d3936ffa4bb4d13e3891f6c7c40483cc9854fc28d0e88a1d",
19
+ "src/sources.ts": "93dad72dd9b5e54a7781b642a0715317bdd8d23d107d53ce64a7e62ea960bdde",
20
20
  "src/verification.ts": "754e39554c0c432cf1404741503f66b4199b84f39003d8e42f300a7256ffaa6a",
21
- "src/epoch.ts": "91bb0d818315dfb5841e1a0450c4b8839d6992ae7d573b33bda1f8797f071aa0",
22
- "contracts/EpochEntropy.sol": "ece8798c2a608d20e1863debaab993a108266bd10b2f45bd762fab9f1db8243f",
21
+ "src/epoch.ts": "226f0c12f138fa4b45c0d073a5aceac4344882ecbdf9593053535ecd4e9a772a",
22
+ "contracts/EpochEntropy.sol": "100785392042af99e710af2e13855fd44d9c1018e33220d2601f75a3cb72f441",
23
23
  "contracts/D20Proxy.sol": "83dcef3b72e2a0a4809c2d8df0d083d2a0f659fe2b3b01fadb1b2eaf8afb7286",
24
24
  "contracts/libraries/DataTemplate.sol": "f0d93c2ec3bcb8cfb625001506fe24f938e6e2cfbae85632f2980d23595ed961",
25
- "src/templates.ts": "7f74968e746461e168a863769736399d74bdab29041ab8dd1cdc0d197b927e61"
25
+ "src/templates.ts": "7f74968e746461e168a863769736399d74bdab29041ab8dd1cdc0d197b927e61",
26
+ "contracts/D20BeaconVerifier.sol": "f2eb0917c2d996f4c3e4969d37e32366163532605cb9ae8e7e97e86935c7b068",
27
+ "contracts/interfaces/IBeaconVerifier.sol": "75782d629af93a368e216272cd1ab25191907ab65e79510294e8679a0f7c6f74",
28
+ "contracts/vendor/bls-bn254/BLS.sol": "887fd94553b9d6d0a247900bdb05523d3b9ef3b8ca049e2a0524a0b1a9d37e69",
29
+ "contracts/vendor/bls-bn254/ModExp.sol": "fd91ae9291511668914b05fece03fb9bf6e2b57f7784d2edddc82456af12b495",
30
+ "contracts/vendor/bls-bn254/LICENSE": "af36460fa628a7aca8c5b1f1b6b3615376f00212284fa0fd61a013ee982a3666",
31
+ "src/beacon.ts": "ab94d0702d7174405be085ac4ed4139a71c9d62929e05b93dc3f9e09f6d0a776"
26
32
  }
27
33
  }
package/README.md CHANGED
@@ -1,4 +1,6 @@
1
- # d20dao VRF SDK
1
+ # D20DAO VRF SDK
2
+
3
+ [Verifiable randomness for onchain apps](https://d20dao.org), currently deployed on Arc Mainnet and Arc Testnet. [Get started](https://d20dao.org/docs/getting-started) · [Integration guide](https://d20dao.org/docs/integration) · [Public proof replay](https://d20dao.org/docs/verification).
2
4
 
3
5
  **Randomness your users can check.** Your contract asks the coordinator for a random result and pays a fee. A few seconds later the coordinator calls your contract back with a word taken from a VRF proof it verified on chain. Nobody picks the answer, nobody gets a second attempt, and anyone can replay the proof afterwards with this package.
4
6
 
@@ -32,7 +34,8 @@ For agent-assisted integration, give your agent the installed `AGENTS.md`, `API.
32
34
  7. **Never re-roll a result you dislike.** The word is final once `fulfilled` is true. Re-requesting after seeing an outcome is the one thing verifiable randomness cannot protect your users from, and the evidence trail makes it visible.
33
35
  8. **Never use `blockhash` or `block.timestamp` as randomness.** Both are chosen by whoever builds the block, and `blockhash` is only available for the last 256 blocks. That is the problem this service exists to solve.
34
36
  9. **Withdraw the refund credit your fee buffer leaves behind.** Anything above the escrowed quote is credited to the refund address (`FeeOverpaymentCredited`), readable with `refundCredits(address)` and pulled with `withdrawRefundCredit(recipient)`. Returning the change in the requesting transaction, as `DiceConsumer` does, avoids the second transaction entirely.
35
- 10. **Freeze any list before you request an index into it.** `chooseOne`, `chooseMany` and `shuffle` answer with indices. Commit the list — hashing it into `clientSeed` puts the commitment in the request log, as `RaffleConsumer` does.
37
+ 10. **Close bets and entries when you request.** No one can predict the word before the keeper submits it, but the pending fulfillment transaction reveals it about one block before it lands. Anything the result decides — stakes, entries, choices — must be fixed in the requesting transaction and unchangeable until the callback, as `RaffleConsumer` does when it closes entries at the draw.
38
+ 11. **Freeze any list before you request an index into it.** `chooseOne`, `chooseMany` and `shuffle` answer with indices. Commit the list — hashing it into `clientSeed` puts the commitment in the request log, as `RaffleConsumer` does.
36
39
 
37
40
  ## Networks
38
41
 
@@ -136,13 +139,14 @@ The coordinator prices every request from the base fee of the transaction that c
136
139
  fee = max(minFee, feeMultiplier × baseFee × (fulfillGasOverhead + callbackGasLimit))
137
140
  ```
138
141
 
139
- `pricing()` returns the live `(minFee, feeMultiplier, fulfillGasOverhead)`. The owner can move them with `setPricing(minFee, multiplier, overhead)` (event `PricingChanged`) only within fixed bounds: `minFee` at most 10 USDC (`10e18` wei; native USDC on Arc uses 18 decimals), `feeMultiplier` 0 to 20 where 0 means a flat `minFee`, `fulfillGasOverhead` 100,000 to 2,000,000 gas. Both Arc deployments were initialized with a 0.08 USDC minimum fee (`initialMinFee()`), multiplier 5 and overhead 300,000 gas, together with a 50% keeper share (`keeperFeeBps` 5000) and a 100% refund ratio. These are initialization values, not fixed prices: read the live values instead of hard-coding them. A pricing change never touches requests that are already open, because each request settles from the fee it escrowed.
142
+ `pricing()` returns the live `(minFee, feeMultiplier, fulfillGasOverhead)`. The owner can move them with `setPricing(minFee, multiplier, overhead)` (event `PricingChanged`) only within fixed bounds: `minFee` at most 10 USDC (`10e18` wei; native USDC on Arc uses 18 decimals), `feeMultiplier` 0 to 20 where 0 means a flat `minFee`, `fulfillGasOverhead` 100,000 to 2,000,000 gas. Both Arc deployments were initialized with a 0.08 USDC minimum fee (`initialMinFee()`), multiplier 5 and overhead 300,000 gas, together with a 50% keeper share (`keeperFeeBps` 5000) and a 100% refund ratio. Arc Testnet still uses these values. Since 2026-09-18 Arc Mainnet charges a 0.02 USDC minimum fee, multiplier 3 and overhead 300,000 gas, with a 60% keeper share (`keeperFeeBps` 6000). Prices are not fixed: read the live values instead of hard-coding them. A pricing change never touches requests that are already open, because each request settles from the fee it escrowed.
140
143
 
141
- Labelled examples with the initialization values (multiplier 5, overhead 300,000 gas, 0.08 USDC minimum fee):
144
+ Labelled examples. A to C use the initialization values (multiplier 5, overhead 300,000 gas, 0.08 USDC minimum fee); D uses Arc Mainnet pricing.
142
145
 
143
146
  - **A, 176 gwei base fee, 100,000 callback gas.** Dynamic part 5 × 176 gwei × 400,000 = 0.352 USDC, above the minimum, so the fee is 0.352 USDC.
144
147
  - **B, 20 gwei base fee, 100,000 callback gas.** Dynamic part 5 × 20 gwei × 400,000 = 0.04 USDC, below the minimum, so the fee is 0.08 USDC.
145
148
  - **C, multiplier set to 0.** The fee is `minFee` at any base fee.
149
+ - **D, Arc Mainnet, 20 gwei base fee, 100,000 callback gas.** Dynamic part 3 × 20 gwei × 400,000 = 0.024 USDC, above the 0.02 USDC minimum, so the fee is 0.024 USDC.
146
150
 
147
151
  `quoteFeeAt(callbackGasLimit, baseFee)` evaluates the formula for a base fee you supply; `quoteFee(callbackGasLimit)` evaluates it for `block.basefee`. Quotes above the `uint96` escrow limit revert with `FeeOverflow` rather than truncating.
148
152
 
@@ -236,6 +240,7 @@ for (;;) {
236
240
  - The package is ESM only (`"type": "module"`, `import` export conditions) for Node 22.13+ and bundlers. In a browser application, import it through a bundler such as Vite, webpack or esbuild; the test suite bundles the root and `/abi` entries for the browser platform with esbuild. Import `@d20dao/vrf-sdk/abi` alone when only ABIs are needed.
237
241
  - `quoteRequestFee(provider, coordinator, callbackGasLimit, options)` expects an ethers v6 provider such as `JsonRpcProvider` or `BrowserProvider`, or any object with ethers-v6-shaped `getBlock(tag)` (with `baseFeePerGas` as `bigint`) and `call(tx)`. With viem or another client, repeat its steps: read the latest block's `baseFeePerGas`, add the buffer and call `quoteFeeAt(callbackGasLimit, bufferedBaseFee)`.
238
242
  - The SDK does not wrap viem or other clients; its ABIs are plain JSON and work with any library.
243
+ - Beacon verification uses the BN254 curve of `@noble/curves`, which takes about 45 ms to evaluate. It is read only inside the functions that verify a round (`verifyBeaconRound`, `beaconRoundMessage`, `verifyEpochAttestation`, `replayEpochCommitment`, `replayCoordinator`), so a bundler leaves it out of a bundle that reaches none of them; importing the package in Node loads it.
239
244
  - To add Arc to a browser wallet, use `wallet_addEthereumChain` with the values from [Networks](#networks). The native currency uses 18 decimals:
240
245
 
241
246
  ```js
@@ -257,7 +262,7 @@ for (;;) {
257
262
 
258
263
  ## Request lifecycle
259
264
 
260
- Epochs last 200 blocks. Each epoch uses the catalog in force for it: 1 to 10 ordered sources, each a registered recipe with its signer (see [Recipes](#recipes)). The keeper selects a source using the canonical block hash at epoch start minus one and prepares its first validated API3 snapshot locally. If the selected source yields no valid packet, the next source in catalog order can be committed instead, one source per 20-block window (attempts 1 to count − 1); a saved response is never refreshed or resampled. The registry committer publishes, or a backup committer the owner allowed so that a second keeper can take over. Idle preparation publishes no transaction. An unused local snapshot can be retained for 50 epochs (10,000 blocks), subject to live-demand and unresolved-transaction protection.
265
+ Epochs last 200 blocks. Each epoch uses the catalog in force for it: 1 to 10 ordered sources, each a registered recipe with its signer (see [Recipes](#recipes)). The keeper selects a source using the canonical block hash at epoch start minus one and prepares its first valid snapshot locally: a signed API record, or for a beacon recipe a drand round (see [Beacon epochs](#beacon-epochs)). If the selected source yields no valid packet, the next source in catalog order can be committed instead, one source per 20-block window (attempts 1 to count − 1); a catalog of one source, such as the drand catalog, has no fallback. A saved signed record is never refreshed or resampled; a saved drand round that has grown too old to be accepted is replaced by a current round, and only while no commit of its epoch has been sent. The registry committer publishes, or a backup committer the owner allowed so that a second keeper can take over. Idle preparation publishes no transaction. An unused local snapshot can be retained for 50 epochs (10,000 blocks), subject to live-demand and unresolved-transaction protection.
261
266
 
262
267
  A request escrows its quoted fee even when its epoch packet is not published yet, and fixes its original block, epoch, client seed, mapping, refund address, `feePaid`, `refundBps` and 60-second deadline. The keeper publishes the saved packet only for live paid demand. The randomness target becomes `max(requestBlock, committedBlock + 1)`, so its hash is unknown at publication; before publication the request has no usable target or VRF seed. Multiple requests share the packet, and timely requests can settle across epoch boundaries without changing their epoch.
263
268
 
@@ -295,13 +300,13 @@ The first attempt forwards 100,000 gas. A reverting or gas-exhausting hook canno
295
300
 
296
301
  ## Replay and verification
297
302
 
298
- Use independently trusted successful receipts and state. Decode the registry `EpochCommitted` packet with `decodeEpochEvidencePacket` and verify with `replayEpochCommitment`, using the original source anchor, exact packet, commit block/time, the epoch's catalog with its recipe definitions and the registry identity. Decode the coordinator `FulfillmentEvidence` packet with `decodeEvidencePacket`, then call `replayCoordinator` with its exported input type (`Parameters<typeof replayCoordinator>[0]`).
303
+ Use independently trusted successful receipts and state. Decode the registry `EpochCommitted` packet with `decodeEpochEvidencePacket` and verify with `replayEpochCommitment`, using the original source anchor, exact packet, commit block/time, the epoch's catalog with its recipe definitions and the registry identity. It checks both record types: the signed record of an API recipe against the catalog's signer, and the round of a beacon recipe against the beacon registration in the recipe book ([Beacon epochs](#beacon-epochs)). Decode the coordinator `FulfillmentEvidence` packet with `decodeEvidencePacket`, then call `replayCoordinator` with its exported input type (`Parameters<typeof replayCoordinator>[0]`).
299
304
 
300
305
  `RequestContext` binds both `requestBlock` and `targetBlock`. Validate the epoch from the original request block, reconstruct the target from the actual publication block, and compare the event and stored transcript. Proof evidence is 416 bytes; fulfillment calldata is 452 bytes. Neither evidence packet has a version prefix. Choose the decoder from trusted emitter/event context. Decoding and mapping alone are not proof verification; replay does not authenticate RPC or establish receipt inclusion.
301
306
 
302
307
  `EpochProtocolConfiguration` is the initialized configuration: `feeRecipient` from `initialFeeRecipient()`, `initialMinFee` from `initialMinFee()` (the `initialize` fee argument), `catalogHash` from `catalogHash()`. Live `pricing()`, `feeRecipient()` and scheduled catalogs never change `protocolConfigurationHash`.
303
308
 
304
- Catalogs are per epoch. `catalogAt(epochId)` returns the hash, recipe ids and signers an epoch selects and commits with, and `Epoch.catalogHash` records that hash at publication. A registry starts with its initial catalog, recipes 0 to 3 with the four signers given at initialization, whose hash `catalogHash()` is bound into the configuration hash; `catalogHash()` and the initial signer getters never change. The owner replaces the catalog for epochs at least two ahead with `scheduleCatalog(recipes, signers, fromEpoch)`: 1 to 10 distinct registered recipes with one signer each, hashed as `keccak256(abi.encode(RECIPE_DOMAIN, recipes, signers))` (event `CatalogScheduled(fromEpoch, catalogHash, recipes, signers)`). A new schedule replaces a version that has not taken effect yet, which can return the next epoch to the previous catalog; the current epoch, prepared snapshots and open requests keep their catalog. For replay, build `epoch.catalog` from the epoch's `catalogAt` view with `resolveEpochCatalog(base, view)`, which recognizes the initial catalog by its hash (or from the `CatalogScheduled` history without replaced versions), while `configuration.catalogHash` stays the initial `catalogHash()`; `replayEpochCommitment` binds the supplied catalog to `record.catalogHash`.
309
+ Catalogs are per epoch. `catalogAt(epochId)` returns the hash, recipe ids and signers an epoch selects and commits with, and `Epoch.catalogHash` records that hash at publication. A registry starts with its initial catalog, recipes 0 to 3 with the four signers given at initialization, whose hash `catalogHash()` is bound into the configuration hash; `catalogHash()` and the initial signer getters never change. The owner replaces the catalog for epochs at least two ahead with `scheduleCatalog(recipes, signers, fromEpoch)`: 1 to 10 distinct registered recipes with one signer each, hashed as `keccak256(abi.encode(RECIPE_DOMAIN, recipes, signers))` (event `CatalogScheduled(fromEpoch, catalogHash, recipes, signers)`). A new schedule replaces a pending version, one that takes effect two or more epochs ahead; the version that takes effect at the next epoch is kept, as is every active one, so the current and next epoch, prepared snapshots and open requests keep their catalog. For replay, build `epoch.catalog` from the epoch's `catalogAt` view with `resolveEpochCatalog(base, view)`, which recognizes the initial catalog by its hash (or from the `CatalogScheduled` history without replaced versions), while `configuration.catalogHash` stays the initial `catalogHash()`; `replayEpochCommitment` binds the supplied catalog to `record.catalogHash`.
305
310
 
306
311
  ```ts
307
312
  import { readEpochRecipes, resolveEpochCatalog } from '@d20dao/vrf-sdk/epoch';
@@ -311,11 +316,11 @@ const recipeBook = await readEpochRecipes(provider, registryAddress, recipes.map
311
316
  const catalog = resolveEpochCatalog({ registry: registryAddress, chainId, firstEpochStart, recipeBook }, { hash, recipes, signers });
312
317
  ```
313
318
 
314
- At publication a signed attestation may be at most 240 seconds old and never future-dated (`MAX_ATTESTATION_AGE`, exported from `/epoch`); `replayEpochCommitment` enforces the same bound against the commit timestamp.
319
+ At publication an attestation, a signed record or a beacon round at its scheduled time, may be at most 240 seconds old and never future-dated (`MAX_ATTESTATION_AGE`, exported from `/epoch`); `replayEpochCommitment` enforces the same bound against the commit timestamp.
315
320
 
316
321
  ### Recipes
317
322
 
318
- Epoch sources are recipes in an owner-managed, append-only registry in `EpochEntropy`. A recipe is its canonical request, whose `keccak256` is the query hash the signer signs; a data template that fixes the exact signed bytes the registry accepts ([Data templates](#data-templates)); and the JSON body keepers post to the provider gateway. `registerRecipe(canonicalRequest, template, body)` appends the next id (0 to 255) and emits `RecipeRegistered` with the full definition. A registered recipe never changes, so a changed listing becomes a new id. `recipeCount()` and `getRecipe(id)` read the registry. Every registry registers six built-in recipes itself, at initialization or in its recipe-registry upgrade:
323
+ Epoch sources are recipes in an owner-managed, append-only registry in `EpochEntropy`. A recipe is its canonical request, whose `keccak256` is the query hash the signer signs; a data template that fixes the exact signed bytes the registry accepts ([Data templates](#data-templates)); and the body keepers send: the JSON they post to the provider gateway, or a beacon's canonical request. `registerRecipe(canonicalRequest, template, body)` appends the next id (0 to 255) and emits `RecipeRegistered` with the full definition; `registerBeacon` appends a beacon recipe ([Beacon epochs](#beacon-epochs)). A registered recipe never changes, so a changed listing becomes a new id. `recipeCount()` and `getRecipe(id)` read the registry. Every registry registers six built-in recipes itself, at initialization or in its recipe-registry upgrade:
319
324
 
320
325
  | Recipe | Provider | Query | Exact signed record |
321
326
  | --- | --- | --- | --- |
@@ -326,11 +331,43 @@ Epoch sources are recipes in an owner-managed, append-only registry in `EpochEnt
326
331
  | 4 | Nodary | `latestFeeds`, name ETH/USD | `{"ETH/USD":{"value":<number>,"timestamp":<13 digits>,"category":"crypto"}}` |
327
332
  | 5 | dRPC | as recipe 1 on Base | as recipe 1 |
328
333
 
329
- Both networks now draw from a five-source catalog, `[0, 1, 2, 4, 5]`: Hyperliquid BTC day volume, the dRPC Ethereum block hash, TickerLayer BTCUSD, Nodary ETH/USD and the dRPC Base block hash: Arc Testnet from epoch 966 and Arc Mainnet from epoch 848. Read the catalog an epoch actually used from `catalogAt(epochId)` rather than assuming this one.
334
+ The owner registered more recipes after these. Recipes 6 to 10 are the passthrough form of built-in recipes 0, 1, 2, 4 and 5: the same listings reached through the gateway's `/api` path, answered with the same signed records under other request hashes, so they keep the built-in templates. Their canonical request is also their body, and `passthroughEpochRecipe(builtinId)` builds it. Recipe 11 is the drand evmnet beacon, registered on both networks.
335
+
336
+ Each network's catalog changed over time. Read the catalog an epoch actually used from `catalogAt(epochId)` rather than assuming one:
337
+
338
+ | Network | From epoch | Catalog | Date (UTC) |
339
+ | --- | --- | --- | --- |
340
+ | Arc Testnet | 966 | `[0, 1, 2, 4, 5]`: Hyperliquid BTC day volume, dRPC Ethereum block hash, TickerLayer BTCUSD, Nodary ETH/USD, dRPC Base block hash | 2026-09-17 |
341
+ | Arc Testnet | 10108 | `[6, 7, 8, 9, 10]`: the same five listings through the passthrough path | 2026-09-28 |
342
+ | Arc Testnet | 11319 | `[11]`: drand evmnet only | 2026-09-30 |
343
+ | Arc Mainnet | 848 | `[0, 1, 2, 4, 5]` | 2026-09-18 |
344
+ | Arc Mainnet | 10070 | `[6, 7, 8, 9, 10]` | 2026-09-28 |
345
+ | Arc Mainnet | 12448 | `[11]`: drand evmnet only | 2026-10-01 |
346
+
347
+ Before those catalogs each registry used its initial catalog, recipes 0 to 3.
348
+
349
+ `BUILTIN_EPOCH_RECIPES` (from `@d20dao/vrf-sdk/epoch`) holds the built-in definitions, and replay uses them unless the catalog carries a `recipeBook`. For any other recipe, put its definition in `epoch.catalog.recipeBook`: `readEpochRecipes(provider, registry, ids)` reads `getRecipe` and checks each query hash, and for a beacon recipe reads its registration from `beaconOf`; or rebuild them from `RecipeRegistered` and `BeaconRegistered` logs. Replay checks every definition it uses: the committed packet must carry the recipe's canonical request and the record must match its template.
350
+
351
+ Registry implementations before variable catalogs hardcoded ANU random numbers as recipe 1. Neither public registry ever committed an epoch from it. Older evidence that did use ANU replays when its definition is supplied in `recipeBook` under id 1: canonical request `["randomNumbers",[["length",4],["size",8],["type","hex8"]]]`, body `{"operation":"randomNumbers","parameters":{"type":"hex8","length":4,"size":8}}` and the template described in [Data templates](#data-templates).
352
+
353
+ ### Beacon epochs
330
354
 
331
- `BUILTIN_EPOCH_RECIPES` (from `@d20dao/vrf-sdk/epoch`) holds these definitions, and replay uses them unless the catalog carries a `recipeBook`. For any other recipe, put its definition in `epoch.catalog.recipeBook`: `readEpochRecipes(provider, registry, ids)` reads `getRecipe` and checks each query hash, or rebuild it from `RecipeRegistered` logs. Replay checks every definition it uses: the committed packet must carry the recipe's canonical request and the signed data must match its template.
355
+ A beacon recipe commits one round of a public randomness beacon instead of a signed API record. Arc uses [drand](https://drand.love)'s evmnet (`bls-bn254-unchained-on-g1`: BN254, a round every 3 seconds, chain hash `0x04f1e9062b8a81f848fded9c12306733282b2727ecced50032187751166ec8c3`), exported as `DRAND_EVMNET`. The owner registers a beacon with `registerBeacon(verifier, chainHash, publicKey, genesis, period, sampleRound, sampleSignature)`. The `verifier` is a contract such as `D20BeaconVerifier` that checks a round's signature under `publicKey`; the registry accepts the registration only if the verifier accepts a past `sampleRound`, under the same gas allowance a publication gets. A registration never changes. Its recipe has the canonical request `["drand","<chainHash>"]`, which is also its body, and a template that accepts one round number: 1 to 19 digits, no leading zero.
356
+
357
+ An epoch published from a beacon recipe commits round `r` as the data, its scheduled time `genesis + (r − 1) × period` as the timestamp and the beacon's 64-byte signature (`x ‖ y` of a BN254 G1 point) as the signature. The registry requires the timestamp to be that scheduled time and, as for any attestation, not in the future and at most `MAX_ATTESTATION_AGE` old, and the registered verifier to accept the signature. It calls the verifier with a fixed allowance of `BEACON_VERIFY_GAS` and reverts `BeaconGasTooLow`, not `InvalidSigner`, when the transaction's gas cannot give it that allowance. The signer a catalog lists for a beacon slot is `slotSigner(recipe)`, an identity derived from the registration and not a key, and `scheduleCatalog` accepts no other. Any round scheduled within the 240 seconds before the publication block is valid, so the committer chooses which one an epoch commits, and one round can serve several consecutive epochs: their epoch hashes differ, but their data hashes and signatures can repeat, so key an epoch on its epoch ID or epoch hash, never on its round. Otherwise an epoch works as before: the anchor selects the slot, and every request's target is a block after publication.
358
+
359
+ Replay needs the registration in the epoch's recipe book. `readEpochRecipes(provider, registry, ids)` reads `beaconOf` for every recipe whose canonical request names drand and adds it as `beacon` (`verifier`, `chainHash`, `publicKey`, `genesis`, `period`). With `{ blockTag }` it reads recipes and registrations as of that block, for a registry that has since moved to an implementation without `beaconOf`; that needs an RPC that serves the state of past blocks. `replayEpochCommitment` then checks a beacon epoch's round number against the template, its timestamp against the schedule, its signature with `verifyBeaconRound` and the catalog's signer against `beaconSlotSigner(beacon)`; `replayCoordinator` does the same inside a full request replay. `verifyBeaconRound(publicKey, round, signature)` is the pairing check of `D20BeaconVerifier` computed in TypeScript with `@noble/curves`; it returns false for malformed input instead of throwing. Replay does not call the registered verifier, so the verifier's address and runtime code are chain context to check against the reviewed `D20BeaconVerifier` ([Deployments](#deployments) lists its code hash).
360
+
361
+ ```ts
362
+ import { readEpochRecipes, resolveEpochCatalog, replayEpochCommitment } from '@d20dao/vrf-sdk/epoch';
363
+
364
+ const [hash, recipes, signers] = await registry.catalogAt(epochId); // registry: ethers Contract with epochEntropyAbi
365
+ const recipeBook = await readEpochRecipes(provider, registryAddress, recipes.map(Number)); // adds `beacon` to a beacon recipe
366
+ const catalog = resolveEpochCatalog({ registry: registryAddress, chainId, firstEpochStart, recipeBook }, { hash, recipes, signers });
367
+ replayEpochCommitment({ catalog, epochId, record, commitTimestamp, packet }); // throws unless round, time, signature and signer check out
368
+ ```
332
369
 
333
- Registry implementations before variable catalogs hardcoded ANU random numbers as recipe 1. Neither public registry ever committed an epoch from it, so every published Arc epoch replays with the built-in recipes. Older evidence that did use ANU replays when its definition is supplied in `recipeBook` under id 1: canonical request `["randomNumbers",[["length",4],["size",8],["type","hex8"]]]`, body `{"operation":"randomNumbers","parameters":{"type":"hex8","length":4,"size":8}}` and the template described in [Data templates](#data-templates).
370
+ The root entry exports `DRAND_EVMNET`, `BEACON_TEMPLATE`, `BEACON_DST`, `BEACON_DOMAIN`, `beaconRoundTime(registration, round)`, `beaconRoundAt(registration, timestamp)`, `encodeBeaconRound`, `decodeBeaconRound`, `beaconCanonicalRequest(chainHash)`, `beaconSlotSigner(registration)`, `beaconRoundMessage(round)` (the hash-to-curve point a round's signature signs), `verifyBeaconRound` and the `BeaconRegistration` type.
334
371
 
335
372
  ### Data templates
336
373
 
@@ -368,39 +405,40 @@ The contracts have not had an external security audit. The coordinator source ca
368
405
  Trust model:
369
406
 
370
407
  - **Owner.** On Arc Mainnet both service proxies are owned by the DAO treasury Safe `0xB57f656149749eff6b496dF090336491f977E744`, which is also the fee recipient; each manifest records the owner for its network. The owner can upgrade either implementation, which can change any behavior. Ownership moves only through a two-step transfer, and `renounceOwnership` reverts.
371
- - **Owner settings without an upgrade.** Coordinator: fee recipient, keeper share (0–100%), pricing within the bounds in [Pricing](#pricing), and the refund ratio for future requests (50–100%). Registry: committer, up to four backup committers, recipe registration (append-only) and catalogs for epochs at least two ahead. Open requests keep their escrowed fee and refund ratio.
372
- - **Publishers.** The committer and each backup committer can publish an epoch from any valid signed record of its selected source, under the same rules. A backup committer has no other role, and earns the keeper share of the requests whose proofs it submits itself.
408
+ - **Owner settings without an upgrade.** Coordinator: fee recipient, keeper share (0–100%), pricing within the bounds in [Pricing](#pricing), and the refund ratio for future requests (50–100%). Registry: committer, up to four backup committers, recipe and beacon registration (append-only) and catalogs for epochs at least two ahead. Open requests keep their escrowed fee and refund ratio.
409
+ - **Publishers.** The committer and each backup committer can publish an epoch from any valid record of its selected source, a signed record or a beacon round, under the same rules. A backup committer has no other role, and earns the keeper share of the requests whose proofs it submits itself.
373
410
  - **Recipes.** A signature establishes what a provider's gateway signed for a recipe's request, not that the upstream value is unbiased. The owner decides which recipes and signers future epochs use; a lax template accepts more signed records for a publisher to choose from.
411
+ - **Beacons.** The registry trusts the yes or no that a registered verifier gives within its gas allowance, and `registerBeacon` accepts any contract as verifier. It does not check that the chain hash names the network of the key or that the verifier's code is the reviewed one: the owner vouches for a registration, which `BeaconRegistered` publishes. `D20BeaconVerifier` is stateless, has no owner and is not behind a proxy, so its behavior cannot change after registration; a different verifier is a different registration and so a different `slotSigner`. A beacon signature shows what the beacon's key signed for a round, not that the beacon is unbiased or independent of its operators, and the committer chooses which round, among those scheduled within `MAX_ATTESTATION_AGE` of the publication block, an epoch commits.
374
412
  - **Keeper.** The VRF key holder can withhold a proof but cannot substitute a different result for a request's fixed seed. A request that is not served within 60 seconds is refundable at its snapshotted ratio.
375
413
 
376
414
  Both proxies are atomically initialized ERC1967 endpoints with owner-authorized UUPS upgrades, and the implementations behind them are locked against initialization. No setter rewrites a request, a published epoch or the VRF key, but upgrade authority can change code and is an explicit trust assumption. Stable proxy addresses alone do not identify executed code, so verify the implementation history of **both** proxies at the relevant receipts when you integrate, and again whenever the deployment manifest records an upgrade or a proxy emits `Upgraded(implementation)`. A mismatch with the manifest means stop and review before sending more requests.
377
415
 
378
416
  ## Use locally
379
417
 
380
- For SDK development, run `npm ci` and `npm test` from this repository. The test builds, checks that `API.md` matches the reference that `scripts/api-reference.mjs` generates from the built ABIs and the curated `scripts/api-descriptions.mjs` (regenerate with `npm run build && npm run api-reference`), packs and installs a real tarball in an isolated consumer, replays the recipe fixtures, type-checks a strict consumer, exercises `quoteRequestFee` against a mock provider, and compiles all three examples from the installed package and checks the outcomes they publish. `npm pack` also produces an installable local artifact.
418
+ For SDK development, run `npm ci` and `npm test` from this repository. The test builds, checks that `API.md` matches the reference that `scripts/api-reference.mjs` generates from the built ABIs and the curated `scripts/api-descriptions.mjs` (regenerate with `npm run build && npm run api-reference`), packs and installs a real tarball in an isolated consumer, replays the recipe fixtures and real Arc epochs (signed records and drand beacon epochs of both networks, with tampered-signature, wrong-round, wrong-signer and missing-registration cases), type-checks a strict consumer, exercises `quoteRequestFee` against a mock provider, and compiles all three examples from the installed package and checks the outcomes they publish. `npm pack` also produces an installable local artifact.
381
419
 
382
420
  ```js
383
421
  import { builtins, mapRandomness, replayCoordinator, quoteRequestFee } from '@d20dao/vrf-sdk';
384
- import { coordinatorAbi, epochEntropyAbi } from '@d20dao/vrf-sdk/abi';
422
+ import { coordinatorAbi, epochEntropyAbi, beaconVerifierAbi } from '@d20dao/vrf-sdk/abi';
385
423
  const mapping = builtins.d20();
386
424
  // Use only an independently verified accepted word for real outcomes.
387
425
  ```
388
426
 
389
- The root exports ESM and TypeScript declarations, including `quoteRequestFee`, `DEFAULT_FEE_BUFFER_BPS` and the `FeeQuote`, `FeeQuoteOptions` and `FeeQuoteProvider` types; `/epoch` exports epoch helpers, `BUILTIN_EPOCH_RECIPES`, `readEpochRecipes`, `resolveEpochCatalog` and `MAX_ATTESTATION_AGE`, and the root also exports the data-template helpers (`encodeDataTemplate`, `decodeDataTemplate`, `matchesDataTemplate`, `isValidDataTemplate`, `validateDataTemplate`). `/abi` exports `coordinatorAbi` and `epochEntropyAbi`, with JSON forms `D20VRFCoordinator.json` and `EpochEntropy.json`, both described in the installed `API.md` (`@d20dao/vrf-sdk/API.md`). The service implementations have locked empty constructors and explicit initializers. Registry initialization takes `address[4]`; it is not a four-address constructor deployment.
427
+ The root exports ESM and TypeScript declarations, including `quoteRequestFee`, `DEFAULT_FEE_BUFFER_BPS` and the `FeeQuote`, `FeeQuoteOptions` and `FeeQuoteProvider` types; `/epoch` exports epoch helpers, `BUILTIN_EPOCH_RECIPES`, `readEpochRecipes`, `resolveEpochCatalog` and `MAX_ATTESTATION_AGE`, and the root also exports the data-template helpers (`encodeDataTemplate`, `decodeDataTemplate`, `matchesDataTemplate`, `isValidDataTemplate`, `validateDataTemplate`), the passthrough-recipe helpers (`PASSTHROUGH_EPOCH_REQUESTS`, `passthroughEpochRecipe`, `canonicalPassthroughRequest`, `parsePassthroughRequest`, `passthroughUrl`) and the beacon helpers ([Beacon epochs](#beacon-epochs)). `/abi` exports `coordinatorAbi`, `epochEntropyAbi` and `beaconVerifierAbi`, with JSON forms `D20VRFCoordinator.json`, `EpochEntropy.json` and `D20BeaconVerifier.json`, all described in the installed `API.md` (`@d20dao/vrf-sdk/API.md`). The service implementations have locked empty constructors and explicit initializers. Registry initialization takes `address[4]`; it is not a four-address constructor deployment.
390
428
 
391
429
  ## What this package is not
392
430
 
393
431
  This SDK contains no keeper service, API fetching, proof generation, signer secrets or deployment automation. Installing it neither authorizes nor performs anything on chain.
394
432
 
395
- The public protocol source is this repository's [`protocol/`](https://github.com/d20dao/d20-sdk/tree/main/protocol) folder. `PROTOCOL-PROVENANCE.json` names the keeper commit it was copied from and the SHA-256 of every file; that keeper repository is not public, so read the source in `protocol/`. Builds use these reviewed protocol Git blobs and verify every SHA-256 in PROTOCOL-PROVENANCE.json. `src/fees.ts` (the fee-quoting helper) is SDK-owned rather than vendored; BUILD-MANIFEST.json records it under `packageSources` next to the protocol source, dependency-lock and imported OpenZeppelin hashes. The UUPS build uses OpenZeppelin contracts and contracts-upgradeable 5.6.1. Consumer source is copied exactly; service implementations, test fixtures and provers are excluded from the tarball.
433
+ The protocol source is this repository's [`protocol/`](https://github.com/d20dao/d20-sdk/tree/main/protocol) folder, a byte-for-byte copy of the keeper source at the commit `PROTOCOL-PROVENANCE.json` names; every build checks each file against that file's SHA-256 list. The public keeper repository, [d20dao/keeper](https://github.com/d20dao/keeper), publishes release snapshots, and that commit is ahead of its latest release. `src/fees.ts` (the fee-quoting helper) is SDK-owned rather than vendored; BUILD-MANIFEST.json records it under `packageSources` next to the protocol source, dependency-lock and imported OpenZeppelin hashes. The UUPS build uses OpenZeppelin contracts and contracts-upgradeable 5.6.1. Consumer source is copied exactly; service implementations, test fixtures and provers are excluded from the tarball.
396
434
 
397
- Each replay fixture set records how it was produced, so a real API3 capture is never mistaken for a test signature. Fixtures are not included in the package. The browser-target bundle is executed under Node, not in an actual browser; independently trusted chain context is still required for real verification.
435
+ Each replay fixture set records how it was produced, so a real capture is never mistaken for a test signature. The Arc sets are accepted requests recorded from the public RPCs with `scripts/record-live-fixture.mjs`: signed-record and drand beacon epochs of both networks. Real drand rounds with hash-to-curve points computed by another library check the beacon code independently. Fixtures are not included in the package. The browser-target bundle is executed under Node, not in an actual browser; independently trusted chain context is still required for real verification.
398
436
 
399
437
  SDK installation provides consumer and verification tooling. Chain availability, provider quotas, upgrade administration and application settlement remain separate concerns, and none of them guarantees a particular request's timely fulfillment.
400
438
 
401
439
  ## Deployments
402
440
 
403
- Both networks run the implementations this package describes, behind the same proxy addresses as before: the recipe registry in `EpochEntropy` and the coordinator that pays the keeper share to the authorized wallet which submitted the accepted proof. The two chains run the same implementation addresses. Epochs published before the upgrade still replay with this SDK's built-in recipes.
441
+ Both networks run the recipe registry in `EpochEntropy` and the coordinator that pays the keeper share to the authorized wallet which submitted the accepted proof, behind the same proxy addresses as before. Since 2026-09-22 the coordinator also budgets every served member's callback gas before `fulfillRandomnessBatch` reveals any result, and `setPricing` rejects a zero minimum fee with a zero multiplier. The registry implementation also carries beacon recipes (`registerBeacon`, `beaconOf`, `slotSigner`, `verifyBeacon`), whose drand rounds the stateless `D20BeaconVerifier` checks. It is live behind the Arc Testnet proxy since block 64712965 (transaction `0x751797af884715214dd4a4e339dbd3355c935b64afe7a6b1eaf38e7e18895140`) and behind the Arc Mainnet proxy since block 23724929 (transaction `0x5a7a2fa8f15eefccee99f6bd363ce7717e65c5571c8c76396337fd8a1261a7cb`), so the two chains run the same implementation addresses and the same verifier. Their ABIs and storage layouts are those of the source in `protocol/`. Code hashes (`keccak256` of the runtime code): registry implementation `0xb5e125e3b0f63ffe516d781266c1cbacebe3d131148b69f8736d3ca78bcddb12`, coordinator implementation `0x3dda400d8360d7e03b8dacd8ba1ffad7ad672e07628bee7de4542d754dd5348c`, beacon verifier `0x4388250d26298224c4a39d030263550655390ca831ab44c4124f8b0be1f65351`. Epochs published before an upgrade still replay with this SDK: built-in recipes with their built-in definitions, later recipes with the definitions read from the registry.
404
442
 
405
443
  Obtain proxy addresses, implementation addresses and independently checked code hashes from the public deployment manifests, [arc-mainnet.json](https://d20dao.org/deployments/arc-mainnet.json) and [arc-testnet.json](https://d20dao.org/deployments/arc-testnet.json). The addresses below are copied from them and are only valid together with the manifest revision they came from, because implementations move through owner-authorized upgrades. Before relying on this SDK's interface, check that the implementation at your chain's proxy matches the manifest entry.
406
444
 
@@ -413,11 +451,12 @@ Chain ID: **5042**. The live service; use the **coordinator proxy** when constru
413
451
  | D20VRFCoordinator | Consumer entry point / proxy | [`0xd20da057469C45928912d983F45790C41e290571`](https://explorer.arc.io/address/0xd20da057469C45928912d983F45790C41e290571) |
414
452
  | EpochEntropy | Epoch registry / proxy | [`0xd20Da048C1A68fa3Bc0B5f5Bc454D1530062C82D`](https://explorer.arc.io/address/0xd20Da048C1A68fa3Bc0B5f5Bc454D1530062C82D) |
415
453
  | D20CostClient | Restricted cost client / proxy | [`0xD20da0048aED2BBb9f0e7078Bc452815D626D29d`](https://explorer.arc.io/address/0xD20da0048aED2BBb9f0e7078Bc452815D626D29d) |
416
- | D20VRFCoordinator | Implementation | [`0xd20da0DADa4352A1a9722be43a2D85923443458c`](https://explorer.arc.io/address/0xd20da0DADa4352A1a9722be43a2D85923443458c) |
417
- | EpochEntropy | Implementation | [`0xd20dA048C969e5aDcC703Dfdf8220cc9dCB2f865`](https://explorer.arc.io/address/0xd20dA048C969e5aDcC703Dfdf8220cc9dCB2f865) |
454
+ | D20VRFCoordinator | Implementation | [`0xD20da000125643B4db5A6A36A3b853c17745DF44`](https://explorer.arc.io/address/0xD20da000125643B4db5A6A36A3b853c17745DF44) |
455
+ | EpochEntropy | Implementation | [`0xD20dA0853a6f894c0cdc9018fD4F8F67Eac15704`](https://explorer.arc.io/address/0xD20dA0853a6f894c0cdc9018fD4F8F67Eac15704) |
456
+ | D20BeaconVerifier | Beacon verifier, stateless, not behind a proxy | [`0xd20dA01Aa16AeD6b77Cd8DDb869151802599100a`](https://explorer.arc.io/address/0xd20dA01Aa16AeD6b77Cd8DDb869151802599100a) |
418
457
  | D20CostClient | Implementation | [`0xD20DA00A872acfDe3e4721Fc1051BD23CC84B66b`](https://explorer.arc.io/address/0xD20DA00A872acfDe3e4721Fc1051BD23CC84B66b) |
419
458
 
420
- Addresses are copied from the [Arc Mainnet deployment manifest](https://d20dao.org/deployments/arc-mainnet.json). Both networks run the same coordinator and registry implementations.
459
+ Addresses are copied from the [Arc Mainnet deployment manifest](https://d20dao.org/deployments/arc-mainnet.json). The EpochEntropy implementation above is live behind the proxy from block 23724929 (transaction `0x5a7a2fa8f15eefccee99f6bd363ce7717e65c5571c8c76396337fd8a1261a7cb`); before it the proxy ran `0xd20dA048C969e5aDcC703Dfdf8220cc9dCB2f865`.
421
460
 
422
461
  ### Arc Testnet
423
462
 
@@ -428,8 +467,9 @@ Chain ID: **5042002**. For development and testing. Use the **coordinator proxy*
428
467
  | D20VRFCoordinator | Consumer entry point / proxy | [`0xd20DA0FF9087d053f0291524Eac12abA1ADBd945`](https://testnet.arcscan.app/address/0xd20DA0FF9087d053f0291524Eac12abA1ADBd945) |
429
468
  | EpochEntropy | Epoch registry / proxy | [`0xD20Da00B47A7cD2211dC4683E306913b05903756`](https://testnet.arcscan.app/address/0xD20Da00B47A7cD2211dC4683E306913b05903756) |
430
469
  | D20CostClient | Restricted cost client / proxy | [`0xD20da026090B8472579a2B93030F1fC4c94807F1`](https://testnet.arcscan.app/address/0xD20da026090B8472579a2B93030F1fC4c94807F1) |
431
- | D20VRFCoordinator | Implementation | [`0xd20da0DADa4352A1a9722be43a2D85923443458c`](https://testnet.arcscan.app/address/0xd20da0DADa4352A1a9722be43a2D85923443458c) |
432
- | EpochEntropy | Implementation | [`0xd20dA048C969e5aDcC703Dfdf8220cc9dCB2f865`](https://testnet.arcscan.app/address/0xd20dA048C969e5aDcC703Dfdf8220cc9dCB2f865) |
470
+ | D20VRFCoordinator | Implementation | [`0xD20da000125643B4db5A6A36A3b853c17745DF44`](https://testnet.arcscan.app/address/0xD20da000125643B4db5A6A36A3b853c17745DF44) |
471
+ | EpochEntropy | Implementation | [`0xD20dA0853a6f894c0cdc9018fD4F8F67Eac15704`](https://testnet.arcscan.app/address/0xD20dA0853a6f894c0cdc9018fD4F8F67Eac15704) |
472
+ | D20BeaconVerifier | Beacon verifier, stateless, not behind a proxy | [`0xd20dA01Aa16AeD6b77Cd8DDb869151802599100a`](https://testnet.arcscan.app/address/0xd20dA01Aa16AeD6b77Cd8DDb869151802599100a) |
433
473
  | D20CostClient | Implementation | [`0xD20DA00A872acfDe3e4721Fc1051BD23CC84B66b`](https://testnet.arcscan.app/address/0xD20DA00A872acfDe3e4721Fc1051BD23CC84B66b) |
434
474
 
435
- Addresses are copied from the [Arc Testnet deployment manifest](https://d20dao.org/deployments/arc-testnet.json). Explorer links identify addresses; they do not assert explorer source-code verification. The cost client is internal tooling, not a shared application entry point.
475
+ Addresses are copied from the [Arc Testnet deployment manifest](https://d20dao.org/deployments/arc-testnet.json). The EpochEntropy implementation above is live behind the proxy from block 64712965; before it the proxy ran `0xd20dA048C969e5aDcC703Dfdf8220cc9dCB2f865`. Explorer links identify addresses; they do not assert explorer source-code verification. The cost client is internal tooling, not a shared application entry point.
@@ -1,9 +1,11 @@
1
1
  # Third-party attribution
2
2
 
3
- The redistributed consumer Solidity files and public TypeScript helpers are from this repository under its MIT LICENSE: the protocol sources are vendored from d20dao/keeper as recorded in PROTOCOL-PROVENANCE.json, and the fee-quoting helper (`src/fees.ts`) is maintained here. The coordinator and registry ABIs are generated, not hand-maintained. The coordinator implementation and Chainlink Solidity verifier are not distributed as SDK runtime or import sources.
3
+ The redistributed consumer Solidity files and public TypeScript helpers are from this repository under its MIT LICENSE: the protocol sources are copied byte for byte from the keeper source at the commit recorded in PROTOCOL-PROVENANCE.json, and the fee-quoting helper (`src/fees.ts`) is maintained here. The coordinator, registry and beacon verifier ABIs are generated, not hand-maintained. The coordinator implementation, the beacon verifier and the Chainlink Solidity verifier are not distributed as SDK runtime or import sources.
4
4
 
5
5
  Public proof verification implements compatibility with the pinned Chainlink secp256k1/Keccak construction. Its upstream provenance and full preserved root license are included in `notices/PROVENANCE.md` and `notices/CHAINLINK-LICENSE`. The provenance path describes the original repository, not a bundled verifier. This is not a Chainlink service or an extension of an upstream audit.
6
6
 
7
+ Beacon verification implements, in TypeScript, the hash-to-curve and point checks of the kevincharm/bls-bn254 library (MIT), on which the `D20BeaconVerifier` contract is built. Its upstream provenance is in `notices/PROVENANCE.md` and its preserved MIT license in `notices/BLS-BN254-LICENSE`; the library source is not distributed as SDK runtime or import source. This is not a drand or League of Entropy service or an extension of any upstream review.
8
+
7
9
  `ethers` and `@noble/curves` are runtime npm dependencies, not copied/bundled source. Their distributions carry their own licenses and transitive dependency notices. Build-only Solidity compilation uses OpenZeppelin 5.6.1 and solc 0.8.28; neither is bundled into the public JavaScript.
8
10
 
9
11
  The repository and isolated smoke consumer override solc's build-only `tmp` dependency to 0.2.7 to address GHSA-52f5-9888-hmc6, GHSA-ph9p-34f9-6g65 and GHSA-7c78-jf6q-g5cm while preserving compiler 0.8.28. npm overrides apply only at a project's root; this does not impose an override on SDK consumers, and solc is not an SDK runtime dependency.