Remove AMM Pools (#1614)

This commit is contained in:
EnzoVezzaro 2022-08-02 05:53:22 -04:00 committed by GitHub
parent 8edd61bfa7
commit eb29c4ce3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
127 changed files with 370 additions and 5236 deletions

View File

@ -1,10 +1,8 @@
#NEXT_PUBLIC_INFURA_PROJECT_ID="xxx" #NEXT_PUBLIC_INFURA_PROJECT_ID="xxx"
#NEXT_PUBLIC_MARKET_FEE_ADDRESS="0xxx" #NEXT_PUBLIC_MARKET_FEE_ADDRESS="0xxx"
#NEXT_PUBLIC_PUBLISHER_MARKET_ORDER_FEE="1" #NEXT_PUBLIC_PUBLISHER_MARKET_ORDER_FEE="1"
#NEXT_PUBLIC_PUBLISHER_MARKET_POOL_SWAP_FEE="1"
#NEXT_PUBLIC_PUBLISHER_MARKET_FIXED_SWAP_FEE="1" #NEXT_PUBLIC_PUBLISHER_MARKET_FIXED_SWAP_FEE="1"
#NEXT_PUBLIC_CONSUME_MARKET_ORDER_FEE="1" #NEXT_PUBLIC_CONSUME_MARKET_ORDER_FEE="1"
#NEXT_PUBLIC_CONSUME_MARKET_POOL_SWAP_FEE="1"
#NEXT_PUBLIC_CONSUME_MARKET_FIXED_SWAP_FEE="1" #NEXT_PUBLIC_CONSUME_MARKET_FIXED_SWAP_FEE="1"
# #

View File

@ -109,7 +109,7 @@ All displayed data in the app is presented around the concept of one data set, w
- the actual data set files - the actual data set files
- the NFT which represents the data set - the NFT which represents the data set
- the datatokens representing access rights to the data set files - the datatokens representing access rights to the data set files
- financial data connected to these datatokens - financial data connected to these datatokens, either a fixed rate exchange contract or a dispenser for free assets
- calculations and conversions based on financial data - calculations and conversions based on financial data
- metadata about publisher accounts - metadata about publisher accounts

View File

@ -33,9 +33,6 @@ module.exports = {
// publisher market fee that is taken upon ordering an asset, it is an absolute value, it is declared on erc20 creation // publisher market fee that is taken upon ordering an asset, it is an absolute value, it is declared on erc20 creation
publisherMarketOrderFee: publisherMarketOrderFee:
process.env.NEXT_PUBLIC_PUBLISHER_MARKET_ORDER_FEE || '0', process.env.NEXT_PUBLIC_PUBLISHER_MARKET_ORDER_FEE || '0',
// fee recieved by the publisher market when a dt is swaped from a pool, percent
publisherMarketPoolSwapFee:
process.env.NEXT_PUBLIC_PUBLISHER_MARKET_POOL_SWAP_FEE || '0',
// fee recieved by the publisher market when a dt is bought from a fixed rate exchange, percent // fee recieved by the publisher market when a dt is bought from a fixed rate exchange, percent
publisherMarketFixedSwapFee: publisherMarketFixedSwapFee:
process.env.NEXT_PUBLIC_PUBLISHER_MARKET_FIXED_SWAP_FEE || '0', process.env.NEXT_PUBLIC_PUBLISHER_MARKET_FIXED_SWAP_FEE || '0',
@ -43,9 +40,6 @@ module.exports = {
// consume market fee that is taken upon ordering an asset, it is an absolute value, it is specified on order // consume market fee that is taken upon ordering an asset, it is an absolute value, it is specified on order
consumeMarketOrderFee: consumeMarketOrderFee:
process.env.NEXT_PUBLIC_CONSUME_MARKET_ORDER_FEE || '0', process.env.NEXT_PUBLIC_CONSUME_MARKET_ORDER_FEE || '0',
// fee recieved by the consume market when a dt is swaped from a pool, percent
consumeMarketPoolSwapFee:
process.env.NEXT_PUBLIC_CONSUME_MARKET_POOL_SWAP_FEE || '0',
// fee recieved by the consume market when a dt is bought from a fixed rate exchange, percent // fee recieved by the consume market when a dt is bought from a fixed rate exchange, percent
consumeMarketFixedSwapFee: consumeMarketFixedSwapFee:
process.env.NEXT_PUBLIC_CONSUME_MARKET_FIXED_SWAP_FEE || '0', process.env.NEXT_PUBLIC_CONSUME_MARKET_FIXED_SWAP_FEE || '0',
@ -75,10 +69,9 @@ module.exports = {
storageKey: 'oceanDarkMode' storageKey: 'oceanDarkMode'
}, },
// Used to show or hide the fixed, dynamic or free price options // Used to show or hide the fixed or free price options
// tab to publishers during the price creation. // tab to publishers during the price creation.
allowFixedPricing: process.env.NEXT_PUBLIC_ALLOW_FIXED_PRICING || 'true', allowFixedPricing: process.env.NEXT_PUBLIC_ALLOW_FIXED_PRICING || 'true',
allowDynamicPricing: process.env.NEXT_PUBLIC_ALLOW_DYNAMIC_PRICING || 'true',
allowFreePricing: process.env.NEXT_PUBLIC_ALLOW_FREE_PRICING || 'true', allowFreePricing: process.env.NEXT_PUBLIC_ALLOW_FREE_PRICING || 'true',
// Set the default privacy policy to initially display // Set the default privacy policy to initially display

View File

@ -14,6 +14,6 @@
} }
], ],
"stats": { "stats": {
"note": "Counted on-chain from our NFT and pool factories. Includes assets in all Ocean Market forks and [purgatory](https://github.com/oceanprotocol/list-purgatory)." "note": "Counted on-chain from our NFT factories. Includes assets in all Ocean Market forks and [purgatory](https://github.com/oceanprotocol/list-purgatory)."
} }
} }

View File

@ -1,13 +1,5 @@
{ {
"create": { "create": {
"empty": {
"title": "No Price Created",
"info": "This data set has no price yet. As the publisher you can create a fixed price, or a dynamic price for it. Onwards!",
"action": {
"name": "Create Pricing",
"help": "Create Pricing will mint your datatokens, approve spending, and create either a pool or a fixed rate exchange in one process. You will need to approve those multiple steps in your wallet."
}
},
"fixed": { "fixed": {
"title": "Fixed", "title": "Fixed",
"info": "Set your price for accessing this data set. The datatoken for this data set will be worth the entered amount of OCEAN.", "info": "Set your price for accessing this data set. The datatoken for this data set will be worth the entered amount of OCEAN.",
@ -30,24 +22,10 @@
] ]
} }
}, },
"pool": { "approval": {
"tooltips": { "tooltips": {
"price": "The price is determined by an automated market maker, which is a type of decentralized exchange protocol that relies on a mathematical formula. It is an alternative to a traditional order book.",
"liquidity": "Providing liquidity will earn you SWAPFEE% on every transaction in this pool, proportionally to your share of the pool.",
"approveSpecific": "Give the smart contract permission to spend your COIN which has to be done for each transaction. You can optionally set this to infinite in your user preferences.", "approveSpecific": "Give the smart contract permission to spend your COIN which has to be done for each transaction. You can optionally set this to infinite in your user preferences.",
"approveInfinite": "Give the smart contract permission to spend infinte amounts of your COIN so you have to do this only once. You can disable allowing infinite amounts in your user preferences." "approveInfinite": "Give the smart contract permission to spend infinte amounts of your COIN so you have to do this only once. You can disable allowing infinite amounts in your user preferences."
},
"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.",
"output": {
"titleOutExpected": "Expected output",
"titleOutMinimum": "Minimum received"
},
"action": "Remove"
} }
},
"trade": {
"action": "Swap"
} }
} }

View File

@ -122,7 +122,7 @@
"label": "Algorithm Privacy", "label": "Algorithm Privacy",
"type": "checkbox", "type": "checkbox",
"options": ["Keep my algorithm private"], "options": ["Keep my algorithm private"],
"help": "By default, your algorithm can be downloaded for a fixed or dynamic price in addition to running in compute jobs. Enabling this option will prevent downloading, so your algorithm can only be run as part of a compute job on a data set.", "help": "By default, your algorithm can be downloaded for free or a fixed price, in addition to running in compute jobs. Enabling this option will prevent downloading, so your algorithm can only be run as part of a compute job on a data set.",
"required": false "required": false
}, },
{ {

View File

@ -14,7 +14,7 @@
"link": "/profile" "link": "/profile"
} }
], ],
"announcement": "Data NFTs, One-Sided Staking and more.", "announcement": "Explore [OceanONDA V4](https://blog.oceanprotocol.com/how-to-publish-a-data-nft-f58ad2a622a9).",
"warning": { "warning": {
"ctd": "Compute-to-Data is still in a testing phase, please use it only on test networks." "ctd": "Compute-to-Data is still in a testing phase, please use it only on test networks."
} }

46
package-lock.json generated
View File

