Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 33 additions & 146 deletions website/src/api/client.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import NodeCounterService from "./node-counter-service";
import type {
NodeCounterListener,
NodeRpcListener,
NodeData,
NodeCounterOptions,
NodeCounterState,
} from "./node-counter-service";
NodeRpcOptions,
NodeRpcState,
} from "./node-rpc-service";
import env from "@/config";
import NodeRpcService from "./node-rpc-service";
import type { EthereumAddressData } from "@/interfaces/EthereumSecurity";

// Types for API responses
interface ChainStatsData {
Expand Down Expand Up @@ -68,111 +69,22 @@ export interface RaidLeaderboardEntrant {
last_activity: string;
}

interface LeaderboardOptions {
page?: number;
pageSize?: number;
filterByReferralCode?: string;
}

export interface LeaderboardResponse {
data: LeaderboardEntrant[];
meta: {
page: number;
page_size: number;
total_items: number;
total_pages: number;
};
}

export interface RaidLeaderboardResponse {
data: RaidLeaderboardEntrant[];
meta: {
page: number;
page_size: number;
total_items: number;
total_pages: number;
};
}

type ApiResponse<T = any> = Promise<Response>;

const createApiClient = () => {
const nodeCounter = new NodeCounterService();
const nodeRpc = new NodeRpcService();

const methods = {
fetchRaidLeaderboard: (
options: LeaderboardOptions,
): ApiResponse<RaidLeaderboardResponse> => {
const firstRaid = 1;
let url = `${env.TASK_MASTER_URL}/raid-quests/leaderboards/${firstRaid}`;

let queryParams = [];
if (options.page) queryParams.push(`page=${options.page}`);
if (options.pageSize) queryParams.push(`page_size=${options.pageSize}`);
if (options.filterByReferralCode)
queryParams.push(`referral_code=${options.filterByReferralCode}`);

if (queryParams.length != 0) url = url + "?" + queryParams.join("&");

return fetch(url, {
headers: {
"Content-Type": "application/json",
},
});
},
fetchLeaderboard: (
options: LeaderboardOptions,
): ApiResponse<LeaderboardResponse> => {
let url = `${env.TASK_MASTER_URL}/addresses/leaderboard`;

let queryParams = [];
if (options.page) queryParams.push(`page=${options.page}`);
if (options.pageSize) queryParams.push(`page_size=${options.pageSize}`);
if (options.filterByReferralCode)
queryParams.push(`referral_code=${options.filterByReferralCode}`);

if (queryParams.length != 0) url = url + "?" + queryParams.join("&");

return fetch(url, {
headers: {
"Content-Type": "application/json",
},
});
},

/**
* Fetch blockchain statistics including transaction and account counts
* Get Ethereum security analysis
*/
chainStats: (): ApiResponse<GraphQLResponse<ChainStatsData>> => {
const query = `
query GetStatus {
allTransactions: eventsConnection(
orderBy: id_ASC,
where: {
OR: {
type_in: [TRANSFER, REVERSIBLE_TRANSFER]
}
}
) {
totalCount
}
allAccounts: accountsConnection(
orderBy: id_ASC
) {
totalCount
}
}
`;

return fetch(env.GRAPHQL_URL, {
headers: {
"Content-Type": "application/json",
},
method: "POST",
body: JSON.stringify({
query,
}),
});
getEthereumSecurityAnalysis: async (
addressOrEnsName: string,
): Promise<EthereumAddressData | null> => {
const data = await fetch(
`${env.TASK_MASTER_URL}/risk-checker/${addressOrEnsName}`,
);
return (await data.json())?.data as EthereumAddressData | null;
},

/**
Expand Down Expand Up @@ -213,78 +125,53 @@ const createApiClient = () => {
});
},

/**
* Helper method to handle GraphQL responses with proper error checking
*/
async handleGraphQLResponse<T>(response: Response): Promise<T> {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const data: GraphQLResponse<T> = await response.json();

if (data.errors && data.errors.length > 0) {
throw new Error(`GraphQL error: ${data.errors[0].message}`);
}

if (!data.data) {
throw new Error("No data returned from GraphQL query");
}

return data.data;
},

/**
* Convenience method to get chain stats with automatic error handling
*/
async getChainStats(): Promise<ChainStatsData> {
const response = await methods.chainStats();
return methods.handleGraphQLResponse<ChainStatsData>(response);
},

/**
* Node Counter Methods
*/
nodeCounter: {
nodeRpc: {
/**
* Subscribe to node count updates
* Subscribe to node RPC updates
* @param listener Callback function that receives state updates
* @returns Unsubscribe function
*/
subscribe: (listener: NodeCounterListener) =>
nodeCounter.subscribe(listener),
subscribe: (listener: NodeRpcListener) => nodeRpc.subscribe(listener),

/**
* Get current node counter state
*/
getState: () => nodeCounter.getState(),
getState: () => nodeRpc.getState(),

/**
* Start the WebSocket connection to track nodes
*/
connect: () => nodeCounter.connect(),
connect: () => nodeRpc.connect(),

/**
* Disconnect from the node tracking WebSocket
*/
disconnect: () => nodeCounter.disconnect(),
disconnect: () => nodeRpc.disconnect(),

/**
* Check if currently connected
*/
isConnected: () => nodeCounter.isConnected(),
isConnected: () => nodeRpc.isConnected(),

/**
* Get just the current node count (convenience method)
*/
getCount: () => nodeCounter.getState().count,
getCount: () => nodeRpc.getState().count,

/**
* Get the current block height
*/
getBlockHeight: () => nodeRpc.getState().blockHeight,

/**
* Get current connection status
*/
getStatus: () => ({
status: nodeCounter.getState().status,
message: nodeCounter.getState().statusMessage,
status: nodeRpc.getState().status,
message: nodeRpc.getState().statusMessage,
}),
},
};
Expand All @@ -302,7 +189,7 @@ export type {
ContactData,
SubscribeData,
NodeData,
NodeCounterState,
NodeCounterOptions,
NodeCounterListener,
NodeRpcState,
NodeRpcOptions,
NodeRpcListener,
};
Loading
Loading