diff --git a/.gitignore b/.gitignore index ab1b93b7..dfc182df 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,37 @@ -.env -.vscode/ -package-lock.json -node_modules +# --- Node / npm +**/node_modules/ +**/package-lock.json +**/npm-debug.log* +**/pnpm-debug.log* + +# --- Env / secrets --- +**/.env +**/.env.* + +# --- Editors --- +**/.vscode/ +**/.idea/ + +# --- Build outputs --- +**/dist/ +**/build/ +**/out/ +**/.cache/ +**/.turbo/ + +# --- Rust --- +**/target/ +**/*.rs.bk + +# --- OS --- +.DS_Store +Thumbs.db + +# --- React --- +.react-router + +# +artifacts +cache -# Rust build artifacts -/target -**/target diff --git a/assignments/2-gas-hash/package.json b/assignments/2-gas-hash/package.json new file mode 100644 index 00000000..40cabcec --- /dev/null +++ b/assignments/2-gas-hash/package.json @@ -0,0 +1,16 @@ +{ + "type": "module", + "scripts": { + "hash": "ts-node ./hash.ts" + }, + "dependencies": { + "@types/node": "^25.0.9", + "crypto": "^1.0.1", + "ethers": "^6.16.0", + "readline-sync": "^1.4.10" + }, + "devDependencies": { + "@types/readline-sync": "^1.4.8", + "ts-node": "^10.9.2" + } +} diff --git a/assignments/7-explorer-wallet/.dockerignore b/assignments/7-explorer-wallet/.dockerignore new file mode 100644 index 00000000..9b8d5147 --- /dev/null +++ b/assignments/7-explorer-wallet/.dockerignore @@ -0,0 +1,4 @@ +.react-router +build +node_modules +README.md \ No newline at end of file diff --git a/assignments/7-explorer-wallet/.gitignore b/assignments/7-explorer-wallet/.gitignore new file mode 100644 index 00000000..039ee62d --- /dev/null +++ b/assignments/7-explorer-wallet/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +.env +/node_modules/ + +# React Router +/.react-router/ +/build/ diff --git a/assignments/7-explorer-wallet/Dockerfile b/assignments/7-explorer-wallet/Dockerfile new file mode 100644 index 00000000..207bf937 --- /dev/null +++ b/assignments/7-explorer-wallet/Dockerfile @@ -0,0 +1,22 @@ +FROM node:20-alpine AS development-dependencies-env +COPY . /app +WORKDIR /app +RUN npm ci + +FROM node:20-alpine AS production-dependencies-env +COPY ./package.json package-lock.json /app/ +WORKDIR /app +RUN npm ci --omit=dev + +FROM node:20-alpine AS build-env +COPY . /app/ +COPY --from=development-dependencies-env /app/node_modules /app/node_modules +WORKDIR /app +RUN npm run build + +FROM node:20-alpine +COPY ./package.json package-lock.json /app/ +COPY --from=production-dependencies-env /app/node_modules /app/node_modules +COPY --from=build-env /app/build /app/build +WORKDIR /app +CMD ["npm", "run", "start"] \ No newline at end of file diff --git a/assignments/7-explorer-wallet/README.md b/assignments/7-explorer-wallet/README.md new file mode 100644 index 00000000..08bbcc16 --- /dev/null +++ b/assignments/7-explorer-wallet/README.md @@ -0,0 +1,272 @@ +# Cryptographic Wallet + Ethereum Dashboard + +This project is a learning-focused Ethereum application built with **React Router**. + +It combines: +- A **from-scratch Ethereum wallet generator** +- A **live Ethereum dashboard** (price, gas, blocks, transactions) +- A modern **React + TypeScript + Tailwind** stack + +The goal is to **show what is really happening under the hood**, not hide it behind libraries. + +This project is for **education and experimentation only**. + +--- + +## What This App Does + +This app has two main parts: + +1. **Wallet Generator Page** +2. **Ethereum Network Dashboard (Home Page)** + +Each part is explained below. + +--- + +## Wallet Generator Page + +The wallet page generates a **deterministic Ethereum wallet** step by step. + +- Nothing is fetched from an API +- Nothing is stored in a database +- Everything is generated locally + +All cryptographic steps are visible and easy to follow. + +--- + +### What You See in the UI + +- A **Create Wallet** button +- A generated **Ethereum address** +- A **mnemonic recovery phrase** +- Hold-to-reveal interaction for safety +- Dark / Light theme toggle +- Animated entropy background (visual randomness) + +--- + +## Wallet Generation Flow (Simple Explanation) + +This is the exact logic used to generate the wallet. + +--- + +### 1. Secure Random Numbers + +The process starts with cryptographically secure randomness. + +Node.js `crypto.randomBytes` is used. + +Each byte is a number between **0 and 255**. + +These numbers are the root of all security. + +If randomness is weak, everything after it is weak. + +--- + +### 2. Random Numbers → Words + +A local `dictionary.json` file is used as the word source. + +- The dictionary values are converted into an array +- Each random number is mapped to a word using modulo arithmetic +- This guarantees the index always fits inside the dictionary + +This creates a list of human-readable words. + +--- + +### 3. Mnemonic Phrase + +All generated words are joined together using spaces. + +Example: + + +word1 word2 word3 word4 +```yaml + +At this stage, it is **just text**. + +--- + +### 4. Mnemonic → Seed (SHA-256) + +The mnemonic phrase is hashed using **SHA-256**. + +This produces: +- A 256-bit value +- Represented as 64 hexadecimal characters + +This hash is treated as the **seed**. + +The seed represents all previous steps combined. + +--- + +### 5. Seed → Private Key (Keccak-256) + +The seed is hashed again using **Keccak-256**. + +This produces another 256-bit value. + +This value is used as the **Ethereum private key**. + +⚠️ Anyone with this key fully controls the wallet. + +--- + +### 6. Private Key → Public Key (secp256k1) + +The private key is passed into the **secp256k1 elliptic curve**. + +This generates an **uncompressed public key**. + +- Starts with `0x04` +- Contains both X and Y coordinates + +--- + +### 7. Public Key → Hash + +- The `0x04` prefix is removed +- The remaining bytes are hashed using **Keccak-256** + +This produces a 32-byte hash. + +--- + +### 8. Ethereum Address + +- The **last 20 bytes** of the public key hash are taken +- Converted to hexadecimal +- `0x` is prepended + +This final value is the **Ethereum address**. + +--- + +## Final Wallet Output + +- Mnemonic phrase +- Seed (SHA-256) +- Private key (Keccak-256) +- Public key (secp256k1) +- Ethereum address + +Each step depends entirely on the previous one. + +--- + +## Important Warning + +This wallet logic is **for learning purposes only**. + +It does NOT fully follow official wallet standards: +- BIP-39 +- BIP-32 +- BIP-44 + +**Do not use this wallet to store real funds.** + +--- + +## How Real Wallets Improve This Process + +Real wallets like MetaMask and hardware wallets add extra security layers. + +--- + +### BIP-39 Mnemonic Standard + +- Fixed 2048-word list +- Embedded checksum +- Detects typing errors + +--- + +### PBKDF2 (2048 Rounds) + +- Uses HMAC-SHA512 +- Repeats hashing 2048 times +- Optional passphrase support +- Makes brute-force attacks much slower + +--- + +### Hierarchical Deterministic Wallets + +- One mnemonic controls unlimited addresses +- Defined in BIP-32 and BIP-44 +- Used by all modern wallets + +--- + +## Ethereum Dashboard (Home Page) + +The home page shows **live Ethereum network data**. + +--- + +### Features + +- Current **ETH price** +- Current **gas price** +- Latest blocks +- Latest transactions + +--- + +### Technical Details + +- Data fetched from the **Sepolia test network** +- Custom React hooks for data fetching +- Loading and error states handled cleanly +- Values formatted from Wei → Gwei / ETH + +--- + +## Tech Stack + +- React Router +- React +- TypeScript +- Tailwind CSS +- Framer Motion +- Node.js crypto +- secp256k1 +- Keccak-256 + +--- + +## Getting Started + +### Install Dependencies + +```bash +npm install + +``` + + +## Run Development Server +```bash +npm run dev +``` + + +The app will be available at: + +```arduino +http://localhost:5173 +``` + +## Build for Production +```bash +npm run build +``` + + diff --git a/assignments/7-explorer-wallet/app/app.css b/assignments/7-explorer-wallet/app/app.css new file mode 100644 index 00000000..99345d82 --- /dev/null +++ b/assignments/7-explorer-wallet/app/app.css @@ -0,0 +1,15 @@ +@import "tailwindcss"; + +@theme { + --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; +} + +html, +body { + @apply bg-white dark:bg-gray-950; + + @media (prefers-color-scheme: dark) { + color-scheme: dark; + } +} diff --git a/assignments/7-explorer-wallet/app/blocks/BlockDetailPage.tsx b/assignments/7-explorer-wallet/app/blocks/BlockDetailPage.tsx new file mode 100644 index 00000000..9acd7619 --- /dev/null +++ b/assignments/7-explorer-wallet/app/blocks/BlockDetailPage.tsx @@ -0,0 +1,66 @@ +import { useParams, Link } from 'react-router'; +import { useBlock } from '../../src/hooks/useSepolia'; +import { + shortenHash, + formatTimestamp, + hexToNumber, + weiToEth, +} from '../../src/services/formatters'; +import LoadingSpinner from '../../src/components/ui/LoadingSpinner'; +import ErrorMessage from '../../src/components/ui/ErrorMessage'; +import type { RpcTransaction } from '../../src/types/types'; + +export default function BlockDetailPage() { + const { blockNumber } = useParams>(); + const num = blockNumber ? parseInt(blockNumber, 10) : 0; + const { data, isLoading, error } = useBlock(num); + + if (isLoading) return ; + if (error) return ; + if (!data) return ; + + return ( +
+

