@aztec/bot 6.0.0-nightly.20260604 → 6.0.0-nightly.20260721

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.
@@ -31,6 +31,7 @@ import type { ExtendedViemWalletClient } from '@aztec/ethereum/types';
31
31
  import { Fr } from '@aztec/foundation/curves/bn254';
32
32
  import { EthAddress } from '@aztec/foundation/eth-address';
33
33
  import type { TestContract } from '@aztec/noir-test-contracts.js/Test';
34
+ import type { BlockTag } from '@aztec/stdlib/block';
34
35
  import type { AztecNode, AztecNodeAdmin } from '@aztec/stdlib/interfaces/client';
35
36
  import type { EmbeddedWallet } from '@aztec/wallets/embedded';
36
37
 
@@ -60,6 +61,7 @@ export class CrossChainBot extends BaseBot {
60
61
  private readonly rollupVersion: bigint,
61
62
  private readonly store: BotStore,
62
63
  config: BotConfig,
64
+ private readonly syncChainTip?: BlockTag,
63
65
  ) {
64
66
  super(node, wallet, defaultAccountAddress, config);
65
67
  }
@@ -70,11 +72,12 @@ export class CrossChainBot extends BaseBot {
70
72
  aztecNode: AztecNode,
71
73
  aztecNodeAdmin: AztecNodeAdmin | undefined,
72
74
  store: BotStore,
75
+ syncChainTip?: BlockTag,
73
76
  ): Promise<CrossChainBot> {
74
77
  if (config.followChain === 'NONE') {
75
78
  throw new Error(`CrossChainBot requires followChain to be set (got NONE)`);
76
79
  }
77
- const factory = new BotFactory(config, wallet, store, aztecNode, aztecNodeAdmin);
80
+ const factory = new BotFactory(config, wallet, store, aztecNode, aztecNodeAdmin, syncChainTip);
78
81
  const { defaultAccountAddress, contract, l1Client, rollupVersion } = await factory.setupCrossChain();
79
82
  const l1Recipient = EthAddress.fromString(l1Client.account!.address);
80
83
  const { l1ContractAddresses } = await aztecNode.getNodeInfo();
@@ -90,6 +93,7 @@ export class CrossChainBot extends BaseBot {
90
93
  rollupVersion,
91
94
  store,
92
95
  config,
96
+ syncChainTip,
93
97
  );
94
98
  }
95
99
 
