Initial commit
This commit is contained in:
37
src/blockchain.ts
Normal file
37
src/blockchain.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import 'dotenv/config.js';
|
||||
import { GasPrices } from '../types/gasPrices'
|
||||
const Web3 = require('web3');
|
||||
|
||||
const rpcUrl = process.env.RPC_URL || 'ws://localhost:8545';
|
||||
|
||||
// Create a new web3 instance
|
||||
const web3 = new Web3(rpcUrl);
|
||||
|
||||
// Get the current gas price in gwei
|
||||
const getGasPricesInGwei = async (): Promise<GasPrices> => {
|
||||
const gweiFromWei = (priceInWei: string): number =>
|
||||
Number(web3.utils.fromWei(priceInWei, 'gwei').toFixed(2));
|
||||
|
||||
try {
|
||||
const [fastPrice, averagePrice, slowPrice] = await Promise.all([
|
||||
web3.eth.getGasPrice(),
|
||||
web3.eth.getGasPrice('average'),
|
||||
web3.eth.getGasPrice('slow'),
|
||||
]);
|
||||
|
||||
const gasPrices = {
|
||||
fast: gweiFromWei(fastPrice),
|
||||
average: gweiFromWei(averagePrice),
|
||||
slow: gweiFromWei(slowPrice),
|
||||
};
|
||||
|
||||
return Promise.resolve(gasPrices);
|
||||
// await redisClient.set('gas-prices', JSON.stringify(gasPrices));
|
||||
} catch (error) {
|
||||
console.error(`Error fetching gas prices: ${error}`);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
// Export the getCurrentGasPrice function
|
||||
export { getGasPricesInGwei };
|
37
src/commands/gas.ts
Normal file
37
src/commands/gas.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { SlashCommandBuilder, ChatInputCommandInteraction } from 'discord.js';
|
||||
import { handleGasAlertCommand, handleGasCommand, handleGasPendingCommand } from '../handlers';
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('gas')
|
||||
.setDescription('Set alerts for GWEI')
|
||||
|
||||
.addSubcommand(subcommand =>
|
||||
subcommand
|
||||
.setName('pending')
|
||||
.setDescription('List your current alert threshhold'))
|
||||
|
||||
.addSubcommand(subcommand =>
|
||||
subcommand
|
||||
.setName('alert')
|
||||
.setDescription('List your current alert threshhold')
|
||||
.addStringOption(option =>
|
||||
option.setName('gwei')
|
||||
.setDescription('gwei threshold')
|
||||
.setRequired(true))),
|
||||
|
||||
async execute(interaction: ChatInputCommandInteraction) {
|
||||
const subcommand = interaction.options.getSubcommand();
|
||||
|
||||
if (subcommand == 'pending' ) {
|
||||
console.log(`Replying to command "/gas pending"`)
|
||||
return await handleGasPendingCommand(interaction);
|
||||
} else if (subcommand == 'gwei') {
|
||||
return await handleGasAlertCommand(interaction);
|
||||
} else {
|
||||
|
||||
return await handleGasCommand(interaction);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
38
src/gasPriceChecker.ts
Normal file
38
src/gasPriceChecker.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { Client, EmbedBuilder, TextChannel } from 'discord.js';
|
||||
import { getGasPricesInGwei } from './blockchain';
|
||||
import redisClient from './redis';
|
||||
|
||||
import { GasAlert } from '../types/gasAlert';
|
||||
|
||||
const createGasPriceChecker = (client: Client) => {
|
||||
setInterval(async () => {
|
||||
try {
|
||||
const gasPrices = await getGasPricesInGwei();
|
||||
|
||||
const gasAlerts: GasAlert[] = await redisClient
|
||||
.get('gas-alerts')
|
||||
.then((value) => (value ? JSON.parse(value) : []));
|
||||
|
||||
gasAlerts.forEach(async (gasAlert) => {
|
||||
if (gasPrices.fast <= gasAlert.threshold) {
|
||||
const channel = await client.channels.fetch(gasAlert.channelId) as TextChannel;
|
||||
const user = await client.users.fetch(gasAlert.userId);
|
||||
|
||||
channel.send({
|
||||
embeds: [
|
||||
new EmbedBuilder()
|
||||
.setTitle('Gas price alert!')
|
||||
.setDescription(`<@${gasAlert.userId}>!\n\nThe current gas prices have fallen below your alert threshold of ${gasAlert.threshold} Gwei. The current gas prices are: \n\n Fast: ${gasPrices.fast} Gwei \n\n Average: ${gasPrices.average} Gwei \n\n Slow: ${gasPrices.slow} Gwei`)
|
||||
.setColor('#FF8C00')
|
||||
],
|
||||
target: user
|
||||
})
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error checking gas prices: ${error}`);
|
||||
}
|
||||
}, 15000);
|
||||
};
|
||||
|
||||
export { createGasPriceChecker };
|
50
src/handlers.ts
Normal file
50
src/handlers.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { ChatInputCommandInteraction, EmbedBuilder } from 'discord.js';
|
||||
import redisClient from './redis';
|
||||
import { getGasPricesInGwei } from './blockchain';
|
||||
|
||||
import { GasAlert } from '../types/gasAlert';
|
||||
|
||||
// Respond to the "/gas" command
|
||||
const handleGasCommand = async (interaction: ChatInputCommandInteraction): Promise<void> => {
|
||||
console.log(`Replying to command "/gas"`);
|
||||
|
||||
const gasPrices = await getGasPricesInGwei();
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor('#0099ff')
|
||||
.setTitle('Current Gas Prices')
|
||||
.setDescription(`The current gas prices are: \n\n Fast: ${gasPrices.fast} Gwei \n\n Average: ${gasPrices.average} Gwei \n\n Slow: ${gasPrices.slow} Gwei`)
|
||||
|
||||
await interaction.reply({ embeds: [embed] });
|
||||
};
|
||||
|
||||
// Respond to the "/gas alert ${gwei}" command
|
||||
const handleGasAlertCommand = async (interaction: ChatInputCommandInteraction): Promise<void> => {
|
||||
const threshold = parseInt(interaction.options.getString('gwei')!);
|
||||
const channelId = interaction.channelId;
|
||||
const userId = interaction.user.id;
|
||||
|
||||
const gasAlert: GasAlert = { threshold, channelId, userId };
|
||||
|
||||
console.log(`Replying to command "/gas gwei ${threshold}"`);
|
||||
|
||||
await redisClient.hSet('gas-alerts', `${userId}`, JSON.stringify(gasAlert));
|
||||
|
||||
await interaction.reply(`Your gas alert threshold of ${threshold} Gwei has been set.`);
|
||||
};
|
||||
|
||||
// Respond to the "/gas pending" command
|
||||
const handleGasPendingCommand = async (interaction: ChatInputCommandInteraction): Promise<void> => {
|
||||
const userId = interaction.user.id;
|
||||
const alertThreshold = await redisClient.get(`${userId}`);
|
||||
|
||||
console.log(`Replying to command "/gas pending"`);
|
||||
|
||||
if (alertThreshold === null) {
|
||||
await interaction.reply('No gas price alerts set');
|
||||
} else {
|
||||
await interaction.reply(`Current gas price alert threshold: ${alertThreshold} Gwei`);
|
||||
}
|
||||
};
|
||||
|
||||
export { handleGasCommand, handleGasAlertCommand, handleGasPendingCommand };
|
68
src/index.ts
Normal file
68
src/index.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import 'dotenv/config.js';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { ApplicationCommand, Client, Collection, GatewayIntentBits, REST, Routes } from 'discord.js';
|
||||
import { createGasPriceChecker } from './gasPriceChecker';
|
||||
|
||||
const clientId = process.env.DISCORD_CLIENT || "";
|
||||
const token = process.env.DISCORD_BOT_TOKEN || "";
|
||||
|
||||
export class DiscordClient extends Client {
|
||||
public commands: Collection<string, any> = new Collection();
|
||||
}
|
||||
|
||||
// Create a new Discord client
|
||||
const client = new DiscordClient({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages] });
|
||||
|
||||
const commands: ApplicationCommand[] = [];
|
||||
const commandsPath = path.join(__dirname, 'commands');
|
||||
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
|
||||
|
||||
for (const file of commandFiles) {
|
||||
const filePath = path.join(commandsPath, file);
|
||||
const command = require(filePath);
|
||||
// Set a new item in the Collection with the key as the command name and the value as the exported module
|
||||
if ('data' in command && 'execute' in command) {
|
||||
commands.push(command.data.toJSON())
|
||||
client.commands.set(command.data.name, command);
|
||||
} else {
|
||||
console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
|
||||
}
|
||||
}
|
||||
|
||||
const eventsPath = path.join(__dirname, 'events');
|
||||
const eventFiles = fs.readdirSync(eventsPath).filter(file => file.endsWith('.js'));
|
||||
|
||||
for (const file of eventFiles) {
|
||||
const filePath = path.join(eventsPath, file);
|
||||
const event = require(filePath);
|
||||
if (event.once) {
|
||||
client.once(event.name, (...args) => event.execute(...args));
|
||||
} else {
|
||||
client.on(event.name, (...args) => event.execute(...args));
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Booting up discord bot')
|
||||
|
||||
// Log in to Discord
|
||||
client.login(process.env.DISCORD_BOT_TOKEN)
|
||||
.then(async () => {
|
||||
console.log(`Started refreshing ${commands.length} global application (/) commands.`);
|
||||
|
||||
|
||||
// Construct and prepare an instance of the REST module
|
||||
const rest = new REST({ version: '10' }).setToken(token);
|
||||
|
||||
// The put method is used to fully refresh all commands in the guild with the current set
|
||||
const data = await rest.put(
|
||||
Routes.applicationCommands(clientId),
|
||||
{ body: commands },
|
||||
) as ApplicationCommand[];
|
||||
|
||||
console.log(`Successfully reloaded ${data.length} global application (/) commands.`);
|
||||
})
|
||||
.then(() => {
|
||||
// Start the gas price checker
|
||||
createGasPriceChecker(client);
|
||||
});
|
14
src/redis.ts
Normal file
14
src/redis.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import Redis from 'redis';
|
||||
|
||||
// Create a new Redis client
|
||||
const client = Redis.createClient({
|
||||
url: 'redis://redis:6379'
|
||||
});
|
||||
|
||||
// Log any Redis errors to the console
|
||||
client.on('error', (error) => {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
// Export the Redis client
|
||||
export default client;
|
Reference in New Issue
Block a user