diff --git a/.env.example b/.env.example index 8752a9076..04e49f347 100644 --- a/.env.example +++ b/.env.example @@ -24,7 +24,7 @@ NEXT_PUBLIC_AVATAR_COLLECTION_ID="2e55d4bf2e85715b63-ZEITASTAGE" NEXT_PUBLIC_SINGULAR_URL="https://singular-rmrk2-dev.vercel.app" NEXT_PUBLIC_RMRK_INDEXER_API="https://gql2.rmrk.dev/v1/graphql" NEXT_PUBLIC_IPFS_NODE="http://ipfs.zeitgeist.pm:5001" -NEXT_PUBLIC_RMRK_CHAIN_RPC_NODE="wss://staging.node.rmrk.app" +NEXT_PUBLIC_RMRK_CHAIN_RPC_NODE="wss://kusama-node-staging.rmrk.link/" NEXT_PUBLIC_AVATAR_API_HOST="https://avatar-bsr.zeitgeist.pm/" NEXT_PUBLIC_MIGRATION_IN_PROGRESS=false diff --git a/components/account/AccountButton.tsx b/components/account/AccountButton.tsx index 86015a130..9146e4f3d 100644 --- a/components/account/AccountButton.tsx +++ b/components/account/AccountButton.tsx @@ -14,7 +14,6 @@ import { useWallet } from "lib/state/wallet"; import { formatNumberLocalized, shortenAddress } from "lib/util"; import { FaNetworkWired } from "react-icons/fa"; -import dynamic from "next/dynamic"; import Link from "next/link"; import { useRouter } from "next/router"; import React, { FC, Fragment, PropsWithChildren, useState } from "react"; diff --git a/components/markets/market-card/index.tsx b/components/markets/market-card/index.tsx index d9c85b8c5..74a2c9865 100644 --- a/components/markets/market-card/index.tsx +++ b/components/markets/market-card/index.tsx @@ -1,6 +1,5 @@ import type { ScalarRangeType } from "@zeitgeistpm/sdk"; import Skeleton from "components/ui/Skeleton"; -import { motion } from "framer-motion"; import Decimal from "decimal.js"; import { ZTG } from "lib/constants"; import { MarketOutcomes } from "lib/types/markets"; @@ -18,8 +17,6 @@ import { } from "@zeitgeistpm/sdk"; import { lookupAssetImagePath } from "lib/constants/foreign-asset"; import Image from "next/image"; -import MarketImage from "components/ui/MarketImage"; -import { useHover } from "lib/hooks/events/useHover"; export interface IndexedMarketCardData { marketId: number; @@ -28,11 +25,11 @@ export interface IndexedMarketCardData { creation: string; creator: string; outcomes: MarketOutcomes; - marketType: { categorical?: string; scalar?: string[] }; - scalarType: ScalarRangeType; + marketType: MarketType; + scalarType: ScalarRangeType | null; prediction: { name: string; price: number }; volume: number; - pool: { poolId?: number; volume: string } | null; + pool?: { poolId?: number; volume: string } | null; baseAsset: string; tags?: string[]; status: string; @@ -40,6 +37,12 @@ export interface IndexedMarketCardData { liquidity?: string; numParticipants?: number; } + +export interface MarketType { + categorical?: string; + scalar?: string[]; +} + export interface MarketCardProps extends IndexedMarketCardData { className?: string; disableLink?: boolean; @@ -256,7 +259,7 @@ export const MarketCard = ({ ) : pool && marketType?.categorical ? ( - ) : pool && Object.keys(pool).length !== 0 ? ( + ) : pool && scalarType && Object.keys(pool).length !== 0 ? ( => { const ids = await getFeaturedMarketIds(); - const featuredMarkets = await Promise.all( - ids.map(async (id) => { - const marketRes = await client.request<{ - markets: { - pool: { - poolId: number; - volume: string; - } | null; - marketId: number; - baseAsset: string; - img: string; - question: string; - creator: string; - creation: ZeitgeistPrimitivesMarketMarketCreation["type"]; - marketType: { [key: string]: string }; - scalarType: ScalarRangeType; - categories: { color: string; name: string }[]; - outcomeAssets: string[]; - tags: []; - status: string; - period: { end: string }; - }[]; - }>(marketQuery, { - marketId: id, - }); - - const market = marketRes.markets[0]; - - if (!market) return; - const pool = market.pool; - - if (!pool) { - const noPoolMarket: IndexedMarketCardData = { - marketId: market.marketId, - question: market.question, - creation: market.creation, - creator: market.creator, - pool: market.pool, - img: market.img, - prediction: { name: "None", price: 0 }, - marketType: market.marketType, - scalarType: market.scalarType, - volume: 0, - baseAsset: market.baseAsset, - outcomes: [], - tags: [], - status: market.status, - endDate: market.period.end, + const { markets } = await sdk.indexer.markets({ + where: { + marketId_in: ids, + }, + }); + + const featuredMarkets: IndexedMarketCardData[] = markets.map((market) => { + const marketCategories: MarketOutcomes = + market.categories?.map((category, index) => { + const asset = market.assets[index]; + const marketCategory: MarketOutcome = { + name: category.name ?? "", + assetId: market.outcomeAssets[index], + price: asset?.price ?? 0, }; - return noPoolMarket; - } - const assetsRes = await client.request<{ - assets: { - pool: { poolId: number }; - price: number; - assetId: string; - }[]; - }>(assetsQuery, { - poolId: pool.poolId, - }); - - const assets = assetsRes.assets; - - const prediction = getCurrentPrediction(assets, market); - - const marketCategories: MarketOutcomes = market.categories.map( - (category, index) => { - const asset = assets[index]; - const marketCategory: MarketOutcome = { - ...category, - assetId: market.outcomeAssets[index], - price: asset.price, - }; - - return marketCategory; - }, - ); - const featuredMarket: IndexedMarketCardData = { - marketId: market.marketId, - question: market.question, - creation: market.creation, - creator: market.creator, - img: market.img, - prediction: prediction, - volume: new Decimal(pool.volume).div(ZTG).toNumber(), - baseAsset: market.baseAsset, - outcomes: marketCategories, - pool: market.pool, - marketType: market.marketType, - scalarType: market.scalarType, - tags: market.tags, - status: market.status, - endDate: market.period.end, - }; - - return featuredMarket; - }), - ); - - const filterMarkets = featuredMarkets.filter(isPresent); - - return filterMarkets; + return marketCategory; + }) ?? []; + + const prediction = getCurrentPrediction(market.assets, market); + + const cardMarket: IndexedMarketCardData = { + marketId: market.marketId, + question: market.question ?? "", + creation: market.creation, + creator: market.creator, + img: market.img ?? "", + prediction: prediction, + volume: new Decimal(market.pool?.volume ?? 0).div(ZTG).toNumber(), + baseAsset: market.baseAsset, + outcomes: marketCategories, + pool: market.pool, + marketType: market.marketType as MarketType, + scalarType: (market.scalarType ?? null) as ScalarRangeType, + tags: market.tags?.filter(isPresent) ?? [], + status: market.status, + endDate: market.period.end, + }; + return cardMarket; + }); + + return featuredMarkets; }; export default getFeaturedMarkets; diff --git a/lib/hooks/queries/useAmm2MarketSpotPrices.ts b/lib/hooks/queries/useAmm2MarketSpotPrices.ts new file mode 100644 index 000000000..37933d76b --- /dev/null +++ b/lib/hooks/queries/useAmm2MarketSpotPrices.ts @@ -0,0 +1,58 @@ +import { useQuery } from "@tanstack/react-query"; +import { AssetId, isRpcSdk } from "@zeitgeistpm/sdk"; +import Decimal from "decimal.js"; +import { calculateSpotPrice } from "lib/util/amm2"; +import { getApiAtBlock } from "lib/util/get-api-at"; +import { useSdkv2 } from "../useSdkv2"; + +export const amm2MarketSpotPricesRootKey = "amm2-market-spot-prices"; + +export const useAmm2MarketSpotPrices = ( + marketIds?: number[], + blockNumber?: number, +) => { + const [sdk, id] = useSdkv2(); + + const enabled = !!sdk && !!marketIds && isRpcSdk(sdk); + const query = useQuery( + [id, amm2MarketSpotPricesRootKey, blockNumber], + + async () => { + if (!enabled) return; + const api = await getApiAtBlock(sdk.api, blockNumber); + + const pools = await Promise.all( + marketIds.map((marketId) => api.query.neoSwaps.pools(marketId)), + ); + + const prices: { [key: string]: Decimal } = {}; + + pools.forEach((wrappedPool) => { + const pool = wrappedPool.unwrapOr(null); + if (pool) { + pool.reserves.forEach((reserve, asset) => { + prices[asset.toString().toLowerCase()] = calculateSpotPrice( + new Decimal(reserve.toString()), + new Decimal(pool.liquidityParameter.toString()), + ); + }); + } + }); + + return prices; + }, + { + enabled: enabled, + staleTime: 10_000, + }, + ); + + return query; +}; + +export const lookupAssetPrice = ( + assetId: AssetId, + prices?: { [key: string]: Decimal }, +): Decimal | undefined => { + return prices?.[JSON.stringify(assetId).toLowerCase()]; +}; diff --git a/lib/hooks/queries/useInfiniteMarkets.ts b/lib/hooks/queries/useInfiniteMarkets.ts index cfb004890..6eb7aea5b 100644 --- a/lib/hooks/queries/useInfiniteMarkets.ts +++ b/lib/hooks/queries/useInfiniteMarkets.ts @@ -13,6 +13,7 @@ import { MarketOutcomes } from "lib/types/markets"; import { MarketStatus } from "@zeitgeistpm/indexer"; import { hiddenMarketIds } from "lib/constants/markets"; import { marketMetaFilter } from "./constants"; +import { ScoringRule } from "@zeitgeistpm/indexer"; export const rootKey = "markets-filtered"; @@ -62,45 +63,62 @@ export const useInfiniteMarkets = ( const markets: Market[] = await sdk.model.markets.list({ where: { - ...validMarketWhereInput, - status_not_in: [MarketStatus.Destroyed], - status_in: statuses.length === 0 ? undefined : statuses, - tags_containsAny: tags?.length === 0 ? undefined : tags, - pool_isNull: withLiquidityOnly ? false : undefined, - baseAsset_in: currencies?.length !== 0 ? currencies : undefined, - ...(withLiquidityOnly - ? { - pool: { - account: { - balances_some: { - balance_gt: 0, - }, - }, + AND: [ + { + ...validMarketWhereInput, + status_not_in: [MarketStatus.Destroyed], + status_in: statuses.length === 0 ? undefined : statuses, + tags_containsAny: tags?.length === 0 ? undefined : tags, + baseAsset_in: currencies?.length !== 0 ? currencies : undefined, + }, + { + OR: [ + { + scoringRule_eq: ScoringRule.Cpmm, + pool_isNull: withLiquidityOnly ? false : undefined, + ...(withLiquidityOnly + ? { + pool: { + account: { + balances_some: { + balance_gt: 0, + }, + }, + }, + } + : {}), }, - } - : {}), + { + scoringRule_eq: ScoringRule.Lmsr, + neoPool_isNull: withLiquidityOnly ? false : undefined, + }, + ], + }, + ], }, offset: !pageParam ? 0 : limit * pageParam, limit: limit, order: orderByMap[orderBy], }); - const outcomes = await getOutcomesForMarkets(sdk.indexer.client, markets); - - let resMarkets: Array = []; + const resMarkets: Array = markets.map((market) => { + const outcomes: MarketOutcomes = market.assets.map((asset, index) => { + return { + price: asset.price, + name: market.categories?.[index].name ?? "", + assetId: asset.assetId, + amountInPool: asset.amountInPool, + }; + }); - for (const m of markets) { - const marketOutcomes = outcomes[m.marketId]; - const prediction = - m.pool != null - ? getCurrentPrediction(marketOutcomes, m as any) - : { name: "None", price: 0 }; + const prediction = getCurrentPrediction(outcomes, market); - resMarkets = [ - ...resMarkets, - { ...m, outcomes: marketOutcomes, prediction }, - ]; - } + return { + ...market, + outcomes, + prediction, + }; + }); return { data: resMarkets, diff --git a/lib/hooks/queries/usePortfolioPositions.ts b/lib/hooks/queries/usePortfolioPositions.ts index 923724ebf..c9e3985aa 100644 --- a/lib/hooks/queries/usePortfolioPositions.ts +++ b/lib/hooks/queries/usePortfolioPositions.ts @@ -44,6 +44,11 @@ import { ForeignAssetPrices, useAllForeignAssetUsdPrices, } from "./useAssetUsdPrice"; +import { ScoringRule } from "@zeitgeistpm/indexer"; +import { + lookupAssetPrice, + useAmm2MarketSpotPrices, +} from "./useAmm2MarketSpotPrices"; export type UsePortfolioPositions = { /** @@ -74,9 +79,9 @@ export type Position = { */ market: FullMarketFragment; /** - * The pool related to the position. + * The cpmm pool related to the position. */ - pool: IndexedPool; + pool?: IndexedPool; /** * The outcome of the position. Name of the outcome. */ @@ -113,7 +118,7 @@ export type Position = { * The total issuance of the positions pool shares. * @nb This is only available for pool share positions. */ - totalIssuance: Decimal; + totalIssuance?: Decimal; /** * The change in the price of the position the last 24 hours. */ @@ -195,6 +200,16 @@ export const usePortfolioPositions = ( const pools = usePoolsByIds(filter); const markets = useMarketsByIds(filter); + const amm2MarketIds = markets.data + ?.filter((market) => market.scoringRule === ScoringRule.Lmsr) + .map((m) => m.marketId); + + const { data: amm2SpotPrices } = useAmm2MarketSpotPrices(amm2MarketIds); + + const { data: amm2SpotPrices24HoursAgo } = useAmm2MarketSpotPrices( + amm2MarketIds, + block24HoursAgo, + ); const poolAccountIds = usePoolAccountIds(pools.data); @@ -322,21 +337,23 @@ export const usePortfolioPositions = ( pool = pools.data?.find((pool) => pool.marketId === marketId); } - if (!market || !pool) { + if (!market) { continue; } const balance = address ? userAssetBalances?.get(address, assetId)?.data?.balance : undefined; - const totalIssuanceForPoolQuery = poolsTotalIssuance[pool.poolId]; - const totalIssuanceData = poolsTotalIssuance[pool.poolId]?.data; + const totalIssuanceForPoolQuery = pool && poolsTotalIssuance[pool.poolId]; + const totalIssuanceData = pool && poolsTotalIssuance[pool.poolId]?.data; const userBalance = new Decimal(balance?.free.toNumber() ?? 0); - const totalIssuance = new Decimal( - totalIssuanceForPoolQuery.data?.totalIssuance.toString() ?? 0, - ); + const totalIssuance = + totalIssuanceForPoolQuery && + new Decimal( + totalIssuanceForPoolQuery.data?.totalIssuance.toString() ?? 0, + ); let price: Decimal | undefined; let price24HoursAgo: Decimal | undefined; @@ -346,23 +363,32 @@ export const usePortfolioPositions = ( price = calcResolvedMarketPrices(market).get(getIndexOf(assetId)); price24HoursAgo = price; } else { - price = - calculatePrice( - pool, + if (market.scoringRule === ScoringRule.Cpmm && pool) { + price = + calculatePrice( + pool, + assetId, + poolsZtgBalances.data, + poolAssetBalances, + ) ?? undefined; + + price24HoursAgo = + calculatePrice( + pool, + assetId, + poolsZtgBalances24HoursAgo.data, + poolAssetBalances24HoursAgo, + ) ?? undefined; + } else if (market.scoringRule === ScoringRule.Lmsr) { + price = lookupAssetPrice(assetId, amm2SpotPrices); + + price24HoursAgo = lookupAssetPrice( assetId, - poolsZtgBalances.data, - poolAssetBalances, - ) ?? undefined; - - price24HoursAgo = - calculatePrice( - pool, - assetId, - poolsZtgBalances24HoursAgo.data, - poolAssetBalances24HoursAgo, - ) ?? undefined; + amm2SpotPrices24HoursAgo, + ); + } } - } else if (IOPoolShareAssetId.is(assetId)) { + } else if (IOPoolShareAssetId.is(assetId) && pool) { const poolAssetIds = pool.weights .map((w) => parseAssetId(w.assetId).unwrap()) .filter(IOMarketOutcomeAssetId.is.bind(IOMarketOutcomeAssetId)); diff --git a/package.json b/package.json index 86e859726..24776496d 100644 --- a/package.json +++ b/package.json @@ -39,11 +39,11 @@ "@web3auth/modal": "^7.0.4", "@yornaath/batshit": "^0.7.1", "@yornaath/batshit-devtools-react": "^0.5.4", - "@zeitgeistpm/augment-api": "2.23.0", + "@zeitgeistpm/augment-api": "2.23.1", "@zeitgeistpm/avatara-nft-sdk": "^1.3.1", "@zeitgeistpm/avatara-react": "^1.3.2", "@zeitgeistpm/avatara-util": "^1.2.0", - "@zeitgeistpm/sdk": "2.47.0", + "@zeitgeistpm/sdk": "2.47.1", "@zeitgeistpm/utility": "^2.20.0", "axios": "^0.21.4", "boring-avatars": "^1.6.1", diff --git a/yarn.lock b/yarn.lock index 42da8ca2c..5cfb8d846 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4438,14 +4438,14 @@ __metadata: languageName: node linkType: hard -"@zeitgeistpm/augment-api@npm:2.23.0, @zeitgeistpm/augment-api@npm:^2.23.0": - version: 2.23.0 - resolution: "@zeitgeistpm/augment-api@npm:2.23.0" +"@zeitgeistpm/augment-api@npm:2.23.1, @zeitgeistpm/augment-api@npm:^2.23.1": + version: 2.23.1 + resolution: "@zeitgeistpm/augment-api@npm:2.23.1" peerDependencies: "@polkadot/api-base": "*" "@polkadot/rpc-core": "*" "@polkadot/types": "*" - checksum: 482ea1c15b8c2e42325b730015979d16b369348307497d0a3c9451dee20aaa507e1dd1f6c0b241ca05c8d466bac075c0146ec917d96222e67c4df41abf213c79 + checksum: 87751b2b100b5bc304e0a4a925cd7bbb15fb90eb03b6ea229d8f9ec4043a33c5ee20a5a915d9576daf73cfd1b2692c2945455b2c6e43592e3be9e4d14632991f languageName: node linkType: hard @@ -4526,14 +4526,14 @@ __metadata: languageName: node linkType: hard -"@zeitgeistpm/indexer@npm:^3.18.0": - version: 3.18.0 - resolution: "@zeitgeistpm/indexer@npm:3.18.0" +"@zeitgeistpm/indexer@npm:^3.18.1": + version: 3.18.1 + resolution: "@zeitgeistpm/indexer@npm:3.18.1" dependencies: graphql: ^16.6.0 graphql-request: ^5.0.0 graphql-tag: ^2.12.6 - checksum: befbc749a39fa3777fd7c7036056010322ee5270bef661bc72c0e69d52ca64a86cfea747a481d97b5cd7f43961863d12a658eb9eed853d140f9570d4c1bfd4da + checksum: 8dd36e336f74fee95ad09c7f94f0cd0217fbc68820b661982e381fa8102fe547239ae955cc21355e94f26d6276cf4d4ee074b90715ecee00634bf9bfd4bcbc0f languageName: node linkType: hard @@ -4551,12 +4551,12 @@ __metadata: languageName: node linkType: hard -"@zeitgeistpm/sdk@npm:2.47.0": - version: 2.47.0 - resolution: "@zeitgeistpm/sdk@npm:2.47.0" +"@zeitgeistpm/sdk@npm:2.47.1": + version: 2.47.1 + resolution: "@zeitgeistpm/sdk@npm:2.47.1" dependencies: - "@zeitgeistpm/augment-api": ^2.23.0 - "@zeitgeistpm/indexer": ^3.18.0 + "@zeitgeistpm/augment-api": ^2.23.1 + "@zeitgeistpm/indexer": ^3.18.1 "@zeitgeistpm/rpc": ^2.14.0 "@zeitgeistpm/utility": ^2.24.0 "@zeitgeistpm/web3.storage": ^2.15.0 @@ -4573,7 +4573,7 @@ __metadata: "@polkadot/api": "*" "@polkadot/types": "*" "@polkadot/util": "*" - checksum: ffcb1a82229a5f5f225262513f554d89ca642555a2311dacb38d17b44164a8c273e60080f0ee238ff43de869626d7c129981d093ab015ab23d9ca2bddd7ca3e4 + checksum: 44186464d5d99301ac9dd3af5732fbac7aa6bde8ce38f6b2f7f49f51f7577f188af794af030107738b2e5c5dfafe798ac4809ad38950c4276717ad59aa9a71c7 languageName: node linkType: hard @@ -4631,11 +4631,11 @@ __metadata: "@web3auth/modal": ^7.0.4 "@yornaath/batshit": ^0.7.1 "@yornaath/batshit-devtools-react": ^0.5.4 - "@zeitgeistpm/augment-api": 2.23.0 + "@zeitgeistpm/augment-api": 2.23.1 "@zeitgeistpm/avatara-nft-sdk": ^1.3.1 "@zeitgeistpm/avatara-react": ^1.3.2 "@zeitgeistpm/avatara-util": ^1.2.0 - "@zeitgeistpm/sdk": 2.47.0 + "@zeitgeistpm/sdk": 2.47.1 "@zeitgeistpm/utility": ^2.20.0 autoprefixer: 10.2.5 axios: ^0.21.4