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

merge main

This commit is contained in:
Bogdan Fazakas 2022-07-22 06:41:40 +03:00
commit 2632e7ed0f
42 changed files with 273 additions and 819 deletions

View File

@ -14,7 +14,7 @@ jobs:
- uses: actions/setup-node@v2 - uses: actions/setup-node@v2
- run: npm ci --legacy-peer-deps - run: npm ci --legacy-peer-deps
- run: npm run build - run: npm run build:static
env: env:
NEXT_PUBLIC_INFURA_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_INFURA_PROJECT_ID }} NEXT_PUBLIC_INFURA_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_INFURA_PROJECT_ID }}

View File

@ -50,6 +50,8 @@ cd market
nvm use nvm use
npm install npm install
# in case of dependency errors, rather use:
# npm install --legacy-peer-deps
npm start npm start
``` ```

View File

@ -16,16 +16,6 @@
"marketplaceFee": "Goes to the marketplace owner that is hosting and providing the marketplace and is collected when downloading or using an asset in a compute job. In Ocean Market, it is treated as network revenue that goes to the Ocean community." "marketplaceFee": "Goes to the marketplace owner that is hosting and providing the marketplace and is collected when downloading or using an asset in a compute job. In Ocean Market, it is treated as network revenue that goes to the Ocean community."
} }
}, },
"dynamic": {
"title": "Dynamic",
"info": "Let's create a decentralized, automated market for your data set. The datatoken for this data set will be worth the entered amount of OCEAN. Additionally, you will provide liquidity into a Datatoken/OCEAN liquidity pool with Balancer.",
"tooltips": {
"poolInfo": "The liquidity pool provides the funds for traders to trade against. The price of the asset is determined by the ratio of OCEAN to datatokens.",
"swapFee": "Liquidity providers earn this fee on all pool trades, proportionally to their share of the pool. The fee is set by the creator of the pool and is used to incentivize liquidity providers to join the pool.",
"communityFee": "Goes to Ocean DAO for teams to improve the tools, build apps, do outreach, and more. A small fraction is used to burn OCEAN. This fee is collected when downloading or using an asset in a compute job.",
"marketplaceFee": "Goes to the marketplace owner that is hosting and providing the marketplace and is collected when downloading or using an asset in a compute job. In Ocean Market, it is treated as network revenue that goes to the Ocean community."
}
},
"free": { "free": {
"title": "Free", "title": "Free",
"info": "Set your data set as free. The datatoken for this data set will be given for free via creating a faucet.", "info": "Set your data set as free. The datatoken for this data set will be given for free via creating a faucet.",
@ -47,15 +37,6 @@
"approveSpecific": "Give the smart contract permission to spend your COIN which has to be done for each transaction. You can optionally set this to infinite in your user preferences.", "approveSpecific": "Give the smart contract permission to spend your COIN which has to be done for each transaction. You can optionally set this to infinite in your user preferences.",
"approveInfinite": "Give the smart contract permission to spend infinte amounts of your COIN so you have to do this only once. You can disable allowing infinite amounts in your user preferences." "approveInfinite": "Give the smart contract permission to spend infinte amounts of your COIN so you have to do this only once. You can disable allowing infinite amounts in your user preferences."
}, },
"add": {
"title": "Add Liquidity",
"output": {
"help": "Providing liquidity will earn you SWAPFEE% on every transaction in this pool, proportionally to your share of the pool.",
"titleIn": "You will receive",
"titleOut": "Pool conversion"
},
"action": "Supply"
},
"remove": { "remove": {
"title": "Remove Liquidity", "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.", "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.",

44
package-lock.json generated
View File

@ -13,7 +13,7 @@
"@coingecko/cryptoformat": "^0.5.4", "@coingecko/cryptoformat": "^0.5.4",
"@loadable/component": "^5.15.2", "@loadable/component": "^5.15.2",
"@oceanprotocol/art": "^3.2.0", "@oceanprotocol/art": "^3.2.0",
"@oceanprotocol/lib": "^1.1.5", "@oceanprotocol/lib": "^1.1.6",
"@oceanprotocol/typographies": "^0.1.0", "@oceanprotocol/typographies": "^0.1.0",
"@tippyjs/react": "^4.2.6", "@tippyjs/react": "^4.2.6",
"@urql/exchange-refocus": "^0.2.5", "@urql/exchange-refocus": "^0.2.5",
@ -4598,9 +4598,9 @@
"integrity": "sha512-rDCIooe1WHipLejuGhx2Wv/88SB7bWrN3+XHCWxXyPKTmmSQsgxKZPPzbIVBQ0ESChQZqGSBBJyqErqwwW4eBw==" "integrity": "sha512-rDCIooe1WHipLejuGhx2Wv/88SB7bWrN3+XHCWxXyPKTmmSQsgxKZPPzbIVBQ0ESChQZqGSBBJyqErqwwW4eBw=="
}, },
"node_modules/@oceanprotocol/lib": { "node_modules/@oceanprotocol/lib": {
"version": "1.1.5", "version": "1.1.6",
"resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-1.1.5.tgz", "resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-1.1.6.tgz",
"integrity": "sha512-quc2SGAr8G1v664ll3eOystA2v94OQleW6KGKhVWEPXDldnNAAhlG/QTi86XWRU9HmncGE7OghC5am+9DRGWcQ==", "integrity": "sha512-n1ync5LYBjJDg9Znq61zajnkeXFeFGOU8dN2LCeewArljCOdfYW/SEgQtiLALQb1dgqsYSjMHym40O2jPTUxoQ==",
"dependencies": { "dependencies": {
"@oceanprotocol/contracts": "^1.0.0", "@oceanprotocol/contracts": "^1.0.0",
"bignumber.js": "^9.0.2", "bignumber.js": "^9.0.2",
@ -35024,9 +35024,9 @@
} }
}, },
"node_modules/parse-path": { "node_modules/parse-path": {
"version": "4.0.3", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/parse-path/-/parse-path-4.0.3.tgz", "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-4.0.4.tgz",
"integrity": "sha512-9Cepbp2asKnWTJ9x2kpw6Fe8y9JDbqwahGCTvklzd/cEq5C5JC59x2Xb0Kx+x0QZ8bvNquGO8/BWP0cwBHzSAA==", "integrity": "sha512-Z2lWUis7jlmXC1jeOG9giRO2+FsuyNipeQ43HAjqAZjwSe3SEf+q/84FGPHoso3kyntbxa4c4i77t3m6fGf8cw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"is-ssh": "^1.3.0", "is-ssh": "^1.3.0",
@ -35054,14 +35054,14 @@
} }
}, },
"node_modules/parse-url": { "node_modules/parse-url": {
"version": "6.0.0", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/parse-url/-/parse-url-6.0.0.tgz", "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-6.0.2.tgz",
"integrity": "sha512-cYyojeX7yIIwuJzledIHeLUBVJ6COVLeT4eF+2P6aKVzwvgKQPndCBv3+yQ7pcWjqToYwaligxzSYNNmGoMAvw==", "integrity": "sha512-uCSjOvD3T+6B/sPWhR+QowAZcU/o4bjPrVBQBGFxcDF6J6FraCGIaDBsdoQawiaaAVdHvtqBe3w3vKlfBKySOQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"is-ssh": "^1.3.0", "is-ssh": "^1.3.0",
"normalize-url": "^6.1.0", "normalize-url": "^6.1.0",
"parse-path": "^4.0.0", "parse-path": "^4.0.4",
"protocols": "^1.4.0" "protocols": "^1.4.0"
} }
}, },
@ -46660,16 +46660,15 @@
"integrity": "sha512-rDCIooe1WHipLejuGhx2Wv/88SB7bWrN3+XHCWxXyPKTmmSQsgxKZPPzbIVBQ0ESChQZqGSBBJyqErqwwW4eBw==" "integrity": "sha512-rDCIooe1WHipLejuGhx2Wv/88SB7bWrN3+XHCWxXyPKTmmSQsgxKZPPzbIVBQ0ESChQZqGSBBJyqErqwwW4eBw=="
}, },
"@oceanprotocol/lib": { "@oceanprotocol/lib": {
"version": "1.1.5", "version": "1.1.6",
"resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-1.1.5.tgz", "resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-1.1.6.tgz",
"integrity": "sha512-quc2SGAr8G1v664ll3eOystA2v94OQleW6KGKhVWEPXDldnNAAhlG/QTi86XWRU9HmncGE7OghC5am+9DRGWcQ==", "integrity": "sha512-n1ync5LYBjJDg9Znq61zajnkeXFeFGOU8dN2LCeewArljCOdfYW/SEgQtiLALQb1dgqsYSjMHym40O2jPTUxoQ==",
"requires": { "requires": {
"@oceanprotocol/contracts": "^1.0.0", "@oceanprotocol/contracts": "^1.0.0",
"bignumber.js": "^9.0.2", "bignumber.js": "^9.0.2",
"cross-fetch": "^3.1.5", "cross-fetch": "^3.1.5",
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
"decimal.js": "^10.3.1", "decimal.js": "^10.3.1",
"web3": "^1.7.4",
"web3-core": "^1.7.1", "web3-core": "^1.7.1",
"web3-eth-contract": "^1.7.1" "web3-eth-contract": "^1.7.1"
} }
@ -46721,7 +46720,6 @@
"integrity": "sha512-rmVKYEsKzurfRU0xJz+iHelbi1LGlihIWZ7Qvmb/CBz1EkhL7nOkW4SVXmG2dA5Ce0si2gr88i6q4eBOMRNJ1w==", "integrity": "sha512-rmVKYEsKzurfRU0xJz+iHelbi1LGlihIWZ7Qvmb/CBz1EkhL7nOkW4SVXmG2dA5Ce0si2gr88i6q4eBOMRNJ1w==",
"dev": true, "dev": true,
"requires": { "requires": {
"@oclif/config": "^1.18.2",
"@oclif/errors": "^1.3.5", "@oclif/errors": "^1.3.5",
"@oclif/help": "^1.0.1", "@oclif/help": "^1.0.1",
"@oclif/parser": "^3.8.6", "@oclif/parser": "^3.8.6",
@ -70390,9 +70388,9 @@
} }
}, },
"parse-path": { "parse-path": {
"version": "4.0.3", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/parse-path/-/parse-path-4.0.3.tgz", "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-4.0.4.tgz",
"integrity": "sha512-9Cepbp2asKnWTJ9x2kpw6Fe8y9JDbqwahGCTvklzd/cEq5C5JC59x2Xb0Kx+x0QZ8bvNquGO8/BWP0cwBHzSAA==", "integrity": "sha512-Z2lWUis7jlmXC1jeOG9giRO2+FsuyNipeQ43HAjqAZjwSe3SEf+q/84FGPHoso3kyntbxa4c4i77t3m6fGf8cw==",
"dev": true, "dev": true,
"requires": { "requires": {
"is-ssh": "^1.3.0", "is-ssh": "^1.3.0",
@ -70416,14 +70414,14 @@
} }
}, },
"parse-url": { "parse-url": {
"version": "6.0.0", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/parse-url/-/parse-url-6.0.0.tgz", "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-6.0.2.tgz",
"integrity": "sha512-cYyojeX7yIIwuJzledIHeLUBVJ6COVLeT4eF+2P6aKVzwvgKQPndCBv3+yQ7pcWjqToYwaligxzSYNNmGoMAvw==", "integrity": "sha512-uCSjOvD3T+6B/sPWhR+QowAZcU/o4bjPrVBQBGFxcDF6J6FraCGIaDBsdoQawiaaAVdHvtqBe3w3vKlfBKySOQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"is-ssh": "^1.3.0", "is-ssh": "^1.3.0",
"normalize-url": "^6.1.0", "normalize-url": "^6.1.0",
"parse-path": "^4.0.0", "parse-path": "^4.0.4",
"protocols": "^1.4.0" "protocols": "^1.4.0"
} }
}, },

