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: daily point allocator cron job #3

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
12 changes: 8 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
"start-dnmm-actions": "apibara run --allow-env=.env src/strkfarm/dnmm/user_actions.ts --sink-id=120",
"start-dep-withdraw": "apibara run --allow-env=.env src/strkfarm/deposits-withdraws.ts --sink-id=130 --status-server-address=0.0.0.0:4130",
"start-harvests": "apibara run --allow-env=.env src/strkfarm/harvests.ts --sink-id=140 --status-server-address=0.0.0.0:4140",
"start-daily-points-allocator": "ts-node src/dailyPointsAllocator.ts",
"calculate-accumulated-points": "ts-node src/calculateAccumulatedPoints",
"postinstall": "npx prisma generate"
},
"author": "akiraonstarknet",
Expand All @@ -21,23 +23,24 @@
"@apibara/protocol": "0.4.9",
"@apibara/starknet": "0.4.0",
"@apollo/server": "^4.11.0",
"@prisma/client": "5.12.1",
"@prisma/client": "^5.21.1",
"dotenv": "16.4.5",
"express": "^4.19.2",
"graphql": "16.9.0",
"graphql-fields": "^2.0.3",
"graphql-scalars": "^1.23.0",
"node-cron": "^3.0.3",
"parquets": "0.10.10",
"prisma": "5.18.0",
"reflect-metadata": "^0.2.2",
"starknet": "6.7.0",
"tslib": "2.6.3",
"type-graphql": "^2.0.0-rc.2",
"typegraphql-prisma": "0.28.0",
"typegraphql-prisma": "^0.28.0",
"yahoo-finance2": "^2.11.3"
},
"devDependencies": {
"@eslint/js": "9.0.0",
"@types/node-cron": "^3.0.11",
"browserify": "17.0.0",
"eslint": "9.0.0",
"eslint-config-airbnb": "19.0.4",
Expand All @@ -46,8 +49,9 @@
"eslint-plugin-react": "7.28.0",
"eslint-plugin-react-hooks": "4.3.0",
"eslint-plugin-unused-imports": "3.1.0",
"prisma": "^5.21.1",
"sucrase": "3.35.0",
"ts-node": "10.9.1",
"ts-node": "^10.9.1",
"tsify": "5.0.4",
"tsx": "4.9.1",
"typescript": "5.4.5",
Expand Down
13 changes: 13 additions & 0 deletions schema.prisma → prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init

generator client {
provider = "prisma-client-js"
}
Expand Down Expand Up @@ -119,3 +125,10 @@ model harvests {

@@unique([block_number, txIndex, eventIndex], name: "event_id")
}

model user_points {
id Int @id @default(autoincrement())
owner String @unique
points Int
hold_days Int @default(0)
}
95 changes: 95 additions & 0 deletions src/calculateAccumulatedPoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();

interface TVL {
tvl: number;
}

const API_BASE_URL = "https://app.strkfarm.xyz/api/stats";

async function fetchTVL(url: string, retries = 3, delay = 1000): Promise<TVL | undefined> {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const tvl: TVL = await response.json();
return tvl;
} catch (error) {
console.error(`Failed to fetch TVL (Attempt ${attempt}):`, error);
if (attempt < retries) {
console.log(`Retrying in ${delay / 1000} seconds...`);
await new Promise((res) => setTimeout(res, delay));
}
}
}
console.error("All attempts to fetch TVL failed.");
}

async function main() {
const tvl = await fetchTVL(API_BASE_URL);
let baseMultiplier = 1;

if (!tvl) {
console.log("Invalid TVL value:", tvl);
return;
}

if (tvl.tvl < 1_000_000) {
baseMultiplier = 1.5;
} else if (tvl.tvl < 3_000_000) {
baseMultiplier = 1.25;
} else if (tvl.tvl < 5_000_000) {
baseMultiplier = 1.1;
}

const users = await prisma.investment_flows.findMany({
distinct: ["owner"],
where: { type: "deposit" },
select: { owner: true }
});

for (let user of users) {
let netDeposit = 0;
let accumulatedPoints = 0;
let hold_days = 0;

const transactions = await prisma.investment_flows.findMany({
where: { owner: user.owner },
orderBy: { timestamp: "asc" },
select: { amount: true, asset: true, type: true, timestamp: true }
});

for (let i = 0; i < transactions.length; i++) {
const tx = transactions[i];
const amount =
tx.asset === "0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8"
? parseFloat(tx.amount) / 10 ** 6
: parseFloat(tx.amount) / 10 ** 18;

if (tx.type === "deposit") {
hold_days = netDeposit < 10 ? 0 : hold_days + 1;

const currentMultiplier = baseMultiplier + 0.5 * (hold_days / 365);
accumulatedPoints += amount * currentMultiplier;

netDeposit += amount;
} else if (tx.type === "withdraw") {
netDeposit -= amount;
}
}

await prisma.user_points.upsert({
where: { owner: user.owner },
update: { points: accumulatedPoints, hold_days },
create: { owner: user.owner, points: accumulatedPoints, hold_days }
});

console.log(`Accumulated points for ${user.owner}: ${accumulatedPoints}`);
}

console.log("One-time points calculation completed.");
}

