@aztec-labs/bot 6.0.0-nightly.20260829

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +3 -0
  2. package/dest/amm_bot.d.ts +33 -0
  3. package/dest/amm_bot.d.ts.map +1 -0
  4. package/dest/amm_bot.js +108 -0
  5. package/dest/base_bot.d.ts +21 -0
  6. package/dest/base_bot.d.ts.map +1 -0
  7. package/dest/base_bot.js +69 -0
  8. package/dest/bot.d.ts +29 -0
  9. package/dest/bot.d.ts.map +1 -0
  10. package/dest/bot.js +60 -0
  11. package/dest/config.d.ts +175 -0
  12. package/dest/config.d.ts.map +1 -0
  13. package/dest/config.js +241 -0
  14. package/dest/cross_chain_bot.d.ts +56 -0
  15. package/dest/cross_chain_bot.d.ts.map +1 -0
  16. package/dest/cross_chain_bot.js +138 -0
  17. package/dest/factory.d.ts +74 -0
  18. package/dest/factory.d.ts.map +1 -0
  19. package/dest/factory.js +532 -0
  20. package/dest/index.d.ts +9 -0
  21. package/dest/index.d.ts.map +1 -0
  22. package/dest/index.js +8 -0
  23. package/dest/interface.d.ts +19 -0
  24. package/dest/interface.d.ts.map +1 -0
  25. package/dest/interface.js +38 -0
  26. package/dest/l1_to_l2_seeding.d.ts +8 -0
  27. package/dest/l1_to_l2_seeding.d.ts.map +1 -0
  28. package/dest/l1_to_l2_seeding.js +63 -0
  29. package/dest/rpc.d.ts +4 -0
  30. package/dest/rpc.d.ts.map +1 -0
  31. package/dest/rpc.js +8 -0
  32. package/dest/runner.d.ts +54 -0
  33. package/dest/runner.d.ts.map +1 -0
  34. package/dest/runner.js +566 -0
  35. package/dest/store/bot_store.d.ts +69 -0
  36. package/dest/store/bot_store.d.ts.map +1 -0
  37. package/dest/store/bot_store.js +138 -0
  38. package/dest/store/index.d.ts +2 -0
  39. package/dest/store/index.d.ts.map +1 -0
  40. package/dest/store/index.js +1 -0
  41. package/dest/utils.d.ts +19 -0
  42. package/dest/utils.d.ts.map +1 -0
  43. package/dest/utils.js +29 -0
  44. package/package.json +95 -0
  45. package/src/amm_bot.ts +132 -0
  46. package/src/base_bot.ts +75 -0
  47. package/src/bot.ts +102 -0
  48. package/src/config.ts +316 -0
  49. package/src/cross_chain_bot.ts +208 -0
  50. package/src/factory.ts +628 -0
  51. package/src/index.ts +14 -0
  52. package/src/interface.ts +31 -0
  53. package/src/l1_to_l2_seeding.ts +79 -0
  54. package/src/rpc.ts +8 -0
  55. package/src/runner.ts +220 -0
  56. package/src/store/bot_store.ts +196 -0
  57. package/src/store/index.ts +1 -0
  58. package/src/utils.ts +38 -0
