diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml
index 63ade28..d710f3d 100644
--- a/.github/workflows/lint.yaml
+++ b/.github/workflows/lint.yaml
@@ -27,17 +27,25 @@ jobs:
node-version: ${{ matrix.node }}
cache: yarn
+ - name: Install Foundry
+ uses: foundry-rs/foundry-toolchain@v1
+ with:
+ version: nightly
+
- name: Install dependencies
run: yarn install --immutable
- - name: Run hardhat node, deploy contracts (& generate contracts typescript output)
- run: yarn chain & yarn deploy
+ - name: Compile contracts
+ run: |
+ cd packages/hardhat
+ yarn hardhat clean
+ yarn compile
- name: Run nextjs lint
- run: yarn next:lint --max-warnings=0
+ run: yarn next:lint
- name: Check typings on nextjs
run: yarn next:check-types
- name: Run hardhat lint
- run: yarn hardhat:lint --max-warnings=0
\ No newline at end of file
+ run: yarn hardhat:lint
\ No newline at end of file
diff --git a/README.md b/README.md
index 0eec9dc..9fef62f 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,46 @@
-# fil-frame
-Quickstart your Filecoin dApp using this open source dev stack.
+# FIL-Frame 🚀
-Tutorial video: https://youtu.be/dzg7ygwAp1Q
+Welcome to FIL-Frame, a starter repository designed to help developers quickly get started with building decentralized applications (dApps) on the Filecoin network. This repository provides various integration options, including an example template using Lighthouse.
-Axelarscan (testnet): https://testnet.axelarscan.io/gmp/search?sourceChain=filecoin-2
+## Table of Contents 📚
-Filecoin Calibration faucet: https://faucet.calibnet.chainsafe-fil.io/
+- Overview
+- Getting Started
+ - Prerequisites
+ - Installation
+ - Configuration
+- Usage
+ - Deploying smart contracts
+ - Running the frontend
+- Storage Onramp Options
+ - Lighthouse
+ - Storacha
+- Project Structure
+- Contribution guidelines
+- License
-# Getting Started
+## Overview 🌐
+
+FIL-Frame is a monorepo that includes two main packages:
+
+`hardhat`: Manages the blockchain-related aspects, including smart contract development, deployment, and testing.
+
+`nextjs`: Handles the frontend and API aspects of the project using Next.js.
+
+This repository is designed to be a quickstart for developers new to the Filecoin ecosystem, providing various integration options to suit different needs.
+
+## Getting Started 🚀
+
+### Prerequisites 📋
+
+Before you begin, ensure you have the following installed:
+- [Node.js](https://nodejs.org/en/download/package-manager) (v20.9.0)
+- [Yarn](https://yarnpkg.com/getting-started/install)
+- [Hardhat](https://hardhat.org/hardhat-runner/docs/getting-started#installation)
+
+### Installation 💻
+
+### From source code
1. Clone the repository:
diff --git a/package.json b/package.json
index b32eb4e..78c2d93 100644
--- a/package.json
+++ b/package.json
@@ -14,11 +14,13 @@
"fork": "yarn workspace @fil-frame/hardhat fork",
"generate": "yarn workspace @fil-frame/hardhat run scripts/generateAccount.ts",
"flatten": "yarn workspace @fil-frame/hardhat flatten",
- "lint": "yarn workspace @fil-frame/hardhat eslint --config ./.eslintrc.json --ignore-path ./.eslintignore ./*.ts ./deploy/**/*.ts ./scripts/**/*.ts ./test/**/*.ts",
+ "next:lint": "yarn workspace @fil-frame/hardhat eslint --config ./.eslintrc.json --ignore-path ./.eslintignore ./*.ts ./deploy/**/*.ts ./scripts/**/*.ts ./test/**/*.ts",
+ "next:check-types": "yarn workspace @fil-frame/nextjs check-types",
"lint-staged": "yarn workspace @fil-frame/hardhat eslint --config ./.eslintrc.json --ignore-path ./.eslintignore",
"format": "yarn workspace @fil-frame/hardhat prettier --write ./*.ts ./deploy/**/*.ts ./scripts/**/*.ts ./test/**/*.ts",
"verify": "yarn workspace @fil-frame/hardhat etherscan-verify",
"hardhat-verify": "yarn workspace @fil-frame/hardhat verify",
+ "hardhat:lint": "yarn workspace @fil-frame/hardhat lint",
"deploy": "yarn workspace @fil-frame/hardhat deploy",
"deploy:verify": "yarn workspace @fil-frame/hardhat deploy:verify",
"chain": "yarn workspace @fil-frame/hardhat chain",
diff --git a/packages/hardhat/.eslintignore b/packages/hardhat/.eslintignore
new file mode 100644
index 0000000..faef36d
--- /dev/null
+++ b/packages/hardhat/.eslintignore
@@ -0,0 +1,8 @@
+# folders
+artifacts
+cache
+contracts
+node_modules/
+typechain-types
+# files
+**/*.json
\ No newline at end of file
diff --git a/packages/hardhat/.eslintrc.json b/packages/hardhat/.eslintrc.json
new file mode 100644
index 0000000..edb3c31
--- /dev/null
+++ b/packages/hardhat/.eslintrc.json
@@ -0,0 +1,20 @@
+{
+ "env": {
+ "node": true
+ },
+ "parser": "@typescript-eslint/parser",
+ "extends": [
+ "plugin:prettier/recommended",
+ "plugin:@typescript-eslint/recommended"
+ ],
+ "rules": {
+ "@typescript-eslint/no-unused-vars": "error",
+ "@typescript-eslint/no-explicit-any": "off",
+ "prettier/prettier": [
+ "warn",
+ {
+ "endOfLine": "auto"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/packages/hardhat/.prettierrc.json b/packages/hardhat/.prettierrc.json
new file mode 100644
index 0000000..7cdc6f4
--- /dev/null
+++ b/packages/hardhat/.prettierrc.json
@@ -0,0 +1,19 @@
+{
+ "arrowParens": "avoid",
+ "printWidth": 120,
+ "tabWidth": 2,
+ "trailingComma": "all",
+ "overrides": [
+ {
+ "files": "*.sol",
+ "options": {
+ "printWidth": 80,
+ "tabWidth": 4,
+ "useTabs": true,
+ "singleQuote": false,
+ "bracketSpacing": true,
+ "explicitTypes": "always"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/nextjs/.env.example b/packages/nextjs/.env.example
index c8d03d7..a3e56c9 100644
--- a/packages/nextjs/.env.example
+++ b/packages/nextjs/.env.example
@@ -11,3 +11,8 @@
# More info: https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables
NEXT_PUBLIC_ALCHEMY_API_KEY=
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=
+<<<<<<< HEAD
+=======
+LIGHTHOUSE_API_KEY=
+WEB3_STORAGE_API_TOKEN=
+>>>>>>> 993d799 (Fix CI pipeline)
diff --git a/packages/nextjs/app/blockexplorer/_components/AddressComponent.tsx b/packages/nextjs/app/blockexplorer/_components/AddressComponent.tsx
index fb975a4..3e60921 100644
--- a/packages/nextjs/app/blockexplorer/_components/AddressComponent.tsx
+++ b/packages/nextjs/app/blockexplorer/_components/AddressComponent.tsx
@@ -19,10 +19,10 @@ export const AddressComponent = ({
diff --git a/packages/nextjs/app/blockexplorer/_components/ContractTabs.tsx b/packages/nextjs/app/blockexplorer/_components/ContractTabs.tsx
index f19588c..8a2b548 100644
--- a/packages/nextjs/app/blockexplorer/_components/ContractTabs.tsx
+++ b/packages/nextjs/app/blockexplorer/_components/ContractTabs.tsx
@@ -32,7 +32,7 @@ export const ContractTabs = ({ address, contractData }: PageProps) => {
useEffect(() => {
const checkIsContract = async () => {
- const contractCode = await publicClient.getBytecode({ address: address });
+ const contractCode = await publicClient.getBytecode({ address: address as `0x${string}` });
setIsContract(contractCode !== undefined && contractCode !== "0x");
};
@@ -85,8 +85,8 @@ export const ContractTabs = ({ address, contractData }: PageProps) => {
{activeTab === "code" && contractData && (
)}
- {activeTab === "storage" &&
}
- {activeTab === "logs" &&
}
+ {activeTab === "storage" &&
}
+ {activeTab === "logs" &&
}
>
);
};
diff --git a/packages/nextjs/app/dealClient/_components/WriteContractFunctionForm.tsx b/packages/nextjs/app/dealClient/_components/WriteContractFunctionForm.tsx
new file mode 100644
index 0000000..7c00ece
--- /dev/null
+++ b/packages/nextjs/app/dealClient/_components/WriteContractFunctionForm.tsx
@@ -0,0 +1,207 @@
+"use client";
+
+import { useMemo, useState } from "react";
+import { useEffect } from "react";
+import { DealInputs, createDealObject, getDefaultDealInputs } from "../utils";
+import { Abi, AbiFunction } from "abitype";
+import { JsonView, allExpanded, darkStyles } from "react-json-view-lite";
+import "react-json-view-lite/dist/index.css";
+import { Address, TransactionReceipt } from "viem";
+import { useAccount, useWaitForTransactionReceipt, useWriteContract } from "wagmi";
+import {
+ ContractInput,
+ TxReceipt,
+ getFunctionInputKey,
+ getInitialFormState,
+ getParsedContractFunctionArgs,
+ transformAbiFunction,
+} from "~~/app/debug/_components/contract";
+import { InheritanceTooltip } from "~~/app/debug/_components/contract/InheritanceTooltip";
+import { IntegerInput } from "~~/components/fil-frame";
+import { useTransactor } from "~~/hooks/fil-frame";
+import { useTargetNetwork } from "~~/hooks/fil-frame/useTargetNetwork";
+import { DealInfoData } from "~~/hooks/lighthouse/useUpload";
+
+type WriteContractFunctionProps = {
+ abi: Abi;
+ abiFunction: AbiFunction;
+ onChange: () => void;
+ contractAddress: Address;
+ inheritedFrom?: string;
+ dealParams?: DealInfoData;
+};
+
+export const WriteContractFunctionForm = ({
+ abi,
+ abiFunction,
+ onChange,
+ contractAddress,
+ inheritedFrom,
+ dealParams,
+}: WriteContractFunctionProps) => {
+ const [form, setForm] = useState
>(() => getInitialFormState(abiFunction));
+ const [txValue, setTxValue] = useState("");
+ const { chain } = useAccount();
+ const writeTxn = useTransactor();
+ const { targetNetwork } = useTargetNetwork();
+ const writeDisabled = !chain || chain?.id !== targetNetwork.id;
+
+ const { data: result, isPending, writeContractAsync } = useWriteContract();
+
+ const handleWrite = async () => {
+ if (writeContractAsync) {
+ try {
+ const makeWriteWithParams = () =>
+ writeContractAsync({
+ address: contractAddress,
+ functionName: abiFunction.name,
+ abi: abi,
+ args: getParsedContractFunctionArgs(form),
+ value: BigInt(txValue),
+ });
+ await writeTxn(makeWriteWithParams);
+ onChange();
+ } catch (e: any) {
+ console.error("⚡️ ~ file: WriteOnlyFunctionForm.tsx:handleWrite ~ error", e);
+ }
+ }
+ };
+
+ const [displayedTxResult, setDisplayedTxResult] = useState();
+ const { data: txResult } = useWaitForTransactionReceipt({
+ hash: result,
+ });
+ useEffect(() => {
+ setDisplayedTxResult(txResult);
+ }, [txResult]);
+
+ // TODO use `useMemo` to optimize also update in ReadOnlyFunctionForm
+ const transformedFunction = transformAbiFunction(abiFunction);
+ const inputs = transformedFunction.inputs.map((input, inputIndex) => {
+ const key = getFunctionInputKey(abiFunction.name, input, inputIndex);
+ return (
+ {
+ setDisplayedTxResult(undefined);
+ setForm(updatedFormValue);
+ }}
+ form={form}
+ stateObjectKey={key}
+ paramType={input}
+ />
+ );
+ });
+ const zeroInputs = inputs.length === 0 && abiFunction.stateMutability !== "payable";
+ const defaultDealInputs = useMemo(() => getDefaultDealInputs(dealParams), [dealParams]);
+ const [dealInputs, setDealInputs] = useState(defaultDealInputs);
+
+ const setFormValue = () => {
+ const dealObject = createDealObject(dealInputs);
+ setForm(prevForm => ({
+ ...prevForm,
+ ["makeDealProposal_deal_struct DealRequest_tuple"]: JSON.stringify(dealObject),
+ }));
+ };
+
+ useEffect(() => {
+ setFormValue();
+ }, [dealParams]);
+ const handleInputChange = (e: React.ChangeEvent) => {
+ const { name, value, type, checked } = e.target;
+ setDealInputs(prevInputs => ({
+ ...prevInputs,
+ [name]: type === "checkbox" ? checked : value,
+ }));
+ };
+ return (
+
+
+
+ {abiFunction.name}
+
+
+
+
+
+
+
+ {abiFunction.stateMutability === "payable" ? (
+
+
+ payable value
+ wei
+
+
{
+ setDisplayedTxResult(undefined);
+ setTxValue(updatedTxValue);
+ }}
+ placeholder="value (wei)"
+ />
+
+ ) : null}
+
+ {!zeroInputs && (
+
+ {displayedTxResult ? : null}
+
+ )}
+
+
+
+
+
+ {zeroInputs && txResult ? (
+
+
+
+ ) : null}
+
+ );
+};
+
+const DealForm = ({
+ dealInputs,
+ handleInputChange,
+}: {
+ dealInputs: DealInputs;
+ handleInputChange: (e: React.ChangeEvent) => void;
+}) => {
+ const handleDurationChange = (e: React.ChangeEvent) => {
+ const months = Math.max(7, Math.min(36, Number(e.target.value)));
+ const endEpoch = dealInputs.start_epoch + months * 43200;
+ handleInputChange({
+ ...e,
+ // @ts-ignore: number to string conversion
+ target: { ...e.target, name: "end_epoch", value: endEpoch },
+ });
+ };
+
+ return (
+
+ );
+};
diff --git a/packages/nextjs/components/assets/BlueBox.tsx b/packages/nextjs/components/assets/BlueBox.tsx
new file mode 100644
index 0000000..1e067dd
--- /dev/null
+++ b/packages/nextjs/components/assets/BlueBox.tsx
@@ -0,0 +1,34 @@
+export default function BlueBox(props: any) {
+ return (
+
+ );
+}
diff --git a/packages/nextjs/components/assets/Logo.tsx b/packages/nextjs/components/assets/Logo.tsx
new file mode 100644
index 0000000..0344351
--- /dev/null
+++ b/packages/nextjs/components/assets/Logo.tsx
@@ -0,0 +1,34 @@
+export default function Logo(props: any) {
+ return (
+
+ );
+}
diff --git a/packages/nextjs/components/assets/LogoLandscape.tsx b/packages/nextjs/components/assets/LogoLandscape.tsx
new file mode 100644
index 0000000..cd2a0b9
--- /dev/null
+++ b/packages/nextjs/components/assets/LogoLandscape.tsx
@@ -0,0 +1,39 @@
+export default function LogoLandscape(props: any) {
+ return (
+
+ );
+}
diff --git a/packages/nextjs/components/assets/LogoWithText.tsx b/packages/nextjs/components/assets/LogoWithText.tsx
new file mode 100644
index 0000000..e520f9a
--- /dev/null
+++ b/packages/nextjs/components/assets/LogoWithText.tsx
@@ -0,0 +1,34 @@
+export default function LogoWithText(props: any) {
+ return (
+
+ );
+}
diff --git a/packages/nextjs/components/assets/RedArrow.tsx b/packages/nextjs/components/assets/RedArrow.tsx
new file mode 100644
index 0000000..8cb636d
--- /dev/null
+++ b/packages/nextjs/components/assets/RedArrow.tsx
@@ -0,0 +1,39 @@
+export default function RedArrow(props: any) {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/packages/nextjs/components/assets/RedPopBox.tsx b/packages/nextjs/components/assets/RedPopBox.tsx
new file mode 100644
index 0000000..68903db
--- /dev/null
+++ b/packages/nextjs/components/assets/RedPopBox.tsx
@@ -0,0 +1,34 @@
+export default function RedPopBox(props: any) {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/packages/nextjs/components/assets/YellowCircle.tsx b/packages/nextjs/components/assets/YellowCircle.tsx
new file mode 100644
index 0000000..a7b25bd
--- /dev/null
+++ b/packages/nextjs/components/assets/YellowCircle.tsx
@@ -0,0 +1,34 @@
+export default function YellowCircle(props: any) {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/packages/nextjs/components/svg/Logo.tsx b/packages/nextjs/components/svg/Logo.tsx
new file mode 100644
index 0000000..0344351
--- /dev/null
+++ b/packages/nextjs/components/svg/Logo.tsx
@@ -0,0 +1,34 @@
+export default function Logo(props: any) {
+ return (
+
+ );
+}
diff --git a/packages/nextjs/components/svg/LogoLandscape.tsx b/packages/nextjs/components/svg/LogoLandscape.tsx
new file mode 100644
index 0000000..5cc5f94
--- /dev/null
+++ b/packages/nextjs/components/svg/LogoLandscape.tsx
@@ -0,0 +1,31 @@
+export default function LogoLandscape(props: any) {
+ return (
+
+ );
+}
diff --git a/packages/nextjs/hooks/lighthouse/index.ts b/packages/nextjs/hooks/lighthouse/index.ts
new file mode 100644
index 0000000..a030532
--- /dev/null
+++ b/packages/nextjs/hooks/lighthouse/index.ts
@@ -0,0 +1,58 @@
+import { generate, recoverKey, recoverShards, saveShards } from "@lighthouse-web3/kavach";
+import lighthouse from "@lighthouse-web3/sdk";
+
+type accessControlConditions = any;
+
+export const uploadFilesEncrypted = async (
+ files: File[],
+ apiKey: string,
+ userAddress: string,
+ jwt: string,
+ conditions?: accessControlConditions[],
+ aggregator?: string,
+) => {
+ let cid = await _uploadFilesEncrypted(files, apiKey, userAddress, jwt);
+ if (conditions?.length === 0 || !conditions || !aggregator) {
+ return cid;
+ }
+ cid = await applyAccessConditions(cid, userAddress, jwt, conditions, aggregator);
+ return cid;
+};
+
+export const uploadFiles = async (files: File[], apiKey: string) => {
+ const output = await lighthouse.upload(files, apiKey);
+ return output.data.Hash;
+};
+
+/* Deploy file along with encryption */
+export const _uploadFilesEncrypted = async (files: File[], apiKey: string, userAddress: string, jwt: string) => {
+ const output = await lighthouse.uploadEncrypted(files, apiKey, userAddress, jwt);
+ console.log("output", output.data[0].Hash);
+
+ const { keyShards } = await generate();
+
+ await saveShards(userAddress, output.data[0].Hash, jwt, keyShards);
+
+ return output.data[0].Hash;
+};
+
+export const decrypt = async (cid: string, userAddress: string, jwt: string) => {
+ let decrypted;
+ const { shards } = await recoverShards(userAddress, cid, jwt, 3);
+ try {
+ const { masterKey } = await recoverKey(shards);
+ decrypted = await lighthouse.decryptFile(cid, masterKey);
+ } catch {}
+ return decrypted;
+};
+
+export const applyAccessConditions = async (
+ cid: string,
+ address: string,
+ jwt: string,
+ conditions: accessControlConditions[],
+ aggregator: string,
+) => {
+ const response = await lighthouse.applyAccessCondition(address, cid, jwt, conditions, aggregator);
+ return response.data.cid;
+};