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

feat(mfi-notifications): liquidation notifications #564

Closed
wants to merge 37 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
bc1df85
feat: wallet UI updates, accordion with tokens and notifications plac…
chambaz Jan 24, 2024
7407c5f
feat: wallet accordion for tokens / notifications
chambaz Jan 25, 2024
cdf6a58
feat: add points to wallet
chambaz Jan 30, 2024
29b7e8d
feat: new notifications app, NextJS install with placeholder webhook …
chambaz Jan 30, 2024
326e8e5
feat: install / configure firebase, check for user in webhook and pri…
chambaz Jan 30, 2024
8564d49
feat: install / configure resend and fire email notification
chambaz Jan 30, 2024
84c0738
feat: remove product updates / add ybx updates
chambaz Jan 30, 2024
d22826c
feat: add interval check to send notifications every 24 hours
chambaz Jan 30, 2024
8509fdf
chore: remove home page, layout, styles
chambaz Jan 30, 2024
e6494f2
fix: notifications webhook http response codes
chambaz Jan 30, 2024
9464a99
chore: move notifications app inside parent folder with new account-h…
chambaz Feb 21, 2024
9f24139
feat: load bank metadata / accounts and start account health websocke…
chambaz Feb 21, 2024
7004e8c
feat: v1 notifications poller / webhook system
chambaz Feb 21, 2024
37a1117
chore: fix conflicts from main rebase
chambaz Feb 22, 2024
82c2da4
feat: move firebase / resend logic into poller app, add redis cache f…
chambaz Feb 22, 2024
a5fa36d
fix: firebase / resend
chambaz Feb 22, 2024
39ecb18
feat: remove webhooks, combine into single notifications app
chambaz Feb 22, 2024
b9874f7
chore: gitignore redis dump
chambaz Feb 22, 2024
bc989d1
chore: notifications readme update
chambaz Feb 22, 2024
3e835f4
feat: notifications pg implementation
chambaz Mar 1, 2024
2333933
chore: yarn clean / install
chambaz Mar 1, 2024
e59ea23
feat: margin-api app, fresh nextjs install, basic notifications get r…
chambaz Mar 4, 2024
f64e869
feat: marginfi-api post request for upseting notification settings
chambaz Mar 4, 2024
28f1f54
feat: add api key checks to marginfi api get / post request handlers
chambaz Mar 4, 2024
21e1dce
feat: make all fields optional in marginfi-api upsert
chambaz Mar 4, 2024
8fd9f12
feat: replace firebase / redis with new api in notifications poller
chambaz Mar 4, 2024
fa55b43
feat: wip update ui to use new marginfi api
chambaz Mar 5, 2024
89ff17e
chore: react / node version matching
chambaz Mar 6, 2024
7cc2235
chore: remove old pg env vars / clean up
chambaz Mar 6, 2024
a642c07
fix: add trezor wallet adapter resolution to package.json
chambaz Mar 6, 2024
62aaba7
feat: add api key to notification settings req
chambaz Mar 6, 2024
cdc0543
feat: handle no notification settings found / fix ybx updates checkbox
chambaz Mar 7, 2024
3e11bd2
feat: add clear notification settings function
chambaz Mar 7, 2024
33c5122
feat: redesign email template, update to prod resend settings
chambaz Mar 7, 2024
3808695
chore: prod ready email updates
chambaz Mar 7, 2024
2d67676
chore: inline styles
chambaz Mar 7, 2024
1693626
chore: api / notifications config
chambaz Mar 7, 2024
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
14 changes: 14 additions & 0 deletions api.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM node:lts-bullseye

# WORKDIR /workspace
# Create and change to the app directory.
WORKDIR /app

COPY . .

RUN yarn install
RUN yarn build

WORKDIR /app/apps/marginfi-api

CMD "./scripts/start.sh"
3 changes: 3 additions & 0 deletions apps/marginfi-api/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
36 changes: 36 additions & 0 deletions apps/marginfi-api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
36 changes: 36 additions & 0 deletions apps/marginfi-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
4 changes: 4 additions & 0 deletions apps/marginfi-api/next.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};

export default nextConfig;
26 changes: 26 additions & 0 deletions apps/marginfi-api/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "@mrgnlabs/marginfi-api",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next": "14.1.1",
"pg": "^8.11.3",
"react": "18.2.0",
"react-dom": "18.2.0"
},
"devDependencies": {
"@mrgnlabs/eslint-config-custom": "*",
"@mrgnlabs/tsconfig": "*",
"@types/node": "18.11.18",
"@types/react": "^18.2.61",
"@types/react-dom": "^18.2.19",
"eslint": "8.30.0",
"typescript": "4.9.4"
}
}
16 changes: 16 additions & 0 deletions apps/marginfi-api/scripts/pm2.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const path = require("path");

module.exports = [
{
name: "fetcher",
script: path.join(__dirname, "../dist/rpcFetcher.js"),
instances: 1,
exec_mode: "fork",
},
{
name: "api",
script: path.join(__dirname, "../.next/index.js"),
wait_ready: false,
listen_timeout: 5000,
},
];
4 changes: 4 additions & 0 deletions apps/marginfi-api/scripts/start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
while true; do
yarn start
sleep 1 # Sleep for 1 second before restarting, adjust as needed
done
129 changes: 129 additions & 0 deletions apps/marginfi-api/src/app/notifications/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { NextResponse } from "next/server";