@@ -176,7 +180,7 @@ export class CrossChainBot extends BaseBot {
176
180
  ): Promise<PendingL1ToL2Message | undefined> {
177
181
  const now = Date.now();
178
182
  for (const msg of pendingMessages) {
179
- const ready = await isL1ToL2MessageReady(this.node, Fr.fromHexString(msg.msgHash));
183
+ const ready = await isL1ToL2MessageReady(this.node, Fr.fromHexString(msg.msgHash), this.syncChainTip);
180
184
  if (ready) {
181
185
  return msg;
182
186
  }
package/src/factory.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { getInitialTestAccountsData } from '@aztec/accounts/testing';
2
- import { NO_FROM } from '@aztec/aztec.js/account';
3
2
  import { AztecAddress } from '@aztec/aztec.js/addresses';
4
3
  import {
5
4
  BatchCall,
@@ -17,20 +16,18 @@ import { createLogger } from '@aztec/aztec.js/log';
17
16
  import { waitForL1ToL2MessageReady } from '@aztec/aztec.js/messaging';
18
17
  import { waitForTx } from '@aztec/aztec.js/node';
19
18
  import { getFeeJuiceBalance } from '@aztec/aztec.js/utils';
20
- import { ContractInitializationStatus } from '@aztec/aztec.js/wallet';
21
19
  import { createEthereumChain } from '@aztec/ethereum/chain';
22
20
  import { createExtendedL1Client } from '@aztec/ethereum/client';
23
21
  import { RollupContract } from '@aztec/ethereum/contracts';
24
22
  import type { ExtendedViemWalletClient } from '@aztec/ethereum/types';
25
23
  import { Fr } from '@aztec/foundation/curves/bn254';
26
24
  import { EthAddress } from '@aztec/foundation/eth-address';
27
- import { Timer } from '@aztec/foundation/timer';
28
25
  import { AMMContract } from '@aztec/noir-contracts.js/AMM';
29
26
  import { PrivateTokenContract } from '@aztec/noir-contracts.js/PrivateToken';
30
27
  import { TokenContract } from '@aztec/noir-contracts.js/Token';
31
28
  import { TestContract } from '@aztec/noir-test-contracts.js/Test';
29
+ import type { BlockTag } from '@aztec/stdlib/block';
32
30
  import type { ContractInstanceWithAddress } from '@aztec/stdlib/contract';
33
- import { GasFees, GasSettings, ManaUsageEstimate } from '@aztec/stdlib/gas';
34
31
  import type { AztecNode, AztecNodeAdmin } from '@aztec/stdlib/interfaces/client';
35
32
  import { deriveSigningKey } from '@aztec/stdlib/keys';
36
33
  import { EmbeddedWallet } from '@aztec/wallets/embedded';
@@ -43,7 +40,6 @@ import { getBalances, getPrivateBalance, isStandardTokenContract } from './utils
43
40
  const MINT_BALANCE = 1e12;
44
41
  const MIN_BALANCE = 1e3;
45
42
  const FEE_JUICE_TOP_UP_THRESHOLD = 100n * 10n ** 18n;
46
- const FEE_JUICE_TOP_UP_TARGET = 10_000n * 10n ** 18n;
47
43
 
48
44
  export class BotFactory {
49
45
  private log = createLogger('bot');
@@ -54,6 +50,7 @@ export class BotFactory {
54
50
  private readonly store: BotStore,
55
51
  private readonly aztecNode: AztecNode,
56
52
  private readonly aztecNodeAdmin?: AztecNodeAdmin,
53
+ private readonly syncChainTip?: BlockTag,
57
54
  ) {
58
55
  // Set fee padding on the wallet so that all transactions during setup
59
56
  // (token deploy, minting, etc.) use the configured padding, not the default.
@@ -73,8 +70,8 @@ export class BotFactory {
73
70
  }> {
74
71
  const defaultAccountAddress = await this.setupAccount();
75
72
  const recipient = (await this.wallet.createSchnorrAccount(Fr.random(), Fr.random())).address;
76
- const token = await this.setupTokenWithOptionalEarlyRefuel(defaultAccountAddress);
77
- await this.ensureFeeJuiceBalance(defaultAccountAddress, token);
73
+ await this.ensureFeeJuiceBalance(defaultAccountAddress);
74
+ const token = await this.setupToken(defaultAccountAddress);
78
75
  await this.mintTokens(token, defaultAccountAddress);
79
76
  return { wallet: this.wallet, defaultAccountAddress, token, node: this.aztecNode, recipient };
80
77
  }
@@ -88,13 +85,8 @@ export class BotFactory {
88
85
  node: AztecNode;
89
86
  }> {
90
87
  const defaultAccountAddress = await this.setupAccount();
91
- const token0 = await this.setupTokenContractWithOptionalEarlyRefuel(
92
- defaultAccountAddress,
93
- this.config.tokenSalt,
94
- 'BotToken0',
95
- 'BOT0',
96
- );
97
- await this.ensureFeeJuiceBalance(defaultAccountAddress, token0);
88
+ await this.ensureFeeJuiceBalance(defaultAccountAddress);
89
+ const token0 = await this.setupTokenContract(defaultAccountAddress, this.config.tokenSalt, 'BotToken0', 'BOT0');
98
90
  const token1 = await this.setupTokenContract(defaultAccountAddress, this.config.tokenSalt, 'BotToken1', 'BOT1');
99
91
  const liquidityToken = await this.setupTokenContract(
100
92
  defaultAccountAddress,
@@ -129,6 +121,7 @@ export class BotFactory {
129
121
  rollupVersion: bigint;
130
122
  }> {
131
123
  const defaultAccountAddress = await this.setupAccount();
124
+ await this.ensureFeeJuiceBalance(defaultAccountAddress);
132
125
 
133
126
  // Create L1 client (same pattern as bridgeL1FeeJuice)
134
127
  const l1RpcUrls = this.config.l1RpcUrls;
@@ -147,7 +140,7 @@ export class BotFactory {
147
140
  const rollupContract = new RollupContract(l1Client, l1ContractAddresses.rollupAddress.toString());
148
141
  const rollupVersion = await rollupContract.getVersion();
149
142
 
150
- // Deploy TestContract
143
+ // Deploy TestContract (pays from the standing balance funded above).
151
144
  const contract = await this.setupTestContract(defaultAccountAddress);
152
145
 
153
146
  // Recover any pending messages from store (clean up stale ones first)
@@ -174,6 +167,7 @@ export class BotFactory {
174
167
  const firstMsg = allMessages[0];
175
168
  await waitForL1ToL2MessageReady(this.aztecNode, Fr.fromHexString(firstMsg.msgHash), {
176
169
  timeoutSeconds: this.config.l1ToL2MessageTimeoutSeconds,
170
+ chainTip: this.syncChainTip,
177
171
  });
178
172
  this.log.info(`First L1→L2 message is ready`);
179
173
  }
@@ -210,48 +204,16 @@ export class BotFactory {
210
204
  }
211
205
  }
212
206
 
213
- private async setupAccountWithPrivateKey(secret: Fr) {
214
- const salt = this.config.senderSalt ?? Fr.ONE;
215
- const signingKey = deriveSigningKey(secret);
216
- const accountManager = await this.wallet.createSchnorrAccount(secret, salt, signingKey);
217
- const metadata = await this.wallet.getContractMetadata(accountManager.address);
218
- if (metadata.initializationStatus === ContractInitializationStatus.INITIALIZED) {
219
- this.log.info(`Account at ${accountManager.address.toString()} already initialized`);
220
- const timer = new Timer();
221
- const address = accountManager.address;
222
- this.log.info(`Account at ${address} registered. duration=${timer.ms()}`);
223
- await this.store.deleteBridgeClaim(address);
224
- return address;
225
- } else {
226
- const address = accountManager.address;
227
- this.log.info(`Deploying account at ${address}`);
228
-
229
- const claim = await this.getOrCreateBridgeClaim(address);
230
-
231
- const paymentMethod = new FeeJuicePaymentMethodWithClaim(accountManager.address, claim);
232
- const deployMethod = await accountManager.getDeployMethod();
233
-
234
- await this.withNoMinTxsPerBlock(async () => {
235
- const { txHash } = await deployMethod.send({
236
- from: NO_FROM,
237
- fee: { paymentMethod },
238
- wait: NO_WAIT,
239
- });
240
- this.log.info(`Sent tx for account deployment with hash ${txHash.toString()}`);
241
- return waitForTx(this.aztecNode, txHash, { timeout: this.config.txMinedWaitSeconds });
242
- });
243
- this.log.info(`Account deployed at ${address}`);
244
-
245
- // Clean up the consumed bridge claim
246
- await this.store.deleteBridgeClaim(address);
247
-
248
- return accountManager.address;
249
- }
250
- }
251
-
207
+ /**
208
+ * Keyless fallback for tests and local dev: reuses the first genesis test account, whose address is
209
+ * pre-funded with fee juice via `initialFundedAccounts`. The test accounts are initializerless, so this
210
+ * must create an initializerless account for the address to match the funded one. Production bots set a
211
+ * sender private key and fund the resulting initializerless account from L1 instead; see
212
+ * setupAccountWithPrivateKey.
213
+ */
252
214
  private async setupTestAccount() {
253
215
  const [initialAccountData] = await getInitialTestAccountsData();
254
- const accountManager = await this.wallet.createSchnorrAccount(
216
+ const accountManager = await this.wallet.createSchnorrInitializerlessAccount(
255
217
  initialAccountData.secret,
256
218
  initialAccountData.salt,
257
219
  initialAccountData.signingKey,
@@ -259,62 +221,11 @@ export class BotFactory {
259
221
  return accountManager.address;
260
222
  }
261
223
 
262
- /**
263
- * Setup token and refuel first: if the token already exists (restart scenario),
264
- * run ensureFeeJuiceBalance before any step that might need fee juice. When deploying,
265
- * use a bridge claim if balance is below threshold.
266
- */
267
- private async setupTokenWithOptionalEarlyRefuel(sender: AztecAddress): Promise<TokenContract | PrivateTokenContract> {
268
- const token = await this.getTokenInstance(sender);
269
- const address = token.address;
270
- const metadata = await this.wallet.getContractMetadata(address);
271
- if (metadata.isContractPublished) {
272
- this.log.info(`Token at ${address.toString()} already deployed, refueling before setup`);
273
- await this.ensureFeeJuiceBalance(sender, token);
274
- }
275
- return this.setupToken(sender);
276
- }
277
-
278
- /**
279
- * Setup token0 for AMM with refuel-first behaviour when token already exists.
280
- */
281
- private async setupTokenContractWithOptionalEarlyRefuel(
282
- deployer: AztecAddress,
283
- salt: Fr,
284
- name: string,
285
- ticker: string,
286
- decimals = 18,
287
- ): Promise<TokenContract> {
288
- const deploy = TokenContract.deploy(this.wallet, deployer, name, ticker, decimals, { salt, universalDeploy: true });
289
- const instance = await deploy.getInstance();
290
- const metadata = await this.wallet.getContractMetadata(instance.address);
291
- if (metadata.isContractPublished) {
292
- this.log.info(`Token ${name} at ${instance.address.toString()} already deployed, refueling before setup`);
293
- const token = TokenContract.at(instance.address, this.wallet);
294
- await this.ensureFeeJuiceBalance(deployer, token);
295
- }
296
- return this.setupTokenContract(deployer, salt, name, ticker, decimals);
297
- }
298
-
299
- private async getTokenInstance(sender: AztecAddress): Promise<TokenContract | PrivateTokenContract> {
300
- const salt = this.config.tokenSalt;
301
- if (this.config.contract === SupportedTokenContracts.TokenContract) {
302
- const deploy = TokenContract.deploy(this.wallet, sender, 'BotToken', 'BOT', 18, { salt, universalDeploy: true });
303
- const instance = await deploy.getInstance();
304
- return TokenContract.at(instance.address, this.wallet);
305
- }
306
- if (this.config.contract === SupportedTokenContracts.PrivateTokenContract) {
307
- const tokenSecretKey = Fr.random();
308
- const tokenPublicKeys = (await deriveKeys(tokenSecretKey)).publicKeys;
309
- const deploy = PrivateTokenContract.deploy(this.wallet, MINT_BALANCE, sender, {
310
- salt,
311
- universalDeploy: true,
312
- publicKeys: tokenPublicKeys,
313
- });
314
- const instance = await deploy.getInstance();
315
- return PrivateTokenContract.at(instance.address, this.wallet);
316
- }
317
- throw new Error(`Unsupported token contract type: ${this.config.contract}`);
224
+ private async setupAccountWithPrivateKey(secret: Fr) {
225
+ const salt = this.config.senderSalt ?? Fr.ONE;
226
+ const signingKey = deriveSigningKey(secret);
227
+ const accountManager = await this.wallet.createSchnorrInitializerlessAccount(secret, salt, signingKey);
228
+ return accountManager.address;
318
229
  }
319
230
 
320
231
  /**
@@ -511,66 +422,39 @@ export class BotFactory {
511
422
  if (metadata.isContractPublished) {
512
423
  this.log.info(`Contract ${name} at ${address.toString()} already deployed`);
513
424
  await deploy.register();
514
- } else {
515
- const sender = deployOpts.from === NO_FROM ? undefined : deployOpts.from;
516
- const balance = sender ? await getFeeJuiceBalance(sender, this.aztecNode) : 0n;
517
- const useClaim =
518
- sender &&
519
- balance < FEE_JUICE_TOP_UP_THRESHOLD &&
520
- this.config.feePaymentMethod === 'fee_juice' &&
521
- !!this.config.l1RpcUrls?.length;
522
- const mnemonicOrPrivateKey = this.config.l1PrivateKey?.getValue() ?? this.config.l1Mnemonic?.getValue();
523
-
524
- if (useClaim && mnemonicOrPrivateKey) {
525
- const claim = await this.getOrCreateBridgeClaim(sender!);
526
- const paymentMethod = new FeeJuicePaymentMethodWithClaim(sender!, claim);
527
- const { estimatedGas } = await deploy.simulate({ ...deployOpts, fee: { estimateGas: true, paymentMethod } });
528
- const maxFeesPerGas = (await this.getMinFees()).mul(1 + this.config.minFeePadding);
529
- const gasSettings = GasSettings.from({
530
- ...estimatedGas!,
531
- maxFeesPerGas,
532
- maxPriorityFeesPerGas: GasFees.empty(),
533
- });
534
- await this.withNoMinTxsPerBlock(async () => {
535
- const { txHash } = await deploy.send({ ...deployOpts, fee: { gasSettings, paymentMethod }, wait: NO_WAIT });
536
- this.log.info(
537
- `Sent contract ${name} deploy tx ${txHash.toString()} (using bridge claim, balance was ${balance})`,
538
- );
539
- return waitForTx(this.aztecNode, txHash, { timeout: this.config.txMinedWaitSeconds });
540
- });
541
- await this.store.deleteBridgeClaim(sender!);
542
- } else {
543
- const { estimatedGas } = await deploy.simulate({ ...deployOpts, fee: { estimateGas: true } });
544
- this.log.info(`Deploying contract ${name} at ${address.toString()}`, { estimatedGas });
545
- await this.withNoMinTxsPerBlock(async () => {
546
- const { txHash } = await deploy.send({ ...deployOpts, fee: { gasSettings: estimatedGas }, wait: NO_WAIT });
547
- this.log.info(`Sent contract ${name} setup tx with hash ${txHash.toString()}`);
548
- return waitForTx(this.aztecNode, txHash, { timeout: this.config.txMinedWaitSeconds });
549
- });
550
- }
425
+ return instance;
551
426
  }
427
+
428
+ // Setup always runs ensureFeeJuiceBalance before any deploy, so the account pays from its standing
429
+ // balance here. No manual gas estimation: the embedded wallet simulates before sending and derives
430
+ // the gas limits and padded maxFeesPerGas itself.
431
+ this.log.info(`Deploying contract ${name} at ${address.toString()}`);
432
+ await this.withNoMinTxsPerBlock(async () => {
433
+ const { txHash } = await deploy.send({ ...deployOpts, wait: NO_WAIT });
434
+ this.log.info(`Sent contract ${name} deploy tx ${txHash.toString()}`);
435
+ return waitForTx(this.aztecNode, txHash, { timeout: this.config.txMinedWaitSeconds });
436
+ });
437
+
552
438
  return instance;
553
439
  }
554
440
 
441
+ /** True when the config allows bridging fee juice from L1 (fee_juice mode, an L1 RPC, and an L1 key). */
442
+ private isL1BridgingConfigured(): boolean {
443
+ const mnemonicOrPrivateKey = this.config.l1PrivateKey?.getValue() ?? this.config.l1Mnemonic?.getValue();
444
+ return this.config.feePaymentMethod === 'fee_juice' && !!this.config.l1RpcUrls?.length && !!mnemonicOrPrivateKey;
445
+ }
446
+
555
447
  /**
556
- * Mints private and public tokens for the sender if their balance is below the minimum.
557
- * @param token - Token contract.
558
- */
559
- /**
560
- * Ensures the account has sufficient fee juice by bridging from L1 if balance is below threshold.
561
- * Bridges repeatedly until balance reaches the target (10k FJ).
562
- * Used on startup/restart to top up when the account has run out after previous runs.
448
+ * Ensures the account holds enough fee juice before any other setup step. The account starts empty
449
+ * (initializerless accounts have no deployment tx) and the runtime loop pays fees from this balance and
450
+ * never refuels itself, so every flow funds the account up front. Bridges claims from L1 and consumes
451
+ * each with a claim-only tx until the balance clears the threshold, working from a zero (fresh run) or
452
+ * drained (restart) balance. Each bridge mints a fixed amount well above the threshold, so this is a
453
+ * single bridge in practice. No-op when L1 bridging is not configured or the balance is already above
454
+ * the threshold.
563
455
  */
564
- private async ensureFeeJuiceBalance(
565
- account: AztecAddress,
566
- token: TokenContract | PrivateTokenContract,
567
- ): Promise<void> {
568
- const { feePaymentMethod, l1RpcUrls } = this.config;
569
- if (feePaymentMethod !== 'fee_juice' || !l1RpcUrls?.length) {
570
- return;
571
- }
572
- const mnemonicOrPrivateKey = this.config.l1PrivateKey?.getValue() ?? this.config.l1Mnemonic?.getValue();
573
- if (!mnemonicOrPrivateKey) {
456
+ private async ensureFeeJuiceBalance(account: AztecAddress): Promise<void> {
457
+ if (!this.isL1BridgingConfigured()) {
574
458
  return;
575
459
  }
576
460
 
@@ -580,36 +464,21 @@ export class BotFactory {
580
464
  return;
581
465
  }
582
466
 
583
- this.log.info(
584
- `Fee juice balance ${balance} below threshold ${FEE_JUICE_TOP_UP_THRESHOLD}, bridging from L1 until ${FEE_JUICE_TOP_UP_TARGET}`,
585
- );
586
- const maxFeesPerGas = (await this.getMinFees()).mul(1 + this.config.minFeePadding);
587
- const minimalInteraction = isStandardTokenContract(token)
588
- ? token.methods.transfer_in_public(account, account, 0n, 0)
589
- : token.methods.transfer(0n, account, account);
467
+ this.log.info(`Fee juice balance ${balance} below threshold ${FEE_JUICE_TOP_UP_THRESHOLD}, bridging from L1`);
590
468
 
591
- while (balance < FEE_JUICE_TOP_UP_TARGET) {
592
- const claim = await this.bridgeL1FeeJuice(account);
469
+ while (balance < FEE_JUICE_TOP_UP_THRESHOLD) {
470
+ // Persist the claim before consuming it: if the top-up tx fails or the bot crashes mid-loop, the
471
+ // next run reuses the pending claim instead of bridging again (and wasting the bridged funds).
472
+ const claim = await this.getOrCreateBridgeClaim(account);
593
473
  const paymentMethod = new FeeJuicePaymentMethodWithClaim(account, claim);
594
- const { estimatedGas } = await minimalInteraction.simulate({
595
- from: account,
596
- fee: { estimateGas: true, paymentMethod },
597
- });
598
- const gasSettings = GasSettings.from({
599
- ...estimatedGas!,
600
- maxFeesPerGas,
601
- maxPriorityFeesPerGas: GasFees.empty(),
602
- });
603
474
 
604
475
  await this.withNoMinTxsPerBlock(async () => {
605
- const { txHash } = await minimalInteraction.send({
606
- from: account,
607
- fee: { gasSettings, paymentMethod },
608
- wait: NO_WAIT,
609
- });
476
+ const executionPayload = await paymentMethod.getExecutionPayload();
477
+ const { txHash } = await this.wallet.sendTx(executionPayload, { from: account, wait: NO_WAIT });
610
478
  this.log.info(`Sent fee juice top-up tx ${txHash.toString()}`);
611
479
  return waitForTx(this.aztecNode, txHash, { timeout: this.config.txMinedWaitSeconds });
612
480
  });
481
+ await this.store.deleteBridgeClaim(account);
613
482
  balance = await getFeeJuiceBalance(account, this.aztecNode);
614
483
  this.log.info(`Fee juice balance after top-up: ${balance}`);
615
484
  }
@@ -661,22 +530,20 @@ export class BotFactory {
661
530
  }
662
531
 
663
532
  /**
664
- * Gets or creates a bridge claim for the recipient.
665
- * Checks if a claim already exists in the store and reuses it if valid.
666
- * Only creates a new bridge if fee juice balance is below threshold.
533
+ * Returns a usable bridge claim for the recipient, reusing a persisted one when its L1→L2 message is
534
+ * still available (resuming a top-up that failed or crashed before the claim was consumed) and bridging
535
+ * a fresh claim otherwise. The caller deletes the claim from the store once it has been consumed.
667
536
  */
668
537
  private async getOrCreateBridgeClaim(recipient: AztecAddress): Promise<L2AmountClaim> {
669
- // Check if we have an existing claim in the store
670
538
  const existingClaim = await this.store.getBridgeClaim(recipient);
671
539
  if (existingClaim) {
672
540
  this.log.info(`Found existing bridge claim for ${recipient.toString()}, checking validity...`);
673
-
674
- // Check if the message is ready on L2
675
541
  try {
676
542
  const messageHash = Fr.fromHexString(existingClaim.claim.messageHash);
677
543
  await this.withNoMinTxsPerBlock(() =>
678
544
  waitForL1ToL2MessageReady(this.aztecNode, messageHash, {
679
545
  timeoutSeconds: this.config.l1ToL2MessageTimeoutSeconds,
546
+ chainTip: this.syncChainTip,
680
547
  }),
681
548
  );
682
549
  return existingClaim.claim;
@@ -688,7 +555,6 @@ export class BotFactory {
688
555
 
689
556
  const claim = await this.bridgeL1FeeJuice(recipient);
690
557
  await this.store.saveBridgeClaim(recipient, claim);
691
-
692
558
  return claim;
693
559
  }
694
560
 
@@ -715,6 +581,7 @@ export class BotFactory {
715
581
  await this.withNoMinTxsPerBlock(() =>
716
582
  waitForL1ToL2MessageReady(this.aztecNode, Fr.fromHexString(claim.messageHash), {
717
583
  timeoutSeconds: this.config.l1ToL2MessageTimeoutSeconds,
584
+ chainTip: this.syncChainTip,
718
585
  }),
719
586
  );
720
587
 
@@ -723,19 +590,6 @@ export class BotFactory {
723
590
  return claim as L2AmountClaim;
724
591
  }
725
592
 
726
- /** Returns worst-case min fees across predicted slots, with fallback to current min fees. */
727
- private async getMinFees(): Promise<GasFees> {
728
- try {
729
- const predicted = await this.aztecNode.getPredictedMinFees(ManaUsageEstimate.Limit);
730
- if (predicted.length === 0) {
731
- return this.aztecNode.getCurrentMinFees();
732
- }
733
- return predicted.reduce((worst, fees) => (fees.feePerL2Gas > worst.feePerL2Gas ? fees : worst));
734
- } catch {
735
- return this.aztecNode.getCurrentMinFees();
736
- }
737
- }
738
-
739
593
  private async withNoMinTxsPerBlock<T>(fn: () => Promise<T>): Promise<T> {
740
594
  if (!this.aztecNodeAdmin || !this.config.flushSetupTransactions) {
741
595
  this.log.verbose(`No node admin client or flushing not requested (not setting minTxsPerBlock to 0)`);
package/src/runner.ts CHANGED
@@ -2,6 +2,7 @@ import { createLogger } from '@aztec/aztec.js/log';
2
2
  import type { AztecNode } from '@aztec/aztec.js/node';
3
3
  import { omit } from '@aztec/foundation/collection';
4
4
  import { RunningPromise } from '@aztec/foundation/running-promise';
5
+ import type { BlockTag } from '@aztec/stdlib/block';
5
6
  import type { AztecNodeAdmin } from '@aztec/stdlib/interfaces/client';
6
7
  import { type TelemetryClient, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
7
8
  import type { EmbeddedWallet } from '@aztec/wallets/embedded';
@@ -30,6 +31,7 @@ export class BotRunner implements BotRunnerApi, Traceable {
30
31
  private readonly telemetry: TelemetryClient,
31
32
  private readonly aztecNodeAdmin: AztecNodeAdmin | undefined,
32
33
  private readonly store: BotStore,
34
+ private readonly syncChainTip?: BlockTag,
33
35
  ) {
34
36
  this.tracer = telemetry.getTracer('Bot');
35
37
 
@@ -149,13 +151,34 @@ export class BotRunner implements BotRunnerApi, Traceable {
149
151
  try {
150
152
  switch (this.config.botMode) {
151
153
  case 'crosschain':
152
- this.bot = CrossChainBot.create(this.config, this.wallet, this.aztecNode, this.aztecNodeAdmin, this.store);
154
+ this.bot = CrossChainBot.create(
155
+ this.config,
156
+ this.wallet,
157
+ this.aztecNode,
158
+ this.aztecNodeAdmin,
159
+ this.store,
160
+ this.syncChainTip,
161
+ );
153
162
  break;
154
163
  case 'amm':
155
- this.bot = AmmBot.create(this.config, this.wallet, this.aztecNode, this.aztecNodeAdmin, this.store);
164
+ this.bot = AmmBot.create(
165
+ this.config,
166
+ this.wallet,
167
+ this.aztecNode,
168
+ this.aztecNodeAdmin,
169
+ this.store,
170
+ this.syncChainTip,
171
+ );
156
172
  break;
157
173
  case 'transfer':
158
- this.bot = Bot.create(this.config, this.wallet, this.aztecNode, this.aztecNodeAdmin, this.store);
174
+ this.bot = Bot.create(
175
+ this.config,
176
+ this.wallet,
177
+ this.aztecNode,
178
+ this.aztecNodeAdmin,
179
+ this.store,
180
+ this.syncChainTip,
181
+ );
159
182
  break;
160
183
  default: {
161
184
  const _exhaustive: never = this.config.botMode;