Skip to content

Commit 38e6ca5

Browse files
committed
chore: sync main
2 parents c8f910b + 819957b commit 38e6ca5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+3002
-107
lines changed

pages/api/SGhoService.ts

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import dayjs from 'dayjs';
2+
3+
import {
4+
ApiResponse,
5+
GraphQLResponse,
6+
SGhoApyQueryOptions,
7+
SGhoRatesData,
8+
TransformedDailyData,
9+
} from './SGhoService.types';
10+
11+
export const sghoConfig = {
12+
graphqlEndpoint: 'https://tokenlogic-data.ddn.hasura.app/graphql',
13+
apiKey: process.env.TOKENLOGIC_API_KEY,
14+
defaultLimit: 1000,
15+
} as const;
16+
17+
/**
18+
* GraphQL queries for sGHO APY data
19+
*/
20+
export const sghoQueries = {
21+
/**
22+
* Query for sGHO APY data with date range filtering
23+
*/
24+
getApyHistoryDateRange: (startDate: string, endDate: string, limit: number) =>
25+
`{ aaveV3RatesSgho(limit: ${limit}, where: {blockHour: {_gte: "${startDate}", _lte: "${endDate}"}}) { blockHour apr } }`,
26+
27+
/**
28+
* Query for recent sGHO APY data
29+
*/
30+
getApyHistory: (limit: number) => `{ aaveV3RatesSgho(limit: ${limit}) { blockHour apr } }`,
31+
} as const;
32+
33+
/**
34+
* Transform GraphQL data to the format expected by the frontend
35+
* Aggregates multiple hourly entries per day to a single daily entry
36+
*/
37+
export const transformGraphQLData = (graphqlData: SGhoRatesData[]): TransformedDailyData[] => {
38+
const dailyData = new Map<string, { timestamp: dayjs.Dayjs; merit_apy: number }>();
39+
40+
graphqlData.forEach((item) => {
41+
const timestamp = dayjs(item.blockHour);
42+
const dateString = timestamp.format('YYYY-MM-DD');
43+
44+
// Keep the latest entry for each day (or first if no existing entry)
45+
const existing = dailyData.get(dateString);
46+
if (!existing || timestamp.isAfter(existing.timestamp)) {
47+
dailyData.set(dateString, {
48+
timestamp,
49+
merit_apy: item.apr,
50+
});
51+
}
52+
});
53+
54+
return Array.from(dailyData.entries()).map(([dateString, { merit_apy }]) => ({
55+
day: {
56+
value: dateString,
57+
},
58+
merit_apy,
59+
}));
60+
};
61+
62+
/**
63+
* Execute GraphQL query against the TokenLogic API
64+
*/
65+
export const executeGraphQLQuery = async (query: string): Promise<GraphQLResponse> => {
66+
if (!sghoConfig.apiKey) {
67+
throw new Error('TOKENLOGIC_API_KEY environment variable not set');
68+
}
69+
70+
const response = await fetch(sghoConfig.graphqlEndpoint, {
71+
method: 'POST',
72+
headers: {
73+
'Content-Type': 'application/json',
74+
'x-api-key': sghoConfig.apiKey,
75+
},
76+
body: JSON.stringify({
77+
query,
78+
}),
79+
});
80+
81+
if (!response.ok) {
82+
throw new Error(`GraphQL HTTP error: ${response.status} - ${response.statusText}`);
83+
}
84+
85+
const result: GraphQLResponse = await response.json();
86+
87+
if (result.errors && result.errors.length > 0) {
88+
throw new Error(`GraphQL error: ${result.errors.map((e) => e.message).join(', ')}`);
89+
}
90+
91+
if (!result.data?.aaveV3RatesSgho || !Array.isArray(result.data.aaveV3RatesSgho)) {
92+
throw new Error('Invalid response format from data source');
93+
}
94+
95+
return result;
96+
};
97+
98+
/**
99+
* Fetch and transform sGHO APY data
100+
*/
101+
/**
102+
* Normalize date string to ISO format without milliseconds (to match API expectation)
103+
*/
104+
const normalizeDate = (dateInput: string, isEndDate = false): string => {
105+
let date: dayjs.Dayjs;
106+
107+
// Parse the input date
108+
date = dayjs(dateInput);
109+
110+
// If it's just a date (YYYY-MM-DD), convert to proper start/end of day
111+
if (/^\d{4}-\d{2}-\d{2}$/.test(dateInput)) {
112+
if (isEndDate) {
113+
// End of day: 23:59:59
114+
date = date.endOf('day');
115+
} else {
116+
// Start of day: 00:00:00
117+
date = date.startOf('day');
118+
}
119+
}
120+
121+
return date.toISOString().replace(/\.\d{3}Z$/, 'Z');
122+
};
123+
124+
export const fetchSGhoApyData = async (options: SGhoApyQueryOptions): Promise<ApiResponse> => {
125+
const { limit = sghoConfig.defaultLimit, startDate, endDate } = options;
126+
127+
// Determine which query to use based on whether date filtering is requested
128+
let query: string;
129+
130+
if (startDate && endDate) {
131+
const normalizedStartDate = normalizeDate(startDate, false);
132+
const normalizedEndDate = normalizeDate(endDate, true);
133+
query = sghoQueries.getApyHistoryDateRange(normalizedStartDate, normalizedEndDate, limit);
134+
} else {
135+
query = sghoQueries.getApyHistory(limit);
136+
}
137+
138+
const result = await executeGraphQLQuery(query);
139+
140+
const transformedData = transformGraphQLData(result.data!.aaveV3RatesSgho);
141+
const sortedData = transformedData.sort((a, b) => {
142+
const dateA = dayjs(a.day.value);
143+
const dateB = dayjs(b.day.value);
144+
return dateA.isBefore(dateB) ? -1 : dateA.isAfter(dateB) ? 1 : 0;
145+
});
146+
147+
return { data: sortedData };
148+
};

