The main implementation went into `src/crud/chat/index.tsx. The architecture, the contexts for sockets and state, and the UI components that tied it all together. I wanted all connection logic in one place, so I created `SocketContext': `ChatContext': 'SocketContext' with React.The main implementation went into `src/crud/chat/index.tsx. The architecture, the contexts for sockets and state, and the UI components that tied it all together. I wanted all connection logic in one place, so I created `SocketContext': `ChatContext': 'SocketContext' with React.

I Built My Own Chat Instead of Relying on Jivo or LiveChat: Here's How

2025/08/27 21:00

So, I recently had a project where I needed a chat feature. My first thought was whether to just integrate an existing tool like Jivo or LiveChat, but I didn’t want to depend on third-party products for something that could be built directly into my admin panel.

\ In this post, I’ll go through how I built it: the architecture, the contexts for sockets and state, and the UI components that tied it all together.

Why Admiral?

Admiral is designed to be extensible. With file-based routing, hooks, and flexible components, it doesn’t lock you in—it gives you space to implement custom features. That’s exactly what I needed for chat: not just CRUD, but real-time messaging that still fit seamlessly into the panel.

Chat Architecture

Here’s how I structured things:

Core components

  • ChatPage – the main chat page
  • ChatSidebar – conversation list with previews
  • ChatPanel – renders the selected chat
  • MessageFeed – the thread of messages
  • MessageInput – the input with file upload

\ Context providers

  • SocketContext – manages WebSocket connections
  • ChatContext – manages dialogs and message state

Main Chat Page

With Admiral’s routing, setting up a new page was straightforward.

// pages/chat/index.tsx  import ChatPage from '@/src/crud/chat' export default ChatPage 

\ That was enough to make the page available at /chat.

\ The main implementation went into src/crud/chat/index.tsx:

// src/crud/chat/index.tsx  import React from 'react'  import { Card } from '@devfamily/admiral' import { usePermissions, usePermissionsRedirect } from '@devfamily/admiral' import { SocketProvider } from './contexts/SocketContext' import { ChatProvider } from './contexts/ChatContext' import ChatSidebar from './components/ChatSidebar' import ChatPanel from './components/ChatPanel' import styles from './Chat.module.css'  export default function ChatPage() {   const { permissions, loaded, isAdmin } = usePermissions()   const identityPermissions = permissions?.chat?.chat    usePermissionsRedirect({ identityPermissions, isAdmin, loaded })    return (     <SocketProvider>       <ChatProvider>         <Card className={styles.page}>           <PageTitle title="Corporate chat" />           <div className={styles.chat}>             <ChatSidebar />             <ChatPanel />           </div>         </Card>       </ChatProvider>     </SocketProvider>   ) } 

Here, I wrapped the page in SocketProvider and ChatProvider, and used Admiral’s hooks for permissions and redirects.

Managing WebSocket Connections With SocketContext

For real-time chat, I chose Centrifuge. I wanted all connection logic in one place, so I created SocketContext:

// src/crud/chat/SocketContext.tsx  import React from 'react'  import { Centrifuge } from 'centrifuge' import { createContext, ReactNode, useContext, useEffect, useRef, useState } from 'react' import { useGetIdentity } from '@devfamily/admiral'  const SocketContext = createContext(null)  export const SocketProvider = ({ children }: { children: ReactNode }) => {     const { identity: user } = useGetIdentity()     const [lastMessage, setLastMessage] = useState(null)     const centrifugeRef = useRef(null)     const subscribedRef = useRef(false)      useEffect(() => {         if (!user?.ws_token) return          const WS_URL = import.meta.env.VITE_WS_URL         if (!WS_URL) {             console.error('❌ Missing VITE_WS_URL in env')             return         }          const centrifuge = new Centrifuge(WS_URL, {             token: user.ws_token, // Initializing the WebSocket connection with a token         })          centrifugeRef.current = centrifuge         centrifugeRef.current.connect()          // Subscribing to the chat channel         const sub = centrifugeRef.current.newSubscription(`admin_chat`)          sub.on('publication', function (ctx: any) {                setLastMessage(ctx.data);         }).subscribe()          // Cleaning up on component unmount         return () => {             subscribedRef.current = false             centrifuge.disconnect()         }     }, [user?.ws_token])      return (         <SocketContext.Provider value={{ lastMessage, centrifuge: centrifugeRef.current }}>             {children}         </SocketContext.Provider>     ) }  export const useSocket = () => {     const ctx = useContext(SocketContext)     if (!ctx) throw new Error('useSocket must be used within SocketProvider')     return ctx } 

