Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Relay bounty #2

Merged
merged 3 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ There are three main parts to this project:

<!-- Make a table with links to each folder -->

| Folder | Description |
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| [Contracts](./packages/foundry) | This is where the smart contracts live |
| [Frontend](./packages/nextjs) | This is the frontend of the app built using Scaffold-Eth 2 |
| [Lightning server](./packages/server) | This is the lightning service provider websocket who is paying the invoices, this connects to your lightning node (is not one itself) |
| Folder | Description |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| [Contracts](./packages/foundry) | This is where the smart contracts live |
| [Frontend](./packages/nextjs) | This is the frontend of the app built using Scaffold-Eth 2 |
| [Lightning server](./packages/server) | This is the lightning service provider websocket who is paying the invoices, this connects to your lightning node (is not one itself) |
| [Relay server](./packages/relay-server) | This is the server that tries to claim the HTLC on behalf of the user who has no gas to recieve his payment |

## Payment Flow

Expand Down
57 changes: 57 additions & 0 deletions packages/nextjs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Next.js Webapp for Lightning EVM Bridge

This webapp is the front-end component of the Lightning EVM Bridge project, designed to interact seamlessly with the blockchain and the Lightning Network. Built with Next.js, it provides a user-friendly interface to initiate and monitor transactions across EVM chains and the Lightning Network through the lightning server.

## Setup

### Requirements

- Node.js (v18 LTS)
- Yarn (v1 or v2+)

### Installation

1. **Clone the repository if you haven't already:**

```bash
git clone https://github.com/diyahir/lightning-dapp.git
cd lightning-dapp/packages/nextjs
```

2. **Install dependencies:**

```bash
yarn install
```

### Environment Configuration

1. **Set up the `.env` file:**

Copy the `sample.env` or `sample.live.env` file depending on your setup preference (local or using pre-configured remote services) and rename it to `.env`. Fill in the necessary environment variables:

## Running Locally

To start the webapp locally, you can run:

```bash
yarn dev
```

