Skip to content

Commit

Permalink
Renaming for clarity, Server status, Mock Server
Browse files Browse the repository at this point in the history
Renaming some env vars for clarity

Adding a server status

Adding a Mock Server for if you don't have an LND node running
  • Loading branch information
diyahir committed Mar 24, 2024
1 parent 586a1a2 commit 6ecd91b
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 25 deletions.
30 changes: 28 additions & 2 deletions packages/nextjs/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import Link from "next/link";
import { usePathname } from "next/navigation";
import { FaucetButton, RainbowKitCustomConnectButton } from "~~/components/scaffold-eth";
import { useLightningApp } from "~~/hooks/LightningProvider";
import { ServerStatus } from "~~/types/utils";
import { get } from "http";

type HeaderMenuLink = {
label: string;
Expand Down Expand Up @@ -55,7 +57,28 @@ export const HeaderMenuLinks = () => {
* Site header
*/
export const Header = () => {
const { isWebSocketConnected, reconnect } = useLightningApp();
const { isWebSocketConnected, reconnect, lspStatus } = useLightningApp();
function getColorFromStatus(status: ServerStatus) {
switch (status) {
case ServerStatus.ACTIVE:
return "bg-success";
case ServerStatus.INACTIVE:
return "bg-error";
case ServerStatus.MOCK:
return "bg-warning";
}
}

function getTooltipFromStatus(status: ServerStatus) {
switch (status) {
case ServerStatus.ACTIVE:
return "Server is active";
case ServerStatus.INACTIVE:
return "Server is inactive";
case ServerStatus.MOCK:
return "Server is in mock mode, all invoice payments will be mocked by the LSP";
}
}
return (
<div className="sticky font-mono lg:static top-0 navbar bg-base-100 min-h-0 flex-shrink-0 justify-between z-20 px-0 sm:px-2 ">
<div className="navbar-start w-auto lg:w-1/2">
Expand All @@ -70,8 +93,11 @@ export const Header = () => {
onClick={() => {
if (!isWebSocketConnected) reconnect();
}}

>
<div className={`${isWebSocketConnected ? "bg-success" : "bg-error"} rounded-full w-2 h-2 self-center`}></div>
<div className={`tooltip tooltip-bottom ${getColorFromStatus(lspStatus)} rounded-full w-2 h-2 self-center`}
data-tip={getTooltipFromStatus(lspStatus)}
></div>
{isWebSocketConnected ? "LSP Connected" : "LSP Disconnected"}
</button>
&nbsp;
Expand Down
2 changes: 1 addition & 1 deletion packages/nextjs/components/SendModalPopup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ function SendModal({ isOpen, onClose }: SendModalProps) {
yourContract.write
.newContract(
[
"0xf89335a26933d8Dd6193fD91cAB4e1466e5198Bf",
process.env.LSP_ADDRESS ?? "0xf89335a26933d8Dd6193fD91cAB4e1466e5198Bf",
lnInvoiceRef.current.paymentHash,
BigInt(getMinTimelock(lnInvoiceRef.current.timeExpireDate)),
],
Expand Down
9 changes: 6 additions & 3 deletions packages/nextjs/hooks/LightningProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useNativeCurrencyPrice, useScaffoldEventSubscriber } from "./scaffold-e
import { useWebSocket } from "./useWebSocket";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import { InvoiceRequest, InvoiceResponse } from "~~/types/utils";
import { InvoiceRequest, InvoiceResponse, ServerStatus } from "~~/types/utils";

// Define the types for your historical transactions and context
export type HistoricalTransaction = {
Expand All @@ -26,6 +26,7 @@ export type LightningAppContextType = {
price: number;
toastSuccess: (message: string) => void;
toastError: (message: string) => void;
lspStatus: ServerStatus;
};

// Create the context
Expand All @@ -42,7 +43,7 @@ export const LightningProvider = ({ children }: { children: React.ReactNode }) =
setTransactionsState(transactions);
};
console.log(process.env.WEBSOCKET_URL ?? "ws://localhost:3003");
const { sendMessage, isWebSocketConnected, data, reconnect } = useWebSocket(
const { sendMessage, isWebSocketConnected, data, reconnect, status } = useWebSocket(
process.env.WEBSOCKET_URL ?? "ws://localhost:3003",
);

Expand Down Expand Up @@ -80,6 +81,7 @@ export const LightningProvider = ({ children }: { children: React.ReactNode }) =
useEffect(() => {
const lastTransaction = transactionRef.current[0];
if (invoiceContractIdPair.length === 0) return;
if (lastTransaction === undefined) return;
const [contractId, lnInvoice] = invoiceContractIdPair;
addTransaction({
status: "pending",
Expand All @@ -95,7 +97,7 @@ export const LightningProvider = ({ children }: { children: React.ReactNode }) =
useEffect(() => {
if (data === null) return;
const lastTransaction = transactionRef.current[0];
console.log("Last Transaction", lastTransaction);
if (lastTransaction === undefined) return;

if (data?.status === "success") {
addTransaction({
Expand Down Expand Up @@ -154,6 +156,7 @@ export const LightningProvider = ({ children }: { children: React.ReactNode }) =
reconnect,
toastError,
toastSuccess,
lspStatus: status,
}}
>
{children}
Expand Down
13 changes: 8 additions & 5 deletions packages/nextjs/hooks/useWebSocket.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { InvoiceRequest, InvoiceResponse } from "~~/types/utils";
import { InvoiceRequest, InvoiceResponse, ServerStatus } from "~~/types/utils";

export const useWebSocket = (url: string) => {
const socket = useRef<WebSocket | null>(null);
const [status, setStatus] =useState<ServerStatus>(ServerStatus.INACTIVE);
const [data, setData] = useState<InvoiceResponse | null>(null);
const [error, setError] = useState<Event | null>(null);
const [isWebSocketConnected, setIsWebSocketConnected] = useState<boolean>(false);
Expand Down Expand Up @@ -40,10 +41,12 @@ export const useWebSocket = (url: string) => {
socket.current.onerror = event => setError(event);
socket.current.onmessage = event => {
try {
if (event.data === "Server online: You are now connected!") {
return;
}

const responseData: InvoiceResponse = JSON.parse(event.data);
if (responseData.status) {
setStatus(responseData.status as ServerStatus);
return;
}
setData(responseData);
} catch (err) {
console.error("Failed to parse message", err);
Expand Down Expand Up @@ -74,5 +77,5 @@ export const useWebSocket = (url: string) => {
[isWebSocketConnected],
);

return { sendMessage, data, error, isWebSocketConnected, reconnect };
return { sendMessage, data, error, isWebSocketConnected, reconnect, status };
};
2 changes: 2 additions & 0 deletions packages/nextjs/.env.example → packages/nextjs/sample.env
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=""
NEXT_PUBLIC_VERCEL_URL="http://localhost:3000"
NEXTAUTH_URL="http://localhost:3000"
PORT=3000
LSP_ADDRESS="0xf89335a26933d8Dd6193fD91cAB4e1466e5198Bf"
WEBSOCKET_URL="ws://localhost:3003"
18 changes: 18 additions & 0 deletions packages/nextjs/sample.live.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Template for NextJS environment variables.

# For local development, copy this file, rename it to .env.local, and fill in the values.
# When deploying live, you'll need to store the vars in Vercel/System config.

# If not set, we provide default values (check `scaffold.config.ts`) so developers can start prototyping out of the box,
# but we recommend getting your own API Keys for Production Apps.

# To access the values stored in this env file you can use: process.env.VARIABLENAME
# You'll need to prefix the variables names with NEXT_PUBLIC_ if you want to access them on the client side.
# More info: https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables
NEXT_PUBLIC_ALCHEMY_API_KEY=""
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=""
NEXT_PUBLIC_VERCEL_URL="http://localhost:3000"
NEXTAUTH_URL="http://localhost:3000"
PORT=3000
LSP_ADDRESS="0xf89335a26933d8Dd6193fD91cAB4e1466e5198Bf"
WEBSOCKET_URL="wss://botanix.diyahir.com/websocket"
6 changes: 6 additions & 0 deletions packages/nextjs/types/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,9 @@ export interface InvoiceResponse {
status: "success" | "error";
message: string;
}

export enum ServerStatus {
ACTIVE = "ACTIVE",
INACTIVE = "INACTIVE",
MOCK = "MOCK",
}
17 changes: 11 additions & 6 deletions packages/server/sample.env
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
CERT=''
MACAROON=''
SOCKET=''
ETH_PROVIDER_URL=""
CHAIN_ID=""
PRIVATE_KEY=""
### LND Node Info ###
LND_MACAROON=""
LND_SOCKET="arandomnode.voltageapp.io:10009"
### You may need to use port 10009 if running on voltage

### Chain Info ###
RPC_URL="https://node.botanixlabs.dev"
CHAIN_ID="3636"

### LSP Private Ket ###
LSP_PRIVATE_KEY=""
36 changes: 28 additions & 8 deletions packages/server/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import { providerConfig } from "./provider.config";
dotenv.config();

// Verify environment variables
const { PORT, MACAROON, SOCKET, ETH_PROVIDER_URL, PRIVATE_KEY, CHAIN_ID } =
const { PORT, LND_MACAROON, LND_SOCKET, RPC_URL, LSP_PRIVATE_KEY, CHAIN_ID } =
process.env;
if (!MACAROON || !SOCKET || !ETH_PROVIDER_URL || !PRIVATE_KEY || !CHAIN_ID) {
if ( !RPC_URL || !LSP_PRIVATE_KEY || !CHAIN_ID) {
console.error("Missing environment variables");
process.exit(1);
}
Expand All @@ -22,11 +22,12 @@ if (!MACAROON || !SOCKET || !ETH_PROVIDER_URL || !PRIVATE_KEY || !CHAIN_ID) {
const wss = new WebSocket.Server({ port: Number(PORT) || 3003 });
const { lnd } = lnService.authenticatedLndGrpc({
cert: "",
macaroon: MACAROON,
socket: SOCKET,
macaroon: LND_MACAROON,
socket: LND_SOCKET,
});
const provider = new ethers.JsonRpcProvider(ETH_PROVIDER_URL);
const signer = new ethers.Wallet(PRIVATE_KEY, provider);

const provider = new ethers.JsonRpcProvider(RPC_URL);
const signer = new ethers.Wallet(LSP_PRIVATE_KEY, provider);
const htlcContractInfo = deployedContracts[CHAIN_ID]?.HashedTimelock;
const htlcContract = new ethers.Contract(
htlcContractInfo.address,
Expand All @@ -41,12 +42,14 @@ export type CachedPayment = {
let cachedPayments: CachedPayment[] = [];
// ideally this should be stored in a database, but for the sake of simplicity we are using an in-memory cache

console.log(`RPC Provider is running on ${ETH_PROVIDER_URL}`);
console.log(`RPC Provider is running on ${RPC_URL}`);
console.log(`WebSocket server is running on ws://localhost:${PORT || 3003}`);
console.log(`LSP Address: ${signer.address}`);

wss.on("connection", (ws: WebSocket) => {
const serverStatus = process.env.LND_MACAROON ? "ACTIVE" : "MOCK";
console.log("Client connected");
ws.send("Server online: You are now connected!");
ws.send(JSON.stringify({ status: serverStatus, message: "Connected to server" }));

ws.on("message", async (message: string) => {
console.log("Received message:", message);
Expand All @@ -72,6 +75,23 @@ async function processInvoiceRequest(request: InvoiceRequest, ws: WebSocket) {

console.log("Invoice Request Received:", request);

// Check if LND_MACAROON and LND_SOCKET are empty to simulate mock mode
if (!process.env.LND_MACAROON && !process.env.LND_SOCKET) {
console.log("Mock Server Mode: Simulating payment success");

// Simulate processing delay
await new Promise(resolve => setTimeout(resolve, 1000)); // 1 second delay for realism

// Directly respond with a simulated success message
ws.send(JSON.stringify({
status: "success",
message: "Invoice paid successfully in mock mode.",
}));

// Exit early since we're in mock mode
return;
}

try {
const options = { gasPrice: ethers.parseUnits("0.001", "gwei") };

Expand Down
6 changes: 6 additions & 0 deletions packages/server/src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,10 @@ export type ContractDetails = {
preimage: string;
};

enum ServerStatus {
ACTIVE = "ACTIVE",
INACTIVE = "INACTIVE",
MOCK = "MOCK",
}

export const GWEIPERSAT = 1e10;

0 comments on commit 6ecd91b

Please sign in to comment.