Skip to content

Commit 7cf6259

Browse files
committed
feat: implement EVM balance tracker and integrate with metrics
1 parent 62cb38c commit 7cf6259

File tree

6 files changed

+218
-51
lines changed

6 files changed

+218
-51
lines changed

apps/price_pusher/grafana-dashboard.sample.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,9 +396,15 @@
396396
"job#C": true,
397397
"price_id": false,
398398
"price_id#B": true,
399-
"price_id#C": true
399+
"price_id#C": true,
400+
"Value #C": false
401+
},
402+
"includeByName": {
403+
"alias 1": true,
404+
"price_id": true,
405+
"Value #B": true,
406+
"Value #C": true
400407
},
401-
"includeByName": {},
402408
"indexByName": {
403409
"Time 1": 4,
404410
"Time 2": 10,
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { SuperWalletClient } from "../evm/super-wallet";
2+
import { BaseBalanceTracker, BaseBalanceTrackerConfig } from "./interface";
3+
4+
/**
5+
* EVM-specific configuration for balance tracker
6+
*/
7+
export interface EvmBalanceTrackerConfig extends BaseBalanceTrackerConfig {
8+
/** EVM wallet client */
9+
client: SuperWalletClient;
10+
/** EVM address with 0x prefix */
11+
address: `0x${string}`;
12+
}
13+
14+
/**
15+
* EVM-specific implementation of the balance tracker
16+
*/
17+
export class EvmBalanceTracker extends BaseBalanceTracker {
18+
private client: SuperWalletClient;
19+
private evmAddress: `0x${string}`;
20+
21+
constructor(config: EvmBalanceTrackerConfig) {
22+
super({
23+
...config,
24+
logger: config.logger.child({ module: "EvmBalanceTracker" }),
25+
});
26+
27+
this.client = config.client;
28+
this.evmAddress = config.address;
29+
}
30+
31+
/**
32+
* EVM-specific implementation of balance update
33+
*/
34+
protected async updateBalance(): Promise<void> {
35+
try {
36+
const balance = await this.client.getBalance({
37+
address: this.evmAddress,
38+
});
39+
40+
this.metrics.updateWalletBalance(this.address, this.network, balance);
41+
this.logger.debug(
42+
`Updated EVM wallet balance: ${this.address} = ${balance.toString()}`,
43+
);
44+
} catch (error) {
45+
this.logger.error(
46+
{ error },
47+
"Error fetching EVM wallet balance for metrics",
48+
);
49+
}
50+
}
51+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Export the interfaces
2+
export * from "./interface";
3+
4+
// Export chain-specific implementations
5+
export * from "./evm";
6+
// export * from "./aptos";
7+
8+
// Factory function to create the appropriate balance tracker based on the chain
9+
import { PricePusherMetrics } from "../metrics";
10+
import { Logger } from "pino";
11+
import { DurationInSeconds } from "../utils";
12+
import { IBalanceTracker } from "./interface";
13+
import { EvmBalanceTracker } from "./evm";
14+
import { SuperWalletClient } from "../evm/super-wallet";
15+
16+
/**
17+
* Parameters for creating an EVM balance tracker
18+
*/
19+
export interface CreateEvmBalanceTrackerParams {
20+
client: SuperWalletClient;
21+
address: `0x${string}`;
22+
network: string;
23+
updateInterval: DurationInSeconds;
24+
metrics: PricePusherMetrics;
25+
logger: Logger;
26+
}
27+
28+
/**
29+
* Factory function to create a balance tracker for EVM chains
30+
*/
31+
export function createEvmBalanceTracker(
32+
params: CreateEvmBalanceTrackerParams,
33+
): IBalanceTracker {
34+
return new EvmBalanceTracker({
35+
client: params.client,
36+
address: params.address,
37+
network: params.network,
38+
updateInterval: params.updateInterval,
39+
metrics: params.metrics,
40+
logger: params.logger,
41+
});
42+
}
43+
44+
// Additional factory functions for other chains would follow the same pattern:
45+
// export function createSuiBalanceTracker(params: CreateSuiBalanceTrackerParams): IBalanceTracker { ... }
46+
// export function createSolanaBalanceTracker(params: CreateSolanaBalanceTrackerParams): IBalanceTracker { ... }
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { Logger } from "pino";
2+
import { PricePusherMetrics } from "../metrics";
3+
import { DurationInSeconds } from "../utils";
4+
5+
/**
6+
* Common configuration properties for all balance trackers
7+
*/
8+
export interface BaseBalanceTrackerConfig {
9+
/** Address of the wallet to track */
10+
address: string;
11+
/** Name/ID of the network/chain */
12+
network: string;
13+
/** How often to update the balance */
14+
updateInterval: DurationInSeconds;
15+
/** Metrics instance to report balance updates */
16+
metrics: PricePusherMetrics;
17+
/** Logger instance */
18+
logger: Logger;
19+
}
20+
21+
/**
22+
* Interface for all balance trackers to implement
23+
* Each chain will have its own implementation of this interface
24+
*/
25+
export interface IBalanceTracker {
26+
/**
27+
* Start tracking the wallet balance
28+
*/
29+
start(): Promise<void>;
30+
31+
/**
32+
* Stop tracking the wallet balance
33+
*/
34+
stop(): void;
35+
}
36+
37+
/**
38+
* Abstract base class that implements common functionality for all balance trackers
39+
*/
40+
export abstract class BaseBalanceTracker implements IBalanceTracker {
41+
protected address: string;
42+
protected network: string;
43+
protected updateInterval: DurationInSeconds;
44+
protected metrics: PricePusherMetrics;
45+
protected logger: Logger;
46+
protected isRunning: boolean = false;
47+
48+
constructor(config: BaseBalanceTrackerConfig) {
49+
this.address = config.address;
50+
this.network = config.network;
51+
this.updateInterval = config.updateInterval;
52+
this.metrics = config.metrics;
53+
this.logger = config.logger;
54+
}
55+
56+
public async start(): Promise<void> {
57+
if (this.isRunning) {
58+
return;
59+
}
60+
61+
this.isRunning = true;
62+
63+
// Initial balance update
64+
await this.updateBalance();
65+
66+
// Start the update loop
67+
this.startUpdateLoop();
68+
}
69+
70+
private async startUpdateLoop(): Promise<void> {
71+
// We're using dynamic import to avoid circular dependencies
72+
const { sleep } = await import("../utils");
73+
74+
// Run in a loop to regularly update the balance
75+
for (;;) {
76+
// Wait first, since we already did the initial update in start()
77+
await sleep(this.updateInterval * 1000);
78+
79+
// Only continue if we're still running
80+
if (!this.isRunning) {
81+
break;
82+
}
83+
84+
await this.updateBalance();
85+
}
86+
}
87+
88+
/**
89+
* Chain-specific balance update implementation
90+
* Each chain will implement this method differently
91+
*/
92+
protected abstract updateBalance(): Promise<void>;
93+
94+
public stop(): void {
95+
this.isRunning = false;
96+
}
97+
}

apps/price_pusher/src/controller.ts

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,10 @@ import { IPriceListener, IPricePusher } from "./interface";
44
import { PriceConfig, shouldUpdate, UpdateCondition } from "./price-config";
55
import { Logger } from "pino";
66
import { PricePusherMetrics } from "./metrics";
7-
import { SuperWalletClient } from "./evm/super-wallet";
8-
9-
// Define the wallet balance info interface
10-
interface WalletBalanceInfo {
11-
client: SuperWalletClient;
12-
address: `0x${string}`;
13-
network: string;
14-
updateInterval: DurationInSeconds;
15-
}
167

178
export class Controller {
189
private pushingFrequency: DurationInSeconds;
1910
private metrics?: PricePusherMetrics;
20-
private walletBalanceInfo?: WalletBalanceInfo;
2111

2212
constructor(
2313
private priceConfigs: PriceConfig[],
@@ -28,46 +18,20 @@ export class Controller {
2818
config: {
2919
pushingFrequency: DurationInSeconds;
3020
metrics?: PricePusherMetrics;
31-
walletBalanceInfo?: WalletBalanceInfo;
3221
},
3322
) {
3423
this.pushingFrequency = config.pushingFrequency;
3524
this.metrics = config.metrics;
36-
this.walletBalanceInfo = config.walletBalanceInfo;
3725

3826
// Set the number of price feeds if metrics are enabled
3927
this.metrics?.setPriceFeedsTotal(this.priceConfigs.length);
4028
}
4129

42-
// Get wallet balance and update metrics
43-
private async updateWalletBalance(): Promise<void> {
44-
if (!this.metrics || !this.walletBalanceInfo) return;
45-
46-
try {
47-
const { client, address, network } = this.walletBalanceInfo;
48-
const balance = await client.getBalance({
49-
address: address,
50-
});
51-
52-
this.metrics.updateWalletBalance(address, network, balance);
53-
this.logger.debug(
54-
`Updated wallet balance: ${address} = ${balance.toString()}`,
55-
);
56-
} catch (error) {
57-
this.logger.error({ error }, "Error fetching wallet balance for metrics");
58-
}
59-
}
60-
6130
async start() {
6231
// start the listeners
6332
await this.sourcePriceListener.start();
6433
await this.targetPriceListener.start();
6534

66-
// Update wallet balance initially if metrics are enabled
67-
if (this.metrics && this.walletBalanceInfo) {
68-
await this.updateWalletBalance();
69-
}
70-
7135
// wait for the listeners to get updated. There could be a restart
7236
// before this run and we need to respect the cooldown duration as
7337
// their might be a message sent before.
@@ -80,11 +44,6 @@ export class Controller {
8044
const pricesToPush: PriceConfig[] = [];
8145
const pubTimesToPush: UnixTimestamp[] = [];
8246

83-
// Update wallet balance if metrics are enabled
84-
if (this.metrics && this.walletBalanceInfo) {
85-
await this.updateWalletBalance();
86-
}
87-
8847
for (const priceConfig of this.priceConfigs) {
8948
const priceId = priceConfig.id;
9049
const alias = priceConfig.alias;

apps/price_pusher/src/evm/command.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { createClient } from "./super-wallet";
1212
import { createPythContract } from "./pyth-contract";
1313
import { isWsEndpoint, filterInvalidPriceItems } from "../utils";
1414
import { PricePusherMetrics } from "../metrics";
15+
import { createEvmBalanceTracker } from "../balance-tracker";
1516

1617
export default {
1718
command: "evm",
@@ -199,17 +200,24 @@ export default {
199200
{
200201
pushingFrequency,
201202
metrics,
202-
walletBalanceInfo: metrics
203-
? {
204-
client,
205-
address: client.account.address,
206-
network: await client.getChainId().then((id) => id.toString()),
207-
updateInterval: pushingFrequency,
208-
}
209-
: undefined,
210203
},
211204
);
212205

206+
// Create and start the balance tracker if metrics are enabled
207+
if (metrics) {
208+
const balanceTracker = createEvmBalanceTracker({
209+
client,
210+
address: client.account.address,
211+
network: await client.getChainId().then((id) => id.toString()),
212+
updateInterval: pushingFrequency,
213+
metrics,
214+
logger,
215+
});
216+
217+
// Start the balance tracker
218+
await balanceTracker.start();
219+
}
220+
213221
await controller.start();
214222
},
215223
};

0 commit comments

Comments
 (0)