diff --git a/@types/index.d.ts b/@types/index.d.ts
index 9b9471d..b630a55 100644
--- a/@types/index.d.ts
+++ b/@types/index.d.ts
@@ -1,4 +1,12 @@
+import 'dayjs'
+
declare module '*.svg' {
const content: any
export default content
}
+
+declare module 'dayjs' {
+ interface Dayjs {
+ utc()
+ }
+}
diff --git a/components/LeaderboardTable.tsx b/components/LeaderboardTable.tsx
new file mode 100644
index 0000000..fea3447
--- /dev/null
+++ b/components/LeaderboardTable.tsx
@@ -0,0 +1,242 @@
+import { useEffect, useState } from 'react'
+import styled from '@emotion/styled'
+import dayjs from 'dayjs'
+import { Table, Thead, Tbody, Tr, Th, Td } from 'react-super-responsive-table'
+import { AreaChart, Area, ReferenceLine, XAxis, YAxis, Tooltip } from 'recharts'
+import { ExternalLinkIcon } from '@heroicons/react/outline'
+import { usdFormatter } from '../utils'
+import { AwardIcon, TrophyIcon } from './icons'
+import useMangoStore from '../stores/useMangoStore'
+
+const utc = require('dayjs/plugin/utc')
+dayjs.extend(utc)
+
+const StyledTooltipWrapper = styled.div`
+ min-width: 180px;
+`
+
+const LeaderboardTable = () => {
+ const [pnlHistory, setPnlHistory] = useState([])
+ const [loading, setLoading] = useState(false)
+ const pnlLeaderboard = useMangoStore((s) => s.pnlLeaderboard)
+
+ /* API Returns:
+ * [ { cumulative_pnl: -3.687498
+ date: "2021-06-10"
+ margin_account: "J8XtwLVyZjeH1PG1Nnk9cWbLn3zEemS1rCbn4x6AjtXM"
+ name: ""
+ owner: "APLKzSqJQw79q4U4ipBWnLdqkVzijSPNpDCNKwL8mW3B"
+ }, ... ]
+ */
+ useEffect(() => {
+ const getPnlHistory = async () => {
+ setLoading(true)
+ const start = dayjs().utc().subtract(31, 'day').format('YYYY-MM-DD')
+ console.log(start)
+ const results = await Promise.all(
+ pnlLeaderboard.slice(pnlHistory.length).map(async (acc) => {
+ const response = await fetch(
+ `https://mango-transaction-log.herokuapp.com/stats/pnl_history/${acc.margin_account}?start_date=${start}`
+ )
+ const parsedResponse = await response.json()
+ return parsedResponse ? parsedResponse.reverse() : []
+ })
+ )
+ setPnlHistory(pnlHistory.concat(results))
+ setLoading(false)
+ }
+ getPnlHistory()
+ }, [pnlLeaderboard])
+
+ const formatPnlHistoryData = (data) => {
+ const start = new Date(
+ dayjs().utc().hour(0).minute(0).subtract(31, 'day')
+ ).getTime()
+
+ return data.filter((d) => new Date(d.date).getTime() > start)
+ }
+
+ const tooltipContent = (tooltipProps) => {
+ if (tooltipProps.payload.length > 0) {
+ return (
+
+
+
Date
+
+ {tooltipProps.payload[0].payload.date}
+
+
+
+
PNL
+
+ {usdFormatter.format(
+ tooltipProps.payload[0].payload.cumulative_pnl
+ )}
+
+
+
+ )
+ }
+ return null
+ }
+
+ return (
+
+
+
+ {pnlLeaderboard.length > 0 ? (
+
+
+
+
+
+ Rank
+ |
+
+ Account
+ |
+
+ PNL
+ |
+
+ PNL / Time
+ |
+
+
+ View on Step
+
+
+ |
+
+
+
+ {pnlLeaderboard.map((acc, index) => (
+
+
+
+ {acc.rank}
+ {acc.rank === 1 ? (
+
+ ) : null}
+ {acc.rank === 2 || acc.rank === 3 ? (
+
+ ) : null}
+
+ |
+
+ {acc.name
+ ? acc.name
+ : `${acc.margin_account.slice(
+ 0,
+ 5
+ )}...${acc.margin_account.slice(-5)}`}
+ |
+
+
+ {usdFormatter.format(acc.pnl)}
+
+ |
+
+ {loading && !pnlHistory[index] ? (
+
+ ) : (
+
+
+
+
+
+
+
+ )}
+ |
+
+
+ View
+
+
+ |
+
+ ))}
+
+
+
+ ) : (
+
+ )}
+
+
+
+ )
+}
+
+export default LeaderboardTable
diff --git a/components/TopBar.tsx b/components/TopBar.tsx
index 83d9651..5c84026 100644
--- a/components/TopBar.tsx
+++ b/components/TopBar.tsx
@@ -31,6 +31,7 @@ const TopBar = () => {
+
diff --git a/components/account-page/AccountAssets.tsx b/components/account-page/AccountAssets.tsx
index 8105937..936085b 100644
--- a/components/account-page/AccountAssets.tsx
+++ b/components/account-page/AccountAssets.tsx
@@ -147,7 +147,7 @@ export default function AccountAssets() {
scope="col"
className={`px-6 py-3 text-left font-normal`}
>
- Available
+ Deposits
{
)
}
+
+export const AwardIcon = ({ className }) => {
+ return (
+
+ )
+}
+
+export const TrophyIcon = ({ className }) => {
+ return (
+
+ )
+}
diff --git a/pages/leaderboard.tsx b/pages/leaderboard.tsx
new file mode 100644
index 0000000..f43676c
--- /dev/null
+++ b/pages/leaderboard.tsx
@@ -0,0 +1,47 @@
+import { useEffect, useState } from 'react'
+import useMangoStore from '../stores/useMangoStore'
+import PageBodyContainer from '../components/PageBodyContainer'
+import TopBar from '../components/TopBar'
+import LeaderboardTable from '../components/LeaderboardTable'
+import { LinkButton } from '../components/Button'
+
+export default function Leaderboard() {
+ const [offsetResults, setOffsetResults] = useState(0)
+ const actions = useMangoStore((s) => s.actions)
+ const pnlLeaderboard = useMangoStore((s) => s.pnlLeaderboard)
+
+ useEffect(() => {
+ actions.fetchPnlLeaderboard(offsetResults, 29)
+ }, [])
+
+ const handleShowMore = async () => {
+ const offset = offsetResults + 25
+ await actions.fetchPnlLeaderboard(offset, 29)
+ setOffsetResults(offset)
+ }
+
+ return (
+
+
+
+
+
+ Leaderboard
+
+
+
+ Top 100 accounts by PNL over the last 30 days
+
+ {pnlLeaderboard.length < 100 ? (
+ handleShowMore()}
+ >
+ Show More
+
+ ) : null}
+
+
+
+ )
+}
diff --git a/public/assets/icons/step.png b/public/assets/icons/step.png
new file mode 100644
index 0000000..a7364f5
Binary files /dev/null and b/public/assets/icons/step.png differ
diff --git a/stores/useMangoStore.tsx b/stores/useMangoStore.tsx
index fe9fe49..6adac4b 100644
--- a/stores/useMangoStore.tsx
+++ b/stores/useMangoStore.tsx
@@ -10,11 +10,15 @@ import {
} from '@blockworks-foundation/mango-client'
import { SRM_DECIMALS } from '@project-serum/serum/lib/token-instructions'
import { AccountInfo, Connection, PublicKey } from '@solana/web3.js'
+import dayjs from 'dayjs'
import { EndpointInfo, WalletAdapter } from '../@types/types'
import { getWalletTokenInfo } from '../utils/tokens'
import { isDefined } from '../utils/index'
import { notify } from '../utils/notifications'
+const utc = require('dayjs/plugin/utc')
+dayjs.extend(utc)
+
export const ENDPOINTS: EndpointInfo[] = [
{
name: 'mainnet-beta',
@@ -57,6 +61,14 @@ interface AccountInfoList {
[key: string]: AccountInfo
}
+interface AccountPnl {
+ margin_account: string
+ name: string
+ owner: string
+ pnl: number
+ rank: number
+}
+
interface MangoStore extends State {
notifications: Array<{
type: string
@@ -119,9 +131,12 @@ interface MangoStore extends State {
liquidationHistory: any[]
withdrawalHistory: any[]
tradeHistory: any[]
+ pnlHistory: any[]
+ pnlLeaderboard: any[]
+ accountPnl: AccountPnl
set: (x: any) => void
actions: {
- [key: string]: () => void
+ [key: string]: (...args: any[]) => void
}
}
@@ -177,6 +192,9 @@ const useMangoStore = create((set, get) => ({
liquidationHistory: [],
withdrawalHistory: [],
tradeHistory: [],
+ pnlHistory: [],
+ pnlLeaderboard: [],
+ accountPnl: null,
set: (fn) => set(produce(fn)),
actions: {
async fetchWalletBalances() {
@@ -428,6 +446,82 @@ const useMangoStore = create((set, get) => ({
state.withdrawalHistory = results
})
},
+ async fetchPnlHistory(marginAccount = null) {
+ const selectedMarginAccount =
+ marginAccount ||
+ get().selectedMarginAccount.current.publicKey.toString()
+ const set = get().set
+
+ if (!selectedMarginAccount) return
+
+ const response = await fetch(
+ `https://mango-transaction-log.herokuapp.com/stats/pnl_history/${selectedMarginAccount}`
+ )
+ const parsedResponse = await response.json()
+ const results = parsedResponse.length ? parsedResponse.reverse() : []
+
+ set((state) => {
+ state.pnlHistory = results
+ })
+ },
+ async fetchPnlLeaderboard(offset = 0, start?: number) {
+ const baseUrl =
+ 'https://mango-transaction-log.herokuapp.com/stats/pnl_leaderboard'
+
+ const startAt = start
+ ? dayjs().utc().subtract(start, 'day').format('YYYY-MM-DD')
+ : null
+
+ const url = startAt
+ ? `${baseUrl}?start_date=${startAt}&limit=25&offset=${offset}`
+ : `${baseUrl}?limit=25&offset=${offset}`
+
+ const response = await fetch(url)
+ const parsedResponse = await response.json()
+ const results = parsedResponse ? parsedResponse : []
+
+ const currentLeaderboard = get().pnlLeaderboard
+
+ if (currentLeaderboard.length > 0 && offset > 0) {
+ const updatedLeaderboard = currentLeaderboard.concat(results)
+ set((state) => {
+ state.pnlLeaderboard = updatedLeaderboard
+ })
+ } else {
+ set((state) => {
+ state.pnlLeaderboard = results
+ })
+ }
+ },
+ async fetchPnlByAccount(marginAccount = null, start?: number) {
+ const selectedMarginAccount =
+ marginAccount || get().selectedMarginAccount.current
+ const set = get().set
+
+ if (!selectedMarginAccount) return
+
+ const startAt = start
+ ? new Date(Date.now() - start * 24 * 60 * 60 * 1000).toLocaleDateString(
+ 'en-ZA'
+ )
+ : null
+
+ const baseUrl =
+ 'https://mango-transaction-log.herokuapp.com/stats/pnl_leaderboard_rank'
+
+ const url = startAt
+ ? `${baseUrl}/${selectedMarginAccount.publicKey.toString()}?start_date=${startAt}`
+ : `${baseUrl}/${selectedMarginAccount.publicKey.toString()}`
+
+ const response = await fetch(url)
+ const parsedResponse =
+ response.status === 200 ? await response.json() : null
+ const results = parsedResponse ? parsedResponse : null
+
+ set((state) => {
+ state.accountPnl = results
+ })
+ },
},
}))
diff --git a/yarn.lock b/yarn.lock
index 57336e8..74f9f14 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2515,9 +2515,9 @@ boolbase@^1.0.0, boolbase@~1.0.0:
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
-"borsh@git+https://github.com/defactojob/borsh-js.git#field-mapper":
+"borsh@https://github.com/defactojob/borsh-js#field-mapper":
version "0.3.1"
- resolved "git+https://github.com/defactojob/borsh-js.git#33a0d24af281112c0a48efb3fa503f3212443de9"
+ resolved "https://github.com/defactojob/borsh-js#33a0d24af281112c0a48efb3fa503f3212443de9"
dependencies:
"@types/bn.js" "^4.11.5"
bn.js "^5.0.0"
|