('')
const [isTableReady, setIsTableReady] = useState(false)
@@ -39,16 +39,16 @@ const Table = ({ data, userFuulData }) => {
const displayData = useMemo(() => {
let dataToDisplay = [...data.slice(0, 10)]
- if (userFuulData.points) {
+ if (userBoltsData.bolts) {
const userInTop10 = data.find(
- (user: LeaderboardUser) => user.address === userFuulData.address && user.rank <= 10
+ (user: LeaderboardUser) => user.address === userBoltsData.address && user.rank <= 10
)
if (!userInTop10) {
- dataToDisplay.push(userFuulData)
+ dataToDisplay.push(userBoltsData)
}
}
return dataToDisplay
- }, [data, userFuulData])
+ }, [data, userBoltsData])
const columns = [
columnHelper.accessor('rank', {
@@ -60,7 +60,7 @@ const Table = ({ data, userFuulData }) => {
if (rank <= 3) {
color = '#FFFFFF'
badge =
- } else if (rank === userFuulData.rank) {
+ } else if (rank === userBoltsData.rank) {
color = '#1A74EC'
}
return (
@@ -75,11 +75,11 @@ const Table = ({ data, userFuulData }) => {
header: 'Address',
cell: (info) => {
const address = info.getValue()
- return
+ return
},
}),
- columnHelper.accessor('points', {
- header: 'Points',
+ columnHelper.accessor('bolts', {
+ header: 'Bolts',
// @ts-ignore
cell: (info) => {info.getValue().toLocaleString()},
}),
@@ -144,7 +144,7 @@ const Table = ({ data, userFuulData }) => {
key={row.id}
style={
// @ts-ignore
- row.original.address === userFuulData.address
+ row.original.address === userBoltsData.address
? { backgroundColor: '#8DB2FF99' }
: { backgroundColor: '#1A74EC' }
}
@@ -156,7 +156,7 @@ const Table = ({ data, userFuulData }) => {
else tdStyle = { paddingTop: '10px', color: '#eeeeee' }
if (index === 0) tdStyle.paddingBottom = '10px'
// @ts-ignore
- if (row?.original.address === userFuulData.address) tdStyle.color = '#1A74EC'
+ if (row?.original.address === userBoltsData.address) tdStyle.color = '#1A74EC'
return (
diff --git a/src/containers/Bolts/index.tsx b/src/containers/Bolts/index.tsx
index a845ccd0..8528ec84 100644
--- a/src/containers/Bolts/index.tsx
+++ b/src/containers/Bolts/index.tsx
@@ -4,47 +4,48 @@ import { useActiveWeb3React } from '~/hooks'
import Button from '~/components/Button'
import { MULTIPLIERS, QUESTS } from './quests'
import QuestBlock from './QuestBlock'
-import Image from '~/assets/quests-img.png'
import styled from 'styled-components'
import Leaderboard from '~/containers/Bolts/Leaderboard'
import { useStoreState, useStoreActions } from '~/store'
+import DataCard from '~/containers/Analytics/DataCard'
const Bolts = () => {
const { account } = useActiveWeb3React()
- const userFuulData = useStoreState((state) => state.boltsModel.userFuulData)
+ const userBoltsData = useStoreState((state) => state.boltsModel.userBoltsData)
const leaderboardData = useStoreState((state) => state.boltsModel.leaderboardData)
const boltsEarnedData = useStoreState((state) => state.boltsModel.boltsEarnedData)
+ const multipliersData = useStoreState((state) => state.boltsModel.multipliersData)
+
const fetchData = useStoreActions((actions) => actions.boltsModel.fetchData)
useEffect(() => {
fetchData({ account } as { account: string | null })
}, [account, fetchData])
-
return (
Bolts
Welcome Vault Keepers!
+
+
-
-
-
- Complete the quests below to earn Bolts.
-
- Deposits, borrows, and LPs are awarded Bolts based on their equivalent value in ETH. For
- program details, see our{' '}
-
- blog
-
- .
-
-
-
+ Leaderboard
+
@@ -56,7 +57,7 @@ const Bolts = () => {
Multipliers
- {MULTIPLIERS(boltsEarnedData).map((quest, index) => (
+ {MULTIPLIERS(multipliersData).map((quest, index) => (
))}
@@ -79,56 +80,71 @@ const Bolts = () => {
)
}
+const FlexMultipleRow = styled.div`
+ display: flex;
+ gap: 24px;
+ margin-bottom: 24px;
+
+ ${({ theme }) => theme.mediaWidth.upToSmall`
+ display: block;
+
+ & div {
+ margin-bottom: 24px;
+ }
+ `}
+`
+
const Container = styled.div`
margin: 80px auto;
max-width: 1362px;
@media (max-width: 767px) {
margin: 50px auto;
+ padding: 0 10px;
}
color: ${(props) => props.theme.colors.accent};
`
-const MessageBox = styled.div`
- max-width: 800px;
- margin-left: auto;
- margin-right: auto;
- border-radius: 4px;
- background: ${(props) => props.theme.colors.gradientBg};
- color: white;
- padding-left: 28px;
- display: flex;
- align-items: center;
-
- & h3 {
- font-size: 32px;
- font-weight: 700;
- font-family: ${(props) => props.theme.family.headers};
- margin-bottom: 10px;
- line-height: 36px;
- }
-
- a {
- text-decoration: underline;
- color: white;
- }
-
- @media (max-width: 767px) {
- display: flex;
- flex-direction: column;
- align-items: center;
- text-align: center;
- padding-left: 0;
- padding-bottom: 36px;
- padding-left: 25px;
- padding-right: 25px;
- border-radius: 0;
- }
-`
-
-const Text = styled.div`
- max-width: 400px;
-`
+// const MessageBox = styled.div`
+// max-width: 800px;
+// margin-left: auto;
+// margin-right: auto;
+// border-radius: 4px;
+// background: ${(props) => props.theme.colors.gradientBg};
+// color: white;
+// padding-left: 28px;
+// display: flex;
+// align-items: center;
+
+// & h3 {
+// font-size: 32px;
+// font-weight: 700;
+// font-family: ${(props) => props.theme.family.headers};
+// margin-bottom: 10px;
+// line-height: 36px;
+// }
+
+// a {
+// text-decoration: underline;
+// color: white;
+// }
+
+// @media (max-width: 767px) {
+// display: flex;
+// flex-direction: column;
+// align-items: center;
+// text-align: center;
+// padding-left: 0;
+// padding-bottom: 36px;
+// padding-left: 25px;
+// padding-right: 25px;
+// border-radius: 0;
+// }
+// `
+
+// const Text = styled.div`
+// max-width: 400px;
+// `
const Title = styled.h2`
font-size: 34px;
@@ -184,6 +200,6 @@ const BtnWrapper = styled.div`
}
`
-const Link = styled.a``
+// const Link = styled.a``
export default Bolts
diff --git a/src/containers/Bolts/quests.tsx b/src/containers/Bolts/quests.tsx
index 830df806..3c8cd215 100644
--- a/src/containers/Bolts/quests.tsx
+++ b/src/containers/Bolts/quests.tsx
@@ -1,11 +1,11 @@
import Button from '~/components/Button'
import { ExternalLink } from 'react-feather'
import styled from 'styled-components'
-import Affiliate from './Affiliate'
import zealyLogo from '~/assets/zealy.svg'
import galxeLogo from '~/assets/galxe.svg'
import camelotLogo from '~/assets/camelot.svg'
import odLogo from '~/assets/od-full-logo-light.svg'
+import turtleClubLogo from '~/assets/turtle-club.png'
import { useHistory } from 'react-router-dom'
import TokenIcon from '~/components/TokenIcon'
@@ -20,6 +20,12 @@ const LogoText = styled.p`
align-items: center;
`
+const TurtleClubLogo = () => (
+
+ Turtle Club
+
+)
+
const CamelotLogo = () => (
@@ -108,29 +114,29 @@ export type BoltsEarnedData = {
[key: string]: string
}
-export const MULTIPLIERS = (boltsEarnedData: BoltsEarnedData) => [
+export type MultipliersData = {
+ [key: string]: string
+}
+
+export const MULTIPLIERS = (multipliersData: MultipliersData) => [
{
- title: 'Invite a Friend',
- text: (
- <>
- Create a referral link by signing a message with your wallet.
-
- >
+ title: 'Turtle Club',
+ button: (
+
),
- button: '',
+ text: `Become a Turtle Club member.`,
items: [
{
title: 'Source',
- status: ,
- },
- {
- title: 'Bolts',
- status: '10% of referrals + friends receive 250 Bolts per ETH deposited for 30 days',
- },
- {
- title: 'Earned',
- status: boltsEarnedData['Invite a Friend'] || '-',
+ status: ,
},
+ { title: 'Multiplier', status: '+10%' },
+ { title: 'Status', status: multipliersData['TURTLE_CLUB'] || '-' },
],
},
{
@@ -140,79 +146,79 @@ export const MULTIPLIERS = (boltsEarnedData: BoltsEarnedData) => [
Learn more
),
- text: `Depositors using a Genesis NFV receive a 10% bonus for all deposit and borrow points earned with that vault.`,
+ text: `Hold the Genesis NFV.`,
items: [
{
title: 'Source',
status: ,
},
- { title: 'Bolts', status: '+10% to deposit/borrow' },
- { title: 'Holder', status: boltsEarnedData['OgNFV'] || '-' },
+ { title: 'Multiplier', status: '+10%' },
+ { title: 'Status', status: multipliersData['GENESIS_NFV'] || '-' },
],
},
{
- title: 'ODOG NFT Holder',
+ title: 'Genesis NFT Holder',
button: (
),
text: (
- Holders of the
+ Hold the
- ODOG NFT
+ Genesis NFT
- receive a 3% bonus for all points earned.
+ {'.'}
),
-
items: [
- { title: 'Source', status: 'Guild.xyz' },
- { title: 'Bolts', status: '+3% to all points' },
- { title: 'Holder', status: boltsEarnedData['OgNFT'] || '-' },
+ { title: 'Source', status: 'NFTs2Me' },
+ { title: 'Multiplier', status: '+7%' },
+ { title: 'Status', status: multipliersData['GENESIS_NFT'] || '-' },
],
},
{
- title: 'Genesis NFT Holder',
+ title: 'ODOG NFT Holder',
button: (
),
text: (
- Holders of the
+ Hold the
- Genesis NFT
+ ODOG NFT
- receive a 7% bonus for all points earned.
+ {'.'}
),
+
items: [
- { title: 'Source', status: 'NFTs2Me' },
- { title: 'Bolts', status: '+7% to all points' },
- { title: 'Holder', status: boltsEarnedData['GenesisNFT'] || '-' },
+ { title: 'Source', status: 'Guild.xyz' },
+ { title: 'Multiplier', status: '+3%' },
+ { title: 'Status', status: multipliersData['OG_NFT'] || '-' },
],
},
{
@@ -224,8 +230,8 @@ export const MULTIPLIERS = (boltsEarnedData: BoltsEarnedData) => [
title: 'Source',
status: ,
},
- { title: 'Bolts', status: '+30%' },
- { title: 'Earned', status: boltsEarnedData['Community Goal: 20K ETH TVL'] || '-' },
+ { title: 'Multiplier', status: '+30% one-time bonus' },
+ { title: 'Status', status: multipliersData['ETH_TVL_20K'] || '-' },
],
},
]
@@ -250,7 +256,7 @@ export const QUESTS = (boltsEarnedData: BoltsEarnedData) => [
status: ,
},
{ title: 'Bolts', status: '500 per ETH' },
- { title: 'Earned', status: boltsEarnedData[3] || '-' },
+ { title: 'Earned', status: boltsEarnedData['COLLATERAL_DEPOSIT'] || '-' },
],
},
{
@@ -270,7 +276,7 @@ export const QUESTS = (boltsEarnedData: BoltsEarnedData) => [
status: ,
},
{ title: 'Bolts', status: '1,000 per ETH' },
- { title: 'Earned', status: boltsEarnedData[1] || '-' },
+ { title: 'Earned', status: boltsEarnedData['DEBT_BORROW'] || '-' },
],
},
{
@@ -297,7 +303,7 @@ export const QUESTS = (boltsEarnedData: BoltsEarnedData) => [
items: [
{ title: 'Source', status: },
{ title: 'Bolts', status: '2,000 per ETH' },
- { title: 'Earned', status: boltsEarnedData[8] || '-' },
+ { title: 'Earned', status: boltsEarnedData['ODG_ETH_LP'] || '-' },
],
},
{
@@ -324,34 +330,53 @@ export const QUESTS = (boltsEarnedData: BoltsEarnedData) => [
items: [
{ title: 'Source', status: },
{ title: 'Bolts', status: '3,000 per ETH' },
- { title: 'Earned', status: boltsEarnedData[7] || '-' },
+ { title: 'Earned', status: boltsEarnedData['OD_ETH_LP'] || '-' },
],
},
{
- title: 'Galxe and Zealy',
+ title: 'Galxe',
button: (
<>
+ >
+ ),
+ text: 'Complete quests on Galxe.',
+ items: [
+ {
+ title: 'Source',
+ status: (
+ <>
+
+ >
+ ),
+ },
+ { title: 'Bolts', status: '1 per Point' },
+ { title: 'Earned', status: boltsEarnedData['GALXE'] || '-' },
+ ],
+ },
+ {
+ title: 'Zealy',
+ button: (
+ <>
>
),
- text: 'Complete tasks on Galxe and Zealy to earn Bolts.',
+ text: 'Complete quests on Zealy.',
items: [
{
title: 'Source',
status: (
<>
-
>
),
},
- { title: 'Bolts', status: 'Varies' },
- { title: 'Earned', status: boltsEarnedData[6] || '-' },
+ { title: 'Bolts', status: '1 per Point' },
+ { title: 'Earned', status: boltsEarnedData['ZEALY'] || '-' },
],
},
]
diff --git a/src/containers/Explore/ExploreTable.tsx b/src/containers/Explore/ExploreTable.tsx
new file mode 100644
index 00000000..fd509df7
--- /dev/null
+++ b/src/containers/Explore/ExploreTable.tsx
@@ -0,0 +1,352 @@
+import {
+ createColumnHelper,
+ flexRender,
+ getCoreRowModel,
+ useReactTable,
+ getSortedRowModel,
+ getFilteredRowModel,
+ SortingState,
+ ColumnDef,
+} from '@tanstack/react-table'
+import './index.css'
+import styled from 'styled-components'
+import Button from '~/components/Button'
+import { useState } from 'react'
+
+type Vault = {
+ id: string
+ collateral: string
+ image?: string | any
+ collateralAmount: string
+ debtAmount: string
+ riskStatus: string
+ actions?: any
+}
+
+const riskStatusMapping: { [key: string]: number } = {
+ NO: 0,
+ LOW: 1,
+ ELEVATED: 2,
+ HIGH: 3,
+ LIQUIDATION: 4,
+}
+
+const parseDebtAmount = (value: string): number => {
+ return parseFloat(value.replace(/,/g, '').replace(' OD', ''))
+}
+
+const columnHelper = createColumnHelper()
+const columns: ColumnDef[] = [
+ columnHelper.accessor('id', {
+ header: () => 'ID',
+ cell: (info) => info.getValue(),
+ sortingFn: 'alphanumeric',
+ enableSorting: true,
+ }),
+ columnHelper.accessor('image', {
+ header: () => '',
+ cell: (info) => {
+ const image = info.row.original.image
+ const vaultID = info.row.original.id
+ return image ? (
+
+
+
+ ) : null
+ },
+ enableSorting: false,
+ }),
+ columnHelper.accessor('collateralAmount', {
+ header: () => 'Collateral Amount',
+ cell: (info) => info.getValue().toLocaleString(),
+ sortingFn: (rowA, rowB) => {
+ const a = rowA.getValue('collateralAmount')
+ const b = rowB.getValue('collateralAmount')
+ return a - b
+ },
+ filterFn: (row, columnId, filterValue) => {
+ const value = row.getValue(columnId)
+ return value.toString().includes(filterValue)
+ },
+ }),
+ columnHelper.accessor('collateral', {
+ header: () => 'Collateral',
+ cell: (info) => info.getValue(),
+ sortingFn: 'alphanumeric',
+ enableSorting: true,
+ }),
+ columnHelper.accessor('debtAmount', {
+ header: () => 'Debt Amount',
+ cell: (info) => info.getValue(),
+ sortingFn: (rowA, rowB) => {
+ const a = parseDebtAmount(rowA.getValue('debtAmount'))
+ const b = parseDebtAmount(rowB.getValue('debtAmount'))
+ return a - b
+ },
+ filterFn: (row, columnId, filterValue) => {
+ const value = parseDebtAmount(row.getValue(columnId))
+ return value.toString().includes(filterValue)
+ },
+ }),
+ columnHelper.accessor('riskStatus', {
+ header: () => 'Risk Status',
+ cell: (info) => info.getValue().toLocaleString(),
+ sortingFn: (rowA, rowB) => {
+ const a = riskStatusMapping[rowA.getValue('riskStatus')] || 1
+ const b = riskStatusMapping[rowB.getValue('riskStatus')] || 1
+ return a - b
+ },
+ filterFn: (row, columnId, filterValue) => {
+ const value = row.getValue(columnId)
+ return value.includes(filterValue)
+ },
+ }),
+ columnHelper.accessor('actions', {
+ header: '',
+ cell: (info) => {
+ return (
+
+
+
+ )
+ },
+ enableSorting: false,
+ }),
+]
+
+const ExploreTable = ({ data }: { data: Vault[] }) => {
+ const [sorting, setSorting] = useState([])
+ const [globalFilter, setGlobalFilter] = useState('')
+
+ const table = useReactTable({
+ data: data,
+ columns,
+ state: {
+ sorting,
+ globalFilter,
+ },
+ onSortingChange: setSorting,
+ onGlobalFilterChange: setGlobalFilter,
+ getCoreRowModel: getCoreRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ getFilteredRowModel: getFilteredRowModel(),
+ })
+
+ return (
+
+ setGlobalFilter(String(e.target.value))}
+ placeholder="Search all columns..."
+ style={{ marginBottom: '10px', padding: '8px', width: '100%', fontFamily: 'Barlow' }}
+ />
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => (
+
+ {header.isPlaceholder ? null : (
+
+ {flexRender(header.column.columnDef.header, header.getContext())}
+ {header.column.getCanSort() ? (
+ header.column.getIsSorted() ? (
+ header.column.getIsSorted() === 'asc' ? (
+ ▲
+ ) : (
+ ▼
+ )
+ ) : (
+ ⇅
+ )
+ ) : null}
+
+ )}
+ |
+ ))}
+
+ ))}
+
+
+ {table.getRowModel().rows.map((row) => (
+
+ {row.getVisibleCells().map((cell) => {
+ const header = cell.column.columnDef.header
+ let headerText = ''
+
+ if (typeof header === 'function') {
+ // @ts-ignore
+ const renderedHeader = header(cell.getContext())
+ if (typeof renderedHeader === 'string') {
+ headerText = renderedHeader
+ } else if (
+ typeof renderedHeader === 'object' &&
+ renderedHeader.props &&
+ renderedHeader.props.children
+ ) {
+ headerText = renderedHeader.props.children
+ }
+ } else {
+ headerText = header ? header.toString() : ''
+ }
+
+ return (
+
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
+ |
+ )
+ })}
+
+ ))}
+
+
+
+ )
+}
+
+export default ExploreTable
+
+const ArrowUpAndDownIcon = styled.span`
+ font-size: 15px;
+ padding-bottom: 4px;
+`
+
+const StyledArrow = styled.div`
+ padding-left: 4px;
+`
+
+const SortableHeader = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: start;
+ cursor: pointer;
+ font-family: 'Open Sans', sans-serif;
+ font-size: ${(props) => props.theme.font.xSmall};
+`
+
+const TableContainer = styled.div`
+ overflow-x: auto;
+ table {
+ width: 100%;
+ border-collapse: collapse;
+ min-width: 600px;
+ padding: 20px;
+ background-color: rgba(255, 255, 255, 0);
+ backdrop-filter: blur(10px);
+ }
+ th,
+ td {
+ padding: 8px 0px;
+ text-align: left;
+ }
+
+ th {
+ background-color: #fff;
+ border-top: 2px solid #000;
+ border-bottom: 2px solid #000;
+ }
+
+ tr {
+ margin-bottom: 20px;
+ }
+
+ tr:not(:last-child) td {
+ border-bottom: 1px solid #ddd;
+ }
+
+ @media (max-width: 768px) {
+ table {
+ min-width: 100%;
+ display: block;
+ overflow-x: auto;
+ }
+
+ thead {
+ display: none;
+ }
+
+ tbody,
+ td {
+ display: block;
+ width: 100%;
+ box-sizing: border-box;
+ }
+
+ tr {
+ display: flex;
+ flex-direction: column;
+ margin-bottom: 20px;
+ border-bottom: 4px solid #ddd;
+ }
+
+ td {
+ display: flex;
+ justify-content: space-between;
+ padding: 10px;
+ }
+
+ td::before {
+ display: flex;
+ content: attr(data-label);
+ left: 10px;
+ white-space: nowrap;
+ font-weight: bold;
+ text-align: left;
+ }
+ }
+`
+
+const SVGContainer = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 139px;
+ height: 139px;
+ position: relative;
+ margin: 20px 10px 20px 10px;
+ box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.3), 0 12px 40px 0 rgba(0, 0, 0, 0.25);
+
+ @media (max-width: 768px) {
+ width: 294px;
+ height: 294px;
+ justify-content: center;
+ margin-left: auto;
+ margin-right: auto;
+ }
+`
+
+const SvgWrapper = styled.div`
+ transform: scale(0.33);
+ border-radius: 10px;
+
+ @media (max-width: 768px) {
+ transform: scale(0.7);
+ }
+`
+
+const ButtonFloat = styled.div`
+ position: relative;
+ top: 0;
+ right: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
+ border-radius: 5px;
+ z-index: 2;
+ button {
+ margin: 5px;
+ padding: 5px;
+ }
+`
diff --git a/src/containers/Explore/index.css b/src/containers/Explore/index.css
new file mode 100644
index 00000000..3978e6d9
--- /dev/null
+++ b/src/containers/Explore/index.css
@@ -0,0 +1,38 @@
+html {
+ font-family: 'Open Sans', sans-serif;
+ font-size: 18px;
+}
+
+table {
+ width: 100%;
+ background-color: #fff;
+ border-radius: 4px;
+
+}
+
+th {
+ padding: 20px;
+ text-align: left;
+ font-family: 'Barlow', sans-serif;
+ text-transform: uppercase;
+ border-bottom: 1px solid #b6c7d3cc;
+ white-space: nowrap;
+
+ &:first-child {
+ /* width: 150px; */
+ }
+
+ &:not(:first-child) {
+ /* text-align: right; */
+ }
+}
+
+td {
+ padding: 20px;
+ text-transform: capitalize;
+ text-decoration: none;
+
+ &:not(:first-child) {
+ /* text-align: right; */
+ }
+}
diff --git a/src/containers/Explore/index.tsx b/src/containers/Explore/index.tsx
new file mode 100644
index 00000000..f8d64744
--- /dev/null
+++ b/src/containers/Explore/index.tsx
@@ -0,0 +1,162 @@
+import styled from 'styled-components'
+import { useState, useEffect } from 'react'
+// @ts-ignore
+import { generateSvg } from '@opendollar/svg-generator'
+import ExploreTable from './ExploreTable'
+import { ethers } from 'ethers'
+import {
+ parseRay,
+ formatDataNumber,
+ multiplyRates,
+ transformToAnnualRate,
+ calculateRiskStatusText,
+ ratioChecker,
+ parseFormattedNumber,
+} from '~/utils'
+import { AllVaults, useVaultSubgraph } from '~/hooks/useVaultSubgraph'
+import useAnalyticsData from '~/hooks/useAnalyticsData'
+import useGeb from '~/hooks/useGeb'
+
+const Explore: React.FC = () => {
+ const [isLoading, setIsLoading] = useState(true)
+ const [tableData, setTableData] = useState([])
+ const allVaults: AllVaults = useVaultSubgraph()
+ const geb = useGeb()
+ const analyticsData = useAnalyticsData()
+
+ const parseNumber = (value: string): number => {
+ return parseFloat(value.replace(/,/g, ''))
+ }
+
+ const getSafeData = async () => {
+ setIsLoading(true)
+ const tableRows = []
+
+ if (!allVaults?.vaults || !analyticsData?.tokenAnalyticsData) return
+
+ for (const vault of allVaults.vaults) {
+ try {
+ const estimatedValue = `${(
+ +ethers.utils.formatUnits(vault.collateral) *
+ +ethers.utils.formatUnits(analyticsData.tokenAnalyticsData[vault.collateralType].currentPrice)
+ ).toFixed(2)}`
+
+ const stabilityFee = transformToAnnualRate(
+ multiplyRates(
+ analyticsData.tokenAnalyticsData[vault.collateralType].stabilityFee.toString(),
+ analyticsData.redemptionRate?.toString()
+ ) || '0',
+ 27
+ )
+
+ const formattedDebt = parseFormattedNumber(formatDataNumber(vault.debt))
+ let cratio = 0
+ if (formattedDebt !== 0) {
+ cratio = (+estimatedValue / formattedDebt) * 100
+ }
+
+ let correctCollateralizationRatio
+ if (Number(cratio) > 0) {
+ correctCollateralizationRatio = Number(cratio)
+ } else if (Number(cratio) === 0 && parseFormattedNumber(formatDataNumber(vault.collateral)) > 0) {
+ correctCollateralizationRatio = '∞'
+ } else if (Number(cratio) === 0 && parseFormattedNumber(formatDataNumber(vault.collateral)) === 0) {
+ correctCollateralizationRatio = 0
+ }
+
+ const svgData = {
+ vaultID: vault.id,
+ stabilityFee,
+ debtAmount: formatDataNumber(vault.debt),
+ collateralAmount: formatDataNumber(vault.collateral) + ' ' + vault.collateralType,
+ collateralizationRatio: correctCollateralizationRatio,
+ liqRatio: Number(
+ parseRay(analyticsData.tokenAnalyticsData[vault.collateralType].liquidationCRatio)
+ ),
+ safetyRatio: Number(parseRay(analyticsData.tokenAnalyticsData[vault.collateralType].safetyCRatio)),
+ }
+
+ const riskStatus = calculateRiskStatusText(
+ ratioChecker(
+ Number(cratio),
+ Number(parseRay(analyticsData.tokenAnalyticsData[vault.collateralType].liquidationCRatio)),
+ Number(parseRay(analyticsData.tokenAnalyticsData[vault.collateralType].safetyCRatio))
+ )
+ )
+
+ let svg = null
+ try {
+ svg = await generateSvg(svgData)
+ } catch (e) {
+ console.error(e)
+ }
+
+ tableRows.push({
+ id: vault.id,
+ collateral: vault.collateralType,
+ image: svg ? svg : null,
+ collateralAmount: parseNumber(formatDataNumber(vault.collateral)),
+ debtAmount: formatDataNumber(vault.debt) + ' OD',
+ riskStatus: riskStatus,
+ })
+ } catch (e) {
+ console.error(e)
+ }
+ }
+ // @ts-ignore
+ setTableData(tableRows)
+ setIsLoading(false)
+ }
+
+ useEffect(() => {
+ getSafeData()
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [allVaults, analyticsData, geb])
+
+ return (
+
+
+ {tableData.length !== 0 && }
+ {tableData.length === 0 && isLoading && Loading... }
+ {tableData.length === 0 && !isLoading && No vaults available }
+
+ )
+}
+
+export default Explore
+
+const Container = styled.div`
+ max-width: 1362px;
+ margin: 80px auto;
+ padding: 0 15px;
+ @media (max-width: 767px) {
+ margin: 50px auto;
+ display: flex;
+ flex-direction: column;
+ }
+ color: ${(props) => props.theme.colors.accent};
+`
+
+const Title = styled.h2`
+ font-size: 34px;
+ font-weight: 700;
+ font-family: ${(props) => props.theme.family.headers};
+
+ color: ${(props) => props.theme.colors.accent};
+ @media (max-width: 767px) {
+ font-size: 32px;
+ }
+`
+
+const Header = styled.div`
+ display: flex;
+ justify-content: space-between;
+ @media (max-width: 767px) {
+ flex-direction: column;
+ justify-content: left;
+ }
+
+ margin-bottom: 40px;
+`
diff --git a/src/hooks/useFuulSDK.ts b/src/hooks/useFuulSDK.ts
deleted file mode 100644
index 343265b2..00000000
--- a/src/hooks/useFuulSDK.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import { Fuul } from '@fuul/sdk'
-import { SiweMessage } from 'siwe'
-import { useActiveWeb3React } from '~/hooks/useActiveWeb3React'
-
-function useFuulSDK() {
- const { chainId, provider } = useActiveWeb3React()
-
- const fuulSendPageViewEvent = async (pageName: string) => {
- const response = await Fuul.sendPageview(pageName)
- return response
- }
-
- async function createSiweMessage(address: string, statement: string) {
- const scheme = window.location.protocol.slice(0, -1)
- const domain = window.location.host
- const origin = window.location.origin
-
- const message = new SiweMessage({
- scheme,
- domain,
- address,
- statement,
- uri: origin,
- version: '1',
- chainId: chainId,
- })
- return message.prepareMessage()
- }
-
- const sendConnectWalletEvent = async (walletAddress: string): Promise => {
- const message = await createSiweMessage(
- walletAddress,
- `Sign in with Ethereum to Open Dollar and agree to the Terms of Service at opendollar.com/terms and fuul.xyz/terms/users`
- )
- const signature = await provider?.getSigner().signMessage(message)
- return await Fuul.sendConnectWallet({
- address: walletAddress,
- signature,
- message,
- })
- }
-
- const createAffiliateCode = async (walletAddress: string, affiliateCode: string): Promise => {
- // DO NOT CHANGE THIS MESSAGE - THIS MESSAGE MUST MATCH EXACTLY OR THE SIGNATURE WILL FAIL
- const message = `I confirm that I am creating the ${affiliateCode} code on Fuul`
- const signature = await provider?.getSigner().signMessage(message)
- if (!signature) {
- throw new Error('Failed to sign message')
- }
- return await Fuul.createAffiliateCode(walletAddress, affiliateCode, signature)
- }
-
- const getAffiliateCode = async (walletAddress: string): Promise => {
- try {
- return await Fuul.getAffiliateCode(walletAddress)
- } catch (error) {
- console.debug('No affiliate code found:', error)
- return null
- }
- }
-
- const getUserData = async (user_address: string): Promise => {
- try {
- const data = await Fuul.getPointsLeaderboard({ user_address })
- const results = data?.results[0]
- if (!results) {
- return {
- points: '0 🔩',
- rank: 0,
- }
- }
- return {
- points: `${parseInt(results.total_amount).toLocaleString()} 🔩`,
- rank: `#${results.rank}`,
- }
- } catch (error) {
- console.debug('No user data found:', error)
- return null
- }
- }
-
- return {
- Fuul,
- fuulSendPageViewEvent,
- sendConnectWalletEvent,
- createAffiliateCode,
- getAffiliateCode,
- getUserData,
- }
-}
-
-export default useFuulSDK
diff --git a/src/hooks/useSafeData.ts b/src/hooks/useSafeData.ts
index edfa74f4..a284fbe6 100644
--- a/src/hooks/useSafeData.ts
+++ b/src/hooks/useSafeData.ts
@@ -12,9 +12,23 @@ export default function useSafeData() {
const geb = useGeb()
const tokensData = geb?.tokenList
const { account } = useActiveWeb3React()
- const { safeModel: safeActions } = useStoreActions((state) => state)
+ const { safeModel: safeActions, globalSafeModel: globalSafeActions } = useStoreActions((state) => state)
+
const previousAccount = usePrevious(account)
+ const fetchGlobalSafes = useCallback(() => {
+ if (geb && tokensData) {
+ try {
+ globalSafeActions.fetchGlobalSafes({
+ geb,
+ tokensData,
+ })
+ } catch (error) {
+ console.debug('Failed to fetch user safes', error)
+ }
+ }
+ }, [geb, tokensData, globalSafeActions])
+
const fetchUserSafes = useCallback(() => {
if (account && geb && tokensData) {
try {
@@ -32,14 +46,20 @@ export default function useSafeData() {
// Fetch safes initially and on account or geb change
useEffect(() => {
fetchUserSafes()
+ fetchGlobalSafes()
const interval = setInterval(fetchUserSafes, 60000)
- return () => clearInterval(interval)
- }, [fetchUserSafes])
+ const globalInterval = setInterval(fetchGlobalSafes, 60000)
+ return () => {
+ clearInterval(interval)
+ clearInterval(globalInterval)
+ }
+ }, [fetchUserSafes, fetchGlobalSafes])
// Handles account changes
useEffect(() => {
if (account && previousAccount !== account) {
fetchUserSafes()
+ fetchGlobalSafes()
}
- }, [account, previousAccount, fetchUserSafes])
+ }, [account, previousAccount, fetchUserSafes, fetchGlobalSafes])
}
diff --git a/src/hooks/useVaultSubgraph.ts b/src/hooks/useVaultSubgraph.ts
index 6c2420c3..3c1c63ca 100644
--- a/src/hooks/useVaultSubgraph.ts
+++ b/src/hooks/useVaultSubgraph.ts
@@ -28,6 +28,13 @@ export type VaultDetails = {
collateralType: string
}
+export type AllVaults = {
+ vaults: VaultDetails[]
+ owners: string[]
+ vaultsByOwner: { [key: string]: string[] }
+ vaultsByCollateral: { [key: string]: string[] }
+}
+
export const fetchAllVaults = async () => {
const data = await postQuery(
`
@@ -65,8 +72,12 @@ export const fetchAllVaults = async () => {
}
export const useVaultSubgraph = () => {
- const [vaults, setVaults] = useState({ vaults: [] })
-
+ const [vaults, setVaults] = useState({
+ vaults: [],
+ owners: [],
+ vaultsByOwner: {},
+ vaultsByCollateral: {},
+ })
const getVaults = async () => {
const allVaults = await fetchAllVaults()
setVaults(allVaults)
diff --git a/src/model/boltsModel.ts b/src/model/boltsModel.ts
index e4f4ea38..eff8781a 100644
--- a/src/model/boltsModel.ts
+++ b/src/model/boltsModel.ts
@@ -1,26 +1,25 @@
import { action, Action, thunk, Thunk } from 'easy-peasy'
-import { BoltsEarnedData } from '~/containers/Bolts/quests'
+import { BoltsEarnedData, MultipliersData } from '~/containers/Bolts/quests'
-type Conversion = {
- is_referrer: boolean
- conversion_id: number
- conversion_name: string
- total_amount: string
+type Campaign = {
+ type: number
+ amount: string
}
export type LeaderboardUser = {
rank: number
address: string
- points: number
+ bolts: number
ens?: string
}
export interface BoltsModel {
- userFuulData: {
+ userBoltsData: {
rank: string
- points: string
+ bolts: string
+ multiplier: string
}
- setUserFuulData: Action
+ setUserBoltsData: Action
leaderboardData: LeaderboardUser[]
setLeaderboardData: Action
@@ -28,6 +27,9 @@ export interface BoltsModel {
boltsEarnedData: BoltsEarnedData
setBoltsEarnedData: Action
+ multipliersData: MultipliersData
+ setMultipliersData: Action
+
hasFetched: boolean
setHasFetched: Action
@@ -38,12 +40,13 @@ export interface BoltsModel {
}
const boltsModel: BoltsModel = {
- userFuulData: {
+ userBoltsData: {
rank: '',
- points: '',
+ bolts: '',
+ multiplier: '',
},
- setUserFuulData: action((state, payload) => {
- state.userFuulData = payload
+ setUserBoltsData: action((state, payload) => {
+ state.userBoltsData = payload
}),
leaderboardData: [],
@@ -56,6 +59,11 @@ const boltsModel: BoltsModel = {
state.boltsEarnedData = payload
}),
+ multipliersData: {},
+ setMultipliersData: action((state, payload) => {
+ state.multipliersData = payload
+ }),
+
hasFetched: false,
setHasFetched: action((state, payload) => {
state.hasFetched = payload
@@ -68,46 +76,38 @@ const boltsModel: BoltsModel = {
fetchData: thunk(async (actions, { account }, { getState }) => {
try {
- const BOT_DOMAIN = 'https://bot.opendollar.com'
+ const BOT_DOMAIN = process.env.REACT_APP_OD_API_URL
+ ? process.env.REACT_APP_OD_API_URL
+ : 'https://bot.opendollar.com'
const BOT_API = `${BOT_DOMAIN}/api/bolts`
const response = account ? await fetch(`${BOT_API}?address=${account}`) : await fetch(BOT_API)
const result = await response.json()
if (result.success) {
- const users = result.data.fuul.leaderboard.users
+ const { leaderboard, user } = result.data
const state = getState()
// Populate ENS cache for leaderboard users
- users.forEach((user: LeaderboardUser) => {
+ leaderboard.forEach((user: LeaderboardUser) => {
const ens = state.ensCache[user.address] || null
if (ens) {
user.ens = ens
}
})
- actions.setLeaderboardData(users)
-
- if (account) {
- actions.setUserFuulData(result.data.fuul.user)
-
+ actions.setLeaderboardData(leaderboard)
+ if (account && user) {
const boltsEarned: BoltsEarnedData = {}
- const { data } = result
- let combinedBorrowBolts = 0
- let combinedDepositBolts = 0
- data.fuul.user.conversions.forEach((conversion: Conversion) => {
- if ([1, 2].includes(conversion.conversion_id))
- combinedBorrowBolts += parseInt(conversion.total_amount)
- else if ([3, 4].includes(conversion.conversion_id))
- combinedDepositBolts += parseInt(conversion.total_amount)
- else boltsEarned[conversion.conversion_id] = parseInt(conversion.total_amount).toLocaleString()
+ const multipliers: MultipliersData = {}
+ user.campaigns?.forEach((campaign: Campaign) => {
+ boltsEarned[campaign.type] = campaign.amount.toLocaleString()
+ })
+ user.multipliers?.forEach((multiplier: Campaign) => {
+ multipliers[multiplier.type] = parseInt(multiplier.amount) > 0 ? 'Active' : 'Inactive'
})
- boltsEarned[1] = combinedBorrowBolts.toLocaleString()
- boltsEarned[3] = combinedDepositBolts.toLocaleString()
-
- boltsEarned['OgNFT'] = data.OgNFT ? 'Yes' : 'No'
- boltsEarned['OgNFV'] = data.OgNFV ? 'Yes' : 'No'
- boltsEarned['GenesisNFT'] = data.GenesisNFT ? 'Yes' : 'No'
+ actions.setUserBoltsData(user)
actions.setBoltsEarnedData(boltsEarned)
+ actions.setMultipliersData(multipliers)
}
}
actions.setHasFetched(true)
diff --git a/src/model/globalSafeModel.ts b/src/model/globalSafeModel.ts
new file mode 100644
index 00000000..695d4b2d
--- /dev/null
+++ b/src/model/globalSafeModel.ts
@@ -0,0 +1,39 @@
+import { action, Action, thunk, Thunk } from 'easy-peasy'
+import { StoreModel } from '~/model'
+import { fetchGlobalSafes } from '~/services/safes'
+import { timeout, ILiquidationData, ISafe, IFetchGlobalSafesPayload } from '~/utils'
+
+export interface GlobalSafeModel {
+ list: Array
+ liquidationData: ILiquidationData | null
+ fetchGlobalSafes: Thunk
+ setList: Action>
+ setLiquidationData: Action
+}
+
+const globalSafeModel: GlobalSafeModel = {
+ list: [],
+ liquidationData: null,
+ fetchGlobalSafes: thunk(async (actions, payload) => {
+ let fetched
+ try {
+ fetched = await fetchGlobalSafes(payload)
+ } catch (e) {
+ console.debug('Failed to fetch global safes', e)
+ }
+ if (fetched) {
+ actions.setList(fetched.globalSafes)
+ actions.setLiquidationData(fetched.liquidationData)
+ await timeout(200)
+ return fetched
+ }
+ }),
+ setList: action((state, payload) => {
+ state.list = payload
+ }),
+ setLiquidationData: action((state, payload) => {
+ state.liquidationData = payload
+ }),
+}
+
+export default globalSafeModel
diff --git a/src/model/index.ts b/src/model/index.ts
index 959573ec..8ddf1734 100755
--- a/src/model/index.ts
+++ b/src/model/index.ts
@@ -2,6 +2,7 @@ import settingsModel, { SettingsModel } from './settingsModel'
import popupsModel, { PopupsModel } from './popupsModel'
import connectWalletModel, { ConnectWalletModel } from './connectWalletModel'
import safeModel, { SafeModel } from './safeModel'
+import globalSafeModel, { GlobalSafeModel } from './globalSafeModel'
import transactionsModel, { TransactionsModel } from './transactionsModel'
import multicallModel, { MulticallModel } from './multicallModel'
import auctionModel, { AuctionModel } from './auctionModel'
@@ -16,6 +17,7 @@ export interface StoreModel {
popupsModel: PopupsModel
connectWalletModel: ConnectWalletModel
safeModel: SafeModel
+ globalSafeModel: GlobalSafeModel
transactionsModel: TransactionsModel
multicallModel: MulticallModel
auctionModel: AuctionModel
@@ -31,6 +33,7 @@ const model: StoreModel = {
popupsModel,
connectWalletModel,
safeModel,
+ globalSafeModel,
transactionsModel,
multicallModel,
auctionModel,
diff --git a/src/services/checkSanctions.ts b/src/services/checkSanctions.ts
index dddf6ec9..fb134d70 100644
--- a/src/services/checkSanctions.ts
+++ b/src/services/checkSanctions.ts
@@ -3,7 +3,11 @@ import axios from 'axios'
async function checkSanctions(address: string) {
let res
try {
- res = await axios.get(`https://bot.opendollar.com/api/screen?address=${address}`, {
+ const BOT_DOMAIN = process.env.REACT_APP_OD_API_URL
+ ? process.env.REACT_APP_OD_API_URL
+ : 'https://bot.opendollar.com'
+ const BOT_API = `${BOT_DOMAIN}/screen?address=${address}`
+ res = await axios.get(BOT_API, {
headers: {
Accept: 'application/json',
},
diff --git a/src/services/safes.ts b/src/services/safes.ts
index 7c75889d..1c2a12e3 100644
--- a/src/services/safes.ts
+++ b/src/services/safes.ts
@@ -1,4 +1,4 @@
-import { formatUserSafe, IFetchSafesPayload, IUserSafeList } from '~/utils'
+import { formatUserSafe, IFetchGlobalSafesPayload, IFetchSafesPayload, IUserSafeList } from '~/utils'
import gebManager from '~/utils/gebManager'
export const fetchUserSafes = async (config: IFetchSafesPayload) => {
@@ -42,3 +42,26 @@ export const fetchUserSafesRaw = async (config: IFetchSafesPayload) => {
return response
}
+
+export const fetchGlobalSafes = async (config: IFetchGlobalSafesPayload) => {
+ let ownerAddressesResponse = await gebManager.getGlobalSafesRpc()
+ if (!ownerAddressesResponse) return
+
+ let safesResponse = await gebManager.fetchSafesForOwners(config, ownerAddressesResponse.ownerAddresses)
+ if (!safesResponse) return
+
+ const liquidationData = {
+ collateralLiquidationData: safesResponse.collateralLiquidationData,
+ currentRedemptionPrice: safesResponse.systemState.currentRedemptionPrice.value,
+ currentRedemptionRate: safesResponse.systemState.currentRedemptionRate.annualizedRate,
+ globalDebt: safesResponse.systemState.globalDebt,
+ globalDebtCeiling: safesResponse.systemState.globalDebtCeiling,
+ perSafeDebtCeiling: safesResponse.systemState.perSafeDebtCeiling,
+ }
+
+ const globalSafes = formatUserSafe(safesResponse.safes, liquidationData, config.tokensData)
+ return {
+ globalSafes,
+ liquidationData,
+ }
+}
diff --git a/src/utils/formatDataNumber.ts b/src/utils/formatDataNumber.ts
index 5c7fbb8c..0651d44d 100644
--- a/src/utils/formatDataNumber.ts
+++ b/src/utils/formatDataNumber.ts
@@ -31,7 +31,9 @@ export function formatDataNumber(
return new Intl.NumberFormat('en-US', {
minimumFractionDigits: minimumDecimals,
maximumFractionDigits:
- minimumDecimals >= formatDecimal ? Math.min(minimumDecimals, formatDecimal) + 1 : formatDecimal,
+ minimumDecimals >= formatDecimal && formatDecimal !== 0
+ ? Math.min(minimumDecimals, formatDecimal) + 1
+ : formatDecimal,
notation: compact ? 'compact' : 'standard',
style: currency ? 'currency' : 'decimal',
currency: 'USD',
diff --git a/src/utils/gebManager/index.ts b/src/utils/gebManager/index.ts
index 766c1bd8..8666b1a0 100644
--- a/src/utils/gebManager/index.ts
+++ b/src/utils/gebManager/index.ts
@@ -1,8 +1,7 @@
+import axios from 'axios'
import { BigNumber } from 'ethers'
-import { Geb, utils } from '@opendollar/sdk'
-import { ILiquidationResponse, IUserSafeList } from '../interfaces'
-
-import { TokenLiquidationData, fetchLiquidationData } from '@opendollar/sdk/lib/virtual/virtualLiquidationData'
+import { fetchLiquidationData, Geb, TokenLiquidationData, utils } from '@opendollar/sdk'
+import { ILiquidationResponse, IUserSafeList, IOwnerAddressesResponse } from '../interfaces'
import { fetchUserSafes } from '@opendollar/sdk/lib/virtual/virtualUserSafes.js'
import { TokenData } from '@opendollar/sdk/lib/contracts/addreses'
@@ -14,6 +13,11 @@ interface UserListConfig {
safeId_not?: null
}
+interface GlobalSafesConfig {
+ geb: Geb
+ tokensData: { [key: string]: TokenData }
+}
+
// returns LiquidationData
const getLiquidationDataRpc = async (
geb: Geb,
@@ -98,6 +102,7 @@ const getUserSafesRpc = async (config: UserListConfig): Promise =
safeHandler: safe.addy,
safeId: safe.id.toString(),
collateralType: safe.collateralType,
+ ownerAddress: config.address,
}))
return {
@@ -111,12 +116,36 @@ const getUserSafesRpc = async (config: UserListConfig): Promise =
}
}
+const getGlobalSafesRpc = async (): Promise => {
+ const response = await axios.get('https://bot.opendollar.com/api/vaults')
+ const ownerAddresses: string[] = Array.from(new Set(response.data.details.map((safe: any) => safe.owner)))
+ return { ownerAddresses }
+}
+
+const fetchSafesForOwners = async (config: GlobalSafesConfig, ownerAddresses: string[]): Promise => {
+ const allSafes: any[] = []
+ const safePromises = ownerAddresses.map((address) => getUserSafesRpc({ ...config, address }))
+
+ const results = await Promise.all(safePromises)
+ results.forEach((result) => {
+ allSafes.push(...result.safes)
+ })
+
+ const liquidationData = await getLiquidationDataRpc(config.geb, config.tokensData)
+ return {
+ safes: allSafes,
+ erc20Balances: [],
+ ...liquidationData,
+ }
+}
+
const gebManager = {
getUserSafesRpc,
+ getGlobalSafesRpc,
+ fetchSafesForOwners,
getLiquidationDataRpc,
}
-// Helper functions
export const parseWad = (val: BigNumber) => utils.wadToFixed(val).toString()
export const parseRay = (val: BigNumber) => utils.rayToFixed(val).toString()
export const parseRad = (val: BigNumber) => utils.radToFixed(val).toString()
diff --git a/src/utils/helper.ts b/src/utils/helper.ts
index 34e4cbfb..ef509e36 100644
--- a/src/utils/helper.ts
+++ b/src/utils/helper.ts
@@ -127,6 +127,13 @@ export const toFixedString = (value: string, type: keyof typeof floatsTypes = 'W
}
}
+export const getBytes32String = (collateralType: string, tokensData: { [key: string]: TokenData }): string | null => {
+ const token = Object.values(tokensData).find(
+ (token) => token.symbol === collateralType || token.bytes32String === collateralType
+ )
+ return token ? token.bytes32String : null
+}
+
export const formatUserSafe = (
safes: Array,
liquidationData: ILiquidationData,
@@ -141,18 +148,19 @@ export const formatUserSafe = (
const { currentRedemptionPrice, currentRedemptionRate, collateralLiquidationData } = liquidationData
return safes
- .filter((s) => s.collateralType in collateralBytes32)
.map((s) => {
- const token = collateralBytes32[s.collateralType]
+ const bytes32String = getBytes32String(s.collateralType, tokensData)
+ if (!bytes32String || !(bytes32String in collateralBytes32)) return null
+
+ const token = collateralBytes32[bytes32String]
const accumulatedRate = collateralLiquidationData[token]?.accumulatedRate
const currentPrice = collateralLiquidationData[token]?.currentPrice
+ const availableDebt = returnAvailableDebt(currentPrice?.safetyPrice, '0', s.collateral, s.debt)
const liquidationCRatio = collateralLiquidationData[token]?.liquidationCRatio
const safetyCRatio = collateralLiquidationData[token]?.safetyCRatio
const liquidationPenalty = collateralLiquidationData[token]?.liquidationPenalty
const totalAnnualizedStabilityFee = collateralLiquidationData[token]?.totalAnnualizedStabilityFee
- const availableDebt = returnAvailableDebt(currentPrice?.safetyPrice, '0', s.collateral, s.debt)
-
const totalDebt = returnTotalValue(returnTotalDebt(s.debt, accumulatedRate) as string, '0').toString()
const liquidationPrice = getLiquidationPrice(
@@ -171,12 +179,13 @@ export const formatUserSafe = (
return {
id: s.safeId,
+ ownerAddress: s.ownerAddress,
safeHandler: s.safeHandler,
date: s.createdAt,
riskState: ratioChecker(Number(collateralRatio), Number(liquidationCRatio), Number(safetyCRatio)),
collateral: s.collateral,
collateralType: s.collateralType,
- collateralName: collateralBytes32[s.collateralType],
+ collateralName: collateralBytes32[bytes32String],
debt: s.debt,
totalDebt,
availableDebt,
@@ -192,6 +201,7 @@ export const formatUserSafe = (
currentRedemptionRate: currentRedemptionRate || '0',
} as ISafe
})
+ .filter((s): s is ISafe => s !== null)
.sort((a, b) => Number(b.riskState) - Number(a.riskState) || Number(b.debt) - Number(a.debt))
}
@@ -245,6 +255,35 @@ export const safeIsSafe = (totalCollateral: string, totalDebt: string, safetyPri
return totalDebtBN.lte(totalCollateralBN.mul(safetyPriceBN).div(gebUtils.RAY))
}
+/**
+ * Removes commas from a formatted number
+ * @param value
+ */
+export const parseFormattedNumber = (value: string): number => {
+ return parseFloat(value.replace(/,/g, ''))
+}
+
+/**
+ * Calculate the risk status text given a numeric risk status
+ * @param riskStatusNumeric
+ */
+export const calculateRiskStatusText = (riskStatusNumeric: Number) => {
+ switch (riskStatusNumeric) {
+ case 0:
+ return 'NO'
+ case 1:
+ return 'LOW'
+ case 2:
+ return 'ELEVATED'
+ case 3:
+ return 'HIGH'
+ case 4:
+ return 'LIQUIDATION'
+ default:
+ return 'LOW'
+ }
+}
+
/**
* Check the risk state of the current liquidation ratio given a fixed minLiquidationRatio
* @param currentLiquidationRatio
diff --git a/src/utils/index.test.ts b/src/utils/index.test.ts
index bd9a8c95..863624da 100644
--- a/src/utils/index.test.ts
+++ b/src/utils/index.test.ts
@@ -1,3 +1,4 @@
+import { formatDataNumber } from './formatDataNumber'
import {
formatNumber,
getCollateralRatio,
@@ -167,4 +168,26 @@ describe('utils', () => {
expect(returnTotalValue('2', '2', true, true)).toEqual('0')
})
})
+
+ describe('formatDataNumber', () => {
+ it('handles format decimals correctly', () => {
+ const input = '2457565485783295579314012'
+ expect(formatDataNumber(input, 18, 2)).toEqual('2,457,565.49')
+ })
+
+ it('handles dollar currency sign correctly', () => {
+ const input = '2457565485783295579314012'
+ expect(formatDataNumber(input, 18, 2, true)).toEqual('$2,457,565.49')
+ })
+
+ it('handles compact value and currency sign correctly', () => {
+ const input = '2457565485783295579314012'
+ expect(formatDataNumber(input, 18, 2, true, true)).toEqual('$2.46M')
+ })
+
+ it('handles compact format decimals and minimum decimals correctly', () => {
+ const input = '1000000000000000000'
+ expect(formatDataNumber(input, 18, 3, true, undefined, 4)).toEqual('$1.0000')
+ })
+ })
})
diff --git a/src/utils/interfaces.ts b/src/utils/interfaces.ts
index c44ac3c3..98ae62fc 100644
--- a/src/utils/interfaces.ts
+++ b/src/utils/interfaces.ts
@@ -16,6 +16,10 @@ export interface DynamicObject {
[key: string]: any
}
+export interface IOwnerAddressesResponse {
+ ownerAddresses: string[]
+}
+
interface IColors {
primary: string
secondary: string
@@ -366,6 +370,11 @@ export interface IFetchSafesPayload {
tokensData: { [key: string]: TokenData }
}
+export interface IFetchGlobalSafesPayload {
+ geb: Geb
+ tokensData: { [key: string]: TokenData }
+}
+
export interface IFetchSafeById extends IFetchSafesPayload {
safeId: string
}
diff --git a/yarn.lock b/yarn.lock
index a0afed32..595a4588 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2938,16 +2938,6 @@ __metadata:
languageName: node
linkType: hard
-"@fuul/sdk@npm:^4.7.1":
- version: 4.7.4
- resolution: "@fuul/sdk@npm:4.7.4"
- dependencies:
- axios: ^1.2.2
- nanoid: ^4.0.0
- checksum: 0351e5f52733dcc12f1f8f28c75289a36387721b64f7425e2e33c7e6c438e04af56c207e3bded8799c12500a4584be7070e26d559351dbdb8b28072678b16a0f
- languageName: node
- linkType: hard
-
"@graphql-typed-document-node/core@npm:^3.1.1":
version: 3.2.0
resolution: "@graphql-typed-document-node/core@npm:3.2.0"
@@ -3688,15 +3678,15 @@ __metadata:
languageName: node
linkType: hard
-"@opendollar/sdk@npm:1.7.3-rc.1":
- version: 1.7.3-rc.1
- resolution: "@opendollar/sdk@npm:1.7.3-rc.1"
+"@opendollar/sdk@npm:1.7.4-rc.1":
+ version: 1.7.4-rc.1
+ resolution: "@opendollar/sdk@npm:1.7.4-rc.1"
dependencies:
"@opendollar/abis": 0.0.0-605371bd
ethers: 5.4.7
peerDependencies:
utf-8-validate: ^5.0.2
- checksum: 43ad0437424c39a0938fc8994f1469ac9e3f83b3246e2fde7c3d293ecd68ffe11dae8eb60eebaaee7d91fb4182132014e165c7799a4713b504057aaa0246b8ff
+ checksum: 74974d2ca6af9030f04bb3bfeb692cd2028d83cc1fa606708069fba03bf0f3fdb9a4368dfbfb9f4403f3c4df04ac6bd7ad7d1e48d7a9852afb865dcb10fa2aed
languageName: node
linkType: hard
@@ -7016,7 +7006,7 @@ __metadata:
languageName: node
linkType: hard
-"axios@npm:^1.2.2, axios@npm:^1.6.7":
+"axios@npm:^1.6.7":
version: 1.7.2
resolution: "axios@npm:1.7.2"
dependencies:
@@ -14656,15 +14646,6 @@ __metadata:
languageName: node
linkType: hard
-"nanoid@npm:^4.0.0":
- version: 4.0.2
- resolution: "nanoid@npm:4.0.2"
- bin:
- nanoid: bin/nanoid.js
- checksum: 3fec62f422bc4727918eda0e7aa43e9cbb2e759be72813a0587b9dac99727d3c7ad972efce7f4f1d4cb5c7c554136a1ec3b1043d1d91d28d818d6acbe98200e5
- languageName: node
- linkType: hard
-
"napi-wasm@npm:^1.1.0":
version: 1.1.0
resolution: "napi-wasm@npm:1.1.0"
@@ -15022,8 +15003,7 @@ __metadata:
"@ethersproject/address": ^5.0.10
"@ethersproject/experimental": 5.4.0
"@ethersproject/providers": 5.4.5
- "@fuul/sdk": ^4.7.1
- "@opendollar/sdk": 1.7.3-rc.1
+ "@opendollar/sdk": 1.7.4-rc.1
"@opendollar/svg-generator": 1.0.5
"@react-spring/web": ^9.7.3
"@sentry/cli": ^2.31.0
|