This context handled connection setup, subscription, and cleanup. Other parts of the app just used useSocket().

Managing Chat State With ChatContext

Next, I needed to fetch dialogs, load messages, send new ones, and react to WebSocket updates. For that, I created ChatContext:

// src/crud/chat/ChatContext.tsx  import React, { useRef } from "react";  import {   createContext,   useContext,   useEffect,   useState,   useRef,   useCallback, } from "react"; import { useSocket } from "./SocketContext"; import { useUrlState } from "@devfamily/admiral"; import api from "../api";  const ChatContext = createContext(null);  export const ChatProvider = ({ children }) => {   const { lastMessage } = useSocket();   const [dialogs, setDialogs] = useState([]);   const [messages, setMessages] = useState([]);   const [selectedDialog, setSelectedDialog] = useState(null);   const [urlState] = useUrlState();   const { client_id } = urlState;    const fetchDialogs = useCallback(async () => {     const res = await api.dialogs();     setDialogs(res.data || []);   }, []);    const fetchMessages = useCallback(async (id) => {     const res = await api.messages(id);     setMessages(res.data || []);   }, []);    useEffect(() => {     fetchMessages(client_id);   }, [fetchMessages, client_id]);    useEffect(() => {     fetchDialogs();   }, [fetchDialogs]);    useEffect(() => {     if (!lastMessage) return;      fetchDialogs();      setMessages((prev) => [...prev, lastMessage.data]);   }, [lastMessage]);    const sendMessage = useCallback(     async (value, onSuccess, onError) => {       try {         const res = await api.send(value);         if (res?.data) setMessages((prev) => [...prev, res.data]);         fetchDialogs();         onSuccess();       } catch (err) {         onError(err);       }     },     [messages]   );    // Within this context, you can extend the logic to:   // – Mark messages as read (api.read())   // – Group messages by date, and more.    return (     <ChatContext.Provider       value={{         dialogs,         messages: groupMessagesByDate(messages),         selectedDialog,         setSelectedDialog,         sendMessage,       }}     >       {children}     </ChatContext.Provider>   ); };  export const useChat = () => {   const ctx = useContext(ChatContext);   if (!ctx) throw new Error("useChat must be used within ChatProvider");   return ctx; }; 

This kept everything — fetching, storing, updating — in one place.

API Client Example

I added a small API client for requests:

// src/crud/chat/api.ts  import _ from '../../config/request' import { apiUrl } from '@/src/config/api'  const api = {     dialogs: () => _.get(`${apiUrl}/chat/dialogs`)(),     messages: (id) => _.get(`${apiUrl}/chat/messages/${id}`)(),     send: (data) => _.postFD(`${apiUrl}/chat/send`)({ data }),     read: (data) => _.post(`${apiUrl}/chat/read`)({ data }), }  export default api 

UI Components: Sidebar + Panel + Input

Then I moved to the UI layer.

ChatSidebar

// src/crud/chat/components/ChatSidebar.tsx  import React from "react";  import styles from "./ChatSidebar.module.scss"; import ChatSidebarItem from "../ChatSidebarItem/ChatSidebarItem"; import { useChat } from "../../model/ChatContext";  function ChatSidebar({}) {   const { dialogs } = useChat();      if (!dialogs.length) {     return (       <div className={styles.empty}>         <span>No active активных dialogs</span>       </div>     );   }    return <div className={styles.list}>       {dialogs.map((item) => (         <ChatSidebarItem key={item.id} data={item} />       ))}     </div> }  export default ChatSidebar; 

ChatSidebarItem

