-
Notifications
You must be signed in to change notification settings - Fork 674
feat(playground): add Token Price Tracker page with live market data #8855
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import { TrendingUpIcon } from "lucide-react"; | ||
| import { PageLayout } from "@/components/blocks/APIHeader"; | ||
| import { TokenPriceTracker } from "@/components/token-price/token-price-tracker"; | ||
| import ThirdwebProvider from "@/components/thirdweb-provider"; | ||
| import { createMetadata } from "@/lib/metadata"; | ||
|
|
||
| const title = "Price Tracker"; | ||
| const description = | ||
| "Live token prices, market cap, and 24h volume across chains"; | ||
|
|
||
| export const metadata = createMetadata({ | ||
| title, | ||
| description, | ||
| image: { | ||
| icon: "wallets", | ||
| title, | ||
| }, | ||
| }); | ||
|
|
||
| export default function Page() { | ||
| return ( | ||
| <ThirdwebProvider> | ||
| <PageLayout | ||
| containerClassName="space-y-12" | ||
| icon={TrendingUpIcon} | ||
| description={description} | ||
| docsLink="https://portal.thirdweb.com/typescript/v5/bridge/get-supported-tokens?utm_source=playground" | ||
| title={title} | ||
| > | ||
| <TokenPriceTracker /> | ||
| </PageLayout> | ||
| </ThirdwebProvider> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,209 @@ | ||
| "use client"; | ||
|
|
||
| import { useQuery } from "@tanstack/react-query"; | ||
| import { useState } from "react"; | ||
| import { Bridge } from "thirdweb"; | ||
| import { | ||
| arbitrum, | ||
| base, | ||
| ethereum, | ||
| optimism, | ||
| polygon, | ||
| } from "thirdweb/chains"; | ||
| import { THIRDWEB_CLIENT } from "@/lib/client"; | ||
| import { CodeExample } from "../code/code-example"; | ||
|
|
||
| const CHAINS = [ | ||
| { chain: ethereum, label: "Ethereum", id: 1 }, | ||
| { chain: base, label: "Base", id: 8453 }, | ||
| { chain: polygon, label: "Polygon", id: 137 }, | ||
| { chain: arbitrum, label: "Arbitrum", id: 42161 }, | ||
| { chain: optimism, label: "Optimism", id: 10 }, | ||
| ] as const; | ||
|
|
||
| function formatUsd(value: number): string { | ||
| if (value >= 1_000_000_000) { | ||
| return `$${(value / 1_000_000_000).toFixed(2)}B`; | ||
| } | ||
| if (value >= 1_000_000) { | ||
| return `$${(value / 1_000_000).toFixed(2)}M`; | ||
| } | ||
| if (value >= 1_000) { | ||
| return `$${(value / 1_000).toFixed(2)}K`; | ||
| } | ||
| return `$${value.toFixed(2)}`; | ||
| } | ||
|
|
||
| function formatPrice(value: number): string { | ||
| if (value >= 1) { | ||
| return `$${value.toFixed(2)}`; | ||
| } | ||
| if (value >= 0.01) { | ||
| return `$${value.toFixed(4)}`; | ||
| } | ||
| return `$${value.toFixed(6)}`; | ||
| } | ||
|
|
||
| function TokenPriceTrackerPreview() { | ||
| const [selectedChainId, setSelectedChainId] = useState(1); | ||
|
|
||
| const tokensQuery = useQuery({ | ||
| queryKey: ["bridge-tokens-price", selectedChainId], | ||
| queryFn: () => | ||
| Bridge.tokens({ | ||
| client: THIRDWEB_CLIENT, | ||
| chainId: selectedChainId, | ||
| limit: 15, | ||
| sortBy: "market_cap", | ||
| includePrices: true, | ||
| }), | ||
| refetchInterval: 30_000, | ||
| }); | ||
|
|
||
| return ( | ||
| <div className="w-full max-w-3xl space-y-4 px-4"> | ||
| {/* Chain Selector */} | ||
| <div className="flex items-center gap-2 flex-wrap"> | ||
| {CHAINS.map((c) => ( | ||
| <button | ||
| key={c.id} | ||
| type="button" | ||
| onClick={() => setSelectedChainId(c.id)} | ||
| className={`rounded-full border px-3 py-1 text-xs font-medium transition-colors ${ | ||
| selectedChainId === c.id | ||
| ? "border-primary bg-primary text-primary-foreground" | ||
| : "border-border bg-background text-muted-foreground hover:bg-accent" | ||
| }`} | ||
| > | ||
| {c.label} | ||
| </button> | ||
| ))} | ||
|
Comment on lines
+68
to
+80
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Use the design-system button and expose the selected chain. Use As per coding guidelines, “Import UI component primitives from Also applies to: 170-208 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| </div> | ||
|
|
||
| {/* Token List */} | ||
| <div className="rounded-lg border bg-card"> | ||
| {/* Header */} | ||
| <div className="grid grid-cols-[1fr_auto_auto_auto] gap-4 border-b px-4 py-2.5 text-xs font-medium text-muted-foreground"> | ||
| <span>Token</span> | ||
| <span className="w-24 text-right">Price</span> | ||
| <span className="hidden w-24 text-right sm:block">Market Cap</span> | ||
| <span className="hidden w-24 text-right sm:block">24h Volume</span> | ||
| </div> | ||
|
|
||
| {/* Loading State */} | ||
| {tokensQuery.isLoading && ( | ||
| <div className="space-y-0"> | ||
| {Array.from({ length: 8 }).map((_, i) => ( | ||
| <div | ||
| key={`skeleton-${i.toString()}`} | ||
| className="grid grid-cols-[1fr_auto_auto_auto] gap-4 border-b px-4 py-3 last:border-b-0" | ||
| > | ||
| <div className="flex items-center gap-3"> | ||
| <div className="size-8 animate-pulse rounded-full bg-muted" /> | ||
| <div className="space-y-1.5"> | ||
| <div className="h-3.5 w-20 animate-pulse rounded bg-muted" /> | ||
| <div className="h-3 w-12 animate-pulse rounded bg-muted" /> | ||
| </div> | ||
| </div> | ||
| <div className="h-4 w-24 animate-pulse self-center rounded bg-muted" /> | ||
| <div className="hidden h-4 w-24 animate-pulse self-center rounded bg-muted sm:block" /> | ||
| <div className="hidden h-4 w-24 animate-pulse self-center rounded bg-muted sm:block" /> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| )} | ||
|
|
||
| {/* Error State */} | ||
| {tokensQuery.isError && ( | ||
| <div className="px-4 py-8 text-center text-sm text-muted-foreground"> | ||
| Failed to load token data. Please try again. | ||
| </div> | ||
| )} | ||
|
|
||
| {/* Data */} | ||
| {tokensQuery.data?.map((token) => ( | ||
| <div | ||
| key={`${token.chainId}-${token.address}`} | ||
| className="grid grid-cols-[1fr_auto_auto_auto] gap-4 border-b px-4 py-3 last:border-b-0 hover:bg-accent/50 transition-colors" | ||
| > | ||
| <div className="flex items-center gap-3"> | ||
| {token.iconUri ? ( | ||
| <img | ||
| src={token.iconUri} | ||
| alt={token.name} | ||
| className="size-8 rounded-full" | ||
| /> | ||
| ) : ( | ||
| <div className="size-8 rounded-full bg-muted" /> | ||
| )} | ||
| <div> | ||
| <p className="text-sm font-medium leading-tight"> | ||
| {token.name} | ||
| </p> | ||
| <p className="text-xs text-muted-foreground">{token.symbol}</p> | ||
| </div> | ||
| </div> | ||
| <span className="w-24 self-center text-right text-sm font-medium tabular-nums"> | ||
| {token.prices?.usd ? formatPrice(token.prices.usd) : "—"} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | rg '(^|/)token-price-tracker\.tsx$|/^package\.json$|pnpm-lock\.yaml$' || true
echo "== tracker outline =="
ast-grep outline apps/playground-web/src/components/token-price/token-price-tracker.tsx || true
echo "== tracker relevant lines =="
cat -n apps/playground-web/src/components/token-price/token-price-tracker.tsx | sed -n '1,230p'
echo "== search Bridge.tokens/price tracker usage =="
rg -n "Bridge\.tokens|prices\.(USD|usd)|token-price-tracker|price-tracker|from \"thirdweb\"" apps/playground-web/src . --glob '!node_modules' --glob '!dist' --glob '!build' | head -200Repository: thirdweb-dev/js Length of output: 28540 🌐 Web query:
💡 Result: You can retrieve token metadata and pricing information using the thirdweb TypeScript SDK's Bridge.tokens utility [1][2]. The Bridge.tokens method allows you to query supported bridge tokens with various filters, including specific token addresses, chain IDs, symbols, and names [1][2]. The data returned by this utility includes a prices object, which provides the token's current value in multiple currencies, including USD [1][3]. Example usage to retrieve a specific token: import { Bridge } from "thirdweb"; // Retrieve USDC token information on Ethereum const token = await Bridge.tokens({ chainId: 1, tokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", client, }); // Access the price in USD console.log(token.prices.USD); Additionally, if you specifically need to convert a crypto amount to a fiat value, you can use the convertCryptoToFiat function from the thirdweb/pay package [4][5]. Note that this specific function currently only supports USD as the target fiat currency [4][5]. Example usage for conversion: import { convertCryptoToFiat } from "thirdweb/pay"; import { NATIVE_TOKEN_ADDRESS } from "thirdweb"; // Get the price of 1 ETH in USD const result = await convertCryptoToFiat({ fromTokenAddress: NATIVE_TOKEN_ADDRESS, to: "USD", chain: ethereum, fromAmount: 1, }); console.log(result.result); // Returns the price in USD Citations:
Read the documented uppercase USD price key.
📍 Affects 1 file
🤖 Prompt for AI Agents |
||
| </span> | ||
| <span className="hidden w-24 self-center text-right text-xs text-muted-foreground tabular-nums sm:block"> | ||
| {token.marketCapUsd ? formatUsd(token.marketCapUsd) : "—"} | ||
| </span> | ||
| <span className="hidden w-24 self-center text-right text-xs text-muted-foreground tabular-nums sm:block"> | ||
| {token.volume24hUsd ? formatUsd(token.volume24hUsd) : "—"} | ||
| </span> | ||
| </div> | ||
| ))} | ||
| </div> | ||
|
|
||
| {/* Auto-refresh indicator */} | ||
| <p className="text-center text-xs text-muted-foreground"> | ||
| Auto-refreshes every 30s | ||
| {tokensQuery.isFetching && !tokensQuery.isLoading && ( | ||
| <span className="ml-1.5 inline-block size-2 animate-pulse rounded-full bg-green-500" /> | ||
| )} | ||
| </p> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export function TokenPriceTracker() { | ||
| return ( | ||
| <CodeExample | ||
| header={{ | ||
| title: "Token Price Tracker", | ||
| description: | ||
| "Fetch live token prices, market cap, and 24h volume using the Bridge.tokens() API with multi-chain support and auto-refresh.", | ||
| }} | ||
| code={`import { Bridge } from "thirdweb"; | ||
| import { useQuery } from "@tanstack/react-query"; | ||
|
|
||
| function App() { | ||
| const { data: tokens } = useQuery({ | ||
| queryKey: ["tokens", chainId], | ||
| queryFn: () => | ||
| Bridge.tokens({ | ||
| client: THIRDWEB_CLIENT, | ||
| chainId: 1, // Ethereum | ||
| limit: 15, | ||
| sortBy: "market_cap", | ||
| includePrices: true, | ||
| }), | ||
| refetchInterval: 30_000, // auto-refresh every 30s | ||
| }); | ||
|
|
||
| return tokens?.map((token) => ( | ||
| <div key={token.address}> | ||
| <img src={token.iconUri} alt={token.name} /> | ||
| <span>{token.name} ({token.symbol})</span> | ||
| <span>\${token.prices?.usd?.toFixed(2)}</span> | ||
| <span>MCap: {token.marketCapUsd}</span> | ||
| <span>Vol: {token.volume24hUsd}</span> | ||
| </div> | ||
| )); | ||
| }`} | ||
| lang="tsx" | ||
| preview={<TokenPriceTrackerPreview />} | ||
| /> | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: thirdweb-dev/js
Length of output: 212
🌐 Web query:
Third.js bridge get supported tokens TypeScript docs portal.thirdweb.com💡 Result:
To retrieve supported tokens for the thirdweb bridge in the TypeScript SDK, you use the Bridge.tokens utility [1][2]. This function allows you to query, filter, and paginate through supported bridge tokens [1][3]. You can access this functionality by importing Bridge from the thirdweb package [1][2]: import { Bridge } from "thirdweb"; The Bridge.tokens function accepts an options object to filter results, which acts as an AND operator when multiple filters are applied [1][4][2]. Common usage examples include: Filtering by chainId: const ethTokens = await Bridge.tokens({ chainId: 1, client: thirdwebClient, }); Searching by symbol or name: const usdcTokens = await Bridge.tokens({ symbol: "USDC", client: thirdwebClient, }); const tokensByName = await Bridge.tokens({ name: "Ethereum", client: thirdwebClient, }); Retrieving a specific token by address: const token = await Bridge.tokens({ tokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", client: thirdwebClient, }); For large datasets, you can paginate the results using the limit and offset parameters: const tokens = await Bridge.tokens({ limit: 50, offset: 0, client: thirdwebClient, }); For complete details on the function signature and available options, refer to the official thirdweb TypeScript SDK reference documentation [1][4].
Citations:
🏁 Script executed:
Repository: thirdweb-dev/js
Length of output: 19042
🌐 Web query:
site:portal.thirdweb.com/references/typescript/v5 Bridge.tokens thirdweb💡 Result:
In the thirdweb TypeScript SDK, Bridge.tokens is a function used to retrieve a list of supported tokens available for bridging operations [1]. Key details about Bridge.tokens include: Functionality: It returns an array of tokens that are supported for bridging, which can be filtered based on specific criteria such as chain ID, token symbol, token name, or token address [1]. Usage: It is part of the thirdweb Bridge module and requires a thirdweb client instance [1]. Filtering: When multiple filters are provided, they act as an AND operator, meaning a token must satisfy all specified conditions to be included in the results [1]. Pagination: The function supports pagination through limit and offset parameters to manage the size of the returned token list [1]. This function is one component of the broader thirdweb Bridge infrastructure, which also includes utilities for fetching supported chains, generating quotes for buy/sell intentions, and preparing transactions for cross-chain operations [1][2][3][4].
Citations:
Use the
Bridge.tokensreference for the Docs link.Price TrackershowcasesBridge.tokens, so this link should go to the current supported-tokens documentation instead of the generic SDK landing page. (portal.thirdweb.com/references/typescript/v5/tokens)🤖 Prompt for AI Agents