Skip to content

Commit

Permalink
add top up functionality to balance monitor (#626)
Browse files Browse the repository at this point in the history
* add try fill functionality to balance monitor

* changeset add

* fix num comp by using bigint not ethers big number

* dry up

* fix monitor condition
  • Loading branch information
luketchang authored Nov 20, 2023
1 parent 4631ea2 commit 2a0c971
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 20 deletions.
5 changes: 5 additions & 0 deletions .changeset/happy-kangaroos-remain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nocturne-xyz/balance-monitor": minor
---

add ETH top up functionality to monitor
3 changes: 2 additions & 1 deletion actors/balance-monitor/.env.example
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
RPC_URL=
RPC_URL=
TX_SIGNER_KEY=
8 changes: 7 additions & 1 deletion actors/balance-monitor/src/cli/commands/run/monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,17 @@ export const runMonitor = new Command("monitor")
}
const provider = new ethers.providers.JsonRpcProvider(rpcUrl);

const txSignerKey = process.env.TX_SIGNER_KEY;
if (!txSignerKey) {
throw new Error("missing TX_SIGNER_KEY");
}
const wallet = new ethers.Wallet(txSignerKey, provider);

const config = loadNocturneConfig(configName);

const balanceMonitor = new BalanceMonitor(
config,
provider,
wallet,
{
bundler: bundlerAddress,
screener: screenerAddress,
Expand Down
82 changes: 64 additions & 18 deletions actors/balance-monitor/src/monitor.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { NocturneConfig } from "@nocturne-xyz/config";
import { Address } from "@nocturne-xyz/core";
import {
ActorHandle,
makeCreateObservableGaugeFn,
Expand All @@ -9,16 +8,16 @@ import * as ot from "@opentelemetry/api";
import * as ethers from "ethers";
import { Logger } from "winston";
import ERC20_ABI from "./abis/ERC20.json";
import {
ACTOR_NAME,
BALANCE_THRESHOLDS,
ActorAddresses,
BalanceThresholdInfo,
ActorToCheck,
} from "./types";

const ACTOR_NAME = "balance-monitor";
const COMPONENT_NAME = "monitor";

interface ActorAddresses {
bundler: Address;
updater: Address;
screener: Address;
}

interface BalanceMonitorMetrics {
bundlerEthBalanceGauge: ot.ObservableGauge;
bundlerGasTokenBalanceGauge: ot.ObservableGauge;
Expand All @@ -27,27 +26,33 @@ interface BalanceMonitorMetrics {
}

export class BalanceMonitor {
private provider: ethers.providers.JsonRpcProvider;
private wallet: ethers.Wallet;
private actorAddresses: ActorAddresses;
private balanceThresholdInfo: Map<ActorToCheck, BalanceThresholdInfo>;
private gasToken: ethers.Contract;
private logger: Logger;
private closed = false;

constructor(
config: NocturneConfig,
provider: ethers.providers.JsonRpcProvider,
wallet: ethers.Wallet,
actorAddresses: ActorAddresses,
gasTokenTicker: string,
logger: Logger
) {
this.wallet = wallet;
this.logger = logger;
this.provider = provider;
this.actorAddresses = actorAddresses;

this.balanceThresholdInfo = BALANCE_THRESHOLDS(
this.wallet.address,
actorAddresses
);

const gasTokenAddress = config.erc20s.get(gasTokenTicker)!.address;

this.logger.info(`gas token address ${gasTokenAddress}`);
this.gasToken = new ethers.Contract(gasTokenAddress, ERC20_ABI, provider);
this.gasToken = new ethers.Contract(gasTokenAddress, ERC20_ABI, wallet);
}

private registerMetrics(): BalanceMonitorMetrics {
Expand Down Expand Up @@ -83,7 +88,7 @@ export class BalanceMonitor {

bundlerEthBalanceGauge.addCallback(async (observableResult) => {
try {
const balance = await this.provider.getBalance(
const balance = await this.wallet.provider.getBalance(
this.actorAddresses.bundler
);
const balanceEther = parseFloat(
Expand Down Expand Up @@ -115,7 +120,7 @@ export class BalanceMonitor {

updaterEthBalanceGauge.addCallback(async (observableResult) => {
try {
const balance = await this.provider.getBalance(
const balance = await this.wallet.provider.getBalance(
this.actorAddresses.updater
);
const balanceEther = parseFloat(
Expand All @@ -130,7 +135,7 @@ export class BalanceMonitor {

screenerEthBalanceGauge.addCallback(async (observableResult) => {
try {
const balance = await this.provider.getBalance(
const balance = await this.wallet.provider.getBalance(
this.actorAddresses.screener
);
const balanceEther = parseFloat(
Expand All @@ -151,24 +156,65 @@ export class BalanceMonitor {
};
}

private async tryFillBalances(): Promise<void> {
try {
for (const [actor, info] of this.balanceThresholdInfo.entries()) {
const ethBalance = (
await this.wallet.provider.getBalance(info.address)
).toBigInt();

this.logger.info(`current ${actor} ETH balance: ${ethBalance}`);
if (ethBalance < info.minBalance) {
if (actor === "BalanceMonitor") {
// Log errors so alerts are triggered and team can top up balance monitor wallet
if (ethBalance < info.minBalance) {
this.logger.error(
`need to top up balance monitor! current balance: ${ethBalance}. min balance: ${info.minBalance}`
);
}
} else {
// Top up actor if not balance monitor
const diff = info.targetBalance - ethBalance;

this.logger.info(`topping up ${actor} balance. amount: ${diff}`);
const tx = await this.wallet.sendTransaction({
to: info.address,
value: diff,
});
await tx.wait(1);
}
}
}
} catch (error) {
this.logger.error("error filling balances", { error });
}
}

public start(): ActorHandle {
this.logger.info(
"Balance Monitor started. Piping balance metrics every 60 seconds."
);
this.registerMetrics();

const promise = new Promise<void>((resolve) => {
const checkBalanceAndReport = async () => {
const poll = async () => {
this.logger.info("polling...");

if (this.closed) {
this.logger.info("Balance Monitor stopping...");
resolve();
return;
}

setTimeout(checkBalanceAndReport, 60_000);
// try to top up balances
await this.tryFillBalances();

// balance monitor metrics piping is implicit, automatically executed via register metrics
// callbacks
setTimeout(poll, 60_000);
};

void checkBalanceAndReport();
void poll();
});

return {
Expand Down
61 changes: 61 additions & 0 deletions actors/balance-monitor/src/types.ts
Original file line number Diff line number Diff line change
@@ -1 +1,62 @@
import { Address } from "@nocturne-xyz/core";
import { ethers } from "ethers";

export const ACTOR_NAME = "balance-monitor";

export type ActorToCheck =
| "BalanceMonitor"
| "Bundler"
| "SubtreeUpdater"
| "DepositScreener";

export interface ActorAddresses {
bundler: Address;
updater: Address;
screener: Address;
}

export interface BalanceThresholdInfo {
address: Address;
minBalance: bigint;
targetBalance: bigint;
}

export const BALANCE_THRESHOLDS = (
balanceMonitorAddress: Address,
actorAddresses: ActorAddresses
): Map<ActorToCheck, BalanceThresholdInfo> => {
return new Map<ActorToCheck, BalanceThresholdInfo>([
[
"BalanceMonitor",
{
address: balanceMonitorAddress,
minBalance: ethers.utils.parseEther("1.0").toBigInt(),
targetBalance: ethers.utils.parseEther("2.0").toBigInt(),
},
],
[
"Bundler",
{
address: actorAddresses.bundler,
minBalance: ethers.utils.parseEther("0.4").toBigInt(),
targetBalance: ethers.utils.parseEther("0.8").toBigInt(),
},
],
[
"SubtreeUpdater",
{
address: actorAddresses.updater,
minBalance: ethers.utils.parseEther("0.2").toBigInt(),
targetBalance: ethers.utils.parseEther("0.8").toBigInt(),
},
],
[
"DepositScreener",
{
address: actorAddresses.screener,
minBalance: ethers.utils.parseEther("0.4").toBigInt(),
targetBalance: ethers.utils.parseEther("0.5").toBigInt(),
},
],
]);
};

0 comments on commit 2a0c971

Please sign in to comment.