@@ -0,0 +1,79 @@
1
+ import { InboxAbi } from '@aztec/l1-artifacts';
2
+
3
+ import { generateClaimSecret } from '@aztec-labs/aztec.js/ethereum';
4
+ import type { ExtendedViemWalletClient } from '@aztec-labs/ethereum/types';
5
+ import { compactArray } from '@aztec-labs/foundation/collection';
6
+ import { Fr } from '@aztec-labs/foundation/curves/bn254';
7
+ import { EthAddress } from '@aztec-labs/foundation/eth-address';
8
+ import type { Logger } from '@aztec-labs/foundation/log';
9
+ import type { AztecAddress } from '@aztec-labs/stdlib/aztec-address';
10
+ import { decodeEventLog, getContract } from 'viem';
11
+
12
+ import type { BotStore, PendingL1ToL2Message } from './store/index.js';
13
+
14
+ /** Sends an L1→L2 message via the Inbox contract and stores it. */
15
+ export async function seedL1ToL2Message(
16
+ l1Client: ExtendedViemWalletClient,
17
+ inboxAddress: EthAddress,
18
+ l2Recipient: AztecAddress,
19
+ rollupVersion: bigint,
20
+ store: BotStore,
21
+ log: Logger,
22
+ ): Promise<PendingL1ToL2Message> {
23
+ log.info('Seeding L1→L2 message');
24
+ const [secret, secretHash] = await generateClaimSecret(log);
25
+ const content = Fr.random();
26
+
27
+ const inbox = getContract({
28
+ address: inboxAddress.toString(),
29
+ abi: InboxAbi,
30
+ client: l1Client,
31
+ });
32
+
33
+ const txHash = await inbox.write.sendL2Message(
34
+ [{ actor: l2Recipient.toString(), version: rollupVersion }, content.toString(), secretHash.toString()],
35
+ { gas: 1_000_000n },
36
+ );
37
+ log.info(`L1→L2 message sent in tx ${txHash}`);
38
+
39
+ const txReceipt = await l1Client.waitForTransactionReceipt({ hash: txHash });
40
+ if (txReceipt.status !== 'success') {
41
+ throw new Error(`L1→L2 message tx failed: ${txHash}`);
42
+ }
43
+
44
+ // Extract MessageSent event
45
+ const messageSentLogs = compactArray(
46
+ txReceipt.logs
47
+ .filter(l => l.address.toLowerCase() === inboxAddress.toString().toLowerCase())
48
+ .map(l => {
49
+ try {
50
+ return decodeEventLog({ abi: InboxAbi, eventName: 'MessageSent', data: l.data, topics: l.topics });
51
+ } catch {
52
+ return undefined;
53
+ }
54
+ }),
55
+ );
56
+
57
+ if (messageSentLogs.length !== 1) {
58
+ throw new Error(`Expected 1 MessageSent event, got ${messageSentLogs.length}`);
59
+ }
60
+
61
+ const event = messageSentLogs[0];
62
+
63
+ const msgHash = event.args.hash;
64
+ const globalLeafIndex = event.args.message.index;
65
+
66
+ const msg: PendingL1ToL2Message = {
67
+ content: content.toString(),
68
+ secret: secret.toString(),
69
+ secretHash: secretHash.toString(),
70
+ msgHash,
71
+ sender: l1Client.account!.address,
72
+ globalLeafIndex: globalLeafIndex.toString(),
73
+ timestamp: Date.now(),
74
+ };
75
+
76
+ await store.savePendingL1ToL2Message(msg);
77
+ log.info(`Seeded L1→L2 message msgHash=${msg.msgHash}`);
78
+ return msg;
79
+ }
package/src/rpc.ts ADDED
@@ -0,0 +1,8 @@
1
+ import type { ApiHandler } from '@aztec-labs/foundation/json-rpc/server';
2
+
3
+ import { BotRunnerApiSchema } from './interface.js';
4
+ import type { BotRunner } from './runner.js';
5
+
6
+ export function getBotRunnerApiHandler(botRunner: BotRunner): ApiHandler {
7
+ return [botRunner, BotRunnerApiSchema, botRunner.isHealthy.bind(botRunner)];
8
+ }
package/src/runner.ts ADDED
@@ -0,0 +1,220 @@
1
+ import { createLogger } from '@aztec-labs/aztec.js/log';
2
+ import type { AztecNode } from '@aztec-labs/aztec.js/node';
3
+ import { omit } from '@aztec-labs/foundation/collection';
4
+ import { RunningPromise } from '@aztec-labs/foundation/running-promise';
5
+ import type { BlockTag } from '@aztec-labs/stdlib/block';
6
+ import type { AztecNodeAdmin } from '@aztec-labs/stdlib/interfaces/client';
7
+ import { type TelemetryClient, type Traceable, type Tracer, trackSpan } from '@aztec-labs/telemetry-client';
8
+ import type { EmbeddedWallet } from '@aztec-labs/wallets/embedded';
9
+
10
+ import { AmmBot } from './amm_bot.js';
11
+ import type { BaseBot } from './base_bot.js';
12
+ import { Bot } from './bot.js';
13
+ import type { BotConfig } from './config.js';
14
+ import { CrossChainBot } from './cross_chain_bot.js';
15
+ import type { BotInfo, BotRunnerApi } from './interface.js';
16
+ import { BotStore } from './store/index.js';
17
+
18
+ export class BotRunner implements BotRunnerApi, Traceable {
19
+ private log = createLogger('bot');
20
+ private bot?: Promise<BaseBot>;
21
+ private runningPromise: RunningPromise;
22
+ private consecutiveErrors = 0;
23
+ private healthy = true;
24
+
25
+ public readonly tracer: Tracer;
26
+
27
+ public constructor(
28
+ private config: BotConfig,
29
+ private readonly wallet: EmbeddedWallet,
30
+ private readonly aztecNode: AztecNode,
31
+ private readonly telemetry: TelemetryClient,
32
+ private readonly aztecNodeAdmin: AztecNodeAdmin | undefined,
33
+ private readonly store: BotStore,
34
+ private readonly syncChainTip?: BlockTag,
35
+ ) {
36
+ this.tracer = telemetry.getTracer('Bot');
37
+
38
+ this.runningPromise = new RunningPromise(() => this.#work(), this.log, config.txIntervalSeconds * 1000);
39
+ }
40
+
41
+ /** Initializes the bot if needed. Blocks until the bot setup is finished. */
42
+ public async setup() {
43
+ if (!this.bot) {
44
+ await this.doSetup();
45
+ }
46
+ }
47
+
48
+ @trackSpan('Bot.setup')
49
+ private async doSetup() {
50
+ this.log.verbose(`Setting up bot`);
51
+ await this.#createBot();
52
+ this.log.info(`Bot set up completed`);
53
+ }
54
+
55
+ /**
56
+ * Initializes the bot if needed and starts sending txs at regular intervals.
57
+ * Blocks until the bot setup is finished.
58
+ */
59
+ public async start() {
60
+ await this.setup();
61
+ if (!this.runningPromise.isRunning()) {
62
+ this.log.info(`Starting bot with interval of ${this.config.txIntervalSeconds}s`);
63
+ this.runningPromise.start();
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Stops sending txs. Returns once all ongoing txs are finished.
69
+ */
70
+ public async stop() {
71
+ if (this.runningPromise.isRunning()) {
72
+ this.log.verbose(`Stopping bot`);
73
+ await this.runningPromise.stop();
74
+ }
75
+ await this.store.close();
76
+ this.log.info(`Stopped bot`);
77
+ }
78
+
79
+ public isHealthy() {
80
+ return this.runningPromise.isRunning() && this.healthy;
81
+ }
82
+
83
+ /** Returns whether the bot is running. */
84
+ public isRunning() {
85
+ return this.runningPromise.isRunning();
86
+ }
87
+
88
+ /**
89
+ * Updates the bot config and recreates the bot. Will stop and restart the bot automatically if it was
90
+ * running when this method was called. Blocks until the new bot is set up.
91
+ */
92
+ public async update(config: BotConfig) {
93
+ this.log.verbose(`Updating bot config`);
94
+ const wasRunning = this.isRunning();
95
+ if (wasRunning) {
96
+ await this.stop();
97
+ }
98
+ this.config = { ...this.config, ...config };
99
+ this.runningPromise.setPollingIntervalMS(this.config.txIntervalSeconds * 1000);
100
+ await this.#createBot();
101
+ this.log.info(`Bot config updated`);
102
+ if (wasRunning) {
103
+ await this.start();
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Triggers a single iteration of the bot. Requires the bot to be initialized.
109
+ * Blocks until the run is finished.
110
+ */
111
+ public async run() {
112
+ if (!this.bot) {
113
+ this.log.error(`Trying to run with uninitialized bot`);
114
+ throw new Error(`Bot is not initialized`);
115
+ }
116
+
117
+ let bot;
118
+ try {
119
+ bot = await this.bot;
120
+ } catch (err) {
121
+ this.log.error(`Error awaiting bot set up: ${err}`);
122
+ throw err;
123
+ }
124
+
125
+ try {
126
+ await bot.run();
127
+ this.consecutiveErrors = 0;
128
+ } catch (err) {
129
+ this.consecutiveErrors += 1;
130
+ this.log.error(`Error running bot consecutiveCount=${this.consecutiveErrors}: ${err}`);
131
+ throw err;
132
+ }
133
+ }
134
+
135
+ /** Returns the current configuration for the bot. */
136
+ public getConfig() {
137
+ const redacted = omit(this.config, 'l1Mnemonic', 'l1PrivateKey', 'senderPrivateKey');
138
+ return Promise.resolve(redacted as BotConfig);
139
+ }
140
+
141
+ /** Returns the bot sender address. */
142
+ public async getInfo(): Promise<BotInfo> {
143
+ if (!this.bot) {
144
+ throw new Error(`Bot is not initialized`);
145
+ }
146
+ const botAddress = await this.bot.then(b => b.defaultAccountAddress);
147
+ return { botAddress };
148
+ }
149
+
150
+ async #createBot() {
151
+ try {
152
+ switch (this.config.botMode) {
153
+ case 'crosschain':
154
+ this.bot = CrossChainBot.create(
155
+ this.config,
156
+ this.wallet,
157
+ this.aztecNode,
158
+ this.aztecNodeAdmin,
159
+ this.store,
160
+ this.syncChainTip,
161
+ );
162
+ break;
163
+ case 'amm':
164
+ this.bot = AmmBot.create(
165
+ this.config,
166
+ this.wallet,
167
+ this.aztecNode,
168
+ this.aztecNodeAdmin,
169
+ this.store,
170
+ this.syncChainTip,
171
+ );
172
+ break;
173
+ case 'transfer':
174
+ this.bot = Bot.create(
175
+ this.config,
176
+ this.wallet,
177
+ this.aztecNode,
178
+ this.aztecNodeAdmin,
179
+ this.store,
180
+ this.syncChainTip,
181
+ );
182
+ break;
183
+ default: {
184
+ const _exhaustive: never = this.config.botMode;
185
+ throw new Error(`Unsupported bot mode: [${_exhaustive}]`);
186
+ }
187
+ }
188
+ await this.bot;
189
+ } catch (err) {
190
+ this.log.error(`Error setting up bot: ${err}`);
191
+ throw err;
192
+ }
193
+ }
194
+
195
+ @trackSpan('Bot.work')
196
+ async #work() {
197
+ if (this.config.maxPendingTxs > 0) {
198
+ const pendingTxCount = await this.aztecNode.getPendingTxCount();
199
+ if (pendingTxCount >= this.config.maxPendingTxs) {
200
+ this.log.verbose(`Not sending bot tx since node has ${pendingTxCount} pending txs`);
201
+ return;
202
+ }
203
+ }
204
+
205
+ try {
206
+ await this.run();
207
+ } catch {
208
+ // Already logged in run()
209
+ if (this.config.maxConsecutiveErrors > 0 && this.consecutiveErrors >= this.config.maxConsecutiveErrors) {
210
+ this.log.error(`Too many errors bot is unhealthy`);
211
+ this.healthy = false;
212
+ }
213
+ }
214
+
215
+ if (!this.healthy && this.config.stopWhenUnhealthy) {
216
+ this.log.fatal(`Stopping bot due to errors`);
217
+ process.exit(1); // workaround docker not restarting the container if its unhealthy. We have to exit instead
218
+ }
219
+ }
220
+ }
@@ -0,0 +1,196 @@
1
+ import { AztecAddress } from '@aztec-labs/aztec.js/addresses';
2
+ import type { L2AmountClaim } from '@aztec-labs/aztec.js/ethereum';
3
+ import { Fr } from '@aztec-labs/foundation/curves/bn254';
4
+ import { type Logger, createLogger } from '@aztec-labs/foundation/log';
5
+ import { DateProvider } from '@aztec-labs/foundation/timer';
6
+ import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec-labs/kv-store';
7
+
8
+ export interface BridgeClaimData {
9
+ claim: L2AmountClaim;
10
+ timestamp: number;
11
+ recipient: string;
12
+ }
13
+
14
+ export interface PendingL1ToL2Message {
15
+ /** Random content field sent in the message. */
16
+ content: string;
17
+ /** Secret for consuming the message. */
18
+ secret: string;
19
+ /** Hash of the secret. */
20
+ secretHash: string;
21
+ /** Hash of the L1→L2 message. */
22
+ msgHash: string;
23
+ /** L1 sender address (hex). */
24
+ sender: string;
25
+ /** Global leaf index in the L1→L2 message tree. */
26
+ globalLeafIndex: string;
27
+ /** Timestamp when the message was seeded. */
28
+ timestamp: number;
29
+ }
30
+
31
+ /**
32
+ * Simple data store for the bot to persist L1 bridge claims.
33
+ */
34
+ export class BotStore {
35
+ public static readonly SCHEMA_VERSION = 1;
36
+ private readonly bridgeClaims: AztecAsyncMap<string, string>;
37
+ private readonly pendingL1ToL2: AztecAsyncMap<string, string>;
38
+
39
+ constructor(
40
+ private readonly store: AztecAsyncKVStore,
41
+ private readonly log: Logger = createLogger('bot:store'),
42
+ private readonly dateProvider: DateProvider = new DateProvider(),
43
+ ) {
44
+ this.bridgeClaims = store.openMap<string, string>('bridge_claims');
45
+ this.pendingL1ToL2 = store.openMap<string, string>('pending_l1_to_l2');
46
+ }
47
+
48
+ /**
49
+ * Saves a bridge claim for a recipient.
50
+ */
51
+ public async saveBridgeClaim(recipient: AztecAddress, claim: L2AmountClaim): Promise<void> {
52
+ // Convert Fr fields and BigInts to strings for JSON serialization
53
+ const serializableClaim = {
54
+ claimAmount: claim.claimAmount.toString(),
55
+ claimSecret: claim.claimSecret.toString(),
56
+ claimSecretHash: claim.claimSecretHash.toString(),
57
+ messageHash: claim.messageHash,
58
+ messageLeafIndex: claim.messageLeafIndex.toString(),
59
+ };
60
+
61
+ const data = {
62
+ claim: serializableClaim,
63
+ timestamp: this.dateProvider.now(),
64
+ recipient: recipient.toString(),
65
+ };
66
+
67
+ await this.bridgeClaims.set(recipient.toString(), JSON.stringify(data));
68
+ this.log.info(`Saved bridge claim for ${recipient.toString()}`);
69
+ }
70
+
71
+ /**
72
+ * Gets a bridge claim for a recipient if it exists.
73
+ */
74
+ public async getBridgeClaim(recipient: AztecAddress): Promise<BridgeClaimData | undefined> {
75
+ const data = await this.bridgeClaims.getAsync(recipient.toString());
76
+ if (!data) {
77
+ return undefined;
78
+ }
79
+
80
+ const parsed = JSON.parse(data);
81
+
82
+ // Reconstruct L2AmountClaim from serialized data
83
+ const claim: L2AmountClaim = {
84
+ claimAmount: BigInt(parsed.claim.claimAmount),
85
+ claimSecret: Fr.fromString(parsed.claim.claimSecret),
86
+ claimSecretHash: Fr.fromString(parsed.claim.claimSecretHash),
87
+ messageHash: parsed.claim.messageHash,
88
+ messageLeafIndex: BigInt(parsed.claim.messageLeafIndex),
89
+ };
90
+
91
+ return {
92
+ claim,
93
+ timestamp: parsed.timestamp,
94
+ recipient: parsed.recipient,
95
+ };
96
+ }
97
+
98
+ /**
99
+ * Deletes a bridge claim for a recipient.
100
+ */
101
+ public async deleteBridgeClaim(recipient: AztecAddress): Promise<void> {
102
+ await this.bridgeClaims.delete(recipient.toString());
103
+ this.log.info(`Deleted bridge claim for ${recipient.toString()}`);
104
+ }
105
+
106
+ /**
107
+ * Gets all stored bridge claims.
108
+ */
109
+ public async getAllBridgeClaims(): Promise<BridgeClaimData[]> {
110
+ const claims: BridgeClaimData[] = [];
111
+ const entries = this.bridgeClaims.entriesAsync();
112
+
113
+ for await (const [_, data] of entries) {
114
+ const parsed = JSON.parse(data);
115
+
116
+ // Reconstruct L2AmountClaim from serialized data
117
+ const claim: L2AmountClaim = {
118
+ claimAmount: BigInt(parsed.claim.claimAmount),
119
+ claimSecret: Fr.fromString(parsed.claim.claimSecret),
120
+ claimSecretHash: Fr.fromString(parsed.claim.claimSecretHash),
121
+ messageHash: parsed.claim.messageHash,
122
+ messageLeafIndex: BigInt(parsed.claim.messageLeafIndex),
123
+ };
124
+
125
+ claims.push({
126
+ claim,
127
+ timestamp: parsed.timestamp,
128
+ recipient: parsed.recipient,
129
+ });
130
+ }
131
+
132
+ return claims;
133
+ }
134
+
135
+ /**
136
+ * Cleans up old bridge claims (older than 24 hours).
137
+ */
138
+ public async cleanupOldClaims(maxAgeMs: number = 24 * 60 * 60 * 1000): Promise<number> {
139
+ const now = this.dateProvider.now();
140
+ let cleanedCount = 0;
141
+ const entries = this.bridgeClaims.entriesAsync();
142
+
143
+ for await (const [key, data] of entries) {
144
+ const parsed = JSON.parse(data);
145
+ if (now - parsed.timestamp > maxAgeMs) {
146
+ await this.bridgeClaims.delete(key);
147
+ cleanedCount++;
148
+ this.log.info(`Cleaned up old bridge claim for ${parsed.recipient}`);
149
+ }
150
+ }
151
+
152
+ return cleanedCount;
153
+ }
154
+
155
+ /** Saves a pending L1→L2 message keyed by msgHash. */
156
+ public async savePendingL1ToL2Message(msg: PendingL1ToL2Message): Promise<void> {
157
+ await this.pendingL1ToL2.set(msg.msgHash, JSON.stringify(msg));
158
+ this.log.info(`Saved pending L1→L2 message ${msg.msgHash}`);
159
+ }
160
+
161
+ /** Returns all unconsumed pending L1→L2 messages. */
162
+ public async getUnconsumedL1ToL2Messages(): Promise<PendingL1ToL2Message[]> {
163
+ const messages: PendingL1ToL2Message[] = [];
164
+ for await (const [_, data] of this.pendingL1ToL2.entriesAsync()) {
165
+ messages.push(JSON.parse(data));
166
+ }
167
+ return messages;
168
+ }
169
+
170
+ /** Deletes a consumed L1→L2 message from the store. */
171
+ public async deleteL1ToL2Message(msgHash: string): Promise<void> {
172
+ await this.pendingL1ToL2.delete(msgHash);
173
+ this.log.info(`Deleted consumed L1→L2 message ${msgHash}`);
174
+ }
175
+
176
+ /** Cleans up pending L1→L2 messages older than maxAgeMs. */
177
+ public async cleanupOldPendingMessages(maxAgeMs: number = 24 * 60 * 60 * 1000): Promise<number> {
178
+ const now = this.dateProvider.now();
179
+ let cleanedCount = 0;
180
+ for await (const [key, data] of this.pendingL1ToL2.entriesAsync()) {
181
+ const parsed = JSON.parse(data);
182
+ if (now - parsed.timestamp > maxAgeMs) {
183
+ await this.pendingL1ToL2.delete(key);
184
+ cleanedCount++;
185
+ this.log.info(`Cleaned up old pending L1→L2 message ${key}`);
186
+ }
187
+ }
188
+ return cleanedCount;
189
+ }
190
+
191
+ /** Closes the store. */
192
+ public async close(): Promise<void> {
193
+ await this.store.close();
194
+ this.log.info('Closed bot data store');
195
+ }
196
+ }
@@ -0,0 +1 @@
1
+ export { BotStore, type BridgeClaimData, type PendingL1ToL2Message } from './bot_store.js';
package/src/utils.ts ADDED
@@ -0,0 +1,38 @@
1
+ import { ContractBase } from '@aztec-labs/aztec.js/contracts';
2
+ import type { AMMContract } from '@aztec-labs/noir-contracts.js/AMM';
3
+ import type { PrivateTokenContract } from '@aztec-labs/noir-contracts.js/PrivateToken';
4
+ import type { TokenContract } from '@aztec-labs/noir-contracts.js/Token';
5
+ import type { AztecAddress } from '@aztec-labs/stdlib/aztec-address';
6
+
7
+ /**
8
+ * Gets the private and public balance of the given token for the given address.
9
+ * @param token - Token contract.
10
+ * @param who - Address to get the balance for.
11
+ * @returns - Private and public token balances as bigints.
12
+ */
13
+ export async function getBalances(
14
+ token: TokenContract,
15
+ who: AztecAddress,
16
+ from?: AztecAddress,
17
+ ): Promise<{ privateBalance: bigint; publicBalance: bigint }> {
18
+ const { result: privateBalance } = await token.methods.balance_of_private(who).simulate({ from: from ?? who });
19
+ const { result: publicBalance } = await token.methods.balance_of_public(who).simulate({ from: from ?? who });
20
+ return { privateBalance, publicBalance };
21
+ }
22
+
23
+ export async function getPrivateBalance(
24
+ token: PrivateTokenContract,
25
+ who: AztecAddress,
26
+ from?: AztecAddress,
27
+ ): Promise<bigint> {
28
+ const { result: privateBalance } = await token.methods.get_balance(who).simulate({ from: from ?? who });
29
+ return privateBalance;
30
+ }
31
+
32
+ export function isStandardTokenContract(token: ContractBase): token is TokenContract {
33
+ return 'mint_to_public' in token.methods;
34
+ }
35
+
36
+ export function isAMMContract(contract: ContractBase): contract is AMMContract {
37
+ return 'add_liquidity' in contract.methods;
38
+ }