1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-12-02 05:57:29 +01:00

Merge pull request #58 from oceanprotocol/feature/balancer

Pool interaction
This commit is contained in:
Matthias Kretschmann 2020-08-31 11:51:19 +02:00 committed by GitHub
commit 5e772ca843
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1784 additions and 1257 deletions

2130
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -22,35 +22,35 @@
"@coingecko/cryptoformat": "^0.3.8",
"@loadable/component": "^5.13.1",
"@oceanprotocol/art": "^3.0.0",
"@oceanprotocol/lib": "^0.1.12",
"@oceanprotocol/react": "^0.0.37",
"@oceanprotocol/lib": "^0.1.16",
"@oceanprotocol/react": "^0.0.40",
"@oceanprotocol/typographies": "^0.1.0",
"@sindresorhus/slugify": "^1.0.0",
"@tippyjs/react": "^4.1.0",
"@toruslabs/torus-embed": "^1.8.2",
"@toruslabs/torus-embed": "^1.8.3",
"@types/classnames": "^2.2.10",
"@vercel/node": "^1.7.4",
"@walletconnect/web3-provider": "^1.2.1",
"axios": "^0.19.2",
"@vercel/node": "^1.8.1",
"@walletconnect/web3-provider": "^1.2.2",
"axios": "^0.20.0",
"classnames": "^2.2.6",
"date-fns": "^2.15.0",
"date-fns": "^2.16.0",
"decimal.js": "^10.2.0",
"dotenv": "^8.2.0",
"ethereum-blockies": "github:MyEtherWallet/blockies",
"filesize": "^6.1.0",
"formik": "^2.1.5",
"gatsby": "^2.24.47",
"gatsby": "^2.24.52",
"gatsby-image": "^2.4.16",
"gatsby-plugin-manifest": "^2.4.23",
"gatsby-plugin-manifest": "^2.4.26",
"gatsby-plugin-react-helmet": "^3.3.10",
"gatsby-plugin-remove-trailing-slashes": "^2.3.11",
"gatsby-plugin-sharp": "^2.6.27",
"gatsby-plugin-sharp": "^2.6.30",
"gatsby-plugin-svgr": "^2.0.2",
"gatsby-plugin-webpack-size": "^1.0.0",
"gatsby-source-filesystem": "^2.3.24",
"gatsby-source-graphql": "^2.7.1",
"gatsby-source-filesystem": "^2.3.27",
"gatsby-source-graphql": "^2.7.2",
"gatsby-transformer-json": "^2.4.11",
"gatsby-transformer-remark": "^2.8.28",
"gatsby-transformer-remark": "^2.8.31",
"gatsby-transformer-sharp": "^2.5.13",
"intersection-observer": "^0.11.0",
"is-url-superb": "^4.0.0",
@ -58,7 +58,7 @@
"lodash.omit": "^4.5.0",
"query-string": "^6.13.1",
"react": "^16.13.1",
"react-data-table-component": "^6.11.0",
"react-data-table-component": "^6.11.2",
"react-datepicker": "^3.1.3",
"react-dom": "^16.13.1",
"react-dotdotdot": "^1.3.1",
@ -72,32 +72,32 @@
"react-toastify": "^6.0.8",
"shortid": "^2.2.15",
"slugify": "^1.4.5",
"swr": "^0.3.0",
"swr": "^0.3.1",
"yup": "^0.29.3"
},
"devDependencies": {
"@babel/core": "^7.11.1",
"@types/lodash.debounce": "^4.0.3",
"@types/lodash.omit": "^4.5.6",
"@babel/core": "^7.11.4",
"@babel/preset-typescript": "^7.10.1",
"@storybook/addon-actions": "^6.0.12",
"@storybook/addon-storyshots": "^6.0.12",
"@storybook/react": "^6.0.12",
"@storybook/addon-actions": "^6.0.20",
"@storybook/addon-storyshots": "^6.0.20",
"@storybook/react": "^6.0.20",
"@svgr/webpack": "^5.4.0",
"@testing-library/jest-dom": "^5.11.3",
"@testing-library/react": "^10.4.8",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^10.4.9",
"@types/jest": "^26.0.10",
"@types/loadable__component": "^5.13.0",
"@types/node": "^14.6.0",
"@types/react": "^16.9.46",
"@types/lodash.debounce": "^4.0.3",
"@types/lodash.omit": "^4.5.6",
"@types/node": "^14.6.1",
"@types/react": "^16.9.48",
"@types/react-datepicker": "^3.1.1",
"@types/react-helmet": "^6.1.0",
"@types/react-paginate": "^6.2.1",
"@types/react-tabs": "^2.3.2",
"@types/shortid": "0.0.29",
"@types/yup": "^0.29.5",
"@typescript-eslint/eslint-plugin": "^3.9.1",
"@typescript-eslint/parser": "^3.9.1",
"@types/yup": "^0.29.6",
"@typescript-eslint/eslint-plugin": "^3.10.1",
"@typescript-eslint/parser": "^3.10.1",
"babel-loader": "^8.1.0",
"babel-preset-react-app": "^9.1.2",
"eslint": "^7.7.0",
@ -106,10 +106,10 @@
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-react": "^7.20.6",
"identity-obj-proxy": "^3.0.0",
"jest": "^26.4.0",
"prettier": "^2.0.5",
"jest": "^26.4.2",
"prettier": "^2.1.1",
"serve": "^11.3.2",
"source-map-explorer": "^2.4.2",
"source-map-explorer": "^2.5.0",
"typescript": "^3.9.7"
},
"repository": {

View File

@ -0,0 +1,24 @@
.link {
color: var(--brand-grey);
}
.link svg {
width: 0.7em;
height: 0.7em;
display: inline-block;
fill: var(--brand-grey-light);
}
.link code {
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-all;
padding: 0;
}
.link:hover,
.link:focus,
.link:hover *,
.link:focus * {
color: var(--brand-pink);
}

View File

@ -0,0 +1,29 @@
import React, { ReactElement, ReactNode } from 'react'
import { ReactComponent as External } from '../../images/external.svg'
import styles from './EtherscanLink.module.css'
export default function EtherscanLink({
network,
path,
children
}: {
network?: 'rinkeby' | 'kovan' | 'ropsten'
path: string
children: ReactNode
}): ReactElement {
const url = network
? `https://${network}.etherscan.io`
: `https://etherscan.io`
return (
<a
href={`${url}/${path}`}
title="View on Etherscan"
target="_blank"
rel="noreferrer"
className={styles.link}
>
{children} <External />
</a>
)
}

View File

@ -3,4 +3,5 @@
font-size: var(--font-size-small);
margin-left: calc(var(--spacer) / 6);
color: var(--color-secondary);
font-weight: var(--font-weight-base);
}

