abc-scaffold 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Kimberley Bezuidenhout
3
+ Copyright (c) 2026 Africa's Blockchain Club
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/package.json CHANGED
@@ -1,42 +1,42 @@
1
- {
2
- "name": "abc-scaffold",
3
- "version": "1.0.0",
4
- "description": "Africa's Blockchain Club — project-agnostic blockchain scaffolding tool",
5
- "author": "Africa's Blockchain Club",
6
- "license": "MIT",
7
- "type": "module",
8
- "bin": {
9
- "abc-scaffold": "bin/abc-scaffold.js"
10
- },
11
- "files": [
12
- "bin",
13
- "src",
14
- "templates",
15
- "README.md",
16
- "LICENSE"
17
- ],
18
- "scripts": {
19
- "start": "node bin/abc-scaffold.js"
20
- },
21
- "keywords": [
22
- "hardhat",
23
- "blockchain",
24
- "scaffold",
25
- "ethereum",
26
- "smart-contracts",
27
- "africa",
28
- "web3",
29
- "erc4337",
30
- "openzeppelin"
31
- ],
32
- "dependencies": {
33
- "chalk": "^5.3.0",
34
- "fs-extra": "^11.2.0"
35
- },
36
- "engines": {
37
- "node": ">=18.0.0"
38
- },
39
- "publishConfig": {
40
- "access": "public"
41
- }
42
- }
1
+ {
2
+ "name": "abc-scaffold",
3
+ "version": "1.0.2",
4
+ "description": "Africa's Blockchain Club — project-agnostic blockchain scaffolding tool",
5
+ "author": "Africa's Blockchain Club",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "bin": {
9
+ "abc-scaffold": "bin/abc-scaffold.js"
10
+ },
11
+ "files": [
12
+ "bin",
13
+ "src",
14
+ "templates",
15
+ "README.md",
16
+ "LICENSE"
17
+ ],
18
+ "scripts": {
19
+ "start": "node bin/abc-scaffold.js"
20
+ },
21
+ "keywords": [
22
+ "hardhat",
23
+ "blockchain",
24
+ "scaffold",
25
+ "ethereum",
26
+ "smart-contracts",
27
+ "africa",
28
+ "web3",
29
+ "erc4337",
30
+ "openzeppelin"
31
+ ],
32
+ "dependencies": {
33
+ "chalk": "^5.3.0",
34
+ "fs-extra": "^11.2.0"
35
+ },
36
+ "engines": {
37
+ "node": ">=18.0.0"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ }
42
+ }
package/src/scaffold.js CHANGED
@@ -1,341 +1,346 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { fileURLToPath } from 'url';
4
- import { createInterface } from 'readline';
5
- import fse from 'fs-extra';
6
- import chalk from 'chalk';
7
- import * as logger from './logger.js';
8
-
9
- const __filename = fileURLToPath(import.meta.url);
10
- const __dirname = path.dirname(__filename);
11
- const TEMPLATES = path.join(__dirname, '..', 'templates');
12
-
13
- function prompt(question) {
14
- return new Promise((resolve) => {
15
- const rl = createInterface({ input: process.stdin, output: process.stdout });
16
- rl.question(question, (answer) => { rl.close(); resolve(answer.trim()); });
17
- });
18
- }
19
-
20
- function isValidName(name) {
21
- return /^[a-z0-9][a-z0-9\-_]*$/.test(name);
22
- }
23
-
24
- function copyTemplate(filename, targetDir) {
25
- fse.copySync(path.join(TEMPLATES, filename), path.join(targetDir, filename));
26
- logger.success(filename);
27
- }
28
-
29
- function buildReadme(projectName) {
30
- return `# ${projectName}
31
-
32
- > Scaffolded with [abc-scaffold](https://github.com/africas-blockchain-club/abc-scaffold) by **Africa's Blockchain Club**
33
-
34
- A project-agnostic Hardhat workspace ready for smart contract development, testing, and frontend integration. Build anything — NFTs, DeFi protocols, DAOs, smart accounts, or any on-chain application.
35
-
36
- ---
37
-
38
- ## Prerequisites
39
-
40
- | Tool | Version |
41
- |------|---------|
42
- | Node.js | >= 18.0.0 |
43
- | npm | >= 9.0.0 |
44
- | MetaMask | Latest |
45
-
46
- ---
47
-
48
- ## Getting Started
49
-
50
- ### 1. Install dependencies
51
-
52
- \`\`\`bash
53
- npm install
54
- \`\`\`
55
-
56
- ### 2. Set up environment variables
57
-
58
- \`\`\`bash
59
- cp .env.example .env
60
- \`\`\`
61
-
62
- Fill in your values in \`.env\`:
63
-
64
- - \`SEPOLIA_RPC_URL\` — get one free from [Infura](https://infura.io) or [Alchemy](https://alchemy.com)
65
- - \`PRIVATE_KEY\` — your deployer wallet private key (**never commit this file**)
66
- - \`ETHERSCAN_API_KEY\` — for contract verification on [Etherscan](https://etherscan.io)
67
-
68
- ### 3. Compile contracts
69
-
70
- \`\`\`bash
71
- npm run compile
72
- \`\`\`
73
-
74
- ### 4. Run tests
75
-
76
- \`\`\`bash
77
- npm test
78
- \`\`\`
79
-
80
- ### 5. Start a local Hardhat node
81
-
82
- \`\`\`bash
83
- npx hardhat node
84
- \`\`\`
85
-
86
- ### 6. Deploy contracts locally
87
-
88
- \`\`\`bash
89
- npx hardhat run scripts/deploy.js --network localhost
90
- \`\`\`
91
-
92
- ### 7. Start the frontend
93
-
94
- \`\`\`bash
95
- cd frontend
96
- npm install
97
- npm run dev
98
- # → http://localhost:5173
99
- \`\`\`
100
-
101
- ---
102
-
103
- ## Directory Structure
104
-
105
- \`\`\`
106
- ${projectName}/
107
- ├── contracts/ # Solidity smart contracts
108
- ├── scripts/ # Deployment and utility scripts
109
- ├── test/ # Hardhat test files
110
- ├── frontend/ # React + Vite frontend
111
- │ └── src/
112
- │ ├── App.jsx
113
- │ └── components/
114
- │ └── Navbar.jsx ← Connect Wallet button lives here
115
- ├── hardhat.config.js # Hardhat configuration
116
- ├── .env.example # Environment variable template
117
- └── README.md
118
- \`\`\`
119
-
120
- ---
121
-
122
- ## Writing Smart Contracts
123
-
124
- Place your Solidity files in \`contracts/\`. The config targets Solidity \`0.8.26\` with the Cancun EVM and the optimizer enabled (200 runs).
125
-
126
- **Example** — \`contracts/MyContract.sol\`:
127
-
128
- \`\`\`solidity
129
- // SPDX-License-Identifier: MIT
130
- pragma solidity ^0.8.26;
131
-
132
- contract MyContract {
133
- // Your logic here
134
- }
135
- \`\`\`
136
-
137
- ### Included libraries
138
-
139
- | Package | Use case |
140
- |---------|----------|
141
- | \`@openzeppelin/contracts\` | ERC-20, ERC-721, ERC-1155, access control, etc. |
142
- | \`@openzeppelin/contracts-upgradeable\` | UUPS / Transparent proxy upgradeable variants |
143
- | \`@openzeppelin/hardhat-upgrades\` | Deploy and upgrade helper tasks |
144
- | \`@account-abstraction/contracts\` | ERC-4337 interfaces and EntryPoint |
145
-
146
- ---
147
-
148
- ## Writing Tests
149
-
150
- Place test files in \`test/\`. Hardhat ships with Mocha + Chai.
151
-
152
- **Example** — \`test/MyContract.test.js\`:
153
-
154
- \`\`\`javascript
155
- const { expect } = require('chai');
156
- const { ethers } = require('hardhat');
157
-
158
- describe('MyContract', function () {
159
- it('should deploy successfully', async function () {
160
- const MyContract = await ethers.getContractFactory('MyContract');
161
- const contract = await MyContract.deploy();
162
- expect(await contract.getAddress()).to.not.equal(ethers.ZeroAddress);
163
- });
164
- });
165
- \`\`\`
166
-
167
- ---
168
-
169
- ## Writing Deployment Scripts
170
-
171
- **Example** — \`scripts/deploy.js\`:
172
-
173
- \`\`\`javascript
174
- const { ethers } = require('hardhat');
175
-
176
- async function main() {
177
- const [deployer] = await ethers.getSigners();
178
- console.log('Deploying from:', deployer.address);
179
-
180
- const MyContract = await ethers.getContractFactory('MyContract');
181
- const contract = await MyContract.deploy();
182
- await contract.waitForDeployment();
183
-
184
- console.log('MyContract deployed to:', await contract.getAddress());
185
- }
186
-
187
- main().catch((err) => {
188
- console.error(err);
189
- process.exitCode = 1;
190
- });
191
- \`\`\`
192
-
193
- ---
194
-
195
- ## Networks
196
-
197
- | Network | RPC | Chain ID |
198
- |---------|-----|----------|
199
- | Hardhat (local) | \`http://127.0.0.1:8545\` | 31337 |
200
- | Sepolia (testnet) | \`SEPOLIA_RPC_URL\` in \`.env\` | 11155111 |
201
-
202
- ---
203
-
204
- ## Verifying Contracts
205
-
206
- \`\`\`bash
207
- npx hardhat verify --network sepolia <CONTRACT_ADDRESS> <CONSTRUCTOR_ARGS>
208
- \`\`\`
209
-
210
- ---
211
-
212
- ## Frontend
213
-
214
- The \`frontend/\` folder is a Vite + React app with:
215
-
216
- - A sticky navigation bar with a **Connect Wallet** button (MetaMask / EIP-1193)
217
- - Ethers.js v6 already wired up
218
-
219
- ### Connecting to a deployed contract
220
-
221
- Copy the ABI from \`artifacts/contracts/YourContract.sol/YourContract.json\` and import it:
222
-
223
- \`\`\`javascript
224
- import { BrowserProvider, Contract } from 'ethers';
225
- import MyContractABI from '../../artifacts/contracts/MyContract.sol/MyContract.json';
226
-
227
- const CONTRACT_ADDRESS = '0x...';
228
-
229
- async function interact() {
230
- const provider = new BrowserProvider(window.ethereum);
231
- const signer = await provider.getSigner();
232
- const contract = new Contract(CONTRACT_ADDRESS, MyContractABI.abi, signer);
233
- // call contract methods here
234
- }
235
- \`\`\`
236
-
237
- ---
238
-
239
- ## Resources
240
-
241
- - [Hardhat Docs](https://hardhat.org/docs)
242
- - [Solidity Docs](https://docs.soliditylang.org)
243
- - [OpenZeppelin Docs](https://docs.openzeppelin.com)
244
- - [EIP-4337 (Account Abstraction)](https://eips.ethereum.org/EIPS/eip-4337)
245
- - [Ethers.js v6 Docs](https://docs.ethers.org/v6)
246
- - [Vite Docs](https://vitejs.dev)
247
-
248
- ---
249
-
250
- *Built with Africa's Blockchain Club — scaffold your next project with \`npx abc-scaffold\`.*
251
- `;
252
- }
253
-
254
- function scaffoldFrontend(frontendDir, projectName) {
255
- fse.ensureDirSync(path.join(frontendDir, 'src', 'components'));
256
- fse.copySync(path.join(TEMPLATES, 'frontend'), frontendDir);
257
-
258
- // Substitute project name in index.html
259
- const htmlPath = path.join(frontendDir, 'index.html');
260
- const html = fs.readFileSync(htmlPath, 'utf-8').replace(/{{PROJECT_NAME}}/g, projectName);
261
- fs.writeFileSync(htmlPath, html);
262
-
263
- logger.success('frontend/ (React + Vite + Connect Wallet)');
264
- }
265
-
266
- export async function scaffold(initialName) {
267
- let projectName = initialName;
268
-
269
- if (!projectName) {
270
- projectName = await prompt(chalk.bold('? Project name: '));
271
- }
272
-
273
- if (!projectName) {
274
- logger.error('Project name is required.');
275
- process.exit(1);
276
- }
277
-
278
- // Normalise: lowercase, spaces → hyphens
279
- projectName = projectName.trim().toLowerCase().replace(/\s+/g, '-');
280
-
281
- if (!isValidName(projectName)) {
282
- logger.error(`"${projectName}" is not a valid package name. Use lowercase letters, numbers, hyphens, or underscores.`);
283
- process.exit(1);
284
- }
285
-
286
- const targetDir = path.resolve(process.cwd(), projectName);
287
-
288
- if (fs.existsSync(targetDir)) {
289
- logger.error(`Directory "${projectName}" already exists. Choose a different name or delete it first.`);
290
- process.exit(1);
291
- }
292
-
293
- // ── Create directory structure ──────────────────────────────────────────────
294
- logger.step('Creating project structure');
295
-
296
- fs.mkdirSync(targetDir, { recursive: true });
297
-
298
- for (const dir of ['contracts', 'scripts', 'test']) {
299
- fs.mkdirSync(path.join(targetDir, dir), { recursive: true });
300
- logger.success(`${dir}/`);
301
- }
302
-
303
- // ── Copy configuration files ────────────────────────────────────────────────
304
- logger.step('Copying configuration files');
305
-
306
- copyTemplate('hardhat.config.js', targetDir);
307
- copyTemplate('.env.example', targetDir);
308
- copyTemplate('.gitignore', targetDir);
309
-
310
- // package.json substitute project name
311
- const pkgTemplate = fs.readFileSync(path.join(TEMPLATES, 'package.json'), 'utf-8');
312
- fs.writeFileSync(
313
- path.join(targetDir, 'package.json'),
314
- pkgTemplate.replace('"{{PROJECT_NAME}}"', `"${projectName}"`)
315
- );
316
- logger.success('package.json');
317
-
318
- // ── Generate README ─────────────────────────────────────────────────────────
319
- logger.step('Generating README.md');
320
- fs.writeFileSync(path.join(targetDir, 'README.md'), buildReadme(projectName));
321
- logger.success('README.md');
322
-
323
- // ── Scaffold React frontend ─────────────────────────────────────────────────
324
- logger.step('Scaffolding React frontend');
325
- scaffoldFrontend(path.join(targetDir, 'frontend'), projectName);
326
-
327
- // ── Done ────────────────────────────────────────────────────────────────────
328
- console.log('');
329
- console.log(chalk.bold.green('✨ Project scaffolded!'));
330
- console.log('');
331
- console.log(' Next steps:');
332
- console.log(chalk.cyan(` cd ${projectName}`));
333
- console.log(chalk.cyan(' npm install') + chalk.gray(' # install root dependencies'));
334
- console.log(chalk.cyan(' cp .env.example .env') + chalk.gray(' # fill in your keys'));
335
- console.log(chalk.cyan(' npm run compile') + chalk.gray(' # compile contracts'));
336
- console.log(chalk.cyan(' cd frontend && npm install') + chalk.gray(' # install frontend dependencies'));
337
- console.log(chalk.cyan(' npm run dev') + chalk.gray(' # start the UI'));
338
- console.log('');
339
- console.log(chalk.gray(' Docs see README.md'));
340
- console.log('');
341
- }
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { createInterface } from 'readline';
5
+ import fse from 'fs-extra';
6
+ import chalk from 'chalk';
7
+ import * as logger from './logger.js';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+ const TEMPLATES = path.join(__dirname, '..', 'templates');
12
+
13
+ function prompt(question) {
14
+ return new Promise((resolve) => {
15
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
16
+ rl.question(question, (answer) => { rl.close(); resolve(answer.trim()); });
17
+ });
18
+ }
19
+
20
+ function isValidName(name) {
21
+ return /^[a-z0-9][a-z0-9\-_]*$/.test(name);
22
+ }
23
+
24
+ function copyTemplate(filename, targetDir) {
25
+ fse.copySync(path.join(TEMPLATES, filename), path.join(targetDir, filename));
26
+ logger.success(filename);
27
+ }
28
+
29
+ function buildReadme(projectName) {
30
+ return `# ${projectName}
31
+
32
+ > Scaffolded with [abc-scaffold](https://github.com/africas-blockchain-club/abc-scaffold) by **Africa's Blockchain Club**
33
+
34
+ A project-agnostic Hardhat workspace ready for smart contract development, testing, and frontend integration. Build anything — NFTs, DeFi protocols, DAOs, smart accounts, or any on-chain application.
35
+
36
+ ---
37
+
38
+ ## Prerequisites
39
+
40
+ | Tool | Version |
41
+ |------|---------|
42
+ | Node.js | >= 18.0.0 |
43
+ | npm | >= 9.0.0 |
44
+ | MetaMask | Latest |
45
+
46
+ ---
47
+
48
+ ## Getting Started
49
+
50
+ ### 1. Install dependencies
51
+
52
+ \`\`\`bash
53
+ npm install
54
+ \`\`\`
55
+
56
+ ### 2. Set up environment variables
57
+
58
+ \`\`\`bash
59
+ cp .env.example .env
60
+ \`\`\`
61
+
62
+ Fill in your values in \`.env\`:
63
+
64
+ - \`SEPOLIA_RPC_URL\` — get one free from [Infura](https://infura.io) or [Alchemy](https://alchemy.com)
65
+ - \`PRIVATE_KEY\` — your deployer wallet private key (**never commit this file**)
66
+ - \`ETHERSCAN_API_KEY\` — for contract verification on [Etherscan](https://etherscan.io)
67
+
68
+ ### 3. Compile contracts
69
+
70
+ \`\`\`bash
71
+ npx hardhat compile
72
+ \`\`\`
73
+
74
+ ### 4. Run tests
75
+
76
+ \`\`\`bash
77
+ npx hardhat test
78
+ \`\`\`
79
+
80
+ ### 5. Deploy contracts locally
81
+
82
+ Open two terminals:
83
+
84
+ \`\`\`bash
85
+ # Terminal 1 — start a local node
86
+ npx hardhat node
87
+
88
+ # Terminal 2 — deploy your contracts
89
+ npx hardhat run scripts/deploy.js --network localhost
90
+ \`\`\`
91
+
92
+ ### 6. Start the frontend
93
+
94
+ \`\`\`bash
95
+ cd frontend
96
+ npm install
97
+ npm run dev
98
+ # → http://localhost:5173
99
+ \`\`\`
100
+
101
+ ---
102
+
103
+ ## Directory Structure
104
+
105
+ \`\`\`
106
+ ${projectName}/
107
+ ├── contracts/ # Solidity smart contracts
108
+ ├── scripts/ # Deployment and utility scripts
109
+ ├── test/ # Hardhat test files
110
+ ├── frontend/ # React + Vite frontend
111
+ │ └── src/
112
+ │ ├── App.jsx
113
+ │ └── components/
114
+ │ └── Navbar.jsx ← Connect Wallet button lives here
115
+ ├── hardhat.config.js # Hardhat configuration
116
+ ├── .env.example # Environment variable template
117
+ └── README.md
118
+ \`\`\`
119
+
120
+ ---
121
+
122
+ ## Writing Smart Contracts
123
+
124
+ Place your Solidity files in \`contracts/\`. The config targets Solidity \`0.8.26\` with the Cancun EVM and the optimizer enabled (200 runs).
125
+
126
+ **Example** — \`contracts/MyContract.sol\`:
127
+
128
+ \`\`\`solidity
129
+ // SPDX-License-Identifier: MIT
130
+ pragma solidity ^0.8.26;
131
+
132
+ contract MyContract {
133
+ // Your logic here
134
+ }
135
+ \`\`\`
136
+
137
+ ### Included libraries
138
+
139
+ | Package | Use case |
140
+ |---------|----------|
141
+ | \`@openzeppelin/contracts\` | ERC-20, ERC-721, ERC-1155, access control, etc. |
142
+ | \`@openzeppelin/contracts-upgradeable\` | UUPS / Transparent proxy upgradeable variants |
143
+ | \`@openzeppelin/hardhat-upgrades\` | Deploy and upgrade helper tasks |
144
+ | \`@account-abstraction/contracts\` | ERC-4337 interfaces and EntryPoint |
145
+
146
+ ---
147
+
148
+ ## Writing Tests
149
+
150
+ Place test files in \`test/\`. Hardhat ships with Mocha + Chai.
151
+
152
+ **Example** — \`test/MyContract.test.js\`:
153
+
154
+ \`\`\`javascript
155
+ const { expect } = require('chai');
156
+ const { ethers } = require('hardhat');
157
+
158
+ describe('MyContract', function () {
159
+ it('should deploy successfully', async function () {
160
+ const MyContract = await ethers.getContractFactory('MyContract');
161
+ const contract = await MyContract.deploy();
162
+ expect(await contract.getAddress()).to.not.equal(ethers.ZeroAddress);
163
+ });
164
+ });
165
+ \`\`\`
166
+
167
+ ---
168
+
169
+ ## Writing Deployment Scripts
170
+
171
+ **Example** — \`scripts/deploy.js\`:
172
+
173
+ \`\`\`javascript
174
+ const { ethers } = require('hardhat');
175
+
176
+ async function main() {
177
+ const [deployer] = await ethers.getSigners();
178
+ console.log('Deploying from:', deployer.address);
179
+
180
+ const MyContract = await ethers.getContractFactory('MyContract');
181
+ const contract = await MyContract.deploy();
182
+ await contract.waitForDeployment();
183
+
184
+ console.log('MyContract deployed to:', await contract.getAddress());
185
+ }
186
+
187
+ main().catch((err) => {
188
+ console.error(err);
189
+ process.exitCode = 1;
190
+ });
191
+ \`\`\`
192
+
193
+ ---
194
+
195
+ ## Networks
196
+
197
+ | Network | RPC | Chain ID |
198
+ |---------|-----|----------|
199
+ | Hardhat (local) | \`http://127.0.0.1:8545\` | 31337 |
200
+ | Sepolia (testnet) | \`SEPOLIA_RPC_URL\` in \`.env\` | 11155111 |
201
+
202
+ ---
203
+
204
+ ## Verifying Contracts
205
+
206
+ \`\`\`bash
207
+ npx hardhat verify --network sepolia <CONTRACT_ADDRESS> <CONSTRUCTOR_ARGS>
208
+ \`\`\`
209
+
210
+ ---
211
+
212
+ ## Frontend
213
+
214
+ The \`frontend/\` folder is a Vite + React app with:
215
+
216
+ - A sticky navigation bar with a **Connect Wallet** button (MetaMask / EIP-1193)
217
+ - Ethers.js v6 already wired up
218
+
219
+ ### Connecting to a deployed contract
220
+
221
+ Copy the ABI from \`artifacts/contracts/YourContract.sol/YourContract.json\` and import it:
222
+
223
+ \`\`\`javascript
224
+ import { BrowserProvider, Contract } from 'ethers';
225
+ import MyContractABI from '../../artifacts/contracts/MyContract.sol/MyContract.json';
226
+
227
+ const CONTRACT_ADDRESS = '0x...';
228
+
229
+ async function interact() {
230
+ const provider = new BrowserProvider(window.ethereum);
231
+ const signer = await provider.getSigner();
232
+ const contract = new Contract(CONTRACT_ADDRESS, MyContractABI.abi, signer);
233
+ // call contract methods here
234
+ }
235
+ \`\`\`
236
+
237
+ ---
238
+
239
+ ## Resources
240
+
241
+ - [Hardhat Docs](https://hardhat.org/docs)
242
+ - [Solidity Docs](https://docs.soliditylang.org)
243
+ - [OpenZeppelin Docs](https://docs.openzeppelin.com)
244
+ - [EIP-4337 (Account Abstraction)](https://eips.ethereum.org/EIPS/eip-4337)
245
+ - [Ethers.js v6 Docs](https://docs.ethers.org/v6)
246
+ - [Vite Docs](https://vitejs.dev)
247
+
248
+ ---
249
+
250
+ *Built with Africa's Blockchain Club — scaffold your next project with \`npx abc-scaffold\`.*
251
+ `;
252
+ }
253
+
254
+ function scaffoldFrontend(frontendDir, projectName) {
255
+ fse.ensureDirSync(path.join(frontendDir, 'src', 'components'));
256
+ fse.copySync(path.join(TEMPLATES, 'frontend'), frontendDir);
257
+
258
+ // Substitute project name in index.html
259
+ const htmlPath = path.join(frontendDir, 'index.html');
260
+ const html = fs.readFileSync(htmlPath, 'utf-8').replace(/{{PROJECT_NAME}}/g, projectName);
261
+ fs.writeFileSync(htmlPath, html);
262
+
263
+ logger.success('frontend/ (React + Vite + Connect Wallet)');
264
+ }
265
+
266
+ export async function scaffold(initialName) {
267
+ let projectName = initialName;
268
+
269
+ if (!projectName) {
270
+ projectName = await prompt(chalk.bold('? Project name: '));
271
+ }
272
+
273
+ if (!projectName) {
274
+ logger.error('Project name is required.');
275
+ process.exit(1);
276
+ }
277
+
278
+ // Normalise: lowercase, spaces → hyphens
279
+ projectName = projectName.trim().toLowerCase().replace(/\s+/g, '-');
280
+
281
+ if (!isValidName(projectName)) {
282
+ logger.error(`"${projectName}" is not a valid package name. Use lowercase letters, numbers, hyphens, or underscores.`);
283
+ process.exit(1);
284
+ }
285
+
286
+ const targetDir = path.resolve(process.cwd(), projectName);
287
+
288
+ if (fs.existsSync(targetDir)) {
289
+ logger.error(`Directory "${projectName}" already exists. Choose a different name or delete it first.`);
290
+ process.exit(1);
291
+ }
292
+
293
+ // ── Create directory structure ──────────────────────────────────────────────
294
+ logger.step('Creating project structure');
295
+
296
+ fs.mkdirSync(targetDir, { recursive: true });
297
+
298
+ for (const dir of ['contracts', 'scripts', 'test']) {
299
+ fs.mkdirSync(path.join(targetDir, dir), { recursive: true });
300
+ logger.success(`${dir}/`);
301
+ }
302
+
303
+ // ── Copy configuration files ────────────────────────────────────────────────
304
+ logger.step('Copying configuration files');
305
+
306
+ copyTemplate('hardhat.config.js', targetDir);
307
+
308
+ // These are stored without a leading dot in templates/ because npm strips
309
+ // dotfiles when publishing. We restore the dot on copy.
310
+ fse.copySync(path.join(TEMPLATES, 'env.example'), path.join(targetDir, '.env.example'));
311
+ logger.success('.env.example');
312
+ fse.copySync(path.join(TEMPLATES, 'gitignore'), path.join(targetDir, '.gitignore'));
313
+ logger.success('.gitignore');
314
+
315
+ // package.json — substitute project name
316
+ const pkgTemplate = fs.readFileSync(path.join(TEMPLATES, 'package.json'), 'utf-8');
317
+ fs.writeFileSync(
318
+ path.join(targetDir, 'package.json'),
319
+ pkgTemplate.replace('"{{PROJECT_NAME}}"', `"${projectName}"`)
320
+ );
321
+ logger.success('package.json');
322
+
323
+ // ── Generate README ─────────────────────────────────────────────────────────
324
+ logger.step('Generating README.md');
325
+ fs.writeFileSync(path.join(targetDir, 'README.md'), buildReadme(projectName));
326
+ logger.success('README.md');
327
+
328
+ // ── Scaffold React frontend ─────────────────────────────────────────────────
329
+ logger.step('Scaffolding React frontend');
330
+ scaffoldFrontend(path.join(targetDir, 'frontend'), projectName);
331
+
332
+ // ── Done ────────────────────────────────────────────────────────────────────
333
+ console.log('');
334
+ console.log(chalk.bold.green(' Project scaffolded!'));
335
+ console.log('');
336
+ console.log(' Next steps:');
337
+ console.log(chalk.cyan(` cd ${projectName}`));
338
+ console.log(chalk.cyan(' npm install') + chalk.gray(' # install root dependencies'));
339
+ console.log(chalk.cyan(' cp .env.example .env') + chalk.gray(' # fill in your keys'));
340
+ console.log(chalk.cyan(' npx hardhat compile') + chalk.gray(' # compile contracts'));
341
+ console.log(chalk.cyan(' cd frontend && npm install') + chalk.gray(' # install frontend dependencies'));
342
+ console.log(chalk.cyan(' npm run dev') + chalk.gray(' # start the UI'));
343
+ console.log('');
344
+ console.log(chalk.gray(' Docs → see README.md'));
345
+ console.log('');
346
+ }
@@ -1,4 +1,4 @@
1
- # Example .env file - Duplicate this file to '.env' and fill in your values
2
- SEPOLIA_RPC_URL="https://sepolia.infura.io/v3/YOUR_INFURA_KEY"
3
- PRIVATE_KEY="YOUR_WALLET_PRIVATE_KEY"
4
- ETHERSCAN_API_KEY="YOUR_ETHERSCAN_API_KEY"
1
+ # Example .env file - Duplicate this file to '.env' and fill in your values
2
+ SEPOLIA_RPC_URL="https://sepolia.infura.io/v3/YOUR_INFURA_KEY"
3
+ PRIVATE_KEY="YOUR_WALLET_PRIVATE_KEY"
4
+ ETHERSCAN_API_KEY="YOUR_ETHERSCAN_API_KEY"
@@ -19,75 +19,63 @@ body {
19
19
  flex-direction: column;
20
20
  }
