diff --git a/app/Components/About.tsx b/app/Components/About.tsx index 966a56c..e8a9f99 100644 --- a/app/Components/About.tsx +++ b/app/Components/About.tsx @@ -1,8 +1,22 @@ import { Box, Card, CardContent, Typography, Link, Button, Avatar, Stack } from "@mui/material"; import GitHubIcon from "@mui/icons-material/GitHub"; +import GetAppIcon from "@mui/icons-material/GetApp"; + +const WIREGUARD_COLOR = "#88171A"; + +function WireGuardLogo({ size = 40 }: { size?: number }) { + return ( + + + + + + ); +} export default function About() { const repo = "https://github.com/cochrane2063/MSBD5017-Depin-WebClient"; + const wireguard = "https://www.wireguard.com/install/"; return ( @@ -32,6 +46,59 @@ export default function About() { + + + + + + + + + Download Wireguard Client + + + + + Wireguard client for the MSBD5017 Depin project. + + + + This project uses the Wireguard VPN protocol. You can download the Wireguard client from the official website. + + + + + + + + + + + ); } \ No newline at end of file diff --git a/app/Components/Contracts/CLRToken/CLRToken.json b/app/Components/Contracts/CLRToken/CLRToken.json new file mode 100644 index 0000000..b3d59a7 --- /dev/null +++ b/app/Components/Contracts/CLRToken/CLRToken.json @@ -0,0 +1,420 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "allowance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientAllowance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "ERC20InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC20InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "ERC20InvalidSender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "ERC20InvalidSpender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/app/Components/Contracts/ClearNet.json b/app/Components/Contracts/ClearNet/ClearNet.json similarity index 95% rename from app/Components/Contracts/ClearNet.json rename to app/Components/Contracts/ClearNet/ClearNet.json index bf704db..ecb3f88 100644 --- a/app/Components/Contracts/ClearNet.json +++ b/app/Components/Contracts/ClearNet/ClearNet.json @@ -615,6 +615,25 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "activeChannelIds", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -740,6 +759,48 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "getActivePaymentChannels", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_offset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_limit", + "type": "uint256" + } + ], + "name": "getActivePaymentChannelsPaginated", + "outputs": [ + { + "internalType": "address[]", + "name": "channels", + "type": "address[]" + }, + { + "internalType": "uint256", + "name": "total", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { diff --git a/app/Components/Contracts/Connections.tsx b/app/Components/Contracts/Connections.tsx index ecd10ce..7ef175c 100644 --- a/app/Components/Contracts/Connections.tsx +++ b/app/Components/Contracts/Connections.tsx @@ -1,28 +1,87 @@ import { Web3 } from 'web3'; -import clearNetJson from './ClearNet.json'; +import type { Node, NodeInfo, PaymentChannelInfo } from '../Util'; +import clearNetJson from './ClearNet/ClearNet.json'; +import clrTokenJson from './CLRTOKEN/CLRToken.json'; const clearNetABI = (clearNetJson as any).abi ?? clearNetJson; -const clearNetAddress = "0xf04cbb756045b276ea962ea98d938a0ed8101f51"; +const clrTokenABI = (clrTokenJson as any).abi ?? clrTokenJson; +const clearNetAddress = "0xb6f537b38b82d08ff3ed796754d9d85b5cfe9cb5"; +const clrTokenAddress = "0xf1664c17887767c8f58695846babb349ca61d2e9"; +const DEFAULT_MIN_STAKE = BigInt(10000) * BigInt(1e18); // 10000 CLR -export const getActiveNodes = async (provider: any) => { + +export const getActiveNodes = async (provider: any): Promise => { + if (!provider) { + return []; + } const web3 = new Web3(provider); - const contract = new web3.eth.Contract(clearNetABI, clearNetAddress); - const activeNodes = await contract.methods.getActiveNodes().call(); - return activeNodes; + const clearnet_contract = new web3.eth.Contract(clearNetABI, clearNetAddress); + const activeNodes: string[] = await clearnet_contract.methods.getActiveNodes().call(); + const nodes: Node[] = await Promise.all(activeNodes.map(async (node: string) => { + const nodeInfo: NodeInfo = await clearnet_contract.methods.getNodeInfo(node).call(); + return { + ip: nodeInfo.ipAddress, + port: Number(nodeInfo.port), + traffic: 0, + price: Number(nodeInfo.pricePerMinute) / 1e18, + rating: Number(nodeInfo.reputationScore) / 1000, + }; + })); + console.log("Fetched nodes:", nodes); + return nodes; } -export async function registerNode(provider: any,account: string) { +export async function approveCLRTokenSpending(provider: any,account: string) { const web3 = new Web3(provider); - const contract = new web3.eth.Contract(clearNetABI, clearNetAddress); + const clr_token_contract = new web3.eth.Contract(clrTokenABI, clrTokenAddress); const gasPrice = await web3.eth.getGasPrice(); const gasLimit = 300000; - const tx = await contract.methods.registerNode("57.158.82.48", 51820, 1000000000000000000n).send({ + const tx = await clr_token_contract.methods.approve(clearNetAddress, DEFAULT_MIN_STAKE).send({ + from: account, + gas: String(gasLimit), + gasPrice: String(gasPrice), + }); + console.log(tx); +} + +export async function openPaymentChannel(provider: any,account: string,amount: BigInt) { + const web3 = new Web3(provider); + const clearnet_contract = new web3.eth.Contract(clearNetABI, clearNetAddress); + const gasPrice = await web3.eth.getGasPrice(); + const gasLimit = 300000; + const tx = await clearnet_contract.methods.openPaymentChannel(amount).send({ from: account, gas: String(gasLimit), gasPrice: String(gasPrice), }); console.log(tx); +} +export async function closePaymentChannel(provider: any,account: string) { + const web3 = new Web3(provider); + const clearnet_contract = new web3.eth.Contract(clearNetABI, clearNetAddress); + const gasPrice = await web3.eth.getGasPrice(); + const gasLimit = 300000; + const tx = await clearnet_contract.methods.closePaymentChannel().send({ + from: account, + gas: String(gasLimit), + gasPrice: String(gasPrice), + }); + console.log(tx); +} + +export async function getPaymentChannelInfo(provider: any,account: string) : Promise { + if (!provider) { + return { balance: BigInt(0), nonce: BigInt(0), isActive: false}; + } + const web3 = new Web3(provider); + const clearnet_contract = new web3.eth.Contract(clearNetABI, clearNetAddress); + const channelInfo: PaymentChannelInfo = await clearnet_contract.methods.getPaymentChannelInfo(account).call(); + return { + balance: channelInfo.balance, + nonce: channelInfo.nonce, + isActive: channelInfo.isActive, + }; } \ No newline at end of file diff --git a/app/Components/Metamask/Connections.tsx b/app/Components/Metamask/Connections.tsx index 7324b70..25f5275 100644 --- a/app/Components/Metamask/Connections.tsx +++ b/app/Components/Metamask/Connections.tsx @@ -16,8 +16,6 @@ export const signMessage = async (message: string,provider: EIP1193Provider,user // For historical reasons, you must submit the message to sign in hex-encoded UTF-8. // This uses a Node.js-style buffer shim in the browser. - // const bytes = new TextEncoder().encode(String(publicKey)); - // const msg = '0x' + Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join(''); const msg = `0x${Buffer.from(message, "utf8").toString("hex")}` const sig = await provider.request({ method: "personal_sign", diff --git a/app/Components/Metamask/DiscoverWalletProviders.tsx b/app/Components/Metamask/DiscoverWalletProviders.tsx index b65052c..3cc7367 100644 --- a/app/Components/Metamask/DiscoverWalletProviders.tsx +++ b/app/Components/Metamask/DiscoverWalletProviders.tsx @@ -1,4 +1,3 @@ -// ...existing code... import React, { useState } from "react"; import { useSyncProviders } from "./useSyncProviders"; import useAuth from "~/hooks/useAuth"; @@ -122,5 +121,4 @@ export const DiscoverWalletProviders: React.FC = () => { ); }; -export default DiscoverWalletProviders; -// ...existing code... \ No newline at end of file +export default DiscoverWalletProviders; \ No newline at end of file diff --git a/app/Components/NavBar.tsx b/app/Components/NavBar.tsx index 876f982..44b9dbc 100644 --- a/app/Components/NavBar.tsx +++ b/app/Components/NavBar.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect } from "react"; import { NavLink } from "react-router"; import AppBar from '@mui/material/AppBar'; import Box from '@mui/material/Box'; @@ -7,13 +7,16 @@ import Button from "@mui/material/Button"; import LoginIcon from "@mui/icons-material/Login"; import { Typography, Menu, MenuItem, IconButton, Avatar, Tooltip } from "@mui/material"; import useAuth from "~/hooks/useAuth"; -import { signMessage } from './Metamask/Connections'; +import type { PaymentChannelInfo } from './Util'; +import { approveCLRTokenSpending, openPaymentChannel, closePaymentChannel, getPaymentChannelInfo } from './Contracts/Connections'; export default function ButtonAppBar() { const { auth, setAuth } = useAuth(); const account = auth?.accounts?.[0]; const isAuthed = Boolean(account); + const [paymentChannelInfo, setPaymentChannelInfo] = React.useState(null); + const [registered, setRegistered] = React.useState(false); const [anchorEl, setAnchorEl] = React.useState(null); const open = Boolean(anchorEl); const handleOpen = (e: React.MouseEvent) => setAnchorEl(e.currentTarget); @@ -27,14 +30,28 @@ export default function ButtonAppBar() { }; const register = async () => { - const sig = await signMessage("H8zfXnSclIQ/wLy7GSt7GNqa1utAi4Uvr7Dg3p9vdHQ=",auth.providerWithInfo.provider, auth.accounts[0]); - try { - const res_string = sig; - } catch (error) { - console.error('Error:', error); - } + const clr_deposit_amount = BigInt(10000) * BigInt(1e18); // 10000 CLR + await approveCLRTokenSpending(auth.providerWithInfo.provider, account!); + await openPaymentChannel(auth.providerWithInfo.provider, account!, clr_deposit_amount); + setRegistered(true); } + const deRegister = async () => { + await closePaymentChannel(auth.providerWithInfo.provider, account!); + setRegistered(false); + } + + useEffect(() => { + getPaymentChannelInfo(auth.providerWithInfo ? auth.providerWithInfo.provider : undefined, account!).then((info) => { + setPaymentChannelInfo(info); + if(registered !== info.isActive) { + setRegistered(info.isActive); + } + console.log("Payment Channel Info:", info); + }); + }, [account, auth.providerWithInfo?.provider,registered]); + + return ( @@ -78,8 +95,7 @@ export default function ButtonAppBar() { Login )} - - {} + {isAuthed && ( <> @@ -105,7 +121,7 @@ export default function ButtonAppBar() { transformOrigin={{ horizontal: 'right', vertical: 'top' }} anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }} > - Register + {(paymentChannelInfo && paymentChannelInfo.isActive) ? (DeRegister) : (Register)} Logout diff --git a/app/Components/ServerList.tsx b/app/Components/ServerList.tsx index 3298c30..6a22add 100644 --- a/app/Components/ServerList.tsx +++ b/app/Components/ServerList.tsx @@ -2,27 +2,41 @@ import * as React from 'react'; import ButtonGroup from '@mui/material/ButtonGroup'; import Button from '@mui/material/Button'; import Avatar from '@mui/material/Avatar'; -import { Grid, Card, CardContent, CardActions, Typography, Box, Chip, Dialog, DialogTitle, DialogContent, DialogActions, TextField } from "@mui/material"; +import { + Grid, + Card, + CardContent, + CardActions, + Typography, + Box, + Chip, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + IconButton, + Tooltip, + CircularProgress, +} from "@mui/material"; import ServerIcon from '@mui/icons-material/Dns'; import SignalCellularAltIcon from '@mui/icons-material/SignalCellularAlt'; import AttachMoneyIcon from '@mui/icons-material/AttachMoney'; import StarIcon from '@mui/icons-material/Star'; import Rating from '@mui/material/Rating'; +import RefreshIcon from '@mui/icons-material/Refresh'; import useAuth from '~/hooks/useAuth'; +import type { AccountInfo } from '~/context/AuthProvider'; import { signMessage } from './Metamask/Connections'; -import { downloadWireguardConfig } from './WireguardConfig'; +import { generateWireguardKeyPair, downloadWireguardConfig } from './WireguardConfig'; +import type { Node } from './Util'; +import { getActiveNodes } from './Contracts/Connections'; import axios from 'axios'; +import { useEffect } from 'react'; -interface Node { - ip: string; - traffic: number; - price : number; - rating : number; -} -function NodeItem({node}: {node: Node}) { + +function NodeItem({node, auth}: {node: Node, auth: AccountInfo}) { const port = 8080; - const { auth } = useAuth(); const [ratingOpen, setRatingOpen] = React.useState(false); const [ratingValue, setRatingValue] = React.useState(null); const [submittingRating, setSubmittingRating] = React.useState(false); @@ -31,13 +45,16 @@ function NodeItem({node}: {node: Node}) { // const publicKey = await getPublicKey(auth.providerWithInfo.provider, auth.accounts[0]); try { const iv = await axios.get(getUrl() + "/connect"); - console.log(iv.data); + const { privatekey: clientPrivateKey, publicKey: clientPublicKey } = generateWireguardKeyPair(); - const sig = await signMessage(iv.data + "H8zfXnSclIQ/wLy7GSt7GNqa1utAi4Uvr7Dg3p9vdHQ=",auth.providerWithInfo.provider, auth.accounts[0]); - const res_string = iv.data + '\n' + "H8zfXnSclIQ/wLy7GSt7GNqa1utAi4Uvr7Dg3p9vdHQ=" + '\n' + sig; + const sig = await signMessage(iv.data + clientPublicKey,auth.providerWithInfo.provider, auth.accounts[0]); + const res_string = iv.data + '\n' + clientPublicKey + '\n' + sig; let response = await axios.post(getUrl() + "/connect", res_string); + const clientCIDR = response.data.WireguardClientCIDR; + const serverPublicKey = response.data.WireguardServerPublicKey; + const dns = response.data.WireguardDNS; console.log(response.data); - downloadWireguardConfig("", "", "", "", node.ip, "51820", "0.0.0.0/0"); + downloadWireguardConfig(clientPrivateKey, serverPublicKey, clientCIDR, dns, node.ip, String(node.port), "0.0.0.0/0"); } catch (error) { console.error('Error:', error); } @@ -180,33 +197,69 @@ function NodeItem({node}: {node: Node}) { } export default function FolderList() { - const [nodes, setNodes] = React.useState([ - { ip: "57.158.82.48", traffic: 5, price: 10, rating: 3 }, - { ip: "8.210.33.199", traffic: 3, price: 15, rating: 4 }, - { ip: "45.77.12.5", traffic: 7, price: 20, rating: 5 }, - { ip: "203.120.45.78", traffic: 2, price: 8, rating: 2 }, - { ip: "91.189.88.25", traffic: 6, price: 12, rating: 4 }, - { ip: "132.148.9.201", traffic: 9, price: 18, rating: 5 }, - { ip: "60.12.180.99", traffic: 4, price: 14, rating: 3 }, - { ip: "199.59.243.100", traffic: 1, price: 6, rating: 1 }, - { ip: "34.216.77.3", traffic: 8, price: 22, rating: 5 }, - { ip: "185.199.108.153", traffic: 5, price: 11, rating: 4 }, - { ip: "13.107.21.200", traffic: 7, price: 16, rating: 4 }, - { ip: "216.58.214.14", traffic: 3, price: 9, rating: 2 }, - { ip: "104.21.44.33", traffic: 10, price: 25, rating: 5 }, - { ip: "47.90.12.201", traffic: 2, price: 7, rating: 1 }, - { ip: "23.45.67.89", traffic: 6, price: 13, rating: 3 }, - { ip: "192.0.2.123", traffic: 4, price: 17, rating: 4 }, - ]); + const { auth } = useAuth(); + const [nodes, setNodes] = React.useState([]); + const [loading, setLoading] = React.useState(false); + const fetchNodes = React.useCallback(async () => { + setLoading(true); + try { + const provider = auth?.providerWithInfo?.provider; + const fetchedNodes = await getActiveNodes(provider); + setNodes(fetchedNodes); + } catch (err) { + console.error("Failed to fetch nodes", err); + } finally { + setLoading(false); + } + }, [auth]); + + useEffect(() => { + fetchNodes(); + }, [fetchNodes]); + // const [nodes, setNodes] = React.useState([ + // { ip: "57.158.82.48", traffic: 5, price: 10, rating: 3 }, + // { ip: "8.210.33.199", traffic: 3, price: 15, rating: 4 }, + // { ip: "45.77.12.5", traffic: 7, price: 20, rating: 5 }, + // { ip: "203.120.45.78", traffic: 2, price: 8, rating: 2 }, + // { ip: "91.189.88.25", traffic: 6, price: 12, rating: 4 }, + // { ip: "132.148.9.201", traffic: 9, price: 18, rating: 5 }, + // { ip: "60.12.180.99", traffic: 4, price: 14, rating: 3 }, + // { ip: "199.59.243.100", traffic: 1, price: 6, rating: 1 }, + // { ip: "34.216.77.3", traffic: 8, price: 22, rating: 5 }, + // { ip: "185.199.108.153", traffic: 5, price: 11, rating: 4 }, + // { ip: "13.107.21.200", traffic: 7, price: 16, rating: 4 }, + // { ip: "216.58.214.14", traffic: 3, price: 9, rating: 2 }, + // { ip: "104.21.44.33", traffic: 10, price: 25, rating: 5 }, + // { ip: "47.90.12.201", traffic: 2, price: 7, rating: 1 }, + // { ip: "23.45.67.89", traffic: 6, price: 13, rating: 3 }, + // { ip: "192.0.2.123", traffic: 4, price: 17, rating: 4 }, + // ]); return ( - - Available Servers - + + + Available Servers + + + + + + + {loading ? : } + + + + + {nodes.map((node, index) => ( - + ))} diff --git a/app/Components/Util.tsx b/app/Components/Util.tsx new file mode 100644 index 0000000..ec3f248 --- /dev/null +++ b/app/Components/Util.tsx @@ -0,0 +1,23 @@ + +export interface Node { + ip: string; + port: number; + traffic: number; + price : number; + rating : number; +} + +export interface NodeInfo { + ipAddress: string; + port: number; + pricePerMinute : BigInt; + reputationScore : BigInt; + totalMinutesServed : BigInt; + totalEarnings : BigInt; +} + +export interface PaymentChannelInfo { + balance: BigInt; + nonce: BigInt; + isActive: boolean; +} \ No newline at end of file diff --git a/app/Components/WireguardConfig.tsx b/app/Components/WireguardConfig.tsx index 638afcf..c01eea0 100644 --- a/app/Components/WireguardConfig.tsx +++ b/app/Components/WireguardConfig.tsx @@ -1,13 +1,57 @@ +// import { utils } from 'noble-ed25519'; +import nacl from "tweetnacl"; +function bytesToBase64(bytes: Uint8Array) { + let binary = ''; + for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]); + return btoa(binary); +} + +function base64ToBytes(base64: string): Uint8Array { + // support URL-safe base64 + base64 = base64.replace(/-/g, "+").replace(/_/g, "/"); + // pad to multiple of 4 + const pad = base64.length % 4; + if (pad) base64 += "=".repeat(4 - pad); + + const binary = atob(base64); + const len = binary.length; + const bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) { + bytes[i] = binary.charCodeAt(i); + } + return bytes; +} + +const generateWireGuardPrivateKey = () => { + // const privateKeyBytes = utils.randomPrivateKey(); + // const privateKeyBase64 = bytesToBase64(privateKeyBytes); + const privateKey = nacl.randomBytes(32); + + return bytesToBase64(privateKey); +} + +const getWireguardPublicKey = (privateKey: string) => { + const privateKeyBytes = base64ToBytes(privateKey); + const publicKey = nacl.scalarMult.base(privateKeyBytes); + return bytesToBase64(publicKey); +} + +export const generateWireguardKeyPair = () => { + const privatekey = generateWireGuardPrivateKey(); + const publicKey = getWireguardPublicKey(privatekey); + return { privatekey, publicKey }; +} + +export const downloadWireguardConfig = async (clientPrivateKey: string, serverPubcliKey: string ,localCIDR: string, dns: string, peerIp: string, peerPort: string, allowedIPs: string) => { -export const downloadWireguardConfig = (privateKey: string, publicKey: string, localCIDR: string, dns: string, peerIp: string, peerPort: string, allowedIPs: string) => { const content = `[Interface]`+'\n'+ - `PrivateKey = ${privateKey}`+'\n'+ + `PrivateKey = ${clientPrivateKey}`+'\n'+ `Address = ${localCIDR}`+'\n'+ `DNS = ${dns}`+'\n'+ '\n'+ `[Peer]`+'\n'+ - `PublicKey = ${publicKey}`+'\n'+ + `PublicKey = ${serverPubcliKey}`+'\n'+ `AllowedIPs = ${allowedIPs}`+'\n'+ `Endpoint = ${peerIp}:${peerPort}`; diff --git a/package-lock.json b/package-lock.json index 4f7276c..f938e75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,9 +14,11 @@ "axios": "^1.13.2", "buffer": "^6.0.3", "isbot": "^5.1.31", + "noble-ed25519": "^1.2.6", "react": "^19.1.1", "react-dom": "^19.1.1", "react-router": "^7.9.2", + "tweetnacl": "^1.0.3", "web3": "^4.16.0" }, "devDependencies": { @@ -4513,6 +4515,13 @@ "node": ">= 0.6" } }, + "node_modules/noble-ed25519": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/noble-ed25519/-/noble-ed25519-1.2.6.tgz", + "integrity": "sha512-zfnWqg9FVMp8CnzUpAjbt1nDXpDjCvxYiCXdnW1mY8zQHw/6twUlkFm14VPdojVzc0kcd+i9zT79+26GcNbsuQ==", + "deprecated": "Switch to namespaced @noble/ed25519 for security and feature updates", + "license": "MIT" + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -5676,6 +5685,12 @@ } } }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "license": "Unlicense" + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", diff --git a/package.json b/package.json index f0f6854..825c5bc 100644 --- a/package.json +++ b/package.json @@ -18,9 +18,11 @@ "axios": "^1.13.2", "buffer": "^6.0.3", "isbot": "^5.1.31", + "noble-ed25519": "^1.2.6", "react": "^19.1.1", "react-dom": "^19.1.1", "react-router": "^7.9.2", + "tweetnacl": "^1.0.3", "web3": "^4.16.0" }, "devDependencies": {