Skip to content

feat: add real-time BTC price ticker#54

Open
cmnisal wants to merge 1 commit intomainfrom
cmnisal/add-live-bitcoin-price-updates
Open

feat: add real-time BTC price ticker#54
cmnisal wants to merge 1 commit intomainfrom
cmnisal/add-live-bitcoin-price-updates

Conversation

@cmnisal
Copy link
Contributor

@cmnisal cmnisal commented Aug 22, 2025

User description

Summary

  • fetch BTC price via CoinCap websocket and convert to LKR
  • display animated price ticker on wallet page

Testing

  • npm test (fails: Missing script "test")
  • npm run lint

https://chatgpt.com/codex/tasks/task_e_68a21bb20284832c9df3d3c93f1cd61b


PR Type

Enhancement


Description

  • Add real-time BTC price ticker with WebSocket connection

  • Convert USD to LKR using exchange rate API

  • Display animated price updates with trend indicators

  • Integrate ticker component into dashboard page


Diagram Walkthrough

flowchart LR
  A["CoinCap WebSocket"] --> B["BTC Price (USD)"]
  C["Exchange Rate API"] --> D["USD to LKR Rate"]
  B --> E["BtcPriceTicker Component"]
  D --> E
  E --> F["Animated Price Display"]
  F --> G["Dashboard Page"]
Loading

File Walkthrough

Relevant files
Enhancement
BtcPriceTicker.tsx
Create BTC price ticker component                                               

src/components/BtcPriceTicker.tsx

  • Create new component for real-time BTC price display
  • Implement WebSocket connection to CoinCap API
  • Add USD to LKR conversion using exchange rate API
  • Include animated price updates with trend indicators
+72/-0   
page.tsx
Integrate BTC ticker into dashboard                                           

src/app/dashboard/page.tsx

  • Import and integrate BtcPriceTicker component
  • Add ticker display above balance card section
+6/-0     

Summary by CodeRabbit

  • New Features
    • Introduced a live BTC price ticker on the wallet dashboard, displayed above the balance card.
    • Shows Bitcoin price in LKR with real-time updates and a loading state until data is available.
    • Includes color‑coded trend indicators (up/down) and subtle highlight animations on price changes.
    • Additive UI only; no changes to existing wallet data fetching, loading states, or refresh behavior.

@railway-app
Copy link

railway-app bot commented Aug 22, 2025

🚅 Deployed to the BitcoinDeepaBot-TMA-pr-54 environment in Bitcoin Deepa DCA

Service Status Web Updated (UTC)
Frontend ◻️ Removed (View Logs) Web Aug 23, 2025 at 4:18 pm

@coderabbitai
Copy link

coderabbitai bot commented Aug 22, 2025

Walkthrough

Adds a new client-side BTC price ticker component and integrates it into the dashboard page above the balance card. The ticker subscribes to CoinCap’s BTC price via WebSocket, fetches a USD→LKR FX rate, converts and animates the price, and cleans up timers and sockets on unmount.

Changes

Cohort / File(s) Summary of Changes
Dashboard UI insertion
src/app/dashboard/page.tsx
Imported BtcPriceTicker and rendered it above the balance card with spacing; no changes to existing data fetching or control flow.
New component: BTC price ticker
src/components/BtcPriceTicker.tsx
Added client component that fetches USD→LKR rate, subscribes to BTC price via WebSocket, converts to LKR, animates updates with Framer Motion, tracks trend, handles loading state, logs FX errors, and cleans up interval/WebSocket on unmount.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant U as User Browser
    participant D as Dashboard Page
    participant T as BtcPriceTicker
    participant FX as OpenExchangeRates API
    participant WS as CoinCap WebSocket
    participant Tim as 1s Timer

    U->>D: Load /dashboard
    D->>T: Mount <BtcPriceTicker />
    T->>FX: GET USD→LKR rate
    FX-->>T: Rate (USD/LKR)
    T->>WS: Open wss for BTC price (USD)
    WS-->>T: BTC price updates (USD) [stream]
    T->>Tim: Start 1s interval
    loop Every second
        T->>T: Convert USD→LKR, compute trend
        T-->>U: Render animated price (LKR)
    end
    note over T: On error fetching FX: console.log<br/>Loading UI until price available
    U-->>T: Unmount (navigate/close)
    T->>WS: Close socket
    T->>Tim: Clear interval
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

