import create, { State } from 'zustand' import produce from 'immer' import { PublicKey } from '@solana/web3.js' import { notify } from '../utils/notifications' export type AlertProvider = 'mail' | 'sms' | 'tg' export interface Alert { acc: PublicKey alertProvider: AlertProvider collateralRatioThresh: number open: boolean timestamp: number triggeredTimestamp: number | undefined } interface AlertRequest { alertProvider: AlertProvider collateralRatioThresh: number mangoGroupPk: string marginAccountPk: string phoneNumber: any email: string | undefined } interface AlertsStore extends State { activeAlerts: Array triggeredAlerts: Array loading: boolean error: string submitting: boolean success: string tgCode: string | null set: (s: any) => void actions: { [key: string]: (any) => void } } const useAlertsStore = create((set, get) => ({ activeAlerts: [], triggeredAlerts: [], loading: false, error: '', submitting: false, success: '', tgCode: null, set: (fn) => set(produce(fn)), actions: { async createAlert(req: AlertRequest) { const set = get().set const alert = { acc: new PublicKey(req.marginAccountPk), alertProvider: req.alertProvider, collateralRatioThresh: req.collateralRatioThresh, open: true, timestamp: Date.now(), } set((state) => { state.submitting = true state.error = '' state.success = '' }) const fetchUrl = `https://mango-margin-call.herokuapp.com/alerts` const headers = { 'Content-Type': 'application/json' } fetch(fetchUrl, { method: 'POST', headers: headers, body: JSON.stringify(req), }) .then((response: any) => { if (!response.ok) { throw response } return response.json() }) .then((json: any) => { const alerts = get().activeAlerts set((state) => { state.activeAlerts = [alert as Alert].concat(alerts) state.success = json.code ? '' : 'Alert saved successfully' state.tgCode = json.code }) }) .catch((err) => { if (typeof err.text === 'function') { err.text().then((errorMessage: string) => { set((state) => { state.error = errorMessage }) notify({ message: errorMessage, type: 'error', }) }) } else { set((state) => { state.error = 'Something went wrong' }) notify({ message: 'Something went wrong', type: 'error', }) } }) .finally(() => { set((state) => { state.submitting = false }) }) }, async loadAlerts(marginAccounts: PublicKey[]) { const set = get().set set((state) => { state.loading = true }) const headers = { 'Content-Type': 'application/json' } const responses = await Promise.all( marginAccounts.map((pubkey) => { return fetch( `https://mango-margin-call.herokuapp.com/alerts/${pubkey}`, { method: 'GET', headers: headers, } ) .then((response: any) => { if (!response.ok) { throw response } return response.json() }) .catch((err) => { if (typeof err.text === 'function') { err.text().then((errorMessage: string) => { notify({ message: errorMessage, type: 'error', }) }) } else { notify({ message: 'Something went wrong', type: 'error', }) } }) }) ) // Add margin account address to alerts // Assign triggeredTimestamp for old alerts in the DB that don't have this property yet responses.forEach((accounts, index) => accounts.alerts.forEach((alert) => { alert.acc = marginAccounts[index] if (!alert.open) { alert.triggeredTimestamp ||= alert.timestamp } }) ) const flattenAccountAlerts = responses.map((acc) => acc.alerts).flat() // sort active by latest creation time first const activeAlerts = flattenAccountAlerts .filter((alert) => alert.open) .sort((a, b) => { return b.timestamp - a.timestamp }) // sort triggered by latest trigger time first const triggeredAlerts = flattenAccountAlerts .filter((alert) => !alert.open) .sort((a, b) => { return b.triggeredTimestamp - a.triggeredTimestamp }) set((state) => { state.activeAlerts = activeAlerts state.triggeredAlerts = triggeredAlerts state.loading = false }) }, }, })) export default useAlertsStore