main();
142 changes: 142 additions & 0 deletions src/dailyPointsAllocator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { PrismaClient, type PrismaPromise } from "@prisma/client";
import cron from "node-cron";

const prisma = new PrismaClient();

interface TVL {
tvl: number;
}

interface UserStats {
holdingsUSD: number;
strategyWise: {
id: string;
usdValue: number;
tokenInfo: {
name: string;
symbol: string;
logo: string;
decimals: number;
displayDecimals: number;
};
amount: string;
}[];
}

const API_BASE_URL = "https://app.strkfarm.xyz/api/stats";

async function fetchTVL(url: string, retries = 3, delay = 1000): Promise<TVL | undefined> {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const tvl: TVL = await response.json();
return tvl;
} catch (error) {
console.error(`Failed to fetch TVL (Attempt ${attempt}):`, error);
if (attempt < retries) {
console.log(`Retrying in ${delay / 1000} seconds...`);
await new Promise((res) => setTimeout(res, delay));
}
}
}
console.error("All attempts to fetch TVL failed.");
}

async function fetchUserStats(address: string, retries = 3, delay = 1000): Promise<UserStats | undefined> {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
const response = await fetch(`${API_BASE_URL}/${address}`);

if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}

const stats: UserStats = await response.json();
return stats;
} catch (error) {
console.error(`Failed to fetch stats for user ${address} (Attempt ${attempt}):`, error);
await new Promise((res) => setTimeout(res, delay));
}
}
}

async function updateUserPoints(user: { owner: string }, baseMultiplier: number) {
try {
const stats = await fetchUserStats(user.owner);
if (!stats) {
console.log(`Unable to fetch stats for user: ${user.owner}`);
return;
}

let userRecord = await prisma.user_points.upsert({
where: { owner: user.owner },
update: {},
create: { owner: user.owner, points: 0 }
});

const holdDaysRecord = await prisma.user_points.findFirst({
where: { owner: user.owner },
select: { id: true, hold_days: true }
});

const currentHoldDays = holdDaysRecord?.hold_days ?? 0;
const newHoldDays = stats.holdingsUSD >= 10 ? currentHoldDays + 1 : 0;

await prisma.user_points.update({
where: { id: holdDaysRecord?.id },
data: { hold_days: newHoldDays }
});

const currentMultiplier = baseMultiplier + 0.5 * (newHoldDays / 365);
const dailyUserPoints = stats.holdingsUSD * currentMultiplier;

const updatedPoints = (userRecord.points ?? 0) + dailyUserPoints;

await prisma.user_points.update({
where: { id: userRecord.id },
data: { points: updatedPoints }
});
} catch (error) {
console.error("Error updating user points:", error);
}
}

async function main() {
const tvl = await fetchTVL(API_BASE_URL);
let baseMultiplier = 1;

if (!tvl) {
console.log("Invalid TVL value:", tvl);
return;
}

if (tvl.tvl < 1_000_000) {
baseMultiplier = 1.5;
} else if (tvl.tvl < 3_000_000) {
baseMultiplier = 1.25;
} else if (tvl.tvl < 5_000_000) {
baseMultiplier = 1.1;
}

const users = await prisma.investment_flows.findMany({
distinct: ["owner"],
where: { type: "deposit" },
select: { owner: true, timestamp: true }
});

if (!users.length) {
console.log("No users found");
return;
}

for (let user of users) {
await updateUserPoints(user, baseMultiplier);
}

console.log("Daily points allocation completed.");
}

cron.schedule("0 1 * * *", main);
Loading