pages/api/SGhoService.types.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* Types for sGHO APY data service
3+
*/
4+
5+
export type SGhoRatesData = {
6+
blockHour: string;
7+
apr: number;
8+
};
9+
10+
export type GraphQLResponse = {
11+
data?: {
12+
aaveV3RatesSgho: SGhoRatesData[];
13+
};
14+
errors?: Array<{
15+
message: string;
16+
locations?: Array<{ line: number; column: number }>;
17+
path?: string[];
18+
}>;
19+
};
20+
21+
export type ApiResponse = {
22+
data?: Array<{
23+
day: { value: string };
24+
merit_apy: number;
25+
}>;
26+
error?: string;
27+
};
28+
29+
export type SGhoApyQueryOptions = {
30+
limit?: number;
31+
startDate?: string;
32+
endDate?: string;
33+
};
34+
35+
export type TransformedDailyData = {
36+
day: { value: string };
37+
merit_apy: number;
38+
};

pages/api/sgho-apy.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import type { NextApiRequest, NextApiResponse } from 'next';
2+
import { fetchSGhoApyData } from 'pages/api/SGhoService';
3+
import { ApiResponse } from 'pages/api/SGhoService.types';
4+
5+
/**
6+
* Next.js API route to fetch sGHO APY data from TokenLogic GraphQL API
7+
*
8+
* GET /api/sgho-apy
9+
* Query parameters:
10+
* - limit: number (optional, default: 100) - Number of records to fetch
11+
* - startDate: string (optional) - Start date for filtering (ISO format or YYYY-MM-DD)
12+
* - endDate: string (optional) - End date for filtering (ISO format or YYYY-MM-DD)
13+
*
14+
* Note: Both startDate and endDate must be provided together for date filtering to work.
15+
*/
16+
export default async function handler(req: NextApiRequest, res: NextApiResponse<ApiResponse>) {
17+
// Only allow GET requests
18+
if (req.method !== 'GET') {
19+
return res.status(405).json({ error: 'Method not allowed' });
20+
}
21+
22+
try {
23+
const limit = req.query.limit ? parseInt(req.query.limit as string) : undefined;
24+
const startDate = req.query.startDate as string;
25+
const endDate = req.query.endDate as string;
26+
27+
const result = await fetchSGhoApyData({
28+
limit,
29+
startDate,
30+
endDate,
31+
});
32+
33+
res.status(200).json(result);
34+
} catch (error) {
35+
console.error('API route error:', error);
36+
37+
if (error.message.includes('GraphQL error')) {
38+
return res.status(400).json({
39+
error: error.message,
40+
});
41+
}
42+
43+
if (error.message.includes('HTTP error')) {
44+
return res.status(502).json({
45+
error: 'Failed to fetch data from external service',
46+
});
47+
}
48+
}
49+
50+
res.status(500).json({
51+
error: 'Internal server error',
52+
});
53+
}