// src/crud/chat/components/ChatSidebarItem.tsx  import React from "react";  import { Badge } from '@devfamily/admiral' import dayjs from "dayjs"; import { BsCheck2, BsCheck2All } from "react-icons/bs"; import styles from "./ChatSidebarItem.module.scss";  function ChatSidebarItem({ data }) {   const { client_name, client_id, last_message, last_message_ } = data;    const [urlState, setUrlState] = useUrlState();   const { client_id } = urlState;    const { setSelectedDialog } = useChat();    const onSelectDialog = useCallback(() => {     setUrlState({ client_id: client.id });     setSelectedDialog(data);   }, [order.id]);    return (     <div       className={`${styles.item} ${isSelected ? styles.active : ""}`}       onClick={onSelectDialog}       role="button"     >       <div className={styles.avatar}>{client_name.charAt(0).toUpperCase()}</div>        <div className={styles.content}>         <div className={styles.header}>           <span className={styles.name}>{client_name}</span>           <span className={styles.time}>             {dayjs(last_message_).format("HH:mm")}             {message.is_read ? (               <BsCheck2All size="16px" />             ) : (               <BsCheck2 size="16px" />             )}           </span>         </div>         <span className={styles.preview}>{last_message.text}</span>         {unread_count > 0 && (             <Badge>{unread_count}</Badge>           )}       </div>     </div>   ); }  export default ChatSidebarItem; 

ChatPanel

// src/crud/chat/components/ChatPanel.tsx  import React from "react";  import { Card } from '@devfamily/admiral'; import { useChat } from "../../contexts/ChatContext"; import MessageFeed from "../MessageFeed"; import MessageInput from "../MessageInput"; import styles from "./ChatPanel.module.scss";  function ChatPanel() {   const { selectedDialog } = useChat();    if (!selectedDialog) {     return (       <Card className={styles.emptyPanel}>         <div className={styles.emptyState}>           <h3>Choose the dialog</h3>           <p>Choose the dialog from the list to start conversation</p>         </div>       </Card>     );   }    return (     <div className={styles.panel}>       <MessageFeed />       <div className={styles.divider} />       <MessageInput />     </div>   ); }  export default ChatPanel; 

MessageFeed

// src/crud/chat/components/MessageFeed.tsx  import React, { useRef, useEffect } from "react";  import { BsCheck2, BsCheck2All } from "react-icons/bs"; import { useChat } from "../../contexts/ChatContext"; import MessageItem from "../MessageItem"; import styles from "./MessageFeed.module.scss";  function MessageFeed() {   const { messages } = useChat();   const scrollRef = useRef(null);    useEffect(() => {     scrollRef.current?.scrollIntoView({ behavior: "auto" });   }, [messages]);    return (     <div ref={scrollRef} className={styles.feed}>       {messages.map((group) => (         <div key={group.date} className={styles.dateGroup}>           <div className={styles.dateDivider}>             <span>{group.date}</span>           </div>           {group.messages.map((msg) => (             <div className={styles.message}>               {msg.text && <p>{msg.text}</p>}               {msg.image && (                 <img                   src={msg.image}                   alt=""                   style={{ maxWidth: "200px", borderRadius: 4 }}                 />               )}               {msg.file && (                 <a href={msg.file} target="_blank" rel="noopener noreferrer">                   Скачать файл                 </a>               )}               <div style={{ fontSize: "0.8rem", opacity: 0.6 }}>                 {dayjs(msg.created_at).format("HH:mm")}                 {msg.is_read ? <BsCheck2All /> : <BsCheck2 />}               </div>             </div>           ))}         </div>       ))}     </div>   ); }  export default MessageFeed; 

MessageInput