21
21
 
22
- /* ── Main content ── */
22
+ /* ── Centered content ── */
23
23
  .main {
24
24
  flex: 1;
25
- max-width: 1100px;
26
- margin: 0 auto;
27
- padding: 5rem 2rem;
28
- width: 100%;
29
- }
30
-
31
- /* ── Hero ── */
32
- .hero {
33
- text-align: center;
25
+ display: flex;
26
+ align-items: center;
27
+ justify-content: center;
34
28
  }
35
29
 
36
- .hero h1 {
37
- font-size: 3rem;
38
- font-weight: 800;
30
+ /* ── Connect button ── */
31
+ .connect-btn {
32
+ background-color: #e94560;
39
33
  color: #ffffff;
40
- margin-bottom: 0.75rem;
41
- letter-spacing: -0.5px;
42
- }
43
-
44
- .subtitle {
34
+ border: none;
35
+ border-radius: 10px;
36
+ padding: 1rem 2.5rem;
45
37
  font-size: 1.1rem;
46
- color: #e94560;
47
- margin-bottom: 3.5rem;
48
- font-weight: 500;
38
+ font-weight: 700;
39
+ cursor: pointer;
40
+ transition: background-color 0.18s ease, transform 0.12s ease;
41
+ letter-spacing: 0.3px;
49
42
  }
50
43
 
51
- /* ── Cards ── */
52
- .cards {
53
- display: grid;
54
- grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
55
- gap: 1.5rem;
56
- margin-top: 1rem;
44
+ .connect-btn:hover:not(:disabled) {
45
+ background-color: #c73652;
46
+ transform: translateY(-2px);
57
47
  }
58
48
 
59
- .card {
60
- background-color: #1a1a2e;
61
- border: 1px solid #2a2a4a;
62
- border-radius: 12px;
63
- padding: 1.75rem 1.5rem;
64
- text-align: left;
65
- transition: border-color 0.2s ease, transform 0.2s ease;
49
+ .connect-btn:active:not(:disabled) {
50
+ transform: translateY(0);
66
51
  }
67
52
 
68
- .card:hover {
69
- border-color: #e94560;
70
- transform: translateY(-3px);
53
+ .connect-btn:disabled {
54
+ background-color: #555566;
55
+ cursor: not-allowed;
71
56
  }
72
57
 
73
- .card h3 {
74
- font-size: 1.05rem;
75
- font-weight: 700;
76
- color: #ffffff;
77
- margin-bottom: 0.6rem;
58
+ /* ── Connected state ── */
59
+ .connected {
60
+ display: flex;
61
+ align-items: center;
62
+ gap: 0.6rem;
63
+ background-color: #1a1a2e;
64
+ border: 1px solid #e94560;
65
+ border-radius: 10px;
66
+ padding: 0.75rem 1.5rem;
78
67
  }
79
68
 
80
- .card p {
81
- font-size: 0.9rem;
82
- color: #9090a8;
83
- line-height: 1.5;
69
+ .wallet-dot {
70
+ width: 10px;
71
+ height: 10px;
72
+ background-color: #4caf50;
73
+ border-radius: 50%;
74
+ flex-shrink: 0;
84
75
  }
85
76
 
86
- .card code {
87
- background-color: #0d0d1a;
88
- color: #e94560;
89
- padding: 0.1rem 0.4rem;
90
- border-radius: 4px;
91
- font-size: 0.85rem;
77
+ .wallet-address {
92
78
  font-family: 'Fira Code', 'Cascadia Code', monospace;
79
+ font-size: 1rem;
80
+ color: #e2e2e2;
93
81
  }
@@ -1,34 +1,52 @@
1
+ import { useState } from 'react';
2
+ import { BrowserProvider } from 'ethers';
1
3
  import Navbar from './components/Navbar.jsx';
2
4
  import './App.css';
3
5
 
4
6
  function App() {
7
+ const [account, setAccount] = useState(null);
8
+ const [connecting, setConnecting] = useState(false);
9
+
10
+ async function connectWallet() {
11
+ if (!window.ethereum) {
12
+ alert('MetaMask is not installed. Please install it at https://metamask.io');
13
+ return;
14
+ }
15
+
16
+ setConnecting(true);
17
+ try {
18
+ const provider = new BrowserProvider(window.ethereum);
19
+ const accounts = await provider.send('eth_requestAccounts', []);
20
+ setAccount(accounts[0]);
21
+ } catch (err) {
22
+ console.error('Wallet connection rejected:', err);
23
+ } finally {
24
+ setConnecting(false);
25
+ }
26
+ }
27
+
28
+ function truncate(addr) {
29
+ return `${addr.slice(0, 6)}…${addr.slice(-4)}`;
30
+ }
31
+
5
32
  return (
6
33
  <div className="app">
7
34
  <Navbar />
8
35
  <main className="main">
9
- <div className="hero">
10
- <h1>Your Blockchain Project</h1>
11
- <p className="subtitle">Scaffolded by Africa's Blockchain Club</p>
12
-
13
- <div className="cards">
14
- <div className="card">
15
- <h3>📄 Write Contracts</h3>
16
- <p>Add your Solidity files to <code>contracts/</code></p>
17
- </div>
18
- <div className="card">
19
- <h3>⚙️ Compile</h3>
20
- <p>Run <code>npm run compile</code> in the project root</p>
21
- </div>
22
- <div className="card">
23
- <h3>🚀 Deploy</h3>
24
- <p>Add a script to <code>scripts/</code> and run it with Hardhat</p>
25
- </div>
26
- <div className="card">
27
- <h3>🔗 Connect</h3>
28
- <p>Use the wallet button above to connect MetaMask, then interact with your contracts</p>
29
- </div>
36
+ {account ? (
37
+ <div className="connected">
38
+ <span className="wallet-dot" />
39
+ <span className="wallet-address">{truncate(account)}</span>
30
40
  </div>
31
- </div>
41
+ ) : (
42
+ <button
43
+ className="connect-btn"
44
+ onClick={connectWallet}
45
+ disabled={connecting}
46
+ >
47
+ {connecting ? 'Connecting…' : 'Connect Wallet'}
48
+ </button>
49
+ )}
32
50
  </main>
33
51
  </div>
34
52
  );
@@ -1,7 +1,6 @@
1
1
  .navbar {
2
2
  display: flex;
3
3
  align-items: center;
4
- justify-content: space-between;
5
4
  padding: 1rem 2rem;
6
5
  background-color: #1a1a2e;
7
6
  border-bottom: 1px solid #2a2a4a;
@@ -10,12 +9,10 @@
10
9
  z-index: 100;
11
10
  }
12
11
 
13
- /* ── Brand ── */
14
12
  .navbar-brand {
15
13
  display: flex;
16
14
  align-items: center;
17
15
  gap: 0.6rem;
18
- text-decoration: none;
19
16
  }
20
17
 
21
18
  .navbar-logo {
@@ -29,62 +26,3 @@
29
26
  color: #e94560;
30
27
  letter-spacing: 0.3px;
31
28
  }
32
-
33
- /* ── Actions ── */
34
- .navbar-actions {
35
- display: flex;
36
- align-items: center;
37
- }
38
-
39
- /* Connect button */
40
- .connect-btn {
41
- background-color: #e94560;
42
- color: #ffffff;
43
- border: none;
44
- border-radius: 8px;
45
- padding: 0.55rem 1.3rem;
46
- font-size: 0.9rem;
47
- font-weight: 600;
48
- cursor: pointer;
49
- transition: background-color 0.18s ease, transform 0.12s ease;
50
- white-space: nowrap;
51
- }
52
-
53
- .connect-btn:hover:not(:disabled) {
54
- background-color: #c73652;
55
- transform: translateY(-1px);
56
- }
57
-
58
- .connect-btn:active:not(:disabled) {
59
- transform: translateY(0);
60
- }
61
-
62
- .connect-btn:disabled {
63
- background-color: #555566;
64
- cursor: not-allowed;
65
- }
66
-
67
- /* Connected badge */
68
- .wallet-badge {
69
- display: flex;
70
- align-items: center;
71
- gap: 0.5rem;
72
- background-color: #0f3460;
73
- border: 1px solid #e94560;
74
- border-radius: 8px;
75
- padding: 0.45rem 1rem;
76
- }
77
-
78
- .wallet-dot {
79
- width: 8px;
80
- height: 8px;
81
- background-color: #4caf50;
82
- border-radius: 50%;
83
- flex-shrink: 0;
84
- }
85
-
86
- .wallet-address {
87
- color: #e2e2e2;
88
- font-family: 'Fira Code', 'Cascadia Code', monospace;
89
- font-size: 0.875rem;
90
- }
@@ -1,56 +1,12 @@
1
- import { useState } from 'react';
2
- import { BrowserProvider } from 'ethers';
3
1
  import './Navbar.css';
4
2
 
5
3
  export default function Navbar() {
6
- const [account, setAccount] = useState(null);
7
- const [connecting, setConnecting] = useState(false);
8
-
9
- async function connectWallet() {
10
- if (!window.ethereum) {
11
- alert('MetaMask is not installed. Please install it at https://metamask.io');
12
- return;
13
- }
14
-
15
- setConnecting(true);
16
- try {
17
- const provider = new BrowserProvider(window.ethereum);
18
- const accounts = await provider.send('eth_requestAccounts', []);
19
- setAccount(accounts[0]);
20
- } catch (err) {
21
- console.error('Wallet connection rejected:', err);
22
- } finally {
23
- setConnecting(false);
24
- }
25
- }
26
-
27
- function truncate(addr) {
28
- return `${addr.slice(0, 6)}…${addr.slice(-4)}`;
29
- }
30
-
31
4
  return (
32
5
  <nav className="navbar">
33
6
  <div className="navbar-brand">
34
7
  <span className="navbar-logo">🔗</span>
35
8
  <span className="navbar-title">Africa's Blockchain Club</span>
36
9
  </div>
37
-
38
- <div className="navbar-actions">
39
- {account ? (
40
- <div className="wallet-badge">
41
- <span className="wallet-dot" />
42
- <span className="wallet-address">{truncate(account)}</span>
43
- </div>
44
- ) : (
45
- <button
46
- className="connect-btn"
47
- onClick={connectWallet}
48
- disabled={connecting}
49
- >
50
- {connecting ? 'Connecting…' : 'Connect Wallet'}
51
- </button>
52
- )}
53
- </div>
54
10
  </nav>
55
11
  );
56
12
  }
@@ -0,0 +1,11 @@
1
+ node_modules
2
+ .env
3
+ coverage
4
+ coverage.json
5
+ typechain
6
+ typechain-types
7
+ artifacts
8
+ cache
9
+ package-lock.json
10
+ .claude
11
+ CLAUDE.md