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

EthTracker notif settings implemented #33

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/sample_showrunners/bank/bankChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export default class BankChannel extends EPNSChannel {

}


// async apyNotif(userAlice, apy, simulate) {
// try {
// this.logInfo("Getting events ---> apyNotif");
Expand Down Expand Up @@ -144,7 +145,7 @@ export default class BankChannel extends EPNSChannel {
try {
const notifRes = await userAlice.channel.send(['*'], payload);

return notifRes
return notifRes;
} catch (error) {
this.logInfo("ERROR🔴 from sendThroughNotifSettings: ", error);
}
Expand Down
171 changes: 171 additions & 0 deletions src/sample_showrunners/ethTicker/ethTickerChannel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// @name: ETH Tracker Channel
// @version: 1.0
// @recent_changes: ETH Price Tracker

import { Service, Inject } from 'typedi';
import config from '../../config';

import axios from 'axios';

import { EPNSChannel } from '../../helpers/epnschannel';
import { Logger } from 'winston';

// Import the Push SDK
import { PushAPI } from "@pushprotocol/restapi";

import { ethers } from "ethers";
import { mongo } from 'mongoose';

import { ethTickerModel } from './ethTickerModel';


const bent = require('bent'); // Download library

const NETWORK_TO_MONITOR = config.web3MainnetNetwork;
// const ethTickerSettings = require('./ethTickerSettings.json')

@Service()
export default class EthTickerChannel extends EPNSChannel {
constructor(@Inject('logger') public logger: Logger) {
super(logger, {
networkToMonitor: NETWORK_TO_MONITOR,
dirname: __dirname,
name: 'ETH Ticker',
url: 'https://epns.io/',
useOffChain: true,
});
}

/*
1. Fetch all the user settings - userAlice.channel.subscribers()
2.
*/

// To form and write to smart contract
public async sendMessageToContract(simulate) {
const logger = this.logger;

this.getNewPrice()
.then(async (payload: any) => {

for (let i = 0; i < payload.recipients.length; i++) {
this.sendNotification({
// recipient: this.channelAddress,
recipient: payload.recipients[i], // new
title: payload.notifTitle,
message: payload.notifMsg,
payloadTitle: payload.title,
payloadMsg: payload.msg,
notificationType: payload.type,
simulate: simulate,
image: null,
});
}
})
.catch(err => {
logger.error(`[${new Date(Date.now())}]-[ETH Ticker]- Errored on CMC API... skipped with error: %o`, err);
});
}

public async getNewPrice() {
const logger = this.logger;
logger.debug(`[${new Date(Date.now())}]-[ETH Ticker]-Getting price of eth... `);

return await new Promise((resolve, reject) => {
const getJSON = bent('json');

const cmcroute = 'v1/cryptocurrency/quotes/latest';
const cmcEndpoint = 'https://pro-api.coinmarketcap.com/'
const pollURL = `${cmcEndpoint}${cmcroute}?symbol=ETH&CMC_PRO_API_KEY=${'1bbe0bab-4ee7-4a38-8b03-b49a0b4fff4e' || config.cmcAPIKey}`;

console.log(`CMC Cnnfig: ${cmcEndpoint}, CMC_PRO_API_KEY = ${'1bbe0bab-4ee7-4a38-8b03-b49a0b4fff4e' || config.cmcAPIKey}`);

getJSON(pollURL)
.then(async (response: any) => {
if (response.status.error_code) {
reject(`CMC Error: ${response.status}`);
}

logger.info(`[${new Date(Date.now())}]-[ETH Ticker]-CMC Response: %o`, response);

// Get data
const data = response.data['ETH'];

// construct Title and Message from data
const price = data.quote.USD.price;
const formattedPrice = Number(Number(price).toFixed(2)).toLocaleString();


const hourChange = Number(data.quote.USD.percent_change_1h);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you need to fetch the price percent change in 6 hour and then on the basis of that change, we will send notification.
please refer this : https://coinmarketcap.com/api/documentation/v1/#operation/getV3CryptocurrencyQuotesHistorical [Interval Options section].

const dayChange = Number(data.quote.USD.percent_change_24h);
const weekChange = Number(data.quote.USD.percent_change_7d);

const hourChangeFixed = hourChange.toFixed(2);
const dayChangeFixed = dayChange.toFixed(2);
const weekChangeFixed = weekChange.toFixed(2);

// Initialize arr for all recepients
let recipients: string[] = [];

// 1. Store prev price in MongoDB
// Store
await ethTickerModel.findByIdAndUpdate(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can skip storing of % change if we directly get the data for 6h(current cron job) from the CMC API, I just checked it and it can be done.

{ _id: 'prev_eth_price' },
{ prevEthPrice: Number(formattedPrice) },
{ upsert: true },
);

// Retrieve
// const prevPrice = 0; //= mongo.findOne();
const prevPrice = await ethTickerModel.findOne({ _id: 'prev_eth_price' });

// 2. Calculate percentage change. |prevValue - currentValue| / prevValue
const changePercentage = Math.abs(prevPrice.prevEthPrice - price) / prevPrice.prevEthPrice;

// 3. Get the list of all the addresses opted in for the setting - /subscribers?category=2&setting=true
const { data: userData } = await axios(`https://backend-staging.epns.io/apis/v1/channels/eip155:${'11155111'}:${'0x9C2dA92ff312b630B67cEa1d2C234250c2d3410e'}/subscribers?category=${'2'}&setting=${'true'}`);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SDK has incorporated this API. So you can replace this with the SDK call


// 4. Loop through the `settings` array for the required type (say 2 here) and get the `user` value
userData.subscribers.map((subscriberObj) => {
const userSettings = JSON.parse(subscriberObj.settings);
const temp = userSettings.find((obj) => obj.index === 1); // arr[0] = index: 1 ---> arrIndex + 1
let userValue : number;

if (temp.enabled === true) {
userValue = temp.user;

// Construct payload if optted in or pass
// 5. If the value matches the percentage change, send the notification, else pass
if (userValue === hourChange) {
recipients.push(subscriberObj.subscriber);
}
}
});

const title = 'ETH at $' + formattedPrice;
const message = `\nHourly Movement: ${hourChangeFixed}%\nDaily Movement: ${dayChangeFixed}%\nWeekly Movement: ${weekChangeFixed}%`;

const payloadTitle = `ETH Price Movement`;
const payloadMsg = `ETH at [d:$${formattedPrice}]\n\nHourly Movement: ${
hourChange >= 0 ? '[s:' + hourChangeFixed + '%]' : '[t:' + hourChangeFixed + '%]'
}\nDaily Movement: ${
dayChange >= 0 ? '[s:' + dayChangeFixed + '%]' : '[t:' + dayChangeFixed + '%]'
}\nWeekly Movement: ${
weekChange >= 0 ? '[s:' + weekChangeFixed + '%]' : '[t:' + weekChangeFixed + '%]'
}[timestamp: ${Math.floor(Date.now() / 1000)}]`;

const payload = {
type: 1, // Type of Notification
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type will be 3 here as we are sending Targetted notifications at line 54.

notifTitle: title, // Title of Notification
notifMsg: message, // Message of Notification
title: payloadTitle, // Internal Title
msg: payloadMsg, // Internal Message
recipients: recipients // Recipients Array
};

resolve(payload);
})
.catch(err => reject(`Unable to reach CMC API, error: ${err}`));
});
}
}
48 changes: 48 additions & 0 deletions src/sample_showrunners/ethTicker/ethTickerJobs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Do Scheduling
// https://github.com/node-schedule/node-schedule
// * * * * * *
// ┬ ┬ ┬ ┬ ┬ ┬
// │ │ │ │ │ │
// │ │ │ │ │ └ day of week (0 - 7) (0 or 7 is Sun)
// │ │ │ │ └───── month (1 - 12)
// │ │ │ └────────── day of month (1 - 31)
// │ │ └─────────────── hour (0 - 23)
// │ └──────────────────── minute (0 - 59)
// └───────────────────────── second (0 - 59, OPTIONAL)
// Execute a cron job every 5 Minutes = */5 * * * *
// Starts from seconds = * * * * * *

import config from '../../config';
import logger from '../../loaders/logger';

// Import the Push SDK
import { PushAPI } from "@pushprotocol/restapi";

import { ethers } from "ethers";

import { Container } from 'typedi';
import schedule from 'node-schedule';

import EthTickerChannel from './ethTickerChannel';

export default async () => {
const startTime = new Date(new Date().setHours(0, 0, 0, 0));

const sixHourRule = new schedule.RecurrenceRule();
sixHourRule.hour = new schedule.Range(0, 23, 6);
sixHourRule.minute = 0;
sixHourRule.second = 0;
const channel = Container.get(EthTickerChannel);
channel.logInfo(`🛵 Scheduling Showrunner`);

schedule.scheduleJob({ start: startTime, rule: sixHourRule }, async function() {
const taskName = 'ETH Ticker Fetch and sendMessageToContract()';
try {
await channel.sendMessageToContract(true);
logger.info(`[${new Date(Date.now())}] 🐣 Cron Task Completed -- ${taskName}`);
} catch (err) {
logger.error(`[${new Date(Date.now())}] ❌ Cron Task Failed -- ${taskName}`);
logger.error(`[${new Date(Date.now())}] Error Object: %o`, err);
}
});
};
8 changes: 8 additions & 0 deletions src/sample_showrunners/ethTicker/ethTickerKeys.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove Private key values

"PRIVATE_KEY_NEW_STANDARD": {
"PK": "0x929bd276327aea16fde459a68e2a6c2ea487685203497b597b971707164ace20",
"CHAIN_ID": "eip155:42"
},
"PRIVATE_KEY_OLD_STANDARD": "0x929bd276327aea16fde459a68e2a6c2ea487685203497b597b971707164ace20"
}