Review effort 4/5

Poem

A nibble of ticks, a hop of delight,
I watch BTC blink in emerald light.
LKR sings as numbers dance,
WebSockets whisper, rates advance.
I twitch my ears—so swift, so slick—
New ticker thumps: thump-thump... tick! 🐇💸

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch cmnisal/add-live-bitcoin-price-updates

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@qodo-code-review
Copy link
Contributor

You are nearing your monthly Qodo Merge usage quota. For more information, please visit here.

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Stale State

The WebSocket effect depends on 'price', which causes the effect to re-run on every price update, repeatedly closing and reopening the socket and interval. This can lead to missed messages, performance issues, and memory churn. The effect should likely depend only on 'rate' and use functional state updates or refs to avoid stale closures.

useEffect(() => {
    if (!rate) return;
    const ws = new WebSocket("wss://ws.coincap.io/prices?assets=bitcoin");
    ws.onmessage = (event) => {
        const data = JSON.parse(event.data);
        if (data.bitcoin) {
            priceRef.current = parseFloat(data.bitcoin) * rate;
        }
    };
    const interval = setInterval(() => {
        setPrevPrice((p) => price);
        setPrice(priceRef.current);
    }, 1000);
    return () => {
        ws.close();
        clearInterval(interval);
    };
}, [rate, price]);
Error Handling

Missing checks for failed fetch/invalid response and WebSocket errors; 'rate' may be undefined if API shape changes, and the UI stays in "Loading" indefinitely. Consider validating 'data?.rates?.LKR', handling non-OK responses, adding ws.onclose/onerror, and showing a fallback/error state.

    const fetchRate = async () => {
        try {
            const res = await fetch("https://open.er-api.com/v6/latest/USD");
            const data = await res.json();
            setRate(data.rates.LKR);
        } catch (err) {
            console.error("Failed to fetch LKR rate", err);
        }
    };
    fetchRate();
}, []);
Number Parsing

'parseFloat(data.bitcoin)' assumes a string; if it's already a number or malformed, this can yield NaN and propagate to UI. Add type guards and bail if the computed value is NaN before updating refs/state.

ws.onmessage = (event) => {
    const data = JSON.parse(event.data);
    if (data.bitcoin) {
        priceRef.current = parseFloat(data.bitcoin) * rate;
    }
};
const interval = setInterval(() => {
    setPrevPrice((p) => price);
    setPrice(priceRef.current);
}, 1000);

@vercel
Copy link

vercel bot commented Aug 22, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
bitcoin-deepa-tma-bot Ready Ready Preview Comment Aug 22, 2025 1:22pm

@qodo-code-review
Copy link
Contributor

You are nearing your monthly Qodo Merge usage quota. For more information, please visit here.

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Handle rate/API reliability

The ticker hard-depends on two unauthenticated public APIs (open.er-api.com and
CoinCap WS) without fallbacks, caching, or retry/backoff, so transient failures
will break the display or cause flicker. Centralize pricing/exchange-rate
fetching via your backend or a shared client utility with resilience (caching,
retry, circuit breaker) and a fallback USD display if LKR conversion fails.

Examples:

