@aztec/ethereum 0.0.1-commit.b655e406 → 0.0.1-commit.b9865e97

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.
Files changed (236) hide show
  1. package/dest/account.d.ts +1 -1
  2. package/dest/chain.d.ts +1 -1
  3. package/dest/client.d.ts +25 -3
  4. package/dest/client.d.ts.map +1 -1
  5. package/dest/client.js +60 -4
  6. package/dest/config.d.ts +29 -73
  7. package/dest/config.d.ts.map +1 -1
  8. package/dest/config.js +70 -387
  9. package/dest/constants.d.ts +1 -1
  10. package/dest/contracts/chain_state_override.d.ts +78 -0
  11. package/dest/contracts/chain_state_override.d.ts.map +1 -0
  12. package/dest/contracts/chain_state_override.js +137 -0
  13. package/dest/contracts/empire_base.d.ts +7 -5
  14. package/dest/contracts/empire_base.d.ts.map +1 -1
  15. package/dest/contracts/empire_base.js +1 -1
  16. package/dest/contracts/errors.d.ts +1 -1
  17. package/dest/contracts/errors.d.ts.map +1 -1
  18. package/dest/contracts/fee_asset_handler.d.ts +6 -5
  19. package/dest/contracts/fee_asset_handler.d.ts.map +1 -1
  20. package/dest/contracts/fee_asset_handler.js +11 -9
  21. package/dest/contracts/fee_asset_price_oracle.d.ts +105 -0
  22. package/dest/contracts/fee_asset_price_oracle.d.ts.map +1 -0
  23. package/dest/contracts/fee_asset_price_oracle.js +656 -0
  24. package/dest/contracts/fee_juice.d.ts +1 -1
  25. package/dest/contracts/fee_juice.d.ts.map +1 -1
  26. package/dest/contracts/governance.d.ts +113 -36
  27. package/dest/contracts/governance.d.ts.map +1 -1
  28. package/dest/contracts/governance.js +207 -16
  29. package/dest/contracts/governance_proposer.d.ts +38 -4
  30. package/dest/contracts/governance_proposer.d.ts.map +1 -1
  31. package/dest/contracts/governance_proposer.js +455 -12
  32. package/dest/contracts/gse.d.ts +1 -1
  33. package/dest/contracts/gse.d.ts.map +1 -1
  34. package/dest/contracts/inbox.d.ts +24 -3
  35. package/dest/contracts/inbox.d.ts.map +1 -1
  36. package/dest/contracts/inbox.js +42 -2
  37. package/dest/contracts/index.d.ts +6 -3
  38. package/dest/contracts/index.d.ts.map +1 -1
  39. package/dest/contracts/index.js +5 -2
  40. package/dest/contracts/log.d.ts +13 -0
  41. package/dest/contracts/log.d.ts.map +1 -0
  42. package/dest/contracts/log.js +1 -0
  43. package/dest/contracts/multicall.d.ts +127 -12
  44. package/dest/contracts/multicall.d.ts.map +1 -1
  45. package/dest/contracts/multicall.js +201 -93
  46. package/dest/contracts/outbox.d.ts +64 -0
  47. package/dest/contracts/outbox.d.ts.map +1 -0
  48. package/dest/contracts/outbox.js +113 -0
  49. package/dest/contracts/registry.d.ts +3 -1
  50. package/dest/contracts/registry.d.ts.map +1 -1
  51. package/dest/contracts/registry.js +30 -1
  52. package/dest/contracts/rollup.d.ts +5432 -131
  53. package/dest/contracts/rollup.d.ts.map +1 -1
  54. package/dest/contracts/rollup.js +1114 -231
  55. package/dest/contracts/slasher_contract.d.ts +1 -1
  56. package/dest/contracts/slasher_contract.d.ts.map +1 -1
  57. package/dest/contracts/{tally_slashing_proposer.d.ts → slashing_proposer.d.ts} +11 -10
  58. package/dest/contracts/slashing_proposer.d.ts.map +1 -0
  59. package/dest/contracts/{tally_slashing_proposer.js → slashing_proposer.js} +21 -16
  60. package/dest/contracts/utils.d.ts +1 -1
  61. package/dest/deploy_aztec_l1_contracts.d.ts +257 -0
  62. package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -0
  63. package/dest/deploy_aztec_l1_contracts.js +410 -0
  64. package/dest/deploy_l1_contract.d.ts +68 -0
  65. package/dest/deploy_l1_contract.d.ts.map +1 -0
  66. package/dest/deploy_l1_contract.js +312 -0
  67. package/dest/eth-signer/eth-signer.d.ts +1 -1
  68. package/dest/eth-signer/index.d.ts +1 -1
  69. package/dest/forwarder_proxy.d.ts +32 -0
  70. package/dest/forwarder_proxy.d.ts.map +1 -0
  71. package/dest/forwarder_proxy.js +93 -0
  72. package/dest/generated/l1-contracts-defaults.d.ts +30 -0
  73. package/dest/generated/l1-contracts-defaults.d.ts.map +1 -0
  74. package/dest/generated/l1-contracts-defaults.js +30 -0
  75. package/dest/l1_artifacts.d.ts +19816 -16730
  76. package/dest/l1_artifacts.d.ts.map +1 -1
  77. package/dest/l1_artifacts.js +9 -24
  78. package/dest/l1_contract_addresses.d.ts +61 -65
  79. package/dest/l1_contract_addresses.d.ts.map +1 -1
  80. package/dest/l1_contract_addresses.js +99 -80
  81. package/dest/l1_reader.d.ts +7 -5
  82. package/dest/l1_reader.d.ts.map +1 -1
  83. package/dest/l1_reader.js +15 -7
  84. package/dest/l1_tx_utils/config.d.ts +11 -5
  85. package/dest/l1_tx_utils/config.d.ts.map +1 -1
  86. package/dest/l1_tx_utils/config.js +43 -7
  87. package/dest/l1_tx_utils/constants.d.ts +8 -2
  88. package/dest/l1_tx_utils/constants.d.ts.map +1 -1
  89. package/dest/l1_tx_utils/constants.js +27 -2
  90. package/dest/l1_tx_utils/factory.d.ts +18 -10
  91. package/dest/l1_tx_utils/factory.d.ts.map +1 -1
  92. package/dest/l1_tx_utils/factory.js +17 -7
  93. package/dest/l1_tx_utils/fee-strategies/index.d.ts +10 -0
  94. package/dest/l1_tx_utils/fee-strategies/index.d.ts.map +1 -0
  95. package/dest/l1_tx_utils/fee-strategies/index.js +12 -0
  96. package/dest/l1_tx_utils/fee-strategies/p75_competitive.d.ts +8 -0
  97. package/dest/l1_tx_utils/fee-strategies/p75_competitive.d.ts.map +1 -0
  98. package/dest/l1_tx_utils/fee-strategies/p75_competitive.js +129 -0
  99. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.d.ts +23 -0
  100. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.d.ts.map +1 -0
  101. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.js +191 -0
  102. package/dest/l1_tx_utils/fee-strategies/types.d.ts +51 -0
  103. package/dest/l1_tx_utils/fee-strategies/types.d.ts.map +1 -0
  104. package/dest/l1_tx_utils/fee-strategies/types.js +3 -0
  105. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts +41 -0
  106. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts.map +1 -0
  107. package/dest/l1_tx_utils/forwarder_l1_tx_utils.js +42 -0
  108. package/dest/l1_tx_utils/index-blobs.d.ts +3 -0
  109. package/dest/l1_tx_utils/index-blobs.d.ts.map +1 -0
  110. package/dest/l1_tx_utils/index-blobs.js +2 -0
  111. package/dest/l1_tx_utils/index.d.ts +4 -1
  112. package/dest/l1_tx_utils/index.d.ts.map +1 -1
  113. package/dest/l1_tx_utils/index.js +3 -0
  114. package/dest/l1_tx_utils/interfaces.d.ts +2 -2
  115. package/dest/l1_tx_utils/interfaces.d.ts.map +1 -1
  116. package/dest/l1_tx_utils/l1_fee_analyzer.d.ts +233 -0
  117. package/dest/l1_tx_utils/l1_fee_analyzer.d.ts.map +1 -0
  118. package/dest/l1_tx_utils/l1_fee_analyzer.js +506 -0
  119. package/dest/l1_tx_utils/l1_tx_utils.d.ts +26 -8
  120. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
  121. package/dest/l1_tx_utils/l1_tx_utils.js +118 -70
  122. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +19 -30
  123. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -1
  124. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +65 -168
  125. package/dest/l1_tx_utils/signer.d.ts +1 -1
  126. package/dest/l1_tx_utils/tx_delayer.d.ts +58 -0
  127. package/dest/l1_tx_utils/tx_delayer.d.ts.map +1 -0
  128. package/dest/{test → l1_tx_utils}/tx_delayer.js +70 -39
  129. package/dest/l1_tx_utils/types.d.ts +1 -1
  130. package/dest/l1_tx_utils/types.d.ts.map +1 -1
  131. package/dest/l1_tx_utils/utils.d.ts +1 -1
  132. package/dest/l1_types.d.ts +1 -1
  133. package/dest/publisher_manager.d.ts +21 -6
  134. package/dest/publisher_manager.d.ts.map +1 -1
  135. package/dest/publisher_manager.js +81 -7
  136. package/dest/queries.d.ts +14 -3
  137. package/dest/queries.d.ts.map +1 -1
  138. package/dest/queries.js +78 -9
  139. package/dest/test/chain_monitor.d.ts +48 -25
  140. package/dest/test/chain_monitor.d.ts.map +1 -1
  141. package/dest/test/chain_monitor.js +71 -38
  142. package/dest/test/eth_cheat_codes.d.ts +22 -6
  143. package/dest/test/eth_cheat_codes.d.ts.map +1 -1
  144. package/dest/test/eth_cheat_codes.js +28 -7
  145. package/dest/test/eth_cheat_codes_with_state.d.ts +1 -1
  146. package/dest/test/eth_cheat_codes_with_state.d.ts.map +1 -1
  147. package/dest/test/index.d.ts +1 -3
  148. package/dest/test/index.d.ts.map +1 -1
  149. package/dest/test/index.js +0 -2
  150. package/dest/test/rollup_cheat_codes.d.ts +46 -14
  151. package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
  152. package/dest/test/rollup_cheat_codes.js +118 -42
  153. package/dest/test/start_anvil.d.ts +25 -2
  154. package/dest/test/start_anvil.d.ts.map +1 -1
  155. package/dest/test/start_anvil.js +143 -28
  156. package/dest/test/upgrade_utils.d.ts +1 -1
  157. package/dest/test/upgrade_utils.js +2 -2
  158. package/dest/types.d.ts +57 -2
  159. package/dest/types.d.ts.map +1 -1
  160. package/dest/utils.d.ts +24 -4
  161. package/dest/utils.d.ts.map +1 -1
  162. package/dest/utils.js +112 -16
  163. package/dest/zkPassportVerifierAddress.d.ts +1 -1
  164. package/package.json +35 -17
  165. package/src/client.ts +66 -2
  166. package/src/config.ts +91 -469
  167. package/src/contracts/README.md +157 -0
  168. package/src/contracts/chain_state_override.ts +200 -0
  169. package/src/contracts/empire_base.ts +6 -5
  170. package/src/contracts/fee_asset_handler.ts +10 -7
  171. package/src/contracts/fee_asset_price_oracle.ts +285 -0
  172. package/src/contracts/governance.ts +279 -13
  173. package/src/contracts/governance_proposer.ts +74 -7
  174. package/src/contracts/inbox.ts +64 -4
  175. package/src/contracts/index.ts +5 -2
  176. package/src/contracts/log.ts +13 -0
  177. package/src/contracts/multicall.ts +243 -102
  178. package/src/contracts/outbox.ts +132 -0
  179. package/src/contracts/registry.ts +31 -1
  180. package/src/contracts/rollup.ts +819 -192
  181. package/src/contracts/{tally_slashing_proposer.ts → slashing_proposer.ts} +26 -21
  182. package/src/deploy_aztec_l1_contracts.ts +646 -0
  183. package/src/deploy_l1_contract.ts +362 -0
  184. package/src/forwarder_proxy.ts +108 -0
  185. package/src/generated/l1-contracts-defaults.ts +32 -0
  186. package/src/l1_artifacts.ts +12 -35
  187. package/src/l1_contract_addresses.ts +120 -92
  188. package/src/l1_reader.ts +25 -10
  189. package/src/l1_tx_utils/config.ts +52 -11
  190. package/src/l1_tx_utils/constants.ts +13 -2
  191. package/src/l1_tx_utils/factory.ts +31 -31
  192. package/src/l1_tx_utils/fee-strategies/index.ts +22 -0
  193. package/src/l1_tx_utils/fee-strategies/p75_competitive.ts +163 -0
  194. package/src/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.ts +245 -0
  195. package/src/l1_tx_utils/fee-strategies/types.ts +56 -0
  196. package/src/l1_tx_utils/forwarder_l1_tx_utils.ts +108 -0
  197. package/src/l1_tx_utils/index-blobs.ts +2 -0
  198. package/src/l1_tx_utils/index.ts +3 -0
  199. package/src/l1_tx_utils/interfaces.ts +1 -1
  200. package/src/l1_tx_utils/l1_fee_analyzer.ts +803 -0
  201. package/src/l1_tx_utils/l1_tx_utils.ts +123 -52
  202. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +79 -214
  203. package/src/{test → l1_tx_utils}/tx_delayer.ts +97 -58
  204. package/src/publisher_manager.ts +107 -10
  205. package/src/queries.ts +87 -10
  206. package/src/test/chain_monitor.ts +115 -51
  207. package/src/test/eth_cheat_codes.ts +25 -7
  208. package/src/test/index.ts +0 -2
  209. package/src/test/rollup_cheat_codes.ts +123 -45
  210. package/src/test/start_anvil.ts +179 -27
  211. package/src/test/upgrade_utils.ts +2 -2
  212. package/src/types.ts +62 -0
  213. package/src/utils.ts +128 -19
  214. package/dest/contracts/empire_slashing_proposer.d.ts +0 -65
  215. package/dest/contracts/empire_slashing_proposer.d.ts.map +0 -1
  216. package/dest/contracts/empire_slashing_proposer.js +0 -194
  217. package/dest/contracts/tally_slashing_proposer.d.ts.map +0 -1
  218. package/dest/deploy_l1_contracts.d.ts +0 -226
  219. package/dest/deploy_l1_contracts.d.ts.map +0 -1
  220. package/dest/deploy_l1_contracts.js +0 -1473
  221. package/dest/index.d.ts +0 -18
  222. package/dest/index.d.ts.map +0 -1
  223. package/dest/index.js +0 -17
  224. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts +0 -26
  225. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts.map +0 -1
  226. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.js +0 -26
  227. package/dest/test/delayed_tx_utils.d.ts +0 -13
  228. package/dest/test/delayed_tx_utils.d.ts.map +0 -1
  229. package/dest/test/delayed_tx_utils.js +0 -28
  230. package/dest/test/tx_delayer.d.ts +0 -36
  231. package/dest/test/tx_delayer.d.ts.map +0 -1
  232. package/src/contracts/empire_slashing_proposer.ts +0 -259
  233. package/src/deploy_l1_contracts.ts +0 -1849
  234. package/src/index.ts +0 -17
  235. package/src/l1_tx_utils/l1_tx_utils_with_blobs.ts +0 -77
  236. package/src/test/delayed_tx_utils.ts +0 -52