View File

@ -35,7 +35,7 @@ export default function Conversion({
const { eur } = data['ocean-protocol']
const converted = eur * Number(price)
setPriceEur(`${formatCurrency(converted, 'EUR', 'en', true)}`)
setPriceEur(`${formatCurrency(converted, 'EUR', undefined, true)}`)
}
useEffect(() => {
@ -61,7 +61,7 @@ export default function Conversion({
className={styleClasses}
title="Approximation based on current spot price on Coingecko"
>
EUR {priceEur}
{priceEur} EUR
</span>
)
}

View File

@ -0,0 +1,19 @@
.price {
display: inline-block;
font-weight: var(--font-weight-bold);
font-size: var(--font-size-large);
color: var(--brand-grey-dark);
line-height: 1;
}
.price span:first-child {
font-weight: var(--font-weight-base);
color: var(--color-secondary);
font-size: var(--font-size-base);
}
.small {
/* lazy making-conversion-smaller-with-same-markup */
transform: scale(0.8);
transform-origin: left 80%;
}

View File

@ -0,0 +1,39 @@
import React, { ReactElement } from 'react'
import { formatCurrency } from '@coingecko/cryptoformat'
import classNames from 'classnames/bind'
import Conversion from './Conversion'
import styles from './PriceUnit.module.css'
const cx = classNames.bind(styles)
export default function PriceUnit({
price,
className,
small,
conversion,
symbol
}: {
price: string
className?: string
small?: boolean
conversion?: boolean
symbol?: string
}): ReactElement {
const styleClasses = cx({
price: true,
small: small,
[className]: className
})
return (
<div className={styleClasses}>
{Number.isInteger(Number(price))
? price
: Number.isNaN(Number(price))
? '-'
: formatCurrency(Number(price), '', undefined, false, true)}{' '}
<span>{symbol || 'OCEAN'}</span>
{conversion && <Conversion price={price} />}
</div>
)
}

View File

@ -1,23 +1,4 @@
.price {
font-weight: var(--font-weight-bold);
font-size: var(--font-size-large);
color: var(--brand-grey-dark);
line-height: 1;
}
.price span:first-child {
font-weight: var(--font-weight-base);
color: var(--color-secondary);
font-size: var(--font-size-base);
}
.empty {
color: var(--color-secondary);
font-weight: var(--font-weight-bold);
}
.small {
/* lazy making-conversion-smaller-with-same-markup */
transform: scale(0.8);
transform-origin: left 80%;
}

View File

@ -1,61 +1,34 @@
import React, { ReactElement, useState, useEffect } from 'react'
import classNames from 'classnames/bind'
import PriceConversion from './Conversion'
import styles from './index.module.css'
import { formatCurrency } from '@coingecko/cryptoformat'
import { useMetadata, useOcean } from '@oceanprotocol/react'
import { DDO } from '@oceanprotocol/lib'
import Loader from '../Loader'
import Tooltip from '../Tooltip'
const cx = classNames.bind(styles)
import PriceUnit from './PriceUnit'
export default function Price({
ddo,
className,
small,
setPriceOutside
conversion
}: {
ddo: DDO
className?: string
small?: boolean
setPriceOutside?: (price: string) => void
conversion?: boolean
}): ReactElement {
const { ocean, chainId, accountId } = useOcean()
const { getBestPrice } = useMetadata()
const [price, setPrice] = useState<string>()
useEffect(() => {
async function init() {
console.log(ocean)
const price = await getBestPrice(ddo.dataToken)
setPrice(price)
setPriceOutside && price !== '' && setPriceOutside(price)
}
init()
}, [chainId, accountId, ocean])
const styleClasses = cx({
price: true,
small: small,
[className]: className
})
const isFree = price === '0'
const displayPrice = isFree ? (
'Free'
) : (
<>
<span>OCEAN</span> {formatCurrency(Number(price), '', 'en', false, true)}
<PriceConversion price={price} />
</>
)
const { ocean } = useOcean()
const { price } = useMetadata(ddo)
return !ocean ? (
<div className={styles.empty}>Please connect your wallet to view price</div>
<div className={styles.empty}>Connect your wallet to view price</div>
) : price ? (
<div className={styleClasses}>{displayPrice}</div>
<PriceUnit
price={price}
className={className}
small={small}
conversion={conversion}
/>
) : price === '' ? (
<div className={styles.empty}>
No price found{' '}

View File

@ -23,7 +23,7 @@ export default function Tabs({
<Tab
className={styles.tab}
key={item.title}
onClick={() => handleTabChange(item.title)}
onClick={handleTabChange ? () => handleTabChange(item.title) : null}
>
{item.title}
</Tab>

View File

@ -11,8 +11,8 @@
}
.icon {
width: 15px;
height: 15px;
width: 1rem;
height: 1rem;
cursor: help;
display: inline-block;
margin-bottom: -0.1rem;

View File

@ -1,12 +1,10 @@
import React, { useState, useEffect } from 'react'
import React from 'react'
import { Link } from 'gatsby'
import Dotdotdot from 'react-dotdotdot'
import { MetadataMarket } from '../../@types/Metadata'
import Price from '../atoms/Price'
import styles from './AssetTeaser.module.css'
import { useMetadata } from '@oceanprotocol/react'
import { DDO } from '@oceanprotocol/lib'
import Loader from '../atoms/Loader'
declare type AssetTeaserProps = {
ddo: DDO

View File

@ -17,7 +17,7 @@ export default function Details(): ReactElement {
{Object.entries(balance).map(([key, value]) => (
<li className={styles.balance} key={key}>
<span>{key.toUpperCase()}</span>{' '}
{formatCurrency(value, '', 'en', true, true)}
{formatCurrency(value, '', undefined, true, true)}
{key === 'ocean' && <Conversion price={value} />}
</li>
))}

View File

@ -17,12 +17,10 @@ import Alert from '../../atoms/Alert'
export default function Compute({
ddo,
isBalanceSufficient,
setPrice
isBalanceSufficient
}: {
ddo: DDO
isBalanceSufficient: boolean
setPrice: (price: string) => void
}): ReactElement {
const { ocean } = useOcean()
const { compute, isLoading, computeStepText, computeError } = useCompute()
@ -89,7 +87,7 @@ export default function Compute({
return (
<div className={styles.compute}>
<Price ddo={ddo} setPriceOutside={setPrice} />
<Price ddo={ddo} conversion />
<div className={styles.info}>
<div className={styles.selectType}>

View File

@ -12,13 +12,11 @@ import { useOcean, useConsume } from '@oceanprotocol/react'
export default function Consume({
ddo,
file,
isBalanceSufficient,
setPrice
isBalanceSufficient
}: {
ddo: DDO
file: FileMetadata
isBalanceSufficient: boolean
setPrice: (price: string) => void
}): ReactElement {
const { ocean } = useOcean()
const { consumeStepText, consume, consumeError } = useConsume()
@ -49,7 +47,7 @@ export default function Consume({
<File file={file} />
</div>
<div className={styles.pricewrapper}>
<Price ddo={ddo} setPriceOutside={setPrice} />
<Price ddo={ddo} conversion />
<PurchaseButton />
</div>
</div>

View File

@ -0,0 +1,17 @@
.actions {
margin-left: -2rem;
margin-right: -2rem;
padding-left: var(--spacer);
padding-right: var(--spacer);
margin-top: calc(var(--spacer) / 2);
padding-top: calc(var(--spacer) / 1.5);
border-top: 1px solid var(--brand-grey-lighter);
text-align: center;
display: flex;
justify-content: center;
}
.actions button {
margin-left: calc(var(--spacer) / 4);
margin-right: calc(var(--spacer) / 4);
}

View File

@ -0,0 +1,32 @@
import React, { ReactElement } from 'react'
import Loader from '../../../atoms/Loader'
import Button from '../../../atoms/Button'
import Alert from '../../../atoms/Alert'
import styles from './Actions.module.css'
export default function Actions({
isLoading,
loaderMessage,
txId,
actionName,
action
}: {
isLoading: boolean
loaderMessage: string
txId: string
actionName: string
action: () => void
}): ReactElement {
return (
<div className={styles.actions}>
{isLoading ? (
<Loader message={loaderMessage} />
) : (
<Button style="primary" size="small" onClick={() => action()}>
{actionName}
</Button>
)}
{txId && <Alert text={`Transaction ID: ${txId}`} state="success" />}
</div>
)
}

View File

@ -0,0 +1,30 @@
.add {
}
.addInput {
max-width: 12rem;
margin: 0 auto var(--spacer) auto;
}
.userBalance {
display: flex;
justify-content: space-between;
align-items: center;
font-size: var(--font-size-mini);
margin-bottom: calc(var(--spacer) / 4);
color: var(--color-secondary);
}
.userBalance span + div {
transform: scale(0.7);
transform-origin: right center;
}
.output {
text-align: center;
}
.output p {
font-weight: var(--font-weight-bold);
margin-bottom: calc(var(--spacer) / 4);
}

View File

@ -0,0 +1,95 @@
import React, { ReactElement, useState, ChangeEvent } from 'react'
import styles from './Add.module.css'
import { useOcean } from '@oceanprotocol/react'
import Header from './Header'
import { toast } from 'react-toastify'
import InputElement from '../../../atoms/Input/InputElement'
import Token from './Token'
import { Balance } from './'
import PriceUnit from '../../../atoms/Price/PriceUnit'
import Actions from './Actions'
// TODO: handle and display all fees somehow
export default function Add({
setShowAdd,
poolAddress,
totalPoolTokens,
totalBalance
}: {
setShowAdd: (show: boolean) => void
poolAddress: string
totalPoolTokens: string
totalBalance: Balance
}): ReactElement {
const { ocean, accountId, balance } = useOcean()
const [amount, setAmount] = useState('')
const [txId, setTxId] = useState<string>('')
const [isLoading, setIsLoading] = useState<boolean>()
const newPoolTokens =
totalBalance &&
((Number(amount) / Number(totalBalance.ocean)) * 100).toFixed(2)
const newPoolShare =
totalBalance &&
((Number(newPoolTokens) / Number(totalPoolTokens)) * 100).toFixed(2)
async function handleAddLiquidity() {
setIsLoading(true)
try {
const result = await ocean.pool.addOceanLiquidity(
accountId,
poolAddress,
amount
)
setTxId(result.transactionHash)
} catch (error) {
console.error(error.message)
toast.error(error.message)
} finally {
setIsLoading(false)
}
}
function handleAmountChange(e: ChangeEvent<HTMLInputElement>) {
setAmount(e.target.value)
}
return (
<div className={styles.add}>
<Header title="Add Liquidity" backAction={() => setShowAdd(false)} />
<div className={styles.addInput}>
<div className={styles.userBalance}>
<span>Available:</span>
<PriceUnit price={balance.ocean} symbol="OCEAN" small />
</div>
<InputElement
value={amount}
name="ocean"
type="number"
prefix="OCEAN"
placeholder="0"
onChange={handleAmountChange}
/>
</div>
<div className={styles.output}>
<p>You will receive</p>
<Token symbol="BPT" balance={newPoolTokens} />
<Token symbol="% of pool" balance={newPoolShare} />
</div>
<Actions
isLoading={isLoading}
loaderMessage="Adding Liquidity..."
actionName="Supply"
action={handleAddLiquidity}
txId={txId}
/>
</div>
)
}

View File

@ -0,0 +1,23 @@
.header {
display: flex;
justify-content: center;
margin-bottom: var(--spacer);
padding-bottom: calc(var(--spacer) / 2);
border-bottom: 1px solid var(--brand-grey-lighter);
margin-top: -1rem;
margin-left: -2rem;
margin-right: -2rem;
padding-left: var(--spacer);
padding-right: var(--spacer);
}
.headerTitle {
font-size: var(--font-size-large);
margin: 0;
margin-right: auto;
margin-left: -3rem;
}
.back {
margin-right: auto;
}

View File

@ -0,0 +1,25 @@
import React, { ReactElement } from 'react'
import styles from './Header.module.css'
import Button from '../../../atoms/Button'
export default function Header({
title,
backAction
}: {
title: string
backAction: () => void
}): ReactElement {
return (
<header className={styles.header}>
<Button
className={styles.back}
style="text"
size="small"
onClick={backAction}
>
Back
</Button>
<h3 className={styles.headerTitle}>{title}</h3>
</header>
)
}

View File

@ -0,0 +1,4 @@
.removeInput {
max-width: 12rem;
margin: 0 auto var(--spacer) auto;
}

View File

@ -0,0 +1,77 @@
import React, { ReactElement, useState, ChangeEvent } from 'react'
import styles from './Remove.module.css'
import { useOcean } from '@oceanprotocol/react'
import Header from './Header'
import { toast } from 'react-toastify'
import InputElement from '../../../atoms/Input/InputElement'
import Actions from './Actions'
export default function Remove({
setShowRemove,
poolAddress,
totalPoolTokens
}: {
setShowRemove: (show: boolean) => void
poolAddress: string
totalPoolTokens: string
}): ReactElement {
const { ocean, accountId } = useOcean()
const [amount, setAmount] = useState('')
const [isLoading, setIsLoading] = useState<boolean>()
const [txId, setTxId] = useState<string>('')
async function handleRemoveLiquidity() {
setIsLoading(true)
try {
const result = await ocean.pool.removeOceanLiquidity(
accountId,
poolAddress,
amount,
totalPoolTokens
)
setTxId(result.transactionHash)
} catch (error) {
console.error(error.message)
toast.error(error.message)
} finally {
setIsLoading(false)
}
}
function handleAmountChange(e: ChangeEvent<HTMLInputElement>) {
setAmount(e.target.value)
}
return (
<div className={styles.remove}>
<Header
title="Remove Liquidity"
backAction={() => setShowRemove(false)}
/>
<div className={styles.removeInput}>
<InputElement
value={amount}
name="ocean"
type="number"
prefix="OCEAN"
placeholder="0"
onChange={handleAmountChange}
/>
</div>
{/* <Input name="dt" label={dtSymbol} type="number" placeholder="0" /> */}
<p>You will receive</p>
<Actions
isLoading={isLoading}
loaderMessage="Removing Liquidity..."
actionName="Remove"
action={handleRemoveLiquidity}
txId={txId}
/>
</div>
)
}

View File

@ -0,0 +1,36 @@
.token {
font-weight: var(--font-weight-bold);
margin-bottom: calc(var(--spacer) / 3);
white-space: nowrap;
}
.symbol {
font-weight: var(--font-weight-base);
color: var(--color-secondary);
font-size: var(--font-size-base);
}
.icon {
display: inline-block;
border: 1px solid var(--brand-grey-lighter);
border-radius: 50%;
padding: 0.3rem;
vertical-align: middle;
margin-right: calc(var(--spacer) / 8);
margin-top: -0.3rem;
}
.icon svg {
width: var(--font-size-base);
height: var(--font-size-base);
}
/* Data Token Icon Style */
.icon[class*='DT'] path {
fill: var(--brand-violet);
}
.icon[class*='%'],
.icon[class*='BPT'] {
opacity: 0;
}

View File

@ -0,0 +1,21 @@
import React, { ReactElement } from 'react'
import styles from './Token.module.css'
import { ReactComponent as Logo } from '../../../../images/logo.svg'
import PriceUnit from '../../../atoms/Price/PriceUnit'
export default function Token({
symbol,
balance
}: {
symbol: string
balance: string
}): ReactElement {
return (
<div className={styles.token}>
<figure className={`${styles.icon} ${symbol}`}>
<Logo />
</figure>
<PriceUnit price={balance} symbol={symbol} small />
</div>
)
}

View File

@ -0,0 +1,34 @@
.dataToken {
margin-bottom: var(--spacer);
padding-bottom: calc(var(--spacer) / 1.5);
font-size: var(--font-size-large);
border-bottom: 1px solid var(--brand-grey-lighter);
margin-left: -2rem;
margin-right: -2rem;
padding-left: var(--spacer);
padding-right: var(--spacer);
text-align: center;
}
.dataTokenLinks {
display: flex;
justify-content: center;
font-size: var(--font-size-small);
margin-top: calc(var(--spacer) / 2);
}
.dataTokenLinks a {
margin-left: calc(var(--spacer) / 3);
margin-right: calc(var(--spacer) / 3);
}
.poolTokens {
display: grid;
gap: var(--spacer);
grid-template-columns: 1fr 1fr;
}
.title {
font-size: var(--font-size-base);
margin-bottom: calc(var(--spacer) / 1.5);
}

View File

@ -0,0 +1,183 @@
import React, { ReactElement, useEffect, useState } from 'react'
import { useOcean, useMetadata } from '@oceanprotocol/react'
import { DDO } from '@oceanprotocol/lib'
import styles from './index.module.css'
import stylesActions from './Actions.module.css'
import Token from './Token'
import PriceUnit from '../../../atoms/Price/PriceUnit'
import Loader from '../../../atoms/Loader'
import Button from '../../../atoms/Button'
import Add from './Add'
import Remove from './Remove'
import Tooltip from '../../../atoms/Tooltip'
import Conversion from '../../../atoms/Price/Conversion'
import EtherscanLink from '../../../atoms/EtherscanLink'
export interface Balance {
ocean: string
dt: string
}
/*
TODO: create tooltip copy
*/
export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
const { ocean, accountId } = useOcean()
const { price, poolAddress } = useMetadata(ddo)
const [poolTokens, setPoolTokens] = useState<string>()
const [totalPoolTokens, setTotalPoolTokens] = useState<string>()
const [totalBalance, setTotalBalance] = useState<Balance>()
const [dtSymbol, setDtSymbol] = useState<string>()
const [userBalance, setUserBalance] = useState<Balance>()
const [showAdd, setShowAdd] = useState(false)
const [showRemove, setShowRemove] = useState(false)
const [isLoading, setIsLoading] = useState(true)
const hasAddedLiquidity =
userBalance && (Number(userBalance.ocean) > 0 || Number(userBalance.dt) > 0)
const poolShare =
totalBalance &&
userBalance &&
((Number(poolTokens) / Number(totalPoolTokens)) * 100).toFixed(2)
useEffect(() => {
if (!ocean || !accountId || !poolAddress || !price) return
async function init() {
setIsLoading(true)
try {
//
// Get data token symbol
//
const dtSymbol = await ocean.datatokens.getSymbol(
ddo.dataToken,
accountId
)
setDtSymbol(dtSymbol)
//
// Get everything which is in the pool
//
const oceanReserve = await ocean.pool.getOceanReserve(
accountId,
poolAddress
)
const dtReserve = await ocean.pool.getDTReserve(accountId, poolAddress)
setTotalBalance({
ocean: oceanReserve,
dt: dtReserve
})
const totalPoolTokens = await ocean.pool.totalSupply(poolAddress)
setTotalPoolTokens(totalPoolTokens)
//
// Get everything the user has put into the pool
//
const poolTokens = await ocean.pool.sharesBalance(
accountId,
poolAddress
)
setPoolTokens(poolTokens)
// calculate user's provided liquidity based on pool tokens
const userOceanBalance =
(Number(poolTokens) / Number(totalPoolTokens)) * Number(oceanReserve)
const userDtBalance =
(Number(poolTokens) / Number(totalPoolTokens)) * Number(dtReserve)
const userBalance = {
ocean: `${userOceanBalance}`,
dt: `${userDtBalance}`
}
setUserBalance(userBalance)
} catch (error) {
console.error(error.message)
} finally {
setIsLoading(false)
}
}
init()
}, [ocean, accountId, price, poolAddress])
return (
<>
{isLoading && !userBalance ? (
<Loader message="Retrieving pools..." />
) : showAdd ? (
<Add
setShowAdd={setShowAdd}
poolAddress={poolAddress}
totalPoolTokens={totalPoolTokens}
totalBalance={totalBalance}
/>
) : showRemove ? (
<Remove
setShowRemove={setShowRemove}
poolAddress={poolAddress}
totalPoolTokens={totalPoolTokens}
/>
) : (
<>
<div className={styles.dataToken}>
<PriceUnit price="1" symbol={dtSymbol} /> ={' '}
<PriceUnit price={price} />
<Conversion price={price} />
<Tooltip content="Explain how this price is determined..." />
<div className={styles.dataTokenLinks}>
<EtherscanLink network="rinkeby" path={`address/${poolAddress}`}>
Pool
</EtherscanLink>
<EtherscanLink network="rinkeby" path={`token/${ddo.dataToken}`}>
Data Token
</EtherscanLink>
</div>
</div>
<div className={styles.poolTokens}>
<div className={styles.tokens}>
<h3 className={styles.title}>
Your Liquidity
<Tooltip content="Explain what this represents, advantage of providing liquidity..." />
</h3>
<Token symbol="OCEAN" balance={userBalance.ocean} />
<Token symbol={dtSymbol} balance={userBalance.dt} />
<Token symbol="BPT" balance={poolTokens} />
<Token symbol="% of pool" balance={poolShare} />
</div>
<div className={styles.tokens}>
<h3 className={styles.title}>Pool Statistics</h3>
<Token symbol="OCEAN" balance={totalBalance.ocean} />
<Token symbol={dtSymbol} balance={totalBalance.dt} />
<Token symbol="BPT" balance={totalPoolTokens} />
</div>
</div>
<div className={stylesActions.actions}>
<Button
style="primary"
size="small"
onClick={() => setShowAdd(true)}
>
Add Liquidity
</Button>
{hasAddedLiquidity && (
<Button size="small" onClick={() => setShowRemove(true)}>
Remove
</Button>
)}
</div>
</>
)}
</>
)
}

View File

@ -2,24 +2,20 @@ import React, { ReactElement, useState, useEffect } from 'react'
import styles from './index.module.css'
import Compute from './Compute'
import Consume from './Consume'
import { MetadataMarket } from '../../../@types/Metadata'
import { DDO } from '@oceanprotocol/lib'
import Tabs from '../../atoms/Tabs'
import { useOcean } from '@oceanprotocol/react'
import { useOcean, useMetadata } from '@oceanprotocol/react'
import compareAsBN from '../../../utils/compareAsBN'
import Pool from './Pool'
export default function AssetActions({
metadata,
ddo
}: {
metadata: MetadataMarket
ddo: DDO
}): ReactElement {
export default function AssetActions({ ddo }: { ddo: DDO }): ReactElement {
const { balance } = useOcean()
const [price, setPrice] = useState<string>()
const { price } = useMetadata(ddo)
const [isBalanceSufficient, setIsBalanceSufficient] = useState<boolean>()
const isCompute = Boolean(ddo.findServiceByType('compute'))
const { attributes } = ddo.findServiceByType('metadata')
const { priceType } = attributes.additionalInformation
// Check user balance against price
useEffect(() => {
@ -34,17 +30,12 @@ export default function AssetActions({
}, [balance, price])
const UseContent = isCompute ? (
<Compute
ddo={ddo}
isBalanceSufficient={isBalanceSufficient}
setPrice={setPrice}
/>
<Compute ddo={ddo} isBalanceSufficient={isBalanceSufficient} />
) : (
<Consume
ddo={ddo}
isBalanceSufficient={isBalanceSufficient}
file={metadata.main.files[0]}
setPrice={setPrice}
file={attributes.main.files[0]}
/>
)
@ -53,9 +44,9 @@ export default function AssetActions({
title: 'Use',
content: UseContent
},
{
title: 'Trade',
content: 'Trade Me'
(!priceType || priceType === 'advanced') && {
title: 'Pool',
content: <Pool ddo={ddo} />
}
]

View File

@ -14,10 +14,3 @@
word-break: break-all;
padding: 0;
}
.metaFull svg {
width: var(--font-size-mini);
height: var(--font-size-mini);
display: inline-block;
fill: currentColor;
}

View File

@ -4,7 +4,7 @@ import MetaItem from './MetaItem'
import styles from './MetaFull.module.css'
import { MetadataMarket } from '../../../@types/Metadata'
import { DDO } from '@oceanprotocol/lib'
import { ReactComponent as External } from '../../../images/external.svg'
import EtherscanLink from '../../atoms/EtherscanLink'
export default function MetaFull({
ddo,
@ -32,18 +32,11 @@ export default function MetaFull({
<MetaItem title="DID" content={<code>{id}</code>} />
<MetaItem
title="Data Token Address"
title="Data Token"
content={
<a
href={`https://rinkeby.etherscan.io/token/${dataToken}`}
title="View on Etherscan"
target="_blank"
rel="noreferrer"
>
<code>
{dataToken} <External />
</code>
</a>
<EtherscanLink network="rinkeby" path={`token/${dataToken}`}>
<code>{dataToken}</code>
</EtherscanLink>
}
/>
</div>

View File

@ -1,6 +1,6 @@
.grid {
display: grid;
gap: calc(var(--spacer) * 2);
gap: calc(var(--spacer) * 1.5);
position: relative;
margin-top: -1.5rem;
}

View File

@ -58,7 +58,7 @@ export default function AssetContent({
</div>
<div>
<div className={styles.sticky}>
<AssetActions metadata={metadata} ddo={ddo} />
<AssetActions ddo={ddo} />
</div>
</div>
</article>

View File

@ -26,7 +26,6 @@ export default function PageTemplateAssetDetails({
try {
const metadataStore = new MetadataStore(config.metadataStoreUri, Logger)
const ddo = await metadataStore.retrieveDDO(did)
setDdo(ddo)
if (!ddo) {
setTitle('Could not retrieve asset')
@ -34,6 +33,8 @@ export default function PageTemplateAssetDetails({
return
}
setDdo(ddo)
const { attributes }: ServiceMetadataMarket = ddo.findServiceByType(
'metadata'
)