Skip to content

Commit

Permalink
Merge pull request #10 from celo-tools/1.3.0
Browse files Browse the repository at this point in the history
Implement price chart and more account options
  • Loading branch information
jmrossy authored Jan 1, 2022
2 parents 8410a4a + fe16737 commit 787b5c0
Show file tree
Hide file tree
Showing 22 changed files with 596 additions and 267 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@celo-tools/mento-fi",
"version": "1.2.0",
"version": "1.3.0",
"description": "A simple DApp for Celo Mento exchanges",
"keywords": [
"Celo",
Expand All @@ -26,6 +26,7 @@
},
"dependencies": {
"@celo-tools/use-contractkit": "^2.1.2",
"@ethersproject/abi": "^5.5.0",
"@ethersproject/address": "^5.5.0",
"@metamask/inpage-provider": "6.0.1",
"@metamask/jazzicon": "https://github.com/jmrossy/jazzicon#7a8df28",
Expand Down
3 changes: 3 additions & 0 deletions src/blockchain/ABIs/sortedOracles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const ABI = JSON.parse(
'[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"MedianUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"address","name":"oracleAddress","type":"address"}],"name":"OracleAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"address","name":"oracleAddress","type":"address"}],"name":"OracleRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"address","name":"oracle","type":"address"}],"name":"OracleReportRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"address","name":"oracle","type":"address"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"OracleReported","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"reportExpiry","type":"uint256"}],"name":"ReportExpirySet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"reportExpiry","type":"uint256"}],"name":"TokenReportExpirySet","type":"event"},{"constant":true,"inputs":[],"name":"initialized","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"isOracle","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isOwner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"oracles","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"renounceOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"reportExpirySeconds","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"tokenReportExpirySeconds","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getVersionNumber","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_reportExpirySeconds","type":"uint256"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_reportExpirySeconds","type":"uint256"}],"name":"setReportExpiry","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_reportExpirySeconds","type":"uint256"}],"name":"setTokenReportExpiry","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"oracleAddress","type":"address"}],"name":"addOracle","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"oracleAddress","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"removeOracle","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"n","type":"uint256"}],"name":"removeExpiredReports","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"isOldestReportExpired","outputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"address","name":"lesserKey","type":"address"},{"internalType":"address","name":"greaterKey","type":"address"}],"name":"report","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"numRates","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"medianRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"getRates","outputs":[{"internalType":"address[]","name":"","type":"address[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"enum SortedLinkedListWithMedian.MedianRelation[]","name":"","type":"uint8[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"numTimestamps","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"medianTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"getTimestamps","outputs":[{"internalType":"address[]","name":"","type":"address[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"enum SortedLinkedListWithMedian.MedianRelation[]","name":"","type":"uint8[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"getOracles","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"getTokenReportExpirySeconds","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]'
)
21 changes: 7 additions & 14 deletions src/blockchain/blocks.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,26 @@
import type { ContractKit } from '@celo/contractkit'
import BigNumber from 'bignumber.js'
import { AVG_BLOCK_TIMES } from 'src/config/consts'
import { logger } from 'src/utils/logger'

export interface LatestBlockDetails {
nodeUrl: string
number: number
timestamp: number
}

//TODO
const provider = {
connection: {
url: 'foobar',
},
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getBlock: (s: any) => ({ number: 9125760, timestamp: 1633205701023 }),
}
export async function getLatestBlockDetails(kit: ContractKit): Promise<LatestBlockDetails | null> {
const block = await kit.web3.eth.getBlock('latest')

export async function getLatestBlockDetails(): Promise<LatestBlockDetails | null> {
const nodeUrl = provider.connection?.url
const block = await provider.getBlock('latest')
if (!block || !block.number) {
logger.warn('Latest block is not valid')
return null
}

const timestamp = new BigNumber(block.timestamp).toNumber()

return {
nodeUrl,
number: block.number,
timestamp: block.timestamp,
timestamp,
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/components/buttons/SolidButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ export function SolidButton(props: PropsWithChildren<ButtonProps>) {
onActive = 'active:bg-red-400'
} else if (color === 'white') {
baseColors = 'bg-white text-black'
onHover = 'hover:bg-gray-50'
onActive = 'active:bg-gray-100'
onHover = 'hover:bg-gray-100'
onActive = 'active:bg-gray-200'
}
const onDisabled = 'disabled:bg-gray-300 disabled:text-gray-500'
const weight = bold ? 'font-semibold' : ''
Expand Down
20 changes: 20 additions & 0 deletions src/components/nav/BalancesSummary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useAppSelector } from 'src/app/hooks'
import { NativeTokenId, NativeTokens } from 'src/config/tokens'
import { TokenIcon } from 'src/images/tokens/TokenIcon'
import { fromWeiRounded } from 'src/utils/amount'

export function BalancesSummary() {
const balances = useAppSelector((s) => s.account.balances)
const tokenIds = Object.keys(balances) as NativeTokenId[]

return (
<div className="flex flex-wrap">
{tokenIds.map((id) => (
<div style={{ minWidth: '35%' }} className="flex mx-2 my-2" key={id}>
<TokenIcon token={NativeTokens[id]} size="xs" />
<div className="ml-1">{fromWeiRounded(balances[id])}</div>
</div>
))}
</div>
)
}
58 changes: 51 additions & 7 deletions src/components/nav/ConnectButton.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,34 @@
import { useContractKit } from '@celo-tools/use-contractkit'
import Image from 'next/image'
import { useState } from 'react'
import useDropdownMenu from 'react-accessible-dropdown-menu-hook'
import { SolidButton } from 'src/components/buttons/SolidButton'
import { Identicon } from 'src/components/Identicon'
import { BalancesSummary } from 'src/components/nav/BalancesSummary'
import { NetworkModal } from 'src/components/nav/NetworkModal'
import Clipboard from 'src/images/icons/clipboard-plus.svg'
import Cube from 'src/images/icons/cube.svg'
import Wallet from 'src/images/icons/wallet.svg'
import XCircle from 'src/images/icons/x-circle.svg'
import { shortenAddress } from 'src/utils/addresses'
import { tryClipboardSet } from 'src/utils/clipboard'

export function ConnectButton() {
const { connect, address, destroy } = useContractKit()

const { buttonProps, itemProps, isOpen, setIsOpen } = useDropdownMenu(1)
const { buttonProps, itemProps, isOpen, setIsOpen } = useDropdownMenu(3)

const onClickCopy = async () => {
setIsOpen(false)
if (!address) return
await tryClipboardSet(address)
}

const [showNetworkModal, setShowNetworkModal] = useState(false)
const onClickChangeNetwork = () => {
setIsOpen(false)
setShowNetworkModal(true)
}

const onClickDisconnect = async () => {
setIsOpen(false)
Expand Down Expand Up @@ -45,18 +63,26 @@ export function ConnectButton() {
</SolidButton>
)}
<div
className={`dropdown-menu w-36 mt-12 mr-px bg-white ${isOpen ? '' : 'hidden'}`}
className={`dropdown-menu w-60 mt-12 mr-px bg-white ${isOpen ? '' : 'hidden'}`}
role="menu"
>
<a
{...itemProps[0]}
className="flex items-center cursor-pointer hover:bg-gray-50"
onClick={onClickDisconnect}
>
<BalancesSummary />
<a {...itemProps[0]} className={menuOptionClasses} onClick={onClickCopy}>
<CopyIcon />
<div>Copy Address</div>
</a>
<a {...itemProps[1]} className={menuOptionClasses} onClick={onClickChangeNetwork}>
<NetworkIcon />
<div>Change Network</div>
</a>
<a {...itemProps[2]} className={menuOptionClasses} onClick={onClickDisconnect}>
<LogoutIcon />
<div>Disconnect</div>
</a>
</div>
{showNetworkModal && (
<NetworkModal isOpen={showNetworkModal} close={() => setShowNetworkModal(false)} />
)}
</div>
)
}
Expand All @@ -76,3 +102,21 @@ function LogoutIcon() {
</div>
)
}

function NetworkIcon() {
return (
<div className="flex items-center sm:mr-3">
<Image src={Cube} alt="Network" width={18} height={18} />
</div>
)
}

function CopyIcon() {
return (
<div className="flex items-center sm:mr-3">
<Image src={Clipboard} alt="Copy" width={18} height={18} />
</div>
)
}

const menuOptionClasses = 'flex items-center cursor-pointer p-2 mt-1 rounded hover:bg-gray-100'
4 changes: 3 additions & 1 deletion src/components/nav/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ function BlockIndicator() {
></div>
<div className="hidden bg-yellow-300 bg-red-600"></div>
</button>
<NetworkModal isOpen={showNetworkModal} close={() => setShowNetworkModal(false)} />
{showNetworkModal && (
<NetworkModal isOpen={showNetworkModal} close={() => setShowNetworkModal(false)} />
)}
</>
)
}
Expand Down
12 changes: 5 additions & 7 deletions src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,18 @@ interface Config {
version: string | null
url: string
discordUrl: string
chainId: number
blockscoutUrl: string
showPriceChart: boolean
}

