Skip to content

Commit

Permalink
Merge pull request #2 from diyahir/relay-bounty
Browse files Browse the repository at this point in the history
Relay bounty
  • Loading branch information
diyahir authored May 8, 2024
2 parents b14f304 + 1cb695c commit 2823733
Show file tree
Hide file tree
Showing 37 changed files with 733 additions and 889 deletions.
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

0 comments on commit 2823733

Please sign in to comment.