@aztec/ethereum 0.0.1-commit.9b94fc1 → 0.0.1-commit.9badcec54

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 (212) hide show
  1. package/dest/client.d.ts +10 -2
  2. package/dest/client.d.ts.map +1 -1
  3. package/dest/client.js +13 -3
  4. package/dest/config.d.ts +26 -73
  5. package/dest/config.d.ts.map +1 -1
  6. package/dest/config.js +64 -387
  7. package/dest/contracts/chain_state_override.d.ts +38 -0
  8. package/dest/contracts/chain_state_override.d.ts.map +1 -0
  9. package/dest/contracts/chain_state_override.js +100 -0
  10. package/dest/contracts/empire_base.d.ts +4 -1
  11. package/dest/contracts/empire_base.d.ts.map +1 -1
  12. package/dest/contracts/fee_asset_handler.d.ts +6 -5
  13. package/dest/contracts/fee_asset_handler.d.ts.map +1 -1
  14. package/dest/contracts/fee_asset_handler.js +11 -9
  15. package/dest/contracts/fee_asset_price_oracle.d.ts +101 -0
  16. package/dest/contracts/fee_asset_price_oracle.d.ts.map +1 -0
  17. package/dest/contracts/fee_asset_price_oracle.js +651 -0
  18. package/dest/contracts/governance.d.ts +3 -1
  19. package/dest/contracts/governance.d.ts.map +1 -1
  20. package/dest/contracts/governance.js +14 -4
  21. package/dest/contracts/governance_proposer.d.ts +4 -1
  22. package/dest/contracts/governance_proposer.d.ts.map +1 -1
  23. package/dest/contracts/governance_proposer.js +404 -9
  24. package/dest/contracts/inbox.d.ts +24 -3
  25. package/dest/contracts/inbox.d.ts.map +1 -1
  26. package/dest/contracts/inbox.js +35 -1
  27. package/dest/contracts/index.d.ts +6 -3
  28. package/dest/contracts/index.d.ts.map +1 -1
  29. package/dest/contracts/index.js +5 -2
  30. package/dest/contracts/log.d.ts +13 -0
  31. package/dest/contracts/log.d.ts.map +1 -0
  32. package/dest/contracts/log.js +1 -0
  33. package/dest/contracts/multicall.d.ts +51 -2
  34. package/dest/contracts/multicall.d.ts.map +1 -1
  35. package/dest/contracts/multicall.js +87 -1
  36. package/dest/contracts/outbox.d.ts +41 -0
  37. package/dest/contracts/outbox.d.ts.map +1 -0
  38. package/dest/contracts/outbox.js +86 -0
  39. package/dest/contracts/registry.d.ts +3 -1
  40. package/dest/contracts/registry.d.ts.map +1 -1
  41. package/dest/contracts/registry.js +30 -1
  42. package/dest/contracts/rollup.d.ts +251 -107
  43. package/dest/contracts/rollup.d.ts.map +1 -1
  44. package/dest/contracts/rollup.js +991 -188
  45. package/dest/contracts/{tally_slashing_proposer.d.ts → slashing_proposer.d.ts} +5 -5
  46. package/dest/contracts/slashing_proposer.d.ts.map +1 -0
  47. package/dest/contracts/{tally_slashing_proposer.js → slashing_proposer.js} +18 -13
  48. package/dest/deploy_aztec_l1_contracts.d.ts +256 -0
  49. package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -0
  50. package/dest/deploy_aztec_l1_contracts.js +411 -0
  51. package/dest/deploy_l1_contract.d.ts +68 -0
  52. package/dest/deploy_l1_contract.d.ts.map +1 -0
  53. package/dest/deploy_l1_contract.js +312 -0
  54. package/dest/forwarder_proxy.d.ts +32 -0
  55. package/dest/forwarder_proxy.d.ts.map +1 -0
  56. package/dest/forwarder_proxy.js +93 -0
  57. package/dest/generated/l1-contracts-defaults.d.ts +30 -0
  58. package/dest/generated/l1-contracts-defaults.d.ts.map +1 -0
  59. package/dest/generated/l1-contracts-defaults.js +30 -0
  60. package/dest/l1_artifacts.d.ts +14182 -17121
  61. package/dest/l1_artifacts.d.ts.map +1 -1
  62. package/dest/l1_artifacts.js +9 -24
  63. package/dest/l1_contract_addresses.d.ts +1 -5
  64. package/dest/l1_contract_addresses.d.ts.map +1 -1
  65. package/dest/l1_contract_addresses.js +3 -9
  66. package/dest/l1_reader.d.ts +5 -1
  67. package/dest/l1_reader.d.ts.map +1 -1
  68. package/dest/l1_reader.js +12 -1
  69. package/dest/l1_tx_utils/config.d.ts +9 -3
  70. package/dest/l1_tx_utils/config.d.ts.map +1 -1
  71. package/dest/l1_tx_utils/config.js +31 -4
  72. package/dest/l1_tx_utils/constants.d.ts +8 -2
  73. package/dest/l1_tx_utils/constants.d.ts.map +1 -1
  74. package/dest/l1_tx_utils/constants.js +27 -2
  75. package/dest/l1_tx_utils/factory.d.ts +18 -10
  76. package/dest/l1_tx_utils/factory.d.ts.map +1 -1
  77. package/dest/l1_tx_utils/factory.js +17 -7
  78. package/dest/l1_tx_utils/fee-strategies/index.d.ts +10 -0
  79. package/dest/l1_tx_utils/fee-strategies/index.d.ts.map +1 -0
  80. package/dest/l1_tx_utils/fee-strategies/index.js +12 -0
  81. package/dest/l1_tx_utils/fee-strategies/p75_competitive.d.ts +8 -0
  82. package/dest/l1_tx_utils/fee-strategies/p75_competitive.d.ts.map +1 -0
  83. package/dest/l1_tx_utils/fee-strategies/p75_competitive.js +129 -0
  84. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.d.ts +23 -0
  85. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.d.ts.map +1 -0
  86. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.js +191 -0
  87. package/dest/l1_tx_utils/fee-strategies/types.d.ts +51 -0
  88. package/dest/l1_tx_utils/fee-strategies/types.d.ts.map +1 -0
  89. package/dest/l1_tx_utils/fee-strategies/types.js +3 -0
  90. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts +41 -0
  91. package/dest/l1_tx_utils/forwarder_l1_tx_utils.d.ts.map +1 -0
  92. package/dest/l1_tx_utils/forwarder_l1_tx_utils.js +42 -0
  93. package/dest/l1_tx_utils/index-blobs.d.ts +3 -0
  94. package/dest/l1_tx_utils/index-blobs.d.ts.map +1 -0
  95. package/dest/l1_tx_utils/index-blobs.js +2 -0
  96. package/dest/l1_tx_utils/index.d.ts +4 -1
  97. package/dest/l1_tx_utils/index.d.ts.map +1 -1
  98. package/dest/l1_tx_utils/index.js +3 -0
  99. package/dest/l1_tx_utils/interfaces.d.ts +2 -2
  100. package/dest/l1_tx_utils/interfaces.d.ts.map +1 -1
  101. package/dest/l1_tx_utils/l1_fee_analyzer.d.ts +233 -0
  102. package/dest/l1_tx_utils/l1_fee_analyzer.d.ts.map +1 -0
  103. package/dest/l1_tx_utils/l1_fee_analyzer.js +506 -0
  104. package/dest/l1_tx_utils/l1_tx_utils.d.ts +20 -7
  105. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -1
  106. package/dest/l1_tx_utils/l1_tx_utils.js +98 -61
  107. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +4 -15
  108. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -1
  109. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +61 -164
  110. package/dest/l1_tx_utils/tx_delayer.d.ts +56 -0
  111. package/dest/l1_tx_utils/tx_delayer.d.ts.map +1 -0
  112. package/dest/{test → l1_tx_utils}/tx_delayer.js +65 -36
  113. package/dest/publisher_manager.d.ts +21 -6
  114. package/dest/publisher_manager.d.ts.map +1 -1
  115. package/dest/publisher_manager.js +81 -7
  116. package/dest/queries.d.ts +2 -2
  117. package/dest/queries.d.ts.map +1 -1
  118. package/dest/queries.js +15 -7
  119. package/dest/test/chain_monitor.d.ts +35 -14
  120. package/dest/test/chain_monitor.d.ts.map +1 -1
  121. package/dest/test/chain_monitor.js +40 -11
  122. package/dest/test/eth_cheat_codes.d.ts +18 -4
  123. package/dest/test/eth_cheat_codes.d.ts.map +1 -1
  124. package/dest/test/eth_cheat_codes.js +10 -6
  125. package/dest/test/index.d.ts +1 -3
  126. package/dest/test/index.d.ts.map +1 -1
  127. package/dest/test/index.js +0 -2
  128. package/dest/test/rollup_cheat_codes.d.ts +13 -6
  129. package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
  130. package/dest/test/rollup_cheat_codes.js +41 -7
  131. package/dest/test/start_anvil.d.ts +24 -2
  132. package/dest/test/start_anvil.d.ts.map +1 -1
  133. package/dest/test/start_anvil.js +143 -29
  134. package/dest/test/upgrade_utils.js +2 -2
  135. package/dest/types.d.ts +57 -2
  136. package/dest/types.d.ts.map +1 -1
  137. package/dest/utils.d.ts +16 -3
  138. package/dest/utils.d.ts.map +1 -1
  139. package/dest/utils.js +80 -12
  140. package/package.json +33 -16
  141. package/src/client.ts +10 -2
  142. package/src/config.ts +82 -468
  143. package/src/contracts/README.md +157 -0
  144. package/src/contracts/chain_state_override.ts +147 -0
  145. package/src/contracts/empire_base.ts +3 -1
  146. package/src/contracts/fee_asset_handler.ts +10 -7
  147. package/src/contracts/fee_asset_price_oracle.ts +280 -0
  148. package/src/contracts/governance.ts +13 -4
  149. package/src/contracts/governance_proposer.ts +16 -2
  150. package/src/contracts/inbox.ts +55 -3
  151. package/src/contracts/index.ts +5 -2
  152. package/src/contracts/log.ts +13 -0
  153. package/src/contracts/multicall.ts +70 -3
  154. package/src/contracts/outbox.ts +98 -0
  155. package/src/contracts/registry.ts +31 -1
  156. package/src/contracts/rollup.ts +689 -149
  157. package/src/contracts/{tally_slashing_proposer.ts → slashing_proposer.ts} +18 -14
  158. package/src/deploy_aztec_l1_contracts.ts +646 -0
  159. package/src/deploy_l1_contract.ts +362 -0
  160. package/src/forwarder_proxy.ts +108 -0
  161. package/src/generated/l1-contracts-defaults.ts +32 -0
  162. package/src/l1_artifacts.ts +12 -35
  163. package/src/l1_contract_addresses.ts +21 -26
  164. package/src/l1_reader.ts +21 -1
  165. package/src/l1_tx_utils/config.ts +44 -6
  166. package/src/l1_tx_utils/constants.ts +13 -2
  167. package/src/l1_tx_utils/factory.ts +31 -31
  168. package/src/l1_tx_utils/fee-strategies/index.ts +22 -0
  169. package/src/l1_tx_utils/fee-strategies/p75_competitive.ts +163 -0
  170. package/src/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.ts +245 -0
  171. package/src/l1_tx_utils/fee-strategies/types.ts +56 -0
  172. package/src/l1_tx_utils/forwarder_l1_tx_utils.ts +108 -0
  173. package/src/l1_tx_utils/index-blobs.ts +2 -0
  174. package/src/l1_tx_utils/index.ts +3 -0
  175. package/src/l1_tx_utils/interfaces.ts +1 -1
  176. package/src/l1_tx_utils/l1_fee_analyzer.ts +803 -0
  177. package/src/l1_tx_utils/l1_tx_utils.ts +98 -40
  178. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +75 -210
  179. package/src/{test → l1_tx_utils}/tx_delayer.ts +82 -52
  180. package/src/publisher_manager.ts +107 -10
  181. package/src/queries.ts +14 -6
  182. package/src/test/chain_monitor.ts +77 -18
  183. package/src/test/eth_cheat_codes.ts +8 -6
  184. package/src/test/index.ts +0 -2
  185. package/src/test/rollup_cheat_codes.ts +43 -10
  186. package/src/test/start_anvil.ts +178 -28
  187. package/src/test/upgrade_utils.ts +2 -2
  188. package/src/types.ts +62 -0
  189. package/src/utils.ts +100 -15
  190. package/dest/contracts/empire_slashing_proposer.d.ts +0 -66
  191. package/dest/contracts/empire_slashing_proposer.d.ts.map +0 -1
  192. package/dest/contracts/empire_slashing_proposer.js +0 -200
  193. package/dest/contracts/tally_slashing_proposer.d.ts.map +0 -1
  194. package/dest/deploy_l1_contracts.d.ts +0 -673
  195. package/dest/deploy_l1_contracts.d.ts.map +0 -1
  196. package/dest/deploy_l1_contracts.js +0 -1491
  197. package/dest/index.d.ts +0 -18
  198. package/dest/index.d.ts.map +0 -1
  199. package/dest/index.js +0 -17
  200. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts +0 -26
  201. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts.map +0 -1
  202. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.js +0 -26
  203. package/dest/test/delayed_tx_utils.d.ts +0 -13
  204. package/dest/test/delayed_tx_utils.d.ts.map +0 -1
  205. package/dest/test/delayed_tx_utils.js +0 -28
  206. package/dest/test/tx_delayer.d.ts +0 -36
  207. package/dest/test/tx_delayer.d.ts.map +0 -1
  208. package/src/contracts/empire_slashing_proposer.ts +0 -265
  209. package/src/deploy_l1_contracts.ts +0 -1869
  210. package/src/index.ts +0 -17
  211. package/src/l1_tx_utils/l1_tx_utils_with_blobs.ts +0 -77
  212. package/src/test/delayed_tx_utils.ts +0 -52
