diff --git a/README.md b/README.md index 1114cef..33a2ab5 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,12 @@ There are three main parts to this project: -| 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 diff --git a/packages/nextjs/README.md b/packages/nextjs/README.md new file mode 100644 index 0000000..e1a31b0 --- /dev/null +++ b/packages/nextjs/README.md @@ -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. diff --git a/packages/nextjs/app/blockexplorer/address/[address]/page.tsx b/packages/nextjs/app/blockexplorer/address/[address]/page.tsx index 2f8dc26..a241593 100644 --- a/packages/nextjs/app/blockexplorer/address/[address]/page.tsx +++ b/packages/nextjs/app/blockexplorer/address/[address]/page.tsx @@ -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 = { diff --git a/packages/nextjs/components/Header.tsx b/packages/nextjs/components/Header.tsx index 6a36a8d..15e2d60 100644 --- a/packages/nextjs/components/Header.tsx +++ b/packages/nextjs/components/Header.tsx @@ -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"; diff --git a/packages/nextjs/components/HistoryTable.tsx b/packages/nextjs/components/HistoryTable.tsx index 9136a60..af65c05 100644 --- a/packages/nextjs/components/HistoryTable.tsx +++ b/packages/nextjs/components/HistoryTable.tsx @@ -8,7 +8,7 @@ export const HistoryTable = () => { const { transactions, addTransaction, toastSuccess, toastError } = useLightningApp(); const [expandedRow, setExpandedRow] = useState(null); // State to manage expanded row index const { data: walletClient } = useWalletClient(); - const { data: yourContract } = useScaffoldContract({ + const { data: htlcContract } = useScaffoldContract({ contractName: "HashedTimelock", walletClient, }); @@ -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); diff --git a/packages/nextjs/components/RecieveModalPopup.tsx b/packages/nextjs/components/RecieveModalPopup.tsx index 3aaf7c2..1c99ead 100644 --- a/packages/nextjs/components/RecieveModalPopup.tsx +++ b/packages/nextjs/components/RecieveModalPopup.tsx @@ -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"; @@ -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, @@ -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; } @@ -121,7 +161,6 @@ function RecieveModal({ isOpen, onClose }: RecieveModalProps) { function onClickQRCode() { navigator.clipboard.writeText(invoice); toastSuccess("Lightning Invoice Copied"); - // setActiveStep(activeStep + 1); } function onClickContinue() { @@ -166,14 +205,12 @@ function RecieveModal({ isOpen, onClose }: RecieveModalProps) {
  1. = 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" }`} > = 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 @@ -197,14 +234,12 @@ function RecieveModal({ isOpen, onClose }: RecieveModalProps) {
  2. = 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" }`} > = 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 @@ -228,14 +263,12 @@ function RecieveModal({ isOpen, onClose }: RecieveModalProps) {
  3. = 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" }`} > = 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 @@ -245,7 +278,7 @@ function RecieveModal({ isOpen, onClose }: RecieveModalProps) {
{activeStep === 1 && - step1({ + Step1({ amount, invoice, recipientAddress, @@ -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 })} @@ -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 ( -
-
- Recipient Address - { - setRecipientAddress(newAddress); - }} - disabled={invoice !== ""} - /> -
-
- Amount (sats) - setAmount(BigInt(val))} - disableMultiplyBy1e18 - disabled={invoice !== ""} - /> -
- - {invoice && ( -
- -
- -
-
- Service Fee: {paymentRequest.satoshis} sats -
-
- )} -
- ); -} - -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 ( -
onClickQRCode()}> -   -
- - -
- Invoice: {paymentRequest.satoshis} sats -
-
-
- ); -} - -function step3({ txHash }: { txHash: string }) { - return ( -
- - View Transaction - -
- ); -} - export default RecieveModal; diff --git a/packages/nextjs/components/SendModalPopup.tsx b/packages/nextjs/components/SendModalPopup.tsx index 5048883..f39f574 100644 --- a/packages/nextjs/components/SendModalPopup.tsx +++ b/packages/nextjs/components/SendModalPopup.tsx @@ -14,6 +14,7 @@ type SendModalProps = { isOpen: boolean; onClose: () => void; }; + function SendModal({ isOpen, onClose }: SendModalProps) { const { addTransaction, transactions, toastError } = useLightningApp(); const [invoice, setInvoice] = useState(""); @@ -47,7 +48,7 @@ function SendModal({ isOpen, onClose }: SendModalProps) { }, [transactions]); const { data: walletClient } = useWalletClient(); - const { data: yourContract } = useScaffoldContract({ + const { data: htlcContract } = useScaffoldContract({ contractName: "HashedTimelock", walletClient, }); @@ -78,9 +79,9 @@ function SendModal({ isOpen, onClose }: SendModalProps) { function submitPayment() { console.log("submitting payment"); - if (!yourContract) return; + if (!htlcContract) return; if (!lnInvoiceRef.current) return; - yourContract.write + htlcContract.write .newContract( [ process.env.LSP_ADDRESS ?? "0xf89335a26933d8Dd6193fD91cAB4e1466e5198Bf", diff --git a/packages/nextjs/components/recieve-steps/Step1.tsx b/packages/nextjs/components/recieve-steps/Step1.tsx new file mode 100644 index 0000000..8a8cd38 --- /dev/null +++ b/packages/nextjs/components/recieve-steps/Step1.tsx @@ -0,0 +1,82 @@ +import { AddressInput, IntegerInput } from "../scaffold-eth"; +import { PaymentRequestObject, decode } from "bolt11"; +import QRCode from "qrcode.react"; + +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 ( +
+
+ Recipient Address + { + setRecipientAddress(newAddress); + }} + disabled={invoice !== ""} + /> +
+
+ Amount (sats) + setAmount(BigInt(val))} + disableMultiplyBy1e18 + disabled={invoice !== ""} + /> +
+ + {invoice && ( +
+ +
+ +
+
+ Service Fee: {paymentRequest.satoshis} sats +
+
+ )} +
+ ); +} diff --git a/packages/nextjs/components/recieve-steps/Step2.tsx b/packages/nextjs/components/recieve-steps/Step2.tsx new file mode 100644 index 0000000..8d193ee --- /dev/null +++ b/packages/nextjs/components/recieve-steps/Step2.tsx @@ -0,0 +1,26 @@ +import { PaymentRequestObject, decode } from "bolt11"; +import QRCode from "qrcode.react"; + +export 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 ( +
onClickQRCode()}> +   +
+ + +
+ Invoice: {paymentRequest.satoshis} sats +
+
+
+ ); +} diff --git a/packages/nextjs/components/recieve-steps/Step3.tsx b/packages/nextjs/components/recieve-steps/Step3.tsx new file mode 100644 index 0000000..5a141db --- /dev/null +++ b/packages/nextjs/components/recieve-steps/Step3.tsx @@ -0,0 +1,14 @@ +export function Step3({ txHash }: { txHash: string }) { + return ( +
+ + View Transaction + +
+ ); +} diff --git a/packages/nextjs/components/scaffold-eth/Input/AddressInput.tsx b/packages/nextjs/components/scaffold-eth/Input/AddressInput.tsx index d92ab07..cefe10a 100644 --- a/packages/nextjs/components/scaffold-eth/Input/AddressInput.tsx +++ b/packages/nextjs/components/scaffold-eth/Input/AddressInput.tsx @@ -78,7 +78,7 @@ export const AddressInput = ({ value, name, placeholder, onChange, disabled }: C } ) : null} - {enteredEnsName ?? ensName} + {enteredEnsName ?? ensName} ) } diff --git a/packages/nextjs/contracts/deployedContracts.ts b/packages/nextjs/contracts/deployedContracts.ts deleted file mode 100644 index f8741e4..0000000 --- a/packages/nextjs/contracts/deployedContracts.ts +++ /dev/null @@ -1,676 +0,0 @@ -/** - * This file is autogenerated by Scaffold-ETH. - * You should not edit it manually or your changes might be overwritten. - */ -import { GenericContractsDeclaration } from "~~/utils/scaffold-eth/contract"; - -const deployedContracts = { - 3636: { - HashedTimelock: { - address: "0x184375F7d9104fc34C523Ee16c1270A16Bb4BFC2", - abi: [ - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "bytes32", - name: "contractId", - type: "bytes32", - }, - { - indexed: true, - internalType: "address", - name: "sender", - type: "address", - }, - { - indexed: true, - internalType: "address", - name: "receiver", - type: "address", - }, - { - indexed: false, - internalType: "uint256", - name: "amount", - type: "uint256", - }, - { - indexed: false, - internalType: "bytes32", - name: "hashlock", - type: "bytes32", - }, - { - indexed: false, - internalType: "uint256", - name: "timelock", - type: "uint256", - }, - ], - name: "LogHTLCNew", - type: "event", - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "bytes32", - name: "contractId", - type: "bytes32", - }, - ], - name: "LogHTLCRefund", - type: "event", - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "bytes32", - name: "contractId", - type: "bytes32", - }, - ], - name: "LogHTLCWithdraw", - type: "event", - }, - { - inputs: [ - { - internalType: "bytes32", - name: "_contractId", - type: "bytes32", - }, - ], - name: "getContract", - outputs: [ - { - internalType: "address", - name: "sender", - type: "address", - }, - { - internalType: "address", - name: "receiver", - type: "address", - }, - { - internalType: "uint256", - name: "amount", - type: "uint256", - }, - { - internalType: "bytes32", - name: "hashlock", - type: "bytes32", - }, - { - internalType: "uint256", - name: "timelock", - type: "uint256", - }, - { - internalType: "bool", - name: "withdrawn", - type: "bool", - }, - { - internalType: "bool", - name: "refunded", - type: "bool", - }, - { - internalType: "bytes32", - name: "preimage", - type: "bytes32", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "bytes32", - name: "_contractId", - type: "bytes32", - }, - ], - name: "haveContract", - outputs: [ - { - internalType: "bool", - name: "exists", - type: "bool", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "address payable", - name: "_receiver", - type: "address", - }, - { - internalType: "bytes32", - name: "_hashlock", - type: "bytes32", - }, - { - internalType: "uint256", - name: "_timelock", - type: "uint256", - }, - ], - name: "newContract", - outputs: [ - { - internalType: "bytes32", - name: "contractId", - type: "bytes32", - }, - ], - stateMutability: "payable", - type: "function", - }, - { - inputs: [ - { - internalType: "bytes32", - name: "_contractId", - type: "bytes32", - }, - ], - name: "refund", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [ - { - internalType: "bytes32", - name: "_contractId", - type: "bytes32", - }, - { - internalType: "bytes32", - name: "_preimage", - type: "bytes32", - }, - ], - name: "withdraw", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "nonpayable", - type: "function", - }, - ], - inheritedFunctions: {}, - }, - }, - 31337: { - HashedTimelock: { - address: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", - abi: [ - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "bytes32", - name: "contractId", - type: "bytes32", - }, - { - indexed: true, - internalType: "address", - name: "sender", - type: "address", - }, - { - indexed: true, - internalType: "address", - name: "receiver", - type: "address", - }, - { - indexed: false, - internalType: "uint256", - name: "amount", - type: "uint256", - }, - { - indexed: false, - internalType: "bytes32", - name: "hashlock", - type: "bytes32", - }, - { - indexed: false, - internalType: "uint256", - name: "timelock", - type: "uint256", - }, - ], - name: "LogHTLCNew", - type: "event", - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "bytes32", - name: "contractId", - type: "bytes32", - }, - ], - name: "LogHTLCRefund", - type: "event", - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "bytes32", - name: "contractId", - type: "bytes32", - }, - ], - name: "LogHTLCWithdraw", - type: "event", - }, - { - inputs: [ - { - internalType: "bytes32", - name: "_contractId", - type: "bytes32", - }, - ], - name: "getContract", - outputs: [ - { - internalType: "address", - name: "sender", - type: "address", - }, - { - internalType: "address", - name: "receiver", - type: "address", - }, - { - internalType: "uint256", - name: "amount", - type: "uint256", - }, - { - internalType: "bytes32", - name: "hashlock", - type: "bytes32", - }, - { - internalType: "uint256", - name: "timelock", - type: "uint256", - }, - { - internalType: "bool", - name: "withdrawn", - type: "bool", - }, - { - internalType: "bool", - name: "refunded", - type: "bool", - }, - { - internalType: "bytes32", - name: "preimage", - type: "bytes32", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "bytes32", - name: "_contractId", - type: "bytes32", - }, - ], - name: "haveContract", - outputs: [ - { - internalType: "bool", - name: "exists", - type: "bool", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "address payable", - name: "_receiver", - type: "address", - }, - { - internalType: "bytes32", - name: "_hashlock", - type: "bytes32", - }, - { - internalType: "uint256", - name: "_timelock", - type: "uint256", - }, - ], - name: "newContract", - outputs: [ - { - internalType: "bytes32", - name: "contractId", - type: "bytes32", - }, - ], - stateMutability: "payable", - type: "function", - }, - { - inputs: [ - { - internalType: "bytes32", - name: "_contractId", - type: "bytes32", - }, - ], - name: "refund", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [ - { - internalType: "bytes32", - name: "_contractId", - type: "bytes32", - }, - { - internalType: "bytes32", - name: "_preimage", - type: "bytes32", - }, - ], - name: "withdraw", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "nonpayable", - type: "function", - }, - ], - inheritedFunctions: {}, - }, - }, - 11155111: { - HashedTimelock: { - address: "0xd7B6Ee95a03d5C538C7C8E7370aFd88864b06506", - abi: [ - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "bytes32", - name: "contractId", - type: "bytes32", - }, - { - indexed: true, - internalType: "address", - name: "sender", - type: "address", - }, - { - indexed: true, - internalType: "address", - name: "receiver", - type: "address", - }, - { - indexed: false, - internalType: "uint256", - name: "amount", - type: "uint256", - }, - { - indexed: false, - internalType: "bytes32", - name: "hashlock", - type: "bytes32", - }, - { - indexed: false, - internalType: "uint256", - name: "timelock", - type: "uint256", - }, - ], - name: "LogHTLCNew", - type: "event", - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "bytes32", - name: "contractId", - type: "bytes32", - }, - ], - name: "LogHTLCRefund", - type: "event", - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "bytes32", - name: "contractId", - type: "bytes32", - }, - ], - name: "LogHTLCWithdraw", - type: "event", - }, - { - inputs: [ - { - internalType: "bytes32", - name: "_contractId", - type: "bytes32", - }, - ], - name: "getContract", - outputs: [ - { - internalType: "address", - name: "sender", - type: "address", - }, - { - internalType: "address", - name: "receiver", - type: "address", - }, - { - internalType: "uint256", - name: "amount", - type: "uint256", - }, - { - internalType: "bytes32", - name: "hashlock", - type: "bytes32", - }, - { - internalType: "uint256", - name: "timelock", - type: "uint256", - }, - { - internalType: "bool", - name: "withdrawn", - type: "bool", - }, - { - internalType: "bool", - name: "refunded", - type: "bool", - }, - { - internalType: "bytes32", - name: "preimage", - type: "bytes32", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "bytes32", - name: "_contractId", - type: "bytes32", - }, - ], - name: "haveContract", - outputs: [ - { - internalType: "bool", - name: "exists", - type: "bool", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - internalType: "address payable", - name: "_receiver", - type: "address", - }, - { - internalType: "bytes32", - name: "_hashlock", - type: "bytes32", - }, - { - internalType: "uint256", - name: "_timelock", - type: "uint256", - }, - ], - name: "newContract", - outputs: [ - { - internalType: "bytes32", - name: "contractId", - type: "bytes32", - }, - ], - stateMutability: "payable", - type: "function", - }, - { - inputs: [ - { - internalType: "bytes32", - name: "_contractId", - type: "bytes32", - }, - ], - name: "refund", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [ - { - internalType: "bytes32", - name: "_contractId", - type: "bytes32", - }, - { - internalType: "bytes32", - name: "_preimage", - type: "bytes32", - }, - ], - name: "withdraw", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "nonpayable", - type: "function", - }, - ], - inheritedFunctions: {}, - }, - }, -} as const; - -export default deployedContracts satisfies GenericContractsDeclaration; diff --git a/packages/nextjs/contracts/externalContracts.ts b/packages/nextjs/contracts/externalContracts.ts deleted file mode 100644 index ab6daa8..0000000 --- a/packages/nextjs/contracts/externalContracts.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { GenericContractsDeclaration } from "~~/utils/scaffold-eth/contract"; - -/** - * @example - * const externalContracts = { - * 1: { - * DAI: { - * address: "0x...", - * abi: [...], - * }, - * }, - * } as const; - */ -const externalContracts = {} as const; - -export default externalContracts satisfies GenericContractsDeclaration; diff --git a/packages/nextjs/hooks/LightningProvider.tsx b/packages/nextjs/hooks/LightningProvider.tsx index 84173bd..9141e78 100644 --- a/packages/nextjs/hooks/LightningProvider.tsx +++ b/packages/nextjs/hooks/LightningProvider.tsx @@ -1,9 +1,16 @@ import React, { createContext, useContext, useEffect, useState } from "react"; import { useNativeCurrencyPrice, useScaffoldEventSubscriber } from "./scaffold-eth"; import { useWebSocket } from "./useWebSocket"; +import { + ClientRequest, + HodlInvoiceResponse, + InitiationResponse, + InvoiceResponse, + KIND, + ServerStatus, +} from "@lightning-evm-bridge/shared"; import { ToastContainer, toast } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; -import { ClientRequest, HodlInvoiceResponse, InitiationResponse, InvoiceResponse, KIND, ServerStatus } from "shared"; import { HashLock } from "~~/types/utils"; // Define the types for your historical transactions and context @@ -81,7 +88,7 @@ export const LightningProvider = ({ children }: { children: React.ReactNode }) = contractName: "HashedTimelock", eventName: "LogHTLCNew", listener: event => { - const tmpContractId = event[0].args.contractId; + const tmpContractId = event[0].args.contractId as string; const txHash = event[0].transactionHash; if (!tmpContractId) return; // check if the transaction has the same has as one of the transactions in the list diff --git a/packages/nextjs/hooks/useWebSocket.tsx b/packages/nextjs/hooks/useWebSocket.tsx index 1f0b9fc..66bd4c3 100644 --- a/packages/nextjs/hooks/useWebSocket.tsx +++ b/packages/nextjs/hooks/useWebSocket.tsx @@ -7,7 +7,7 @@ import { KIND, ServerResponse, ServerStatus, -} from "shared"; +} from "@lightning-evm-bridge/shared"; export const useWebSocket = (url: string) => { const socket = useRef(null); diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 7a31300..0d36b9a 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -19,6 +19,7 @@ "@emotion/styled": "^11.11.0", "@ethersproject/providers": "~5.7.2", "@heroicons/react": "~2.0.11", + "@lightning-evm-bridge/shared": "workspace:^", "@rainbow-me/rainbowkit": "^1.3.6", "@uniswap/sdk-core": "~4.0.1", "@uniswap/v2-sdk": "~3.0.1", @@ -39,7 +40,6 @@ "react-hot-toast": "~2.4.0", "react-spinners": "^0.13.8", "react-toastify": "^10.0.4", - "shared": "workspace:^", "use-debounce": "~8.0.4", "usehooks-ts": "~2.9.1", "viem": "1.19.9", diff --git a/packages/nextjs/utils/scaffold-eth/contract.ts b/packages/nextjs/utils/scaffold-eth/contract.ts index ac7fe4a..f0190f3 100644 --- a/packages/nextjs/utils/scaffold-eth/contract.ts +++ b/packages/nextjs/utils/scaffold-eth/contract.ts @@ -1,3 +1,4 @@ +import { deployedContracts, externalContracts } from "@lightning-evm-bridge/shared"; import { Abi, AbiParameterToPrimitiveType, @@ -19,8 +20,6 @@ import { TransactionReceipt, } from "viem"; import { UseContractEventConfig, UseContractReadConfig, UseContractWriteConfig } from "wagmi"; -import deployedContractsData from "~~/contracts/deployedContracts"; -import externalContractsData from "~~/contracts/externalContracts"; import scaffoldConfig from "~~/scaffold.config"; type AddExternalFlag = { @@ -51,7 +50,7 @@ const deepMergeContracts = , E extends Record return result as MergeDeepRecord, AddExternalFlag, { arrayMergeMode: "replace" }>; }; -const contractsData = deepMergeContracts(deployedContractsData, externalContractsData); +const contractsData = deepMergeContracts(deployedContracts, externalContracts); export type InheritedFunctions = { readonly [key: string]: string }; diff --git a/packages/nextjs/utils/scaffold-eth/decodeTxData.ts b/packages/nextjs/utils/scaffold-eth/decodeTxData.ts index 481b978..2e4718f 100644 --- a/packages/nextjs/utils/scaffold-eth/decodeTxData.ts +++ b/packages/nextjs/utils/scaffold-eth/decodeTxData.ts @@ -1,8 +1,8 @@ import { TransactionWithFunction } from "./block"; import { GenericContractsDeclaration } from "./contract"; +import { deployedContracts as contractData } from "@lightning-evm-bridge/shared"; import { Abi, decodeFunctionData, getAbiItem } from "viem"; import { hardhat } from "viem/chains"; -import contractData from "~~/contracts/deployedContracts"; type ContractsInterfaces = Record; type TransactionType = TransactionWithFunction | null; diff --git a/packages/relay-server/README.md b/packages/relay-server/README.md new file mode 100644 index 0000000..7de21c9 --- /dev/null +++ b/packages/relay-server/README.md @@ -0,0 +1,61 @@ +# Relay Server for Lightning EVM Bridge + +The Relay Server is a crucial component of the Lightning EVM Bridge system, handling the automation of Hashed Timelock Contract (HTLC) claims. This server's primary role is to facilitate the secure and timely claiming of HTLCs on behalf of users who might not have the necessary resources (like gas for transaction fees) to do it themselves. + +## Overview + +The Relay Server monitors blockchain events related to HTLCs and interacts directly with the smart contracts to execute claims. This process is critical for ensuring that the payment channel is reliable and that the participants receive their funds in a timely manner. + +## Setup + +### Requirements + +- Node.js (v18 LTS) +- Yarn (v1 or v2+) +- Access to an Ethereum RPC endpoint + +### Installation + +1. **Clone the repository if not already done:** + + ```bash + git clone https://github.com/diyahir/lightning-dapp.git + cd lightning-dapp/packages/relay-server + ``` + +2. **Install dependencies:** + + ```bash + yarn install + ``` + +### Environment Configuration + +Create an `.env` file based on the `sample.env` template included in the directory. Update the following keys with appropriate values: + +```plaintext +RPC_URL="your_ethereum_rpc_url" +PRIVATE_KEY="your_private_key_for_transaction_signing" +``` + +## Running the Server + +To start the Relay Server, use: + +```bash +yarn start +``` + +This command will initiate the server, which listens for blockchain events and processes HTLC claims automatically. + +## Features + +- **Automated Claiming**: Automates the process of claiming funds locked in HTLCs, ensuring that users don't lose their funds due to missed deadlines. +- **Monitoring and Notifications**: Monitors blockchain events and can be configured to send notifications upon successful or failed claims. +- **Security Measures**: Implements necessary security measures to manage and use private keys safely for signing transactions without exposing them. + +## Future Enhancements + +- **Optimization of Gas Usage**: Implement strategies to minimize gas costs when claiming HTLCs. +- **Support for Multiple Chains**: Extend support to multiple EVM-compatible chains to enhance the utility of the service. +- **Enhanced Error Handling**: Develop robust error handling mechanisms to deal with unforeseen network or smart contract errors. diff --git a/packages/relay-server/package.json b/packages/relay-server/package.json new file mode 100644 index 0000000..dc579aa --- /dev/null +++ b/packages/relay-server/package.json @@ -0,0 +1,24 @@ +{ + "name": "@lightning-evm-bridge/relay-server", + "packageManager": "yarn@3.2.3", + "scripts": { + "build": "tsc", + "start": "ts-node src/app.ts" + }, + "dependencies": { + "@lightning-evm-bridge/shared": "workspace:^", + "body-parser": "^1.20.2", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "ethers": "^6.12.1", + "express": "^4.19.2", + "js-sha256": "^0.11.0" + }, + "devDependencies": { + "@types/body-parser": "^1.19.5", + "@types/cors": "^2", + "@types/express": "^4.17.21", + "@types/node": "^20.12.10", + "typescript": "^5.4.5" + } +} diff --git a/packages/relay-server/sample.env b/packages/relay-server/sample.env new file mode 100644 index 0000000..7860d91 --- /dev/null +++ b/packages/relay-server/sample.env @@ -0,0 +1,8 @@ +PORT="3004" +### Chain Info ### +RPC_URL="https://node.botanixlabs.dev" +CHAIN_ID="3636" + +### LSP Private Ket ### +LSP_PRIVATE_KEY="0x1233....." +# Generate a dummy evm private key, not a seed phase \ No newline at end of file diff --git a/packages/relay-server/src/app.ts b/packages/relay-server/src/app.ts new file mode 100644 index 0000000..a002d21 --- /dev/null +++ b/packages/relay-server/src/app.ts @@ -0,0 +1,129 @@ +import { + ContractDetails, + KIND, + RelayRequest, + parseContractDetails, + deployedContracts, + RelayResponse, +} from "@lightning-evm-bridge/shared"; +import express, { Request, Response } from "express"; +import bodyParser from "body-parser"; +import dotenv from "dotenv"; +import { ethers } from "ethers"; +import cors from "cors"; +import { sha256 } from "js-sha256"; + +dotenv.config(); + +const { PORT, RPC_URL, LSP_PRIVATE_KEY, CHAIN_ID } = process.env; + +if (!RPC_URL || !LSP_PRIVATE_KEY || !CHAIN_ID || !PORT) { + console.error("Missing environment variables"); + process.exit(1); +} + +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, + htlcContractInfo.abi, + signer +); + +const app = express(); + +// Middleware to parse JSON bodies +app.use(cors()); +app.use(bodyParser.json()); + +// Post route to handle relay requests +app.post("/relay", async (req: Request, res: Response) => { + try { + const { kind, contractId, preimage } = req.body as RelayRequest; + + if (kind === KIND.RELAY_REQUEST && contractId && preimage) { + console.log( + `Received relay request for contract ID: ${contractId} with preimage: ${preimage}` + ); + + if (await validateContractAndPreimage(contractId, preimage)) { + const options = { gasPrice: ethers.parseUnits("0.001", "gwei") }; + + await htlcContract + .withdraw(contractId, "0x" + preimage, options) + .then(async (tx: any) => { + await tx.wait().then(async () => { + console.log("Withdrawal Transaction:", tx); + + const msg: RelayResponse = { + kind: KIND.RELAY_RESPONSE, + status: "success", + txHash: tx.hash, + }; + // Insert your logic here to interact with the smart contract + res.status(200).send(msg); + }); + }) + .catch((error: any) => { + console.error("Withdrawal Error:", error); + res.status(500).send({ message: "Failed to withdraw contract" }); + }); + } else { + res + .status(400) + .send({ message: "Unable to validate contract and preimage" }); + return; + } + } else { + res.status(400).send({ message: "Invalid request" }); + } + } catch (error) { + console.error("Error processing request:", error); + res.status(500).send({ message: "Internal server error" }); + } +}); + +app.listen(PORT, () => { + console.log(`Server running on http://localhost:${PORT}`); +}); + +async function validateContractAndPreimage( + contractId: string, + preimage: string +) { + const contractDetails: ContractDetails = await getContractDetails(contractId); + + console.log("Contract Details:", contractDetails); + + if (!contractDetails) { + console.error("Contract not found"); + return false; + } + + const hash = sha256.hex(Buffer.from(preimage, "hex")); + + if (contractDetails.hashlock !== "0x" + hash) { + console.error("Preimage does not match"); + return false; + } + + if (contractDetails.withdrawn) { + console.error("Contract already withdrawn"); + return false; + } + + if (contractDetails.refunded) { + console.error("Contract already refunded"); + return false; + } + + return true; +} + +async function getContractDetails( + contractId: string +): Promise { + const response: any = await htlcContract.getContract(contractId); + return parseContractDetails(response); +} diff --git a/packages/relay-server/tsconfig.json b/packages/relay-server/tsconfig.json new file mode 100644 index 0000000..c17cf71 --- /dev/null +++ b/packages/relay-server/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "module": "commonjs", + "esModuleInterop": true, + "target": "es6", + "moduleResolution": "node", + "sourceMap": true, + "outDir": "dist", + "lib": ["ESNext"] // Exclude "DOM" if not needed + }, + "lib": ["es2015"] + } \ No newline at end of file diff --git a/packages/server/README.md b/packages/server/README.md new file mode 100644 index 0000000..8e9018b --- /dev/null +++ b/packages/server/README.md @@ -0,0 +1,72 @@ +# Lightning Server + +The Lightning Server acts as the bridge between Ethereum Virtual Machine (EVM) chains and the Lightning Network, facilitating secure and trust-minimized payments. This server interacts with the providers's Lightning node to facilitate payments based on HTLC (Hashed Time-Locked Contracts) states communicated via smart contracts. + +## Overview + +The Lightning Server connects to a configured Lightning node and acts as a payment relay between the node and the Ethereum blockchain. It verifies the authenticity of payment requests and ensures that only valid and confirmed transactions trigger the corresponding actions on the Lightning Network. + +## Setup + +### Installation + +1. **Install dependencies:** + + ```bash + yarn install + ``` + +2. **Environment Setup:** + + Copy the `sample.env` file to `.env` and update the environment variables according to your setup: + + ```bash + cp sample.env .env + ``` + + ```plaintext + LND_MACAROON=path_to_macaroon + LND_SOCKET=your_lightning_node_socket + ``` + +### Running the Server + +- **With Docker:** + + ```bash + docker-compose up --build + ``` + +- **Manually:** + + ```bash + yarn start + ``` + + This will start the server locally on the configured port, connecting to your specified Lightning node. + +## Configuration + +Ensure that the `.env` file is configured correctly. Key configurations include: + +- **`LND_MACAROON`**: Path to your macaroon file for authenticating with your Lightning node. +- **`LND_SOCKET`**: The address and port of your Lightning node's API. + +## Features + +- **Payment Verification**: Checks the validity of HTLC-based payment requests. +- **Invoice Payment**: Pays Lightning Network invoices if the associated blockchain transaction is verified. +- **Preimage Handling**: Retrieves payment preimages from the Lightning Network and communicates them back to the smart contract on the EVM chain. + +## Mock Mode + +To run the server in mock mode, set the Lightning configurations in the `.env` file to empty. This mode simulates Lightning Network operations and can be used for testing: + +```plaintext +LND_MACAROON="" +LND_SOCKET="" +``` + +## Production Deployment + +For production, make sure to secure your server, especially the API keys and macaroon files. Use Docker for easy and consistent deployments. Configure SSL/TLS if accessible over the Internet. diff --git a/packages/server/package.json b/packages/server/package.json index 691e0c8..0ffe80e 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -27,13 +27,13 @@ "typescript": "^5.3.3" }, "dependencies": { + "@lightning-evm-bridge/shared": "workspace:^", "bolt11": "^1.4.1", "dotenv": "^16.4.4", "ethers": "^6.11.1", "lightning": "^10.10.0", "ln-service": "^57.5.1", "nextjs": "^0.0.3", - "shared": "workspace:^", "ts-pattern": "^5.1.1", "uuid": "^9.0.1", "ws": "^8.16.0" diff --git a/packages/server/src/app.ts b/packages/server/src/app.ts index 9131cfb..fc2fe59 100644 --- a/packages/server/src/app.ts +++ b/packages/server/src/app.ts @@ -2,13 +2,21 @@ import * as WebSocket from "ws"; import dotenv from "dotenv"; import { ethers } from "ethers"; import { v4 as uuidv4 } from "uuid"; -import deployedContracts from "./contracts/deployedContracts"; + import { match } from "ts-pattern"; -import { ClientRequest, ConnectionResponse, KIND, ServerStatus } from "shared"; +import { + ClientRequest, + ConnectionResponse, + KIND, + ServerStatus, + deployedContracts, +} from "@lightning-evm-bridge/shared"; import { processClientInvoiceRequest } from "./utils/lightningSendUtils"; import { processClientLightningReceiveRequest } from "./utils/lightningRecieveUtils"; import { CachedPayment, ServerState } from "./types/types"; import { authenticatedLndGrpc } from "lightning"; +import { providerConfig } from "./provider.config"; + dotenv.config(); // Verify environment variables @@ -75,6 +83,7 @@ wss.on("connection", (ws: WebSocket) => { const connectionResponse: ConnectionResponse = { serverStatus: serverState.serverStatus, + serverConfig: providerConfig, uuid, message: "Connected to server", }; @@ -90,8 +99,7 @@ wss.on("connection", (ws: WebSocket) => { }) .with({ kind: KIND.INITIATION_RECIEVE }, async (request) => { await processClientLightningReceiveRequest(request, ws, serverState); - }) - .exhaustive(); + }); }); ws.on("close", () => console.log("Client disconnected")); diff --git a/packages/server/src/provider.config.ts b/packages/server/src/provider.config.ts index 7f1472f..9d93855 100644 --- a/packages/server/src/provider.config.ts +++ b/packages/server/src/provider.config.ts @@ -1,13 +1,4 @@ -export type ProviderConfig = { - minSats: number; - maxSats: number; - sendBaseFee: number; - sendBasisPointFee: number; // 100 = 1% - secondsTillInvoiceExpires: number; - maxLNFee: number; - recieveBaseFee: number; - recieveBasisPointFee: number; -}; +import { ProviderConfig } from "@lightning-evm-bridge/shared"; export const providerConfig: ProviderConfig = { minSats: 2, diff --git a/packages/server/src/tests/index.test.ts b/packages/server/src/tests/index.test.ts index 6d4abb1..02949dd 100644 --- a/packages/server/src/tests/index.test.ts +++ b/packages/server/src/tests/index.test.ts @@ -6,7 +6,7 @@ import { getContractAmountFromInvoice, } from "../utils/validation"; import { PaymentRequestObject } from "bolt11"; -import { ContractDetails } from "shared"; +import { ContractDetails } from "@lightning-evm-bridge/shared"; describe("validateLnInvoiceAndContract", () => { const mockCurrentTime = 1700000000; // Mock current timestamp diff --git a/packages/server/src/types/types.ts b/packages/server/src/types/types.ts index 14c53bf..8f35476 100644 --- a/packages/server/src/types/types.ts +++ b/packages/server/src/types/types.ts @@ -1,6 +1,6 @@ import { ethers } from "ethers"; import { AuthenticatedLnd } from "lightning"; -import { ServerStatus } from "shared"; +import { ServerStatus } from "@lightning-evm-bridge/shared"; export type validationResponse = { isValid: boolean; diff --git a/packages/server/src/utils/lightningRecieveUtils.ts b/packages/server/src/utils/lightningRecieveUtils.ts index b3ab93e..93bc54e 100644 --- a/packages/server/src/utils/lightningRecieveUtils.ts +++ b/packages/server/src/utils/lightningRecieveUtils.ts @@ -6,7 +6,7 @@ import { GWEIPERSAT, KIND, HodlInvoiceContractResponse, -} from "shared"; +} from "@lightning-evm-bridge/shared"; import { ServerState } from "../types/types"; import * as WebSocket from "ws"; import { @@ -199,6 +199,7 @@ async function processPaidHodlInvoice( contractId, serverState.htlcContract ); + console.log("Contract Details:", contractDetails); if (contractDetails.withdrawn) { console.log("Preimage found, settling hodl invoice"); diff --git a/packages/server/src/utils/lightningSendUtils.ts b/packages/server/src/utils/lightningSendUtils.ts index bb501aa..b778a0c 100644 --- a/packages/server/src/utils/lightningSendUtils.ts +++ b/packages/server/src/utils/lightningSendUtils.ts @@ -1,6 +1,6 @@ import { decode } from "bolt11"; import { ethers } from "ethers"; -import { ContractDetails, InvoiceRequest } from "shared"; +import { ContractDetails, InvoiceRequest } from "@lightning-evm-bridge/shared"; import { providerConfig } from "../provider.config"; import { ServerState } from "../types/types"; import { pay } from "lightning"; diff --git a/packages/server/src/utils/validation.ts b/packages/server/src/utils/validation.ts index c63eaf0..b73ed28 100644 --- a/packages/server/src/utils/validation.ts +++ b/packages/server/src/utils/validation.ts @@ -2,7 +2,11 @@ import { PaymentRequestObject } from "bolt11"; import { validationResponse } from "../types/types"; import { providerConfig } from "../provider.config"; import { ethers } from "ethers"; -import { GWEIPERSAT, parseContractDetails, ContractDetails } from "shared"; +import { + GWEIPERSAT, + parseContractDetails, + ContractDetails, +} from "@lightning-evm-bridge/shared"; export function validateLnInvoiceAndContract( lnInvoiceDetails: PaymentRequestObject, diff --git a/packages/server/src/contracts/deployedContracts.ts b/packages/shared/contracts/deployedContracts.ts similarity index 100% rename from packages/server/src/contracts/deployedContracts.ts rename to packages/shared/contracts/deployedContracts.ts diff --git a/packages/server/src/contracts/externalContracts.ts b/packages/shared/contracts/externalContracts.ts similarity index 100% rename from packages/server/src/contracts/externalContracts.ts rename to packages/shared/contracts/externalContracts.ts diff --git a/packages/shared/index.ts b/packages/shared/index.ts index b2608d5..bbf7899 100644 --- a/packages/shared/index.ts +++ b/packages/shared/index.ts @@ -1,4 +1,32 @@ -export type ClientRequest = InvoiceRequest | InitiationRequest; +import deployedContracts from "./contracts/deployedContracts"; +import externalContracts from "./contracts/externalContracts"; + +export { deployedContracts, externalContracts }; + +export type ProviderConfig = { + minSats: number; + maxSats: number; + sendBaseFee: number; + sendBasisPointFee: number; // 100 = 1% + secondsTillInvoiceExpires: number; + maxLNFee: number; + recieveBaseFee: number; + recieveBasisPointFee: number; +}; + +export type ClientRequest = InvoiceRequest | InitiationRequest | RelayRequest; + +export interface RelayRequest { + kind: KIND.RELAY_REQUEST; + contractId: string; + preimage: string; +} + +export interface RelayResponse { + kind: KIND.RELAY_RESPONSE; + status: "success" | "error"; + txHash?: string; +} export interface InvoiceRequest { kind: KIND.INVOICE_SEND; @@ -28,6 +56,8 @@ export interface HodlInvoiceContractResponse { } export enum KIND { + RELAY_REQUEST = "relay_request", + RELAY_RESPONSE = "relay_response", INVOICE_SEND = "invoice_send", INITIATION_RECIEVE = "initiation_recieve", HODL_RES = "hodl_res", @@ -47,6 +77,7 @@ export enum ServerStatus { export interface ConnectionResponse { serverStatus: ServerStatus; + serverConfig: ProviderConfig; uuid: string; message: string; } diff --git a/packages/shared/package.json b/packages/shared/package.json index 3c15197..068cdc8 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,5 +1,5 @@ { - "name": "shared", + "name": "@lightning-evm-bridge/shared", "packageManager": "yarn@3.2.3", "devDependencies": { "typescript": "^5.4.5" diff --git a/yarn.lock b/yarn.lock index 4e789fc..8a2a35e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2716,6 +2716,7 @@ __metadata: "@emotion/styled": ^11.11.0 "@ethersproject/providers": ~5.7.2 "@heroicons/react": ~2.0.11 + "@lightning-evm-bridge/shared": "workspace:^" "@rainbow-me/rainbowkit": ^1.3.6 "@trivago/prettier-plugin-sort-imports": ~4.1.1 "@types/node": ^17.0.45 @@ -2749,7 +2750,6 @@ __metadata: react-hot-toast: ~2.4.0 react-spinners: ^0.13.8 react-toastify: ^10.0.4 - shared: "workspace:^" tailwindcss: ~3.3.3 type-fest: ~4.6.0 typescript: ~5.1.6 @@ -2762,6 +2762,25 @@ __metadata: languageName: unknown linkType: soft +"@lightning-evm-bridge/relay-server@workspace:packages/relay-server": + version: 0.0.0-use.local + resolution: "@lightning-evm-bridge/relay-server@workspace:packages/relay-server" + dependencies: + "@lightning-evm-bridge/shared": "workspace:^" + "@types/body-parser": ^1.19.5 + "@types/cors": ^2 + "@types/express": ^4.17.21 + "@types/node": ^20.12.10 + body-parser: ^1.20.2 + cors: ^2.8.5 + dotenv: ^16.4.5 + ethers: ^6.12.1 + express: ^4.19.2 + js-sha256: ^0.11.0 + typescript: ^5.4.5 + languageName: unknown + linkType: soft + "@lightning-evm-bridge/server@workspace:packages/server": version: 0.0.0-use.local resolution: "@lightning-evm-bridge/server@workspace:packages/server" @@ -2770,6 +2789,7 @@ __metadata: "@babel/preset-env": ^7.24.4 "@babel/preset-typescript": ^7.24.1 "@jest/globals": ^29.7.0 + "@lightning-evm-bridge/shared": "workspace:^" "@types/babel__core": ^7 "@types/babel__preset-env": ^7 "@types/jest": ^29.5.12 @@ -2783,7 +2803,6 @@ __metadata: lightning: ^10.10.0 ln-service: ^57.5.1 nextjs: ^0.0.3 - shared: "workspace:^" ts-jest: ^29.1.2 ts-pattern: ^5.1.1 typescript: ^5.3.3 @@ -2792,6 +2811,14 @@ __metadata: languageName: unknown linkType: soft +"@lightning-evm-bridge/shared@workspace:^, @lightning-evm-bridge/shared@workspace:packages/shared": + version: 0.0.0-use.local + resolution: "@lightning-evm-bridge/shared@workspace:packages/shared" + dependencies: + typescript: ^5.4.5 + languageName: unknown + linkType: soft + "@lit-labs/ssr-dom-shim@npm:^1.0.0, @lit-labs/ssr-dom-shim@npm:^1.1.0": version: 1.2.0 resolution: "@lit-labs/ssr-dom-shim@npm:1.2.0" @@ -3885,7 +3912,7 @@ __metadata: languageName: node linkType: hard -"@types/body-parser@npm:*": +"@types/body-parser@npm:*, @types/body-parser@npm:^1.19.5": version: 1.19.5 resolution: "@types/body-parser@npm:1.19.5" dependencies: @@ -3911,6 +3938,15 @@ __metadata: languageName: node linkType: hard +"@types/cors@npm:^2": + version: 2.8.17 + resolution: "@types/cors@npm:2.8.17" + dependencies: + "@types/node": "*" + checksum: 469bd85e29a35977099a3745c78e489916011169a664e97c4c3d6538143b0a16e4cc72b05b407dc008df3892ed7bf595f9b7c0f1f4680e169565ee9d64966bde + languageName: node + linkType: hard + "@types/debug@npm:^4.1.7": version: 4.1.12 resolution: "@types/debug@npm:4.1.12" @@ -3932,6 +3968,18 @@ __metadata: languageName: node linkType: hard +"@types/express-serve-static-core@npm:^4.17.33": + version: 4.19.0 + resolution: "@types/express-serve-static-core@npm:4.19.0" + dependencies: + "@types/node": "*" + "@types/qs": "*" + "@types/range-parser": "*" + "@types/send": "*" + checksum: 39c09fcb3f61de96ed56d97273874cafe50e6675ac254af4d77014e569e4fdc29d1d0d1dd12e11f008cb9a52785b07c2801c6ba91397965392b20c75ee01fb4e + languageName: node + linkType: hard + "@types/express@npm:^4.17.1": version: 4.17.1 resolution: "@types/express@npm:4.17.1" @@ -3943,6 +3991,18 @@ __metadata: languageName: node linkType: hard +"@types/express@npm:^4.17.21": + version: 4.17.21 + resolution: "@types/express@npm:4.17.21" + dependencies: + "@types/body-parser": "*" + "@types/express-serve-static-core": ^4.17.33 + "@types/qs": "*" + "@types/serve-static": "*" + checksum: fb238298630370a7392c7abdc80f495ae6c716723e114705d7e3fb67e3850b3859bbfd29391463a3fb8c0b32051847935933d99e719c0478710f8098ee7091c5 + languageName: node + linkType: hard + "@types/graceful-fs@npm:^4.1.3": version: 4.1.9 resolution: "@types/graceful-fs@npm:4.1.9" @@ -4070,6 +4130,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^20.12.10": + version: 20.12.10 + resolution: "@types/node@npm:20.12.10" + dependencies: + undici-types: ~5.26.4 + checksum: 8c35a349f494846c1f1db4e4f8b21e7fc7f6ee14d99a0998f38699f941652be0e2a236b11f13fe404a6fec9b9a82e0e39b80ca71dafc38c742b160cc498b56c3 + languageName: node + linkType: hard + "@types/nprogress@npm:^0": version: 0.2.3 resolution: "@types/nprogress@npm:0.2.3" @@ -6102,7 +6171,7 @@ __metadata: languageName: node linkType: hard -"body-parser@npm:1.20.2": +"body-parser@npm:1.20.2, body-parser@npm:^1.20.2": version: 1.20.2 resolution: "body-parser@npm:1.20.2" dependencies: @@ -6874,6 +6943,16 @@ __metadata: languageName: node linkType: hard +"cors@npm:^2.8.5": + version: 2.8.5 + resolution: "cors@npm:2.8.5" + dependencies: + object-assign: ^4 + vary: ^1 + checksum: ced838404ccd184f61ab4fdc5847035b681c90db7ac17e428f3d81d69e2989d2b680cc254da0e2554f5ed4f8a341820a1ce3d1c16b499f6e2f47a1b9b07b5006 + languageName: node + linkType: hard + "cosmiconfig@npm:^7.0.0": version: 7.1.0 resolution: "cosmiconfig@npm:7.1.0" @@ -7317,6 +7396,13 @@ __metadata: languageName: node linkType: hard +"dotenv@npm:^16.4.5": + version: 16.4.5 + resolution: "dotenv@npm:16.4.5" + checksum: 301a12c3d44fd49888b74eb9ccf9f07a1f5df43f489e7fcb89647a2edcd84c42d6bc349dc8df099cd18f07c35c7b04685c1a4f3e6a6a9e6b30f8d48c15b7f49c + languageName: node + linkType: hard + "dotenv@npm:~16.3.1": version: 16.3.2 resolution: "dotenv@npm:16.3.2" @@ -8314,6 +8400,21 @@ __metadata: languageName: node linkType: hard +"ethers@npm:^6.12.1": + version: 6.12.1 + resolution: "ethers@npm:6.12.1" + dependencies: + "@adraffy/ens-normalize": 1.10.1 + "@noble/curves": 1.2.0 + "@noble/hashes": 1.3.2 + "@types/node": 18.15.13 + aes-js: 4.0.0-beta.5 + tslib: 2.4.0 + ws: 8.5.0 + checksum: ddf398c91f584b9e643740ec17a9c82b4a1c4ea3fb6efd00f1a043b89d1ec6f9427aa80894f75850ee805722e91b8d054bce18579a2c621226302c096774df90 + languageName: node + linkType: hard + "ethers@npm:~5.7.1": version: 5.7.2 resolution: "ethers@npm:5.7.2" @@ -8508,7 +8609,7 @@ __metadata: languageName: node linkType: hard -"express@npm:^4.13.3": +"express@npm:^4.13.3, express@npm:^4.19.2": version: 4.19.2 resolution: "express@npm:4.19.2" dependencies: @@ -12231,7 +12332,7 @@ __metadata: languageName: node linkType: hard -"object-assign@npm:^4.0.1, object-assign@npm:^4.1.1": +"object-assign@npm:^4, object-assign@npm:^4.0.1, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" checksum: fcc6e4ea8c7fe48abfbb552578b1c53e0d194086e2e6bbbf59e0a536381a292f39943c6e9628af05b5528aa5e3318bb30d6b2e53cadaf5b8fe9e12c4b69af23f @@ -14061,14 +14162,6 @@ __metadata: languageName: node linkType: hard -"shared@workspace:^, shared@workspace:packages/shared": - version: 0.0.0-use.local - resolution: "shared@workspace:packages/shared" - dependencies: - typescript: ^5.4.5 - languageName: unknown - linkType: soft - "shebang-command@npm:^2.0.0": version: 2.0.0 resolution: "shebang-command@npm:2.0.0" @@ -15692,7 +15785,7 @@ __metadata: languageName: node linkType: hard -"vary@npm:~1.1.2": +"vary@npm:^1, vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" checksum: ae0123222c6df65b437669d63dfa8c36cee20a504101b2fcd97b8bf76f91259c17f9f2b4d70a1e3c6bbcee7f51b28392833adb6b2770b23b01abec84e369660b