// src/crud/chat/components/MessageInput.tsx  import React from "react";  import {   ChangeEventHandler,   useCallback,   useEffect,   useRef,   useState, } from "react";  import { FiPaperclip } from "react-icons/fi"; import { RxPaperPlane } from "react-icons/rx"; import { Form, Button, useUrlState, Textarea } from "@devfamily/admiral";  import { useChat } from "../../model/ChatContext";  import styles from "./MessageInput.module.scss";  function MessageInput() {   const { sendMessage } = useChat();   const [urlState] = useUrlState();   const { client_id } = urlState;   const [values, setValues] = useState({});   const textRef = useRef < HTMLTextAreaElement > null;    useEffect(() => {     setValues({});     setErrors(null);   }, [client_id]);    const onSubmit = useCallback(     async (e?: React.FormEvent<HTMLFormElement>) => {       e?.preventDefault();       const textIsEmpty = !values.text?.trim()?.length;        sendMessage(         {           ...(values.image && { image: values.image }),           ...(!textIsEmpty && { text: values.text }),           client_id,         },         () => {           setValues({ text: "" });         },         (err: any) => {           if (err.errors) {             setErrors(err.errors);           }         }       );     },     [values, sendMessage, client_id]   );    const onUploadFile: ChangeEventHandler<HTMLInputElement> = useCallback(     (e) => {       const file = Array.from(e.target.files || [])[0];       setValues((prev: any) => ({ ...prev, image: file }));       e.target.value = "";     },     [values]   );    const onChange = useCallback((e) => {     setValues((prev) => ({ ...prev, text: e.target.value }));   }, []);    const onKeyDown = useCallback((e: React.KeyboardEvent<HTMLTextAreaElement>) => {     if ((e.code === "Enter" || e.code === "NumpadEnter") && !e.shiftKey) {       onSubmit();       e.preventDefault();     }   }, [onSubmit]);    return (     <form className={styles.form} onSubmit={onSubmit}>       <label className={styles.upload}>         <input           type="file"           onChange={onUploadFile}           className={styles.visuallyHidden}         />         <FiPaperclip size="24px" />       </label>       <Textarea         value={values.text ?? ""}         onChange={onChange}         rows={1}         onKeyDown={onKeyDown}         placeholder="Написать сообщение..."         ref={textRef}         className={styles.textarea}       />       <Button         view="secondary"         type="submit"         disabled={!values.image && !values.text?.trim().length}         className={styles.submitBtn}       >         <RxPaperPlane />       </Button>     </form>   ); }  export default MessageInput; 

Styling

I styled it using Admiral’s CSS variables to keep everything consistent:

.chat {   border-radius: var(--radius-m);   border: 2px solid var(--color-bg-border);   background-color: var(--color-bg-default); }  .message {   padding: var(--space-m);   border-radius: var(--radius-s);   background-color: var(--color-bg-default); } 

Adding Notifications

I also added notifications for new messages when the user wasn’t viewing that chat:

import { useNotifications } from '@devfamily/admiral'  const ChatContext = () => {   const { showNotification } = useNotifications()    useEffect(() => {     if (!lastMessage) return      if (selectedDialog?.client_id !== lastMessage.client_id) {       showNotification({         title: 'New message',         message: `${lastMessage.client_name}: ${lastMessage.text || 'Image'}`,         type: 'info',         duration: 5000       })     }   }, [lastMessage, selectedDialog, showNotification]) } 

Conclusion

And just like that, instead of using third-party tools, I built it directly into my Admiral-based admin panel. Admiral’s routing, contexts, hooks, and design system made it possible to build a real-time chat that felt native to the panel.

\ The result was a fully custom chat: real-time messaging, dialogs, file uploads, and notifications—all integrated and under my control.

\ Check it out, and let me know what you think!

Disclaimer: The articles reposted on this site are sourced from public platforms and are provided for informational purposes only. They do not necessarily reflect the views of MEXC. All rights remain with the original authors. If you believe any content infringes on third-party rights, please contact service@support.mexc.com for removal. MEXC makes no guarantees regarding the accuracy, completeness, or timeliness of the content and is not responsible for any actions taken based on the information provided. The content does not constitute financial, legal, or other professional advice, nor should it be considered a recommendation or endorsement by MEXC.
Share Insights

You May Also Like

US Spot ETH ETFs Witness Remarkable $244M Inflow Surge

US Spot ETH ETFs Witness Remarkable $244M Inflow Surge