import db from "~/lib/pg";

export const dynamic = "force-dynamic";

export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const wallet = searchParams.get("wallet_address");
const apiKey = request.headers.get("X-API-KEY");

if (!apiKey || apiKey !== process.env.MARGINFI_API_KEY) {
console.error("Invalid or missing API key");
return NextResponse.json(
{ error: "Invalid or missing API key" },
{
status: 401,
}
);
}

if (!wallet) {
console.error("Missing wallet address parameter");
return NextResponse.json(
{ error: "Missing wallet address parameter" },
{
status: 400,
}
);
}

try {
const queryText = "SELECT * FROM notification_settings WHERE wallet_address = $1";
const { rows } = await db.query(queryText, [wallet]);

if (rows.length === 0) {
console.error("Notification settings not found");
return NextResponse.json(
{ error: "Notification settings not found" },
{
status: 404,
}
);
}

return NextResponse.json(rows[0], {
status: 200,
});
} catch (error) {
console.error("Database error:", error);
return NextResponse.json(
{ error: "Internal server error" },
{
status: 500,
}
);
}
}

export async function POST(request: Request) {
const data = await request.json();
const apiKey = request.headers.get("X-API-KEY");

if (!apiKey || apiKey !== process.env.MARGINFI_API_KEY) {
return NextResponse.json({ error: "Invalid or missing API key" }, { status: 401 });
}

if (!data.wallet_address) {
return NextResponse.json({ error: "Missing wallet address parameter" }, { status: 400 });
}

// Start constructing the query dynamically
let fields: string[] = ["wallet_address"];
let values: string[] = [data.wallet_address];
let params: string[] = ["$1"];
let updateSets: string[] = [];
let counter: number = 2;

// Dynamically add fields that are present
["email", "account_health", "ybx_updates", "last_notification"].forEach((field) => {
if (data.hasOwnProperty(field)) {
fields.push(field);
values.push(data[field]);
params.push(`$${counter}`);
updateSets.push(`${field} = EXCLUDED.${field}`);
counter++;
}
});

const queryText = `
INSERT INTO notification_settings (${fields.join(", ")})
VALUES (${params.join(", ")})
ON CONFLICT (wallet_address)
DO UPDATE SET ${updateSets.join(", ")};
`;

try {
await db.query(queryText, values);
return NextResponse.json({ success: true }, { status: 200 });
} catch (error) {
console.error("Database error:", error);
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
}
}

export async function DELETE(request: Request) {
const { searchParams } = new URL(request.url);
const wallet = searchParams.get("wallet_address");
const apiKey = request.headers.get("X-API-KEY");

if (!apiKey || apiKey !== process.env.MARGINFI_API_KEY) {
return NextResponse.json({ error: "Invalid or missing API key" }, { status: 401 });
}

if (!wallet) {
return NextResponse.json({ error: "Missing wallet address parameter" }, { status: 400 });
}

const queryText = `DELETE FROM notification_settings WHERE wallet_address = '${wallet}'`;

try {
const res = await db.query(queryText);
console.log(res);
return NextResponse.json({ success: true }, { status: 200 });
} catch (error) {
console.error("Database error:", error);
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
}
}
11 changes: 11 additions & 0 deletions apps/marginfi-api/src/lib/pg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Pool } from "pg";

const pool = new Pool({
host: process.env.PG_HOST,
database: process.env.PG_DATABASE,
user: process.env.PG_USER,
password: process.env.PG_PASSWORD,
port: parseInt(process.env.PG_PORT || "5432"),
});

export default pool;
28 changes: 28 additions & 0 deletions apps/marginfi-api/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"extends": "@mrgnlabs/tsconfig/nextjs.json",
"compilerOptions": {
"jsx": "preserve",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"downlevelIteration": true,
"paths": {
"~/*": [
"./src/*"
]
},
"plugins": [
{
"name": "next"
}
]
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}
35 changes: 35 additions & 0 deletions apps/marginfi-notifications/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Solana Configuration
WALLET_KEYPAIR=~/.config/solana/id.json
RPC_ENDPOINT=https://api.mainnet-beta.solana.com

# Marginfi Configuration
MRGN_ENV=production
MARGINFI_ACCOUNT_WHITELIST=
MARGINFI_ACCOUNT_BLACKLIST=
HEALTH_FACTOR_THRESHOLD=0.95
SLEEP_INTERVAL_SECONDS=5
WS_RESET_INTERVAL_SECONDS=300

# API Configuration
MARGINFI_API_URL=https://yourapi.example.com
MARGINFI_API_KEY=your_api_key_here

# PostgreSQL Database Configuration
PG_USER=postgres_user
PG_PASSWORD=postgres_password
PG_HOST=localhost
PG_PORT=5432
PG_DATABASE=postgres_db

# Resend Configuration
RESEND_API_KEY=

# Marginfi API key
MARGINFI_API_KEY=

# Sentry Configuration (Optional)
SENTRY=false
SENTRY_DSN=

# WebSocket Endpoint (Optional)
WS_ENDPOINT=wss://api.example.com
5 changes: 5 additions & 0 deletions apps/marginfi-notifications/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
dist
.env
!.env.example
dump.rdb
1 change: 1 addition & 0 deletions apps/marginfi-notifications/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# marginfi notifications
Loading
Loading