@@ -1,16 +1,28 @@
1
+ import { CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
2
+ import { Buffer32 } from '@aztec/foundation/buffer';
3
+ import { Fr } from '@aztec/foundation/curves/bn254';
1
4
  import { memoize } from '@aztec/foundation/decorators';
2
5
  import { EthAddress } from '@aztec/foundation/eth-address';
3
6
  import type { ViemSignature } from '@aztec/foundation/eth-signature';
7
+ import { createLogger } from '@aztec/foundation/log';
8
+ import { makeBackoff, retry } from '@aztec/foundation/retry';
9
+ import { getErrorCause } from '@aztec/foundation/types';
10
+ import { EscapeHatchAbi } from '@aztec/l1-artifacts/EscapeHatchAbi';
4
11
  import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';
5
12
  import { RollupStorage } from '@aztec/l1-artifacts/RollupStorage';
6
13
 
7
14
  import chunk from 'lodash.chunk';
8
15
  import {
9
16
  type Account,
17
+ ContractFunctionRevertedError,
10
18
  type GetContractReturnType,
11
19
  type Hex,
20
+ type Log,
21
+ RpcRequestError,
12
22
  type StateOverride,
13
23
  type WatchContractEventReturnType,
24
+ decodeErrorResult,
25
+ encodeAbiParameters,
14
26
  encodeFunctionData,
15
27
  getContract,
16
28
  hexToBigInt,
@@ -18,16 +30,16 @@ import {
18
30
  } from 'viem';
19
31
 
20
32
  import { getPublicClient } from '../client.js';
21
- import type { DeployL1ContractsReturnType } from '../deploy_l1_contracts.js';
33
+ import type { DeployAztecL1ContractsReturnType } from '../deploy_aztec_l1_contracts.js';
22
34
  import type { L1ContractAddresses } from '../l1_contract_addresses.js';
23
35
  import type { L1ReaderConfig } from '../l1_reader.js';
24
36
  import type { L1TxRequest, L1TxUtils } from '../l1_tx_utils/index.js';
25
37
  import type { ViemClient } from '../types.js';
26
38
  import { formatViemError } from '../utils.js';
27
- import { EmpireSlashingProposerContract } from './empire_slashing_proposer.js';
28
39
  import { GSEContract } from './gse.js';
40
+ import type { L1EventLog } from './log.js';
29
41
  import { SlasherContract } from './slasher_contract.js';
30
- import { TallySlashingProposerContract } from './tally_slashing_proposer.js';
42
+ import { SlashingProposerContract } from './slashing_proposer.js';
31
43
  import { checkBlockTag } from './utils.js';
32
44
 
33
45
  export type ViemCommitteeAttestation = {
@@ -49,19 +61,22 @@ export type L1RollupContractAddresses = Pick<
49
61
  | 'feeJuiceAddress'
50
62
  | 'stakingAssetAddress'
51
63
  | 'rewardDistributorAddress'
52
- | 'slashFactoryAddress'
53
64
  | 'gseAddress'
54
65
  >;
55
66
 
56
67
  export type EpochProofPublicInputArgs = {
57
68
  previousArchive: `0x${string}`;
58
69
  endArchive: `0x${string}`;
70
+ outHash: `0x${string}`;
59
71
  proverId: `0x${string}`;
60
72
  };
61
73
 
62
74
  export type ViemHeader = {
63
75
  lastArchiveRoot: `0x${string}`;
64
- contentCommitment: ViemContentCommitment;
76
+ blockHeadersHash: `0x${string}`;
77
+ blobsHash: `0x${string}`;
78
+ inHash: `0x${string}`;
79
+ outHash: `0x${string}`;
65
80
  slotNumber: bigint;
66
81
  timestamp: bigint;
67
82
  coinbase: `0x${string}`;
@@ -70,43 +85,190 @@ export type ViemHeader = {
70
85
  totalManaUsed: bigint;
71
86
  };
72
87
 
73
- export type ViemContentCommitment = {
74
- blobsHash: `0x${string}`;
75
- inHash: `0x${string}`;
76
- outHash: `0x${string}`;
77
- };
78
-
79
88
  export type ViemGasFees = {
80
89
  feePerDaGas: bigint;
81
90
  feePerL2Gas: bigint;
82
91
  };
83
92
 
84
- export type ViemStateReference = {
85
- l1ToL2MessageTree: ViemAppendOnlyTreeSnapshot;
86
- partialStateReference: ViemPartialStateReference;
93
+ /**
94
+ * Status of a validator/attester in the staking system.
95
+ * Matches the Status enum in StakingLib.sol
96
+ */
97
+ export enum AttesterStatus {
98
+ NONE = 0,
99
+ VALIDATING = 1,
100
+ ZOMBIE = 2,
101
+ EXITING = 3,
102
+ }
103
+
104
+ /**
105
+ * Fee header data for a checkpoint
106
+ */
107
+ export type FeeHeader = {
108
+ excessMana: bigint;
109
+ manaUsed: bigint;
110
+ ethPerFeeAsset: bigint;
111
+ congestionCost: bigint;
112
+ proverCost: bigint;
113
+ };
114
+
115
+ /**
116
+ * Checkpoint log data returned from the rollup contract
117
+ */
118
+ export type CheckpointLog = {
119
+ archive: Fr;
120
+ headerHash: Buffer32;
121
+ blobCommitmentsHash: Buffer32;
122
+ attestationsHash: Buffer32;
123
+ payloadDigest: Buffer32;
124
+ slotNumber: SlotNumber;
125
+ feeHeader: FeeHeader;
126
+ };
127
+
128
+ /**
129
+ * L1 fee data (base fee and blob fee)
130
+ */
131
+ export type L1FeeData = {
132
+ baseFee: bigint;
133
+ blobFee: bigint;
134
+ };
135
+
136
+ /** Field offsets within the CompressedTempCheckpointLog struct in Solidity storage. */
137
+ export enum TempCheckpointLogField {
138
+ HeaderHash = 0,
139
+ BlobCommitmentsHash = 1,
140
+ OutHash = 2,
141
+ AttestationsHash = 3,
142
+ PayloadDigest = 4,
143
+ SlotNumber = 5,
144
+ FeeHeader = 6,
145
+ }
146
+
147
+ /**
148
+ * Field-level override input for `tempCheckpointLogs[checkpointNumber]`. Covers the fields the
149
+ * `propose()` path actually reads back. `payloadDigest` is `Buffer32` because it carries an
150
+ * arbitrary `bytes32` value rather than a BN254 scalar. `slotNumber` carries the uint32 portion
151
+ * of the on-chain `CompressedSlot`.
152
+ */
153
+ export type TempCheckpointLogOverrideFields = {
154
+ headerHash?: Fr;
155
+ outHash?: Fr;
156
+ payloadDigest?: Buffer32;
157
+ slotNumber?: SlotNumber;
158
+ feeHeader?: FeeHeader;
159
+ };
160
+
161
+ /** Components of the minimum fee per mana, as returned by the L1 rollup contract. */
162
+ export type ManaMinFeeComponents = {
163
+ sequencerCost: bigint;
164
+ proverCost: bigint;
165
+ congestionCost: bigint;
166
+ congestionMultiplier: bigint;
167
+ };
168
+
169
+ /**
170
+ * Reward configuration for the rollup
171
+ */
172
+ export type RewardConfig = {
173
+ rewardDistributor: EthAddress;
174
+ sequencerBps: bigint;
175
+ booster: EthAddress;
176
+ checkpointReward: bigint;
87
177
  };
88
178
 
89
- export type ViemPartialStateReference = {
90
- noteHashTree: ViemAppendOnlyTreeSnapshot;
91
- nullifierTree: ViemAppendOnlyTreeSnapshot;
92
- publicDataTree: ViemAppendOnlyTreeSnapshot;
179
+ /**
180
+ * Exit information for a validator
181
+ */
182
+ export type Exit = {
183
+ withdrawalId: bigint;
184
+ amount: bigint;
185
+ exitableAt: bigint;
186
+ recipientOrWithdrawer: EthAddress;
187
+ isRecipient: boolean;
188
+ exists: boolean;
93
189
  };
94
190
 
95
- export type ViemAppendOnlyTreeSnapshot = {
96
- root: `0x${string}`;
97
- nextAvailableLeafIndex: number;
191
+ /**
192
+ * Attester configuration including public key and withdrawer
193
+ */
194
+ export type AttesterConfig = {
195
+ publicKey: {
196
+ x: bigint;
197
+ y: bigint;
198
+ };
199
+ withdrawer: EthAddress;
98
200
  };
99
201
 
100
- export enum SlashingProposerType {
101
- None = 0,
102
- Tally = 1,
103
- Empire = 2,
202
+ /**
203
+ * Complete view of an attester's state
204
+ */
205
+ export type AttesterView = {
206
+ status: AttesterStatus;
207
+ effectiveBalance: bigint;
208
+ exit: Exit;
209
+ config: AttesterConfig;
210
+ };
211
+
212
+ /**
213
+ * Return for a status call
214
+ */
215
+ export type RollupStatusResponse = {
216
+ provenCheckpointNumber: CheckpointNumber;
217
+ provenArchive: Fr;
218
+ pendingCheckpointNumber: CheckpointNumber;
219
+ pendingArchive: Fr;
220
+ archiveOfMyCheckpoint: Fr;
221
+ };
222
+
223
+ /** Arguments for the CheckpointProposed event. */
224
+ export type CheckpointProposedArgs = {
225
+ checkpointNumber: CheckpointNumber;
226
+ archive: Fr;
227
+ versionedBlobHashes: Buffer[];
228
+ /** Hash of attestations emitted in the CheckpointProposed event. */
229
+ attestationsHash: Buffer32;
230
+ /** Digest of the payload emitted in the CheckpointProposed event. */
231
+ payloadDigest: Buffer32;
232
+ };
233
+
234
+ /** Log type for CheckpointProposed events. */
235
+ export type CheckpointProposedLog = L1EventLog<CheckpointProposedArgs>;
236
+
237
+ const INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR = 'ValidatorSelection__InsufficientValidatorSetSize';
238
+
239
+ function isValidatorSelectionError(err: unknown, errorName: string): boolean {
240
+ return (
241
+ getErrorCause(err, ContractFunctionRevertedError)?.data?.errorName === errorName ||
242
+ decodeRpcRequestErrorName(err) === errorName
243
+ );
244
+ }
245
+
246
+ function decodeRpcRequestErrorName(err: unknown): string | undefined {
247
+ const data = getErrorCause(err, RpcRequestError)?.data;
248
+ if (!isHexString(data)) {
249
+ return undefined;
250
+ }
251
+
252
+ try {
253
+ return decodeErrorResult({ abi: RollupAbi, data }).errorName;
254
+ } catch {
255
+ return undefined;
256
+ }
257
+ }
258
+
259
+ function isHexString(value: unknown): value is Hex {
260
+ return typeof value === 'string' && value.startsWith('0x');
104
261
  }
105
262
 
106
263
  export class RollupContract {
107
264
  private readonly rollup: GetContractReturnType<typeof RollupAbi, ViemClient>;
265
+ private readonly logger = createLogger('ethereum:rollup');
108
266
 
109
267
  private static cachedStfStorageSlot: Hex | undefined;
268
+ private cachedEscapeHatch?: {
269
+ address: EthAddress;
270
+ contract: GetContractReturnType<typeof EscapeHatchAbi, ViemClient>;
271
+ };
110
272
 
111
273
  static get checkBlobStorageSlot(): bigint {
112
274
  const asString = RollupStorage.find(storage => storage.label === 'checkBlob')?.slot;
@@ -120,7 +282,7 @@ export class RollupContract {
120
282
  return (RollupContract.cachedStfStorageSlot ??= keccak256(Buffer.from('aztec.stf.storage', 'utf-8')));
121
283
  }
122
284
 
123
- static getFromL1ContractsValues(deployL1ContractsValues: DeployL1ContractsReturnType) {
285
+ static getFromL1ContractsValues(deployL1ContractsValues: DeployAztecL1ContractsReturnType) {
124
286
  const {
125
287
  l1Client,
126
288
  l1ContractAddresses: { rollupAddress },
@@ -130,7 +292,7 @@ export class RollupContract {
130
292
 
131
293
  static getFromConfig(config: L1ReaderConfig) {
132
294
  const client = getPublicClient(config);
133
- const address = config.l1Contracts.rollupAddress.toString();
295
+ const address = config.rollupAddress.toString();
134
296
  return new RollupContract(client, address);
135
297
  }
136
298
 
@@ -144,8 +306,8 @@ export class RollupContract {
144
306
  this.rollup = getContract({ address, abi: RollupAbi, client });
145
307
  }
146
308
 
147
- getGSE() {
148
- return this.rollup.read.getGSE();
309
+ async getGSE(): Promise<EthAddress> {
310
+ return EthAddress.fromString(await this.rollup.read.getGSE());
149
311
  }
150
312
 
151
313
  public get address() {
@@ -156,119 +318,122 @@ export class RollupContract {
156
318
  return this.rollup;
157
319
  }
158
320
 
159
- public async getSlashingProposer(): Promise<
160
- EmpireSlashingProposerContract | TallySlashingProposerContract | undefined
161
- > {
321
+ public async getSlashingProposer(): Promise<SlashingProposerContract | undefined> {
162
322
  const slasher = await this.getSlasherContract();
163
323
  if (!slasher) {
164
324
  return undefined;
165
325
  }
166
326
 
167
327
  const proposerAddress = await slasher.getProposer();
168
- const proposerAbi = [
169
- {
170
- type: 'function',
171
- name: 'SLASHING_PROPOSER_TYPE',
172
- inputs: [],
173
- outputs: [{ name: '', type: 'uint8', internalType: 'enum SlasherFlavor' }],
174
- stateMutability: 'view',
175
- },
176
- ] as const;
177
-
178
- const proposer = getContract({ address: proposerAddress.toString(), abi: proposerAbi, client: this.client });
179
- const proposerType = await proposer.read.SLASHING_PROPOSER_TYPE();
180
- if (proposerType === SlashingProposerType.Tally.valueOf()) {
181
- return new TallySlashingProposerContract(this.client, proposerAddress);
182
- } else if (proposerType === SlashingProposerType.Empire.valueOf()) {
183
- return new EmpireSlashingProposerContract(this.client, proposerAddress);
184
- } else {
185
- throw new Error(`Unknown slashing proposer type: ${proposerType}`);
328
+ if (proposerAddress.isZero()) {
329
+ return undefined;
186
330
  }
331
+
332
+ return new SlashingProposerContract(this.client, proposerAddress);
187
333
  }
188
334
 
189
335
  @memoize
190
- getL1StartBlock() {
336
+ getL1StartBlock(): Promise<bigint> {
191
337
  return this.rollup.read.L1_BLOCK_AT_GENESIS();
192
338
  }
193
339
 
194
340
  @memoize
195
- getL1GenesisTime() {
341
+ getL1GenesisTime(): Promise<bigint> {
196
342
  return this.rollup.read.getGenesisTime();
197
343
  }
198
344
 
199
345
  @memoize
200
- getProofSubmissionEpochs() {
201
- return this.rollup.read.getProofSubmissionEpochs();
346
+ async getProofSubmissionEpochs(): Promise<number> {
347
+ return Number(await this.rollup.read.getProofSubmissionEpochs());
202
348
  }
203
349
 
204
350
  @memoize
205
- getEpochDuration() {
206
- return this.rollup.read.getEpochDuration();
351
+ async getEpochDuration(): Promise<number> {
352
+ return Number(await this.rollup.read.getEpochDuration());
207
353
  }
208
354
 
209
355
  @memoize
210
- getSlotDuration() {
211
- return this.rollup.read.getSlotDuration();
356
+ async getSlotDuration(): Promise<number> {
357
+ return Number(await this.rollup.read.getSlotDuration());
212
358
  }
213
359
 
214
360
  @memoize
215
- getTargetCommitteeSize() {
216
- return this.rollup.read.getTargetCommitteeSize();
361
+ async getTargetCommitteeSize(): Promise<number> {
362
+ return Number(await this.rollup.read.getTargetCommitteeSize());
217
363
  }
218
364
 
219
365
  @memoize
220
- getEjectionThreshold() {
366
+ getEjectionThreshold(): Promise<bigint> {
221
367
  return this.rollup.read.getEjectionThreshold();
222
368
  }
223
369
 
224
370
  @memoize
225
- getLocalEjectionThreshold() {
371
+ getLocalEjectionThreshold(): Promise<bigint> {
226
372
  return this.rollup.read.getLocalEjectionThreshold();
227
373
  }
228
374
 
229
375
  @memoize
230
- getLagInEpochs() {
231
- return this.rollup.read.getLagInEpochs();
376
+ async getLagInEpochsForValidatorSet(): Promise<number> {
377
+ return Number(await this.rollup.read.getLagInEpochsForValidatorSet());
378
+ }
379
+
380
+ @memoize
381
+ async getLagInEpochsForRandao(): Promise<number> {
382
+ return Number(await this.rollup.read.getLagInEpochsForRandao());
232
383
  }
233
384
 
234
385
  @memoize
235
- getActivationThreshold() {
386
+ getActivationThreshold(): Promise<bigint> {
236
387
  return this.rollup.read.getActivationThreshold();
237
388
  }
238
389
 
239
390
  @memoize
240
- getExitDelay() {
241
- return this.rollup.read.getExitDelay();
391
+ async getExitDelay(): Promise<number> {
392
+ return Number(await this.rollup.read.getExitDelay());
242
393
  }
243
394
 
244
395
  @memoize
245
- getManaTarget() {
396
+ getManaTarget(): Promise<bigint> {
246
397
  return this.rollup.read.getManaTarget();
247
398
  }
248
399
 
249
400
  @memoize
250
- getProvingCostPerMana() {
401
+ getProvingCostPerMana(): Promise<bigint> {
251
402
  return this.rollup.read.getProvingCostPerManaInEth();
252
403
  }
253
404
 
254
405
  @memoize
255
- getProvingCostPerManaInFeeAsset() {
406
+ getProvingCostPerManaInFeeAsset(): Promise<bigint> {
256
407
  return this.rollup.read.getProvingCostPerManaInFeeAsset();
257
408
  }
258
409
 
259
410
  @memoize
260
- getManaLimit() {
411
+ getManaLimit(): Promise<bigint> {
261
412
  return this.rollup.read.getManaLimit();
262
413
  }
263
414
 
264
415
  @memoize
265
- getVersion() {
416
+ getVersion(): Promise<bigint> {
266
417
  return this.rollup.read.getVersion();
267
418
  }
268
419
 
269
420
  @memoize
270
- async getGenesisArchiveTreeRoot(): Promise<`0x${string}`> {
271
- return await this.rollup.read.archiveAt([0n]);
421
+ async getGenesisArchiveTreeRoot(): Promise<Fr> {
422
+ return Fr.fromString(await this.rollup.read.archiveAt([0n]));
423
+ }
424
+
425
+ @memoize
426
+ async getVkTreeRoot(): Promise<Fr> {
427
+ const slot = BigInt(RollupContract.stfStorageSlot) + 3n;
428
+ const value = await this.client.getStorageAt({ address: this.address, slot: `0x${slot.toString(16)}` });
429
+ return Fr.fromString(value ?? '0x0');
430
+ }
431
+
432
+ @memoize
433
+ async getProtocolContractsHash(): Promise<Fr> {
434
+ const slot = BigInt(RollupContract.stfStorageSlot) + 4n;
435
+ const value = await this.client.getStorageAt({ address: this.address, slot: `0x${slot.toString(16)}` });
436
+ return Fr.fromString(value ?? '0x0');
272
437
  }
273
438
 
274
439
  /**
@@ -283,44 +448,114 @@ export class RollupContract {
283
448
  slotDuration: number;
284
449
  epochDuration: number;
285
450
  proofSubmissionEpochs: number;
451
+ targetCommitteeSize: number;
452
+ rollupManaLimit: number;
286
453
  }> {
287
- const [l1StartBlock, l1GenesisTime, slotDuration, epochDuration, proofSubmissionEpochs] = await Promise.all([
454
+ const [
455
+ l1StartBlock,
456
+ l1GenesisTime,
457
+ slotDuration,
458
+ epochDuration,
459
+ proofSubmissionEpochs,
460
+ targetCommitteeSize,
461
+ rollupManaLimit,
462
+ ] = await Promise.all([
288
463
  this.getL1StartBlock(),
289
464
  this.getL1GenesisTime(),
290
465
  this.getSlotDuration(),
291
466
  this.getEpochDuration(),
292
467
  this.getProofSubmissionEpochs(),
468
+ this.getTargetCommitteeSize(),
469
+ this.getManaLimit(),
293
470
  ]);
294
471
  return {
295
472
  l1StartBlock,
296
473
  l1GenesisTime,
297
- slotDuration: Number(slotDuration),
474
+ slotDuration,
298
475
  epochDuration: Number(epochDuration),
299
476
  proofSubmissionEpochs: Number(proofSubmissionEpochs),
477
+ targetCommitteeSize,
478
+ rollupManaLimit: Number(rollupManaLimit),
300
479
  };
301
480
  }
302
481
 
303
- getSlasherAddress() {
304
- return this.rollup.read.getSlasher();
482
+ async getSlasherAddress(): Promise<EthAddress> {
483
+ return EthAddress.fromString(await this.rollup.read.getSlasher());
484
+ }
485
+
486
+ /**
487
+ * Returns the configured escape hatch contract address, or zero if disabled.
488
+ */
489
+ async getEscapeHatchAddress(): Promise<EthAddress> {
490
+ return EthAddress.fromString(await this.rollup.read.getEscapeHatch());
491
+ }
492
+
493
+ private async getEscapeHatchContract(): Promise<
494
+ GetContractReturnType<typeof EscapeHatchAbi, ViemClient> | undefined
495
+ > {
496
+ const escapeHatchAddress = await this.getEscapeHatchAddress();
497
+ if (escapeHatchAddress.isZero()) {
498
+ return undefined;
499
+ }
500
+
501
+ // Cache the viem contract wrapper since it will be used frequently.
502
+ if (!this.cachedEscapeHatch || !this.cachedEscapeHatch.address.equals(escapeHatchAddress)) {
503
+ this.cachedEscapeHatch = {
504
+ address: escapeHatchAddress,
505
+ contract: getContract({
506
+ address: escapeHatchAddress.toString(),
507
+ abi: EscapeHatchAbi,
508
+ client: this.client,
509
+ }),
510
+ };
511
+ }
512
+
513
+ return this.cachedEscapeHatch.contract;
514
+ }
515
+
516
+ /**
517
+ * Returns whether the escape hatch is open for the given epoch.
518
+ * If escape hatch is not configured, returns false.
519
+ *
520
+ * This function is intentionally defensive: any failure to query the escape hatch
521
+ * (RPC issues, transient errors, etc.) is treated as "closed" to avoid callers
522
+ * needing to sprinkle try/catch everywhere.
523
+ */
524
+ async isEscapeHatchOpen(epoch: EpochNumber): Promise<boolean> {
525
+ try {
526
+ const escapeHatch = await this.getEscapeHatchContract();
527
+ if (!escapeHatch) {
528
+ return false;
529
+ }
530
+
531
+ const [isOpen] = await escapeHatch.read.isHatchOpen([BigInt(epoch)]);
532
+ return isOpen;
533
+ } catch (err) {
534
+ this.logger.warn('isEscapeHatchOpen failed (treating as closed); RPC or contract error may cause liveness risk', {
535
+ epoch: Number(epoch),
536
+ error: err,
537
+ });
538
+ return false;
539
+ }
305
540
  }
306
541
 
307
542
  /**
308
543
  * Returns a SlasherContract instance for interacting with the slasher contract.
309
544
  */
310
545
  async getSlasherContract(): Promise<SlasherContract | undefined> {
311
- const slasherAddress = EthAddress.fromString(await this.getSlasherAddress());
546
+ const slasherAddress = await this.getSlasherAddress();
312
547
  if (slasherAddress.isZero()) {
313
548
  return undefined;
314
549
  }
315
550
  return new SlasherContract(this.client, slasherAddress);
316
551
  }
317
552
 
318
- getOwner() {
319
- return this.rollup.read.owner();
553
+ async getOwner(): Promise<EthAddress> {
554
+ return EthAddress.fromString(await this.rollup.read.owner());
320
555
  }
321
556
 
322
- getActiveAttesterCount() {
323
- return this.rollup.read.getActiveAttesterCount();
557
+ async getActiveAttesterCount(): Promise<number> {
558
+ return Number(await this.rollup.read.getActiveAttesterCount());
324
559
  }
325
560
 
326
561
  public async getSlashingProposerAddress() {
@@ -331,31 +566,50 @@ export class RollupContract {
331
566
  return await slasher.getProposer();
332
567
  }
333
568
 
334
- getBlockReward() {
335
- return this.rollup.read.getBlockReward();
569
+ getCheckpointReward(): Promise<bigint> {
570
+ return this.rollup.read.getCheckpointReward();
336
571
  }
337
572
 
338
- getBlockNumber() {
339
- return this.rollup.read.getPendingBlockNumber();
573
+ async getCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
574
+ await checkBlockTag(options?.blockNumber, this.client);
575
+ return CheckpointNumber.fromBigInt(await this.rollup.read.getPendingCheckpointNumber(options));
340
576
  }
341
577
 
342
- getProvenBlockNumber() {
343
- return this.rollup.read.getProvenBlockNumber();
578
+ async getProvenCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
579
+ await checkBlockTag(options?.blockNumber, this.client);
580
+ return CheckpointNumber.fromBigInt(await this.rollup.read.getProvenCheckpointNumber(options));
344
581
  }
345
582
 
346
- getSlotNumber() {
347
- return this.rollup.read.getCurrentSlot();
583
+ async getSlotNumber(options?: { blockNumber?: bigint }): Promise<SlotNumber> {
584
+ await checkBlockTag(options?.blockNumber, this.client);
585
+ return SlotNumber.fromBigInt(await this.rollup.read.getCurrentSlot(options));
348
586
  }
349
587
 
350
- getL1FeesAt(timestamp: bigint) {
351
- return this.rollup.read.getL1FeesAt([timestamp]);
588
+ async getL1FeesAt(timestamp: bigint, options?: { blockNumber?: bigint }): Promise<L1FeeData> {
589
+ await checkBlockTag(options?.blockNumber, this.client);
590
+ const result = await this.rollup.read.getL1FeesAt([timestamp], options);
591
+ return {
592
+ baseFee: result.baseFee,
593
+ blobFee: result.blobFee,
594
+ };
595
+ }
596
+
597
+ async getFeeHeader(checkpointNumber: bigint): Promise<FeeHeader> {
598
+ const result = await this.rollup.read.getFeeHeader([checkpointNumber]);
599
+ return {
600
+ excessMana: result.excessMana,
601
+ manaUsed: result.manaUsed,
602
+ ethPerFeeAsset: result.ethPerFeeAsset,
603
+ congestionCost: result.congestionCost,
604
+ proverCost: result.proverCost,
605
+ };
352
606
  }
353
607
 
354
- getFeeAssetPerEth() {
355
- return this.rollup.read.getFeeAssetPerEth();
608
+ getEthPerFeeAsset(): Promise<bigint> {
609
+ return this.rollup.read.getEthPerFeeAsset();
356
610
  }
357
611
 
358
- async getCommitteeAt(timestamp: bigint): Promise<readonly `0x${string}`[] | undefined> {
612
+ async getCommitteeAt(timestamp: bigint): Promise<EthAddress[] | undefined> {
359
613
  const { result } = await this.client
360
614
  .simulateContract({
361
615
  address: this.address,
@@ -364,28 +618,28 @@ export class RollupContract {
364
618
  args: [timestamp],
365
619
  })
366
620
  .catch(e => {
367
- if (e instanceof Error && e.message.includes('ValidatorSelection__InsufficientValidatorSetSize')) {
621
+ if (isValidatorSelectionError(e, INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR)) {
368
622
  return { result: undefined };
369
623
  }
370
624
  throw e;
371
625
  });
372
626
 
373
- return result;
627
+ return result ? result.map(addr => EthAddress.fromString(addr)) : undefined;
374
628
  }
375
629
 
376
- getSampleSeedAt(timestamp: bigint) {
377
- return this.rollup.read.getSampleSeedAt([timestamp]);
630
+ async getSampleSeedAt(timestamp: bigint): Promise<Buffer32> {
631
+ return Buffer32.fromBigInt(await this.rollup.read.getSampleSeedAt([timestamp]));
378
632
  }
379
633
 
380
- getCurrentSampleSeed() {
381
- return this.rollup.read.getCurrentSampleSeed();
634
+ async getCurrentSampleSeed(): Promise<Buffer32> {
635
+ return Buffer32.fromBigInt(await this.rollup.read.getCurrentSampleSeed());
382
636
  }
383
637
 
384
- getCurrentEpoch() {
385
- return this.rollup.read.getCurrentEpoch();
638
+ async getCurrentEpoch(): Promise<EpochNumber> {
639
+ return EpochNumber.fromBigInt(await this.rollup.read.getCurrentEpoch());
386
640
  }
387
641
 
388
- async getCurrentEpochCommittee(): Promise<readonly `0x${string}`[] | undefined> {
642
+ async getCurrentEpochCommittee(): Promise<EthAddress[] | undefined> {
389
643
  const { result } = await this.client
390
644
  .simulateContract({
391
645
  address: this.address,
@@ -394,16 +648,16 @@ export class RollupContract {
394
648
  args: [],
395
649
  })
396
650
  .catch(e => {
397
- if (e instanceof Error && e.message.includes('ValidatorSelection__InsufficientValidatorSetSize')) {
651
+ if (isValidatorSelectionError(e, INSUFFICIENT_VALIDATOR_SET_SIZE_ERROR)) {
398
652
  return { result: undefined };
399
653
  }
400
654
  throw e;
401
655
  });
402
656
 
403
- return result;
657
+ return result ? result.map(addr => EthAddress.fromString(addr)) : undefined;
404
658
  }
405
659
 
406
- async getCurrentProposer() {
660
+ async getCurrentProposer(): Promise<EthAddress> {
407
661
  const { result } = await this.client.simulateContract({
408
662
  address: this.address,
409
663
  abi: RollupAbi,
@@ -411,10 +665,10 @@ export class RollupContract {
411
665
  args: [],
412
666
  });
413
667
 
414
- return result;
668
+ return EthAddress.fromString(result);
415
669
  }
416
670
 
417
- async getProposerAt(timestamp: bigint) {
671
+ async getProposerAt(timestamp: bigint): Promise<EthAddress> {
418
672
  const { result } = await this.client.simulateContract({
419
673
  address: this.address,
420
674
  abi: RollupAbi,
@@ -422,39 +676,98 @@ export class RollupContract {
422
676
  args: [timestamp],
423
677
  });
424
678
 
425
- return result;
679
+ return EthAddress.fromString(result);
426
680
  }
427
681
 
428
- getBlock(blockNumber: bigint | number) {
429
- return this.rollup.read.getBlock([BigInt(blockNumber)]);
682
+ async getCheckpoint(checkpointNumber: CheckpointNumber, options?: { blockNumber?: bigint }): Promise<CheckpointLog> {
683
+ await checkBlockTag(options?.blockNumber, this.client);
684
+ const result = await this.rollup.read.getCheckpoint([BigInt(checkpointNumber)], options);
685
+ return {
686
+ archive: Fr.fromString(result.archive),
687
+ headerHash: Buffer32.fromString(result.headerHash),
688
+ blobCommitmentsHash: Buffer32.fromString(result.blobCommitmentsHash),
689
+ attestationsHash: Buffer32.fromString(result.attestationsHash),
690
+ payloadDigest: Buffer32.fromString(result.payloadDigest),
691
+ slotNumber: SlotNumber.fromBigInt(result.slotNumber),
692
+ feeHeader: {
693
+ excessMana: result.feeHeader.excessMana,
694
+ manaUsed: result.feeHeader.manaUsed,
695
+ ethPerFeeAsset: result.feeHeader.ethPerFeeAsset,
696
+ congestionCost: result.feeHeader.congestionCost,
697
+ proverCost: result.feeHeader.proverCost,
698
+ },
699
+ };
430
700
  }
431
701
 
432
- getTips() {
433
- return this.rollup.read.getTips();
702
+ /** Returns the pending checkpoint from the rollup contract */
703
+ getPendingCheckpoint() {
704
+ // We retry because of race conditions during prunes: we may get a pending checkpoint number which is immediately
705
+ // reorged out due to a prune happening, causing the subsequent getCheckpoint call to fail. So we try again in that case.
706
+ return retry(
707
+ async () => {
708
+ const pendingCheckpointNumber = await this.getCheckpointNumber();
709
+ const pendingCheckpoint = await this.getCheckpoint(pendingCheckpointNumber);
710
+ return pendingCheckpoint;
711
+ },
712
+ 'getting pending checkpoint',
713
+ makeBackoff([0.5, 0.5, 0.5]),
714
+ );
434
715
  }
435
716
 
436
- getTimestampForSlot(slot: bigint) {
437
- return this.rollup.read.getTimestampForSlot([slot]);
717
+ /**
718
+ * Returns the effective pending checkpoint, accounting for potential prunes.
719
+ * When a prune can happen, the L1 contract uses the proven checkpoint instead of the pending one.
720
+ * This mirrors the behavior of getEffectivePendingCheckpointNumber in STFLib.sol.
721
+ * @param atTimestamp - The timestamp to evaluate pruneability at. Defaults to the current L1 block timestamp.
722
+ * @param options - Optional L1 block number to pin the queries to.
723
+ */
724
+ getEffectivePendingCheckpoint(atTimestamp?: bigint, options?: { blockNumber?: bigint }) {
725
+ return retry(
726
+ async () => {
727
+ const timestamp = atTimestamp ?? (await this.client.getBlock()).timestamp;
728
+ const canPrune = await this.canPruneAtTime(timestamp, options);
729
+ if (canPrune) {
730
+ const provenCheckpointNumber = await this.getProvenCheckpointNumber(options);
731
+ return await this.getCheckpoint(provenCheckpointNumber, options);
732
+ }
733
+ const pendingCheckpointNumber = await this.getCheckpointNumber(options);
734
+ return await this.getCheckpoint(pendingCheckpointNumber, options);
735
+ },
736
+ 'getting effective pending checkpoint',
737
+ makeBackoff([0.5, 0.5, 0.5]),
738
+ );
739
+ }
740
+
741
+ async getTips(): Promise<{ pending: CheckpointNumber; proven: CheckpointNumber }> {
742
+ const { pending, proven } = await this.rollup.read.getTips();
743
+ return {
744
+ pending: CheckpointNumber.fromBigInt(pending),
745
+ proven: CheckpointNumber.fromBigInt(proven),
746
+ };
438
747
  }
439
748
 
440
- getEntryQueueLength() {
441
- return this.rollup.read.getEntryQueueLength();
749
+ getTimestampForSlot(slot: SlotNumber): Promise<bigint> {
750
+ return this.rollup.read.getTimestampForSlot([BigInt(slot)]);
442
751
  }
443
752
 
444
- getAvailableValidatorFlushes() {
445
- return this.rollup.read.getAvailableValidatorFlushes();
753
+ async getEntryQueueLength(): Promise<number> {
754
+ return Number(await this.rollup.read.getEntryQueueLength());
446
755
  }
447
756
 
448
- getNextFlushableEpoch() {
449
- return this.rollup.read.getNextFlushableEpoch();
757
+ async getAvailableValidatorFlushes(): Promise<number> {
758
+ return Number(await this.rollup.read.getAvailableValidatorFlushes());
450
759
  }
451
760
 
452
- getCurrentEpochNumber(): Promise<bigint> {
453
- return this.rollup.read.getCurrentEpoch();
761
+ async getNextFlushableEpoch(): Promise<EpochNumber> {
762
+ return EpochNumber.fromBigInt(await this.rollup.read.getNextFlushableEpoch());
454
763
  }
455
764
 
456
- getEpochNumberForBlock(blockNumber: bigint) {
457
- return this.rollup.read.getEpochForBlock([BigInt(blockNumber)]);
765
+ async getCurrentEpochNumber(): Promise<EpochNumber> {
766
+ return EpochNumber.fromBigInt(await this.rollup.read.getCurrentEpoch());
767
+ }
768
+
769
+ async getEpochNumberForCheckpoint(checkpointNumber: CheckpointNumber): Promise<EpochNumber> {
770
+ return EpochNumber.fromBigInt(await this.rollup.read.getEpochForCheckpoint([BigInt(checkpointNumber)]));
458
771
  }
459
772
 
460
773
  async getRollupAddresses(): Promise<L1RollupContractAddresses> {
@@ -494,14 +807,15 @@ export class RollupContract {
494
807
  return EthAddress.fromString(await this.rollup.read.getFeeAssetPortal());
495
808
  }
496
809
 
497
- public async getEpochNumberForSlotNumber(slotNumber: bigint): Promise<bigint> {
498
- return await this.rollup.read.getEpochAtSlot([slotNumber]);
810
+ public async getEpochNumberForSlotNumber(slotNumber: SlotNumber): Promise<EpochNumber> {
811
+ return EpochNumber.fromBigInt(await this.rollup.read.getEpochAtSlot([BigInt(slotNumber)]));
499
812
  }
500
813
 
501
- getEpochProofPublicInputs(
814
+ async getEpochProofPublicInputs(
502
815
  args: readonly [bigint, bigint, EpochProofPublicInputArgs, readonly `0x${string}`[], `0x${string}`],
503
- ) {
504
- return this.rollup.read.getEpochProofPublicInputs(args);
816
+ ): Promise<Fr[]> {
817
+ const result = await this.rollup.read.getEpochProofPublicInputs(args);
818
+ return result.map(Fr.fromString);
505
819
  }
506
820
 
507
821
  public async validateHeader(
@@ -538,53 +852,68 @@ export class RollupContract {
538
852
  * @dev Throws if unable to propose
539
853
  *
540
854
  * @param archive - The archive that we expect to be current state
541
- * @return [slot, blockNumber] - If you can propose, the L2 slot number and L2 block number of the next Ethereum block,
855
+ * @return [slot, checkpointNumber, timeOfNextL1Slot] - If you can propose, the L2 slot number, checkpoint number and
856
+ * timestamp of the next L1 block
542
857
  * @throws otherwise
543
858
  */
544
- public async canProposeAtNextEthBlock(
859
+ public async canProposeAt(
545
860
  archive: Buffer,
546
861
  account: `0x${string}` | Account,
547
- slotDuration: bigint | number,
548
- opts: { forcePendingBlockNumber?: number } = {},
549
- ): Promise<{ slot: bigint; blockNumber: bigint; timeOfNextL1Slot: bigint }> {
550
- if (typeof slotDuration === 'number') {
551
- slotDuration = BigInt(slotDuration);
552
- }
553
- const latestBlock = await this.client.getBlock();
554
- const timeOfNextL1Slot = latestBlock.timestamp + slotDuration;
862
+ timestamp: bigint,
863
+ stateOverride: StateOverride = [],
864
+ ): Promise<{ slot: SlotNumber; checkpointNumber: CheckpointNumber; timeOfNextL1Slot: bigint }> {
865
+ const timeOfNextL1Slot = timestamp;
555
866
  const who = typeof account === 'string' ? account : account.address;
556
867
 
557
868
  try {
558
869
  const {
559
- result: [slot, blockNumber],
870
+ result: [slot, checkpointNumber],
560
871
  } = await this.client.simulateContract({
561
872
  address: this.address,
562
873
  abi: RollupAbi,
563
874
  functionName: 'canProposeAtTime',
564
875
  args: [timeOfNextL1Slot, `0x${archive.toString('hex')}`, who],
565
876
  account,
566
- stateOverride: await this.makePendingBlockNumberOverride(opts.forcePendingBlockNumber),
877
+ stateOverride,
567
878
  });
568
879
 
569
- return { slot, blockNumber, timeOfNextL1Slot };
880
+ return {
881
+ slot: SlotNumber.fromBigInt(slot),
882
+ checkpointNumber: CheckpointNumber.fromBigInt(checkpointNumber),
883
+ timeOfNextL1Slot,
884
+ };
570
885
  } catch (err: unknown) {
571
886
  throw formatViemError(err);
572
887
  }
573
888
  }
574
889
 
575
890
  /**
576
- * Returns a state override that sets the pending block number to the specified value. Useful for simulations.
577
- * Requires querying the current state of the contract to get the current proven block number, as they are both
578
- * stored in the same slot. If the argument is undefined, it returns an empty override.
891
+ * Returns a state override that sets the pending and/or proven checkpoint numbers. Useful for simulations.
892
+ * Both values share a single storage slot (pending in the upper 128 bits, proven in the lower 128 bits), so
893
+ * a single combined override is emitted to avoid the second state-diff clobbering the first. The current live
894
+ * value is read once to preserve any half not being overridden. Returns an empty override if neither is set.
895
+ *
896
+ * Throws if the resulting `proven > pending`, which would crash the simulation: `STFLib.canPruneAtTime` calls
897
+ * `getEpochForCheckpoint(proven + 1)` whenever `pending != proven`, and that asserts `_n <= tips.pending`.
579
898
  */
580
- public async makePendingBlockNumberOverride(forcePendingBlockNumber: number | undefined): Promise<StateOverride> {
581
- if (forcePendingBlockNumber === undefined) {
899
+ public async makeChainTipsOverride(override: {
900
+ pending?: CheckpointNumber;
901
+ proven?: CheckpointNumber;
902
+ }): Promise<StateOverride> {
903
+ if (override.pending === undefined && override.proven === undefined) {
582
904
  return [];
583
905
  }
584
906
  const slot = RollupContract.stfStorageSlot;
585
907
  const currentValue = await this.client.getStorageAt({ address: this.address, slot });
586
- const currentProvenBlockNumber = currentValue ? hexToBigInt(currentValue) & ((1n << 128n) - 1n) : 0n;
587
- const newValue = (BigInt(forcePendingBlockNumber) << 128n) | currentProvenBlockNumber;
908
+ const currentRaw = currentValue ? hexToBigInt(currentValue) : 0n;
909
+ const currentPending = currentRaw >> 128n;
910
+ const currentProven = currentRaw & ((1n << 128n) - 1n);
911
+ const newPending = override.pending !== undefined ? BigInt(override.pending) : currentPending;
912
+ const newProven = override.proven !== undefined ? BigInt(override.proven) : currentProven;
913
+ if (newProven > newPending) {
914
+ throw new Error(`Invalid chain tips override: proven (${newProven}) > pending (${newPending})`);
915
+ }
916
+ const newValue = (newPending << 128n) | newProven;
588
917
  return [
589
918
  {
590
919
  address: this.address,
@@ -593,20 +922,183 @@ export class RollupContract {
593
922
  ];
594
923
  }
595
924
 
925
+ /**
926
+ * Returns a state override that patches `tempCheckpointLogs[checkpointNumber]` with every field that
927
+ * the L1 contract reads during `propose()` for the checkpoint that builds on top of it (proposer
928
+ * pipelining simulation). Mirrors the writes done by `ProposeLib.addTempCheckpointLog`.
929
+ *
930
+ * `blobCommitmentsHash` and `attestationsHash` are intentionally not exposed here — the propose path
931
+ * never asserts against them, so leaving them at storage zero is harmless.
932
+ */
933
+ public async makeTempCheckpointLogOverride(
934
+ checkpointNumber: CheckpointNumber,
935
+ fields: TempCheckpointLogOverrideFields,
936
+ ): Promise<StateOverride> {
937
+ const constants = await this.getRollupConstants();
938
+ const slotAt = (field: TempCheckpointLogField) =>
939
+ `0x${this.computeTempCheckpointLogStorageSlot(checkpointNumber, field, constants).toString(16).padStart(64, '0')}` as const;
940
+ const word = (v: bigint) => `0x${v.toString(16).padStart(64, '0')}` as const;
941
+
942
+ const stateDiff: { slot: `0x${string}`; value: `0x${string}` }[] = [];
943
+
944
+ if (fields.headerHash) {
945
+ stateDiff.push({ slot: slotAt(TempCheckpointLogField.HeaderHash), value: fields.headerHash.toString() });
946
+ }
947
+ if (fields.outHash) {
948
+ stateDiff.push({ slot: slotAt(TempCheckpointLogField.OutHash), value: fields.outHash.toString() });
949
+ }
950
+ if (fields.payloadDigest) {
951
+ stateDiff.push({
952
+ slot: slotAt(TempCheckpointLogField.PayloadDigest),
953
+ value: fields.payloadDigest.toString() as `0x${string}`,
954
+ });
955
+ }
956
+ if (fields.slotNumber !== undefined) {
957
+ // CompressedSlot is uint32 on L1 (SafeCast.toUint32 reverts on overflow). Match that behavior here
958
+ // so a malformed override surfaces immediately rather than silently truncating into a wrong slot.
959
+ const slotNumber = BigInt(fields.slotNumber);
960
+ if (slotNumber < 0n || slotNumber > 0xffffffffn) {
961
+ throw new Error(`slotNumber ${slotNumber} does not fit in uint32`);
962
+ }
963
+ stateDiff.push({ slot: slotAt(TempCheckpointLogField.SlotNumber), value: word(slotNumber) });
964
+ }
965
+ if (fields.feeHeader) {
966
+ stateDiff.push({
967
+ slot: slotAt(TempCheckpointLogField.FeeHeader),
968
+ value: word(RollupContract.compressFeeHeader(fields.feeHeader)),
969
+ });
970
+ }
971
+
972
+ if (stateDiff.length === 0) {
973
+ return [];
974
+ }
975
+ return [{ address: this.address, stateDiff }];
976
+ }
977
+
978
+ /**
979
+ * Returns a state override that sets archives[checkpointNumber] to the given archive value.
980
+ * Used when simulating a canProposeAtTime call where the local archive differs from L1
981
+ * (e.g. pipelining where the parent checkpoint hasn't landed on L1 yet).
982
+ */
983
+ public makeArchiveOverride(checkpointNumber: CheckpointNumber, archive: Fr): StateOverride {
984
+ const archivesMappingBase = hexToBigInt(RollupContract.stfStorageSlot) + 1n;
985
+ const archiveSlot = hexToBigInt(
986
+ keccak256(
987
+ encodeAbiParameters(
988
+ [{ type: 'uint256' }, { type: 'uint256' }],
989
+ [BigInt(checkpointNumber), archivesMappingBase],
990
+ ),
991
+ ),
992
+ );
993
+ return [
994
+ {
995
+ address: this.address,
996
+ stateDiff: [
997
+ {
998
+ slot: `0x${archiveSlot.toString(16).padStart(64, '0')}`,
999
+ value: archive.toString(),
1000
+ },
1001
+ ],
1002
+ },
1003
+ ];
1004
+ }
1005
+
1006
+ /** Merges multiple StateOverride arrays, combining stateDiff entries for the same address. */
1007
+ public static mergeStateOverrides(...overrides: StateOverride[]): StateOverride {
1008
+ type StateDiffEntry = { slot: `0x${string}`; value: `0x${string}` };
1009
+ const byAddress = new Map<string, { address: `0x${string}`; balance?: bigint; stateDiff: StateDiffEntry[] }>();
1010
+ for (const override of overrides) {
1011
+ for (const entry of override) {
1012
+ const key = entry.address.toLowerCase();
1013
+ const existing = byAddress.get(key);
1014
+ if (existing) {
1015
+ existing.stateDiff.push(...(entry.stateDiff ?? []));
1016
+ if (entry.balance !== undefined) {
1017
+ existing.balance = entry.balance;
1018
+ }
1019
+ } else {
1020
+ byAddress.set(key, {
1021
+ address: entry.address,
1022
+ balance: entry.balance,
1023
+ stateDiff: [...(entry.stateDiff ?? [])],
1024
+ });
1025
+ }
1026
+ }
1027
+ }
1028
+ return [...byAddress.values()];
1029
+ }
1030
+
1031
+ /** Compresses a FeeHeader into a uint256 matching FeeHeaderLib.compress() in FeeStructs.sol. */
1032
+ public static compressFeeHeader(feeHeader: FeeHeader): bigint {
1033
+ const MASK_48_BITS = (1n << 48n) - 1n;
1034
+ const MASK_64_BITS = (1n << 64n) - 1n;
1035
+ const MASK_63_BITS = (1n << 63n) - 1n;
1036
+
1037
+ let value = BigInt(feeHeader.manaUsed) & ((1n << 32n) - 1n); // bits [0:31]
1038
+ value |= (feeHeader.excessMana < MASK_48_BITS ? feeHeader.excessMana : MASK_48_BITS) << 32n; // bits [32:79]
1039
+ value |= (BigInt(feeHeader.ethPerFeeAsset) & MASK_48_BITS) << 80n; // bits [80:127]
1040
+ value |= (feeHeader.congestionCost < MASK_64_BITS ? feeHeader.congestionCost : MASK_64_BITS) << 128n; // bits [128:191]
1041
+ value |= (feeHeader.proverCost < MASK_63_BITS ? feeHeader.proverCost : MASK_63_BITS) << 192n; // bits [192:254]
1042
+ value |= 1n << 255n; // preheat flag
1043
+ return value;
1044
+ }
1045
+
1046
+ /** Computes the fee header for a child checkpoint given parent fee header and child data.
1047
+ * Must stay in sync with Solidity FeeLib.sol (computeNewEthPerFeeAsset, clampedAdd). */
1048
+ public static computeChildFeeHeader(
1049
+ parentFeeHeader: FeeHeader,
1050
+ childManaUsed: bigint,
1051
+ feeAssetPriceModifier: bigint,
1052
+ manaTarget: bigint,
1053
+ ): FeeHeader {
1054
+ const MIN_ETH_PER_FEE_ASSET = 100n;
1055
+ const MAX_ETH_PER_FEE_ASSET = 100_000_000_000_000n; // 1e14, matches FeeLib.sol
1056
+
1057
+ // excessMana = clampedAdd(parent.excessMana + parent.manaUsed, -manaTarget)
1058
+ const sum = parentFeeHeader.excessMana + parentFeeHeader.manaUsed;
1059
+ const excessMana = sum > manaTarget ? sum - manaTarget : 0n;
1060
+
1061
+ // ethPerFeeAsset = computeNewEthPerFeeAsset(max(parent.ethPerFeeAsset, MIN), modifier)
1062
+ const parentPrice =
1063
+ parentFeeHeader.ethPerFeeAsset > MIN_ETH_PER_FEE_ASSET ? parentFeeHeader.ethPerFeeAsset : MIN_ETH_PER_FEE_ASSET;
1064
+ let newPrice: bigint;
1065
+ if (feeAssetPriceModifier >= 0n) {
1066
+ newPrice = (parentPrice * (10_000n + feeAssetPriceModifier)) / 10_000n;
1067
+ } else {
1068
+ const absMod = -feeAssetPriceModifier;
1069
+ newPrice = (parentPrice * (10_000n - absMod)) / 10_000n;
1070
+ }
1071
+ if (newPrice < MIN_ETH_PER_FEE_ASSET) {
1072
+ newPrice = MIN_ETH_PER_FEE_ASSET;
1073
+ }
1074
+ if (newPrice > MAX_ETH_PER_FEE_ASSET) {
1075
+ newPrice = MAX_ETH_PER_FEE_ASSET;
1076
+ }
1077
+
1078
+ return {
1079
+ excessMana,
1080
+ manaUsed: childManaUsed,
1081
+ ethPerFeeAsset: newPrice,
1082
+ congestionCost: 0n,
1083
+ proverCost: 0n,
1084
+ };
1085
+ }
1086
+
596
1087
  /** Creates a request to Rollup#invalidateBadAttestation to be simulated or sent */
597
1088
  public buildInvalidateBadAttestationRequest(
598
- blockNumber: number,
1089
+ checkpointNumber: CheckpointNumber,
599
1090
  attestationsAndSigners: ViemCommitteeAttestations,
600
1091
  committee: EthAddress[],
601
1092
  invalidIndex: number,
602
1093
  ): L1TxRequest {
603
1094
  return {
604
1095
  to: this.address,
1096
+ abi: RollupAbi,
605
1097
  data: encodeFunctionData({
606
1098
  abi: RollupAbi,
607
1099
  functionName: 'invalidateBadAttestation',
608
1100
  args: [
609
- BigInt(blockNumber),
1101
+ BigInt(checkpointNumber),
610
1102
  attestationsAndSigners,
611
1103
  committee.map(addr => addr.toString()),
612
1104
  BigInt(invalidIndex),
@@ -617,112 +1109,156 @@ export class RollupContract {
617
1109
 
618
1110
  /** Creates a request to Rollup#invalidateInsufficientAttestations to be simulated or sent */
619
1111
  public buildInvalidateInsufficientAttestationsRequest(
620
- blockNumber: number,
1112
+ checkpointNumber: CheckpointNumber,
621
1113
  attestationsAndSigners: ViemCommitteeAttestations,
622
1114
  committee: EthAddress[],
623
1115
  ): L1TxRequest {
624
1116
  return {
625
1117
  to: this.address,
1118
+ abi: RollupAbi,
626
1119
  data: encodeFunctionData({
627
1120
  abi: RollupAbi,
628
1121
  functionName: 'invalidateInsufficientAttestations',
629
- args: [BigInt(blockNumber), attestationsAndSigners, committee.map(addr => addr.toString())],
1122
+ args: [BigInt(checkpointNumber), attestationsAndSigners, committee.map(addr => addr.toString())],
630
1123
  }),
631
1124
  };
632
1125
  }
633
1126
 
634
1127
  /** Calls getHasSubmitted directly. Returns whether the given prover has submitted a proof with the given length for the given epoch. */
635
- public getHasSubmittedProof(epochNumber: number, numberOfBlocksInEpoch: number, prover: Hex | EthAddress) {
1128
+ public getHasSubmittedProof(epochNumber: EpochNumber, numberOfCheckpointsInEpoch: number, prover: Hex | EthAddress) {
636
1129
  if (prover instanceof EthAddress) {
637
1130
  prover = prover.toString();
638
1131
  }
639
- return this.rollup.read.getHasSubmitted([BigInt(epochNumber), BigInt(numberOfBlocksInEpoch), prover]);
1132
+ return this.rollup.read.getHasSubmitted([BigInt(epochNumber), BigInt(numberOfCheckpointsInEpoch), prover]);
640
1133
  }
641
1134
 
642
- getManaBaseFeeAt(timestamp: bigint, inFeeAsset: boolean) {
643
- return this.rollup.read.getManaBaseFeeAt([timestamp, inFeeAsset]);
1135
+ getManaMinFeeAt(timestamp: bigint, inFeeAsset: boolean, stateOverride?: StateOverride): Promise<bigint> {
1136
+ return this.rollup.read.getManaMinFeeAt([timestamp, inFeeAsset], { stateOverride });
644
1137
  }
645
1138
 
646
- getSlotAt(timestamp: bigint) {
647
- return this.rollup.read.getSlotAt([timestamp]);
1139
+ async getManaMinFeeComponentsAt(timestamp: bigint, inFeeAsset: boolean): Promise<ManaMinFeeComponents> {
1140
+ const result = await this.rollup.read.getManaMinFeeComponentsAt([timestamp, inFeeAsset]);
1141
+ return {
1142
+ sequencerCost: result.sequencerCost,
1143
+ proverCost: result.proverCost,
1144
+ congestionCost: result.congestionCost,
1145
+ congestionMultiplier: result.congestionMultiplier,
1146
+ };
648
1147
  }
649
1148
 
650
- async status(blockNumber: bigint, options?: { blockNumber?: bigint }) {
1149
+ async getSlotAt(timestamp: bigint): Promise<SlotNumber> {
1150
+ return SlotNumber.fromBigInt(await this.rollup.read.getSlotAt([timestamp]));
1151
+ }
1152
+
1153
+ async status(checkpointNumber: CheckpointNumber, options?: { blockNumber?: bigint }): Promise<RollupStatusResponse> {
651
1154
  await checkBlockTag(options?.blockNumber, this.client);
652
- return this.rollup.read.status([blockNumber], options);
1155
+ const result = await this.rollup.read.status([BigInt(checkpointNumber)], options);
1156
+ return {
1157
+ provenCheckpointNumber: CheckpointNumber.fromBigInt(result[0]),
1158
+ provenArchive: Fr.fromString(result[1]),
1159
+ pendingCheckpointNumber: CheckpointNumber.fromBigInt(result[2]),
1160
+ pendingArchive: Fr.fromString(result[3]),
1161
+ archiveOfMyCheckpoint: Fr.fromString(result[4]),
1162
+ };
653
1163
  }
654
1164
 
655
- async canPruneAtTime(timestamp: bigint, options?: { blockNumber?: bigint }) {
1165
+ async canPruneAtTime(timestamp: bigint, options?: { blockNumber?: bigint }): Promise<boolean> {
656
1166
  await checkBlockTag(options?.blockNumber, this.client);
657
1167
  return this.rollup.read.canPruneAtTime([timestamp], options);
658
1168
  }
659
1169
 
660
- archive() {
661
- return this.rollup.read.archive();
1170
+ async archive(): Promise<Fr> {
1171
+ return Fr.fromString(await this.rollup.read.archive());
662
1172
  }
663
1173
 
664
- archiveAt(blockNumber: bigint) {
665
- return this.rollup.read.archiveAt([blockNumber]);
1174
+ async archiveAt(checkpointNumber: CheckpointNumber): Promise<Fr> {
1175
+ return Fr.fromString(await this.rollup.read.archiveAt([BigInt(checkpointNumber)]));
666
1176
  }
667
1177
 
668
- getSequencerRewards(address: Hex | EthAddress) {
1178
+ getSequencerRewards(address: Hex | EthAddress): Promise<bigint> {
669
1179
  if (address instanceof EthAddress) {
670
1180
  address = address.toString();
671
1181
  }
672
1182
  return this.rollup.read.getSequencerRewards([address]);
673
1183
  }
674
1184
 
675
- getSpecificProverRewardsForEpoch(epoch: bigint, prover: Hex | EthAddress) {
1185
+ getSpecificProverRewardsForEpoch(epoch: bigint, prover: Hex | EthAddress): Promise<bigint> {
676
1186
  if (prover instanceof EthAddress) {
677
1187
  prover = prover.toString();
678
1188
  }
679
1189
  return this.rollup.read.getSpecificProverRewardsForEpoch([epoch, prover]);
680
1190
  }
681
1191
 
682
- async getAttesters() {
1192
+ async getAttesters(timestamp?: bigint): Promise<EthAddress[]> {
683
1193
  const attesterSize = await this.getActiveAttesterCount();
684
1194
  const gse = new GSEContract(this.client, await this.getGSE());
685
- const ts = (await this.client.getBlock()).timestamp;
686
-
687
- const indices = Array.from({ length: Number(attesterSize) }, (_, i) => BigInt(i));
1195
+ const ts = timestamp ?? (await this.client.getBlock()).timestamp;
1196
+ const indices = Array.from({ length: attesterSize }, (_, i) => BigInt(i));
688
1197
  const chunks = chunk(indices, 1000);
689
1198
 
690
- return (await Promise.all(chunks.map(chunk => gse.getAttestersFromIndicesAtTime(this.address, ts, chunk)))).flat();
1199
+ const results = await Promise.all(chunks.map(chunk => gse.getAttestersFromIndicesAtTime(this.address, ts, chunk)));
1200
+ return results.flat().map(addr => EthAddress.fromString(addr));
691
1201
  }
692
1202
 
693
- getAttesterView(address: Hex | EthAddress) {
1203
+ async getAttesterView(address: Hex | EthAddress): Promise<AttesterView> {
694
1204
  if (address instanceof EthAddress) {
695
1205
  address = address.toString();
696
1206
  }
697
- return this.rollup.read.getAttesterView([address]);
1207
+ const result = await this.rollup.read.getAttesterView([address]);
1208
+ return {
1209
+ status: result.status as AttesterStatus,
1210
+ effectiveBalance: result.effectiveBalance,
1211
+ exit: {
1212
+ withdrawalId: result.exit.withdrawalId,
1213
+ amount: result.exit.amount,
1214
+ exitableAt: result.exit.exitableAt,
1215
+ recipientOrWithdrawer: EthAddress.fromString(result.exit.recipientOrWithdrawer),
1216
+ isRecipient: result.exit.isRecipient,
1217
+ exists: result.exit.exists,
1218
+ },
1219
+ config: {
1220
+ publicKey: {
1221
+ x: result.config.publicKey.x,
1222
+ y: result.config.publicKey.y,
1223
+ },
1224
+ withdrawer: EthAddress.fromString(result.config.withdrawer),
1225
+ },
1226
+ };
698
1227
  }
699
1228
 
700
- getStatus(address: Hex | EthAddress) {
1229
+ async getStatus(address: Hex | EthAddress): Promise<AttesterStatus> {
701
1230
  if (address instanceof EthAddress) {
702
1231
  address = address.toString();
703
1232
  }
704
- return this.rollup.read.getStatus([address]);
1233
+ return (await this.rollup.read.getStatus([address])) as AttesterStatus;
705
1234
  }
706
1235
 
707
- getBlobCommitmentsHash(blockNumber: bigint) {
708
- return this.rollup.read.getBlobCommitmentsHash([blockNumber]);
1236
+ async getBlobCommitmentsHash(checkpointNumber: CheckpointNumber): Promise<Buffer32> {
1237
+ return Buffer32.fromString(await this.rollup.read.getBlobCommitmentsHash([BigInt(checkpointNumber)]));
709
1238
  }
710
1239
 
711
- getCurrentBlobCommitmentsHash() {
712
- return this.rollup.read.getCurrentBlobCommitmentsHash();
1240
+ async getCurrentBlobCommitmentsHash(): Promise<Buffer32> {
1241
+ return Buffer32.fromString(await this.rollup.read.getCurrentBlobCommitmentsHash());
713
1242
  }
714
1243
 
715
- getStakingAsset() {
716
- return this.rollup.read.getStakingAsset();
1244
+ async getStakingAsset(): Promise<EthAddress> {
1245
+ return EthAddress.fromString(await this.rollup.read.getStakingAsset());
717
1246
  }
718
1247
 
719
- getRewardConfig() {
720
- return this.rollup.read.getRewardConfig();
1248
+ async getRewardConfig(): Promise<RewardConfig> {
1249
+ const result = await this.rollup.read.getRewardConfig();
1250
+ return {
1251
+ rewardDistributor: EthAddress.fromString(result.rewardDistributor),
1252
+ sequencerBps: BigInt(result.sequencerBps),
1253
+ booster: EthAddress.fromString(result.booster),
1254
+ checkpointReward: result.checkpointReward,
1255
+ };
721
1256
  }
722
1257
 
723
1258
  setupEpoch(l1TxUtils: L1TxUtils) {
724
1259
  return l1TxUtils.sendAndMonitorTransaction({
725
1260
  to: this.address,
1261
+ abi: RollupAbi,
726
1262
  data: encodeFunctionData({
727
1263
  abi: RollupAbi,
728
1264
  functionName: 'setupEpoch',
@@ -734,6 +1270,7 @@ export class RollupContract {
734
1270
  vote(l1TxUtils: L1TxUtils, proposalId: bigint) {
735
1271
  return l1TxUtils.sendAndMonitorTransaction({
736
1272
  to: this.address,
1273
+ abi: RollupAbi,
737
1274
  data: encodeFunctionData({
738
1275
  abi: RollupAbi,
739
1276
  functionName: 'vote',
@@ -760,15 +1297,17 @@ export class RollupContract {
760
1297
  );
761
1298
  }
762
1299
 
763
- public listenToBlockInvalidated(callback: (args: { blockNumber: bigint }) => unknown): WatchContractEventReturnType {
764
- return this.rollup.watchEvent.BlockInvalidated(
1300
+ public listenToCheckpointInvalidated(
1301
+ callback: (args: { checkpointNumber: CheckpointNumber; event: Log }) => unknown,
1302
+ ): WatchContractEventReturnType {
1303
+ return this.rollup.watchEvent.CheckpointInvalidated(
765
1304
  {},
766
1305
  {
767
1306
  onLogs: logs => {
768
1307
  for (const log of logs) {
769
1308
  const args = log.args;
770
- if (args.blockNumber !== undefined) {
771
- callback({ blockNumber: args.blockNumber });
1309
+ if (args.checkpointNumber !== undefined) {
1310
+ callback({ checkpointNumber: CheckpointNumber.fromBigInt(args.checkpointNumber), event: log });
772
1311
  }
773
1312
  }
774
1313
  },
@@ -800,4 +1339,92 @@ export class RollupContract {
800
1339
  },
801
1340
  );
802
1341
  }
1342
+
1343
+ /**
1344
+ * Fetches OwnershipTransferred events emitted on the L1 block this rollup was deployed on.
1345
+ * The Rollup inherits from Ownable and emits this event in its constructor, so the event
1346
+ * is guaranteed to exist on `l1StartBlock` for any correctly deployed rollup. Used as a
1347
+ * probe to detect RPC nodes that prune historical logs.
1348
+ */
1349
+ async getOwnershipTransferredEventsAtDeploy() {
1350
+ const l1StartBlock = await this.getL1StartBlock();
1351
+ return await this.rollup.getEvents.OwnershipTransferred({}, { fromBlock: l1StartBlock, toBlock: l1StartBlock });
1352
+ }
1353
+
1354
+ /** Fetches CheckpointProposed events within the given block range. */
1355
+ async getCheckpointProposedEvents(fromBlock: bigint, toBlock: bigint): Promise<CheckpointProposedLog[]> {
1356
+ const logs = await this.rollup.getEvents.CheckpointProposed({}, { fromBlock, toBlock });
1357
+ return logs
1358
+ .filter(log => log.blockNumber! >= fromBlock && log.blockNumber! <= toBlock)
1359
+ .map(log => ({
1360
+ l1BlockNumber: log.blockNumber!,
1361
+ l1BlockHash: Buffer32.fromString(log.blockHash!),
1362
+ l1TransactionHash: log.transactionHash!,
1363
+ args: {
1364
+ checkpointNumber: CheckpointNumber.fromBigInt(log.args.checkpointNumber!),
1365
+ archive: Fr.fromString(log.args.archive!),
1366
+ versionedBlobHashes: log.args.versionedBlobHashes!.map(h => Buffer.from(h.slice(2), 'hex')),
1367
+ attestationsHash: (() => {
1368
+ if (!log.args.attestationsHash) {
1369
+ throw new Error(
1370
+ `CheckpointProposed event missing attestationsHash for checkpoint ${log.args.checkpointNumber}`,
1371
+ );
1372
+ }
1373
+ return Buffer32.fromString(log.args.attestationsHash);
1374
+ })(),
1375
+ payloadDigest: (() => {
1376
+ if (!log.args.payloadDigest) {
1377
+ throw new Error(
1378
+ `CheckpointProposed event missing payloadDigest for checkpoint ${log.args.checkpointNumber}`,
1379
+ );
1380
+ }
1381
+ return Buffer32.fromString(log.args.payloadDigest);
1382
+ })(),
1383
+ },
1384
+ }));
1385
+ }
1386
+
1387
+ /** Packs pending and proven checkpoint numbers into the chain tips storage format. */
1388
+ static packChainTips(pendingCheckpointNumber: bigint, provenCheckpointNumber: bigint): bigint {
1389
+ return (pendingCheckpointNumber << 128n) | (provenCheckpointNumber & ((1n << 128n) - 1n));
1390
+ }
1391
+
1392
+ /** Storage slot for the chain tips (offset 0 within the STF storage struct). */
1393
+ static get chainTipsStorageSlot(): bigint {
1394
+ return BigInt(RollupContract.stfStorageSlot);
1395
+ }
1396
+
1397
+ /**
1398
+ * Computes the storage slot for a field within a tempCheckpointLog entry.
1399
+ * @param checkpointNumber - The checkpoint number
1400
+ * @param field - The field within the CompressedTempCheckpointLog struct
1401
+ */
1402
+ async getTempCheckpointLogStorageSlot(
1403
+ checkpointNumber: CheckpointNumber,
1404
+ field: TempCheckpointLogField,
1405
+ ): Promise<bigint> {
1406
+ const [epochDuration, proofSubmissionEpochs] = await Promise.all([
1407
+ this.getEpochDuration(),
1408
+ this.getProofSubmissionEpochs(),
1409
+ ]);
1410
+ return this.computeTempCheckpointLogStorageSlot(checkpointNumber, field, { epochDuration, proofSubmissionEpochs });
1411
+ }
1412
+
1413
+ private computeTempCheckpointLogStorageSlot(
1414
+ checkpointNumber: CheckpointNumber,
1415
+ field: TempCheckpointLogField,
1416
+ constants: { epochDuration: number; proofSubmissionEpochs: number },
1417
+ ): bigint {
1418
+ const fieldOffset = BigInt(field);
1419
+ const { epochDuration, proofSubmissionEpochs } = constants;
1420
+ const roundaboutSize = BigInt(epochDuration) * (BigInt(proofSubmissionEpochs) + 1n) + 1n;
1421
+ const tempCheckpointLogsBase = BigInt(RollupContract.stfStorageSlot) + 2n;
1422
+ const circularIndex = BigInt(checkpointNumber) % roundaboutSize;
1423
+ const entryBase = BigInt(
1424
+ keccak256(
1425
+ encodeAbiParameters([{ type: 'uint256' }, { type: 'uint256' }], [circularIndex, tempCheckpointLogsBase]),
1426
+ ),
1427
+ );
1428
+ return entryBase + fieldOffset;
1429
+ }
803
1430
  }