abc-scaffold 1.0.0
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 +21 -0
- package/README.md +109 -0
- package/bin/abc-scaffold.js +17 -0
- package/package.json +42 -0
- package/src/logger.js +16 -0
- package/src/scaffold.js +341 -0
- package/templates/.env.example +4 -0
- package/templates/frontend/index.html +12 -0
- package/templates/frontend/package.json +20 -0
- package/templates/frontend/src/App.css +93 -0
- package/templates/frontend/src/App.jsx +37 -0
- package/templates/frontend/src/components/Navbar.css +90 -0
- package/templates/frontend/src/components/Navbar.jsx +56 -0
- package/templates/frontend/src/main.jsx +10 -0
- package/templates/frontend/vite.config.js +6 -0
- package/templates/hardhat.config.js +33 -0
- package/templates/package.json +24 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kimberley Bezuidenhout
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# abc-scaffold
|
|
2
|
+
|
|
3
|
+
**Africa's Blockchain Club** — project-agnostic blockchain scaffolding tool. Bootstrap a complete Hardhat + React workspace in seconds.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx abc-scaffold my-project
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## What gets scaffolded
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
my-project/
|
|
15
|
+
├── contracts/ # Drop your Solidity files here
|
|
16
|
+
├── scripts/ # Deployment and utility scripts
|
|
17
|
+
├── test/ # Hardhat test files
|
|
18
|
+
├── frontend/ # React + Vite UI
|
|
19
|
+
│ └── src/
|
|
20
|
+
│ ├── App.jsx
|
|
21
|
+
│ └── components/
|
|
22
|
+
│ └── Navbar.jsx ← sticky nav with Connect Wallet button
|
|
23
|
+
├── hardhat.config.js
|
|
24
|
+
├── .env.example
|
|
25
|
+
├── .gitignore
|
|
26
|
+
└── README.md
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Both root and frontend dependencies are installed automatically.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
- **Project-agnostic** — works for NFTs, DeFi, DAOs, smart accounts, or anything else
|
|
36
|
+
- **Hardhat pre-configured** — Solidity 0.8.26, Cancun EVM, optimizer on, Sepolia network
|
|
37
|
+
- **OpenZeppelin + Account Abstraction** — ERC-20/721/1155, upgradeable contracts, ERC-4337 interfaces included as dependencies
|
|
38
|
+
- **React frontend** — Vite + React with a navbar and MetaMask wallet connection (ethers.js v6)
|
|
39
|
+
- **Auto install** — runs `npm install` in both the root and `frontend/` at the end
|
|
40
|
+
- **Plain JavaScript** — no TypeScript, no build step
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# With a name argument
|
|
48
|
+
npx abc-scaffold my-nft-project
|
|
49
|
+
|
|
50
|
+
# Or let it prompt you
|
|
51
|
+
npx abc-scaffold
|
|
52
|
+
? Project name: my-dao
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Inside the generated project
|
|
58
|
+
|
|
59
|
+
### Compile contracts
|
|
60
|
+
```bash
|
|
61
|
+
npm run compile
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Run tests
|
|
65
|
+
```bash
|
|
66
|
+
npm test
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Start a local node
|
|
70
|
+
```bash
|
|
71
|
+
npx hardhat node
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Deploy locally
|
|
75
|
+
```bash
|
|
76
|
+
npx hardhat run scripts/deploy.js --network localhost
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Start the frontend
|
|
80
|
+
```bash
|
|
81
|
+
cd frontend
|
|
82
|
+
npm run dev # → http://localhost:5173
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Repository structure
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
abc-scaffold/ (this repo)
|
|
91
|
+
├── bin/
|
|
92
|
+
│ └── abc-scaffold.js # CLI entry point
|
|
93
|
+
├── src/
|
|
94
|
+
│ ├── scaffold.js # Scaffolding logic
|
|
95
|
+
│ └── logger.js # Coloured output
|
|
96
|
+
├── templates/
|
|
97
|
+
│ ├── package.json # Template for generated projects
|
|
98
|
+
│ ├── hardhat.config.js
|
|
99
|
+
│ ├── .env.example
|
|
100
|
+
│ ├── .gitignore
|
|
101
|
+
│ └── frontend/ # Full React app template
|
|
102
|
+
└── package.json
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
MIT — Africa's Blockchain Club
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { scaffold } from '../src/scaffold.js';
|
|
4
|
+
import { printBanner } from '../src/logger.js';
|
|
5
|
+
|
|
6
|
+
const arg = process.argv[2];
|
|
7
|
+
|
|
8
|
+
if (arg === '--help' || arg === '-h') {
|
|
9
|
+
printBanner();
|
|
10
|
+
console.log(' Usage: npx abc-scaffold [project-name]\n');
|
|
11
|
+
console.log(' Scaffolds a Hardhat + React blockchain project.\n');
|
|
12
|
+
console.log(' If project-name is omitted you will be prompted.\n');
|
|
13
|
+
process.exit(0);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
printBanner();
|
|
17
|
+
scaffold(arg);
|
package/package.json
ADDED
|
@@ -0,0 +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
|
+
}
|
package/src/logger.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
export function printBanner() {
|
|
4
|
+
console.log('');
|
|
5
|
+
console.log(chalk.yellow('╔══════════════════════════════════════════════╗'));
|
|
6
|
+
console.log(chalk.yellow('║') + chalk.bold.green(" Africa's Blockchain Club ") + chalk.yellow('║'));
|
|
7
|
+
console.log(chalk.yellow('║') + chalk.cyan(' abc-scaffold v1.0.0 ') + chalk.yellow('║'));
|
|
8
|
+
console.log(chalk.yellow('╚══════════════════════════════════════════════╝'));
|
|
9
|
+
console.log('');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const step = (msg) => console.log('\n' + chalk.bold.blue('▶ ' + msg));
|
|
13
|
+
export const success = (msg) => console.log(chalk.green(' ✔ ') + msg);
|
|
14
|
+
export const info = (msg) => console.log(chalk.cyan(' → ') + msg);
|
|
15
|
+
export const warn = (msg) => console.log(chalk.yellow(' ⚠ ') + msg);
|
|
16
|
+
export const error = (msg) => console.log(chalk.red(' ✖ ') + msg);
|
package/src/scaffold.js
ADDED
|
@@ -0,0 +1,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
|
+
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
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>{{PROJECT_NAME}}</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.jsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "frontend",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"ethers": "^6.16.0",
|
|
13
|
+
"react": "^18.2.0",
|
|
14
|
+
"react-dom": "^18.2.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@vitejs/plugin-react": "^4.2.1",
|
|
18
|
+
"vite": "^5.0.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
*,
|
|
2
|
+
*::before,
|
|
3
|
+
*::after {
|
|
4
|
+
margin: 0;
|
|
5
|
+
padding: 0;
|
|
6
|
+
box-sizing: border-box;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
body {
|
|
10
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
11
|
+
background-color: #0d0d1a;
|
|
12
|
+
color: #e2e2e2;
|
|
13
|
+
min-height: 100vh;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.app {
|
|
17
|
+
min-height: 100vh;
|
|
18
|
+
display: flex;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* ── Main content ── */
|
|
23
|
+
.main {
|
|
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;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.hero h1 {
|
|
37
|
+
font-size: 3rem;
|
|
38
|
+
font-weight: 800;
|
|
39
|
+
color: #ffffff;
|
|
40
|
+
margin-bottom: 0.75rem;
|
|
41
|
+
letter-spacing: -0.5px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.subtitle {
|
|
45
|
+
font-size: 1.1rem;
|
|
46
|
+
color: #e94560;
|
|
47
|
+
margin-bottom: 3.5rem;
|
|
48
|
+
font-weight: 500;
|
|
49
|
+
}
|
|
50
|
+
|
|
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;
|
|
57
|
+
}
|
|
58
|
+
|
|
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;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.card:hover {
|
|
69
|
+
border-color: #e94560;
|
|
70
|
+
transform: translateY(-3px);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.card h3 {
|
|
74
|
+
font-size: 1.05rem;
|
|
75
|
+
font-weight: 700;
|
|
76
|
+
color: #ffffff;
|
|
77
|
+
margin-bottom: 0.6rem;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.card p {
|
|
81
|
+
font-size: 0.9rem;
|
|
82
|
+
color: #9090a8;
|
|
83
|
+
line-height: 1.5;
|
|
84
|
+
}
|
|
85
|
+
|
|
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;
|
|
92
|
+
font-family: 'Fira Code', 'Cascadia Code', monospace;
|
|
93
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import Navbar from './components/Navbar.jsx';
|
|
2
|
+
import './App.css';
|
|
3
|
+
|
|
4
|
+
function App() {
|
|
5
|
+
return (
|
|
6
|
+
<div className="app">
|
|
7
|
+
<Navbar />
|
|
8
|
+
<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>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</main>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default App;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
.navbar {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
justify-content: space-between;
|
|
5
|
+
padding: 1rem 2rem;
|
|
6
|
+
background-color: #1a1a2e;
|
|
7
|
+
border-bottom: 1px solid #2a2a4a;
|
|
8
|
+
position: sticky;
|
|
9
|
+
top: 0;
|
|
10
|
+
z-index: 100;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* ── Brand ── */
|
|
14
|
+
.navbar-brand {
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
gap: 0.6rem;
|
|
18
|
+
text-decoration: none;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.navbar-logo {
|
|
22
|
+
font-size: 1.4rem;
|
|
23
|
+
line-height: 1;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.navbar-title {
|
|
27
|
+
font-size: 1.1rem;
|
|
28
|
+
font-weight: 700;
|
|
29
|
+
color: #e94560;
|
|
30
|
+
letter-spacing: 0.3px;
|
|
31
|
+
}
|
|
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
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { BrowserProvider } from 'ethers';
|
|
3
|
+
import './Navbar.css';
|
|
4
|
+
|
|
5
|
+
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
|
+
return (
|
|
32
|
+
<nav className="navbar">
|
|
33
|
+
<div className="navbar-brand">
|
|
34
|
+
<span className="navbar-logo">🔗</span>
|
|
35
|
+
<span className="navbar-title">Africa's Blockchain Club</span>
|
|
36
|
+
</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
|
+
</nav>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require("@nomicfoundation/hardhat-toolbox");
|
|
2
|
+
require("@openzeppelin/hardhat-upgrades");
|
|
3
|
+
require("dotenv").config();
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
solidity: {
|
|
7
|
+
compilers: [
|
|
8
|
+
{
|
|
9
|
+
version: "0.8.26",
|
|
10
|
+
settings: {
|
|
11
|
+
evmVersion: "cancun",
|
|
12
|
+
optimizer: { enabled: true, runs: 200 }
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
networks: {
|
|
18
|
+
localhost: {
|
|
19
|
+
url: "http://127.0.0.1:8545"
|
|
20
|
+
},
|
|
21
|
+
hardhat: {},
|
|
22
|
+
sepolia: {
|
|
23
|
+
url: process.env.SEPOLIA_RPC_URL || "",
|
|
24
|
+
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : []
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
etherscan: {
|
|
28
|
+
apiKey: process.env.ETHERSCAN_API_KEY || ""
|
|
29
|
+
},
|
|
30
|
+
sourcify: {
|
|
31
|
+
enabled: true
|
|
32
|
+
}
|
|
33
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Project agnostic blockchain repo",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"compile": "hardhat compile",
|
|
8
|
+
"test": "hardhat test"
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
|
|
12
|
+
"dotenv": "^16.4.5"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@account-abstraction/contracts": "^0.6.0",
|
|
16
|
+
"@openzeppelin/contracts": "^5.0.2",
|
|
17
|
+
"@openzeppelin/contracts-upgradeable": "^5.4.0",
|
|
18
|
+
"@openzeppelin/hardhat-upgrades": "^3.9.1",
|
|
19
|
+
"cors": "^2.8.6",
|
|
20
|
+
"ethers": "^6.16.0",
|
|
21
|
+
"express": "^5.2.1",
|
|
22
|
+
"hardhat": "^2.19.4"
|
|
23
|
+
}
|
|
24
|
+
}
|