tr]:last:border-b-0",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
+ return (
+
+ )
+}
+
+function TableHead({ className, ...props }: React.ComponentProps<"th">) {
+ return (
+ [role=checkbox]]:translate-y-[2px]",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function TableCell({ className, ...props }: React.ComponentProps<"td">) {
+ return (
+ | [role=checkbox]]:translate-y-[2px]",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function TableCaption({
+ className,
+ ...props
+}: React.ComponentProps<"caption">) {
+ return (
+
+ )
+}
+
+export {
+ Table,
+ TableHeader,
+ TableBody,
+ TableFooter,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableCaption,
+}
diff --git a/assignments/7-block-explorer/block-explorer/eslint.config.mjs b/assignments/7-block-explorer/block-explorer/eslint.config.mjs
new file mode 100644
index 00000000..05e726d1
--- /dev/null
+++ b/assignments/7-block-explorer/block-explorer/eslint.config.mjs
@@ -0,0 +1,18 @@
+import { defineConfig, globalIgnores } from "eslint/config";
+import nextVitals from "eslint-config-next/core-web-vitals";
+import nextTs from "eslint-config-next/typescript";
+
+const eslintConfig = defineConfig([
+ ...nextVitals,
+ ...nextTs,
+ // Override default ignores of eslint-config-next.
+ globalIgnores([
+ // Default ignores of eslint-config-next:
+ ".next/**",
+ "out/**",
+ "build/**",
+ "next-env.d.ts",
+ ]),
+]);
+
+export default eslintConfig;
diff --git a/assignments/7-block-explorer/block-explorer/lib/utils.ts b/assignments/7-block-explorer/block-explorer/lib/utils.ts
new file mode 100644
index 00000000..bd0c391d
--- /dev/null
+++ b/assignments/7-block-explorer/block-explorer/lib/utils.ts
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from "clsx"
+import { twMerge } from "tailwind-merge"
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs))
+}
diff --git a/assignments/7-block-explorer/block-explorer/next.config.ts b/assignments/7-block-explorer/block-explorer/next.config.ts
new file mode 100644
index 00000000..e9ffa308
--- /dev/null
+++ b/assignments/7-block-explorer/block-explorer/next.config.ts
@@ -0,0 +1,7 @@
+import type { NextConfig } from "next";
+
+const nextConfig: NextConfig = {
+ /* config options here */
+};
+
+export default nextConfig;
diff --git a/assignments/7-block-explorer/block-explorer/package.json b/assignments/7-block-explorer/block-explorer/package.json
new file mode 100644
index 00000000..cd89fc42
--- /dev/null
+++ b/assignments/7-block-explorer/block-explorer/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "block-explorer",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "eslint"
+ },
+ "dependencies": {
+ "axios": "^1.13.4",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "lucide-react": "^0.563.0",
+ "next": "16.1.6",
+ "react": "19.2.3",
+ "react-dom": "19.2.3",
+ "react-icons": "^5.5.0",
+ "tailwind-merge": "^3.4.0"
+ },
+ "devDependencies": {
+ "@tailwindcss/postcss": "^4",
+ "@types/node": "^20",
+ "@types/react": "^19",
+ "@types/react-dom": "^19",
+ "eslint": "^9",
+ "eslint-config-next": "16.1.6",
+ "tailwindcss": "^4",
+ "tw-animate-css": "^1.4.0",
+ "typescript": "^5"
+ }
+}
diff --git a/assignments/7-block-explorer/block-explorer/postcss.config.mjs b/assignments/7-block-explorer/block-explorer/postcss.config.mjs
new file mode 100644
index 00000000..61e36849
--- /dev/null
+++ b/assignments/7-block-explorer/block-explorer/postcss.config.mjs
@@ -0,0 +1,7 @@
+const config = {
+ plugins: {
+ "@tailwindcss/postcss": {},
+ },
+};
+
+export default config;
diff --git a/assignments/7-block-explorer/block-explorer/public/file.svg b/assignments/7-block-explorer/block-explorer/public/file.svg
new file mode 100644
index 00000000..004145cd
--- /dev/null
+++ b/assignments/7-block-explorer/block-explorer/public/file.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assignments/7-block-explorer/block-explorer/public/globe.svg b/assignments/7-block-explorer/block-explorer/public/globe.svg
new file mode 100644
index 00000000..567f17b0
--- /dev/null
+++ b/assignments/7-block-explorer/block-explorer/public/globe.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assignments/7-block-explorer/block-explorer/public/next.svg b/assignments/7-block-explorer/block-explorer/public/next.svg
new file mode 100644
index 00000000..5174b28c
--- /dev/null
+++ b/assignments/7-block-explorer/block-explorer/public/next.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assignments/7-block-explorer/block-explorer/public/vercel.svg b/assignments/7-block-explorer/block-explorer/public/vercel.svg
new file mode 100644
index 00000000..77053960
--- /dev/null
+++ b/assignments/7-block-explorer/block-explorer/public/vercel.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assignments/7-block-explorer/block-explorer/public/window.svg b/assignments/7-block-explorer/block-explorer/public/window.svg
new file mode 100644
index 00000000..b2b2a44f
--- /dev/null
+++ b/assignments/7-block-explorer/block-explorer/public/window.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assignments/7-block-explorer/block-explorer/tsconfig.json b/assignments/7-block-explorer/block-explorer/tsconfig.json
new file mode 100644
index 00000000..3a13f90a
--- /dev/null
+++ b/assignments/7-block-explorer/block-explorer/tsconfig.json
@@ -0,0 +1,34 @@
+{
+ "compilerOptions": {
+ "target": "ES2017",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "react-jsx",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": ["./*"]
+ }
+ },
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts",
+ ".next/dev/types/**/*.ts",
+ "**/*.mts"
+ ],
+ "exclude": ["node_modules"]
+}
diff --git a/crowd/.gitignore b/crowd/.gitignore
new file mode 100644
index 00000000..991a319e
--- /dev/null
+++ b/crowd/.gitignore
@@ -0,0 +1,20 @@
+# Node modules
+/node_modules
+
+# Compilation output
+/dist
+
+# pnpm deploy output
+/bundle
+
+# Hardhat Build Artifacts
+/artifacts
+
+# Hardhat compilation (v2) support directory
+/cache
+
+# Typechain output
+/types
+
+# Hardhat coverage reports
+/coverage
diff --git a/crowd/README.md b/crowd/README.md
new file mode 100644
index 00000000..a6893ebd
--- /dev/null
+++ b/crowd/README.md
@@ -0,0 +1,89 @@
+CrowdFunding Smart Contract
+
+A simple crowdfunding smart contract written in Solidity (^0.8.28).
+
+Users can create campaigns, contribute ETH, request refunds if the goal is not met, and creators can withdraw funds if the campaign succeeds.
+
+How It Works
+1️⃣ Create Campaign
+
+Anyone can create a campaign by providing:
+
+Title
+
+Funding goal (in wei)
+
+Duration (in seconds)
+
+The campaign becomes ACTIVE immediately.
+
+2️⃣ Contribute
+
+Users send ETH to a campaign using contribute().
+
+Must send ETH
+
+Campaign must still be active
+
+Cannot contribute after deadline
+
+If user sends more than needed, extra ETH is refunded automatically
+
+If goal is reached → campaign becomes SUCCESSFUL
+
+3️⃣ Refund
+
+If:
+
+Deadline has passed
+
+Goal was NOT reached
+
+Contributors can call requestRefund() to get their money back.
+
+Campaign becomes UNSUCCEEDED.
+
+4️⃣ Withdraw (Creator Only)
+
+If campaign is SUCCESSFUL,
+the campaign creator can call withdraw() to collect the funds.
+
+Funds can only be withdrawn once.
+
+Campaign Status
+
+ACTIVE
+
+SUCCESSFUL
+
+UNSUCCEEDED
+
+DELETED
+
+Security
+
+Uses nonReentrant modifier to prevent reentrancy attacks
+
+Uses safe call for ETH transfers
+
+Prevents double withdrawals
+
+Main Functions
+
+createCampaign()
+
+contribute()
+
+requestRefund()
+
+withdraw()
+
+Use Cases
+
+Fundraising
+
+Charity campaigns
+
+Startup funding
+
+Community projects
\ No newline at end of file
diff --git a/crowd/contracts/Counter.sol b/crowd/contracts/Counter.sol
new file mode 100644
index 00000000..8d00cb7c
--- /dev/null
+++ b/crowd/contracts/Counter.sol
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.28;
+
+contract Counter {
+ uint public x;
+
+ event Increment(uint by);
+
+ function inc() public {
+ x++;
+ emit Increment(1);
+ }
+
+ function incBy(uint by) public {
+ require(by > 0, "incBy: increment should be positive");
+ x += by;
+ emit Increment(by);
+ }
+}
diff --git a/crowd/contracts/Counter.t.sol b/crowd/contracts/Counter.t.sol
new file mode 100644
index 00000000..ac71d5b8
--- /dev/null
+++ b/crowd/contracts/Counter.t.sol
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.28;
+
+import {Counter} from "./Counter.sol";
+import {Test} from "forge-std/Test.sol";
+
+// Solidity tests are compatible with foundry, so they
+// use the same syntax and offer the same functionality.
+
+contract CounterTest is Test {
+ Counter counter;
+
+ function setUp() public {
+ counter = new Counter();
+ }
+
+ function test_InitialValue() public view {
+ require(counter.x() == 0, "Initial value should be 0");
+ }
+
+ function testFuzz_Inc(uint8 x) public {
+ for (uint8 i = 0; i < x; i++) {
+ counter.inc();
+ }
+ require(counter.x() == x, "Value after calling inc x times should be x");
+ }
+
+ function test_IncByZero() public {
+ vm.expectRevert();
+ counter.incBy(0);
+ }
+}
diff --git a/crowd/contracts/crowd.sol b/crowd/contracts/crowd.sol
new file mode 100644
index 00000000..84a86bb1
--- /dev/null
+++ b/crowd/contracts/crowd.sol
@@ -0,0 +1,166 @@
+
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.28;
+
+contract CrowdFunding{
+
+ address private immutable owner;
+
+
+ uint private nextId;
+ bool private locked;
+
+ modifier nonReentrant() {
+ require(!locked, "Reentrant call");
+ locked = true;
+ _;
+ locked = false;
+ }
+
+ struct Campaign {
+ uint id;
+ string title;
+ address creator;
+ uint goal;
+ uint deadline;
+ uint amountRaised;
+ STATUS status;
+ uint startsAt;
+ uint endsAt;
+ uint totalContributions;
+ bool claimed;
+ mapping(address => uint) contributions;
+ //uint[] contributionAmounts;
+ }
+
+ enum STATUS {
+ ACTIVE,
+ DELETED,
+ SUCCESSFUL,
+ UNSUCCEEDED
+ }
+
+ mapping(uint => Campaign) public campaigns;
+ uint public campaignCount;
+
+ event CampaignCreated(uint indexed campaignId, address campaignCreator, string title, STATUS status);
+ event CampaignDeleted(uint indexed campaignId, address campaignCreator, STATUS status);
+ event ContributionMade(uint indexed campaignId, address contributor, uint amount);
+ event RefundMade(uint indexed campaignId, address contributor, uint amount);
+
+ function createCampaign(
+ string memory _title,
+ uint _goal,
+ uint _duration)public {
+ require(bytes(_title).length > 0, "Title must not be empty");
+ require(_goal > 0, "Goal must be greater than zero");
+ require(_duration > 0, "Ends time must be greater than zero");
+
+ campaignCount++;
+ nextId++;
+ Campaign storage campaign = campaigns[campaignCount];
+ campaign.id = nextId;
+ campaign.creator = msg.sender;
+ campaign.title =_title;
+ campaign.goal = _goal;
+ campaign.startsAt = block.timestamp;
+ campaign.status = STATUS.ACTIVE;
+ campaign.endsAt = block.timestamp + _duration;
+
+ emit CampaignCreated(nextId , msg.sender, _title, STATUS.ACTIVE);
+ }
+
+
+ function contribute(uint _id)
+ public
+ payable
+ nonReentrant
+{
+ Campaign storage campaign = campaigns[_id];
+
+ require(msg.value > 0, "Must send ETH");
+ require(block.timestamp < campaign.endsAt, "Campaign ended");
+ require(campaign.status == STATUS.ACTIVE, "Not active");
+
+ uint remaining = campaign.goal - campaign.totalContributions;
+
+ uint acceptedAmount = msg.value;
+
+ if (msg.value > remaining) {
+ acceptedAmount = remaining;
+
+ uint excess = msg.value - remaining;
+
+ (bool success, ) = payable(msg.sender).call{value: excess}("");
+ require(success, "Refund failed");
+ }
+
+ campaign.totalContributions += acceptedAmount;
+ campaign.contributions[msg.sender] += acceptedAmount;
+
+ if (campaign.totalContributions >= campaign.goal) {
+ campaign.status = STATUS.SUCCESSFUL;
+ }
+
+ emit ContributionMade(_id, msg.sender, acceptedAmount);
+}
+
+ function requestRefund(uint _id)
+ public
+ nonReentrant
+{
+ Campaign storage campaign = campaigns[_id];
+
+
+ if (
+ block.timestamp >= campaign.endsAt &&
+ campaign.totalContributions < campaign.goal
+ ) {
+ campaign.status = STATUS.UNSUCCEEDED;
+ }
+
+ require(campaign.status == STATUS.UNSUCCEEDED, "Refund not allowed");
+
+ uint contributedAmount = campaign.contributions[msg.sender];
+ require(contributedAmount > 0, "No contribution found");
+
+
+ campaign.contributions[msg.sender] = 0;
+ campaign.totalContributions -= contributedAmount;
+
+ (bool success, ) = payable(msg.sender).call{value: contributedAmount}("");
+ require(success, "Refund failed");
+
+ emit RefundMade(_id, msg.sender, contributedAmount);
+}
+
+ function withdraw(uint _id)
+ public
+ nonReentrant
+{
+ Campaign storage campaign = campaigns[_id];
+
+
+ require(msg.sender == campaign.creator, "Not campaign creator");
+
+
+ require(campaign.status == STATUS.SUCCESSFUL, "Campaign not successful");
+
+
+ require(!campaign.claimed, "Funds already withdrawn");
+
+ uint amount = campaign.totalContributions;
+ require(amount > 0, "No funds to withdraw");
+
+ campaign.claimed = true;
+
+ (bool success, ) = payable(msg.sender).call{value: amount}("");
+ require(success, "Withdrawal failed");
+
+ emit RefundMade(_id, msg.sender, amount);
+}
+
+
+
+}
+
diff --git a/crowd/hardhat.config.ts b/crowd/hardhat.config.ts
new file mode 100644
index 00000000..7092b852
--- /dev/null
+++ b/crowd/hardhat.config.ts
@@ -0,0 +1,38 @@
+import hardhatToolboxMochaEthersPlugin from "@nomicfoundation/hardhat-toolbox-mocha-ethers";
+import { configVariable, defineConfig } from "hardhat/config";
+
+export default defineConfig({
+ plugins: [hardhatToolboxMochaEthersPlugin],
+ solidity: {
+ profiles: {
+ default: {
+ version: "0.8.28",
+ },
+ production: {
+ version: "0.8.28",
+ settings: {
+ optimizer: {
+ enabled: true,
+ runs: 200,
+ },
+ },
+ },
+ },
+ },
+ networks: {
+ hardhatMainnet: {
+ type: "edr-simulated",
+ chainType: "l1",
+ },
+ hardhatOp: {
+ type: "edr-simulated",
+ chainType: "op",
+ },
+ sepolia: {
+ type: "http",
+ chainType: "l1",
+ url: configVariable("SEPOLIA_RPC_URL"),
+ accounts: [configVariable("SEPOLIA_PRIVATE_KEY")],
+ },
+ },
+});
diff --git a/crowd/ignition/modules/Counter.ts b/crowd/ignition/modules/Counter.ts
new file mode 100644
index 00000000..042e61c8
--- /dev/null
+++ b/crowd/ignition/modules/Counter.ts
@@ -0,0 +1,9 @@
+import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
+
+export default buildModule("CounterModule", (m) => {
+ const counter = m.contract("Counter");
+
+ m.call(counter, "incBy", [5n]);
+
+ return { counter };
+});
diff --git a/crowd/package.json b/crowd/package.json
new file mode 100644
index 00000000..ababa8dc
--- /dev/null
+++ b/crowd/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "crowd",
+ "version": "1.0.0",
+ "type": "module",
+ "devDependencies": {
+ "@nomicfoundation/hardhat-ethers": "^4.0.4",
+ "@nomicfoundation/hardhat-ignition": "^3.0.7",
+ "@nomicfoundation/hardhat-toolbox-mocha-ethers": "^3.0.2",
+ "@types/chai": "^4.3.20",
+ "@types/chai-as-promised": "^8.0.2",
+ "@types/mocha": "^10.0.10",
+ "@types/node": "^22.19.11",
+ "chai": "^5.3.3",
+ "ethers": "^6.16.0",
+ "forge-std": "github:foundry-rs/forge-std#v1.9.4",
+ "hardhat": "^3.1.8",
+ "mocha": "^11.7.5",
+ "typescript": "~5.8.0"
+ }
+}
diff --git a/crowd/scripts/send-op-tx.ts b/crowd/scripts/send-op-tx.ts
new file mode 100644
index 00000000..c10a2360
--- /dev/null
+++ b/crowd/scripts/send-op-tx.ts
@@ -0,0 +1,22 @@
+import { network } from "hardhat";
+
+const { ethers } = await network.connect({
+ network: "hardhatOp",
+ chainType: "op",
+});
+
+console.log("Sending transaction using the OP chain type");
+
+const [sender] = await ethers.getSigners();
+
+console.log("Sending 1 wei from", sender.address, "to itself");
+
+console.log("Sending L2 transaction");
+const tx = await sender.sendTransaction({
+ to: sender.address,
+ value: 1n,
+});
+
+await tx.wait();
+
+console.log("Transaction sent successfully");
diff --git a/crowd/test/Counter.ts b/crowd/test/Counter.ts
new file mode 100644
index 00000000..f8c38986
--- /dev/null
+++ b/crowd/test/Counter.ts
@@ -0,0 +1,36 @@
+import { expect } from "chai";
+import { network } from "hardhat";
+
+const { ethers } = await network.connect();
+
+describe("Counter", function () {
+ it("Should emit the Increment event when calling the inc() function", async function () {
+ const counter = await ethers.deployContract("Counter");
+
+ await expect(counter.inc()).to.emit(counter, "Increment").withArgs(1n);
+ });
+
+ it("The sum of the Increment events should match the current value", async function () {
+ const counter = await ethers.deployContract("Counter");
+ const deploymentBlockNumber = await ethers.provider.getBlockNumber();
+
+ // run a series of increments
+ for (let i = 1; i <= 10; i++) {
+ await counter.incBy(i);
+ }
+
+ const events = await counter.queryFilter(
+ counter.filters.Increment(),
+ deploymentBlockNumber,
+ "latest",
+ );
+
+ // check that the aggregated events match the current value
+ let total = 0n;
+ for (const event of events) {
+ total += event.args.by;
+ }
+
+ expect(await counter.x()).to.equal(total);
+ });
+});
diff --git a/crowd/tsconfig.json b/crowd/tsconfig.json
new file mode 100644
index 00000000..9b1380cc
--- /dev/null
+++ b/crowd/tsconfig.json
@@ -0,0 +1,13 @@
+/* Based on https://github.com/tsconfig/bases/blob/501da2bcd640cf95c95805783e1012b992338f28/bases/node22.json */
+{
+ "compilerOptions": {
+ "lib": ["es2023"],
+ "module": "node16",
+ "target": "es2022",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "moduleResolution": "node16",
+ "outDir": "dist"
+ }
+}
diff --git a/excrowv1/.gitignore b/excrowv1/.gitignore
new file mode 100644
index 00000000..991a319e
--- /dev/null
+++ b/excrowv1/.gitignore
@@ -0,0 +1,20 @@
+# Node modules
+/node_modules
+
+# Compilation output
+/dist
+
+# pnpm deploy output
+/bundle
+
+# Hardhat Build Artifacts
+/artifacts
+
+# Hardhat compilation (v2) support directory
+/cache
+
+# Typechain output
+/types
+
+# Hardhat coverage reports
+/coverage
diff --git a/excrowv1/README.md b/excrowv1/README.md
new file mode 100644
index 00000000..6d3b5b8f
--- /dev/null
+++ b/excrowv1/README.md
@@ -0,0 +1,90 @@
+Escrow Smart Contract
+
+A simple escrow system written in Solidity (^0.8.28).
+
+It allows a buyer to deposit ETH, and the seller receives payment only after delivery is confirmed.
+
+The project also includes an EscrowFactory contract to create multiple escrow contracts.
+
+How It Works
+1️⃣ Create Escrow
+
+Using EscrowFactory, a new escrow contract is created with:
+
+Buyer address
+
+Seller address
+
+Each escrow is a separate contract.
+
+2️⃣ Buyer Deposits ETH
+
+deposit()
+
+Only buyer can call
+
+Must send ETH
+
+Escrow status changes from PENDING → PAID
+
+3️⃣ Confirm Delivery
+
+confirmDelivery()
+
+Only buyer can call
+
+ETH is sent to seller
+
+Status changes to COMPLETE
+
+4️⃣ Refund Buyer
+
+refundBuyer()
+
+Only buyer can call
+
+ETH is returned to buyer
+
+Status changes to REFUNDED
+
+Escrow Status
+
+PENDING → Waiting for payment
+
+PAID → Buyer has deposited ETH
+
+COMPLETE → Seller has been paid
+
+REFUNDED → Buyer received refund
+
+EscrowFactory
+
+The factory contract allows you to:
+
+Create new escrow contracts
+
+Store all escrow addresses
+
+Get total number of escrows
+
+Retrieve all escrows
+
+Simple Flow
+
+Create Escrow → Buyer deposits →
+Either:
+
+Confirm delivery → Seller gets paid 💰
+OR
+
+Refund → Buyer gets money back
+
+Use Cases
+
+Online purchases
+
+Freelance payments
+
+Peer-to-peer transactions
+
+Secure ETH transfers between two parties
\ No newline at end of file
diff --git a/excrowv1/contracts/Counter.sol b/excrowv1/contracts/Counter.sol
new file mode 100644
index 00000000..8d00cb7c
--- /dev/null
+++ b/excrowv1/contracts/Counter.sol
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.28;
+
+contract Counter {
+ uint public x;
+
+ event Increment(uint by);
+
+ function inc() public {
+ x++;
+ emit Increment(1);
+ }
+
+ function incBy(uint by) public {
+ require(by > 0, "incBy: increment should be positive");
+ x += by;
+ emit Increment(by);
+ }
+}
diff --git a/excrowv1/contracts/Counter.t.sol b/excrowv1/contracts/Counter.t.sol
new file mode 100644
index 00000000..ac71d5b8
--- /dev/null
+++ b/excrowv1/contracts/Counter.t.sol
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.28;
+
+import {Counter} from "./Counter.sol";
+import {Test} from "forge-std/Test.sol";
+
+// Solidity tests are compatible with foundry, so they
+// use the same syntax and offer the same functionality.
+
+contract CounterTest is Test {
+ Counter counter;
+
+ function setUp() public {
+ counter = new Counter();
+ }
+
+ function test_InitialValue() public view {
+ require(counter.x() == 0, "Initial value should be 0");
+ }
+
+ function testFuzz_Inc(uint8 x) public {
+ for (uint8 i = 0; i < x; i++) {
+ counter.inc();
+ }
+ require(counter.x() == x, "Value after calling inc x times should be x");
+ }
+
+ function test_IncByZero() public {
+ vm.expectRevert();
+ counter.incBy(0);
+ }
+}
diff --git a/excrowv1/contracts/Excrow1.sol b/excrowv1/contracts/Excrow1.sol
new file mode 100644
index 00000000..e69de29b
diff --git a/excrowv1/crowd/.gitignore b/excrowv1/crowd/.gitignore
new file mode 100644
index 00000000..991a319e
--- /dev/null
+++ b/excrowv1/crowd/.gitignore
@@ -0,0 +1,20 @@
+# Node modules
+/node_modules
+
+# Compilation output
+/dist
+
+# pnpm deploy output
+/bundle
+
+# Hardhat Build Artifacts
+/artifacts
+
+# Hardhat compilation (v2) support directory
+/cache
+
+# Typechain output
+/types
+
+# Hardhat coverage reports
+/coverage
diff --git a/excrowv1/crowd/README.md b/excrowv1/crowd/README.md
new file mode 100644
index 00000000..a6893ebd
--- /dev/null
+++ b/excrowv1/crowd/README.md
@@ -0,0 +1,89 @@
+CrowdFunding Smart Contract
+
+A simple crowdfunding smart contract written in Solidity (^0.8.28).
+
+Users can create campaigns, contribute ETH, request refunds if the goal is not met, and creators can withdraw funds if the campaign succeeds.
+
+How It Works
+1️⃣ Create Campaign
+
+Anyone can create a campaign by providing:
+
+Title
+
+Funding goal (in wei)
+
+Duration (in seconds)
+
+The campaign becomes ACTIVE immediately.
+
+2️⃣ Contribute
+
+Users send ETH to a campaign using contribute().
+
+Must send ETH
+
+Campaign must still be active
+
+Cannot contribute after deadline
+
+If user sends more than needed, extra ETH is refunded automatically
+
+If goal is reached → campaign becomes SUCCESSFUL
+
+3️⃣ Refund
+
+If:
+
+Deadline has passed
+
+Goal was NOT reached
+
+Contributors can call requestRefund() to get their money back.
+
+Campaign becomes UNSUCCEEDED.
+
+4️⃣ Withdraw (Creator Only)
+
+If campaign is SUCCESSFUL,
+the campaign creator can call withdraw() to collect the funds.
+
+Funds can only be withdrawn once.
+
+Campaign Status
+
+ACTIVE
+
+SUCCESSFUL
+
+UNSUCCEEDED
+
+DELETED
+
+Security
+
+Uses nonReentrant modifier to prevent reentrancy attacks
+
+Uses safe call for ETH transfers
+
+Prevents double withdrawals
+
+Main Functions
+
+createCampaign()
+
+contribute()
+
+requestRefund()
+
+withdraw()
+
+Use Cases
+
+Fundraising
+
+Charity campaigns
+
+Startup funding
+
+Community projects
\ No newline at end of file
diff --git a/excrowv1/crowd/contracts/Counter.sol b/excrowv1/crowd/contracts/Counter.sol
new file mode 100644
index 00000000..8d00cb7c
--- /dev/null
+++ b/excrowv1/crowd/contracts/Counter.sol
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.28;
+
+contract Counter {
+ uint public x;
+
+ event Increment(uint by);
+
+ function inc() public {
+ x++;
+ emit Increment(1);
+ }
+
+ function incBy(uint by) public {
+ require(by > 0, "incBy: increment should be positive");
+ x += by;
+ emit Increment(by);
+ }
+}
diff --git a/excrowv1/crowd/contracts/Counter.t.sol b/excrowv1/crowd/contracts/Counter.t.sol
new file mode 100644
index 00000000..ac71d5b8
--- /dev/null
+++ b/excrowv1/crowd/contracts/Counter.t.sol
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.28;
+
+import {Counter} from "./Counter.sol";
+import {Test} from "forge-std/Test.sol";
+
+// Solidity tests are compatible with foundry, so they
+// use the same syntax and offer the same functionality.
+
+contract CounterTest is Test {
+ Counter counter;
+
+ function setUp() public {
+ counter = new Counter();
+ }
+
+ function test_InitialValue() public view {
+ require(counter.x() == 0, "Initial value should be 0");
+ }
+
+ function testFuzz_Inc(uint8 x) public {
+ for (uint8 i = 0; i < x; i++) {
+ counter.inc();
+ }
+ require(counter.x() == x, "Value after calling inc x times should be x");
+ }
+
+ function test_IncByZero() public {
+ vm.expectRevert();
+ counter.incBy(0);
+ }
+}
diff --git a/excrowv1/crowd/contracts/crowd.sol b/excrowv1/crowd/contracts/crowd.sol
new file mode 100644
index 00000000..84a86bb1
--- /dev/null
+++ b/excrowv1/crowd/contracts/crowd.sol
@@ -0,0 +1,166 @@
+
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.28;
+
+contract CrowdFunding{
+
+ address private immutable owner;
+
+
+ uint private nextId;
+ bool private locked;
+
+ modifier nonReentrant() {
+ require(!locked, "Reentrant call");
+ locked = true;
+ _;
+ locked = false;
+ }
+
+ struct Campaign {
+ uint id;
+ string title;
+ address creator;
+ uint goal;
+ uint deadline;
+ uint amountRaised;
+ STATUS status;
+ uint startsAt;
+ uint endsAt;
+ uint totalContributions;
+ bool claimed;
+ mapping(address => uint) contributions;
+ //uint[] contributionAmounts;
+ }
+
+ enum STATUS {
+ ACTIVE,
+ DELETED,
+ SUCCESSFUL,
+ UNSUCCEEDED
+ }
+
+ mapping(uint => Campaign) public campaigns;
+ uint public campaignCount;
+
+ event CampaignCreated(uint indexed campaignId, address campaignCreator, string title, STATUS status);
+ event CampaignDeleted(uint indexed campaignId, address campaignCreator, STATUS status);
+ event ContributionMade(uint indexed campaignId, address contributor, uint amount);
+ event RefundMade(uint indexed campaignId, address contributor, uint amount);
+
+ function createCampaign(
+ string memory _title,
+ uint _goal,
+ uint _duration)public {
+ require(bytes(_title).length > 0, "Title must not be empty");
+ require(_goal > 0, "Goal must be greater than zero");
+ require(_duration > 0, "Ends time must be greater than zero");
+
+ campaignCount++;
+ nextId++;
+ Campaign storage campaign = campaigns[campaignCount];
+ campaign.id = nextId;
+ campaign.creator = msg.sender;
+ campaign.title =_title;
+ campaign.goal = _goal;
+ campaign.startsAt = block.timestamp;
+ campaign.status = STATUS.ACTIVE;
+ campaign.endsAt = block.timestamp + _duration;
+
+ emit CampaignCreated(nextId , msg.sender, _title, STATUS.ACTIVE);
+ }
+
+
+ function contribute(uint _id)
+ public
+ payable
+ nonReentrant
+{
+ Campaign storage campaign = campaigns[_id];
+
+ require(msg.value > 0, "Must send ETH");
+ require(block.timestamp < campaign.endsAt, "Campaign ended");
+ require(campaign.status == STATUS.ACTIVE, "Not active");
+
+ uint remaining = campaign.goal - campaign.totalContributions;
+
+ uint acceptedAmount = msg.value;
+
+ if (msg.value > remaining) {
+ acceptedAmount = remaining;
+
+ uint excess = msg.value - remaining;
+
+ (bool success, ) = payable(msg.sender).call{value: excess}("");
+ require(success, "Refund failed");
+ }
+
+ campaign.totalContributions += acceptedAmount;
+ campaign.contributions[msg.sender] += acceptedAmount;
+
+ if (campaign.totalContributions >= campaign.goal) {
+ campaign.status = STATUS.SUCCESSFUL;
+ }
+
+ emit ContributionMade(_id, msg.sender, acceptedAmount);
+}
+
+ function requestRefund(uint _id)
+ public
+ nonReentrant
+{
+ Campaign storage campaign = campaigns[_id];
+
+
+ if (
+ block.timestamp >= campaign.endsAt &&
+ campaign.totalContributions < campaign.goal
+ ) {
+ campaign.status = STATUS.UNSUCCEEDED;
+ }
+
+ require(campaign.status == STATUS.UNSUCCEEDED, "Refund not allowed");
+
+ uint contributedAmount = campaign.contributions[msg.sender];
+ require(contributedAmount > 0, "No contribution found");
+
+
+ campaign.contributions[msg.sender] = 0;
+ campaign.totalContributions -= contributedAmount;
+
+ (bool success, ) = payable(msg.sender).call{value: contributedAmount}("");
+ require(success, "Refund failed");
+
+ emit RefundMade(_id, msg.sender, contributedAmount);
+}
+
+ function withdraw(uint _id)
+ public
+ nonReentrant
+{
+ Campaign storage campaign = campaigns[_id];
+
+
+ require(msg.sender == campaign.creator, "Not campaign creator");
+
+
+ require(campaign.status == STATUS.SUCCESSFUL, "Campaign not successful");
+
+
+ require(!campaign.claimed, "Funds already withdrawn");
+
+ uint amount = campaign.totalContributions;
+ require(amount > 0, "No funds to withdraw");
+
+ campaign.claimed = true;
+
+ (bool success, ) = payable(msg.sender).call{value: amount}("");
+ require(success, "Withdrawal failed");
+
+ emit RefundMade(_id, msg.sender, amount);
+}
+
+
+
+}
+
diff --git a/excrowv1/crowd/hardhat.config.ts b/excrowv1/crowd/hardhat.config.ts
new file mode 100644
index 00000000..7092b852
--- /dev/null
+++ b/excrowv1/crowd/hardhat.config.ts
@@ -0,0 +1,38 @@
+import hardhatToolboxMochaEthersPlugin from "@nomicfoundation/hardhat-toolbox-mocha-ethers";
+import { configVariable, defineConfig } from "hardhat/config";
+
+export default defineConfig({
+ plugins: [hardhatToolboxMochaEthersPlugin],
+ solidity: {
+ profiles: {
+ default: {
+ version: "0.8.28",
+ },
+ production: {
+ version: "0.8.28",
+ settings: {
+ optimizer: {
+ enabled: true,
+ runs: 200,
+ },
+ },
+ },
+ },
+ },
+ networks: {
+ hardhatMainnet: {
+ type: "edr-simulated",
+ chainType: "l1",
+ },
+ hardhatOp: {
+ type: "edr-simulated",
+ chainType: "op",
+ },
+ sepolia: {
+ type: "http",
+ chainType: "l1",
+ url: configVariable("SEPOLIA_RPC_URL"),
+ accounts: [configVariable("SEPOLIA_PRIVATE_KEY")],
+ },
+ },
+});
diff --git a/excrowv1/crowd/ignition/modules/Counter.ts b/excrowv1/crowd/ignition/modules/Counter.ts
new file mode 100644
index 00000000..042e61c8
--- /dev/null
+++ b/excrowv1/crowd/ignition/modules/Counter.ts
@@ -0,0 +1,9 @@
+import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
+
+export default buildModule("CounterModule", (m) => {
+ const counter = m.contract("Counter");
+
+ m.call(counter, "incBy", [5n]);
+
+ return { counter };
+});
diff --git a/excrowv1/crowd/package.json b/excrowv1/crowd/package.json
new file mode 100644
index 00000000..ababa8dc
--- /dev/null
+++ b/excrowv1/crowd/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "crowd",
+ "version": "1.0.0",
+ "type": "module",
+ "devDependencies": {
+ "@nomicfoundation/hardhat-ethers": "^4.0.4",
+ "@nomicfoundation/hardhat-ignition": "^3.0.7",
+ "@nomicfoundation/hardhat-toolbox-mocha-ethers": "^3.0.2",
+ "@types/chai": "^4.3.20",
+ "@types/chai-as-promised": "^8.0.2",
+ "@types/mocha": "^10.0.10",
+ "@types/node": "^22.19.11",
+ "chai": "^5.3.3",
+ "ethers": "^6.16.0",
+ "forge-std": "github:foundry-rs/forge-std#v1.9.4",
+ "hardhat": "^3.1.8",
+ "mocha": "^11.7.5",
+ "typescript": "~5.8.0"
+ }
+}
diff --git a/excrowv1/crowd/scripts/send-op-tx.ts b/excrowv1/crowd/scripts/send-op-tx.ts
new file mode 100644
index 00000000..c10a2360
--- /dev/null
+++ b/excrowv1/crowd/scripts/send-op-tx.ts
@@ -0,0 +1,22 @@
+import { network } from "hardhat";
+
+const { ethers } = await network.connect({
+ network: "hardhatOp",
+ chainType: "op",
+});
+
+console.log("Sending transaction using the OP chain type");
+
+const [sender] = await ethers.getSigners();
+
+console.log("Sending 1 wei from", sender.address, "to itself");
+
+console.log("Sending L2 transaction");
+const tx = await sender.sendTransaction({
+ to: sender.address,
+ value: 1n,
+});
+
+await tx.wait();
+
+console.log("Transaction sent successfully");
diff --git a/excrowv1/crowd/test/Counter.ts b/excrowv1/crowd/test/Counter.ts
new file mode 100644
index 00000000..f8c38986
--- /dev/null
+++ b/excrowv1/crowd/test/Counter.ts
@@ -0,0 +1,36 @@
+import { expect } from "chai";
+import { network } from "hardhat";
+
+const { ethers } = await network.connect();
+
+describe("Counter", function () {
+ it("Should emit the Increment event when calling the inc() function", async function () {
+ const counter = await ethers.deployContract("Counter");
+
+ await expect(counter.inc()).to.emit(counter, "Increment").withArgs(1n);
+ });
+
+ it("The sum of the Increment events should match the current value", async function () {
+ const counter = await ethers.deployContract("Counter");
+ const deploymentBlockNumber = await ethers.provider.getBlockNumber();
+
+ // run a series of increments
+ for (let i = 1; i <= 10; i++) {
+ await counter.incBy(i);
+ }
+
+ const events = await counter.queryFilter(
+ counter.filters.Increment(),
+ deploymentBlockNumber,
+ "latest",
+ );
+
+ // check that the aggregated events match the current value
+ let total = 0n;
+ for (const event of events) {
+ total += event.args.by;
+ }
+
+ expect(await counter.x()).to.equal(total);
+ });
+});
diff --git a/excrowv1/crowd/tsconfig.json b/excrowv1/crowd/tsconfig.json
new file mode 100644
index 00000000..9b1380cc
--- /dev/null
+++ b/excrowv1/crowd/tsconfig.json
@@ -0,0 +1,13 @@
+/* Based on https://github.com/tsconfig/bases/blob/501da2bcd640cf95c95805783e1012b992338f28/bases/node22.json */
+{
+ "compilerOptions": {
+ "lib": ["es2023"],
+ "module": "node16",
+ "target": "es2022",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "moduleResolution": "node16",
+ "outDir": "dist"
+ }
+}
diff --git a/excrowv1/hardhat.config.ts b/excrowv1/hardhat.config.ts
new file mode 100644
index 00000000..7092b852
--- /dev/null
+++ b/excrowv1/hardhat.config.ts
@@ -0,0 +1,38 @@
+import hardhatToolboxMochaEthersPlugin from "@nomicfoundation/hardhat-toolbox-mocha-ethers";
+import { configVariable, defineConfig } from "hardhat/config";
+
+export default defineConfig({
+ plugins: [hardhatToolboxMochaEthersPlugin],
+ solidity: {
+ profiles: {
+ default: {
+ version: "0.8.28",
+ },
+ production: {
+ version: "0.8.28",
+ settings: {
+ optimizer: {
+ enabled: true,
+ runs: 200,
+ },
+ },
+ },
+ },
+ },
+ networks: {
+ hardhatMainnet: {
+ type: "edr-simulated",
+ chainType: "l1",
+ },
+ hardhatOp: {
+ type: "edr-simulated",
+ chainType: "op",
+ },
+ sepolia: {
+ type: "http",
+ chainType: "l1",
+ url: configVariable("SEPOLIA_RPC_URL"),
+ accounts: [configVariable("SEPOLIA_PRIVATE_KEY")],
+ },
+ },
+});
diff --git a/excrowv1/ignition/modules/Counter.ts b/excrowv1/ignition/modules/Counter.ts
new file mode 100644
index 00000000..042e61c8
--- /dev/null
+++ b/excrowv1/ignition/modules/Counter.ts
@@ -0,0 +1,9 @@
+import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
+
+export default buildModule("CounterModule", (m) => {
+ const counter = m.contract("Counter");
+
+ m.call(counter, "incBy", [5n]);
+
+ return { counter };
+});
diff --git a/excrowv1/package.json b/excrowv1/package.json
new file mode 100644
index 00000000..8a3a0667
--- /dev/null
+++ b/excrowv1/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "excrowv1",
+ "version": "1.0.0",
+ "type": "module",
+ "devDependencies": {
+ "@nomicfoundation/hardhat-ethers": "^4.0.4",
+ "@nomicfoundation/hardhat-ignition": "^3.0.7",
+ "@nomicfoundation/hardhat-toolbox-mocha-ethers": "^3.0.2",
+ "@types/chai": "^4.3.20",
+ "@types/chai-as-promised": "^8.0.2",
+ "@types/mocha": "^10.0.10",
+ "@types/node": "^22.19.11",
+ "chai": "^5.3.3",
+ "ethers": "^6.16.0",
+ "forge-std": "github:foundry-rs/forge-std#v1.9.4",
+ "hardhat": "^3.1.8",
+ "mocha": "^11.7.5",
+ "typescript": "~5.8.0"
+ }
+}
diff --git a/excrowv1/scripts/send-op-tx.ts b/excrowv1/scripts/send-op-tx.ts
new file mode 100644
index 00000000..c10a2360
--- /dev/null
+++ b/excrowv1/scripts/send-op-tx.ts
@@ -0,0 +1,22 @@
+import { network } from "hardhat";
+
+const { ethers } = await network.connect({
+ network: "hardhatOp",
+ chainType: "op",
+});
+
+console.log("Sending transaction using the OP chain type");
+
+const [sender] = await ethers.getSigners();
+
+console.log("Sending 1 wei from", sender.address, "to itself");
+
+console.log("Sending L2 transaction");
+const tx = await sender.sendTransaction({
+ to: sender.address,
+ value: 1n,
+});
+
+await tx.wait();
+
+console.log("Transaction sent successfully");
diff --git a/excrowv1/test/Counter.ts b/excrowv1/test/Counter.ts
new file mode 100644
index 00000000..f8c38986
--- /dev/null
+++ b/excrowv1/test/Counter.ts
@@ -0,0 +1,36 @@
+import { expect } from "chai";
+import { network } from "hardhat";
+
+const { ethers } = await network.connect();
+
+describe("Counter", function () {
+ it("Should emit the Increment event when calling the inc() function", async function () {
+ const counter = await ethers.deployContract("Counter");
+
+ await expect(counter.inc()).to.emit(counter, "Increment").withArgs(1n);
+ });
+
+ it("The sum of the Increment events should match the current value", async function () {
+ const counter = await ethers.deployContract("Counter");
+ const deploymentBlockNumber = await ethers.provider.getBlockNumber();
+
+ // run a series of increments
+ for (let i = 1; i <= 10; i++) {
+ await counter.incBy(i);
+ }
+
+ const events = await counter.queryFilter(
+ counter.filters.Increment(),
+ deploymentBlockNumber,
+ "latest",
+ );
+
+ // check that the aggregated events match the current value
+ let total = 0n;
+ for (const event of events) {
+ total += event.args.by;
+ }
+
+ expect(await counter.x()).to.equal(total);
+ });
+});
diff --git a/excrowv1/tsconfig.json b/excrowv1/tsconfig.json
new file mode 100644
index 00000000..9b1380cc
--- /dev/null
+++ b/excrowv1/tsconfig.json
@@ -0,0 +1,13 @@
+/* Based on https://github.com/tsconfig/bases/blob/501da2bcd640cf95c95805783e1012b992338f28/bases/node22.json */
+{
+ "compilerOptions": {
+ "lib": ["es2023"],
+ "module": "node16",
+ "target": "es2022",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "moduleResolution": "node16",
+ "outDir": "dist"
+ }
+}
|