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:
commit
5e772ca843
2130
package-lock.json
generated
2130
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
62
package.json
62
package.json
@ -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": {
|
||||
|
24
src/components/atoms/EtherscanLink.module.css
Normal file
24
src/components/atoms/EtherscanLink.module.css
Normal 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);
|
||||
}
|
29
src/components/atoms/EtherscanLink.tsx
Normal file
29
src/components/atoms/EtherscanLink.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
19
src/components/atoms/Price/PriceUnit.module.css
Normal file
19
src/components/atoms/Price/PriceUnit.module.css
Normal 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%;
|
||||
}
|
39
src/components/atoms/Price/PriceUnit.tsx
Normal file
39
src/components/atoms/Price/PriceUnit.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -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%;
|
||||
}
|
||||
|
@ -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{' '}
|
||||
|
@ -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>
|
||||
|
@ -11,8 +11,8 @@
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
cursor: help;
|
||||
display: inline-block;
|
||||
margin-bottom: -0.1rem;
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
))}
|
||||
|
@ -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}>
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
32
src/components/organisms/AssetActions/Pool/Actions.tsx
Normal file
32
src/components/organisms/AssetActions/Pool/Actions.tsx
Normal 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>
|
||||
)
|
||||
}
|
30
src/components/organisms/AssetActions/Pool/Add.module.css
Normal file
30
src/components/organisms/AssetActions/Pool/Add.module.css
Normal 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);
|
||||
}
|
95
src/components/organisms/AssetActions/Pool/Add.tsx
Normal file
95
src/components/organisms/AssetActions/Pool/Add.tsx
Normal 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>
|
||||
)
|
||||
}
|
23
src/components/organisms/AssetActions/Pool/Header.module.css
Normal file
23
src/components/organisms/AssetActions/Pool/Header.module.css
Normal 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;
|
||||
}
|
25
src/components/organisms/AssetActions/Pool/Header.tsx
Normal file
25
src/components/organisms/AssetActions/Pool/Header.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
.removeInput {
|
||||
max-width: 12rem;
|
||||
margin: 0 auto var(--spacer) auto;
|
||||
}
|
77
src/components/organisms/AssetActions/Pool/Remove.tsx
Normal file
77
src/components/organisms/AssetActions/Pool/Remove.tsx
Normal 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>
|
||||
)
|
||||
}
|
36
src/components/organisms/AssetActions/Pool/Token.module.css
Normal file
36
src/components/organisms/AssetActions/Pool/Token.module.css
Normal 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;
|
||||
}
|
21
src/components/organisms/AssetActions/Pool/Token.tsx
Normal file
21
src/components/organisms/AssetActions/Pool/Token.tsx
Normal 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>
|
||||
)
|
||||
}
|
34
src/components/organisms/AssetActions/Pool/index.module.css
Normal file
34
src/components/organisms/AssetActions/Pool/index.module.css
Normal 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);
|
||||
}
|
183
src/components/organisms/AssetActions/Pool/index.tsx
Normal file
183
src/components/organisms/AssetActions/Pool/index.tsx
Normal 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>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
@ -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} />
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -1,6 +1,6 @@
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: calc(var(--spacer) * 2);
|
||||
gap: calc(var(--spacer) * 1.5);
|
||||
position: relative;
|
||||
margin-top: -1.5rem;
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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'
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user