diff --git a/.gitignore b/.gitignore index fd3dbb5..00bba9b 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ yarn-error.log* # local env files .env*.local +.env # vercel .vercel diff --git a/package-lock.json b/package-lock.json index edc9006..a8fce86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,8 @@ "@solana/web3.js": "^1.91.4", "next": "14.1.3", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "react18-json-view": "^0.2.8" }, "devDependencies": { "autoprefixer": "^10.0.1", @@ -1782,6 +1783,14 @@ "react": "^18.2.0" } }, + "node_modules/react18-json-view": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/react18-json-view/-/react18-json-view-0.2.8.tgz", + "integrity": "sha512-uJlcf5PEDaba6yTqfcDAcMSYECZ15SLcpP94mLFTa/+fa1kZANjERqKzS7YxxsrGP4+jDxt6sIaglR0PbQcKPw==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/package.json b/package.json index 781b7bc..24d0596 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "@solana/web3.js": "^1.91.4", "next": "14.1.3", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "react18-json-view": "^0.2.8" }, "devDependencies": { "autoprefixer": "^10.0.1", diff --git a/pages/index.js b/pages/index.js index 3a9cee9..b6b9c2a 100644 --- a/pages/index.js +++ b/pages/index.js @@ -2,7 +2,10 @@ import { useCallback, useEffect, useState, useRef, Fragment } from "react"; import { PublicKey } from "@solana/web3.js"; import { ClockIcon, ArrowRightCircleIcon, CubeTransparentIcon, ChatBubbleOvalLeftEllipsisIcon, XCircleIcon, ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline"; import { CursorArrowRaysIcon, SunIcon, MoonIcon } from "@heroicons/react/20/solid"; -import { Switch, Dialog, Transition, Popover } from "@headlessui/react"; +import { Switch, Dialog, Transition, Popover, RadioGroup, Tab } from "@headlessui/react"; +import Head from 'next/head'; +import JsonView from 'react18-json-view' +import 'react18-json-view/src/style.css' const classNames = (...classes) => { return classes.filter(Boolean).join(' '); @@ -18,10 +21,13 @@ export default function Home() { const [commitmentState, setCommitmentState] = useState("confirmed"); const [details, setDetails] = useState("full"); const [encoding, setEncoding] = useState("base58"); - const [apiKey, setApiKey] = useState(""); const [countdown, setCountdown] = useState(30); const [showNotif, setShowNotif] = useState(false); - const [theme, setTheme] = useState('dark'); + const [includeVote, setIncludeVote] = useState(false); + const [includeFailed, setIncludeFailed] = useState(false); + const [showRewards, setShowRewards] = useState(false); + const apiKey = process.env.NEXT_PUBLIC_APIKEY; + const wsUrl = `wss://atlas-mainnet.helius-rpc.com?api-key=${apiKey}`; // Function to validate Solana address const validateAddress = (address) => { @@ -33,7 +39,7 @@ export default function Home() { try { const pubkey = new PublicKey(address); // Check if the encoded address has the typical length of 43 characters - if (pubkey.toBase58().length === 43) { + if (PublicKey.isOnCurve(pubkey.toBytes())) { return true; } else { alert('Invalid Solana address'); @@ -75,12 +81,15 @@ export default function Home() { } }; - const cloeWebSocket = useCallback(() => { + const closeWebsocket = useCallback(() => { if (ws.current) { ws.current.close(); - ws.current = null; - setIsConnected(false); - setCountdown(30); + ws.current.onclose = () => { + ws.current = null; + setIsConnected(false); + setCountdown(30); // Reset countdown on websocket close + }; + } }, []); @@ -102,21 +111,22 @@ export default function Home() { params: [ { accountInclude: accountsIncluded, - accountRequire: accountsRequired + accountRequire: accountsRequired, + vote: includeVote, + failed: includeFailed }, { commitment: commitmentState, encoding: encoding, transactionDetails: details, - showRewards: true, + showRewards: showRewards, maxSupportedTransactionVersion: 1 } ] }; if (!ws.current) { - // Open WebSocket - const wsUrl = `wss://atlas-mainnet.helius-rpc.com?api-key=${apiKey}`; + // Open websocket ws.current = new WebSocket(wsUrl); ws.current.onopen = () => { ws.current.send(JSON.stringify(request)); @@ -133,36 +143,16 @@ export default function Home() { setNotifications((prevNotifications) => [...prevNotifications, message]); }; }, [apiKey, addresses, commitmentState, details, encoding]); - - useEffect(() => { - const currentTheme = localStorage.getItem('theme') || 'light'; - setTheme(currentTheme); - document.documentElement.classList.toggle('dark', currentTheme === 'dark'); - }, []); - - const toggleTheme = () => { - const newTheme = theme === 'light' ? 'dark' : 'light'; - setTheme(newTheme); - localStorage.setItem('theme', newTheme); - document.documentElement.classList.toggle('dark', newTheme === 'dark'); - }; useEffect(() => { let countdownInterval; - if (ws.current) { - ws.current.onclose = () => { - setIsConnected(false); - setCountdown(30); // Reset countdown on WebSocket close - }; - } - if (isConnected && ws.current) { countdownInterval = setInterval(() => { setCountdown((prevCountdown) => { if (prevCountdown === 1) { clearInterval(countdownInterval); - cloeWebSocket(); // Close WebSocket when countdown reaches zero + closeWebsocket(); // Close websocket when countdown reaches zero setShowNotif(true); // Notify the user of the timeout return 30; // Reset countdown } @@ -178,6 +168,31 @@ export default function Home() { return ( <> + + Websocket Widget | Helius Labs + + + + + {/* Open Graph / Facebook */} + + + + + + + {/* Twitter */} + + + + + + + {/* Additional tags for finer control if needed */} + + + +
-
+
Want to learn more?{" "} @@ -336,46 +333,25 @@ export default function Home() {
-

+

Go Faster With
Websockets

-

- Websockets keep a persistent connection open,
enabling real-time data exchange. Test the transactionSubscribe method on Mainnet below. +

+ Websockets keep a persistent connection open,
enabling real-time data exchange. Test the transactionSubscribe method on Mainnet.

-
- -
- setApiKey(event.target.value)} - className="overflow-x-scroll px-2 block w-full rounded-md border-0 bg-white/5 py-2 text-gray-800 dark:text-gray-200 shadow-sm ring-1 ring-inset ring-black/10 dark:ring-white/10 focus:ring-2 focus:ring-inset focus:ring-indigo-500 sm:text-sm sm:leading-6" - /> -
-
-
-
@@ -386,7 +362,7 @@ export default function Home() { checked={isRequired} onChange={setIsRequired} className={classNames( - isRequired ? 'bg-orange-300/40' : 'bg-black/10 dark:bg-white/10', + isRequired ? 'bg-orange-300/40' : 'bg-white/10 ', "relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out" )} > @@ -399,7 +375,7 @@ export default function Home() { /> - Is Required{' '} + Is Required{' '}
@@ -408,28 +384,28 @@ export default function Home() {
-
+
{addresses.length === 0 ? ( -
+
Enter an address
to get started
) : ( addresses.map(item => ( -
+
{item.address.slice(0, 6)}...{item.address.slice(-6)} @@ -450,72 +426,155 @@ export default function Home() {
-
-
-
+
+ +
+ + + + + Include Failed Transactions{' '} + + +
+
+
+ +
+ + + + + Include Vote Transactions{' '} + + +
+
+ +
+ +
+ + + + +
+
+
-
-
+
+
Transaction Compute Units Fee (SOL) @@ -523,23 +582,30 @@ export default function Home() {
{notifications.length === 0 ? ( -
+
Websocket feed will
display here
) : ( notifications.reverse().map((notification, index) => ( - notification.params && + notification.params && -
- {notification.params.result.signature.slice(0, 3)}..{notification.params.result.signature.slice(-3)} - {notification.params.result.transaction.meta.computeUnitsConsumed} - {notification.params.result.transaction.meta.fee / 1000000000} -
+
+ + {notification.params.result.signature.slice(0, 3)}..{notification.params.result.signature.slice(-3)} + + {notification.params.result.transaction?.meta.computeUnitsConsumed ? notification.params.result.transaction.meta.computeUnitsConsumed : `N/A`} + {notification.params.result.transaction?.meta.fee ? notification.params.result.transaction.meta.fee / 1000000000 : "N/A"} +
+ +
-
- -
-
-

- Method - -

-

{notification.method}

-
-
- -
-
-

- Transaction Version - -

-

{notification.params.result.transaction.version}

-
-
- -
-
-

- Subscription - -

-

{notification.params.subscription}

-
-
- -
-
-

- Encoding - -

-

{notification.params.result.transaction.transaction[1]}

-
-
- + + + + {({ selected }) => ( + + )} + + + {({ selected }) => ( + + )} + + + + + + + + + + +
- -
-
+
)) @@ -621,28 +682,15 @@ export default function Home() {
-
+
-

- Helius websockets consume 1 credit per event push. This app is for testing and demo purposes. - In order to preserve your credits, the websocket stream will automatically close after {countdown} seconds. +

+ This app is for testing and demo purposes. + The websocket stream will automatically close after {countdown} seconds.

- - ); diff --git a/styles/globals.css b/styles/globals.css index 58bcdce..42cd5a5 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -3,9 +3,9 @@ @tailwind utilities; :root { - --foreground-rgb: 0, 0, 0; - --background-start-rgb: 214, 219, 220; - --background-end-rgb: 255, 255, 255; + --foreground-rgb: 255, 255, 255; + --background-start-rgb: 12, 8, 8; + --background-end-rgb: 0, 0, 0; } @media (prefers-color-scheme: dark) { @@ -16,12 +16,6 @@ } } -.dark { - --foreground-rgb: 255, 255, 255; - --background-start-rgb: 0, 0, 0; - --background-end-rgb: 0, 0, 0; -} - body { color: rgb(var(--foreground-rgb)); background: linear-gradient(