1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-12-02 05:57:29 +01:00

Merge branch 'feature/v4-c2d' into 'fix/compute-jobs'

This commit is contained in:
Bogdan Fazakas 2022-04-04 15:22:18 +03:00
commit 2dc12a2542
14 changed files with 199 additions and 35 deletions

View File

@ -56,7 +56,13 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
// const [fetchInterval, setFetchInterval] = useState<NodeJS.Timeout>()
const fetchAllData = useCallback(async () => {
if (!asset?.chainId || !asset?.accessDetails?.addressOrId || !owner) return
if (
!accountId ||
!asset?.chainId ||
!asset?.accessDetails?.addressOrId ||
!owner
)
return
const response = await getPoolData(
asset.chainId,
@ -64,6 +70,7 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
owner,
accountId || ''
)
if (!response) return
setPoolData(response.poolData)

View File

@ -1 +1 @@
export const MAX_DECIMALS = 5
export const MAX_DECIMALS = 6

View File

@ -65,7 +65,7 @@ export default function FilesInput(props: InputProps): ReactElement {
return (
<>
{field.value[0].valid !== undefined ? (
{field?.value && field?.value[0]?.valid !== undefined ? (
<FileInfo file={field.value[0]} handleClose={handleClose} />
) : (
<UrlInput

View File

@ -23,7 +23,7 @@ const txHistoryQueryByPool = gql`
poolTransactions(
orderBy: timestamp
orderDirection: desc
where: { pool: $pool }
where: { pool: $pool, user: $user }
first: 1000
) {
baseToken {
@ -40,6 +40,9 @@ const txHistoryQueryByPool = gql`
tx
timestamp
pool {
datatoken {
id
}
id
}
}
@ -67,6 +70,9 @@ const txHistoryQuery = gql`
tx
timestamp
pool {
datatoken {
id
}
id
}
}
@ -124,7 +130,7 @@ export default function PoolTransactions({
accountId
}: {
poolAddress?: string
poolChainId?: number[]
poolChainId?: number
minimal?: boolean
accountId: string
}): ReactElement {
@ -146,7 +152,7 @@ export default function PoolTransactions({
const result = await fetchDataForMultipleChains(
poolAddress ? txHistoryQueryByPool : txHistoryQuery,
variables,
poolAddress ? poolChainId : chainIds
poolAddress ? [poolChainId] : chainIds
)
for (let i = 0; i < result.length; i++) {
@ -166,24 +172,24 @@ export default function PoolTransactions({
return
}
const poolTransactions: PoolTransaction[] = []
const dtList: string[] = []
for (let i = 0; i < data.length; i++) {
dtList.push(data[i]?.datatoken?.address)
}
let dtList: string[] = []
dtList = [...new Set(data.map((item) => item.pool.datatoken.id))]
if (dtList.length === 0) {
setTransactions([])
setIsLoading(false)
return
}
const ddoList = await getAssetsFromDtList(dtList, chainIds, cancelToken)
const ddoList = !minimal
? await getAssetsFromDtList(dtList, chainIds, cancelToken)
: []
for (let i = 0; i < data.length; i++) {
poolTransactions.push({
...data[i],
networkId: getAsset(ddoList, data[i].datatoken.address).chainId,
asset: getAsset(ddoList, data[i].datatoken.address)
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(

View File

@ -26,6 +26,7 @@
.button:last-child {
margin-right: 0;
min-width: auto;
}
.button:hover,
@ -33,8 +34,8 @@
color: var(--brand-white);
background: var(--brand-grey-light);
text-decoration: none;
transform: translate3d(0, -0.05rem, 0);
box-shadow: 0 12px 30px 0 rgba(0, 0, 0, 0.1);
transform: translate3d(0, -0.05rem, 0);
}
.button:active {

View File

@ -10,7 +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 { AccessDetails } from 'src/@types/Price'
import { AccessDetails, OrderPriceAndFees } from 'src/@types/Price'
import {
getAccessDetailsForAssets,
getAccessDetails
@ -40,7 +40,9 @@ export default function FormStartCompute({
selectedComputeAssetTimeout,
stepText,
isConsumable,
consumableFeedback
consumableFeedback,
datasetOrderPriceAndFees,
algoOrderPriceAndFees
}: {
algorithms: AssetSelectionAsset[]
ddoListAlgorithms: Asset[]
@ -65,11 +67,19 @@ export default function FormStartCompute({
stepText: string
isConsumable: boolean
consumableFeedback: string
datasetOrderPriceAndFees?: OrderPriceAndFees
algoOrderPriceAndFees?: OrderPriceAndFees
}): ReactElement {
const { isValid, values }: FormikContextType<{ algorithm: string }> =
useFormikContext()
const { asset, isAssetNetwork } = useAsset()
const [totalPrice, setTotalPrice] = useState(asset?.accessDetails?.price)
const [datasetOrderPrice, setDatasetOrderPrice] = useState(
asset?.accessDetails?.price
)
const [algoOrderPrice, setAlgoOrderPrice] = useState(
selectedAlgorithmAsset?.accessDetails?.price
)
const [isBalanceSufficient, setIsBalanceSufficient] = useState<boolean>(false)
const { accountId, balance } = useWeb3()
@ -106,12 +116,24 @@ export default function FormStartCompute({
useEffect(() => {
if (!asset?.accessDetails || !selectedAlgorithmAsset?.accessDetails) return
setDatasetOrderPrice(
datasetOrderPriceAndFees?.price || asset.accessDetails.price
)
setAlgoOrderPrice(
algoOrderPriceAndFees?.price ||
selectedAlgorithmAsset?.accessDetails.price
)
const priceDataset =
hasPreviousOrder || hasDatatoken ? 0 : Number(asset.accessDetails.price)
hasPreviousOrder || hasDatatoken
? 0
: Number(datasetOrderPriceAndFees?.price || asset.accessDetails.price)
const priceAlgo =
hasPreviousOrderSelectedComputeAsset || hasDatatokenSelectedComputeAsset
? 0
: Number(selectedAlgorithmAsset?.accessDetails.price)
: Number(
algoOrderPriceAndFees?.price ||
selectedAlgorithmAsset?.accessDetails.price
)
setTotalPrice((priceDataset + priceAlgo).toString())
}, [
@ -120,7 +142,9 @@ export default function FormStartCompute({
hasPreviousOrder,
hasDatatoken,
hasPreviousOrderSelectedComputeAsset,
hasDatatokenSelectedComputeAsset
hasDatatokenSelectedComputeAsset,
datasetOrderPriceAndFees,
algoOrderPriceAndFees
])
useEffect(() => {
@ -153,6 +177,8 @@ export default function FormStartCompute({
algorithmConsumeDetails={selectedAlgorithmAsset?.accessDetails}
symbol={oceanSymbol}
totalPrice={Number.parseFloat(totalPrice)}
datasetOrderPrice={datasetOrderPrice}
algoOrderPrice={algoOrderPrice}
/>
<ButtonBuy

View File

@ -15,6 +15,8 @@ interface PriceOutputProps {
hasDatatokenSelectedComputeAsset: boolean
algorithmConsumeDetails: AccessDetails
selectedComputeAssetTimeout: string
datasetOrderPrice?: number
algoOrderPrice?: number
}
function Row({
@ -62,7 +64,9 @@ export default function PriceOutput({
hasPreviousOrderSelectedComputeAsset,
hasDatatokenSelectedComputeAsset,
algorithmConsumeDetails,
selectedComputeAssetTimeout
selectedComputeAssetTimeout,
datasetOrderPrice,
algoOrderPrice
}: PriceOutputProps): ReactElement {
const { asset } = useAsset()
@ -76,14 +80,20 @@ export default function PriceOutput({
<Row
hasPreviousOrder={hasPreviousOrder}
hasDatatoken={hasDatatoken}
price={Number.parseFloat(asset?.accessDetails?.price)}
price={
datasetOrderPrice ||
Number.parseFloat(asset?.accessDetails?.price)
}
timeout={assetTimeout}
symbol={symbol}
/>
<Row
hasPreviousOrder={hasPreviousOrderSelectedComputeAsset}
hasDatatoken={hasDatatokenSelectedComputeAsset}
price={Number.parseFloat(algorithmConsumeDetails?.price)}
price={
algoOrderPrice ||
Number.parseFloat(algorithmConsumeDetails?.price)
}
timeout={selectedComputeAssetTimeout}
symbol={symbol}
sign="+"

View File

@ -83,13 +83,21 @@ export default function Compute({
const [isConsumablePrice, setIsConsumablePrice] = useState(true)
const [isAlgoConsumablePrice, setIsAlgoConsumablePrice] = useState(true)
const [computeStatusText, setComputeStatusText] = useState('')
const [datasetOrderPriceAndFees, setDatasetOrderPriceAndFees] =
useState<OrderPriceAndFees>()
const [isRequestingDataseOrderPrice, setIsRequestingDataseOrderPrice] =
useState(false)
const [algoOrderPriceAndFees, setAlgoOrderPriceAndFees] =
useState<OrderPriceAndFees>()
const [isRequestingAlgoOrderPrice, setIsRequestingAlgoOrderPrice] =
useState(false)
const isComputeButtonDisabled =
isJobStarting === true ||
file === null ||
(!validOrderTx && !hasDatatoken && !isConsumablePrice) ||
(!validAlgorithmOrderTx && !hasAlgoAssetDatatoken && !isAlgoConsumablePrice)
async function checkAssetDTBalance(asset: DDO) {
async function checkAssetDTBalance(asset: DDO): Promise<boolean> {
if (!asset?.services[0].datatokenAddress) return
const datatokenInstance = new Datatoken(web3)
const dtBalance = await datatokenInstance.balance(
@ -97,7 +105,9 @@ export default function Compute({
accountId
)
setAlgorithmDTBalance(new Decimal(dtBalance).toString())
setHasAlgoAssetDatatoken(Number(dtBalance) >= 1)
const hasAlgoDt = Number(dtBalance) >= 1
setHasAlgoAssetDatatoken(hasAlgoDt)
return hasAlgoDt
}
useEffect(() => {
@ -106,16 +116,56 @@ export default function Compute({
setIsConsumablePrice(asset?.accessDetails?.isPurchasable)
setIsOwned(asset?.accessDetails?.isOwned)
setValidOrderTx(asset?.accessDetails?.validOrderTx)
async function initDatasetPriceAndFees() {
if (
asset?.accessDetails?.addressOrId === ZERO_ADDRESS ||
asset?.accessDetails?.type === 'free'
)
return
setIsRequestingDataseOrderPrice(true)
setComputeStatusText('Calculating price including fees.')
const orderPriceAndFees = await getOrderPriceAndFees(asset, accountId)
setDatasetOrderPriceAndFees(orderPriceAndFees)
setIsRequestingDataseOrderPrice(false)
}
initDatasetPriceAndFees()
}, [asset?.accessDetails])
useEffect(() => {
if (!selectedAlgorithmAsset?.accessDetails || !accountId) return
checkAssetDTBalance(selectedAlgorithmAsset)
setIsConsumablePrice(selectedAlgorithmAsset?.accessDetails?.isPurchasable)
setIsAlgorithmOwned(selectedAlgorithmAsset?.accessDetails?.isOwned)
setValidAlgorithmOrderTx(
selectedAlgorithmAsset?.accessDetails?.validOrderTx
)
async function initAlgoPriceAndFees() {
if (
selectedAlgorithmAsset?.accessDetails?.addressOrId === ZERO_ADDRESS ||
selectedAlgorithmAsset?.accessDetails?.type === 'free'
)
return
setIsRequestingAlgoOrderPrice(true)
setComputeStatusText('Calculating price including fees.')
const orderPriceAndFees = await getOrderPriceAndFees(
selectedAlgorithmAsset,
accountId
)
setAlgoOrderPriceAndFees(orderPriceAndFees)
setIsRequestingAlgoOrderPrice(false)
}
async function initSelectedAlgo() {
const hasAlgoDt = await checkAssetDTBalance(selectedAlgorithmAsset)
!hasAlgoDt && (await initAlgoPriceAndFees())
}
initSelectedAlgo()
}, [selectedAlgorithmAsset])
useEffect(() => {
@ -406,7 +456,11 @@ export default function Compute({
ddoListAlgorithms={ddoAlgorithmList}
selectedAlgorithmAsset={selectedAlgorithmAsset}
setSelectedAlgorithm={setSelectedAlgorithmAsset}
isLoading={isJobStarting}
isLoading={
isJobStarting ||
isRequestingDataseOrderPrice ||
isRequestingAlgoOrderPrice
}
isComputeButtonDisabled={isComputeButtonDisabled}
hasPreviousOrder={validOrderTx !== undefined}
hasDatatoken={hasDatatoken}
@ -436,6 +490,8 @@ export default function Compute({
stepText={computeStatusText}
isConsumable={isConsumable}
consumableFeedback={consumableFeedback}
datasetOrderPriceAndFees={datasetOrderPriceAndFees}
algoOrderPriceAndFees={algoOrderPriceAndFees}
/>
</Formik>
)}

View File

@ -25,7 +25,7 @@ export function getOptions(
borderColor: isDarkMode ? `#41474e` : `#e2e2e2`,
callbacks: {
label: (tooltipItem: TooltipItem<any>) =>
`${formatPrice(`${tooltipItem.formattedValue}`, locale)} ${symbol}`
`${tooltipItem.formattedValue} ${symbol}`
}
}
},

View File

@ -31,13 +31,17 @@
.maximum {
position: absolute;
right: -2rem;
bottom: 2rem;
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;

View File

@ -67,11 +67,16 @@ export default function Remove({
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)
}
}
@ -80,8 +85,10 @@ export default function Remove({
if (!accountId || !poolTokens) return
async function getMax() {
const poolTokensAmount =
!poolTokens || poolTokens === '0' ? '1' : poolTokens
const maxTokensToRemoveFromPool = calcMaxExactOut(totalPoolTokens)
const poolTokensDecimal = new Decimal(poolTokens)
const poolTokensDecimal = new Decimal(poolTokensAmount)
const maxTokensToRemoveForUser = maxTokensToRemoveFromPool.greaterThan(
poolTokensDecimal
)
@ -105,6 +112,7 @@ export default function Remove({
tokenOutAddress,
newAmountPoolShares
)
setAmountOcean(newAmountOcean)
}, 150)
)
@ -116,6 +124,11 @@ export default function Remove({
}, [amountPoolShares, accountId, poolTokens, poolAddress, totalPoolTokens])
useEffect(() => {
if (!amountOcean || amountPercent === '0') {
setMinOceanAmount('0')
return
}
const minOceanAmount = new Decimal(amountOcean)
.mul(new Decimal(100).minus(new Decimal(slippage)))
.dividedBy(100)
@ -220,7 +233,12 @@ export default function Remove({
actionName={content.pool.remove.action}
action={handleRemoveLiquidity}
successMessage="Successfully removed liquidity."
isDisabled={!isAssetNetwork || amountOcean === '0'}
isDisabled={
!isAssetNetwork ||
amountPercent === '0' ||
amountOcean === '0' ||
poolTokens === '0'
}
txId={txId}
tokenAddress={tokenOutAddress}
tokenSymbol={tokenOutSymbol}

View File

@ -208,7 +208,7 @@ export default function Pool(): ReactElement {
<PoolTransactions
accountId={accountId}
poolAddress={asset?.accessDetails?.addressOrId}
poolChainId={[asset?.chainId]}
poolChainId={asset?.chainId}
minimal
/>
</AssetActionHistoryTable>

View File

@ -34,7 +34,13 @@ export default function Price({
<>
<div className={styles.grid}>
<div className={styles.form}>
<Input type="number" prefix="OCEAN" {...field} />
<Input
type="number"
min="1"
placeholder="0"
prefix="OCEAN"
{...field}
/>
<Error meta={meta} />
</div>
<div className={styles.datatoken}>

View File

@ -1,8 +1,10 @@
import { MAX_DECIMALS } from '@utils/constants'
import * as Yup from 'yup'
// TODO: conditional validation
// e.g. when algo is selected, Docker image is required
// hint, hint: https://github.com/jquense/yup#mixedwhenkeys-string--arraystring-builder-object--value-schema-schema-schema
const validationMetadata = {
type: Yup.string()
.matches(/dataset|algorithm/g, { excludeEmptyString: true })
@ -54,25 +56,53 @@ const validationService = {
})
}
const maxDecimalsValidation = new RegExp(
'^\\d+(\\.\\d{1,' + MAX_DECIMALS + '})?$'
)
const validationPricing = {
type: Yup.string()
.matches(/fixed|dynamic|free/g, { excludeEmptyString: true })
.required('Required'),
// https://github.com/jquense/yup#mixedwhenkeys-string--arraystring-builder-object--value-schema-schema-schema
price: Yup.number()
.min(1, (param: { min: number }) => `Must be more or equal to ${param.min}`)
.max(
1000000,
(param: { max: number }) => `Must be less than or equal to ${param.max}`
)
.test(
'maxDigitsAfterDecimal',
`Must have maximum ${MAX_DECIMALS} decimal digits`,
(param) => maxDecimalsValidation.test(param?.toString())
)
.required('Required'),
amountDataToken: Yup.number()
.min(50, (param) => `Must be more or equal to ${param.min}`)
.required('Required'),
amountOcean: Yup.number()
.min(50, (param) => `Must be more or equal to ${param.min}`)
.max(
1000000,
(param: { max: number }) => `Must be less than or equal to ${param.max}`
)
.test(
'maxDigitsAfterDecimal',
`Must have maximum ${MAX_DECIMALS} decimal digits`,
(param) => maxDecimalsValidation.test(param?.toString())
)
.required('Required'),
weightOnDataToken: Yup.string().required('Required'),
weightOnOcean: Yup.string().required('Required'),
swapFee: Yup.number()
.min(0.1, (param) => `Must be more or equal to ${param.min}`)
.max(10, 'Maximum is 10%')
.test(
'maxDigitsAfterDecimal',
`Must have maximum ${MAX_DECIMALS} decimal digits`,
(param) => maxDecimalsValidation.test(param?.toString())
)
.required('Required')
}