Skip to content

Commit

Permalink
Minor fixes and cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
acenolaza committed Jun 14, 2023
1 parent c253bad commit 8392359
Show file tree
Hide file tree
Showing 8 changed files with 41 additions and 51 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Wallet Watcher

The wallet watcher loads addresses defined in wallets.json and checks that their balances are not below a defined
threshold. If they are not then an alert will be sent to OpsGenie.
threshold. If they are then an alert will be sent to OpsGenie.

## Deployment

Expand Down Expand Up @@ -54,8 +54,8 @@ Be sure to watch the logs to make sure the applications are behaving as you expe
- `<chainId>[n].apiName` (optional): The name of the API provider.
- `<chainId>[n].providerXpub`: The extended public key of the sponsor address.
- `<chainId>[n].sponsor`: The sponsor address to derive the destination wallet.
- `<chainId>[n].lowBalance.value`: The wallet balance value below which an alert is triggered.
- `<chainId>[n].lowBalance.value`: The value used to send an alert if wallet balance is below it.
- `<chainId>[n].lowBalance.unit`: The token units used to parse the `lowBalance.value` for balance check (i.e. ether,
wei, etc).
- `<chainId>[n].lowBalance.criticalPercentage`: The percentage below the `lowBalance.value` used to trigger a critical
alert.
- `<chainId>[n].lowBalance.criticalValue`: The value used to send a _critical_ alert if wallet balance is below it. This
value must be below the `lowBalance.value` value.
2 changes: 1 addition & 1 deletion config/wallets.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"apiName": "FunderDepository contract",
"walletType": "API3",
"address": "0xE889956fA885F42B0C294507d0e67a3168BE188b",
"lowThreshold": { "value": 0.3, "unit": "ether", "criticalPercentage": 33 }
"lowThreshold": { "value": 0.3, "unit": "ether", "criticalValue": 0.1 }
}
],
"5": [
Expand Down
4 changes: 2 additions & 2 deletions src/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ describe('wallets.json', () => {
apiName: 'api3',
walletType: 'API3',
address: '0x9fEe9F24ab79adacbB51af82fb82CFb9D818c6d9',
lowThreshold: { value: '0.2', unit: 'satoshi', criticalPercentage: '50' },
lowThreshold: { value: '0.2', unit: 'satoshi', criticalValue: '0.1' },
},
],
};
Expand Down Expand Up @@ -286,7 +286,7 @@ describe('wallets.json', () => {
code: 'invalid_type',
expected: 'number',
received: 'string',
path: ['1', 0, 'lowThreshold', 'criticalPercentage'],
path: ['1', 0, 'lowThreshold', 'criticalValue'],
message: 'Expected number, received string',
},
])}`
Expand Down
17 changes: 12 additions & 5 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,18 @@ export const namedUnits = z.union([

const baseWalletSchema = z.object({
apiName: z.string().optional(),
lowThreshold: z.object({
value: z.number().positive(),
unit: namedUnits,
criticalPercentage: z.number().positive().lt(100).optional(),
}),
lowThreshold: z
.object({
value: z.number().positive(),
unit: namedUnits,
criticalValue: z.number().positive().optional(),
})
.refine(
({ value, criticalValue }) => {
return criticalValue === undefined || criticalValue < value;
},
{ message: 'Critical value must be lower than value' }
),
});

const providerWalletSchema = baseWalletSchema.extend({
Expand Down
28 changes: 6 additions & 22 deletions src/wallet-watcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,8 @@ describe('walletWatcher', () => {
const opsGenieAliasSuffix = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(`${addressToBeFunded}${chainId}`));

expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(1, `get-balance-error-${opsGenieAliasSuffix}`);
expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(2, `low-balance-${opsGenieAliasSuffix}`);
expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(
3,
2,
`critical-low-balance-${opsGenieAliasSuffix}`
);
expect(limitedSendToOpsGenieLowLevel).toHaveBeenCalledWith(
Expand All @@ -169,12 +168,8 @@ describe('walletWatcher', () => {

const opsGenieAliasSuffix = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(`${addressToBeFunded}${chainId}`));

expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(1, `get-balance-error-${opsGenieAliasSuffix}`);
expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(2, `low-balance-${opsGenieAliasSuffix}`);
expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(
3,
`critical-low-balance-${opsGenieAliasSuffix}`
);
expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenCalledWith(`get-balance-error-${opsGenieAliasSuffix}`);

expect(limitedSendToOpsGenieLowLevel).toHaveBeenCalledWith(
{
priority: 'P1',
Expand All @@ -197,10 +192,7 @@ describe('walletWatcher', () => {

expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(1, `get-balance-error-${opsGenieAliasSuffix}`);
expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(2, `low-balance-${opsGenieAliasSuffix}`);
expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(
3,
`critical-low-balance-${opsGenieAliasSuffix}`
);

expect(limitedSendToOpsGenieLowLevel).not.toHaveBeenCalled();
});

Expand All @@ -225,9 +217,8 @@ describe('walletWatcher', () => {
const opsGenieAliasSuffix = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(`${addressToBeFunded}${chainId}`));

expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(1, `get-balance-error-${opsGenieAliasSuffix}`);
expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(2, `low-balance-${opsGenieAliasSuffix}`);
expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(
3,
2,
`critical-low-balance-${opsGenieAliasSuffix}`
);
expect(limitedSendToOpsGenieLowLevel).toHaveBeenCalledTimes(1);
Expand Down Expand Up @@ -255,9 +246,8 @@ describe('walletWatcher', () => {
expect(getBalanceMock).toHaveBeenCalledTimes(2);

expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(1, `get-balance-error-${opsGenieAliasSuffix}`);
expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(2, `low-balance-${opsGenieAliasSuffix}`);
expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(
3,
2,
`critical-low-balance-${opsGenieAliasSuffix}`
);
expect(limitedSendToOpsGenieLowLevel).toHaveBeenCalledWith(
Expand All @@ -282,12 +272,6 @@ describe('walletWatcher', () => {

expect(getBalanceMock).toHaveBeenCalledTimes(3);

expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(1, `get-balance-error-${opsGenieAliasSuffix}`);
expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(2, `low-balance-${opsGenieAliasSuffix}`);
expect(limitedCloseOpsGenieAlertWithAlias).toHaveBeenNthCalledWith(
3,
`critical-low-balance-${opsGenieAliasSuffix}`
);
expect(limitedSendToOpsGenieLowLevel).toHaveBeenCalledWith(
{
priority: 'P2',
Expand Down
29 changes: 14 additions & 15 deletions src/wallet-watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,6 @@ export const runWalletWatcher = async ({ chains, ...config }: Config, wallets: W
ethers.utils.toUtf8Bytes(`${wallet.address}${wallet.chainId}`)
);

// Close previous cycle alerts
limitedCloseOpsGenieAlertWithAlias(`get-balance-error-${opsGenieAliasSuffix}`);
limitedCloseOpsGenieAlertWithAlias(`low-balance-${opsGenieAliasSuffix}`);
limitedCloseOpsGenieAlertWithAlias(`critical-low-balance-${opsGenieAliasSuffix}`);

const chainState = chainStates[wallet.chainId];
const getBalanceResult = await go(() => chainState.provider.getBalance(wallet.address), {
totalTimeoutMs: 15_000,
Expand All @@ -147,20 +142,20 @@ export const runWalletWatcher = async ({ chains, ...config }: Config, wallets: W
config.opsGenieConfig
);
throw new Error(message);
} else {
limitedCloseOpsGenieAlertWithAlias(`get-balance-error-${opsGenieAliasSuffix}`);
}

const balance = getBalanceResult.data;

// Send alert if balance is equal or below threshold
const walletBalanceThreshold = ethers.utils.parseUnits(
wallet.lowThreshold.value.toString(),
wallet.lowThreshold.unit
);
if (balance.lte(walletBalanceThreshold)) {
if (wallet.lowThreshold.criticalPercentage) {
const balanceThreshold = ethers.utils.parseUnits(wallet.lowThreshold.value.toString(), wallet.lowThreshold.unit);
if (balance.lte(balanceThreshold)) {
if (wallet.lowThreshold.criticalValue) {
// Send emergency alert if balance is even below a critical percentage
const criticalBalanceThreshold = walletBalanceThreshold.sub(
walletBalanceThreshold.mul(wallet.lowThreshold.criticalPercentage).div(100)
const criticalBalanceThreshold = ethers.utils.parseUnits(
wallet.lowThreshold.criticalValue.toString(),
wallet.lowThreshold.unit
);
if (balance.lte(criticalBalanceThreshold)) {
const criticalMessage = `Critical low balance alert for address ${wallet.address} on chain ${chainState.chainName}`;
Expand All @@ -169,11 +164,13 @@ export const runWalletWatcher = async ({ chains, ...config }: Config, wallets: W
priority: 'P1',
alias: `critical-low-balance-${opsGenieAliasSuffix}`,
message: criticalMessage,
description: `Current balance: ${balance.toString()}\nThreshold: ${walletBalanceThreshold.toString()}\nCritical threshold: ${criticalBalanceThreshold.toString()}`,
description: `Current balance: ${balance.toString()}\nThreshold: ${balanceThreshold.toString()}\nCritical threshold: ${criticalBalanceThreshold.toString()}`,
},
config.opsGenieConfig
);
return;
} else {
limitedCloseOpsGenieAlertWithAlias(`critical-low-balance-${opsGenieAliasSuffix}`);
}
}

Expand All @@ -183,10 +180,12 @@ export const runWalletWatcher = async ({ chains, ...config }: Config, wallets: W
priority: 'P2',
alias: `low-balance-${opsGenieAliasSuffix}`,
message,
description: `Current balance: ${balance.toString()}\nThreshold: ${walletBalanceThreshold.toString()}`,
description: `Current balance: ${balance.toString()}\nThreshold: ${balanceThreshold.toString()}`,
},
config.opsGenieConfig
);
} else {
limitedCloseOpsGenieAlertWithAlias(`low-balance-${opsGenieAliasSuffix}`);
}
})
);
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/wallet-watcher.feature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe('walletWatcher', () => {

await walletWatcher.runWalletWatcher(config, wallets);

expect(limitedCloseOpsGenieAlertWithAliasMock).toHaveBeenCalledTimes(3);
expect(limitedCloseOpsGenieAlertWithAliasMock).toHaveBeenCalledTimes(2);
expect(limitedSendToOpsGenieLowLevelMock).toHaveBeenCalledWith(
{
alias: 'low-balance-0xa029e0cf3ff3ea6562c3e67315c9bbec596fb7ac0ac862365eb7ec783b426c0d',
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const buildWallets = (): Wallets => ({
address: '0xC26f10e1b37A1E7A7De266FeF0c19533489C3e75',
providerXpub:
'xpub661MyMwAqRbcFeZ1CUvUpMs5bBSVLPHiuTqj7dZPertAGtd3xyTW1vrPspz7B34A7sdPahw7psrJjCXmn8KpF92jQssoqmsTk8fZ9PZN8xK',
lowThreshold: { value: 0.2, unit: 'ether', criticalPercentage: 50 },
lowThreshold: { value: 0.2, unit: 'ether', criticalValue: 0.1 },
},
],
});

0 comments on commit 8392359

Please sign in to comment.