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 [email protected] 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.

You May Also Like

Little Pepe (LILPEPE) koers, nu investeren in de lopende presale?

Little Pepe (LILPEPE) koers, nu investeren in de lopende presale?

i Kennisgeving: Dit artikel bevat inzichten van onafhankelijke auteurs en valt buiten de redactionele verantwoordelijkheid van BitcoinMagazine.nl. De informatie is bedoeld ter educatie en reflectie. Dit is geen financieel advies. Doe zelf onderzoek voordat je financiële beslissingen neemt. Crypto is zeer volatiel er zitten kansen en risicos aan deze investering. Je kunt je inleg verliezen. Little Pepe (LILPEPE) is dit jaar uitgegroeid tot een van de meest besproken meme coins. Het project ontwikkelt een eigen Layer 2 blockchain die speciaal is ontworpen voor meme projecten. De presale van LILPEPE startte op 10 juni 2025 en haalde sindsdien meer dan $ 25,9 miljoen bij investeerders op. Tot nu toe was elke fase van de presale ruim voor tijd uitverkocht. Nu zit het project in fase 13 en kun je de tokens aanschaffen voor een prijs van $ 0,0022 per stuk. Little Pepe combineert heel slim de meme cultuur met geavanceerde blockchain technologie. Het team bouwde een EVM-compatibel Layer 2 netwerk dat razendsnelle transacties en vrijwel geen kosten biedt. Daarmee steekt LILPEPE ver boven de typische meme coins uit die op bestaande netwerken draaien. Het project heeft 26,5% van de totale voorraad van 100 miljard tokens gereserveerd voor de presale. Elke nieuwe fase stijgt de token prijs, waardoor deelnemers worden aangemoedigd sneller toe te slaan. Nu al zijn meer dan 15 miljard tokens verkocht en de presale nadert snel het einde. Little Pepe presale blijft sterk presteren De presale heeft sinds de start in juni een stevige groei laten zien. Zo is in meerdere ronden al meer dan $ 25,9 miljoen opgehaald. Ronde 1 startte met een prijs van $ 0,001 per token en was al binnen slechts 72 uur uitverkocht, goed voor bijna $ 500.000. Tijdens de tweede presale fase kostte de coin tussen $ 0,0011 en $ 0,0015 en haalde het project meer dan $ 1,23 miljoen op voordat alles snel uitverkocht was. In ronde 3 steeg de prijs naar $ 0,0012, met een bevestigde exchange listing prijs van $ 0,003. Wie er vroeg bij was, zag daardoor een potentiële winst van 150%. De eerdere presale rondes trokken zoveel belangstelling dat de tokens sneller uitverkochten dan verwacht. Inmiddels hebben meer dan 38.000 mensen deelgenomen. In ronde 13 van de presale staat de token momenteel geprijsd op $ 0,0022. Doordat de prijs bij elke mijlpaal stapsgewijs stijgt, voelt men er vanzelf een soort urgentie bij. Vroege deelnemers hebben zo veel lagere prijzen kunnen pakken dan de huidige kopers. Dankzij deze gefaseerde aanpak blijft de presale de hele periode door spannend en interessant. Belangrijkste kenmerken van Little Pepe’s technologie Little Pepe is de native currency van een gloednieuwe Layer 2 chain, speciaal voor meme coins. De blockchain is razendsnel, extreem goedkoop en sterk beveiligd en vooral aantrekkelijk voor traders en ontwikkelaars. Het netwerk verwerkt transacties in een oogwenk en de gas fees zijn bijna nul. De trades worden niet belast en dat zie je maar zelden bij meme coins. Bovendien is de blockchain beschermd tegen sniper bots, zodat kwaadaardige bots geen kans krijgen om presale lanceringen te manipuleren. Ontwikkelaars kunnen dankzij EVM-compatibiliteit heel eenvoudig smart contracts en meme tokens bouwen en lanceren. De infrastructuur is opgezet als hét centrale platform voor meme-innovatie, met on-chain communitytools en governance-opties. “Pepe’s Pump Pad” is het launchpad voor de meme tokens van het project. Tokens die hier worden gelanceerd, hebben ingebouwde anti-scam beveiligingen en liquidity locks worden automatisch toegepast om rug pulls te voorkomen. Zo kunnen makers nieuwe meme tokens lanceren zonder zich zorgen te maken over veiligheidsrisico’s. Is LILPEPE de beste crypto presale om nu te kopen? Little Pepe is de allereerste Layer 2 blockchain die volledig draait om memes. Dat geeft het project een unieke plek in de drukke wereld van meme coins. Het doel is om de “meme verse” te worden: een plek waar meme projecten kunnen lanceren, verhandelen en echt groeien. Het succes van de presale laat zien dat er veel interesse is voor deze aanpak. In de vroege fases waren de fase binnen 72 uur uitverkocht en zelfs de latere fases gingen sneller dan gepland. Met meer dan $ 25,9 miljoen dat is opgehaald, is er veel vertrouwen in deze meme coin. Little Pepe staat technisch stevig dankzij zijn Layer 2 infrastructuur. Het project heeft een CertiK security audit doorstaan, wat het vertrouwen van investeerders aanzienlijk versterkt. Als je naar de listings op CoinMarketCap en CoinGecko kijkt, is duidelijk te zien dat het project ook buiten de meme community steeds meer erkenning krijgt. Little Pepe is volgens analisten dan ook een van de meest veelbelovende meme coins voor 2025. De combinatie van meme cultuur en echte functionaliteit, maakt deze meme coin betrouwbaarder en waardevoller dan de meeste puur speculatieve tokens. Dankzij de snelle presale en het innovatieve ecosysteem is Little Pepe klaar om zich als serieuze speler in de wereld van meme coins te vestigen. Het project werkt volgens een roadmap met onder andere exchange listings, staking en uitbreiding van het ecosysteem. Door LILPEPE tokens te listen op grote gecentraliseerde exchanges, wordt het voor iedereen makkelijker om te traden en neemt de liquiditeit flink toe. Mega Giveaway campagne vergroot betrokkenheid community Little Pepe is gestart met een Mega Giveaway om de community te belonen voor hun deelname. De Mega Giveaway richt zich op de deelnemers die tijdens fases 12 tot en met 17 de meeste LILPEPE tokens hebben gekocht. De grootste koper wint 5 ETH, de tweede plaats ontvangt 3 ETH en de derde plaats 2 ETH. Ook worden 15 willekeurige deelnemers elk met 0,5 ETH beloond. Iedereen die LILPEPE bezit kan meedoen. Dat gaat heel handig. Je vult je ERC20-wallet adres in en voert een paar social media opdrachten uit. Deze actie moet gedurende de presale voor extra spanning en een gevoel van urgentie om snel mee te doen gaan zorgen, zowel aan de giveaway als aan de presale. De giveaway loopt dan ook tot fase 17 volledig is uitverkocht. De community blijft op alle platforms hard doorgroeien. Tijdens de giveaway is de activiteit op social media flink omhooggeschoten. Zo’n betrokkenheid is vaak een goed teken dat een meme coin op weg is naar succes. Little Pepe analyse koers verwachting De tokens van Little Pepe gaan tijdens fase 13 voor $ 0,0022 over de toonbank. De listing prijs op de exchanges is bevestigd op $ 0,003 en kan de deelnemers aan de presale mooie winsten kan opleveren. Volgens analisten kan de prijs van LILPEPE tegen het einde van 2025 naar $ 0,01 stijgen. Dit zou het project een marktwaarde van $ 1 miljard kunnen geven. Deze voorspelling gaat uit van een sterke cryptomarkt en van succesvolle exchange listings. Voor 2026 lopen de koers verwachtingen voor LILPEPE sterk uiteen. Als de cryptomarkt blijft stijgen, zou de token $ 0,015 kunnen bereiken. Maar als de markt instort en een bear market toeslaat, kan de prijs terugvallen naar $ 0,0015. Dat is een groot verschil, maar zo werkt crypto nu eenmaal. Zeker bij meme coins, omdat ze sterk reageren op de marktsfeer. Op de lange termijn, richting het jaar 2030, wijzen sommige verwachtingen op prijzen van $ 0,03 in gunstige scenario’s. Dat gaat uit van een succesvolle aanname van Layer 2 en verdere groei van de meme coin sector. Voorzichtige schattingen plaatsen de prijs in 2030 rond $ 0,0095. Zelfs een klein stukje van de marktwaarde van grote meme coins kan volgens experts al voor flinke winsten zorgen. Sommige analisten verwachten dat de opbrengsten zelfs 15.000% tot 20.000% kunnen bereiken als Little Pepe hetzelfde succes haalt als eerdere populaire meme coins. Doe mee aan de Little Pepe presale Wil je erbij zijn? Ga naar de officiële website van de coin om mee te doen aan de presale. Tijdens de huidige fase kost een token $ 0,0022 en je kunt eenvoudig betalen met ETH of USDT via je wallet. Je kunt aan de presale deelnemen met MetaMask of Trust Wallet. Verbind je wallet eenvoudig met de officiële website en zorg dat je voldoende ETH of USDT hebt om het gewenste aantal tokens te kopen. De presale accepteert ERC-20 tokens op het Ethereum netwerk. Na aankoop kun je je tokens claimen zodra alle presale rondes zijn afgerond. Alle informatie over het claimen vind je via de officiële website en communicatiekanalen. NEEM NU DEEL AAN DE LITTLE PEPE ($ LILPEPE) PRESALE Website    |    (X) Twitter    |  Telegram i Kennisgeving: Dit artikel bevat inzichten van onafhankelijke auteurs en valt buiten de redactionele verantwoordelijkheid van BitcoinMagazine.nl. De informatie is bedoeld ter educatie en reflectie. Dit is geen financieel advies. Doe zelf onderzoek voordat je financiële beslissingen neemt. Crypto is zeer volatiel er zitten kansen en risicos aan deze investering. Je kunt je inleg verliezen. Het bericht Little Pepe (LILPEPE) koers, nu investeren in de lopende presale? is geschreven door Redactie en verscheen als eerst op Bitcoinmagazine.nl.
Share
Coinstats2025/09/18 18:50