@ -19,7 +19,6 @@
"@urql/exchange-refocus": "^0.2.5", "@urql/exchange-refocus": "^0.2.5",
"@walletconnect/web3-provider": "^1.7.8", "@walletconnect/web3-provider": "^1.7.8",
"axios": "^0.27.2", "axios": "^0.27.2",
"chart.js": "^3.8.0",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"date-fns": "^2.29.1", "date-fns": "^2.29.1",
"decimal.js": "^10.3.1", "decimal.js": "^10.3.1",
@ -37,7 +36,6 @@
"next": "^12.1.6", "next": "^12.1.6",
"query-string": "^7.1.1", "query-string": "^7.1.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-chartjs-2": "^4.2.0",
"react-clipboard.js": "^2.0.16", "react-clipboard.js": "^2.0.16",
"react-data-table-component": "^7.5.2", "react-data-table-component": "^7.5.2",
"react-dom": "^18.1.0", "react-dom": "^18.1.0",
@ -70,7 +68,6 @@
"@svgr/webpack": "^6.2.1", "@svgr/webpack": "^6.2.1",
"@testing-library/jest-dom": "^5.16.4", "@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0", "@testing-library/react": "^13.3.0",
"@types/chart.js": "^2.9.37",
"@types/js-cookie": "^3.0.2", "@types/js-cookie": "^3.0.2",
"@types/loadable__component": "^5.13.4", "@types/loadable__component": "^5.13.4",
"@types/lodash.debounce": "^4.0.7", "@types/lodash.debounce": "^4.0.7",
@ -17910,15 +17907,6 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/chart.js": {
"version": "2.9.37",
"resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.37.tgz",
"integrity": "sha512-9bosRfHhkXxKYfrw94EmyDQcdjMaQPkU1fH2tDxu8DWXxf1mjzWQAV4laJF51ZbC2ycYwNDvIm1rGez8Bug0vg==",
"dev": true,
"dependencies": {
"moment": "^2.10.2"
}
},
"node_modules/@types/clipboard": { "node_modules/@types/clipboard": {
"version": "2.0.7", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/clipboard/-/clipboard-2.0.7.tgz", "resolved": "https://registry.npmjs.org/@types/clipboard/-/clipboard-2.0.7.tgz",
@ -22298,11 +22286,6 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/chart.js": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.8.0.tgz",
"integrity": "sha512-cr8xhrXjLIXVLOBZPkBZVF6NDeiVIrPLHcMhnON7UufudL+CNeRrD+wpYanswlm8NpudMdrt3CHoLMQMxJhHRg=="
},
"node_modules/checkpoint-store": { "node_modules/checkpoint-store": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/checkpoint-store/-/checkpoint-store-1.1.0.tgz", "resolved": "https://registry.npmjs.org/checkpoint-store/-/checkpoint-store-1.1.0.tgz",
@ -39429,15 +39412,6 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/react-chartjs-2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-4.2.0.tgz",
"integrity": "sha512-9Vm9Sg9XAKiR579/FnBkesofjW9goaaFLfS7XlGTzUJlWFZGSE6A/pBI6+i/bP3pobKZoFcWJdFnjShytToqXw==",
"peerDependencies": {
"chart.js": "^3.5.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-clipboard.js": { "node_modules/react-clipboard.js": {
"version": "2.0.16", "version": "2.0.16",
"resolved": "https://registry.npmjs.org/react-clipboard.js/-/react-clipboard.js-2.0.16.tgz", "resolved": "https://registry.npmjs.org/react-clipboard.js/-/react-clipboard.js-2.0.16.tgz",
@ -60513,15 +60487,6 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"@types/chart.js": {
"version": "2.9.37",
"resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.37.tgz",
"integrity": "sha512-9bosRfHhkXxKYfrw94EmyDQcdjMaQPkU1fH2tDxu8DWXxf1mjzWQAV4laJF51ZbC2ycYwNDvIm1rGez8Bug0vg==",
"dev": true,
"requires": {
"moment": "^2.10.2"
}
},
"@types/clipboard": { "@types/clipboard": {
"version": "2.0.7", "version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/clipboard/-/clipboard-2.0.7.tgz", "resolved": "https://registry.npmjs.org/@types/clipboard/-/clipboard-2.0.7.tgz",
@ -64101,11 +64066,6 @@
"integrity": "sha512-Y4kiDb+AM4Ecy58YkuZrrSRJBDQdQ2L+NyS1vHHFtNtUjgutcZfx3yp1dAONI/oPaPmyGfCLx5CxL+zauIMyKQ==", "integrity": "sha512-Y4kiDb+AM4Ecy58YkuZrrSRJBDQdQ2L+NyS1vHHFtNtUjgutcZfx3yp1dAONI/oPaPmyGfCLx5CxL+zauIMyKQ==",
"dev": true "dev": true
}, },
"chart.js": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.8.0.tgz",
"integrity": "sha512-cr8xhrXjLIXVLOBZPkBZVF6NDeiVIrPLHcMhnON7UufudL+CNeRrD+wpYanswlm8NpudMdrt3CHoLMQMxJhHRg=="
},
"checkpoint-store": { "checkpoint-store": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/checkpoint-store/-/checkpoint-store-1.1.0.tgz", "resolved": "https://registry.npmjs.org/checkpoint-store/-/checkpoint-store-1.1.0.tgz",
@ -77612,12 +77572,6 @@
"loose-envify": "^1.1.0" "loose-envify": "^1.1.0"
} }
}, },
"react-chartjs-2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-4.2.0.tgz",
"integrity": "sha512-9Vm9Sg9XAKiR579/FnBkesofjW9goaaFLfS7XlGTzUJlWFZGSE6A/pBI6+i/bP3pobKZoFcWJdFnjShytToqXw==",
"requires": {}
},
"react-clipboard.js": { "react-clipboard.js": {
"version": "2.0.16", "version": "2.0.16",
"resolved": "https://registry.npmjs.org/react-clipboard.js/-/react-clipboard.js-2.0.16.tgz", "resolved": "https://registry.npmjs.org/react-clipboard.js/-/react-clipboard.js-2.0.16.tgz",

View File

@ -32,7 +32,6 @@
"@urql/exchange-refocus": "^0.2.5", "@urql/exchange-refocus": "^0.2.5",
"@walletconnect/web3-provider": "^1.7.8", "@walletconnect/web3-provider": "^1.7.8",
"axios": "^0.27.2", "axios": "^0.27.2",
"chart.js": "^3.8.0",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"date-fns": "^2.29.1", "date-fns": "^2.29.1",
"decimal.js": "^10.3.1", "decimal.js": "^10.3.1",
@ -50,7 +49,6 @@
"next": "^12.1.6", "next": "^12.1.6",
"query-string": "^7.1.1", "query-string": "^7.1.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-chartjs-2": "^4.2.0",
"react-clipboard.js": "^2.0.16", "react-clipboard.js": "^2.0.16",
"react-data-table-component": "^7.5.2", "react-data-table-component": "^7.5.2",
"react-dom": "^18.1.0", "react-dom": "^18.1.0",
@ -83,7 +81,6 @@
"@svgr/webpack": "^6.2.1", "@svgr/webpack": "^6.2.1",
"@testing-library/jest-dom": "^5.16.4", "@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0", "@testing-library/react": "^13.3.0",
"@types/chart.js": "^2.9.37",
"@types/js-cookie": "^3.0.2", "@types/js-cookie": "^3.0.2",
"@types/loadable__component": "^5.13.4", "@types/loadable__component": "^5.13.4",
"@types/lodash.debounce": "^4.0.7", "@types/lodash.debounce": "^4.0.7",

View File

@ -13,7 +13,6 @@ import { checkV3Asset, retrieveAsset } from '@utils/aquarius'
import { useWeb3 } from './Web3' import { useWeb3 } from './Web3'
import { useCancelToken } from '@hooks/useCancelToken' import { useCancelToken } from '@hooks/useCancelToken'
import { getOceanConfig, getDevelopmentConfig } from '@utils/ocean' import { getOceanConfig, getDevelopmentConfig } from '@utils/ocean'
import { AssetExtended } from 'src/@types/AssetExtended'
import { getAccessDetails } from '@utils/accessDetailsAndPricing' import { getAccessDetails } from '@utils/accessDetailsAndPricing'
import { useIsMounted } from '@hooks/useIsMounted' import { useIsMounted } from '@hooks/useIsMounted'
import { useMarketMetadata } from './MarketMetadata' import { useMarketMetadata } from './MarketMetadata'
@ -126,6 +125,7 @@ function AssetProvider({
// ----------------------------------- // -----------------------------------
const fetchAccessDetails = useCallback(async (): Promise<void> => { const fetchAccessDetails = useCallback(async (): Promise<void> => {
if (!asset?.chainId || !asset?.services) return if (!asset?.chainId || !asset?.services) return
const accessDetails = await getAccessDetails( const accessDetails = await getAccessDetails(
asset.chainId, asset.chainId,
asset.services[0].datatokenAddress, asset.services[0].datatokenAddress,

View File

@ -12,14 +12,11 @@ export interface AppConfig {
chainIdsSupported: number[] chainIdsSupported: number[]
marketFeeAddress: string marketFeeAddress: string
publisherMarketOrderFee: string publisherMarketOrderFee: string
publisherMarketPoolSwapFee: string
publisherMarketFixedSwapFee: string publisherMarketFixedSwapFee: string
consumeMarketOrderFee: string consumeMarketOrderFee: string
consumeMarketPoolSwapFee: string
consumeMarketFixedSwapFee: string consumeMarketFixedSwapFee: string
currencies: string[] currencies: string[]
allowFixedPricing: string allowFixedPricing: string
allowDynamicPricing: string
allowFreePricing: string allowFreePricing: string
defaultPrivacyPolicySlug: string defaultPrivacyPolicySlug: string
privacyPreferenceCenter: string privacyPreferenceCenter: string

View File

@ -1,57 +0,0 @@
import { gql } from 'urql'
export const poolDataQuery = gql`
query PoolData(
$pool: ID!
$poolAsString: String!
$owner: String!
$user: String
) {
poolData: pool(id: $pool) {
id
totalShares
liquidityProviderSwapFee
publishMarketSwapFee
spotPrice
baseToken {
address
symbol
decimals
}
baseTokenWeight
baseTokenLiquidity
datatoken {
address
symbol
decimals
}
datatokenWeight
datatokenLiquidity
shares(where: { user: $owner }) {
shares
}
}
poolDataUser: pool(id: $pool) {
shares(where: { user: $user }) {
shares
}
}
poolSnapshots(first: 1000, where: { pool: $poolAsString }, orderBy: date) {
date
spotPrice
baseTokenLiquidity
datatokenLiquidity
swapVolume
baseToken {
address
symbol
decimals
}
datatoken {
address
symbol
decimals
}
}
}
`

View File

@ -1,35 +0,0 @@
import {
PoolData_poolSnapshots as PoolDataPoolSnapshots,
PoolData_poolData as PoolDataPoolData
} from 'src/@types/subgraph/PoolData'
export interface PoolInfo {
liquidityProviderSwapFee: string
publishMarketSwapFee: string
weightBaseToken: string
weightDt: string
datatokenSymbol: string
datatokenAddress: string
datatokenDecimals: number
baseTokenSymbol: string
baseTokenAddress: string
baseTokenDecimals: number
totalPoolTokens: string
}
export interface PoolInfoUser {
liquidity: string
poolShares: string
poolSharePercentage: string
}
export interface PoolProviderValue {
poolData: PoolDataPoolData
poolInfo: PoolInfo
poolInfoOwner: PoolInfoUser
poolInfoUser: PoolInfoUser
poolSnapshots: PoolDataPoolSnapshots[]
hasUserAddedLiquidity: boolean
refreshInterval: number
fetchAllData: () => void
}

View File

@ -1,38 +0,0 @@
import { isValidNumber } from '@utils/numbers'
import { getQueryContext, fetchData } from '@utils/subgraph'
import Decimal from 'decimal.js'
import { PoolData } from 'src/@types/subgraph/PoolData'
import { OperationResult } from 'urql'
import { poolDataQuery } from './_queries'
export async function getPoolData(
chainId: number,
pool: string,
owner: string,
user: string
) {
const queryVariables = {
// Using `pool` & `poolAsString` is a workaround to make the mega query work.
// See https://github.com/oceanprotocol/ocean-subgraph/issues/301
pool: pool.toLowerCase(),
poolAsString: pool.toLowerCase(),
owner: owner.toLowerCase(),
user: user.toLowerCase()
}
const response: OperationResult<PoolData> = await fetchData(
poolDataQuery,
queryVariables,
getQueryContext(chainId)
)
return response?.data
}
export function getWeight(weight: string) {
return isValidNumber(weight) ? new Decimal(weight).mul(10).toString() : '0'
}
export function getFee(fee: string) {
// fees are tricky: to get 0.1% you need to convert from 0.001
return isValidNumber(fee) ? new Decimal(fee).mul(100).toString() : '0'
}

View File

@ -1,196 +0,0 @@
import { LoggerInstance } from '@oceanprotocol/lib'
import Decimal from 'decimal.js'
import React, {
useContext,
useState,
useEffect,
createContext,
ReactElement,
useCallback,
ReactNode
} from 'react'
import {
PoolData_poolSnapshots as PoolDataPoolSnapshots,
PoolData_poolData as PoolDataPoolData
} from 'src/@types/subgraph/PoolData'
import { useAsset } from '../Asset'
import { useWeb3 } from '../Web3'
import { calcSingleOutGivenPoolIn } from '@utils/pool'
import { PoolProviderValue, PoolInfo, PoolInfoUser } from './_types'
import { getFee, getPoolData, getWeight } from './_utils'
import { useMarketMetadata } from '@context/MarketMetadata'
const PoolContext = createContext({} as PoolProviderValue)
const refreshInterval = 10000 // 10 sec.
const initialPoolInfoUser: Partial<PoolInfoUser> = {
liquidity: '0',
poolShares: '0'
}
const initialPoolInfoCreator: Partial<PoolInfoUser> = initialPoolInfoUser
function PoolProvider({ children }: { children: ReactNode }): ReactElement {
const { accountId, web3, chainId } = useWeb3()
const { asset, owner } = useAsset()
const { getOpcFeeForToken } = useMarketMetadata()
const [poolData, setPoolData] = useState<PoolDataPoolData>()
const [poolInfo, setPoolInfo] = useState<PoolInfo>()
const [poolInfoOwner, setPoolInfoOwner] = useState<PoolInfoUser>(
initialPoolInfoCreator as PoolInfoUser
)
const [poolInfoUser, setPoolInfoUser] = useState<PoolInfoUser>(
initialPoolInfoUser as PoolInfoUser
)
const [poolSnapshots, setPoolSnapshots] = useState<PoolDataPoolSnapshots[]>()
const [hasUserAddedLiquidity, setUserHasAddedLiquidity] = useState(false)
const fetchAllData = useCallback(async () => {
if (!asset?.chainId || !asset?.accessDetails?.addressOrId || !owner) return
const response = await getPoolData(
asset.chainId,
asset.accessDetails.addressOrId,
owner,
accountId || ''
)
if (!response) return
setPoolData(response.poolData)
// calculate pool info user
const poolInfoShares = response.poolDataUser?.shares[0]?.shares || '0'
const userLiquidity = calcSingleOutGivenPoolIn(
response.poolData.baseTokenLiquidity,
response.poolData.totalShares,
poolInfoShares
)
// Pool share in %. We double it to compensate for ss bot
const poolSharePercentage = new Decimal(poolInfoShares)
.dividedBy(new Decimal(response.poolData.totalShares))
.mul(200)
.toFixed(2)
setUserHasAddedLiquidity(Number(poolSharePercentage) > 0)
const newPoolInfoUser: PoolInfoUser = {
liquidity: userLiquidity,
poolShares: poolInfoShares,
poolSharePercentage
}
setPoolInfoUser((prevState: PoolInfoUser) => ({
...prevState,
...newPoolInfoUser
}))
setPoolSnapshots(response.poolSnapshots)
LoggerInstance.log('[pool] Fetched pool data:', response.poolData)
LoggerInstance.log('[pool] Fetched user data:', response.poolDataUser)
LoggerInstance.log('[pool] Fetched pool snapshots:', response.poolSnapshots)
}, [asset?.chainId, asset?.accessDetails?.addressOrId, owner, accountId])
// 0 Fetch all the data on mount if we are on a pool.
// All further effects depend on the fetched data
// and only do further data checking and manipulation.
//
useEffect(() => {
if (asset?.accessDetails?.type !== 'dynamic') return
fetchAllData()
const interval = setInterval(() => {
fetchAllData()
}, refreshInterval)
return () => clearInterval(interval)
}, [fetchAllData, asset?.accessDetails?.type])
//
// 1 General Pool Info
//
useEffect(() => {
if (!poolData) return
const newPoolInfo = {
liquidityProviderSwapFee: getFee(poolData.liquidityProviderSwapFee),
publishMarketSwapFee: getFee(poolData.publishMarketSwapFee),
opcFee: getFee(
getOpcFeeForToken(poolData.baseToken.address, asset?.chainId)
),
weightBaseToken: getWeight(poolData.baseTokenWeight),
weightDt: getWeight(poolData.datatokenWeight),
datatokenSymbol: poolData.datatoken.symbol,
datatokenAddress: poolData.datatoken.address,
datatokenDecimals: poolData.datatoken.decimals,
baseTokenSymbol: poolData.baseToken.symbol,
baseTokenAddress: poolData.baseToken.address,
baseTokenDecimals: poolData.baseToken.decimals,
totalPoolTokens: poolData.totalShares
}
setPoolInfo(newPoolInfo)
LoggerInstance.log('[pool] Created new pool info:', newPoolInfo)
}, [asset?.chainId, chainId, getOpcFeeForToken, poolData, web3])
//
// 2 Pool Creator Info
//
useEffect(() => {
if (
!poolData ||
!poolInfo?.totalPoolTokens ||
!poolData.shares[0]?.shares ||
poolData.shares[0]?.shares === '0'
)
return
// Pool share tokens. We double it to compensate for ss bot
const poolSharePercentage = new Decimal(poolData.shares[0]?.shares)
.dividedBy(poolInfo.totalPoolTokens)
.mul(200)
.toFixed(2)
const ownerLiquidity = calcSingleOutGivenPoolIn(
poolData.baseTokenLiquidity,
poolData.totalShares,
poolData?.shares[0]?.shares
)
const newPoolOwnerInfo = {
liquidity: ownerLiquidity,
poolShares: poolData.shares[0]?.shares,
poolSharePercentage
}
setPoolInfoOwner(newPoolOwnerInfo)
LoggerInstance.log('[pool] Created new pool creatorinfo:', newPoolOwnerInfo)
}, [
asset?.chainId,
poolData,
poolInfo?.baseTokenAddress,
poolInfo?.totalPoolTokens
])
return (
<PoolContext.Provider
value={
{
poolData,
poolInfo,
poolInfoOwner,
poolInfoUser,
poolSnapshots,
hasUserAddedLiquidity,
refreshInterval,
fetchAllData
} as PoolProviderValue
}
>
{children}
</PoolContext.Provider>
)
}
// Helper hook to access the provider values
const usePool = (): PoolProviderValue => useContext(PoolContext)
export { PoolProvider, usePool, PoolContext }
export default PoolProvider

View File

@ -7,13 +7,8 @@ import React, {
useCallback, useCallback,
ReactNode ReactNode
} from 'react' } from 'react'
import { import { getUserSales, getUserTokenOrders } from '@utils/subgraph'
getPoolSharesData,
getUserSales,
getUserTokenOrders
} from '@utils/subgraph'
import { useUserPreferences } from './UserPreferences' import { useUserPreferences } from './UserPreferences'
import { PoolShares_poolShares as PoolShare } from '../@types/subgraph/PoolShares'
import { Asset, LoggerInstance } from '@oceanprotocol/lib' import { Asset, LoggerInstance } from '@oceanprotocol/lib'
import { getDownloadAssets, getPublishedAssets } from '@utils/aquarius' import { getDownloadAssets, getPublishedAssets } from '@utils/aquarius'
import { accountTruncate } from '@utils/web3' import { accountTruncate } from '@utils/web3'
@ -24,8 +19,6 @@ import { useMarketMetadata } from './MarketMetadata'
interface ProfileProviderValue { interface ProfileProviderValue {
profile: Profile profile: Profile
poolShares: PoolShare[]
isPoolSharesLoading: boolean
assets: Asset[] assets: Asset[]
assetsTotal: number assetsTotal: number
isEthAddress: boolean isEthAddress: boolean
@ -121,55 +114,6 @@ function ProfileProvider({
} }
}, [accountId, accountEns, isEthAddress]) }, [accountId, accountEns, isEthAddress])
//
// POOL SHARES
//
const [poolShares, setPoolShares] = useState<PoolShare[]>()
const [isPoolSharesLoading, setIsPoolSharesLoading] = useState<boolean>(false)
const [poolSharesInterval, setPoolSharesInterval] = useState<NodeJS.Timeout>()
const fetchPoolShares = useCallback(
async (accountId: string, chainIds: number[], isEthAddress: boolean) => {
if (!accountId || !chainIds || !isEthAddress) return
try {
setIsPoolSharesLoading(true)
const poolShares = await getPoolSharesData(accountId, chainIds)
setPoolShares(poolShares)
LoggerInstance.log(
`[profile] Fetched ${poolShares.length} pool shares.`,
poolShares
)
} catch (error) {
LoggerInstance.error('Error fetching pool shares: ', error.message)
} finally {
setIsPoolSharesLoading(false)
}
},
[]
)
useEffect(() => {
async function init() {
await fetchPoolShares(accountId, chainIds, isEthAddress)
if (poolSharesInterval) return
const interval = setInterval(async () => {
LoggerInstance.log(
`[profile] Re-fetching pool shares after ${refreshInterval / 1000}s.`
)
await fetchPoolShares(accountId, chainIds, isEthAddress)
}, refreshInterval)
setPoolSharesInterval(interval)
}
init()
return () => {
clearInterval(poolSharesInterval)
}
}, [poolSharesInterval, fetchPoolShares, accountId, chainIds, isEthAddress])
// //
// PUBLISHED ASSETS // PUBLISHED ASSETS
// //
@ -300,8 +244,6 @@ function ProfileProvider({
<ProfileContext.Provider <ProfileContext.Provider
value={{ value={{
profile, profile,
poolShares,
isPoolSharesLoading,
assets, assets,
assetsTotal, assetsTotal,
isEthAddress, isEthAddress,

View File

@ -1,5 +1,9 @@
import { Asset } from '@oceanprotocol/lib' import { Asset } from '@oceanprotocol/lib'
interface AssetExtended extends Asset { // declaring into global scope to be able to use this as
accessDetails?: AccessDetails // ambiant types despite the above imports
declare global {
interface AssetExtended extends Asset {
accessDetails?: AccessDetails
}
} }

110
src/@types/Price.d.ts vendored
View File

@ -1,64 +1,56 @@
import { ProviderFees } from '@oceanprotocol/lib' import { ProviderFees } from '@oceanprotocol/lib'
/** // declaring into global scope to be able to use this as
* @interface OrderPriceAndFee // ambiant types despite the above imports
* @prop {string} price total price including fees declare global {
* @prop {string} publisherMarketOrderFee fee received by the market where the asset was published. It is set on erc20 creation. It is a absolute value /**
* @prop {string} publisherMarketPoolSwapFee fee received by the market where the asset was published on any swap (pool or fre). Absolute value based on the configured percentage * @interface OrderPriceAndFee
* @prop {string} publisherMarketFixedSwapFee fee received by the market where the asset was published on any swap (pool or fre). Absolute value based on the configured percentage * @prop {string} price total price including fees
* @prop {string} consumeMarketOrderFee fee received by the market where the asset is ordered. It is set on erc20 creation. It is a absolute value * @prop {string} publisherMarketOrderFee fee received by the market where the asset was published. It is set on erc20 creation. It is a absolute value
* @prop {string} consumeMarketPoolSwapFee fee received by the market where the asset is ordered on any swap (pool or fre). Absolute value based on the configured percentage * @prop {string} publisherMarketFixedSwapFee fee received by the market where the asset was published on any swap (fre). Absolute value based on the configured percentage
* @prop {string} consumeMarketFixedSwapFee fee received by the market where the asset is ordered on any swap (pool or fre). Absolute value based on the configured percentage * @prop {string} consumeMarketOrderFee fee received by the market where the asset is ordered. It is set on erc20 creation. It is a absolute value
* @prop {string} liquidityProviderSwapFee fee received by the liquidity providers of the pool. It is a percentage ( ex 50% means liquidityProviderSwapFee=0.5) * @prop {string} consumeMarketFixedSwapFee fee received by the market where the asset is ordered on any swap (fre). Absolute value based on the configured percentage
* @prop {ProviderFees} providerFee received from provider * @prop {ProviderFees} providerFee received from provider
* @prop {string} opcFee ocean protocol community fee, Absolute value based on the configured percentage * @prop {string} opcFee ocean protocol community fee, Absolute value based on the configured percentage
*/ */
interface OrderPriceAndFees { interface OrderPriceAndFees {
price: string price: string
publisherMarketOrderFee: string publisherMarketOrderFee: string
publisherMarketPoolSwapFee: string publisherMarketFixedSwapFee: string
publisherMarketFixedSwapFee: string consumeMarketOrderFee: string
consumeMarketOrderFee: string consumeMarketFixedSwapFee: string
consumeMarketPoolSwapFee: string providerFee: ProviderFees
consumeMarketFixedSwapFee: string opcFee: string
liquidityProviderSwapFee: string }
providerFee: ProviderFees
opcFee: string
}
/** /**
* @interface AccessDetails * @interface AccessDetails
* @prop {'dynamic' | 'fixed' | 'free' | ''} type * @prop {'fixed' | 'free' | 'NOT_SUPPORTED'} type
* @prop {string} price can be either spotPrice/rate * @prop {string} price can be either spotPrice/rate
* @prop {string} addressOrId if type is dynamic this is the pool address, for fixed/free this is an id. * @prop {string} addressOrId for fixed/free this is an id.
* @prop {TokenInfo} baseToken * @prop {TokenInfo} baseToken
* @prop {TokenInfo} datatoken * @prop {TokenInfo} datatoken
* @prop {bool} isPurchasable checks if you can buy a datatoken from fixed rate exchange/pool/dispenser. For pool it also checks if there is enough dt liquidity * @prop {bool} isPurchasable checks if you can buy a datatoken from fixed rate exchange/dispenser.
* @prop {bool} isOwned checks if there are valid orders for this, it also takes in consideration timeout * @prop {bool} isOwned checks if there are valid orders for this, it also takes in consideration timeout
* @prop {string} validOrderTx the latest valid order tx, it also takes in consideration timeout * @prop {string} validOrderTx the latest valid order tx, it also takes in consideration timeout
* @prop {string} publisherMarketOrderFee this is here just because it's more efficient, it's allready in the query * @prop {string} publisherMarketOrderFee this is here just because it's more efficient, it's allready in the query
* @prop {FeeInfo} feeInfo values of the relevant fees * @prop {FeeInfo} feeInfo values of the relevant fees
*/ */
interface AccessDetails { interface AccessDetails {
type: 'dynamic' | 'fixed' | 'free' | '' type: 'fixed' | 'free' | 'NOT_SUPPORTED'
price: string price: string
addressOrId: string addressOrId: string
baseToken: TokenInfo baseToken: TokenInfo
datatoken: TokenInfo datatoken: TokenInfo
isPurchasable?: boolean isPurchasable?: boolean
isOwned: bool isOwned: bool
validOrderTx: string validOrderTx: string
publisherMarketOrderFee: string publisherMarketOrderFee: string
} }
interface PriceOptions { interface PricePublishOptions {
price: number price: number
amountDataToken: number type: 'fixed' | 'free'
amountOcean: number freeAgreement: boolean
type: 'dynamic' | 'fixed' | 'free' | '' }
weightOnDataToken: string
weightOnOcean: string
// easier to keep this as number for Yup input validation
swapFee: number
freeAgreement: boolean
} }

View File

@ -1,8 +1,3 @@
interface PoolBalance {
baseToken: string
datatoken: string
}
interface UserBalance { interface UserBalance {
eth: string eth: string
ocean: string ocean: string

View File

@ -1,9 +0,0 @@
interface CalcInGivenOutParams {
tokenInLiquidity: string
tokenOutLiquidity: string
tokenOutAmount: string
opcFee: string
lpSwapFee: string
publishMarketSwapFee: string
consumeMarketSwapFee: string
}

View File

@ -14,13 +14,12 @@ import {
ProviderFees, ProviderFees,
ProviderInstance ProviderInstance
} from '@oceanprotocol/lib' } from '@oceanprotocol/lib'
import { AssetExtended } from 'src/@types/AssetExtended'
import { calcInGivenOut } from './pool'
import { getFixedBuyPrice } from './fixedRateExchange' import { getFixedBuyPrice } from './fixedRateExchange'
import { AccessDetails, OrderPriceAndFees } from 'src/@types/Price'
import Decimal from 'decimal.js' import Decimal from 'decimal.js'
import { consumeMarketOrderFee } from '../../app.config' import {
import Web3 from 'web3' consumeMarketOrderFee,
publisherMarketOrderFee
} from '../../app.config'
const tokensPriceQuery = gql` const tokensPriceQuery = gql`
query TokensPriceQuery($datatokenIds: [ID!], $account: String) { query TokensPriceQuery($datatokenIds: [ID!], $account: String) {
@ -75,22 +74,6 @@ const tokensPriceQuery = gql`
} }
active active
} }
pools {
id
spotPrice
isFinalized
datatokenLiquidity
baseToken {
symbol
name
address
}
datatoken {
symbol
name
address
}
}
} }
} }
` `
@ -147,22 +130,6 @@ const tokenPriceQuery = gql`
} }
active active
} }
pools {
id
spotPrice
isFinalized
datatokenLiquidity
baseToken {
symbol
name
address
}
datatoken {
symbol
name
address
}
}
} }
} }
` `
@ -173,6 +140,15 @@ function getAccessDetailsFromTokenPrice(
): AccessDetails { ): AccessDetails {
const accessDetails = {} as AccessDetails const accessDetails = {} as AccessDetails
// Return early when no supported pricing schema found.
if (
tokenPrice?.dispensers?.length === 0 &&
tokenPrice?.fixedRateExchanges?.length === 0
) {
accessDetails.type = 'NOT_SUPPORTED'
return accessDetails
}
if (tokenPrice?.orders?.length > 0) { if (tokenPrice?.orders?.length > 0) {
const order = tokenPrice.orders[0] const order = tokenPrice.orders[0]
const reusedOrder = order?.reuses?.length > 0 ? order.reuses[0] : null const reusedOrder = order?.reuses?.length > 0 ? order.reuses[0] : null
@ -198,7 +174,6 @@ function getAccessDetailsFromTokenPrice(
name: dispenser.token.name, name: dispenser.token.name,
symbol: dispenser.token.symbol symbol: dispenser.token.symbol
} }
return accessDetails
} }
// checking for fixed price // checking for fixed price
@ -219,30 +194,8 @@ function getAccessDetailsFromTokenPrice(
name: fixed.datatoken.name, name: fixed.datatoken.name,
symbol: fixed.datatoken.symbol symbol: fixed.datatoken.symbol
} }
return accessDetails
} }
// checking for pools
if (tokenPrice?.pools?.length > 0) {
const pool = tokenPrice.pools[0]
accessDetails.type = 'dynamic'
accessDetails.addressOrId = pool.id
accessDetails.price = pool.spotPrice
// TODO: pool.datatokenLiquidity > 3 is kinda random here, we shouldn't run into this anymore now , needs more thinking/testing
accessDetails.isPurchasable =
pool.isFinalized && pool.datatokenLiquidity > 3
accessDetails.baseToken = {
address: pool.baseToken.address,
name: pool.baseToken.name,
symbol: pool.baseToken.symbol
}
accessDetails.datatoken = {
address: pool.datatoken.address,
name: pool.datatoken.name,
symbol: pool.datatoken.symbol
}
return accessDetails
}
return accessDetails return accessDetails
} }
@ -254,17 +207,13 @@ function getAccessDetailsFromTokenPrice(
export async function getOrderPriceAndFees( export async function getOrderPriceAndFees(
asset: AssetExtended, asset: AssetExtended,
accountId?: string, accountId?: string,
paramsForPool?: CalcInGivenOutParams,
providerFees?: ProviderFees providerFees?: ProviderFees
): Promise<OrderPriceAndFees> { ): Promise<OrderPriceAndFees> {
const orderPriceAndFee = { const orderPriceAndFee = {
price: '0', price: '0',
publisherMarketOrderFee: publisherMarketOrderFee: publisherMarketOrderFee || '0',
asset?.accessDetails?.publisherMarketOrderFee || '0',
publisherMarketPoolSwapFee: '0',
publisherMarketFixedSwapFee: '0', publisherMarketFixedSwapFee: '0',
consumeMarketOrderFee: consumeMarketOrderFee || '0', consumeMarketOrderFee: consumeMarketOrderFee || '0',
consumeMarketPoolSwapFee: '0',
consumeMarketFixedSwapFee: '0', consumeMarketFixedSwapFee: '0',
providerFee: { providerFee: {
providerFeeAmount: '0' providerFeeAmount: '0'
@ -273,7 +222,6 @@ export async function getOrderPriceAndFees(
} as OrderPriceAndFees } as OrderPriceAndFees
// fetch provider fee // fetch provider fee
const initializeData = const initializeData =
!providerFees && !providerFees &&
(await ProviderInstance.initialize( (await ProviderInstance.initialize(
@ -286,27 +234,12 @@ export async function getOrderPriceAndFees(
orderPriceAndFee.providerFee = providerFees || initializeData.providerFee orderPriceAndFee.providerFee = providerFees || initializeData.providerFee
// fetch price and swap fees // fetch price and swap fees
switch (asset?.accessDetails?.type) { if (asset?.accessDetails?.type === 'fixed') {
case 'dynamic': { const fixed = await getFixedBuyPrice(asset?.accessDetails, asset?.chainId)
const poolPrice = calcInGivenOut(paramsForPool) orderPriceAndFee.price = fixed.baseTokenAmount
orderPriceAndFee.price = poolPrice.tokenAmount orderPriceAndFee.opcFee = fixed.oceanFeeAmount
orderPriceAndFee.liquidityProviderSwapFee = orderPriceAndFee.publisherMarketFixedSwapFee = fixed.marketFeeAmount
poolPrice.liquidityProviderSwapFeeAmount orderPriceAndFee.consumeMarketFixedSwapFee = fixed.consumeMarketFeeAmount
orderPriceAndFee.publisherMarketPoolSwapFee =
poolPrice.publishMarketSwapFeeAmount
orderPriceAndFee.consumeMarketPoolSwapFee =
poolPrice.consumeMarketSwapFeeAmount
break
}
case 'fixed': {
const fixed = await getFixedBuyPrice(asset?.accessDetails, asset?.chainId)
orderPriceAndFee.price = fixed.baseTokenAmount
orderPriceAndFee.opcFee = fixed.oceanFeeAmount
orderPriceAndFee.publisherMarketFixedSwapFee = fixed.marketFeeAmount
orderPriceAndFee.consumeMarketFixedSwapFee = fixed.consumeMarketFeeAmount
break
}
} }
// calculate full price, we assume that all the values are in ocean, otherwise this will be incorrect // calculate full price, we assume that all the values are in ocean, otherwise this will be incorrect
@ -314,6 +247,7 @@ export async function getOrderPriceAndFees(
.add(new Decimal(+orderPriceAndFee?.consumeMarketOrderFee || 0)) .add(new Decimal(+orderPriceAndFee?.consumeMarketOrderFee || 0))
.add(new Decimal(+orderPriceAndFee?.publisherMarketOrderFee || 0)) .add(new Decimal(+orderPriceAndFee?.publisherMarketOrderFee || 0))
.toString() .toString()
return orderPriceAndFee return orderPriceAndFee
} }

View File

@ -1,5 +1,4 @@
import { getAccessDetailsForAssets } from './accessDetailsAndPricing' import { getAccessDetailsForAssets } from './accessDetailsAndPricing'
import { AssetExtended } from 'src/@types/AssetExtended'
import { PublisherTrustedAlgorithm, Asset } from '@oceanprotocol/lib' import { PublisherTrustedAlgorithm, Asset } from '@oceanprotocol/lib'
import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection' import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection'
import { getServiceByName } from './ddo' import { getServiceByName } from './ddo'

View File

@ -24,7 +24,6 @@ import { getServiceById, getServiceByName } from './ddo'
import { SortTermOptions } from 'src/@types/aquarius/SearchQuery' import { SortTermOptions } from 'src/@types/aquarius/SearchQuery'
import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection' import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection'
import { transformAssetToAssetSelection } from './assetConvertor' import { transformAssetToAssetSelection } from './assetConvertor'
import { AssetExtended } from 'src/@types/AssetExtended'
import { ComputeEditForm } from 'src/components/Asset/Edit/_types' import { ComputeEditForm } from 'src/components/Asset/Edit/_types'
import { getFileDidInfo } from './provider' import { getFileDidInfo } from './provider'

View File

@ -4,7 +4,7 @@ export function getOrderFeedback(
datatokenSymbol: string datatokenSymbol: string
): { [key in number]: string } { ): { [key in number]: string } {
return { return {
0: `Approving and buying one ${datatokenSymbol} from pool`, 0: `Approving and buying one ${datatokenSymbol}`,
1: `Ordering asset`, 1: `Ordering asset`,
2: `Approving ${baseTokenSymbol} and ordering asset`, 2: `Approving ${baseTokenSymbol} and ordering asset`,
3: 'Generating signature to access download url' 3: 'Generating signature to access download url'

View File

@ -1,8 +1,7 @@
import { FixedRateExchange, PriceAndFees } from '@oceanprotocol/lib' import { FixedRateExchange, PriceAndFees } from '@oceanprotocol/lib'
import { AccessDetails } from 'src/@types/Price' import { consumeMarketFixedSwapFee } from '../../app.config'
import Web3 from 'web3' import Web3 from 'web3'
import { getOceanConfig } from './ocean' import { getOceanConfig } from './ocean'
import { consumeMarketPoolSwapFee } from '../../app.config'
import { getDummyWeb3 } from './web3' import { getDummyWeb3 } from './web3'
/** /**
@ -30,7 +29,7 @@ export async function getFixedBuyPrice(
const estimatedPrice = await fixed.calcBaseInGivenOutDT( const estimatedPrice = await fixed.calcBaseInGivenOutDT(
accessDetails.addressOrId, accessDetails.addressOrId,
'1', '1',
consumeMarketPoolSwapFee consumeMarketFixedSwapFee
) )
return estimatedPrice return estimatedPrice
} }

View File

@ -10,21 +10,17 @@ import {
ProviderFees, ProviderFees,
ProviderInstance ProviderInstance
} from '@oceanprotocol/lib' } from '@oceanprotocol/lib'
import { AssetExtended } from 'src/@types/AssetExtended'
import Web3 from 'web3' import Web3 from 'web3'
import { getOceanConfig } from './ocean' import { getOceanConfig } from './ocean'
import { TransactionReceipt } from 'web3-eth' import { TransactionReceipt } from 'web3-eth'
import { OrderPriceAndFees } from 'src/@types/Price'
import { import {
marketFeeAddress, marketFeeAddress,
consumeMarketOrderFee, consumeMarketOrderFee,
consumeMarketFixedSwapFee consumeMarketFixedSwapFee
} from '../../app.config' } from '../../app.config'
import { buyDtFromPool } from './pool'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
/** /**
* For pool you need to buy the datatoken beforehand, this always assumes you want to order the first service
* @param web3 * @param web3
* @param asset * @param asset
* @param orderPriceAndFees * @param orderPriceAndFees
@ -102,17 +98,6 @@ export async function order(
return tx return tx
} }
case 'dynamic': {
const tx = await datatoken.startOrder(
asset.accessDetails.datatoken.address,
accountId,
computeConsumerAddress || accountId,
0,
providerFees || initializeData.providerFee
)
return tx
}
case 'free': { case 'free': {
const tx = await datatoken.buyFromDispenserAndOrder( const tx = await datatoken.buyFromDispenserAndOrder(
asset.services[0].datatokenAddress, asset.services[0].datatokenAddress,
@ -191,14 +176,6 @@ async function startOrder(
initializeData: ProviderComputeInitialize, initializeData: ProviderComputeInitialize,
computeConsumerAddress?: string computeConsumerAddress?: string
): Promise<TransactionReceipt> { ): Promise<TransactionReceipt> {
if (!hasDatatoken && asset?.accessDetails.type === 'dynamic') {
const poolTx = await buyDtFromPool(asset?.accessDetails, accountId, web3)
LoggerInstance.log('[compute] Bought datatoken from pool: ', poolTx)
if (!poolTx) {
toast.error('Failed to buy datatoken from pool!')
return
}
}
const tx = await order( const tx = await order(
web3, web3,
asset, asset,

View File

@ -1,185 +0,0 @@
import { approve, Pool, PoolPriceAndFees } from '@oceanprotocol/lib'
import Web3 from 'web3'
import { getDummyWeb3 } from './web3'
import { TransactionReceipt } from 'web3-eth'
import Decimal from 'decimal.js'
import { AccessDetails } from 'src/@types/Price'
import { consumeMarketPoolSwapFee, marketFeeAddress } from '../../app.config'
/**
* This is used to calculate the price to buy one datatoken from a pool, that is different from spot price. You need to pass either a web3 object or a chainId. If you pass a chainId a dummy web3 object will be created
* @param {AccessDetails} accessDetails
* @param {Web3?} [web3]
* @param {number?} [chainId]
* @return {Promise<PoolPriceAndFees>}
*/
export async function calculateBuyPrice(
accessDetails: AccessDetails,
chainId?: number,
web3?: Web3
): Promise<PoolPriceAndFees> {
if (!web3 && !chainId)
throw new Error("web3 and chainId can't be undefined at the same time!")
if (!web3) {
web3 = await getDummyWeb3(chainId)
}
const pool = new Pool(web3)
const estimatedPrice = await pool.getAmountInExactOut(
accessDetails.addressOrId,
accessDetails.baseToken.address,
accessDetails.datatoken.address,
'1',
consumeMarketPoolSwapFee,
accessDetails.baseToken.decimals,
accessDetails.datatoken.decimals
)
return estimatedPrice
}
export async function buyDtFromPool(
accessDetails: AccessDetails,
accountId: string,
web3: Web3
): Promise<TransactionReceipt> {
const pool = new Pool(web3)
// we need to calculate the actual price to buy one datatoken
const dtPrice = await calculateBuyPrice(accessDetails, null, web3)
const approveTx = await approve(
web3,
accountId,
accessDetails.baseToken.address,
accessDetails.addressOrId,
dtPrice.tokenAmount,
false,
accessDetails.baseToken.decimals
)
if (!approveTx) {
return
}
const result = await pool.swapExactAmountOut(
accountId,
accessDetails.addressOrId,
{
marketFeeAddress,
tokenIn: accessDetails.baseToken.address,
tokenOut: accessDetails.datatoken.address,
tokenInDecimals: accessDetails.baseToken.decimals,
tokenOutDecimals: accessDetails.datatoken.decimals
},
{
// this is just to be safe
maxAmountIn: new Decimal(dtPrice.tokenAmount).mul(10).toString(),
swapMarketFee: consumeMarketPoolSwapFee,
tokenAmountOut: '1'
}
)
return result
}
/**
* This is used to calculate the actual price of buying a datatoken, it's a copy of the math in the contracts.
* @param params
* @returns
*/
export function calcInGivenOut(params: CalcInGivenOutParams): PoolPriceAndFees {
const result = {
tokenAmount: '0',
liquidityProviderSwapFeeAmount: '0',
oceanFeeAmount: '0',
publishMarketSwapFeeAmount: '0',
consumeMarketSwapFeeAmount: '0'
} as PoolPriceAndFees
const one = new Decimal(1)
const tokenOutLiqudity = new Decimal(params.tokenOutLiquidity)
const tokenInLiquidity = new Decimal(params.tokenInLiquidity)
const tokenOutAmount = new Decimal(params.tokenOutAmount)
const opcFee = new Decimal(params.opcFee)
const lpFee = new Decimal(params.lpSwapFee)
const publishMarketSwapFee = new Decimal(params.publishMarketSwapFee)
const consumeMarketSwapFee = new Decimal(params.consumeMarketSwapFee)
const diff = tokenOutLiqudity.minus(tokenOutAmount)
const y = tokenOutLiqudity.div(diff)
let foo = y.pow(one)
foo = foo.minus(one)
const totalFee = lpFee
.plus(opcFee)
.plus(publishMarketSwapFee)
.plus(consumeMarketSwapFee)
const tokenAmountIn = tokenInLiquidity.mul(foo).div(one.sub(totalFee))
result.tokenAmount = tokenAmountIn.toString()
result.oceanFeeAmount = tokenAmountIn
.sub(tokenAmountIn.mul(one.sub(opcFee)))
.toString()
result.publishMarketSwapFeeAmount = tokenAmountIn
.sub(tokenAmountIn.mul(one.sub(publishMarketSwapFee)))
.toString()
result.consumeMarketSwapFeeAmount = tokenAmountIn
.sub(tokenAmountIn.mul(one.sub(consumeMarketSwapFee)))
.toString()
result.liquidityProviderSwapFeeAmount = tokenAmountIn
.sub(tokenAmountIn.mul(one.sub(lpFee)))
.toString()
return result
}
/**
* Used to calculate swap values, it's a copy of the math in the contracts.
* @param tokenLiquidity
* @param poolSupply
* @param poolShareAmount
* @returns
*/
export function calcSingleOutGivenPoolIn(
tokenLiquidity: string,
poolSupply: string,
poolShareAmount: string
): string {
const tokenLiquidityD = new Decimal(tokenLiquidity)
const poolSupplyD = new Decimal(poolSupply)
const poolShareAmountD = new Decimal(poolShareAmount).mul(2)
const newPoolSupply = poolSupplyD.sub(poolShareAmountD)
const poolRatio = newPoolSupply.div(poolSupplyD)
const tokenOutRatio = new Decimal(1).sub(poolRatio)
const newTokenBalanceOut = tokenLiquidityD.mul(tokenOutRatio)
return newTokenBalanceOut.toString()
}
/**
* Returns the amount of tokens (based on tokenAddress) that can be withdrawn from the pool
* @param {string} poolAddress
* @param {string} tokenAddress
* @param {string} shares
* @param {number} chainId
* @returns
*/
export async function getLiquidityByShares(
pool: string,
tokenAddress: string,
tokenDecimals: number,
shares: string,
chainId: number
): Promise<string> {
// we only use the dummyWeb3 connection here
const web3 = await getDummyWeb3(chainId)
const poolInstance = new Pool(web3)
// get shares VL in ocean
const amountBaseToken = await poolInstance.calcSingleOutGivenPoolIn(
pool,
tokenAddress,
shares,
18,
tokenDecimals
)
return amountBaseToken
}

View File

@ -8,7 +8,6 @@ import {
ProviderComputeInitializeResults, ProviderComputeInitializeResults,
ProviderInstance ProviderInstance
} from '@oceanprotocol/lib' } from '@oceanprotocol/lib'
import { AssetExtended } from 'src/@types/AssetExtended'
import Web3 from 'web3' import Web3 from 'web3'
import { getValidUntilTime } from './compute' import { getValidUntilTime } from './compute'

View File

@ -1,23 +1,11 @@
import { gql, OperationResult, TypedDocumentNode, OperationContext } from 'urql' import { gql, OperationResult, TypedDocumentNode, OperationContext } from 'urql'
import { Asset, LoggerInstance } from '@oceanprotocol/lib' import { LoggerInstance } from '@oceanprotocol/lib'
import { getUrqlClientInstance } from '@context/UrqlProvider' import { getUrqlClientInstance } from '@context/UrqlProvider'
import { getOceanConfig } from './ocean' import { getOceanConfig } from './ocean'
import { AssetPoolPrice } from '../@types/subgraph/AssetPoolPrice'
import { AssetPreviousOrder } from '../@types/subgraph/AssetPreviousOrder' import { AssetPreviousOrder } from '../@types/subgraph/AssetPreviousOrder'
import {
HighestLiquidityAssets_pools as HighestLiquidityAssetsPool,
HighestLiquidityAssets as HighestLiquidityGraphAssets
} from '../@types/subgraph/HighestLiquidityAssets'
import {
PoolShares as PoolSharesList,
PoolShares_poolShares as PoolShare
} from '../@types/subgraph/PoolShares'
import { OrdersData_orders as OrdersData } from '../@types/subgraph/OrdersData' import { OrdersData_orders as OrdersData } from '../@types/subgraph/OrdersData'
import { UserSalesQuery as UsersSalesList } from '../@types/subgraph/UserSalesQuery'
import { OpcFeesQuery as OpcFeesData } from '../@types/subgraph/OpcFeesQuery' import { OpcFeesQuery as OpcFeesData } from '../@types/subgraph/OpcFeesQuery'
import { calcSingleOutGivenPoolIn } from './pool'
import Decimal from 'decimal.js'
import { MAX_DECIMALS } from './constants'
import { getPublishedAssets, getTopPublishers } from '@utils/aquarius' import { getPublishedAssets, getTopPublishers } from '@utils/aquarius'
export interface UserLiquidity { export interface UserLiquidity {
price: string price: string
@ -28,24 +16,6 @@ export interface PriceList {
[key: string]: string [key: string]: string
} }
const AssetPoolPriceQuery = gql`
query AssetPoolPrice($datatokenAddress: String) {
pools(where: { datatoken: $datatokenAddress }) {
id
spotPrice
datatoken {
address
symbol
}
baseToken {
symbol
}
datatokenLiquidity
baseTokenLiquidity
}
}
`
const PreviousOrderQuery = gql` const PreviousOrderQuery = gql`
query AssetPreviousOrder($id: String!, $account: String!) { query AssetPreviousOrder($id: String!, $account: String!) {
orders( orders(
@ -59,84 +29,6 @@ const PreviousOrderQuery = gql`
} }
} }
` `
const HighestLiquidityAssets = gql`
query HighestLiquidityAssets {
pools(
where: { datatokenLiquidity_gte: 1 }
orderBy: baseTokenLiquidity
orderDirection: desc
first: 15
) {
id
datatoken {
address
}
baseToken {
symbol
}
baseTokenLiquidity
datatokenLiquidity
}
}
`
const UserSharesQuery = gql`
query UserSharesQuery($user: String, $pools: [String!]) {
poolShares(where: { user: $user, pool_in: $pools }) {
id
shares
user {
id
}
pool {
id
datatoken {
address
symbol
}
baseToken {
address
symbol
}
datatokenLiquidity
baseTokenLiquidity
totalShares
spotPrice
createdTimestamp
}
}
}
`
const userPoolSharesQuery = gql`
query PoolShares($user: String) {
poolShares(where: { user: $user, shares_gt: 0.001 }, first: 1000) {
id
shares
user {
id
}
pool {
id
datatoken {
id
address
symbol
}
baseToken {
id
address
symbol
}
baseTokenLiquidity
datatokenLiquidity
totalShares
spotPrice
createdTimestamp
}
}
}
`
const UserTokenOrders = gql` const UserTokenOrders = gql`
query OrdersData($user: String!) { query OrdersData($user: String!) {
@ -163,24 +55,6 @@ const UserTokenOrders = gql`
} }
` `
const UserSalesQuery = gql`
query UserSalesQuery($user: ID!) {
users(where: { id: $user }) {
id
totalSales
}
}
`
const TopSalesQuery = gql`
query TopSalesQuery {
users(first: 20, orderBy: totalSales, orderDirection: desc) {
id
totalSales
}
}
`
const OpcFeesQuery = gql` const OpcFeesQuery = gql`
query OpcFeesQuery($id: ID!) { query OpcFeesQuery($id: ID!) {
opc(id: $id) { opc(id: $id) {
@ -289,99 +163,6 @@ export async function getPreviousOrders(
} }
} }
export async function getSpotPrice(asset: Asset): Promise<number> {
const poolVariables = {
datatokenAddress: asset?.services[0].datatokenAddress.toLowerCase()
}
const queryContext = getQueryContext(Number(asset.chainId))
const poolPriceResponse: OperationResult<AssetPoolPrice> = await fetchData(
AssetPoolPriceQuery,
poolVariables,
queryContext
)
return poolPriceResponse.data.pools[0].spotPrice
}
export async function getHighestLiquidityDatatokens(
chainIds: number[]
): Promise<string[]> {
const dtList: string[] = []
let highestLiquidityAssets: HighestLiquidityAssetsPool[] = []
for (const chain of chainIds) {
const queryContext = getQueryContext(Number(chain))
const fetchedPools: OperationResult<HighestLiquidityGraphAssets, any> =
await fetchData(HighestLiquidityAssets, null, queryContext)
highestLiquidityAssets = highestLiquidityAssets.concat(
fetchedPools?.data?.pools
)
}
highestLiquidityAssets.sort(
(a, b) => b.baseTokenLiquidity - a.baseTokenLiquidity
)
for (let i = 0; i < highestLiquidityAssets.length; i++) {
if (!highestLiquidityAssets[i]?.datatoken?.address) continue
dtList.push(highestLiquidityAssets[i].datatoken.address)
}
return dtList
}
export async function getAccountLiquidityInOwnAssets(
accountId: string,
chainIds: number[],
pools: string[]
): Promise<string> {
const queryVariables = {
user: accountId.toLowerCase(),
pools
}
const results: PoolSharesList[] = await fetchDataForMultipleChains(
UserSharesQuery,
queryVariables,
chainIds
)
let totalLiquidity = new Decimal(0)
for (const result of results) {
for (const poolShare of result.poolShares) {
const poolUserLiquidity = calcSingleOutGivenPoolIn(
poolShare.pool.baseTokenLiquidity,
poolShare.pool.totalShares,
poolShare.shares
)
totalLiquidity = totalLiquidity.add(new Decimal(poolUserLiquidity))
}
}
return totalLiquidity.toDecimalPlaces(MAX_DECIMALS).toString()
}
export async function getPoolSharesData(
accountId: string,
chainIds: number[]
): Promise<PoolShare[]> {
const variables = { user: accountId?.toLowerCase() }
const data: PoolShare[] = []
try {
const result = await fetchDataForMultipleChains(
userPoolSharesQuery,
variables,
chainIds
)
for (let i = 0; i < result.length; i++) {
result[i].poolShares.forEach((poolShare: PoolShare) => {
data.push(poolShare)
})
}
return data
} catch (error) {
LoggerInstance.error('Error getPoolSharesData: ', error.message)
}
}
export async function getUserTokenOrders( export async function getUserTokenOrders(
accountId: string, accountId: string,
chainIds: number[] chainIds: number[]

View File

@ -6,9 +6,6 @@ import classNames from 'classnames/bind'
import Loader from '@shared/atoms/Loader' import Loader from '@shared/atoms/Loader'
import { useUserPreferences } from '@context/UserPreferences' import { useUserPreferences } from '@context/UserPreferences'
import { useIsMounted } from '@hooks/useIsMounted' import { useIsMounted } from '@hooks/useIsMounted'
// not sure why this import is required
import { AssetExtended } from 'src/@types/AssetExtended'
import { Asset } from '@oceanprotocol/lib'
import { getAccessDetailsForAssets } from '@utils/accessDetailsAndPricing' import { getAccessDetailsForAssets } from '@utils/accessDetailsAndPricing'
import { useWeb3 } from '@context/Web3' import { useWeb3 } from '@context/Web3'
@ -23,7 +20,7 @@ function LoaderArea() {
} }
declare type AssetListProps = { declare type AssetListProps = {
assets: Asset[] assets: AssetExtended[]
showPagination: boolean showPagination: boolean
page?: number page?: number
totalPages?: number totalPages?: number
@ -51,6 +48,7 @@ export default function AssetList({
useEffect(() => { useEffect(() => {
if (!assets) return if (!assets) return
setAssetsWithPrices(assets as AssetExtended[]) setAssetsWithPrices(assets as AssetExtended[])
setLoading(false) setLoading(false)
async function fetchPrices() { async function fetchPrices() {
@ -58,7 +56,7 @@ export default function AssetList({
assets, assets,
accountId || '' accountId || ''
) )
if (!isMounted()) return if (!isMounted() || !assetsWithPrices) return
setAssetsWithPrices([...assetsWithPrices]) setAssetsWithPrices([...assetsWithPrices])
} }
fetchPrices() fetchPrices()

View File

@ -8,7 +8,6 @@ import AssetType from '@shared/AssetType'
import NetworkName from '@shared/NetworkName' import NetworkName from '@shared/NetworkName'
import styles from './AssetTeaser.module.css' import styles from './AssetTeaser.module.css'
import { getServiceByName } from '@utils/ddo' import { getServiceByName } from '@utils/ddo'
import { AssetExtended } from 'src/@types/AssetExtended'
declare type AssetTeaserProps = { declare type AssetTeaserProps = {
asset: AssetExtended asset: AssetExtended

View File

@ -10,7 +10,6 @@ interface ButtonBuyProps {
hasDatatoken: boolean hasDatatoken: boolean
dtSymbol: string dtSymbol: string
dtBalance: string dtBalance: string
datasetLowPoolLiquidity: boolean
assetType: string assetType: string
assetTimeout: string assetTimeout: string
isConsumable: boolean isConsumable: boolean
@ -19,7 +18,6 @@ interface ButtonBuyProps {
hasDatatokenSelectedComputeAsset?: boolean hasDatatokenSelectedComputeAsset?: boolean
dtSymbolSelectedComputeAsset?: string dtSymbolSelectedComputeAsset?: string
dtBalanceSelectedComputeAsset?: string dtBalanceSelectedComputeAsset?: string
selectedComputeAssetLowPoolLiquidity?: boolean
selectedComputeAssetType?: string selectedComputeAssetType?: string
isBalanceSufficient: boolean isBalanceSufficient: boolean
isLoading?: boolean isLoading?: boolean
@ -39,7 +37,6 @@ function getConsumeHelpText(
dtSymbol: string, dtSymbol: string,
hasDatatoken: boolean, hasDatatoken: boolean,
hasPreviousOrder: boolean, hasPreviousOrder: boolean,
lowPoolLiquidity: boolean,
assetType: string, assetType: string,
isConsumable: boolean, isConsumable: boolean,
isBalanceSufficient: boolean, isBalanceSufficient: boolean,
@ -52,11 +49,9 @@ function getConsumeHelpText(
? `You bought this ${assetType} already allowing you to use it without paying again.` ? `You bought this ${assetType} already allowing you to use it without paying again.`
: hasDatatoken : hasDatatoken
? `You own ${dtBalance} ${dtSymbol} allowing you to use this data set by spending 1 ${dtSymbol}, but without paying OCEAN again.` ? `You own ${dtBalance} ${dtSymbol} allowing you to use this data set by spending 1 ${dtSymbol}, but without paying OCEAN again.`
: lowPoolLiquidity
? `There are not enought ${dtSymbol} available in the pool for the transaction to take place`
: isBalanceSufficient === false : isBalanceSufficient === false
? 'You do not have enough OCEAN in your wallet to purchase this asset.' ? 'You do not have enough OCEAN in your wallet to purchase this asset.'
: `For using this ${assetType}, you will buy 1 ${dtSymbol} and immediately spend it back to the publisher and pool.` : `For using this ${assetType}, you will buy 1 ${dtSymbol} and immediately spend it back to the publisher.`
return text return text
} }
@ -65,16 +60,14 @@ function getComputeAssetHelpText(
hasDatatoken: boolean, hasDatatoken: boolean,
dtSymbol: string, dtSymbol: string,
dtBalance: string, dtBalance: string,
lowPoolLiquidity: boolean,
assetType: string,
isConsumable: boolean, isConsumable: boolean,
consumableFeedback: string, consumableFeedback: string,
isBalanceSufficient: boolean, isBalanceSufficient: boolean,
hasPreviousOrderSelectedComputeAsset?: boolean, hasPreviousOrderSelectedComputeAsset?: boolean,
hasDatatokenSelectedComputeAsset?: boolean, hasDatatokenSelectedComputeAsset?: boolean,
assetType?: string,
dtSymbolSelectedComputeAsset?: string, dtSymbolSelectedComputeAsset?: string,
dtBalanceSelectedComputeAsset?: string, dtBalanceSelectedComputeAsset?: string,
selectedComputeAssettLowPoolLiquidity?: boolean,
selectedComputeAssetType?: string, selectedComputeAssetType?: string,
isAlgorithmConsumable?: boolean, isAlgorithmConsumable?: boolean,
hasProviderFee?: boolean hasProviderFee?: boolean
@ -84,12 +77,12 @@ function getComputeAssetHelpText(
dtSymbol, dtSymbol,
hasDatatoken, hasDatatoken,
hasPreviousOrder, hasPreviousOrder,
lowPoolLiquidity,
assetType, assetType,
isConsumable, isConsumable,
isBalanceSufficient, isBalanceSufficient,
consumableFeedback consumableFeedback
) )
const computeAlgoHelpText = const computeAlgoHelpText =
(!dtSymbolSelectedComputeAsset && !dtBalanceSelectedComputeAsset) || (!dtSymbolSelectedComputeAsset && !dtBalanceSelectedComputeAsset) ||
isConsumable === false || isConsumable === false ||
@ -99,19 +92,13 @@ function getComputeAssetHelpText(
? `You already bought the selected ${selectedComputeAssetType}, allowing you to use it without paying again.` ? `You already bought the selected ${selectedComputeAssetType}, allowing you to use it without paying again.`
: hasDatatokenSelectedComputeAsset : hasDatatokenSelectedComputeAsset
? `You own ${dtBalanceSelectedComputeAsset} ${dtSymbolSelectedComputeAsset} allowing you to use the selected ${selectedComputeAssetType} by spending 1 ${dtSymbolSelectedComputeAsset}, but without paying OCEAN again.` ? `You own ${dtBalanceSelectedComputeAsset} ${dtSymbolSelectedComputeAsset} allowing you to use the selected ${selectedComputeAssetType} by spending 1 ${dtSymbolSelectedComputeAsset}, but without paying OCEAN again.`
: selectedComputeAssettLowPoolLiquidity
? `There are not enought ${dtSymbolSelectedComputeAsset} available in the pool for the transaction to take place`
: isBalanceSufficient === false : isBalanceSufficient === false
? '' ? ''
: `Additionally, you will buy 1 ${dtSymbolSelectedComputeAsset} for the ${selectedComputeAssetType} and spend it back to its publisher and pool.` : `Additionally, you will buy 1 ${dtSymbolSelectedComputeAsset} for the ${selectedComputeAssetType} and spend it back to its publisher.`
const providerFeeHelpText = hasProviderFee const providerFeeHelpText = hasProviderFee
? 'In order to start the job you also need to pay the fees for renting the c2d resources.' ? 'In order to start the job you also need to pay the fees for renting the c2d resources.'
: 'C2D resources required to start the job are available, no payment required for those fees.' : 'C2D resources required to start the job are available, no payment required for those fees.'
const computeHelpText = selectedComputeAssettLowPoolLiquidity const computeHelpText = `${computeAssetHelpText} ${computeAlgoHelpText} ${providerFeeHelpText}`
? computeAlgoHelpText
: lowPoolLiquidity
? computeAssetHelpText
: `${computeAssetHelpText} ${computeAlgoHelpText} ${providerFeeHelpText}`
return computeHelpText return computeHelpText
} }
@ -122,7 +109,6 @@ export default function ButtonBuy({
hasDatatoken, hasDatatoken,
dtSymbol, dtSymbol,
dtBalance, dtBalance,
datasetLowPoolLiquidity,
assetType, assetType,
assetTimeout, assetTimeout,
isConsumable, isConsumable,
@ -132,7 +118,6 @@ export default function ButtonBuy({
hasDatatokenSelectedComputeAsset, hasDatatokenSelectedComputeAsset,
dtSymbolSelectedComputeAsset, dtSymbolSelectedComputeAsset,
dtBalanceSelectedComputeAsset, dtBalanceSelectedComputeAsset,
selectedComputeAssetLowPoolLiquidity,
selectedComputeAssetType, selectedComputeAssetType,
onClick, onClick,
stepText, stepText,
@ -180,7 +165,6 @@ export default function ButtonBuy({
dtSymbol, dtSymbol,
hasDatatoken, hasDatatoken,
hasPreviousOrder, hasPreviousOrder,
datasetLowPoolLiquidity,
assetType, assetType,
isConsumable, isConsumable,
isBalanceSufficient, isBalanceSufficient,
@ -191,16 +175,14 @@ export default function ButtonBuy({
hasDatatoken, hasDatatoken,
dtSymbol, dtSymbol,
dtBalance, dtBalance,
datasetLowPoolLiquidity,
assetType,
isConsumable, isConsumable,
consumableFeedback, consumableFeedback,
isBalanceSufficient, isBalanceSufficient,
hasPreviousOrderSelectedComputeAsset, hasPreviousOrderSelectedComputeAsset,
hasDatatokenSelectedComputeAsset, hasDatatokenSelectedComputeAsset,
assetType,
dtSymbolSelectedComputeAsset, dtSymbolSelectedComputeAsset,
dtBalanceSelectedComputeAsset, dtBalanceSelectedComputeAsset,
selectedComputeAssetLowPoolLiquidity,
selectedComputeAssetType, selectedComputeAssetType,
isAlgorithmConsumable, isAlgorithmConsumable,
hasProviderFee hasProviderFee

View File

@ -1,3 +0,0 @@
.titleText {
white-space: pre;
}

View File

@ -1,86 +0,0 @@
import React, { useState, useEffect, ReactElement } from 'react'
import { PoolTransaction } from '.'
import { useUserPreferences } from '@context/UserPreferences'
import ExplorerLink from '@shared/ExplorerLink'
import { formatPrice } from '@shared/Price/PriceUnit'
import styles from './Title.module.css'
function getTitle(row: PoolTransaction, locale: string) {
let title = ''
switch (row.type) {
case 'SWAP': {
const { datatoken, baseToken, datatokenValue, baseTokenValue } = row
const outToken =
(datatokenValue < 0 && datatoken) || (baseTokenValue < 0 && baseToken)
const outTokenValue =
(datatokenValue < 0 && datatokenValue) ||
(baseTokenValue < 0 && baseTokenValue)
const outTokenSymbol = outToken?.symbol
const inToken =
(datatokenValue > 0 && datatoken) || (baseTokenValue > 0 && baseToken)
const inTokenValue =
(datatokenValue > 0 && datatokenValue) ||
(baseTokenValue > 0 && baseTokenValue)
const inTokenSymbol = inToken?.symbol
title += `Swap ${formatPrice(
Math.abs(inTokenValue).toString(),
locale
)}${inTokenSymbol} for ${formatPrice(
Math.abs(outTokenValue).toString(),
locale
)}${outTokenSymbol}`
break
}
case 'SETUP': {
const firstToken = row.baseToken
const firstTokenSymbol = firstToken?.symbol
title += `Create pool with ${formatPrice(
Math.abs(row.baseTokenValue).toString(),
locale
)}${firstTokenSymbol}`
break
}
case 'JOIN':
case 'EXIT': {
const tokenMoved =
Math.abs(row.baseTokenValue) > 0 ? row.baseToken : row.datatoken
const tokenValueMoved =
Math.abs(row.baseTokenValue) > 0
? row.baseTokenValue
: row.datatokenValue
const tokenSymbol = tokenMoved.symbol
title += `${row.type === 'JOIN' ? 'Add' : 'Remove'} ${formatPrice(
Math.abs(tokenValueMoved).toString(),
locale
)}${tokenSymbol}`
break
}
}
return title
}
export default function Title({ row }: { row: PoolTransaction }): ReactElement {
const [title, setTitle] = useState<string>()
const { locale } = useUserPreferences()
useEffect(() => {
if (!locale || !row) return
const title = getTitle(row, locale)
setTitle(title)
}, [row, locale])
return title ? (
<ExplorerLink networkId={row.networkId} path={`/tx/${row.tx}`}>
<span className={styles.titleText}>{title}</span>
</ExplorerLink>
) : null
}

View File

@ -1,3 +0,0 @@
.time {
color: var(--color-secondary);
}

View File

@ -1,264 +0,0 @@
import React, { ReactElement, useCallback, useEffect, useState } from 'react'
import Time from '@shared/atoms/Time'
import Table, { TableOceanColumn } from '@shared/atoms/Table'
import AssetTitle from '@shared/AssetList/AssetListTitle'
import { useUserPreferences } from '@context/UserPreferences'
import { gql } from 'urql'
import { TransactionHistory_poolTransactions as TransactionHistoryPoolTransactions } from '../../../@types/subgraph/TransactionHistory'
import { fetchDataForMultipleChains } from '@utils/subgraph'
import NetworkName from '@shared/NetworkName'
import { getAssetsFromDtList } from '@utils/aquarius'
import { getAsset } from '../../Profile/History/PoolShares/_utils'
import { CancelToken } from 'axios'
import Title from './Title'
import styles from './index.module.css'
import { Asset, LoggerInstance } from '@oceanprotocol/lib'
import { useCancelToken } from '@hooks/useCancelToken'
import { useMarketMetadata } from '@context/MarketMetadata'
const REFETCH_INTERVAL = 20000
const txHistoryQueryByPool = gql`
query TransactionHistoryByPool($user: String, $pool: String) {
poolTransactions(
orderBy: timestamp
orderDirection: desc
where: { pool: $pool, user: $user }
first: 1000
) {
baseToken {
symbol
address
}
baseTokenValue
datatoken {
symbol
address
}
datatokenValue
type
tx
timestamp
pool {
datatoken {
id
}
id
}
}
}
`
const txHistoryQuery = gql`
query TransactionHistory($user: String) {
poolTransactions(
orderBy: timestamp
orderDirection: desc
where: { user: $user }
first: 1000
) {
baseToken {
symbol
address
}
baseTokenValue
datatoken {
symbol
address
}
datatokenValue
type
tx
timestamp
pool {
datatoken {
id
}
id
}
}
}
`
export interface PoolTransaction extends TransactionHistoryPoolTransactions {
networkId: number
asset: Asset
}
const columns: TableOceanColumn<PoolTransaction>[] = [
{
name: 'Title',
selector: (row) => <Title row={row} />
},
{
name: 'Data Set',
selector: (row) => <AssetTitle asset={row.asset} />
},
{
name: 'Network',
selector: (row) => <NetworkName networkId={row.networkId} />,
maxWidth: '12rem'
},
{
name: 'Time',
selector: (row) => (
<Time
className={styles.time}
date={row.timestamp.toString()}
relative
isUnix
/>
),
maxWidth: '10rem'
}
]
// hack! if we use a function to omit one field this will display a strange refresh to the enduser for each row
const columnsMinimal = [columns[0], columns[3]]
export default function PoolTransactions({
poolAddress,
poolChainId,
minimal,
accountId
}: {
poolAddress?: string
poolChainId?: number
minimal?: boolean
accountId: string
}): ReactElement {
const { chainIds } = useUserPreferences()
const { appConfig } = useMarketMetadata()
const cancelToken = useCancelToken()
const [transactions, setTransactions] = useState<PoolTransaction[]>()
const [isLoading, setIsLoading] = useState<boolean>(false)
const [dataFetchInterval, setDataFetchInterval] = useState<NodeJS.Timeout>()
const [data, setData] = useState<PoolTransaction[]>()
const getPoolTransactionData = useCallback(async () => {
const variables = {
user: accountId?.toLowerCase(),
pool: poolAddress?.toLowerCase()
}
const transactions: PoolTransaction[] = []
const result = await fetchDataForMultipleChains(
poolAddress ? txHistoryQueryByPool : txHistoryQuery,
variables,
poolAddress ? [poolChainId] : chainIds
)
for (let i = 0; i < result.length; i++) {
result[i].poolTransactions.forEach((poolTransaction: PoolTransaction) => {
transactions.push(poolTransaction)
})
}
if (JSON.stringify(data) !== JSON.stringify(transactions)) {
setData(transactions)
}
}, [accountId, chainIds, data, poolAddress, poolChainId])
const getPoolTransactions = useCallback(
async (cancelToken: CancelToken) => {
if (!data) {
return
}
const poolTransactions: PoolTransaction[] = []
let dtList: string[] = []
dtList = [...new Set(data.map((item) => item.pool.datatoken.id))]
if (dtList.length === 0) {
setTransactions([])
setIsLoading(false)
return
}
const ddoList = !minimal
? await getAssetsFromDtList(dtList, chainIds, cancelToken)
: []
for (let i = 0; i < data.length; i++) {
poolTransactions.push({
...data[i],
networkId: !minimal
? getAsset(ddoList, data[i].pool.datatoken.id)?.chainId
: poolChainId,
asset: !minimal ? getAsset(ddoList, data[i].pool.datatoken.id) : null
})
}
const sortedTransactions = poolTransactions.sort(
(a, b) => b.timestamp - a.timestamp
)
setTransactions(sortedTransactions)
setIsLoading(false)
},
[data, minimal, chainIds, poolChainId]
)
//
// Get data, periodically
//
useEffect(() => {
if (!appConfig?.metadataCacheUri) return
async function getTransactions() {
try {
await getPoolTransactionData()
if (dataFetchInterval) return
const interval = setInterval(async () => {
await getPoolTransactionData()
}, REFETCH_INTERVAL)
setDataFetchInterval(interval)
} catch (error) {
LoggerInstance.error(
'Error fetching pool transactions: ',
error.message
)
}
}
getTransactions()
return () => {
clearInterval(dataFetchInterval)
}
}, [getPoolTransactionData, dataFetchInterval, appConfig.metadataCacheUri])
//
// Transform to final transactions
//
useEffect(() => {
if (!cancelToken()) return
async function transformData() {
try {
setIsLoading(true)
await getPoolTransactions(cancelToken())
} catch (error) {
LoggerInstance.error(
'Error fetching pool transactions: ',
error.message
)
}
}
transformData()
return () => {
cancelToken()
}
}, [cancelToken, getPoolTransactions])
return accountId ? (
<Table
columns={minimal ? columnsMinimal : columns}
data={transactions}
isLoading={isLoading}
noTableHead={minimal}
dense={minimal}
pagination={
minimal ? transactions?.length >= 4 : transactions?.length >= 9
}
paginationPerPage={minimal ? 5 : 10}
emptyMessage={chainIds.length === 0 ? 'No network selected' : null}
/>
) : (
<div>Please connect your Web3 wallet.</div>
)
}

View File

@ -6,9 +6,6 @@
font-weight: var(--font-weight-base); font-weight: var(--font-weight-base);
} }
.removeTvlPadding {
padding-left: 0 !important;
}
/* fiat currency symbol */ /* fiat currency symbol */
.conversion strong span { .conversion strong span {
font-weight: var(--font-weight-base); font-weight: var(--font-weight-base);

View File

@ -33,16 +33,13 @@ export default function PriceUnit({
return ( return (
<div className={`${styles.price} ${styles[size]} ${className}`}> <div className={`${styles.price} ${styles[size]} ${className}`}>
{type && type === 'free' ? ( {type === 'free' ? (
<div> Free </div> <div>Free</div>
) : ( ) : (
<> <>
<div> <div>
{Number.isNaN(Number(price)) ? '-' : formatPrice(price, locale)}{' '} {Number.isNaN(Number(price)) ? '-' : formatPrice(price, locale)}{' '}
<span className={styles.symbol}>{symbol}</span> <span className={styles.symbol}>{symbol}</span>
{type && type === 'dynamic' && (
<Badge label="pool" className={styles.badge} />
)}
</div> </div>
{conversion && <Conversion price={price} />} {conversion && <Conversion price={price} />}
</> </>

View File

@ -1,4 +0,0 @@
.empty {
color: var(--color-secondary);
font-weight: var(--font-weight-bold);
}

View File

@ -1,9 +1,5 @@
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import styles from './index.module.css'
import Loader from '../atoms/Loader'
import Tooltip from '../atoms/Tooltip'
import PriceUnit from './PriceUnit' import PriceUnit from './PriceUnit'
import { AccessDetails, OrderPriceAndFees } from 'src/@types/Price'
export default function Price({ export default function Price({
accessDetails, accessDetails,
@ -18,7 +14,10 @@ export default function Price({
conversion?: boolean conversion?: boolean
size?: 'small' | 'mini' | 'large' size?: 'small' | 'mini' | 'large'
}): ReactElement { }): ReactElement {
return accessDetails?.price || accessDetails?.type === 'free' ? ( const isSupported =
accessDetails?.type === 'fixed' || accessDetails?.type === 'free'
return isSupported ? (
<PriceUnit <PriceUnit
price={`${orderPriceAndFees?.price || accessDetails?.price}`} price={`${orderPriceAndFees?.price || accessDetails?.price}`}
symbol={accessDetails.baseToken?.symbol} symbol={accessDetails.baseToken?.symbol}
@ -27,18 +26,5 @@ export default function Price({
conversion={conversion} conversion={conversion}
type={accessDetails.type} type={accessDetails.type}
/> />
) : !accessDetails || accessDetails?.type === '' ? ( ) : null
<div className={styles.empty}>
No price set{' '}
<Tooltip content="No pricing mechanism has been set on this asset yet." />
</div>
) : (
// TODO: Hacky hack, put back some check for low liquidity
// ) : price.isConsumable !== 'true' ? (
// <div className={styles.empty}>
// Low liquidity{' '}
// <Tooltip content="This pool does not have enough liquidity for using this data set." />
// </div>
<Loader message="Retrieving price..." />
)
} }

View File

@ -1,54 +0,0 @@
.token {
font-weight: var(--font-weight-bold);
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(--border-color);
border-radius: 50%;
padding: 0.3rem;
vertical-align: middle;
margin-right: calc(var(--spacer) / 8);
margin-top: -0.2rem;
background: var(--background-body);
}
.icon svg {
width: var(--font-size-base);
height: var(--font-size-base);
}
.conversion {
composes: token;
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);
}
.conversion strong {
font-size: var(--font-size-base);
color: var(--font-color-heading);
line-height: 1;
}
/* Data Token Icon Style */
.icon:not([class*='OCEAN']) path {
fill: var(--brand-violet);
}
.noIcon {
opacity: 0;
}
.token.mini {
font-size: var(--font-size-mini);
}

View File

@ -1,35 +0,0 @@
import React, { ReactElement } from 'react'
import styles from './index.module.css'
import PriceUnit from '@shared/Price/PriceUnit'
import Logo from '@shared/atoms/Logo'
import Conversion from '@shared/Price/Conversion'
export default function Token({
symbol,
balance,
conversion,
noIcon,
size
}: {
symbol: string
balance: string
conversion?: boolean
noIcon?: boolean
size?: 'small' | 'mini'
}): ReactElement {
return (
<>
<div className={`${styles.token} ${size ? styles[size] : ''}`}>
<figure
className={`${styles.icon} ${symbol} ${noIcon ? styles.noIcon : ''}`}
>
<Logo noWordmark />
</figure>
<PriceUnit price={balance} symbol={symbol} size={size} />
</div>
{conversion && (
<Conversion price={balance} className={`${styles.conversion}`} />
)}
</>
)
}

View File

@ -1,49 +0,0 @@
import React, { ReactElement } from 'react'
import Button from '@shared/atoms/Button'
import Loader from '@shared/atoms/Loader'
import { useUserPreferences } from '@context/UserPreferences'
import Tooltip from '@shared/atoms/Tooltip'
import content from '../../../../content/price.json'
export function ButtonApprove({
amount,
tokenSymbol,
approveTokens,
isLoading
}: {
amount: string
tokenSymbol: string
approveTokens: (amount: string) => void
isLoading: boolean
}): ReactElement {
const { infiniteApproval } = useUserPreferences()
return isLoading ? (
<Loader message={`Approving ${tokenSymbol}...`} />
) : infiniteApproval ? (
<Button
style="primary"
size="small"
disabled={parseInt(amount) < 1}
onClick={() => approveTokens(`${2 ** 53 - 1}`)}
>
Approve {tokenSymbol}{' '}
<Tooltip
content={content.pool.tooltips.approveInfinite.replace(
'COIN',
tokenSymbol
)}
/>
</Button>
) : (
<Button style="primary" size="small" onClick={() => approveTokens(amount)}>
Approve {amount} {tokenSymbol}
<Tooltip
content={content.pool.tooltips.approveSpecific.replace(
'COIN',
tokenSymbol
)}
/>
</Button>
)
}

View File

@ -1,96 +0,0 @@
import React, { ReactElement, useCallback, useEffect, useState } from 'react'
import { useAsset } from '@context/Asset'
import { useWeb3 } from '@context/Web3'
import Decimal from 'decimal.js'
import { ButtonApprove } from './ButtonApprove'
import { allowance, approve, LoggerInstance } from '@oceanprotocol/lib'
export default function TokenApproval({
actionButton,
disabled,
amount,
tokenAddress,
tokenSymbol,
setSubmitting,
setIsTokenApproved
}: {
actionButton: JSX.Element
disabled: boolean
amount: string
tokenAddress: string
tokenSymbol: string
setSubmitting?: (isSubmitting: boolean) => void
setIsTokenApproved: (isApproved: boolean) => void
}): ReactElement {
const { asset, isAssetNetwork } = useAsset()
const [tokenApproved, setTokenApproved] = useState(false)
const [loading, setLoading] = useState(false)
const { web3, accountId } = useWeb3()
const spender = asset?.accessDetails?.addressOrId
const checkTokenApproval = useCallback(async () => {
if (!web3 || !tokenAddress || !spender || !isAssetNetwork || !amount) return
const allowanceValue = await allowance(
web3,
tokenAddress,
accountId,
spender
)
LoggerInstance.log(`[token approval] allowanceValue: ${allowanceValue}`)
if (!allowanceValue) return
new Decimal(amount).greaterThan(new Decimal('0')) &&
setTokenApproved(
new Decimal(allowanceValue).greaterThanOrEqualTo(new Decimal(amount))
)
setIsTokenApproved(
new Decimal(allowanceValue).greaterThanOrEqualTo(new Decimal(amount))
)
}, [web3, tokenAddress, spender, accountId, amount, isAssetNetwork])
useEffect(() => {
checkTokenApproval()
}, [checkTokenApproval])
async function approveTokens(amount: string) {
setLoading(true)
setSubmitting(true)
try {
const tx = await approve(web3, accountId, tokenAddress, spender, amount)
LoggerInstance.log(`[token approval] Approve tokens tx:`, tx)
} catch (error) {
LoggerInstance.error(
`[token approval] Approve tokens tx failed:`,
error.message
)
} finally {
await checkTokenApproval()
setLoading(false)
setSubmitting(false)
}
}
return (
<>
{tokenApproved ||
disabled ||
amount === '0' ||
amount === '' ||
!amount ||
typeof amount === 'undefined' ? (
actionButton
) : (
<ButtonApprove
amount={amount}
tokenSymbol={tokenSymbol}
approveTokens={approveTokens}
isLoading={loading}
/>
)}
</>
)
}

View File

@ -19,26 +19,11 @@ export default function App({
const { siteContent, appConfig } = useMarketMetadata() const { siteContent, appConfig } = useMarketMetadata()
const { accountId } = useWeb3() const { accountId } = useWeb3()
const { isInPurgatory, purgatoryData } = useAccountPurgatory(accountId) const { isInPurgatory, purgatoryData } = useAccountPurgatory(accountId)
function openInNewTab() {
window
.open(
'https://blog.oceanprotocol.com/how-to-publish-a-data-nft-f58ad2a622a9',
'_blank'
)
.focus()
}
return ( return (
<div className={styles.app}> <div className={styles.app}>
{siteContent?.announcement !== '' && ( {siteContent?.announcement !== '' && (
<AnnouncementBanner <AnnouncementBanner text={siteContent?.announcement} />
text={siteContent?.announcement}
action={{
name: 'Explore OceanONDA V4.',
style: 'link',
handleAction: openInNewTab
}}
/>
)} )}
<Header /> <Header />

View File

@ -5,7 +5,6 @@ import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection'
import AssetComputeList from '@shared/AssetList/AssetComputeList' import AssetComputeList from '@shared/AssetList/AssetComputeList'
import { useCancelToken } from '@hooks/useCancelToken' import { useCancelToken } from '@hooks/useCancelToken'
import { getServiceByName } from '@utils/ddo' import { getServiceByName } from '@utils/ddo'
import { AssetExtended } from 'src/@types/AssetExtended'
export default function AlgorithmDatasetsListForCompute({ export default function AlgorithmDatasetsListForCompute({
asset, asset,
@ -19,7 +18,7 @@ export default function AlgorithmDatasetsListForCompute({
useState<AssetSelectionAsset[]>() useState<AssetSelectionAsset[]>()
useEffect(() => { useEffect(() => {
if (!asset) return if (!asset || !asset?.accessDetails?.type) return
async function getDatasetsAllowedForCompute() { async function getDatasetsAllowedForCompute() {
const isCompute = Boolean(getServiceByName(asset, 'compute')) const isCompute = Boolean(getServiceByName(asset, 'compute'))

View File

@ -10,9 +10,7 @@ import { useAsset } from '@context/Asset'
import { useWeb3 } from '@context/Web3' import { useWeb3 } from '@context/Web3'
import content from '../../../../../content/pages/startComputeDataset.json' import content from '../../../../../content/pages/startComputeDataset.json'
import { Asset } from '@oceanprotocol/lib' import { Asset } from '@oceanprotocol/lib'
import { OrderPriceAndFees } from 'src/@types/Price'
import { getAccessDetails } from '@utils/accessDetailsAndPricing' import { getAccessDetails } from '@utils/accessDetailsAndPricing'
import { AssetExtended } from 'src/@types/AssetExtended'
import Decimal from 'decimal.js' import Decimal from 'decimal.js'
import { MAX_DECIMALS } from '@utils/constants' import { MAX_DECIMALS } from '@utils/constants'
import { useMarketMetadata } from '@context/MarketMetadata' import { useMarketMetadata } from '@context/MarketMetadata'
@ -28,7 +26,6 @@ export default function FormStartCompute({
hasPreviousOrder, hasPreviousOrder,
hasDatatoken, hasDatatoken,
dtBalance, dtBalance,
datasetLowPoolLiquidity,
assetType, assetType,
assetTimeout, assetTimeout,
hasPreviousOrderSelectedComputeAsset, hasPreviousOrderSelectedComputeAsset,
@ -36,7 +33,6 @@ export default function FormStartCompute({
oceanSymbol, oceanSymbol,
dtSymbolSelectedComputeAsset, dtSymbolSelectedComputeAsset,
dtBalanceSelectedComputeAsset, dtBalanceSelectedComputeAsset,
selectedComputeAssetLowPoolLiquidity,
selectedComputeAssetType, selectedComputeAssetType,
selectedComputeAssetTimeout, selectedComputeAssetTimeout,
stepText, stepText,
@ -56,7 +52,6 @@ export default function FormStartCompute({
hasPreviousOrder: boolean hasPreviousOrder: boolean
hasDatatoken: boolean hasDatatoken: boolean
dtBalance: string dtBalance: string
datasetLowPoolLiquidity: boolean
assetType: string assetType: string
assetTimeout: string assetTimeout: string
hasPreviousOrderSelectedComputeAsset?: boolean hasPreviousOrderSelectedComputeAsset?: boolean
@ -64,7 +59,6 @@ export default function FormStartCompute({
oceanSymbol?: string oceanSymbol?: string
dtSymbolSelectedComputeAsset?: string dtSymbolSelectedComputeAsset?: string
dtBalanceSelectedComputeAsset?: string dtBalanceSelectedComputeAsset?: string
selectedComputeAssetLowPoolLiquidity?: boolean
selectedComputeAssetType?: string selectedComputeAssetType?: string
selectedComputeAssetTimeout?: string selectedComputeAssetTimeout?: string
stepText: string stepText: string
@ -180,15 +174,17 @@ export default function FormStartCompute({
state="info" state="info"
text={siteContent.warning.ctd} text={siteContent.warning.ctd}
/> />
{content.form.data.map((field: FormFieldContent) => ( {content.form.data.map((field: FormFieldContent) => {
<Field return (
key={field.name} <Field
{...field} key={field.name}
options={algorithms} {...field}
component={Input} options={algorithms}
disabled={isLoading} component={Input}
/> disabled={isLoading || isComputeButtonDisabled}
))} />
)
})}
<PriceOutput <PriceOutput
hasPreviousOrder={hasPreviousOrder} hasPreviousOrder={hasPreviousOrder}
@ -221,7 +217,6 @@ export default function FormStartCompute({
hasDatatoken={hasDatatoken} hasDatatoken={hasDatatoken}
dtSymbol={asset?.datatokens[0]?.symbol} dtSymbol={asset?.datatokens[0]?.symbol}
dtBalance={dtBalance} dtBalance={dtBalance}
datasetLowPoolLiquidity={datasetLowPoolLiquidity}
assetTimeout={assetTimeout} assetTimeout={assetTimeout}
assetType={assetType} assetType={assetType}
hasPreviousOrderSelectedComputeAsset={ hasPreviousOrderSelectedComputeAsset={
@ -230,9 +225,6 @@ export default function FormStartCompute({
hasDatatokenSelectedComputeAsset={hasDatatokenSelectedComputeAsset} hasDatatokenSelectedComputeAsset={hasDatatokenSelectedComputeAsset}
dtSymbolSelectedComputeAsset={dtSymbolSelectedComputeAsset} dtSymbolSelectedComputeAsset={dtSymbolSelectedComputeAsset}
dtBalanceSelectedComputeAsset={dtBalanceSelectedComputeAsset} dtBalanceSelectedComputeAsset={dtBalanceSelectedComputeAsset}
selectedComputeAssetLowPoolLiquidity={
selectedComputeAssetLowPoolLiquidity
}
selectedComputeAssetType={selectedComputeAssetType} selectedComputeAssetType={selectedComputeAssetType}
stepText={stepText} stepText={stepText}
isLoading={isLoading} isLoading={isLoading}

View File

@ -1,5 +1,37 @@
.container {
margin-left: calc(-1 * var(--spacer) / 1.5);
margin-right: calc(-1 * var(--spacer) / 1.5);
padding: calc(var(--spacer) / 1.5) calc(var(--spacer) / 1.5)
calc(var(--spacer) / 2) calc(var(--spacer) / 1.5);
}
@media (min-width: 40rem) {
.container {
padding-left: var(--spacer);
padding-right: var(--spacer);
margin-left: calc(-1 * var(--spacer));
margin-right: calc(-1 * var(--spacer));
}
}
.section {
composes: container;
border-top: 1px solid var(--border-color);
position: relative;
}
.section.highlight {
background: var(--background-highlight);
}
.section:first-child {
padding-top: 0;
border-top: 0;
}
.actions { .actions {
composes: section from './Pool/Section/index.module.css'; composes: section;
margin-top: calc(var(--spacer) / 1.5); margin-top: calc(var(--spacer) / 1.5);
padding: calc(var(--spacer) / 1.5); padding: calc(var(--spacer) / 1.5);
background: var(--background-highlight); background: var(--background-highlight);
@ -11,7 +43,9 @@
} }
.title { .title {
composes: title from './Pool/Section/Title.module.css'; font-size: var(--font-size-base);
margin-bottom: calc(var(--spacer) / 3);
color: var(--color-secondary);
margin-bottom: 0; margin-bottom: 0;
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -1,9 +1,9 @@
import React, { ReactElement, ReactNode, useState } from 'react' import React, { ReactElement, ReactNode, useState } from 'react'
import Button from '@shared/atoms/Button' import Button from '@shared/atoms/Button'
import styles from './AssetActionHistoryTable.module.css' import styles from './History.module.css'
import Caret from '@images/caret.svg' import Caret from '@images/caret.svg'
export default function AssetActionHistoryTable({ export default function ComputeHistory({
title, title,
children children
}: { }: {

View File

@ -3,7 +3,6 @@ import { useAsset } from '@context/Asset'
import PriceUnit from '@shared/Price/PriceUnit' import PriceUnit from '@shared/Price/PriceUnit'
import Tooltip from '@shared/atoms/Tooltip' import Tooltip from '@shared/atoms/Tooltip'
import styles from './PriceOutput.module.css' import styles from './PriceOutput.module.css'
import { AccessDetails } from 'src/@types/Price'
import { MAX_DECIMALS } from '@utils/constants' import { MAX_DECIMALS } from '@utils/constants'
import Decimal from 'decimal.js' import Decimal from 'decimal.js'
@ -17,8 +16,8 @@ interface PriceOutputProps {
hasDatatokenSelectedComputeAsset: boolean hasDatatokenSelectedComputeAsset: boolean
algorithmConsumeDetails: AccessDetails algorithmConsumeDetails: AccessDetails
selectedComputeAssetTimeout: string selectedComputeAssetTimeout: string
datasetOrderPrice?: number datasetOrderPrice?: string
algoOrderPrice?: number algoOrderPrice?: string
providerFeeAmount?: string providerFeeAmount?: string
validUntil?: string validUntil?: string
} }

View File

@ -11,6 +11,11 @@
calc(var(--spacer) * 1.5); calc(var(--spacer) * 1.5);
} }
.warning {
padding-bottom: 0;
border-bottom: 0;
}
.feedback { .feedback {
width: 100%; width: 100%;
margin-top: calc(var(--spacer) / 2); margin-top: calc(var(--spacer) / 2);

View File

@ -33,19 +33,14 @@ import {
} from '@utils/compute' } from '@utils/compute'
import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection' import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection'
import AlgorithmDatasetsListForCompute from './AlgorithmDatasetsListForCompute' import AlgorithmDatasetsListForCompute from './AlgorithmDatasetsListForCompute'
import AssetActionHistoryTable from '../AssetActionHistoryTable' import ComputeHistory from './History'
import ComputeJobs from '../../../Profile/History/ComputeJobs' import ComputeJobs from '../../../Profile/History/ComputeJobs'
import { useCancelToken } from '@hooks/useCancelToken' import { useCancelToken } from '@hooks/useCancelToken'
import { Decimal } from 'decimal.js' import { Decimal } from 'decimal.js'
import { useAbortController } from '@hooks/useAbortController' import { useAbortController } from '@hooks/useAbortController'
import { getOrderPriceAndFees } from '@utils/accessDetailsAndPricing' import { getOrderPriceAndFees } from '@utils/accessDetailsAndPricing'
import { OrderPriceAndFees } from 'src/@types/Price'
import { handleComputeOrder } from '@utils/order' import { handleComputeOrder } from '@utils/order'
import { AssetExtended } from 'src/@types/AssetExtended'
import { getComputeFeedback } from '@utils/feedback' import { getComputeFeedback } from '@utils/feedback'
import { usePool } from '@context/Pool'
import { useMarketMetadata } from '@context/MarketMetadata'
import { getPoolData } from '@context/Pool/_utils'
import { getDummyWeb3 } from '@utils/web3' import { getDummyWeb3 } from '@utils/web3'
import { initializeProviderForCompute } from '@utils/provider' import { initializeProviderForCompute } from '@utils/provider'
@ -63,8 +58,6 @@ export default function Compute({
consumableFeedback?: string consumableFeedback?: string
}): ReactElement { }): ReactElement {
const { accountId, web3 } = useWeb3() const { accountId, web3 } = useWeb3()
const { getOpcFeeForToken } = useMarketMetadata()
const { poolData } = usePool()
const newAbortController = useAbortController() const newAbortController = useAbortController()
const newCancelToken = useCancelToken() const newCancelToken = useCancelToken()
@ -108,6 +101,8 @@ export default function Compute({
!hasAlgoAssetDatatoken && !hasAlgoAssetDatatoken &&
!isConsumableaAlgorithmPrice) !isConsumableaAlgorithmPrice)
const isUnsupportedPricing = asset?.accessDetails?.type === 'NOT_SUPPORTED'
async function checkAssetDTBalance(asset: DDO): Promise<boolean> { async function checkAssetDTBalance(asset: DDO): Promise<boolean> {
if (!asset?.services[0].datatokenAddress) return if (!asset?.services[0].datatokenAddress) return
const web3 = await getDummyWeb3(asset?.chainId) const web3 = await getDummyWeb3(asset?.chainId)
@ -168,26 +163,9 @@ export default function Compute({
asset.metadata.type asset.metadata.type
)[0] )[0]
) )
const poolParams =
asset?.accessDetails?.type === 'dynamic'
? {
tokenInLiquidity: poolData?.baseTokenLiquidity,
tokenOutLiquidity: poolData?.datatokenLiquidity,
tokenOutAmount: '1',
opcFee: getOpcFeeForToken(
asset?.accessDetails?.baseToken.address,
asset?.chainId
),
lpSwapFee: poolData?.liquidityProviderSwapFee,
publishMarketSwapFee:
asset?.accessDetails?.publisherMarketOrderFee,
consumeMarketSwapFee: '0'
}
: null
const datasetPriceAndFees = await getOrderPriceAndFees( const datasetPriceAndFees = await getOrderPriceAndFees(
asset, asset,
ZERO_ADDRESS, ZERO_ADDRESS,
poolParams,
initializedProvider?.datasets?.[0]?.providerFee initializedProvider?.datasets?.[0]?.providerFee
) )
if (!datasetPriceAndFees) if (!datasetPriceAndFees)
@ -208,32 +186,9 @@ export default function Compute({
selectedAlgorithmAsset?.metadata?.type selectedAlgorithmAsset?.metadata?.type
)[0] )[0]
) )
let algoPoolParams = null
if (selectedAlgorithmAsset?.accessDetails?.type === 'dynamic') {
const response = await getPoolData(
selectedAlgorithmAsset.chainId,
selectedAlgorithmAsset.accessDetails?.addressOrId,
selectedAlgorithmAsset?.nft.owner,
accountId || ''
)
algoPoolParams = {
tokenInLiquidity: response?.poolData?.baseTokenLiquidity,
tokenOutLiquidity: response?.poolData?.datatokenLiquidity,
tokenOutAmount: '1',
opcFee: getOpcFeeForToken(
selectedAlgorithmAsset?.accessDetails?.baseToken.address,
selectedAlgorithmAsset?.chainId
),
lpSwapFee: response?.poolData?.liquidityProviderSwapFee,
publishMarketSwapFee:
selectedAlgorithmAsset?.accessDetails?.publisherMarketOrderFee,
consumeMarketSwapFee: '0'
}
}
const algorithmOrderPriceAndFees = await getOrderPriceAndFees( const algorithmOrderPriceAndFees = await getOrderPriceAndFees(
selectedAlgorithmAsset, selectedAlgorithmAsset,
ZERO_ADDRESS, ZERO_ADDRESS,
algoPoolParams,
initializedProvider.algorithm.providerFee initializedProvider.algorithm.providerFee
) )
if (!algorithmOrderPriceAndFees) if (!algorithmOrderPriceAndFees)
@ -248,11 +203,11 @@ export default function Compute({
} }
useEffect(() => { useEffect(() => {
if (!asset?.accessDetails || !accountId) return if (!asset?.accessDetails || !accountId || isUnsupportedPricing) return
setIsConsumablePrice(asset?.accessDetails?.isPurchasable) setIsConsumablePrice(asset?.accessDetails?.isPurchasable)
setValidOrderTx(asset?.accessDetails?.validOrderTx) setValidOrderTx(asset?.accessDetails?.validOrderTx)
}, [asset?.accessDetails, accountId]) }, [asset?.accessDetails, accountId, isUnsupportedPricing])
useEffect(() => { useEffect(() => {
if (!selectedAlgorithmAsset?.accessDetails || !accountId) return if (!selectedAlgorithmAsset?.accessDetails || !accountId) return
@ -276,7 +231,8 @@ export default function Compute({
}, [selectedAlgorithmAsset, accountId]) }, [selectedAlgorithmAsset, accountId])
useEffect(() => { useEffect(() => {
if (!asset) return if (!asset?.accessDetails || isUnsupportedPricing) return
getAlgorithmsForAsset(asset, newCancelToken()).then((algorithmsAssets) => { getAlgorithmsForAsset(asset, newCancelToken()).then((algorithmsAssets) => {
setDdoAlgorithmList(algorithmsAssets) setDdoAlgorithmList(algorithmsAssets)
getAlgorithmAssetSelectionList(asset, algorithmsAssets).then( getAlgorithmAssetSelectionList(asset, algorithmsAssets).then(
@ -285,7 +241,7 @@ export default function Compute({
} }
) )
}) })
}, [asset]) }, [asset, isUnsupportedPricing])
// Output errors in toast UI // Output errors in toast UI
useEffect(() => { useEffect(() => {
@ -323,13 +279,7 @@ export default function Compute({
selectedAlgorithmAsset.accessDetails.baseToken?.symbol, selectedAlgorithmAsset.accessDetails.baseToken?.symbol,
selectedAlgorithmAsset.accessDetails.datatoken?.symbol, selectedAlgorithmAsset.accessDetails.datatoken?.symbol,
selectedAlgorithmAsset.metadata.type selectedAlgorithmAsset.metadata.type
)[ )[selectedAlgorithmAsset.accessDetails?.type === 'fixed' ? 2 : 3]
selectedAlgorithmAsset.accessDetails?.type === 'fixed'
? 2
: selectedAlgorithmAsset.accessDetails?.type === 'dynamic'
? 1
: 3
]
) )
const algorithmOrderTx = await handleComputeOrder( const algorithmOrderTx = await handleComputeOrder(
@ -348,13 +298,7 @@ export default function Compute({
asset.accessDetails.baseToken?.symbol, asset.accessDetails.baseToken?.symbol,
asset.accessDetails.datatoken?.symbol, asset.accessDetails.datatoken?.symbol,
asset.metadata.type asset.metadata.type
)[ )[asset.accessDetails?.type === 'fixed' ? 2 : 3]
asset.accessDetails?.type === 'fixed'
? 2
: asset.accessDetails?.type === 'dynamic'
? 1
: 3
]
) )
const datasetOrderTx = await handleComputeOrder( const datasetOrderTx = await handleComputeOrder(
web3, web3,
@ -406,17 +350,37 @@ export default function Compute({
return ( return (
<> <>
<div className={styles.info}> <div
className={`${styles.info} ${
isUnsupportedPricing ? styles.warning : null
}`}
>
<FileIcon file={file} isLoading={fileIsLoading} small /> <FileIcon file={file} isLoading={fileIsLoading} small />
<Price accessDetails={asset?.accessDetails} conversion /> {isUnsupportedPricing ? (
</div>
{asset.metadata.type === 'algorithm' ? (
<>
<Alert <Alert
text="This algorithm has been set to private by the publisher and can't be downloaded. You can run it against any allowed data sets though!" text={`No pricing schema available for this asset.`}
state="info" state="info"
/> />
) : (
<Price
accessDetails={asset?.accessDetails}
orderPriceAndFees={datasetOrderPriceAndFees}
conversion
size="large"
/>
)}
</div>
{isUnsupportedPricing ? null : asset.metadata.type === 'algorithm' ? (
<>
{asset.services[0].type === 'compute' && (
<Alert
text={
"This algorithm has been set to private by the publisher and can't be downloaded. You can run it against any allowed data sets though!"
}
state="info"
/>
)}
<AlgorithmDatasetsListForCompute <AlgorithmDatasetsListForCompute
algorithmDid={asset.id} algorithmDid={asset.id}
asset={asset} asset={asset}
@ -442,7 +406,6 @@ export default function Compute({
hasPreviousOrder={validOrderTx !== undefined} hasPreviousOrder={validOrderTx !== undefined}
hasDatatoken={hasDatatoken} hasDatatoken={hasDatatoken}
dtBalance={dtBalance} dtBalance={dtBalance}
datasetLowPoolLiquidity={!isConsumablePrice}
assetType={asset?.metadata.type} assetType={asset?.metadata.type}
assetTimeout={secondsToString(asset?.services[0].timeout)} assetTimeout={secondsToString(asset?.services[0].timeout)}
hasPreviousOrderSelectedComputeAsset={ hasPreviousOrderSelectedComputeAsset={
@ -480,13 +443,13 @@ export default function Compute({
)} )}
</footer> </footer>
{accountId && asset?.accessDetails?.datatoken && ( {accountId && asset?.accessDetails?.datatoken && (
<AssetActionHistoryTable title="Your Compute Jobs"> <ComputeHistory title="Your Compute Jobs">
<ComputeJobs <ComputeJobs
minimal minimal
assetChainIds={[asset?.chainId]} assetChainIds={[asset?.chainId]}
refetchJobs={refetchJobs} refetchJobs={refetchJobs}
/> />
</AssetActionHistoryTable> </ComputeHistory>
)} )}
</> </>
) )

View File

@ -1,11 +1,14 @@
.consume { .consume {
display: flex; width: auto;
flex-wrap: wrap; margin-bottom: calc(var(--spacer) / 2);
margin-top: -1rem;
margin-left: -2rem;
} }
.info { .info {
display: flex; display: flex;
width: auto; width: auto;
padding: 0 calc(var(--spacer) / 2) 0 calc(var(--spacer) * 1.5);
} }
.filewrapper { .filewrapper {

View File

@ -9,16 +9,13 @@ import AlgorithmDatasetsListForCompute from './Compute/AlgorithmDatasetsListForC
import styles from './Download.module.css' import styles from './Download.module.css'
import { FileInfo, LoggerInstance, ZERO_ADDRESS } from '@oceanprotocol/lib' import { FileInfo, LoggerInstance, ZERO_ADDRESS } from '@oceanprotocol/lib'
import { order } from '@utils/order' import { order } from '@utils/order'
import { AssetExtended } from 'src/@types/AssetExtended'
import { buyDtFromPool } from '@utils/pool'
import { downloadFile } from '@utils/provider' import { downloadFile } from '@utils/provider'
import { getOrderFeedback } from '@utils/feedback' import { getOrderFeedback } from '@utils/feedback'
import { getOrderPriceAndFees } from '@utils/accessDetailsAndPricing' import { getOrderPriceAndFees } from '@utils/accessDetailsAndPricing'
import { OrderPriceAndFees } from 'src/@types/Price'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import { useIsMounted } from '@hooks/useIsMounted' import { useIsMounted } from '@hooks/useIsMounted'
import { usePool } from '@context/Pool'
import { useMarketMetadata } from '@context/MarketMetadata' import { useMarketMetadata } from '@context/MarketMetadata'
import Alert from '@shared/atoms/Alert'
export default function Download({ export default function Download({
asset, asset,
@ -38,7 +35,6 @@ export default function Download({
const { accountId, web3 } = useWeb3() const { accountId, web3 } = useWeb3()
const { getOpcFeeForToken } = useMarketMetadata() const { getOpcFeeForToken } = useMarketMetadata()
const { isInPurgatory, isAssetNetwork } = useAsset() const { isInPurgatory, isAssetNetwork } = useAsset()
const { poolData } = usePool()
const isMounted = useIsMounted() const isMounted = useIsMounted()
const [isDisabled, setIsDisabled] = useState(true) const [isDisabled, setIsDisabled] = useState(true)
@ -50,63 +46,50 @@ export default function Download({
const [orderPriceAndFees, setOrderPriceAndFees] = const [orderPriceAndFees, setOrderPriceAndFees] =
useState<OrderPriceAndFees>() useState<OrderPriceAndFees>()
useEffect(() => { const isUnsupportedPricing = asset?.accessDetails?.type === 'NOT_SUPPORTED'
if (!asset?.accessDetails) return
asset?.accessDetails?.isOwned && setIsOwned(asset?.accessDetails?.isOwned) useEffect(() => {
asset?.accessDetails?.validOrderTx && if (!asset?.accessDetails || isUnsupportedPricing) return
asset.accessDetails.isOwned && setIsOwned(asset?.accessDetails?.isOwned)
asset.accessDetails.validOrderTx &&
setValidOrderTx(asset?.accessDetails?.validOrderTx) setValidOrderTx(asset?.accessDetails?.validOrderTx)
// get full price and fees // get full price and fees
async function init() { async function init() {
if ( if (
asset?.accessDetails?.addressOrId === ZERO_ADDRESS || asset.accessDetails.addressOrId === ZERO_ADDRESS ||
asset?.accessDetails?.type === 'free' || asset.accessDetails.type === 'free' ||
(!poolData && asset?.accessDetails?.type === 'dynamic') ||
isLoading isLoading
) )
return return
!orderPriceAndFees && setIsLoading(true) const _orderPriceAndFees = await getOrderPriceAndFees(asset, ZERO_ADDRESS)
setStatusText('Refreshing price')
// this is needed just for pool
const paramsForPool: CalcInGivenOutParams = {
tokenInLiquidity: poolData?.baseTokenLiquidity,
tokenOutLiquidity: poolData?.datatokenLiquidity,
tokenOutAmount: '1',
opcFee: getOpcFeeForToken(
asset?.accessDetails?.baseToken.address,
asset?.chainId
),
lpSwapFee: poolData?.liquidityProviderSwapFee,
publishMarketSwapFee: asset?.accessDetails?.publisherMarketOrderFee,
consumeMarketSwapFee: '0'
}
const _orderPriceAndFees = await getOrderPriceAndFees(
asset,
ZERO_ADDRESS,
paramsForPool
)
setOrderPriceAndFees(_orderPriceAndFees) setOrderPriceAndFees(_orderPriceAndFees)
!orderPriceAndFees && setIsLoading(false)
} }
init() init()
/** /**
* we listen to the assets' changes to get the most updated price * we listen to the assets' changes to get the most updated price
* based on the asset and the poolData's information. * based on the asset and the poolData's information.
* Not adding isLoading and getOpcFeeForToken because we set these here. It is a compromise * Not adding isLoading and getOpcFeeForToken because we set these here. It is a compromise
*/ */
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [asset, accountId, poolData, getOpcFeeForToken]) }, [asset, accountId, getOpcFeeForToken, isUnsupportedPricing])
useEffect(() => { useEffect(() => {
setHasDatatoken(Number(dtBalance) >= 1) setHasDatatoken(Number(dtBalance) >= 1)
}, [dtBalance]) }, [dtBalance])
useEffect(() => { useEffect(() => {
if (!isMounted || !accountId || !asset?.accessDetails) return if (
!isMounted ||
!accountId ||
!asset?.accessDetails ||
isUnsupportedPricing
)
return
/** /**
* disabled in these cases: * disabled in these cases:
@ -128,7 +111,8 @@ export default function Download({
isAssetNetwork, isAssetNetwork,
hasDatatoken, hasDatatoken,
accountId, accountId,
isOwned isOwned,
isUnsupportedPricing
]) ])
async function handleOrderOrDownload() { async function handleOrderOrDownload() {
@ -138,30 +122,18 @@ export default function Download({
if (isOwned) { if (isOwned) {
setStatusText( setStatusText(
getOrderFeedback( getOrderFeedback(
asset.accessDetails?.baseToken?.symbol, asset.accessDetails.baseToken?.symbol,
asset.accessDetails?.datatoken?.symbol asset.accessDetails.datatoken?.symbol
)[3] )[3]
) )
await downloadFile(web3, asset, accountId, validOrderTx) await downloadFile(web3, asset, accountId, validOrderTx)
} else { } else {
if (!hasDatatoken && asset.accessDetails.type === 'dynamic') {
setStatusText(
getOrderFeedback(
asset.accessDetails.baseToken?.symbol,
asset.accessDetails.datatoken?.symbol
)[0]
)
const tx = await buyDtFromPool(asset.accessDetails, accountId, web3)
if (!tx) {
throw new Error()
}
}
setStatusText( setStatusText(
getOrderFeedback( getOrderFeedback(
asset.accessDetails.baseToken?.symbol, asset.accessDetails.baseToken?.symbol,
asset.accessDetails.datatoken?.symbol asset.accessDetails.datatoken?.symbol
)[asset.accessDetails?.type === 'fixed' ? 2 : 1] )[asset.accessDetails.type === 'fixed' ? 2 : 1]
) )
const orderTx = await order(web3, asset, orderPriceAndFees, accountId) const orderTx = await order(web3, asset, orderPriceAndFees, accountId)
if (!orderTx) { if (!orderTx) {
@ -188,7 +160,6 @@ export default function Download({
hasDatatoken={hasDatatoken} hasDatatoken={hasDatatoken}
dtSymbol={asset?.datatokens[0]?.symbol} dtSymbol={asset?.datatokens[0]?.symbol}
dtBalance={dtBalance} dtBalance={dtBalance}
datasetLowPoolLiquidity={!asset.accessDetails?.isPurchasable}
onClick={handleOrderOrDownload} onClick={handleOrderOrDownload}
assetTimeout={secondsToString(asset.services[0].timeout)} assetTimeout={secondsToString(asset.services[0].timeout)}
assetType={asset?.metadata?.type} assetType={asset?.metadata?.type}
@ -201,21 +172,37 @@ export default function Download({
/> />
) )
const AssetAction = ({ asset }: { asset: AssetExtended }) => {
return (
<div>
{isUnsupportedPricing ? (
<Alert
className={styles.fieldWarning}
state="info"
text={`No pricing schema available for this asset.`}
/>
) : (
<>
<Price
accessDetails={asset.accessDetails}
orderPriceAndFees={orderPriceAndFees}
conversion
size="large"
/>
{!isInPurgatory && <PurchaseButton />}
</>
)}
</div>
)
}
return ( return (
<aside className={styles.consume}> <aside className={styles.consume}>
<div className={styles.info}> <div className={styles.info}>
<div className={styles.filewrapper}> <div className={styles.filewrapper}>
<FileIcon file={file} isLoading={fileIsLoading} /> <FileIcon file={file} isLoading={fileIsLoading} small />
</div>
<div className={styles.pricewrapper}>
<Price
accessDetails={asset.accessDetails}
orderPriceAndFees={orderPriceAndFees}
conversion
size="large"
/>
{!isInPurgatory && <PurchaseButton />}
</div> </div>
<AssetAction asset={asset} />
</div> </div>
{asset?.metadata?.type === 'algorithm' && ( {asset?.metadata?.type === 'algorithm' && (

View File

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

View File

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

View File

@ -1,25 +0,0 @@
.actions {
margin-left: -2rem;
margin-right: -2rem;
padding-left: var(--spacer);
padding-right: var(--spacer);
padding-top: calc(var(--spacer) / 1.5);
border-top: 1px solid var(--border-color);
text-align: center;
display: flex;
justify-content: center;
}
.success {
margin-top: calc(var(--spacer) / 2);
margin-bottom: calc(var(--spacer) / 2);
}
.actions button {
margin-left: calc(var(--spacer) / 4);
margin-right: calc(var(--spacer) / 4);
}
.actions button svg {
fill: currentColor;
}

View File

@ -1,102 +0,0 @@
import React, { ReactElement, useState } from 'react'
import Loader from '@shared/atoms/Loader'
import Button from '@shared/atoms/Button'
import styles from './index.module.css'
import ExplorerLink from '@shared/ExplorerLink'
import SuccessConfetti from '@shared/SuccessConfetti'
import { useWeb3 } from '@context/Web3'
import TokenApproval from '@shared/TokenApproval'
import Decimal from 'decimal.js'
export default function Actions({
isLoading,
loaderMessage,
successMessage,
slippage,
txId,
actionName,
amount,
action,
isDisabled,
tokenAddress,
tokenSymbol,
setSubmitting
}: {
isLoading: boolean
loaderMessage: string
successMessage: string
slippage?: string
txId: string
actionName: string
amount?: string
action: () => void
isDisabled?: boolean
tokenAddress: string
tokenSymbol: string
setSubmitting?: (isSubmitting: boolean) => void
}): ReactElement {
const { networkId } = useWeb3()
const [isTokenApproved, setIsTokenApproved] = useState(false)
const actionButton = (
<Button
style="primary"
size="small"
onClick={() => action()}
disabled={isDisabled}
>
{actionName}
</Button>
)
const applySlippage = (amount: string) => {
if (!amount) return '0'
const newAmount = new Decimal(amount)
.mul(
new Decimal(1)
.plus(new Decimal(slippage).div(new Decimal(100)))
.toString()
)
.toString()
return newAmount
}
return (
<>
<div className={styles.actions}>
{isLoading ? (
<Loader
message={
isTokenApproved || actionName === 'Remove'
? loaderMessage
: `Approving ${tokenSymbol}...`
}
/>
) : actionName === 'Supply' || actionName === 'Swap' ? (
<TokenApproval
actionButton={actionButton}
amount={slippage ? applySlippage(amount) : amount}
setIsTokenApproved={setIsTokenApproved}
tokenAddress={tokenAddress}
tokenSymbol={tokenSymbol}
disabled={isDisabled}
setSubmitting={setSubmitting}
/>
) : (
actionButton
)}
</div>
{txId && (
<SuccessConfetti
className={styles.success}
success={successMessage}
action={
<ExplorerLink networkId={networkId} path={`/tx/${txId}`}>
View transaction
</ExplorerLink>
}
/>
)}
</>
)
}

View File

@ -1,27 +0,0 @@
.type {
position: relative;
z-index: 1;
width: 100%;
text-align: center;
padding: 5px var(--spacer);
border-top: 1px solid var(--border-color);
border-bottom: 1px solid var(--border-color);
}
.button,
.button:hover,
.button:focus,
.button:active {
display: inline-block;
color: var(--color-secondary);
font-size: var(--font-size-mini);
border: 1px solid transparent;
border-radius: var(--border-radius);
padding: calc(var(--spacer) / 16) calc(var(--spacer) / 4) !important;
transform: none;
}
.button.active {
color: var(--font-color-text);
border-color: var(--border-color);
}

View File

@ -1,40 +0,0 @@
import Button from '@shared/atoms/Button'
import React, {
ChangeEvent,
Dispatch,
ReactElement,
SetStateAction
} from 'react'
import styles from './Nav.module.css'
import { graphTypes, GraphType } from './_constants'
export default function Nav({
graphType,
setGraphType
}: {
graphType: GraphType
setGraphType: Dispatch<SetStateAction<GraphType>>
}): ReactElement {
function handleGraphTypeSwitch(e: ChangeEvent<HTMLButtonElement>) {
e.preventDefault()
setGraphType(e.currentTarget.textContent.toLowerCase() as GraphType)
}
return (
<nav className={styles.type}>
{graphTypes.map((type: GraphType) => (
<Button
key={type}
style="text"
size="small"
onClick={handleGraphTypeSwitch}
className={`${styles.button} ${
graphType === type.toLowerCase() ? styles.active : null
}`}
>
{type}
</Button>
))}
</nav>
)
}

View File

@ -1,67 +0,0 @@
import {
Chart as ChartJS,
LinearScale,
CategoryScale,
PointElement,
Tooltip,
BarElement,
LineElement,
LineController,
BarController,
ChartDataset,
TooltipOptions,
defaults
} from 'chart.js'
export declare type GraphType = 'tvl' | 'price' | 'volume'
export const graphTypes = ['TVL', 'Price', 'Volume']
// Chart.js global defaults
ChartJS.register(
LineElement,
BarElement,
PointElement,
LinearScale,
CategoryScale,
Tooltip,
LineController,
BarController
)
defaults.font.family = `'Sharp Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif`
defaults.animation = { easing: 'easeInOutQuart', duration: 1000 }
export const lineStyle: Partial<ChartDataset> = {
fill: false,
borderWidth: 2,
pointBorderWidth: 1,
pointRadius: 2,
pointHoverRadius: 4,
pointHoverBorderWidth: 0,
pointHitRadius: 2,
pointHoverBackgroundColor: '#ff4092'
}
export const tooltipOptions: Partial<TooltipOptions> = {
intersect: false,
displayColors: false,
padding: 10,
cornerRadius: 3,
borderWidth: 1,
caretSize: 7,
bodyFont: {
size: 13,
weight: 'bold',
lineHeight: 1,
style: 'normal',
family: defaults.font.family
},
titleFont: {
size: 10,
weight: 'normal',
lineHeight: 1,
style: 'normal',
family: defaults.font.family
}
}

View File

@ -1,38 +0,0 @@
import { formatPrice } from '@shared/Price/PriceUnit'
import { ChartOptions, TooltipItem } from 'chart.js'
import { tooltipOptions } from './_constants'
export function getOptions(
locale: string,
isDarkMode: boolean,
symbol: string
): ChartOptions {
return {
layout: {
padding: {
left: 0,
right: 0,
top: 0,
bottom: 20
}
},
plugins: {
tooltip: {
...tooltipOptions,
backgroundColor: isDarkMode ? `#141414` : `#fff`,
titleColor: isDarkMode ? `#e2e2e2` : `#303030`,
bodyColor: isDarkMode ? `#fff` : `#141414`,
borderColor: isDarkMode ? `#41474e` : `#e2e2e2`,
callbacks: {
label: (tooltipItem: TooltipItem<any>) =>
`${tooltipItem.formattedValue} ${symbol}`
}
}
},
hover: { intersect: false },
scales: {
y: { display: false, beginAtZero: true },
x: { display: false, offset: true }
}
}
}

View File

@ -1,18 +0,0 @@
.graphWrap {
composes: container from '../Section/index.module.css';
padding: 0;
grid-column: full;
min-height: 120px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
margin-bottom: calc(var(--spacer) / 2);
position: relative;
}
.graphWrap canvas {
position: relative;
z-index: 0;
}

View File

@ -1,114 +0,0 @@
import React, { ReactElement, useEffect, useState } from 'react'
import { ChartData, ChartOptions } from 'chart.js'
import { Bar, Line } from 'react-chartjs-2'
import Loader from '@shared/atoms/Loader'
import { useUserPreferences } from '@context/UserPreferences'
import useDarkMode from 'use-dark-mode'
import { darkModeConfig } from '../../../../../../app.config'
import { LoggerInstance } from '@oceanprotocol/lib'
import styles from './index.module.css'
import Decimal from 'decimal.js'
import { lineStyle, GraphType } from './_constants'
import Nav from './Nav'
import { getOptions } from './_utils'
import { usePrices } from '@context/Prices'
import { MAX_DECIMALS } from '@utils/constants'
import { usePool } from '@context/Pool'
export default function Graph(): ReactElement {
const { locale, currency } = useUserPreferences()
const { prices } = usePrices()
const { poolSnapshots } = usePool()
const darkMode = useDarkMode(false, darkModeConfig)
const [options, setOptions] = useState<ChartOptions<any>>()
const [graphType, setGraphType] = useState<GraphType>('tvl')
const [graphData, setGraphData] = useState<ChartData<any>>()
//
// 0 Get Graph options
//
useEffect(() => {
if (!poolSnapshots) return
LoggerInstance.log('[pool graph] Fired getOptions().')
const symbol =
graphType === 'tvl' ? currency : poolSnapshots[0]?.baseToken?.symbol
const options = getOptions(locale, darkMode.value, symbol)
setOptions(options)
}, [locale, darkMode.value, graphType, currency, poolSnapshots])
//
// 1 Data manipulation
//
useEffect(() => {
if (!poolSnapshots) return
const timestamps = poolSnapshots.map((item) => {
const date = new Date(item.date * 1000)
return `${date.toLocaleDateString(locale)}`
})
const tvlHistory = poolSnapshots.map((item) => {
const conversionSpotPrice = prices[currency.toLowerCase()]
const tvl = new Decimal(item.baseTokenLiquidity)
.mul(conversionSpotPrice) // convert to user currency
.toString()
return tvl
})
const priceHistory = poolSnapshots.map((item) => item.spotPrice)
const volumeHistory = poolSnapshots.map((item) => {
const volume = new Decimal(item.swapVolume)
.toDecimalPlaces(MAX_DECIMALS)
.toString()
return volume
})
let data
switch (graphType) {
case 'price':
data = priceHistory
break
case 'volume':
data = volumeHistory
break
default:
data = tvlHistory
break
}
const newGraphData = {
labels: timestamps,
datasets: [
{
...lineStyle,
data,
borderColor: `#8b98a9`,
backgroundColor: darkMode.value ? '#201f1f' : '#f7f7f7'
}
]
}
setGraphData(newGraphData)
LoggerInstance.log('[pool graph] New graph data created:', newGraphData)
}, [poolSnapshots, graphType, currency, prices, locale, darkMode.value])
return (
<div className={styles.graphWrap}>
{!graphData ? (
<Loader />
) : (
<>
{graphType === 'volume' ? (
<Bar width={416} height={120} data={graphData} options={options} />
) : (
<Line width={416} height={120} data={graphData} options={options} />
)}
<Nav graphType={graphType} setGraphType={setGraphType} />
</>
)}
</div>
)
}

View File

@ -1,38 +0,0 @@
import { PoolInfo, PoolInfoUser } from '@context/Pool/_types'
import { calcMaxExactOut, Pool } from '@oceanprotocol/lib'
import Decimal from 'decimal.js'
import { PoolData_poolData as PoolData } from 'src/@types/subgraph/PoolData'
export async function getMax(
poolInstance: Pool,
poolInfo: PoolInfo,
poolInfoUser: PoolInfoUser,
poolData: PoolData
) {
const maxBaseTokensRemovable = calcMaxExactOut(poolData.baseTokenLiquidity)
const maxPoolSharesRemovable = await poolInstance.calcPoolInGivenSingleOut(
poolData.id,
poolInfo.baseTokenAddress,
maxBaseTokensRemovable.toString()
)
const userPoolShares = poolInfoUser.poolShares
const maxPoolSharesRemovableDecimal = new Decimal(maxPoolSharesRemovable)
const userPoolSharesDecimal = new Decimal(userPoolShares)
const maxPoolSharesRemovableByUser =
maxPoolSharesRemovableDecimal.greaterThan(userPoolSharesDecimal)
? userPoolSharesDecimal
: maxPoolSharesRemovableDecimal
// small hack because if the values are equal the decimal.js result is 99.(9)
const maxPercent = maxPoolSharesRemovableByUser.equals(userPoolSharesDecimal)
? new Decimal(100)
: new Decimal(100)
.mul(maxPoolSharesRemovableByUser)
.div(userPoolSharesDecimal)
return maxPercent.toDecimalPlaces(0, Decimal.ROUND_DOWN).toString()
}

View File

@ -1,79 +0,0 @@
.removeInput {
background: var(--background-highlight);
padding: var(--spacer) calc(var(--spacer) * 2.5) calc(var(--spacer) * 1.2)
calc(var(--spacer) * 2.5);
border-bottom: 1px solid var(--border-color);
margin-top: -2rem;
margin-left: -2rem;
margin-right: -2rem;
position: relative;
padding-left: calc(var(--spacer) * 2);
padding-right: calc(var(--spacer) * 2);
padding-bottom: calc(var(--spacer) / 2);
margin-bottom: 0;
}
.range {
margin-top: calc(var(--spacer) / 2);
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;
}
.slider {
position: relative;
z-index: 1;
}
.maximum {
position: absolute;
right: 0;
bottom: 2.5rem;
font-size: var(--font-size-mini);
min-width: 5rem;
text-align: center;
}
.maximum:hover {
transform: none;
}
.toggle {
margin-top: calc(var(--spacer) / 2);
margin-bottom: 0;
font-size: var(--font-size-mini);
margin-bottom: -2rem;
}
.output {
composes: container from '../Section/index.module.css';
display: grid;
gap: var(--spacer);
grid-template-columns: 1fr 1fr;
}
.output p {
font-weight: var(--font-weight-bold);
margin-bottom: calc(var(--spacer) / 8);
font-size: var(--font-size-small);
}
.output [class*='token'] > figure {
display: none;
}
.slippage {
composes: slippage from '../../Trade/Slippage.module.css';
}

View File

@ -1,235 +0,0 @@
import React, {
ReactElement,
useState,
ChangeEvent,
useEffect,
useRef
} from 'react'
import styles from './index.module.css'
import Header from '../Actions/Header'
import { toast } from 'react-toastify'
import Actions from '../Actions'
import { LoggerInstance, Pool } from '@oceanprotocol/lib'
import Token from '../../../../@shared/Token'
import FormHelp from '@shared/FormInput/Help'
import Button from '@shared/atoms/Button'
import debounce from 'lodash.debounce'
import UserLiquidity from '../../UserLiquidity'
import InputElement from '@shared/FormInput/InputElement'
import { useWeb3 } from '@context/Web3'
import Decimal from 'decimal.js'
import { useAsset } from '@context/Asset'
import content from '../../../../../../content/price.json'
import { usePool } from '@context/Pool'
import { getMax } from './_utils'
const slippagePresets = ['5', '10', '15', '25', '50']
export default function Remove({
setShowRemove
}: {
setShowRemove: (show: boolean) => void
}): ReactElement {
const { accountId, web3 } = useWeb3()
const { isAssetNetwork } = useAsset()
const { poolData, poolInfo, poolInfoUser, fetchAllData } = usePool()
const [amountPercent, setAmountPercent] = useState('0')
const [amountMaxPercent, setAmountMaxPercent] = useState('100')
const [amountPoolShares, setAmountPoolShares] = useState('0')
const [amountOcean, setAmountOcean] = useState('0')
const [isLoading, setIsLoading] = useState<boolean>()
const [txId, setTxId] = useState<string>()
const [slippage, setSlippage] = useState(slippagePresets[0])
const [minOceanAmount, setMinOceanAmount] = useState<string>('0')
const [poolInstance, setPoolInstance] = useState<Pool>()
useEffect(() => {
if (!web3) return
setPoolInstance(new Pool(web3))
}, [web3])
async function handleRemoveLiquidity() {
setIsLoading(true)
try {
const result = await poolInstance.exitswapPoolAmountIn(
accountId,
poolData?.id,
amountPoolShares,
minOceanAmount
)
setTxId(result?.transactionHash)
// fetch new data
fetchAllData()
} catch (error) {
LoggerInstance.error(error.message)
toast.error(error.message)
} finally {
// reset slider after transaction
setAmountPercent('0')
setAmountOcean('0')
setMinOceanAmount('0')
setIsLoading(false)
}
}
//
// Calculate and set maximum shares user is able to remove
//
useEffect(() => {
if (!accountId || !poolInfoUser || !poolInfo || !poolInstance) return
getMax(poolInstance, poolInfo, poolInfoUser, poolData).then((max) =>
setAmountMaxPercent(max)
)
}, [accountId, poolInfoUser, poolInfo, poolInstance, poolData])
const getValues = useRef(
debounce(async (poolInstance, id, poolInfo, newAmountPoolShares) => {
if (newAmountPoolShares === '0') {
setAmountOcean('0')
return
}
const newAmountOcean = await poolInstance.calcSingleOutGivenPoolIn(
id,
poolInfo.baseTokenAddress,
newAmountPoolShares,
18,
poolInfo.baseTokenDecimals
)
setAmountOcean(newAmountOcean)
}, 150)
)
// Check and set outputs when amountPoolShares changes
useEffect(() => {
if (!accountId || !poolInfo || !poolData?.id || !poolInstance) return
getValues.current(poolInstance, poolData?.id, poolInfo, amountPoolShares)
}, [amountPoolShares, accountId, poolInfo, poolData?.id, poolInstance])
useEffect(() => {
if (!amountOcean || amountPercent === '0') {
setMinOceanAmount('0')
return
}
const minOceanAmount = new Decimal(amountOcean)
.mul(new Decimal(100).minus(new Decimal(slippage)))
.dividedBy(100)
.toString()
setMinOceanAmount(minOceanAmount.slice(0, 18))
}, [slippage, amountOcean, amountPercent])
// Set amountPoolShares based on set slider value
function handleAmountPercentChange(e: ChangeEvent<HTMLInputElement>) {
setAmountPercent(e.target.value)
if (!poolInfoUser?.poolShares) return
const amountPoolShares = new Decimal(e.target.value)
.dividedBy(100)
.mul(new Decimal(poolInfoUser.poolShares))
.toString()
setAmountPoolShares(amountPoolShares)
}
function handleMaxButton(e: ChangeEvent<HTMLInputElement>) {
e.preventDefault()
setAmountPercent(amountMaxPercent)
const amountPoolShares = new Decimal(amountMaxPercent)
.dividedBy(100)
.mul(new Decimal(poolInfoUser?.poolShares))
.toString()
setAmountPoolShares(amountPoolShares)
}
function handleSlippageChange(e: ChangeEvent<HTMLSelectElement>) {
setSlippage(e.target.value)
}
return (
<div className={styles.remove}>
<Header
title={content.pool.remove.title}
backAction={() => {
setShowRemove(false)
fetchAllData()
}}
/>
<form className={styles.removeInput}>
<UserLiquidity amount={poolInfoUser?.poolShares} symbol="pool shares" />
<div className={styles.range}>
<h3>{amountPercent}%</h3>
<div className={styles.slider}>
<input
type="range"
min="0"
max={amountMaxPercent}
disabled={!isAssetNetwork || isLoading}
value={amountPercent}
onChange={handleAmountPercentChange}
/>
<Button
style="text"
size="small"
className={styles.maximum}
disabled={!isAssetNetwork || isLoading}
onClick={handleMaxButton}
>
{`${amountMaxPercent}% max`}
</Button>
</div>
<FormHelp>{content.pool.remove.simple}</FormHelp>
</div>
</form>
<div className={styles.output}>
<div>
<p>{content.pool.remove.output.titleOutExpected}</p>
<Token
symbol={poolInfo?.baseTokenSymbol}
balance={amountOcean}
noIcon
/>
</div>
<div>
<p>{content.pool.remove.output.titleOutMinimum}</p>
<Token symbol={poolInfo?.baseTokenSymbol} balance={minOceanAmount} />
</div>
</div>
<div className={styles.slippage}>
<strong>Slippage Tolerance</strong>
<InputElement
name="slippage"
type="select"
size="mini"
postfix="%"
sortOptions={false}
options={slippagePresets}
disabled={!isAssetNetwork || isLoading}
value={slippage}
onChange={handleSlippageChange}
/>
</div>
<Actions
isLoading={isLoading}
loaderMessage="Removing Liquidity..."
actionName={content.pool.remove.action}
action={handleRemoveLiquidity}
successMessage="Successfully removed liquidity."
isDisabled={
!isAssetNetwork ||
amountPercent === '0' ||
amountOcean === '0' ||
poolInfo?.totalPoolTokens === '0'
}
txId={txId}
tokenAddress={poolInfo?.baseTokenAddress}
tokenSymbol={poolInfo?.baseTokenSymbol}
/>
</div>
)
}

View File

@ -1,13 +0,0 @@
.title {
font-size: var(--font-size-base);
margin-bottom: calc(var(--spacer) / 3);
color: var(--color-secondary);
}
.titlePostfix {
font-size: var(--font-size-mini);
font-family: var(--font-family-base);
font-weight: var(--font-weight-base);
display: inline-block;
margin-left: 0.3rem;
}

View File

@ -1,26 +0,0 @@
import Tooltip from '@shared/atoms/Tooltip'
import React from 'react'
import styles from './Title.module.css'
export default function Title({
title,
tooltip,
titlePostfix,
titlePostfixTitle
}: {
title: string
tooltip?: string
titlePostfix?: string
titlePostfixTitle?: string
}) {
return (
<h3 className={styles.title}>
{title} {tooltip && <Tooltip content={tooltip} />}{' '}
{titlePostfix && (
<span className={styles.titlePostfix} title={titlePostfixTitle}>
{titlePostfix}
</span>
)}
</h3>
)
}

View File

@ -1,39 +0,0 @@
.container {
margin-left: calc(-1 * var(--spacer) / 1.5);
margin-right: calc(-1 * var(--spacer) / 1.5);
padding: calc(var(--spacer) / 1.5) calc(var(--spacer) / 1.5)
calc(var(--spacer) / 2) calc(var(--spacer) / 1.5);
}
@media (min-width: 40rem) {
.container {
padding-left: var(--spacer);
padding-right: var(--spacer);
margin-left: calc(-1 * var(--spacer));
margin-right: calc(-1 * var(--spacer));
}
}
.section {
composes: container;
border-top: 1px solid var(--border-color);
position: relative;
}
.section.highlight {
background: var(--background-highlight);
}
.section:first-child {
padding-top: 0;
border-top: 0;
}
.grid {
display: grid;
gap: calc(var(--spacer) / 2);
grid-template-columns:
[full-start] minmax(13rem, 1fr) [break] minmax(7rem, 1fr)
[full-end];
}

View File

@ -1,39 +0,0 @@
import React, { ReactElement, ReactNode } from 'react'
import styles from './index.module.css'
import Title from './Title'
export default function PoolSection({
title,
tooltip,
titlePostfix,
titlePostfixTitle,
children,
highlight,
className
}: {
title?: string
children: ReactNode
tooltip?: string
titlePostfix?: string
titlePostfixTitle?: string
highlight?: boolean
className?: string
}): ReactElement {
return (
<div
className={`${styles.section} ${highlight ? styles.highlight : ''} ${
className || ''
}`}
>
{title && (
<Title
title={title}
tooltip={tooltip}
titlePostfix={titlePostfix}
titlePostfixTitle={titlePostfixTitle}
/>
)}
<div className={styles.grid}>{children}</div>
</div>
)
}

View File

@ -1,38 +0,0 @@
.update {
display: block;
margin: 0;
font-size: var(--font-size-mini);
color: var(--color-secondary);
text-align: center;
padding-top: calc(var(--spacer) / 2);
padding-bottom: calc(var(--spacer) / 2);
}
.update:before {
content: '';
width: 6px;
height: 6px;
border-radius: 50%;
display: inline-block;
border: 1px solid var(--brand-alert-green);
margin-right: 0.2rem;
margin-top: -0.1rem;
vertical-align: middle;
animation: pulse 2s ease-in-out infinite;
}
.button {
font-size: var(--font-size-mini);
}
@keyframes pulse {
0% {
background: transparent;
}
50% {
background: var(--brand-alert-green);
}
100% {
background: transparent;
}
}

View File

@ -1,26 +0,0 @@
import { usePool } from '@context/Pool'
import { useUserPreferences } from '@context/UserPreferences'
import Button from '@shared/atoms/Button'
import React from 'react'
import styles from './Update.module.css'
export default function Update() {
const { debug } = useUserPreferences()
const { fetchAllData, refreshInterval } = usePool()
return (
<p className={styles.update}>
Fetching every {refreshInterval / 1000} sec.{' '}
{debug && (
<Button
style="text"
size="small"
onClick={() => fetchAllData()}
className={styles.button}
>
Refresh Data
</Button>
)}
</p>
)
}

View File

@ -1,35 +0,0 @@
.dataToken {
font-size: var(--font-size-large);
text-align: center;
}
.dataToken > div {
display: block;
}
.dataTokenLinks {
display: flex;
justify-content: center;
font-size: var(--font-size-small);
margin-top: calc(var(--spacer) / 4);
}
.dataTokenLinks a {
margin-left: calc(var(--spacer) / 3);
margin-right: calc(var(--spacer) / 3);
}
.fees {
border-top: none;
padding-top: 0;
margin-top: -0.5rem;
}
.fees > div {
grid-template-columns: repeat(auto-fit, minmax(5rem, 1fr));
text-align: center;
}
.fees figure {
display: none;
}

View File

@ -1,139 +0,0 @@
import { useAsset } from '@context/Asset'
import { usePool } from '@context/Pool'
import Tooltip from '@shared/atoms/Tooltip'
import ExplorerLink from '@shared/ExplorerLink'
import PriceUnit from '@shared/Price/PriceUnit'
import React, { useEffect, useState } from 'react'
import Graph from '../Graph'
import PoolSection from '../Section'
import Token from '../../../../@shared/Token'
import content from '../../../../../../content/price.json'
import styles from './index.module.css'
import Update from './Update'
import { useMarketMetadata } from '@context/MarketMetadata'
import { OpcFeesQuery_opc as OpcFeesData } from '../../../../../@types/subgraph/OpcFeesQuery'
import { getOpcFees } from '@utils/subgraph'
import { useWeb3 } from '@context/Web3'
import Decimal from 'decimal.js'
export default function PoolSections() {
const { asset } = useAsset()
const { poolData, poolInfo, poolInfoUser, poolInfoOwner } = usePool()
const { getOpcFeeForToken } = useMarketMetadata()
const { chainId } = useWeb3()
const [oceanCommunitySwapFee, setOceanCommunitySwapFee] = useState<string>('')
useEffect(() => {
getOpcFees(chainId || 1).then((response: OpcFeesData) => {
setOceanCommunitySwapFee(
response?.swapOceanFee
? new Decimal(response.swapOceanFee).mul(100).toString()
: '0'
)
})
}, [chainId])
return (
<>
<PoolSection className={styles.dataToken}>
<PriceUnit price="1" symbol={poolInfo?.datatokenSymbol} size="large" />{' '}
={' '}
<PriceUnit
price={`${poolData?.spotPrice}`}
symbol={poolInfo?.baseTokenSymbol}
size="large"
/>
<Tooltip content={content.pool.tooltips.price} />
<div className={styles.dataTokenLinks}>
<ExplorerLink
networkId={asset?.chainId}
path={`address/${asset?.accessDetails?.addressOrId}`}
>
Pool
</ExplorerLink>
<ExplorerLink
networkId={asset?.chainId}
path={
asset?.chainId === 2021000 || asset?.chainId === 1287
? `tokens/${asset?.services[0].datatokenAddress}`
: `token/${asset?.services[0].datatokenAddress}`
}
>
Datatoken
</ExplorerLink>
</div>
</PoolSection>
<PoolSection
title="Your liquidity"
titlePostfix={
poolInfoUser?.poolSharePercentage &&
`${poolInfoUser?.poolSharePercentage}% of pool`
}
tooltip={content.pool.tooltips.liquidity.replace(
'SWAPFEE',
poolInfo?.liquidityProviderSwapFee
)}
highlight
>
<Token
symbol={poolInfo?.baseTokenSymbol}
balance={poolInfoUser?.liquidity}
conversion
/>
</PoolSection>
<PoolSection
title="Owner liquidity"
titlePostfix={`${poolInfoOwner?.poolSharePercentage}% of pool`}
>
<Token
symbol={poolInfo?.baseTokenSymbol}
balance={poolInfoOwner?.liquidity}
conversion
/>
</PoolSection>
<PoolSection title="Total Value Locked">
<Token
symbol={poolInfo?.baseTokenSymbol}
balance={poolData?.baseTokenLiquidity.toString()}
conversion
/>
</PoolSection>
<PoolSection
title="Pool Statistics"
titlePostfix={
poolInfo?.weightDt &&
`${poolInfo?.weightBaseToken}/${poolInfo?.weightDt}`
}
titlePostfixTitle={`Weight of ${poolInfo?.weightBaseToken}% ${poolInfo?.baseTokenSymbol} & ${poolInfo?.weightDt}% ${poolInfo?.datatokenSymbol}`}
>
<Graph />
</PoolSection>
<PoolSection className={styles.fees}>
<Token
symbol="% swap fee"
balance={poolInfo?.liquidityProviderSwapFee}
noIcon
size="mini"
/>
<Token
symbol="% market fee"
balance={poolInfo?.publishMarketSwapFee}
noIcon
size="mini"
/>
<Token
symbol="% OPC fee"
balance={oceanCommunitySwapFee}
noIcon
size="mini"
/>
</PoolSection>
<Update />
</>
)
}

View File

@ -1,55 +0,0 @@
import React, { ReactElement, useState } from 'react'
import stylesActions from './Actions/index.module.css'
import Button from '@shared/atoms/Button'
import Remove from './Remove'
import AssetActionHistoryTable from '../AssetActionHistoryTable'
import { useAsset } from '@context/Asset'
import { useWeb3 } from '@context/Web3'
import PoolTransactions from '@shared/PoolTransactions'
import { usePool } from '@context/Pool'
import PoolSections from './Sections'
export default function Pool(): ReactElement {
const { asset, isAssetNetwork } = useAsset()
const { hasUserAddedLiquidity } = usePool()
const { accountId } = useWeb3()
const [showRemove, setShowRemove] = useState(false)
return (
<>
{showRemove ? (
<Remove setShowRemove={setShowRemove} />
) : (
<>
<PoolSections />
{hasUserAddedLiquidity && (
<div className={stylesActions.actions}>
<Button
style="primary"
size="small"
onClick={() => setShowRemove(true)}
disabled={!isAssetNetwork}
>
Remove Liquidity
</Button>
</div>
)}
{accountId && (
<AssetActionHistoryTable title="Your Pool Transactions">
<PoolTransactions
accountId={accountId}
poolAddress={asset?.accessDetails?.addressOrId}
poolChainId={asset?.chainId}
minimal
/>
</AssetActionHistoryTable>
)}
</>
)}
</>
)
}

View File

@ -1,5 +0,0 @@
.alertWrap {
min-height: 320px;
display: flex;
align-items: center;
}

View File

@ -1,240 +0,0 @@
import React, { ReactElement, useState } from 'react'
import {
AmountsInMaxFee,
AmountsOutMaxFee,
LoggerInstance,
Pool,
TokenInOutMarket
} from '@oceanprotocol/lib'
import * as Yup from 'yup'
import { Formik } from 'formik'
import Actions from '../Pool/Actions'
import { useUserPreferences } from '@context/UserPreferences'
import { toast } from 'react-toastify'
import Swap from './Swap'
import Alert from '@shared/atoms/Alert'
import styles from './FormTrade.module.css'
import Decimal from 'decimal.js'
import { useWeb3 } from '@context/Web3'
import { useAsset } from '@context/Asset'
import { FormTradeData } from './_types'
import { initialValues } from './_constants'
import content from '../../../../../content/price.json'
import { AssetExtended } from 'src/@types/AssetExtended'
import { usePool } from '@context/Pool'
import { useMarketMetadata } from '@context/MarketMetadata'
export default function FormTrade({
asset,
balance
}: {
asset: AssetExtended
balance: PoolBalance
}): ReactElement {
const { web3, accountId } = useWeb3()
const { isAssetNetwork } = useAsset()
const { debug } = useUserPreferences()
const { appConfig } = useMarketMetadata()
const { poolInfo, fetchAllData } = usePool()
const [txId, setTxId] = useState<string>()
const [coinFrom, setCoinFrom] = useState<string>('OCEAN')
const [maximumBaseToken, setMaximumBaseToken] = useState('0')
const [maximumDt, setMaximumDt] = useState('0')
const validationSchema: Yup.SchemaOf<FormTradeData> = Yup.object()
.shape({
baseToken: Yup.number()
.max(
Number(maximumBaseToken),
(param) => `Must be less or equal to ${param.max}`
)
.min(0.001, (param) => `Must be more or equal to ${param.min}`)
.required('Required')
.nullable(),
datatoken: Yup.number()
.max(
Number(maximumDt),
(param) => `Must be less or equal to ${param.max}`
)
.min(0.00001, (param) => `Must be more or equal to ${param.min}`)
.required('Required')
.nullable(),
type: Yup.string(),
slippage: Yup.string()
})
.defined()
async function handleTrade(values: FormTradeData) {
if (!web3 || !asset || !poolInfo || !values || !appConfig) return
try {
const poolInstance = new Pool(web3)
let tx
if (values.output === 'exactIn') {
const tokenInOutMarket: TokenInOutMarket = {
tokenIn:
values.type === 'sell'
? poolInfo.datatokenAddress
: poolInfo.baseTokenAddress,
tokenOut:
values.type === 'sell'
? poolInfo.baseTokenAddress
: poolInfo.datatokenAddress,
marketFeeAddress: appConfig.marketFeeAddress,
tokenInDecimals:
values.type === 'sell'
? poolInfo.datatokenDecimals
: poolInfo.baseTokenDecimals,
tokenOutDecimals:
values.type === 'sell'
? poolInfo.baseTokenDecimals
: poolInfo.datatokenDecimals
}
const amountsInOutMaxFee: AmountsInMaxFee = {
tokenAmountIn:
values.type === 'sell' ? values.datatoken : values.baseToken,
minAmountOut: new Decimal(
values.type === 'sell' ? values.baseToken : values.datatoken
)
.mul(
new Decimal(1)
.minus(new Decimal(values.slippage).div(new Decimal(100)))
.toString()
)
.toString(),
swapMarketFee: appConfig.consumeMarketPoolSwapFee
}
tx = await poolInstance.swapExactAmountIn(
accountId,
asset.accessDetails.addressOrId,
tokenInOutMarket,
amountsInOutMaxFee
)
if (!tx) {
throw new Error('Failed to swap tokens!')
}
}
if (values.output === 'exactOut') {
const tokenOutMarket: TokenInOutMarket = {
tokenIn:
values.type === 'sell'
? poolInfo.datatokenAddress
: poolInfo.baseTokenAddress,
tokenOut:
values.type === 'sell'
? poolInfo.baseTokenAddress
: poolInfo.datatokenAddress,
marketFeeAddress: appConfig.marketFeeAddress,
tokenInDecimals:
values.type === 'sell'
? poolInfo.datatokenDecimals
: poolInfo.baseTokenDecimals,
tokenOutDecimals:
values.type === 'sell'
? poolInfo.baseTokenDecimals
: poolInfo.datatokenDecimals
}
const amountsOutMaxFee: AmountsOutMaxFee = {
maxAmountIn: new Decimal(
values.type === 'sell' ? values.datatoken : values.baseToken
)
.mul(
new Decimal(1)
.plus(new Decimal(values.slippage).div(new Decimal(100)))
.toString()
)
.toString(),
tokenAmountOut:
values.type === 'sell' ? values.baseToken : values.datatoken,
swapMarketFee: appConfig.consumeMarketPoolSwapFee
}
tx = await poolInstance.swapExactAmountOut(
accountId,
asset.accessDetails.addressOrId,
tokenOutMarket,
amountsOutMaxFee
)
if (!tx) {
throw new Error('Failed to swap tokens!')
}
}
await fetchAllData()
setTxId(tx?.transactionHash)
} catch (error) {
LoggerInstance.error(error.message)
toast.error(error.message)
}
}
return (
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={async (values, { setFieldValue, setSubmitting, resetForm }) => {
await handleTrade(values)
await setFieldValue('baseToken', '')
await setFieldValue('datatoken', '')
resetForm()
setSubmitting(false)
}}
>
{({ isSubmitting, setSubmitting, submitForm, values, isValid }) => (
<>
<Swap
asset={asset}
balance={balance}
setCoin={setCoinFrom}
setMaximumBaseToken={setMaximumBaseToken}
setMaximumDt={setMaximumDt}
isLoading={isSubmitting}
/>
<Actions
isDisabled={
!isValid ||
!isAssetNetwork ||
values.datatoken === undefined ||
values.baseToken === undefined
}
isLoading={isSubmitting}
loaderMessage="Swapping tokens..."
successMessage="Successfully swapped tokens."
actionName={content.trade.action}
slippage={values.slippage}
amount={
values.type === 'sell'
? values.datatoken
? `${values.datatoken}`
: undefined
: values.baseToken
? `${values.baseToken}`
: undefined
}
action={submitForm}
txId={txId}
tokenAddress={
values.type === 'buy'
? poolInfo.baseTokenAddress
: poolInfo.datatokenAddress
}
tokenSymbol={
values.type === 'buy'
? poolInfo.baseTokenSymbol
: poolInfo.datatokenSymbol
}
setSubmitting={setSubmitting}
/>
{debug && (
<pre>
<code>{JSON.stringify(values, null, 2)}</code>
</pre>
)}
</>
)}
</Formik>
)
}

View File

@ -1,24 +0,0 @@
.output {
border-top: 1px solid var(--border-color);
padding: var(--spacer);
padding-bottom: calc(var(--spacer) / 1.5);
display: grid;
gap: var(--spacer);
grid-template-columns: 1fr 1fr;
margin-left: -2rem;
margin-right: -2rem;
}
.output p {
font-weight: var(--font-weight-bold);
margin-bottom: calc(var(--spacer) / 8);
font-size: var(--font-size-small);
}
.output [class*='token'] {
white-space: normal;
}
.output [class*='token'] > figure {
display: none;
}

View File

@ -1,95 +0,0 @@
import { FormikContextType, useFormikContext } from 'formik'
import React, { ReactElement, useEffect, useState } from 'react'
import { useAsset } from '@context/Asset'
import Token from '../../../@shared/Token'
import styles from './Output.module.css'
import Decimal from 'decimal.js'
import { FormTradeData } from './_types'
import { usePool } from '@context/Pool'
Decimal.set({ toExpNeg: -18, precision: 18, rounding: 1 })
export default function Output({
poolAddress,
lpSwapFee
}: {
poolAddress: string
lpSwapFee: string
}): ReactElement {
const { isAssetNetwork } = useAsset()
const { poolInfo } = usePool()
const [outputWithSlippage, setOutputWithSlippage] = useState<string>('0')
// Connect with form
const { values }: FormikContextType<FormTradeData> = useFormikContext()
// Get output values
useEffect(() => {
if (!poolAddress || !isAssetNetwork) return
async function getOutput() {
if (!values.baseToken || !values.datatoken || !values.output) return
const output =
values.output === 'exactIn'
? new Decimal(
values.type === 'sell' ? values.baseToken : values.datatoken
)
.mul(
new Decimal(1)
.minus(new Decimal(values.slippage).div(new Decimal(100)))
.toString()
)
.toString()
: new Decimal(
values.type === 'sell' ? values.datatoken : values.baseToken
)
.mul(
new Decimal(1)
.plus(new Decimal(values.slippage).div(new Decimal(100)))
.toString()
)
.toString()
setOutputWithSlippage(output)
}
getOutput()
}, [poolAddress, values, isAssetNetwork])
return (
<div className={styles.output}>
<div>
<p>
{values.output === 'exactIn' ? 'Minimum Received' : 'Maximum Sent'}
</p>
<Token
symbol={
values.type === 'buy'
? values.output === 'exactIn'
? poolInfo.datatokenSymbol
: poolInfo.baseTokenSymbol
: values.output === 'exactIn'
? poolInfo.baseTokenSymbol
: poolInfo.datatokenSymbol
}
balance={outputWithSlippage}
/>
</div>
<div>
<p>Swap fee</p>
<Token
symbol={`${
values.type === 'buy'
? poolInfo.baseTokenSymbol
: poolInfo.datatokenSymbol
} ${
poolInfo.liquidityProviderSwapFee
? `(${poolInfo.liquidityProviderSwapFee}%)`
: ''
}`}
balance={lpSwapFee}
/>
</div>
</div>
)
}

View File

@ -1,24 +0,0 @@
.priceImpact {
font-size: var(--font-size-small);
border-top: 1px solid var(--border-color);
margin-left: -2rem;
margin-right: -2rem;
padding: calc(var(--spacer) / 4) var(--spacer);
color: var(--color-secondary);
display: grid;
grid-template-columns: 1fr 1fr;
gap: calc(var(--spacer) / 3);
}
.priceImpact strong {
font-weight: var(--font-weight-base);
text-align: right;
}
.alert {
color: var(--brand-alert-red);
}
.number {
font-weight: var(--font-weight-bold);
}

View File

@ -1,65 +0,0 @@
import React, { ReactElement, useEffect, useState } from 'react'
import Decimal from 'decimal.js'
import Tooltip from '@shared/atoms/Tooltip'
import styles from './PriceImpact.module.css'
export default function PriceImpact({
totalValue,
tokenAmount,
spotPrice
}: {
/// how much the user actually pays (doesn't matter witch token it is)
totalValue: string
/// how many tokens the user trades (doesn't matter witch token it is)
tokenAmount: string
/// the spot price of the traded token (doesn't matter witch token it is))
spotPrice: string
}): ReactElement {
const [priceImpact, setPriceImpact] = useState<string>('0')
async function getPriceImpact(
totalValue: string,
tokenAmount: string,
spotPrice: string
) {
const dtotalValue = new Decimal(totalValue)
const dTokenAmount = new Decimal(tokenAmount)
const dSpotPrice = new Decimal(spotPrice)
let priceImpact = Decimal.abs(
dtotalValue.div(dTokenAmount.times(dSpotPrice)).minus(1)
).mul(100)
if (priceImpact.isNaN()) priceImpact = new Decimal(0)
return priceImpact.toDecimalPlaces(2, Decimal.ROUND_DOWN)
}
useEffect(() => {
if (!totalValue || !tokenAmount || !spotPrice) {
setPriceImpact('0')
return
}
async function init() {
const newPriceImpact = await getPriceImpact(
totalValue,
tokenAmount,
spotPrice
)
setPriceImpact(newPriceImpact.toString())
}
init()
}, [totalValue, tokenAmount, spotPrice])
return (
<div className={styles.priceImpact}>
<strong>Price Impact</strong>
<div>
<span
className={`${styles.number} ${
parseInt(priceImpact) > 5 && styles.alert
}`}
>{`${priceImpact}%`}</span>
<Tooltip content="The difference between the market price and estimated price due to trade size." />
</div>
</div>
)
}

View File

@ -1,30 +0,0 @@
.slippage {
font-size: var(--font-size-small);
border-top: 1px solid var(--border-color);
margin-left: -2rem;
margin-right: -2rem;
padding: calc(var(--spacer) / 4) var(--spacer);
color: var(--color-secondary);
display: grid;
grid-template-columns: 1fr 1fr;
gap: calc(var(--spacer) / 3);
}
.slippage strong {
font-weight: var(--font-weight-base);
text-align: right;
}
.title {
font-family: var(--font-family-base);
font-weight: var(--font-weight-base);
font-size: var(--font-size-mini);
text-align: center;
margin-bottom: calc(var(--spacer) / 4);
color: var(--color-secondary);
}
.slippage select {
width: fit-content;
display: inline-block;
}

View File

@ -1,41 +0,0 @@
import { FormikContextType, useFormikContext } from 'formik'
import React, { ChangeEvent, ReactElement } from 'react'
import InputElement from '@shared/FormInput/InputElement'
import Tooltip from '@shared/atoms/Tooltip'
import styles from './Slippage.module.css'
import { FormTradeData } from './_types'
import { slippagePresets } from './_constants'
export default function Slippage({
disabled
}: {
disabled: boolean
}): ReactElement {
// Connect with form
const { setFieldValue, values }: FormikContextType<FormTradeData> =
useFormikContext()
function handleChange(e: ChangeEvent<HTMLSelectElement>) {
setFieldValue('slippage', e.target.value)
}
return (
<div className={styles.slippage}>
<strong>Slippage Tolerance</strong>
<div>
<InputElement
name="slippage"
type="select"
size="mini"
postfix="%"
sortOptions={false}
options={slippagePresets}
value={values.slippage}
disabled={disabled}
onChange={handleChange}
/>
<Tooltip content="Your transaction will revert if the price changes unfavorably by more than this percentage." />
</div>
</div>
)
}

View File

@ -1,27 +0,0 @@
.swap {
margin-top: -2rem;
}
.swapButton,
.swapButton:hover,
.swapButton:focus,
.swapButton:active {
padding: 0;
display: block;
width: calc(100% + 4rem);
text-align: center;
margin-left: -2rem;
margin-right: -2rem;
border-top: 1px solid var(--border-color);
border-bottom: 1px solid var(--border-color);
padding: calc(var(--spacer) / 3) 0 calc(var(--spacer) / 6) 0;
transform: none;
}
.swapButton svg {
display: inline-block;
width: var(--font-size-large);
height: var(--font-size-large);
fill: var(--brand-pink);
transform: rotate(90deg);
}

View File

@ -1,355 +0,0 @@
import React, { ReactElement, useEffect, useState } from 'react'
import styles from './Swap.module.css'
import TradeInput from './TradeInput'
import Button from '@shared/atoms/Button'
import Arrow from '@images/arrow.svg'
import { FormikContextType, useFormikContext } from 'formik'
import Output from './Output'
import Slippage from './Slippage'
import PriceImpact from './PriceImpact'
import Decimal from 'decimal.js'
import { useAsset } from '@context/Asset'
import { useWeb3 } from '@context/Web3'
import { FormTradeData, TradeItem } from './_types'
import {
calcMaxExactIn,
calcMaxExactOut,
LoggerInstance,
Pool,
PoolPriceAndFees
} from '@oceanprotocol/lib'
import { AssetExtended } from 'src/@types/AssetExtended'
import { usePool } from '@context/Pool'
import { MAX_DECIMALS } from '@utils/constants'
import { useMarketMetadata } from '@context/MarketMetadata'
// Decimal.set({ toExpNeg: -15, precision: 5, rounding: Decimal.ROUND_DOWN })
export default function Swap({
asset,
balance,
setMaximumDt,
setMaximumBaseToken,
setCoin,
isLoading
}: {
asset: AssetExtended
balance: PoolBalance
setMaximumDt: (value: string) => void
setMaximumBaseToken: (value: string) => void
setCoin: (value: string) => void
isLoading: boolean
}): ReactElement {
const { isAssetNetwork } = useAsset()
const { web3 } = useWeb3()
const { poolInfo, poolData } = usePool()
const { appConfig } = useMarketMetadata()
const [baseTokenItem, setBaseTokenItem] = useState<TradeItem>({
amount: '0',
token: poolInfo?.baseTokenSymbol,
maxAmount: '0',
address: poolInfo?.baseTokenAddress
})
const [dtItem, setDtItem] = useState<TradeItem>({
amount: '0',
token: poolInfo?.datatokenSymbol,
maxAmount: '0',
address: poolInfo?.datatokenAddress
})
const {
setFieldValue,
values,
setErrors,
validateForm
}: FormikContextType<FormTradeData> = useFormikContext()
// Values used for calculation of price impact
const [spotPrice, setSpotPrice] = useState<string>()
const [totalValue, setTotalValue] = useState<string>()
const [tokenAmount, setTokenAmount] = useState<string>()
const [lpSwapFee, setLpSwapFee] = useState<string>()
useEffect(() => {
if (!asset || !balance || !values?.type || !web3 || !appConfig || !poolInfo)
return
const poolInstance = new Pool(web3)
async function calculateMaximum() {
const datatokenLiquidity = await poolInstance.getReserve(
poolData.id,
poolData.datatoken.address,
poolData.datatoken.decimals
)
const baseTokenLiquidity = await poolInstance.getReserve(
poolData.id,
poolData.baseToken.address,
poolData.baseToken.decimals
)
if (values.type === 'buy') {
const maxBaseTokenFromPool = calcMaxExactIn(baseTokenLiquidity)
const maxBaseTokens = maxBaseTokenFromPool.greaterThan(
new Decimal(balance.baseToken)
)
? balance.baseToken
: maxBaseTokenFromPool.toDecimalPlaces(MAX_DECIMALS).toString()
const maxDt = await poolInstance.getAmountOutExactIn(
asset.accessDetails?.addressOrId,
poolInfo.baseTokenAddress,
poolInfo.datatokenAddress,
maxBaseTokens.toString(),
appConfig.consumeMarketPoolSwapFee,
poolInfo.baseTokenDecimals,
poolInfo.datatokenDecimals
)
const maximumDt = new Decimal(maxDt.tokenAmount)
.toDecimalPlaces(MAX_DECIMALS)
.toString()
setMaximumDt(maximumDt)
setMaximumBaseToken(maxBaseTokens)
setBaseTokenItem((prevState) => ({
...prevState,
amount: balance.baseToken,
maxAmount: maxBaseTokens
}))
setDtItem((prevState) => ({
...prevState,
amount: datatokenLiquidity,
maxAmount: maximumDt
}))
} else {
const maxDtFromPool = calcMaxExactIn(datatokenLiquidity)
const maxDatatokens = maxDtFromPool.greaterThan(
new Decimal(balance.datatoken)
)
? balance.datatoken
: maxDtFromPool.toDecimalPlaces(MAX_DECIMALS).toString()
const maxBaseTokens = await poolInstance.getAmountOutExactIn(
asset.accessDetails?.addressOrId,
poolInfo?.datatokenAddress,
poolInfo?.baseTokenAddress,
maxDatatokens.toString(),
appConfig.consumeMarketPoolSwapFee,
poolInfo.datatokenDecimals,
poolInfo.baseTokenDecimals
)
const maximumBasetokens = new Decimal(maxBaseTokens.tokenAmount)
.toDecimalPlaces(MAX_DECIMALS)
.toString()
setMaximumDt(maxDatatokens)
setMaximumBaseToken(maximumBasetokens)
setDtItem((prevState) => ({
...prevState,
amount: balance.datatoken,
maxAmount: maxDatatokens
}))
setBaseTokenItem((prevState) => ({
...prevState,
amount: baseTokenLiquidity,
maxAmount: maximumBasetokens
}))
}
}
calculateMaximum()
}, [
poolData,
balance,
values.type,
setMaximumDt,
setMaximumBaseToken,
asset,
web3,
dtItem.token,
dtItem.amount,
baseTokenItem.token,
baseTokenItem.amount,
appConfig,
poolInfo
])
const switchTokens = () => {
setFieldValue('type', values.type === 'buy' ? 'sell' : 'buy')
setCoin(
values.type === 'sell'
? poolInfo.baseTokenSymbol
: poolInfo.datatokenSymbol
)
// don't reset form because we don't want to reset type
setFieldValue('datatoken', 0)
setFieldValue('baseToken', 0)
setErrors({})
}
const handleValueChange = async (name: string, value: number) => {
try {
let tokenIn = ''
let tokenOut = ''
const poolInstance = new Pool(web3)
let newValue: PoolPriceAndFees
if (name === 'baseToken') {
if (values.type === 'sell') {
newValue = await poolInstance.getAmountInExactOut(
asset.accessDetails.addressOrId,
dtItem.address,
baseTokenItem.address,
value.toString(),
appConfig.consumeMarketPoolSwapFee
)
setFieldValue('output', 'exactOut')
setTotalValue(
new Decimal(newValue.tokenAmount)
.toDecimalPlaces(MAX_DECIMALS)
.toString()
)
setLpSwapFee(
new Decimal(newValue.liquidityProviderSwapFeeAmount)
.toDecimalPlaces(MAX_DECIMALS)
.toString()
)
setTokenAmount(value.toString())
tokenIn = poolInfo.datatokenAddress
tokenOut = poolInfo.baseTokenAddress
} else {
newValue = await poolInstance.getAmountOutExactIn(
asset.accessDetails.addressOrId,
baseTokenItem.address,
dtItem.address,
value.toString(),
appConfig.consumeMarketPoolSwapFee
)
setFieldValue('output', 'exactIn')
setTotalValue(value.toString())
setTokenAmount(
new Decimal(newValue.tokenAmount)
.toDecimalPlaces(MAX_DECIMALS)
.toString()
)
setLpSwapFee(
new Decimal(newValue.liquidityProviderSwapFeeAmount)
.toDecimalPlaces(MAX_DECIMALS)
.toString()
)
tokenIn = poolInfo.baseTokenAddress
tokenOut = poolInfo.datatokenAddress
}
} else {
if (values.type === 'sell') {
newValue = await poolInstance.getAmountOutExactIn(
asset.accessDetails.addressOrId,
dtItem.address,
baseTokenItem.address,
value.toString(),
appConfig.consumeMarketPoolSwapFee
)
setFieldValue('output', 'exactIn')
setTotalValue(value.toString())
setTokenAmount(
new Decimal(newValue.tokenAmount)
.toDecimalPlaces(MAX_DECIMALS)
.toString()
)
setLpSwapFee(
new Decimal(newValue.liquidityProviderSwapFeeAmount)
.toDecimalPlaces(MAX_DECIMALS)
.toString()
)
tokenIn = poolInfo.datatokenAddress
tokenOut = poolInfo.baseTokenAddress
} else {
newValue = await poolInstance.getAmountInExactOut(
asset.accessDetails.addressOrId,
baseTokenItem.address,
dtItem.address,
value.toString(),
appConfig.consumeMarketPoolSwapFee
)
setFieldValue('output', 'exactOut')
setTotalValue(
new Decimal(newValue.tokenAmount)
.toDecimalPlaces(MAX_DECIMALS)
.toString()
)
setTokenAmount(value.toString())
setLpSwapFee(
new Decimal(newValue.liquidityProviderSwapFeeAmount)
.toDecimalPlaces(MAX_DECIMALS)
.toString()
)
tokenIn = poolInfo.baseTokenAddress
tokenOut = poolInfo.datatokenAddress
}
}
await setFieldValue(
name === 'baseToken' ? 'datatoken' : 'baseToken',
new Decimal(newValue.tokenAmount)
.toDecimalPlaces(MAX_DECIMALS)
.toString()
)
const spotPrice = await poolInstance.getSpotPrice(
asset.accessDetails.addressOrId,
tokenIn,
tokenOut,
appConfig.consumeMarketPoolSwapFee
)
setSpotPrice(spotPrice)
validateForm()
} catch (ex) {
LoggerInstance.error(ex)
}
}
return (
<div className={styles.swap}>
<TradeInput
name={values.type === 'sell' ? 'datatoken' : 'baseToken'}
item={values.type === 'sell' ? dtItem : baseTokenItem}
disabled={!isAssetNetwork || isLoading}
handleValueChange={handleValueChange}
/>
<Button
className={styles.swapButton}
style="text"
onClick={switchTokens}
disabled={!isAssetNetwork || isLoading}
>
<Arrow />
</Button>
<TradeInput
name={values.type === 'sell' ? 'baseToken' : 'datatoken'}
item={values.type === 'sell' ? baseTokenItem : dtItem}
disabled={!isAssetNetwork || isLoading}
handleValueChange={handleValueChange}
/>
<Output
poolAddress={asset.accessDetails?.addressOrId}
lpSwapFee={lpSwapFee}
/>
<PriceImpact
totalValue={totalValue}
tokenAmount={tokenAmount}
spotPrice={spotPrice}
/>
<Slippage disabled={!isAssetNetwork || isLoading} />
</div>
)
}

View File

@ -1,34 +0,0 @@
.tradeInput {
position: relative;
padding: var(--spacer) calc(var(--spacer) * 2);
margin-left: -2rem;
margin-right: -2rem;
background: var(--background-highlight);
}
.tradeInput input {
text-align: center;
}
.tradeInput div[class*='field'] {
margin-bottom: 0;
}
.tradeInput div[class*='prefix'] {
min-width: 6.5rem;
justify-content: center;
}
.label {
font-family: var(--font-family-heading);
font-size: var(--font-size-base);
text-align: center;
display: block;
}
.buttonMax {
position: absolute;
font-size: var(--font-size-mini);
bottom: calc(var(--spacer) / 2);
right: calc(var(--spacer) * 2);
}

View File

@ -1,94 +0,0 @@
import React, { ChangeEvent, ReactElement } from 'react'
import styles from './TradeInput.module.css'
import {
Field,
FieldInputProps,
FormikContextType,
useFormikContext,
useField
} from 'formik'
import debounce from 'lodash.debounce'
import Button from '@shared/atoms/Button'
import Input from '@shared/FormInput'
import Error from '@shared/FormInput/Error'
import { FormTradeData, TradeItem } from './_types'
import UserLiquidity from '../UserLiquidity'
import { useWeb3 } from '@context/Web3'
export default function TradeInput({
name,
item,
disabled,
handleValueChange
}: {
name: string
item: TradeItem
disabled: boolean
handleValueChange: (name: string, value: number) => void
}): ReactElement {
const { accountId } = useWeb3()
// Connect with form
const {
handleChange,
validateForm,
values
}: FormikContextType<FormTradeData> = useFormikContext()
const [field, meta] = useField(name)
const isTopField =
(name === 'baseToken' && values.type === 'buy') ||
(name === 'datatoken' && values.type === 'sell')
const titleAvailable = isTopField ? `Your Balance` : `Pool Balance`
const titleMaximum = isTopField ? `Maximum to spend` : `Maximum to receive`
return (
<section className={styles.tradeInput}>
<UserLiquidity
amount={`${item?.amount}`}
amountMax={`${item?.maxAmount}`}
symbol={item?.token}
titleAvailable={titleAvailable}
titleMaximum={titleMaximum}
/>
<Field name={name}>
{({ field, form }: { field: FieldInputProps<number>; form: any }) => (
<Input
type="number"
max={`${item?.maxAmount}`}
min="0"
prefix={item?.token}
placeholder="0"
field={field}
form={form}
disabled={!accountId || disabled}
additionalComponent={<Error meta={meta} />}
value={`${field.value}`}
{...field}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
handleChange(e)
handleValueChange(name, Number(e.target.value))
// debounce needed to avoid validating the wrong (pass) value
debounce(() => validateForm(), 100)
}}
/>
)}
</Field>
{/* {!isTopField && (
<Button
className={styles.buttonMax}
disabled={disabled}
style="text"
size="small"
onClick={() => {
setFieldValue(name, item?.maxAmount)
handleValueChange(name, Number(item?.maxAmount))
}}
>
Use Max
</Button>
)} */}
</section>
)
}

View File

@ -1,13 +0,0 @@
import { FormTradeData } from './_types'
export const initialValues: FormTradeData = {
baseToken: undefined,
datatoken: undefined,
type: 'buy',
output: 'exactIn',
slippage: '5'
}
export const slippagePresets = ['5', '10', '15', '25', '50']
// validationSchema lives in components/organisms/AssetActions/Trade/FormTrade.tsx

View File

@ -1,14 +0,0 @@
export interface FormTradeData extends PoolBalance {
// in reference to datatoken, buy = swap from ocean to dt ( buy dt) , sell = swap from dt to ocean (sell dt)
type: 'buy' | 'sell'
// based on what the user inputs, if he fill the top input it is 'exactIn'
output: 'exactIn' | 'exactOut'
slippage: string
}
export interface TradeItem {
amount: string
token: string
maxAmount: string
address: string
}

View File

@ -1,52 +0,0 @@
import React, { ReactElement, useEffect, useState } from 'react'
import FormTrade from './FormTrade'
import { useAsset } from '@context/Asset'
import { useWeb3 } from '@context/Web3'
import { isValidNumber } from '@utils/numbers'
import Decimal from 'decimal.js'
import { Datatoken, LoggerInstance, Pool } from '@oceanprotocol/lib'
import { usePool } from '@context/Pool'
Decimal.set({ toExpNeg: -18, precision: 18, rounding: 1 })
export default function Trade(): ReactElement {
const { accountId, balance, web3 } = useWeb3()
const { isAssetNetwork } = useAsset()
const [tokenBalance, setTokenBalance] = useState<PoolBalance>()
const { asset } = useAsset()
const { poolInfo } = usePool()
// Get datatoken balance, and combine with OCEAN balance from hooks into one object
useEffect(() => {
if (
!web3 ||
!accountId ||
!isAssetNetwork ||
!balance?.ocean ||
!accountId ||
!poolInfo?.datatokenAddress
)
return
async function getTokenBalance() {
const datatokenInstance = new Datatoken(web3)
const dtBalance = await datatokenInstance.balance(
poolInfo.datatokenAddress,
accountId
)
setTokenBalance({
baseToken: new Decimal(balance.ocean).toString(),
datatoken: new Decimal(dtBalance).toString()
})
}
getTokenBalance()
}, [
web3,
balance.ocean,
accountId,
poolInfo?.datatokenAddress,
isAssetNetwork
])
return <FormTrade asset={asset} balance={tokenBalance} />
}

View File

@ -1,12 +0,0 @@
.userLiquidity > div {
display: flex;
justify-content: space-between;
align-items: center;
font-size: var(--font-size-mini);
color: var(--color-secondary);
}
.userLiquidity span + div {
transform: scale(0.8);
transform-origin: right center;
}

View File

@ -1,51 +0,0 @@
import React, { ReactElement } from 'react'
import PriceUnit from '@shared/Price/PriceUnit'
import styles from './UserLiquidity.module.css'
function UserLiquidityLine({
title,
amount,
symbol
}: {
title: string
amount: string
symbol: string
}) {
return (
<div>
<span>{title}</span>
<PriceUnit price={amount} symbol={symbol} size="small" />
</div>
)
}
export default function UserLiquidity({
amount,
symbol,
amountMax,
titleAvailable = 'Balance',
titleMaximum = 'Maximum'
}: {
amount: string
symbol: string
titleAvailable?: string
titleMaximum?: string
amountMax?: string
}): ReactElement {
return (
<div className={styles.userLiquidity}>
<UserLiquidityLine
title={titleAvailable}
amount={amount}
symbol={symbol}
/>
{amountMax && (
<UserLiquidityLine
title={titleMaximum}
amount={amountMax}
symbol={symbol}
/>
)}
</div>
)
}

View File

@ -4,8 +4,6 @@ import Consume from './Download'
import { FileInfo, LoggerInstance, Datatoken } from '@oceanprotocol/lib' import { FileInfo, LoggerInstance, Datatoken } from '@oceanprotocol/lib'
import Tabs, { TabsItem } from '@shared/atoms/Tabs' import Tabs, { TabsItem } from '@shared/atoms/Tabs'
import { compareAsBN } from '@utils/numbers' import { compareAsBN } from '@utils/numbers'
import Pool from './Pool'
import Trade from './Trade'
import { useAsset } from '@context/Asset' import { useAsset } from '@context/Asset'
import { useWeb3 } from '@context/Web3' import { useWeb3 } from '@context/Web3'
import Web3Feedback from '@shared/Web3Feedback' import Web3Feedback from '@shared/Web3Feedback'
@ -16,8 +14,6 @@ import { useIsMounted } from '@hooks/useIsMounted'
import styles from './index.module.css' import styles from './index.module.css'
import { useFormikContext } from 'formik' import { useFormikContext } from 'formik'
import { FormPublishData } from 'src/components/Publish/_types' import { FormPublishData } from 'src/components/Publish/_types'
import { AssetExtended } from 'src/@types/AssetExtended'
import PoolProvider from '@context/Pool'
import AssetStats from './AssetStats' import AssetStats from './AssetStats'
export default function AssetActions({ export default function AssetActions({
@ -148,18 +144,13 @@ export default function AssetActions({
const tabs: TabsItem[] = [{ title: 'Use', content: UseContent }] const tabs: TabsItem[] = [{ title: 'Use', content: UseContent }]
asset?.accessDetails?.type === 'dynamic' &&
tabs.push({ title: 'Pool', content: <Pool /> })
return ( return (
<> <>
<PoolProvider> <Tabs items={tabs} className={styles.actions} />
<Tabs items={tabs} className={styles.actions} /> <Web3Feedback
<Web3Feedback networkId={asset?.chainId}
networkId={asset?.chainId} isAssetNetwork={isAssetNetwork}
isAssetNetwork={isAssetNetwork} />
/>
</PoolProvider>
</> </>
) )
} }

View File

@ -5,7 +5,6 @@ import AddToken from '@shared/AddToken'
import ExplorerLink from '@shared/ExplorerLink' import ExplorerLink from '@shared/ExplorerLink'
import Publisher from '@shared/Publisher' import Publisher from '@shared/Publisher'
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import { AssetExtended } from 'src/@types/AssetExtended'
import styles from './MetaAsset.module.css' import styles from './MetaAsset.module.css'
export default function MetaAsset({ export default function MetaAsset({

View File

@ -1,10 +1,8 @@
import { useAsset } from '@context/Asset'
import AssetType from '@shared/AssetType' import AssetType from '@shared/AssetType'
import Time from '@shared/atoms/Time' import Time from '@shared/atoms/Time'
import Publisher from '@shared/Publisher' import Publisher from '@shared/Publisher'
import { getServiceByName } from '@utils/ddo' import { getServiceByName } from '@utils/ddo'
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import { AssetExtended } from 'src/@types/AssetExtended'
import styles from './MetaInfo.module.css' import styles from './MetaInfo.module.css'
export default function MetaInfo({ export default function MetaInfo({

View File

@ -3,7 +3,6 @@ import styles from './index.module.css'
import MetaAsset from './MetaAsset' import MetaAsset from './MetaAsset'
import MetaInfo from './MetaInfo' import MetaInfo from './MetaInfo'
import Nft from '../Nft' import Nft from '../Nft'
import { AssetExtended } from 'src/@types/AssetExtended'
const blockscoutNetworks = [1287, 2021000, 2021001, 44787, 246, 1285] const blockscoutNetworks = [1287, 2021000, 2021001, 44787, 246, 1285]

View File

@ -1,5 +1,4 @@
import React, { ReactElement, useState, useEffect } from 'react' import React, { ReactElement, useState, useEffect } from 'react'
import Link from 'next/link'
import Markdown from '@shared/Markdown' import Markdown from '@shared/Markdown'
import MetaFull from './MetaFull' import MetaFull from './MetaFull'
import MetaSecondary from './MetaSecondary' import MetaSecondary from './MetaSecondary'
@ -14,7 +13,6 @@ import EditHistory from './EditHistory'
import styles from './index.module.css' import styles from './index.module.css'
import NetworkName from '@shared/NetworkName' import NetworkName from '@shared/NetworkName'
import content from '../../../../content/purgatory.json' import content from '../../../../content/purgatory.json'
import { AssetExtended } from 'src/@types/AssetExtended'
import Web3 from 'web3' import Web3 from 'web3'
import Button from '@shared/atoms/Button' import Button from '@shared/atoms/Button'

View File

@ -17,7 +17,6 @@ import {
computeSettingsValidationSchema computeSettingsValidationSchema
} from './_constants' } from './_constants'
import content from '../../../../content/pages/editComputeDataset.json' import content from '../../../../content/pages/editComputeDataset.json'
import { AssetExtended } from 'src/@types/AssetExtended'
import { getServiceByName } from '@utils/ddo' import { getServiceByName } from '@utils/ddo'
import { setMinterToPublisher, setMinterToDispenser } from '@utils/dispenser' import { setMinterToPublisher, setMinterToDispenser } from '@utils/dispenser'
import { transformComputeFormToServiceComputeOptions } from '@utils/compute' import { transformComputeFormToServiceComputeOptions } from '@utils/compute'

View File

@ -16,7 +16,6 @@ import FormEditMetadata from './FormEditMetadata'
import { mapTimeoutStringToSeconds } from '@utils/ddo' import { mapTimeoutStringToSeconds } from '@utils/ddo'
import styles from './index.module.css' import styles from './index.module.css'
import content from '../../../../content/pages/editMetadata.json' import content from '../../../../content/pages/editMetadata.json'
import { AssetExtended } from 'src/@types/AssetExtended'
import { useAbortController } from '@hooks/useAbortController' import { useAbortController } from '@hooks/useAbortController'
import DebugEditMetadata from './DebugEditMetadata' import DebugEditMetadata from './DebugEditMetadata'
import { getOceanConfig } from '@utils/ocean' import { getOceanConfig } from '@utils/ocean'

Some files were not shown because too many files have changed in this diff Show More