src/components/BtcPriceTicker.tsx [13-43]
    useEffect(() => {
        const fetchRate = async () => {
            try {
                const res = await fetch("https://open.er-api.com/v6/latest/USD");
                const data = await res.json();
                setRate(data.rates.LKR);
            } catch (err) {
                console.error("Failed to fetch LKR rate", err);
            }
        };

 ... (clipped 21 lines)

Solution Walkthrough:

Before:

// BtcPriceTicker.tsx
export default function BtcPriceTicker() {
    const [rate, setRate] = useState<number | null>(null);
    const priceRef = useRef<number>(0);

    useEffect(() => {
        // Fetches rate once, no retry or fallback
        fetch("https://open.er-api.com/v6/latest/USD")
            .then(res => res.json())
            .then(data => setRate(data.rates.LKR))
            .catch(err => console.error("Failed to fetch LKR rate", err));
    }, []);

    useEffect(() => {
        if (!rate) return; // Fails silently if rate fetch fails
        // WebSocket connection with no error handling or reconnect logic
        const ws = new WebSocket("wss://ws.coincap.io/prices?assets=bitcoin");
        // ...
    }, [rate]);
    // ...
}

After:

// lib/useBtcPrice.ts (new resilient hook)
function useBtcPrice() {
  // Use a robust client like react-query for resilient fetching
  const { data: lkrRate, error } = useQuery({
    queryKey: ['lkr-rate'],
    queryFn: fetchLkrRateWithRetry, // Implements retry logic
    staleTime: 3_600_000, // Cache for 1 hour
  });

  // Use a hook that handles WebSocket state and reconnects
  const { priceUsd } = useResilientCoinCapSocket();

  const priceLkr = (priceUsd && lkrRate) ? priceUsd * lkrRate : null;

  // Return structured data with fallbacks
  return { priceLkr, priceUsd, isLoading: !priceUsd };
}

// BtcPriceTicker.tsx (simplified component)
function BtcPriceTicker() {
  const { priceLkr, priceUsd, isLoading } = useBtcPrice();
  // ... render logic with fallback to USD if LKR is unavailable
}
Suggestion importance[1-10]: 9

__

Why: This suggestion correctly identifies a critical architectural flaw regarding the fragile dependency on external APIs without any resilience, which could easily break the feature.

High
Possible issue
Prevent websocket and timer duplication

Remove price from the dependency array to avoid recreating the WebSocket and
interval every second, which can leak connections and duplicate timers. Use the
functional updater to set prevPrice based on the previous state within the
interval.

src/components/BtcPriceTicker.tsx [26-43]

 useEffect(() => {
     if (!rate) return;
     const ws = new WebSocket("wss://ws.coincap.io/prices?assets=bitcoin");
     ws.onmessage = (event) => {
         const data = JSON.parse(event.data);
         if (data.bitcoin) {
             priceRef.current = parseFloat(data.bitcoin) * rate;
         }
     };
     const interval = setInterval(() => {
-        setPrevPrice((p) => price);
+        setPrevPrice((prev) => (prev === null ? priceRef.current : prev));
         setPrice(priceRef.current);
     }, 1000);
     return () => {
         ws.close();
         clearInterval(interval);
     };
-}, [rate, price]);
+}, [rate]);
  • Apply / Chat
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical performance bug where adding price to the useEffect dependency array causes the WebSocket connection and interval timer to be re-created every second, which is highly inefficient.

High
Guard against invalid FX rate

Validate the response and presence of the LKR rate before using it to avoid
undefined leading to NaN prices. Fallback to a sensible default or skip starting
the socket when the rate is invalid.

src/components/BtcPriceTicker.tsx [14-22]

 const fetchRate = async () => {
     try {
         const res = await fetch("https://open.er-api.com/v6/latest/USD");
+        if (!res.ok) throw new Error(`HTTP ${res.status}`);
         const data = await res.json();
-        setRate(data.rates.LKR);
+        const nextRate = data?.rates?.LKR;
+        if (typeof nextRate === "number" && isFinite(nextRate)) {
+            setRate(nextRate);
+        } else {
+            console.error("Missing or invalid LKR rate");
+            setRate(null);
+        }
     } catch (err) {
         console.error("Failed to fetch LKR rate", err);
+        setRate(null);
     }
 };
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion improves robustness by adding validation for the fetched exchange rate, preventing potential NaN values and runtime errors if the API response is unexpected or fails.

Medium
General
Harden websocket message handling

Handle parse errors and ensure numeric conversion succeeds to prevent runtime
crashes and NaN updates. Add basic onerror and onclose logging for
observability.