pages/safety-module.page.tsx

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { useUserStakeUiData } from 'src/hooks/stake/useUserStakeUiData';
1818
import { useModalContext } from 'src/hooks/useModal';
1919
import { MainLayout } from 'src/layouts/MainLayout';
2020
import { GetABPToken } from 'src/modules/staking/GetABPToken';
21-
import { GhoStakingPanel } from 'src/modules/staking/GhoStakingPanel';
21+
// import { GhoStakingPanel } from 'src/modules/staking/GhoStakingPanel';
2222
import { StakingHeader } from 'src/modules/staking/StakingHeader';
2323
import { StakingPanel } from 'src/modules/staking/StakingPanel';
2424
import { useRootStore } from 'src/store/root';
@@ -81,10 +81,9 @@ export default function Staking() {
8181

8282
let stkAaveUserData: StakeUIUserData | undefined;
8383
let stkBptUserData: StakeUIUserData | undefined;
84-
let stkGhoUserData: StakeUIUserData | undefined;
8584
let stkBptV2UserData: StakeUIUserData | undefined;
8685
if (stakeUserResult && Array.isArray(stakeUserResult)) {
87-
[stkAaveUserData, stkBptUserData, stkGhoUserData, stkBptV2UserData] = stakeUserResult;
86+
[stkAaveUserData, stkBptUserData, , stkBptV2UserData] = stakeUserResult;
8887
}
8988

9089
const {
@@ -94,8 +93,8 @@ export default function Staking() {
9493
openStakeRewardsClaim,
9594
openStakeRewardsRestakeClaim,
9695
openStakingMigrate,
97-
openSavingsGhoDeposit,
98-
openSavingsGhoWithdraw,
96+
// openSavingsGhoDeposit,
97+
// openSavingsGhoWithdraw,
9998
} = useModalContext();
10099

101100
const [mode, setMode] = useState<Stake>(Stake.aave);
@@ -125,7 +124,7 @@ export default function Staking() {
125124
);
126125

127126
const isStakeAAVE = mode === 'aave';
128-
const isStkGho = mode === 'gho';
127+
// const isStkGho = mode === 'gho';
129128
const isStkBpt = mode === 'bpt';
130129

131130
const showAbptPanel =
@@ -160,11 +159,6 @@ export default function Staking() {
160159
<Trans>Stake AAVE</Trans>
161160
</Typography>
162161
</StyledToggleButton>
163-
<StyledToggleButton value="gho" disabled={mode === 'gho'}>
164-
<Typography variant="subheader1">
165-
<Trans>sGHO</Trans>
166-
</Typography>
167-
</StyledToggleButton>
168162
<StyledToggleButton value="bpt" disabled={mode === 'bpt'}>
169163
<Typography variant="subheader1">
170164
<Trans>Stake ABPT</Trans>
@@ -233,7 +227,7 @@ export default function Staking() {
233227
}}
234228
/>
235229
</Grid>
236-
<Grid
230+
{/* <Grid
237231
item
238232
xs={12}
239233
lg={6}
@@ -280,7 +274,7 @@ export default function Staking() {
280274
openStakeRewardsClaim(Stake.gho, 'AAVE');
281275
}}
282276
/>
283-
</Grid>
277+
</Grid> */}
284278

285279
<Grid
286280
item

0 commit comments

Comments
 (0)