const isDevMode = process?.env?.NODE_ENV === 'development'
const version = process?.env?.NEXT_PUBLIC_VERSION ?? null

const configMainnet: Config = {
export const config: Config = Object.freeze({
debug: isDevMode,
version,
url: 'https://mento.finance',
discordUrl: 'https://discord.gg/E9AqUQnWQE',
chainId: 42220,
showPriceChart: false,
}

export const config = Object.freeze(configMainnet)
blockscoutUrl: 'https://explorer.celo.org',
showPriceChart: true,
})
6 changes: 0 additions & 6 deletions src/config/tokens.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { config } from 'src/config/config'
import { Color } from 'src/styles/Color'

export interface Token {
Expand All @@ -7,7 +6,6 @@ export interface Token {
name: string
color: string
decimals: number
chainId: number
}

export interface TokenWithBalance extends Token {
Expand Down Expand Up @@ -37,31 +35,27 @@ export const NativeTokens: INativeTokens = {
name: 'Celo Native',
color: Color.celoGold,
decimals: 18,
chainId: config.chainId,
},
cUSD: {
id: NativeTokenId.cUSD,
symbol: NativeTokenId.cUSD,
name: 'Celo Dollar',
color: Color.celoGreen,
decimals: 18,
chainId: config.chainId,
},
cEUR: {
id: NativeTokenId.cEUR,
symbol: NativeTokenId.cEUR,
name: 'Celo Euro',
color: Color.celoGreen,
decimals: 18,
chainId: config.chainId,
},
cREAL: {
id: NativeTokenId.cREAL,
symbol: NativeTokenId.cREAL,
name: 'Celo Real',
color: Color.celoGreen,
decimals: 18,
chainId: config.chainId,
},
}

Expand Down
74 changes: 46 additions & 28 deletions src/features/chart/PriceChartCelo.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { useAppSelector } from 'src/app/hooks'
import { Mainnet, useContractKit } from '@celo-tools/use-contractkit'
import { useEffect } from 'react'
import { toast } from 'react-toastify'
import { useAppDispatch, useAppSelector } from 'src/app/hooks'
import { NativeTokenId } from 'src/config/tokens'
import { fetchTokenPrice } from 'src/features/chart/fetchPrices'
import styles from 'src/features/chart/PriceChart.module.css'
// import { fetchTokenPriceActions } from 'src/features/chart/fetchPrices'
import { tokenPriceHistoryToChartData } from 'src/features/chart/utils'
import { FloatingBox } from 'src/layout/FloatingBox'
import { Color } from 'src/styles/Color'
import { logger } from 'src/utils/logger'
import ReactFrappeChart from './ReactFrappeChart'

interface PriceChartProps {
Expand All @@ -16,40 +20,54 @@ interface PriceChartProps {
export function PriceChartCelo(props: PriceChartProps) {
const { stableTokenId, containerClasses, height } = props

// const dispatch = useAppDispatch()
// useEffect(() => {
// dispatch(
// fetchTokenPriceActions.trigger({
// baseCurrency: NativeTokenId.CELO,
// })
// )
// }, [dispatch])
const { kit, initialised, network } = useContractKit()

const dispatch = useAppDispatch()
useEffect(() => {
if (!kit || !initialised || network?.chainId !== Mainnet.chainId) return
dispatch(
fetchTokenPrice({
kit,
baseCurrency: NativeTokenId.CELO,
})
)
.unwrap()
.catch((err) => {
toast.warn('Error retrieving chart data')
logger.error('Failed to token prices', err)
})
}, [dispatch, kit, initialised, network])

const allPrices = useAppSelector((s) => s.tokenPrice.prices)
const celoPrices = allPrices[NativeTokenId.CELO]
const stableTokenPrices = celoPrices ? celoPrices[stableTokenId] : undefined
const chartData = tokenPriceHistoryToChartData(stableTokenPrices)
const chartHeight = height || 250

// Only show chart for Mainnet
if (network?.chainId !== Mainnet.chainId) return null

return (
<FloatingBox width="w-96" classes={`overflow-hidden ${containerClasses}`}>
<div className="flex justify-between">
<h2 className="text-md font-medium pl-3 py-1">CELO Price (USD)</h2>
{/* TODO duration toggle */}
<div></div>
</div>
<div className={`-ml-6 -mr-4 -my-1 ${styles.priceChartContainer}`}>
<ReactFrappeChart
type="line"
colors={chartConfig.colors}
height={chartHeight}
axisOptions={chartConfig.axis}
tooltipOptions={chartConfig.tooltipOptions}
// @ts-ignore TODO find issue, works in Celo Wallet
data={chartData}
/>
</div>
</FloatingBox>
<div className="mb-12 ml-10">
<FloatingBox width="w-96" classes={`overflow-hidden ${containerClasses}`}>
<div className="flex justify-between">
<h2 className="text-md font-medium pl-3 py-1">CELO Price (USD)</h2>
{/* TODO duration toggle */}
<div></div>
</div>
<div className={`-ml-6 -mr-4 -my-1 ${styles.priceChartContainer}`}>
<ReactFrappeChart
type="line"
colors={chartConfig.colors}
height={chartHeight}
axisOptions={chartConfig.axis}
tooltipOptions={chartConfig.tooltipOptions}
// @ts-ignore TODO find issue, works in Celo Wallet
data={chartData}
/>
</div>
</FloatingBox>
</div>
)
}

Expand Down
Loading

1 comment on commit 787b5c0

@vercel
Copy link

@vercel vercel bot commented on 787b5c0 Jan 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.