src/components/BtcPriceTicker.tsx [29-34]

 ws.onmessage = (event) => {
-    const data = JSON.parse(event.data);
-    if (data.bitcoin) {
-        priceRef.current = parseFloat(data.bitcoin) * rate;
+    try {
+        const data = JSON.parse(event.data);
+        const btc = data?.bitcoin;
+        const usd = typeof btc === "string" ? parseFloat(btc) : Number(btc);
+        if (typeof usd === "number" && isFinite(usd) && typeof rate === "number") {
+            priceRef.current = usd * rate;
+        }
+    } catch (e) {
+        console.error("Failed to parse BTC price message", e);
     }
 };
+ws.onerror = (e) => {
+    console.error("BTC websocket error", e);
+};
+ws.onclose = () => {
+    // Optionally implement reconnection/backoff here
+    // console.warn("BTC websocket closed");
+};
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: This suggestion enhances the component's reliability by adding error handling for WebSocket message parsing and data validation, which prevents potential crashes from malformed data and improves observability.

Medium
  • More

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
src/components/BtcPriceTicker.tsx (4)

61-66: Avoid remounting on every price update

Using key={price} forces a full remount on each tick. You can still animate without remounting.

-        <motion.div
-            key={price}
+        <motion.div
             initial={{ scale: 1 }}
             animate={{ scale: [1, 1.05, 1] }}
             transition={{ duration: 0.5 }}
             className={`text-sm font-medium ${trendClass}`}
         >

68-69: Align LKR symbol formatting with the rest of the app

Elsewhere in the dashboard, LKR is displayed as “රු.”. Use the same here for consistency.

-            BTC Price: රු {price.toLocaleString("en-LK", { maximumFractionDigits: 0 })}
+            BTC Price: රු. {price.toLocaleString("en-LK", { maximumFractionDigits: 0 })}

If the preferred product style is different, update the dashboard instances instead. Do you want me to sweep the repo and normalize all LKR displays?


28-35: Optional: Add reconnection/backoff for WebSocket resilience

Right now, a transient WS drop will freeze updates until a reload. Consider a simple reconnect with backoff capped at, say, 30s.

I can provide a tiny reconnect helper (with jitter) if you’d like.


53-56: Optional: Better empty/loading states

If the FX rate fails to load, you’ll show “Loading” indefinitely. Consider displaying USD price as a fallback or a clear error state and retry.

src/app/dashboard/page.tsx (1)

285-289: Good UI placement; consider lazy/dynamic import to trim initial bundle

The ticker is purely client-side. To keep the page lean (and to ease a future move back to an SSR page), consider dynamic import with ssr: false.

Apply this diff to the import:

-import BtcPriceTicker from "@/components/BtcPriceTicker";
+// Lazy load the ticker to reduce initial bundle and avoid SSR constraints
+const BtcPriceTicker = dynamic(() => import("@/components/BtcPriceTicker"), { ssr: false });

And add (outside this hunk) at the top-level imports:

import dynamic from "next/dynamic";
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bc87c97 and bf8664b.

📒 Files selected for processing (2)
  • src/app/dashboard/page.tsx (2 hunks)
  • src/components/BtcPriceTicker.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

All HTTP requests must use the fetchy wrapper from @/lib/fetchy

Files:

  • src/app/dashboard/page.tsx
  • src/components/BtcPriceTicker.tsx
src/app/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place page and route components under src/app/ (Next.js App Router)

Files:

  • src/app/dashboard/page.tsx
src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Prefer server-side rendering where possible

Files:

  • src/app/dashboard/page.tsx
src/components/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place reusable components under src/components/

Files:

  • src/components/BtcPriceTicker.tsx
🧬 Code graph analysis (1)
src/app/dashboard/page.tsx (1)
src/components/BtcPriceTicker.tsx (1)
  • BtcPriceTicker (6-71)
🔇 Additional comments (3)
src/components/BtcPriceTicker.tsx (2)

6-12: Nice, focused client component with cleanup and trend UI

Encapsulated state, cleanup, and simple animation look good.


13-24: Use fetchy wrapper in BtcPriceTicker.tsx and confirm API provider

I’ve verified that in src/components/BtcPriceTicker.tsx the only direct fetch call is on lines 16–18. All other direct fetch occurrences lie outside this PR’s scope. Please apply the following change to replace the native fetch with our fetchy wrapper and add basic response validation. Also, the PR description mentions “Open Exchange Rates” but the code currently calls open.er-api.com (ExchangeRate-API). Confirm whether to update the description or switch to Open Exchange Rates’ endpoint.

Files needing updates:

  • src/components/BtcPriceTicker.tsx

Proposed diff:

 import { motion } from "framer-motion";
+import fetchy from "@/lib/fetchy";

 useEffect(() => {
     const fetchRate = async () => {
         try {
-            const res = await fetch("https://open.er-api.com/v6/latest/USD");
-            const data = await res.json();
-            setRate(data.rates.LKR);
+            const data = await fetchy.get<{ rates?: Record<string, number> }>(
+                "https://open.er-api.com/v6/latest/USD"
+            );
+            const lkr = data?.rates?.LKR;
+            if (typeof lkr === "number") {
+                setRate(lkr);
+            } else {
+                console.error("LKR rate not found in response");
+            }
         } catch (err) {
             console.error("Failed to fetch LKR rate", err);
         }
     };
     fetchRate();
 }, []);
src/app/dashboard/page.tsx (1)

23-23: Correct placement and import of reusable component

Importing BtcPriceTicker from src/components aligns with the repo’s component placement guidelines.

Comment on lines +26 to +44
useEffect(() => {
if (!rate) return;
const ws = new WebSocket("wss://ws.coincap.io/prices?assets=bitcoin");
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.bitcoin) {
priceRef.current = parseFloat(data.bitcoin) * rate;
}
};
const interval = setInterval(() => {
setPrevPrice((p) => price);
setPrice(priceRef.current);
}, 1000);
return () => {
ws.close();
clearInterval(interval);
};
}, [rate, price]);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Fix WebSocket re-creation loop and incorrect prevPrice update

