From 146e794b33db9e79f143460774e8c94b3224a714 Mon Sep 17 00:00:00 2001 From: deborahamoni0-prog Date: Thu, 29 Jan 2026 15:01:42 +0100 Subject: [PATCH 1/6] feat: Implement Readme --- assignments/6-wallet-address/README.md | 220 +----------- assignments/6-wallet-address/dictionary.json | 258 -------------- .../6-wallet-address/package-lock.json | 328 ------------------ assignments/6-wallet-address/package.json | 19 - assignments/6-wallet-address/tsconfig.json | 10 - .../6-wallet-address/wallet-address.ts | 126 ------- 6 files changed, 1 insertion(+), 960 deletions(-) delete mode 100644 assignments/6-wallet-address/dictionary.json delete mode 100644 assignments/6-wallet-address/package-lock.json delete mode 100644 assignments/6-wallet-address/package.json delete mode 100644 assignments/6-wallet-address/tsconfig.json delete mode 100644 assignments/6-wallet-address/wallet-address.ts diff --git a/assignments/6-wallet-address/README.md b/assignments/6-wallet-address/README.md index 0d3a2654..c55444f2 100644 --- a/assignments/6-wallet-address/README.md +++ b/assignments/6-wallet-address/README.md @@ -1,219 +1 @@ -# 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. - -This is an educational implementation meant to explain the full flow in simple human language. - - -## What This Project Does - -This script generates an Ethereum address by following a clear logical chain. - -1. Random numbers -2. Mnemonic words -3. Mnemonic phrase -4. Seed -5. Private key -6. Public key -7. Ethereum address - -Every step is visible, logged, and easy to follow. - - -## Dictionary and Words - -A local dictionary.json file is used as the word source. - -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. - -This is similar in spirit to BIP-39 but simplified for learning purposes. - -## Step by Step Process - -### 1. Secure Random Number Generation - -The process starts with a cryptographically secure random number generator. - -Node.js crypto.randomBytes is used to generate random bytes. -Each byte is a number between 0 and 255. - -These numbers are the root of all security in this system. - -If randomness is weak, everything after it is weak. - ---- - -### 2. Converting Random Numbers to Words - -Each random number is converted into a word. - -The number is reduced using modulo dictionary length so it always maps to a valid word index. - -This creates a list of human readable words. - ---- - -### 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. +# WALLET ADDRESS GENERATION \ No newline at end of file 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 deleted file mode 100644 index 9cbca588..00000000 --- a/assignments/6-wallet-address/package-lock.json +++ /dev/null @@ -1,328 +0,0 @@ -{ - "name": "6-wallet-address", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "6-wallet-address", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "ethers": "^6.16.0" - }, - "devDependencies": { - "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": "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/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/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": "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", - "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/6-wallet-address/package.json b/assignments/6-wallet-address/package.json deleted file mode 100644 index 8570b2d5..00000000 --- a/assignments/6-wallet-address/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "6-wallet-address", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "wallet-address": "ts-node wallet-address.ts" - }, - "keywords": [], - "author": "", - "license": "ISC", - "type": "module", - "dependencies": { - "ethers": "^6.16.0" - }, - "devDependencies": { - "ts-node": "^10.9.2" - } -} 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* - - - - From a18e10be1f41f5368ed1028f9867060379e30ee5 Mon Sep 17 00:00:00 2001 From: deborahamoni0-prog Date: Thu, 29 Jan 2026 15:37:08 +0100 Subject: [PATCH 2/6] feat: Implement the wallet address generation --- CONTRIBUTING.md | 2 +- assignments/6-wallet-address/README.md | 191 +++++++++++++++++++++- assignments/6-wallet-address/package.json | 28 ++++ assignments/6-wallet-address/script.js | 41 +++++ 4 files changed, 260 insertions(+), 2 deletions(-) create mode 100644 assignments/6-wallet-address/package.json create mode 100644 assignments/6-wallet-address/script.js diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cbd55a65..0e55c302 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,7 @@ test: add coverage for revert conditions 4. Pull Request Checklist - [x] Code compiles - [x] Tests pass -- [x] README included +- [x] README - [x] No changes outside /submissions 5. PR Review Process diff --git a/assignments/6-wallet-address/README.md b/assignments/6-wallet-address/README.md index c55444f2..f2181247 100644 --- a/assignments/6-wallet-address/README.md +++ b/assignments/6-wallet-address/README.md @@ -1 +1,190 @@ -# WALLET ADDRESS GENERATION \ No newline at end of file +# Quantum-computer Wallet Alogrithm and Improvement Analysis +Blockchain wallets are fundamental tools for managing digital assets. Their security relies heavily on cryptographic primitives such as hash functions and public-key cryptography. Current wallet systems, including those used by major blockchains like Bitcoin and Ethereum, are designed under the assumption that adversaries use classical computers. + +However, the emergence of quantum computing introduces new attack models that significantly weaken or completely break many classical cryptographic systems. Algorithms such as Shor’s algorithm and Grover’s algorithm fundamentally change the security assumptions behind elliptic-curve cryptography and hash functions. +## Wallet Generation Algorithm +### Mnemonic Code Word Generation + +The random entropy generated by the CSPRNG is converted into a set of mnemonic code words. These words serve two purposes: + +They allow human-readable backup and recovery + +They preserve the underlying entropy in a structured form + +### Concatenation of Mnemonic Words + +All mnemonic words are concatenated into a single string. This creates a deterministic input for hashing while ensuring that the entire mnemonic contributes to the final seed. +HA Hashing of the Mnemonic + +The concatenated mnemonic string is hashed using the Secure Hash Algorithm (SHA). This step compresses the input into a fixed-length output and ensures diffusion, meaning small changes in the input produce large, unpredictable changes in the output. + +### Seed Consolidation + +The output of the SHA hash is treated as the cryptographic seed. At this stage, all previous steps are combined into a single value that represents the wallet’s core secret material. + +### Keccak-256 Hashing + +The seed is hashed again using Keccak-256. Keccak is the hash function used by Ethereum and is designed to be resistant to known cryptanalytic attacks. This second hashing step further randomizes the seed and reduces structural patterns. + +### Private Key Generation + +The output of the Keccak-256 hash is interpreted as a 256-bit private key. This private key is the most sensitive component of the wallet, as possession of it grants full control over the associated funds. + +### Public Key Derivation Using secp256k1 + +Using elliptic-curve cryptography (ECC) on the secp256k1 curve, the private key is transformed into a public key. This operation is computationally easy in one direction but extremely difficult to reverse using classical computers. +### Public Key Hashing + +The public key is hashed using Keccak-256. This step hides the raw public key and adds an additional layer of protection against certain attacks. + +### Address Generation + +The final wallet address is produced by taking the last 20 bytes of the Keccak-256 hash of the public key and prefixing it with 0x. This address is what users share publicly to receive funds. + +# Role of BIP-32 Hierarchical Deterministic Wallets + +BIP-32 allows multiple private keys to be deterministically derived from a single master seed. This design enables: + +Easy backup and recovery using one seed + +Generation of many addresses without storing each private key + +Structured wallet organization using derivation paths + +Despite these benefits, BIP-32 relies on elliptic-curve cryptography and therefore inherits its quantum vulnerabilities. + + +# Quantum Threat Model and Vulnerability Analysis + +### Grover’s Algorithm and Hash Functions + +Grover’s algorithm provides a quadratic speedup for brute-force search. This effectively halves the security of hash functions. For example: + +SHA-256 provides ~128-bit security against a quantum attacker + +Keccak-256 provides ~128-bit security against a quantum attacker + +While these levels are still secure today, they are insufficient for long-term, future-proof systems. + +### Shor’s Algorithm and Elliptic-Curve Cryptography + +Shor’s algorithm can efficiently solve the elliptic-curve discrete logarithm problem. This means that any system relying on secp256k1 becomes completely insecure once a sufficiently powerful quantum computer exists. An attacker could derive the private key directly from the public key. + +### Public Key Exposure Risk + +In many blockchain systems, public keys are revealed during transaction signing. This creates a direct attack surface for quantum adversaries. + +# proposed Improvements +1. #### Stronger Hash Functions (SHA-512 and Keccak-512) + +###### What is changed: + +* SHA-256 is replaced with SHA-512 + +* Keccak-256 is replaced with Keccak-512 + +##### Why it is chosen: + +* Minimal performance overhead + +* Well-studied and widely trusted + +* Easy to integrate into existing systems + + 2. #### Increased Entropy Size + +###### What is changed: + +* Entropy size is increased from 128 bits to 256 or 512 bits + +* Why it is important: Higher entropy significantly increases the difficulty of brute-force attacks, even with quantum acceleration. + +##### Why it is chosen: + +* Directly strengthens the foundation of the wallet + +* Simple change with large security benefits. + +3. #### Memory-Hard Key Derivation Functions (Argon2id) + +##### What is changed: + +* Simple hashing is replaced with Argon2id + +##### Why it is chosen: + +* Winner of the Password Hashing Competition + +* Strong resistance to GPU, ASIC, and quantum attacks + +4. #### Replacement of secp256k1 with Post-Quantum Signatures + +##### What is changed: + +* secp256k1 is replaced with post-quantum digital signature schemes such as Dilithium or SPHINCS+ + +* Why it is important: Elliptic-curve cryptography is completely broken by Shor’s algorithm, making this the most critical improvement. + +##### Why it is chosen: + +* Dilithium is a NIST-standardized algorithm + +* SPHINCS+ relies only on hash functions and offers strong long-term security. + +5. #### Post-Quantum Hierarchical Deterministic Wallets + +##### What is changed: + +* BIP-32 is replaced with a Merkle-tree–based HD wallet + +* Why it is important: This removes reliance on elliptic curves while preserving deterministic key derivation and wallet scalability. + +###### Why it is chosen: + +* Hash-based structures are well understood + +* Naturally compatible with post-quantum signatures + +6. #### Hash-Based Address Design + +##### What is changed: + +* Addresses are derived solely from hashed public keys + +* Why it is important: This minimizes public key exposure, reducing the attack surface for quantum adversaries. + +##### Why it is chosen: + +* Simple + +* Backward-compatible in concept. + +* Enhances privacy and security. + +7. #### Hybrid Cryptography (Transition Strategy) + +##### What is changed: + +* Classical and post-quantum signatures are used together + +* Why it is important: This allows systems to remain secure during the transition period before quantum computers become widespread. + +##### Why it is chosen: + +* Practical + +* Already under research by blockchain communities + +# Proposed Post-Quantum Wallet Architecture + +1. High-entropy or quantum-based random number generation + +2. 512-bit entropy input + +3. SHA-512 combined with Argon2id + +4. Master seed generation + +5. Merkle-tree–based HD derivation + +6. Post-quantum digital signature scheme \ No newline at end of file diff --git a/assignments/6-wallet-address/package.json b/assignments/6-wallet-address/package.json new file mode 100644 index 00000000..996156f5 --- /dev/null +++ b/assignments/6-wallet-address/package.json @@ -0,0 +1,28 @@ +{ + "name": "deborahcohort-8", + "version": "1.0.0", + "description": "Blockchain Development Bootcamp — Cohort 8", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/deborahamoni0-prog/deborahcohort-8.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/deborahamoni0-prog/deborahcohort-8/issues" + }, + "homepage": "https://github.com/deborahamoni0-prog/deborahcohort-8#readme", + "dependencies": { + "bip32": "^5.0.0", + "bip39": "^3.1.0", + "ethereumjs-util": "^7.1.5", + "hdkey": "^2.1.0", + "keccak": "^3.0.4", + "tiny-secp256k1": "^2.2.4" + } +} diff --git a/assignments/6-wallet-address/script.js b/assignments/6-wallet-address/script.js new file mode 100644 index 00000000..43d41c74 --- /dev/null +++ b/assignments/6-wallet-address/script.js @@ -0,0 +1,41 @@ +const bip39 = require("bip39"); +const { BIP32Factory } = require("bip32"); +const tinysecp = require("tiny-secp256k1"); +const bip32 = BIP32Factory(tinysecp); +const secp256k1 = require("secp256k1"); +const { keccak256 } = require("ethereumjs-util"); +const crypto = require("crypto"); + +const entropy = crypto.randomBytes(32); + + +const mnemonic = bip39.entropyToMnemonic(entropy); +console.log("Mnemonic Phrase:\n", mnemonic); + +const seed = bip39.mnemonicToSeedSync(mnemonic); + +const root = bip32.fromSeed(seed); + +const path = "m/44'/60'/0'/0/0"; +const child = root.derivePath(path);const ADDRESS_COUNT = 4; + +for (let i = 0; i < ADDRESS_COUNT; i++) { + const path = `m/44'/60'/0'/0/${i}`; + const child = root.derivePath(path); + + const privateKey = child.privateKey; + // console.log("\nPrivate Key:\n", Buffer.from(privateKey).toString("hex")); + + const publicKeyArray = secp256k1.publicKeyCreate(privateKey, false).slice(1); + const publicKey = Buffer.from(publicKeyArray); + // console.log("\nPublic Key (64 bytes):\n", publicKey.toString("hex")); + + const hash = keccak256(publicKey); + + const address = "0x" + hash.slice(-20).toString("hex"); + // console.log("\nEthereum Address:\n", address); + console.log(`\nAddress #${i}`); + console.log("\nPrivate Key:\n", Buffer.from(privateKey).toString("hex")); + console.log("\nPublic Key (64 bytes):\n", publicKey.toString("hex")); + console.log("\nEthereum Address:\n", address); +} \ No newline at end of file From e62f8cd0bfe0731846d48088016bc68e6b1a7d1a Mon Sep 17 00:00:00 2001 From: deborahamoni0-prog Date: Thu, 12 Feb 2026 17:22:20 +0100 Subject: [PATCH 3/6] feat:Todo Assignment --- assignments/7-Todo/Todos/.gitignore | 20 +++++++ assignments/7-Todo/Todos/README.md | 57 ++++++++++++++++++ .../7-Todo/Todos/contracts/Counter.sol | 19 ++++++ .../7-Todo/Todos/contracts/Counter.t.sol | 32 ++++++++++ assignments/7-Todo/Todos/contracts/bank.sol | 60 +++++++++++++++++++ assignments/7-Todo/Todos/hardhat.config.ts | 38 ++++++++++++ .../7-Todo/Todos/ignition/modules/Counter.ts | 9 +++ assignments/7-Todo/Todos/package.json | 20 +++++++ .../7-Todo/Todos/scripts/send-op-tx.ts | 22 +++++++ assignments/7-Todo/Todos/test/Counter.ts | 36 +++++++++++ assignments/7-Todo/Todos/tsconfig.json | 13 ++++ 11 files changed, 326 insertions(+) create mode 100644 assignments/7-Todo/Todos/.gitignore create mode 100644 assignments/7-Todo/Todos/README.md create mode 100644 assignments/7-Todo/Todos/contracts/Counter.sol create mode 100644 assignments/7-Todo/Todos/contracts/Counter.t.sol create mode 100644 assignments/7-Todo/Todos/contracts/bank.sol create mode 100644 assignments/7-Todo/Todos/hardhat.config.ts create mode 100644 assignments/7-Todo/Todos/ignition/modules/Counter.ts create mode 100644 assignments/7-Todo/Todos/package.json create mode 100644 assignments/7-Todo/Todos/scripts/send-op-tx.ts create mode 100644 assignments/7-Todo/Todos/test/Counter.ts create mode 100644 assignments/7-Todo/Todos/tsconfig.json diff --git a/assignments/7-Todo/Todos/.gitignore b/assignments/7-Todo/Todos/.gitignore new file mode 100644 index 00000000..991a319e --- /dev/null +++ b/assignments/7-Todo/Todos/.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/7-Todo/Todos/README.md b/assignments/7-Todo/Todos/README.md new file mode 100644 index 00000000..968246e9 --- /dev/null +++ b/assignments/7-Todo/Todos/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/7-Todo/Todos/contracts/Counter.sol b/assignments/7-Todo/Todos/contracts/Counter.sol new file mode 100644 index 00000000..8d00cb7c --- /dev/null +++ b/assignments/7-Todo/Todos/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/7-Todo/Todos/contracts/Counter.t.sol b/assignments/7-Todo/Todos/contracts/Counter.t.sol new file mode 100644 index 00000000..ac71d5b8 --- /dev/null +++ b/assignments/7-Todo/Todos/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/7-Todo/Todos/contracts/bank.sol b/assignments/7-Todo/Todos/contracts/bank.sol new file mode 100644 index 00000000..a81ad62c --- /dev/null +++ b/assignments/7-Todo/Todos/contracts/bank.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.31; + +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) 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; + } + } +} diff --git a/assignments/7-Todo/Todos/hardhat.config.ts b/assignments/7-Todo/Todos/hardhat.config.ts new file mode 100644 index 00000000..7092b852 --- /dev/null +++ b/assignments/7-Todo/Todos/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/7-Todo/Todos/ignition/modules/Counter.ts b/assignments/7-Todo/Todos/ignition/modules/Counter.ts new file mode 100644 index 00000000..042e61c8 --- /dev/null +++ b/assignments/7-Todo/Todos/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/7-Todo/Todos/package.json b/assignments/7-Todo/Todos/package.json new file mode 100644 index 00000000..331be59b --- /dev/null +++ b/assignments/7-Todo/Todos/package.json @@ -0,0 +1,20 @@ +{ + "name": "Todos", + "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/7-Todo/Todos/scripts/send-op-tx.ts b/assignments/7-Todo/Todos/scripts/send-op-tx.ts new file mode 100644 index 00000000..c10a2360 --- /dev/null +++ b/assignments/7-Todo/Todos/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/7-Todo/Todos/test/Counter.ts b/assignments/7-Todo/Todos/test/Counter.ts new file mode 100644 index 00000000..f8c38986 --- /dev/null +++ b/assignments/7-Todo/Todos/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/7-Todo/Todos/tsconfig.json b/assignments/7-Todo/Todos/tsconfig.json new file mode 100644 index 00000000..9b1380cc --- /dev/null +++ b/assignments/7-Todo/Todos/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" + } +} From bda34ff620f233ca816726b002a0b7525b482e23 Mon Sep 17 00:00:00 2001 From: deborahamoni0-prog Date: Thu, 12 Feb 2026 17:41:13 +0100 Subject: [PATCH 4/6] feat: Escrowv1 --- assignments/8-escrowv1/escrow/.gitignore | 20 ++++ assignments/8-escrowv1/escrow/README.md | 57 +++++++++++ .../8-escrowv1/escrow/contracts/Counter.sol | 19 ++++ .../8-escrowv1/escrow/contracts/Counter.t.sol | 32 +++++++ .../escrow/contracts/assignment.sol | 45 +++++++++ .../8-escrowv1/escrow/contracts/escrow.sol | 94 +++++++++++++++++++ .../escrow/contracts/escrowFactory.sol | 27 ++++++ .../8-escrowv1/escrow/hardhat.config.ts | 38 ++++++++ .../escrow/ignition/modules/Counter.ts | 9 ++ assignments/8-escrowv1/escrow/package.json | 20 ++++ .../8-escrowv1/escrow/scripts/send-op-tx.ts | 22 +++++ assignments/8-escrowv1/escrow/test/Counter.ts | 36 +++++++ assignments/8-escrowv1/escrow/tsconfig.json | 13 +++ 13 files changed, 432 insertions(+) create mode 100644 assignments/8-escrowv1/escrow/.gitignore create mode 100644 assignments/8-escrowv1/escrow/README.md create mode 100644 assignments/8-escrowv1/escrow/contracts/Counter.sol create mode 100644 assignments/8-escrowv1/escrow/contracts/Counter.t.sol create mode 100644 assignments/8-escrowv1/escrow/contracts/assignment.sol create mode 100644 assignments/8-escrowv1/escrow/contracts/escrow.sol create mode 100644 assignments/8-escrowv1/escrow/contracts/escrowFactory.sol create mode 100644 assignments/8-escrowv1/escrow/hardhat.config.ts create mode 100644 assignments/8-escrowv1/escrow/ignition/modules/Counter.ts create mode 100644 assignments/8-escrowv1/escrow/package.json create mode 100644 assignments/8-escrowv1/escrow/scripts/send-op-tx.ts create mode 100644 assignments/8-escrowv1/escrow/test/Counter.ts create mode 100644 assignments/8-escrowv1/escrow/tsconfig.json diff --git a/assignments/8-escrowv1/escrow/.gitignore b/assignments/8-escrowv1/escrow/.gitignore new file mode 100644 index 00000000..991a319e --- /dev/null +++ b/assignments/8-escrowv1/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/8-escrowv1/escrow/README.md b/assignments/8-escrowv1/escrow/README.md new file mode 100644 index 00000000..968246e9 --- /dev/null +++ b/assignments/8-escrowv1/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/8-escrowv1/escrow/contracts/Counter.sol b/assignments/8-escrowv1/escrow/contracts/Counter.sol new file mode 100644 index 00000000..8d00cb7c --- /dev/null +++ b/assignments/8-escrowv1/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/8-escrowv1/escrow/contracts/Counter.t.sol b/assignments/8-escrowv1/escrow/contracts/Counter.t.sol new file mode 100644 index 00000000..ac71d5b8 --- /dev/null +++ b/assignments/8-escrowv1/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/8-escrowv1/escrow/contracts/assignment.sol b/assignments/8-escrowv1/escrow/contracts/assignment.sol new file mode 100644 index 00000000..9429ea92 --- /dev/null +++ b/assignments/8-escrowv1/escrow/contracts/assignment.sol @@ -0,0 +1,45 @@ +// SPDX License-Identifier: MIT +pragma solidity 0.8.28; + +contract payment { + enum Status { + Roles, + client, + freelancer, + + } + + +contract paymentMilestone{ + uint256 paymentMilestone; + + struct payment { + uint id; + address payable creator; + string text; + Status status; + uint deadline; + uint freelancer; + } + + + + + mapping(uint => payment) payments; + + event paymentCreated(string text, uint deadline); + + +function creatpayment(string memory _text, uint _freelancer) external returns(uint){ + require(bytes(_text).length > 0, "Empty text"); + require(_freelancer > 0, "Invalid freelancer"); + require(msg.sender != address(0), "Zero address"); + + + emit paymentCreated(_text, _freelancer); + return payment; + + + +} + diff --git a/assignments/8-escrowv1/escrow/contracts/escrow.sol b/assignments/8-escrowv1/escrow/contracts/escrow.sol new file mode 100644 index 00000000..85c49d40 --- /dev/null +++ b/assignments/8-escrowv1/escrow/contracts/escrow.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.28; + +contract Escrow { + enum State { + AWAITING_PAYMENT, + AWAITING_DELIVERY, + COMPLETE + } + + State public currentState; + + address public buyer; + address payable public seller; + address public escrowAgent; + + modifier onlyBuyer() { + require(msg.sender == buyer, "Only buyer can call this method"); + _; + } + + modifier onlySeller() { + require(msg.sender == seller, "Only seller can call this method"); + _; + } + + modifier onlyEscrowAgent() { + require( + msg.sender == escrowAgent, + "Only escrow agent can call this method" + ); + _; + } + + modifier inState(State _state) { + require(currentState == _state, "Invalid state"); + _; + } + + event Deposit(address indexed buyer, uint256 amount); + event DeliveryConfirmed(address indexed seller); + event FundsReleased(address indexed seller, uint256 amount); + event FundsRefunded(address indexed buyer, uint256 amount); + + constructor(address _buyer, address payable _seller, address _escrowAgent) { + buyer = _buyer; + seller = _seller; + escrowAgent = _escrowAgent; + currentState = State.AWAITING_PAYMENT; + } + + function deposit() + external + payable + onlyBuyer + inState(State.AWAITING_PAYMENT) + { + require(msg.value > 0, "Deposit amount must be greater than 0"); + currentState = State.AWAITING_DELIVERY; + emit Deposit(msg.sender, msg.value); + } + + function confirmDelivery() + external + onlySeller + inState(State.AWAITING_DELIVERY) + { + emit DeliveryConfirmed(msg.sender); + } + + function releaseFunds() + external + onlyEscrowAgent + inState(State.AWAITING_DELIVERY) + { + currentState = State.COMPLETE; + uint256 amount = address(this).balance; + (bool success, ) = seller.call{value: amount}(""); + require(success, "Transfer to seller failed"); + emit FundsReleased(seller, amount); + } + + function refundBuyer() + external + onlyEscrowAgent + inState(State.AWAITING_DELIVERY) + { + currentState = State.COMPLETE; + uint256 amount = address(this).balance; + (bool success, ) = buyer.call{value: amount}(""); + require(success, "Transfer to buyer failed"); + emit FundsRefunded(buyer, amount); + } +} diff --git a/assignments/8-escrowv1/escrow/contracts/escrowFactory.sol b/assignments/8-escrowv1/escrow/contracts/escrowFactory.sol new file mode 100644 index 00000000..0c0db349 --- /dev/null +++ b/assignments/8-escrowv1/escrow/contracts/escrowFactory.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.28; + +import "./escrow.sol"; + +contract EscrowFactory { + event EscrowCreated( + address indexed escrowAddress, + address indexed buyer, + address indexed seller, + address escrowAgent + ); + + function createEscrow( + address payable _seller, + address _escrowAgent + ) external returns (address) { + Escrow newEscrow = new Escrow(msg.sender, _seller, _escrowAgent); + emit EscrowCreated( + address(newEscrow), + msg.sender, + _seller, + _escrowAgent + ); + return address(newEscrow); + } +} diff --git a/assignments/8-escrowv1/escrow/hardhat.config.ts b/assignments/8-escrowv1/escrow/hardhat.config.ts new file mode 100644 index 00000000..7092b852 --- /dev/null +++ b/assignments/8-escrowv1/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/8-escrowv1/escrow/ignition/modules/Counter.ts b/assignments/8-escrowv1/escrow/ignition/modules/Counter.ts new file mode 100644 index 00000000..042e61c8 --- /dev/null +++ b/assignments/8-escrowv1/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/8-escrowv1/escrow/package.json b/assignments/8-escrowv1/escrow/package.json new file mode 100644 index 00000000..535b8cd1 --- /dev/null +++ b/assignments/8-escrowv1/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/8-escrowv1/escrow/scripts/send-op-tx.ts b/assignments/8-escrowv1/escrow/scripts/send-op-tx.ts new file mode 100644 index 00000000..c10a2360 --- /dev/null +++ b/assignments/8-escrowv1/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/8-escrowv1/escrow/test/Counter.ts b/assignments/8-escrowv1/escrow/test/Counter.ts new file mode 100644 index 00000000..f8c38986 --- /dev/null +++ b/assignments/8-escrowv1/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/8-escrowv1/escrow/tsconfig.json b/assignments/8-escrowv1/escrow/tsconfig.json new file mode 100644 index 00000000..9b1380cc --- /dev/null +++ b/assignments/8-escrowv1/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" + } +} From 93a0971b2cc7b37bd6313d62343430c1386d3341 Mon Sep 17 00:00:00 2001 From: deborahamoni0-prog Date: Thu, 12 Feb 2026 17:54:25 +0100 Subject: [PATCH 5/6] feat: escrowv2 assignment --- .../9-milestone-escrowv2/milestone/.gitignore | 20 ++++ .../9-milestone-escrowv2/milestone/README.md | 57 +++++++++++ .../milestone/contracts/Counter.sol | 19 ++++ .../milestone/contracts/Counter.t.sol | 32 ++++++ .../milestone/contracts/payment.sol | 99 +++++++++++++++++++ .../milestone/hardhat.config.ts | 38 +++++++ .../milestone/ignition/modules/Counter.ts | 9 ++ .../milestone/package.json | 20 ++++ .../milestone/scripts/send-op-tx.ts | 22 +++++ .../milestone/test/Counter.ts | 96 ++++++++++++++++++ .../milestone/tsconfig.json | 13 +++ 11 files changed, 425 insertions(+) create mode 100644 assignments/9-milestone-escrowv2/milestone/.gitignore create mode 100644 assignments/9-milestone-escrowv2/milestone/README.md create mode 100644 assignments/9-milestone-escrowv2/milestone/contracts/Counter.sol create mode 100644 assignments/9-milestone-escrowv2/milestone/contracts/Counter.t.sol create mode 100644 assignments/9-milestone-escrowv2/milestone/contracts/payment.sol create mode 100644 assignments/9-milestone-escrowv2/milestone/hardhat.config.ts create mode 100644 assignments/9-milestone-escrowv2/milestone/ignition/modules/Counter.ts create mode 100644 assignments/9-milestone-escrowv2/milestone/package.json create mode 100644 assignments/9-milestone-escrowv2/milestone/scripts/send-op-tx.ts create mode 100644 assignments/9-milestone-escrowv2/milestone/test/Counter.ts create mode 100644 assignments/9-milestone-escrowv2/milestone/tsconfig.json diff --git a/assignments/9-milestone-escrowv2/milestone/.gitignore b/assignments/9-milestone-escrowv2/milestone/.gitignore new file mode 100644 index 00000000..991a319e --- /dev/null +++ b/assignments/9-milestone-escrowv2/milestone/.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/9-milestone-escrowv2/milestone/README.md b/assignments/9-milestone-escrowv2/milestone/README.md new file mode 100644 index 00000000..968246e9 --- /dev/null +++ b/assignments/9-milestone-escrowv2/milestone/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/9-milestone-escrowv2/milestone/contracts/Counter.sol b/assignments/9-milestone-escrowv2/milestone/contracts/Counter.sol new file mode 100644 index 00000000..8d00cb7c --- /dev/null +++ b/assignments/9-milestone-escrowv2/milestone/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/9-milestone-escrowv2/milestone/contracts/Counter.t.sol b/assignments/9-milestone-escrowv2/milestone/contracts/Counter.t.sol new file mode 100644 index 00000000..ac71d5b8 --- /dev/null +++ b/assignments/9-milestone-escrowv2/milestone/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/9-milestone-escrowv2/milestone/contracts/payment.sol b/assignments/9-milestone-escrowv2/milestone/contracts/payment.sol new file mode 100644 index 00000000..021ef1f6 --- /dev/null +++ b/assignments/9-milestone-escrowv2/milestone/contracts/payment.sol @@ -0,0 +1,99 @@ +// SPDX License-Identifier: unlicensed +pragma solidity 0.8.28; + +contract milestone { + address public Client; + address public Freelancer; + bool public validMilestone; + uint256 public totalMilestones; + uint256 public AmountPerMilestone; + + uint256 public currentMilestoneId; + + enum MilestoneStatus { + PENDING, + COMPLETED, + PAID + } + + struct Milestone { + uint256 id; + bool isCompleted; // Marked by freelancer + bool isPaid; + MilestoneStatus status; + // Marked by client/contract + } + mapping(address => uint256) public Milestones; + + modifier onlyClient() { + require(msg.sender == Client, "Only client can call this function"); + _; + } + + modifier onlyFreelancer() { + require( + msg.sender == Freelancer, + "Only freelancer can call this function" + ); + _; + } + + constructor( + address _client, + address payable _freelancer, + uint256 _ethPerMilestone, + uint256 _totalMilestones, + uint256 _currentMilestoneId + ) payable { + Client = _client; + Freelancer = _freelancer; + AmountPerMilestone = _ethPerMilestone; + totalMilestones = _totalMilestones; + } + + event MilestoneCreated(uint256 milestoneId); + event MilestoneCompleted(uint256 milestoneId); + event MilestonePaid(uint256 milestoneId, uint256 amount); + + function creatJob( + address payable _freelancer, + uint256 _totalMilestones, + uint256 _ethPerMilestone + ) public onlyClient { + require(msg.sender == Client, "Only client can create a milestone"); + Freelancer = _freelancer; + AmountPerMilestone = _ethPerMilestone; + } + + function completeMilestone( + uint256 _id + ) public onlyFreelancer returns (bool isCompleted) { + require( + msg.sender == Freelancer, + "Only freelancer can complete a milestone" + ); + require(Milestones[msg.sender] == _id, "Milestone ID does not exist"); + Milestones[msg.sender] = _id; + return true; + } + + function payMilestone( + uint256 _ethPerMilestone, + uint256 _currentMilestonesId + ) public onlyClient returns (bool isPaid) { + require(msg.sender == Client, "Only client can pay for a milestone"); + require(Milestones[Freelancer] > 0, "Milestone does not exist"); + require(_ethPerMilestone > 0, "Payment amount must be greater than 0"); + payable(Freelancer).transfer(_ethPerMilestone); + Milestones[Freelancer] = 0; + return true; + } + + function calculateMilestone() + public + pure + returns (uint256 _totalMilestones, uint256 _ethPerMilestone) + { + return (10, 1000); + } +} diff --git a/assignments/9-milestone-escrowv2/milestone/hardhat.config.ts b/assignments/9-milestone-escrowv2/milestone/hardhat.config.ts new file mode 100644 index 00000000..7092b852 --- /dev/null +++ b/assignments/9-milestone-escrowv2/milestone/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/9-milestone-escrowv2/milestone/ignition/modules/Counter.ts b/assignments/9-milestone-escrowv2/milestone/ignition/modules/Counter.ts new file mode 100644 index 00000000..042e61c8 --- /dev/null +++ b/assignments/9-milestone-escrowv2/milestone/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/9-milestone-escrowv2/milestone/package.json b/assignments/9-milestone-escrowv2/milestone/package.json new file mode 100644 index 00000000..906e6eb2 --- /dev/null +++ b/assignments/9-milestone-escrowv2/milestone/package.json @@ -0,0 +1,20 @@ +{ + "name": "payment", + "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/9-milestone-escrowv2/milestone/scripts/send-op-tx.ts b/assignments/9-milestone-escrowv2/milestone/scripts/send-op-tx.ts new file mode 100644 index 00000000..c10a2360 --- /dev/null +++ b/assignments/9-milestone-escrowv2/milestone/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/9-milestone-escrowv2/milestone/test/Counter.ts b/assignments/9-milestone-escrowv2/milestone/test/Counter.ts new file mode 100644 index 00000000..7fa5473a --- /dev/null +++ b/assignments/9-milestone-escrowv2/milestone/test/Counter.ts @@ -0,0 +1,96 @@ + +import { expect } from "chai"; +import { network } from "hardhat"; +const { networkHelpers } = await network.connect(); + +const { ethers } = await network.connect(); + + +describe("milestone", function () { +let milestone : any; +const ZeroAddress = "0x0000000000000000000000000000000000000000"; + + +beforeEach(async () => { + milestone = await ethers.deployContract("Milestone"); + + + }); + + + describe("Create Auction", function (){ + it("should create an auction successfully", async () => { + let [owner] = await ethers.getSigners() + await expect(milestone.createAuction(1000, 1200)).to.emit(milestone, "AuctionInitialaized").withArgs(1n); + const a = await milestone.auctionCounter(); + expect(a).to.equal(1); + + const createdAuction = await milestone.auctions(a); + expect(createdAuction[0]).to.equal(1); + expect(createdAuction[1]).to.equal(1000); + expect(createdAuction[2]).to.equal(0); + expect(createdAuction[3]).to.equal(owner); + expect(createdAuction[4]).to.equal(ZeroAddress); + expect(createdAuction[5]).to.equal(0); + expect(createdAuction[6]).to.equal(1200); + + + }) + + it("should create more than an auction successfully", async () => { + let [owner] = await ethers.getSigners() + // await expect(milestone.createAuction(1000, 1200)).to.emit(milestone, "AuctionInitialaized").withArgs(1n); + await milestone.createAuction(1000, 1200); + const a = await milestone.auctionCounter(); + expect(a).to.equal(1); + + // await expect(milestone.createAuction(1500, 2000)).to.emit(milestone, "AuctionInitialaized").withArgs(2n); + await milestone.createAuction(1500, 2000) + + const b = await milestone.auctionCounter(); + + + const createdAuction = await milestone.auctions(b); + expect(createdAuction[0]).to.equal(2); + expect(createdAuction[1]).to.equal(1500); + expect(createdAuction[2]).to.equal(0); + expect(createdAuction[3]).to.equal(owner); + expect(createdAuction[4]).to.equal(ZeroAddress); + expect(createdAuction[5]).to.equal(0); + expect(createdAuction[6]).to.equal(2000); + + + }) + }) + + describe.only("Start Auction", () => { + it("Should Start Auction Successfully", async () => { + let [owner] = await ethers.getSigners() + await expect(milestone.createAuction(1000, 1200)).to.emit(milestone, "AuctionInitialaized").withArgs(1n); + const a = await milestone.auctionCounter(); + + await milestone.startAuction(a); + const currentTime = await networkHelpers.time.latest() + + const startedAuction = await milestone.auctions(a); + expect(startedAuction[2]).to.equal(1); + expect(startedAuction[5]).to.be.closeTo(currentTime, 5); + + }) + + + + it("Should Fail When a Wrong address tries to start the Auction", async () => { + let [owner, addr1] = await ethers.getSigners() + await expect(milestone.createAuction(1000, 1200)).to.emit(milestone, "AuctionInitialaized").withArgs(1n); + const a = await milestone.auctionCounter(); + + await expect( milestone.connect(addr1).startAuction(a)).to.be.revertedWith("Not your Auction"); + + }) + }) + + + + +}); \ No newline at end of file diff --git a/assignments/9-milestone-escrowv2/milestone/tsconfig.json b/assignments/9-milestone-escrowv2/milestone/tsconfig.json new file mode 100644 index 00000000..9b1380cc --- /dev/null +++ b/assignments/9-milestone-escrowv2/milestone/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" + } +} From 9ebf1757808b487eb2ffbc589c3f466178fc6b19 Mon Sep 17 00:00:00 2001 From: deborahamoni0-prog Date: Thu, 12 Feb 2026 18:04:38 +0100 Subject: [PATCH 6/6] feat: vault Assignment --- assignments/10-vault/vaults/.gitignore | 20 ++++++ assignments/10-vault/vaults/README.md | 57 ++++++++++++++++ .../10-vault/vaults/contracts/Counter.sol | 19 ++++++ .../10-vault/vaults/contracts/Counter.t.sol | 35 ++++++++++ .../10-vault/vaults/contracts/vaults.sol | 67 +++++++++++++++++++ assignments/10-vault/vaults/hardhat.config.ts | 38 +++++++++++ .../vaults/ignition/modules/Counter.ts | 9 +++ assignments/10-vault/vaults/package.json | 20 ++++++ .../10-vault/vaults/scripts/send-op-tx.ts | 22 ++++++ assignments/10-vault/vaults/test/Counter.ts | 36 ++++++++++ assignments/10-vault/vaults/tsconfig.json | 13 ++++ 11 files changed, 336 insertions(+) create mode 100644 assignments/10-vault/vaults/.gitignore create mode 100644 assignments/10-vault/vaults/README.md create mode 100644 assignments/10-vault/vaults/contracts/Counter.sol create mode 100644 assignments/10-vault/vaults/contracts/Counter.t.sol create mode 100644 assignments/10-vault/vaults/contracts/vaults.sol create mode 100644 assignments/10-vault/vaults/hardhat.config.ts create mode 100644 assignments/10-vault/vaults/ignition/modules/Counter.ts create mode 100644 assignments/10-vault/vaults/package.json create mode 100644 assignments/10-vault/vaults/scripts/send-op-tx.ts create mode 100644 assignments/10-vault/vaults/test/Counter.ts create mode 100644 assignments/10-vault/vaults/tsconfig.json diff --git a/assignments/10-vault/vaults/.gitignore b/assignments/10-vault/vaults/.gitignore new file mode 100644 index 00000000..991a319e --- /dev/null +++ b/assignments/10-vault/vaults/.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/10-vault/vaults/README.md b/assignments/10-vault/vaults/README.md new file mode 100644 index 00000000..968246e9 --- /dev/null +++ b/assignments/10-vault/vaults/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/10-vault/vaults/contracts/Counter.sol b/assignments/10-vault/vaults/contracts/Counter.sol new file mode 100644 index 00000000..8d00cb7c --- /dev/null +++ b/assignments/10-vault/vaults/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/10-vault/vaults/contracts/Counter.t.sol b/assignments/10-vault/vaults/contracts/Counter.t.sol new file mode 100644 index 00000000..8aa3ab5f --- /dev/null +++ b/assignments/10-vault/vaults/contracts/Counter.t.sol @@ -0,0 +1,35 @@ +// 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/10-vault/vaults/contracts/vaults.sol b/assignments/10-vault/vaults/contracts/vaults.sol new file mode 100644 index 00000000..e1e1eac8 --- /dev/null +++ b/assignments/10-vault/vaults/contracts/vaults.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract TimelockedSavingsVault { + struct Vault { + uint256 amount; + uint256 unlockTime; + } + + mapping(address => Vault) private vaults; + + event Deposited(address indexed user, uint256 amount, uint256 unlockTime); + event Withdrawn(address indexed user, uint256 amount); + + error VaultAlreadyExists(); + error NoActiveVault(); + error UnlockTimeInPast(); + error AmountMustBePositive(); + error NotUnlocked(); + error TransferFailed(); + + function deposit(uint256 unlockTime) external payable { + if (msg.value == 0) revert AmountMustBePositive(); + Vault storage v = vaults[msg.sender]; + if (v.amount != 0) revert VaultAlreadyExists(); + if (unlockTime <= block.timestamp) revert UnlockTimeInPast(); + + v.amount = msg.value; + v.unlockTime = unlockTime; + + emit Deposited(msg.sender, msg.value, unlockTime); + } + + function withdraw() external { + Vault storage v = vaults[msg.sender]; + if (v.amount == 0) revert NoActiveVault(); + if (block.timestamp < v.unlockTime) revert NotUnlocked(); + + uint256 amount = v.amount; + + // Reset vault before transfer to prevent reentrancy + v.amount = 0; + v.unlockTime = 0; + + (bool sent, ) = msg.sender.call{value: amount}(""); + if (!sent) revert TransferFailed(); + + emit Withdrawn(msg.sender, amount); + } + + // Reject direct ETH transfers to force users to call `deposit` + receive() external payable { + revert("Direct transfers not allowed"); + } + + fallback() external payable { + revert("Direct transfers not allowed"); + } + + /// @notice Returns the vault details for `user`. + function getVault( + address user + ) external view returns (uint256 amount, uint256 unlockTime) { + Vault storage v = vaults[user]; + return (v.amount, v.unlockTime); + } +} diff --git a/assignments/10-vault/vaults/hardhat.config.ts b/assignments/10-vault/vaults/hardhat.config.ts new file mode 100644 index 00000000..7092b852 --- /dev/null +++ b/assignments/10-vault/vaults/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/10-vault/vaults/ignition/modules/Counter.ts b/assignments/10-vault/vaults/ignition/modules/Counter.ts new file mode 100644 index 00000000..042e61c8 --- /dev/null +++ b/assignments/10-vault/vaults/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/10-vault/vaults/package.json b/assignments/10-vault/vaults/package.json new file mode 100644 index 00000000..0866515a --- /dev/null +++ b/assignments/10-vault/vaults/package.json @@ -0,0 +1,20 @@ +{ + "name": "vaults", + "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/10-vault/vaults/scripts/send-op-tx.ts b/assignments/10-vault/vaults/scripts/send-op-tx.ts new file mode 100644 index 00000000..c10a2360 --- /dev/null +++ b/assignments/10-vault/vaults/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/10-vault/vaults/test/Counter.ts b/assignments/10-vault/vaults/test/Counter.ts new file mode 100644 index 00000000..f8c38986 --- /dev/null +++ b/assignments/10-vault/vaults/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/10-vault/vaults/tsconfig.json b/assignments/10-vault/vaults/tsconfig.json new file mode 100644 index 00000000..9b1380cc --- /dev/null +++ b/assignments/10-vault/vaults/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" + } +}