mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Remove AMM Pools (#1614)
This commit is contained in:
parent
8edd61bfa7
commit
eb29c4ce3b
@ -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"
|
||||
|
||||
#
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)."
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
},
|
||||
{
|
||||
|
@ -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
46
package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
@ -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
|
||||
}
|
@ -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'
|
||||
}
|
@ -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
|
@ -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,
|
||||
|
8
src/@types/AssetExtended.d.ts
vendored
8
src/@types/AssetExtended.d.ts
vendored
@ -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
110
src/@types/Price.d.ts
vendored
@ -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
|
||||
}
|
||||
}
|
||||
|
5
src/@types/TokenBalance.d.ts
vendored
5
src/@types/TokenBalance.d.ts
vendored
@ -1,8 +1,3 @@
|
||||
interface PoolBalance {
|
||||
baseToken: string
|
||||
datatoken: string
|
||||
}
|
||||
|
||||
interface UserBalance {
|
||||
eth: string
|
||||
ocean: string
|
||||
|
9
src/@types/Utils.d.ts
vendored
9
src/@types/Utils.d.ts
vendored
@ -1,9 +0,0 @@
|
||||
interface CalcInGivenOutParams {
|
||||
tokenInLiquidity: string
|
||||
tokenOutLiquidity: string
|
||||
tokenOutAmount: string
|
||||
opcFee: string
|
||||
lpSwapFee: string
|
||||
publishMarketSwapFee: string
|
||||
consumeMarketSwapFee: string
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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'
|
||||
|
@ -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'
|
||||
|
||||
|
@ -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'
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
@ -8,7 +8,6 @@ import {
|
||||
ProviderComputeInitializeResults,
|
||||
ProviderInstance
|
||||
} from '@oceanprotocol/lib'
|
||||
import { AssetExtended } from 'src/@types/AssetExtended'
|
||||
import Web3 from 'web3'
|
||||
import { getValidUntilTime } from './compute'
|
||||
|
||||
|
@ -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[]
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -1,3 +0,0 @@
|
||||
.titleText {
|
||||
white-space: pre;
|
||||
}
|
@ -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
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
.time {
|
||||
color: var(--color-secondary);
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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);
|
||||
|
@ -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} />}
|
||||
</>
|
||||
|
@ -1,4 +0,0 @@
|
||||
.empty {
|
||||
color: var(--color-secondary);
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
@ -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}`} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
@ -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 />
|
||||
|
||||
|
@ -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'))
|
||||
|
@ -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}
|
||||
|
@ -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;
|
@ -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
|
||||
}: {
|
@ -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
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
@ -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 {
|
||||
|
@ -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' && (
|
||||
|
@ -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;
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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;
|
||||
}
|
@ -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>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
@ -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);
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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()
|
||||
}
|
@ -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';
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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;
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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];
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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;
|
||||
}
|
@ -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 />
|
||||
</>
|
||||
)
|
||||
}
|
@ -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>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
.alertWrap {
|
||||
min-height: 320px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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;
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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);
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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;
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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);
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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);
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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
|
@ -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
|
||||
}
|
@ -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} />
|
||||
}
|
@ -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;
|
||||
}
|
@ -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>
|
||||
)
|
||||
}
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -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({
|
||||
|
@ -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({
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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'
|
||||
|
||||
|
@ -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'
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user