17 changes: 17 additions & 0 deletions src/sample_showrunners/ethTicker/ethTickerModel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* eslint-disable prettier/prettier */
import { model, Schema } from 'mongoose';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file won't be needed.


export interface EthTickerData {
prevEthPrice?: number;
}

const ethTickerSchema = new Schema<EthTickerData>({
_id: {
type: String,
},
prevEthPrice: {
type: Number,
},
});

export const ethTickerModel = model<EthTickerData>('ethTickerDB', ethTickerSchema);
37 changes: 37 additions & 0 deletions src/sample_showrunners/ethTicker/ethTickerRoutes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Router, Request, Response, NextFunction } from 'express';
import { Container } from 'typedi';
import EthTickerChannel from './ethTickerChannel';
import middlewares from '../../api/middlewares';
import { celebrate, Joi } from 'celebrate';
import { Logger } from 'winston';

const route = Router();

export default (app: Router) => {
app.use('/showrunners/ethticker', route);

// to add an incoming feed
route.post(
'/send_message',
celebrate({
body: Joi.object({
simulate: [Joi.bool(), Joi.object()],
}),
}),
middlewares.onlyLocalhost,
async (req: Request, res: Response, next: NextFunction) => {
const logger: Logger = Container.get('logger');
logger.debug('Calling /showrunners/ethticker endpoint with body: %o', req.body);

try {
const ethTicker = Container.get(EthTickerChannel);
const response = await ethTicker.sendMessageToContract(req.body.simulate);

return res.status(201).json(response);
} catch (e) {
logger.error('🔥 error: %o', e);
return next(e);
}
},
);
};
3 changes: 3 additions & 0 deletions src/sample_showrunners/ethTicker/ethTickerSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"cmcEndpoint": "https://pro-api.coinmarketcap.com/"
}