BitcoinWorld US Spot ETH ETFs Witness Remarkable $244M Inflow Surge The world of digital assets is buzzing with exciting news! US spot ETH ETFs recently experienced a significant milestone, recording a whopping $244 million in net inflows on October 28. This marks the second consecutive day of positive movement for these crucial investment vehicles, signaling a growing appetite for Ethereum exposure among mainstream investors. What’s Fueling the Latest US Spot ETH ETFs Inflow? This impressive influx of capital into US spot ETH ETFs highlights a clear trend: institutional and retail investors are increasingly comfortable with regulated crypto investment products. The figures, reported by industry tracker Trader T, show a robust interest that could reshape the market. Fidelity’s FETH led the charge, attracting a substantial $99.27 million. This demonstrates strong confidence in Fidelity’s offering and Ethereum’s long-term potential. BlackRock’s ETHA wasn’t far behind, securing $74.74 million in inflows. BlackRock’s entry into the crypto ETF space has been closely watched, and these numbers confirm its growing influence. Grayscale’s Mini ETH also saw significant action, pulling in $73.03 million. This new product is quickly gaining traction, offering investors another avenue for Ethereum exposure. It’s important to note that while most products saw positive flows, Grayscale’s ETHE experienced a net outflow of $2.66 million. This might suggest a shift in investor preference towards newer, perhaps more cost-effective, spot ETF options. Why Are US Spot ETH ETFs Attracting Such Significant Capital? The appeal of US spot ETH ETFs is multifaceted. For many investors, these products offer a regulated and accessible way to gain exposure to Ethereum without directly owning the cryptocurrency. This removes some of the complexities associated with digital asset management, such as setting up wallets, managing private keys, or dealing with less regulated exchanges. Key benefits include: Accessibility: Investors can buy and sell shares of the ETF through traditional brokerage accounts, just like stocks. Regulation: Being regulated by financial authorities provides a layer of security and trust that some investors seek. Diversification: For traditional portfolios, adding exposure to a leading altcoin like Ethereum through an ETF can offer diversification benefits. Liquidity: ETFs are generally liquid, allowing for easy entry and exit from positions. Moreover, Ethereum itself continues to be a powerhouse in the blockchain space, underpinning a vast ecosystem of decentralized applications (dApps), NFTs, and decentralized finance (DeFi) protocols. Its ongoing development and significant network activity make it an attractive asset for long-term growth. What Does This US Spot ETH ETFs Trend Mean for Investors? The consistent positive inflows into US spot ETH ETFs could be a strong indicator of maturing institutional interest in the broader crypto market. It suggests that major financial players are not just dabbling but are actively integrating digital assets into their investment strategies. For individual investors, this trend offers several actionable insights: Market Validation: The increasing capital flow validates Ethereum’s position as a significant digital asset with real-world utility and investor demand. Potential for Growth: Continued institutional adoption through ETFs could contribute to greater price stability and potential upward momentum for Ethereum. Observing Investor Behavior: The shift from products like Grayscale’s ETHE to newer spot ETFs highlights how investors are becoming more discerning about their investment vehicles, prioritizing efficiency and cost. However, it is crucial to remember that the crypto market remains volatile. While these inflows are positive, investors should always conduct their own research and consider their risk tolerance before making investment decisions. A Compelling Outlook for US Spot ETH ETFs The recent $244 million net inflow into US spot ETH ETFs is more than just a number; it’s a powerful signal. It underscores a growing confidence in Ethereum as an asset class and the increasing mainstream acceptance of regulated cryptocurrency investment products. With major players like Fidelity and BlackRock leading the charge, the landscape for digital asset investment is evolving rapidly, offering exciting new opportunities for both seasoned and new investors alike. This positive momentum suggests a potentially bright future for Ethereum’s integration into traditional financial portfolios. Frequently Asked Questions (FAQs) What is a US spot ETH ETF? A US spot ETH ETF (Exchange-Traded Fund) is an investment product that allows investors to gain exposure to the price movements of Ethereum (ETH) without directly owning the cryptocurrency. The fund holds actual Ethereum, and shares of the fund are traded on traditional stock exchanges. Which firms are leading the inflows into US spot ETH ETFs? On October 28, Fidelity’s FETH led with $99.27 million, followed by BlackRock’s ETHA with $74.74 million, and Grayscale’s Mini ETH with $73.03 million. Why are spot ETH ETFs important for the crypto market? Spot ETH ETFs are crucial because they provide a regulated, accessible, and often more familiar investment vehicle for traditional investors to enter the cryptocurrency market. This can lead to increased institutional adoption, greater liquidity, and enhanced legitimacy for Ethereum as an asset class. What was Grayscale’s ETHE outflow and what does it signify? Grayscale’s ETHE experienced a net outflow of $2.66 million. This might indicate that some investors are shifting capital from older, perhaps less efficient, Grayscale products to newer spot ETH ETFs, which often offer better fee structures or direct exposure without the previous trust structure limitations. If you found this article insightful, consider sharing it with your network! Your support helps us bring more valuable insights into the world of cryptocurrency. Spread the word and let others discover the exciting trends shaping the digital asset space. To learn more about the latest Ethereum trends, explore our article on key developments shaping Ethereum institutional adoption. This post US Spot ETH ETFs Witness Remarkable $244M Inflow Surge first appeared on BitcoinWorld.
Share
2025/10/29 11:45
First Ethereum Treasury Firm Sells ETH For Buybacks: Death Spiral Incoming?