Including price in the effect’s dependency array recreates the WebSocket and timer on every update (roughly every second), causing churn and potential rate limits. Also, setPrevPrice((p) => price) ignores the functional param and does not reliably capture the previously displayed price.

  • Remove price from deps.
  • Track the last displayed price with a ref and update prevPrice before setting the new price.
  • Add basic ws.onerror logging and safer cleanup.

Add a ref for the displayed price somewhere near existing refs:

-    const priceRef = useRef<number>(0);
+    const priceRef = useRef<number>(0); // latest incoming LKR price
+    const displayedPriceRef = useRef<number | null>(null); // last displayed LKR price

Then replace the effect as below:

-useEffect(() => {
-    if (!rate) return;
-    const ws = new WebSocket("wss://ws.coincap.io/prices?assets=bitcoin");
-    ws.onmessage = (event) => {
-        const data = JSON.parse(event.data);
-        if (data.bitcoin) {
-            priceRef.current = parseFloat(data.bitcoin) * rate;
-        }
-    };
-    const interval = setInterval(() => {
-        setPrevPrice((p) => price);
-        setPrice(priceRef.current);
-    }, 1000);
-    return () => {
-        ws.close();
-        clearInterval(interval);
-    };
-}, [rate, price]);
+useEffect(() => {
+    if (!rate) return;
+    const ws = new WebSocket("wss://ws.coincap.io/prices?assets=bitcoin");
+    ws.onmessage = (event: MessageEvent) => {
+        const data = JSON.parse(event.data);
+        if (data.bitcoin) {
+            priceRef.current = parseFloat(data.bitcoin) * rate;
+        }
+    };
+    ws.onerror = (e) => {
+        console.error("CoinCap WebSocket error", e);
+    };
+    const interval = setInterval(() => {
+        const next = priceRef.current;
+        if (typeof next === "number" && next > 0) {
+            setPrevPrice(displayedPriceRef.current);
+            setPrice(next);
+            displayedPriceRef.current = next;
+        }
+    }, 1000);
+    return () => {
+        try {
+            ws.close();
+        } finally {
+            clearInterval(interval);
+        }
+    };
+}, [rate]);

Also applies to: 45-51

🤖 Prompt for AI Agents
In src/components/BtcPriceTicker.tsx around lines 26 to 44 (and similarly
45-51), the effect is recreating the WebSocket and timer every second because
price is in the dependency array and prevPrice is set incorrectly; remove price
from the deps so the effect only depends on rate, add a ref (e.g.,
displayedPriceRef) to hold the last displayed price, update
displayedPriceRef.current before calling setPrice and use that value to
setPrevPrice (instead of setPrevPrice(() => price)), add ws.onerror logging, and
make cleanup robust by checking ws.readyState before closing and always clearing
the interval. Ensure the effect returns the cleanup that closes the socket and
clears the interval and that any refs are updated inside the interval callback
so state updates are stable.

@helloscoopa helloscoopa changed the title Add real-time BTC price ticker feat: aaa real-time BTC price ticker Aug 28, 2025
@helloscoopa helloscoopa changed the title feat: aaa real-time BTC price ticker feat: add real-time BTC price ticker Aug 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant