+
diff --git a/apps/marginfi-v2-ui/src/pages/points.tsx b/apps/marginfi-v2-ui/src/pages/points.tsx
index 3f336076f8..cb65235602 100644
--- a/apps/marginfi-v2-ui/src/pages/points.tsx
+++ b/apps/marginfi-v2-ui/src/pages/points.tsx
@@ -1,4 +1,4 @@
-import React, { useMemo, FC, useEffect, useState, useCallback } from "react";
+import React, { useMemo, FC, useEffect, useState, useCallback, useRef } from "react";
import Link from "next/link";
import { Button } from "@mui/material";
import FileCopyIcon from "@mui/icons-material/FileCopy";
@@ -6,7 +6,7 @@ import CheckIcon from "@mui/icons-material/Check";
import { useRouter } from "next/router";
import { getFavoriteDomain } from "@bonfida/spl-name-service";
import { Connection, PublicKey } from "@solana/web3.js";
-import { LeaderboardRow, fetchLeaderboardData } from "@mrgnlabs/marginfi-v2-ui-state";
+import { useConnection } from "@solana/wallet-adapter-react";
import { CopyToClipboard } from "react-copy-to-clipboard";
import { useUserProfileStore } from "~/store";
import { useWalletContext } from "~/hooks/useWalletContext";
@@ -19,7 +19,6 @@ import {
PointsCheckingUser,
PointsConnectWallet,
} from "~/components/desktop/Points";
-import { useConnection } from "@solana/wallet-adapter-react";
const Points: FC = () => {
const { connected, walletAddress } = useWalletContext();
@@ -31,7 +30,6 @@ const Points: FC = () => {
state.userPointsData,
]);
- const [leaderboardData, setLeaderboardData] = useState([]);
const [domain, setDomain] = useState();
const currentUserId = useMemo(() => domain ?? currentFirebaseUser?.uid, [currentFirebaseUser, domain]);
@@ -53,14 +51,10 @@ const Points: FC = () => {
}
}, [connection, walletAddress]);
- useEffect(() => {
- fetchLeaderboardData(connection).then(setLeaderboardData); // TODO: cache leaderboard and avoid call
- }, [connection, connected, walletAddress]); // Dependency array to re-fetch when these variables change
-
return (
<>
points
-
+
{!connected ? (
) : currentFirebaseUser ? (
@@ -129,7 +123,7 @@ const Points: FC = () => {
-
+
>
);
diff --git a/apps/marginfi-v2-ui/tailwind.config.js b/apps/marginfi-v2-ui/tailwind.config.js
index 3f0c40a196..bb3775d25d 100644
--- a/apps/marginfi-v2-ui/tailwind.config.js
+++ b/apps/marginfi-v2-ui/tailwind.config.js
@@ -32,9 +32,22 @@ module.exports = {
warning: "#daa204",
error: "#e07d6f",
},
+ maxWidth: {
+ "8xl": "90rem",
+ },
},
fontFamily: {
aeonik: ['"Aeonik Pro"'],
+ mono: [
+ "ui-monospace",
+ "SFMono-Regular",
+ "Menlo",
+ "Monaco",
+ "Consolas",
+ "Liberation Mono",
+ "Courier New",
+ "monospace",
+ ],
},
screens: {
sm: "640px",
diff --git a/packages/marginfi-v2-ui-state/src/lib/points.ts b/packages/marginfi-v2-ui-state/src/lib/points.ts
index 2898bd2551..458efcfba9 100644
--- a/packages/marginfi-v2-ui-state/src/lib/points.ts
+++ b/packages/marginfi-v2-ui-state/src/lib/points.ts
@@ -11,13 +11,24 @@ import {
getDoc,
getCountFromServer,
where,
+ QueryDocumentSnapshot,
} from "firebase/firestore";
-import { FavouriteDomain, NAME_OFFERS_ID, reverseLookupBatch } from "@bonfida/spl-name-service";
+import {
+ FavouriteDomain,
+ NAME_OFFERS_ID,
+ reverseLookupBatch,
+ reverseLookup,
+ getAllDomains,
+ getFavoriteDomain,
+} from "@bonfida/spl-name-service";
import { Connection, PublicKey } from "@solana/web3.js";
import { firebaseApi } from ".";
type LeaderboardRow = {
id: string;
+ shortAddress?: string;
+ domain?: string;
+ doc: QueryDocumentSnapshot
;
total_activity_deposit_points: number;
total_activity_borrow_points: number;
total_referral_deposit_points: number;
@@ -27,52 +38,75 @@ type LeaderboardRow = {
socialPoints: number;
};
-async function fetchLeaderboardData(connection?: Connection, rowCap = 100, pageSize = 50): Promise {
+const shortAddress = (address: string) => `${address.slice(0, 4)}...${address.slice(-4)}`;
+
+async function fetchLeaderboardData({
+ connection,
+ queryCursor,
+ pageSize = 50,
+ orderCol = "total_points",
+ orderDir = "desc",
+}: {
+ connection?: Connection;
+ queryCursor?: QueryDocumentSnapshot;
+ pageSize?: number;
+ orderCol?: string;
+ orderDir?: "desc" | "asc";
+}): Promise {
const pointsCollection = collection(firebaseApi.db, "points");
- const leaderboardMap = new Map();
- let initialQueryCursor = null;
- do {
- let pointsQuery: Query;
- if (initialQueryCursor) {
- pointsQuery = query(
- pointsCollection,
- orderBy("total_points", "desc"),
- startAfter(initialQueryCursor),
- limit(pageSize)
- );
- } else {
- pointsQuery = query(pointsCollection, orderBy("total_points", "desc"), limit(pageSize));
- }
-
- const querySnapshot = await getDocs(pointsQuery);
- const leaderboardSlice = querySnapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }));
- const leaderboardSliceFiltered = leaderboardSlice.filter(
- (item) => item.id !== null && item.id !== undefined && item.id != "None"
- );
-
- for (const row of leaderboardSliceFiltered) {
- leaderboardMap.set(row.id, row);
- }
-
- initialQueryCursor = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
- } while (initialQueryCursor !== null && leaderboardMap.size < rowCap);
-
- const leaderboardFinalSlice = [...leaderboardMap.values()].slice(0, 100);
-
- if (connection) {
- const publicKeys = leaderboardFinalSlice.map((value) => {
- const [favoriteDomains] = FavouriteDomain.getKeySync(NAME_OFFERS_ID, new PublicKey(value.id));
- return favoriteDomains;
+ const pointsQuery: Query = query(
+ pointsCollection,
+ orderBy(orderCol, orderDir),
+ ...(queryCursor ? [startAfter(queryCursor)] : []),
+ limit(pageSize)
+ );
+
+ const querySnapshot = await getDocs(pointsQuery);
+ const leaderboardSlice = querySnapshot.docs
+ .filter((item) => item.id !== null && item.id !== undefined && item.id != "None")
+ .map((doc) => {
+ const data = { id: doc.id, doc, ...doc.data() } as LeaderboardRow;
+ return data;
});
- const favoriteDomainsInfo = (await connection.getMultipleAccountsInfo(publicKeys)).map((accountInfo, idx) =>
- accountInfo ? FavouriteDomain.deserialize(accountInfo.data).nameAccount : publicKeys[idx]
- );
- const reverseLookup = await reverseLookupBatch(connection, favoriteDomainsInfo);
- leaderboardFinalSlice.map((value, idx) => (value.id = reverseLookup[idx] ? `${reverseLookup[idx]}.sol` : value.id));
+ const leaderboardFinalSlice: LeaderboardRow[] = [...leaderboardSlice];
+
+ if (!connection) {
+ return leaderboardFinalSlice;
}
- return leaderboardFinalSlice;
+
+ const leaderboardFinalSliceWithDomains: LeaderboardRow[] = await Promise.all(
+ leaderboardFinalSlice.map(async (value) => {
+ const newValue = { ...value, shortAddress: shortAddress(value.id) };
+ // attempt to get favorite domain
+ try {
+ const { reverse } = await getFavoriteDomain(connection, new PublicKey(value.id));
+ return {
+ ...newValue,
+ domain: `${reverse}.sol`,
+ };
+ } catch (e) {
+ // attempt to get all domains
+ try {
+ const domains = await getAllDomains(connection, new PublicKey(value.id));
+ if (domains.length > 0) {
+ const reverse = await reverseLookup(connection, domains[0]);
+ return {
+ ...newValue,
+ domain: `${reverse}.sol`,
+ };
+ }
+ } catch (e) {
+ return newValue;
+ }
+ }
+
+ return newValue;
+ })
+ );
+
+ return leaderboardFinalSliceWithDomains;
}
// Firebase query is very constrained, so we calculate the number of users with more points
@@ -95,7 +129,15 @@ async function fetchUserRank(userPoints: number): Promise {
const nullGreaterDocsCount = querySnapshot1.data().count;
const allGreaterDocsCount = querySnapshot2.data().count;
- return allGreaterDocsCount - nullGreaterDocsCount;
+ return allGreaterDocsCount - nullGreaterDocsCount + 1;
+}
+
+async function fetchTotalUserCount() {
+ const q1 = query(collection(firebaseApi.db, "points"));
+ const q2 = query(collection(firebaseApi.db, "points"), where("owner", "==", null));
+ const q1Count = await getCountFromServer(q1);
+ const q2Count = await getCountFromServer(q2);
+ return q1Count.data().count - q2Count.data().count;
}
interface UserPointsData {
@@ -183,6 +225,13 @@ async function getPointsSummary() {
return pointSummary;
}
-export { fetchLeaderboardData, fetchUserRank, getPointsSummary, getPointsDataForUser, DEFAULT_USER_POINTS_DATA };
+export {
+ fetchLeaderboardData,
+ fetchUserRank,
+ fetchTotalUserCount,
+ getPointsSummary,
+ getPointsDataForUser,
+ DEFAULT_USER_POINTS_DATA,
+};
export type { LeaderboardRow, UserPointsData };