Skip to content

Commit

Permalink
stats: add net flow route
Browse files Browse the repository at this point in the history
  • Loading branch information
sehyunc committed Sep 5, 2024
1 parent 565227d commit 7573d82
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 13 deletions.
4 changes: 4 additions & 0 deletions app/api/stats/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ export type BucketData = {

export const HISTORICAL_VOLUME_KEY_PREFIX = "stats:historical-volume"
export const HISTORICAL_VOLUME_SET_KEY = "stats:historical-volume:set"

// Flows

export const NET_FLOW_KEY = "net_flow_24h"
5 changes: 2 additions & 3 deletions app/api/stats/external-transfer-logs/route.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { NextRequest } from "next/server"
import { kv } from "@vercel/kv"
import {
BucketData,
ExternalTransferData,
INFLOWS_KEY,
INFLOWS_SET_KEY,
} from "@/app/api/stats/constants"
import { getAllSetMembers } from "@/app/lib/kv-utils"

import { kv } from "@vercel/kv"
import { NextRequest } from "next/server"
export const runtime = "edge"
export const dynamic = "force-dynamic"

Expand Down
31 changes: 31 additions & 0 deletions app/api/stats/net-flow/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { NET_FLOW_KEY } from "@/app/api/stats/constants"
import { kv } from "@vercel/kv"
import { NextRequest } from "next/server"

export interface NetFlowResponse {
netFlow: number
timestamp: number
}

export const runtime = "edge"
export const dynamic = "force-dynamic"

export async function GET(req: NextRequest) {
try {
const data = await kv.get<NetFlowResponse>(NET_FLOW_KEY)
if (data) {
return new Response(JSON.stringify(data), {
headers: { "Content-Type": "application/json" },
})
}
return new Response(JSON.stringify({ error: "Net flow data not available" }), {
status: 404,
headers: { "Content-Type": "application/json" },
})
} catch (error) {
return new Response(JSON.stringify({ error: "Failed to retrieve net flow data" }), {
status: 500,
headers: { "Content-Type": "application/json" },
})
}
}
61 changes: 61 additions & 0 deletions app/api/stats/set-net-flow/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { INFLOWS_KEY, INFLOWS_SET_KEY, NET_FLOW_KEY } from "@/app/api/stats/constants"
import { NetFlowResponse } from "@/app/api/stats/net-flow/route"
import { getAllSetMembers } from "@/app/lib/kv-utils"
import { kv } from "@vercel/kv"

const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000 // 24 hours in milliseconds

export async function GET() {
console.log("Starting net flow calculation cron job")
try {
const now = Date.now()
const twentyFourHoursAgo = now - TWENTY_FOUR_HOURS
console.log(`Calculating net flow from ${new Date(twentyFourHoursAgo).toISOString()} to ${new Date(now).toISOString()}`)

const transactionHashes = await getAllSetMembers(kv, INFLOWS_SET_KEY)
console.log(`Retrieved ${transactionHashes.length} transaction hashes`)

const pipeline = kv.pipeline()
transactionHashes.forEach(hash => pipeline.get(`${INFLOWS_KEY}:${hash}`))
const data = await pipeline.exec()
console.log(`Fetched data for ${data.length} transactions`)

let netFlow = 0
let validTransactions = 0
let skippedTransactions = 0

data.forEach(item => {
if (item && typeof item === "object" && "timestamp" in item && "amount" in item) {
const transfer = item as { timestamp: number; amount: number; isWithdrawal: boolean }
if (transfer.timestamp >= twentyFourHoursAgo) {
netFlow += transfer.isWithdrawal ? -transfer.amount : transfer.amount
validTransactions++
} else {
skippedTransactions++
}
}
})

console.log(`Processed ${validTransactions} valid transactions, skipped ${skippedTransactions} outdated transactions`)
console.log(`Calculated net flow: ${netFlow}`)

const response: NetFlowResponse = {
netFlow,
timestamp: now,
}

await kv.set(NET_FLOW_KEY, response)
console.log("Net flow data updated successfully")

return new Response(JSON.stringify({ message: "Net flow data updated successfully" }), {
status: 200,
headers: { "Content-Type": "application/json" },
})
} catch (error) {
console.error("Error updating net flow data:", error)
return new Response(JSON.stringify({ error: "Failed to update net flow data" }), {
status: 500,
headers: { "Content-Type": "application/json" },
})
}
}
17 changes: 17 additions & 0 deletions app/hooks/useNetFlow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { NetFlowResponse } from '@/app/api/stats/net-flow/route'
import { useQuery } from '@tanstack/react-query'

async function fetchNetFlow(): Promise<NetFlowResponse> {
const response = await fetch('/api/stats/net-flow')
if (!response.ok) {
throw new Error('Failed to fetch net flow data')
}
return response.json()
}

export function useNetFlow() {
return useQuery<NetFlowResponse, Error>({
queryKey: ['stats', 'netFlow'],
queryFn: fetchNetFlow,
})
}
14 changes: 4 additions & 10 deletions app/stats/charts/inflows-chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as React from "react"
import numeral from "numeral"
import { Bar, BarChart, CartesianGrid, XAxis } from "recharts"

import { useNetFlow } from "@/app/hooks/useNetFlow"
import { useExternalTransferLogs } from "@/app/stats/hooks/use-external-transfer-data"

import {
Expand Down Expand Up @@ -37,6 +38,7 @@ const chartConfig = {

export function InflowsChart() {
const { data } = useExternalTransferLogs()
const { data: netFlowData, isSuccess } = useNetFlow()
const chartData = React.useMemo(() => {
if (!data || !data.length) return []
return data?.map((day) => ({
Expand All @@ -46,21 +48,13 @@ export function InflowsChart() {
}))
}, [data])

const netFlow24h = React.useMemo(() => {
if (!data || !data.length) return 0
return (
data[data.length - 1].depositAmount -
data[data.length - 1].withdrawalAmount
)
}, [data])

return (
<Card className="w-full rounded-none">
<CardHeader className="flex flex-col items-stretch space-y-0 border-b p-0 sm:flex-row">
<div className="flex flex-1 flex-col justify-center gap-1 px-6 py-5 sm:py-6">
<CardTitle className={`font-serif text-4xl font-bold`}>
{netFlow24h ? (
numeral(netFlow24h).format("$0.00a")
{isSuccess ? (
numeral(netFlowData?.netFlow).format("$0.00a")
) : (
<Skeleton className="h-10 w-40" />
)}
Expand Down

0 comments on commit 7573d82

Please sign in to comment.