+ Block #{hexToNumber(data.number)} +

+
+

+ Hash: {data.hash} +

+

+ Parent Hash: {data.parentHash} +

+

+ Timestamp: {formatTimestamp(data.timestamp)} +

+

+ Miner: {shortenHash(data.miner)} +

+

+ Gas Used / Limit: {weiToEth(data.gasUsed)} /{' '} + {weiToEth(data.gasLimit)} +

+
+

Transactions

+ + + {(data.transactions as RpcTransaction[]).map((tx: RpcTransaction) => ( + + + + + + + ))} + +
+ + {shortenHash(tx.hash)} + + From: {shortenHash(tx.from)} + To: {tx.to ? shortenHash(tx.to) : 'Contract'} + {weiToEth(tx.value)} ETH
+
+ ); +} diff --git a/assignments/7-explorer-wallet/app/blocks/BlocksPage.tsx b/assignments/7-explorer-wallet/app/blocks/BlocksPage.tsx new file mode 100644 index 00000000..ce5399ca --- /dev/null +++ b/assignments/7-explorer-wallet/app/blocks/BlocksPage.tsx @@ -0,0 +1,15 @@ +import LatestBlocks from 'src/components/ui/LatestBlocks'; + +export default function BlocksPage() { + return ( +
+
+

+ Blocks Page +

+ + +
+
+ ); +} diff --git a/assignments/7-explorer-wallet/app/home/HomePage.tsx b/assignments/7-explorer-wallet/app/home/HomePage.tsx new file mode 100644 index 00000000..87ff01d9 --- /dev/null +++ b/assignments/7-explorer-wallet/app/home/HomePage.tsx @@ -0,0 +1,57 @@ +import LatestBlocks from 'src/components/ui/LatestBlocks'; +import LatestTransactions from 'src/components/ui/LatestTransactions'; +import { useEthPrice, useGasPrice } from 'src/hooks/useSepolia'; +import { weiToGwei, weiToEth } from '~/src/services/formatters'; +import LoadingSpinner from 'src/components/ui/LoadingSpinner'; +import ErrorMessage from 'src/components/ui/ErrorMessage'; + +export default function HomePage() { + const { + data: ethPrice, + isLoading: ethPriceLoading, + error: ethPriceError, + } = useEthPrice(); + + const { + data: gasPrice, + isLoading: gasPriceLoading, + error: gasPriceError, + } = useGasPrice(); + + return ( +
+
+
+ {/* Eth Price */} +
+

ETH Price

+ {ethPriceLoading ? ( + + ) : ethPriceError ? ( + + ) : ( +

${ethPrice?.toFixed(2)}

+ )} +
+ + {/* Gas Price */} +
+

Gas Price

+ {gasPriceLoading ? ( + + ) : gasPriceError ? ( + + ) : ( +

{weiToGwei(gasPrice || '0').toFixed(2)} Gwei

+ )} +
+
+ +
+ + +
+
+
+ ); +} diff --git a/assignments/7-explorer-wallet/app/root.tsx b/assignments/7-explorer-wallet/app/root.tsx new file mode 100644 index 00000000..dad6a8fd --- /dev/null +++ b/assignments/7-explorer-wallet/app/root.tsx @@ -0,0 +1,87 @@ +import { + isRouteErrorResponse, + Links, + Meta, + Outlet, + Scripts, + ScrollRestoration, +} from 'react-router'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { useState } from 'react'; +import type { Route } from './+types/root'; +import './app.css'; +import Header from 'src/components/Header'; +import Footer from 'src/components/Footer'; + +export const links: Route.LinksFunction = () => [ + { rel: 'preconnect', href: 'https://fonts.googleapis.com' }, + { + rel: 'preconnect', + href: 'https://fonts.gstatic.com', + crossOrigin: 'anonymous', + }, + { + rel: 'stylesheet', + href: 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap', + }, +]; + +export function Layout({ children }: { children: React.ReactNode }) { + const [queryClient] = useState(() => new QueryClient()); + return ( + + + + + + + + + + +
+
+ +
+