-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1548 from zeitgeistpm/tr-trade-feed
Trade feed
- Loading branch information
Showing
7 changed files
with
246 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters