1- import { headers } from "next/headers" ;
21import { ImageResponse } from "next/og" ;
3- import { formatEther } from "viem" ;
4- import { DAO_ADDRESSES , TREASURY_TOKEN_ADDRESSES , TREASURY_TOKEN_ALLOWLIST } from "@/lib/config" ;
2+ import { DAO_ADDRESSES } from "@/lib/config" ;
53import { formatEthDisplay , formatUsdDisplay , OG_COLORS , OG_FONTS , OG_SIZE } from "@/lib/og-utils" ;
4+ import { loadTreasurySnapshot } from "@/services/treasury" ;
65
76export const alt = "Gnars DAO Treasury" ;
87export const size = OG_SIZE ;
@@ -13,133 +12,26 @@ export const contentType = "image/png";
1312// served from the edge with zero function CPU. Keeps us under Hobby CPU/transfer.
1413const OG_CACHE_CONTROL = "public, max-age=0, s-maxage=1800, stale-while-revalidate=3600" ;
1514
16- type TokenBalance = {
17- contractAddress ?: string ;
18- tokenBalance : string ;
19- decimals ?: number ;
20- } ;
21-
22- type AlchemyTokenResponse = {
23- result ?: {
24- tokenBalances ?: TokenBalance [ ] ;
25- } ;
26- } ;
27-
28- type PriceResponse = {
29- prices ?: Record < string , { usd ?: number } > ;
30- } ;
31-
32- type EthPriceResponse = {
33- usd : number ;
34- error ?: string ;
35- } ;
36-
37- async function fetchTreasurySnapshot ( ) : Promise < { ethBalance : string ; usdTotal : number } | null > {
15+ async function fetchTreasurySnapshot ( ) : Promise < {
16+ ethBalance : string ;
17+ usdTotal : number | null ;
18+ } | null > {
19+ // Delegates to the same service the treasury page uses. This function used to
20+ // re-implement the whole balance+price computation and reach it over HTTP via
21+ // the app's own /api/alchemy, /api/prices and /api/eth-price — two copies of
22+ // financial logic that could disagree, plus four self-requests per render.
3823 try {
39- const baseUrl = await getBaseUrl ( ) ;
40-
41- const [ ethRes , tokenRes , priceRes , ethPriceRes ] = await Promise . all ( [
42- fetchJson < { result ?: string } > ( `${ baseUrl } /api/alchemy` , {
43- method : "POST" ,
44- body : JSON . stringify ( {
45- method : "eth_getBalance" ,
46- params : [ DAO_ADDRESSES . treasury , "latest" ] ,
47- } ) ,
48- } ) ,
49- fetchJson < AlchemyTokenResponse > ( `${ baseUrl } /api/alchemy` , {
50- method : "POST" ,
51- body : JSON . stringify ( {
52- method : "alchemy_getTokenBalances" ,
53- params : [ DAO_ADDRESSES . treasury , TREASURY_TOKEN_ADDRESSES . filter ( Boolean ) ] ,
54- } ) ,
55- } ) ,
56- fetchJson < PriceResponse > ( `${ baseUrl } /api/prices` , {
57- method : "POST" ,
58- body : JSON . stringify ( {
59- addresses : TREASURY_TOKEN_ADDRESSES . map ( ( a ) => String ( a ) . toLowerCase ( ) ) ,
60- } ) ,
61- } ) . catch ( ( ) => ( { prices : { } } ) ) ,
62- fetchJson < EthPriceResponse > ( `${ baseUrl } /api/eth-price` , {
63- method : "GET" ,
64- } ) . catch ( ( ) => ( { usd : 0 } ) ) ,
65- ] ) ;
66-
67- const ethBalanceWei = BigInt ( ethRes . result ?? "0x0" ) ;
68- const ethBalance = Number ( formatEther ( ethBalanceWei ) ) ;
69- const ethPrice = ethPriceRes ?. usd ?? 0 ;
70-
71- const tokenBalances = ( tokenRes . result ?. tokenBalances ?? [ ] ) . filter ( ( token ) => {
72- const balance = token . tokenBalance ?. toLowerCase ( ) ;
73- return balance && balance !== "0" && balance !== "0x0" ;
74- } ) ;
75-
76- const prices : Record < string , { usd : number } > = priceRes . prices ?? { } ;
77- const wethAddress = String ( TREASURY_TOKEN_ALLOWLIST . WETH ) . toLowerCase ( ) ;
78-
79- const priceLookup = Object . fromEntries (
80- Object . entries ( prices ) . map ( ( [ address , value ] ) => [
81- address . toLowerCase ( ) ,
82- address . toLowerCase ( ) === wethAddress ? ethPrice : Number ( value ?. usd ?? 0 ) || 0 ,
83- ] ) ,
84- ) ;
85- priceLookup [ wethAddress ] = ethPrice ;
86-
87- const decimals : Record < string , number > = {
88- [ String ( TREASURY_TOKEN_ALLOWLIST . USDC ) . toLowerCase ( ) ] : 6 ,
89- [ String ( TREASURY_TOKEN_ALLOWLIST . WETH ) . toLowerCase ( ) ] : 18 ,
90- [ String ( TREASURY_TOKEN_ALLOWLIST . SENDIT ) . toLowerCase ( ) ] : 18 ,
91- } ;
92-
93- const tokensUsd = tokenBalances . reduce ( ( sum , token ) => {
94- const address = token . contractAddress ? String ( token . contractAddress ) . toLowerCase ( ) : null ;
95- if ( ! address ) return sum ;
96- const tokenDecimals = decimals [ address ] ?? 18 ;
97- const raw = token . tokenBalance ?? "0x0" ;
98- const parsed = Number . parseInt ( raw , 16 ) ;
99- const balance = Number . isFinite ( parsed ) ? parsed / Math . pow ( 10 , tokenDecimals ) : 0 ;
100- const price = priceLookup [ address ] ?? 0 ;
101- return sum + balance * price ;
102- } , 0 ) ;
103-
104- const usdTotal = tokensUsd + ethBalance * ethPrice ;
105-
24+ const snapshot = await loadTreasurySnapshot ( DAO_ADDRESSES . treasury ) ;
10625 return {
107- ethBalance : formatEthDisplay ( ethBalance ) ,
108- usdTotal,
26+ ethBalance : formatEthDisplay ( snapshot . ethBalance ) ,
27+ usdTotal : snapshot . usdTotal ,
10928 } ;
11029 } catch ( error ) {
11130 console . error ( "[treasury OG] error fetching snapshot:" , error ) ;
11231 return null ;
11332 }
11433}
11534
116- async function getBaseUrl ( ) {
117- const h = await headers ( ) ;
118- const protocol = h . get ( "x-forwarded-proto" ) ?? "https" ;
119- const host = h . get ( "x-forwarded-host" ) ?? h . get ( "host" ) ;
120- if ( ! host ) {
121- throw new Error ( "Unable to determine request host" ) ;
122- }
123- return `${ protocol } ://${ host } ` ;
124- }
125-
126- async function fetchJson < T > ( url : string , init : RequestInit ) : Promise < T > {
127- const response = await fetch ( url , {
128- ...init ,
129- headers : {
130- "Content-Type" : "application/json" ,
131- ...( init . headers || { } ) ,
132- } ,
133- cache : "no-store" ,
134- } ) ;
135-
136- if ( ! response . ok ) {
137- throw new Error ( `Request failed: ${ response . status } ` ) ;
138- }
139-
140- return ( await response . json ( ) ) as T ;
141- }
142-
14335export default async function Image ( { params } : { params : Promise < { locale : string } > } ) {
14436 const { locale } = await params ;
14537 const isPt = locale === "pt-br" ;
@@ -157,7 +49,8 @@ export default async function Image({ params }: { params: Promise<{ locale: stri
15749 }
15850
15951 const ethBalance = treasuryData . ethBalance ;
160- const usdTotal = formatUsdDisplay ( treasuryData . usdTotal ) ;
52+ // An unpriced treasury renders as a dash, never as "$0".
53+ const usdTotal = treasuryData . usdTotal == null ? "—" : formatUsdDisplay ( treasuryData . usdTotal ) ;
16154 const labels = {
16255 title : isPt ? "TESOURO" : "TREASURY" ,
16356 subtitle : isPt ? "Visão Geral Financeira da Gnars DAO" : "Gnars DAO Financial Overview" ,
0 commit comments