Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ready: monitor the funding account of vortex #277

1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ docs/
.lighthouseci/
**/coins/*
*.yml
*.sol

package-lock.json
package.json
Expand Down
2 changes: 1 addition & 1 deletion _redirects
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/api/production/* https://prototype-signer-service-polygon.pendulumchain.tech/:splat 200
/api/staging/* https://prototype-signer-service-polygon-staging.pendulumchain.tech/:splat 200
/* /index.html 200
/* /index.html 200
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@
"@reown/appkit-adapter-wagmi": "^1.3.1",
"@sentry/react": "^8.36.0",
"@sentry/vite-plugin": "^2.22.6",
"@slack/web-api": "^7.7.0",
"@talismn/connect-components": "^1.1.9",
"@talismn/connect-wallets": "^1.2.5",
"@tanstack/react-query": "^5.61.0",
"@walletconnect/modal": "^2.6.2",
"@walletconnect/universal-provider": "^2.12.2",
Expand Down
1 change: 1 addition & 0 deletions signer-service/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ The following environment variables are available to configure the service.
- `FUNDING_SECRET`: Secret key to sign the funding transactions on Stellar.
- `PENDULUM_FUNDING_SEED`: Seed phrase to sign the funding transactions on Pendulum.
- `MOONBEAM_EXECUTOR_PRIVATE_KEY`: Private key to sign the transactions on Moonbeam.
- `SLACK_WEB_HOOK_TOKEN` - Slack web hook token for error reporting.

### Optional

Expand Down
14 changes: 10 additions & 4 deletions signer-service/src/api/controllers/moonbeam.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const {
MOONBEAM_RECEIVER_CONTRACT_ADDRESS,
MOONBEAM_FUNDING_AMOUNT_UNITS,
} = require('../../constants/constants');
const { SlackNotifier } = require('../services/slack.service');
const splitReceiverABI = require('../../../../mooncontracts/splitReceiverABI.json');

exports.executeXcmController = async (req, res) => {
Expand Down Expand Up @@ -54,6 +55,7 @@ exports.executeXcmController = async (req, res) => {
};

exports.sendStatusWithPk = async () => {
const slackService = new SlackNotifier();
let moonbeamExecutorAccount;

try {
Expand All @@ -66,12 +68,16 @@ exports.sendStatusWithPk = async () => {
const balance = await publicClient.getBalance({ address: moonbeamExecutorAccount.address });

// We are checking if the balance is less than 10 GLMR
const minimum_balance = Big(MOONBEAM_FUNDING_AMOUNT_UNITS).times(Big(10).pow(18));
if (balance < minimum_balance) {
const minimumBalance = Big(MOONBEAM_FUNDING_AMOUNT_UNITS).times(Big(10).pow(18));

if (balance < minimumBalance) {
slackService.sendMessage({
text: `Current balance of funding account is ${balance} GLMR please charge the account ${moonbeamExecutorAccount.address}.`,
});
return { status: false, public: moonbeamExecutorAccount.address };
} else {
return { status: true, public: moonbeamExecutorAccount.address };
}

return { status: true, public: moonbeamExecutorAccount.address };
} catch (error) {
console.error('Error fetching Moonbeam executor balance:', error);
return { status: false, public: moonbeamExecutorAccount?.address };
Expand Down
2 changes: 1 addition & 1 deletion signer-service/src/api/routes/v1/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const { sendStatusWithPk: sendStellarStatusWithPk } = require('../../services/st
const { sendStatusWithPk: sendPendulumStatusWithPk } = require('../../services/pendulum.service');
const { sendStatusWithPk: sendMoonbeamStatusWithPk } = require('../../controllers/moonbeam.controller');

async function sendStatusWithPk(req, res, next) {
async function sendStatusWithPk(_, res) {
const stellar = await sendStellarStatusWithPk();
const pendulum = await sendPendulumStatusWithPk();
const moonbeam = await sendMoonbeamStatusWithPk();
Expand Down
33 changes: 33 additions & 0 deletions signer-service/src/api/services/pendulum.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const {
PENDULUM_EPHEMERAL_STARTING_BALANCE_UNITS,
} = require('../../constants/constants');
const { TOKEN_CONFIG } = require('../../constants/tokenConfig');
const { SlackNotifier } = require('./slack.service');

require('dotenv').config();

Expand All @@ -21,6 +22,14 @@ function multiplyByPowerOfTen(bigDecimal, power) {
return newBigDecimal;
}

function divideByPowerOfTen(bigDecimal, power) {
const newBigDecimal = new Big(bigDecimal);
if (newBigDecimal.c[0] === 0) return newBigDecimal;

newBigDecimal.e -= power;
return newBigDecimal;
}

let api;
let previousSpecVersion;

Expand Down Expand Up @@ -82,7 +91,16 @@ exports.fundEphemeralAccount = async (ephemeralAddress) => {
}
};

const ChainDecimals = 12;

const nativeToDecimal = (value, decimals = ChainDecimals) => {
const divisor = new Big(10).pow(decimals);

return value.div(divisor);
};

exports.sendStatusWithPk = async () => {
const slackNotifier = new SlackNotifier();
const apiData = await createPolkadotApi();
const { fundingAccountKeypair } = getFundingData(apiData.ss58Format, apiData.decimals);
const { data: balance } = await apiData.api.query.system.account(fundingAccountKeypair.address);
Expand All @@ -109,14 +127,29 @@ exports.sendStatusWithPk = async () => {
if (remainingMaxSubsidiesAvailable.lt(SUBSIDY_MINIMUM_RATIO_FUND_UNITS)) {
isTokensSufficient = false;
console.log(`Token ${token} balance is insufficient.`);

slackNotifier.sendMessage({
text: `Current balance of funding account is ${nativeToDecimal(
tokenBalance,
).toString()} ${token} please charge the account ${fundingAccountKeypair.address}.`,
});
}
}),
);

const minimumBalanceFundingAccount = multiplyByPowerOfTen(Big(PENDULUM_FUNDING_AMOUNT_UNITS), apiData.decimals);
const nativeBalance = Big(balance?.free?.toString() ?? '0');

if (nativeBalance.gte(minimumBalanceFundingAccount) && isTokensSufficient) {
return { status: true, public: fundingAccountKeypair.address };
}
if (nativeBalance.lt(minimumBalanceFundingAccount)) {
slackNotifier.sendMessage({
text: `Current balance of funding account is ${divideByPowerOfTen(
Sharqiewicz marked this conversation as resolved.
Show resolved Hide resolved
nativeBalance,
apiData.decimals,
).toString()} PEN please charge the account ${fundingAccountKeypair.address}.`,
});
}
return { status: false, public: fundingAccountKeypair.address };
};
56 changes: 56 additions & 0 deletions signer-service/src/api/services/slack.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Store last message timestamps with their message signatures
const messageHistory = new Map();
// 6 hours in milliseconds
const cooldownPeriod = 6 * 60 * 60 * 1000;

class SlackNotifier {
constructor() {
if (process.env.SLACK_WEB_HOOK_TOKEN) {
this.webhookUrl = `https://hooks.slack.com/services/${process.env.SLACK_WEB_HOOK_TOKEN}`;
} else {
throw new Error('SLACK_WEB_HOOK_TOKEN is not defined');
}
}

generateMessageSignature(message) {
// Create a unique signature for the message
return JSON.stringify(message);
}

isMessageAllowed(signature) {
const now = Date.now();
const lastSent = messageHistory.get(signature);

if (!lastSent) return true;

return now - lastSent >= cooldownPeriod;
}

async sendMessage(message) {
const signature = this.generateMessageSignature(message);

if (!this.isMessageAllowed(signature)) {
// Message is still in cooldown period, skip sending
return;
}

const payload = JSON.stringify(message);

const response = await fetch(this.webhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: payload,
});

if (!response.ok) {
throw new Error(`Failed to send message. Status: ${response.status}`);
}

// Update the timestamp for this message
messageHistory.set(signature, Date.now());
}
}

exports.SlackNotifier = SlackNotifier;
13 changes: 10 additions & 3 deletions signer-service/src/api/services/stellar.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const {
STELLAR_EPHEMERAL_STARTING_BALANCE_UNITS,
} = require('../../constants/constants');
const { TOKEN_CONFIG, getTokenConfigByAssetCode } = require('../../constants/tokenConfig');
const { SlackNotifier } = require('./slack.service');

// Derive funding pk
const FUNDING_PUBLIC_KEY = Keypair.fromSecret(FUNDING_SECRET).publicKey();
const horizonServer = new Horizon.Server(HORIZON_URL);
Expand Down Expand Up @@ -132,14 +134,19 @@ async function buildPaymentAndMergeTx(
}

async function sendStatusWithPk() {
const slackNotifier = new SlackNotifier();
let stellarBalance = null;

try {
// ensure the funding account exists
const horizonServer = new Horizon.Server(HORIZON_URL);
let account = await horizonServer.loadAccount(FUNDING_PUBLIC_KEY);
let stellarBalance = account.balances.find((balance) => balance.asset_type === 'native');
const account = await horizonServer.loadAccount(FUNDING_PUBLIC_KEY);
stellarBalance = account.balances.find((balance) => balance.asset_type === 'native');

// ensure we have at the very least 10 XLM in the account
if (Number(stellarBalance.balance) < STELLAR_FUNDING_AMOUNT_UNITS) {
slackNotifier.sendMessage({
text: `Current balance of funding account is ${stellarBalance.balance} XLM please charge the account ${FUNDING_PUBLIC_KEY}.`,
});
return { status: false, public: FUNDING_PUBLIC_KEY };
}

Expand Down
1 change: 0 additions & 1 deletion signer-service/src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const { Keypair } = require('stellar-sdk');
const { port, env } = require('./config/vars');
const logger = require('./config/logger');
const app = require('./config/express');
Expand Down
Loading
Loading