@@ -1,7 +1,12 @@
1
- import { EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
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';
2
4
  import { memoize } from '@aztec/foundation/decorators';
3
5
  import { EthAddress } from '@aztec/foundation/eth-address';
4
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 { EscapeHatchAbi } from '@aztec/l1-artifacts/EscapeHatchAbi';
5
10
  import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';
6
11
  import { RollupStorage } from '@aztec/l1-artifacts/RollupStorage';
7
12
 
@@ -12,6 +17,7 @@ import {
12
17
  type Hex,
13
18
  type StateOverride,
14
19
  type WatchContractEventReturnType,
20
+ encodeAbiParameters,
15
21
  encodeFunctionData,
16
22
  getContract,
17
23
  hexToBigInt,
@@ -19,16 +25,16 @@ import {
19
25
  } from 'viem';
20
26
 
21
27
  import { getPublicClient } from '../client.js';
22
- import type { DeployL1ContractsReturnType } from '../deploy_l1_contracts.js';
28
+ import type { DeployAztecL1ContractsReturnType } from '../deploy_aztec_l1_contracts.js';
23
29
  import type { L1ContractAddresses } from '../l1_contract_addresses.js';
24
30
  import type { L1ReaderConfig } from '../l1_reader.js';
25
31
  import type { L1TxRequest, L1TxUtils } from '../l1_tx_utils/index.js';
26
32
  import type { ViemClient } from '../types.js';
27
33
  import { formatViemError } from '../utils.js';
28
- import { EmpireSlashingProposerContract } from './empire_slashing_proposer.js';
29
34
  import { GSEContract } from './gse.js';
35
+ import type { L1EventLog } from './log.js';
30
36
  import { SlasherContract } from './slasher_contract.js';
31
- import { TallySlashingProposerContract } from './tally_slashing_proposer.js';
37
+ import { SlashingProposerContract } from './slashing_proposer.js';
32
38
  import { checkBlockTag } from './utils.js';
33
39
 
34
40
  export type ViemCommitteeAttestation = {
@@ -50,20 +56,22 @@ export type L1RollupContractAddresses = Pick<
50
56
  | 'feeJuiceAddress'
51
57
  | 'stakingAssetAddress'
52
58
  | 'rewardDistributorAddress'
53
- | 'slashFactoryAddress'
54
59
  | 'gseAddress'
55
60
  >;
56
61
 
57
62
  export type EpochProofPublicInputArgs = {
58
63
  previousArchive: `0x${string}`;
59
64
  endArchive: `0x${string}`;
65
+ outHash: `0x${string}`;
60
66
  proverId: `0x${string}`;
61
67
  };
62
68
 
63
69
  export type ViemHeader = {
64
70
  lastArchiveRoot: `0x${string}`;
65
71
  blockHeadersHash: `0x${string}`;
66
- contentCommitment: ViemContentCommitment;
72
+ blobsHash: `0x${string}`;
73
+ inHash: `0x${string}`;
74
+ outHash: `0x${string}`;
67
75
  slotNumber: bigint;
68
76
  timestamp: bigint;
69
77
  coinbase: `0x${string}`;
@@ -72,27 +80,150 @@ export type ViemHeader = {
72
80
  totalManaUsed: bigint;
73
81
  };
74
82
 
75
- export type ViemContentCommitment = {
76
- blobsHash: `0x${string}`;
77
- inHash: `0x${string}`;
78
- outHash: `0x${string}`;
79
- };
80
-
81
83
  export type ViemGasFees = {
82
84
  feePerDaGas: bigint;
83
85
  feePerL2Gas: bigint;
84
86
  };
85
87
 
86
- export enum SlashingProposerType {
87
- None = 0,
88
- Tally = 1,
89
- Empire = 2,
88
+ /**
89
+ * Status of a validator/attester in the staking system.
90
+ * Matches the Status enum in StakingLib.sol
91
+ */
92
+ export enum AttesterStatus {
93
+ NONE = 0,
94
+ VALIDATING = 1,
95
+ ZOMBIE = 2,
96
+ EXITING = 3,
90
97
  }
91
98
 
99
+ /**
100
+ * Fee header data for a checkpoint
101
+ */
102
+ export type FeeHeader = {
103
+ excessMana: bigint;
104
+ manaUsed: bigint;
105
+ ethPerFeeAsset: bigint;
106
+ congestionCost: bigint;
107
+ proverCost: bigint;
108
+ };
109
+
110
+ /**
111
+ * Checkpoint log data returned from the rollup contract
112
+ */
113
+ export type CheckpointLog = {
114
+ archive: Fr;
115
+ headerHash: Buffer32;
116
+ blobCommitmentsHash: Buffer32;
117
+ attestationsHash: Buffer32;
118
+ payloadDigest: Buffer32;
119
+ slotNumber: SlotNumber;
120
+ feeHeader: FeeHeader;
121
+ };
122
+
123
+ /**
124
+ * L1 fee data (base fee and blob fee)
125
+ */
126
+ export type L1FeeData = {
127
+ baseFee: bigint;
128
+ blobFee: bigint;
129
+ };
130
+
131
+ /** Field offsets within the CompressedTempCheckpointLog struct in Solidity storage. */
132
+ export enum TempCheckpointLogField {
133
+ HeaderHash = 0,
134
+ BlobCommitmentsHash = 1,
135
+ OutHash = 2,
136
+ AttestationsHash = 3,
137
+ PayloadDigest = 4,
138
+ SlotNumber = 5,
139
+ FeeHeader = 6,
140
+ }
141
+
142
+ /** Components of the minimum fee per mana, as returned by the L1 rollup contract. */
143
+ export type ManaMinFeeComponents = {
144
+ sequencerCost: bigint;
145
+ proverCost: bigint;
146
+ congestionCost: bigint;
147
+ congestionMultiplier: bigint;
148
+ };
149
+
150
+ /**
151
+ * Reward configuration for the rollup
152
+ */
153
+ export type RewardConfig = {
154
+ rewardDistributor: EthAddress;
155
+ sequencerBps: bigint;
156
+ booster: EthAddress;
157
+ checkpointReward: bigint;
158
+ };
159
+
160
+ /**
161
+ * Exit information for a validator
162
+ */
163
+ export type Exit = {
164
+ withdrawalId: bigint;
165
+ amount: bigint;
166
+ exitableAt: bigint;
167
+ recipientOrWithdrawer: EthAddress;
168
+ isRecipient: boolean;
169
+ exists: boolean;
170
+ };
171
+
172
+ /**
173
+ * Attester configuration including public key and withdrawer
174
+ */
175
+ export type AttesterConfig = {
176
+ publicKey: {
177
+ x: bigint;
178
+ y: bigint;
179
+ };
180
+ withdrawer: EthAddress;
181
+ };
182
+
183
+ /**
184
+ * Complete view of an attester's state
185
+ */
186
+ export type AttesterView = {
187
+ status: AttesterStatus;
188
+ effectiveBalance: bigint;
189
+ exit: Exit;
190
+ config: AttesterConfig;
191
+ };
192
+
193
+ /**
194
+ * Return for a status call
195
+ */
196
+ export type RollupStatusResponse = {
197
+ provenCheckpointNumber: CheckpointNumber;
198
+ provenArchive: Fr;
199
+ pendingCheckpointNumber: CheckpointNumber;
200
+ pendingArchive: Fr;
201
+ archiveOfMyCheckpoint: Fr;
202
+ };
203
+
204
+ /** Arguments for the CheckpointProposed event. */
205
+ export type CheckpointProposedArgs = {
206
+ checkpointNumber: CheckpointNumber;
207
+ archive: Fr;
208
+ versionedBlobHashes: Buffer[];
209
+ /** Hash of attestations emitted in the CheckpointProposed event. */
210
+ attestationsHash: Buffer32;
211
+ /** Digest of the payload emitted in the CheckpointProposed event. */
212
+ payloadDigest: Buffer32;
213
+ };
214
+
215
+ /** Log type for CheckpointProposed events. */
216
+ export type CheckpointProposedLog = L1EventLog<CheckpointProposedArgs>;
217
+
92
218
  export class RollupContract {
93
219
  private readonly rollup: GetContractReturnType<typeof RollupAbi, ViemClient>;
220
+ private readonly logger = createLogger('ethereum:rollup');
94
221
 
95
222
  private static cachedStfStorageSlot: Hex | undefined;
223
+ private cachedEscapeHatch?: {
224
+ address: EthAddress;
225
+ contract: GetContractReturnType<typeof EscapeHatchAbi, ViemClient>;
226
+ };
96
227
 
97
228
  static get checkBlobStorageSlot(): bigint {
98
229
  const asString = RollupStorage.find(storage => storage.label === 'checkBlob')?.slot;
@@ -106,7 +237,7 @@ export class RollupContract {
106
237
  return (RollupContract.cachedStfStorageSlot ??= keccak256(Buffer.from('aztec.stf.storage', 'utf-8')));
107
238
  }
108
239
 
109
- static getFromL1ContractsValues(deployL1ContractsValues: DeployL1ContractsReturnType) {
240
+ static getFromL1ContractsValues(deployL1ContractsValues: DeployAztecL1ContractsReturnType) {
110
241
  const {
111
242
  l1Client,
112
243
  l1ContractAddresses: { rollupAddress },
@@ -130,8 +261,8 @@ export class RollupContract {
130
261
  this.rollup = getContract({ address, abi: RollupAbi, client });
131
262
  }
132
263
 
133
- getGSE() {
134
- return this.rollup.read.getGSE();
264
+ async getGSE(): Promise<EthAddress> {
265
+ return EthAddress.fromString(await this.rollup.read.getGSE());
135
266
  }
136
267
 
137
268
  public get address() {
@@ -142,54 +273,38 @@ export class RollupContract {
142
273
  return this.rollup;
143
274
  }
144
275
 
145
- public async getSlashingProposer(): Promise<
146
- EmpireSlashingProposerContract | TallySlashingProposerContract | undefined
147
- > {
276
+ public async getSlashingProposer(): Promise<SlashingProposerContract | undefined> {
148
277
  const slasher = await this.getSlasherContract();
149
278
  if (!slasher) {
150
279
  return undefined;
151
280
  }
152
281
 
153
282
  const proposerAddress = await slasher.getProposer();
154
- const proposerAbi = [
155
- {
156
- type: 'function',
157
- name: 'SLASHING_PROPOSER_TYPE',
158
- inputs: [],
159
- outputs: [{ name: '', type: 'uint8', internalType: 'enum SlasherFlavor' }],
160
- stateMutability: 'view',
161
- },
162
- ] as const;
163
-
164
- const proposer = getContract({ address: proposerAddress.toString(), abi: proposerAbi, client: this.client });
165
- const proposerType = await proposer.read.SLASHING_PROPOSER_TYPE();
166
- if (proposerType === SlashingProposerType.Tally.valueOf()) {
167
- return new TallySlashingProposerContract(this.client, proposerAddress);
168
- } else if (proposerType === SlashingProposerType.Empire.valueOf()) {
169
- return new EmpireSlashingProposerContract(this.client, proposerAddress);
170
- } else {
171
- throw new Error(`Unknown slashing proposer type: ${proposerType}`);
283
+ if (proposerAddress.isZero()) {
284
+ return undefined;
172
285
  }
286
+
287
+ return new SlashingProposerContract(this.client, proposerAddress);
173
288
  }
174
289
 
175
290
  @memoize
176
- getL1StartBlock() {
291
+ getL1StartBlock(): Promise<bigint> {
177
292
  return this.rollup.read.L1_BLOCK_AT_GENESIS();
178
293
  }
179
294
 
180
295
  @memoize
181
- getL1GenesisTime() {
296
+ getL1GenesisTime(): Promise<bigint> {
182
297
  return this.rollup.read.getGenesisTime();
183
298
  }
184
299
 
185
300
  @memoize
186
- getProofSubmissionEpochs() {
187
- return this.rollup.read.getProofSubmissionEpochs();
301
+ async getProofSubmissionEpochs(): Promise<number> {
302
+ return Number(await this.rollup.read.getProofSubmissionEpochs());
188
303
  }
189
304
 
190
305
  @memoize
191
- getEpochDuration() {
192
- return this.rollup.read.getEpochDuration();
306
+ async getEpochDuration(): Promise<number> {
307
+ return Number(await this.rollup.read.getEpochDuration());
193
308
  }
194
309
 
195
310
  @memoize
@@ -198,68 +313,82 @@ export class RollupContract {
198
313
  }
199
314
 
200
315
  @memoize
201
- getTargetCommitteeSize() {
202
- return this.rollup.read.getTargetCommitteeSize();
316
+ async getTargetCommitteeSize(): Promise<number> {
317
+ return Number(await this.rollup.read.getTargetCommitteeSize());
203
318
  }
204
319
 
205
320
  @memoize
206
- getEjectionThreshold() {
321
+ getEjectionThreshold(): Promise<bigint> {
207
322
  return this.rollup.read.getEjectionThreshold();
208
323
  }
209
324
 
210
325
  @memoize
211
- getLocalEjectionThreshold() {
326
+ getLocalEjectionThreshold(): Promise<bigint> {
212
327
  return this.rollup.read.getLocalEjectionThreshold();
213
328
  }
214
329
 
215
330
  @memoize
216
- getLagInEpochsForValidatorSet() {
217
- return this.rollup.read.getLagInEpochsForValidatorSet();
331
+ async getLagInEpochsForValidatorSet(): Promise<number> {
332
+ return Number(await this.rollup.read.getLagInEpochsForValidatorSet());
218
333
  }
219
334
 
220
335
  @memoize
221
- getLagInEpochsForRandao() {
222
- return this.rollup.read.getLagInEpochsForRandao();
336
+ async getLagInEpochsForRandao(): Promise<number> {
337
+ return Number(await this.rollup.read.getLagInEpochsForRandao());
223
338
  }
224
339
 
225
340
  @memoize
226
- getActivationThreshold() {
341
+ getActivationThreshold(): Promise<bigint> {
227
342
  return this.rollup.read.getActivationThreshold();
228
343
  }
229
344
 
230
345
  @memoize
231
- getExitDelay() {
232
- return this.rollup.read.getExitDelay();
346
+ async getExitDelay(): Promise<number> {
347
+ return Number(await this.rollup.read.getExitDelay());
233
348
  }
234
349
 
235
350
  @memoize
236
- getManaTarget() {
351
+ getManaTarget(): Promise<bigint> {
237
352
  return this.rollup.read.getManaTarget();
238
353
  }
239
354
 
240
355
  @memoize
241
- getProvingCostPerMana() {
356
+ getProvingCostPerMana(): Promise<bigint> {
242
357
  return this.rollup.read.getProvingCostPerManaInEth();
243
358
  }
244
359
 
245
360
  @memoize
246
- getProvingCostPerManaInFeeAsset() {
361
+ getProvingCostPerManaInFeeAsset(): Promise<bigint> {
247
362
  return this.rollup.read.getProvingCostPerManaInFeeAsset();
248
363
  }
249
364
 
250
365
  @memoize
251
- getManaLimit() {
366
+ getManaLimit(): Promise<bigint> {
252
367
  return this.rollup.read.getManaLimit();
253
368
  }
254
369
 
255
370
  @memoize
256
- getVersion() {
371
+ getVersion(): Promise<bigint> {
257
372
  return this.rollup.read.getVersion();
258
373
  }
259
374
 
260
375
  @memoize
261
- async getGenesisArchiveTreeRoot(): Promise<`0x${string}`> {
262
- return await this.rollup.read.archiveAt([0n]);
376
+ async getGenesisArchiveTreeRoot(): Promise<Fr> {
377
+ return Fr.fromString(await this.rollup.read.archiveAt([0n]));
378
+ }
379
+
380
+ @memoize
381
+ async getVkTreeRoot(): Promise<Fr> {
382
+ const slot = BigInt(RollupContract.stfStorageSlot) + 3n;
383
+ const value = await this.client.getStorageAt({ address: this.address, slot: `0x${slot.toString(16)}` });
384
+ return Fr.fromString(value ?? '0x0');
385
+ }
386
+
387
+ @memoize
388
+ async getProtocolContractsHash(): Promise<Fr> {
389
+ const slot = BigInt(RollupContract.stfStorageSlot) + 4n;
390
+ const value = await this.client.getStorageAt({ address: this.address, slot: `0x${slot.toString(16)}` });
391
+ return Fr.fromString(value ?? '0x0');
263
392
  }
264
393
 
265
394
  /**
@@ -274,13 +403,25 @@ export class RollupContract {
274
403
  slotDuration: number;
275
404
  epochDuration: number;
276
405
  proofSubmissionEpochs: number;
406
+ targetCommitteeSize: number;
407
+ rollupManaLimit: number;
277
408
  }> {
278
- const [l1StartBlock, l1GenesisTime, slotDuration, epochDuration, proofSubmissionEpochs] = await Promise.all([
409
+ const [
410
+ l1StartBlock,
411
+ l1GenesisTime,
412
+ slotDuration,
413
+ epochDuration,
414
+ proofSubmissionEpochs,
415
+ targetCommitteeSize,
416
+ rollupManaLimit,
417
+ ] = await Promise.all([
279
418
  this.getL1StartBlock(),
280
419
  this.getL1GenesisTime(),
281
420
  this.getSlotDuration(),
282
421
  this.getEpochDuration(),
283
422
  this.getProofSubmissionEpochs(),
423
+ this.getTargetCommitteeSize(),
424
+ this.getManaLimit(),
284
425
  ]);
285
426
  return {
286
427
  l1StartBlock,
@@ -288,30 +429,88 @@ export class RollupContract {
288
429
  slotDuration,
289
430
  epochDuration: Number(epochDuration),
290
431
  proofSubmissionEpochs: Number(proofSubmissionEpochs),
432
+ targetCommitteeSize,
433
+ rollupManaLimit: Number(rollupManaLimit),
291
434
  };
292
435
  }
293
436
 
294
- getSlasherAddress() {
295
- return this.rollup.read.getSlasher();
437
+ async getSlasherAddress(): Promise<EthAddress> {
438
+ return EthAddress.fromString(await this.rollup.read.getSlasher());
439
+ }
440
+
441
+ /**
442
+ * Returns the configured escape hatch contract address, or zero if disabled.
443
+ */
444
+ async getEscapeHatchAddress(): Promise<EthAddress> {
445
+ return EthAddress.fromString(await this.rollup.read.getEscapeHatch());
446
+ }
447
+
448
+ private async getEscapeHatchContract(): Promise<
449
+ GetContractReturnType<typeof EscapeHatchAbi, ViemClient> | undefined
450
+ > {
451
+ const escapeHatchAddress = await this.getEscapeHatchAddress();
452
+ if (escapeHatchAddress.isZero()) {
453
+ return undefined;
454
+ }
455
+
456
+ // Cache the viem contract wrapper since it will be used frequently.
457
+ if (!this.cachedEscapeHatch || !this.cachedEscapeHatch.address.equals(escapeHatchAddress)) {
458
+ this.cachedEscapeHatch = {
459
+ address: escapeHatchAddress,
460
+ contract: getContract({
461
+ address: escapeHatchAddress.toString(),
462
+ abi: EscapeHatchAbi,
463
+ client: this.client,
464
+ }),
465
+ };
466
+ }
467
+
468
+ return this.cachedEscapeHatch.contract;
469
+ }
470
+
471
+ /**
472
+ * Returns whether the escape hatch is open for the given epoch.
473
+ * If escape hatch is not configured, returns false.
474
+ *
475
+ * This function is intentionally defensive: any failure to query the escape hatch
476
+ * (RPC issues, transient errors, etc.) is treated as "closed" to avoid callers
477
+ * needing to sprinkle try/catch everywhere.
478
+ */
479
+ async isEscapeHatchOpen(epoch: EpochNumber): Promise<boolean> {
480
+ try {
481
+ const escapeHatch = await this.getEscapeHatchContract();
482
+ if (!escapeHatch) {
483
+ return false;
484
+ }
485
+
486
+ const [isOpen] = await escapeHatch.read.isHatchOpen([BigInt(epoch)]);
487
+ return isOpen;
488
+ } catch (err) {
489
+ this.logger.warn('isEscapeHatchOpen failed (treating as closed); RPC or contract error may cause liveness risk', {
490
+ epoch: Number(epoch),
491
+ error: err,
492
+ });
493
+ return false;
494
+ }
296
495
  }
297
496
 
298
497
  /**
299
498
  * Returns a SlasherContract instance for interacting with the slasher contract.
300
499
  */
301
500
  async getSlasherContract(): Promise<SlasherContract | undefined> {
302
- const slasherAddress = EthAddress.fromString(await this.getSlasherAddress());
501
+ const slasherAddress = await this.getSlasherAddress();
303
502
  if (slasherAddress.isZero()) {
304
503
  return undefined;
305
504
  }
306
505
  return new SlasherContract(this.client, slasherAddress);
307
506
  }
308
507
 
309
- getOwner() {
310
- return this.rollup.read.owner();
508
+ async getOwner(): Promise<EthAddress> {
509
+ return EthAddress.fromString(await this.rollup.read.owner());
311
510
  }
312
511
 
313
- getActiveAttesterCount() {
314
- return this.rollup.read.getActiveAttesterCount();
512
+ async getActiveAttesterCount(): Promise<number> {
513
+ return Number(await this.rollup.read.getActiveAttesterCount());
315
514
  }
316
515
 
317
516
  public async getSlashingProposerAddress() {
@@ -322,31 +521,50 @@ export class RollupContract {
322
521
  return await slasher.getProposer();
323
522
  }
324
523
 
325
- getCheckpointReward() {
524
+ getCheckpointReward(): Promise<bigint> {
326
525
  return this.rollup.read.getCheckpointReward();
327
526
  }
328
527
 
329
- getCheckpointNumber() {
330
- return this.rollup.read.getPendingCheckpointNumber();
528
+ async getCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
529
+ await checkBlockTag(options?.blockNumber, this.client);
530
+ return CheckpointNumber.fromBigInt(await this.rollup.read.getPendingCheckpointNumber(options));
331
531
  }
332
532
 
333
- getProvenCheckpointNumber() {
334
- return this.rollup.read.getProvenCheckpointNumber();
533
+ async getProvenCheckpointNumber(options?: { blockNumber?: bigint }): Promise<CheckpointNumber> {
534
+ await checkBlockTag(options?.blockNumber, this.client);
535
+ return CheckpointNumber.fromBigInt(await this.rollup.read.getProvenCheckpointNumber(options));
335
536
  }
336
537
 
337
- async getSlotNumber(): Promise<SlotNumber> {
338
- return SlotNumber.fromBigInt(await this.rollup.read.getCurrentSlot());
538
+ async getSlotNumber(options?: { blockNumber?: bigint }): Promise<SlotNumber> {
539
+ await checkBlockTag(options?.blockNumber, this.client);
540
+ return SlotNumber.fromBigInt(await this.rollup.read.getCurrentSlot(options));
339
541
  }
340
542
 
341
- getL1FeesAt(timestamp: bigint) {
342
- return this.rollup.read.getL1FeesAt([timestamp]);
543
+ async getL1FeesAt(timestamp: bigint, options?: { blockNumber?: bigint }): Promise<L1FeeData> {
544
+ await checkBlockTag(options?.blockNumber, this.client);
545
+ const result = await this.rollup.read.getL1FeesAt([timestamp], options);
546
+ return {
547
+ baseFee: result.baseFee,
548
+ blobFee: result.blobFee,
549
+ };
343
550
  }
344
551
 
345
- getFeeAssetPerEth() {
346
- return this.rollup.read.getFeeAssetPerEth();
552
+ async getFeeHeader(checkpointNumber: bigint): Promise<FeeHeader> {
553
+ const result = await this.rollup.read.getFeeHeader([checkpointNumber]);
554
+ return {
555
+ excessMana: result.excessMana,
556
+ manaUsed: result.manaUsed,
557
+ ethPerFeeAsset: result.ethPerFeeAsset,
558
+ congestionCost: result.congestionCost,
559
+ proverCost: result.proverCost,
560
+ };
347
561
  }
348
562
 
349
- async getCommitteeAt(timestamp: bigint): Promise<readonly `0x${string}`[] | undefined> {
563
+ getEthPerFeeAsset(): Promise<bigint> {
564
+ return this.rollup.read.getEthPerFeeAsset();
565
+ }
566
+
567
+ async getCommitteeAt(timestamp: bigint): Promise<EthAddress[] | undefined> {
350
568
  const { result } = await this.client
351
569
  .simulateContract({
352
570
  address: this.address,
@@ -361,22 +579,22 @@ export class RollupContract {
361
579
  throw e;
362
580
  });
363
581
 
364
- return result;
582
+ return result ? result.map(addr => EthAddress.fromString(addr)) : undefined;
365
583
  }
366
584
 
367
- getSampleSeedAt(timestamp: bigint) {
368
- return this.rollup.read.getSampleSeedAt([timestamp]);
585
+ async getSampleSeedAt(timestamp: bigint): Promise<Buffer32> {
586
+ return Buffer32.fromBigInt(await this.rollup.read.getSampleSeedAt([timestamp]));
369
587
  }
370
588
 
371
- getCurrentSampleSeed() {
372
- return this.rollup.read.getCurrentSampleSeed();
589
+ async getCurrentSampleSeed(): Promise<Buffer32> {
590
+ return Buffer32.fromBigInt(await this.rollup.read.getCurrentSampleSeed());
373
591
  }
374
592
 
375
593
  async getCurrentEpoch(): Promise<EpochNumber> {
376
594
  return EpochNumber.fromBigInt(await this.rollup.read.getCurrentEpoch());
377
595
  }
378
596
 
379
- async getCurrentEpochCommittee(): Promise<readonly `0x${string}`[] | undefined> {
597
+ async getCurrentEpochCommittee(): Promise<EthAddress[] | undefined> {
380
598
  const { result } = await this.client
381
599
  .simulateContract({
382
600
  address: this.address,
@@ -391,10 +609,10 @@ export class RollupContract {
391
609
  throw e;
392
610
  });
393
611
 
394
- return result;
612
+ return result ? result.map(addr => EthAddress.fromString(addr)) : undefined;
395
613
  }
396
614
 
397
- async getCurrentProposer() {
615
+ async getCurrentProposer(): Promise<EthAddress> {
398
616
  const { result } = await this.client.simulateContract({
399
617
  address: this.address,
400
618
  abi: RollupAbi,
@@ -402,10 +620,10 @@ export class RollupContract {
402
620
  args: [],
403
621
  });
404
622
 
405
- return result;
623
+ return EthAddress.fromString(result);
406
624
  }
407
625
 
408
- async getProposerAt(timestamp: bigint) {
626
+ async getProposerAt(timestamp: bigint): Promise<EthAddress> {
409
627
  const { result } = await this.client.simulateContract({
410
628
  address: this.address,
411
629
  abi: RollupAbi,
@@ -413,27 +631,86 @@ export class RollupContract {
413
631
  args: [timestamp],
414
632
  });
415
633
 
416
- return result;
634
+ return EthAddress.fromString(result);
417
635
  }
418
636
 
419
- getCheckpoint(checkpointNumber: bigint | number) {
420
- return this.rollup.read.getCheckpoint([BigInt(checkpointNumber)]);
637
+ async getCheckpoint(checkpointNumber: CheckpointNumber, options?: { blockNumber?: bigint }): Promise<CheckpointLog> {
638
+ await checkBlockTag(options?.blockNumber, this.client);
639
+ const result = await this.rollup.read.getCheckpoint([BigInt(checkpointNumber)], options);
640
+ return {
641
+ archive: Fr.fromString(result.archive),
642
+ headerHash: Buffer32.fromString(result.headerHash),
643
+ blobCommitmentsHash: Buffer32.fromString(result.blobCommitmentsHash),
644
+ attestationsHash: Buffer32.fromString(result.attestationsHash),
645
+ payloadDigest: Buffer32.fromString(result.payloadDigest),
646
+ slotNumber: SlotNumber.fromBigInt(result.slotNumber),
647
+ feeHeader: {
648
+ excessMana: result.feeHeader.excessMana,
649
+ manaUsed: result.feeHeader.manaUsed,
650
+ ethPerFeeAsset: result.feeHeader.ethPerFeeAsset,
651
+ congestionCost: result.feeHeader.congestionCost,
652
+ proverCost: result.feeHeader.proverCost,
653
+ },
654
+ };
421
655
  }
422
656
 
423
- getTips() {
424
- return this.rollup.read.getTips();
657
+ /** Returns the pending checkpoint from the rollup contract */
658
+ getPendingCheckpoint() {
659
+ // We retry because of race conditions during prunes: we may get a pending checkpoint number which is immediately
660
+ // reorged out due to a prune happening, causing the subsequent getCheckpoint call to fail. So we try again in that case.
661
+ return retry(
662
+ async () => {
663
+ const pendingCheckpointNumber = await this.getCheckpointNumber();
664
+ const pendingCheckpoint = await this.getCheckpoint(pendingCheckpointNumber);
665
+ return pendingCheckpoint;
666
+ },
667
+ 'getting pending checkpoint',
668
+ makeBackoff([0.5, 0.5, 0.5]),
669
+ );
425
670
  }
426
671
 
427
- getTimestampForSlot(slot: SlotNumber) {
672
+ /**
673
+ * Returns the effective pending checkpoint, accounting for potential prunes.
674
+ * When a prune can happen, the L1 contract uses the proven checkpoint instead of the pending one.
675
+ * This mirrors the behavior of getEffectivePendingCheckpointNumber in STFLib.sol.
676
+ * @param atTimestamp - The timestamp to evaluate pruneability at. Defaults to the current L1 block timestamp.
677
+ * @param options - Optional L1 block number to pin the queries to.
678
+ */
679
+ getEffectivePendingCheckpoint(atTimestamp?: bigint, options?: { blockNumber?: bigint }) {
680
+ return retry(
681
+ async () => {
682
+ const timestamp = atTimestamp ?? (await this.client.getBlock()).timestamp;
683
+ const canPrune = await this.canPruneAtTime(timestamp, options);
684
+ if (canPrune) {
685
+ const provenCheckpointNumber = await this.getProvenCheckpointNumber(options);
686
+ return await this.getCheckpoint(provenCheckpointNumber, options);
687
+ }
688
+ const pendingCheckpointNumber = await this.getCheckpointNumber(options);
689
+ return await this.getCheckpoint(pendingCheckpointNumber, options);
690
+ },
691
+ 'getting effective pending checkpoint',
692
+ makeBackoff([0.5, 0.5, 0.5]),
693
+ );
694
+ }
695
+
696
+ async getTips(): Promise<{ pending: CheckpointNumber; proven: CheckpointNumber }> {
697
+ const { pending, proven } = await this.rollup.read.getTips();
698
+ return {
699
+ pending: CheckpointNumber.fromBigInt(pending),
700
+ proven: CheckpointNumber.fromBigInt(proven),
701
+ };
702
+ }
703
+
704
+ getTimestampForSlot(slot: SlotNumber): Promise<bigint> {
428
705
  return this.rollup.read.getTimestampForSlot([BigInt(slot)]);
429
706
  }
430
707
 
431
- getEntryQueueLength() {
432
- return this.rollup.read.getEntryQueueLength();
708
+ async getEntryQueueLength(): Promise<number> {
709
+ return Number(await this.rollup.read.getEntryQueueLength());
433
710
  }
434
711
 
435
- getAvailableValidatorFlushes() {
436
- return this.rollup.read.getAvailableValidatorFlushes();
712
+ async getAvailableValidatorFlushes(): Promise<number> {
713
+ return Number(await this.rollup.read.getAvailableValidatorFlushes());
437
714
  }
438
715
 
439
716
  async getNextFlushableEpoch(): Promise<EpochNumber> {
@@ -444,7 +721,7 @@ export class RollupContract {
444
721
  return EpochNumber.fromBigInt(await this.rollup.read.getCurrentEpoch());
445
722
  }
446
723
 
447
- async getEpochNumberForCheckpoint(checkpointNumber: bigint): Promise<EpochNumber> {
724
+ async getEpochNumberForCheckpoint(checkpointNumber: CheckpointNumber): Promise<EpochNumber> {
448
725
  return EpochNumber.fromBigInt(await this.rollup.read.getEpochForCheckpoint([BigInt(checkpointNumber)]));
449
726
  }
450
727
 
@@ -489,10 +766,11 @@ export class RollupContract {
489
766
  return EpochNumber.fromBigInt(await this.rollup.read.getEpochAtSlot([BigInt(slotNumber)]));
490
767
  }
491
768
 
492
- getEpochProofPublicInputs(
769
+ async getEpochProofPublicInputs(
493
770
  args: readonly [bigint, bigint, EpochProofPublicInputArgs, readonly `0x${string}`[], `0x${string}`],
494
- ) {
495
- return this.rollup.read.getEpochProofPublicInputs(args);
771
+ ): Promise<Fr[]> {
772
+ const result = await this.rollup.read.getEpochProofPublicInputs(args);
773
+ return result.map(Fr.fromString);
496
774
  }
497
775
 
498
776
  public async validateHeader(
@@ -533,14 +811,13 @@ export class RollupContract {
533
811
  * timestamp of the next L1 block
534
812
  * @throws otherwise
535
813
  */
536
- public async canProposeAtNextEthBlock(
814
+ public async canProposeAt(
537
815
  archive: Buffer,
538
816
  account: `0x${string}` | Account,
539
- slotDuration: number,
540
- opts: { forcePendingCheckpointNumber?: number } = {},
541
- ): Promise<{ slot: SlotNumber; checkpointNumber: bigint; timeOfNextL1Slot: bigint }> {
542
- const latestBlock = await this.client.getBlock();
543
- const timeOfNextL1Slot = latestBlock.timestamp + BigInt(slotDuration);
817
+ timestamp: bigint,
818
+ stateOverride: StateOverride = [],
819
+ ): Promise<{ slot: SlotNumber; checkpointNumber: CheckpointNumber; timeOfNextL1Slot: bigint }> {
820
+ const timeOfNextL1Slot = timestamp;
544
821
  const who = typeof account === 'string' ? account : account.address;
545
822
 
546
823
  try {
@@ -552,10 +829,14 @@ export class RollupContract {
552
829
  functionName: 'canProposeAtTime',
553
830
  args: [timeOfNextL1Slot, `0x${archive.toString('hex')}`, who],
554
831
  account,
555
- stateOverride: await this.makePendingCheckpointNumberOverride(opts.forcePendingCheckpointNumber),
832
+ stateOverride,
556
833
  });
557
834
 
558
- return { slot: SlotNumber.fromBigInt(slot), checkpointNumber, timeOfNextL1Slot };
835
+ return {
836
+ slot: SlotNumber.fromBigInt(slot),
837
+ checkpointNumber: CheckpointNumber.fromBigInt(checkpointNumber),
838
+ timeOfNextL1Slot,
839
+ };
559
840
  } catch (err: unknown) {
560
841
  throw formatViemError(err);
561
842
  }
@@ -567,7 +848,7 @@ export class RollupContract {
567
848
  * stored in the same slot. If the argument is undefined, it returns an empty override.
568
849
  */
569
850
  public async makePendingCheckpointNumberOverride(
570
- forcePendingCheckpointNumber: number | undefined,
851
+ forcePendingCheckpointNumber: CheckpointNumber | undefined,
571
852
  ): Promise<StateOverride> {
572
853
  if (forcePendingCheckpointNumber === undefined) {
573
854
  return [];
@@ -584,15 +865,161 @@ export class RollupContract {
584
865
  ];
585
866
  }
586
867
 
868
+ /**
869
+ * Returns a state override that sets tempCheckpointLogs[checkpointNumber].feeHeader to the compressed fee header.
870
+ * Used when simulating a propose call where the parent checkpoint hasn't landed on L1 yet (pipelining).
871
+ */
872
+ public async makeFeeHeaderOverride(checkpointNumber: CheckpointNumber, feeHeader: FeeHeader): Promise<StateOverride> {
873
+ const { epochDuration, proofSubmissionEpochs } = await this.getRollupConstants();
874
+ const roundaboutSize = BigInt(epochDuration * (proofSubmissionEpochs + 1) + 1);
875
+ const circularIndex = BigInt(checkpointNumber) % roundaboutSize;
876
+
877
+ // tempCheckpointLogs is at offset 2 in RollupStore
878
+ const tempCheckpointLogsMappingBase = hexToBigInt(RollupContract.stfStorageSlot) + 2n;
879
+
880
+ // Solidity mapping slot: keccak256(abi.encode(key, baseSlot))
881
+ const structBaseSlot = hexToBigInt(
882
+ keccak256(
883
+ encodeAbiParameters([{ type: 'uint256' }, { type: 'uint256' }], [circularIndex, tempCheckpointLogsMappingBase]),
884
+ ),
885
+ );
886
+
887
+ // feeHeader is the 7th field (offset 6) in CompressedTempCheckpointLog
888
+ const feeHeaderSlot = structBaseSlot + 6n;
889
+ const compressed = RollupContract.compressFeeHeader(feeHeader);
890
+
891
+ return [
892
+ {
893
+ address: this.address,
894
+ stateDiff: [
895
+ {
896
+ slot: `0x${feeHeaderSlot.toString(16).padStart(64, '0')}`,
897
+ value: `0x${compressed.toString(16).padStart(64, '0')}`,
898
+ },
899
+ ],
900
+ },
901
+ ];
902
+ }
903
+
904
+ /**
905
+ * Returns a state override that sets archives[checkpointNumber] to the given archive value.
906
+ * Used when simulating a canProposeAtTime call where the local archive differs from L1
907
+ * (e.g. pipelining where the parent checkpoint hasn't landed on L1 yet).
908
+ */
909
+ public makeArchiveOverride(checkpointNumber: CheckpointNumber, archive: Fr): StateOverride {
910
+ const archivesMappingBase = hexToBigInt(RollupContract.stfStorageSlot) + 1n;
911
+ const archiveSlot = hexToBigInt(
912
+ keccak256(
913
+ encodeAbiParameters(
914
+ [{ type: 'uint256' }, { type: 'uint256' }],
915
+ [BigInt(checkpointNumber), archivesMappingBase],
916
+ ),
917
+ ),
918
+ );
919
+ return [
920
+ {
921
+ address: this.address,
922
+ stateDiff: [
923
+ {
924
+ slot: `0x${archiveSlot.toString(16).padStart(64, '0')}`,
925
+ value: archive.toString(),
926
+ },
927
+ ],
928
+ },
929
+ ];
930
+ }
931
+
932
+ /** Merges multiple StateOverride arrays, combining stateDiff entries for the same address. */
933
+ public static mergeStateOverrides(...overrides: StateOverride[]): StateOverride {
934
+ type StateDiffEntry = { slot: `0x${string}`; value: `0x${string}` };
935
+ const byAddress = new Map<string, { address: `0x${string}`; balance?: bigint; stateDiff: StateDiffEntry[] }>();
936
+ for (const override of overrides) {
937
+ for (const entry of override) {
938
+ const key = entry.address.toLowerCase();
939
+ const existing = byAddress.get(key);
940
+ if (existing) {
941
+ existing.stateDiff.push(...(entry.stateDiff ?? []));
942
+ if (entry.balance !== undefined) {
943
+ existing.balance = entry.balance;
944
+ }
945
+ } else {
946
+ byAddress.set(key, {
947
+ address: entry.address,
948
+ balance: entry.balance,
949
+ stateDiff: [...(entry.stateDiff ?? [])],
950
+ });
951
+ }
952
+ }
953
+ }
954
+ return [...byAddress.values()];
955
+ }
956
+
957
+ /** Compresses a FeeHeader into a uint256 matching FeeHeaderLib.compress() in FeeStructs.sol. */
958
+ public static compressFeeHeader(feeHeader: FeeHeader): bigint {
959
+ const MASK_48_BITS = (1n << 48n) - 1n;
960
+ const MASK_64_BITS = (1n << 64n) - 1n;
961
+ const MASK_63_BITS = (1n << 63n) - 1n;
962
+
963
+ let value = BigInt(feeHeader.manaUsed) & ((1n << 32n) - 1n); // bits [0:31]
964
+ value |= (feeHeader.excessMana < MASK_48_BITS ? feeHeader.excessMana : MASK_48_BITS) << 32n; // bits [32:79]
965
+ value |= (BigInt(feeHeader.ethPerFeeAsset) & MASK_48_BITS) << 80n; // bits [80:127]
966
+ value |= (feeHeader.congestionCost < MASK_64_BITS ? feeHeader.congestionCost : MASK_64_BITS) << 128n; // bits [128:191]
967
+ value |= (feeHeader.proverCost < MASK_63_BITS ? feeHeader.proverCost : MASK_63_BITS) << 192n; // bits [192:254]
968
+ value |= 1n << 255n; // preheat flag
969
+ return value;
970
+ }
971
+
972
+ /** Computes the fee header for a child checkpoint given parent fee header and child data.
973
+ * Must stay in sync with Solidity FeeLib.sol (computeNewEthPerFeeAsset, clampedAdd). */
974
+ public static computeChildFeeHeader(
975
+ parentFeeHeader: FeeHeader,
976
+ childManaUsed: bigint,
977
+ feeAssetPriceModifier: bigint,
978
+ manaTarget: bigint,
979
+ ): FeeHeader {
980
+ const MIN_ETH_PER_FEE_ASSET = 100n;
981
+ const MAX_ETH_PER_FEE_ASSET = 100_000_000_000_000n; // 1e14, matches FeeLib.sol
982
+
983
+ // excessMana = clampedAdd(parent.excessMana + parent.manaUsed, -manaTarget)
984
+ const sum = parentFeeHeader.excessMana + parentFeeHeader.manaUsed;
985
+ const excessMana = sum > manaTarget ? sum - manaTarget : 0n;
986
+
987
+ // ethPerFeeAsset = computeNewEthPerFeeAsset(max(parent.ethPerFeeAsset, MIN), modifier)
988
+ const parentPrice =
989
+ parentFeeHeader.ethPerFeeAsset > MIN_ETH_PER_FEE_ASSET ? parentFeeHeader.ethPerFeeAsset : MIN_ETH_PER_FEE_ASSET;
990
+ let newPrice: bigint;
991
+ if (feeAssetPriceModifier >= 0n) {
992
+ newPrice = (parentPrice * (10_000n + feeAssetPriceModifier)) / 10_000n;
993
+ } else {
994
+ const absMod = -feeAssetPriceModifier;
995
+ newPrice = (parentPrice * (10_000n - absMod)) / 10_000n;
996
+ }
997
+ if (newPrice < MIN_ETH_PER_FEE_ASSET) {
998
+ newPrice = MIN_ETH_PER_FEE_ASSET;
999
+ }
1000
+ if (newPrice > MAX_ETH_PER_FEE_ASSET) {
1001
+ newPrice = MAX_ETH_PER_FEE_ASSET;
1002
+ }
1003
+
1004
+ return {
1005
+ excessMana,
1006
+ manaUsed: childManaUsed,
1007
+ ethPerFeeAsset: newPrice,
1008
+ congestionCost: 0n,
1009
+ proverCost: 0n,
1010
+ };
1011
+ }
1012
+
587
1013
  /** Creates a request to Rollup#invalidateBadAttestation to be simulated or sent */
588
1014
  public buildInvalidateBadAttestationRequest(
589
- checkpointNumber: number,
1015
+ checkpointNumber: CheckpointNumber,
590
1016
  attestationsAndSigners: ViemCommitteeAttestations,
591
1017
  committee: EthAddress[],
592
1018
  invalidIndex: number,
593
1019
  ): L1TxRequest {
594
1020
  return {
595
1021
  to: this.address,
1022
+ abi: RollupAbi,
596
1023
  data: encodeFunctionData({
597
1024
  abi: RollupAbi,
598
1025
  functionName: 'invalidateBadAttestation',
@@ -608,12 +1035,13 @@ export class RollupContract {
608
1035
 
609
1036
  /** Creates a request to Rollup#invalidateInsufficientAttestations to be simulated or sent */
610
1037
  public buildInvalidateInsufficientAttestationsRequest(
611
- checkpointNumber: number,
1038
+ checkpointNumber: CheckpointNumber,
612
1039
  attestationsAndSigners: ViemCommitteeAttestations,
613
1040
  committee: EthAddress[],
614
1041
  ): L1TxRequest {
615
1042
  return {
616
1043
  to: this.address,
1044
+ abi: RollupAbi,
617
1045
  data: encodeFunctionData({
618
1046
  abi: RollupAbi,
619
1047
  functionName: 'invalidateInsufficientAttestations',
@@ -630,90 +1058,133 @@ export class RollupContract {
630
1058
  return this.rollup.read.getHasSubmitted([BigInt(epochNumber), BigInt(numberOfCheckpointsInEpoch), prover]);
631
1059
  }
632
1060
 
633
- getManaBaseFeeAt(timestamp: bigint, inFeeAsset: boolean) {
634
- return this.rollup.read.getManaBaseFeeAt([timestamp, inFeeAsset]);
1061
+ getManaMinFeeAt(timestamp: bigint, inFeeAsset: boolean, stateOverride?: StateOverride): Promise<bigint> {
1062
+ return this.rollup.read.getManaMinFeeAt([timestamp, inFeeAsset], { stateOverride });
1063
+ }
1064
+
1065
+ async getManaMinFeeComponentsAt(timestamp: bigint, inFeeAsset: boolean): Promise<ManaMinFeeComponents> {
1066
+ const result = await this.rollup.read.getManaMinFeeComponentsAt([timestamp, inFeeAsset]);
1067
+ return {
1068
+ sequencerCost: result.sequencerCost,
1069
+ proverCost: result.proverCost,
1070
+ congestionCost: result.congestionCost,
1071
+ congestionMultiplier: result.congestionMultiplier,
1072
+ };
635
1073
  }
636
1074
 
637
1075
  async getSlotAt(timestamp: bigint): Promise<SlotNumber> {
638
1076
  return SlotNumber.fromBigInt(await this.rollup.read.getSlotAt([timestamp]));
639
1077
  }
640
1078
 
641
- async status(checkpointNumber: bigint, options?: { blockNumber?: bigint }) {
1079
+ async status(checkpointNumber: CheckpointNumber, options?: { blockNumber?: bigint }): Promise<RollupStatusResponse> {
642
1080
  await checkBlockTag(options?.blockNumber, this.client);
643
- return this.rollup.read.status([checkpointNumber], options);
1081
+ const result = await this.rollup.read.status([BigInt(checkpointNumber)], options);
1082
+ return {
1083
+ provenCheckpointNumber: CheckpointNumber.fromBigInt(result[0]),
1084
+ provenArchive: Fr.fromString(result[1]),
1085
+ pendingCheckpointNumber: CheckpointNumber.fromBigInt(result[2]),
1086
+ pendingArchive: Fr.fromString(result[3]),
1087
+ archiveOfMyCheckpoint: Fr.fromString(result[4]),
1088
+ };
644
1089
  }
645
1090
 
646
- async canPruneAtTime(timestamp: bigint, options?: { blockNumber?: bigint }) {
1091
+ async canPruneAtTime(timestamp: bigint, options?: { blockNumber?: bigint }): Promise<boolean> {
647
1092
  await checkBlockTag(options?.blockNumber, this.client);
648
1093
  return this.rollup.read.canPruneAtTime([timestamp], options);
649
1094
  }
650
1095
 
651
- archive() {
652
- return this.rollup.read.archive();
1096
+ async archive(): Promise<Fr> {
1097
+ return Fr.fromString(await this.rollup.read.archive());
653
1098
  }
654
1099
 
655
- archiveAt(checkpointNumber: bigint) {
656
- return this.rollup.read.archiveAt([checkpointNumber]);
1100
+ async archiveAt(checkpointNumber: CheckpointNumber): Promise<Fr> {
1101
+ return Fr.fromString(await this.rollup.read.archiveAt([BigInt(checkpointNumber)]));
657
1102
  }
658
1103
 
659
- getSequencerRewards(address: Hex | EthAddress) {
1104
+ getSequencerRewards(address: Hex | EthAddress): Promise<bigint> {
660
1105
  if (address instanceof EthAddress) {
661
1106
  address = address.toString();
662
1107
  }
663
1108
  return this.rollup.read.getSequencerRewards([address]);
664
1109
  }
665
1110
 
666
- getSpecificProverRewardsForEpoch(epoch: bigint, prover: Hex | EthAddress) {
1111
+ getSpecificProverRewardsForEpoch(epoch: bigint, prover: Hex | EthAddress): Promise<bigint> {
667
1112
  if (prover instanceof EthAddress) {
668
1113
  prover = prover.toString();
669
1114
  }
670
1115
  return this.rollup.read.getSpecificProverRewardsForEpoch([epoch, prover]);
671
1116
  }
672
1117
 
673
- async getAttesters() {
1118
+ async getAttesters(timestamp?: bigint): Promise<EthAddress[]> {
674
1119
  const attesterSize = await this.getActiveAttesterCount();
675
1120
  const gse = new GSEContract(this.client, await this.getGSE());
676
- const ts = (await this.client.getBlock()).timestamp;
677
-
678
- const indices = Array.from({ length: Number(attesterSize) }, (_, i) => BigInt(i));
1121
+ const ts = timestamp ?? (await this.client.getBlock()).timestamp;
1122
+ const indices = Array.from({ length: attesterSize }, (_, i) => BigInt(i));
679
1123
  const chunks = chunk(indices, 1000);
680
1124
 
681
- return (await Promise.all(chunks.map(chunk => gse.getAttestersFromIndicesAtTime(this.address, ts, chunk)))).flat();
1125
+ const results = await Promise.all(chunks.map(chunk => gse.getAttestersFromIndicesAtTime(this.address, ts, chunk)));
1126
+ return results.flat().map(addr => EthAddress.fromString(addr));
682
1127
  }
683
1128
 
684
- getAttesterView(address: Hex | EthAddress) {
1129
+ async getAttesterView(address: Hex | EthAddress): Promise<AttesterView> {
685
1130
  if (address instanceof EthAddress) {
686
1131
  address = address.toString();
687
1132
  }
688
- return this.rollup.read.getAttesterView([address]);
1133
+ const result = await this.rollup.read.getAttesterView([address]);
1134
+ return {
1135
+ status: result.status as AttesterStatus,
1136
+ effectiveBalance: result.effectiveBalance,
1137
+ exit: {
1138
+ withdrawalId: result.exit.withdrawalId,
1139
+ amount: result.exit.amount,
1140
+ exitableAt: result.exit.exitableAt,
1141
+ recipientOrWithdrawer: EthAddress.fromString(result.exit.recipientOrWithdrawer),
1142
+ isRecipient: result.exit.isRecipient,
1143
+ exists: result.exit.exists,
1144
+ },
1145
+ config: {
1146
+ publicKey: {
1147
+ x: result.config.publicKey.x,
1148
+ y: result.config.publicKey.y,
1149
+ },
1150
+ withdrawer: EthAddress.fromString(result.config.withdrawer),
1151
+ },
1152
+ };
689
1153
  }
690
1154
 
691
- getStatus(address: Hex | EthAddress) {
1155
+ async getStatus(address: Hex | EthAddress): Promise<AttesterStatus> {
692
1156
  if (address instanceof EthAddress) {
693
1157
  address = address.toString();
694
1158
  }
695
- return this.rollup.read.getStatus([address]);
1159
+ return (await this.rollup.read.getStatus([address])) as AttesterStatus;
696
1160
  }
697
1161
 
698
- getBlobCommitmentsHash(checkpointNumber: bigint) {
699
- return this.rollup.read.getBlobCommitmentsHash([checkpointNumber]);
1162
+ async getBlobCommitmentsHash(checkpointNumber: CheckpointNumber): Promise<Buffer32> {
1163
+ return Buffer32.fromString(await this.rollup.read.getBlobCommitmentsHash([BigInt(checkpointNumber)]));
700
1164
  }
701
1165
 
702
- getCurrentBlobCommitmentsHash() {
703
- return this.rollup.read.getCurrentBlobCommitmentsHash();
1166
+ async getCurrentBlobCommitmentsHash(): Promise<Buffer32> {
1167
+ return Buffer32.fromString(await this.rollup.read.getCurrentBlobCommitmentsHash());
704
1168
  }
705
1169
 
706
- getStakingAsset() {
707
- return this.rollup.read.getStakingAsset();
1170
+ async getStakingAsset(): Promise<EthAddress> {
1171
+ return EthAddress.fromString(await this.rollup.read.getStakingAsset());
708
1172
  }
709
1173
 
710
- getRewardConfig() {
711
- return this.rollup.read.getRewardConfig();
1174
+ async getRewardConfig(): Promise<RewardConfig> {
1175
+ const result = await this.rollup.read.getRewardConfig();
1176
+ return {
1177
+ rewardDistributor: EthAddress.fromString(result.rewardDistributor),
1178
+ sequencerBps: BigInt(result.sequencerBps),
1179
+ booster: EthAddress.fromString(result.booster),
1180
+ checkpointReward: result.checkpointReward,
1181
+ };
712
1182
  }
713
1183
 
714
1184
  setupEpoch(l1TxUtils: L1TxUtils) {
715
1185
  return l1TxUtils.sendAndMonitorTransaction({
716
1186
  to: this.address,
1187
+ abi: RollupAbi,
717
1188
  data: encodeFunctionData({
718
1189
  abi: RollupAbi,
719
1190
  functionName: 'setupEpoch',
@@ -725,6 +1196,7 @@ export class RollupContract {
725
1196
  vote(l1TxUtils: L1TxUtils, proposalId: bigint) {
726
1197
  return l1TxUtils.sendAndMonitorTransaction({
727
1198
  to: this.address,
1199
+ abi: RollupAbi,
728
1200
  data: encodeFunctionData({
729
1201
  abi: RollupAbi,
730
1202
  functionName: 'vote',
@@ -752,7 +1224,7 @@ export class RollupContract {
752
1224
  }
753
1225
 
754
1226
  public listenToCheckpointInvalidated(
755
- callback: (args: { checkpointNumber: bigint }) => unknown,
1227
+ callback: (args: { checkpointNumber: CheckpointNumber }) => unknown,
756
1228
  ): WatchContractEventReturnType {
757
1229
  return this.rollup.watchEvent.CheckpointInvalidated(
758
1230
  {},
@@ -761,7 +1233,7 @@ export class RollupContract {
761
1233
  for (const log of logs) {
762
1234
  const args = log.args;
763
1235
  if (args.checkpointNumber !== undefined) {
764
- callback({ checkpointNumber: args.checkpointNumber });
1236
+ callback({ checkpointNumber: CheckpointNumber.fromBigInt(args.checkpointNumber) });
765
1237
  }
766
1238
  }
767
1239
  },
@@ -793,4 +1265,72 @@ export class RollupContract {
793
1265
  },
794
1266
  );
795
1267
  }
1268
+
1269
+ /** Fetches CheckpointProposed events within the given block range. */
1270
+ async getCheckpointProposedEvents(fromBlock: bigint, toBlock: bigint): Promise<CheckpointProposedLog[]> {
1271
+ const logs = await this.rollup.getEvents.CheckpointProposed({}, { fromBlock, toBlock });
1272
+ return logs
1273
+ .filter(log => log.blockNumber! >= fromBlock && log.blockNumber! <= toBlock)
1274
+ .map(log => ({
1275
+ l1BlockNumber: log.blockNumber!,
1276
+ l1BlockHash: Buffer32.fromString(log.blockHash!),
1277
+ l1TransactionHash: log.transactionHash!,
1278
+ args: {
1279
+ checkpointNumber: CheckpointNumber.fromBigInt(log.args.checkpointNumber!),
1280
+ archive: Fr.fromString(log.args.archive!),
1281
+ versionedBlobHashes: log.args.versionedBlobHashes!.map(h => Buffer.from(h.slice(2), 'hex')),
1282
+ attestationsHash: (() => {
1283
+ if (!log.args.attestationsHash) {
1284
+ throw new Error(
1285
+ `CheckpointProposed event missing attestationsHash for checkpoint ${log.args.checkpointNumber}`,
1286
+ );
1287
+ }
1288
+ return Buffer32.fromString(log.args.attestationsHash);
1289
+ })(),
1290
+ payloadDigest: (() => {
1291
+ if (!log.args.payloadDigest) {
1292
+ throw new Error(
1293
+ `CheckpointProposed event missing payloadDigest for checkpoint ${log.args.checkpointNumber}`,
1294
+ );
1295
+ }
1296
+ return Buffer32.fromString(log.args.payloadDigest);
1297
+ })(),
1298
+ },
1299
+ }));
1300
+ }
1301
+
1302
+ /** Packs pending and proven checkpoint numbers into the chain tips storage format. */
1303
+ static packChainTips(pendingCheckpointNumber: bigint, provenCheckpointNumber: bigint): bigint {
1304
+ return (pendingCheckpointNumber << 128n) | (provenCheckpointNumber & ((1n << 128n) - 1n));
1305
+ }
1306
+
1307
+ /** Storage slot for the chain tips (offset 0 within the STF storage struct). */
1308
+ static get chainTipsStorageSlot(): bigint {
1309
+ return BigInt(RollupContract.stfStorageSlot);
1310
+ }
1311
+
1312
+ /**
1313
+ * Computes the storage slot for a field within a tempCheckpointLog entry.
1314
+ * @param checkpointNumber - The checkpoint number
1315
+ * @param field - The field within the CompressedTempCheckpointLog struct
1316
+ */
1317
+ async getTempCheckpointLogStorageSlot(
1318
+ checkpointNumber: CheckpointNumber,
1319
+ field: TempCheckpointLogField,
1320
+ ): Promise<bigint> {
1321
+ const fieldOffset = BigInt(field);
1322
+ const [epochDuration, proofSubmissionEpochs] = await Promise.all([
1323
+ this.getEpochDuration(),
1324
+ this.getProofSubmissionEpochs(),
1325
+ ]);
1326
+ const roundaboutSize = BigInt(epochDuration) * (BigInt(proofSubmissionEpochs) + 1n) + 1n;
1327
+ const tempCheckpointLogsBase = BigInt(RollupContract.stfStorageSlot) + 2n;
1328
+ const circularIndex = BigInt(checkpointNumber) % roundaboutSize;
1329
+ const entryBase = BigInt(
1330
+ keccak256(
1331
+ encodeAbiParameters([{ type: 'uint256' }, { type: 'uint256' }], [circularIndex, tempCheckpointLogsBase]),
1332
+ ),
1333
+ );
1334
+ return entryBase + fieldOffset;
1335
+ }
796
1336
  }