From bc374205c32ff8cbf4b5d21368e212ea20a95b34 Mon Sep 17 00:00:00 2001 From: Patrick Gallagher Date: Wed, 3 Jul 2024 23:23:55 -0700 Subject: [PATCH] Bolts multiplier update (#604) * Rename GenesisNFV * scaffold multiplier * connect-src update * update quests * update description * display multiplier * lint fix --- public/index.html | 2 +- src/chains.ts | 7 +- src/components/Bolts/AddressCell.tsx | 8 +- src/containers/Bolts/Leaderboard.tsx | 22 ++--- src/containers/Bolts/index.tsx | 126 +++++++++++++++++---------- src/containers/Bolts/quests.tsx | 124 +++++++++++++------------- src/model/boltsModel.ts | 74 ++++++++-------- src/services/checkSanctions.ts | 6 +- 8 files changed, 200 insertions(+), 169 deletions(-) diff --git a/public/index.html b/public/index.html index 6940ab24..f1b85a2e 100644 --- a/public/index.html +++ b/public/index.html @@ -8,7 +8,7 @@ script-src 'self' blob: https://kb.wowto.ai https://app.wowto.ai http://cdn.matomo.cloud/usekeyp.matomo.cloud/matomo.js https://cdn.matomo.cloud/usekeyp.matomo.cloud/matomo.js https://cdn.matomo.cloud/matomo.js https://usekeyp.matomo.cloud/matomo.js; media-src 'self'; img-src 'self' data: blob: https://explorer-api.walletconnect.com https://usekeyp.matomo.cloud https://app.opendollar.com; - connect-src 'self' blob: https://*.quiknode.pro https://api.opensea.io https://api.fuul.xyz https://api.camelot.exchange https://opt-mainnet.g.alchemy.com https://arb-mainnet.g.alchemy.com https://mainnet.optimism.io/ https://eth.llamarpc.com https://base.llamarpc.com https://polygon-bor-rpc.publicnode.com https://eth-pokt.nodies.app https://polygon-pokt.nodies.app https://op-pokt.nodies.app https://arb-pokt.nodies.app https://holy-damp-firefly.arbitrum-mainnet.quiknode.pro https://api.studio.thegraph.com https://od-subgraph-node-image.onrender.com https://usekeyp.matomo.cloud https://o1016103.ingest.us.sentry.io/api/4507153379295232/envelope/ https://o1016103.ingest.us.sentry.io/api/4507153379295232/security/ https://arbitrum-sepolia.infura.io https://arbitrum-sepolia.blockpi.network/v1/rpc/public https://arbitrum.blockpi.network/v1/rpc/public https://optimism.blockpi.network wss://relay.walletconnect.com/ https://verify.walletconnect.org wss://www.walletlink.org/rpc https://explorer-api.walletconnect.com https://chain-proxy.wallet.coinbase.com https://rpc.walletconnect.com https://bot.opendollar.com https://bot.dev.opendollar.com https://subgraph.reflexer.finance/subgraphs/name/reflexer-labs/rai https://api.country.is/ ; + connect-src 'self' blob: https://eth-pokt.nodies.app http://localhost:3000 https://*.quiknode.pro https://api.opensea.io https://api.fuul.xyz https://api.camelot.exchange https://opt-mainnet.g.alchemy.com https://arb-mainnet.g.alchemy.com https://mainnet.optimism.io/ https://eth.llamarpc.com https://base.llamarpc.com https://polygon-bor-rpc.publicnode.com https://eth-pokt.nodies.app https://polygon-pokt.nodies.app https://op-pokt.nodies.app https://arb-pokt.nodies.app https://holy-damp-firefly.arbitrum-mainnet.quiknode.pro https://api.studio.thegraph.com https://od-subgraph-node-image.onrender.com https://usekeyp.matomo.cloud https://o1016103.ingest.us.sentry.io/api/4507153379295232/envelope/ https://o1016103.ingest.us.sentry.io/api/4507153379295232/security/ https://arbitrum-sepolia.infura.io https://arbitrum-sepolia.blockpi.network/v1/rpc/public https://arbitrum.blockpi.network/v1/rpc/public https://optimism.blockpi.network wss://relay.walletconnect.com/ https://verify.walletconnect.org wss://www.walletlink.org/rpc https://explorer-api.walletconnect.com https://chain-proxy.wallet.coinbase.com https://rpc.walletconnect.com https://bot.opendollar.com https://bot.dev.opendollar.com https://subgraph.reflexer.finance/subgraphs/name/reflexer-labs/rai https://api.country.is/ ; object-src 'self' blob:; form-action 'self'; font-src 'self' data: https://fonts.gstatic.com; diff --git a/src/chains.ts b/src/chains.ts index e6e380e5..47783e05 100644 --- a/src/chains.ts +++ b/src/chains.ts @@ -25,10 +25,9 @@ const ETH: AddEthereumChainParameter['nativeCurrency'] = { decimals: 18, } -export const RPC_URL_ETHEREUM = - process.env.REACT_APP_RPC_URL_ETHEREUM && process.env.NODE_ENV !== 'development' - ? process.env.REACT_APP_RPC_URL_ETHEREUM - : 'https://eth.llamarpc.com' +export const RPC_URL_ETHEREUM = process.env.REACT_APP_RPC_URL_ETHEREUM + ? process.env.REACT_APP_RPC_URL_ETHEREUM + : 'https://eth.llamarpc.com' export const RPC_URL_ARBITRUM = 'https://arbitrum.blockpi.network/v1/rpc/public' export const RPC_URL_OPTIMISM = 'https://op-pokt.nodies.app' export const RPC_URL_POLYGON = 'https://polygon-bor-rpc.publicnode.com' diff --git a/src/components/Bolts/AddressCell.tsx b/src/components/Bolts/AddressCell.tsx index ff154c42..528100d3 100644 --- a/src/components/Bolts/AddressCell.tsx +++ b/src/components/Bolts/AddressCell.tsx @@ -7,18 +7,18 @@ import styled from 'styled-components' interface AddressCellProps { address: string - userFuulDataAddress: string + userBoltsDataAddress: string data: LeaderboardUser[] } -const AddressCell: React.FC = ({ address, userFuulDataAddress, data }) => { +const AddressCell: React.FC = ({ address, userBoltsDataAddress, data }) => { const userInTop10 = useMemo(() => data.find((user) => user.rank <= 10 && user.address === address), [data, address]) // Skip ENS check for users not in the top 10 const resolvedAddress = useAddress(address, 0, !userInTop10) return (
- {userFuulDataAddress === address && ( + {userBoltsDataAddress === address && ( = ({ address, userFuulDataAddress, export default React.memo(AddressCell, (prevProps, nextProps) => { return ( prevProps.address === nextProps.address && - prevProps.userFuulDataAddress === nextProps.userFuulDataAddress && + prevProps.userBoltsDataAddress === nextProps.userBoltsDataAddress && prevProps.data === nextProps.data ) }) diff --git a/src/containers/Bolts/Leaderboard.tsx b/src/containers/Bolts/Leaderboard.tsx index 7a64955d..cbf759b9 100644 --- a/src/containers/Bolts/Leaderboard.tsx +++ b/src/containers/Bolts/Leaderboard.tsx @@ -21,7 +21,7 @@ import { LeaderboardUser } from '~/model/boltsModel' const columnHelper = createColumnHelper() // @ts-ignore -const Table = ({ data, userFuulData }) => { +const Table = ({ data, userBoltsData }) => { const [sorting, setSorting] = useState([]) const [globalFilter, setGlobalFilter] = useState('') 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..b11ab59b 100644 --- a/src/containers/Bolts/index.tsx +++ b/src/containers/Bolts/index.tsx @@ -4,22 +4,23 @@ 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 (
@@ -27,10 +28,25 @@ const Bolts = () => { Welcome Vault Keepers!
- Leaderboard - + + + +
+
+ Leaderboard + +
+ {/*
@@ -45,7 +61,7 @@ const Bolts = () => {

-
+ */}
Quests @@ -56,7 +72,7 @@ const Bolts = () => {
Multipliers - {MULTIPLIERS(boltsEarnedData).map((quest, index) => ( + {MULTIPLIERS(multipliersData).map((quest, index) => ( ))}
@@ -79,6 +95,20 @@ 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; @@ -89,46 +119,46 @@ const Container = styled.div` 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 +214,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..f16e6830 100644 --- a/src/containers/Bolts/quests.tsx +++ b/src/containers/Bolts/quests.tsx @@ -1,7 +1,6 @@ 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' @@ -108,31 +107,11 @@ export type BoltsEarnedData = { [key: string]: string } -export const MULTIPLIERS = (boltsEarnedData: BoltsEarnedData) => [ - { - title: 'Invite a Friend', - text: ( - <> - Create a referral link by signing a message with your wallet. - - - ), - button: '', - 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'] || '-', - }, - ], - }, +export type MultipliersData = { + [key: string]: string +} + +export const MULTIPLIERS = (multipliersData: MultipliersData) => [ { title: 'Genesis NFV User', button: ( @@ -140,79 +119,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 +203,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 +229,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 +249,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 +276,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 +303,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/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/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', },