diff --git a/1. b/1. new file mode 100644 index 00000000..e69de29b diff --git a/assignments/1-block-observation/README.md b/assignments/1-block-observation/README.md deleted file mode 100644 index a832de62..00000000 --- a/assignments/1-block-observation/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# The Ethereum Blockchain Explorer (Etherscan) & Transaction Hash - -## The Ethereum Blockchain Explorer (Etherscan) - -### Overview Page Observations -On the Etherscan overview homepage, I observed the following key metrics in the network: -1. **Live Ether Price**: In real-time, this displays the current price of ETH. -2. **Market Capitalization**: Represents the total value of all ETH in circulation. -3. **Transactions**: This displays the total number of transactions processed on the Ethereum network, increasing continuously as new transactions are confirmed. -4. **Last Finalized Block**: This shows us the most recent block that has achieved finality. - - -#### A table that display the lastest blocks -After clicking on the latest block at the time, there are several details I observed about the block and they are: - -1. **Block Height**: This indicates the length of the blockchain, increases after the addition of the new block. -2. **Status**: As at the time I checked it, it was finalized. This shows the status of the finality of the block. -3. **Proposed On** and **Timestamps**: Basically, this gives us information about when the transaction started. -4. Withdrawals -5. **Fee Recipient**: This gives us basic information about the receiver of the transaction. -6. **Block Reward**: This is the total reward earned from the miner. -7. Gas information -8. Block Size -9. **Extra Data**: More information about the recipient - - -#### A table that display the lastest transactions - -After clicking on the latest transaction at the time, there are several transaction details and they are: - -##### Transaction Overview -1. **Transaction Hash**: A unique 64-bit character id for ay given transaction. -2. **Status**: This shows us the progress of the transaction. -3. Block -4. Timestamp**: Giving us information about when the transaction took place. - -##### Transaction Cost Details -This gives us information about the cost of transaction like its fee and gas price. - -Etherscan serves as a powerful blockchain explorer for transparently analyzing Ethereum's blocks, transactions, and validator behavior. - -## Transaction Hash - -### Overview Page Observations - -Similar to the overview page of the etherscan of several transactions and blocks, this page gives us a keen view of the information that pertains to a particular transaction. - -We have the transaction hash of this transaction, the status, spacific block and the amount of block confirmations, timestamp from the time it was proposed (I think), the sender and receiver's wallet address, value of eth sent, transaction fee and gas price. - -There's also a button that when clicked, will show us a dropdown of more information about the transaction - -## Conclusion -In conclusion, I think the Etherscan is a powerful blockchain explorer for analyzing Etherum blocks, transactions, and validator behaviour, transparently. \ No newline at end of file diff --git a/assignments/2-gas-hash/README.md b/assignments/2-gas-hash/README.md deleted file mode 100644 index 6f04904e..00000000 --- a/assignments/2-gas-hash/README.md +++ /dev/null @@ -1,83 +0,0 @@ -# Ethereum Gas Fee and Merkle Root Calculator - - -## Overview -This repo contains: -- Simple explanations and calculation of key concepts around gas. -- TypeScript scripts for computing the Merkle roots of transactions using SHA256 and Keccak256. - -## Gas Fees in Ethereum: Explanation and Formula - -In Ethereum (and other EVM-based blockchains), gas is a unit that measures the computational work required to execute a transaction or smart contract. -It prevents spam and ensures fair use of network resources. - -**Gas fees** are the cost paid for this computation and are paid in ETH (or the chain’s native token). -I'll like to explain some concepts that are closesly associated with gas. - -### Key Concepts -- **Gas Limit**: This is the maximum amount of gas a transaction is allowed to use/consume, usually set by the user when submitting the transaction. If the transaction exceeds the limit in for a transaction, it fails and the gas is used. It is set by the user or wallet. If execution requires more gas than the limit, the transaction fails and is not refunded. -- **Gas Price (Gwei)**: This is the price/unit (Gwei, where 1 Gwei = 10^-9 ETH) of gas that the user bids. Miners will usually prefer a higher gas price. -The price per unit of gas, denominated in Gwei - -1 Gwei = 10⁻⁹ ETH -- **Gas Used**: The actual amount of gas consumed by the transaction after it has been executed. Simple ETH transfers use a fixed amount. Smart contracts consume variable gas depending on logic. - -- **Base Fee**: This fee is dynamically adjusted by the protocol depending on how the network is congested. It would watch for the block to be ~50%. This fee is burned, and ETH suply is reduced. Dynamically adjusted by the protocol based on network congestion. Target block usage is ~50%. Burned (removed from circulation), reducing ETH supply. -- **Priority Fee (Tip)**: An optional extra fee to incentivize miners/validators to include your transaction faster. -Optional extra fee paid to validators. Incentivizes faster inclusion in a block. -- **Effective Gas Price**: The total price per gas unit paid = Base Fee + Priority Fee. - -### Gas Fee Formula -**Total Gas Fee = Gas Used x Effective Gas Price** - - -#### Example Calculation - -If you send **2 ETH** and the transaction uses 4 units of gas, with: - -Base Fee = 11 Gwei - -Priority Fee = 3 Gwei - -Total Fee = 4 × (11 + 3) - = 4 × 14 - = 56 Gwei - - -Convert to ETH: - -56 Gwei = 56 × 10⁻⁹ ETH - = 0.000000056 ETH - - -**Total sent:** -2.000000056 ETH - -### What Affects Gas Fees? - -- **Network congestion:** Higher demand → higher base fee -- **Transaction complexity:** More computation → more gas used -- **User tip:** Higher priority fee → faster confirmation - -Gas fees are **market-driven**, not fixed. - - -## Markle Root (Root Hash) -This is a cryptographic hash that serves as a unique fingerprint for all transactions within a blockchain block. It is the final code in a markle tree. - - -### Setup -run -```bash - git clone https://github.com/Olorunshogo/blockheader-web3-assignments.git - cd gas-hash - npm install - node hash.ts -``` - -## Merkle Root -- Prompts for tx count and strings, derives hashes, computes roots with both algorithms. -- Leaves are 64-char hex (32 bytes). - -## Notes -- Tested on Node.js 24.11.1. \ No newline at end of file diff --git a/assignments/2-gas-hash/hash.ts b/assignments/2-gas-hash/hash.ts deleted file mode 100644 index cd282994..00000000 --- a/assignments/2-gas-hash/hash.ts +++ /dev/null @@ -1,166 +0,0 @@ - -import { ethers } from "ethers"; -import * as crypto from 'crypto'; -import * as readline from 'readline-sync'; - -// Gas Price Calculation -function calculateGasFee() { - // Prompt for gas price (in Gwei) - const gasPriceGwei = parseFloat( - readline.question("Enter gas price in Gwei: "), - ); - - // Prompt for gas used (in units) - const gasUsed = parseFloat(readline.question("Enter gas used: ")); - - // Calculate total gas fee in ETH - const totalGasFee = (gasPriceGwei * gasUsed) / 1e9; // Convert Gwei to ETH - console.log(`Total Gas Fee: ${totalGasFee} ETH`); -} - -// Merkle Root Calculations - -// Helper function to hash using the SHA256 algorithm -function sha256Hash(first: Buffer, second: Buffer): string { - const combinedLeaf = Buffer.concat([first, second]); - return crypto.createHash("sha256").update(combinedLeaf).digest("hex"); - -} - -// Similarly, KACCAK256 helper function to combine two leafs -function keccak256Hash(first: Buffer, second: Buffer): string { - const combinedLeaf = Buffer.concat([first, second]); - return ethers.keccak256(combinedLeaf); -} - -// Function to build Merkle Root -function buildMerkleRoot(leaves: string[], hashFunc: (first: Buffer, second: Buffer) => string,): string { - // Check for leaves - if (leaves.length === 0) { - throw new Error("No leaves provided"); - } - - // Convert hex leaves to Buffers: So we dont have to use the 0x prefix - let level: Buffer[] = leaves.map((leaf) => Buffer.from(leaf.slice(2), "hex")); - - while (level.length > 1) { - const nextLevel: Buffer[] = []; - for (let i = 0; i < level.length; i += 2) { - const first = level[i]; - const second = i + 1 < level.length ? level[i + 1] : first; - const parentHex = hashFunc(first, second); - nextLevel.push(Buffer.from(parentHex.slice(2), "hex")); - } - // Log the current level's hashes - console.log( - "Current level hashes: ", - nextLevel.map((hash) => hash.toString("hex")), - ); - - level = nextLevel; - } - - return "Ox" + level[0].toString("hex"); -} - -// Main hash function -function shaHashingFunction() { - // Prompt User for the Number of Transactions - const numTxs = parseInt( - readline.question("Enter number of transactions: "), - 10, - ); - - // Check if the user input character (s) or a number less than 0 - if (isNaN(numTxs) || numTxs < 0) { - console.error("Invalid number"); - return; - } - - // Check if the number of transaction is equal to zero - if (numTxs === 0) { - console.error("Please input a number greater than 0"); - return; - } - - // Initiate the transaction hashes array - const transactionHashes: string[] = []; - - for (let i = 0; i < numTxs; i++) { - // Prompt the user to enter a string to generate each the transaction hash - const inputStr = readline.question( - `Enter string for transaction ${i + 1}: `, - ); - // Derive transaction hash from input string using Keccak256 (simulating tx hash derivation) - const txHash = ethers.sha256(ethers.toUtf8Bytes(inputStr)); - console.log(`Derived Tx Hash ${i + 1}: ${txHash}`); - console.log(`The length of the Tx Hash is: ${i + 1}: ${txHash.length}`); - - // Append transaction hash to the array - transactionHashes.push(txHash); - - console.log(""); - } - - // Merkle Root Calculation - const sha256Root = buildMerkleRoot(transactionHashes, sha256Hash); - console.log("\n Merkle Root with SHA256: ", sha256Root); -} - -function keccakHashingFunction() { - // Prompt User for the Number of Transactions - const numTxs = parseInt( - readline.question("Enter number of transactions: "), - 10, - ); - - // Check if the user input character (s) or a number less than 0 - if (isNaN(numTxs) || numTxs < 0) { - console.error("Invalid number"); - return; - } - - // Check if the number of transaction is equal to zero - if (numTxs === 0) { - console.error("Please input a number greater than 0"); - return; - } - - // Initiate the transaction hashes array - const transactionHashes: string[] = []; - - for (let i = 0; i < numTxs; i++) { - // Prompt the user to enter a string to generate each the transaction hash - const inputStr = readline.question( - `Enter string for transaction ${i + 1}: `, - ); - // Derive transaction hash from input string using Keccak256 (simulating tx hash derivation) - const txHash = ethers.keccak256(ethers.toUtf8Bytes(inputStr)); - console.log(`Derived Tx Hash ${i + 1}: ${txHash}`); - console.log(`The length of the Tx Hash is: ${i + 1}: ${txHash.length}`); - - // Append transaction hash to the array - transactionHashes.push(txHash); - - console.log(""); - } - - // Merkle Root Calculation - const keccak256Root = buildMerkleRoot(transactionHashes, keccak256Hash); - console.log("\n Merkle Root with Keccak256: ", keccak256Root); -} - -// Run the gas fee calculator -calculateGasFee(); - -// Run the hashing functions -shaHashingFunction(); -keccakHashingFunction(); - - - - - - - - diff --git a/assignments/2-gas-hash/package-lock.json b/assignments/2-gas-hash/package-lock.json deleted file mode 100644 index b088725f..00000000 --- a/assignments/2-gas-hash/package-lock.json +++ /dev/null @@ -1,348 +0,0 @@ -{ - "name": "2-gas-hash", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "@types/node": "^25.0.9", - "crypto": "^1.0.1", - "ethers": "^6.16.0", - "readline-sync": "^1.4.10", - "ts-node": "^10.9.2" - }, - "devDependencies": { - "@types/readline-sync": "^1.4.8" - } - }, - "node_modules/@adraffy/ens-normalize": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", - "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", - "license": "MIT" - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@noble/curves": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", - "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.3.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", - "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "25.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", - "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", - "license": "MIT", - "peer": true, - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/readline-sync": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/@types/readline-sync/-/readline-sync-1.4.8.tgz", - "integrity": "sha512-BL7xOf0yKLA6baAX6MMOnYkoflUyj/c7y3pqMRfU0va7XlwHAOTOIo4x55P/qLfMsuaYdJJKubToLqRVmRtRZA==", - "dev": true, - "license": "MIT" - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/aes-js": { - "version": "4.0.0-beta.5", - "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", - "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", - "license": "MIT" - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "license": "MIT" - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "license": "MIT" - }, - "node_modules/crypto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", - "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==", - "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.", - "license": "ISC" - }, - "node_modules/diff": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", - "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/ethers": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.16.0.tgz", - "integrity": "sha512-U1wulmetNymijEhpSEQ7Ct/P/Jw9/e7R1j5XIbPRydgV2DjLVMsULDlNksq3RQnFgKoLlZf88ijYtWEXcPa07A==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/ethers-io/" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@adraffy/ens-normalize": "1.10.1", - "@noble/curves": "1.2.0", - "@noble/hashes": "1.3.2", - "@types/node": "22.7.5", - "aes-js": "4.0.0-beta.5", - "tslib": "2.7.0", - "ws": "8.17.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/ethers/node_modules/@types/node": { - "version": "22.7.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", - "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~6.19.2" - } - }, - "node_modules/ethers/node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "license": "MIT" - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "license": "ISC" - }, - "node_modules/readline-sync": { - "version": "1.4.10", - "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz", - "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==", - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", - "license": "0BSD" - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "license": "MIT" - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "license": "MIT" - }, - "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "license": "MIT", - "engines": { - "node": ">=6" - } - } - } -} diff --git a/assignments/2-gas-hash/package.json b/assignments/2-gas-hash/package.json deleted file mode 100644 index 3d505094..00000000 --- a/assignments/2-gas-hash/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "type": "module", - "scripts": { - "hash": "ts-node ./hash.ts" - }, - "dependencies": { - "@types/node": "^25.0.9", - "crypto": "^1.0.1", - "ethers": "^6.16.0", - "readline-sync": "^1.4.10", - "ts-node": "^10.9.2" - }, - "devDependencies": { - "@types/readline-sync": "^1.4.8" - } -} diff --git a/assignments/3-block-hash/README.md b/assignments/3-block-hash/README.md deleted file mode 100644 index 3971a6da..00000000 --- a/assignments/3-block-hash/README.md +++ /dev/null @@ -1,110 +0,0 @@ -Read the Mastering Ethereum book and document your findings from the first (1) and sixth (6) chapter comprehensively in a hackMD file. Mastering Bitcoin Book -Write an algorithm to derive the block hash. -Summarize what's inside a transaction. Talk about it. - -# Assignment 3 -## Chapter 1. What is Ethereum? -Ethereum is ofte described as the "world computer". -From a computer science perspective, Ethereum is a deterministic but practically unbounded state machine, consisting of a globally accessible singleton state and a virtual machine that applies changes to that state. -Ethereum is more practically described as an open-source, globally decentralized computing infrastructure that executes programs called smart contract. - -The Ethereum platform enables developers to build powerful decentralized applications with built-in economic functions. It provides high availability, auditability, transparency, and neutrality while reducing or eliminating censorship and reducing certain counterparty risks. - -**Ethereum compared to Bitcoin** - -Yes Ethereum has striking similarities to blockchain currencies that was established before it including Bitcoin and it also has a digital currency (ether), but both the purpose and the construction of Ethereum are strikingly different from those of the open blockchains that preceeded it, including Bitcoin. - -Ethereum's purpose is not primarily to be a digital currency payment network. While the digital currency ether is both integral to and necessary for the operation of Ethereum, ether is intended as a utility currency to pay for use of Ethereum platform as the worl computer. -Unlike Bitcoin, which has a very limited scripting language, Ethereum is designed to be a general-purpose, programmable blockchain that runs a virtual machine capable of executing code of arbitrary and unbounded complexity. -Ethereum can function as a general-purpose computer, where Bitcoin's Script language is intentionally constrained to simple true/false evaluation of spending conditions. - -In September 2022, Ethereum further distinguished itself from Bitcoin With **The Merge Upgrade** by transitioning its consensus model from proof of work (PoW) to proof of stake (PoS). - -**`!!! Investigate further what PoW and PoS really means`**. - -### Components of a Blockchain -The components of an open, public blockchain are (usually) as follows: -- A P2P network connecting participants and propagating transaction and blocks of verified transactions, based on a standardized "gossip" protocol -- Messages, in the formof transactios, representing state transitions -- A set of consensus rules governing what constitutes a transaction and what makes for a valid state transition -- A state machine that processes transactions according to the consensus rules -- A chain of cryptographically secured blocks that acts as a journal of all the verified and accepted state transitions -- A consensus algorithm that decentralizes control over the blockchain by forcing participants to cooperate in the enforcement of the consensus rules -- A game-theory-sound incentivization scheme (e.g., PoW costs plus block rewards) to economically secure the state machine in an open environment -- One or more open source software implementations of these components ("clients") - -Keywords like *open*, *public*, *global decentralized*, *neutral*, and *censorship resistant*, can help us to identify the important emergent characteristics of a "blockchain" system. -We can broadly categorize blockchains into permissioned versus permissionless and public and private: -**Permissionless and Permissioned**: The former is a blockchain that's accessible to anyone like Ethereum and Bitcoin. The decentralized network allows anyone to join, participate in the consensus process, and read and write data, prompting trust and transparency. -while the latter is the opposite. There's restricted access and only authorized participants can join the network and perform some actions. -**Public and Private** -Public blockchains are decentralized and open to anyone. This promotes broad participation in network activities while private blockchains limit access to a specific group of participants, often within organizations or among trusted partners. - -### The Birth of Ethereum -Why?!!! - -Ethereum's founders were thinking about a blockchain without a specific purpose, which could support a broad variety of applications by being *programmed*. The idea was that by using a genral-purpose blockchain like Ethereum, a developer could program their particular application without having to implement the underlying mechanisms of P2P networks, blockchains, consensus algorithms, and the like. - -Ethereum abstracts away those details and offeres a deterministic, secure environment for writing decentralized applications. This doesn't only make development easier but expanded the scope (what it could do) of blockchain. Hence the birth of NFTs, decentralized autonomous organizations (DAOs), which wouldn't have been feasible with earlier single-purpose blockchains. - -**`!!! What are DAOs?`**. - -### Ethereum's Stages of Development - -The four stages of Ethereum's devlopment arelisted below: - -- **Frontier (July 30, 2015):** The was block (Genesis) was mined on this day. This laid the foundation for other developers and miners by enabling the setup of mining rigs, the initiation of the ETH token trading, and testing od DApps in a minimal network setting. -Initially, the block had a gas limit of five thousand (5,000), but that was lifted in September 2015, allowing for transactions and introducing the *difficulty bomb*. -Ethereum *difficulty bomb* is a mechanism that's designed to make minimg over time, exponentially difficult, ultimately, making it infeasible. -- **Homestead (March 14, 2016):** This stage started at block 1,150,000. Although the network remained in the beta phase, Homestead made Ethereum safer and more stable through key protocol updates (EIP-2, EIP-7, and EIP-*). These upgrades enhanced developer friendliness and paved the way for further protocol improvements. -- **Metropolis (October 16, 2017):** THis stage started at block 4,370,000. This stage aimed to foster DApp creation and overall network utility. With this stage, we have optimized gas costs, enhanced security, introduced L2 scaling solutions, reduced minimg reward and so on. These improvements set the stage for Ethereum 2.0, representing the final phase of Ethereum 1.0. -- **Serenity (September 15, 2022):** Comonly known as *Ethereum 2.0*, represents a major upgrade aimed at transforming Ethereum from a PoW to a PoS consensus mechanism. Its focus is to ensure Ethereum is sustainable and capable of handling a growing number of users and applications. This stage addresses critical issues like high energy consumption and network congestion which clears the road for a more robust and efficient blockchain. This stage is divided into several substages that handled/addressed specific aspects of the network's evolution. - -**Sharding** helps Ethereum to scale by splitting the etwork into smaller, manageable pieces, which allows for more transactions per second. - -### Ethereum: A General Purpose Blockchain -The original blockchain-Bitcoin-traks the state of units of Bitcoin and their ownership. -Alternatively, it can be seen as a distributed-consensus *state machine*, where transactions cause a *global state transition*, altering the ownership of the coins. -The rules of consensus guides the state transitions, allowing all participants to eventually converge (consensus) on a common state of the system, after several blocks are mined. - -Similarly, Ethereum is also a state machine. However it distinguishes itselft by not only tracking the state of current ownership, but also traks the state of transitions of a general-purpose data store (data expressible as a *key-value tuple*). - -Like a general-purpose stored-program computer, it stores data and the code and also tracks how the data changes over time. It can load code into its state machine and run that code, storing the resulting changes in its blockchain. - -Two of the most critical differences from the most general-purpose computers are that **Ethereum state changes are governed by the rules of consensus** and that its **state is distributed globally**. - -Ethereum answers the question "**What if we could track any arbitrary state and program the state machine to create a worldwide computer operating under consensus?**" - -### Ethereum's Components -The components of a blockchain are more specifically as follows: - -- **P2P network:** Ethereum runs on the main Ethereum network, which is addressable on TCP port 30303, and runs a protocol called *`DEVp2p`*. -- **Consensus rules:** Ethereum's original protocol was *Ethash*, a PoW model defined in the reference specification: the "Yellow Paper". It then evolved to the PoS in September 2022 during The Merge upgrade. -- **Transactions:** Ethereum transactions are network messages that include (among other things) a sender, a recipient, a value, and a data payload. -- **State Machine:** Ethereum state transitions are processed by the *Ethereum Virtual Machine (EVM)*, a stack-based virtual machine that executes bytecodes (machine-language instructions). EVM programs called *smart contracts* are written in high-level languages and combiled to bytecode for execution on the EVM. -- **Data structures:** Ethereum's state is stored locally on each node as a *database* (usually Google's LevelDB), which contains the transactions and system state in a serialized hashed data structure called a *Merkle-Patricia trie*. -- **Consensus algorithm:** In PoS consensus mechanism, validators stake their cryptocurrency to earn the right to validate transactions, create new blocks, and maintain network security. -- **Economic security:** To provide security to the blockchain, Ethereum uses a PoS algorithm called Gasper. -- **Clients** - -### Ethereum and Turing Completeness -Ethereum's ability toexecute a stored program-in a state machine called the EVM-while reading and writing data to memory makes it a Turing-complete system and therefore a Universal Turing Machine (UTM). - -Ethereum's groundbreaking innovation is to combine the general-purpose computing architecture of a stored-program computer with a decentralized blockchain, thereby creating a distributed single-data (singleton) worl computer. Ethereum programs run "everywhere" yet produce a common state that is secured by the rules of consensus. - -### Implications os Turing Completeness -Turing proved that we cannot predict whether a program will terminate by simulating it on a computer. -Turing-complete systems can run in *infinite loops* - a program that does not terminate - oversimplication. - -Now, it'll be trivial to create a program that runs a loop that never ends. But, unintended never-ending loops can arise without warning due to complex interactions between the starting condition and the code. - -We can't prove/predict how long the program/smart contract will run without actually running it (What if it runs forever). Whether intentional or not, a smart contract can be created such that it runs forever when a node/client tries to validate it. - -In a world computer, a program that abuses resources gets to abuse the world's resources. How does Ethereum constrain the resources used by a smart contract if it cannot predict resource use in advance? - - - - - - - diff --git a/assignments/3-block-hash/block.json b/assignments/3-block-hash/block.json deleted file mode 100644 index 2dae48e2..00000000 --- a/assignments/3-block-hash/block.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "version": 1, - "height": 850000, - "size": 1456789, - - "previousBlockHash": "0x00000000000000000002a7c4b0e4e8f8c9d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5", - - "stateRoot": "0x8f9c0a1b2c3d4e5f60718293a4b5c6d7e8f90123456789abcdef0123456789ab", - - "receiptsRoot": "0xb4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a", - - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - - "difficulty": "0x2fefd8", - - "gasLimit": 30000000, - "gasUsed": 14567890, - - "baseFeePerGas": 1000000000, - - "timestamp": 1735689600, - - "extraData": "0x657468657265756d2d61737369676e6d656e74", - - "mixHash": "0x9b1c2d3e4f5061728394a5b6c7d8e9f00112233445566778899aabbccddeeff", - - "nonce": "0x000000000000abcd", - - "transactionHashes": [ - "0xa3b1c2d4e5f60718293a4b5c6d7e8f90123456789abcdef0123456789abcdef" - ] -} diff --git a/assignments/3-block-hash/blockHash.ts b/assignments/3-block-hash/blockHash.ts deleted file mode 100644 index 2a4efe9f..00000000 --- a/assignments/3-block-hash/blockHash.ts +++ /dev/null @@ -1,113 +0,0 @@ -import * as fs from 'node:fs'; -import { ethers } from 'ethers'; - -// Keccak256 helper -function keccak256Hash(first: Buffer, second: Buffer): string { - const combinedLeaf = Buffer.concat([first, second]); - return ethers.keccak256(combinedLeaf); -} - -// Merkle Root builder -function buildMerkleRoot( - leaves: string[], - hashFunc: (first: Buffer, second: Buffer) => string -): string { - if (leaves.length === 0) { - throw new Error('No leaves provided'); - } - - let level: Buffer[] = leaves.map((leaf) => Buffer.from(leaf.slice(2), 'hex')); - - while (level.length > 1) { - const nextLevel: Buffer[] = []; - - for (let i = 0; i < level.length; i += 2) { - const first = level[i]; - const second = i + 1 < level.length ? level[i + 1] : first; - const parentHex = hashFunc(first, second); - nextLevel.push(Buffer.from(parentHex.slice(2), 'hex')); - } - - console.log( - 'Current level hashes:', - nextLevel.map((hash) => hash.toString('hex')) - ); - - level = nextLevel; - } - - return '0x' + level[0].toString('hex'); -} - -// Read block.json -const data = fs.readFileSync('block.json', 'utf8'); -const blockData = JSON.parse(data); - -// Extract fields (unchanged names) -const { - version, - height, - size, - previousBlockHash, - stateRoot, - receiptsRoot, - logsBloom, - difficulty, - gasLimit, - gasUsed, - baseFeePerGas, - timestamp, - extraData, - mixHash, - nonce, - transactionHashes, -} = blockData; - -// Compute transactionsRoot -const transactionsRoot = buildMerkleRoot(transactionHashes, keccak256Hash); - -// Log block info -console.log('Block Version:', version); -console.log('Block Height:', height); -console.log('Block Size:', size); -console.log('Parent Hash:', previousBlockHash); -console.log('Transactions Root:', transactionsRoot); -console.log('Timestamp:', timestamp); -console.log('Nonce:', nonce); -console.log(''); - -// Serialize Ethereum block header -const headerBuf = Buffer.concat([ - Buffer.from(previousBlockHash.slice(2), 'hex'), - Buffer.from(stateRoot.slice(2), 'hex'), - Buffer.from(transactionsRoot.slice(2), 'hex'), - Buffer.from(receiptsRoot.slice(2), 'hex'), - Buffer.from(logsBloom.slice(2), 'hex'), - Buffer.from(difficulty.slice(2), 'hex'), - - // gas fields - Buffer.alloc(8, 0), - Buffer.alloc(8, 0), - Buffer.alloc(8, 0), -]); - -headerBuf.writeBigUInt64BE(BigInt(gasLimit), headerBuf.length - 24); -headerBuf.writeBigUInt64BE(BigInt(gasUsed), headerBuf.length - 16); -headerBuf.writeBigUInt64BE(BigInt(baseFeePerGas), headerBuf.length - 8); - -// Final header assembly -const timeBuf = Buffer.alloc(8); -timeBuf.writeBigUInt64BE(BigInt(timestamp), 0); - -const finalHeader = Buffer.concat([ - headerBuf, - timeBuf, - Buffer.from(extraData.slice(2), 'hex'), - Buffer.from(mixHash.slice(2), 'hex'), - Buffer.from(nonce.slice(2).padStart(16, '0'), 'hex'), -]); - -// Ethereum Block Hash (Keccak256) -const blockHash = ethers.keccak256(finalHeader); - -console.log('Ethereum Block Hash:', blockHash); diff --git a/assignments/3-block-hash/package-lock.json b/assignments/3-block-hash/package-lock.json deleted file mode 100644 index 621fb32f..00000000 --- a/assignments/3-block-hash/package-lock.json +++ /dev/null @@ -1,354 +0,0 @@ -{ - "name": "3-block-hash", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "3-block-hash", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "crypto": "^1.0.1", - "ethers": "^6.16.0" - }, - "devDependencies": { - "@types/node": "^25.0.10", - "ts-node": "^10.9.2" - } - }, - "node_modules/@adraffy/ens-normalize": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", - "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", - "license": "MIT" - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@noble/curves": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", - "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.3.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", - "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "25.0.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz", - "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/aes-js": { - "version": "4.0.0-beta.5", - "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", - "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", - "license": "MIT" - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/crypto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", - "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==", - "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.", - "license": "ISC" - }, - "node_modules/diff": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", - "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/ethers": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.16.0.tgz", - "integrity": "sha512-U1wulmetNymijEhpSEQ7Ct/P/Jw9/e7R1j5XIbPRydgV2DjLVMsULDlNksq3RQnFgKoLlZf88ijYtWEXcPa07A==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/ethers-io/" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@adraffy/ens-normalize": "1.10.1", - "@noble/curves": "1.2.0", - "@noble/hashes": "1.3.2", - "@types/node": "22.7.5", - "aes-js": "4.0.0-beta.5", - "tslib": "2.7.0", - "ws": "8.17.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/ethers/node_modules/@types/node": { - "version": "22.7.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", - "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", - "license": "MIT", - "dependencies": { - "undici-types": "~6.19.2" - } - }, - "node_modules/ethers/node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "license": "MIT" - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", - "license": "0BSD" - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, - "license": "MIT" - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "license": "MIT" - }, - "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - } - } -} diff --git a/assignments/3-block-hash/package.json b/assignments/3-block-hash/package.json deleted file mode 100644 index 9e4aebd2..00000000 --- a/assignments/3-block-hash/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "3-block-hash", - "version": "1.0.0", - "description": "Read the Mastering Ethereum book and document your findings from the first (1) and sixth (6) chapter comprehensively in a hackMD file. Mastering Bitcoin Book Write an algorithm to derive the block hash. Summarize what's inside a transaction. Talk about it.", - "main": "index.js", - "scripts": { - "blockHash": "ts-node blockHash.ts" - }, - "keywords": [], - "author": "", - "license": "ISC", - "type": "module", - "dependencies": { - "crypto": "^1.0.1", - "ethers": "^6.16.0" - }, - "devDependencies": { - "@types/node": "^25.0.10", - "ts-node": "^10.9.2" - } -} diff --git a/assignments/3-block-hash/tsconfig.json b/assignments/3-block-hash/tsconfig.json deleted file mode 100644 index 7b0fe8c5..00000000 --- a/assignments/3-block-hash/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "module": "CommonJS", - "moduleResolution": "node", - "types": ["node"], - "strict": true, - "esModuleInterop": true - } -} diff --git a/assignments/4-jsonRpc-observations/README.md b/assignments/4-jsonRpc-observations/README.md deleted file mode 100644 index f27b6d26..00000000 --- a/assignments/4-jsonRpc-observations/README.md +++ /dev/null @@ -1,121 +0,0 @@ -# Ethereum JSON-RPC API - Quick Overview - -This document lists **all important JSON-RPC methods** used to talk to an Ethereum node. - -Ethereum has two main parts after The Merge (2022): - -- **Execution client** (Geth, Erigon, Nethermind, Besu, Reth...) -> handles transactions, smart contracts, EVM -- **Consensus client** (Prysm, Lighthouse, Teku, Nimbus, Lodestar...) -> handles proof-of-stake, validators, Beacon Chain - -Most people use the **Execution client JSON-RPC** (port 8545 by default) when they build dApps, wallets or scripts. -This page mainly covers **Execution client methods**. - -> Note: Consensus client has its own API called **Beacon API** (usually port 5052). -> There is also the **Engine API** (between execution ↔ consensus client), but normal developers almost never use it directly. - -## Main JSON-RPC Categories - -### 1. Web3 / Net / Client Info -Methods to check node version, network, connection status - -- web3_clientVersion -- web3_sha3 -- net_version -- net_listening -- net_peerCount -- eth_protocolVersion (not always supported) -- eth_chainId -- eth_syncing -- eth_mining (only PoW networks) -- eth_hashrate (only PoW networks) -- eth_gasPrice -- eth_accounts (only if node manages keys) -- eth_coinbase (deprecated) - -### 2. Block & Chain Head -Methods to know current block and chain status - -- eth_blockNumber -- eth_getBlockByHash -- eth_getBlockByNumber -- eth_getBlockTransactionCountByHash -- eth_getBlockTransactionCountByNumber -- eth_getUncleCountByBlockHash -- eth_getUncleCountByBlockNumber - -### 3. Account & Balance -Check balances, nonce, code, storage - -- eth_getBalance -- eth_getTransactionCount -- eth_getCode -- eth_getStorageAt - -### 4. Transaction Sending -Send / sign transactions - -- eth_sendTransaction -- eth_sendRawTransaction -- eth_sign (dangerous – only for testing) -- eth_signTransaction (signs but does not send) - -### 5. Call & Gas Estimation -Read smart contracts without changing state - -- eth_call -- eth_estimateGas - -### 6. Transaction & Receipt Info -Get details about past transactions - -- eth_getTransactionByHash -- eth_getTransactionByBlockHashAndIndex -- eth_getTransactionByBlockNumberAndIndex -- eth_getTransactionReceipt - -### 7. Uncle Blocks (only relevant on old PoW chains) - -- eth_getUncleByBlockHashAndIndex -- eth_getUncleByBlockNumberAndIndex - -### 8. Logs / Events (Filter system) - -- eth_newFilter -- eth_newBlockFilter -- eth_newPendingTransactionFilter -- eth_uninstallFilter -- eth_getFilterChanges -- eth_getFilterLogs -- eth_getLogs - -## Quick Summary Table – Most Used Methods - -| Group | Very Common Methods | Less Common / Advanced | -|------------------------|--------------------------------------------------|-------------------------------------------------| -| Node info | eth_chainId, net_version, eth_syncing | web3_clientVersion, net_peerCount | -| Current state | eth_blockNumber, eth_getBalance, eth_getCode | eth_getStorageAt | -| Read contracts | eth_call, eth_estimateGas | — | -| Send transactions | eth_sendRawTransaction | eth_sendTransaction, eth_sign | -| Transaction lookup | eth_getTransactionReceipt, eth_getTransactionByHash | eth_getTransactionByBlock... | -| Logs / Events | eth_getLogs | eth_newFilter + eth_getFilterChanges | -| Blocks | eth_getBlockByNumber, eth_getBlockByHash | eth_getUncle... | - -## Useful Tips - -- Always use `"latest"`, `"pending"`, `"safe"`, or `"finalized"` as block tag when possible (safer than raw block numbers) -- `eth_call` and `eth_estimateGas` do **not** cost gas and do **not** change the blockchain -- `eth_sendRawTransaction` is the safest way to send already-signed transactions -- `eth_getLogs` is very powerful but can be slow on big ranges — use filters when you can - -Good starting point for most projects: - -```text -eth_chainId -eth_blockNumber -eth_getBalance -eth_call -eth_estimateGas -eth_sendRawTransaction -eth_getTransactionReceipt -eth_getLogs -``` \ No newline at end of file diff --git a/assignments/5-notes-on-terms/README.md b/assignments/5-notes-on-terms/README.md deleted file mode 100644 index 84f97a87..00000000 --- a/assignments/5-notes-on-terms/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# Read, and write notes on: Exec hash, finalized root, epoch, block hash, slots, and fork choice - -## Block Hash - -**Block hash** in Ethereum is the cryptographic hash of the **block header**, which uniquely identifies each block and ensures the integrity of the blockchain. -It is generated using the **Keccak-256** hashing algorithm and includes all critical fields from the block header, such as: - -+ Parent block hash: This links the current block to its predecessor. -+ Timestamp: when the block was created. -+ Nonce: a value used in the proof-of-stake consensus process. -+ Difficulty level: indicates the complexity of mining the block (in PoW; now less relevant in PoS). -+ Merkle roots: hashes of the transaction tree, state tree, and receipt tree, summarizing the block's contents. - -Any change in the block header‐even a single bit‐results in a completely different block hash, making tampering detectable. This cryptographic linkage ensures the immutability of the Ethereum blockchain. - -The block hash is essential for verifying the authenticity and order of blocks across the network. It is also used in smart contracts via the blockhash opcode to reference the hash of a past block (up to 256 blocks back), enabling time-based logic and stateless validation. - -## Finalized root -**Finalized Root in Ethereum** refers to the **state root** of the most recent block that has been finalized by the consensus mechanism, specifically within Ethereum's Proof-of-Stake (PoS) system using the Casper FFG (Friendly Finality Gadget) protocol. - -The **finalized root** is **a cryptographic hash that represents the entire state of the Ethereum blockchain at a specific block, including all acoount balances, contract storage, and other state data. - -## Epoch -**Epoch in Ethereum** refers to a fixed time period in the Ethereum blockchain. Each epoch consists of **32 slots**, with each slot lasting **12 seconds**, resulting in an epoch duration of approximately **6.4 minutes**. - -In other words, 1 epoch is a bundle of 32 * 1 slot (12 seconds) = **6.4 minutes**. -2 - -Epoch is important to group/bundle slots together, give timeline to validator voting and give a timeline to finalization as it happens at each epoch's boundaries. - -## Slots -A **slot** however is an important unit of time in Ethereum's Proof-of-Stake (PoS) consensus mechanism, with each slot taking 12 seconds. - -It serves as a scheduled window during which a randomly selected validator is assigned to propose a block. -If the validator is online and functional, they create and broadcast a block; otherwise, the slot remains empty. - -## Fork choice -Fork Choice Rule is a core mechanism in Ethereum that enables nodes to agree on a single, canonical blockchain when the network experiences temporary splits or competing chains. -It acts as a decision-making protocol ensuring consensus across the decentralized network. - -In Ethereum's Proof of Stake (PoS) era, following the Merge on September 15, 2022, the **fork choice rule** is defined by the **combination of LMD GHOST (Latest Message Driven Greedy Heaviest Observed SubTree)** and **Casper FFG (Friendly Finality Gadget)**, collectively known as Gasper. -This system selects the chain head based on the heaviest sub-tree of validator attestations, where each validator's most recent vote counts toward a block's weight. - -+ **LMD GHOST** evaluates the chain with the greatest cumulative weight from recent validator attestations, promoting network alignment and resilience against attacks. -+ **Casper FFG** provides economic finality by finalizing checkpoints when a supermajority (≥2/3) of validator stake agrees, significantly reducing the risk of deep reorganizations. - -This evolution from the Longest Chain Rule (used in Proof of Work) to a validator-weighted, attestation-driven model improves security, finality speed, and energy efficiency. -The rule ensures that honest nodes converge on the same chain head, maintaining ledger consistency and enabling trust in DeFi, staking, and settlement systems. diff --git a/assignments/6-wallet-address/README.md b/assignments/6-wallet-address/README.md index 0d3a2654..e8c53c3b 100644 --- a/assignments/6-wallet-address/README.md +++ b/assignments/6-wallet-address/README.md @@ -1,219 +1,78 @@ -# Custom Mnemonic to Ethereum Address Generator -This project demonstrates how a cryptographic wallet can be built from scratch using basic primitives. -It starts from secure randomness, turns that randomness into human readable words, hashes those words into a seed, derives a private key, generates a public key using secp256k1, and finally produces an Ethereum address. +# BIP32 Wallet -This is an educational implementation meant to explain the full flow in simple human language. +A simple, lightweight utility for deriving Ethereum wallets from BIP39 mnemonics using hierarchical deterministic (HD) wallets. +## What is this? -## What This Project Does +This is a straightforward Node.js module that helps you work with Ethereum wallets. It lets you generate mnemonic phrases and derive multiple wallet addresses from a single seed phrase—useful if you want to manage several wallets without storing separate private keys. -This script generates an Ethereum address by following a clear logical chain. +## Features -1. Random numbers -2. Mnemonic words -3. Mnemonic phrase -4. Seed -5. Private key -6. Public key -7. Ethereum address +- **Generate BIP39 mnemonics** - Create secure seed phrases +- **Derive Ethereum wallets** - Get address and private key from a mnemonic +- **Batch derivation** - Generate multiple wallets from a single phrase +- **HD wallet support** - Uses standard Ethereum derivation paths (BIP44) -Every step is visible, logged, and easy to follow. +## Installation +First, make sure you have Node.js installed. Then grab the dependencies: -## Dictionary and Words +```bash +npm install +``` -A local dictionary.json file is used as the word source. +## Usage -The dictionary values are converted into an array of words. Each random number is mapped to one word using modulo arithmetic so it always fits inside the dictionary length. +Here's how to get started: -This is similar in spirit to BIP-39 but simplified for learning purposes. +```javascript +const { deriveWallet, generateMnemonic, deriveMultiple } = require('./bip32wallet'); -## Step by Step Process +// Create a new random mnemonic +const mnemonic = generateMnemonic(); +console.log(mnemonic); // 12 or 24 word seed phrase -### 1. Secure Random Number Generation +// Derive a wallet from it +const wallet = deriveWallet(mnemonic); +console.log(wallet.address); // Your wallet address +console.log(wallet.privateKey); // Your private key -The process starts with a cryptographically secure random number generator. +// Or generate multiple wallets at once +const wallets = deriveMultiple(mnemonic, 5); // Creates 5 wallets +wallets.forEach((w, i) => { + console.log(`Wallet ${i + 1}: ${w.address}`); +}); +``` -Node.js crypto.randomBytes is used to generate random bytes. -Each byte is a number between 0 and 255. +## API -These numbers are the root of all security in this system. +### `generateMnemonic(strength)` +Generates a random BIP39 mnemonic phrase. +- **strength** (optional): Number of bits (default: 128 = 12 words, 256 = 24 words) +- **Returns**: A string mnemonic phrase -If randomness is weak, everything after it is weak. +### `deriveWallet(mnemonic, path)` +Derives a single wallet from a mnemonic. +- **mnemonic**: Your seed phrase +- **path** (optional): HD derivation path (default: `m/44'/60'/0'/0/0`) +- **Returns**: Object with `address`, `publicKey`, and `privateKey` ---- +### `deriveMultiple(mnemonic, count, basePath)` +Generate multiple wallets from a single mnemonic. +- **mnemonic**: Your seed phrase +- **count** (optional): How many wallets to create (default: 5) +- **basePath** (optional): Base derivation path (default: `m/44'/60'/0'/0`) +- **Returns**: Array of wallet objects -### 2. Converting Random Numbers to Words +## Requirements -Each random number is converted into a word. +- Node.js (v12 or higher) +- `bip39` - BIP39 mnemonic utilities +- `ethers` - Ethereum library for wallet operations -The number is reduced using modulo dictionary length so it always maps to a valid word index. +## Security Note -This creates a list of human readable words. +Never share your private keys or mnemonic phrases! This code is meant for development and testing. For production use, consider using hardware wallets or proper key management systems. ---- -### 3. Creating the Mnemonic Phrase - -All generated words are concatenated into a single phrase separated by spaces. - -This phrase is the mnemonic phrase. - -Example - -word1 word2 word3 word4 ... - -At this stage it is just text and has no cryptographic power yet. - ---- - -### 4. Creating the Seed Using SHA-256 - -The mnemonic phrase is hashed using SHA-256. - -The output is a 256-bit value represented as 64 hex characters. - -This hash is treated as the seed. - -The seed represents all previous steps combined. - ---- - -### 5. Creating the Private Key Using Keccak-256 - -The seed is hashed again using Keccak-256. - -This produces another 256-bit value. - -This value is treated as the Ethereum private key. - -At this point you must treat it as extremely sensitive data. - -Anyone with this key owns the funds. - ---- - -### 6. Generating the Public Key Using secp256k1 - -The private key is passed into the secp256k1 elliptic curve. - -This produces a public key. - -The public key is uncompressed and starts with 0x04. - -It contains both X and Y coordinates of the elliptic curve point. - ---- - -### 7. Hashing the Public Key - -The 0x04 prefix is removed from the public key. - -The remaining bytes are hashed using Keccak-256. - -This produces a 32 byte hash. - ---- - -### 8. Creating the Ethereum Address - -The last 20 bytes of the public key hash are taken. - -That is 40 hexadecimal characters. - -0x is prepended to the result. - -This final value is the Ethereum address. - ---- - -## Final Output Summary - -Mnemonic phrase -Seed generated using SHA-256 -Private key generated using Keccak-256 -Public key generated using secp256k1 -Ethereum address derived from public key hash - -Each step depends fully on the previous one. - ---- - -## Important Notes - -This implementation is for learning and experimentation. - -It does not fully follow official wallet standards. - -Do not use this code to store real funds. - ---- - -## How Real Wallets Improve This Process - -Real wallets such as MetaMask or hardware wallets add extra security layers. - -Below are the key improvements. - ---- - -## BIP-39 Mnemonic Standard Improvement - -Instead of directly hashing the mnemonic phrase, real wallets do the following: - -The mnemonic words come from a fixed 2048 word list. - -A checksum is embedded inside the mnemonic. - -This ensures typing errors can be detected. - ---- - -## PBKDF2 with 2048 Rounds - -After generating the mnemonic phrase, real wallets do not hash it only once. - -They use PBKDF2 with HMAC-SHA512. - -The mnemonic phrase is used as the password. - -An optional passphrase is added as salt. - -The hashing process is repeated 2048 times. - -This makes brute force attacks much slower. - -This step dramatically improves security. - ---- - -## Hierarchical Deterministic Wallets - -Instead of generating one key, real wallets generate a master seed. - -From that seed, unlimited private keys can be derived. - -This allows one mnemonic to control many addresses. - -This is defined in BIP-32 and BIP-44. - ---- - -## Why This Project Still Matters - -This code shows what is really happening under the hood. It shows that wallets are built from simple cryptographic steps. - -Understanding this flow makes you a better blockchain developer. - ---- - -## Final Warning - -Never expose private keys or seeds. - -Never log them in production. - -Never store them in plain text. - -This project is for educational purposes only. diff --git a/assignments/6-wallet-address/bip32wallet.js b/assignments/6-wallet-address/bip32wallet.js new file mode 100644 index 00000000..24b7934a --- /dev/null +++ b/assignments/6-wallet-address/bip32wallet.js @@ -0,0 +1,42 @@ +// const bip39 = require('bip39'); +// const { Wallet, HDNodeWallet } = require('ethers'); + +import bip39 from 'bip39'; +import { HDNodeWallet } from "ethers"; + +const ETH_DERIVATION_PATH = "m/44'/60'/0'/0/0"; + +const deriveWallet = ( + mnemonic, + path = ETH_DERIVATION_PATH +) => { + if (!bip39.validateMnemonic(mnemonic)) { + throw new Error('Invalid mnemonic'); + } + + const wallet = HDNodeWallet.fromPhrase(mnemonic, undefined, path); + + return { + path, + address: wallet.address, + publicKey: wallet.publicKey, + privateKey: wallet.privateKey + }; +}; + +const generateMnemonic = (strength = 128) => + bip39.generateMnemonic(strength); + +const deriveMultiple = ( + mnemonic, + count = 5, + basePath = "m/44'/60'/0'/0" +) => + Array.from({ length: count }, (_, i) => + deriveWallet(mnemonic, `${basePath}/${i}`) + ); + +/* Example */ +const mnemonic = generateMnemonic(); +console.log(deriveWallet(mnemonic)); +console.log(deriveMultiple(mnemonic, 3)); diff --git a/assignments/6-wallet-address/dictionary.json b/assignments/6-wallet-address/dictionary.json deleted file mode 100644 index a9bd31d7..00000000 --- a/assignments/6-wallet-address/dictionary.json +++ /dev/null @@ -1,258 +0,0 @@ -{ - "0": "apple", - "1": "bravo", - "2": "cactus", - "3": "delta", - "4": "ember", - "5": "falcon", - "6": "galaxy", - "7": "harbor", - "8": "island", - "9": "jungle", - "10": "kernel", - "11": "lunar", - "12": "meteor", - "13": "nebula", - "14": "oasis", - "15": "planet", - "16": "quantum", - "17": "rocket", - "18": "saturn", - "19": "tiger", - "20": "umbrella", - "21": "vector", - "22": "wander", - "23": "xenon", - "24": "yonder", - "25": "zephyr", - "26": "anchor", - "27": "beacon", - "28": "comet", - "29": "dragon", - "30": "echo", - "31": "forest", - "32": "glacier", - "33": "horizon", - "34": "ignite", - "35": "javelin", - "36": "keystone", - "37": "labyrinth", - "38": "magnet", - "39": "navigator", - "40": "oracle", - "41": "phoenix", - "42": "quartz", - "43": "ripple", - "44": "summit", - "45": "thunder", - "46": "utopia", - "47": "vortex", - "48": "willow", - "49": "xplorer", - "50": "yield", - "51": "zenith", - "52": "atlas", - "53": "binary", - "54": "cipher", - "55": "drift", - "56": "emberly", - "57": "fracture", - "58": "gravity", - "59": "helix", - "60": "ion", - "61": "jigsaw", - "62": "krypton", - "63": "lighthouse", - "64": "monsoon", - "65": "neutron", - "66": "orbit", - "67": "pulse", - "68": "quiver", - "69": "resonance", - "70": "signal", - "71": "terminal", - "72": "uplink", - "73": "velocity", - "74": "waveform", - "75": "xylophone", - "76": "yearling", - "77": "zodiac", - "78": "apex", - "79": "buffer", - "80": "cluster", - "81": "daemon", - "82": "entropy", - "83": "firefly", - "84": "grid", - "85": "hash", - "86": "inertia", - "87": "junction", - "88": "kilobyte", - "89": "latency", - "90": "matrix", - "91": "node", - "92": "opcode", - "93": "payload", - "94": "queue", - "95": "runtime", - "96": "sandbox", - "97": "thread", - "98": "uplift", - "99": "variable", - "100": "webhook", - "101": "xencode", - "102": "yielding", - "103": "zbuffer", - "104": "alpha", - "105": "beta", - "106": "gamma", - "107": "epsilon", - "108": "lambda", - "109": "sigma", - "110": "omega", - "111": "theta", - "112": "deltaflow", - "113": "bitrate", - "114": "checksum", - "115": "datagram", - "116": "endpoint", - "117": "firewall", - "118": "gateway", - "119": "handshake", - "120": "index", - "121": "json", - "122": "keypair", - "123": "ledger", - "124": "middleware", - "125": "namespace", - "126": "overflow", - "127": "protocol", - "128": "query", - "129": "response", - "130": "state", - "131": "token", - "132": "uptime", - "133": "validator", - "134": "worker", - "135": "xml", - "136": "yaml", - "137": "zero", - "138": "aurora", - "139": "blizzard", - "140": "cyclone", - "141": "drought", - "142": "earthquake", - "143": "flood", - "144": "gust", - "145": "hail", - "146": "iceberg", - "147": "jetstream", - "148": "kilowatt", - "149": "lightning", - "150": "monolith", - "151": "nightfall", - "152": "outbreak", - "153": "pressure", - "154": "quarantine", - "155": "rainfall", - "156": "seismic", - "157": "tempest", - "158": "updraft", - "159": "volcano", - "160": "whirlwind", - "161": "xray", - "162": "yearstorm", - "163": "zenwind", - "164": "acorn", - "165": "boulder", - "166": "canopy", - "167": "dandelion", - "168": "evergreen", - "169": "fern", - "170": "grove", - "171": "hollow", - "172": "ivy", - "173": "juniper", - "174": "kelp", - "175": "lichen", - "176": "meadow", - "177": "nutmeg", - "178": "oak", - "179": "pine", - "180": "quince", - "181": "root", - "182": "sprout", - "183": "thicket", - "184": "underbrush", - "185": "vine", - "186": "willowtree", - "187": "xerophyte", - "188": "yucca", - "189": "zinnia", - "190": "arch", - "191": "bridge", - "192": "column", - "193": "dome", - "194": "embankment", - "195": "foundation", - "196": "girder", - "197": "hinge", - "198": "insulation", - "199": "joist", - "200": "keel", - "201": "lintel", - "202": "mortar", - "203": "nail", - "204": "overlay", - "205": "pillar", - "206": "quarry", - "207": "rebar", - "208": "scaffold", - "209": "truss", - "210": "undercut", - "211": "vault", - "212": "weld", - "213": "xbrace", - "214": "yardarm", - "215": "zigzag", - "216": "amber", - "217": "bronze", - "218": "copper", - "219": "diamond", - "220": "emerald", - "221": "feldspar", - "222": "gold", - "223": "hematite", - "224": "iron", - "225": "jade", - "226": "kaolin", - "227": "limestone", - "228": "marble", - "229": "nickel", - "230": "obsidian", - "231": "platinum", - "232": "quartzite", - "233": "ruby", - "234": "silver", - "235": "topaz", - "236": "uranium", - "237": "vanadium", - "238": "wolfram", - "239": "xenolith", - "240": "yellowstone", - "241": "zircon", - "242": "alphaone", - "243": "betatwo", - "244": "gammathree", - "245": "deltafour", - "246": "epsilonfive", - "247": "zetasix", - "248": "etaseven", - "249": "thetaeight", - "250": "iotanine", - "251": "kappaten", - "252": "lambdatwelve", - "253": "mutathirteen", - "254": "nusixteen", - "255": "omegafinal" -} diff --git a/assignments/6-wallet-address/package-lock.json b/assignments/6-wallet-address/package-lock.json index 9cbca588..8baf108b 100644 --- a/assignments/6-wallet-address/package-lock.json +++ b/assignments/6-wallet-address/package-lock.json @@ -1,18 +1,16 @@ { - "name": "6-wallet-address", + "name": "ethereum-hd-wallet", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "6-wallet-address", + "name": "ethereum-hd-wallet", "version": "1.0.0", - "license": "ISC", + "license": "MIT", "dependencies": { - "ethers": "^6.16.0" - }, - "devDependencies": { - "ts-node": "^10.9.2" + "bip39": "^3.1.0", + "ethers": "^6.13.4" } }, "node_modules/@adraffy/ens-normalize": { @@ -21,47 +19,6 @@ "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", "license": "MIT" }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, "node_modules/@noble/curves": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", @@ -74,7 +31,7 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@noble/hashes": { + "node_modules/@noble/curves/node_modules/@noble/hashes": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", @@ -86,98 +43,40 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", - "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT" + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } }, "node_modules/@types/node": { "version": "22.7.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.19.2" } }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/aes-js": { "version": "4.0.0-beta.5", "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", "license": "MIT" }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/diff": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", - "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" + "node_modules/bip39": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.1.0.tgz", + "integrity": "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==", + "license": "ISC", + "dependencies": { + "@noble/hashes": "^1.2.0" } }, "node_modules/ethers": { @@ -208,55 +107,16 @@ "node": ">=14.0.0" } }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, + "node_modules/ethers/node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" + "engines": { + "node": ">= 16" }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } + "funding": { + "url": "https://paulmillr.com/funding/" } }, "node_modules/tslib": { @@ -265,34 +125,12 @@ "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "license": "0BSD" }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "license": "MIT" }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "license": "MIT" - }, "node_modules/ws": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", @@ -313,16 +151,6 @@ "optional": true } } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } } } } diff --git a/assignments/6-wallet-address/package.json b/assignments/6-wallet-address/package.json index 8570b2d5..8b3d7872 100644 --- a/assignments/6-wallet-address/package.json +++ b/assignments/6-wallet-address/package.json @@ -1,19 +1,24 @@ { - "name": "6-wallet-address", + "name": "ethereum-hd-wallet", "version": "1.0.0", - "description": "", + "description": "Ethereum HD wallet derivation using BIP39 and ethers.js", "main": "index.js", + "type": "module", "scripts": { - "wallet-address": "ts-node wallet-address.ts" + "wallet": "node ./bip32wallet.js" }, - "keywords": [], - "author": "", - "license": "ISC", - "type": "module", + "keywords": [ + "ethereum", + "ethers", + "bip39", + "hd-wallet", + "web3", + "crypto" + ], + "author": "Farouq Alhassan", + "license": "MIT", "dependencies": { - "ethers": "^6.16.0" - }, - "devDependencies": { - "ts-node": "^10.9.2" + "bip39": "^3.1.0", + "ethers": "^6.13.4" } } diff --git a/assignments/6-wallet-address/tsconfig.json b/assignments/6-wallet-address/tsconfig.json deleted file mode 100644 index 6d203e3b..00000000 --- a/assignments/6-wallet-address/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "module": "esnext", - "moduleResolution": "node", - "types": ["node"], - "strict": true, - "esModuleInterop": true - } -} diff --git a/assignments/6-wallet-address/wallet-address.ts b/assignments/6-wallet-address/wallet-address.ts deleted file mode 100644 index 08f09656..00000000 --- a/assignments/6-wallet-address/wallet-address.ts +++ /dev/null @@ -1,126 +0,0 @@ - -import fs from 'node:fs'; -import { randomBytes, createHash } from 'node:crypto'; -import { keccak256, SigningKey } from 'ethers'; - -// Read dictionary -const rawDictionaryData = fs.readFileSync('./dictionary.json', 'utf-8'); -const wordDictionary: Record = JSON.parse(rawDictionaryData); - -// Convert dictionary values to array -const wordList: string[] = Object.values(wordDictionary); - -// wordCount -const dictionaryLength: number = wordList.length; - -const mnemonicCount: number = 12; - -function generateRandomNumbers(count: number): number[] { - console.log('Random bytes are: ', count); - return Array.from(randomBytes(count)); -} - -function numberToWord(n: number): string { - const index = n % dictionaryLength; - return wordList[index]; -} - -// 1. Generate secure numbers -const numberArray: number[] = generateRandomNumbers(mnemonicCount); - -// 2. Convert numbers to words -const wordArray: string[] = numberArray.map(numberToWord); - -// 3. Concatenate into final mnemonic phrase -function generateCodeWords(): string { - return wordArray.join(' '); -} - -console.log(' '); -const mnemonicPhrase = generateCodeWords(); -console.log(`My mnemonic phrase is: ${mnemonicPhrase}`); - -console.log(' '); - -// SHA256 and Keccak256 hashing functions -function sha256Hash(input: string): string { - const hash = createHash('sha256').update(input, 'utf8').digest('hex'); - - console.log('SHA-256 seed:', hash); - console.log('Seed length (hex):', hash.length); // 64 - return hash; -} - -// function keccak256Hash(hexInput: string): string { -// const bytes = arrayify('0x' + hexInput.replace(/^0x/, '')); -// const hash = keccak256(bytes); - -// console.log('Keccak-256 hash:', hash); -// console.log('Hash length:', hash.length); // 66 (0x + 64) -// return hash; -// } - -function keccak256Hash(hexInput: string): string { - // Ensure 0x-prefixed hex so ethers treats it as BYTES - const normalized = hexInput.startsWith('0x') ? hexInput : '0x' + hexInput; - - const hash = keccak256(normalized); - - console.log('Keccak-256 hash:', hash); - console.log('Hash length:', hash.length); // 66 - return hash; -} - -// 4. Seed: Hash the concatenated words (mnemonic phrase) -const seed = sha256Hash(mnemonicPhrase); -console.log(' '); - -// 5. Private Key: Hashing the seed using Keccak-256 algorithm -const privateKey = keccak256Hash(seed); -console.log(' '); - -// 6. Generate public key from private key (secp256k1) -// in `ethers`, `SigningKey` is a thin wrapper around *secp256k1* elliptic curve -const signingKey = new SigningKey(privateKey); -const publicKey = signingKey.publicKey; - -console.log('Public Key:', publicKey); -console.log('Public Key length:', publicKey.length); // 132 -console.log(' '); - -// 7. Remove uncompressed prefix (0x04) and hash as BYTES -// const publicKeyWithoutPrefix = '0x' + publicKey.slice(4); -// const publicKeyHash = keccak256(publicKeyWithoutPrefix); -const publicKeyHash = keccak256(publicKey); -console.log('Keccak-256(Public Key) hash:', publicKeyHash); -console.log('The length of the Public Key hash:', publicKeyHash.length); -console.log(' '); - -// Step 10: Take last 20 bytes (40 hex chars) -const slicedPublicKeyHash = publicKeyHash.slice(-40); -console.log("Sliced public key hash is: ", slicedPublicKeyHash); -console.log(' '); - -// const address = '0x' + publicKeyHash.slice(-40); -const address = "0x" + slicedPublicKeyHash; -console.log('Ethereum Address:', address); -console.log('Address length is: ', address.length); -console.log(' '); - - - -// 1. Start with a cryptographically secure pseudo-random number generator -// 2. Generate a set of code words from the random output -// 3. Concatenate all the code words into a single word or phrase -// 4. Hash the concatenated words using SHA to form the mnemonic seed -// 5. Treat the seed as the hash result of all previous steps combined -// 6. Hash the seed again using Keccak-256 -// 7. The Keccak-256 output is the final 256-bit private key - -// 8. Use *secp256k1* to hash your private key to get a public key -// 9. Use Keccak-256 to hash the public key -// 10. Slice the first 20 index of the output of the result of the keccak-256 and pre-pend 0x to its result, you get your *address* - - - - diff --git a/assignments/NFT-Marketplace/.gitignore b/assignments/NFT-Marketplace/.gitignore new file mode 100644 index 00000000..991a319e --- /dev/null +++ b/assignments/NFT-Marketplace/.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/assignments/NFT-Marketplace/README.md b/assignments/NFT-Marketplace/README.md new file mode 100644 index 00000000..968246e9 --- /dev/null +++ b/assignments/NFT-Marketplace/README.md @@ -0,0 +1,57 @@ +# Sample Hardhat 3 Beta Project (`mocha` and `ethers`) + +This project showcases a Hardhat 3 Beta project using `mocha` for tests and the `ethers` library for Ethereum interactions. + +To learn more about the Hardhat 3 Beta, please visit the [Getting Started guide](https://hardhat.org/docs/getting-started#getting-started-with-hardhat-3). To share your feedback, join our [Hardhat 3 Beta](https://hardhat.org/hardhat3-beta-telegram-group) Telegram group or [open an issue](https://github.com/NomicFoundation/hardhat/issues/new) in our GitHub issue tracker. + +## Project Overview + +This example project includes: + +- A simple Hardhat configuration file. +- Foundry-compatible Solidity unit tests. +- TypeScript integration tests using `mocha` and ethers.js +- Examples demonstrating how to connect to different types of networks, including locally simulating OP mainnet. + +## Usage + +### Running Tests + +To run all the tests in the project, execute the following command: + +```shell +npx hardhat test +``` + +You can also selectively run the Solidity or `mocha` tests: + +```shell +npx hardhat test solidity +npx hardhat test mocha +``` + +### Make a deployment to Sepolia + +This project includes an example Ignition module to deploy the contract. You can deploy this module to a locally simulated chain or to Sepolia. + +To run the deployment to a local chain: + +```shell +npx hardhat ignition deploy ignition/modules/Counter.ts +``` + +To run the deployment to Sepolia, you need an account with funds to send the transaction. The provided Hardhat configuration includes a Configuration Variable called `SEPOLIA_PRIVATE_KEY`, which you can use to set the private key of the account you want to use. + +You can set the `SEPOLIA_PRIVATE_KEY` variable using the `hardhat-keystore` plugin or by setting it as an environment variable. + +To set the `SEPOLIA_PRIVATE_KEY` config variable using `hardhat-keystore`: + +```shell +npx hardhat keystore set SEPOLIA_PRIVATE_KEY +``` + +After setting the variable, you can run the deployment with the Sepolia network: + +```shell +npx hardhat ignition deploy --network sepolia ignition/modules/Counter.ts +``` diff --git a/assignments/NFT-Marketplace/contracts/Counter.sol b/assignments/NFT-Marketplace/contracts/Counter.sol new file mode 100644 index 00000000..8d00cb7c --- /dev/null +++ b/assignments/NFT-Marketplace/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/assignments/NFT-Marketplace/contracts/Counter.t.sol b/assignments/NFT-Marketplace/contracts/Counter.t.sol new file mode 100644 index 00000000..ac71d5b8 --- /dev/null +++ b/assignments/NFT-Marketplace/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/assignments/NFT-Marketplace/contracts/track-a.sol b/assignments/NFT-Marketplace/contracts/track-a.sol new file mode 100644 index 00000000..8322dea7 --- /dev/null +++ b/assignments/NFT-Marketplace/contracts/track-a.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +poragma solidity ^0.8.20; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract StakingRewards is ReentrancyGuard, Ownable { + IERC20 public immutable stakingToken; + IERC20 public immutable rewardsToken; + + uint256 public rewardRate = 100; // Tokens per second + uint256 public lastUpdateTime; + uint256 public rewardPerTokenStored; + + mapping(address => uint256) public userRewardPerTokenPaid; + mapping(address => uint256) public rewards; + + uint256 private _totalSupply; + mapping(address => uint256) private _balances; + + constructor(address _stakingToken, address _rewardsToken) { + stakingToken = IERC20(_stakingToken); + rewardsToken = IERC20(_rewardsToken); + } + + // --- Modifiers --- + + modifier updateReward(address account) { + rewardPerTokenStored = rewardPerToken(); + lastUpdateTime = block.timestamp; + if (account != address(0)) { + rewards[account] = earned(account); + userRewardPerTokenPaid[account] = rewardPerTokenStored; + } + _; + } + + // --- Logic Functions --- + + function rewardPerToken() public view returns (uint256) { + if (_totalSupply == 0) { + return rewardPerTokenStored; + } + return rewardPerTokenStored + (rewardRate * (block.timestamp - lastUpdateTime) * 1e18 / _totalSupply); + } + + function earned(address account) public view returns (uint256) { + return (_balances[account] * (rewardPerToken() - userRewardPerTokenPaid[account]) / 1e18) + rewards[account]; + } + + // --- Functional Requirements --- + + function stake(uint256 amount) external nonReentrant updateReward(msg.sender) { + require(amount > 0, "Cannot stake 0"); + _totalSupply += amount; + _balances[msg.sender] += amount; + stakingToken.transferFrom(msg.sender, address(this), amount); + } + + function withdraw(uint256 amount) external nonReentrant updateReward(msg.sender) { + require(amount > 0 && _balances[msg.sender] >= amount, "Invalid amount"); + _totalSupply -= amount; + _balances[msg.sender] -= amount; + stakingToken.transfer(msg.sender, amount); + } + + function claimRewards() external nonReentrant updateReward(msg.sender) { + uint256 reward = rewards[msg.sender]; + if (reward > 0) { + rewards[msg.sender] = 0; + rewardsToken.transfer(msg.sender, reward); + } + } +} \ No newline at end of file diff --git a/assignments/NFT-Marketplace/contracts/track-b.sol b/assignments/NFT-Marketplace/contracts/track-b.sol new file mode 100644 index 00000000..ac743dc3 --- /dev/null +++ b/assignments/NFT-Marketplace/contracts/track-b.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract MinimalMarketplace is ReentrancyGuard, Ownable { + + struct Listing { + address seller; + address nftContract; + uint256 tokenId; + uint256 price; + bool active; + } + + uint256 public feeBps = 250; // 2.5% + address public treasury; + + // nftContract => tokenId => Listing + mapping(address => mapping(uint256 => Listing)) public listings; + + event Listed(address indexed seller, address indexed nft, uint256 indexed tokenId, uint256 price); + event Sale(address indexed buyer, address indexed nft, uint256 indexed tokenId, uint256 price); + event Canceled(address indexed seller, address indexed nft, uint256 indexed tokenId); + + constructor(address _treasury) { + treasury = _treasury; + } + + function listNft(address _nftContract, uint256 _tokenId, uint256 _price) external nonReentrant { + require(_price > 0, "Price must be > 0"); + IERC721 nft = IERC721(_nftContract); + + require(nft.ownerOf(_tokenId) == msg.sender, "Not the owner"); + require(nft.isApprovedForAll(msg.sender, address(this)), "Marketplace not approved"); + + // Escrow the NFT + nft.transferFrom(msg.sender, address(this), _tokenId); + + listings[_nftContract][_tokenId] = Listing({ + seller: msg.sender, + nftContract: _nftContract, + tokenId: _tokenId, + price: _price, + active: true + }); + + emit Listed(msg.sender, _nftContract, _tokenId, _price); + } + + function cancelListing(address _nftContract, uint256 _tokenId) external nonReentrant { + Listing storage listing = listings[_nftContract][_tokenId]; + require(listing.active, "Not active"); + require(listing.seller == msg.sender, "Not the seller"); + + listing.active = false; + IERC721(_nftContract).transferFrom(address(this), msg.sender, _tokenId); + + emit Canceled(msg.sender, _nftContract, _tokenId); + } + + function buyNft(address _nftContract, uint256 _tokenId) external payable nonReentrant { + Listing storage listing = listings[_nftContract][_tokenId]; + require(listing.active, "Listing not active"); + require(msg.value >= listing.price, "Insufficient ETH"); + + listing.active = false; // Prevent reentrancy/double buy + + uint256 fee = (listing.price * feeBps) / 10000; + uint256 sellerProceeds = listing.price - fee; + + // 1. Pay Treasury + (bool feeSuccess, ) = payable(treasury).call{value: fee}(""); + require(feeSuccess, "Fee transfer failed"); + + // 2. Pay Seller + (bool sellerSuccess, ) = payable(listing.seller).call{value: sellerProceeds}(""); + require(sellerSuccess, "Seller transfer failed"); + + // 3. Transfer NFT to Buyer + IERC721(_nftContract).transferFrom(address(this), msg.sender, _tokenId); + + // 4. Refund overpayment if any + if (msg.value > listing.price) { + payable(msg.sender).transfer(msg.value - listing.price); + } + + emit Sale(msg.sender, _nftContract, _tokenId, listing.price); + } + + function updateFee(uint256 _newFeeBps) external onlyOwner { + require(_newFeeBps <= 1000, "Fee too high"); // Cap at 10% + feeBps = _newFeeBps; + } +} \ No newline at end of file diff --git a/assignments/NFT-Marketplace/hardhat.config.ts b/assignments/NFT-Marketplace/hardhat.config.ts new file mode 100644 index 00000000..7092b852 --- /dev/null +++ b/assignments/NFT-Marketplace/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/assignments/NFT-Marketplace/ignition/modules/Counter.ts b/assignments/NFT-Marketplace/ignition/modules/Counter.ts new file mode 100644 index 00000000..042e61c8 --- /dev/null +++ b/assignments/NFT-Marketplace/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/assignments/NFT-Marketplace/package.json b/assignments/NFT-Marketplace/package.json new file mode 100644 index 00000000..c60cfa70 --- /dev/null +++ b/assignments/NFT-Marketplace/package.json @@ -0,0 +1,20 @@ +{ + "name": "Track-A", + "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.9", + "mocha": "^11.7.5", + "typescript": "~5.8.0" + } +} diff --git a/assignments/NFT-Marketplace/scripts/send-op-tx.ts b/assignments/NFT-Marketplace/scripts/send-op-tx.ts new file mode 100644 index 00000000..c10a2360 --- /dev/null +++ b/assignments/NFT-Marketplace/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/assignments/NFT-Marketplace/test/Counter.ts b/assignments/NFT-Marketplace/test/Counter.ts new file mode 100644 index 00000000..f8c38986 --- /dev/null +++ b/assignments/NFT-Marketplace/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/assignments/NFT-Marketplace/tsconfig.json b/assignments/NFT-Marketplace/tsconfig.json new file mode 100644 index 00000000..9b1380cc --- /dev/null +++ b/assignments/NFT-Marketplace/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/assignments/Solidity-Assignment/Todo-list/.gitignore b/assignments/Solidity-Assignment/Todo-list/.gitignore new file mode 100644 index 00000000..3fe15ba0 --- /dev/null +++ b/assignments/Solidity-Assignment/Todo-list/.gitignore @@ -0,0 +1,23 @@ +# 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 + +# dotenv environment variables file +.env diff --git a/assignments/Solidity-Assignment/Todo-list/README.md b/assignments/Solidity-Assignment/Todo-list/README.md new file mode 100644 index 00000000..968246e9 --- /dev/null +++ b/assignments/Solidity-Assignment/Todo-list/README.md @@ -0,0 +1,57 @@ +# Sample Hardhat 3 Beta Project (`mocha` and `ethers`) + +This project showcases a Hardhat 3 Beta project using `mocha` for tests and the `ethers` library for Ethereum interactions. + +To learn more about the Hardhat 3 Beta, please visit the [Getting Started guide](https://hardhat.org/docs/getting-started#getting-started-with-hardhat-3). To share your feedback, join our [Hardhat 3 Beta](https://hardhat.org/hardhat3-beta-telegram-group) Telegram group or [open an issue](https://github.com/NomicFoundation/hardhat/issues/new) in our GitHub issue tracker. + +## Project Overview + +This example project includes: + +- A simple Hardhat configuration file. +- Foundry-compatible Solidity unit tests. +- TypeScript integration tests using `mocha` and ethers.js +- Examples demonstrating how to connect to different types of networks, including locally simulating OP mainnet. + +## Usage + +### Running Tests + +To run all the tests in the project, execute the following command: + +```shell +npx hardhat test +``` + +You can also selectively run the Solidity or `mocha` tests: + +```shell +npx hardhat test solidity +npx hardhat test mocha +``` + +### Make a deployment to Sepolia + +This project includes an example Ignition module to deploy the contract. You can deploy this module to a locally simulated chain or to Sepolia. + +To run the deployment to a local chain: + +```shell +npx hardhat ignition deploy ignition/modules/Counter.ts +``` + +To run the deployment to Sepolia, you need an account with funds to send the transaction. The provided Hardhat configuration includes a Configuration Variable called `SEPOLIA_PRIVATE_KEY`, which you can use to set the private key of the account you want to use. + +You can set the `SEPOLIA_PRIVATE_KEY` variable using the `hardhat-keystore` plugin or by setting it as an environment variable. + +To set the `SEPOLIA_PRIVATE_KEY` config variable using `hardhat-keystore`: + +```shell +npx hardhat keystore set SEPOLIA_PRIVATE_KEY +``` + +After setting the variable, you can run the deployment with the Sepolia network: + +```shell +npx hardhat ignition deploy --network sepolia ignition/modules/Counter.ts +``` diff --git a/assignments/Solidity-Assignment/Todo-list/contracts/Average2.sol b/assignments/Solidity-Assignment/Todo-list/contracts/Average2.sol new file mode 100644 index 00000000..4e0dbb52 --- /dev/null +++ b/assignments/Solidity-Assignment/Todo-list/contracts/Average2.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + + +contract Calculator { + uint public a; + uint public b; + uint public c; + + constructor(uint _a, uint _b){ + a = _a; + b = _b; + } + + + function sub() public returns(uint){ + c = a - b; + + return c; + } + + function add() public returns(uint){ + c = a + b; + + return c; + } + + function div() public returns(uint){ + c = a / b; + + return c; + } + + function mul() public returns(uint){ + c = a * b; + + return c; + } + + function set(uint _a, uint _b) public{ + a = _a; + b = _b; + } + + +} + +interface ICalculation { + function add() external returns(uint); + function sub() external returns(uint); + function mul() external returns(uint); + function div() external returns(uint); + +} + +contract InteractWithCalculatorContract{ + + ICalculation calculationContract; + + constructor(address _calculationContractAddress){ + calculationContract = ICalculation(_calculationContractAddress); + } + + function addInCalculationContract() public { + calculationContract.add(); + } + +} + + + +contract AverageInheritance is Calculator(20, 4){ + + function average() public returns(uint){ + uint d = div(); + c = d / 2; + + return c; + } + + +} +// contract SubFactory{ +// Sub sub; + +// function createNewSub() public returns(address){ +// Sub _sub = new Sub(); + +// return address(_sub); +// } +// } \ No newline at end of file diff --git a/assignments/Solidity-Assignment/Todo-list/contracts/Counter.sol b/assignments/Solidity-Assignment/Todo-list/contracts/Counter.sol new file mode 100644 index 00000000..8d00cb7c --- /dev/null +++ b/assignments/Solidity-Assignment/Todo-list/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/assignments/Solidity-Assignment/Todo-list/contracts/Counter.t.sol b/assignments/Solidity-Assignment/Todo-list/contracts/Counter.t.sol new file mode 100644 index 00000000..ac71d5b8 --- /dev/null +++ b/assignments/Solidity-Assignment/Todo-list/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/assignments/Solidity-Assignment/Todo-list/contracts/todo.sol b/assignments/Solidity-Assignment/Todo-list/contracts/todo.sol new file mode 100644 index 00000000..1701ddca --- /dev/null +++ b/assignments/Solidity-Assignment/Todo-list/contracts/todo.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +contract Todo{ + uint256 todoCounter; + + enum Status{ + Pending, + Done, + Cancelled, + Defaulted + } + + struct TodoList{ + uint id; + address owner; + string text; + Status status; + uint256 deadline; + } + + mapping(uint => TodoList) public todos; + + event TodoCreated(string text, uint deadline); + + + +function createTodo(string memory _text, uint _deadline) external returns(uint){ + require(bytes(_text).length > 0, "Empty text"); + require(_deadline > (block.timestamp + 600), "Invalid deadline"); + require(msg.sender != address(0), "Zero address"); + + todoCounter++; + + todos[todoCounter] = TodoList(todoCounter, msg.sender, _text, Status.Pending, _deadline); + + emit TodoCreated(_text, _deadline); + return todoCounter; +} + +function markAsDone(uint _id) external { + require((_id > 0) && (_id <= todoCounter) , 'invalid id'); + TodoList storage todo = todos[_id]; + require(todo.status == Status.Pending, "Not pending"); + require(msg.sender == todo.owner, "unauthorized Caller"); + + if(block.timestamp > todo.deadline){ + todo.status = Status.Defaulted; + } + else{ + todo.status = Status.Done; + } + +} + +} \ No newline at end of file diff --git a/assignments/Solidity-Assignment/Todo-list/hardhat.config.ts b/assignments/Solidity-Assignment/Todo-list/hardhat.config.ts new file mode 100644 index 00000000..dcaa784d --- /dev/null +++ b/assignments/Solidity-Assignment/Todo-list/hardhat.config.ts @@ -0,0 +1,42 @@ +import hardhatToolboxMochaEthersPlugin from "@nomicfoundation/hardhat-toolbox-mocha-ethers"; +import { configVariable, defineConfig } from "hardhat/config"; + +export default defineConfig({ + plugins: [hardhatToolboxMochaEthersPlugin], + solidity: { + compilers: [ + { + 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")], + }, + }, + + verify: { + etherscan: { + apiKey: configVariable("ETHERSCAN_API_KEY"), + }, + }, + +}); diff --git a/assignments/Solidity-Assignment/Todo-list/ignition/modules/Counter.ts b/assignments/Solidity-Assignment/Todo-list/ignition/modules/Counter.ts new file mode 100644 index 00000000..042e61c8 --- /dev/null +++ b/assignments/Solidity-Assignment/Todo-list/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/assignments/Solidity-Assignment/Todo-list/ignition/modules/todo.ts b/assignments/Solidity-Assignment/Todo-list/ignition/modules/todo.ts new file mode 100644 index 00000000..7f1b42b1 --- /dev/null +++ b/assignments/Solidity-Assignment/Todo-list/ignition/modules/todo.ts @@ -0,0 +1,9 @@ +import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; + +export default buildModule("TodoModule", (m) => { + const todo = m.contract("Todo"); + + m.call(todo, "incBy", [5n]); + + return { todo }; +}); \ No newline at end of file diff --git a/assignments/Solidity-Assignment/Todo-list/package.json b/assignments/Solidity-Assignment/Todo-list/package.json new file mode 100644 index 00000000..ca53ceda --- /dev/null +++ b/assignments/Solidity-Assignment/Todo-list/package.json @@ -0,0 +1,9 @@ +{ + "name": "todo-list", + "version": "1.0.0", + "type": "module", + "devDependencies": { + "hardhat": "^2.22.0", + "@nomicfoundation/hardhat-toolbox": "^6.1.0" + } +} diff --git a/assignments/Solidity-Assignment/Todo-list/scripts/send-op-tx.ts b/assignments/Solidity-Assignment/Todo-list/scripts/send-op-tx.ts new file mode 100644 index 00000000..c10a2360 --- /dev/null +++ b/assignments/Solidity-Assignment/Todo-list/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/assignments/Solidity-Assignment/Todo-list/test/Counter.ts b/assignments/Solidity-Assignment/Todo-list/test/Counter.ts new file mode 100644 index 00000000..f8c38986 --- /dev/null +++ b/assignments/Solidity-Assignment/Todo-list/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/assignments/Solidity-Assignment/Todo-list/tsconfig.json b/assignments/Solidity-Assignment/Todo-list/tsconfig.json new file mode 100644 index 00000000..9b1380cc --- /dev/null +++ b/assignments/Solidity-Assignment/Todo-list/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/assignments/Solidity-Assignment/escrow-freelancer/.gitignore b/assignments/Solidity-Assignment/escrow-freelancer/.gitignore new file mode 100644 index 00000000..991a319e --- /dev/null +++ b/assignments/Solidity-Assignment/escrow-freelancer/.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/assignments/Solidity-Assignment/escrow-freelancer/.vscode_hardhat_config_1770802999882.ts b/assignments/Solidity-Assignment/escrow-freelancer/.vscode_hardhat_config_1770802999882.ts new file mode 100644 index 00000000..7092b852 --- /dev/null +++ b/assignments/Solidity-Assignment/escrow-freelancer/.vscode_hardhat_config_1770802999882.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/assignments/Solidity-Assignment/escrow-freelancer/README.md b/assignments/Solidity-Assignment/escrow-freelancer/README.md new file mode 100644 index 00000000..968246e9 --- /dev/null +++ b/assignments/Solidity-Assignment/escrow-freelancer/README.md @@ -0,0 +1,57 @@ +# Sample Hardhat 3 Beta Project (`mocha` and `ethers`) + +This project showcases a Hardhat 3 Beta project using `mocha` for tests and the `ethers` library for Ethereum interactions. + +To learn more about the Hardhat 3 Beta, please visit the [Getting Started guide](https://hardhat.org/docs/getting-started#getting-started-with-hardhat-3). To share your feedback, join our [Hardhat 3 Beta](https://hardhat.org/hardhat3-beta-telegram-group) Telegram group or [open an issue](https://github.com/NomicFoundation/hardhat/issues/new) in our GitHub issue tracker. + +## Project Overview + +This example project includes: + +- A simple Hardhat configuration file. +- Foundry-compatible Solidity unit tests. +- TypeScript integration tests using `mocha` and ethers.js +- Examples demonstrating how to connect to different types of networks, including locally simulating OP mainnet. + +## Usage + +### Running Tests + +To run all the tests in the project, execute the following command: + +```shell +npx hardhat test +``` + +You can also selectively run the Solidity or `mocha` tests: + +```shell +npx hardhat test solidity +npx hardhat test mocha +``` + +### Make a deployment to Sepolia + +This project includes an example Ignition module to deploy the contract. You can deploy this module to a locally simulated chain or to Sepolia. + +To run the deployment to a local chain: + +```shell +npx hardhat ignition deploy ignition/modules/Counter.ts +``` + +To run the deployment to Sepolia, you need an account with funds to send the transaction. The provided Hardhat configuration includes a Configuration Variable called `SEPOLIA_PRIVATE_KEY`, which you can use to set the private key of the account you want to use. + +You can set the `SEPOLIA_PRIVATE_KEY` variable using the `hardhat-keystore` plugin or by setting it as an environment variable. + +To set the `SEPOLIA_PRIVATE_KEY` config variable using `hardhat-keystore`: + +```shell +npx hardhat keystore set SEPOLIA_PRIVATE_KEY +``` + +After setting the variable, you can run the deployment with the Sepolia network: + +```shell +npx hardhat ignition deploy --network sepolia ignition/modules/Counter.ts +``` diff --git a/assignments/Solidity-Assignment/escrow-freelancer/contracts/Counter.sol b/assignments/Solidity-Assignment/escrow-freelancer/contracts/Counter.sol new file mode 100644 index 00000000..8d00cb7c --- /dev/null +++ b/assignments/Solidity-Assignment/escrow-freelancer/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/assignments/Solidity-Assignment/escrow-freelancer/contracts/Counter.t.sol b/assignments/Solidity-Assignment/escrow-freelancer/contracts/Counter.t.sol new file mode 100644 index 00000000..ac71d5b8 --- /dev/null +++ b/assignments/Solidity-Assignment/escrow-freelancer/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/assignments/Solidity-Assignment/escrow-freelancer/contracts/escrow-freelance.sol b/assignments/Solidity-Assignment/escrow-freelancer/contracts/escrow-freelance.sol new file mode 100644 index 00000000..4cefaecf --- /dev/null +++ b/assignments/Solidity-Assignment/escrow-freelancer/contracts/escrow-freelance.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: SEE LICENSE IN LICENSE +pragma solidity ^0.8.28; + +contract FreeLance { + address public client; + address payable public freelancer; + uint256 public totalMilestones; + uint256 public completed; + uint256 public perMilestone; + bool public isSubmitted; + + modifier only(address account) { + require(msg.sender == account, "Unauthorized"); + _; + } + + constructor(address payable _freelancer, uint256 _count) payable { + require(msg.value > 0 && _count > 0); + client = msg.sender; + freelancer = _freelancer; + totalMilestones = _count; + perMilestone = msg.value / _count; + } + + // Freelancer marks milestone as done + function submit() external only(freelancer) { + require(completed < totalMilestones && !isSubmitted, "Cannot submit"); + isSubmitted = true; + } + + // Client approves and releases ETH + function approve() external only(client) { + require(isSubmitted, "Nothing to approve"); + + isSubmitted = false; + completed++; + + // On final milestone, send the full remaining balance + uint256 amount = (completed == totalMilestones) ? address(this).balance : perMilestone; + + + (bool success, ) = freelancer.call{value: amount}(""); + require(success, "Transfer failed"); + } +} \ No newline at end of file diff --git a/assignments/Solidity-Assignment/escrow-freelancer/hardhat.config.ts b/assignments/Solidity-Assignment/escrow-freelancer/hardhat.config.ts new file mode 100644 index 00000000..7092b852 --- /dev/null +++ b/assignments/Solidity-Assignment/escrow-freelancer/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/assignments/Solidity-Assignment/escrow-freelancer/ignition/modules/Counter.ts b/assignments/Solidity-Assignment/escrow-freelancer/ignition/modules/Counter.ts new file mode 100644 index 00000000..042e61c8 --- /dev/null +++ b/assignments/Solidity-Assignment/escrow-freelancer/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/assignments/Solidity-Assignment/escrow-freelancer/package.json b/assignments/Solidity-Assignment/escrow-freelancer/package.json new file mode 100644 index 00000000..57b0c1bd --- /dev/null +++ b/assignments/Solidity-Assignment/escrow-freelancer/package.json @@ -0,0 +1,20 @@ +{ + "name": "Another-escrow", + "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.8", + "chai": "^5.3.3", + "ethers": "^6.16.0", + "forge-std": "github:foundry-rs/forge-std#v1.9.4", + "hardhat": "^3.1.6", + "mocha": "^11.7.5", + "typescript": "~5.8.0" + } +} diff --git a/assignments/Solidity-Assignment/escrow-freelancer/scripts/send-op-tx.ts b/assignments/Solidity-Assignment/escrow-freelancer/scripts/send-op-tx.ts new file mode 100644 index 00000000..c10a2360 --- /dev/null +++ b/assignments/Solidity-Assignment/escrow-freelancer/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/assignments/Solidity-Assignment/escrow-freelancer/test/Counter.ts b/assignments/Solidity-Assignment/escrow-freelancer/test/Counter.ts new file mode 100644 index 00000000..f8c38986 --- /dev/null +++ b/assignments/Solidity-Assignment/escrow-freelancer/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/assignments/Solidity-Assignment/escrow-freelancer/test/freelance.ts b/assignments/Solidity-Assignment/escrow-freelancer/test/freelance.ts new file mode 100644 index 00000000..98d852e4 --- /dev/null +++ b/assignments/Solidity-Assignment/escrow-freelancer/test/freelance.ts @@ -0,0 +1,140 @@ +import { expect } from "chai"; +import { network } from "hardhat"; + +describe("FreeLance Contract (Hardhat v3)", function () { + let client: any; + let freelancer: any; + let other: any; + let publicClient: any; + let freelance: any; + + const TOTAL_MILESTONES = 2n; + const TOTAL_PAYMENT = 2n * 10n ** 18n; // 2 ETH + + beforeEach(async function () { + const { viem } = await network.connect(); + + // Get wallet clients (accounts) + const wallets = await viem.getWalletClients(); + client = wallets[0]; + freelancer = wallets[1]; + other = wallets[2]; + + publicClient = await viem.getPublicClient(); + + // Deploy contract + freelance = await viem.deployContract("FreeLance", [ + freelancer.account.address, + TOTAL_MILESTONES, + ], { + value: TOTAL_PAYMENT, + account: client.account, + }); + }); + + it("Should initialize correctly", async function () { + expect(await freelance.read.client()).to.equal(client.account.address); + expect(await freelance.read.freelancer()).to.equal( + freelancer.account.address + ); + expect(await freelance.read.totalMilestones()).to.equal( + TOTAL_MILESTONES + ); + + const perMilestone = TOTAL_PAYMENT / TOTAL_MILESTONES; + + expect(await freelance.read.perMilestone()).to.equal(perMilestone); + }); + + it("Only freelancer can submit milestone", async function () { + await expect( + freelance.write.submit({ + account: other.account, + }) + ).to.be.rejected; + + await freelance.write.submit({ + account: freelancer.account, + }); + + expect(await freelance.read.isSubmitted()).to.equal(true); + }); + + it("Client cannot approve without submission", async function () { + await expect( + freelance.write.approve({ + account: client.account, + }) + ).to.be.rejected; + }); + + it("Should release payment per milestone", async function () { + const perMilestone = await freelance.read.perMilestone(); + + // Submit milestone + await freelance.write.submit({ + account: freelancer.account, + }); + + const balanceBefore = await publicClient.getBalance({ + address: freelancer.account.address, + }); + + await freelance.write.approve({ + account: client.account, + }); + + const balanceAfter = await publicClient.getBalance({ + address: freelancer.account.address, + }); + + expect(balanceAfter - balanceBefore).to.equal(perMilestone); + expect(await freelance.read.completed()).to.equal(1n); + }); + + it("Should send remaining balance on final milestone", async function () { + // First milestone + await freelance.write.submit({ + account: freelancer.account, + }); + + await freelance.write.approve({ + account: client.account, + }); + + // Second milestone + await freelance.write.submit({ + account: freelancer.account, + }); + + const balanceBefore = await publicClient.getBalance({ + address: freelancer.account.address, + }); + + await freelance.write.approve({ + account: client.account, + }); + + const balanceAfter = await publicClient.getBalance({ + address: freelancer.account.address, + }); + + expect(await freelance.read.completed()).to.equal(2n); + expect(balanceAfter > balanceBefore).to.equal(true); + }); + + it("Cannot submit after all milestones completed", async function () { + // Complete both milestones + await freelance.write.submit({ account: freelancer.account }); + await freelance.write.approve({ account: client.account }); + + await freelance.write.submit({ account: freelancer.account }); + await freelance.write.approve({ account: client.account }); + + await expect( + freelance.write.submit({ + account: freelancer.account, + }) + ).to.be.rejected; + }); +}); diff --git a/assignments/Solidity-Assignment/escrow-freelancer/tsconfig.json b/assignments/Solidity-Assignment/escrow-freelancer/tsconfig.json new file mode 100644 index 00000000..9b1380cc --- /dev/null +++ b/assignments/Solidity-Assignment/escrow-freelancer/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/assignments/Solidity-Assignment/escrow/.gitignore b/assignments/Solidity-Assignment/escrow/.gitignore new file mode 100644 index 00000000..991a319e --- /dev/null +++ b/assignments/Solidity-Assignment/escrow/.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/assignments/Solidity-Assignment/escrow/README.md b/assignments/Solidity-Assignment/escrow/README.md new file mode 100644 index 00000000..968246e9 --- /dev/null +++ b/assignments/Solidity-Assignment/escrow/README.md @@ -0,0 +1,57 @@ +# Sample Hardhat 3 Beta Project (`mocha` and `ethers`) + +This project showcases a Hardhat 3 Beta project using `mocha` for tests and the `ethers` library for Ethereum interactions. + +To learn more about the Hardhat 3 Beta, please visit the [Getting Started guide](https://hardhat.org/docs/getting-started#getting-started-with-hardhat-3). To share your feedback, join our [Hardhat 3 Beta](https://hardhat.org/hardhat3-beta-telegram-group) Telegram group or [open an issue](https://github.com/NomicFoundation/hardhat/issues/new) in our GitHub issue tracker. + +## Project Overview + +This example project includes: + +- A simple Hardhat configuration file. +- Foundry-compatible Solidity unit tests. +- TypeScript integration tests using `mocha` and ethers.js +- Examples demonstrating how to connect to different types of networks, including locally simulating OP mainnet. + +## Usage + +### Running Tests + +To run all the tests in the project, execute the following command: + +```shell +npx hardhat test +``` + +You can also selectively run the Solidity or `mocha` tests: + +```shell +npx hardhat test solidity +npx hardhat test mocha +``` + +### Make a deployment to Sepolia + +This project includes an example Ignition module to deploy the contract. You can deploy this module to a locally simulated chain or to Sepolia. + +To run the deployment to a local chain: + +```shell +npx hardhat ignition deploy ignition/modules/Counter.ts +``` + +To run the deployment to Sepolia, you need an account with funds to send the transaction. The provided Hardhat configuration includes a Configuration Variable called `SEPOLIA_PRIVATE_KEY`, which you can use to set the private key of the account you want to use. + +You can set the `SEPOLIA_PRIVATE_KEY` variable using the `hardhat-keystore` plugin or by setting it as an environment variable. + +To set the `SEPOLIA_PRIVATE_KEY` config variable using `hardhat-keystore`: + +```shell +npx hardhat keystore set SEPOLIA_PRIVATE_KEY +``` + +After setting the variable, you can run the deployment with the Sepolia network: + +```shell +npx hardhat ignition deploy --network sepolia ignition/modules/Counter.ts +``` diff --git a/assignments/Solidity-Assignment/escrow/contracts/Counter.sol b/assignments/Solidity-Assignment/escrow/contracts/Counter.sol new file mode 100644 index 00000000..8d00cb7c --- /dev/null +++ b/assignments/Solidity-Assignment/escrow/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/assignments/Solidity-Assignment/escrow/contracts/Counter.t.sol b/assignments/Solidity-Assignment/escrow/contracts/Counter.t.sol new file mode 100644 index 00000000..ac71d5b8 --- /dev/null +++ b/assignments/Solidity-Assignment/escrow/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/assignments/Solidity-Assignment/escrow/contracts/escrow.sol b/assignments/Solidity-Assignment/escrow/contracts/escrow.sol new file mode 100644 index 00000000..92f5d6dc --- /dev/null +++ b/assignments/Solidity-Assignment/escrow/contracts/escrow.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +contract Escrow { + enum Status { + AWAITING_PAYMENT, + AWAITING_DELIVERY, + COMPLETED + } + + // Current status (public getter created by compiler) + Status public status; + + // Parties involved in the escrow + address public buyer; + address public seller; + + // Amount held in escrow (in wei) + uint256 public amount; + + // Initialize escrow with buyer and seller addresses + constructor(address _buyer, address _seller) { + buyer = _buyer; + seller = _seller; + status = Status.AWAITING_PAYMENT; + } + + /// Seller deposits payment + function deposit() external payable { + require(msg.sender == seller, "Only seller can deposit"); + require(status == Status.AWAITING_PAYMENT, "Already paid"); + require(msg.value > 0, "Amount must be more than zero"); + + amount = msg.value; + status = Status.AWAITING_DELIVERY; + } + + /// Buyer confirms goods received + function confirmReceipt() external { + require(msg.sender == buyer, "Only buyer can confirm"); + require(status == Status.AWAITING_DELIVERY, "Payment not made"); + + status = Status.COMPLETED; // set final state + + // Transfer the escrowed amount to the seller using `call` + // capture success boolean and revert if transfer fails + (bool success, ) = payable(seller).call{value: amount}(""); + require(success, "Transfer failed"); + } +} diff --git a/assignments/Solidity-Assignment/escrow/contracts/escrow2.sol b/assignments/Solidity-Assignment/escrow/contracts/escrow2.sol new file mode 100644 index 00000000..ae42b37d --- /dev/null +++ b/assignments/Solidity-Assignment/escrow/contracts/escrow2.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import "./escrow.sol"; + +contract EscrowFactory { + Escrow[] public escrows; + + // Event emitted when a new Escrow is created + event EscrowCreated( + address escrowAddress, + address buyer, + address seller + ); + + /// Buyer creates a new escrow order + function createEscrow(address seller) external { + Escrow escrow = new Escrow(msg.sender, seller); + escrows.push(escrow); + + emit EscrowCreated(address(escrow), msg.sender, seller); + } + + // Helper to get the number of escrows created + function getEscrowCount() external view returns (uint256) { + return escrows.length; + } +} diff --git a/assignments/Solidity-Assignment/escrow/contracts/escrow3.sol b/assignments/Solidity-Assignment/escrow/contracts/escrow3.sol new file mode 100644 index 00000000..4b903b06 --- /dev/null +++ b/assignments/Solidity-Assignment/escrow/contracts/escrow3.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +contract BasicEscrow { + // State Management + // Added AWAITING_RELEASE to give confirmDelivery a purpose + enum State { AWAITING_PAYMENT, + AWAITING_DELIVERY, + AWAITING_RELEASE, + COMPLETE } + State public currentState; + + // Participants + address public buyer; + address public seller; + address public escrowAgent; + uint256 public amount; + + // Events (Good for frontend tracking) + event FundsDeposited(address indexed buyer, uint256 amount); + event DeliveryConfirmed(address indexed seller); + event FundsReleased(address indexed seller, uint256 amount); + + // Modifiers for Access Control + modifier onlyBuyer() { + require(msg.sender == buyer, "Only the buyer can perform this action."); + _; + } + modifier onlyEscrowAgent() { + require(msg.sender == escrowAgent, "Only the escrow agent can perform this action."); + _; + + } + + constructor(address _buyer, address _seller) { + buyer = _buyer; + seller = _seller; + escrowAgent = msg.sender; + currentState = State.AWAITING_PAYMENT; + } + + /// Buyer deposits funds into the contract + function deposit() external payable onlyBuyer { + require(currentState == State.AWAITING_PAYMENT, "Already paid or complete."); + require(msg.value > 0, "Must deposit some Ether."); + + amount = msg.value; currentState = State.AWAITING_DELIVERY; + emit FundsDeposited(msg.sender, msg.value); } + + // Seller confirms delivery (updates state) + /// This fixes the "view" warning by changing currentState + function confirmDelivery() external { + require(msg.sender == seller, "Only the seller can confirm delivery."); + require(currentState == State.AWAITING_DELIVERY, "Not in delivery phase."); + + currentState = State.AWAITING_RELEASE; emit DeliveryConfirmed(msg.sender); + + } + + /// Agent releases funds to the seller + function releaseFunds() external onlyEscrowAgent { + + // Updated to require the new state + require(currentState == State.AWAITING_RELEASE, "Cannot release: Delivery not confirmed."); + currentState = State.COMPLETE; payable(seller).transfer(amount); + emit FundsReleased(seller, amount); + + } + /// Agent refunds the buyer + function refundBuyer() external onlyEscrowAgent { require(currentState != State.COMPLETE, "Cannot refund: Already complete."); + currentState = State.COMPLETE; payable(buyer).transfer(amount); + } + } \ No newline at end of file diff --git a/assignments/Solidity-Assignment/escrow/hardhat.config.ts b/assignments/Solidity-Assignment/escrow/hardhat.config.ts new file mode 100644 index 00000000..7092b852 --- /dev/null +++ b/assignments/Solidity-Assignment/escrow/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/assignments/Solidity-Assignment/escrow/ignition/modules/Counter.ts b/assignments/Solidity-Assignment/escrow/ignition/modules/Counter.ts new file mode 100644 index 00000000..042e61c8 --- /dev/null +++ b/assignments/Solidity-Assignment/escrow/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/assignments/Solidity-Assignment/escrow/package.json b/assignments/Solidity-Assignment/escrow/package.json new file mode 100644 index 00000000..535b8cd1 --- /dev/null +++ b/assignments/Solidity-Assignment/escrow/package.json @@ -0,0 +1,20 @@ +{ + "name": "escrow", + "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.8", + "chai": "^5.3.3", + "ethers": "^6.16.0", + "forge-std": "github:foundry-rs/forge-std#v1.9.4", + "hardhat": "^3.1.6", + "mocha": "^11.7.5", + "typescript": "~5.8.0" + } +} diff --git a/assignments/Solidity-Assignment/escrow/scripts/send-op-tx.ts b/assignments/Solidity-Assignment/escrow/scripts/send-op-tx.ts new file mode 100644 index 00000000..c10a2360 --- /dev/null +++ b/assignments/Solidity-Assignment/escrow/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/assignments/Solidity-Assignment/escrow/test/Counter.ts b/assignments/Solidity-Assignment/escrow/test/Counter.ts new file mode 100644 index 00000000..f8c38986 --- /dev/null +++ b/assignments/Solidity-Assignment/escrow/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/assignments/Solidity-Assignment/escrow/tsconfig.json b/assignments/Solidity-Assignment/escrow/tsconfig.json new file mode 100644 index 00000000..9b1380cc --- /dev/null +++ b/assignments/Solidity-Assignment/escrow/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" + } +}