diff --git a/apps/server/package.json b/apps/server/package.json index 051c9269..bc83eb43 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -3,15 +3,17 @@ "version": "0.17.1", "private": true, "scripts": { - "build": "nitro build", + "build": "nitro build && tsc --noEmit", "dev": "dotenvx run --env-file=.env.development.local --env-file=.env.development -- nitro dev --port 3001", "prepare": "nitro prepare", "stacks-node:db:pull": "dotenvx run --env-file=.env.production.local --env-file=.env.production -- prisma db pull", + "prisma:generate": "dotenvx run --env-file=.env.production.local -- prisma generate --sql", "deploy": "fly deploy --remote-only" }, "dependencies": { "@dotenvx/dotenvx": "1.25.1", "@libsql/client": "0.8.0", + "@prisma/client": "6.0.0", "@sentry/node": "8.41.0", "@stacks/blockchain-api-client": "8.2.2", "@stacks/stacks-blockchain-api-types": "7.14.1", diff --git a/apps/server/prisma/schema.prisma b/apps/server/prisma/schema.prisma index 2ab39465..6ffaf496 100644 --- a/apps/server/prisma/schema.prisma +++ b/apps/server/prisma/schema.prisma @@ -1,5 +1,6 @@ generator client { provider = "prisma-client-js" + previewFeatures = ["typedSql"] } datasource db { diff --git a/apps/server/prisma/sql/stackingDAOStats.sql b/apps/server/prisma/sql/stackingDAOStats.sql new file mode 100644 index 00000000..232f07c8 --- /dev/null +++ b/apps/server/prisma/sql/stackingDAOStats.sql @@ -0,0 +1,59 @@ +WITH monthly_blocks AS ( + SELECT + DATE_TRUNC('month', TO_TIMESTAMP(burn_block_time)) AS month, + MIN(block_height) AS min_block_height, + MAX(block_height) AS max_block_height + FROM + blocks + WHERE + block_height >= 132118 + GROUP BY + DATE_TRUNC('month', TO_TIMESTAMP(burn_block_time)) +), + +deposits AS ( + SELECT + mb.month, + SUM(se.amount) AS deposits + FROM + monthly_blocks mb + JOIN + stx_events se ON se.block_height BETWEEN mb.min_block_height AND mb.max_block_height + WHERE + se.recipient = 'SP4SZE494VC2YC5JYG7AYFQ44F5Q4PYV7DVMDPBG.reserve-v1' + AND se.sender NOT LIKE 'SP4SZE494VC2YC5JYG7AYFQ44F5Q4PYV7DVMDPBG%' + AND canonical = TRUE + AND microblock_canonical = TRUE + GROUP BY + mb.month +), + +withdrawals AS ( + SELECT + mb.month, + SUM(se.amount) AS withdrawals + FROM + monthly_blocks mb + JOIN + stx_events se ON se.block_height BETWEEN mb.min_block_height AND mb.max_block_height + WHERE + se.sender = 'SP4SZE494VC2YC5JYG7AYFQ44F5Q4PYV7DVMDPBG.reserve-v1' + AND se.recipient NOT LIKE 'SP4SZE494VC2YC5JYG7AYFQ44F5Q4PYV7DVMDPBG%' + AND canonical = TRUE + AND microblock_canonical = TRUE + GROUP BY + mb.month +) + +SELECT + COALESCE(d.month, w.month) AS month, + COALESCE(d.deposits, 0) AS deposits, + COALESCE(w.withdrawals, 0) AS withdrawals +FROM + deposits d +FULL OUTER JOIN + withdrawals w ON d.month = w.month +WHERE + COALESCE(d.deposits, 0) > 0 OR COALESCE(w.withdrawals, 0) > 0 +ORDER BY + month ASC diff --git a/apps/server/src/api/protocols/stackingdao/index.get.ts b/apps/server/src/api/protocols/stackingdao/index.get.ts index 4a0319a1..df5420b5 100644 --- a/apps/server/src/api/protocols/stackingdao/index.get.ts +++ b/apps/server/src/api/protocols/stackingdao/index.get.ts @@ -1,5 +1,6 @@ -import { sql } from "~/db/db"; +import { stackingDAOStats } from "@prisma/client/sql"; import { apiCacheConfig } from "~/lib/api"; +import { prisma } from "~/lib/prisma"; type StackingDAOProtocolStatsResponse = { month: string; @@ -7,68 +8,8 @@ type StackingDAOProtocolStatsResponse = { withdrawals: number; }[]; -export default defineCachedEventHandler(async (event) => { - const result = await sql` -WITH monthly_blocks AS ( - SELECT - DATE_TRUNC('month', TO_TIMESTAMP(burn_block_time)) AS month, - MIN(block_height) AS min_block_height, - MAX(block_height) AS max_block_height - FROM - blocks - WHERE - block_height >= 132118 - GROUP BY - DATE_TRUNC('month', TO_TIMESTAMP(burn_block_time)) -), - -deposits AS ( - SELECT - mb.month, - SUM(se.amount) AS deposits - FROM - monthly_blocks mb - JOIN - stx_events se ON se.block_height BETWEEN mb.min_block_height AND mb.max_block_height - WHERE - se.recipient = 'SP4SZE494VC2YC5JYG7AYFQ44F5Q4PYV7DVMDPBG.reserve-v1' - AND se.sender NOT LIKE 'SP4SZE494VC2YC5JYG7AYFQ44F5Q4PYV7DVMDPBG%' - AND canonical = TRUE - AND microblock_canonical = TRUE - GROUP BY - mb.month -), - -withdrawals AS ( - SELECT - mb.month, - SUM(se.amount) AS withdrawals - FROM - monthly_blocks mb - JOIN - stx_events se ON se.block_height BETWEEN mb.min_block_height AND mb.max_block_height - WHERE - se.sender = 'SP4SZE494VC2YC5JYG7AYFQ44F5Q4PYV7DVMDPBG.reserve-v1' - AND se.recipient NOT LIKE 'SP4SZE494VC2YC5JYG7AYFQ44F5Q4PYV7DVMDPBG%' - AND canonical = TRUE - AND microblock_canonical = TRUE - GROUP BY - mb.month -) - -SELECT - COALESCE(d.month, w.month) AS month, - COALESCE(d.deposits, 0) AS deposits, - COALESCE(w.withdrawals, 0) AS withdrawals -FROM - deposits d -FULL OUTER JOIN - withdrawals w ON d.month = w.month -WHERE - COALESCE(d.deposits, 0) > 0 OR COALESCE(w.withdrawals, 0) > 0 -ORDER BY - month ASC - `; +export default defineCachedEventHandler(async () => { + const result = await prisma.$queryRawTyped(stackingDAOStats()); const stats: StackingDAOProtocolStatsResponse = result.map((row) => ({ // format of the month is "2021-08-01 00:00:00+00" diff --git a/apps/server/src/lib/prisma.ts b/apps/server/src/lib/prisma.ts new file mode 100644 index 00000000..0426feb2 --- /dev/null +++ b/apps/server/src/lib/prisma.ts @@ -0,0 +1,7 @@ +import { PrismaClient } from "@prisma/client"; + +const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }; + +export const prisma = globalForPrisma.prisma || new PrismaClient(); + +if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma; diff --git a/package.json b/package.json index 9c738fb1..7fd298f3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "root", "private": true, - "packageManager": "pnpm@9.7.0", + "packageManager": "pnpm@9.14.4", "scripts": { "build": "pnpm turbo run build", "format": "biome check --write --no-errors-on-unmatched", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 43cb4e24..3db7c3c4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,6 +33,9 @@ importers: '@libsql/client': specifier: 0.8.0 version: 0.8.0 + '@prisma/client': + specifier: 6.0.0 + version: 6.0.0(prisma@6.0.0) '@sentry/node': specifier: 8.41.0 version: 8.41.0 @@ -50,7 +53,7 @@ importers: version: 0.11.1(typescript@5.7.2)(zod@3.23.8) drizzle-orm: specifier: 0.33.0 - version: 0.33.0(@libsql/client@0.8.0)(@opentelemetry/api@1.9.0)(@prisma/client@5.20.0(prisma@6.0.0))(@types/better-sqlite3@7.6.11)(@types/pg@8.11.10)(@types/react@18.3.12)(postgres@3.4.5)(prisma@6.0.0)(react@18.3.1) + version: 0.33.0(@libsql/client@0.8.0)(@opentelemetry/api@1.9.0)(@prisma/client@6.0.0(prisma@6.0.0))(@types/better-sqlite3@7.6.11)(@types/pg@8.11.10)(@types/react@18.3.12)(postgres@3.4.5)(prisma@6.0.0)(react@18.3.1) h3: specifier: 1.13.0 version: 1.13.0 @@ -75,7 +78,7 @@ importers: devDependencies: nitropack: specifier: 2.10.4 - version: 2.10.4(@libsql/client@0.8.0)(drizzle-orm@0.33.0(@libsql/client@0.8.0)(@opentelemetry/api@1.9.0)(@prisma/client@5.20.0(prisma@6.0.0))(@types/better-sqlite3@7.6.11)(@types/pg@8.11.10)(@types/react@18.3.12)(postgres@3.4.5)(prisma@6.0.0)(react@18.3.1))(typescript@5.7.2) + version: 2.10.4(@libsql/client@0.8.0)(drizzle-orm@0.33.0(@libsql/client@0.8.0)(@opentelemetry/api@1.9.0)(@prisma/client@6.0.0(prisma@6.0.0))(@types/better-sqlite3@7.6.11)(@types/pg@8.11.10)(@types/react@18.3.12)(postgres@3.4.5)(prisma@6.0.0)(react@18.3.1))(typescript@5.7.2) prisma: specifier: 6.0.0 version: 6.0.0 @@ -93,7 +96,7 @@ importers: version: 3.0.5(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@sentry/nextjs': specifier: 8.41.0 - version: 8.41.0(@opentelemetry/core@1.28.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.28.0(@opentelemetry/api@1.9.0))(next@15.0.3(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.95.0) + version: 8.41.0(@opentelemetry/core@1.28.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.28.0(@opentelemetry/api@1.9.0))(next@15.0.3(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.95.0) '@stacks/stacks-blockchain-api-types': specifier: 7.14.1 version: 7.14.1 @@ -132,7 +135,7 @@ importers: version: 2.5.11 next: specifier: 15.0.3 - version: 15.0.3(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 15.0.3(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: 18.3.1 version: 18.3.1 @@ -1248,9 +1251,9 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@prisma/client@5.20.0': - resolution: {integrity: sha512-CLv55ZuMuUawMsxoqxGtLT3bEZoa2W8L3Qnp6rDIFWy+ZBrUcOFKdoeGPSnbBqxc3SkdxJrF+D1veN/WNynZYA==} - engines: {node: '>=16.13'} + '@prisma/client@6.0.0': + resolution: {integrity: sha512-tOBhG35ozqZ/5Y6B0TNOa6cwULUW8ijXqBXcgb12bfozqf6eGMyGs+jphywCsj6uojv5lAZZnxVSoLMVebIP+g==} + engines: {node: '>=18.18'} peerDependencies: prisma: '*' peerDependenciesMeta: @@ -4165,6 +4168,7 @@ packages: libsql@0.3.19: resolution: {integrity: sha512-Aj5cQ5uk/6fHdmeW0TiXK42FqUlwx7ytmMLPSaUQPin5HKKKuUPD62MAbN4OEweGBBI7q1BekoEN4gPUEL6MZA==} + cpu: [x64, arm64, wasm32] os: [darwin, linux, win32] lilconfig@2.1.0: @@ -6905,10 +6909,9 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@prisma/client@5.20.0(prisma@6.0.0)': + '@prisma/client@6.0.0(prisma@6.0.0)': optionalDependencies: prisma: 6.0.0 - optional: true '@prisma/debug@6.0.0': {} @@ -7900,7 +7903,7 @@ snapshots: dependencies: '@sentry/types': 8.41.0 - '@sentry/nextjs@8.41.0(@opentelemetry/core@1.28.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.28.0(@opentelemetry/api@1.9.0))(next@15.0.3(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.95.0)': + '@sentry/nextjs@8.41.0(@opentelemetry/core@1.28.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.28.0(@opentelemetry/api@1.9.0))(next@15.0.3(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.95.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation-http': 0.53.0(@opentelemetry/api@1.9.0) @@ -7915,7 +7918,7 @@ snapshots: '@sentry/vercel-edge': 8.41.0 '@sentry/webpack-plugin': 2.22.6(webpack@5.95.0) chalk: 3.0.0 - next: 15.0.3(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 15.0.3(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) resolve: 1.22.8 rollup: 3.29.5 stacktrace-parser: 0.1.10 @@ -8895,10 +8898,10 @@ snapshots: date-fns@4.1.0: {} - db0@0.2.1(@libsql/client@0.8.0)(drizzle-orm@0.33.0(@libsql/client@0.8.0)(@opentelemetry/api@1.9.0)(@prisma/client@5.20.0(prisma@6.0.0))(@types/better-sqlite3@7.6.11)(@types/pg@8.11.10)(@types/react@18.3.12)(postgres@3.4.5)(prisma@6.0.0)(react@18.3.1)): + db0@0.2.1(@libsql/client@0.8.0)(drizzle-orm@0.33.0(@libsql/client@0.8.0)(@opentelemetry/api@1.9.0)(@prisma/client@6.0.0(prisma@6.0.0))(@types/better-sqlite3@7.6.11)(@types/pg@8.11.10)(@types/react@18.3.12)(postgres@3.4.5)(prisma@6.0.0)(react@18.3.1)): optionalDependencies: '@libsql/client': 0.8.0 - drizzle-orm: 0.33.0(@libsql/client@0.8.0)(@opentelemetry/api@1.9.0)(@prisma/client@5.20.0(prisma@6.0.0))(@types/better-sqlite3@7.6.11)(@types/pg@8.11.10)(@types/react@18.3.12)(postgres@3.4.5)(prisma@6.0.0)(react@18.3.1) + drizzle-orm: 0.33.0(@libsql/client@0.8.0)(@opentelemetry/api@1.9.0)(@prisma/client@6.0.0(prisma@6.0.0))(@types/better-sqlite3@7.6.11)(@types/pg@8.11.10)(@types/react@18.3.12)(postgres@3.4.5)(prisma@6.0.0)(react@18.3.1) debug@2.6.9: dependencies: @@ -8983,11 +8986,11 @@ snapshots: dotenv@16.4.5: {} - drizzle-orm@0.33.0(@libsql/client@0.8.0)(@opentelemetry/api@1.9.0)(@prisma/client@5.20.0(prisma@6.0.0))(@types/better-sqlite3@7.6.11)(@types/pg@8.11.10)(@types/react@18.3.12)(postgres@3.4.5)(prisma@6.0.0)(react@18.3.1): + drizzle-orm@0.33.0(@libsql/client@0.8.0)(@opentelemetry/api@1.9.0)(@prisma/client@6.0.0(prisma@6.0.0))(@types/better-sqlite3@7.6.11)(@types/pg@8.11.10)(@types/react@18.3.12)(postgres@3.4.5)(prisma@6.0.0)(react@18.3.1): optionalDependencies: '@libsql/client': 0.8.0 '@opentelemetry/api': 1.9.0 - '@prisma/client': 5.20.0(prisma@6.0.0) + '@prisma/client': 6.0.0(prisma@6.0.0) '@types/better-sqlite3': 7.6.11 '@types/pg': 8.11.10 '@types/react': 18.3.12 @@ -10306,7 +10309,7 @@ snapshots: neo-async@2.6.2: {} - next@15.0.3(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@15.0.3(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@next/env': 15.0.3 '@swc/counter': 0.1.3 @@ -10316,7 +10319,7 @@ snapshots: postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.6(@babel/core@7.26.0)(react@18.3.1) + styled-jsx: 5.1.6(react@18.3.1) optionalDependencies: '@next/swc-darwin-arm64': 15.0.3 '@next/swc-darwin-x64': 15.0.3 @@ -10337,7 +10340,7 @@ snapshots: h3: 1.13.0 ufo: 1.5.3 - nitropack@2.10.4(@libsql/client@0.8.0)(drizzle-orm@0.33.0(@libsql/client@0.8.0)(@opentelemetry/api@1.9.0)(@prisma/client@5.20.0(prisma@6.0.0))(@types/better-sqlite3@7.6.11)(@types/pg@8.11.10)(@types/react@18.3.12)(postgres@3.4.5)(prisma@6.0.0)(react@18.3.1))(typescript@5.7.2): + nitropack@2.10.4(@libsql/client@0.8.0)(drizzle-orm@0.33.0(@libsql/client@0.8.0)(@opentelemetry/api@1.9.0)(@prisma/client@6.0.0(prisma@6.0.0))(@types/better-sqlite3@7.6.11)(@types/pg@8.11.10)(@types/react@18.3.12)(postgres@3.4.5)(prisma@6.0.0)(react@18.3.1))(typescript@5.7.2): dependencies: '@cloudflare/kv-asset-handler': 0.3.4 '@netlify/functions': 2.8.2 @@ -10361,7 +10364,7 @@ snapshots: cookie-es: 1.2.2 croner: 9.0.0 crossws: 0.3.1 - db0: 0.2.1(@libsql/client@0.8.0)(drizzle-orm@0.33.0(@libsql/client@0.8.0)(@opentelemetry/api@1.9.0)(@prisma/client@5.20.0(prisma@6.0.0))(@types/better-sqlite3@7.6.11)(@types/pg@8.11.10)(@types/react@18.3.12)(postgres@3.4.5)(prisma@6.0.0)(react@18.3.1)) + db0: 0.2.1(@libsql/client@0.8.0)(drizzle-orm@0.33.0(@libsql/client@0.8.0)(@opentelemetry/api@1.9.0)(@prisma/client@6.0.0(prisma@6.0.0))(@types/better-sqlite3@7.6.11)(@types/pg@8.11.10)(@types/react@18.3.12)(postgres@3.4.5)(prisma@6.0.0)(react@18.3.1)) defu: 6.1.4 destr: 2.0.3 dot-prop: 9.0.0 @@ -11371,12 +11374,10 @@ snapshots: dependencies: js-tokens: 9.0.1 - styled-jsx@5.1.6(@babel/core@7.26.0)(react@18.3.1): + styled-jsx@5.1.6(react@18.3.1): dependencies: client-only: 0.0.1 react: 18.3.1 - optionalDependencies: - '@babel/core': 7.26.0 sucrase@3.35.0: dependencies: