Skip to content

Commit

Permalink
Merge pull request #1548 from zeitgeistpm/tr-trade-feed
Browse files Browse the repository at this point in the history
Trade feed
  • Loading branch information
Robiquet authored Jul 20, 2023
2 parents 292c881 + f9803f7 commit a971869
Show file tree
Hide file tree
Showing 7 changed files with 246 additions and 34 deletions.
74 changes: 74 additions & 0 deletions components/front-page/LatestTrades.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import Table, { TableColumn, TableData } from "components/ui/Table";
import Link from "next/link";
import { formatNumberLocalized } from "lib/util";
import { useLatestTrades } from "lib/hooks/queries/useLatestTrades";
import { ZTG } from "@zeitgeistpm/sdk-next";

const columns: TableColumn[] = [
{
header: "Market",
accessor: "question",
type: "component",
},
{
header: "Outcome",
accessor: "outcome",
type: "text",
},
{
header: "Trade",
accessor: "trade",
type: "text",
},
{
header: "Cost",
accessor: "cost",
type: "text",
},
{
header: "Price",
accessor: "price",
type: "text",
},
{
header: "Time",
accessor: "time",
type: "text",
},
];

const TradeHistoryTable = () => {
const { data: trades } = useLatestTrades();

const tableData: TableData[] | undefined = trades?.map((trade) => {
return {
question: (
<Link
href={`/markets/${trade.marketId}`}
className="text-[14px] line-clamp-1"
>
{trade?.question}
</Link>
),
outcome: trade.outcomeName,
trade: trade.type === "buy" ? "Buy" : "Sell",
cost: formatNumberLocalized(trade.cost.div(ZTG).toNumber()),
price: formatNumberLocalized(trade.outcomePrice.toNumber()),
time: new Intl.DateTimeFormat("default", {
dateStyle: "medium",
timeStyle: "medium",
}).format(trade.time),
};
});

return (
<div className="mb-7">
<h2 className="sm:col-span-2 text-center sm:text-start mb-7">
Latest Trades
</h2>
<Table columns={columns} data={tableData} />
</div>
);
};

export default TradeHistoryTable;
1 change: 0 additions & 1 deletion components/front-page/PopularCategories.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { FC } from "react";
import Image from "next/image";
import Link from "next/link";
import { motion } from "framer-motion";

export const CATEGORIES = [
{ name: "Sports", imagePath: "/category/sports.png" },
Expand Down
2 changes: 1 addition & 1 deletion components/portfolio/PortfolioIdentity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const PortfolioIdentity = ({ address }: { address: string }) => {
return (
<div className="flex flex-col items-center justify-center w-full gap-y-3 min-h-[200px]">
{address && <Avatar address={address} size={120} />}
{identity?.twitter && (
{identity?.displayName && (
<div className=" font-extrabold text-[28px] sm:text-[38px]">
{identity.displayName}
</div>
Expand Down
40 changes: 40 additions & 0 deletions lib/gql/market-header.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { gql } from "graphql-request";
import { marketMetaFilter } from "./constants";
import { IndexerContext, Sdk } from "@zeitgeistpm/sdk-next";

const marketHeaderQuery = gql`
query MarketTransactionHeader($marketIds: [Int!]) {
markets(
where: {
marketId_in: $marketIds
${marketMetaFilter}
}
orderBy: marketId_ASC
) {
marketId
question
categories {
name
}
}
}
`;

export type MarketHeader = {
marketId: number;
question: string;
categories: { name: string }[];
};

export const getMarketHeaders = async (
sdk: Sdk<IndexerContext>,
marketIds: number[],
) => {
const { markets } = await sdk.indexer.client.request<{
markets: MarketHeader[];
}>(marketHeaderQuery, {
marketIds: marketIds,
});

return markets;
};
126 changes: 126 additions & 0 deletions lib/hooks/queries/useLatestTrades.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { useQuery } from "@tanstack/react-query";
import { HistoricalSwapOrderByInput } from "@zeitgeistpm/indexer";
import {
getIndexOf,
getMarketIdOf,
IOBaseAssetId,
IOMarketOutcomeAssetId,
isIndexedSdk,
parseAssetId,
} from "@zeitgeistpm/sdk-next";
import Decimal from "decimal.js";
import { BLOCK_TIME_SECONDS } from "lib/constants";
import { getMarketHeaders, MarketHeader } from "lib/gql/market-header";
import { useSdkv2 } from "../useSdkv2";

export const transactionHistoryKey = "latest-trades";

export type TradeItem = {
marketId: number;
question: string;
type: "buy" | "sell";
outcomeName: string;
outcomePrice: Decimal;
time: Date;
cost: Decimal;
};

export const useLatestTrades = () => {
const [sdk, id] = useSdkv2();

const query = useQuery(
[id, transactionHistoryKey],
async () => {
if (isIndexedSdk(sdk)) {
const { historicalSwaps } = await sdk.indexer.historicalSwaps({
limit: 3,
order: HistoricalSwapOrderByInput.BlockNumberDesc,
});

const marketIds = new Set<number>();

historicalSwaps.forEach((swap) => {
const assetInId = parseAssetId(swap.assetIn).unwrap();
const assetOutId = parseAssetId(swap.assetOut).unwrap();

if (IOMarketOutcomeAssetId.is(assetInId)) {
marketIds.add(getMarketIdOf(assetInId));
} else if (IOMarketOutcomeAssetId.is(assetOutId)) {
marketIds.add(getMarketIdOf(assetOutId));
}
});

const marketIdsArray = Array.from(marketIds).sort((a, b) => a - b);

const markets = await getMarketHeaders(sdk, marketIdsArray);

const trades: TradeItem[] = historicalSwaps
.map((swap) => {
const market =
lookupMarket(swap.assetIn, markets) ??
lookupMarket(swap.assetOut, markets);
const outcome =
lookupOutcomeAsset(swap.assetIn, markets) ??
lookupOutcomeAsset(swap.assetOut, markets);

if (!market || !outcome) {
return;
}

const assetInId = parseAssetId(swap.assetIn).unwrap();
const assetInIsBaseAsset = IOBaseAssetId.is(assetInId);

const item: TradeItem = {
marketId: market.marketId,
question: market.question,
outcomeName: outcome,
type: assetInIsBaseAsset === true ? "buy" : "sell",
time: new Date(swap.timestamp),
cost:
assetInIsBaseAsset === true
? new Decimal(swap.assetAmountIn)
: new Decimal(swap.assetAmountOut),
outcomePrice:
assetInIsBaseAsset === true
? new Decimal(swap.assetAmountIn).div(swap.assetAmountOut)
: new Decimal(swap.assetAmountOut).div(swap.assetAmountIn),
};

return item;
})
.filter((trade): trade is TradeItem => trade != null);

return trades;
}

return [];
},
{
keepPreviousData: true,
enabled: Boolean(sdk && isIndexedSdk(sdk)),
refetchInterval: BLOCK_TIME_SECONDS * 1000,
},
);

return query;
};

const lookupOutcomeAsset = (asset: string, markets: MarketHeader[]) => {
const assetId = parseAssetId(asset).unwrap();
const market = lookupMarket(asset, markets);

if (IOMarketOutcomeAssetId.is(assetId)) {
const index = getIndexOf(assetId);
return market && market.categories[index].name;
}
};

const lookupMarket = (asset: string, markets: MarketHeader[]) => {
const assetId = parseAssetId(asset).unwrap();

if (IOMarketOutcomeAssetId.is(assetId)) {
const marketId = getMarketIdOf(assetId);
const market = markets.find((market) => market.marketId === marketId);
return market;
}
};
35 changes: 3 additions & 32 deletions lib/hooks/queries/useTradeHistory.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useQuery } from "@tanstack/react-query";
import { HistoricalSwapOrderByInput } from "@zeitgeistpm/indexer";
import {
getIndexOf,
getMarketIdOf,
Expand All @@ -11,31 +12,11 @@ import {
parseAssetId,
} from "@zeitgeistpm/sdk-next";
import Decimal from "decimal.js";
import { gql } from "graphql-request";
import { getMarketHeaders, MarketHeader } from "lib/gql/market-header";
import { useSdkv2 } from "../useSdkv2";
import { HistoricalSwapOrderByInput } from "@zeitgeistpm/indexer";
import { marketMetaFilter } from "lib/gql/constants";

export const transactionHistoryKey = "trade-history";

const marketHeaderQuery = gql`
query MarketTransactionHeader($marketIds: [Int!]) {
markets(
where: {
marketId_in: $marketIds
${marketMetaFilter}
}
orderBy: marketId_ASC
) {
marketId
question
categories {
name
}
}
}
`;

const lookupAssetName = (
asset: string,
marketsMap: Map<number, MarketHeader>,
Expand Down Expand Up @@ -90,12 +71,6 @@ const calculatePrice = (
}
};

type MarketHeader = {
marketId: number;
question: string;
categories: { name: string }[];
};

export type TradeHistoryItem = {
marketId: MarketId;
question: string;
Expand Down Expand Up @@ -144,11 +119,7 @@ export const useTradeHistory = (address?: string) => {

const marketIdsArray = Array.from(marketIds).sort((a, b) => a - b);

const { markets } = await sdk.indexer.client.request<{
markets: MarketHeader[];
}>(marketHeaderQuery, {
marketIds: marketIdsArray,
});
const markets = await getMarketHeaders(sdk, marketIdsArray);

const marketsMap: Map<number, MarketHeader> = new Map();
marketIdsArray.forEach((marketId) => {
Expand Down
2 changes: 2 additions & 0 deletions pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
IGetPlaiceholderOptions,
IGetPlaiceholderReturn,
} from "plaiceholder";
import LatestTrades from "components/front-page/LatestTrades";

const getPlaiceholders = (
paths: string[],
Expand Down Expand Up @@ -116,6 +117,7 @@ const IndexPage: NextPage<{
/>
</div>
)}
<LatestTrades />
</div>
</>
);
Expand Down

0 comments on commit a971869

Please sign in to comment.