From a5cc3e1a2d886c4e48c6cee40baac12e05cbb9ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Pradel?= Date: Sat, 30 Mar 2024 11:47:05 +0100 Subject: [PATCH 1/3] feat: add stacking dao amounts / month --- src/app/protocols/[protocol]/page.tsx | 7 +++ .../UniqueUsersBarChartClient.tsx | 45 +++++++++++++++++++ .../DepositsWithdrawBarChart/index.tsx | 30 +++++++++++++ 3 files changed, 82 insertions(+) create mode 100644 src/components/Stats/stackingdao/DepositsWithdrawBarChart/UniqueUsersBarChartClient.tsx create mode 100644 src/components/Stats/stackingdao/DepositsWithdrawBarChart/index.tsx diff --git a/src/app/protocols/[protocol]/page.tsx b/src/app/protocols/[protocol]/page.tsx index a42df50..9d72d5a 100644 --- a/src/app/protocols/[protocol]/page.tsx +++ b/src/app/protocols/[protocol]/page.tsx @@ -1,4 +1,5 @@ import { UniqueUsersBarChart } from "@/components/Stats/UniqueUsersBarChart"; +import { DepositWithdrawBarChart } from "@/components/Stats/stackingdao/DepositsWithdrawBarChart"; import { TransactionRow } from "@/components/Transaction/TransactionRow"; import { getTransactions, getTransactionsStats } from "@/db/transactions"; import { @@ -120,6 +121,12 @@ export default async function ProtocolPage({ + {protocol === "stackingdao" ? ( + + + + ) : null} +
Transactions diff --git a/src/components/Stats/stackingdao/DepositsWithdrawBarChart/UniqueUsersBarChartClient.tsx b/src/components/Stats/stackingdao/DepositsWithdrawBarChart/UniqueUsersBarChartClient.tsx new file mode 100644 index 0000000..d36086a --- /dev/null +++ b/src/components/Stats/stackingdao/DepositsWithdrawBarChart/UniqueUsersBarChartClient.tsx @@ -0,0 +1,45 @@ +"use client"; +import { BarChart } from "@/components/ui/BarChart"; +import type { Protocol } from "@/lib/protocols"; +import { Card, Inset, Separator, Text } from "@radix-ui/themes"; + +interface UniqueUsersBarChartProps { + protocol: Protocol; + data: { + month: string; + uniqueSenders: number; + }[]; +} + +export const UniqueUsersBarChartClient = ({ + protocol, + data, +}: UniqueUsersBarChartProps) => { + const formattedData: { + date: string; + }[] = data.map((d) => ({ + date: d.month, + [protocol]: d.uniqueSenders, + })); + + return ( + + + Unique users + + + Total unique addresses per month + + + + + + + ); +}; diff --git a/src/components/Stats/stackingdao/DepositsWithdrawBarChart/index.tsx b/src/components/Stats/stackingdao/DepositsWithdrawBarChart/index.tsx new file mode 100644 index 0000000..5b6c02b --- /dev/null +++ b/src/components/Stats/stackingdao/DepositsWithdrawBarChart/index.tsx @@ -0,0 +1,30 @@ +import { db } from "@/db/db"; +import { transactionTable } from "@/db/schema"; +import { countDistinct, eq, sql } from "drizzle-orm"; +import { UniqueUsersBarChartClient } from "./UniqueUsersBarChartClient"; + +const getData = async () => { + const query = db + .select({ + month: sql`strftime('%Y-%m', timestamp, 'unixepoch') as month`, + deposits: sql`sum(case when action = 'stackingdao-deposit' then 1 else 0 end) as deposits`, + depositsAmount: sql`sum(case when action = 'stackingdao-deposit' then json->>'outAmount' else 0 end) as depositsAmount`, + withdrawals: sql`sum(case when action = 'stackingdao-withdraw' then 1 else 0 end) as withdrawals`, + withdrawalsAmount: sql`sum(case when action = 'stackingdao-withdraw' then json->>'inAmount' else 0 end) as withdrawalsAmount`, + }) + .from(transactionTable) + .where(eq(transactionTable.protocol, "stackingdao")) + .groupBy(sql`month`); + + const stats = await query; + return stats; +}; + +export const DepositWithdrawBarChart = async () => { + const stats = await getData(); + console.log(stats); + + return
toto
; + + // return ; +}; From 525044d4eb0c5278465576b32980e8fb041161b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Pradel?= Date: Sat, 30 Mar 2024 12:11:29 +0100 Subject: [PATCH 2/3] new chart --- .../arkadiko/add-liquidity/route.ts | 2 +- .../arkadiko/remove-liquidity/route.ts | 2 +- .../chainhooks/stackingdao/deposit/route.ts | 2 +- .../chainhooks/stackingdao/withdraw/route.ts | 2 +- src/app/api/chainhooks/swap/route.ts | 2 +- .../UniqueUsersBarChartClient.tsx | 2 + ....tsx => DepositWithdrawBarChartClient.tsx} | 33 +++++------ .../DepositsWithdrawBarChart/index.tsx | 19 +++--- src/components/ui/BarChart.tsx | 9 ++- src/components/ui/utils.ts | 12 ++++ src/db/token.ts | 55 ++++++++++++++++++ src/lib/currencies.ts | 58 +------------------ 12 files changed, 106 insertions(+), 92 deletions(-) rename src/components/Stats/stackingdao/DepositsWithdrawBarChart/{UniqueUsersBarChartClient.tsx => DepositWithdrawBarChartClient.tsx} (52%) create mode 100644 src/db/token.ts diff --git a/src/app/api/chainhooks/arkadiko/add-liquidity/route.ts b/src/app/api/chainhooks/arkadiko/add-liquidity/route.ts index bc38c83..22429d8 100644 --- a/src/app/api/chainhooks/arkadiko/add-liquidity/route.ts +++ b/src/app/api/chainhooks/arkadiko/add-liquidity/route.ts @@ -1,11 +1,11 @@ import { db } from "@/db/db"; import { type InsertTransaction, transactionTable } from "@/db/schema"; +import { getOrInsertToken } from "@/db/token"; import type { ChainhookPayload, ChainhookReceiptEventFTTransferEvent, ChainhookReceiptEventSTXTransferEvent, } from "@/lib/chainhooks"; -import { getOrInsertToken } from "@/lib/currencies"; export const dynamic = "force-dynamic"; diff --git a/src/app/api/chainhooks/arkadiko/remove-liquidity/route.ts b/src/app/api/chainhooks/arkadiko/remove-liquidity/route.ts index 682e3a8..f1d0e6f 100644 --- a/src/app/api/chainhooks/arkadiko/remove-liquidity/route.ts +++ b/src/app/api/chainhooks/arkadiko/remove-liquidity/route.ts @@ -1,11 +1,11 @@ import { db } from "@/db/db"; import { type InsertTransaction, transactionTable } from "@/db/schema"; +import { getOrInsertToken } from "@/db/token"; import type { ChainhookPayload, ChainhookReceiptEventFTTransferEvent, ChainhookReceiptEventSTXTransferEvent, } from "@/lib/chainhooks"; -import { getOrInsertToken } from "@/lib/currencies"; export const dynamic = "force-dynamic"; diff --git a/src/app/api/chainhooks/stackingdao/deposit/route.ts b/src/app/api/chainhooks/stackingdao/deposit/route.ts index 9ba5530..696ec1c 100644 --- a/src/app/api/chainhooks/stackingdao/deposit/route.ts +++ b/src/app/api/chainhooks/stackingdao/deposit/route.ts @@ -1,12 +1,12 @@ import { db } from "@/db/db"; import { type InsertTransaction, transactionTable } from "@/db/schema"; +import { getOrInsertToken } from "@/db/token"; import { conflictUpdateSetAllColumns } from "@/db/utils"; import type { ChainhookPayload, ChainhookReceiptEventFTMintEvent, ChainhookReceiptEventSTXTransferEvent, } from "@/lib/chainhooks"; -import { getOrInsertToken } from "@/lib/currencies"; export const dynamic = "force-dynamic"; diff --git a/src/app/api/chainhooks/stackingdao/withdraw/route.ts b/src/app/api/chainhooks/stackingdao/withdraw/route.ts index b628f17..11c55d3 100644 --- a/src/app/api/chainhooks/stackingdao/withdraw/route.ts +++ b/src/app/api/chainhooks/stackingdao/withdraw/route.ts @@ -1,12 +1,12 @@ import { db } from "@/db/db"; import { type InsertTransaction, transactionTable } from "@/db/schema"; +import { getOrInsertToken } from "@/db/token"; import { conflictUpdateSetAllColumns } from "@/db/utils"; import type { ChainhookPayload, ChainhookReceiptEventFTBurnEvent, ChainhookReceiptEventSTXTransferEvent, } from "@/lib/chainhooks"; -import { getOrInsertToken } from "@/lib/currencies"; export const dynamic = "force-dynamic"; diff --git a/src/app/api/chainhooks/swap/route.ts b/src/app/api/chainhooks/swap/route.ts index 0645411..3dc6a6f 100644 --- a/src/app/api/chainhooks/swap/route.ts +++ b/src/app/api/chainhooks/swap/route.ts @@ -1,12 +1,12 @@ import { db } from "@/db/db"; import { type InsertTransaction, transactionTable } from "@/db/schema"; +import { getOrInsertToken } from "@/db/token"; import { conflictUpdateSetAllColumns } from "@/db/utils"; import type { ChainhookPayload, ChainhookReceiptEventFTTransferEvent, ChainhookReceiptEventSTXTransferEvent, } from "@/lib/chainhooks"; -import { getOrInsertToken } from "@/lib/currencies"; import type { Protocol } from "@/lib/protocols"; export const dynamic = "force-dynamic"; diff --git a/src/components/Stats/UniqueUsersBarChart/UniqueUsersBarChartClient.tsx b/src/components/Stats/UniqueUsersBarChart/UniqueUsersBarChartClient.tsx index d36086a..32dc963 100644 --- a/src/components/Stats/UniqueUsersBarChart/UniqueUsersBarChartClient.tsx +++ b/src/components/Stats/UniqueUsersBarChart/UniqueUsersBarChartClient.tsx @@ -1,5 +1,6 @@ "use client"; import { BarChart } from "@/components/ui/BarChart"; +import { numberValueFormatter } from "@/components/ui/utils"; import type { Protocol } from "@/lib/protocols"; import { Card, Inset, Separator, Text } from "@radix-ui/themes"; @@ -39,6 +40,7 @@ export const UniqueUsersBarChartClient = ({ index="date" categories={[protocol]} colors={["orange"]} + valueFormatter={numberValueFormatter} /> ); diff --git a/src/components/Stats/stackingdao/DepositsWithdrawBarChart/UniqueUsersBarChartClient.tsx b/src/components/Stats/stackingdao/DepositsWithdrawBarChart/DepositWithdrawBarChartClient.tsx similarity index 52% rename from src/components/Stats/stackingdao/DepositsWithdrawBarChart/UniqueUsersBarChartClient.tsx rename to src/components/Stats/stackingdao/DepositsWithdrawBarChart/DepositWithdrawBarChartClient.tsx index d36086a..830a794 100644 --- a/src/components/Stats/stackingdao/DepositsWithdrawBarChart/UniqueUsersBarChartClient.tsx +++ b/src/components/Stats/stackingdao/DepositsWithdrawBarChart/DepositWithdrawBarChartClient.tsx @@ -1,44 +1,37 @@ "use client"; import { BarChart } from "@/components/ui/BarChart"; -import type { Protocol } from "@/lib/protocols"; +import { displayPrice } from "@/lib/currencies"; import { Card, Inset, Separator, Text } from "@radix-ui/themes"; -interface UniqueUsersBarChartProps { - protocol: Protocol; +interface DepositWithdrawBarChartClientProps { data: { - month: string; - uniqueSenders: number; + date: string; + withdrawals: number; + deposits: number; }[]; } -export const UniqueUsersBarChartClient = ({ - protocol, +export const DepositWithdrawBarChartClient = ({ data, -}: UniqueUsersBarChartProps) => { - const formattedData: { - date: string; - }[] = data.map((d) => ({ - date: d.month, - [protocol]: d.uniqueSenders, - })); - +}: DepositWithdrawBarChartClientProps) => { return ( - Unique users + Deposits and Withdrawals in STX - Total unique addresses per month + Amount of deposits and withdrawals made by users per month displayPrice(value, 6)} /> ); diff --git a/src/components/Stats/stackingdao/DepositsWithdrawBarChart/index.tsx b/src/components/Stats/stackingdao/DepositsWithdrawBarChart/index.tsx index 5b6c02b..5bdf369 100644 --- a/src/components/Stats/stackingdao/DepositsWithdrawBarChart/index.tsx +++ b/src/components/Stats/stackingdao/DepositsWithdrawBarChart/index.tsx @@ -1,15 +1,13 @@ import { db } from "@/db/db"; import { transactionTable } from "@/db/schema"; -import { countDistinct, eq, sql } from "drizzle-orm"; -import { UniqueUsersBarChartClient } from "./UniqueUsersBarChartClient"; +import { eq, sql } from "drizzle-orm"; +import { DepositWithdrawBarChartClient } from "./DepositWithdrawBarChartClient"; const getData = async () => { const query = db .select({ month: sql`strftime('%Y-%m', timestamp, 'unixepoch') as month`, - deposits: sql`sum(case when action = 'stackingdao-deposit' then 1 else 0 end) as deposits`, depositsAmount: sql`sum(case when action = 'stackingdao-deposit' then json->>'outAmount' else 0 end) as depositsAmount`, - withdrawals: sql`sum(case when action = 'stackingdao-withdraw' then 1 else 0 end) as withdrawals`, withdrawalsAmount: sql`sum(case when action = 'stackingdao-withdraw' then json->>'inAmount' else 0 end) as withdrawalsAmount`, }) .from(transactionTable) @@ -22,9 +20,16 @@ const getData = async () => { export const DepositWithdrawBarChart = async () => { const stats = await getData(); - console.log(stats); - return
toto
; + const formattedData: { + date: string; + withdrawals: number; + deposits: number; + }[] = stats.map((d) => ({ + date: d.month, + withdrawals: d.withdrawalsAmount, + deposits: d.depositsAmount, + })); - // return ; + return ; }; diff --git a/src/components/ui/BarChart.tsx b/src/components/ui/BarChart.tsx index 64aa5ca..f7416e0 100644 --- a/src/components/ui/BarChart.tsx +++ b/src/components/ui/BarChart.tsx @@ -9,7 +9,7 @@ import { XAxis, YAxis, } from "recharts"; -import { constructCategoryColors } from "./utils"; +import { constructCategoryColors, defaultValueFormatter } from "./utils"; interface BarChartProps { className?: string; @@ -18,6 +18,7 @@ interface BarChartProps { categories: string[]; stack?: boolean; colors: string[]; + valueFormatter?: (value: number | string) => string; } export const BarChart = ({ @@ -27,6 +28,7 @@ export const BarChart = ({ categories, stack, colors, + valueFormatter = defaultValueFormatter, }: BarChartProps) => { const categoryColors = constructCategoryColors(categories, colors); @@ -64,8 +66,9 @@ export const BarChart = ({ stroke="" className="fill-gray-11 text-1" tickMargin={8} + tickFormatter={valueFormatter} /> - cursor={{ fill: "var(--gray-11)", opacity: "0.10" }} content={({ label, payload }) => (
@@ -87,7 +90,7 @@ export const BarChart = ({ {p.name}:{" "} - {p.value?.toLocaleString("en-US")} + {p.value ? valueFormatter(p.value) : ""}
diff --git a/src/components/ui/utils.ts b/src/components/ui/utils.ts index cd10eac..3b5df9a 100644 --- a/src/components/ui/utils.ts +++ b/src/components/ui/utils.ts @@ -10,6 +10,10 @@ export const themeColors: { fillColor: "fill-orange-9", bgColor: "bg-orange-9", }, + indigo: { + fillColor: "fill-indigo-9", + bgColor: "bg-indigo-9", + }, }; export const constructCategoryColors = ( @@ -22,3 +26,11 @@ export const constructCategoryColors = ( }); return categoryColors; }; + +export const defaultValueFormatter = (value: number | string) => { + return value.toString(); +}; + +export const numberValueFormatter = (value: number | string) => { + return value.toLocaleString("en-US"); +}; diff --git a/src/db/token.ts b/src/db/token.ts new file mode 100644 index 0000000..bb2b849 --- /dev/null +++ b/src/db/token.ts @@ -0,0 +1,55 @@ +import { callReadOnlyFunction, cvToValue } from "@stacks/transactions"; +import { eq } from "drizzle-orm"; +import { db } from "./db"; +import { tokenTable } from "./schema"; + +/** + * Get a token from the database or insert it if it doesn't exist. + */ +export const getOrInsertToken = async (tokenId: string) => { + const tokens = await db + .select() + .from(tokenTable) + .where(eq(tokenTable.id, tokenId)); + let token = tokens[0]; + if (!token) { + // Special case for STX + if (tokenId === "STX") { + token = { + id: "STX", + symbol: "STX", + decimals: 6, + }; + await db.insert(tokenTable).values(token); + return; + } + + const smartContract = tokenId.split("::")[0]; + const [contractAddress, contractName] = smartContract.split("."); + const resultDecimals = await callReadOnlyFunction({ + contractAddress, + contractName, + functionName: "get-decimals", + functionArgs: [], + network: "mainnet", + senderAddress: "SP2EVYKET55QH40RAZE5PVZ363QX0X6BSRP4C7H0W", + }); + + const resultSymbol = await callReadOnlyFunction({ + contractAddress, + contractName, + functionName: "get-symbol", + functionArgs: [], + network: "mainnet", + senderAddress: "SP2EVYKET55QH40RAZE5PVZ363QX0X6BSRP4C7H0W", + }); + + token = { + id: tokenId, + symbol: cvToValue(resultSymbol).value, + decimals: Number(cvToValue(resultDecimals).value), + }; + await db.insert(tokenTable).values(token); + } + return token; +}; diff --git a/src/lib/currencies.ts b/src/lib/currencies.ts index ac500c8..f54685c 100644 --- a/src/lib/currencies.ts +++ b/src/lib/currencies.ts @@ -1,10 +1,5 @@ -import { tokenTable } from "@/db/schema"; -import { callReadOnlyFunction, cvToValue } from "@stacks/transactions"; -import { eq } from "drizzle-orm"; -import { db } from "../db/db"; - export const displayPrice = ( - price: bigint | string, + price: bigint | number | string, decimals: number, ): string => { const priceNumber = Number(price) / 10 ** decimals; @@ -17,54 +12,3 @@ export const displayPrice = ( notation: "compact", }); }; - -/** - * Get a token from the database or insert it if it doesn't exist. - */ -export const getOrInsertToken = async (tokenId: string) => { - const tokens = await db - .select() - .from(tokenTable) - .where(eq(tokenTable.id, tokenId)); - let token = tokens[0]; - if (!token) { - // Special case for STX - if (tokenId === "STX") { - token = { - id: "STX", - symbol: "STX", - decimals: 6, - }; - await db.insert(tokenTable).values(token); - return; - } - - const smartContract = tokenId.split("::")[0]; - const [contractAddress, contractName] = smartContract.split("."); - const resultDecimals = await callReadOnlyFunction({ - contractAddress, - contractName, - functionName: "get-decimals", - functionArgs: [], - network: "mainnet", - senderAddress: "SP2EVYKET55QH40RAZE5PVZ363QX0X6BSRP4C7H0W", - }); - - const resultSymbol = await callReadOnlyFunction({ - contractAddress, - contractName, - functionName: "get-symbol", - functionArgs: [], - network: "mainnet", - senderAddress: "SP2EVYKET55QH40RAZE5PVZ363QX0X6BSRP4C7H0W", - }); - - token = { - id: tokenId, - symbol: cvToValue(resultSymbol).value, - decimals: Number(cvToValue(resultDecimals).value), - }; - await db.insert(tokenTable).values(token); - } - return token; -}; From eb082d8b9cb6b2b7dd5ba73ba6dad8a4667df41a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Pradel?= Date: Sat, 30 Mar 2024 12:17:05 +0100 Subject: [PATCH 3/3] cleanup --- src/app/protocols/[protocol]/page.tsx | 36 ++----------------- src/components/Protocol/ProtocolInfo.tsx | 45 ++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 34 deletions(-) create mode 100644 src/components/Protocol/ProtocolInfo.tsx diff --git a/src/app/protocols/[protocol]/page.tsx b/src/app/protocols/[protocol]/page.tsx index 9d72d5a..d67bbbc 100644 --- a/src/app/protocols/[protocol]/page.tsx +++ b/src/app/protocols/[protocol]/page.tsx @@ -1,3 +1,4 @@ +import { ProtocolInfo } from "@/components/Protocol/ProtocolInfo"; import { UniqueUsersBarChart } from "@/components/Stats/UniqueUsersBarChart"; import { DepositWithdrawBarChart } from "@/components/Stats/stackingdao/DepositsWithdrawBarChart"; import { TransactionRow } from "@/components/Transaction/TransactionRow"; @@ -14,13 +15,10 @@ import { Card, Container, Heading, - IconButton, Separator, Text, } from "@radix-ui/themes"; -import { IconBrandX, IconWorld } from "@tabler/icons-react"; import type { Metadata } from "next"; -import Image from "next/image"; import NextLink from "next/link"; import { notFound } from "next/navigation"; import { Fragment, Suspense } from "react"; @@ -62,41 +60,11 @@ export default async function ProtocolPage({ getTransactions({ protocol, action: searchParams.action }), getTransactionsStats({ protocol }), ]); - const protocolInfo = protocolsInfo[protocol]; const protocolActions = protocolsActions[protocol]; return ( -
- {`${protocol} -
- - {protocolInfo.name} - - - {protocolInfo.description} - -
- - - - - - - - - - -
-
-
+
diff --git a/src/components/Protocol/ProtocolInfo.tsx b/src/components/Protocol/ProtocolInfo.tsx new file mode 100644 index 0000000..457a35a --- /dev/null +++ b/src/components/Protocol/ProtocolInfo.tsx @@ -0,0 +1,45 @@ +import { Protocol, protocolsInfo } from "@/lib/protocols"; +import { Heading, IconButton, Text } from "@radix-ui/themes"; +import { IconBrandX, IconWorld } from "@tabler/icons-react"; +import Image from "next/image"; + +interface ProtocolInfoProps { + protocol: Protocol; +} + +export const ProtocolInfo = ({ protocol }: ProtocolInfoProps) => { + const protocolInfo = protocolsInfo[protocol]; + + return ( +
+ {`${protocol} +
+ + {protocolInfo.name} + + + {protocolInfo.description} + +
+ + + + + + + + + + +
+
+
+ ); +};