This command starts the Next.js development server on [http://localhost:3000](http://localhost:3000). Open your browser to this URL to view and interact with the webapp.

## Building and Running in Production

1. **Build the application:**

```bash
yarn build
```

2. **Start the production server:**

```bash
yarn start
```

This compiles the application to optimized production code and runs it on the default Next.js production server.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { deployedContracts } from "@lightning-evm-bridge/shared";
import fs from "fs";
import path from "path";
import { foundry } from "viem/chains";
import { AddressComponent } from "~~/app/blockexplorer/_components/AddressComponent";
import deployedContracts from "~~/contracts/deployedContracts";
import { GenericContractsDeclaration } from "~~/utils/scaffold-eth/contract";

type PageProps = {
Expand Down
2 changes: 1 addition & 1 deletion packages/nextjs/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import React from "react";
import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { ServerStatus } from "shared";
import { ServerStatus } from "@lightning-evm-bridge/shared";
import { FaucetButton, RainbowKitCustomConnectButton } from "~~/components/scaffold-eth";
import { useLightningApp } from "~~/hooks/LightningProvider";

Expand Down
6 changes: 3 additions & 3 deletions packages/nextjs/components/HistoryTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const HistoryTable = () => {
const { transactions, addTransaction, toastSuccess, toastError } = useLightningApp();
const [expandedRow, setExpandedRow] = useState<number | null>(null); // State to manage expanded row index
const { data: walletClient } = useWalletClient();
const { data: yourContract } = useScaffoldContract({
const { data: htlcContract } = useScaffoldContract({
contractName: "HashedTimelock",
walletClient,
});
Expand Down Expand Up @@ -46,8 +46,8 @@ export const HistoryTable = () => {
if (transaction.hashLockTimestamp > Date.now() / 1000) {
return;
}
if (!yourContract) return;
yourContract.write
if (!htlcContract) return;
htlcContract.write
.refund([transaction.contractId as `0x${string}`], {})
.then(tx => {
console.log(tx);
Expand Down
191 changes: 53 additions & 138 deletions packages/nextjs/components/RecieveModalPopup.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
"use client";

import { useEffect, useRef, useState } from "react";
import { AddressInput, IntegerInput } from "./scaffold-eth";
import { Step1 } from "./recieve-steps/Step1";
import { Step2 } from "./recieve-steps/Step2";
import { Step3 } from "./recieve-steps/Step3";
import {
InitiationRequest,
KIND,
RelayRequest,
RelayResponse,
parseContractDetails,
} from "@lightning-evm-bridge/shared";
import { waitForTransaction } from "@wagmi/core";
import { PaymentRequestObject, decode } from "bolt11";
import axios from "axios";
import { randomBytes } from "crypto";
import { sha256 } from "js-sha256";
import QRCode from "qrcode.react";
import { InitiationRequest, KIND, parseContractDetails } from "shared";
import { useWalletClient } from "wagmi";
import { useLightningApp } from "~~/hooks/LightningProvider";
import { useScaffoldContract } from "~~/hooks/scaffold-eth";
Expand Down Expand Up @@ -44,6 +51,29 @@ function RecieveModal({ isOpen, onClose }: RecieveModalProps) {
onClose();
}

function relayContractAndPreimage() {
// send a request to the relayer to get the contract details
const msg: RelayRequest = {
kind: KIND.RELAY_REQUEST,
contractId: recieveContractId,
preimage: hashLock?.secret ?? "",
};

axios.post("http://localhost:3004/relay", msg).then(response => {
console.log(response);
const msg: RelayResponse = response.data;
if (msg.status === "success" && msg.txHash) {
setActiveStep(3);
setTxHash(msg.txHash);
console.log("Relay Response", msg);
} else {
toastError("Failed to relay contract and preimage");
}
});

return;
}

const { data: htlcContract } = useScaffoldContract({
contractName: "HashedTimelock",
walletClient,
Expand All @@ -56,10 +86,20 @@ function RecieveModal({ isOpen, onClose }: RecieveModalProps) {
}, [walletClient?.account.address]);

useEffect(() => {
if (recieveContractId === "") {
return;
}

const retryDelay = 5000; // Delay time in milliseconds
const maxRetries = 3; // Maximum number of retries

const fetchContractDetails = async (retries = maxRetries) => {
relayContractAndPreimage();
if (retryDelay > 0) {
return;
}
// send a request to the relayer to get the contract details

if (recieveContractId === "" || !htlcContract || !hashLock) {
return;
}
Expand Down Expand Up @@ -121,7 +161,6 @@ function RecieveModal({ isOpen, onClose }: RecieveModalProps) {
function onClickQRCode() {
navigator.clipboard.writeText(invoice);
toastSuccess("Lightning Invoice Copied");
// setActiveStep(activeStep + 1);
}

function onClickContinue() {
Expand Down Expand Up @@ -166,14 +205,12 @@ function RecieveModal({ isOpen, onClose }: RecieveModalProps) {
<ol className="flex items-center w-full p-3 space-x-2 text-sm font-medium text-center rounded-lg shadow-sm dark:text-gray-400 sm:text-base dark:bg-gray-800 dark:border-gray-700 sm:p-4 sm:space-x-4 rtl:space-x-reverse justify-between">
<li
className={`flex items-center text-sm text-left ${
activeStep >= 1 ? "text-orange-600 dark:text-orange-500" : "text-gray-500 dark:text-gray-400"
activeStep >= 1 ? "text-blue-400 dark:text-blue-400" : "text-gray-500 dark:text-gray-400"
}`}
>
<span
className={`flex items-center justify-center w-5 h-5 me-2 text-xs border ${
activeStep >= 1
? "border-orange-600 dark:border-orange-500"
: "border-gray-500 dark:border-gray-400"
activeStep >= 1 ? "border-blue-400 dark:border-blue-300" : "border-gray-500 dark:border-gray-400"
} rounded-full shrink-0`}
>
1
Expand All @@ -197,14 +234,12 @@ function RecieveModal({ isOpen, onClose }: RecieveModalProps) {
</li>
<li
className={`flex items-center text-sm text-left ${
activeStep >= 2 ? "text-orange-600 dark:text-orange-500" : "text-gray-500 dark:text-gray-400"
activeStep >= 2 ? "text-blue-400 dark:text-blue-300" : "text-gray-500 dark:text-gray-400"
}`}
>
<span
className={`flex items-center justify-center w-5 h-5 me-2 text-xs border ${
activeStep >= 2
? "border-orange-600 dark:border-orange-500"
: "border-gray-500 dark:border-gray-400"
activeStep >= 2 ? "border-blue-400 dark:border-blue-300" : "border-gray-500 dark:border-gray-400"
} rounded-full shrink-0`}
>
2
Expand All @@ -228,14 +263,12 @@ function RecieveModal({ isOpen, onClose }: RecieveModalProps) {
</li>
<li
className={`flex items-center text-sm text-left ${
activeStep >= 3 ? "text-orange-600 dark:text-orange-500" : "text-gray-500 dark:text-gray-400"
activeStep >= 3 ? "text-blue-400 dark:text-blue-300" : "text-gray-500 dark:text-gray-400"
}`}
>
<span
className={`flex items-center justify-center w-5 h-5 me-2 text-xs border ${
activeStep >= 3
? "border-orange-600 dark:border-orange-500"
: "border-gray-500 dark:border-gray-400"
activeStep >= 3 ? "border-blue-400 dark:border-blue-300" : "border-gray-500 dark:border-gray-400"
} rounded-full shrink-0`}
>
3
Expand All @@ -245,7 +278,7 @@ function RecieveModal({ isOpen, onClose }: RecieveModalProps) {
</ol>

{activeStep === 1 &&
step1({
Step1({
amount,
invoice,
recipientAddress,
Expand All @@ -255,9 +288,9 @@ function RecieveModal({ isOpen, onClose }: RecieveModalProps) {
onClickQRCode,
})}

{activeStep === 2 && step2({ invoice, onClickQRCode })}
{activeStep === 2 && Step2({ invoice, onClickQRCode })}

{activeStep === 3 && step3({ txHash })}
{activeStep === 3 && Step3({ txHash })}
</div>
</div>
</div>
Expand All @@ -266,122 +299,4 @@ function RecieveModal({ isOpen, onClose }: RecieveModalProps) {
);
}

export function step1({
amount,
invoice,
recipientAddress,
setRecipientAddress,
setAmount,
onClickContinue,
onClickQRCode,
}: {
amount: bigint;
invoice: string;
recipientAddress: string;
setRecipientAddress: (val: string) => void;
setAmount: (val: bigint) => void;
onClickContinue: () => void;
onClickQRCode: () => void;
}) {
let paymentRequest: PaymentRequestObject = {
satoshis: Number(0),
tags: [{ tagName: "payment_hash", data: "abc123" }],
};
if (invoice !== "") {
paymentRequest = decode(invoice);
}

function isGenerateQRDisabled(): boolean {
return amount === BigInt(0) || invoice !== "";
}
return (
<div className="flex flex-col text-start w-full gap-2">
<div className="flex-col">
<span className="text-sm text-gray-500">Recipient Address</span>
<AddressInput
placeholder="0x123...321"
value={recipientAddress}
onChange={newAddress => {
setRecipientAddress(newAddress);
}}
disabled={invoice !== ""}
/>
</div>
<div className="flex-col">
<span className="text-sm text-gray-500">Amount (sats)</span>
<IntegerInput
value={amount}
onChange={val => setAmount(BigInt(val))}
disableMultiplyBy1e18
disabled={invoice !== ""}
/>
</div>
<button
className="btn btn-secondary rounded-none w-full"
disabled={isGenerateQRDisabled()}
onClick={() => onClickContinue()}
>
Generate Service Fee Invoice
</button>
{invoice && (
<div className="flex flex-col w-full gap-2 h-full">
<button
className="btn btn-neutral rounded-none text-center w-full"
onClick={() => {
onClickQRCode();
}}
>
Copy Invoice
</button>
<div className="flex flex-col items-center">
<QRCode size={250} value={invoice} className="" />
</div>
<div className="flex flex-col">
<span className="text-center text-gray-500">Service Fee: {paymentRequest.satoshis} sats</span>
</div>
</div>
)}
</div>
);
}

function step2({ invoice, onClickQRCode }: { invoice: string; onClickQRCode: () => void }) {
let paymentRequest: PaymentRequestObject = {
satoshis: Number(0),
tags: [{ tagName: "payment_hash", data: "abc123" }],
};
if (invoice !== "") {
paymentRequest = decode(invoice);
}
return (
<div className="flex flex-col text-start cursor-pointer w-full" onClick={() => onClickQRCode()}>
&nbsp;
<div className="flex flex-col self-center gap-2">
<QRCode size={250} value={invoice} />
<button className="btn btn-neutral rounded-none w-full text-center" onClick={() => onClickQRCode()}>
Copy Invoice
</button>
<div className="flex flex-col">
<span className="text-center text-gray-500">Invoice: {paymentRequest.satoshis} sats</span>
</div>
</div>
</div>
);
}

function step3({ txHash }: { txHash: string }) {
return (
<div className="flex flex-col text-start w-full gap-2">
<a
href={`https://3xpl.com/botanix/transaction/${txHash}`}
target="_blank"
rel="noreferrer"
className="btn btn-secondary rounded-none w-full"
>
View Transaction
</a>
</div>
);
}

export default RecieveModal;
Loading
Loading