-
Notifications
You must be signed in to change notification settings - Fork 14
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
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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); | ||
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( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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'}`); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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}`)); | ||
}); | ||
} | ||
} |
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); | ||
} | ||
}); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/* eslint-disable prettier/prettier */ | ||
import { model, Schema } from 'mongoose'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); |
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); | ||
} | ||
}, | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"cmcEndpoint": "https://pro-api.coinmarketcap.com/" | ||
} |
There was a problem hiding this comment.
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].