From e5f929085c21850c303aa4b75a04e113fdfc9e33 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sehyun=20Chung=20=E2=9C=8C=EF=B8=8E?=
<41171808+sehyunc@users.noreply.github.com>
Date: Wed, 27 Mar 2024 13:24:09 -0700
Subject: [PATCH] trade: integrate price reporter
---
.../app/(desktop)/[base]/[quote]/page.tsx | 30 ++---
.../app/(desktop)/[base]/[quote]/trading.tsx | 4 +-
.../app/(desktop)/[base]/page.tsx | 28 ++--
trade.renegade.fi/app/(desktop)/layout.tsx | 1 +
trade.renegade.fi/app/(mobile)/m/body.tsx | 4 +-
.../app/(mobile)/m/tokens-banner.tsx | 10 +-
trade.renegade.fi/app/providers.tsx | 11 +-
.../components/banners/median-banner.tsx | 119 +++++++++-------
.../components/banners/tokens-banner.tsx | 12 +-
trade.renegade.fi/components/live-price.tsx | 88 ++++--------
.../components/panels/wallets-panel.tsx | 4 +-
.../components/place-order-button.tsx | 29 ++--
.../contexts/Exchange/exchange-context.tsx | 9 +-
trade.renegade.fi/contexts/Exchange/types.ts | 13 +-
.../contexts/PriceContext/price-context.tsx | 127 ++++++++++++++++++
trade.renegade.fi/env.mjs | 14 +-
trade.renegade.fi/hooks/use-price.ts | 27 ++++
trade.renegade.fi/hooks/use-usd-price.ts | 53 ++------
trade.renegade.fi/lib/utils.ts | 35 ++++-
trade.renegade.fi/package.json | 2 +-
trade.renegade.fi/yarn.lock | 8 +-
21 files changed, 383 insertions(+), 245 deletions(-)
create mode 100644 trade.renegade.fi/contexts/PriceContext/price-context.tsx
create mode 100644 trade.renegade.fi/hooks/use-price.ts
diff --git a/trade.renegade.fi/app/(desktop)/[base]/[quote]/page.tsx b/trade.renegade.fi/app/(desktop)/[base]/[quote]/page.tsx
index 508d0a88..7de897b2 100644
--- a/trade.renegade.fi/app/(desktop)/[base]/[quote]/page.tsx
+++ b/trade.renegade.fi/app/(desktop)/[base]/[quote]/page.tsx
@@ -1,4 +1,3 @@
-import { Renegade } from "@renegade-fi/renegade-js"
import Image from "next/image"
import { Main } from "@/app/(desktop)/[base]/[quote]/main"
@@ -6,9 +5,7 @@ import { MedianBanner } from "@/components/banners/median-banner"
import { RelayerStatusData } from "@/components/banners/relayer-status-data"
import { OrdersAndCounterpartiesPanel } from "@/components/panels/orders-panel"
import { WalletsPanel } from "@/components/panels/wallets-panel"
-import { env } from "@/env.mjs"
import backgroundPattern from "@/icons/background_pattern.png"
-import { getToken } from "@/lib/utils"
// export function generateStaticParams() {
// return DISPLAYED_TICKERS.filter(([base]) => base !== "USDC").map(
@@ -21,24 +18,25 @@ import { getToken } from "@/lib/utils"
// )
// }
-const renegade = new Renegade({
- relayerHostname: env.NEXT_PUBLIC_RENEGADE_RELAYER_HOSTNAME,
- relayerHttpPort: 3000,
- relayerWsPort: 4000,
- useInsecureTransport:
- env.NEXT_PUBLIC_RENEGADE_RELAYER_HOSTNAME === "localhost",
- verbose: false,
-})
+// const renegade = new Renegade({
+// relayerHostname: env.NEXT_PUBLIC_RENEGADE_RELAYER_HOSTNAME,
+// relayerHttpPort: 3000,
+// relayerWsPort: 4000,
+// useInsecureTransport:
+// env.NEXT_PUBLIC_RENEGADE_RELAYER_HOSTNAME === "localhost",
+// verbose: false,
+// })
export default async function Page({
params: { base, quote },
}: {
params: { base: string; quote: string }
}) {
- const report = await renegade.queryExchangeHealthStates(
- getToken({ input: base }),
- getToken({ input: quote })
- )
+ // const report = await renegade.queryPriceReporter(
+ // getToken({ input: base }),
+ // getToken({ input: quote })
+ // )
+ // console.log("🚀 ~ report:", report)
return (
diff --git a/trade.renegade.fi/app/(desktop)/[base]/[quote]/trading.tsx b/trade.renegade.fi/app/(desktop)/[base]/[quote]/trading.tsx
index efe38b47..b41660ce 100644
--- a/trade.renegade.fi/app/(desktop)/[base]/[quote]/trading.tsx
+++ b/trade.renegade.fi/app/(desktop)/[base]/[quote]/trading.tsx
@@ -10,6 +10,7 @@ import { PlaceOrderButton } from "@/components/place-order-button"
import { OrderProvider, useOrder } from "@/contexts/Order/order-context"
import { Direction } from "@/contexts/Order/types"
import { useUSDPrice } from "@/hooks/use-usd-price"
+import { formatPrice } from "@/lib/utils"
interface SelectableProps {
text: string
@@ -181,6 +182,7 @@ function TradingInner() {
function HelperText({ baseTicker }: { baseTicker: string }) {
const usdPrice = useUSDPrice(baseTicker, 1)
+ const formattedUsdPrice = formatPrice(usdPrice)
return (
Trades are end-to-end encrypted and always clear at the real-time
- midpoint of ${usdPrice}
+ midpoint of ${formattedUsdPrice}
)
diff --git a/trade.renegade.fi/app/(desktop)/[base]/page.tsx b/trade.renegade.fi/app/(desktop)/[base]/page.tsx
index b57a0cd5..5cbacb6d 100644
--- a/trade.renegade.fi/app/(desktop)/[base]/page.tsx
+++ b/trade.renegade.fi/app/(desktop)/[base]/page.tsx
@@ -1,4 +1,3 @@
-import { Renegade, Token } from "@renegade-fi/renegade-js"
import Image from "next/image"
import { DepositBody } from "@/app/(desktop)/[base]/[quote]/deposit"
@@ -6,7 +5,6 @@ import { MedianBanner } from "@/components/banners/median-banner"
import { RelayerStatusData } from "@/components/banners/relayer-status-data"
import { OrdersAndCounterpartiesPanel } from "@/components/panels/orders-panel"
import { WalletsPanel } from "@/components/panels/wallets-panel"
-import { env } from "@/env.mjs"
import backgroundPattern from "@/icons/background_pattern.png"
// export function generateStaticParams() {
@@ -18,24 +16,24 @@ import backgroundPattern from "@/icons/background_pattern.png"
// })
// }
-const renegade = new Renegade({
- relayerHostname: env.NEXT_PUBLIC_RENEGADE_RELAYER_HOSTNAME,
- relayerHttpPort: 3000,
- relayerWsPort: 4000,
- useInsecureTransport:
- env.NEXT_PUBLIC_RENEGADE_RELAYER_HOSTNAME === "localhost",
- verbose: false,
-})
+// const renegade = new Renegade({
+// relayerHostname: env.NEXT_PUBLIC_RENEGADE_RELAYER_HOSTNAME,
+// relayerHttpPort: 3000,
+// relayerWsPort: 4000,
+// useInsecureTransport:
+// env.NEXT_PUBLIC_RENEGADE_RELAYER_HOSTNAME === "localhost",
+// verbose: false,
+// })
export default async function Page({
params: { base, quote },
}: {
params: { base: string; quote: string }
}) {
- const report = await renegade.queryExchangeHealthStates(
- new Token({ ticker: base === "USDC" ? "WETH" : base }),
- new Token({ ticker: "USDC" })
- )
+ // const report = await renegade.queryExchangeHealthStates(
+ // new Token({ ticker: base === "USDC" ? "WETH" : base }),
+ // new Token({ ticker: "USDC" })
+ // )
return (
diff --git a/trade.renegade.fi/app/(desktop)/layout.tsx b/trade.renegade.fi/app/(desktop)/layout.tsx
index 5ec8ed32..613742a7 100644
--- a/trade.renegade.fi/app/(desktop)/layout.tsx
+++ b/trade.renegade.fi/app/(desktop)/layout.tsx
@@ -39,6 +39,7 @@ export default async function RootLayout({
}) {
const icons = await TICKER_TO_LOGO_URL_HANDLE
const prices = await getTokenBannerData(renegade)
+ console.log("🚀 ~ prices:", prices)
return (
diff --git a/trade.renegade.fi/app/(mobile)/m/body.tsx b/trade.renegade.fi/app/(mobile)/m/body.tsx
index 38fc782d..550d621d 100644
--- a/trade.renegade.fi/app/(mobile)/m/body.tsx
+++ b/trade.renegade.fi/app/(mobile)/m/body.tsx
@@ -2,7 +2,7 @@
import { ArrowForwardIcon } from "@chakra-ui/icons"
import { Button, Flex, HStack, Text } from "@chakra-ui/react"
-import { ExchangeHealthState, PriceReport } from "@renegade-fi/renegade-js"
+import { ExchangeHealthState } from "@renegade-fi/renegade-js"
import Image from "next/image"
import { TokensBanner } from "@/app/(mobile)/m/tokens-banner"
@@ -74,7 +74,7 @@ export function MobileBody({
prices,
report,
}: {
- prices: (PriceReport | undefined)[]
+ prices: number[]
report: ExchangeHealthState
}) {
return (
diff --git a/trade.renegade.fi/app/(mobile)/m/tokens-banner.tsx b/trade.renegade.fi/app/(mobile)/m/tokens-banner.tsx
index 530bbc77..aed71022 100644
--- a/trade.renegade.fi/app/(mobile)/m/tokens-banner.tsx
+++ b/trade.renegade.fi/app/(mobile)/m/tokens-banner.tsx
@@ -1,7 +1,7 @@
"use client"
import { Box, Stack, Text } from "@chakra-ui/react"
-import { Exchange, PriceReport } from "@renegade-fi/renegade-js"
+import { Exchange } from "@renegade-fi/renegade-js"
import { LivePrices } from "@/components/live-price"
@@ -15,11 +15,7 @@ const DISPLAYED_TICKERS = [
["LDO", "USDC"],
]
-export function TokensBanner({
- prices,
-}: {
- prices: (PriceReport | undefined)[]
-}) {
+export function TokensBanner({ prices }: { prices: number[] }) {
return (
)
diff --git a/trade.renegade.fi/app/providers.tsx b/trade.renegade.fi/app/providers.tsx
index 4dabada9..3d445d5a 100644
--- a/trade.renegade.fi/app/providers.tsx
+++ b/trade.renegade.fi/app/providers.tsx
@@ -21,6 +21,7 @@ import { WagmiProvider, createConfig } from "wagmi"
import { AppProvider } from "@/contexts/App/app-context"
import { ExchangeProvider } from "@/contexts/Exchange/exchange-context"
+import { PriceProvider } from "@/contexts/PriceContext/price-context"
import { RenegadeProvider } from "@/contexts/Renegade/renegade-context"
import { env } from "@/env.mjs"
import { stylusDevnetEc2 } from "@/lib/viem"
@@ -247,10 +248,12 @@ export function Providers({
>
-
-
- {children}
-
+
+
+
+ {children}
+
+
diff --git a/trade.renegade.fi/components/banners/median-banner.tsx b/trade.renegade.fi/components/banners/median-banner.tsx
index 11171c5d..271b1c06 100644
--- a/trade.renegade.fi/components/banners/median-banner.tsx
+++ b/trade.renegade.fi/components/banners/median-banner.tsx
@@ -4,7 +4,6 @@ import { Box, Flex, Link, Spacer, Stack, Text } from "@chakra-ui/react"
import {
Exchange,
ExchangeHealthState,
- HealthState,
PriceReport,
Token,
} from "@renegade-fi/renegade-js"
@@ -13,6 +12,7 @@ import React from "react"
import { BannerSeparator } from "@/components/banner-separator"
import { LivePrices } from "@/components/live-price"
import { PulsingConnection } from "@/components/pulsing-connection-indicator"
+import { useExchangePrice } from "@/hooks/use-price"
function LinkWrapper(props: {
link?: string
@@ -60,7 +60,7 @@ interface ExchangeConnectionTripleProps {
activeBaseTicker: string
activeQuoteTicker: string
exchange: Exchange
- priceReport: PriceReport
+ priceReport?: PriceReport
isMobile?: boolean
}
function ExchangeConnectionTriple(props: ExchangeConnectionTripleProps) {
@@ -94,41 +94,62 @@ function ExchangeConnectionTriple(props: ExchangeConnectionTripleProps) {
median: "",
}[props.exchange]
- let healthState = props.priceReport.healthState
- let showPrice: boolean
- let connectionText: string
- let textVariant: string
- if (healthState === HealthState.enum.Connecting) {
- showPrice = true
- connectionText = "LIVE"
- textVariant = "status-green"
- } else if (healthState === HealthState.enum.Unsupported) {
- showPrice = false
- connectionText = "UNSUPPORTED"
- textVariant = "status-gray"
- } else if (healthState === HealthState.enum.Live) {
- showPrice = true
- connectionText = "LIVE"
- textVariant = "status-green"
- } else if (healthState === HealthState.enum.NoDataReported) {
- showPrice = false
- connectionText = "NO DATA"
- textVariant = "status-gray"
- } else if (healthState === HealthState.enum.TooStale) {
- showPrice = false
- connectionText = "TOO STALE"
- textVariant = "status-red"
- } else if (healthState === HealthState.enum.NotEnoughData) {
- showPrice = false
- connectionText = "NOT ENOUGH DATA"
- textVariant = "status-gray"
- } else if (healthState === HealthState.enum.TooMuchDeviation) {
- showPrice = false
- connectionText = "TOO MUCH DEVIATION"
- textVariant = "status-red"
- } else {
- throw new Error("Invalid health state: " + healthState)
- }
+ // Test if price state is accurate
+ const { state } = useExchangePrice(
+ props.exchange,
+ props.activeBaseTicker,
+ props.activeQuoteTicker
+ )
+
+ // let healthState = props.priceReport.healthState
+ let showPrice: boolean = state === "live"
+ let connectionText: string =
+ state === "live" ? "LIVE" : state === "stale" ? "STALE" : "LOADING"
+ let textVariant: string =
+ state === "live"
+ ? "status-green"
+ : state === "stale"
+ ? "status-red"
+ : "status-gray"
+ // If exchange is not supported, modify accordingly
+ // if (props.exchange === Exchange.Uniswapv3) {
+ // showPrice = false
+ // connectionText = "UNSUPPORTED"
+ // textVariant = "status-gray"
+ // }
+ // If incoming price is stale (longer than 1 minute), modify accordingly
+
+ // if (healthState === HealthState.enum.Connecting) {
+ // showPrice = true
+ // connectionText = "LIVE"
+ // textVariant = "status-green"
+ // } else if (healthState === HealthState.enum.Unsupported) {
+ // showPrice = false
+ // connectionText = "UNSUPPORTED"
+ // textVariant = "status-gray"
+ // } else if (healthState === HealthState.enum.Live) {
+ // showPrice = true
+ // connectionText = "LIVE"
+ // textVariant = "status-green"
+ // } else if (healthState === HealthState.enum.NoDataReported) {
+ // showPrice = false
+ // connectionText = "NO DATA"
+ // textVariant = "status-gray"
+ // } else if (healthState === HealthState.enum.TooStale) {
+ // showPrice = false
+ // connectionText = "TOO STALE"
+ // textVariant = "status-red"
+ // } else if (healthState === HealthState.enum.NotEnoughData) {
+ // showPrice = false
+ // connectionText = "NOT ENOUGH DATA"
+ // textVariant = "status-gray"
+ // } else if (healthState === HealthState.enum.TooMuchDeviation) {
+ // showPrice = false
+ // connectionText = "TOO MUCH DEVIATION"
+ // textVariant = "status-red"
+ // } else {
+ // throw new Error("Invalid health state: " + healthState)
+ // }
const pulseState = {
"status-green": "live",
@@ -148,7 +169,7 @@ function ExchangeConnectionTriple(props: ExchangeConnectionTripleProps) {
quoteTicker={props.activeQuoteTicker}
exchange={props.exchange}
isMobile={props.isMobile}
- price={props.priceReport.midpointPrice}
+ // price={props.priceReport.midpointPrice}
/>
)}
@@ -218,7 +239,7 @@ interface ExchangeConnectionsBannerProps {
activeBaseTicker: string
activeQuoteTicker: string
isMobile?: boolean
- report: ExchangeHealthState
+ report?: ExchangeHealthState
}
interface ExchangeConnectionsBannerState {
exchangeConnectionsBannerRef: React.RefObject
@@ -428,7 +449,7 @@ export class MedianBanner extends React.Component<
activeBaseTicker={this.props.activeBaseTicker}
activeQuoteTicker={this.props.activeQuoteTicker}
isMobile={this.props.isMobile}
- priceReport={this.props.report.Median}
+ // priceReport={this.props.report.Median}
/>
-
-
+ */}
@@ -500,7 +521,7 @@ export class MedianBanner extends React.Component<
activeBaseTicker={this.props.activeBaseTicker}
activeQuoteTicker={this.props.activeQuoteTicker}
exchange={Exchange.Kraken}
- priceReport={this.props.report.Kraken}
+ // priceReport={this.props.report.Kraken}
isMobile={this.props.isMobile}
/>
@@ -508,7 +529,7 @@ export class MedianBanner extends React.Component<
activeBaseTicker={this.props.activeBaseTicker}
activeQuoteTicker={this.props.activeQuoteTicker}
exchange={Exchange.Okx}
- priceReport={this.props.report.Okx}
+ // priceReport={this.props.report.Okx}
isMobile={this.props.isMobile}
/>
@@ -516,7 +537,7 @@ export class MedianBanner extends React.Component<
activeBaseTicker={this.props.activeBaseTicker}
activeQuoteTicker={this.props.activeQuoteTicker}
exchange={Exchange.Uniswapv3}
- priceReport={this.props.report.UniswapV3}
+ // priceReport={this.props.report.UniswapV3}
isMobile={this.props.isMobile}
/>
diff --git a/trade.renegade.fi/components/banners/tokens-banner.tsx b/trade.renegade.fi/components/banners/tokens-banner.tsx
index 42b39d3e..42e0b096 100644
--- a/trade.renegade.fi/components/banners/tokens-banner.tsx
+++ b/trade.renegade.fi/components/banners/tokens-banner.tsx
@@ -1,18 +1,14 @@
"use client"
import { Stack, Text } from "@chakra-ui/react"
-import { Exchange, PriceReport } from "@renegade-fi/renegade-js"
+import { Exchange } from "@renegade-fi/renegade-js"
import Link from "next/link"
import Marquee from "@/components/banners/marquee"
import { LivePrices } from "@/components/live-price"
import { DISPLAYED_TICKERS } from "@/lib/tokens"
-export function TokensBanner({
- prices,
-}: {
- prices: (PriceReport | undefined)[]
-}) {
+export function TokensBanner({ prices }: { prices: number[] }) {
return (
diff --git a/trade.renegade.fi/components/live-price.tsx b/trade.renegade.fi/components/live-price.tsx
index 8ac57942..b1836773 100644
--- a/trade.renegade.fi/components/live-price.tsx
+++ b/trade.renegade.fi/components/live-price.tsx
@@ -1,9 +1,9 @@
import { TriangleDownIcon, TriangleUpIcon } from "@chakra-ui/icons"
import { Box, Flex, Text } from "@chakra-ui/react"
-import { Exchange, PriceReport } from "@renegade-fi/renegade-js"
-import { useEffect, useMemo, useRef, useState } from "react"
+import { Exchange } from "@renegade-fi/renegade-js"
+import { useEffect, useMemo, useState } from "react"
-import { useExchange } from "@/contexts/Exchange/exchange-context"
+import { usePrice } from "@/contexts/PriceContext/price-context"
import { TICKER_TO_DEFAULT_DECIMALS } from "@/lib/tokens"
import { BannerSeparator } from "./banner-separator"
@@ -14,7 +14,7 @@ interface LivePricesProps {
quoteTicker: string
isMobile?: boolean
onlyShowPrice?: boolean
- price?: number
+ initialPrice?: number
scaleBy?: number
shouldRotate?: boolean
withCommas?: boolean
@@ -26,27 +26,11 @@ export const LivePrices = ({
quoteTicker,
isMobile,
onlyShowPrice,
- price: priceProp,
+ initialPrice = 0,
scaleBy,
shouldRotate,
withCommas,
}: LivePricesProps) => {
- const [previousPriceReport, setPreviousPriceReport] = useState(
- {}
- )
- const [currentPriceReport, setCurrentPriceReport] = useState({})
-
- const { getPriceData, onRegisterPriceListener } = useExchange()
- const priceReport = getPriceData(exchange, baseTicker, quoteTicker)
-
- useEffect(() => {
- if (!priceReport) return
- setCurrentPriceReport((prev) => {
- setPreviousPriceReport(prev)
- return priceReport
- })
- }, [priceReport])
-
const baseDefaultDecimals = TICKER_TO_DEFAULT_DECIMALS[baseTicker] || 0
const trailingDecimals = useMemo(() => {
if (["USDC", "WETH", "WBTC"].includes(baseTicker)) {
@@ -59,63 +43,43 @@ export const LivePrices = ({
return Math.abs(baseDefaultDecimals) + 2
}
}, [baseDefaultDecimals, baseTicker, quoteTicker])
+ const [price, setPrice] = useState(initialPrice)
+ const [prevPrice, setPrevPrice] = useState(price)
- const callbackIdRef = useRef(false)
+ const { handleSubscribe, handleGetPrice } = usePrice()
+ const priceReport = handleGetPrice(exchange, baseTicker, quoteTicker)
useEffect(() => {
- if (callbackIdRef.current) return
- onRegisterPriceListener(
- exchange,
- baseTicker,
- quoteTicker,
- trailingDecimals
- ).then((callbackId) => {
- if (callbackId) {
- callbackIdRef.current = true
- }
+ if (!priceReport) return
+ setPrice((prev) => {
+ setPrevPrice(prev)
+ return priceReport
})
- }, [
- baseTicker,
- quoteTicker,
- onRegisterPriceListener,
- exchange,
- trailingDecimals,
- ])
+ }, [priceReport])
+ useEffect(() => {
+ handleSubscribe(exchange, baseTicker, quoteTicker, trailingDecimals)
+ }, [baseTicker, exchange, handleSubscribe, quoteTicker, trailingDecimals])
// Given the previous and current price reports, determine the displayed
// price and red/green fade class
let priceStrClass = ""
- if (
- previousPriceReport.midpointPrice &&
- currentPriceReport.midpointPrice &&
- currentPriceReport.midpointPrice > previousPriceReport.midpointPrice
- ) {
+ if (prevPrice && price > prevPrice) {
priceStrClass = "fade-green-to-white"
- } else if (
- previousPriceReport.midpointPrice &&
- currentPriceReport.midpointPrice &&
- currentPriceReport.midpointPrice < previousPriceReport.midpointPrice
- ) {
+ } else if (prevPrice && price < prevPrice) {
priceStrClass = "fade-red-to-white"
}
- let price = currentPriceReport.midpointPrice
- ? currentPriceReport.midpointPrice
- : baseTicker === "USDC"
- ? 1
- : priceProp
- ? priceProp
- : 0
-
+ let scaledPrice = price
// If the caller supplied a scaleBy prop, scale the price appropriately
if (scaleBy !== undefined) {
- price *= scaleBy
+ scaledPrice *= scaleBy
}
// Format the price as a string
let priceStr = price.toFixed(trailingDecimals)
if (
- (!Object.keys(currentPriceReport).length || scaleBy === 0) &&
- baseDefaultDecimals > 0 &&
+ // (!scaledPrice || scaleBy === 0) &&
+ // baseDefaultDecimals > 0 &&
+ !price &&
baseTicker !== "USDC"
) {
const leadingDecimals = priceStr.split(".")[0].length
@@ -136,9 +100,7 @@ export const LivePrices = ({
return ${priceStr}
}
- const key = [baseTicker, quoteTicker, currentPriceReport.localTimestamp].join(
- "_"
- )
+ const key = [baseTicker, quoteTicker, price].join("_")
// Create the icon to display next to the price
let priceIcon
diff --git a/trade.renegade.fi/components/panels/wallets-panel.tsx b/trade.renegade.fi/components/panels/wallets-panel.tsx
index b3ffab73..4ec9f3f5 100644
--- a/trade.renegade.fi/components/panels/wallets-panel.tsx
+++ b/trade.renegade.fi/components/panels/wallets-panel.tsx
@@ -109,9 +109,9 @@ function TokenBalance(props: TokenBalanceProps) {
color="white.40"
fontSize="0.8em"
lineHeight="1"
- opacity={usdPrice === "0.00" ? "40%" : undefined}
+ opacity={!usdPrice ? "40%" : undefined}
>
- ${usdPrice}
+ ${usdPrice.toFixed(2)}
toast.error(`Error placing order: ${e.message}`))
}
+ const costInUsd = useUSDPrice(base.ticker, parseFloat(baseTokenAmount))
const hasInsufficientBalance = useMemo(() => {
const baseBalance = findBalanceByTicker(balances, baseTicker).amount
const quoteBalance = findBalanceByTicker(balances, quoteTicker).amount
- const price = priceReport?.midpointPrice
- ? priceReport.midpointPrice * parseFloat(baseTokenAmount)
- : 0
if (direction === Direction.SELL) {
return baseBalance < parseAmount(baseTokenAmount, base)
}
// TODO: Check this
- return parseFloat(formatAmount(quoteBalance, quote)) < price
+
+ return parseFloat(formatAmount(quoteBalance, quote)) < costInUsd
}, [
balances,
base,
baseTicker,
baseTokenAmount,
+ costInUsd,
direction,
- priceReport?.midpointPrice,
quote,
quoteTicker,
])
+ const [price, setPrice] = useState(0)
+ const { handleSubscribe, handleGetPrice } = usePrice()
+ const priceReport = handleGetPrice(Exchange.Binance, baseTicker, quoteTicker)
+ useEffect(() => {
+ if (!priceReport) return
+ setPrice(priceReport)
+ }, [priceReport])
+ useEffect(() => {
+ handleSubscribe(Exchange.Binance, baseTicker, quoteTicker, 2)
+ }, [baseTicker, quoteTicker, handleSubscribe])
const isSignedIn = accountId !== undefined
let placeOrderButtonContent: string
if (shouldUse) {
placeOrderButtonContent = buttonText
- } else if (!priceReport?.midpointPrice) {
+ } else if (!price) {
placeOrderButtonContent = "No Exchange Data"
} else if (hasInsufficientBalance) {
placeOrderButtonContent = "Insufficient Balance"
diff --git a/trade.renegade.fi/contexts/Exchange/exchange-context.tsx b/trade.renegade.fi/contexts/Exchange/exchange-context.tsx
index c771e697..323b12bf 100644
--- a/trade.renegade.fi/contexts/Exchange/exchange-context.tsx
+++ b/trade.renegade.fi/contexts/Exchange/exchange-context.tsx
@@ -1,4 +1,4 @@
-import { CallbackId, Exchange, PriceReport } from "@renegade-fi/renegade-js"
+import { CallbackId, Exchange } from "@renegade-fi/renegade-js"
import {
PropsWithChildren,
createContext,
@@ -12,7 +12,7 @@ import {
import { renegade } from "@/app/providers"
import { getToken } from "@/lib/utils"
-import { ExchangeContextValue } from "./types"
+import { ExchangeContextValue, PriceReport } from "./types"
const UPDATE_THRESHOLD_MS = 1000
@@ -39,7 +39,6 @@ function ExchangeProvider({ children }: PropsWithChildren) {
}
let lastUpdate = 0
-
const callbackId = await renegade
.registerPriceReportCallback(
(message: string) => {
@@ -54,8 +53,8 @@ function ExchangeProvider({ children }: PropsWithChildren) {
setPriceReport((prev) => {
if (
!prev[key] ||
- prev[key].midpointPrice?.toFixed(decimals || 2) !==
- priceReport.midpointPrice?.toFixed(decimals || 2)
+ prev[key].price?.toFixed(decimals || 2) !==
+ priceReport.price?.toFixed(decimals || 2)
) {
return {
...prev,
diff --git a/trade.renegade.fi/contexts/Exchange/types.ts b/trade.renegade.fi/contexts/Exchange/types.ts
index d6d47df6..a3d50ebb 100644
--- a/trade.renegade.fi/contexts/Exchange/types.ts
+++ b/trade.renegade.fi/contexts/Exchange/types.ts
@@ -1,5 +1,16 @@
-import { CallbackId, Exchange, PriceReport } from "@renegade-fi/renegade-js"
+import { CallbackId, Exchange, } from "@renegade-fi/renegade-js"
+export type PriceReport = {
+ type: "PriceReport"
+ baseToken: {
+ addr: string
+ }
+ quoteToken: {
+ addr: string
+ }
+ price: number
+ localTimestamp: number
+}
export interface ExchangeContextValue {
onRegisterPriceListener: (
exchange: Exchange,
diff --git a/trade.renegade.fi/contexts/PriceContext/price-context.tsx b/trade.renegade.fi/contexts/PriceContext/price-context.tsx
new file mode 100644
index 00000000..6de4b4ba
--- /dev/null
+++ b/trade.renegade.fi/contexts/PriceContext/price-context.tsx
@@ -0,0 +1,127 @@
+import { Exchange, PriceReporterWs, Token } from "@renegade-fi/renegade-js"
+import React, {
+ createContext,
+ useCallback,
+ useContext,
+ useEffect,
+ useState,
+} from "react"
+
+import { env } from "@/env.mjs"
+
+const PriceContext = createContext<{
+ priceReporter: PriceReporterWs | null
+ handleSubscribe: (
+ exchange: Exchange,
+ base: string,
+ quote: string,
+ decimals: number
+ ) => void
+ handleGetPrice: (
+ exchange: Exchange,
+ base: string,
+ quote: string
+ ) => number | undefined
+ handleGetLastUpdate: (
+ exchange: Exchange,
+ base: string,
+ quote: string
+ ) => number | undefined
+} | null>(null)
+
+const UPDATE_THRESHOLD_MS = 1000
+
+const invalid = ["USDT", "BUSD", "CBETH", "RNG"]
+
+export const PriceProvider = ({ children }: { children: React.ReactNode }) => {
+ const [priceReporter, setPriceReporter] = useState(
+ null
+ )
+ const [prices, setPrices] = useState>({})
+ const [lastUpdate, setLastUpdate] = useState>({})
+ const [attempted, setAttempted] = useState>({})
+ useEffect(() => {
+ const priceReporter = new PriceReporterWs(
+ env.NEXT_PUBLIC_PRICE_REPORTER_URL
+ )
+ setPriceReporter(priceReporter)
+ return () => {
+ priceReporter.teardown()
+ }
+ }, [])
+
+ const handleSubscribe = useCallback(
+ (exchange: Exchange, base: string, quote: string, decimals: number) => {
+ if (!priceReporter || invalid.includes(base)) return
+
+ const topic = getTopic(exchange, base, quote)
+ if (attempted[topic]) return
+
+ let lastUpdate = 0
+ const now = Date.now()
+ if (now - lastUpdate <= UPDATE_THRESHOLD_MS) {
+ return
+ }
+ lastUpdate = now
+
+ priceReporter.subscribeToTokenPair(
+ exchange,
+ new Token({ ticker: base }),
+ new Token({ ticker: quote || "USDT" }),
+ (price) => {
+ setPrices((prevPrices) => {
+ if (
+ prevPrices[topic]?.toFixed(decimals) !==
+ Number(price).toFixed(decimals)
+ ) {
+ return { ...prevPrices, [topic]: Number(price) }
+ }
+ return prevPrices
+ })
+ setLastUpdate((prev) => ({ ...prev, [topic]: Date.now() }))
+ }
+ )
+ setAttempted((prev) => ({ ...prev, [topic]: true }))
+ },
+ [attempted, priceReporter]
+ )
+
+ const handleGetPrice = (exchange: Exchange, base: string, quote: string) => {
+ const topic = getTopic(exchange, base, quote)
+ return prices[topic]
+ }
+
+ const handleGetLastUpdate = (
+ exchange: Exchange,
+ base: string,
+ quote: string
+ ) => {
+ const topic = getTopic(exchange, base, quote)
+ return lastUpdate[topic]
+ }
+
+ return (
+
+ {children}
+
+ )
+}
+
+export const usePrice = () => {
+ const context = useContext(PriceContext)
+ if (!context) {
+ throw new Error("usePrice must be used within a PriceProvider")
+ }
+ return context
+}
+
+function getTopic(exchange: Exchange, base: string, quote: string) {
+ return `${exchange}-${base}-${quote}`
+}
diff --git a/trade.renegade.fi/env.mjs b/trade.renegade.fi/env.mjs
index 81c14626..d96c5344 100644
--- a/trade.renegade.fi/env.mjs
+++ b/trade.renegade.fi/env.mjs
@@ -4,20 +4,22 @@ import { z } from "zod"
export const env = createEnv({
server: {},
client: {
+ NEXT_PUBLIC_DARKPOOL_CONTRACT: z.string().min(1),
+ NEXT_PUBLIC_INTERCOM_APP_ID: z.string().min(1),
+ NEXT_PUBLIC_PERMIT2_CONTRACT: z.string().min(1),
+ NEXT_PUBLIC_PRICE_REPORTER_URL: z.string().min(1),
NEXT_PUBLIC_RENEGADE_RELAYER_HOSTNAME: z.string().min(1),
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID: z.string().min(1),
- NEXT_PUBLIC_INTERCOM_APP_ID: z.string().min(1),
- NEXT_PUBLIC_DARKPOOL_CONTRACT: z.string().min(1),
- NEXT_PUBLIC_PERMIT2_CONTRACT: z.string().nonempty()
},
// For Next.js >= 13.4.4, you only need to destructure client variables:
experimental__runtimeEnv: {
+ NEXT_PUBLIC_DARKPOOL_CONTRACT: process.env.NEXT_PUBLIC_DARKPOOL_CONTRACT,
+ NEXT_PUBLIC_INTERCOM_APP_ID: process.env.NEXT_PUBLIC_INTERCOM_APP_ID,
+ NEXT_PUBLIC_PERMIT2_CONTRACT: process.env.NEXT_PUBLIC_PERMIT2_CONTRACT,
+ NEXT_PUBLIC_PRICE_REPORTER_URL: process.env.NEXT_PUBLIC_PRICE_REPORTER_URL,
NEXT_PUBLIC_RENEGADE_RELAYER_HOSTNAME:
process.env.NEXT_PUBLIC_RENEGADE_RELAYER_HOSTNAME,
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID:
process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID,
- NEXT_PUBLIC_INTERCOM_APP_ID: process.env.NEXT_PUBLIC_INTERCOM_APP_ID,
- NEXT_PUBLIC_DARKPOOL_CONTRACT: process.env.NEXT_PUBLIC_DARKPOOL_CONTRACT,
- NEXT_PUBLIC_PERMIT2_CONTRACT: process.env.NEXT_PUBLIC_PERMIT2_CONTRACT,
},
})
diff --git a/trade.renegade.fi/hooks/use-price.ts b/trade.renegade.fi/hooks/use-price.ts
new file mode 100644
index 00000000..95ba7abc
--- /dev/null
+++ b/trade.renegade.fi/hooks/use-price.ts
@@ -0,0 +1,27 @@
+import { usePrice } from "@/contexts/PriceContext/price-context"
+import { Exchange } from "@renegade-fi/renegade-js"
+import { useEffect, useState } from "react"
+
+const THRESHOLD = 60 * 1000 // 1 minute
+
+export const useExchangePrice = (exchange: Exchange, base: string, quote: string) => {
+ const [price, setPrice] = useState(0)
+
+ const { handleSubscribe, handleGetPrice, handleGetLastUpdate } = usePrice()
+ const priceReport = handleGetPrice(exchange, base, quote)
+ useEffect(() => {
+ if (!priceReport) return
+ setPrice(priceReport)
+ }, [priceReport])
+ useEffect(() => {
+ handleSubscribe(exchange, base, quote, 2)
+ }, [base, handleSubscribe, quote, exchange])
+
+ const lastUpdate = handleGetLastUpdate(exchange, base, quote)
+ let state: "live" | "stale" | "idle" = "idle"
+ if (lastUpdate) {
+ state = lastUpdate < Date.now() - THRESHOLD ? "stale" : "live"
+ }
+
+ return { price, state }
+}
diff --git a/trade.renegade.fi/hooks/use-usd-price.ts b/trade.renegade.fi/hooks/use-usd-price.ts
index b35a62ce..5f93da4c 100644
--- a/trade.renegade.fi/hooks/use-usd-price.ts
+++ b/trade.renegade.fi/hooks/use-usd-price.ts
@@ -1,52 +1,19 @@
-import { Exchange, PriceReport } from "@renegade-fi/renegade-js"
-import { useEffect, useMemo, useRef, useState } from "react"
-
-import { useExchange } from "@/contexts/Exchange/exchange-context"
+import { usePrice } from "@/contexts/PriceContext/price-context"
+import { Exchange } from "@renegade-fi/renegade-js"
+import { useEffect, useState } from "react"
export const useUSDPrice = (base: string, amount: number) => {
- const [currentPriceReport, setCurrentPriceReport] = useState({})
-
- const { getPriceData, onRegisterPriceListener } = useExchange()
- const priceReport = getPriceData(Exchange.Median, base, "USDC")
+ const [price, setPrice] = useState(0)
+ const { handleSubscribe, handleGetPrice } = usePrice()
+ const priceReport = handleGetPrice(Exchange.Binance, base, "USDC")
useEffect(() => {
if (!priceReport) return
- setCurrentPriceReport(priceReport)
+ setPrice(priceReport)
}, [priceReport])
-
- const callbackIdRef = useRef(false)
useEffect(() => {
- if (callbackIdRef.current) return
- onRegisterPriceListener(Exchange.Median, base, "USDC", 2).then(
- (callbackId) => {
- if (callbackId) {
- callbackIdRef.current = true
- }
- }
- )
- }, [base, onRegisterPriceListener])
-
- const formattedPrice = useMemo(() => {
- let basePrice
-
- if (currentPriceReport.midpointPrice) {
- basePrice = currentPriceReport.midpointPrice
- } else if (base === "USDC") {
- basePrice = 1
- } else {
- basePrice = 0
- }
-
- let totalPrice = basePrice * amount
-
- let formattedPriceStr = totalPrice.toFixed(2)
- const priceStrParts = formattedPriceStr.split(".")
-
- // Add commas for thousands separation
- priceStrParts[0] = priceStrParts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",")
-
- return priceStrParts.join(".")
- }, [amount, base, currentPriceReport.midpointPrice])
+ handleSubscribe(Exchange.Binance, base, "USDC", 2)
+ }, [base, handleSubscribe])
- return formattedPrice
+ return price * amount
}
diff --git a/trade.renegade.fi/lib/utils.ts b/trade.renegade.fi/lib/utils.ts
index 7441605b..7c6e30ee 100644
--- a/trade.renegade.fi/lib/utils.ts
+++ b/trade.renegade.fi/lib/utils.ts
@@ -17,14 +17,29 @@ export function safeLocalStorageSetItem(key: string, value: string): void {
}
export async function getTokenBannerData(renegade: Renegade) {
- return await Promise.all(
- DISPLAYED_TICKERS.map(([baseTicker, quoteTicker]) =>
- renegade.queryExchangeHealthStates(
- getToken({ ticker: baseTicker }),
- getToken({ ticker: quoteTicker })
+ try {
+ const priceReports = await Promise.all(
+ DISPLAYED_TICKERS.map(([baseTicker, quoteTicker]) =>
+ renegade.queryPriceReporter(
+ getToken({ ticker: baseTicker }),
+ getToken({ ticker: quoteTicker })
+ )
)
- )
- ).then((res) => res.map((e) => e.Median))
+ );
+
+ const formattedPrices = priceReports.map(report => {
+ if (report.price_report?.Nominal) {
+ return report.price_report.Nominal.price as number;
+ } else {
+ return 0;
+ }
+ });
+
+ return formattedPrices;
+ } catch (error) {
+ console.error('Error fetching token banner data:', error);
+ return [];
+ }
}
export function findBalanceByTicker(
@@ -83,3 +98,9 @@ export function parseAmount(amount: string, token: Token) {
// TODO: Should try to fetch decimals from on chain
return parseUnits(amount, decimals)
}
+
+export function formatPrice(num: number) {
+ const formatted = num.toFixed(2)
+ return formatted.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
+}
+
diff --git a/trade.renegade.fi/package.json b/trade.renegade.fi/package.json
index 2a836505..5a8c26aa 100644
--- a/trade.renegade.fi/package.json
+++ b/trade.renegade.fi/package.json
@@ -21,7 +21,7 @@
"@chakra-ui/react": "^2.8.0",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
- "@renegade-fi/renegade-js": "^0.4.24",
+ "@renegade-fi/renegade-js": "^0.4.25",
"@t3-oss/env-nextjs": "^0.6.0",
"@tanstack/react-query": "^5.24.1",
"connectkit": "^1.7.2",
diff --git a/trade.renegade.fi/yarn.lock b/trade.renegade.fi/yarn.lock
index 1f350d1f..fd3e8a07 100644
--- a/trade.renegade.fi/yarn.lock
+++ b/trade.renegade.fi/yarn.lock
@@ -1910,10 +1910,10 @@
dependencies:
merge-options "^3.0.4"
-"@renegade-fi/renegade-js@^0.4.24":
- version "0.4.24"
- resolved "https://registry.yarnpkg.com/@renegade-fi/renegade-js/-/renegade-js-0.4.24.tgz#410c185486edb418a688a41b4687d790a8881fd3"
- integrity sha512-qaG9dvInRKTY3dbr+f2LMm88TtwdzOv0LfErK+4h2f+8PMO42MD46RXLowh5ADXzY1hVda38wGu9crki/Js5YA==
+"@renegade-fi/renegade-js@^0.4.25":
+ version "0.4.25"
+ resolved "https://registry.yarnpkg.com/@renegade-fi/renegade-js/-/renegade-js-0.4.25.tgz#4afc8740ea7d80d6d53cfb3e77e0a5421b4e2698"
+ integrity sha512-Dt58oloZRF/pvdUjVNGiXJ3U2npHJ35hiQnAii/GN6UbQaAoMWaMe8IB6EvwPmrgmq7kYdHrNkiVZOJAQE+yIQ==
dependencies:
"@noble/hashes" "^1.3.0"
axios "^1.3.5"