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_MARKET_FEE_ADDRESS="0xxx"
#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_CONSUME_MARKET_ORDER_FEE="1"
#NEXT_PUBLIC_CONSUME_MARKET_POOL_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 NFT which represents the data set
- 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
- 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
publisherMarketOrderFee:
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
publisherMarketFixedSwapFee:
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
consumeMarketOrderFee:
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
consumeMarketFixedSwapFee:
process.env.NEXT_PUBLIC_CONSUME_MARKET_FIXED_SWAP_FEE || '0',
@ -75,10 +69,9 @@ module.exports = {
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.
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',
// Set the default privacy policy to initially display

View File

@ -14,6 +14,6 @@
}
],
"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": {
"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": {
"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.",
@ -30,24 +22,10 @@
]
}
},
"pool": {
"approval": {
"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.",
"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",
"type": "checkbox",
"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
},
{

View File

@ -14,7 +14,7 @@
"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": {
"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",
"@walletconnect/web3-provider": "^1.7.8",
"axios": "^0.27.2",
"chart.js": "^3.8.0",
"classnames": "^2.3.1",
"date-fns": "^2.29.1",
"decimal.js": "^10.3.1",
@ -37,7 +36,6 @@
"next": "^12.1.6",
"query-string": "^7.1.1",
"react": "^18.2.0",
"react-chartjs-2": "^4.2.0",
"react-clipboard.js": "^2.0.16",
"react-data-table-component": "^7.5.2",
"react-dom": "^18.1.0",
@ -70,7 +68,6 @@
"@svgr/webpack": "^6.2.1",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0",
"@types/chart.js": "^2.9.37",
"@types/js-cookie": "^3.0.2",
"@types/loadable__component": "^5.13.4",
"@types/lodash.debounce": "^4.0.7",
@ -17910,15 +17907,6 @@
"@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": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/clipboard/-/clipboard-2.0.7.tgz",
@ -22298,11 +22286,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": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/checkpoint-store/-/checkpoint-store-1.1.0.tgz",
@ -39429,15 +39412,6 @@
"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": {
"version": "2.0.16",
"resolved": "https://registry.npmjs.org/react-clipboard.js/-/react-clipboard.js-2.0.16.tgz",
@ -60513,15 +60487,6 @@
"@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": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/clipboard/-/clipboard-2.0.7.tgz",
@ -64101,11 +64066,6 @@
"integrity": "sha512-Y4kiDb+AM4Ecy58YkuZrrSRJBDQdQ2L+NyS1vHHFtNtUjgutcZfx3yp1dAONI/oPaPmyGfCLx5CxL+zauIMyKQ==",
"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": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/checkpoint-store/-/checkpoint-store-1.1.0.tgz",
@ -77612,12 +77572,6 @@
"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": {
"version": "2.0.16",
"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",
"@walletconnect/web3-provider": "^1.7.8",
"axios": "^0.27.2",
"chart.js": "^3.8.0",
"classnames": "^2.3.1",
"date-fns": "^2.29.1",
"decimal.js": "^10.3.1",
@ -50,7 +49,6 @@
"next": "^12.1.6",
"query-string": "^7.1.1",
"react": "^18.2.0",
"react-chartjs-2": "^4.2.0",
"react-clipboard.js": "^2.0.16",
"react-data-table-component": "^7.5.2",
"react-dom": "^18.1.0",
@ -83,7 +81,6 @@
"@svgr/webpack": "^6.2.1",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0",
"@types/chart.js": "^2.9.37",
"@types/js-cookie": "^3.0.2",
"@types/loadable__component": "^5.13.4",
"@types/lodash.debounce": "^4.0.7",

View File

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

View File

@ -12,14 +12,11 @@ export interface AppConfig {
chainIdsSupported: number[]
marketFeeAddress: string
publisherMarketOrderFee: string
publisherMarketPoolSwapFee: string
publisherMarketFixedSwapFee: string
consumeMarketOrderFee: string
consumeMarketPoolSwapFee: string
consumeMarketFixedSwapFee: string
currencies: string[]
allowFixedPricing: string
allowDynamicPricing: string
allowFreePricing: string
defaultPrivacyPolicySlug: 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,
ReactNode
} from 'react'
import {
getPoolSharesData,
getUserSales,
getUserTokenOrders
} from '@utils/subgraph'
import { getUserSales, getUserTokenOrders } from '@utils/subgraph'
import { useUserPreferences } from './UserPreferences'
import { PoolShares_poolShares as PoolShare } from '../@types/subgraph/PoolShares'
import { Asset, LoggerInstance } from '@oceanprotocol/lib'
import { getDownloadAssets, getPublishedAssets } from '@utils/aquarius'
import { accountTruncate } from '@utils/web3'
@ -24,8 +19,6 @@ import { useMarketMetadata } from './MarketMetadata'
interface ProfileProviderValue {
profile: Profile
poolShares: PoolShare[]
isPoolSharesLoading: boolean
assets: Asset[]
assetsTotal: number
isEthAddress: boolean
@ -121,55 +114,6 @@ function ProfileProvider({
}
}, [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
//
@ -300,8 +244,6 @@ function ProfileProvider({
<ProfileContext.Provider
value={{
profile,
poolShares,
isPoolSharesLoading,
assets,
assetsTotal,
isEthAddress,

View File

@ -1,5 +1,9 @@
import { Asset } from '@oceanprotocol/lib'
interface AssetExtended extends Asset {
accessDetails?: AccessDetails
// declaring into global scope to be able to use this as
// 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'
/**
* @interface OrderPriceAndFee
* @prop {string} price total price including fees
* @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
* @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} consumeMarketOrderFee fee received by the market where the asset is ordered. 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} 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} liquidityProviderSwapFee fee received by the liquidity providers of the pool. It is a percentage ( ex 50% means liquidityProviderSwapFee=0.5)
* @prop {ProviderFees} providerFee received from provider
* @prop {string} opcFee ocean protocol community fee, Absolute value based on the configured percentage
*/
interface OrderPriceAndFees {
price: string
publisherMarketOrderFee: string
publisherMarketPoolSwapFee: string
publisherMarketFixedSwapFee: string
consumeMarketOrderFee: string
consumeMarketPoolSwapFee: string
consumeMarketFixedSwapFee: string
liquidityProviderSwapFee: string
providerFee: ProviderFees
opcFee: string
}
// declaring into global scope to be able to use this as
// ambiant types despite the above imports
declare global {
/**
* @interface OrderPriceAndFee
* @prop {string} price total price including fees
* @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} publisherMarketFixedSwapFee fee received by the market where the asset was published on any swap (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} 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 {string} opcFee ocean protocol community fee, Absolute value based on the configured percentage
*/
interface OrderPriceAndFees {
price: string
publisherMarketOrderFee: string
publisherMarketFixedSwapFee: string
consumeMarketOrderFee: string
consumeMarketFixedSwapFee: string
providerFee: ProviderFees
opcFee: string
}
/**
* @interface AccessDetails
* @prop {'dynamic' | 'fixed' | 'free' | ''} type
* @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 {TokenInfo} baseToken
* @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} 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} publisherMarketOrderFee this is here just because it's more efficient, it's allready in the query
* @prop {FeeInfo} feeInfo values of the relevant fees
*/
interface AccessDetails {
type: 'dynamic' | 'fixed' | 'free' | ''
price: string
addressOrId: string
baseToken: TokenInfo
datatoken: TokenInfo
isPurchasable?: boolean
isOwned: bool
validOrderTx: string
publisherMarketOrderFee: string
}
/**
* @interface AccessDetails
* @prop {'fixed' | 'free' | 'NOT_SUPPORTED'} type
* @prop {string} price can be either spotPrice/rate
* @prop {string} addressOrId for fixed/free this is an id.
* @prop {TokenInfo} baseToken
* @prop {TokenInfo} datatoken
* @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 {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 {FeeInfo} feeInfo values of the relevant fees
*/
interface AccessDetails {
type: 'fixed' | 'free' | 'NOT_SUPPORTED'
price: string
addressOrId: string
baseToken: TokenInfo
datatoken: TokenInfo
isPurchasable?: boolean
isOwned: bool
validOrderTx: string
publisherMarketOrderFee: string
}
interface PriceOptions {
price: number
amountDataToken: number
amountOcean: number
type: 'dynamic' | 'fixed' | 'free' | ''
weightOnDataToken: string
weightOnOcean: string
// easier to keep this as number for Yup input validation
swapFee: number
freeAgreement: boolean
interface PricePublishOptions {
price: number
type: 'fixed' | 'free'
freeAgreement: boolean
}
}

View File

@ -1,8 +1,3 @@
interface PoolBalance {
baseToken: string
datatoken: string
}
interface UserBalance {
eth: 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,
ProviderInstance
} from '@oceanprotocol/lib'
import { AssetExtended } from 'src/@types/AssetExtended'
import { calcInGivenOut } from './pool'
import { getFixedBuyPrice } from './fixedRateExchange'
import { AccessDetails, OrderPriceAndFees } from 'src/@types/Price'
import Decimal from 'decimal.js'
import { consumeMarketOrderFee } from '../../app.config'
import Web3 from 'web3'
import {
consumeMarketOrderFee,
publisherMarketOrderFee
} from '../../app.config'
const tokensPriceQuery = gql`
query TokensPriceQuery($datatokenIds: [ID!], $account: String) {
@ -75,22 +74,6 @@ const tokensPriceQuery = gql`
}
active
}
pools {
id
spotPrice
isFinalized
datatokenLiquidity
baseToken {
symbol
name
address
}
datatoken {
symbol
name
address
}
}
}
}
`
@ -147,22 +130,6 @@ const tokenPriceQuery = gql`
}
active
}
pools {
id
spotPrice
isFinalized
datatokenLiquidity
baseToken {
symbol
name
address
}
datatoken {
symbol
name
address
}
}
}
}
`
@ -173,6 +140,15 @@ function getAccessDetailsFromTokenPrice(
): 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) {
const order = tokenPrice.orders[0]
const reusedOrder = order?.reuses?.length > 0 ? order.reuses[0] : null
@ -198,7 +174,6 @@ function getAccessDetailsFromTokenPrice(
name: dispenser.token.name,
symbol: dispenser.token.symbol
}
return accessDetails
}
// checking for fixed price
@ -219,30 +194,8 @@ function getAccessDetailsFromTokenPrice(
name: fixed.datatoken.name,
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
}
@ -254,17 +207,13 @@ function getAccessDetailsFromTokenPrice(
export async function getOrderPriceAndFees(
asset: AssetExtended,
accountId?: string,
paramsForPool?: CalcInGivenOutParams,
providerFees?: ProviderFees
): Promise<OrderPriceAndFees> {
const orderPriceAndFee = {
price: '0',
publisherMarketOrderFee:
asset?.accessDetails?.publisherMarketOrderFee || '0',
publisherMarketPoolSwapFee: '0',
publisherMarketOrderFee: publisherMarketOrderFee || '0',
publisherMarketFixedSwapFee: '0',
consumeMarketOrderFee: consumeMarketOrderFee || '0',
consumeMarketPoolSwapFee: '0',
consumeMarketFixedSwapFee: '0',
providerFee: {
providerFeeAmount: '0'
@ -273,7 +222,6 @@ export async function getOrderPriceAndFees(
} as OrderPriceAndFees
// fetch provider fee
const initializeData =
!providerFees &&
(await ProviderInstance.initialize(
@ -286,27 +234,12 @@ export async function getOrderPriceAndFees(
orderPriceAndFee.providerFee = providerFees || initializeData.providerFee
// fetch price and swap fees
switch (asset?.accessDetails?.type) {
case 'dynamic': {
const poolPrice = calcInGivenOut(paramsForPool)
orderPriceAndFee.price = poolPrice.tokenAmount
orderPriceAndFee.liquidityProviderSwapFee =
poolPrice.liquidityProviderSwapFeeAmount
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
}
if (asset?.accessDetails?.type === '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
}
// 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?.publisherMarketOrderFee || 0))
.toString()
return orderPriceAndFee
}

View File

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

View File

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

View File

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

View File

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

View File

@ -10,21 +10,17 @@ import {
ProviderFees,
ProviderInstance
} from '@oceanprotocol/lib'
import { AssetExtended } from 'src/@types/AssetExtended'
import Web3 from 'web3'
import { getOceanConfig } from './ocean'
import { TransactionReceipt } from 'web3-eth'
import { OrderPriceAndFees } from 'src/@types/Price'
import {
marketFeeAddress,
consumeMarketOrderFee,
consumeMarketFixedSwapFee
} from '../../app.config'
import { buyDtFromPool } from './pool'
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 asset
* @param orderPriceAndFees
@ -102,17 +98,6 @@ export async function order(
return tx
}
case 'dynamic': {
const tx = await datatoken.startOrder(
asset.accessDetails.datatoken.address,
accountId,
computeConsumerAddress || accountId,
0,
providerFees || initializeData.providerFee
)
return tx
}
case 'free': {
const tx = await datatoken.buyFromDispenserAndOrder(
asset.services[0].datatokenAddress,
@ -191,14 +176,6 @@ async function startOrder(
initializeData: ProviderComputeInitialize,
computeConsumerAddress?: string
): 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(
web3,
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,
ProviderInstance
} from '@oceanprotocol/lib'
import { AssetExtended } from 'src/@types/AssetExtended'
import Web3 from 'web3'
import { getValidUntilTime } from './compute'

View File

@ -1,23 +1,11 @@
import { gql, OperationResult, TypedDocumentNode, OperationContext } from 'urql'
import { Asset, LoggerInstance } from '@oceanprotocol/lib'
import { LoggerInstance } from '@oceanprotocol/lib'
import { getUrqlClientInstance } from '@context/UrqlProvider'
import { getOceanConfig } from './ocean'
import { AssetPoolPrice } from '../@types/subgraph/AssetPoolPrice'
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 { UserSalesQuery as UsersSalesList } from '../@types/subgraph/UserSalesQuery'
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'
export interface UserLiquidity {
price: string
@ -28,24 +16,6 @@ export interface PriceList {
[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`
query AssetPreviousOrder($id: String!, $account: String!) {
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`
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`
query OpcFeesQuery($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(
accountId: string,
chainIds: number[]

View File

@ -6,9 +6,6 @@ import classNames from 'classnames/bind'
import Loader from '@shared/atoms/Loader'
import { useUserPreferences } from '@context/UserPreferences'
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 { useWeb3 } from '@context/Web3'
@ -23,7 +20,7 @@ function LoaderArea() {
}
declare type AssetListProps = {
assets: Asset[]
assets: AssetExtended[]
showPagination: boolean
page?: number
totalPages?: number
@ -51,6 +48,7 @@ export default function AssetList({
useEffect(() => {
if (!assets) return
setAssetsWithPrices(assets as AssetExtended[])
setLoading(false)
async function fetchPrices() {
@ -58,7 +56,7 @@ export default function AssetList({
assets,
accountId || ''
)
if (!isMounted()) return
if (!isMounted() || !assetsWithPrices) return
setAssetsWithPrices([...assetsWithPrices])
}
fetchPrices()

View File

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

View File

@ -10,7 +10,6 @@ interface ButtonBuyProps {
hasDatatoken: boolean
dtSymbol: string
dtBalance: string
datasetLowPoolLiquidity: boolean
assetType: string
assetTimeout: string
isConsumable: boolean
@ -19,7 +18,6 @@ interface ButtonBuyProps {
hasDatatokenSelectedComputeAsset?: boolean
dtSymbolSelectedComputeAsset?: string
dtBalanceSelectedComputeAsset?: string
selectedComputeAssetLowPoolLiquidity?: boolean
selectedComputeAssetType?: string
isBalanceSufficient: boolean
isLoading?: boolean
@ -39,7 +37,6 @@ function getConsumeHelpText(
dtSymbol: string,
hasDatatoken: boolean,
hasPreviousOrder: boolean,
lowPoolLiquidity: boolean,
assetType: string,
isConsumable: boolean,
isBalanceSufficient: boolean,
@ -52,11 +49,9 @@ function getConsumeHelpText(
? `You bought this ${assetType} already allowing you to use it without paying again.`
: hasDatatoken
? `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
? '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
}
@ -65,16 +60,14 @@ function getComputeAssetHelpText(
hasDatatoken: boolean,
dtSymbol: string,
dtBalance: string,
lowPoolLiquidity: boolean,
assetType: string,
isConsumable: boolean,
consumableFeedback: string,
isBalanceSufficient: boolean,
hasPreviousOrderSelectedComputeAsset?: boolean,
hasDatatokenSelectedComputeAsset?: boolean,
assetType?: string,
dtSymbolSelectedComputeAsset?: string,
dtBalanceSelectedComputeAsset?: string,
selectedComputeAssettLowPoolLiquidity?: boolean,
selectedComputeAssetType?: string,
isAlgorithmConsumable?: boolean,
hasProviderFee?: boolean
@ -84,12 +77,12 @@ function getComputeAssetHelpText(
dtSymbol,
hasDatatoken,
hasPreviousOrder,
lowPoolLiquidity,
assetType,
isConsumable,
isBalanceSufficient,
consumableFeedback
)
const computeAlgoHelpText =
(!dtSymbolSelectedComputeAsset && !dtBalanceSelectedComputeAsset) ||
isConsumable === false ||
@ -99,19 +92,13 @@ function getComputeAssetHelpText(
? `You already bought the selected ${selectedComputeAssetType}, allowing you to use it without paying again.`
: hasDatatokenSelectedComputeAsset
? `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
? ''
: `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
? '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.'
const computeHelpText = selectedComputeAssettLowPoolLiquidity
? computeAlgoHelpText
: lowPoolLiquidity
? computeAssetHelpText
: `${computeAssetHelpText} ${computeAlgoHelpText} ${providerFeeHelpText}`
const computeHelpText = `${computeAssetHelpText} ${computeAlgoHelpText} ${providerFeeHelpText}`
return computeHelpText
}
@ -122,7 +109,6 @@ export default function ButtonBuy({
hasDatatoken,
dtSymbol,
dtBalance,
datasetLowPoolLiquidity,
assetType,
assetTimeout,
isConsumable,
@ -132,7 +118,6 @@ export default function ButtonBuy({
hasDatatokenSelectedComputeAsset,
dtSymbolSelectedComputeAsset,
dtBalanceSelectedComputeAsset,
selectedComputeAssetLowPoolLiquidity,
selectedComputeAssetType,
onClick,
stepText,
@ -180,7 +165,6 @@ export default function ButtonBuy({
dtSymbol,
hasDatatoken,
hasPreviousOrder,
datasetLowPoolLiquidity,
assetType,
isConsumable,
isBalanceSufficient,
@ -191,16 +175,14 @@ export default function ButtonBuy({
hasDatatoken,
dtSymbol,
dtBalance,
datasetLowPoolLiquidity,
assetType,
isConsumable,
consumableFeedback,
isBalanceSufficient,
hasPreviousOrderSelectedComputeAsset,
hasDatatokenSelectedComputeAsset,
assetType,
dtSymbolSelectedComputeAsset,
dtBalanceSelectedComputeAsset,
selectedComputeAssetLowPoolLiquidity,
selectedComputeAssetType,
isAlgorithmConsumable,
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);
}
.removeTvlPadding {
padding-left: 0 !important;
}
/* fiat currency symbol */
.conversion strong span {
font-weight: var(--font-weight-base);

View File

@ -33,16 +33,13 @@ export default function PriceUnit({
return (
<div className={`${styles.price} ${styles[size]} ${className}`}>
{type && type === 'free' ? (
<div> Free </div>
{type === 'free' ? (
<div>Free</div>
) : (
<>
<div>
{Number.isNaN(Number(price)) ? '-' : formatPrice(price, locale)}{' '}
<span className={styles.symbol}>{symbol}</span>
{type && type === 'dynamic' && (
<Badge label="pool" className={styles.badge} />
)}
</div>
{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 styles from './index.module.css'
import Loader from '../atoms/Loader'
import Tooltip from '../atoms/Tooltip'
import PriceUnit from './PriceUnit'
import { AccessDetails, OrderPriceAndFees } from 'src/@types/Price'
export default function Price({
accessDetails,
@ -18,7 +14,10 @@ export default function Price({
conversion?: boolean
size?: 'small' | 'mini' | 'large'
}): ReactElement {
return accessDetails?.price || accessDetails?.type === 'free' ? (
const isSupported =
accessDetails?.type === 'fixed' || accessDetails?.type === 'free'
return isSupported ? (
<PriceUnit
price={`${orderPriceAndFees?.price || accessDetails?.price}`}
symbol={accessDetails.baseToken?.symbol}
@ -27,18 +26,5 @@ export default function Price({
conversion={conversion}
type={accessDetails.type}
/>
) : !accessDetails || accessDetails?.type === '' ? (
<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..." />
)
) : null
}

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 { accountId } = useWeb3()
const { isInPurgatory, purgatoryData } = useAccountPurgatory(accountId)
function openInNewTab() {
window
.open(
'https://blog.oceanprotocol.com/how-to-publish-a-data-nft-f58ad2a622a9',
'_blank'
)
.focus()
}
return (
<div className={styles.app}>
{siteContent?.announcement !== '' && (
<AnnouncementBanner
text={siteContent?.announcement}
action={{
name: 'Explore OceanONDA V4.',
style: 'link',
handleAction: openInNewTab
}}
/>
<AnnouncementBanner text={siteContent?.announcement} />
)}
<Header />

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -33,19 +33,14 @@ import {
} from '@utils/compute'
import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection'
import AlgorithmDatasetsListForCompute from './AlgorithmDatasetsListForCompute'
import AssetActionHistoryTable from '../AssetActionHistoryTable'
import ComputeHistory from './History'
import ComputeJobs from '../../../Profile/History/ComputeJobs'
import { useCancelToken } from '@hooks/useCancelToken'
import { Decimal } from 'decimal.js'
import { useAbortController } from '@hooks/useAbortController'
import { getOrderPriceAndFees } from '@utils/accessDetailsAndPricing'
import { OrderPriceAndFees } from 'src/@types/Price'
import { handleComputeOrder } from '@utils/order'
import { AssetExtended } from 'src/@types/AssetExtended'
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 { initializeProviderForCompute } from '@utils/provider'
@ -63,8 +58,6 @@ export default function Compute({
consumableFeedback?: string
}): ReactElement {
const { accountId, web3 } = useWeb3()
const { getOpcFeeForToken } = useMarketMetadata()
const { poolData } = usePool()
const newAbortController = useAbortController()
const newCancelToken = useCancelToken()
@ -108,6 +101,8 @@ export default function Compute({
!hasAlgoAssetDatatoken &&
!isConsumableaAlgorithmPrice)
const isUnsupportedPricing = asset?.accessDetails?.type === 'NOT_SUPPORTED'
async function checkAssetDTBalance(asset: DDO): Promise<boolean> {
if (!asset?.services[0].datatokenAddress) return
const web3 = await getDummyWeb3(asset?.chainId)
@ -168,26 +163,9 @@ export default function Compute({
asset.metadata.type
)[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(
asset,
ZERO_ADDRESS,
poolParams,
initializedProvider?.datasets?.[0]?.providerFee
)
if (!datasetPriceAndFees)
@ -208,32 +186,9 @@ export default function Compute({
selectedAlgorithmAsset?.metadata?.type
)[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(
selectedAlgorithmAsset,
ZERO_ADDRESS,
algoPoolParams,
initializedProvider.algorithm.providerFee
)
if (!algorithmOrderPriceAndFees)
@ -248,11 +203,11 @@ export default function Compute({
}
useEffect(() => {
if (!asset?.accessDetails || !accountId) return
if (!asset?.accessDetails || !accountId || isUnsupportedPricing) return
setIsConsumablePrice(asset?.accessDetails?.isPurchasable)
setValidOrderTx(asset?.accessDetails?.validOrderTx)
}, [asset?.accessDetails, accountId])
}, [asset?.accessDetails, accountId, isUnsupportedPricing])
useEffect(() => {
if (!selectedAlgorithmAsset?.accessDetails || !accountId) return
@ -276,7 +231,8 @@ export default function Compute({
}, [selectedAlgorithmAsset, accountId])
useEffect(() => {
if (!asset) return
if (!asset?.accessDetails || isUnsupportedPricing) return
getAlgorithmsForAsset(asset, newCancelToken()).then((algorithmsAssets) => {
setDdoAlgorithmList(algorithmsAssets)
getAlgorithmAssetSelectionList(asset, algorithmsAssets).then(
@ -285,7 +241,7 @@ export default function Compute({
}
)
})
}, [asset])
}, [asset, isUnsupportedPricing])
// Output errors in toast UI
useEffect(() => {
@ -323,13 +279,7 @@ export default function Compute({
selectedAlgorithmAsset.accessDetails.baseToken?.symbol,
selectedAlgorithmAsset.accessDetails.datatoken?.symbol,
selectedAlgorithmAsset.metadata.type
)[
selectedAlgorithmAsset.accessDetails?.type === 'fixed'
? 2
: selectedAlgorithmAsset.accessDetails?.type === 'dynamic'
? 1
: 3
]
)[selectedAlgorithmAsset.accessDetails?.type === 'fixed' ? 2 : 3]
)
const algorithmOrderTx = await handleComputeOrder(
@ -348,13 +298,7 @@ export default function Compute({
asset.accessDetails.baseToken?.symbol,
asset.accessDetails.datatoken?.symbol,
asset.metadata.type
)[
asset.accessDetails?.type === 'fixed'
? 2
: asset.accessDetails?.type === 'dynamic'
? 1
: 3
]
)[asset.accessDetails?.type === 'fixed' ? 2 : 3]
)
const datasetOrderTx = await handleComputeOrder(
web3,
@ -406,17 +350,37 @@ export default function Compute({
return (
<>
<div className={styles.info}>
<div
className={`${styles.info} ${
isUnsupportedPricing ? styles.warning : null
}`}
>
<FileIcon file={file} isLoading={fileIsLoading} small />
<Price accessDetails={asset?.accessDetails} conversion />
</div>
{asset.metadata.type === 'algorithm' ? (
<>
{isUnsupportedPricing ? (
<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"
/>
) : (
<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
algorithmDid={asset.id}
asset={asset}
@ -442,7 +406,6 @@ export default function Compute({
hasPreviousOrder={validOrderTx !== undefined}
hasDatatoken={hasDatatoken}
dtBalance={dtBalance}
datasetLowPoolLiquidity={!isConsumablePrice}
assetType={asset?.metadata.type}
assetTimeout={secondsToString(asset?.services[0].timeout)}
hasPreviousOrderSelectedComputeAsset={
@ -480,13 +443,13 @@ export default function Compute({
)}
</footer>
{accountId && asset?.accessDetails?.datatoken && (
<AssetActionHistoryTable title="Your Compute Jobs">
<ComputeHistory title="Your Compute Jobs">
<ComputeJobs
minimal
assetChainIds={[asset?.chainId]}
refetchJobs={refetchJobs}
/>
</AssetActionHistoryTable>
</ComputeHistory>
)}
</>
)

View File

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

View File

@ -9,16 +9,13 @@ import AlgorithmDatasetsListForCompute from './Compute/AlgorithmDatasetsListForC
import styles from './Download.module.css'
import { FileInfo, LoggerInstance, ZERO_ADDRESS } from '@oceanprotocol/lib'
import { order } from '@utils/order'
import { AssetExtended } from 'src/@types/AssetExtended'
import { buyDtFromPool } from '@utils/pool'
import { downloadFile } from '@utils/provider'
import { getOrderFeedback } from '@utils/feedback'
import { getOrderPriceAndFees } from '@utils/accessDetailsAndPricing'
import { OrderPriceAndFees } from 'src/@types/Price'
import { toast } from 'react-toastify'
import { useIsMounted } from '@hooks/useIsMounted'
import { usePool } from '@context/Pool'
import { useMarketMetadata } from '@context/MarketMetadata'
import Alert from '@shared/atoms/Alert'
export default function Download({
asset,
@ -38,7 +35,6 @@ export default function Download({
const { accountId, web3 } = useWeb3()
const { getOpcFeeForToken } = useMarketMetadata()
const { isInPurgatory, isAssetNetwork } = useAsset()
const { poolData } = usePool()
const isMounted = useIsMounted()
const [isDisabled, setIsDisabled] = useState(true)
@ -50,63 +46,50 @@ export default function Download({
const [orderPriceAndFees, setOrderPriceAndFees] =
useState<OrderPriceAndFees>()
useEffect(() => {
if (!asset?.accessDetails) return
const isUnsupportedPricing = asset?.accessDetails?.type === 'NOT_SUPPORTED'
asset?.accessDetails?.isOwned && setIsOwned(asset?.accessDetails?.isOwned)
asset?.accessDetails?.validOrderTx &&
useEffect(() => {
if (!asset?.accessDetails || isUnsupportedPricing) return
asset.accessDetails.isOwned && setIsOwned(asset?.accessDetails?.isOwned)
asset.accessDetails.validOrderTx &&
setValidOrderTx(asset?.accessDetails?.validOrderTx)
// get full price and fees
async function init() {
if (
asset?.accessDetails?.addressOrId === ZERO_ADDRESS ||
asset?.accessDetails?.type === 'free' ||
(!poolData && asset?.accessDetails?.type === 'dynamic') ||
asset.accessDetails.addressOrId === ZERO_ADDRESS ||
asset.accessDetails.type === 'free' ||
isLoading
)
return
!orderPriceAndFees && setIsLoading(true)
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
)
const _orderPriceAndFees = await getOrderPriceAndFees(asset, ZERO_ADDRESS)
setOrderPriceAndFees(_orderPriceAndFees)
!orderPriceAndFees && setIsLoading(false)
}
init()
/**
* we listen to the assets' changes to get the most updated price
* based on the asset and the poolData's information.
* Not adding isLoading and getOpcFeeForToken because we set these here. It is a compromise
*/
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [asset, accountId, poolData, getOpcFeeForToken])
}, [asset, accountId, getOpcFeeForToken, isUnsupportedPricing])
useEffect(() => {
setHasDatatoken(Number(dtBalance) >= 1)
}, [dtBalance])
useEffect(() => {
if (!isMounted || !accountId || !asset?.accessDetails) return
if (
!isMounted ||
!accountId ||
!asset?.accessDetails ||
isUnsupportedPricing
)
return
/**
* disabled in these cases:
@ -128,7 +111,8 @@ export default function Download({
isAssetNetwork,
hasDatatoken,
accountId,
isOwned
isOwned,
isUnsupportedPricing
])
async function handleOrderOrDownload() {
@ -138,30 +122,18 @@ export default function Download({
if (isOwned) {
setStatusText(
getOrderFeedback(
asset.accessDetails?.baseToken?.symbol,
asset.accessDetails?.datatoken?.symbol
asset.accessDetails.baseToken?.symbol,
asset.accessDetails.datatoken?.symbol
)[3]
)
await downloadFile(web3, asset, accountId, validOrderTx)
} 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(
getOrderFeedback(
asset.accessDetails.baseToken?.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)
if (!orderTx) {
@ -188,7 +160,6 @@ export default function Download({
hasDatatoken={hasDatatoken}
dtSymbol={asset?.datatokens[0]?.symbol}
dtBalance={dtBalance}
datasetLowPoolLiquidity={!asset.accessDetails?.isPurchasable}
onClick={handleOrderOrDownload}
assetTimeout={secondsToString(asset.services[0].timeout)}
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 (
<aside className={styles.consume}>
<div className={styles.info}>
<div className={styles.filewrapper}>
<FileIcon file={file} isLoading={fileIsLoading} />
</div>
<div className={styles.pricewrapper}>
<Price
accessDetails={asset.accessDetails}
orderPriceAndFees={orderPriceAndFees}
conversion
size="large"
/>
{!isInPurgatory && <PurchaseButton />}
<FileIcon file={file} isLoading={fileIsLoading} small />
</div>
<AssetAction asset={asset} />
</div>
{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 Tabs, { TabsItem } from '@shared/atoms/Tabs'
import { compareAsBN } from '@utils/numbers'
import Pool from './Pool'
import Trade from './Trade'
import { useAsset } from '@context/Asset'
import { useWeb3 } from '@context/Web3'
import Web3Feedback from '@shared/Web3Feedback'
@ -16,8 +14,6 @@ import { useIsMounted } from '@hooks/useIsMounted'
import styles from './index.module.css'
import { useFormikContext } from 'formik'
import { FormPublishData } from 'src/components/Publish/_types'
import { AssetExtended } from 'src/@types/AssetExtended'
import PoolProvider from '@context/Pool'
import AssetStats from './AssetStats'
export default function AssetActions({
@ -148,18 +144,13 @@ export default function AssetActions({
const tabs: TabsItem[] = [{ title: 'Use', content: UseContent }]
asset?.accessDetails?.type === 'dynamic' &&
tabs.push({ title: 'Pool', content: <Pool /> })
return (
<>
<PoolProvider>
<Tabs items={tabs} className={styles.actions} />
<Web3Feedback
networkId={asset?.chainId}
isAssetNetwork={isAssetNetwork}
/>
</PoolProvider>
<Tabs items={tabs} className={styles.actions} />
<Web3Feedback
networkId={asset?.chainId}
isAssetNetwork={isAssetNetwork}
/>
</>
)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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