successfully generates the wireguard config file

This commit is contained in:
Jincheng Lu 2025-12-05 05:36:14 +08:00
parent cf3d999a2e
commit c4a50e8b3b
12 changed files with 820 additions and 64 deletions

View File

@ -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 (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" aria-hidden>
<circle cx="12" cy="12" r="12" fill={WIREGUARD_COLOR} />
<path d="M6 12c0-3.314 2.686-6 6-6s6 2.686 6 6-2.686 6-6 6S6 15.314 6 12z" fill="#fff" opacity="0.15" />
<path d="M8.5 11.5c1-2 3-3 5-3s4 1 5 3" stroke="#fff" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}
export default function About() {
const repo = "https://github.com/cochrane2063/MSBD5017-Depin-WebClient";
const wireguard = "https://www.wireguard.com/install/";
return (
<Box sx={{ display: "flex", justifyContent: "center", p: 3 }}>
@ -32,6 +46,59 @@ export default function About() {
</Stack>
</CardContent>
</Card>
<Card
sx={{
width: 420,
maxWidth: "100%",
borderLeft: `6px solid ${WIREGUARD_COLOR}`,
background: "linear-gradient(180deg, rgba(122,193,67,0.04), transparent 60%)",
}}
>
<CardContent>
<Stack direction="row" spacing={2} alignItems="center" sx={{ mb: 1 }}>
<Avatar sx={{ bgcolor: "transparent" }}>
<WireGuardLogo />
</Avatar>
<Typography variant="h5" sx={{ color: WIREGUARD_COLOR, fontWeight: 700 }}>
Download Wireguard Client
</Typography>
</Stack>
<Typography variant="body1" color="text.secondary" paragraph>
Wireguard client for the MSBD5017 Depin project.
</Typography>
<Typography variant="body2" color="text.secondary" paragraph>
This project uses the Wireguard VPN protocol. You can download the Wireguard client from the official website.
</Typography>
<Stack direction="row" spacing={2} alignItems="center">
<Link href={wireguard} target="_blank" rel="noopener noreferrer" underline="none">
<Button
startIcon={<GetAppIcon />}
variant="contained"
sx={{
bgcolor: WIREGUARD_COLOR,
"&:hover": { bgcolor: "#65a836" },
}}
>
Download WireGuard
</Button>
</Link>
<Button
variant="outlined"
href="https://www.wireguard.com/quickstart/"
target="_blank"
rel="noopener noreferrer"
sx={{ borderColor: WIREGUARD_COLOR, color: WIREGUARD_COLOR }}
>
Quickstart Guide
</Button>
</Stack>
</CardContent>
</Card>
</Box>
);
}

View File

@ -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"
}
]

View File

@ -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": [
{

View File

@ -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<Node[]> => {
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<PaymentChannelInfo> {
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,
};
}

View File

@ -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",

View File

@ -1,4 +1,3 @@
// ...existing code...
import React, { useState } from "react";
import { useSyncProviders } from "./useSyncProviders";
import useAuth from "~/hooks/useAuth";
@ -123,4 +122,3 @@ export const DiscoverWalletProviders: React.FC = () => {
};
export default DiscoverWalletProviders;
// ...existing code...

View File

@ -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<PaymentChannelInfo | null>(null);
const [registered, setRegistered] = React.useState<boolean>(false);
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
const handleOpen = (e: React.MouseEvent<HTMLElement>) => 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 (
<Box sx={{ flexGrow: 1 }}>
<AppBar position="static">
@ -79,7 +96,6 @@ export default function ButtonAppBar() {
</Button>
)}
{}
{isAuthed && (
<>
<Tooltip title={account ? `Account: ${account}` : "Account"}>
@ -105,7 +121,7 @@ export default function ButtonAppBar() {
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
>
<MenuItem onClick={register}>Register</MenuItem>
{(paymentChannelInfo && paymentChannelInfo.isActive) ? (<MenuItem onClick={deRegister}>DeRegister</MenuItem>) : (<MenuItem onClick={register}>Register</MenuItem>)}
<MenuItem onClick={handleLogout}>Logout</MenuItem>
</Menu>
</>

View File

@ -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<number | null>(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<Node[]>([
{ 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<Node[]>([]);
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<Node[]>([
// { 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 (
<Box sx={{ flexGrow: 1, p: 3 }}>
<Typography variant="h4" gutterBottom component="div" sx={{ mb: 4, fontWeight: 'bold' }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 3 }}>
<Typography variant="h4" component="div" sx={{ fontWeight: 'bold' }}>
Available Servers
</Typography>
<Box>
<Tooltip title="Refresh servers">
<span>
<IconButton
onClick={fetchNodes}
disabled={loading || auth?.providerWithInfo === undefined}
color="primary"
aria-label="refresh servers"
>
{loading ? <CircularProgress size={20} /> : <RefreshIcon />}
</IconButton>
</span>
</Tooltip>
</Box>
</Box>
<Grid container spacing={3}>
{nodes.map((node, index) => (
<NodeItem key={index} node={node} />
<NodeItem key={index} node={node} auth={auth}/>
))}
</Grid>
</Box>

23
app/Components/Util.tsx Normal file
View File

@ -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;
}

View File

@ -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}`;

15
package-lock.json generated
View File

@ -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",

View File

@ -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": {