@aztec/bot 0.47.1

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.
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # Transactions Bot
2
+
3
+ Simple bot that connects to a PXE to send txs on a recurring basis.
package/dest/bot.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ import { type AztecAddress, type Wallet } from '@aztec/aztec.js';
2
+ import { type PXE } from '@aztec/circuit-types';
3
+ import { type TokenContract } from '@aztec/noir-contracts.js';
4
+ import { type BotConfig } from './config.js';
5
+ export declare class Bot {
6
+ readonly wallet: Wallet;
7
+ readonly token: TokenContract;
8
+ readonly recipient: AztecAddress;
9
+ readonly config: BotConfig;
10
+ private log;
11
+ protected constructor(wallet: Wallet, token: TokenContract, recipient: AztecAddress, config: BotConfig);
12
+ static create(config: BotConfig, dependencies?: {
13
+ pxe?: PXE;
14
+ }): Promise<Bot>;
15
+ run(): Promise<void>;
16
+ getBalances(): Promise<{
17
+ sender: {
18
+ privateBalance: bigint;
19
+ publicBalance: bigint;
20
+ };
21
+ recipient: {
22
+ privateBalance: bigint;
23
+ publicBalance: bigint;
24
+ };
25
+ }>;
26
+ }
27
+ //# sourceMappingURL=bot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bot.d.ts","sourceRoot":"","sources":["../src/bot.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EAKjB,KAAK,MAAM,EAEZ,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAqB,KAAK,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAGnE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAM7C,qBAAa,GAAG;aAII,MAAM,EAAE,MAAM;aACd,KAAK,EAAE,aAAa;aACpB,SAAS,EAAE,YAAY;aACvB,MAAM,EAAE,SAAS;IANnC,OAAO,CAAC,GAAG,CAAkC;IAE7C,SAAS,aACS,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,aAAa,EACpB,SAAS,EAAE,YAAY,EACvB,MAAM,EAAE,SAAS;WAGtB,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,YAAY,GAAE;QAAE,GAAG,CAAC,EAAE,GAAG,CAAA;KAAO,GAAG,OAAO,CAAC,GAAG,CAAC;IAKzE,GAAG;IA0BH,WAAW;;;;;;;;;;CAMzB"}
package/dest/bot.js ADDED
@@ -0,0 +1,43 @@
1
+ import { BatchCall, NativeFeePaymentMethod, NoFeePaymentMethod, createDebugLogger, } from '@aztec/aztec.js';
2
+ import { GasSettings } from '@aztec/circuits.js';
3
+ import { times } from '@aztec/foundation/collection';
4
+ import { BotFactory } from './factory.js';
5
+ import { getBalances } from './utils.js';
6
+ const TRANSFER_AMOUNT = 1;
7
+ export class Bot {
8
+ constructor(wallet, token, recipient, config) {
9
+ this.wallet = wallet;
10
+ this.token = token;
11
+ this.recipient = recipient;
12
+ this.config = config;
13
+ this.log = createDebugLogger('aztec:bot');
14
+ }
15
+ static async create(config, dependencies = {}) {
16
+ const { wallet, token, recipient } = await new BotFactory(config, dependencies).setup();
17
+ return new Bot(wallet, token, recipient, config);
18
+ }
19
+ async run() {
20
+ const { privateTransfersPerTx, publicTransfersPerTx, feePaymentMethod } = this.config;
21
+ const { token, recipient, wallet } = this;
22
+ const sender = wallet.getAddress();
23
+ this.log.verbose(`Sending tx with ${feePaymentMethod} fee with ${privateTransfersPerTx} private and ${publicTransfersPerTx} public transfers`);
24
+ const calls = [
25
+ ...times(privateTransfersPerTx, () => token.methods.transfer(recipient, TRANSFER_AMOUNT).request()),
26
+ ...times(publicTransfersPerTx, () => token.methods.transfer_public(sender, recipient, TRANSFER_AMOUNT, 0).request()),
27
+ ];
28
+ const paymentMethod = feePaymentMethod === 'native' ? new NativeFeePaymentMethod(sender) : new NoFeePaymentMethod();
29
+ const gasSettings = GasSettings.default();
30
+ const opts = { estimateGas: true, fee: { paymentMethod, gasSettings } };
31
+ const tx = new BatchCall(wallet, calls).send(opts);
32
+ this.log.verbose(`Sent tx ${tx.getTxHash()}`);
33
+ const receipt = await tx.wait();
34
+ this.log.info(`Tx ${receipt.txHash} mined in block ${receipt.blockNumber}`);
35
+ }
36
+ async getBalances() {
37
+ return {
38
+ sender: await getBalances(this.token, this.wallet.getAddress()),
39
+ recipient: await getBalances(this.token, this.recipient),
40
+ };
41
+ }
42
+ }
43
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYm90LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2JvdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBRUwsU0FBUyxFQUNULHNCQUFzQixFQUN0QixrQkFBa0IsRUFHbEIsaUJBQWlCLEdBQ2xCLE1BQU0saUJBQWlCLENBQUM7QUFFekIsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQ2pELE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUlyRCxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQzFDLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFFekMsTUFBTSxlQUFlLEdBQUcsQ0FBQyxDQUFDO0FBRTFCLE1BQU0sT0FBTyxHQUFHO0lBR2QsWUFDa0IsTUFBYyxFQUNkLEtBQW9CLEVBQ3BCLFNBQXVCLEVBQ3ZCLE1BQWlCO1FBSGpCLFdBQU0sR0FBTixNQUFNLENBQVE7UUFDZCxVQUFLLEdBQUwsS0FBSyxDQUFlO1FBQ3BCLGNBQVMsR0FBVCxTQUFTLENBQWM7UUFDdkIsV0FBTSxHQUFOLE1BQU0sQ0FBVztRQU4zQixRQUFHLEdBQUcsaUJBQWlCLENBQUMsV0FBVyxDQUFDLENBQUM7SUFPMUMsQ0FBQztJQUVKLE1BQU0sQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLE1BQWlCLEVBQUUsZUFBOEIsRUFBRTtRQUNyRSxNQUFNLEVBQUUsTUFBTSxFQUFFLEtBQUssRUFBRSxTQUFTLEVBQUUsR0FBRyxNQUFNLElBQUksVUFBVSxDQUFDLE1BQU0sRUFBRSxZQUFZLENBQUMsQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUN4RixPQUFPLElBQUksR0FBRyxDQUFDLE1BQU0sRUFBRSxLQUFLLEVBQUUsU0FBUyxFQUFFLE1BQU0sQ0FBQyxDQUFDO0lBQ25ELENBQUM7SUFFTSxLQUFLLENBQUMsR0FBRztRQUNkLE1BQU0sRUFBRSxxQkFBcUIsRUFBRSxvQkFBb0IsRUFBRSxnQkFBZ0IsRUFBRSxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUM7UUFDdEYsTUFBTSxFQUFFLEtBQUssRUFBRSxTQUFTLEVBQUUsTUFBTSxFQUFFLEdBQUcsSUFBSSxDQUFDO1FBQzFDLE1BQU0sTUFBTSxHQUFHLE1BQU0sQ0FBQyxVQUFVLEVBQUUsQ0FBQztRQUVuQyxJQUFJLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FDZCxtQkFBbUIsZ0JBQWdCLGFBQWEscUJBQXFCLGdCQUFnQixvQkFBb0IsbUJBQW1CLENBQzdILENBQUM7UUFFRixNQUFNLEtBQUssR0FBbUI7WUFDNUIsR0FBRyxLQUFLLENBQUMscUJBQXFCLEVBQUUsR0FBRyxFQUFFLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsU0FBUyxFQUFFLGVBQWUsQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDO1lBQ25HLEdBQUcsS0FBSyxDQUFDLG9CQUFvQixFQUFFLEdBQUcsRUFBRSxDQUNsQyxLQUFLLENBQUMsT0FBTyxDQUFDLGVBQWUsQ0FBQyxNQUFNLEVBQUUsU0FBUyxFQUFFLGVBQWUsRUFBRSxDQUFDLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FDL0U7U0FDRixDQUFDO1FBRUYsTUFBTSxhQUFhLEdBQUcsZ0JBQWdCLEtBQUssUUFBUSxDQUFDLENBQUMsQ0FBQyxJQUFJLHNCQUFzQixDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLGtCQUFrQixFQUFFLENBQUM7UUFDcEgsTUFBTSxXQUFXLEdBQUcsV0FBVyxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQzFDLE1BQU0sSUFBSSxHQUFzQixFQUFFLFdBQVcsRUFBRSxJQUFJLEVBQUUsR0FBRyxFQUFFLEVBQUUsYUFBYSxFQUFFLFdBQVcsRUFBRSxFQUFFLENBQUM7UUFDM0YsTUFBTSxFQUFFLEdBQUcsSUFBSSxTQUFTLENBQUMsTUFBTSxFQUFFLEtBQUssQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNuRCxJQUFJLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxXQUFXLEVBQUUsQ0FBQyxTQUFTLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFFOUMsTUFBTSxPQUFPLEdBQUcsTUFBTSxFQUFFLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDaEMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsTUFBTSxPQUFPLENBQUMsTUFBTSxtQkFBbUIsT0FBTyxDQUFDLFdBQVcsRUFBRSxDQUFDLENBQUM7SUFDOUUsQ0FBQztJQUVNLEtBQUssQ0FBQyxXQUFXO1FBQ3RCLE9BQU87WUFDTCxNQUFNLEVBQUUsTUFBTSxXQUFXLENBQUMsSUFBSSxDQUFDLEtBQUssRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLFVBQVUsRUFBRSxDQUFDO1lBQy9ELFNBQVMsRUFBRSxNQUFNLFdBQVcsQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUM7U0FDekQsQ0FBQztJQUNKLENBQUM7Q0FDRiJ9
@@ -0,0 +1,24 @@
1
+ import { Fr } from '@aztec/circuits.js';
2
+ export type BotConfig = {
3
+ /** URL to the PXE for sending txs, or undefined if an in-proc PXE is used. */
4
+ pxeUrl: string | undefined;
5
+ /** Signing private key for the sender account. */
6
+ senderPrivateKey: Fr;
7
+ /** Encryption secret for a recipient account. */
8
+ recipientEncryptionSecret: Fr;
9
+ /** Salt for the token contract deployment. */
10
+ tokenSalt: Fr;
11
+ /** Every how many seconds should a new tx be sent. */
12
+ txIntervalSeconds: number;
13
+ /** How many private token transfers are executed per tx. */
14
+ privateTransfersPerTx: number;
15
+ /** How many public token transfers are executed per tx. */
16
+ publicTransfersPerTx: number;
17
+ /** How to handle fee payments. */
18
+ feePaymentMethod: 'native' | 'none';
19
+ /** True to not automatically setup or start the bot on initialization. */
20
+ noStart: boolean;
21
+ };
22
+ export declare function getBotConfigFromEnv(): BotConfig;
23
+ export declare function getBotDefaultConfig(overrides?: Partial<BotConfig>): BotConfig;
24
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAC;AAGxC,MAAM,MAAM,SAAS,GAAG;IACtB,8EAA8E;IAC9E,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,kDAAkD;IAClD,gBAAgB,EAAE,EAAE,CAAC;IACrB,iDAAiD;IACjD,yBAAyB,EAAE,EAAE,CAAC;IAC9B,8CAA8C;IAC9C,SAAS,EAAE,EAAE,CAAC;IACd,sDAAsD;IACtD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,4DAA4D;IAC5D,qBAAqB,EAAE,MAAM,CAAC;IAC9B,2DAA2D;IAC3D,oBAAoB,EAAE,MAAM,CAAC;IAC7B,kCAAkC;IAClC,gBAAgB,EAAE,QAAQ,GAAG,MAAM,CAAC;IACpC,0EAA0E;IAC1E,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,wBAAgB,mBAAmB,IAAI,SAAS,CA4B/C;AAED,wBAAgB,mBAAmB,CAAC,SAAS,GAAE,OAAO,CAAC,SAAS,CAAM,GAAG,SAAS,CAajF"}
package/dest/config.js ADDED
@@ -0,0 +1,36 @@
1
+ import { Fr } from '@aztec/circuits.js';
2
+ import { compact } from '@aztec/foundation/collection';
3
+ export function getBotConfigFromEnv() {
4
+ const { BOT_FEE_PAYMENT_METHOD, BOT_PRIVATE_KEY, BOT_TOKEN_SALT, BOT_RECIPIENT_ENCRYPTION_SECRET, BOT_TX_INTERVAL_SECONDS, BOT_PRIVATE_TRANSFERS_PER_TX, BOT_PUBLIC_TRANSFERS_PER_TX, BOT_NO_START, } = process.env;
5
+ if (BOT_FEE_PAYMENT_METHOD && !['native', 'none'].includes(BOT_FEE_PAYMENT_METHOD)) {
6
+ throw new Error(`Invalid bot fee payment method: ${BOT_FEE_PAYMENT_METHOD}`);
7
+ }
8
+ return getBotDefaultConfig({
9
+ pxeUrl: process.env.BOT_PXE_URL,
10
+ senderPrivateKey: BOT_PRIVATE_KEY ? Fr.fromString(BOT_PRIVATE_KEY) : undefined,
11
+ recipientEncryptionSecret: BOT_RECIPIENT_ENCRYPTION_SECRET
12
+ ? Fr.fromString(BOT_RECIPIENT_ENCRYPTION_SECRET)
13
+ : undefined,
14
+ tokenSalt: BOT_TOKEN_SALT ? Fr.fromString(BOT_TOKEN_SALT) : undefined,
15
+ txIntervalSeconds: BOT_TX_INTERVAL_SECONDS ? parseInt(BOT_TX_INTERVAL_SECONDS) : undefined,
16
+ privateTransfersPerTx: BOT_PRIVATE_TRANSFERS_PER_TX ? parseInt(BOT_PRIVATE_TRANSFERS_PER_TX) : undefined,
17
+ publicTransfersPerTx: BOT_PUBLIC_TRANSFERS_PER_TX ? parseInt(BOT_PUBLIC_TRANSFERS_PER_TX) : undefined,
18
+ feePaymentMethod: BOT_FEE_PAYMENT_METHOD ? BOT_FEE_PAYMENT_METHOD : undefined,
19
+ noStart: BOT_NO_START ? ['1', 'true'].includes(BOT_NO_START) : undefined,
20
+ });
21
+ }
22
+ export function getBotDefaultConfig(overrides = {}) {
23
+ return {
24
+ pxeUrl: undefined,
25
+ senderPrivateKey: Fr.random(),
26
+ recipientEncryptionSecret: Fr.fromString('0xcafecafe'),
27
+ tokenSalt: Fr.fromString('1'),
28
+ txIntervalSeconds: 60,
29
+ privateTransfersPerTx: 1,
30
+ publicTransfersPerTx: 1,
31
+ feePaymentMethod: 'none',
32
+ noStart: false,
33
+ ...compact(overrides),
34
+ };
35
+ }
36
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2NvbmZpZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsRUFBRSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDeEMsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBdUJ2RCxNQUFNLFVBQVUsbUJBQW1CO0lBQ2pDLE1BQU0sRUFDSixzQkFBc0IsRUFDdEIsZUFBZSxFQUNmLGNBQWMsRUFDZCwrQkFBK0IsRUFDL0IsdUJBQXVCLEVBQ3ZCLDRCQUE0QixFQUM1QiwyQkFBMkIsRUFDM0IsWUFBWSxHQUNiLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQztJQUNoQixJQUFJLHNCQUFzQixJQUFJLENBQUMsQ0FBQyxRQUFRLEVBQUUsTUFBTSxDQUFDLENBQUMsUUFBUSxDQUFDLHNCQUFzQixDQUFDLEVBQUUsQ0FBQztRQUNuRixNQUFNLElBQUksS0FBSyxDQUFDLG1DQUFtQyxzQkFBc0IsRUFBRSxDQUFDLENBQUM7SUFDL0UsQ0FBQztJQUVELE9BQU8sbUJBQW1CLENBQUM7UUFDekIsTUFBTSxFQUFFLE9BQU8sQ0FBQyxHQUFHLENBQUMsV0FBVztRQUMvQixnQkFBZ0IsRUFBRSxlQUFlLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxVQUFVLENBQUMsZUFBZSxDQUFDLENBQUMsQ0FBQyxDQUFDLFNBQVM7UUFDOUUseUJBQXlCLEVBQUUsK0JBQStCO1lBQ3hELENBQUMsQ0FBQyxFQUFFLENBQUMsVUFBVSxDQUFDLCtCQUErQixDQUFDO1lBQ2hELENBQUMsQ0FBQyxTQUFTO1FBQ2IsU0FBUyxFQUFFLGNBQWMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLFVBQVUsQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDLENBQUMsU0FBUztRQUNyRSxpQkFBaUIsRUFBRSx1QkFBdUIsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLHVCQUF1QixDQUFDLENBQUMsQ0FBQyxDQUFDLFNBQVM7UUFDMUYscUJBQXFCLEVBQUUsNEJBQTRCLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyw0QkFBNEIsQ0FBQyxDQUFDLENBQUMsQ0FBQyxTQUFTO1FBQ3hHLG9CQUFvQixFQUFFLDJCQUEyQixDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsMkJBQTJCLENBQUMsQ0FBQyxDQUFDLENBQUMsU0FBUztRQUNyRyxnQkFBZ0IsRUFBRSxzQkFBc0IsQ0FBQyxDQUFDLENBQUUsc0JBQTRDLENBQUMsQ0FBQyxDQUFDLFNBQVM7UUFDcEcsT0FBTyxFQUFFLFlBQVksQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLEVBQUUsTUFBTSxDQUFDLENBQUMsUUFBUSxDQUFDLFlBQVksQ0FBQyxDQUFDLENBQUMsQ0FBQyxTQUFTO0tBQ3pFLENBQUMsQ0FBQztBQUNMLENBQUM7QUFFRCxNQUFNLFVBQVUsbUJBQW1CLENBQUMsWUFBZ0MsRUFBRTtJQUNwRSxPQUFPO1FBQ0wsTUFBTSxFQUFFLFNBQVM7UUFDakIsZ0JBQWdCLEVBQUUsRUFBRSxDQUFDLE1BQU0sRUFBRTtRQUM3Qix5QkFBeUIsRUFBRSxFQUFFLENBQUMsVUFBVSxDQUFDLFlBQVksQ0FBQztRQUN0RCxTQUFTLEVBQUUsRUFBRSxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUM7UUFDN0IsaUJBQWlCLEVBQUUsRUFBRTtRQUNyQixxQkFBcUIsRUFBRSxDQUFDO1FBQ3hCLG9CQUFvQixFQUFFLENBQUM7UUFDdkIsZ0JBQWdCLEVBQUUsTUFBTTtRQUN4QixPQUFPLEVBQUUsS0FBSztRQUNkLEdBQUcsT0FBTyxDQUFDLFNBQVMsQ0FBQztLQUN0QixDQUFDO0FBQ0osQ0FBQyJ9
@@ -0,0 +1,42 @@
1
+ import { type PXE } from '@aztec/circuit-types';
2
+ import { TokenContract } from '@aztec/noir-contracts.js/Token';
3
+ import { type BotConfig } from './config.js';
4
+ export declare class BotFactory {
5
+ private readonly config;
6
+ private pxe;
7
+ private log;
8
+ constructor(config: BotConfig, dependencies?: {
9
+ pxe?: PXE;
10
+ });
11
+ /**
12
+ * Initializes a new bot by setting up the sender account, registering the recipient,
13
+ * deploying the token contract, and minting tokens if necessary.
14
+ */
15
+ setup(): Promise<{
16
+ wallet: import("@aztec/aztec.js").AccountWalletWithSecretKey;
17
+ token: TokenContract;
18
+ pxe: PXE;
19
+ recipient: import("@aztec/aztec.js").AztecAddress;
20
+ }>;
21
+ /**
22
+ * Checks if the sender account contract is initialized, and initializes it if necessary.
23
+ * @returns The sender wallet.
24
+ */
25
+ private setupAccount;
26
+ /**
27
+ * Registers the recipient for txs in the pxe.
28
+ */
29
+ private registerRecipient;
30
+ /**
31
+ * Checks if the token contract is deployed and deploys it if necessary.
32
+ * @param wallet - Wallet to deploy the token contract from.
33
+ * @returns The TokenContract instance.
34
+ */
35
+ private setupToken;
36
+ /**
37
+ * Mints private and public tokens for the sender if their balance is below the minimum.
38
+ * @param token - Token contract.
39
+ */
40
+ private mintTokens;
41
+ }
42
+ //# sourceMappingURL=factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../src/factory.ts"],"names":[],"mappings":"AAEA,OAAO,EAAqB,KAAK,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAEnE,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAE/D,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAM7C,qBAAa,UAAU;IAIT,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHnC,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,GAAG,CAAkC;gBAEhB,MAAM,EAAE,SAAS,EAAE,YAAY,GAAE;QAAE,GAAG,CAAC,EAAE,GAAG,CAAA;KAAO;IAQhF;;;OAGG;IACU,KAAK;;;;;;IAQlB;;;OAGG;YACW,YAAY;IAc1B;;OAEG;YACW,iBAAiB;IAK/B;;;;OAIG;YACW,UAAU;IAaxB;;;OAGG;YACW,UAAU;CAkBzB"}
@@ -0,0 +1,94 @@
1
+ import { getSchnorrAccount } from '@aztec/accounts/schnorr';
2
+ import { BatchCall, createDebugLogger, createPXEClient } from '@aztec/aztec.js';
3
+ import { Fr, deriveSigningKey } from '@aztec/circuits.js';
4
+ import { TokenContract } from '@aztec/noir-contracts.js/Token';
5
+ import { getBalances } from './utils.js';
6
+ const MINT_BALANCE = 1e12;
7
+ const MIN_BALANCE = 1e3;
8
+ export class BotFactory {
9
+ constructor(config, dependencies = {}) {
10
+ this.config = config;
11
+ this.log = createDebugLogger('aztec:bot');
12
+ if (!dependencies.pxe && !config.pxeUrl) {
13
+ throw new Error(`Either a PXE client or a PXE URL must be provided`);
14
+ }
15
+ this.pxe = dependencies.pxe ?? createPXEClient(config.pxeUrl);
16
+ }
17
+ /**
18
+ * Initializes a new bot by setting up the sender account, registering the recipient,
19
+ * deploying the token contract, and minting tokens if necessary.
20
+ */
21
+ async setup() {
22
+ const recipient = await this.registerRecipient();
23
+ const wallet = await this.setupAccount();
24
+ const token = await this.setupToken(wallet);
25
+ await this.mintTokens(token);
26
+ return { wallet, token, pxe: this.pxe, recipient };
27
+ }
28
+ /**
29
+ * Checks if the sender account contract is initialized, and initializes it if necessary.
30
+ * @returns The sender wallet.
31
+ */
32
+ async setupAccount() {
33
+ const salt = Fr.ONE;
34
+ const signingKey = deriveSigningKey(this.config.senderPrivateKey);
35
+ const account = getSchnorrAccount(this.pxe, this.config.senderPrivateKey, signingKey, salt);
36
+ const isInit = await this.pxe.isContractInitialized(account.getAddress());
37
+ if (isInit) {
38
+ this.log.info(`Account at ${account.getAddress().toString()} already initialized`);
39
+ return account.register();
40
+ }
41
+ else {
42
+ this.log.info(`Initializing account at ${account.getAddress().toString()}`);
43
+ return account.waitSetup();
44
+ }
45
+ }
46
+ /**
47
+ * Registers the recipient for txs in the pxe.
48
+ */
49
+ async registerRecipient() {
50
+ const recipient = await this.pxe.registerAccount(this.config.recipientEncryptionSecret, Fr.ONE);
51
+ return recipient.address;
52
+ }
53
+ /**
54
+ * Checks if the token contract is deployed and deploys it if necessary.
55
+ * @param wallet - Wallet to deploy the token contract from.
56
+ * @returns The TokenContract instance.
57
+ */
58
+ async setupToken(wallet) {
59
+ const deploy = TokenContract.deploy(wallet, wallet.getAddress(), 'BotToken', 'BOT', 18);
60
+ const deployOpts = { contractAddressSalt: this.config.tokenSalt, universalDeploy: true };
61
+ const address = deploy.getInstance(deployOpts).address;
62
+ if (await this.pxe.isContractPubliclyDeployed(address)) {
63
+ this.log.info(`Token at ${address.toString()} already deployed`);
64
+ return deploy.register();
65
+ }
66
+ else {
67
+ this.log.info(`Deploying token contract at ${address.toString()}`);
68
+ return deploy.send(deployOpts).deployed();
69
+ }
70
+ }
71
+ /**
72
+ * Mints private and public tokens for the sender if their balance is below the minimum.
73
+ * @param token - Token contract.
74
+ */
75
+ async mintTokens(token) {
76
+ const sender = token.wallet.getAddress();
77
+ const { privateBalance, publicBalance } = await getBalances(token, sender);
78
+ const calls = [];
79
+ if (privateBalance < MIN_BALANCE) {
80
+ this.log.info(`Minting private tokens for ${sender.toString()}`);
81
+ calls.push(token.methods.privately_mint_private_note(MINT_BALANCE).request());
82
+ }
83
+ if (publicBalance < MIN_BALANCE) {
84
+ this.log.info(`Minting public tokens for ${sender.toString()}`);
85
+ calls.push(token.methods.mint_public(sender, MINT_BALANCE).request());
86
+ }
87
+ if (calls.length === 0) {
88
+ this.log.info(`Skipping minting as ${sender.toString()} has enough tokens`);
89
+ return;
90
+ }
91
+ await new BatchCall(token.wallet, calls).send().wait();
92
+ }
93
+ }
94
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFjdG9yeS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9mYWN0b3J5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQzVELE9BQU8sRUFBc0IsU0FBUyxFQUFFLGlCQUFpQixFQUFFLGVBQWUsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBRXBHLE9BQU8sRUFBRSxFQUFFLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUMxRCxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFHL0QsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUV6QyxNQUFNLFlBQVksR0FBRyxJQUFJLENBQUM7QUFDMUIsTUFBTSxXQUFXLEdBQUcsR0FBRyxDQUFDO0FBRXhCLE1BQU0sT0FBTyxVQUFVO0lBSXJCLFlBQTZCLE1BQWlCLEVBQUUsZUFBOEIsRUFBRTtRQUFuRCxXQUFNLEdBQU4sTUFBTSxDQUFXO1FBRnRDLFFBQUcsR0FBRyxpQkFBaUIsQ0FBQyxXQUFXLENBQUMsQ0FBQztRQUczQyxJQUFJLENBQUMsWUFBWSxDQUFDLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUN4QyxNQUFNLElBQUksS0FBSyxDQUFDLG1EQUFtRCxDQUFDLENBQUM7UUFDdkUsQ0FBQztRQUVELElBQUksQ0FBQyxHQUFHLEdBQUcsWUFBWSxDQUFDLEdBQUcsSUFBSSxlQUFlLENBQUMsTUFBTSxDQUFDLE1BQU8sQ0FBQyxDQUFDO0lBQ2pFLENBQUM7SUFFRDs7O09BR0c7SUFDSSxLQUFLLENBQUMsS0FBSztRQUNoQixNQUFNLFNBQVMsR0FBRyxNQUFNLElBQUksQ0FBQyxpQkFBaUIsRUFBRSxDQUFDO1FBQ2pELE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLFlBQVksRUFBRSxDQUFDO1FBQ3pDLE1BQU0sS0FB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
@@ -0,0 +1,5 @@
1
+ export { Bot } from './bot.js';
2
+ export { BotRunner } from './runner.js';
3
+ export { BotConfig, getBotConfigFromEnv, getBotDefaultConfig } from './config.js';
4
+ export { createBotRunnerRpcServer } from './rpc.js';
5
+ //# 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,EAAE,SAAS,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClF,OAAO,EAAE,wBAAwB,EAAE,MAAM,UAAU,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 } from './config.js';
4
+ export { createBotRunnerRpcServer } from './rpc.js';
5
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLFVBQVUsQ0FBQztBQUMvQixPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQ3hDLE9BQU8sRUFBYSxtQkFBbUIsRUFBRSxtQkFBbUIsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUNsRixPQUFPLEVBQUUsd0JBQXdCLEVBQUUsTUFBTSxVQUFVLENBQUMifQ==
package/dest/rpc.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { JsonRpcServer } 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): JsonRpcServer;
9
+ //# sourceMappingURL=rpc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc.d.ts","sourceRoot":"","sources":["../src/rpc.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAElE,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,SAAS,iBAE5D"}
package/dest/rpc.js ADDED
@@ -0,0 +1,14 @@
1
+ import { TxHash } from '@aztec/circuit-types';
2
+ import { AztecAddress } from '@aztec/foundation/aztec-address';
3
+ import { EthAddress } from '@aztec/foundation/eth-address';
4
+ import { Fr } from '@aztec/foundation/fields';
5
+ import { JsonRpcServer } from '@aztec/foundation/json-rpc/server';
6
+ /**
7
+ * Wraps a bot runner with a JSON RPC HTTP server.
8
+ * @param botRunner - The BotRunner.
9
+ * @returns An JSON-RPC HTTP server
10
+ */
11
+ export function createBotRunnerRpcServer(botRunner) {
12
+ return new JsonRpcServer(botRunner, { AztecAddress, EthAddress, Fr, TxHash }, {}, []);
13
+ }
14
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnBjLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3JwYy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDOUMsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQy9ELE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQUMzRCxPQUFPLEVBQUUsRUFBRSxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDOUMsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLG1DQUFtQyxDQUFDO0FBSWxFOzs7O0dBSUc7QUFDSCxNQUFNLFVBQVUsd0JBQXdCLENBQUMsU0FBb0I7SUFDM0QsT0FBTyxJQUFJLGFBQWEsQ0FBQyxTQUFTLEVBQUUsRUFBRSxZQUFZLEVBQUUsVUFBVSxFQUFFLEVBQUUsRUFBRSxNQUFNLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxDQUFDLENBQUM7QUFDeEYsQ0FBQyJ9
@@ -0,0 +1,40 @@
1
+ import { type PXE } from '@aztec/aztec.js';
2
+ import { type BotConfig } from './config.js';
3
+ export declare class BotRunner {
4
+ #private;
5
+ private config;
6
+ private log;
7
+ private interval?;
8
+ private bot?;
9
+ private pxe?;
10
+ private running;
11
+ constructor(config: BotConfig, dependencies?: {
12
+ pxe?: PXE;
13
+ });
14
+ /** Initializes the bot if needed. Blocks until the bot setup is finished. */
15
+ setup(): Promise<void>;
16
+ /**
17
+ * Initializes the bot if needed and starts sending txs at regular intervals.
18
+ * Blocks until the bot setup is finished.
19
+ */
20
+ start(): Promise<void>;
21
+ /**
22
+ * Stops sending txs. Returns once all ongoing txs are finished.
23
+ */
24
+ stop(): Promise<void>;
25
+ /** Returns whether the bot is running. */
26
+ isRunning(): boolean;
27
+ /**
28
+ * Updates the bot config and recreates the bot. Will stop and restart the bot automatically if it was
29
+ * running when this method was called. Blocks until the new bot is set up.
30
+ */
31
+ update(config: BotConfig): Promise<void>;
32
+ /**
33
+ * Triggers a single iteration of the bot. Requires the bot to be initialized.
34
+ * Blocks until the run is finished.
35
+ */
36
+ run(): Promise<void>;
37
+ /** Returns the current configuration for the bot. */
38
+ getConfig(): BotConfig;
39
+ }
40
+ //# 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,GAAG,EAAqB,MAAM,iBAAiB,CAAC;AAG9D,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,qBAAa,SAAS;;IAOD,OAAO,CAAC,MAAM;IANjC,OAAO,CAAC,GAAG,CAAkC;IAC7C,OAAO,CAAC,QAAQ,CAAC,CAAiB;IAClC,OAAO,CAAC,GAAG,CAAC,CAAe;IAC3B,OAAO,CAAC,GAAG,CAAC,CAAM;IAClB,OAAO,CAAC,OAAO,CAAiC;gBAErB,MAAM,EAAE,SAAS,EAAE,YAAY,GAAE;QAAE,GAAG,CAAC,EAAE,GAAG,CAAA;KAAO;IAI9E,6EAA6E;IAChE,KAAK;IAQlB;;;OAGG;IACU,KAAK;IAQlB;;OAEG;IACU,IAAI;IAajB,0CAA0C;IACnC,SAAS;IAIhB;;;OAGG;IACU,MAAM,CAAC,MAAM,EAAE,SAAS;IAcrC;;;OAGG;IACU,GAAG;IAYhB,qDAAqD;IAC9C,SAAS;CAQjB"}
package/dest/runner.js ADDED
@@ -0,0 +1,92 @@
1
+ var _BotRunner_instances, _BotRunner_createBot;
2
+ import { __classPrivateFieldGet } from "tslib";
3
+ import { createDebugLogger } from '@aztec/aztec.js';
4
+ import { Bot } from './bot.js';
5
+ export class BotRunner {
6
+ constructor(config, dependencies = {}) {
7
+ _BotRunner_instances.add(this);
8
+ this.config = config;
9
+ this.log = createDebugLogger('aztec:bot');
10
+ this.running = new Set();
11
+ this.pxe = dependencies.pxe;
12
+ }
13
+ /** Initializes the bot if needed. Blocks until the bot setup is finished. */
14
+ async setup() {
15
+ if (!this.bot) {
16
+ this.log.verbose(`Setting up bot`);
17
+ await __classPrivateFieldGet(this, _BotRunner_instances, "m", _BotRunner_createBot).call(this);
18
+ this.log.info(`Bot set up completed`);
19
+ }
20
+ }
21
+ /**
22
+ * Initializes the bot if needed and starts sending txs at regular intervals.
23
+ * Blocks until the bot setup is finished.
24
+ */
25
+ async start() {
26
+ await this.setup();
27
+ if (!this.interval) {
28
+ this.log.info(`Starting bot with interval of ${this.config.txIntervalSeconds}s`);
29
+ this.interval = setInterval(() => this.run(), this.config.txIntervalSeconds * 1000);
30
+ }
31
+ }
32
+ /**
33
+ * Stops sending txs. Returns once all ongoing txs are finished.
34
+ */
35
+ async stop() {
36
+ if (this.interval) {
37
+ this.log.verbose(`Stopping bot`);
38
+ clearInterval(this.interval);
39
+ this.interval = undefined;
40
+ }
41
+ if (this.running.size > 0) {
42
+ this.log.verbose(`Waiting for ${this.running.size} running txs to finish`);
43
+ await Promise.all(this.running);
44
+ }
45
+ this.log.info(`Stopped bot`);
46
+ }
47
+ /** Returns whether the bot is running. */
48
+ isRunning() {
49
+ return !!this.interval;
50
+ }
51
+ /**
52
+ * Updates the bot config and recreates the bot. Will stop and restart the bot automatically if it was
53
+ * running when this method was called. Blocks until the new bot is set up.
54
+ */
55
+ async update(config) {
56
+ this.log.verbose(`Updating bot config`);
57
+ const wasRunning = this.isRunning();
58
+ if (wasRunning) {
59
+ await this.stop();
60
+ }
61
+ this.config = { ...this.config, ...config };
62
+ await __classPrivateFieldGet(this, _BotRunner_instances, "m", _BotRunner_createBot).call(this);
63
+ this.log.info(`Bot config updated`);
64
+ if (wasRunning) {
65
+ await this.start();
66
+ }
67
+ }
68
+ /**
69
+ * Triggers a single iteration of the bot. Requires the bot to be initialized.
70
+ * Blocks until the run is finished.
71
+ */
72
+ async run() {
73
+ if (!this.bot) {
74
+ throw new Error(`Bot is not initialized`);
75
+ }
76
+ this.log.verbose(`Manually triggered bot run`);
77
+ const bot = await this.bot;
78
+ const promise = bot.run();
79
+ this.running.add(promise);
80
+ await promise;
81
+ this.running.delete(promise);
82
+ }
83
+ /** Returns the current configuration for the bot. */
84
+ getConfig() {
85
+ return this.config;
86
+ }
87
+ }
88
+ _BotRunner_instances = new WeakSet(), _BotRunner_createBot = async function _BotRunner_createBot() {
89
+ this.bot = Bot.create(this.config, { pxe: this.pxe });
90
+ await this.bot;
91
+ };
92
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnVubmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3J1bm5lci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLE9BQU8sRUFBWSxpQkFBaUIsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBRTlELE9BQU8sRUFBRSxHQUFHLEVBQUUsTUFBTSxVQUFVLENBQUM7QUFHL0IsTUFBTSxPQUFPLFNBQVM7SUFPcEIsWUFBMkIsTUFBaUIsRUFBRSxlQUE4QixFQUFFOztRQUFuRCxXQUFNLEdBQU4sTUFBTSxDQUFXO1FBTnBDLFFBQUcsR0FBRyxpQkFBaUIsQ0FBQyxXQUFXLENBQUMsQ0FBQztRQUlyQyxZQUFPLEdBQXVCLElBQUksR0FBRyxFQUFFLENBQUM7UUFHOUMsSUFBSSxDQUFDLEdBQUcsR0FBRyxZQUFZLENBQUMsR0FBRyxDQUFDO0lBQzlCLENBQUM7SUFFRCw2RUFBNkU7SUFDdEUsS0FBSyxDQUFDLEtBQUs7UUFDaEIsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztZQUNkLElBQUksQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLGdCQUFnQixDQUFDLENBQUM7WUFDbkMsTUFBTSx1QkFBQSxJQUFJLGtEQUFXLE1BQWYsSUFBSSxDQUFhLENBQUM7WUFDeEIsSUFBSSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsc0JBQXNCLENBQUMsQ0FBQztRQUN4QyxDQUFDO0lBQ0gsQ0FBQztJQUVEOzs7T0FHRztJQUNJLEtBQUssQ0FBQyxLQUFLO1FBQ2hCLE1BQU0sSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ25CLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUM7WUFDbkIsSUFBSSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsaUNBQWlDLElBQUksQ0FBQyxNQUFNLENBQUMsaUJBQWlCLEdBQUcsQ0FBQyxDQUFDO1lBQ2pGLElBQUksQ0FBQyxRQUFRLEdBQUcsV0FBVyxDQUFDLEdBQUcsRUFBRSxDQUFD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
@@ -0,0 +1,13 @@
1
+ import { type AztecAddress } from '@aztec/circuits.js';
2
+ import { type TokenContract } from '@aztec/noir-contracts.js/Token';
3
+ /**
4
+ * Gets the private and public balance of the given token for the given address.
5
+ * @param token - Token contract.
6
+ * @param who - Address to get the balance for.
7
+ * @returns - Private and public token balances as bigints.
8
+ */
9
+ export declare function getBalances(token: TokenContract, who: AztecAddress): Promise<{
10
+ privateBalance: bigint;
11
+ publicBalance: bigint;
12
+ }>;
13
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAEpE;;;;;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"}
package/dest/utils.js ADDED
@@ -0,0 +1,12 @@
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
+ */
7
+ export async function getBalances(token, who) {
8
+ const privateBalance = await token.methods.balance_of_private(who).simulate();
9
+ const publicBalance = await token.methods.balance_of_public(who).simulate();
10
+ return { privateBalance, publicBalance };
11
+ }
12
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvdXRpbHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBR0E7Ozs7O0dBS0c7QUFDSCxNQUFNLENBQUMsS0FBSyxVQUFVLFdBQVcsQ0FDL0IsS0FBb0IsRUFDcEIsR0FBaUI7SUFFakIsTUFBTSxjQUFjLEdBQUcsTUFBTSxLQUFLLENBQUMsT0FBTyxDQUFDLGtCQUFrQixDQUFDLEdBQUcsQ0FBQyxDQUFDLFFBQVEsRUFBRSxDQUFDO0lBQzlFLE1BQU0sYUFBYSxHQUFHLE1BQU0sS0FBSyxDQUFDLE9BQU8sQ0FBQyxpQkFBaUIsQ0FBQyxHQUFHLENBQUMsQ0FBQyxRQUFRLEVBQUUsQ0FBQztJQUM1RSxPQUFPLEVBQUUsY0FBYyxFQUFFLGFBQWEsRUFBRSxDQUFDO0FBQzNDLENBQUMifQ==
package/package.json ADDED
@@ -0,0 +1,84 @@
1
+ {
2
+ "name": "@aztec/bot",
3
+ "version": "0.47.1",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": "./dest/index.js"
7
+ },
8
+ "inherits": [
9
+ "../package.common.json"
10
+ ],
11
+ "scripts": {
12
+ "build": "yarn clean && tsc -b",
13
+ "build:dev": "tsc -b --watch",
14
+ "clean": "rm -rf ./dest .tsbuildinfo",
15
+ "formatting": "run -T prettier --check ./src && run -T eslint ./src",
16
+ "formatting:fix": "run -T eslint --fix ./src && run -T prettier -w ./src",
17
+ "bb": "node --no-warnings ./dest/bb/index.js",
18
+ "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests"
19
+ },
20
+ "jest": {
21
+ "moduleNameMapper": {
22
+ "^(\\.{1,2}/.*)\\.[cm]?js$": "$1"
23
+ },
24
+ "testRegex": "./src/.*\\.test\\.(js|mjs|ts)$",
25
+ "rootDir": "./src",
26
+ "transform": {
27
+ "^.+\\.tsx?$": [
28
+ "@swc/jest",
29
+ {
30
+ "jsc": {
31
+ "parser": {
32
+ "syntax": "typescript",
33
+ "decorators": true
34
+ }
35
+ }
36
+ }
37
+ ]
38
+ },
39
+ "extensionsToTreatAsEsm": [
40
+ ".ts"
41
+ ],
42
+ "reporters": [
43
+ [
44
+ "default",
45
+ {
46
+ "summaryThreshold": 9999
47
+ }
48
+ ]
49
+ ]
50
+ },
51
+ "dependencies": {
52
+ "@aztec/accounts": "0.47.1",
53
+ "@aztec/aztec.js": "0.47.1",
54
+ "@aztec/circuit-types": "0.47.1",
55
+ "@aztec/circuits.js": "0.47.1",
56
+ "@aztec/entrypoints": "0.47.1",
57
+ "@aztec/foundation": "0.47.1",
58
+ "@aztec/noir-contracts.js": "0.47.1",
59
+ "@aztec/protocol-contracts": "0.47.1",
60
+ "@aztec/types": "0.47.1",
61
+ "source-map-support": "^0.5.21",
62
+ "tslib": "^2.4.0"
63
+ },
64
+ "devDependencies": {
65
+ "@jest/globals": "^29.5.0",
66
+ "@types/jest": "^29.5.0",
67
+ "@types/memdown": "^3.0.0",
68
+ "@types/node": "^18.7.23",
69
+ "@types/source-map-support": "^0.5.10",
70
+ "jest": "^29.5.0",
71
+ "jest-mock-extended": "^3.0.3",
72
+ "ts-node": "^10.9.1",
73
+ "typescript": "^5.0.4"
74
+ },
75
+ "files": [
76
+ "dest",
77
+ "src",
78
+ "!*.test.*"
79
+ ],
80
+ "types": "./dest/index.d.ts",
81
+ "engines": {
82
+ "node": ">=18"
83
+ }
84
+ }
package/src/bot.ts ADDED
@@ -0,0 +1,68 @@
1
+ import {
2
+ type AztecAddress,
3
+ BatchCall,
4
+ NativeFeePaymentMethod,
5
+ NoFeePaymentMethod,
6
+ type SendMethodOptions,
7
+ type Wallet,
8
+ createDebugLogger,
9
+ } from '@aztec/aztec.js';
10
+ import { type FunctionCall, type PXE } from '@aztec/circuit-types';
11
+ import { GasSettings } from '@aztec/circuits.js';
12
+ import { times } from '@aztec/foundation/collection';
13
+ import { type TokenContract } from '@aztec/noir-contracts.js';
14
+
15
+ import { type BotConfig } from './config.js';
16
+ import { BotFactory } from './factory.js';
17
+ import { getBalances } from './utils.js';
18
+
19
+ const TRANSFER_AMOUNT = 1;
20
+
21
+ export class Bot {
22
+ private log = createDebugLogger('aztec:bot');
23
+
24
+ protected constructor(
25
+ public readonly wallet: Wallet,
26
+ public readonly token: TokenContract,
27
+ public readonly recipient: AztecAddress,
28
+ public readonly config: BotConfig,
29
+ ) {}
30
+
31
+ static async create(config: BotConfig, dependencies: { pxe?: PXE } = {}): Promise<Bot> {
32
+ const { wallet, token, recipient } = await new BotFactory(config, dependencies).setup();
33
+ return new Bot(wallet, token, recipient, config);
34
+ }
35
+
36
+ public async run() {
37
+ const { privateTransfersPerTx, publicTransfersPerTx, feePaymentMethod } = this.config;
38
+ const { token, recipient, wallet } = this;
39
+ const sender = wallet.getAddress();
40
+
41
+ this.log.verbose(
42
+ `Sending tx with ${feePaymentMethod} fee with ${privateTransfersPerTx} private and ${publicTransfersPerTx} public transfers`,
43
+ );
44
+
45
+ const calls: FunctionCall[] = [
46
+ ...times(privateTransfersPerTx, () => token.methods.transfer(recipient, TRANSFER_AMOUNT).request()),
47
+ ...times(publicTransfersPerTx, () =>
48
+ token.methods.transfer_public(sender, recipient, TRANSFER_AMOUNT, 0).request(),
49
+ ),
50
+ ];
51
+
52
+ const paymentMethod = feePaymentMethod === 'native' ? new NativeFeePaymentMethod(sender) : new NoFeePaymentMethod();
53
+ const gasSettings = GasSettings.default();
54
+ const opts: SendMethodOptions = { estimateGas: true, fee: { paymentMethod, gasSettings } };
55
+ const tx = new BatchCall(wallet, calls).send(opts);
56
+ this.log.verbose(`Sent tx ${tx.getTxHash()}`);
57
+
58
+ const receipt = await tx.wait();
59
+ this.log.info(`Tx ${receipt.txHash} mined in block ${receipt.blockNumber}`);
60
+ }
61
+
62
+ public async getBalances() {
63
+ return {
64
+ sender: await getBalances(this.token, this.wallet.getAddress()),
65
+ recipient: await getBalances(this.token, this.recipient),
66
+ };
67
+ }
68
+ }
package/src/config.ts ADDED
@@ -0,0 +1,68 @@
1
+ import { Fr } from '@aztec/circuits.js';
2
+ import { compact } from '@aztec/foundation/collection';
3
+
4
+ export type BotConfig = {
5
+ /** URL to the PXE for sending txs, or undefined if an in-proc PXE is used. */
6
+ pxeUrl: string | undefined;
7
+ /** Signing private key for the sender account. */
8
+ senderPrivateKey: Fr;
9
+ /** Encryption secret for a recipient account. */
10
+ recipientEncryptionSecret: Fr;
11
+ /** Salt for the token contract deployment. */
12
+ tokenSalt: Fr;
13
+ /** Every how many seconds should a new tx be sent. */
14
+ txIntervalSeconds: number;
15
+ /** How many private token transfers are executed per tx. */
16
+ privateTransfersPerTx: number;
17
+ /** How many public token transfers are executed per tx. */
18
+ publicTransfersPerTx: number;
19
+ /** How to handle fee payments. */
20
+ feePaymentMethod: 'native' | 'none';
21
+ /** True to not automatically setup or start the bot on initialization. */
22
+ noStart: boolean;
23
+ };
24
+
25
+ export function getBotConfigFromEnv(): BotConfig {
26
+ const {
27
+ BOT_FEE_PAYMENT_METHOD,
28
+ BOT_PRIVATE_KEY,
29
+ BOT_TOKEN_SALT,
30
+ BOT_RECIPIENT_ENCRYPTION_SECRET,
31
+ BOT_TX_INTERVAL_SECONDS,
32
+ BOT_PRIVATE_TRANSFERS_PER_TX,
33
+ BOT_PUBLIC_TRANSFERS_PER_TX,
34
+ BOT_NO_START,
35
+ } = process.env;
36
+ if (BOT_FEE_PAYMENT_METHOD && !['native', 'none'].includes(BOT_FEE_PAYMENT_METHOD)) {
37
+ throw new Error(`Invalid bot fee payment method: ${BOT_FEE_PAYMENT_METHOD}`);
38
+ }
39
+
40
+ return getBotDefaultConfig({
41
+ pxeUrl: process.env.BOT_PXE_URL,
42
+ senderPrivateKey: BOT_PRIVATE_KEY ? Fr.fromString(BOT_PRIVATE_KEY) : undefined,
43
+ recipientEncryptionSecret: BOT_RECIPIENT_ENCRYPTION_SECRET
44
+ ? Fr.fromString(BOT_RECIPIENT_ENCRYPTION_SECRET)
45
+ : undefined,
46
+ tokenSalt: BOT_TOKEN_SALT ? Fr.fromString(BOT_TOKEN_SALT) : undefined,
47
+ txIntervalSeconds: BOT_TX_INTERVAL_SECONDS ? parseInt(BOT_TX_INTERVAL_SECONDS) : undefined,
48
+ privateTransfersPerTx: BOT_PRIVATE_TRANSFERS_PER_TX ? parseInt(BOT_PRIVATE_TRANSFERS_PER_TX) : undefined,
49
+ publicTransfersPerTx: BOT_PUBLIC_TRANSFERS_PER_TX ? parseInt(BOT_PUBLIC_TRANSFERS_PER_TX) : undefined,
50
+ feePaymentMethod: BOT_FEE_PAYMENT_METHOD ? (BOT_FEE_PAYMENT_METHOD as 'native' | 'none') : undefined,
51
+ noStart: BOT_NO_START ? ['1', 'true'].includes(BOT_NO_START) : undefined,
52
+ });
53
+ }
54
+
55
+ export function getBotDefaultConfig(overrides: Partial<BotConfig> = {}): BotConfig {
56
+ return {
57
+ pxeUrl: undefined,
58
+ senderPrivateKey: Fr.random(),
59
+ recipientEncryptionSecret: Fr.fromString('0xcafecafe'),
60
+ tokenSalt: Fr.fromString('1'),
61
+ txIntervalSeconds: 60,
62
+ privateTransfersPerTx: 1,
63
+ publicTransfersPerTx: 1,
64
+ feePaymentMethod: 'none',
65
+ noStart: false,
66
+ ...compact(overrides),
67
+ };
68
+ }
package/src/factory.ts ADDED
@@ -0,0 +1,103 @@
1
+ import { getSchnorrAccount } from '@aztec/accounts/schnorr';
2
+ import { type AccountWallet, BatchCall, createDebugLogger, createPXEClient } from '@aztec/aztec.js';
3
+ import { type FunctionCall, type PXE } from '@aztec/circuit-types';
4
+ import { Fr, deriveSigningKey } from '@aztec/circuits.js';
5
+ import { TokenContract } from '@aztec/noir-contracts.js/Token';
6
+
7
+ import { type BotConfig } from './config.js';
8
+ import { getBalances } from './utils.js';
9
+
10
+ const MINT_BALANCE = 1e12;
11
+ const MIN_BALANCE = 1e3;
12
+
13
+ export class BotFactory {
14
+ private pxe: PXE;
15
+ private log = createDebugLogger('aztec:bot');
16
+
17
+ constructor(private readonly config: BotConfig, dependencies: { pxe?: PXE } = {}) {
18
+ if (!dependencies.pxe && !config.pxeUrl) {
19
+ throw new Error(`Either a PXE client or a PXE URL must be provided`);
20
+ }
21
+
22
+ this.pxe = dependencies.pxe ?? createPXEClient(config.pxeUrl!);
23
+ }
24
+
25
+ /**
26
+ * Initializes a new bot by setting up the sender account, registering the recipient,
27
+ * deploying the token contract, and minting tokens if necessary.
28
+ */
29
+ public async setup() {
30
+ const recipient = await this.registerRecipient();
31
+ const wallet = await this.setupAccount();
32
+ const token = await this.setupToken(wallet);
33
+ await this.mintTokens(token);
34
+ return { wallet, token, pxe: this.pxe, recipient };
35
+ }
36
+
37
+ /**
38
+ * Checks if the sender account contract is initialized, and initializes it if necessary.
39
+ * @returns The sender wallet.
40
+ */
41
+ private async setupAccount() {
42
+ const salt = Fr.ONE;
43
+ const signingKey = deriveSigningKey(this.config.senderPrivateKey);
44
+ const account = getSchnorrAccount(this.pxe, this.config.senderPrivateKey, signingKey, salt);
45
+ const isInit = await this.pxe.isContractInitialized(account.getAddress());
46
+ if (isInit) {
47
+ this.log.info(`Account at ${account.getAddress().toString()} already initialized`);
48
+ return account.register();
49
+ } else {
50
+ this.log.info(`Initializing account at ${account.getAddress().toString()}`);
51
+ return account.waitSetup();
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Registers the recipient for txs in the pxe.
57
+ */
58
+ private async registerRecipient() {
59
+ const recipient = await this.pxe.registerAccount(this.config.recipientEncryptionSecret, Fr.ONE);
60
+ return recipient.address;
61
+ }
62
+
63
+ /**
64
+ * Checks if the token contract is deployed and deploys it if necessary.
65
+ * @param wallet - Wallet to deploy the token contract from.
66
+ * @returns The TokenContract instance.
67
+ */
68
+ private async setupToken(wallet: AccountWallet): Promise<TokenContract> {
69
+ const deploy = TokenContract.deploy(wallet, wallet.getAddress(), 'BotToken', 'BOT', 18);
70
+ const deployOpts = { contractAddressSalt: this.config.tokenSalt, universalDeploy: true };
71
+ const address = deploy.getInstance(deployOpts).address;
72
+ if (await this.pxe.isContractPubliclyDeployed(address)) {
73
+ this.log.info(`Token at ${address.toString()} already deployed`);
74
+ return deploy.register();
75
+ } else {
76
+ this.log.info(`Deploying token contract at ${address.toString()}`);
77
+ return deploy.send(deployOpts).deployed();
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Mints private and public tokens for the sender if their balance is below the minimum.
83
+ * @param token - Token contract.
84
+ */
85
+ private async mintTokens(token: TokenContract) {
86
+ const sender = token.wallet.getAddress();
87
+ const { privateBalance, publicBalance } = await getBalances(token, sender);
88
+ const calls: FunctionCall[] = [];
89
+ if (privateBalance < MIN_BALANCE) {
90
+ this.log.info(`Minting private tokens for ${sender.toString()}`);
91
+ calls.push(token.methods.privately_mint_private_note(MINT_BALANCE).request());
92
+ }
93
+ if (publicBalance < MIN_BALANCE) {
94
+ this.log.info(`Minting public tokens for ${sender.toString()}`);
95
+ calls.push(token.methods.mint_public(sender, MINT_BALANCE).request());
96
+ }
97
+ if (calls.length === 0) {
98
+ this.log.info(`Skipping minting as ${sender.toString()} has enough tokens`);
99
+ return;
100
+ }
101
+ await new BatchCall(token.wallet, calls).send().wait();
102
+ }
103
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { Bot } from './bot.js';
2
+ export { BotRunner } from './runner.js';
3
+ export { BotConfig, getBotConfigFromEnv, getBotDefaultConfig } from './config.js';
4
+ export { createBotRunnerRpcServer } from './rpc.js';
package/src/rpc.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { TxHash } from '@aztec/circuit-types';
2
+ import { AztecAddress } from '@aztec/foundation/aztec-address';
3
+ import { EthAddress } from '@aztec/foundation/eth-address';
4
+ import { Fr } from '@aztec/foundation/fields';
5
+ import { JsonRpcServer } from '@aztec/foundation/json-rpc/server';
6
+
7
+ import { type BotRunner } from './runner.js';
8
+
9
+ /**
10
+ * Wraps a bot runner with a JSON RPC HTTP server.
11
+ * @param botRunner - The BotRunner.
12
+ * @returns An JSON-RPC HTTP server
13
+ */
14
+ export function createBotRunnerRpcServer(botRunner: BotRunner) {
15
+ return new JsonRpcServer(botRunner, { AztecAddress, EthAddress, Fr, TxHash }, {}, []);
16
+ }
package/src/runner.ts ADDED
@@ -0,0 +1,102 @@
1
+ import { type PXE, createDebugLogger } from '@aztec/aztec.js';
2
+
3
+ import { Bot } from './bot.js';
4
+ import { type BotConfig } from './config.js';
5
+
6
+ export class BotRunner {
7
+ private log = createDebugLogger('aztec:bot');
8
+ private interval?: NodeJS.Timeout;
9
+ private bot?: Promise<Bot>;
10
+ private pxe?: PXE;
11
+ private running: Set<Promise<void>> = new Set();
12
+
13
+ public constructor(private config: BotConfig, dependencies: { pxe?: PXE } = {}) {
14
+ this.pxe = dependencies.pxe;
15
+ }
16
+
17
+ /** Initializes the bot if needed. Blocks until the bot setup is finished. */
18
+ public async setup() {
19
+ if (!this.bot) {
20
+ this.log.verbose(`Setting up bot`);
21
+ await this.#createBot();
22
+ this.log.info(`Bot set up completed`);
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Initializes the bot if needed and starts sending txs at regular intervals.
28
+ * Blocks until the bot setup is finished.
29
+ */
30
+ public async start() {
31
+ await this.setup();
32
+ if (!this.interval) {
33
+ this.log.info(`Starting bot with interval of ${this.config.txIntervalSeconds}s`);
34
+ this.interval = setInterval(() => this.run(), this.config.txIntervalSeconds * 1000);
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Stops sending txs. Returns once all ongoing txs are finished.
40
+ */
41
+ public async stop() {
42
+ if (this.interval) {
43
+ this.log.verbose(`Stopping bot`);
44
+ clearInterval(this.interval);
45
+ this.interval = undefined;
46
+ }
47
+ if (this.running.size > 0) {
48
+ this.log.verbose(`Waiting for ${this.running.size} running txs to finish`);
49
+ await Promise.all(this.running);
50
+ }
51
+ this.log.info(`Stopped bot`);
52
+ }
53
+
54
+ /** Returns whether the bot is running. */
55
+ public isRunning() {
56
+ return !!this.interval;
57
+ }
58
+
59
+ /**
60
+ * Updates the bot config and recreates the bot. Will stop and restart the bot automatically if it was
61
+ * running when this method was called. Blocks until the new bot is set up.
62
+ */
63
+ public async update(config: BotConfig) {
64
+ this.log.verbose(`Updating bot config`);
65
+ const wasRunning = this.isRunning();
66
+ if (wasRunning) {
67
+ await this.stop();
68
+ }
69
+ this.config = { ...this.config, ...config };
70
+ await this.#createBot();
71
+ this.log.info(`Bot config updated`);
72
+ if (wasRunning) {
73
+ await this.start();
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Triggers a single iteration of the bot. Requires the bot to be initialized.
79
+ * Blocks until the run is finished.
80
+ */
81
+ public async run() {
82
+ if (!this.bot) {
83
+ throw new Error(`Bot is not initialized`);
84
+ }
85
+ this.log.verbose(`Manually triggered bot run`);
86
+ const bot = await this.bot;
87
+ const promise = bot.run();
88
+ this.running.add(promise);
89
+ await promise;
90
+ this.running.delete(promise);
91
+ }
92
+
93
+ /** Returns the current configuration for the bot. */
94
+ public getConfig() {
95
+ return this.config;
96
+ }
97
+
98
+ async #createBot() {
99
+ this.bot = Bot.create(this.config, { pxe: this.pxe });
100
+ await this.bot;
101
+ }
102
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,17 @@
1
+ import { type AztecAddress } from '@aztec/circuits.js';
2
+ import { type TokenContract } from '@aztec/noir-contracts.js/Token';
3
+
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 async function getBalances(
11
+ token: TokenContract,
12
+ who: AztecAddress,
13
+ ): Promise<{ privateBalance: bigint; publicBalance: bigint }> {
14
+ const privateBalance = await token.methods.balance_of_private(who).simulate();
15
+ const publicBalance = await token.methods.balance_of_public(who).simulate();
16
+ return { privateBalance, publicBalance };
17
+ }