View File

@ -7,6 +7,7 @@
"scripts": { "scripts": {
"start": "npm run pregenerate && next dev -p 8000", "start": "npm run pregenerate && next dev -p 8000",
"build": "npm run pregenerate && next build", "build": "npm run pregenerate && next build",
"build:static": "npm run build && next export",
"serve": "serve -s public/", "serve": "serve -s public/",
"pregenerate": "bash scripts/pregenerate.sh", "pregenerate": "bash scripts/pregenerate.sh",
"test": "npm run pregenerate && npm run lint && npm run type-check && npm run jest", "test": "npm run pregenerate && npm run lint && npm run type-check && npm run jest",
@ -25,7 +26,7 @@
"@coingecko/cryptoformat": "^0.5.4", "@coingecko/cryptoformat": "^0.5.4",
"@loadable/component": "^5.15.2", "@loadable/component": "^5.15.2",
"@oceanprotocol/art": "^3.2.0", "@oceanprotocol/art": "^3.2.0",
"@oceanprotocol/lib": "^1.1.5", "@oceanprotocol/lib": "^1.1.6",
"@oceanprotocol/typographies": "^0.1.0", "@oceanprotocol/typographies": "^0.1.0",
"@tippyjs/react": "^4.2.6", "@tippyjs/react": "^4.2.6",
"@urql/exchange-refocus": "^0.2.5", "@urql/exchange-refocus": "^0.2.5",

View File

@ -10,7 +10,7 @@ AWS_S3_BUCKET="www-market"
set -e; set -e;
function s3sync { function s3sync {
aws s3 sync ./public s3://"$1" \ aws s3 sync ./out s3://"$1" \
--include "*" \ --include "*" \
--exclude "*.html" \ --exclude "*.html" \
--exclude "sw.js" \ --exclude "sw.js" \
@ -24,7 +24,7 @@ function s3sync {
--delete \ --delete \
--acl public-read --acl public-read
aws s3 sync ./public s3://"$1" \ aws s3 sync ./out s3://"$1" \
--exclude "*" \ --exclude "*" \
--include "*.html" \ --include "*.html" \
--include "sw.js" \ --include "sw.js" \

View File

@ -9,6 +9,7 @@ interface BaseQueryParams {
nestedQuery?: any nestedQuery?: any
esPaginationOptions?: EsPaginationOptions esPaginationOptions?: EsPaginationOptions
sortOptions?: SortOptions sortOptions?: SortOptions
aggs?: any
filters?: FilterTerm[] filters?: FilterTerm[]
ignorePurgatory?: boolean ignorePurgatory?: boolean
} }

View File

@ -3,4 +3,5 @@ interface PagedAssets {
page: number page: number
totalPages: number totalPages: number
totalResults: number totalResults: number
aggregations: any
} }

View File

@ -5,7 +5,8 @@ export enum SortDirectionOptions {
export enum SortTermOptions { export enum SortTermOptions {
Created = 'metadata.created', Created = 'metadata.created',
Relevance = '_score' Relevance = '_score',
Stats = 'stats.orders'
} }
// Note: could not figure out how to get `enum` to be ambiant // Note: could not figure out how to get `enum` to be ambiant
@ -43,5 +44,6 @@ declare global {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
query: any query: any
sort?: { [jsonPath: string]: SortDirectionOptions } sort?: { [jsonPath: string]: SortDirectionOptions }
aggs?: any
} }
} }

View File

@ -55,6 +55,10 @@ export function generateBaseQuery(
} }
} as SearchQuery } as SearchQuery
if (baseQueryParams.aggs !== undefined) {
generatedQuery.aggs = baseQueryParams.aggs
}
if (baseQueryParams.sortOptions !== undefined) if (baseQueryParams.sortOptions !== undefined)
generatedQuery.sort = { generatedQuery.sort = {
[baseQueryParams.sortOptions.sortBy]: [baseQueryParams.sortOptions.sortBy]:
@ -74,12 +78,15 @@ export function transformQueryResult(
results: [], results: [],
page: 0, page: 0,
totalPages: 0, totalPages: 0,
totalResults: 0 totalResults: 0,
aggregations: []
} }
result.results = (queryResult.hits.hits || []).map( result.results = (queryResult.hits.hits || []).map(
(hit) => hit._source as Asset (hit) => hit._source as Asset
) )
result.aggregations = queryResult.aggregations
result.totalResults = queryResult.hits.total.value result.totalResults = queryResult.hits.total.value
result.totalPages = result.totalPages =
result.totalResults / size < 1 result.totalResults / size < 1
@ -307,6 +314,14 @@ export async function getPublishedAssets(
sortBy: SortTermOptions.Created, sortBy: SortTermOptions.Created,
sortDirection: SortDirectionOptions.Descending sortDirection: SortDirectionOptions.Descending
}, },
aggs: {
totalOrders: {
sum: {
field: SortTermOptions.Stats
}
}
},
ignorePurgatory: true,
esPaginationOptions: { esPaginationOptions: {
from: (Number(page) - 1 || 0) * 9, from: (Number(page) - 1 || 0) * 9,
size: 9 size: 9
@ -314,6 +329,62 @@ export async function getPublishedAssets(
} as BaseQueryParams } as BaseQueryParams
const query = generateBaseQuery(baseQueryParams) const query = generateBaseQuery(baseQueryParams)
try {
const result = await queryMetadata(query, cancelToken)
return result
} catch (error) {
if (axios.isCancel(error)) {
LoggerInstance.log(error.message)
} else {
LoggerInstance.error(error.message)
}
}
}
export async function getTopPublishers(
chainIds: number[],
cancelToken: CancelToken,
page?: number,
type?: string,
accesType?: string
): Promise<PagedAssets> {
const filters: FilterTerm[] = []
accesType !== undefined &&
filters.push(getFilterTerm('services.type', accesType))
type !== undefined && filters.push(getFilterTerm('metadata.type', type))
const baseQueryParams = {
chainIds,
filters,
sortOptions: {
sortBy: SortTermOptions.Created,
sortDirection: SortDirectionOptions.Descending
},
aggs: {
topPublishers: {
terms: {
field: 'nft.owner.keyword',
order: { totalSales: 'desc' }
},
aggs: {
totalSales: {
sum: {
field: SortTermOptions.Stats
}
}
}
}
},
esPaginationOptions: {
from: (Number(page) - 1 || 0) * 9,
size: 9
}
} as BaseQueryParams
const query = generateBaseQuery(baseQueryParams)
try { try {
const result = await queryMetadata(query, cancelToken) const result = await queryMetadata(query, cancelToken)
return result return result

View File

@ -26,6 +26,7 @@ import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection'
import { transformAssetToAssetSelection } from './assetConvertor' import { transformAssetToAssetSelection } from './assetConvertor'
import { AssetExtended } from 'src/@types/AssetExtended' import { AssetExtended } from 'src/@types/AssetExtended'
import { ComputeEditForm } from 'src/components/Asset/Edit/_types' import { ComputeEditForm } from 'src/components/Asset/Edit/_types'
import { getFileDidInfo } from './provider'
const getComputeOrders = gql` const getComputeOrders = gql`
query ComputeOrders($user: String!) { query ComputeOrders($user: String!) {
@ -352,12 +353,18 @@ export async function createTrustedAlgorithmList(
tag: selectedAlgorithm.metadata.algorithm.container.tag, tag: selectedAlgorithm.metadata.algorithm.container.tag,
checksum: selectedAlgorithm.metadata.algorithm.container.checksum checksum: selectedAlgorithm.metadata.algorithm.container.checksum
} }
const filesChecksum = await getFileDidInfo(
selectedAlgorithm?.id,
selectedAlgorithm?.services?.[0].id,
selectedAlgorithm?.services?.[0]?.serviceEndpoint,
true
)
const trustedAlgorithm = { const trustedAlgorithm = {
did: selectedAlgorithm.id, did: selectedAlgorithm.id,
containerSectionChecksum: getHash( containerSectionChecksum: getHash(
JSON.stringify(sanitizedAlgorithmContainer) JSON.stringify(sanitizedAlgorithmContainer)
), ),
filesChecksum: getHash(selectedAlgorithm.services[0].files) filesChecksum: filesChecksum?.[0]?.checksum
} }
trustedAlgorithms.push(trustedAlgorithm) trustedAlgorithms.push(trustedAlgorithm)
} }

View File

@ -67,13 +67,15 @@ export async function getEncryptedFiles(
export async function getFileDidInfo( export async function getFileDidInfo(
did: string, did: string,
serviceId: string, serviceId: string,
providerUrl: string providerUrl: string,
withChecksum = false
): Promise<FileInfo[]> { ): Promise<FileInfo[]> {
try { try {
const response = await ProviderInstance.checkDidFiles( const response = await ProviderInstance.checkDidFiles(
did, did,
serviceId as any, // TODO: why does ocean.js want a number here? serviceId,
providerUrl providerUrl,
withChecksum
) )
return response return response
} catch (error) { } catch (error) {

View File

@ -18,7 +18,7 @@ import { OpcFeesQuery as OpcFeesData } from '../@types/subgraph/OpcFeesQuery'
import { calcSingleOutGivenPoolIn } from './pool' import { calcSingleOutGivenPoolIn } from './pool'
import Decimal from 'decimal.js' import Decimal from 'decimal.js'
import { MAX_DECIMALS } from './constants' import { MAX_DECIMALS } from './constants'
import { getPublishedAssets, getTopPublishers } from '@utils/aquarius'
export interface UserLiquidity { export interface UserLiquidity {
price: string price: string
oceanBalance: string oceanBalance: string
@ -172,19 +172,11 @@ const UserSalesQuery = gql`
} }
` `
// TODO: figure out some way to get this
const TopSalesQuery = gql` const TopSalesQuery = gql`
query TopSalesQuery { query TopSalesQuery {
users( users(first: 20, orderBy: totalSales, orderDirection: desc) {
first: 20
orderBy: sharesOwned
orderDirection: desc
where: { tokenBalancesOwned_not: "0" }
) {
id id
tokenBalancesOwned { totalSales
value
}
} }
} }
` `
@ -419,20 +411,10 @@ export async function getUserSales(
accountId: string, accountId: string,
chainIds: number[] chainIds: number[]
): Promise<number> { ): Promise<number> {
const variables = { user: accountId?.toLowerCase() }
try { try {
const userSales = await fetchDataForMultipleChains( const result = await getPublishedAssets(accountId, chainIds, null)
UserSalesQuery, const { totalOrders } = result.aggregations
variables, return totalOrders.value
chainIds
)
let salesSum = 0
for (let i = 0; i < userSales.length; i++) {
if (userSales[i].users.length > 0) {
salesSum += parseInt(userSales[i].users[0].totalSales)
}
}
return salesSum
} catch (error) { } catch (error) {
LoggerInstance.error('Error getUserSales', error.message) LoggerInstance.error('Error getUserSales', error.message)
} }
@ -442,33 +424,19 @@ export async function getTopAssetsPublishers(
chainIds: number[], chainIds: number[],
nrItems = 9 nrItems = 9
): Promise<AccountTeaserVM[]> { ): Promise<AccountTeaserVM[]> {
const publisherSales: AccountTeaserVM[] = [] const publishers: AccountTeaserVM[] = []
for (const chain of chainIds) { const result = await getTopPublishers(chainIds, null)
const queryContext = getQueryContext(Number(chain)) const { topPublishers } = result.aggregations
const fetchedUsers: OperationResult<UsersSalesList> = await fetchData(
TopSalesQuery, for (let i = 0; i < topPublishers.buckets.length; i++) {
null, publishers.push({
queryContext address: topPublishers.buckets[i].key,
) nrSales: parseInt(topPublishers.buckets[i].totalSales.value)
for (let i = 0; i < fetchedUsers.data.users.length; i++) { })
const publishersIndex = publisherSales.findIndex(
(user) => fetchedUsers.data.users[i].id === user.address
)
if (publishersIndex === -1) {
const publisher: AccountTeaserVM = {
address: fetchedUsers.data.users[i].id,
nrSales: fetchedUsers.data.users[i].totalSales
}
publisherSales.push(publisher)
} else {
publisherSales[publishersIndex].nrSales +=
publisherSales[publishersIndex].nrSales
}
}
} }
publisherSales.sort((a, b) => b.nrSales - a.nrSales) publishers.sort((a, b) => b.nrSales - a.nrSales)
return publisherSales.slice(0, nrItems) return publishers.slice(0, nrItems)
} }

View File

@ -1,5 +1,5 @@
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import styles from './AssetList.module.css' import styles from './index.module.css'
import classNames from 'classnames/bind' import classNames from 'classnames/bind'
import Loader from '../atoms/Loader' import Loader from '../atoms/Loader'
import { useUserPreferences } from '@context/UserPreferences' import { useUserPreferences } from '@context/UserPreferences'
@ -29,7 +29,7 @@ export default function AccountList({
const { chainIds } = useUserPreferences() const { chainIds } = useUserPreferences()
const styleClasses = cx({ const styleClasses = cx({
assetList: true, accountList: true,
[className]: className [className]: className
}) })

View File

@ -0,0 +1,24 @@
.accountList {
display: grid;
grid-template-columns: 1fr;
gap: calc(var(--spacer) / 2);
}
@media screen and (min-width: 25rem) {
.accountList {
grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
gap: var(--spacer);
}
}
.empty {
color: var(--color-secondary);
font-size: var(--font-size-small);
font-style: italic;
}
.loaderWrap {
display: flex;
justify-content: center;
align-items: center;
}

View File

@ -1,46 +1,39 @@
.blockies { .blockies {
aspect-ratio: 1/1; aspect-ratio: 1/1;
width: 13%; width: calc(var(--font-size-large) * 2) !important;
height: 13%; height: calc(var(--font-size-large) * 2) !important;
border-radius: 50%; border-radius: 50%;
margin-left: 0; margin-left: 0;
margin-right: calc(var(--spacer) / 4); margin-right: calc(var(--spacer) / 3);
} }
.teaser { .teaser {
max-width: 40rem;
height: 100%;
}
.link {
composes: box from '../atoms/Box.module.css'; composes: box from '../atoms/Box.module.css';
padding: calc(var(--spacer) / 2) !important; padding: calc(var(--spacer) / 3) calc(var(--spacer) / 2);
font-size: var(--font-size-mini);
height: 90%;
color: var(--color-secondary); color: var(--color-secondary);
position: relative; position: relative;
display: flex;
align-items: center;
}
.link span {
font-size: var(--font-size-large);
margin-right: calc(var(--spacer) / 3);
}
.name {
margin-bottom: 0;
font-size: var(--font-size-base) !important;
padding-top: calc(var(--spacer) / 4);
}
.header {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
} }
.sales { .place {
font-size: small; font-size: var(--font-size-large);
margin-top: -5px !important; margin-right: calc(var(--spacer) / 2);
margin-bottom: clac(var(--spacer) / 2); }
.name {
margin-bottom: 0;
font-size: var(--font-size-base);
padding-top: calc(var(--spacer) / 5);
color: var(--font-color-text);
}
.sales {
margin: 0;
}
.sales span {
font-weight: var(--font-weight-bold);
color: var(--font-color-text);
} }

View File

@ -33,34 +33,26 @@ export default function AccountTeaser({
}, [accountTeaserVM, newCancelToken]) }, [accountTeaserVM, newCancelToken])
return ( return (
<article className={styles.teaser}>
<Link href={`/profile/${accountTeaserVM.address}`}> <Link href={`/profile/${accountTeaserVM.address}`}>
<header className={styles.header}> <a className={styles.teaser}>
{place && <span>{place}</span>} {place && <span className={styles.place}>{place}</span>}
{profile?.image ? (
<img src={profile.image} className={styles.blockies} />
) : (
<Blockies <Blockies
accountId={accountTeaserVM.address} accountId={accountTeaserVM.address}
className={styles.blockies} className={styles.blockies}
image={profile?.image}
/> />
)}
<div> <div>
<Dotdotdot clamp={3}> <Dotdotdot tagName="h4" clamp={2} className={styles.name}>
<h3 className={styles.name}>
{profile?.name {profile?.name
? profile?.name ? profile?.name
: accountTruncate(accountTeaserVM.address)} : accountTruncate(accountTeaserVM.address)}
</h3>
</Dotdotdot> </Dotdotdot>
<p className={styles.sales}> <p className={styles.sales}>
{`${accountTeaserVM.nrSales} ${ <span>{accountTeaserVM.nrSales}</span>
accountTeaserVM.nrSales === 1 ? 'sale' : 'sales' {`${accountTeaserVM.nrSales === 1 ? ' sale' : ' sales'}`}
}`}
</p> </p>
</div> </div>
</header> </a>
</Link> </Link>
</article>
) )
} }

View File

@ -24,14 +24,16 @@ export default function AssetTeaser({
const isCompute = Boolean(getServiceByName(asset, 'compute')) const isCompute = Boolean(getServiceByName(asset, 'compute'))
const accessType = isCompute ? 'compute' : 'access' const accessType = isCompute ? 'compute' : 'access'
const { owner } = asset.nft const { owner } = asset.nft
const { orders } = asset.stats
return ( return (
<article className={`${styles.teaser} ${styles[type]}`}> <article className={`${styles.teaser} ${styles[type]}`}>
<Link href={`/asset/${asset.id}`}> <Link href={`/asset/${asset.id}`}>
<a className={styles.link}> <a className={styles.link}>
<header className={styles.header}> <header className={styles.header}>
<div className={styles.symbol}>{datatokens[0]?.symbol}</div> <div className={styles.symbol}>{datatokens[0]?.symbol}</div>
<Dotdotdot clamp={3}> <Dotdotdot tagName="h1" clamp={3} className={styles.title}>
<h1 className={styles.title}>{name}</h1> {name}
</Dotdotdot> </Dotdotdot>
{!noPublisher && ( {!noPublisher && (
<Publisher account={owner} minimal className={styles.publisher} /> <Publisher account={owner} minimal className={styles.publisher} />
@ -42,6 +44,7 @@ export default function AssetTeaser({
type={type} type={type}
accessType={accessType} accessType={accessType}
className={styles.typeDetails} className={styles.typeDetails}
totalSales={orders}
/> />
<div className={styles.content}> <div className={styles.content}>

View File

@ -1,26 +1,22 @@
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import styles from './index.module.css' import styles from './index.module.css'
import classNames from 'classnames/bind'
import Compute from '@images/compute.svg' import Compute from '@images/compute.svg'
import Download from '@images/download.svg' import Download from '@images/download.svg'
import Lock from '@images/lock.svg' import Lock from '@images/lock.svg'
const cx = classNames.bind(styles)
export default function AssetType({ export default function AssetType({
type, type,
accessType, accessType,
className className,
totalSales
}: { }: {
type: string type: string
accessType: string accessType: string
className?: string className?: string
totalSales?: number
}): ReactElement { }): ReactElement {
const styleClasses = cx({
[className]: className
})
return ( return (
<div className={styleClasses}> <div className={className || null}>
{accessType === 'access' ? ( {accessType === 'access' ? (
<Download role="img" aria-label="Download" className={styles.icon} /> <Download role="img" aria-label="Download" className={styles.icon} />
) : accessType === 'compute' && type === 'algorithm' ? ( ) : accessType === 'compute' && type === 'algorithm' ? (
@ -32,6 +28,12 @@ export default function AssetType({
<div className={styles.typeLabel}> <div className={styles.typeLabel}>
{type === 'dataset' ? 'data set' : 'algorithm'} {type === 'dataset' ? 'data set' : 'algorithm'}
</div> </div>
{totalSales ? (
<div className={styles.typeLabel}>
{`${totalSales} ${totalSales === 1 ? 'sale' : 'sales'}`}
</div>
) : null}
</div> </div>
) )
} }

View File

@ -5,11 +5,13 @@ import styles from './index.module.css'
export interface BlockiesProps { export interface BlockiesProps {
accountId: string accountId: string
className?: string className?: string
image?: string
} }
export default function Blockies({ export default function Blockies({
accountId, accountId,
className className,
image
}: BlockiesProps): ReactElement { }: BlockiesProps): ReactElement {
if (!accountId) return null if (!accountId) return null
@ -18,7 +20,7 @@ export default function Blockies({
return ( return (
<img <img
className={`${className || ''} ${styles.blockies} `} className={`${className || ''} ${styles.blockies} `}
src={blockies} src={image || blockies}
alt="Blockies" alt="Blockies"
aria-hidden="true" aria-hidden="true"
/> />

View File

@ -3,7 +3,7 @@
margin-top: calc(var(--spacer) / 1.5); margin-top: calc(var(--spacer) / 1.5);
padding: calc(var(--spacer) / 1.5); padding: calc(var(--spacer) / 1.5);
background: var(--background-highlight); background: var(--background-highlight);
margin-bottom: -2rem; margin-bottom: -1rem;
} }
.actions [class*='rdt_Pagination'] { .actions [class*='rdt_Pagination'] {

View File

@ -1,6 +0,0 @@
.buttonMax {
position: absolute;
font-size: var(--font-size-mini);
bottom: calc(var(--spacer) / 2.6);
right: calc(var(--spacer) * 2.5);
}

View File

@ -1,114 +0,0 @@
import React, { ReactElement, useEffect } from 'react'
import styles from './FormAdd.module.css'
import Input from '@shared/FormInput'
import Error from '@shared/FormInput/Error'
import { FormikContextType, useField, useFormikContext } from 'formik'
import Button from '@shared/atoms/Button'
import { FormAddLiquidity } from '.'
import UserLiquidity from '../../UserLiquidity'
import { useWeb3 } from '@context/Web3'
import { isValidNumber } from '@utils/numbers'
import Decimal from 'decimal.js'
import { useAsset } from '@context/Asset'
import { Pool } from '@oceanprotocol/lib'
import { usePool } from '@context/Pool'
export default function FormAdd({
amountMax,
setNewPoolTokens,
setNewPoolShare
}: {
amountMax: string
setNewPoolTokens: (value: string) => void
setNewPoolShare: (value: string) => void
}): ReactElement {
const { balance, web3 } = useWeb3()
const { isAssetNetwork } = useAsset()
const { poolData, poolInfo } = usePool()
// Connect with form
const {
setFieldValue,
values,
isSubmitting
}: FormikContextType<FormAddLiquidity> = useFormikContext()
const [field, meta] = useField('amount')
useEffect(() => {
async function calculatePoolShares() {
if (!web3 || !poolData?.id || !poolInfo?.totalPoolTokens) return
if (!values.amount || !poolInfo?.baseTokenAddress) {
setNewPoolTokens('0')
setNewPoolShare('0')
return
}
if (Number(values.amount) > Number(amountMax)) return
const poolInstance = new Pool(web3)
const poolTokens = await poolInstance.calcPoolOutGivenSingleIn(
poolData.id,
poolInfo.baseTokenAddress,
values.amount.toString(),
18,
poolInfo.baseTokenDecimals
)
setNewPoolTokens(poolTokens)
const newPoolShareDecimal =
isValidNumber(poolTokens) && isValidNumber(poolInfo.totalPoolTokens)
? new Decimal(poolTokens)
.dividedBy(
new Decimal(poolInfo.totalPoolTokens).plus(
new Decimal(poolTokens)
)
)
.mul(100)
.toString()
: '0'
setNewPoolShare(newPoolShareDecimal)
}
calculatePoolShares()
}, [
poolInfo?.baseTokenAddress,
poolInfo?.baseTokenDecimals,
web3,
values.amount,
poolInfo?.totalPoolTokens,
amountMax,
poolData?.id,
setNewPoolTokens,
setNewPoolShare
])
return (
<>
<UserLiquidity
amount={balance.ocean}
amountMax={amountMax}
symbol={poolInfo?.baseTokenSymbol}
/>
<Input
type="number"
min="0"
prefix={poolInfo?.baseTokenSymbol}
placeholder="0"
disabled={!isAssetNetwork || isSubmitting}
{...field}
additionalComponent={<Error meta={meta} />}
/>
{Number(balance.ocean) > 0 && (
<Button
className={styles.buttonMax}
style="text"
size="small"
disabled={!web3 || isSubmitting}
onClick={() => setFieldValue('amount', amountMax)}
>
Use Max
</Button>
)}
</>
)
}

View File

@ -1,13 +0,0 @@
.output {
padding-bottom: calc(var(--spacer) / 2);
}
.output p {
font-weight: var(--font-weight-bold);
margin-bottom: calc(var(--spacer) / 8);
font-size: var(--font-size-small);
}
.help {
text-align: center;
}

View File

@ -1,30 +0,0 @@
import React, { ReactElement } from 'react'
import FormHelp from '@shared/FormInput/Help'
import Token from '../../../../@shared/Token'
import styles from './Output.module.css'
import content from '../../../../../../content/price.json'
import { usePool } from '@context/Pool'
export default function Output({
newPoolTokens,
newPoolShare
}: {
newPoolTokens: string
newPoolShare: string
}): ReactElement {
const { help, titleIn } = content.pool.add.output
const { poolInfo } = usePool()
return (
<>
<FormHelp className={styles.help}>
{help.replace('SWAPFEE', poolInfo?.liquidityProviderSwapFee)}
</FormHelp>
<div className={styles.output}>
<p>{titleIn}</p>
<Token symbol="pool shares" balance={newPoolTokens} noIcon />
<Token symbol="% of pool" balance={newPoolShare} noIcon />
</div>
</>
)
}

View File

@ -1,24 +0,0 @@
.addInput {
margin: 0 auto calc(var(--spacer) / 1.5) auto;
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;
}
.addInput input {
text-align: center;
}
.addInput div[class*='field'] {
margin-bottom: 0;
}
.warning {
margin-left: -3rem;
margin-right: -3rem;
}

View File

@ -1,174 +0,0 @@
import React, { ReactElement, useState, useEffect } from 'react'
import Header from '../Actions/Header'
import { toast } from 'react-toastify'
import Actions from '../Actions'
import * as Yup from 'yup'
import { Formik } from 'formik'
import FormAdd from './FormAdd'
import styles from './index.module.css'
import Alert from '@shared/atoms/Alert'
import { useUserPreferences } from '@context/UserPreferences'
import Output from './Output'
import DebugOutput from '@shared/DebugOutput'
import { useWeb3 } from '@context/Web3'
import { useAsset } from '@context/Asset'
import content from '../../../../../../content/price.json'
import { calcMaxExactIn, LoggerInstance, Pool } from '@oceanprotocol/lib'
import { usePool } from '@context/Pool'
import { MAX_DECIMALS } from '@utils/constants'
import { getMaxDecimalsValidation } from '@utils/numbers'
import Decimal from 'decimal.js'
export interface FormAddLiquidity {
amount: number
}
const initialValues: FormAddLiquidity = {
amount: 0
}
export default function Add({
setShowAdd
}: {
setShowAdd: (show: boolean) => void
}): ReactElement {
const { accountId, balance, web3 } = useWeb3()
const { isAssetNetwork } = useAsset()
const { poolData, poolInfo, fetchAllData } = usePool()
const { debug } = useUserPreferences()
const [txId, setTxId] = useState<string>()
const [amountMax, setAmountMax] = useState<string>()
const [newPoolTokens, setNewPoolTokens] = useState('0')
const [newPoolShare, setNewPoolShare] = useState('0')
// Live validation rules
// https://github.com/jquense/yup#number
const validationSchema: Yup.SchemaOf<FormAddLiquidity> = Yup.object().shape({
amount: Yup.number()
.min(0.00001, (param) => `Must be more or equal to ${param.min}`)
.max(
Number(amountMax),
`Maximum you can add is ${Number(amountMax).toFixed(2)} OCEAN`
)
.test(
'maxDigitsAfterDecimal',
`Must have maximum ${MAX_DECIMALS} decimal digits`,
(param) =>
getMaxDecimalsValidation(MAX_DECIMALS).test(param?.toString())
)
.required('Required')
})
// Get maximum amount for OCEAN
useEffect(() => {
if (
!web3 ||
!accountId ||
!isAssetNetwork ||
!poolData?.id ||
!poolInfo?.baseTokenAddress
)
return
async function getMaximum() {
try {
const poolInstance = new Pool(web3)
const poolReserve = await poolInstance.getReserve(
poolData.id,
poolInfo.baseTokenAddress,
poolInfo.baseTokenDecimals
)
const amountMaxPool = calcMaxExactIn(poolReserve)
const oceanDecimal = new Decimal(balance.ocean)
const amountMax = oceanDecimal.greaterThan(amountMaxPool)
? amountMaxPool
: oceanDecimal
setAmountMax(amountMax.toFixed(3, Decimal.ROUND_DOWN))
} catch (error) {
LoggerInstance.error(error.message)
}
}
getMaximum()
}, [
web3,
accountId,
isAssetNetwork,
poolData?.id,
poolInfo?.baseTokenAddress,
poolInfo?.baseTokenDecimals,
balance?.ocean
])
// Submit
async function handleAddLiquidity(amount: string, resetForm: () => void) {
const poolInstance = new Pool(web3)
const minPoolAmountOut = '0' // ? how to get? : you would get this value by using `calcPoolOutGivenSingleIn` and substracting slippage from that , like we don in trade. it is ok to be 0 here. We can change after we implement global slippage
try {
const result = await poolInstance.joinswapExternAmountIn(
accountId,
poolData?.id,
amount,
minPoolAmountOut
)
setTxId(result?.transactionHash)
fetchAllData()
resetForm()
} catch (error) {
LoggerInstance.error(error.message)
toast.error(error.message)
}
}
return (
<>
<Header
title={content.pool.add.title}
backAction={() => {
setShowAdd(false)
fetchAllData()
}}
/>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={async (values, { setSubmitting, resetForm }) => {
await handleAddLiquidity(values.amount.toString(), resetForm)
setSubmitting(false)
}}
>
{({ isSubmitting, setSubmitting, submitForm, values, isValid }) => (
<>
<div className={styles.addInput}>
<FormAdd
amountMax={amountMax}
setNewPoolTokens={setNewPoolTokens}
setNewPoolShare={setNewPoolShare}
/>
</div>
{/* TODO: will be fixed in #1481 <Output newPoolTokens={newPoolTokens} newPoolShare={newPoolShare} /> */}
<Actions
isDisabled={!isValid || !values.amount || values.amount === 0}
isLoading={isSubmitting}
loaderMessage="Adding Liquidity..."
successMessage="Successfully added liquidity."
actionName={content.pool.add.action}
action={submitForm}
amount={values.amount.toString()}
tokenAddress={poolInfo?.baseTokenAddress}
tokenSymbol={poolInfo?.baseTokenSymbol}
txId={txId}
setSubmitting={setSubmitting}
/>
{debug && <DebugOutput output={values} />}
</>
)}
</Formik>
</>
)
}

View File

@ -1,5 +1,12 @@
.removeInput { .removeInput {
composes: addInput from '../Add/index.module.css'; 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-left: calc(var(--spacer) * 2);
padding-right: calc(var(--spacer) * 2); padding-right: calc(var(--spacer) * 2);
padding-bottom: calc(var(--spacer) / 2); padding-bottom: calc(var(--spacer) / 2);

View File

@ -1,7 +1,6 @@
import React, { ReactElement, useState } from 'react' import React, { ReactElement, useState } from 'react'
import stylesActions from './Actions/index.module.css' import stylesActions from './Actions/index.module.css'
import Button from '@shared/atoms/Button' import Button from '@shared/atoms/Button'
import Add from './Add'
import Remove from './Remove' import Remove from './Remove'
import AssetActionHistoryTable from '../AssetActionHistoryTable' import AssetActionHistoryTable from '../AssetActionHistoryTable'
import { useAsset } from '@context/Asset' import { useAsset } from '@context/Asset'
@ -12,43 +11,33 @@ import { usePool } from '@context/Pool'
import PoolSections from './Sections' import PoolSections from './Sections'
export default function Pool(): ReactElement { export default function Pool(): ReactElement {
const { isInPurgatory, asset, isAssetNetwork } = useAsset() const { asset, isAssetNetwork } = useAsset()
const { hasUserAddedLiquidity } = usePool() const { hasUserAddedLiquidity } = usePool()
const { accountId } = useWeb3() const { accountId } = useWeb3()
const [showAdd, setShowAdd] = useState(false)
const [showRemove, setShowRemove] = useState(false) const [showRemove, setShowRemove] = useState(false)
return ( return (
<> <>
{showAdd ? ( {showRemove ? (
<Add setShowAdd={setShowAdd} />
) : showRemove ? (
<Remove setShowRemove={setShowRemove} /> <Remove setShowRemove={setShowRemove} />
) : ( ) : (
<> <>
<PoolSections /> <PoolSections />
{hasUserAddedLiquidity && (
<div className={stylesActions.actions}> <div className={stylesActions.actions}>
<Button <Button
style="primary" style="primary"
size="small"
onClick={() => setShowAdd(true)}
disabled={isInPurgatory || !isAssetNetwork}
>
Add Liquidity
</Button>
{hasUserAddedLiquidity && (
<Button
size="small" size="small"
onClick={() => setShowRemove(true)} onClick={() => setShowRemove(true)}
disabled={!isAssetNetwork} disabled={!isAssetNetwork}
> >
Remove Remove Liquidity
</Button> </Button>
)}
</div> </div>
)}
{accountId && ( {accountId && (
<AssetActionHistoryTable title="Your Pool Transactions"> <AssetActionHistoryTable title="Your Pool Transactions">
<PoolTransactions <PoolTransactions

View File

@ -137,10 +137,7 @@ export default function AssetActions({
const tabs: TabsItem[] = [{ title: 'Use', content: UseContent }] const tabs: TabsItem[] = [{ title: 'Use', content: UseContent }]
asset?.accessDetails?.type === 'dynamic' && asset?.accessDetails?.type === 'dynamic' &&
tabs.push( tabs.push({ title: 'Pool', content: <Pool /> })
{ title: 'Pool', content: <Pool /> },
{ title: 'Trade', content: <Trade /> }
)
return ( return (
<> <>

View File

@ -51,7 +51,7 @@ export default function Edit({
const setPriceResp = await fixedRateInstance.setRate( const setPriceResp = await fixedRateInstance.setRate(
accountId, accountId,
asset.accessDetails.addressOrId, asset.accessDetails.addressOrId,
newPrice newPrice.toString()
) )
LoggerInstance.log('[edit] setFixedRate result', setPriceResp) LoggerInstance.log('[edit] setFixedRate result', setPriceResp)
if (!setPriceResp) { if (!setPriceResp) {

View File

@ -1,26 +0,0 @@
.tooltipStats {
margin-bottom: calc(var(--spacer) / 3);
padding-bottom: calc(var(--spacer) / 3);
border-bottom: 1px solid var(--border-color);
}
.statsList {
composes: statsList from './index.module.css';
padding-bottom: 0;
}
.statsList,
.note {
padding: calc(var(--spacer) / 4);
}
.note {
margin-bottom: 0;
padding-top: 0;
font-size: var(--font-size-mini);
color: var(--color-secondary);
}
.network {
font-weight: var(--font-weight-bold);
}

View File

@ -1,47 +0,0 @@
import React, { ReactElement } from 'react'
import Conversion from '@shared/Price/Conversion'
import PriceUnit from '@shared/Price/PriceUnit'
import NetworkName from '@shared/NetworkName'
import styles from './Tooltip.module.css'
import { StatsValue } from './_types'
import content from '../../../../content/footer.json'
import Markdown from '@shared/Markdown'
export default function MarketStatsTooltip({
totalValueLockedInOcean,
poolCount,
totalOceanLiquidity,
mainChainIds
}: {
totalValueLockedInOcean: StatsValue
poolCount: StatsValue
totalOceanLiquidity: StatsValue
mainChainIds: number[]
}): ReactElement {
return (
<>
<ul className={styles.statsList}>
{mainChainIds?.map((chainId, key) => (
<li className={styles.tooltipStats} key={key}>
<NetworkName networkId={chainId} className={styles.network} />
<br />
<Conversion
price={totalValueLockedInOcean?.[chainId] || '0'}
hideApproximateSymbol
/>{' '}
<abbr title="Total Value Locked">TVL</abbr>
{' | '}
<strong>{poolCount?.[chainId] || '0'}</strong> pools
{' | '}
<PriceUnit
price={totalOceanLiquidity?.[chainId] || '0'}
symbol="OCEAN"
size="small"
/>
</li>
))}
</ul>
<Markdown className={styles.note} text={content.stats.note} />
</>
)
}

View File

@ -1,6 +1,4 @@
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import Conversion from '@shared/Price/Conversion'
import PriceUnit from '@shared/Price/PriceUnit'
import { StatsTotal } from './_types' import { StatsTotal } from './_types'
export default function MarketStatsTotal({ export default function MarketStatsTotal({
@ -10,23 +8,9 @@ export default function MarketStatsTotal({
}): ReactElement { }): ReactElement {
return ( return (
<> <>
<p>
<strong>{total.orders}</strong> orders across{' '} <strong>{total.orders}</strong> orders across{' '}
<strong>{total.nfts}</strong> assets with{' '} <strong>{total.nfts}</strong> assets with{' '}
<strong>{total.datatokens}</strong> different datatokens. <strong>{total.datatokens}</strong> different datatokens.
</p>
<Conversion
price={`${total.totalValueLockedInOcean}`}
hideApproximateSymbol
/>{' '}
<abbr title="Total Value Locked">TVL</abbr> across{' '}
<strong>{total.pools}</strong> asset pools that contain{' '}
<PriceUnit
price={`${total.totalOceanLiquidity}`}
symbol="OCEAN"
size="small"
/>
, plus datatokens for each pool.
</> </>
) )
} }

View File

@ -3,18 +3,9 @@ import { gql } from 'urql'
export const queryGlobalStatistics = gql` export const queryGlobalStatistics = gql`
query FooterStatsValues { query FooterStatsValues {
globalStatistics { globalStatistics {
poolCount
nftCount nftCount
datatokenCount datatokenCount
orderCount orderCount
totalLiquidity {
value
token {
address
name
symbol
}
}
} }
} }
` `

View File

@ -3,9 +3,6 @@ export interface StatsValue {
} }
export interface StatsTotal { export interface StatsTotal {
totalValueLockedInOcean: number
totalOceanLiquidity: number
pools: number
nfts: number nfts: number
datatokens: number datatokens: number
orders: number orders: number

View File

@ -1,6 +1,5 @@
import React, { ReactElement, useCallback, useEffect, useState } from 'react' import React, { ReactElement, useCallback, useEffect, useState } from 'react'
import { OperationContext } from 'urql' import { OperationContext } from 'urql'
import Tooltip from '@shared/atoms/Tooltip'
import { fetchData, getSubgraphUri } from '@utils/subgraph' import { fetchData, getSubgraphUri } from '@utils/subgraph'
import useNetworkMetadata, { import useNetworkMetadata, {
filterNetworksByType filterNetworksByType
@ -8,19 +7,15 @@ import useNetworkMetadata, {
import { LoggerInstance } from '@oceanprotocol/lib' import { LoggerInstance } from '@oceanprotocol/lib'
import styles from './index.module.css' import styles from './index.module.css'
import { FooterStatsValues_globalStatistics as FooterStatsValuesGlobalStatistics } from 'src/@types/subgraph/FooterStatsValues' import { FooterStatsValues_globalStatistics as FooterStatsValuesGlobalStatistics } from 'src/@types/subgraph/FooterStatsValues'
import MarketStatsTooltip from './Tooltip'
import MarketStatsTotal from './Total' import MarketStatsTotal from './Total'
import { queryGlobalStatistics } from './_queries' import { queryGlobalStatistics } from './_queries'
import { usePrices } from '@context/Prices' import { StatsTotal } from './_types'
import { useUserPreferences } from '@context/UserPreferences'
import Decimal from 'decimal.js'
import { StatsTotal, StatsValue } from './_types'
import { useMarketMetadata } from '@context/MarketMetadata' import { useMarketMetadata } from '@context/MarketMetadata'
import Tooltip from '@shared/atoms/Tooltip'
import Markdown from '@shared/Markdown'
import content from '../../../../content/footer.json'
const initialTotal: StatsTotal = { const initialTotal: StatsTotal = {
totalValueLockedInOcean: 0,
totalOceanLiquidity: 0,
pools: 0,
nfts: 0, nfts: 0,
datatokens: 0, datatokens: 0,
orders: 0 orders: 0
@ -29,17 +24,10 @@ const initialTotal: StatsTotal = {
export default function MarketStats(): ReactElement { export default function MarketStats(): ReactElement {
const { appConfig } = useMarketMetadata() const { appConfig } = useMarketMetadata()
const { networksList } = useNetworkMetadata() const { networksList } = useNetworkMetadata()
const { currency } = useUserPreferences()
const { prices } = usePrices()
const [mainChainIds, setMainChainIds] = useState<number[]>() const [mainChainIds, setMainChainIds] = useState<number[]>()
const [data, setData] = useState<{ const [data, setData] = useState<{
[chainId: number]: FooterStatsValuesGlobalStatistics [chainId: number]: FooterStatsValuesGlobalStatistics
}>() }>()
const [totalValueLockedInOcean, setTotalValueLockedInOcean] =
useState<StatsValue>()
const [totalOceanLiquidity, setTotalOceanLiquidity] = useState<StatsValue>()
const [poolCount, setPoolCount] = useState<StatsValue>()
const [total, setTotal] = useState(initialTotal) const [total, setTotal] = useState(initialTotal)
// //
@ -98,34 +86,13 @@ export default function MarketStats(): ReactElement {
const newTotal: StatsTotal = { const newTotal: StatsTotal = {
...initialTotal // always start calculating beginning from initial 0 values ...initialTotal // always start calculating beginning from initial 0 values
} }
const newTVLInOcean: StatsValue = {}
const newTotalLiquidity: StatsValue = {}
const newPoolCount: StatsValue = {}
for (const chainId of mainChainIds) { for (const chainId of mainChainIds) {
const baseTokenValue = data[chainId]?.totalLiquidity[0]?.value
try { try {
const totalValueLockedInOcean = baseTokenValue
? new Decimal(baseTokenValue).mul(2)
: new Decimal(0)
newTVLInOcean[chainId] = `${totalValueLockedInOcean}`
const totalOceanLiquidity = Number(baseTokenValue) || 0
newTotalLiquidity[chainId] = `${totalOceanLiquidity}`
const poolCount = data[chainId]?.poolCount || 0
newPoolCount[chainId] = `${poolCount}`
const nftCount = data[chainId]?.nftCount || 0 const nftCount = data[chainId]?.nftCount || 0
const datatokenCount = data[chainId]?.datatokenCount || 0 const datatokenCount = data[chainId]?.datatokenCount || 0
const orderCount = data[chainId]?.orderCount || 0 const orderCount = data[chainId]?.orderCount || 0
newTotal.totalValueLockedInOcean += totalValueLockedInOcean.toNumber()
newTotal.totalOceanLiquidity += totalOceanLiquidity
newTotal.pools += poolCount
newTotal.nfts += nftCount newTotal.nfts += nftCount
newTotal.datatokens += datatokenCount newTotal.datatokens += datatokenCount
newTotal.orders += orderCount newTotal.orders += orderCount
@ -133,11 +100,9 @@ export default function MarketStats(): ReactElement {
LoggerInstance.error('Error data manipulation: ', error.message) LoggerInstance.error('Error data manipulation: ', error.message)
} }
} }
setTotalValueLockedInOcean(newTVLInOcean)
setTotalOceanLiquidity(newTotalLiquidity)
setPoolCount(newPoolCount)
setTotal(newTotal) setTotal(newTotal)
}, [data, mainChainIds, prices, currency]) }, [data, mainChainIds])
return ( return (
<div className={styles.stats}> <div className={styles.stats}>
@ -146,12 +111,7 @@ export default function MarketStats(): ReactElement {
<Tooltip <Tooltip
className={styles.info} className={styles.info}
content={ content={
<MarketStatsTooltip <Markdown className={styles.note} text={content.stats.note} />
totalValueLockedInOcean={totalValueLockedInOcean}
poolCount={poolCount}
totalOceanLiquidity={totalOceanLiquidity}
mainChainIds={mainChainIds}
/>
} }
/> />
</> </>

View File

@ -1,8 +1,9 @@
import { useUserPreferences } from '@context/UserPreferences' import { useUserPreferences } from '@context/UserPreferences'
import { LoggerInstance } from '@oceanprotocol/lib'
import AccountList from '@shared/AccountList/AccountList' import AccountList from '@shared/AccountList/AccountList'
import { getTopAssetsPublishers } from '@utils/subgraph' import { getTopAssetsPublishers } from '@utils/subgraph'
import React, { ReactElement, useEffect, useState } from 'react' import React, { ReactElement, useEffect, useState } from 'react'
import styles from './Home.module.css' import styles from './index.module.css'
export default function PublishersWithMostSales({ export default function PublishersWithMostSales({
title, title,
@ -17,18 +18,19 @@ export default function PublishersWithMostSales({
useEffect(() => { useEffect(() => {
async function init() { async function init() {
setLoading(true)
if (chainIds.length === 0) { if (chainIds.length === 0) {
const result: AccountTeaserVM[] = [] const result: AccountTeaserVM[] = []
setResult(result) setResult(result)
setLoading(false) setLoading(false)
} else { } else {
try { try {
setLoading(true)
const publishers = await getTopAssetsPublishers(chainIds) const publishers = await getTopAssetsPublishers(chainIds)
setResult(publishers) setResult(publishers)
setLoading(false) setLoading(false)
} catch (error) { } catch (error) {
// Logger.error(error.message) LoggerInstance.error(error.message)
setLoading(false)
} }
} }
} }

View File

@ -14,6 +14,7 @@ import styles from './index.module.css'
import { useIsMounted } from '@hooks/useIsMounted' import { useIsMounted } from '@hooks/useIsMounted'
import { useCancelToken } from '@hooks/useCancelToken' import { useCancelToken } from '@hooks/useCancelToken'
import { SortTermOptions } from '../../@types/aquarius/SearchQuery' import { SortTermOptions } from '../../@types/aquarius/SearchQuery'
import PublishersWithMostSales from './PublishersWithMostSales'
async function getQueryHighest( async function getQueryHighest(
chainIds: number[] chainIds: number[]
@ -66,7 +67,8 @@ function SectionQueryResult({
results: [], results: [],
page: 0, page: 0,
totalPages: 0, totalPages: 0,
totalResults: 0 totalResults: 0,
aggregations: undefined
} }
setResult(result) setResult(result)
setLoading(false) setLoading(false)
@ -153,6 +155,8 @@ export default function HomePage(): ReactElement {
</Button> </Button>
} }
/> />
<PublishersWithMostSales title="Publishers With Most Sales" />
</> </>
) )
} }

View File

@ -18,7 +18,7 @@ export default function PricingFields(): ReactElement {
// Connect with main publish form // Connect with main publish form
const { values, setFieldValue } = useFormikContext<FormPublishData>() const { values, setFieldValue } = useFormikContext<FormPublishData>()
const { pricing } = values const { pricing } = values
const { price, amountOcean, weightOnOcean, weightOnDataToken, type } = pricing const { price, type } = pricing
// Switch type value upon tab change // Switch type value upon tab change
function handleTabChange(tabName: string) { function handleTabChange(tabName: string) {
@ -29,36 +29,11 @@ export default function PricingFields(): ReactElement {
type !== 'free' && setFieldValue('pricing.amountDataToken', 1000) type !== 'free' && setFieldValue('pricing.amountDataToken', 1000)
} }
// Update ocean amount when price is changed // Update price when price is changed
useEffect(() => { useEffect(() => {
if (type === 'fixed' || type === 'free') return setFieldValue('pricing.price', price)
setFieldValue('pricing.type', type)
const amountOcean = }, [price, setFieldValue, type])
isValidNumber(weightOnOcean) && isValidNumber(price) && price > 0
? new Decimal(price).mul(new Decimal(weightOnOcean).mul(10)).mul(2)
: new Decimal(initialValues.pricing.amountOcean)
setFieldValue('pricing.amountOcean', amountOcean)
}, [price, weightOnOcean, type, setFieldValue])
// Update dataToken value when ocean amount is changed
useEffect(() => {
if (type === 'fixed' || type === 'free') return
const amountDataToken =
isValidNumber(amountOcean) &&
isValidNumber(weightOnOcean) &&
isValidNumber(price) &&
isValidNumber(weightOnDataToken) &&
price > 0
? new Decimal(amountOcean)
.dividedBy(new Decimal(weightOnOcean))
.dividedBy(new Decimal(price))
.mul(new Decimal(weightOnDataToken))
: new Decimal(initialValues.pricing.amountDataToken)
setFieldValue('pricing.amountDataToken', amountDataToken)
}, [amountOcean, weightOnOcean, weightOnDataToken, type, setFieldValue])
const tabs = [ const tabs = [
appConfig.allowFixedPricing === 'true' appConfig.allowFixedPricing === 'true'
@ -67,12 +42,6 @@ export default function PricingFields(): ReactElement {
content: <Fixed content={content.create.fixed} /> content: <Fixed content={content.create.fixed} />
} }
: undefined, : undefined,
appConfig.allowDynamicPricing === 'true'
? {
title: content.create.dynamic.title,
content: <Dynamic content={content.create.dynamic} />
}
: undefined,
appConfig.allowFreePricing === 'true' appConfig.allowFreePricing === 'true'
? { ? {
title: content.create.free.title, title: content.create.free.title,
@ -85,7 +54,7 @@ export default function PricingFields(): ReactElement {
<Tabs <Tabs
items={tabs} items={tabs}
handleTabChange={handleTabChange} handleTabChange={handleTabChange}
defaultIndex={type === 'dynamic' ? 1 : type === 'free' ? 2 : 0} defaultIndex={type === 'free' ? 1 : 0}
className={styles.pricing} className={styles.pricing}
showRadio showRadio
/> />

View File

@ -82,13 +82,8 @@ export const initialValues: FormPublishData = {
], ],
pricing: { pricing: {
price: 0, price: 0,
type: type: allowFixedPricing === 'true' ? 'fixed' : 'free',
allowDynamicPricing === 'true' amountDataToken: 1000,
? 'dynamic'
: allowFixedPricing === 'true'
? 'fixed'
: 'free',
amountDataToken: allowDynamicPricing === 'true' ? 100 : 1000,
amountOcean: 100, amountOcean: 100,
weightOnOcean: '5', // 50% on OCEAN weightOnOcean: '5', // 50% on OCEAN
weightOnDataToken: '5', // 50% on datatoken weightOnDataToken: '5', // 50% on datatoken

View File

@ -210,7 +210,7 @@ export async function createTokensAndPricing(
// TODO: cap is hardcoded for now to 1000, this needs to be discussed at some point // TODO: cap is hardcoded for now to 1000, this needs to be discussed at some point
const ercParams: Erc20CreateParams = { const ercParams: Erc20CreateParams = {
templateIndex: values.pricing.type === 'dynamic' ? 1 : 2, templateIndex: 2,
minter: accountId, minter: accountId,
paymentCollector: accountId, paymentCollector: accountId,
mpFeeAddress: marketFeeAddress, mpFeeAddress: marketFeeAddress,
@ -228,63 +228,6 @@ export async function createTokensAndPricing(
// TODO: cleaner code for this huge switch !??!? // TODO: cleaner code for this huge switch !??!?
switch (values.pricing.type) { switch (values.pricing.type) {
case 'dynamic': {
// no vesting in market by default, maybe at a later time , vestingAmount and vestedBlocks are hardcoded
// we use only ocean as basetoken
// swapFeeLiquidityProvider is the swap fee of the liquidity providers
// swapFeeMarketRunner is the swap fee of the market where the swap occurs
const poolParams: PoolCreationParams = {
ssContract: config.sideStakingAddress,
baseTokenAddress: config.oceanTokenAddress,
baseTokenSender: config.erc721FactoryAddress,
publisherAddress: accountId,
marketFeeCollector: marketFeeAddress,
poolTemplateAddress: config.poolTemplateAddress,
rate: new Decimal(1).div(values.pricing.price).toString(),
baseTokenDecimals: 18,
vestingAmount: '0',
vestedBlocks: 2726000,
initialBaseTokenLiquidity: values.pricing.amountOcean.toString(),
swapFeeLiquidityProvider: (values.pricing.swapFee / 100).toString(),
swapFeeMarketRunner: publisherMarketPoolSwapFee
}
LoggerInstance.log(
'[publish] Creating dynamic pricing with poolParams',
poolParams
)
// the spender in this case is the erc721Factory because we are delegating
const txApprove = await approve(
web3,
accountId,
config.oceanTokenAddress,
config.erc721FactoryAddress,
values.pricing.amountOcean.toString(),
false
)
LoggerInstance.log('[publish] pool.approve tx', txApprove, nftFactory)
if (!txApprove) {
throw new Error(
'MetaMask Approve TX Signature: User denied transaction signature'
)
}
const result = await nftFactory.createNftErc20WithPool(
accountId,
nftCreateData,
ercParams,
poolParams
)
erc721Address = result.events.NFTCreated.returnValues[0]
datatokenAddress = result.events.TokenCreated.returnValues[0]
txHash = result.transactionHash
LoggerInstance.log('[publish] createNftErcWithPool tx', txHash)
break
}
case 'fixed': { case 'fixed': {
const freParams: FreCreationParams = { const freParams: FreCreationParams = {
fixedRateAddress: config.fixedRateExchangeAddress, fixedRateAddress: config.fixedRateExchangeAddress,