Skip to content

Commit

Permalink
add wallet address for cron job and test (#4831)
Browse files Browse the repository at this point in the history
* add wallet address for cron job and test

* add test

* add to eslint

* test alert

* update errors

* fix webhook

* add link to wallet

* update cron to use prd secrets

* fix typecheck
  • Loading branch information
mattcasey authored Oct 17, 2024
1 parent 98ed95f commit 6e825fd
Show file tree
Hide file tree
Showing 16 changed files with 207 additions and 47 deletions.
4 changes: 2 additions & 2 deletions .ebstalk.apps.env/cron.env
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ DATABASE_URL="{{pull:secretsmanager:/io.cv.app/prd/db:SecretString:database_url}
DD_AGENT_HOST="datadog-agent"
DD_API_KEY="{{pull:secretsmanager:/io.cv.app/shared/datadog:SecretString:dd_api_key}}"
DEEPDAO_API_KEY="{{pull:secretsmanager:/io.cv.app/prd/deepdao:SecretString:deepdao_api_key}}"
DISCORD_BOT_TOKEN="{{pull:secretsmanager:/io.cv.app/shared/discord:SecretString:discord_bot_token}}"
DISCORD_EVENTS_WEBHOOK="{{pull:secretsmanager:/io.cv.app/shared/discord:SecretString:discord_events_webhook}}"
DISCORD_BOT_TOKEN="{{pull:secretsmanager:/io.cv.app/prd/discord:SecretString:discord_bot_token}}"
DISCORD_EVENTS_WEBHOOK="{{pull:secretsmanager:/io.cv.app/prd/discord:SecretString:discord_events_webhook}}"
NEXT_PUBLIC_DISCORD_WEBHOOK_ERRORS="{{pull:secretsmanager:/io.cv.app/prd/discord:SecretString:discord_errors_webhook}}"
NODE_ENV="production"
PERMISSIONS_API_URL="{{pull:secretsmanager:/io.cv.app/prd/permissions:SecretString:permissions_api_url}}"
Expand Down
2 changes: 1 addition & 1 deletion .ebstalk.apps.env/scoutgamecron.env
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ REACT_APP_BUILDER_NFT_CONTRACT_ADDRESS="0x743ec903FE6D05E73b19a6DB807271bb66100e

REACT_APP_DECENT_API_KEY="{{pull:secretsmanager:/io.cv.app/prd/decent:SecretString:decent_api_key}}"
ALCHEMY_API_KEY="{{pull:secretsmanager:/io.cv.app/prd/alchemy:SecretString:alchemy_api_key}}"
DISCORD_EVENTS_WEBHOOK="{{pull:secretsmanager:/io.cv.app/prd/discord:SecretString:discord_events_webhook}}"
DISCORD_ALERTS_WEBHOOK="{{pull:secretsmanager:/io.cv.app/prd/discord:SecretString:discord_alerts_webhook}}"
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
"./packages/charmeditor/",
"./packages/github/",
"./packages/scoutgame/",
"./packages/testing/",
"./packages/utils/"
]
}
Expand Down
5 changes: 3 additions & 2 deletions apps/scoutgamecron/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@
"@octokit/plugin-throttling": "^9.3.1",
"@packages/farcaster": "^0.0.0",
"@packages/github": "^0.0.0",
"@packages/mixpanel": "^0.0.0",
"@packages/onchain": "^0.0.0",
"@packages/scoutgame": "^0.0.0",
"@packages/mixpanel": "^0.0.0",
"@packages/utils": "^1.0.0",
"web-push": "^3.6.7"
},
"devDependencies": {
"@faker-js/faker": "^9.0.1",
"@jest/globals": "^29.7.0",
"@packages/testing": "^1.0.0",
"@swc/core": "^1.7.26",
"@types/web-push": "^3.6.3"
}
}
}
2 changes: 1 addition & 1 deletion apps/scoutgamecron/src/scripts/processBuilderActivity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import { prisma } from '@charmverse/core/prisma-client';
// season: '2024-W40'
// });
// return;
await processAllBuilderActivity({
await processAllBuilderActivity(null, {
createdAfter: DateTime.fromISO('2024-09-27', { zone: 'utc' }).toJSDate()
});
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { jest } from '@jest/globals';
import { builderCreatorAddress } from '@packages/scoutgame/builderNfts/constants';
import { createContext } from '@packages/testing/koa/context';

jest.unstable_mockModule('@packages/utils/http', () => ({
POST: jest.fn()
}));

jest.unstable_mockModule('../getWalletGasBalanceInUSD', () => ({
getWalletGasBalanceInUSD: jest.fn()
}));

const { POST } = await import('@packages/utils/http');
const { getWalletGasBalanceInUSD } = await import('../getWalletGasBalanceInUSD');
const { alertLowWalletGasBalance } = await import('../index');

const discordWebhook = 'https://discord.webhook.url';

describe('alertLowWalletGasBalance', () => {
beforeEach(() => {
jest.resetAllMocks();
});

it('should call the webhook when balance is below threshold', async () => {
(getWalletGasBalanceInUSD as jest.Mock<typeof getWalletGasBalanceInUSD>).mockResolvedValue(20); // Below threshold of 25

await alertLowWalletGasBalance(createContext(), discordWebhook);

expect(getWalletGasBalanceInUSD).toHaveBeenCalledWith(builderCreatorAddress);
expect(POST).toHaveBeenCalledWith(
discordWebhook,
expect.objectContaining({
content: expect.any(String)
})
);
});

it('should not call the webhook when balance is above threshold', async () => {
(getWalletGasBalanceInUSD as jest.Mock<typeof getWalletGasBalanceInUSD>).mockResolvedValue(30); // Above threshold of 25

await alertLowWalletGasBalance(createContext(), discordWebhook);

expect(getWalletGasBalanceInUSD).toHaveBeenCalledWith(builderCreatorAddress);
expect(POST).not.toHaveBeenCalled();
});

it('should throw an error if no discord webhook is provided', async () => {
await expect(alertLowWalletGasBalance(createContext())).rejects.toThrow('No Discord webhook found');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { jest } from '@jest/globals';
import { builderCreatorAddress } from '@packages/scoutgame/builderNfts/constants';

jest.unstable_mockModule('@packages/utils/http', () => ({
POST: jest.fn(),
GET: jest.fn()
}));

const { POST, GET } = await import('@packages/utils/http');
const { getWalletGasBalanceInUSD } = await import('../getWalletGasBalanceInUSD');

describe('getWalletGasBalanceInUSD', () => {
it('should return $25 when balance and price are set accordingly', async () => {
// Mock the POST request to Alchemy API
(POST as jest.Mock<typeof POST>).mockResolvedValue({
result: '0x3635c9adc5dea00000' // 1000000000000000000000 wei (1000 ETH)
});

// Mock the GET request to CoinGecko API
(GET as jest.Mock<typeof GET>).mockResolvedValue({
ethereum: { usd: 0.025 } // $0.025 per ETH
});

const balance = await getWalletGasBalanceInUSD(builderCreatorAddress, 'test-api-key');

expect(balance).toBeCloseTo(25, 2); // $25 with 2 decimal places precision
expect(POST).toHaveBeenCalledWith(
expect.stringContaining('https://opt-mainnet.g.alchemy.com/v2/test-api-key'),
expect.any(Object)
);
expect(GET).toHaveBeenCalledWith('https://api.coingecko.com/api/v3/simple/price', expect.any(Object));
});
});
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import env from '@beam-australia/react-env';
import { GET, POST } from '@packages/utils/http';

export async function getWalletGasBalanceInUSD(walletAddress: string) {
const apiKey = process.env.ALCHEMY_API_KEY || env('ALCHEMY_API_KEY');
export async function getWalletGasBalanceInUSD(
walletAddress: string,
apiKey: string | undefined = process.env.ALCHEMY_API_KEY
) {
if (!apiKey) {
throw new Error('No Alchemy API key found');
}
const alchemyApiUrl = `https://opt-mainnet.g.alchemy.com/v2/${apiKey}`;

const response = await POST<{ result: string }>(alchemyApiUrl, {
jsonrpc: '2.0',
method: 'eth_getBalance',
Expand Down
36 changes: 23 additions & 13 deletions apps/scoutgamecron/src/tasks/alertLowWalletGasBalance/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
import env from '@beam-australia/react-env';
import { log } from '@charmverse/core/log';
import { builderCreatorAddress } from '@packages/scoutgame/builderNfts/constants';
import { POST } from '@packages/utils/http';
import type Koa from 'koa';

import { getWalletGasBalanceInUSD } from './getWalletGasBalanceInUSD';

export async function alertLowWalletGasBalance() {
const discordWebhook = process.env.DISCORD_EVENTS_WEBHOOK || env('DISCORD_EVENTS_WEBHOOK');
const thresholdUSD = 25;

try {
const balanceInUSD = await getWalletGasBalanceInUSD('');
log.info(`Admin wallet has a balance of ${balanceInUSD} USD`);
if (balanceInUSD <= 25) {
await POST(discordWebhook, {
content: `Admin wallet has a balance of ${balanceInUSD} USD`
});
}
} catch (error) {
log.error('Error alerting low wallet gas balance:', { error });
export async function alertLowWalletGasBalance(
ctx: Koa.Context,
discordWebhook: string | undefined = process.env.DISCORD_ALERTS_WEBHOOK
) {
if (!discordWebhook) {
throw new Error('No Discord webhook found');
}

const balanceInUSD = await getWalletGasBalanceInUSD(builderCreatorAddress);
log.info(`Admin wallet has a balance of ${balanceInUSD} USD`);
if (balanceInUSD <= thresholdUSD) {
await POST(discordWebhook, {
content: `<@&1027309276454207519>: Admin wallet has a low balance: ${balanceInUSD} USD. (Threshold is ${thresholdUSD} USD)`,
embeds: [
{
title: `View wallet: ${builderCreatorAddress}`,
url: 'https://optimism.blockscout.com/address/0x518AF6fA5eEC4140e4283f7BDDaB004D45177946'
}
]
});
}
}
9 changes: 5 additions & 4 deletions apps/scoutgamecron/src/tasks/processBuilderActivity/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { log } from '@charmverse/core/log';
import { prisma } from '@charmverse/core/prisma-client';
import { getCurrentWeek, currentSeason, getDateFromISOWeek } from '@packages/scoutgame/dates';
import type Koa from 'koa';

import { processBuilderActivity } from './processBuilderActivity';
import { updateBuildersRank } from './updateBuildersRank';
Expand All @@ -10,10 +11,10 @@ type ProcessPullRequestsOptions = {
season?: string;
};

export async function processAllBuilderActivity({
createdAfter = new Date(Date.now() - 30 * 60 * 1000),
season = currentSeason
}: ProcessPullRequestsOptions = {}) {
export async function processAllBuilderActivity(
ctx: Koa.Context | null,
{ createdAfter = new Date(Date.now() - 30 * 60 * 1000), season = currentSeason }: ProcessPullRequestsOptions = {}
) {
const builders = await prisma.scout.findMany({
where: {
builderStatus: 'approved',
Expand Down
2 changes: 1 addition & 1 deletion apps/scoutgamecron/src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const app = new Koa();
const router = new Router();

// add a task endpoint which will be configured in cron.yml
function addTask(path: string, handler: (ctx: Koa.DefaultContext) => any) {
function addTask(path: string, handler: (ctx: Koa.Context) => any) {
router.post(path, async (ctx) => {
// just in case we need to disable cron in production
if (process.env.DISABLE_CRON === 'true') {
Expand Down
Loading

0 comments on commit 6e825fd

Please sign in to comment.