223 lines
5.6 KiB
TypeScript
223 lines
5.6 KiB
TypeScript
import Koa from "koa"
|
|
import Router from "koa-router"
|
|
import mongo from "koa-mongo"
|
|
import bodyParser from "koa-bodyparser"
|
|
import cors from "@koa/cors"
|
|
import * as cron from "node-cron"
|
|
import { MongoClient, ObjectId } from "mongodb"
|
|
|
|
import {
|
|
Cluster,
|
|
Config,
|
|
GroupConfig,
|
|
MangoClient,
|
|
MangoAccount,
|
|
IDS,
|
|
} from "@blockworks-foundation/mango-client"
|
|
import { Commitment, Connection, PublicKey } from "@solana/web3.js"
|
|
|
|
import { UserError } from "./errors"
|
|
// import { sendLogsToDiscord } from "./logger"
|
|
import {
|
|
// initiateTelegramBot,
|
|
// generateTelegramCode,
|
|
validateMangoAccount,
|
|
// validatePhoneNumber,
|
|
validateEmail,
|
|
reduceMangoGroups,
|
|
sendAlert,
|
|
} from "./utils"
|
|
import config from "./environment"
|
|
|
|
const MESSAGE = "Your health ratio is at or below @ratio@% \n"
|
|
|
|
const app = new Koa()
|
|
const router = new Router()
|
|
|
|
// const rpcUrl = "https://mango.rpcpool.com/946ef7337da3f5b8d3e4a34e7f88"
|
|
|
|
const clientConfig = new Config(IDS)
|
|
|
|
const cluster = (process.env.CLUSTER || "mainnet") as Cluster
|
|
const groupName = process.env.GROUP || "mainnet.1"
|
|
const groupIds = clientConfig.getGroup(cluster, groupName)
|
|
if (!groupIds) {
|
|
throw new Error(`Group ${groupName} not found`)
|
|
}
|
|
|
|
const mangoProgramId = groupIds.mangoProgramId
|
|
// const mangoGroupKey = groupIds.publicKey
|
|
|
|
const connection = new Connection(
|
|
process.env.ENDPOINT_URL || clientConfig.cluster_urls[cluster],
|
|
"processed" as Commitment
|
|
)
|
|
|
|
const client = new MangoClient(connection, mangoProgramId)
|
|
|
|
let db: any
|
|
|
|
app.use(cors())
|
|
app.use(bodyParser())
|
|
app.use(
|
|
mongo(
|
|
{
|
|
uri: config.dbConnectionString,
|
|
},
|
|
{ useUnifiedTopology: true }
|
|
)
|
|
)
|
|
|
|
// initiateTelegramBot()
|
|
|
|
router.post("/alerts", async (ctx, next) => {
|
|
try {
|
|
const alert: any = ctx.request.body
|
|
// await validateMangoAccount(client, alert)
|
|
if (alert.alertProvider == "mail") {
|
|
validateEmail(alert.email)
|
|
ctx.body = { status: "success" }
|
|
} else {
|
|
throw new UserError("Invalid alert provider")
|
|
}
|
|
alert.open = true
|
|
alert.timestamp = Date.now()
|
|
ctx.db.collection("alerts").insertOne(alert)
|
|
} catch (e: any) {
|
|
let errorMessage = "Something went wrong"
|
|
if (e.name == "UserError") {
|
|
errorMessage = e.message
|
|
} else {
|
|
// sendLogsToDiscord(null, e)
|
|
}
|
|
ctx.throw(400, errorMessage)
|
|
}
|
|
await next()
|
|
})
|
|
|
|
router.post("/delete-alert", async (ctx, next) => {
|
|
try {
|
|
const id: any = new ObjectId(ctx.request.body.id)
|
|
if (id) {
|
|
ctx.body = { status: "success" }
|
|
}
|
|
ctx.db.collection("alerts").deleteOne({ _id: id })
|
|
} catch (e: any) {
|
|
let errorMessage = "Something went wrong"
|
|
if (e.name == "UserError") {
|
|
errorMessage = e.message
|
|
}
|
|
ctx.throw(400, errorMessage)
|
|
}
|
|
await next()
|
|
})
|
|
|
|
router.get("/alerts/:mangoAccountPk", async (ctx, next) => {
|
|
try {
|
|
const { mangoAccountPk } = ctx.params
|
|
if (!mangoAccountPk) {
|
|
throw new UserError("Missing margin account")
|
|
}
|
|
const alerts = await ctx.db
|
|
.collection("alerts")
|
|
.find(
|
|
{ mangoAccountPk },
|
|
{
|
|
projection: {
|
|
_id: 1,
|
|
health: 1,
|
|
alertProvider: 1,
|
|
open: 1,
|
|
timestamp: 1,
|
|
triggeredTimestamp: 1,
|
|
},
|
|
}
|
|
)
|
|
.toArray()
|
|
ctx.body = { alerts }
|
|
} catch (e: any) {
|
|
let errorMessage = "Something went wrong"
|
|
if (e.name == "UserError") {
|
|
errorMessage = e.message
|
|
} else {
|
|
// sendLogsToDiscord(null, e)
|
|
}
|
|
ctx.throw(400, errorMessage)
|
|
}
|
|
})
|
|
|
|
app.use(router.allowedMethods())
|
|
app.use(router.routes())
|
|
|
|
app.listen(config.port, () => {
|
|
const readyMessage = `> Server ready on http://localhost:${config.port}`
|
|
console.log(readyMessage)
|
|
// sendLogsToDiscord(readyMessage, null)
|
|
})
|
|
|
|
const handleAlert = async (alert: any, db: any) => {
|
|
try {
|
|
const mangoAccountPk = new PublicKey(alert.mangoAccountPk)
|
|
const mangoGroupPk = new PublicKey(alert.mangoGroupPk)
|
|
const mangoGroup = await client.getMangoGroup(mangoGroupPk)
|
|
const mangoCache = await mangoGroup.loadCache(connection)
|
|
const mangoAccount = await client.getMangoAccount(
|
|
mangoAccountPk,
|
|
mangoGroup.dexProgramId
|
|
)
|
|
const health = await mangoAccount.getHealthRatio(
|
|
mangoGroup,
|
|
mangoCache,
|
|
"Maint"
|
|
)
|
|
if (health.toNumber() <= parseFloat(alert.health)) {
|
|
let message = MESSAGE.replace("@ratio@", alert.health)
|
|
message += mangoAccount.name || alert.mangoAccountPk
|
|
message += "\nVisit https://trade.mango.markets/"
|
|
const alertSent = await sendAlert(alert, message)
|
|
if (alertSent) {
|
|
db.collection("alerts").updateOne(
|
|
{ _id: new ObjectId(alert._id) },
|
|
{ $set: { open: false, triggeredTimestamp: Date.now() } }
|
|
)
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.log(e)
|
|
// sendLogsToDiscord(null, e)
|
|
}
|
|
}
|
|
|
|
const runCron = async () => {
|
|
const uri = config.dbConnectionString
|
|
const mongoClient = new MongoClient(uri, {
|
|
useNewUrlParser: true,
|
|
useUnifiedTopology: true,
|
|
})
|
|
const mongoConnection = await mongoClient.connect()
|
|
if (!db) db = mongoConnection.db("mango")
|
|
cron.schedule("* * * * *", async () => {
|
|
try {
|
|
const alerts: any[] = await db
|
|
.collection("alerts")
|
|
.find({ open: true })
|
|
.toArray()
|
|
console.log(alerts)
|
|
// const uniqueMangoGroupPks: string[] = [
|
|
// ...new Set(alerts.map((alert) => alert.mangoGroupPk)),
|
|
// ]
|
|
// const mangoGroups: any = await reduceMangoGroups(
|
|
// client,
|
|
// uniqueMangoGroupPks
|
|
// )
|
|
alerts.forEach(async (alert) => {
|
|
handleAlert(alert, db)
|
|
})
|
|
} catch (e) {
|
|
// sendLogsToDiscord(null, e)
|
|
}
|
|
})
|
|
}
|
|
|
|
runCron()
|