@aztec/bot 0.0.0-test.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,229 @@
1
+ import { getSchnorrAccount } from '@aztec/accounts/schnorr';
2
+ import { getDeployedTestAccountsWallets, getInitialTestAccounts } from '@aztec/accounts/testing';
3
+ import { BatchCall, FeeJuicePaymentMethodWithClaim, L1FeeJuicePortalManager, createLogger, createPXEClient, retryUntil } from '@aztec/aztec.js';
4
+ import { createEthereumChain, createL1Clients } from '@aztec/ethereum';
5
+ import { Fr } from '@aztec/foundation/fields';
6
+ import { EasyPrivateTokenContract } from '@aztec/noir-contracts.js/EasyPrivateToken';
7
+ import { TokenContract } from '@aztec/noir-contracts.js/Token';
8
+ import { deriveSigningKey } from '@aztec/stdlib/keys';
9
+ import { makeTracedFetch } from '@aztec/telemetry-client';
10
+ import { SupportedTokenContracts, getVersions } from './config.js';
11
+ import { getBalances, getPrivateBalance, isStandardTokenContract } from './utils.js';
12
+ const MINT_BALANCE = 1e12;
13
+ const MIN_BALANCE = 1e3;
14
+ export class BotFactory {
15
+ config;
16
+ pxe;
17
+ node;
18
+ log;
19
+ constructor(config, dependencies = {}){
20
+ this.config = config;
21
+ this.log = createLogger('bot');
22
+ if (config.flushSetupTransactions && !dependencies.node) {
23
+ throw new Error(`Either a node client or node url must be provided if transaction flushing is requested`);
24
+ }
25
+ if (config.senderPrivateKey && !dependencies.node) {
26
+ throw new Error(`Either a node client or node url must be provided for bridging L1 fee juice to deploy an account with private key`);
27
+ }
28
+ if (!dependencies.pxe && !config.pxeUrl) {
29
+ throw new Error(`Either a PXE client or a PXE URL must be provided`);
30
+ }
31
+ this.node = dependencies.node;
32
+ if (dependencies.pxe) {
33
+ this.log.info(`Using local PXE`);
34
+ this.pxe = dependencies.pxe;
35
+ return;
36
+ }
37
+ this.log.info(`Using remote PXE at ${config.pxeUrl}`);
38
+ this.pxe = createPXEClient(config.pxeUrl, getVersions(), makeTracedFetch([
39
+ 1,
40
+ 2,
41
+ 3
42
+ ], false));
43
+ }
44
+ /**
45
+ * Initializes a new bot by setting up the sender account, registering the recipient,
46
+ * deploying the token contract, and minting tokens if necessary.
47
+ */ async setup() {
48
+ const recipient = await this.registerRecipient();
49
+ const wallet = await this.setupAccount();
50
+ const token = await this.setupToken(wallet);
51
+ await this.mintTokens(token);
52
+ return {
53
+ wallet,
54
+ token,
55
+ pxe: this.pxe,
56
+ recipient
57
+ };
58
+ }
59
+ /**
60
+ * Checks if the sender account contract is initialized, and initializes it if necessary.
61
+ * @returns The sender wallet.
62
+ */ async setupAccount() {
63
+ if (this.config.senderPrivateKey) {
64
+ return await this.setupAccountWithPrivateKey(this.config.senderPrivateKey);
65
+ } else {
66
+ return await this.setupTestAccount();
67
+ }
68
+ }
69
+ async setupAccountWithPrivateKey(privateKey) {
70
+ const salt = Fr.ONE;
71
+ const signingKey = deriveSigningKey(privateKey);
72
+ const account = await getSchnorrAccount(this.pxe, privateKey, signingKey, salt);
73
+ const isInit = (await this.pxe.getContractMetadata(account.getAddress())).isContractInitialized;
74
+ if (isInit) {
75
+ this.log.info(`Account at ${account.getAddress().toString()} already initialized`);
76
+ const wallet = await account.register();
77
+ return wallet;
78
+ } else {
79
+ const address = account.getAddress();
80
+ this.log.info(`Deploying account at ${address}`);
81
+ const claim = await this.bridgeL1FeeJuice(address, 10n ** 22n);
82
+ const wallet = await account.getWallet();
83
+ const paymentMethod = new FeeJuicePaymentMethodWithClaim(wallet, claim);
84
+ const sentTx = account.deploy({
85
+ fee: {
86
+ paymentMethod
87
+ }
88
+ });
89
+ const txHash = await sentTx.getTxHash();
90
+ this.log.info(`Sent tx with hash ${txHash.toString()}`);
91
+ await this.tryFlushTxs();
92
+ this.log.verbose('Waiting for account deployment to settle');
93
+ await sentTx.wait({
94
+ timeout: this.config.txMinedWaitSeconds
95
+ });
96
+ this.log.info(`Account deployed at ${address}`);
97
+ return wallet;
98
+ }
99
+ }
100
+ async setupTestAccount() {
101
+ let [wallet] = await getDeployedTestAccountsWallets(this.pxe);
102
+ if (wallet) {
103
+ this.log.info(`Using funded test account: ${wallet.getAddress()}`);
104
+ } else {
105
+ this.log.info('Registering funded test account');
106
+ const [account] = await getInitialTestAccounts();
107
+ const manager = await getSchnorrAccount(this.pxe, account.secret, account.signingKey, account.salt);
108
+ wallet = await manager.register();
109
+ this.log.info(`Funded test account registered: ${wallet.getAddress()}`);
110
+ }
111
+ return wallet;
112
+ }
113
+ /**
114
+ * Registers the recipient for txs in the pxe.
115
+ */ async registerRecipient() {
116
+ const recipient = await this.pxe.registerAccount(this.config.recipientEncryptionSecret, Fr.ONE);
117
+ return recipient.address;
118
+ }
119
+ /**
120
+ * Checks if the token contract is deployed and deploys it if necessary.
121
+ * @param wallet - Wallet to deploy the token contract from.
122
+ * @returns The TokenContract instance.
123
+ */ async setupToken(wallet) {
124
+ let deploy;
125
+ const deployOpts = {
126
+ contractAddressSalt: this.config.tokenSalt,
127
+ universalDeploy: true
128
+ };
129
+ if (this.config.contract === SupportedTokenContracts.TokenContract) {
130
+ deploy = TokenContract.deploy(wallet, wallet.getAddress(), 'BotToken', 'BOT', 18);
131
+ } else if (this.config.contract === SupportedTokenContracts.EasyPrivateTokenContract) {
132
+ deploy = EasyPrivateTokenContract.deploy(wallet, MINT_BALANCE, wallet.getAddress());
133
+ deployOpts.skipPublicDeployment = true;
134
+ deployOpts.skipClassRegistration = true;
135
+ deployOpts.skipInitialization = false;
136
+ deployOpts.skipPublicSimulation = true;
137
+ } else {
138
+ throw new Error(`Unsupported token contract type: ${this.config.contract}`);
139
+ }
140
+ const address = (await deploy.getInstance(deployOpts)).address;
141
+ if ((await this.pxe.getContractMetadata(address)).isContractPubliclyDeployed) {
142
+ this.log.info(`Token at ${address.toString()} already deployed`);
143
+ return deploy.register();
144
+ } else {
145
+ this.log.info(`Deploying token contract at ${address.toString()}`);
146
+ const sentTx = deploy.send(deployOpts);
147
+ const txHash = await sentTx.getTxHash();
148
+ this.log.info(`Sent tx with hash ${txHash.toString()}`);
149
+ await this.tryFlushTxs();
150
+ this.log.verbose('Waiting for token setup to settle');
151
+ return sentTx.deployed({
152
+ timeout: this.config.txMinedWaitSeconds
153
+ });
154
+ }
155
+ }
156
+ /**
157
+ * Mints private and public tokens for the sender if their balance is below the minimum.
158
+ * @param token - Token contract.
159
+ */ async mintTokens(token) {
160
+ const sender = token.wallet.getAddress();
161
+ const isStandardToken = isStandardTokenContract(token);
162
+ let privateBalance = 0n;
163
+ let publicBalance = 0n;
164
+ if (isStandardToken) {
165
+ ({ privateBalance, publicBalance } = await getBalances(token, sender));
166
+ } else {
167
+ privateBalance = await getPrivateBalance(token, sender);
168
+ }
169
+ const calls = [];
170
+ if (privateBalance < MIN_BALANCE) {
171
+ this.log.info(`Minting private tokens for ${sender.toString()}`);
172
+ const from = sender; // we are setting from to sender here because we need a sender to calculate the tag
173
+ calls.push(isStandardToken ? await token.methods.mint_to_private(from, sender, MINT_BALANCE).request() : await token.methods.mint(MINT_BALANCE, sender).request());
174
+ }
175
+ if (isStandardToken && publicBalance < MIN_BALANCE) {
176
+ this.log.info(`Minting public tokens for ${sender.toString()}`);
177
+ calls.push(await token.methods.mint_to_public(sender, MINT_BALANCE).request());
178
+ }
179
+ if (calls.length === 0) {
180
+ this.log.info(`Skipping minting as ${sender.toString()} has enough tokens`);
181
+ return;
182
+ }
183
+ const sentTx = new BatchCall(token.wallet, calls).send();
184
+ const txHash = await sentTx.getTxHash();
185
+ this.log.info(`Sent tx with hash ${txHash.toString()}`);
186
+ await this.tryFlushTxs();
187
+ this.log.verbose('Waiting for token mint to settle');
188
+ await sentTx.wait({
189
+ timeout: this.config.txMinedWaitSeconds
190
+ });
191
+ }
192
+ async bridgeL1FeeJuice(recipient, amount) {
193
+ const l1RpcUrls = this.config.l1RpcUrls;
194
+ if (!l1RpcUrls?.length) {
195
+ throw new Error('L1 Rpc url is required to bridge the fee juice to fund the deployment of the account.');
196
+ }
197
+ const mnemonicOrPrivateKey = this.config.l1PrivateKey || this.config.l1Mnemonic;
198
+ if (!mnemonicOrPrivateKey) {
199
+ throw new Error('Either a mnemonic or private key of an L1 account is required to bridge the fee juice to fund the deployment of the account.');
200
+ }
201
+ const { l1ChainId } = await this.pxe.getNodeInfo();
202
+ const chain = createEthereumChain(l1RpcUrls, l1ChainId);
203
+ const { publicClient, walletClient } = createL1Clients(chain.rpcUrls, mnemonicOrPrivateKey, chain.chainInfo);
204
+ const portal = await L1FeeJuicePortalManager.new(this.pxe, publicClient, walletClient, this.log);
205
+ const claim = await portal.bridgeTokensPublic(recipient, amount, true);
206
+ const isSynced = async ()=>await this.pxe.isL1ToL2MessageSynced(Fr.fromHexString(claim.messageHash));
207
+ await retryUntil(isSynced, `message ${claim.messageHash} sync`, 24, 1);
208
+ this.log.info(`Created a claim for ${amount} L1 fee juice to ${recipient}.`, claim);
209
+ // Progress by 2 L2 blocks so that the l1ToL2Message added above will be available to use on L2.
210
+ await this.advanceL2Block();
211
+ await this.advanceL2Block();
212
+ return claim;
213
+ }
214
+ async advanceL2Block() {
215
+ const initialBlockNumber = await this.node.getBlockNumber();
216
+ await this.tryFlushTxs();
217
+ await retryUntil(async ()=>await this.node.getBlockNumber() >= initialBlockNumber + 1);
218
+ }
219
+ async tryFlushTxs() {
220
+ if (this.config.flushSetupTransactions) {
221
+ this.log.verbose('Flushing transactions');
222
+ try {
223
+ await this.node.flushTxs();
224
+ } catch (err) {
225
+ this.log.error(`Failed to flush transactions: ${err}`);
226
+ }
227
+ }
228
+ }
229
+ }
@@ -0,0 +1,6 @@
1
+ export { Bot } from './bot.js';
2
+ export { BotRunner } from './runner.js';
3
+ export { type BotConfig, getBotConfigFromEnv, getBotDefaultConfig, botConfigMappings, SupportedTokenContracts, } from './config.js';
4
+ export { createBotRunnerRpcServer, getBotRunnerApiHandler } from './rpc.js';
5
+ export * from './interface.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EACL,KAAK,SAAS,EACd,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAC5E,cAAc,gBAAgB,CAAC"}
package/dest/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { Bot } from './bot.js';
2
+ export { BotRunner } from './runner.js';
3
+ export { getBotConfigFromEnv, getBotDefaultConfig, botConfigMappings, SupportedTokenContracts } from './config.js';
4
+ export { createBotRunnerRpcServer, getBotRunnerApiHandler } from './rpc.js';
5
+ export * from './interface.js';
@@ -0,0 +1,12 @@
1
+ import type { ApiSchemaFor } from '@aztec/stdlib/schemas';
2
+ import { type BotConfig } from './config.js';
3
+ export interface BotRunnerApi {
4
+ start(): Promise<void>;
5
+ stop(): Promise<void>;
6
+ run(): Promise<void>;
7
+ setup(): Promise<void>;
8
+ getConfig(): Promise<BotConfig>;
9
+ update(config: BotConfig): Promise<void>;
10
+ }
11
+ export declare const BotRunnerApiSchema: ApiSchemaFor<BotRunnerApi>;
12
+ //# sourceMappingURL=interface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../src/interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAI1D,OAAO,EAAE,KAAK,SAAS,EAAmB,MAAM,aAAa,CAAC;AAE9D,MAAM,WAAW,YAAY;IAC3B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACrB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,SAAS,IAAI,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C;AAED,eAAO,MAAM,kBAAkB,EAAE,YAAY,CAAC,YAAY,CAOzD,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { z } from 'zod';
2
+ import { BotConfigSchema } from './config.js';
3
+ export const BotRunnerApiSchema = {
4
+ start: z.function().args().returns(z.void()),
5
+ stop: z.function().args().returns(z.void()),
6
+ run: z.function().args().returns(z.void()),
7
+ setup: z.function().args().returns(z.void()),
8
+ getConfig: z.function().args().returns(BotConfigSchema),
9
+ update: z.function().args(BotConfigSchema).returns(z.void())
10
+ };
package/dest/rpc.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import type { ApiHandler } from '@aztec/foundation/json-rpc/server';
2
+ import type { BotRunner } from './runner.js';
3
+ /**
4
+ * Wraps a bot runner with a JSON RPC HTTP server.
5
+ * @param botRunner - The BotRunner.
6
+ * @returns An JSON-RPC HTTP server
7
+ */
8
+ export declare function createBotRunnerRpcServer(botRunner: BotRunner): void;
9
+ export declare function getBotRunnerApiHandler(botRunner: BotRunner): ApiHandler;
10
+ //# sourceMappingURL=rpc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc.d.ts","sourceRoot":"","sources":["../src/rpc.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AAIpE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,SAAS,QAK5D;AAED,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,SAAS,GAAG,UAAU,CAEvE"}
package/dest/rpc.js ADDED
@@ -0,0 +1,19 @@
1
+ import { createTracedJsonRpcServer } from '@aztec/telemetry-client';
2
+ import { BotRunnerApiSchema } from './interface.js';
3
+ /**
4
+ * Wraps a bot runner with a JSON RPC HTTP server.
5
+ * @param botRunner - The BotRunner.
6
+ * @returns An JSON-RPC HTTP server
7
+ */ export function createBotRunnerRpcServer(botRunner) {
8
+ createTracedJsonRpcServer(botRunner, BotRunnerApiSchema, {
9
+ http200OnError: false,
10
+ healthCheck: botRunner.isHealthy.bind(botRunner)
11
+ });
12
+ }
13
+ export function getBotRunnerApiHandler(botRunner) {
14
+ return [
15
+ botRunner,
16
+ BotRunnerApiSchema,
17
+ botRunner.isHealthy.bind(botRunner)
18
+ ];
19
+ }
@@ -0,0 +1,49 @@
1
+ import { type AztecNode, type PXE } from '@aztec/aztec.js';
2
+ import { type TelemetryClient, type Traceable, type Tracer } from '@aztec/telemetry-client';
3
+ import { type BotConfig } from './config.js';
4
+ import type { BotRunnerApi } from './interface.js';
5
+ export declare class BotRunner implements BotRunnerApi, Traceable {
6
+ #private;
7
+ private config;
8
+ private log;
9
+ private bot?;
10
+ private pxe?;
11
+ private node;
12
+ private runningPromise;
13
+ private consecutiveErrors;
14
+ private healthy;
15
+ readonly tracer: Tracer;
16
+ constructor(config: BotConfig, dependencies: {
17
+ pxe?: PXE;
18
+ node?: AztecNode;
19
+ telemetry: TelemetryClient;
20
+ });
21
+ /** Initializes the bot if needed. Blocks until the bot setup is finished. */
22
+ setup(): Promise<void>;
23
+ private doSetup;
24
+ /**
25
+ * Initializes the bot if needed and starts sending txs at regular intervals.
26
+ * Blocks until the bot setup is finished.
27
+ */
28
+ start(): Promise<void>;
29
+ /**
30
+ * Stops sending txs. Returns once all ongoing txs are finished.
31
+ */
32
+ stop(): Promise<void>;
33
+ isHealthy(): boolean;
34
+ /** Returns whether the bot is running. */
35
+ isRunning(): boolean;
36
+ /**
37
+ * Updates the bot config and recreates the bot. Will stop and restart the bot automatically if it was
38
+ * running when this method was called. Blocks until the new bot is set up.
39
+ */
40
+ update(config: BotConfig): Promise<void>;
41
+ /**
42
+ * Triggers a single iteration of the bot. Requires the bot to be initialized.
43
+ * Blocks until the run is finished.
44
+ */
45
+ run(): Promise<void>;
46
+ /** Returns the current configuration for the bot. */
47
+ getConfig(): Promise<BotConfig>;
48
+ }
49
+ //# sourceMappingURL=runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,GAAG,EAAuC,MAAM,iBAAiB,CAAC;AAEhG,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,SAAS,EAAE,KAAK,MAAM,EAA8B,MAAM,yBAAyB,CAAC;AAGxH,OAAO,EAAE,KAAK,SAAS,EAAe,MAAM,aAAa,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,qBAAa,SAAU,YAAW,YAAY,EAAE,SAAS;;IAYrD,OAAO,CAAC,MAAM;IAXhB,OAAO,CAAC,GAAG,CAAuB;IAClC,OAAO,CAAC,GAAG,CAAC,CAAe;IAC3B,OAAO,CAAC,GAAG,CAAC,CAAM;IAClB,OAAO,CAAC,IAAI,CAAY;IACxB,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,OAAO,CAAQ;IAEvB,SAAgB,MAAM,EAAE,MAAM,CAAC;gBAGrB,MAAM,EAAE,SAAS,EACzB,YAAY,EAAE;QAAE,GAAG,CAAC,EAAE,GAAG,CAAC;QAAC,IAAI,CAAC,EAAE,SAAS,CAAC;QAAC,SAAS,EAAE,eAAe,CAAA;KAAE;IAY3E,6EAA6E;IAChE,KAAK;YAOJ,OAAO;IAMrB;;;OAGG;IACU,KAAK;IAQlB;;OAEG;IACU,IAAI;IAQV,SAAS;IAIhB,0CAA0C;IACnC,SAAS;IAIhB;;;OAGG;IACU,MAAM,CAAC,MAAM,EAAE,SAAS;IAerC;;;OAGG;IACU,GAAG;IAwBhB,qDAAqD;IAC9C,SAAS;CAuCjB"}
package/dest/runner.js ADDED
@@ -0,0 +1,158 @@
1
+ function _ts_decorate(decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ }
7
+ import { createAztecNodeClient, createLogger } from '@aztec/aztec.js';
8
+ import { RunningPromise } from '@aztec/foundation/running-promise';
9
+ import { makeTracedFetch, trackSpan } from '@aztec/telemetry-client';
10
+ import { Bot } from './bot.js';
11
+ import { getVersions } from './config.js';
12
+ export class BotRunner {
13
+ config;
14
+ log;
15
+ bot;
16
+ pxe;
17
+ node;
18
+ runningPromise;
19
+ consecutiveErrors;
20
+ healthy;
21
+ tracer;
22
+ constructor(config, dependencies){
23
+ this.config = config;
24
+ this.log = createLogger('bot');
25
+ this.consecutiveErrors = 0;
26
+ this.healthy = true;
27
+ this.tracer = dependencies.telemetry.getTracer('Bot');
28
+ this.pxe = dependencies.pxe;
29
+ if (!dependencies.node && !config.nodeUrl) {
30
+ throw new Error(`Missing node URL in config or dependencies`);
31
+ }
32
+ this.node = dependencies.node ?? createAztecNodeClient(config.nodeUrl, getVersions(), makeTracedFetch([
33
+ 1,
34
+ 2,
35
+ 3
36
+ ], true));
37
+ this.runningPromise = new RunningPromise(()=>this.#work(), this.log, config.txIntervalSeconds * 1000);
38
+ }
39
+ /** Initializes the bot if needed. Blocks until the bot setup is finished. */ async setup() {
40
+ if (!this.bot) {
41
+ await this.doSetup();
42
+ }
43
+ }
44
+ async doSetup() {
45
+ this.log.verbose(`Setting up bot`);
46
+ await this.#createBot();
47
+ this.log.info(`Bot set up completed`);
48
+ }
49
+ /**
50
+ * Initializes the bot if needed and starts sending txs at regular intervals.
51
+ * Blocks until the bot setup is finished.
52
+ */ async start() {
53
+ await this.setup();
54
+ if (!this.runningPromise.isRunning()) {
55
+ this.log.info(`Starting bot with interval of ${this.config.txIntervalSeconds}s`);
56
+ this.runningPromise.start();
57
+ }
58
+ }
59
+ /**
60
+ * Stops sending txs. Returns once all ongoing txs are finished.
61
+ */ async stop() {
62
+ if (this.runningPromise.isRunning()) {
63
+ this.log.verbose(`Stopping bot`);
64
+ await this.runningPromise.stop();
65
+ }
66
+ this.log.info(`Stopped bot`);
67
+ }
68
+ isHealthy() {
69
+ return this.runningPromise.isRunning() && this.healthy;
70
+ }
71
+ /** Returns whether the bot is running. */ isRunning() {
72
+ return this.runningPromise.isRunning();
73
+ }
74
+ /**
75
+ * Updates the bot config and recreates the bot. Will stop and restart the bot automatically if it was
76
+ * running when this method was called. Blocks until the new bot is set up.
77
+ */ async update(config) {
78
+ this.log.verbose(`Updating bot config`);
79
+ const wasRunning = this.isRunning();
80
+ if (wasRunning) {
81
+ await this.stop();
82
+ }
83
+ this.config = {
84
+ ...this.config,
85
+ ...config
86
+ };
87
+ this.runningPromise.setPollingIntervalMS(this.config.txIntervalSeconds * 1000);
88
+ await this.#createBot();
89
+ this.log.info(`Bot config updated`);
90
+ if (wasRunning) {
91
+ await this.start();
92
+ }
93
+ }
94
+ /**
95
+ * Triggers a single iteration of the bot. Requires the bot to be initialized.
96
+ * Blocks until the run is finished.
97
+ */ async run() {
98
+ if (!this.bot) {
99
+ this.log.error(`Trying to run with uninitialized bot`);
100
+ throw new Error(`Bot is not initialized`);
101
+ }
102
+ let bot;
103
+ try {
104
+ bot = await this.bot;
105
+ } catch (err) {
106
+ this.log.error(`Error awaiting bot set up: ${err}`);
107
+ throw err;
108
+ }
109
+ try {
110
+ await bot.run();
111
+ this.consecutiveErrors = 0;
112
+ } catch (err) {
113
+ this.consecutiveErrors += 1;
114
+ this.log.error(`Error running bot consecutiveCount=${this.consecutiveErrors}: ${err}`);
115
+ throw err;
116
+ }
117
+ }
118
+ /** Returns the current configuration for the bot. */ getConfig() {
119
+ return Promise.resolve(this.config);
120
+ }
121
+ async #createBot() {
122
+ try {
123
+ this.bot = Bot.create(this.config, {
124
+ pxe: this.pxe,
125
+ node: this.node
126
+ });
127
+ await this.bot;
128
+ } catch (err) {
129
+ this.log.error(`Error setting up bot: ${err}`);
130
+ throw err;
131
+ }
132
+ }
133
+ async #work() {
134
+ if (this.config.maxPendingTxs > 0) {
135
+ const pendingTxs = await this.node.getPendingTxs();
136
+ if (pendingTxs.length >= this.config.maxPendingTxs) {
137
+ this.log.verbose(`Not sending bot tx since node has ${pendingTxs.length} pending txs`);
138
+ return;
139
+ }
140
+ }
141
+ try {
142
+ await this.run();
143
+ } catch (err) {
144
+ // Already logged in run()
145
+ if (this.config.maxConsecutiveErrors > 0 && this.consecutiveErrors >= this.config.maxConsecutiveErrors) {
146
+ this.log.error(`Too many errors bot is unhealthy`);
147
+ this.healthy = false;
148
+ }
149
+ }
150
+ if (!this.healthy && this.config.stopWhenUnhealthy) {
151
+ this.log.fatal(`Stopping bot due to errors`);
152
+ process.exit(1); // workaround docker not restarting the container if its unhealthy. We have to exit instead
153
+ }
154
+ }
155
+ }
156
+ _ts_decorate([
157
+ trackSpan('Bot.setup')
158
+ ], BotRunner.prototype, "doSetup", null);
@@ -0,0 +1,16 @@
1
+ import type { EasyPrivateTokenContract } from '@aztec/noir-contracts.js/EasyPrivateToken';
2
+ import type { TokenContract } from '@aztec/noir-contracts.js/Token';
3
+ import type { AztecAddress } from '@aztec/stdlib/aztec-address';
4
+ /**
5
+ * Gets the private and public balance of the given token for the given address.
6
+ * @param token - Token contract.
7
+ * @param who - Address to get the balance for.
8
+ * @returns - Private and public token balances as bigints.
9
+ */
10
+ export declare function getBalances(token: TokenContract, who: AztecAddress): Promise<{
11
+ privateBalance: bigint;
12
+ publicBalance: bigint;
13
+ }>;
14
+ export declare function getPrivateBalance(token: EasyPrivateTokenContract, who: AztecAddress): Promise<bigint>;
15
+ export declare function isStandardTokenContract(token: TokenContract | EasyPrivateTokenContract): token is TokenContract;
16
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2CAA2C,CAAC;AAC1F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAEhE;;;;;GAKG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,aAAa,EACpB,GAAG,EAAE,YAAY,GAChB,OAAO,CAAC;IAAE,cAAc,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC,CAI5D;AAED,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,wBAAwB,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAG3G;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,aAAa,GAAG,wBAAwB,GAAG,KAAK,IAAI,aAAa,CAE/G"}
package/dest/utils.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Gets the private and public balance of the given token for the given address.
3
+ * @param token - Token contract.
4
+ * @param who - Address to get the balance for.
5
+ * @returns - Private and public token balances as bigints.
6
+ */ export async function getBalances(token, who) {
7
+ const privateBalance = await token.methods.balance_of_private(who).simulate();
8
+ const publicBalance = await token.methods.balance_of_public(who).simulate();
9
+ return {
10
+ privateBalance,
11
+ publicBalance
12
+ };
13
+ }
14
+ export async function getPrivateBalance(token, who) {
15
+ const privateBalance = await token.methods.get_balance(who).simulate();
16
+ return privateBalance;
17
+ }
18
+ export function isStandardTokenContract(token) {
19
+ return 'mint_to_public' in token.methods;
20
+ }
package/package.json ADDED
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "@aztec/bot",
3
+ "version": "0.0.0-test.0",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": "./dest/index.js",
7
+ "./config": "./dest/config.js"
8
+ },
9
+ "inherits": [
10
+ "../package.common.json"
11
+ ],
12
+ "scripts": {
13
+ "build": "yarn clean && tsc -b",
14
+ "build:dev": "tsc -b --watch",
15
+ "clean": "rm -rf ./dest .tsbuildinfo",
16
+ "formatting": "run -T prettier --check ./src && run -T eslint ./src",
17
+ "formatting:fix": "run -T eslint --fix ./src && run -T prettier -w ./src",
18
+ "bb": "node --no-warnings ./dest/bb/index.js",
19
+ "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests --maxWorkers=${JEST_MAX_WORKERS:-8}"
20
+ },
21
+ "jest": {
22
+ "moduleNameMapper": {
23
+ "^(\\.{1,2}/.*)\\.[cm]?js$": "$1"
24
+ },
25
+ "testRegex": "./src/.*\\.test\\.(js|mjs|ts)$",
26
+ "rootDir": "./src",
27
+ "transform": {
28
+ "^.+\\.tsx?$": [
29
+ "@swc/jest",
30
+ {
31
+ "jsc": {
32
+ "parser": {
33
+ "syntax": "typescript",
34
+ "decorators": true
35
+ },
36
+ "transform": {
37
+ "decoratorVersion": "2022-03"
38
+ }
39
+ }
40
+ }
41
+ ]
42
+ },
43
+ "extensionsToTreatAsEsm": [
44
+ ".ts"
45
+ ],
46
+ "reporters": [
47
+ "default"
48
+ ],
49
+ "testTimeout": 120000,
50
+ "setupFiles": [
51
+ "../../foundation/src/jest/setup.mjs"
52
+ ]
53
+ },
54
+ "dependencies": {
55
+ "@aztec/accounts": "0.0.0-test.0",
56
+ "@aztec/aztec.js": "0.0.0-test.0",
57
+ "@aztec/entrypoints": "0.0.0-test.0",
58
+ "@aztec/ethereum": "0.0.0-test.0",
59
+ "@aztec/foundation": "0.0.0-test.0",
60
+ "@aztec/noir-contracts.js": "0.0.0-test.0",
61
+ "@aztec/noir-protocol-circuits-types": "0.0.0-test.0",
62
+ "@aztec/protocol-contracts": "0.0.0-test.0",
63
+ "@aztec/stdlib": "0.0.0-test.0",
64
+ "@aztec/telemetry-client": "0.0.0-test.0",
65
+ "source-map-support": "^0.5.21",
66
+ "tslib": "^2.4.0",
67
+ "zod": "^3.23.8"
68
+ },
69
+ "devDependencies": {
70
+ "@jest/globals": "^29.5.0",
71
+ "@types/jest": "^29.5.0",
72
+ "@types/memdown": "^3.0.0",
73
+ "@types/node": "^18.7.23",
74
+ "@types/source-map-support": "^0.5.10",
75
+ "jest": "^29.5.0",
76
+ "jest-mock-extended": "^3.0.3",
77
+ "ts-node": "^10.9.1",
78
+ "typescript": "^5.0.4"
79
+ },
80
+ "files": [
81
+ "dest",
82
+ "src",
83
+ "!*.test.*"
84
+ ],
85
+ "types": "./dest/index.d.ts",
86
+ "engines": {
87
+ "node": ">=18"
88
+ }
89
+ }