First Ethereum Treasury Firm Sells ETH For Buybacks: Death Spiral Incoming?

Ethereum-focused treasury company ETHZilla said it has sold roughly $40 million worth of ether to fund ongoing share repurchases, a maneuver aimed at closing what it calls a “significant discount to NAV.” In a press statement on Monday, the company disclosed that since Friday, October 24, it has bought back about 600,000 common shares for approximately $12 million under a broader authorization of up to $250 million, and that it intends to continue buying while the discount persists. ETHZilla Dumps ETH For BuyBacks The company framed the buybacks as balance-sheet arbitrage rather than a strategic retreat from its core Ethereum exposure. “We are leveraging the strength of our balance sheet, including reducing our ETH holdings, to execute share repurchases,” chairman and CEO McAndrew Rudisill said, adding that ETH sales are being used as “cash” while common shares trade below net asset value. He argued the transactions would be immediately accretive to remaining shareholders. Related Reading: Crypto Analyst Shows The Possibility Of The Ethereum Price Reaching $16,000 ETHZilla amplified the message on X, saying it would “use its strong balance sheet to support shareholders through buybacks, reduce shares available for short borrow, [and] drive up NAV per share” and reiterating that it still holds “~$400 million of ETH” on the balance sheet and carries “no net debt.” The company also cited “recent, concentrated short selling” as a factor keeping the stock under pressure. The market-structure logic is straightforward: when a digital-asset treasury trades below the value of its coin holdings and cash, buying back stock with “coin-cash” can, in theory, collapse the discount and lift NAV per share. But the optics are contentious inside crypto because the mechanism requires selling the underlying asset—here, ETH—to purchase equity, potentially weakening the very treasury backing that investors originally sought. Death Spiral Incoming? Popular crypto trader SalsaTekila (@SalsaTekila) commented on X: “This is extremely bearish, especially if it invites similar behavior. ETH treasuries are not Saylor; they haven’t shown diamond-hand will. If treasury companies start dumping the coin to buy shares, it’s a death spiral setup.” Skeptics also zeroed in on funding choices. “I am mostly curious why the company chose to sell ETH and not use the $569m in cash they had on the balance sheet last month,” another analyst Dan Smith wrote, noting ETHZilla had just said it still holds about $400 million of ETH and thus didn’t deploy it on fresh ETH accumulation. “Why not just use cash?” The question cuts to the core of treasury signaling: using ETH as a liquidity reservoir to defend a discounted equity can be read as rational capital allocation, or as capitulation that undermines the ETH-as-reserve narrative. Beyond the buyback, a retail-driven storyline has rapidly formed around the stock. Business Insider reported that Dimitri Semenikhin—who recently became the face of the Beyond Meat surge—has targeted ETHZilla, saying he purchased roughly 2% of the company at what he views as a 50% discount to modified NAV. He has argued that the market is misreading ETHZilla’s balance sheet because it still reflects legacy biotech results rather than the current digital-asset treasury model. Related Reading: Ethereum Emerges As The Sole Trillion-Dollar Institutional Store Of Value — Here’s Why The same report cites liquid holdings on the order of 102,300 ETH and roughly $560 million in cash, translating to about $62 per share in liquid assets, and calls out a 1-for-10 reverse split on October 15 that, in his view, muddied the optics for retail. Semenikhin flagged November 13 as a potential catalyst if results show the pivot to ETH generating profits. The company’s own messaging emphasizes the discount-to-NAV lens rather than a change in strategy. ETHZilla told investors it would keep buying while the stock trades below asset value and highlighted a goal of shrinking lendable supply to blunt short-selling pressure. For Ethereum markets, the immediate flow effect is limited—$40 million is marginal in ETH’s daily liquidity—but the second-order risk flagged by traders is behavioral contagion. If other ETH-heavy treasuries follow the playbook, selling the underlying to buy their own stock, the flow could become pro-cyclical: coins are sold to close equity discounts, the selling pressures spot, and wider discounts reappear as equity screens rerate to the weaker mark—repeat. That is the “death spiral” scenario skeptics warn about when the treasury asset doubles as the company’s signal of conviction. At press time, ETH traded at $4,156. Featured image created with DALL.E, chart from TradingView.com
Share
2025/10/29 12:00