Skip to content
1 change: 1 addition & 0 deletions .github/workflows/sepolia-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ env:
TF_VAR_L1_CHAIN_ID: 11155111
TF_VAR_ETHEREUM_HOST: https://sepolia.infura.io/v3/${{ secrets.SEPOLIA_API_KEY }}
TF_VAR_PROVING_ENABLED: false
TF_VAR_BOT_TOKEN_CONTRACT: EasyPrivateTokenContract
TF_VAR_API_KEY: ${{ secrets.SEPOLIANET_API_KEY }}
# Node / Sequencer
TF_VAR_BOOTSTRAP_NODES: ""
Expand Down
1 change: 1 addition & 0 deletions yarn-project/aztec-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
},
"scripts": {
"start": "node --no-warnings ./dest/bin",
"start:debug": "node --no-warnings --inspect ./dest/bin",
"build": "yarn clean && tsc -b",
"build:dev": "tsc -b --watch",
"clean": "rm -rf ./dest .tsbuildinfo",
Expand Down
1 change: 1 addition & 0 deletions yarn-project/aztec.js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export {
type ContractStorageLayout,
DefaultWaitOpts,
DeployMethod,
DeployOptions,
DeploySentTx,
type SendMethodOptions,
SentTx,
Expand Down
3 changes: 2 additions & 1 deletion yarn-project/aztec/terraform/bot/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@ resource "aws_ecs_task_definition" "aztec-bot" {
{ name = "BOT_SKIP_PUBLIC_SIMULATION", value = tostring(var.BOT_SKIP_PUBLIC_SIMULATION) },
{ name = "BOT_L2_GAS_LIMIT", value = var.BOT_L2_GAS_LIMIT },
{ name = "BOT_DA_GAS_LIMIT", value = var.BOT_DA_GAS_LIMIT },
{ name = "LOG_JSON", value = "1" }
{ name = "LOG_JSON", value = "1" },
{ name = "BOT_TOKEN_CONTRACT", value = var.BOT_TOKEN_CONTRACT }
]
logConfiguration = {
logDriver = "awslogs"
Expand Down
4 changes: 4 additions & 0 deletions yarn-project/aztec/terraform/bot/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,8 @@ variable "BOT_DA_GAS_LIMIT" {
type = string
}

variable "BOT_TOKEN_CONTRACT" {
type = string
default = "TokenContract"
}

48 changes: 35 additions & 13 deletions yarn-project/bot/src/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import {
import { type AztecNode, type FunctionCall, type PXE } from '@aztec/circuit-types';
import { Gas, GasSettings } from '@aztec/circuits.js';
import { times } from '@aztec/foundation/collection';
import { type TokenContract } from '@aztec/noir-contracts.js';
import { type EasyPrivateTokenContract, type TokenContract } from '@aztec/noir-contracts.js';

import { type BotConfig } from './config.js';
import { BotFactory } from './factory.js';
import { getBalances } from './utils.js';
import { getBalances, getPrivateBalance, isStandardTokenContract } from './utils.js';

const TRANSFER_AMOUNT = 1;

Expand All @@ -23,7 +23,7 @@ export class Bot {

protected constructor(
public readonly wallet: Wallet,
public readonly token: TokenContract,
public readonly token: TokenContract | EasyPrivateTokenContract,
public readonly recipient: AztecAddress,
public config: BotConfig,
) {}
Expand All @@ -50,12 +50,21 @@ export class Bot {
logCtx,
);

const calls: FunctionCall[] = [
...times(privateTransfersPerTx, () => token.methods.transfer(recipient, TRANSFER_AMOUNT).request()),
...times(publicTransfersPerTx, () =>
token.methods.transfer_public(sender, recipient, TRANSFER_AMOUNT, 0).request(),
),
];
const calls: FunctionCall[] = [];
if (isStandardTokenContract(token)) {
calls.push(...times(privateTransfersPerTx, () => token.methods.transfer(recipient, TRANSFER_AMOUNT).request()));
calls.push(
...times(publicTransfersPerTx, () =>
token.methods.transfer_public(sender, recipient, TRANSFER_AMOUNT, 0).request(),
),
);
} else {
calls.push(
...times(privateTransfersPerTx, () =>
token.methods.transfer(TRANSFER_AMOUNT, sender, recipient, sender).request(),
),
);
}

const opts = this.getSendMethodOpts();
const batch = new BatchCall(wallet, calls);
Expand Down Expand Up @@ -88,10 +97,23 @@ export class Bot {
}

public async getBalances() {
return {
sender: await getBalances(this.token, this.wallet.getAddress()),
recipient: await getBalances(this.token, this.recipient),
};
if (isStandardTokenContract(this.token)) {
return {
sender: await getBalances(this.token, this.wallet.getAddress()),
recipient: await getBalances(this.token, this.recipient),
};
} else {
return {
sender: {
privateBalance: await getPrivateBalance(this.token, this.wallet.getAddress()),
publicBalance: 0n,
},
recipient: {
privateBalance: await getPrivateBalance(this.token, this.recipient),
publicBalance: 0n,
},
};
}
}

private getSendMethodOpts(): SendMethodOptions {
Expand Down
22 changes: 22 additions & 0 deletions yarn-project/bot/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ import {
const botFollowChain = ['NONE', 'PENDING', 'PROVEN'] as const;
type BotFollowChain = (typeof botFollowChain)[number];

export enum SupportedTokenContracts {
TokenContract = 'TokenContract',
EasyPrivateTokenContract = 'EasyPrivateTokenContract',
}

export type BotConfig = {
/** The URL to the Aztec node to check for tx pool status. */
nodeUrl: string | undefined;
Expand Down Expand Up @@ -46,6 +51,8 @@ export type BotConfig = {
l2GasLimit: number | undefined;
/** DA gas limit for the tx (empty to have the bot trigger an estimate gas). */
daGasLimit: number | undefined;
/** Token contract to use */
contract: SupportedTokenContracts;
};

export const botConfigMappings: ConfigMappingsType<BotConfig> = {
Expand Down Expand Up @@ -142,6 +149,21 @@ export const botConfigMappings: ConfigMappingsType<BotConfig> = {
description: 'DA gas limit for the tx (empty to have the bot trigger an estimate gas).',
...optionalNumberConfigHelper(),
},
contract: {
env: 'BOT_TOKEN_CONTRACT',
description: 'Token contract to use',
defaultValue: SupportedTokenContracts.TokenContract,
parseEnv(val) {
if (!Object.values(SupportedTokenContracts).includes(val as any)) {
throw new Error(
`Invalid value for BOT_TOKEN_CONTRACT: ${val}. Valid values: ${Object.values(SupportedTokenContracts).join(
', ',
)}`,
);
}
return val as SupportedTokenContracts;
},
},
};

export function getBotConfigFromEnv(): BotConfig {
Expand Down
54 changes: 44 additions & 10 deletions yarn-project/bot/src/factory.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import { getSchnorrAccount } from '@aztec/accounts/schnorr';
import { type AccountWallet, BatchCall, createDebugLogger, createPXEClient } from '@aztec/aztec.js';
import {
type AccountWallet,
BatchCall,
type DeployMethod,
type DeployOptions,
createDebugLogger,
createPXEClient,
} from '@aztec/aztec.js';
import { type AztecNode, type FunctionCall, type PXE } from '@aztec/circuit-types';
import { Fr, deriveSigningKey } from '@aztec/circuits.js';
import { EasyPrivateTokenContract } from '@aztec/noir-contracts.js';
import { TokenContract } from '@aztec/noir-contracts.js/Token';

import { type BotConfig } from './config.js';
import { getBalances } from './utils.js';
import { type BotConfig, SupportedTokenContracts } from './config.js';
import { getBalances, getPrivateBalance, isStandardTokenContract } from './utils.js';

const MINT_BALANCE = 1e12;
const MIN_BALANCE = 1e3;
Expand Down Expand Up @@ -86,9 +94,21 @@ export class BotFactory {
* @param wallet - Wallet to deploy the token contract from.
* @returns The TokenContract instance.
*/
private async setupToken(wallet: AccountWallet): Promise<TokenContract> {
const deploy = TokenContract.deploy(wallet, wallet.getAddress(), 'BotToken', 'BOT', 18);
const deployOpts = { contractAddressSalt: this.config.tokenSalt, universalDeploy: true };
private async setupToken(wallet: AccountWallet): Promise<TokenContract | EasyPrivateTokenContract> {
let deploy: DeployMethod<TokenContract | EasyPrivateTokenContract>;
const deployOpts: DeployOptions = { contractAddressSalt: this.config.tokenSalt, universalDeploy: true };
if (this.config.contract === SupportedTokenContracts.TokenContract) {
deploy = TokenContract.deploy(wallet, wallet.getAddress(), 'BotToken', 'BOT', 18);
} else if (this.config.contract === SupportedTokenContracts.EasyPrivateTokenContract) {
deploy = EasyPrivateTokenContract.deploy(wallet, MINT_BALANCE, wallet.getAddress(), wallet.getAddress());
deployOpts.skipPublicDeployment = true;
deployOpts.skipClassRegistration = true;
deployOpts.skipInitialization = false;
deployOpts.skipPublicSimulation = true;
} else {
throw new Error(`Unsupported token contract type: ${this.config.contract}`);
}

const address = deploy.getInstance(deployOpts).address;
if (await this.pxe.isContractPubliclyDeployed(address)) {
this.log.info(`Token at ${address.toString()} already deployed`);
Expand All @@ -111,15 +131,29 @@ export class BotFactory {
* Mints private and public tokens for the sender if their balance is below the minimum.
* @param token - Token contract.
*/
private async mintTokens(token: TokenContract) {
private async mintTokens(token: TokenContract | EasyPrivateTokenContract) {
const sender = token.wallet.getAddress();
const { privateBalance, publicBalance } = await getBalances(token, sender);
const isStandardToken = isStandardTokenContract(token);
let privateBalance = 0n;
let publicBalance = 0n;

if (isStandardToken) {
({ privateBalance, publicBalance } = await getBalances(token, sender));
} else {
privateBalance = await getPrivateBalance(token, sender);
}

const calls: FunctionCall[] = [];
if (privateBalance < MIN_BALANCE) {
this.log.info(`Minting private tokens for ${sender.toString()}`);
calls.push(token.methods.privately_mint_private_note(MINT_BALANCE).request());

calls.push(
isStandardToken
? token.methods.privately_mint_private_note(MINT_BALANCE).request()
: token.methods.mint(MINT_BALANCE, sender, sender).request(),
);
}
if (publicBalance < MIN_BALANCE) {
if (isStandardToken && publicBalance < MIN_BALANCE) {
this.log.info(`Minting public tokens for ${sender.toString()}`);
calls.push(token.methods.mint_public(sender, MINT_BALANCE).request());
}
Expand Down
8 changes: 7 additions & 1 deletion yarn-project/bot/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
export { Bot } from './bot.js';
export { BotRunner } from './runner.js';
export { BotConfig, getBotConfigFromEnv, getBotDefaultConfig, botConfigMappings } from './config.js';
export {
BotConfig,
getBotConfigFromEnv,
getBotDefaultConfig,
botConfigMappings,
SupportedTokenContracts,
} from './config.js';
export { createBotRunnerRpcServer } from './rpc.js';
10 changes: 10 additions & 0 deletions yarn-project/bot/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { type AztecAddress } from '@aztec/circuits.js';
import { type EasyPrivateTokenContract } from '@aztec/noir-contracts.js';
import { type TokenContract } from '@aztec/noir-contracts.js/Token';

/**
Expand All @@ -15,3 +16,12 @@ export async function getBalances(
const publicBalance = await token.methods.balance_of_public(who).simulate();
return { privateBalance, publicBalance };
}

export async function getPrivateBalance(token: EasyPrivateTokenContract, who: AztecAddress): Promise<bigint> {
const privateBalance = await token.methods.get_balance(who).simulate();
return privateBalance;
}

export function isStandardTokenContract(token: TokenContract | EasyPrivateTokenContract): token is TokenContract {
return 'mint_public' in token.methods;
}
18 changes: 17 additions & 1 deletion yarn-project/end-to-end/src/e2e_bot.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Fr, type PXE } from '@aztec/aztec.js';
import { Bot, type BotConfig } from '@aztec/bot';
import { SupportedTokenContracts, getBotDefaultConfig } from '@aztec/bot';

import { getBotDefaultConfig } from '../../bot/src/config.js';
import { setup } from './fixtures/utils.js';

describe('e2e_bot', () => {
Expand Down Expand Up @@ -49,4 +49,20 @@ describe('e2e_bot', () => {
expect(bot2.token.address.toString()).toEqual(token.address.toString());
expect(bot2.recipient.toString()).toEqual(recipient.toString());
});

it('sends token from the bot using EasyPrivateToken', async () => {
const easyBot = await Bot.create(
{
...config,
contract: SupportedTokenContracts.EasyPrivateTokenContract,
},
{ pxe },
);
const { recipient: recipientBefore } = await easyBot.getBalances();

await easyBot.run();
const { recipient: recipientAfter } = await easyBot.getBalances();
expect(recipientAfter.privateBalance - recipientBefore.privateBalance).toEqual(1n);
expect(recipientAfter.publicBalance - recipientBefore.publicBalance).toEqual(0n);
});
});
1 change: 1 addition & 0 deletions yarn-project/foundation/src/config/env_var.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export type EnvVar =
| 'PXE_PROVER_ENABLED'
| 'BOT_FOLLOW_CHAIN'
| 'BOT_FLUSH_SETUP_TRANSACTIONS'
| 'BOT_TOKEN_CONTRACT'
| 'VALIDATOR_PRIVATE_KEY'
| 'VALIDATOR_DISABLED'
| 'VALIDATOR_ATTESTATIONS_WAIT_TIMEOUT_MS'
Expand Down
11 changes: 2 additions & 9 deletions yarn-project/p2p/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
type ConfigMappingsType,
booleanConfigHelper,
getConfigFromMappings,
getDefaultConfig,
numberConfigHelper,
pickConfigMappings,
} from '@aztec/foundation/config';
Expand Down Expand Up @@ -306,15 +307,7 @@ export function getP2PConfigEnvVars(): P2PConfig {
}

export function getP2PDefaultConfig(): P2PConfig {
const result: Partial<P2PConfig> = {};

for (const [key, mapping] of Object.entries(p2pConfigMappings)) {
if (mapping.defaultValue !== undefined) {
result[key as keyof P2PConfig] = mapping.defaultValue;
}
}

return result as P2PConfig;
return getDefaultConfig<P2PConfig>(p2pConfigMappings);
}

/**
Expand Down