From d76bee10056668e119272a428a61b361a40ef453 Mon Sep 17 00:00:00 2001 From: JojoFlex1 Date: Mon, 23 Feb 2026 07:57:37 +0300 Subject: [PATCH] feat: add upcoming unlocks calendar endpoint GET /api/user/:address/calendar --- backend/src/index.js | 13 ++++++ backend/src/services/calendarService.js | 53 +++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 backend/src/services/calendarService.js diff --git a/backend/src/index.js b/backend/src/index.js index a669eec5..60fe6672 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -42,6 +42,7 @@ const discordBotService = require('./services/discordBotService'); const cacheService = require('./services/cacheService'); const tvlService = require('./services/tvlService'); const vaultExportService = require('./services/vaultExportService'); +const calendarService = require('./services/calendarService'); app.get('/', (req, res) => { res.json({ message: 'Vesting Vault API is running!' }); @@ -203,6 +204,18 @@ app.get('/api/stats/tvl', async (req, res) => { } }); +// Calendar Route +app.get('/api/user/:address/calendar', async (req, res) => { + try { + const { address } = req.params; + const unlocks = await calendarService.getUpcomingUnlocks(address); + res.json({ success: true, data: unlocks }); + } catch (error) { + console.error('Error fetching calendar:', error); + res.status(500).json({ success: false, error: error.message }); + } +}); + app.get('/api/vault/:id/export', async (req, res) => { try { const { id } = req.params; diff --git a/backend/src/services/calendarService.js b/backend/src/services/calendarService.js new file mode 100644 index 00000000..f1befceb --- /dev/null +++ b/backend/src/services/calendarService.js @@ -0,0 +1,53 @@ +const { sequelize } = require('../database/connection'); +const { QueryTypes } = require('sequelize'); + +class CalendarService { + async getUpcomingUnlocks(userAddress, days = 30) { + const today = new Date(); + const endDate = new Date(); + endDate.setDate(today.getDate() + days); + + // Fetch all vaults where user is a beneficiary + const vaults = await sequelize.query( + `SELECT v.id, v.token_address, v.total_amount, v.created_at, + b.total_allocated, b.total_withdrawn + FROM vaults v + JOIN beneficiaries b ON b.vault_id = v.id + WHERE b.address = :userAddress`, + { + replacements: { userAddress }, + type: QueryTypes.SELECT, + } + ); + + if (!vaults.length) return []; + + const unlocks = []; + + for (const vault of vaults) { + const remaining = parseFloat(vault.total_allocated) - parseFloat(vault.total_withdrawn); + if (remaining <= 0) continue; + + // Simple linear daily unlock projection over 30 days + const dailyUnlock = remaining / days; + + for (let i = 1; i <= days; i++) { + const unlockDate = new Date(today); + unlockDate.setDate(today.getDate() + i); + + unlocks.push({ + date: unlockDate.toISOString().split('T')[0], + token: vault.token_address, + amount_unlocking: parseFloat(dailyUnlock.toFixed(6)), + }); + } + } + + // Sort by date + unlocks.sort((a, b) => new Date(a.date) - new Date(b.date)); + + return unlocks; + } +} + +module.exports = new CalendarService();