Skip to content

Commit

Permalink
Merge pull request #35 from mario-purestake/mariops-bot-alert-when-ru…
Browse files Browse the repository at this point in the history
…nning-low-on-funds

Alert on Slack webhook when bot is running low on funds
  • Loading branch information
tgmichel authored Sep 24, 2020
2 parents ca52d10 + 263c33d commit 9d58d02
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 14 deletions.
123 changes: 114 additions & 9 deletions tools/bots/mission-control-bot.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,55 @@
import { Client, MessageEmbed, Message } from "discord.js";
import Web3 from "web3";
import https from "https";


const TOKEN_DECIMAL = 18n;
const FAUCET_SEND_INTERVAL = 1; // hours
const EMBED_COLOR_CORRECT = 0x642f95;
const EMBED_COLOR_ERROR = 0xc0392b;
const SLACK_MSG_CONTENTS = `
{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "The account linked to the bot is running low on funds."
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*Account ID:*\n{{ account-fix-me }}"
},
{
"type": "mrkdwn",
"text": "*Current balance:*\n{{ balance-fix-me }} DEV"
}
]
}
]
}
`;

const params = {
// Discord app information
DISCORD_TOKEN: process.env.DISCORD_TOKEN,
DISCORD_CHANNEL: process.env.DISCORD_CHANNEL,

// Slack app information
SLACK_WEBHOOK: process.env.SLACK_WEBHOOK,

// Web3 RPC access
RPC_URL: process.env.RPC_URL,
ACCOUNT_ID: process.env.ACCOUNT_ID,
ACCOUNT_KEY: process.env.ACCOUNT_KEY,

// Token distribution
TOKEN_COUNT: BigInt(process.env.TOKEN_COUNT || 10),
FAUCET_SEND_INTERVAL: parseInt(process.env.FAUCET_SEND_INTERVAL || "1"), // hours
BALANCE_ALERT_THRESHOLD: BigInt(process.env.BALANCE_ALERT_THRESHOLD || 100), // DEV
}

Object.keys(params).forEach(param => {
Expand All @@ -34,22 +66,69 @@ console.log(`Connecting web3 to ${params.RPC_URL}...`);

const client: Client = new Client();
const receivers: { [author: string]: number } = {};
const lastBalanceCheck = {
timestamp: 0,
balance: BigInt(0)
};

client.on("ready", () => {
console.log(`Logged in as ${client.user.tag}!`);
});
/**
* Send notification to Slack using a webhook URL and the
* message payload read from SLACK_MSG_CONTENT_FILEPATH.
* @param {BigInt} account_balance Balance of the account in DEV
*/
const sendSlackNotification = async (account_balance: BigInt) => {
// Message to send to Slack (JSON payload)
const data = SLACK_MSG_CONTENTS
.replace("{{ account-fix-me }}", params.ACCOUNT_ID)
.replace("{{ balance-fix-me }}", account_balance.toString());

// Options for the HTTP request (data is written later)
const options = {
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": data.length
}
};

// Promise to "await" until request has ended
const completed_request = new Promise((resolve, reject) => {
// Send request to Slack webhook
const request = https.request(params.SLACK_WEBHOOK, options, (response) => {
let data = '';

response.on('data', (chunk) => {
data += chunk;
});

response.on('end', () => {
console.log("Received data from Slack webhook:", data);
resolve(data);
});

}).on("error", (err) => {
console.log("Error while sending Slack notification:", err.message);
reject(err);
});

request.write(data);
request.end();
});

return await completed_request;
}

/**
* Returns the approximated remaining time until being able to request tokens again.
* @param {Date} lastTokenRequestMoment Last moment in which the user requested funds
* @param {number} lastTokenRequestMoment Last moment in which the user requested funds
*/
const nextAvailableToken = (lastTokenRequestMoment: number) => {
// how many ms there are in minutes/hours
const msPerMinute = 60 * 1000;
const msPerHour = msPerMinute * 60;

// when the author of the message will be able to request more tokens
const availableAt = lastTokenRequestMoment + (FAUCET_SEND_INTERVAL * msPerHour);
const availableAt = lastTokenRequestMoment + (params.FAUCET_SEND_INTERVAL * msPerHour);
// remaining time until able to request more tokens
let remain = availableAt - Date.now();

Expand Down Expand Up @@ -94,7 +173,7 @@ const checkH160AddressIsCorrect = (address: string, msg: Message) => {
.setColor(EMBED_COLOR_ERROR)
.setTitle("Invalid address")
.setFooter("Addresses must follow the H160 address format");

// send message to channel
msg.channel.send(errorEmbed);
}
Expand All @@ -110,7 +189,7 @@ const checkH160AddressIsCorrect = (address: string, msg: Message) => {
* @param {string} messageContent Content of the message
*/
const botActionFaucetSend = async (msg: Message, authorId: string, messageContent: string) => {
if (receivers[authorId] > Date.now() - 3600 * 1000) {
if (receivers[authorId] > Date.now() - params.FAUCET_SEND_INTERVAL * 3600 * 1000) {
const errorEmbed = new MessageEmbed()
.setColor(EMBED_COLOR_ERROR)
.setTitle(`You already received tokens!`)
Expand Down Expand Up @@ -145,6 +224,18 @@ const botActionFaucetSend = async (msg: Message, authorId: string, messageConten
);
const accountBalance = BigInt(await web3Api.eth.getBalance(`0x${address}`));

// Check balance every 10min (minimum interval, dependent on when the function is called)
if (lastBalanceCheck.timestamp < Date.now() - 600 * 1000) {
// Update cached info for last balance check
lastBalanceCheck.balance = BigInt(await web3Api.eth.getBalance(`0x${params.ACCOUNT_ID}`));
lastBalanceCheck.timestamp = Date.now();

// If balance is low, send notification to Slack
if (lastBalanceCheck.balance < params.BALANCE_ALERT_THRESHOLD * (10n ** TOKEN_DECIMAL)) {
await sendSlackNotification(lastBalanceCheck.balance / (10n ** TOKEN_DECIMAL));
}
}

const fundsTransactionEmbed = new MessageEmbed()
.setColor(EMBED_COLOR_CORRECT)
.setTitle("Transaction of funds")
Expand Down Expand Up @@ -197,6 +288,20 @@ const onReceiveMessage = async (msg: Message) => {
}
};

client.on("message", onReceiveMessage);
// Prompt when logged in
client.on("ready", () => {
console.log(`Logged in as ${client.user.tag}!`);
});

// Bind message event to custom listener
client.on("message", async (msg) => {
try {
await onReceiveMessage(msg);
} catch (e) {
console.log(new Date().toISOString(), "ERROR", e.stack || e);
}
});


// Perform login and listen for new events
client.login(params.DISCORD_TOKEN);
8 changes: 3 additions & 5 deletions tools/bots/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"web3": ["node_modules/web3/dist/web3"],
"discord.js": ["node_modules/discord.js/webpack/discord.min"],
},
"target": "esnext"
"moduleResolution": "node",
"target": "esnext",
"esModuleInterop": true
},
"files": ["mission-control-bot.ts"]
}

0 comments on commit 9d58d02

Please sign in to comment.