1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-11-15 01:34:57 +01:00

Merge branch 'main' into feature/history-compute

Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro>
This commit is contained in:
mihaisc 2020-10-19 12:11:37 +03:00
commit 6db26e3a69
No known key found for this signature in database
GPG Key ID: 4FB0C2329B4C6E29
47 changed files with 4626 additions and 1666 deletions

View File

@ -2,6 +2,5 @@
GATSBY_NETWORK="rinkeby"
#GATSBY_INFURA_PROJECT_ID="xxx"
#GATSBY_METADATA_STORE_URI="xxx"
#GATSBY_METADATA_CACHE_URI="xxx"
#GATSBY_MARKET_FEE_ADDRESS="0xxx"
#GATSBY_MARKET_FEE_AMOUNT="xxx"

View File

@ -1,11 +1,11 @@
module.exports = {
network: process.env.GATSBY_NETWORK || 'rinkeby',
infuraProjectId: process.env.GATSBY_INFURA_PROJECT_ID || 'xxx',
metadataStoreUri: process.env.GATSBY_METADATA_STORE_URI,
metadataCacheUri: process.env.GATSBY_METADATA_CACHE_URI,
// The ETH address the marketplace fee will be sent to.
marketFeeAddress:
process.env.GATSBY_MARKET_FEE_ADDRESS ||
'0x903322C7E45A60d7c8C3EA236c5beA9Af86310c7',
marketFeeAmount: process.env.GATSBY_MARKET_FEE_AMOUNT || '0.1', // in %
// Used for conversion display, can be whatever coingecko API supports
// see: https://api.coingecko.com/api/v3/simple/supported_vs_currencies
currencies: ['EUR', 'USD', 'ETH', 'BTC']

26
content/price.json Normal file
View File

@ -0,0 +1,26 @@
{
"pool": {
"tooltips": {
"price": "Explain how this price is determined...",
"liquidity": "Explain what this represents, advantage of providing liquidity..."
},
"add": {
"title": "Add Liquidity",
"output": {
"titleIn": "You will receive",
"titleOut": "You will earn"
},
"action": "Approve & Supply"
},
"remove": {
"title": "Remove Liquidity",
"simple": "Set the amount of your pool shares to spend. You will get the equivalent value in OCEAN, limited to maximum amount for pool protection. If you have Datatokens left in your wallet, you can add them to the pool to increase the maximum amount.",
"advanced": "Set the amount of your pool shares to spend. You will get OCEAN and Datatokens equivalent to your pool share, without any limit. You can use these Datatokens in other DeFi tools.",
"output": {
"titleIn": "You will spend",
"titleOut": "You will receive"
},
"action": "Approve & Remove"
}
}
}

4940
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -22,15 +22,15 @@
"@coingecko/cryptoformat": "^0.4.2",
"@loadable/component": "5.13.1",
"@oceanprotocol/art": "^3.0.0",
"@oceanprotocol/lib": "^0.5.6",
"@oceanprotocol/react": "^0.1.2",
"@oceanprotocol/lib": "^0.6.5",
"@oceanprotocol/react": "^0.2.2",
"@oceanprotocol/typographies": "^0.1.0",
"@sindresorhus/slugify": "^1.0.0",
"@tippyjs/react": "^4.2.0",
"@toruslabs/torus-embed": "^1.8.5",
"@toruslabs/torus-embed": "^1.8.6",
"@types/classnames": "^2.2.10",
"@vercel/node": "^1.8.3",
"@walletconnect/web3-provider": "^1.2.2",
"@vercel/node": "^1.8.4",
"@walletconnect/web3-provider": "^1.3.1",
"axios": "^0.20.0",
"classnames": "^2.2.6",
"date-fns": "^2.16.1",
@ -39,29 +39,29 @@
"dotenv": "^8.2.0",
"ethereum-blockies": "github:MyEtherWallet/blockies",
"filesize": "^6.1.0",
"formik": "^2.1.7",
"gatsby": "^2.24.67",
"gatsby-image": "^2.4.20",
"gatsby-plugin-manifest": "^2.4.33",
"gatsby-plugin-react-helmet": "^3.3.12",
"formik": "^2.2.0",
"gatsby": "^2.24.80",
"gatsby-image": "^2.4.21",
"gatsby-plugin-manifest": "^2.4.35",
"gatsby-plugin-react-helmet": "^3.3.14",
"gatsby-plugin-remove-trailing-slashes": "^2.3.13",
"gatsby-plugin-sharp": "^2.6.38",
"gatsby-plugin-sharp": "^2.6.42",
"gatsby-plugin-svgr": "^2.0.2",
"gatsby-plugin-webpack-size": "^1.0.0",
"gatsby-source-filesystem": "^2.3.32",
"gatsby-source-graphql": "^2.7.5",
"gatsby-transformer-json": "^2.4.13",
"gatsby-transformer-remark": "^2.8.37",
"gatsby-transformer-sharp": "^2.5.16",
"gatsby-source-filesystem": "^2.3.35",
"gatsby-source-graphql": "^2.7.6",
"gatsby-transformer-json": "^2.4.14",
"gatsby-transformer-remark": "^2.8.42",
"gatsby-transformer-sharp": "^2.5.18",
"intersection-observer": "^0.11.0",
"is-url-superb": "^4.0.0",
"lodash.debounce": "^4.0.8",
"lodash.omit": "^4.5.0",
"query-string": "^6.13.5",
"react": "^16.13.1",
"query-string": "^6.13.6",
"react": "^16.14.0",
"react-data-table-component": "^6.11.5",
"react-datepicker": "^3.2.2",
"react-dom": "^16.13.1",
"react-datepicker": "^3.3.0",
"react-dom": "^16.14.0",
"react-dotdotdot": "^1.3.1",
"react-dropzone": "^11.2.0",
"react-helmet": "^6.1.0",
@ -70,7 +70,7 @@
"react-responsive-modal": "^5.1.1",
"react-spring": "^8.0.27",
"react-tabs": "^3.1.1",
"react-toastify": "^6.0.8",
"react-toastify": "^6.0.9",
"remove-markdown": "^0.3.0",
"shortid": "^2.2.15",
"slugify": "^1.4.5",
@ -78,39 +78,39 @@
"yup": "^0.29.3"
},
"devDependencies": {
"@babel/core": "^7.11.6",
"@babel/preset-typescript": "^7.10.1",
"@babel/core": "^7.12.3",
"@babel/preset-typescript": "^7.12.1",
"@storybook/addon-actions": "^6.0.26",
"@storybook/addon-storyshots": "^6.0.26",
"@storybook/react": "^6.0.26",
"@svgr/webpack": "^5.4.0",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.0.4",
"@testing-library/react": "^11.1.0",
"@types/jest": "^26.0.14",
"@types/loadable__component": "^5.13.1",
"@types/lodash.debounce": "^4.0.3",
"@types/lodash.omit": "^4.5.6",
"@types/node": "^14.11.2",
"@types/react": "^16.9.50",
"@types/node": "^14.11.10",
"@types/react": "^16.9.53",
"@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/remove-markdown": "^0.1.1",
"@types/shortid": "0.0.29",
"@types/yup": "^0.29.7",
"@typescript-eslint/eslint-plugin": "^4.3.0",
"@typescript-eslint/parser": "^4.3.0",
"@types/yup": "^0.29.8",
"@typescript-eslint/eslint-plugin": "^4.4.1",
"@typescript-eslint/parser": "^4.4.1",
"babel-loader": "^8.1.0",
"babel-preset-react-app": "^9.1.2",
"eslint": "^7.10.0",
"eslint": "^7.11.0",
"eslint-config-oceanprotocol": "^1.5.0",
"eslint-config-prettier": "^6.12.0",
"eslint-config-prettier": "^6.13.0",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-react": "^7.21.3",
"eslint-plugin-react": "^7.21.4",
"eslint-plugin-react-hooks": "^4.1.2",
"identity-obj-proxy": "^3.0.0",
"jest": "^26.5.0",
"jest": "^26.5.3",
"prettier": "^2.1.2",
"serve": "^11.3.2",
"source-map-explorer": "^2.5.0",

View File

@ -11,7 +11,7 @@
margin: 0;
border-radius: var(--border-radius);
transition: 0.2s ease-out;
min-height: 43px;
height: 43px;
min-width: 0;
appearance: none;
display: block;
@ -49,6 +49,11 @@
display: none;
}
.textarea {
composes: input;
height: auto;
}
.select {
composes: input;
height: 43px;
@ -179,15 +184,16 @@
.prefix,
.postfix {
border: 1px solid var(--brand-grey-lighter);
min-height: 43px;
height: 43px;
display: flex;
align-items: center;
padding-left: calc(var(--spacer) / 4);
padding-right: calc(var(--spacer) / 4);
color: var(--color-secondary);
color: var(--brand-grey);
font-size: var(--font-size-small);
transition: border 0.2s ease-out;
white-space: nowrap;
position: relative;
}
.prefix {
@ -204,6 +210,34 @@
border-color: var(--brand-grey);
}
input[type='range'] {
background: none;
}
input[type='range']:focus {
outline: none;
}
input[type='range']::-webkit-slider-thumb,
input[type='range']::-moz-range-thumb {
appearance: none;
background: var(--brand-gradient);
border: 2px solid var(--brand-grey-lighter);
width: var(--font-size-large);
height: var(--font-size-large);
border-radius: 50%;
cursor: pointer;
box-shadow: 0 2px 9px 0 rgba(0, 0, 0, 0.2);
}
input[type='range']::-webkit-slider-runnable-track,
input[type='range']::-moz-range-track {
background: var(--brand-grey-lighter);
border-radius: var(--border-radius);
height: 0.3rem;
border: none;
}
/* Size modifiers */
.small {

View File

@ -57,7 +57,12 @@ export default function InputElement({
)
case 'textarea':
return (
<textarea name={name} id={name} className={styles.input} {...props} />
<textarea
name={name}
id={name}
className={styles.textarea}
{...props}
/>
)
case 'radio':
case 'checkbox':

View File

@ -13,12 +13,18 @@
}
.error {
font-size: var(--font-size-small);
color: var(--brand-alert-red);
display: inline-block;
font-size: var(--font-size-mini);
line-height: 1.2;
font-weight: var(--font-weight-bold);
color: var(--brand-white);
background: var(--brand-alert-red);
border-radius: var(--border-radius);
padding: 0.2rem 0.4rem;
position: absolute;
text-align: right;
right: 0;
top: 0;
top: 85%;
z-index: 1;
}
.hasError label {
@ -26,7 +32,10 @@
}
.hasError input,
.hasError input:focus,
.hasError select,
.hasError textarea {
.hasError textarea,
.hasError [class*='prefix'],
.hasError [class*='postfix'] {
border-color: var(--brand-alert-red);
}

View File

@ -3,7 +3,7 @@ import InputElement from './InputElement'
import Help from './Help'
import Label from './Label'
import styles from './index.module.css'
import { ErrorMessage } from 'formik'
import { ErrorMessage, FieldInputProps } from 'formik'
import classNames from 'classnames/bind'
const cx = classNames.bind(styles)
@ -33,7 +33,7 @@ export interface InputProps {
max?: string
disabled?: boolean
readOnly?: boolean
field?: any
field?: FieldInputProps<any>
form?: any
prefix?: string | ReactElement
postfix?: string | ReactElement
@ -71,7 +71,7 @@ export default function Input(props: Partial<InputProps>): ReactElement {
</Label>
<InputElement small={small} {...field} {...props} />
{field && field.name !== 'price' && (
{field && field.name !== 'price' && hasError && (
<div className={styles.error}>
<ErrorMessage name={field.name} />
</div>

View File

@ -1,6 +1,6 @@
import React, { ReactElement } from 'react'
import styles from './index.module.css'
import { useMetadata, useOcean } from '@oceanprotocol/react'
import { useMetadata } from '@oceanprotocol/react'
import { DDO } from '@oceanprotocol/lib'
import Loader from '../Loader'
import Tooltip from '../Tooltip'

View File

@ -0,0 +1,4 @@
.action {
text-align: center;
display: block;
}

View File

@ -1,8 +1,7 @@
import Alert from '../../atoms/Alert'
import Button from '../../atoms/Button'
import React, { ReactElement, useEffect } from 'react'
import Alert from './Alert'
import React, { ReactElement, ReactNode, useEffect } from 'react'
import { confetti } from 'dom-confetti'
import styles from './Success.module.css'
import styles from './SuccessConfetti.module.css'
const confettiConfig = {
angle: 90,
@ -24,33 +23,29 @@ const confettiConfig = {
]
}
export default function Success({
export default function SuccessConfetti({
success,
did
action
}: {
success: string
did: string
action: ReactNode
}): ReactElement {
// Have some confetti upon success
useEffect(() => {
if (!success || typeof window === 'undefined') return
const startElement: HTMLElement = document.querySelector('a[data-confetti]')
const startElement: HTMLElement = document.querySelector(
'span[data-confetti]'
)
confetti(startElement, confettiConfig)
}, [success])
return (
<>
<Alert text={success} state="success" />
<Button
style="primary"
size="small"
href={`/asset/${did}`}
className={styles.action}
data-confetti
>
Go to data set
</Button>
<span className={styles.action} data-confetti>
{action}
</span>
</>
)
}

View File

@ -11,18 +11,18 @@
.balance {
text-align: center;
font-size: var(--font-size-small);
font-size: var(--font-size-small) !important;
border: 1px solid var(--brand-grey-lighter);
border-right: 0;
margin-right: -3px;
padding: calc(var(--spacer) / 4.5) calc(var(--spacer) / 2);
height: 35px;
padding: calc(var(--spacer) / 3) calc(var(--spacer) / 2)
calc(var(--spacer) / 4.5) calc(var(--spacer) / 2);
border-top-left-radius: var(--border-radius);
border-bottom-left-radius: var(--border-radius);
color: var(--color-secondary);
}
.balance strong {
color: var(--brand-grey);
display: flex;
align-items: center;
}
.title {

View File

@ -1,4 +1,5 @@
import { DataTokenOptions, useOcean } from '@oceanprotocol/react'
import PriceUnit from '../../../atoms/Price/PriceUnit'
import React, { ReactElement, useEffect, useState } from 'react'
import { PriceOptionsMarket } from '../../../../@types/MetaData'
import { useSiteMetadata } from '../../../../hooks/useSiteMetadata'
@ -66,9 +67,12 @@ export default function Dynamic({
<aside className={styles.wallet}>
{balance?.ocean && (
<div className={styles.balance}>
OCEAN <strong>{balance.ocean}</strong>
</div>
<PriceUnit
className={styles.balance}
price={balance.ocean}
symbol="OCEAN"
small
/>
)}
<Wallet />
</aside>

View File

@ -55,7 +55,7 @@ export default function Fees({
<Tooltip content={tooltips.marketplaceFee} />
</>
}
value={appConfig.marketFeeAmount}
value="0.1"
name="marketplaceFee"
postfix="%"
readOnly

View File

@ -15,6 +15,7 @@ import styles from './Compute.module.css'
import Button from '../../atoms/Button'
import Input from '../../atoms/Input'
import Alert from '../../atoms/Alert'
import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
export default function Compute({
ddo,
@ -25,6 +26,7 @@ export default function Compute({
}): ReactElement {
const { ocean } = useOcean()
const { compute, isLoading, computeStepText, computeError } = useCompute()
const { marketFeeAddress } = useSiteMetadata()
const computeService = ddo.findServiceByType('compute')
const metadataService = ddo.findServiceByType('metadata')
@ -73,7 +75,8 @@ export default function Compute({
computeService,
ddo.dataToken,
algorithmRawCode,
computeContainer
computeContainer,
marketFeeAddress
)
setIsPublished(true)

View File

@ -8,6 +8,7 @@ import Web3Feedback from '../../molecules/Wallet/Feedback'
import styles from './Consume.module.css'
import Loader from '../../atoms/Loader'
import { useOcean, useConsume } from '@oceanprotocol/react'
import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
export default function Consume({
ddo,
@ -19,6 +20,7 @@ export default function Consume({
isBalanceSufficient: boolean
}): ReactElement {
const { ocean } = useOcean()
const { marketFeeAddress } = useSiteMetadata()
const { consumeStepText, consume, consumeError } = useConsume()
const isDisabled = !ocean || !isBalanceSufficient
@ -34,7 +36,9 @@ export default function Consume({
) : (
<Button
style="primary"
onClick={() => consume(ddo.id, ddo.dataToken, 'access')}
onClick={() =>
consume(ddo.id, ddo.dataToken, 'access', marketFeeAddress)
}
disabled={isDisabled}
>
Buy

View File

@ -1,9 +1,9 @@
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'
import EtherscanLink from '../../../atoms/EtherscanLink'
import SuccessConfetti from '../../../atoms/SuccessConfetti'
export default function Actions({
isLoading,
@ -30,15 +30,14 @@ export default function Actions({
)}
</div>
{txId && (
<>
<Alert
text={`Successfully added liquidity. Transaction ID: ${txId}`}
state="success"
/>
<EtherscanLink network="rinkeby" path={`/tx/${txId}`}>
Etherscan
</EtherscanLink>
</>
<SuccessConfetti
success="Successfully added liquidity."
action={
<EtherscanLink network="rinkeby" path={`/tx/${txId}`}>
See on Etherscan
</EtherscanLink>
}
/>
)}
</>
)

View File

@ -1,8 +1,8 @@
.addInput {
margin: 0 auto calc(var(--spacer) / 1.5) auto;
background: var(--brand-grey-dimmed);
padding: var(--spacer) calc(var(--spacer) * 3) calc(var(--spacer) * 1.2)
calc(var(--spacer) * 3);
padding: var(--spacer) calc(var(--spacer) * 2.5) calc(var(--spacer) * 1.2)
calc(var(--spacer) * 2.5);
border-bottom: 1px solid var(--brand-grey-lighter);
margin-top: -2rem;
margin-left: -2rem;
@ -14,45 +14,34 @@
text-align: center;
}
.addInput div[class*='field'] {
margin-bottom: 0;
}
.buttonMax {
position: absolute;
font-size: var(--font-size-mini);
bottom: calc(var(--spacer) / 2);
right: calc(var(--spacer) * 3);
right: calc(var(--spacer) * 2.5);
}
.userLiquidity {
.userLiquidity > div {
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);
}
.userLiquidity > div:last-child {
margin-bottom: calc(var(--spacer) / 4);
}
.userLiquidity span + div {
transform: scale(0.8);
transform-origin: right center;
}
.coinswitch,
.coinPopover li {
cursor: pointer;
}
.coinswitch svg {
width: 0.6em;
height: 0.6em;
display: inline-block;
fill: currentColor;
margin-right: 0.5rem;
margin-left: 0.25rem;
}
.coinPopover li {
padding: calc(var(--spacer) / 4) calc(var(--spacer) / 2);
}
.output {
display: grid;
gap: var(--spacer);

View File

@ -1,17 +1,49 @@
import React, { ReactElement, useState, ChangeEvent, useEffect } from 'react'
import React, { ReactElement, useState, useEffect } 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 Button from '../../../atoms/Button'
import Token from './Token'
import { Balance } from './'
import PriceUnit from '../../../atoms/Price/PriceUnit'
import Actions from './Actions'
import { useUserPreferences } from '../../../../providers/UserPreferences'
import Tooltip from '../../../atoms/Tooltip'
import { ReactComponent as Caret } from '../../../../images/caret.svg'
import { graphql, useStaticQuery } from 'gatsby'
import * as Yup from 'yup'
import { Field, FieldInputProps, Formik } from 'formik'
import Input from '../../../atoms/Input'
import CoinSelect from './CoinSelect'
const contentQuery = graphql`
query PoolAddQuery {
content: allFile(filter: { relativePath: { eq: "price.json" } }) {
edges {
node {
childContentJson {
pool {
add {
title
output {
titleIn
titleOut
}
action
}
}
}
}
}
}
}
`
interface FormAddLiquidity {
amount: number
}
const initialValues: FormAddLiquidity = {
amount: undefined
}
export default function Add({
setShowAdd,
@ -30,133 +62,191 @@ export default function Add({
dtSymbol: string
dtAddress: string
}): ReactElement {
const { debug } = useUserPreferences()
const data = useStaticQuery(contentQuery)
const content = data.content.edges[0].node.childContentJson.pool.add
const { ocean, accountId, balance } = useOcean()
const [amount, setAmount] = useState('')
const [txId, setTxId] = useState<string>('')
const [isLoading, setIsLoading] = useState<boolean>()
const [coin, setCoin] = useState<string>('OCEAN')
const [txId, setTxId] = useState<string>()
const [coin, setCoin] = useState('OCEAN')
const [dtBalance, setDtBalance] = useState<string>()
const [amountMax, setAmountMax] = useState<string>()
const newPoolTokens =
totalBalance &&
((Number(amount) / Number(totalBalance.ocean)) * 100).toFixed(2)
const newPoolShare =
totalBalance &&
((Number(newPoolTokens) / Number(totalPoolTokens)) * 100).toFixed(2)
// Live validation rules
// https://github.com/jquense/yup#number
const validationSchema = Yup.object().shape<FormAddLiquidity>({
amount: Yup.number()
.min(1, 'Must be more or equal to 1')
.max(
Number(amountMax),
`Maximum you can add is ${Number(amountMax).toFixed(2)} ${coin}`
)
.required('Required')
})
// Get datatoken balance when datatoken selected
useEffect(() => {
if (!ocean) return
if (!ocean || coin === 'OCEAN') return
async function getDtBalance() {
const dtBalance = await ocean.datatokens.balance(dtAddress, accountId)
setDtBalance(dtBalance)
}
getDtBalance()
}, [ocean, accountId, dtAddress])
}, [ocean, accountId, dtAddress, coin])
async function handleAddLiquidity() {
setIsLoading(true)
// Get maximum amount for either OCEAN or datatoken
useEffect(() => {
if (!ocean) return
async function getMaximum() {
const amountMaxPool =
coin === 'OCEAN'
? await ocean.pool.getOceanMaxAddLiquidity(poolAddress)
: await ocean.pool.getDTMaxAddLiquidity(poolAddress)
const amountMax =
coin === 'OCEAN'
? Number(balance.ocean) > Number(amountMaxPool)
? amountMaxPool
: balance.ocean
: Number(dtBalance) > Number(amountMaxPool)
? amountMaxPool
: dtBalance
setAmountMax(amountMax)
}
getMaximum()
}, [ocean, poolAddress, coin, dtBalance, balance.ocean])
// Submit
async function handleAddLiquidity(amount: number, resetForm: () => void) {
try {
const result =
coin === 'OCEAN'
? await ocean.pool.addOceanLiquidity(accountId, poolAddress, amount)
: await ocean.pool.addDTLiquidity(accountId, poolAddress, amount)
? await ocean.pool.addOceanLiquidity(
accountId,
poolAddress,
`${amount}`
)
: await ocean.pool.addDTLiquidity(accountId, poolAddress, `${amount}`)
setTxId(result?.transactionHash)
resetForm()
} catch (error) {
console.error(error.message)
toast.error(error.message)
} finally {
setIsLoading(false)
}
}
function handleAmountChange(e: ChangeEvent<HTMLInputElement>) {
setAmount(e.target.value)
}
function handleMax() {
setAmount(coin === 'OCEAN' ? balance.ocean : dtBalance)
}
// TODO: this is only a prototype and is an accessibility nightmare.
// Needs to be refactored to either use custom select element instead of tippy.js,
// or use <button> in this implementation.
// Also needs to be closed when users click an option.
const CoinSelect = () => (
<ul className={styles.coinPopover}>
<li onClick={() => setCoin('OCEAN')}>OCEAN</li>
<li onClick={() => setCoin(dtSymbol)}>{dtSymbol}</li>
</ul>
)
return (
<>
<Header title="Add Liquidity" backAction={() => setShowAdd(false)} />
<Header title={content.title} backAction={() => setShowAdd(false)} />
<div className={styles.addInput}>
<div className={styles.userLiquidity}>
<span>Available: </span>
{coin === 'OCEAN' ? (
<PriceUnit price={balance.ocean} symbol="OCEAN" small />
) : (
<PriceUnit price={dtBalance} symbol={dtSymbol} small />
)}
</div>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={async (values, { setSubmitting, resetForm }) => {
await handleAddLiquidity(values.amount, resetForm)
setSubmitting(false)
}}
>
{({
values,
touched,
setTouched,
isSubmitting,
setFieldValue,
submitForm,
handleChange
}) => {
const newPoolTokens =
totalBalance &&
((values.amount / Number(totalBalance.ocean)) * 100).toFixed(2)
<InputElement
value={amount}
name="coin"
type="number"
prefix={
<Tooltip
content={<CoinSelect />}
trigger="click focus"
className={styles.coinswitch}
placement="bottom"
>
{coin}
<Caret aria-hidden="true" />
</Tooltip>
}
placeholder="0"
onChange={handleAmountChange}
/>
const newPoolShare =
totalBalance &&
((Number(newPoolTokens) / Number(totalPoolTokens)) * 100).toFixed(2)
{(balance.ocean || dtBalance) > amount && (
<Button
className={styles.buttonMax}
style="text"
size="small"
onClick={handleMax}
>
Use Max
</Button>
)}
</div>
return (
<>
<div className={styles.addInput}>
<div className={styles.userLiquidity}>
<div>
<span>Available:</span>
{coin === 'OCEAN' ? (
<PriceUnit price={balance.ocean} symbol="OCEAN" small />
) : (
<PriceUnit price={dtBalance} symbol={dtSymbol} small />
)}
</div>
<div>
<span>Maximum:</span>
<PriceUnit price={amountMax} symbol={coin} small />
</div>
</div>
<div className={styles.output}>
<div>
<p>You will receive</p>
{debug === true && <Token symbol="BPT" balance={newPoolTokens} />}
<Token symbol="% of pool" balance={newPoolShare} />
</div>
<div>
<p>You will earn</p>
<Token symbol="% swap fee" balance={swapFee} />
</div>
</div>
<Field name="amount">
{({
field,
form
}: {
field: FieldInputProps<FormAddLiquidity>
form: any
}) => (
<Input
type="number"
max={amountMax}
value={`${values.amount}`}
prefix={
<CoinSelect dtSymbol={dtSymbol} setCoin={setCoin} />
}
placeholder="0"
field={field}
form={form}
onChange={(e) => {
// Workaround so validation kicks in on first touch
!touched?.amount && setTouched({ amount: true })
handleChange(e)
}}
/>
)}
</Field>
<Actions
isLoading={isLoading}
loaderMessage="Adding Liquidity..."
actionName="Supply"
action={handleAddLiquidity}
txId={txId}
/>
{(Number(balance.ocean) || dtBalance) >
(values.amount || 0) && (
<Button
className={styles.buttonMax}
style="text"
size="small"
onClick={() => setFieldValue('amount', amountMax)}
>
Use Max
</Button>
)}
</div>
<div className={styles.output}>
<div>
<p>{content.output.titleIn}</p>
<Token symbol="pool shares" balance={newPoolTokens} />
<Token symbol="% of pool" balance={newPoolShare} />
</div>
<div>
<p>{content.output.titleOut}</p>
<Token symbol="% swap fee" balance={swapFee} />
</div>
</div>
<Actions
isLoading={isSubmitting}
loaderMessage="Adding Liquidity..."
actionName={content.action}
action={submitForm}
txId={txId}
/>
</>
)
}}
</Formik>
</>
)
}

View File

@ -0,0 +1,26 @@
.coinSelect {
composes: select from '../../../atoms/Input/InputElement.module.css';
font-size: var(--font-size-small);
font-weight: var(--font-weight-base);
border: none;
margin-left: -0.5rem;
margin-right: -0.5rem;
background-color: var(--brand-grey-dimmed);
width: auto;
padding: 0 1.25rem 0 0.25rem;
height: 41px;
text-align: center;
/* custom arrow, without the divider line */
background-image: linear-gradient(
45deg,
transparent 50%,
var(--brand-purple) 50%
),
linear-gradient(135deg, var(--brand-grey) 50%, transparent 50%);
background-position: calc(100% - 14px) 1.2rem, calc(100% - 9px) 1.2rem, 100% 0;
}
.option {
color: var(--brand-grey-dark);
}

View File

@ -0,0 +1,24 @@
import React, { ReactElement } from 'react'
import styles from './CoinSelect.module.css'
export default function CoinSelect({
dtSymbol,
setCoin
}: {
dtSymbol: string
setCoin: (coin: string) => void
}): ReactElement {
return (
<select
className={styles.coinSelect}
onChange={(e) => setCoin(e.target.value)}
>
<option className={styles.option} value="OCEAN">
OCEAN
</option>
<option className={styles.option} value={dtSymbol}>
{dtSymbol}
</option>
</select>
)
}

View File

@ -1,10 +0,0 @@
.statistics {
}
.title {
composes: title from './index.module.css';
}
.totalLiquidity {
composes: totalLiquidity from './index.module.css';
}

View File

@ -1,39 +0,0 @@
import { useUserPreferences } from '../../../../providers/UserPreferences'
import React, { ReactElement } from 'react'
import { Balance } from '.'
import styles from './PoolStatistics.module.css'
import Token from './Token'
import Conversion from '../../../atoms/Price/Conversion'
export default function PoolStatistics({
price,
dtSymbol,
totalBalance,
totalPoolTokens,
swapFee
}: {
price: string
dtSymbol: string
totalBalance: Balance
totalPoolTokens: string
swapFee: string
}): ReactElement {
const { debug } = useUserPreferences()
const totalLiquidityInOcean =
totalBalance.ocean + totalBalance.datatoken * Number(price)
return (
<div className={styles.statistics}>
<h3 className={styles.title}>Pool Statistics</h3>
<Token symbol="OCEAN" balance={`${totalBalance.ocean}`} />
<Token symbol={dtSymbol} balance={`${totalBalance.datatoken}`} />
{debug === true && <Token symbol="BPT" balance={totalPoolTokens} />}
<Conversion
price={`${totalLiquidityInOcean}`}
className={styles.totalLiquidity}
/>
<Token symbol="% swap fee" balance={swapFee} />
</div>
)
}

View File

@ -1,11 +1,71 @@
.removeInput {
composes: addInput from './Add.module.css';
}
.buttonMax {
composes: buttonMax from './Add.module.css';
padding-left: calc(var(--spacer) * 2);
padding-right: calc(var(--spacer) * 2);
}
.userLiquidity {
composes: userLiquidity from './Add.module.css';
max-width: 12rem;
margin-top: -1rem;
margin-left: auto;
margin-right: auto;
margin-bottom: calc(var(--spacer) / 2);
}
.range {
text-align: center;
}
.range h3 {
margin-bottom: calc(var(--spacer) / 4);
}
.range input {
width: 100%;
}
.range p {
margin-bottom: 0;
margin-left: -2rem;
margin-right: -2rem;
}
.range button {
margin-top: calc(var(--spacer) / 4);
margin-bottom: 0;
position: absolute;
bottom: 1rem;
right: 2rem;
font-size: var(--font-size-mini);
}
.slider {
position: relative;
z-index: 1;
}
.maximum {
position: absolute;
right: -1.5rem;
bottom: 1.5rem;
font-size: var(--font-size-small);
z-index: 0;
pointer-events: none;
}
.output {
composes: output from './Add.module.css';
}
.output [class*='token'] {
white-space: nowrap;
}
.output [class*='token'] > figure {
display: inline-block;
}
.output figure[class*='pool shares'] {
display: none;
}

View File

@ -1,42 +1,91 @@
import React, { ReactElement, useState, ChangeEvent } from 'react'
import React, {
ReactElement,
useState,
ChangeEvent,
useEffect,
FormEvent
} 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'
import { Logger } from '@oceanprotocol/lib'
import Token from './Token'
import FormHelp from '../../../atoms/Input/Help'
import Button from '../../../atoms/Button'
import { getMaxValuesRemove } from './utils'
import { graphql, useStaticQuery } from 'gatsby'
import PriceUnit from '../../../atoms/Price/PriceUnit'
import { Balance } from '.'
const contentQuery = graphql`
query PoolRemoveQuery {
content: allFile(filter: { relativePath: { eq: "price.json" } }) {
edges {
node {
childContentJson {
pool {
remove {
title
simple
advanced
output {
titleIn
titleOut
}
action
}
}
}
}
}
}
}
`
export default function Remove({
setShowRemove,
poolAddress,
totalPoolTokens,
userLiquidity
poolTokens,
dtSymbol
}: {
setShowRemove: (show: boolean) => void
poolAddress: string
totalPoolTokens: string
userLiquidity: Balance
poolTokens: string
dtSymbol: string
}): ReactElement {
const data = useStaticQuery(contentQuery)
const content = data.content.edges[0].node.childContentJson.pool.remove
const { ocean, accountId } = useOcean()
const [amount, setAmount] = useState('')
const [amountPercent, setAmountPercent] = useState('0')
const [amountMaxPercent, setAmountMaxPercent] = useState('100')
const [amountPoolShares, setAmountPoolShares] = useState('0')
const [amountOcean, setAmountOcean] = useState('0')
const [amountDatatoken, setAmountDatatoken] = useState('0')
const [isAdvanced, setIsAdvanced] = useState(false)
const [isLoading, setIsLoading] = useState<boolean>()
const [txId, setTxId] = useState<string>('')
const [txId, setTxId] = useState<string>()
async function handleRemoveLiquidity() {
setIsLoading(true)
try {
const result = await ocean.pool.removeOceanLiquidity(
accountId,
poolAddress,
amount,
totalPoolTokens
)
setTxId(result.transactionHash)
const result =
isAdvanced === true
? await ocean.pool.removePoolLiquidity(
accountId,
poolAddress,
amountPoolShares
)
: await ocean.pool.removeOceanLiquidity(
accountId,
poolAddress,
amountDatatoken,
amountPoolShares
)
setTxId(result?.transactionHash)
} catch (error) {
Logger.error(error.message)
toast.error(error.message)
@ -45,55 +94,103 @@ export default function Remove({
}
}
function handleAmountChange(e: ChangeEvent<HTMLInputElement>) {
setAmount(e.target.value)
function handleAmountPercentChange(e: ChangeEvent<HTMLInputElement>) {
setAmountPercent(e.target.value)
}
function handleMax() {
setAmount(`${userLiquidity.ocean}`)
function handleAdvancedButton(e: FormEvent<HTMLButtonElement>) {
e.preventDefault()
setIsAdvanced(!isAdvanced)
}
// Check and set outputs when percentage changes
useEffect(() => {
if (!ocean || !poolTokens) return
async function getValues() {
const amountPoolShares =
(Number(amountPercent) / 100) * Number(poolTokens)
setAmountPoolShares(`${amountPoolShares}`)
if (isAdvanced === true) {
setAmountMaxPercent('100')
const tokens = await ocean.pool.getTokensRemovedforPoolShares(
poolAddress,
`${amountPoolShares}`
)
setAmountOcean(tokens?.oceanAmount)
setAmountDatatoken(tokens?.dtAmount)
} else {
const { amountMaxPercent, amountOcean } = await getMaxValuesRemove(
ocean,
poolAddress,
poolTokens,
`${amountPoolShares}`
)
setAmountMaxPercent(amountMaxPercent)
setAmountOcean(amountOcean)
}
}
getValues()
}, [amountPercent, isAdvanced, ocean, poolTokens, poolAddress])
return (
<div className={styles.remove}>
<Header
title="Remove Liquidity"
backAction={() => setShowRemove(false)}
/>
<Header title={content.title} backAction={() => setShowRemove(false)} />
<form className={styles.removeInput}>
<div className={styles.userLiquidity}>
<span>Your pool liquidity: </span>
<PriceUnit price={`${userLiquidity.ocean}`} symbol="OCEAN" small />
<div>
<span>Available:</span>
<PriceUnit price={poolTokens} symbol="pool shares" small />
</div>
</div>
<InputElement
value={amount}
name="ocean"
type="number"
prefix="OCEAN"
placeholder="0"
onChange={handleAmountChange}
/>
{userLiquidity.ocean > Number(amount) && (
<Button
className={styles.buttonMax}
style="text"
size="small"
onClick={handleMax}
>
Use Max
<div className={styles.range}>
<h3>{amountPercent}%</h3>
<div className={styles.slider}>
<input
type="range"
min="0"
max={amountMaxPercent}
value={amountPercent}
onChange={handleAmountPercentChange}
/>
{isAdvanced === false && (
<span
className={styles.maximum}
>{`${amountMaxPercent}% max.`}</span>
)}
</div>
<FormHelp>
{isAdvanced === true ? content.advanced : content.simple}
</FormHelp>
<Button style="text" size="small" onClick={handleAdvancedButton}>
{isAdvanced === true ? 'Simple' : 'Advanced'}
</Button>
)}
</div>
</form>
{/* <Input name="dt" label={dtSymbol} type="number" placeholder="0" /> */}
<p>You will receive</p>
<div className={styles.output}>
<div>
<p>{content.output.titleIn}</p>
<Token symbol="pool shares" balance={amountPoolShares} noIcon />
</div>
<div>
<p>{content.output.titleOut}</p>
<Token symbol="OCEAN" balance={amountOcean} />
{isAdvanced === true && (
<Token symbol={dtSymbol} balance={amountDatatoken} />
)}
</div>
</div>
<Actions
isLoading={isLoading}
loaderMessage="Removing Liquidity..."
actionName="Remove"
actionName={content.action}
action={handleRemoveLiquidity}
txId={txId}
/>

View File

@ -1,6 +1,6 @@
.token {
font-weight: var(--font-weight-bold);
margin-bottom: calc(var(--spacer) / 3);
margin-bottom: calc(var(--spacer) / 4);
white-space: nowrap;
}
@ -17,7 +17,7 @@
padding: 0.3rem;
vertical-align: middle;
margin-right: calc(var(--spacer) / 8);
margin-top: -0.3rem;
margin-top: -0.2rem;
}
.icon svg {
@ -30,7 +30,6 @@
fill: var(--brand-violet);
}
.icon[class*='%'],
.icon[class*='BPT'] {
.noIcon {
opacity: 0;
}

View File

@ -5,14 +5,18 @@ import PriceUnit from '../../../atoms/Price/PriceUnit'
export default function Token({
symbol,
balance
balance,
noIcon
}: {
symbol: string
balance: string
noIcon?: boolean
}): ReactElement {
return (
<div className={styles.token}>
<figure className={`${styles.icon} ${symbol}`}>
<figure
className={`${styles.icon} ${symbol} ${noIcon ? styles.noIcon : ''}`}
>
<Logo />
</figure>
<PriceUnit price={balance} symbol={symbol} small />

View File

@ -0,0 +1,47 @@
.tokeninfo {
padding-top: calc(var(--spacer) / 1.5);
}
.tokens {
display: grid;
grid-template-columns: 1fr 1fr;
}
/* visually tweak the order so tokens are underneath each other */
.tokens > *:nth-child(1) {
grid-row: 1 / 2;
}
.tokens > *:nth-child(2) {
grid-row: 2 / 2;
}
.title {
font-size: var(--font-size-base);
margin-bottom: calc(var(--spacer) / 3);
color: var(--color-secondary);
}
.highlight {
background: var(--brand-grey-dimmed);
padding: calc(var(--spacer) / 1.5) var(--spacer) calc(var(--spacer) / 2)
var(--spacer);
border-bottom: 1px solid var(--brand-grey-lighter);
margin-left: -2rem;
margin-right: -2rem;
}
.totalLiquidity {
composes: token from './Token.module.css';
margin-bottom: 0;
font-weight: var(--font-weight-base) !important;
font-size: var(--font-size-small);
padding-left: var(--font-size-base);
padding-top: calc(var(--spacer) / 10);
}
.totalLiquidity strong {
font-size: var(--font-size-base);
color: var(--brand-grey-dark);
line-height: 1;
}

View File

@ -0,0 +1,44 @@
import Conversion from '../../../atoms/Price/Conversion'
import React, { ReactElement, ReactNode } from 'react'
import Token from './Token'
import styles from './TokenList.module.css'
export default function TokenList({
title,
children,
ocean,
dt,
dtSymbol,
poolShares,
conversion,
highlight
}: {
title: string | ReactNode
children: ReactNode
ocean: string
dt: string
dtSymbol: string
poolShares: string
conversion: number
highlight?: boolean
}): ReactElement {
return (
<div className={`${styles.tokeninfo} ${highlight ? styles.highlight : ''}`}>
<h3 className={styles.title}>{title}</h3>
<div className={styles.tokens}>
<Token symbol="OCEAN" balance={ocean} />
<Token symbol={dtSymbol} balance={dt} />
<Token symbol="pool shares" balance={poolShares} noIcon />
{children}
{conversion > 0 && (
<Conversion
price={`${conversion}`}
className={styles.totalLiquidity}
/>
)}
</div>
</div>
)
}

View File

@ -1,12 +1,11 @@
.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);
padding-left: calc(var(--spacer) / 1.5);
padding-right: calc(var(--spacer) / 1.5);
text-align: center;
}
@ -21,27 +20,3 @@
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);
}
.totalLiquidity {
composes: token from './Token.module.css';
font-weight: var(--font-weight-base) !important;
font-size: var(--font-size-small);
padding-left: var(--font-size-base);
}
.totalLiquidity strong {
font-size: var(--font-size-base);
color: var(--brand-grey-dark);
line-height: 1;
}

View File

@ -3,7 +3,6 @@ import { useOcean, useMetadata } from '@oceanprotocol/react'
import { DDO, Logger } from '@oceanprotocol/lib'
import styles from './index.module.css'
import stylesActions from './Actions.module.css'
import { useUserPreferences } from '../../../../providers/UserPreferences'
import PriceUnit from '../../../atoms/Price/PriceUnit'
import Loader from '../../../atoms/Loader'
import Button from '../../../atoms/Button'
@ -12,20 +11,38 @@ import Remove from './Remove'
import Tooltip from '../../../atoms/Tooltip'
import Conversion from '../../../atoms/Price/Conversion'
import EtherscanLink from '../../../atoms/EtherscanLink'
import PoolStatistics from './PoolStatistics'
import Token from './Token'
import TokenList from './TokenList'
import { graphql, useStaticQuery } from 'gatsby'
export interface Balance {
ocean: number
datatoken: number
}
/*
TODO: create tooltip copy
*/
const contentQuery = graphql`
query PoolQuery {
content: allFile(filter: { relativePath: { eq: "price.json" } }) {
edges {
node {
childContentJson {
pool {
tooltips {
price
liquidity
}
}
}
}
}
}
}
`
export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
const { debug } = useUserPreferences()
const data = useStaticQuery(contentQuery)
const content = data.content.edges[0].node.childContentJson.pool
const { ocean, accountId } = useOcean()
const { price } = useMetadata(ddo)
@ -39,6 +56,8 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
const [showRemove, setShowRemove] = useState(false)
const [isLoading, setIsLoading] = useState(true)
// TODO: put all these variables behind some useEffect
// to prevent unneccessary updating on every render
const hasAddedLiquidity =
userLiquidity && (userLiquidity.ocean > 0 || userLiquidity.datatoken > 0)
@ -51,6 +70,8 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
const totalUserLiquidityInOcean =
userLiquidity?.ocean + userLiquidity?.datatoken * price?.value
const totalLiquidityInOcean = price?.ocean + price?.datatoken * price?.value
useEffect(() => {
if (!ocean || !accountId || !price || !price.value) return
@ -67,7 +88,9 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
//
// Get everything which is in the pool
//
const totalPoolTokens = await ocean.pool.totalSupply(price.address)
const totalPoolTokens = await ocean.pool.getPoolSharesTotalSupply(
price.address
)
setTotalPoolTokens(totalPoolTokens)
//
@ -124,8 +147,8 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
<Remove
setShowRemove={setShowRemove}
poolAddress={price.address}
totalPoolTokens={totalPoolTokens}
userLiquidity={userLiquidity}
poolTokens={poolTokens}
dtSymbol={dtSymbol}
/>
) : (
<>
@ -133,7 +156,7 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
<PriceUnit price="1" symbol={dtSymbol} /> ={' '}
<PriceUnit price={`${price.value}`} />
<Conversion price={`${price.value}`} />
<Tooltip content="Explain how this price is determined..." />
<Tooltip content={content.tooltips.price} />
<div className={styles.dataTokenLinks}>
<EtherscanLink
network="rinkeby"
@ -147,30 +170,33 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
</div>
</div>
<div className={styles.poolTokens}>
<div className={styles.tokens}>
<h3 className={styles.title}>
<TokenList
title={
<>
Your Liquidity
<Tooltip content="Explain what this represents, advantage of providing liquidity..." />
</h3>
<Token symbol="OCEAN" balance={`${userLiquidity.ocean}`} />
<Token symbol={dtSymbol} balance={`${userLiquidity.datatoken}`} />
{debug === true && <Token symbol="BPT" balance={poolTokens} />}
<Conversion
price={`${totalUserLiquidityInOcean}`}
className={styles.totalLiquidity}
/>
<Token symbol="% of pool" balance={poolShare} />
</div>
<Tooltip content={content.tooltips.liquidity} />
</>
}
ocean={`${userLiquidity.ocean}`}
dt={`${userLiquidity.datatoken}`}
dtSymbol={dtSymbol}
poolShares={poolTokens}
conversion={totalUserLiquidityInOcean}
highlight
>
<Token symbol="% of pool" balance={poolShare} noIcon />
</TokenList>
<PoolStatistics
price={`${price.value}`}
totalPoolTokens={totalPoolTokens}
totalBalance={{ ocean: price.ocean, datatoken: price.datatoken }}
swapFee={swapFee}
dtSymbol={dtSymbol}
/>
</div>
<TokenList
title="Pool Statistics"
ocean={`${price.ocean}`}
dt={`${price.datatoken}`}
dtSymbol={dtSymbol}
poolShares={totalPoolTokens}
conversion={totalLiquidityInOcean}
>
<Token symbol="% swap fee" balance={swapFee} noIcon />
</TokenList>
<div className={stylesActions.actions}>
<Button

View File

@ -0,0 +1,28 @@
import { Ocean } from '@oceanprotocol/lib'
export async function getMaxValuesRemove(
ocean: Ocean,
poolAddress: string,
poolTokens: string,
amountPoolShares: string
): Promise<{ amountMaxPercent: string; amountOcean: string }> {
const amountMaxOcean = await ocean.pool.getOceanMaxRemoveLiquidity(
poolAddress
)
const amountMaxPoolShares = await ocean.pool.getPoolSharesRequiredToRemoveOcean(
poolAddress,
amountMaxOcean
)
const amountMaxPercent = `${Math.floor(
(Number(amountMaxPoolShares) / Number(poolTokens)) * 100
)}`
const amountOcean = await ocean.pool.getOceanRemovedforPoolShares(
poolAddress,
amountPoolShares
)
return { amountMaxPercent, amountOcean }
}

View File

@ -1,6 +1,6 @@
import AssetTeaser from '../molecules/AssetTeaser'
import React from 'react'
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatastore/MetadataStore'
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
import { useLocation, useNavigate } from '@reach/router'
import Pagination from '../molecules/Pagination'
import { updateQueryStringParameter } from '../../utils'
@ -57,7 +57,7 @@ const AssetList: React.FC<AssetListProps> = ({ queryResult }) => {
})
) : (
<div className={styles.empty}>
No results found in {config.metadataStoreUri}
No results found in {config.metadataCacheUri}
</div>
)}
</div>

View File

@ -1,5 +1,5 @@
import { Logger } from '@oceanprotocol/lib'
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatastore/MetadataStore'
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
import { useOcean } from '@oceanprotocol/react'
import React, { ReactElement, useEffect, useState } from 'react'
import Loader from '../../atoms/Loader'

View File

@ -1,12 +1,12 @@
import React, { ReactElement, useEffect, useState } from 'react'
import SearchBar from '../molecules/SearchBar'
import styles from './Home.module.css'
import { MetadataStore, Logger } from '@oceanprotocol/lib'
import { MetadataCache, Logger } from '@oceanprotocol/lib'
import AssetList from '../organisms/AssetList'
import {
QueryResult,
SearchQuery
} from '@oceanprotocol/lib/dist/node/metadatastore/MetadataStore'
} from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
import Container from '../atoms/Container'
import Loader from '../atoms/Loader'
import { useOcean } from '@oceanprotocol/react'
@ -15,7 +15,14 @@ const queryHighest = {
page: 1,
offset: 3,
query: { 'price.type': ['pool'] },
sort: { 'price.value': 1 }
sort: { 'price.ocean': -1 }
}
const queryPoolsLatest = {
page: 1,
offset: 6,
query: { 'price.type': ['pool'] },
sort: { created: -1 }
}
const queryLatest = {
@ -25,10 +32,10 @@ const queryLatest = {
sort: { created: -1 }
}
async function getAssets(query: SearchQuery, metadataStoreUri: string) {
async function getAssets(query: SearchQuery, metadataCacheUri: string) {
try {
const metadataStore = new MetadataStore(metadataStoreUri, Logger)
const result = await metadataStore.queryMetadata(query)
const metadataCache = new MetadataCache(metadataCacheUri, Logger)
const result = await metadataCache.queryMetadata(query)
return result
} catch (error) {
@ -39,6 +46,9 @@ async function getAssets(query: SearchQuery, metadataStoreUri: string) {
export default function HomePage(): ReactElement {
const { config } = useOcean()
const [queryResultLatest, setQueryResultLatest] = useState<QueryResult>()
const [queryResultPoolsLatest, setQueryResultPoolsLatest] = useState<
QueryResult
>()
const [queryResultHighest, setQueryResultHighest] = useState<QueryResult>()
const [loading, setLoading] = useState(true)
@ -46,19 +56,25 @@ export default function HomePage(): ReactElement {
async function init() {
const queryResultHighest = await getAssets(
queryHighest,
config.metadataStoreUri
config.metadataCacheUri
)
setQueryResultHighest(queryResultHighest)
const queryResultPoolsLatest = await getAssets(
queryPoolsLatest,
config.metadataCacheUri
)
setQueryResultPoolsLatest(queryResultPoolsLatest)
const queryResultLatest = await getAssets(
queryLatest,
config.metadataStoreUri
config.metadataCacheUri
)
setQueryResultLatest(queryResultLatest)
setLoading(false)
}
init()
}, [config.metadataStoreUri])
}, [config.metadataCacheUri])
return (
<>
@ -67,7 +83,7 @@ export default function HomePage(): ReactElement {
</Container>
<section className={styles.latest}>
<h3>Highest Liquidity</h3>
<h3>Highest Liquidity Pools</h3>
{loading ? (
<Loader />
) : (
@ -76,7 +92,18 @@ export default function HomePage(): ReactElement {
</section>
<section className={styles.latest}>
<h3>Latest</h3>
<h3>New Liquidity Pools</h3>
{loading ? (
<Loader />
) : (
queryResultPoolsLatest && (
<AssetList queryResult={queryResultPoolsLatest} />
)
)}
</section>
<section className={styles.latest}>
<h3>New Data Sets</h3>
{loading ? (
<Loader />
) : (

View File

@ -1,9 +1,9 @@
import Alert from '../../atoms/Alert'
import Success from './Success'
import Button from '../../atoms/Button'
import Loader from '../../atoms/Loader'
import React, { ReactElement } from 'react'
import styles from './Feedback.module.css'
import SuccessConfetti from '../../atoms/SuccessConfetti'
export default function Feedback({
error,
@ -18,6 +18,17 @@ export default function Feedback({
publishStepText: string
setError: (error: string) => void
}): ReactElement {
const SuccessAction = () => (
<Button
style="primary"
size="small"
href={`/asset/${did}`}
className={styles.action}
>
Go to data set
</Button>
)
return (
<div className={styles.feedback}>
<div className={styles.box}>
@ -35,7 +46,7 @@ export default function Feedback({
</Button>
</>
) : success ? (
<Success success={success} did={did} />
<SuccessConfetti success={success} action={<SuccessAction />} />
) : (
<Loader message={publishStepText} />
)}

View File

@ -1,7 +1,7 @@
import React, { ReactElement, useEffect, FormEvent } from 'react'
import styles from './PublishForm.module.css'
import { useOcean } from '@oceanprotocol/react'
import { useFormikContext, Form, Field } from 'formik'
import { useFormikContext, Field } from 'formik'
import Input from '../../atoms/Input'
import Button from '../../atoms/Button'
import { FormContent, FormFieldProps } from '../../../@types/Form'
@ -37,7 +37,7 @@ export default function PublishForm({
}
return (
<Form
<form
className={styles.form}
// do we need this?
onChange={() => status === 'empty' && setStatus(null)}
@ -61,6 +61,6 @@ export default function PublishForm({
</Button>
)}
</footer>
</Form>
</form>
)
}

View File

@ -1,3 +0,0 @@
.action {
margin-top: calc(var(--spacer) / 1.5);
}

View File

@ -3,7 +3,7 @@ import { Router } from '@reach/router'
import AssetContent from '../../components/organisms/AssetContent'
import Layout from '../../components/Layout'
import { MetadataMarket } from '../../@types/MetaData'
import { MetadataStore, Logger, DDO } from '@oceanprotocol/lib'
import { MetadataCache, Logger, DDO } from '@oceanprotocol/lib'
import Alert from '../../components/atoms/Alert'
import Loader from '../../components/atoms/Loader'
import { useOcean } from '@oceanprotocol/react'
@ -24,12 +24,12 @@ export default function PageTemplateAssetDetails({
useEffect(() => {
async function init() {
try {
const metadataStore = new MetadataStore(config.metadataStoreUri, Logger)
const ddo = await metadataStore.retrieveDDO(did)
const metadataCache = new MetadataCache(config.metadataCacheUri, Logger)
const ddo = await metadataCache.retrieveDDO(did)
if (!ddo) {
setTitle('Could not retrieve asset')
setError('The DDO was not found in MetadataStore.')
setError('The DDO was not found in MetadataCache.')
return
}
@ -44,7 +44,7 @@ export default function PageTemplateAssetDetails({
}
}
init()
}, [did, config.metadataStoreUri])
}, [did, config.metadataCacheUri])
return did && metadata ? (
<Layout title={title} uri={uri}>

View File

@ -1,5 +1,5 @@
import React, { ReactElement, useState, useEffect } from 'react'
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatastore/MetadataStore'
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
import SearchBar from '../../molecules/SearchBar'
import AssetList from '../../organisms/AssetList'
import styles from './index.module.css'
@ -28,12 +28,12 @@ export default function SearchPage({
useEffect(() => {
async function initSearch() {
setLoading(true)
const queryResult = await getResults(parsed, config.metadataStoreUri)
const queryResult = await getResults(parsed, config.metadataCacheUri)
setQueryResult(queryResult)
setLoading(false)
}
initSearch()
}, [text, tag, page, config.metadataStoreUri])
}, [text, tag, page, config.metadataCacheUri])
return (
<section className={styles.grid}>

View File

@ -1,8 +1,8 @@
import {
SearchQuery,
QueryResult
} from '@oceanprotocol/lib/dist/node/metadatastore/MetadataStore'
import { MetadataStore, Logger } from '@oceanprotocol/lib'
} from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
import { MetadataCache, Logger } from '@oceanprotocol/lib'
export function getSearchQuery(
page?: string | string[],
@ -30,12 +30,12 @@ export function getSearchQuery(
export async function getResults(
params: { text?: string; tag?: string; page?: string; offset?: string },
metadataStoreUri: string
metadataCacheUri: string
): Promise<QueryResult> {
const { text, tag, page, offset } = params
const metadataStore = new MetadataStore(metadataStoreUri, Logger)
const queryResult = await metadataStore.queryMetadata(
const metadataCache = new MetadataCache(metadataCacheUri, Logger)
const queryResult = await metadataCache.queryMetadata(
getSearchQuery(page, offset, text, tag)
)

View File

@ -5,7 +5,7 @@ import appConfig from '../../app.config'
import { Logger } from '@oceanprotocol/lib'
export function NetworkMonitor(): ReactElement {
const { metadataStoreUri } = appConfig
const { metadataCacheUri } = appConfig
const { connect, web3Provider } = useOcean()
useEffect(() => {
@ -16,8 +16,8 @@ export function NetworkMonitor(): ReactElement {
const newConfig = {
...initialConfig,
// add metadataStoreUri only when defined
...(metadataStoreUri && { metadataStoreUri })
// add metadataCacheUri only when defined
...(metadataCacheUri && { metadataCacheUri })
}
try {
@ -32,7 +32,7 @@ export function NetworkMonitor(): ReactElement {
return () => {
web3Provider.removeListener('chainChanged', handleNetworkChanged)
}
}, [web3Provider, connect, metadataStoreUri])
}, [web3Provider, connect, metadataCacheUri])
return <></>
}

View File

@ -24,13 +24,13 @@ export default function wrapRootElement({
}: {
element: ReactElement
}): ReactElement {
const { metadataStoreUri, network } = appConfig
const { metadataCacheUri, network } = appConfig
const oceanInitialConfig = getOceanConfig(network)
const initialConfig = {
...oceanInitialConfig,
// add metadataStoreUri only when defined
...(metadataStoreUri && { metadataStoreUri })
// add metadataCacheUri only when defined
...(metadataCacheUri && { metadataCacheUri })
}
return (

View File

@ -17,7 +17,6 @@ const query = graphql`
infuraProjectId
network
marketFeeAddress
marketFeeAmount
currencies
}
}

View File

@ -1,5 +1,6 @@
import loadable from '@loadable/component'
import { infuraProjectId, network } from '../../app.config'
import WalletConnectProvider from '@walletconnect/web3-provider'
// import Torus from '@toruslabs/torus-embed'
const web3ModalTheme = {
background: 'var(--brand-white)',
@ -9,28 +10,23 @@ const web3ModalTheme = {
hover: 'var(--brand-grey-dimmed)'
}
const WalletConnectProvider = loadable(
() => import('@walletconnect/web3-provider') as any
)
const Torus = loadable(() => import('@toruslabs/torus-embed') as any)
const providerOptions = {
walletconnect: {
package: WalletConnectProvider,
options: {
infuraId: infuraProjectId
}
},
torus: {
package: Torus,
options: {
networkParams: {
// host: oceanConfig.url // optional
// chainId: 1337, // optional
// networkId: 1337 // optional
}
}
}
// torus: {
// package: Torus,
// options: {
// networkParams: {
// // host: oceanConfig.url // optional
// // chainId: 1337, // optional
// // networkId: 1337 // optional
// }
// }
// }
}
export const web3ModalOpts = {

View File

@ -1,6 +1,6 @@
import ddo from '../../__fixtures__/ddo'
const metadataStore = {
const metadataCache = {
queryMetadata: () => {
return {
results: [] as any[],
@ -11,13 +11,13 @@ const metadataStore = {
}
const libMock = {
MetadataStore: () => metadataStore,
MetadataStore: () => metadataCache,
DDO: () => ddo,
ocean: {
accounts: {
list: () => ['xxx', 'xxx']
},
metadataStore,
metadataCache,
// compute: {
// status: (account: string) => {
// return [job]