abc-blockchain 0.1.1
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/PUBLISHING.md +89 -0
- package/README.md +144 -0
- package/SECURITY.md +41 -0
- package/dist/branding.d.ts +7 -0
- package/dist/branding.js +14 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +31 -0
- package/dist/config.d.ts +22 -0
- package/dist/config.js +15 -0
- package/dist/create-project.d.ts +3 -0
- package/dist/create-project.js +55 -0
- package/dist/create-project.test.d.ts +1 -0
- package/dist/create-project.test.js +29 -0
- package/dist/logger.d.ts +7 -0
- package/dist/logger.js +15 -0
- package/dist/package-manager.d.ts +3 -0
- package/dist/package-manager.js +14 -0
- package/dist/template.d.ts +3 -0
- package/dist/template.js +30 -0
- package/package.json +72 -0
- package/templates/hardhat-erc4337/.env.example +7 -0
- package/templates/hardhat-erc4337/.eslintrc.cjs +14 -0
- package/templates/hardhat-erc4337/.github/workflows/ci.yml +36 -0
- package/templates/hardhat-erc4337/.github/workflows/release.yml +26 -0
- package/templates/hardhat-erc4337/.husky/pre-commit +1 -0
- package/templates/hardhat-erc4337/.prettierrc.json +7 -0
- package/templates/hardhat-erc4337/README.md.tmpl +98 -0
- package/templates/hardhat-erc4337/contracts/AccountFactory.sol +45 -0
- package/templates/hardhat-erc4337/contracts/EntryPoint.sol +69 -0
- package/templates/hardhat-erc4337/contracts/SmartAccount.sol +93 -0
- package/templates/hardhat-erc4337/contracts/Token.sol +15 -0
- package/templates/hardhat-erc4337/contracts/interfaces/IEntryPoint.sol +24 -0
- package/templates/hardhat-erc4337/contracts/interfaces/IPaymasterHook.sol +12 -0
- package/templates/hardhat-erc4337/deployments/.gitkeep +1 -0
- package/templates/hardhat-erc4337/hardhat.config.ts +41 -0
- package/templates/hardhat-erc4337/ignition/modules/AccountAbstraction.ts +14 -0
- package/templates/hardhat-erc4337/package.json.tmpl +62 -0
- package/templates/hardhat-erc4337/scripts/createAccount.ts +33 -0
- package/templates/hardhat-erc4337/scripts/deploy.ts +50 -0
- package/templates/hardhat-erc4337/scripts/lib/bundler.ts +17 -0
- package/templates/hardhat-erc4337/scripts/lib/env.ts +18 -0
- package/templates/hardhat-erc4337/scripts/lib/logger.ts +8 -0
- package/templates/hardhat-erc4337/slither.config.json +4 -0
- package/templates/hardhat-erc4337/tasks/accounts.ts +17 -0
- package/templates/hardhat-erc4337/test/SmartAccount.ts +32 -0
- package/templates/hardhat-erc4337/tsconfig.json +15 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
security-events: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
test:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: actions/setup-node@v4
|
|
18
|
+
with:
|
|
19
|
+
node-version: 22
|
|
20
|
+
cache: npm
|
|
21
|
+
- run: npm ci
|
|
22
|
+
- run: npm run ci
|
|
23
|
+
|
|
24
|
+
codeql:
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
permissions:
|
|
27
|
+
security-events: write
|
|
28
|
+
packages: read
|
|
29
|
+
actions: read
|
|
30
|
+
contents: read
|
|
31
|
+
steps:
|
|
32
|
+
- uses: actions/checkout@v4
|
|
33
|
+
- uses: github/codeql-action/init@v3
|
|
34
|
+
with:
|
|
35
|
+
languages: javascript-typescript
|
|
36
|
+
- uses: github/codeql-action/analyze@v3
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: write
|
|
9
|
+
id-token: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
release:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
if: github.repository_owner != ''
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: actions/setup-node@v4
|
|
18
|
+
with:
|
|
19
|
+
node-version: 22
|
|
20
|
+
cache: npm
|
|
21
|
+
registry-url: https://registry.npmjs.org
|
|
22
|
+
- run: npm ci
|
|
23
|
+
- run: npm test
|
|
24
|
+
- run: npm publish --provenance --access public
|
|
25
|
+
env:
|
|
26
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npm run format:check && npm run lint && npm test
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# {{projectName}}
|
|
2
|
+
|
|
3
|
+
ERC-4337 Hardhat development environment generated by ABC Blockchain.
|
|
4
|
+
|
|
5
|
+
Creator: {{creator}}
|
|
6
|
+
Framework Signature: {{signature}}
|
|
7
|
+
|
|
8
|
+
## What Is Included
|
|
9
|
+
|
|
10
|
+
- Hardhat with TypeScript
|
|
11
|
+
- Ethers v6
|
|
12
|
+
- OpenZeppelin contracts
|
|
13
|
+
- Upgrade-ready smart account template
|
|
14
|
+
- Deterministic account factory
|
|
15
|
+
- EntryPoint integration scaffold
|
|
16
|
+
- UserOperation type support
|
|
17
|
+
- Paymaster hook interface
|
|
18
|
+
- Bundler RPC integration point
|
|
19
|
+
- Deployment manifests
|
|
20
|
+
- GitHub Actions CI and release workflow
|
|
21
|
+
- ESLint, Prettier, Husky-ready scripts
|
|
22
|
+
- Slither-ready configuration
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
cp .env.example .env
|
|
28
|
+
npm install
|
|
29
|
+
npm run ci
|
|
30
|
+
npm test
|
|
31
|
+
npm run deploy:local
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Project Structure
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
{{projectName}}/
|
|
38
|
+
├── contracts/
|
|
39
|
+
│ ├── interfaces/
|
|
40
|
+
│ ├── SmartAccount.sol
|
|
41
|
+
│ ├── AccountFactory.sol
|
|
42
|
+
│ ├── EntryPoint.sol
|
|
43
|
+
│ └── Token.sol
|
|
44
|
+
├── scripts/
|
|
45
|
+
├── deployments/
|
|
46
|
+
├── ignition/
|
|
47
|
+
├── tasks/
|
|
48
|
+
├── test/
|
|
49
|
+
├── .github/workflows/
|
|
50
|
+
├── hardhat.config.ts
|
|
51
|
+
├── tsconfig.json
|
|
52
|
+
├── .env.example
|
|
53
|
+
├── README.md
|
|
54
|
+
└── package.json
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Security Notes
|
|
58
|
+
|
|
59
|
+
- Do not commit `.env`.
|
|
60
|
+
- Use dedicated deployer keys with limited funds.
|
|
61
|
+
- Run `npm audit` and Slither before production deployments.
|
|
62
|
+
- Replace the included development EntryPoint with the canonical audited EntryPoint for production networks.
|
|
63
|
+
- Review upgrade authorization and owner custody before deploying account implementations.
|
|
64
|
+
- Validate bundler and paymaster provider trust assumptions.
|
|
65
|
+
|
|
66
|
+
## Commands
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm run build
|
|
70
|
+
npm test
|
|
71
|
+
npm run deploy:local
|
|
72
|
+
npm run deploy:sepolia
|
|
73
|
+
npm run lint
|
|
74
|
+
npm run format:check
|
|
75
|
+
npm run audit
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Environment
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
PRIVATE_KEY=
|
|
82
|
+
SEPOLIA_RPC_URL=
|
|
83
|
+
MAINNET_RPC_URL=
|
|
84
|
+
ETHERSCAN_API_KEY=
|
|
85
|
+
REPORT_GAS=false
|
|
86
|
+
BUNDLER_RPC_URL=
|
|
87
|
+
PAYMASTER_RPC_URL=
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Production Checklist
|
|
91
|
+
|
|
92
|
+
- Replace development EntryPoint with a canonical ERC-4337 EntryPoint deployment.
|
|
93
|
+
- Add invariant and fuzz tests for account validation and nonce handling.
|
|
94
|
+
- Run static analysis with Slither.
|
|
95
|
+
- Run gas reports on all account execution paths.
|
|
96
|
+
- Confirm UUPS upgrade controls with a multisig or governance process.
|
|
97
|
+
- Add deployment approvals and protected environments in GitHub.
|
|
98
|
+
- Pin dependency ranges before audited releases.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.24;
|
|
3
|
+
|
|
4
|
+
import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
|
|
5
|
+
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
|
|
6
|
+
import {IEntryPoint} from "./interfaces/IEntryPoint.sol";
|
|
7
|
+
import {SmartAccount} from "./SmartAccount.sol";
|
|
8
|
+
|
|
9
|
+
contract AccountFactory {
|
|
10
|
+
SmartAccount public immutable accountImplementation;
|
|
11
|
+
IEntryPoint public immutable entryPoint;
|
|
12
|
+
|
|
13
|
+
event AccountCreated(address indexed account, address indexed owner, uint256 salt);
|
|
14
|
+
|
|
15
|
+
constructor(IEntryPoint factoryEntryPoint) {
|
|
16
|
+
entryPoint = factoryEntryPoint;
|
|
17
|
+
accountImplementation = new SmartAccount();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function createAccount(address owner, uint256 salt) external returns (SmartAccount account) {
|
|
21
|
+
address predicted = getAccountAddress(owner, salt);
|
|
22
|
+
if (predicted.code.length > 0) {
|
|
23
|
+
return SmartAccount(payable(predicted));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
bytes memory initializer = abi.encodeCall(SmartAccount.initialize, (owner, entryPoint));
|
|
27
|
+
bytes memory bytecode = abi.encodePacked(
|
|
28
|
+
type(ERC1967Proxy).creationCode,
|
|
29
|
+
abi.encode(address(accountImplementation), initializer)
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
account = SmartAccount(payable(Create2.deploy(0, bytes32(salt), bytecode)));
|
|
33
|
+
emit AccountCreated(address(account), owner, salt);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getAccountAddress(address owner, uint256 salt) public view returns (address) {
|
|
37
|
+
bytes memory initializer = abi.encodeCall(SmartAccount.initialize, (owner, entryPoint));
|
|
38
|
+
bytes memory bytecode = abi.encodePacked(
|
|
39
|
+
type(ERC1967Proxy).creationCode,
|
|
40
|
+
abi.encode(address(accountImplementation), initializer)
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
return Create2.computeAddress(bytes32(salt), keccak256(bytecode));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.24;
|
|
3
|
+
|
|
4
|
+
import {IEntryPoint} from "./interfaces/IEntryPoint.sol";
|
|
5
|
+
|
|
6
|
+
contract EntryPoint is IEntryPoint {
|
|
7
|
+
mapping(address => uint256) private deposits;
|
|
8
|
+
|
|
9
|
+
event UserOperationHandled(address indexed sender, bytes32 indexed userOpHash, bool success);
|
|
10
|
+
event Deposited(address indexed account, uint256 amount);
|
|
11
|
+
event Withdrawn(address indexed account, address indexed withdrawAddress, uint256 amount);
|
|
12
|
+
|
|
13
|
+
receive() external payable {
|
|
14
|
+
depositTo(msg.sender);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function handleOps(
|
|
18
|
+
UserOperation[] calldata ops,
|
|
19
|
+
address payable beneficiary
|
|
20
|
+
) external override {
|
|
21
|
+
uint256 collected;
|
|
22
|
+
for (uint256 i = 0; i < ops.length; i++) {
|
|
23
|
+
bytes32 opHash = getUserOpHash(ops[i]);
|
|
24
|
+
(bool success, ) = ops[i].sender.call(ops[i].callData);
|
|
25
|
+
emit UserOperationHandled(ops[i].sender, opHash, success);
|
|
26
|
+
collected += ops[i].preVerificationGas;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (collected > 0 && address(this).balance >= collected) {
|
|
30
|
+
beneficiary.transfer(collected);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getUserOpHash(UserOperation calldata userOp) public view override returns (bytes32) {
|
|
35
|
+
return
|
|
36
|
+
keccak256(
|
|
37
|
+
abi.encode(
|
|
38
|
+
userOp.sender,
|
|
39
|
+
userOp.nonce,
|
|
40
|
+
keccak256(userOp.initCode),
|
|
41
|
+
keccak256(userOp.callData),
|
|
42
|
+
userOp.callGasLimit,
|
|
43
|
+
userOp.verificationGasLimit,
|
|
44
|
+
userOp.preVerificationGas,
|
|
45
|
+
userOp.maxFeePerGas,
|
|
46
|
+
userOp.maxPriorityFeePerGas,
|
|
47
|
+
keccak256(userOp.paymasterAndData),
|
|
48
|
+
block.chainid,
|
|
49
|
+
address(this)
|
|
50
|
+
)
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function depositTo(address account) public payable override {
|
|
55
|
+
deposits[account] += msg.value;
|
|
56
|
+
emit Deposited(account, msg.value);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function balanceOf(address account) external view override returns (uint256) {
|
|
60
|
+
return deposits[account];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function withdrawTo(address payable withdrawAddress, uint256 amount) external override {
|
|
64
|
+
require(deposits[msg.sender] >= amount, "Insufficient deposit");
|
|
65
|
+
deposits[msg.sender] -= amount;
|
|
66
|
+
withdrawAddress.transfer(amount);
|
|
67
|
+
emit Withdrawn(msg.sender, withdrawAddress, amount);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.24;
|
|
3
|
+
|
|
4
|
+
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
|
|
5
|
+
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
|
|
6
|
+
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
|
7
|
+
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
|
8
|
+
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
|
|
9
|
+
import {IEntryPoint} from "./interfaces/IEntryPoint.sol";
|
|
10
|
+
|
|
11
|
+
contract SmartAccount is Initializable, OwnableUpgradeable, UUPSUpgradeable {
|
|
12
|
+
using ECDSA for bytes32;
|
|
13
|
+
|
|
14
|
+
error NotEntryPoint();
|
|
15
|
+
error ExecutionFailed(bytes result);
|
|
16
|
+
error InvalidSignature();
|
|
17
|
+
|
|
18
|
+
IEntryPoint public entryPoint;
|
|
19
|
+
uint256 public nonce;
|
|
20
|
+
|
|
21
|
+
event EntryPointUpdated(address indexed entryPoint);
|
|
22
|
+
event AccountExecuted(address indexed target, uint256 value, bytes data);
|
|
23
|
+
|
|
24
|
+
modifier onlyEntryPoint() {
|
|
25
|
+
if (msg.sender != address(entryPoint)) revert NotEntryPoint();
|
|
26
|
+
_;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
constructor() {
|
|
30
|
+
_disableInitializers();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function initialize(address initialOwner, IEntryPoint accountEntryPoint) external initializer {
|
|
34
|
+
__Ownable_init(initialOwner);
|
|
35
|
+
entryPoint = accountEntryPoint;
|
|
36
|
+
emit EntryPointUpdated(address(accountEntryPoint));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
receive() external payable {}
|
|
40
|
+
|
|
41
|
+
function execute(address target, uint256 value, bytes calldata data) external onlyEntryPoint {
|
|
42
|
+
_call(target, value, data);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function executeBatch(
|
|
46
|
+
address[] calldata targets,
|
|
47
|
+
uint256[] calldata values,
|
|
48
|
+
bytes[] calldata payloads
|
|
49
|
+
) external onlyEntryPoint {
|
|
50
|
+
require(
|
|
51
|
+
targets.length == values.length && targets.length == payloads.length,
|
|
52
|
+
"Length mismatch"
|
|
53
|
+
);
|
|
54
|
+
for (uint256 i = 0; i < targets.length; i++) {
|
|
55
|
+
_call(targets[i], values[i], payloads[i]);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function validateUserOp(
|
|
60
|
+
IEntryPoint.UserOperation calldata userOp,
|
|
61
|
+
bytes32 userOpHash,
|
|
62
|
+
uint256 missingAccountFunds
|
|
63
|
+
) external onlyEntryPoint returns (uint256 validationData) {
|
|
64
|
+
bytes32 digest = MessageHashUtils.toEthSignedMessageHash(userOpHash);
|
|
65
|
+
address recovered = ECDSA.recover(digest, userOp.signature);
|
|
66
|
+
if (recovered != owner()) revert InvalidSignature();
|
|
67
|
+
|
|
68
|
+
require(userOp.nonce == nonce, "Invalid nonce");
|
|
69
|
+
unchecked {
|
|
70
|
+
nonce++;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (missingAccountFunds > 0) {
|
|
74
|
+
(bool sent, ) = payable(msg.sender).call{value: missingAccountFunds}("");
|
|
75
|
+
require(sent, "Prefund failed");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function updateEntryPoint(IEntryPoint newEntryPoint) external onlyOwner {
|
|
82
|
+
entryPoint = newEntryPoint;
|
|
83
|
+
emit EntryPointUpdated(address(newEntryPoint));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function _call(address target, uint256 value, bytes calldata data) internal {
|
|
87
|
+
(bool success, bytes memory result) = target.call{value: value}(data);
|
|
88
|
+
if (!success) revert ExecutionFailed(result);
|
|
89
|
+
emit AccountExecuted(target, value, data);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function _authorizeUpgrade(address) internal override onlyOwner {}
|
|
93
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.24;
|
|
3
|
+
|
|
4
|
+
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
|
5
|
+
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
|
6
|
+
|
|
7
|
+
contract Token is ERC20, Ownable {
|
|
8
|
+
constructor(address initialOwner) ERC20("ABC Demo Token", "ABC") Ownable(initialOwner) {
|
|
9
|
+
_mint(initialOwner, 1_000_000 ether);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function mint(address to, uint256 amount) external onlyOwner {
|
|
13
|
+
_mint(to, amount);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.24;
|
|
3
|
+
|
|
4
|
+
interface IEntryPoint {
|
|
5
|
+
struct UserOperation {
|
|
6
|
+
address sender;
|
|
7
|
+
uint256 nonce;
|
|
8
|
+
bytes initCode;
|
|
9
|
+
bytes callData;
|
|
10
|
+
uint256 callGasLimit;
|
|
11
|
+
uint256 verificationGasLimit;
|
|
12
|
+
uint256 preVerificationGas;
|
|
13
|
+
uint256 maxFeePerGas;
|
|
14
|
+
uint256 maxPriorityFeePerGas;
|
|
15
|
+
bytes paymasterAndData;
|
|
16
|
+
bytes signature;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function handleOps(UserOperation[] calldata ops, address payable beneficiary) external;
|
|
20
|
+
function getUserOpHash(UserOperation calldata userOp) external view returns (bytes32);
|
|
21
|
+
function depositTo(address account) external payable;
|
|
22
|
+
function balanceOf(address account) external view returns (uint256);
|
|
23
|
+
function withdrawTo(address payable withdrawAddress, uint256 amount) external;
|
|
24
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.24;
|
|
3
|
+
|
|
4
|
+
import {IEntryPoint} from "./IEntryPoint.sol";
|
|
5
|
+
|
|
6
|
+
interface IPaymasterHook {
|
|
7
|
+
function validatePaymasterUserOp(
|
|
8
|
+
IEntryPoint.UserOperation calldata userOp,
|
|
9
|
+
bytes32 userOpHash,
|
|
10
|
+
uint256 maxCost
|
|
11
|
+
) external returns (bytes memory context, uint256 validationData);
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import hardhatEthers from "@nomicfoundation/hardhat-ethers";
|
|
2
|
+
import hardhatMocha from "@nomicfoundation/hardhat-mocha";
|
|
3
|
+
import accountsTask from "./tasks/accounts.js";
|
|
4
|
+
import { defineConfig } from "hardhat/config";
|
|
5
|
+
import { env } from "./scripts/lib/env.js";
|
|
6
|
+
|
|
7
|
+
const accounts = env.PRIVATE_KEY ? [env.PRIVATE_KEY] : [];
|
|
8
|
+
|
|
9
|
+
export default defineConfig({
|
|
10
|
+
plugins: [hardhatEthers, hardhatMocha],
|
|
11
|
+
tasks: [accountsTask],
|
|
12
|
+
solidity: {
|
|
13
|
+
version: "0.8.24",
|
|
14
|
+
settings: {
|
|
15
|
+
optimizer: {
|
|
16
|
+
enabled: true,
|
|
17
|
+
runs: 200
|
|
18
|
+
},
|
|
19
|
+
viaIR: true
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
networks: {
|
|
23
|
+
localhost: {
|
|
24
|
+
type: "http",
|
|
25
|
+
url: "http://127.0.0.1:8545",
|
|
26
|
+
chainId: 31337
|
|
27
|
+
},
|
|
28
|
+
sepolia: {
|
|
29
|
+
type: "http",
|
|
30
|
+
url: env.SEPOLIA_RPC_URL,
|
|
31
|
+
chainId: 11155111,
|
|
32
|
+
accounts
|
|
33
|
+
},
|
|
34
|
+
mainnet: {
|
|
35
|
+
type: "http",
|
|
36
|
+
url: env.MAINNET_RPC_URL,
|
|
37
|
+
chainId: 1,
|
|
38
|
+
accounts
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Install @nomicfoundation/hardhat-ignition when enabling Ignition deployments.
|
|
2
|
+
// The module shape is kept here so deployment ownership stays explicit.
|
|
3
|
+
import { buildModule } from "hardhat/ignition";
|
|
4
|
+
|
|
5
|
+
const AccountAbstractionModule = buildModule("AccountAbstractionModule", (m) => {
|
|
6
|
+
const deployer = m.getAccount(0);
|
|
7
|
+
const entryPoint = m.contract("EntryPoint");
|
|
8
|
+
const factory = m.contract("AccountFactory", [entryPoint]);
|
|
9
|
+
const token = m.contract("Token", [deployer]);
|
|
10
|
+
|
|
11
|
+
return { entryPoint, factory, token };
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export default AccountAbstractionModule;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "ERC-4337 development environment generated by ABC Blockchain.",
|
|
6
|
+
"author": "{{creator}}",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "hardhat compile",
|
|
11
|
+
"clean": "hardhat clean",
|
|
12
|
+
"test": "hardhat test",
|
|
13
|
+
"ci": "npm run format:check && npm run lint && npm run typecheck && npm test && npm run audit",
|
|
14
|
+
"deploy:local": "hardhat run scripts/deploy.ts --network localhost",
|
|
15
|
+
"deploy:sepolia": "hardhat run scripts/deploy.ts --network sepolia",
|
|
16
|
+
"lint": "eslint \"{scripts,tasks,test,ignition}/**/*.ts\"",
|
|
17
|
+
"format": "prettier --write .",
|
|
18
|
+
"format:check": "prettier --check .",
|
|
19
|
+
"typecheck": "tsc --noEmit",
|
|
20
|
+
"audit": "npm audit --audit-level=moderate",
|
|
21
|
+
"slither": "slither . --config-file slither.config.json"
|
|
22
|
+
},
|
|
23
|
+
"abcBlockchain": {
|
|
24
|
+
"creator": "{{creator}}",
|
|
25
|
+
"signature": "{{signature}}",
|
|
26
|
+
"generatedBy": "abc-blockchain",
|
|
27
|
+
"telemetry": false
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@openzeppelin/contracts": "5.0.2",
|
|
31
|
+
"@openzeppelin/contracts-upgradeable": "5.0.2",
|
|
32
|
+
"dotenv": "16.4.5",
|
|
33
|
+
"ethers": "6.13.1",
|
|
34
|
+
"zod": "3.23.8"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@nomicfoundation/hardhat-ethers": "4.0.12",
|
|
38
|
+
"@nomicfoundation/hardhat-mocha": "3.0.20",
|
|
39
|
+
"@types/chai": "4.3.16",
|
|
40
|
+
"@types/mocha": "10.0.7",
|
|
41
|
+
"@types/node": "22.10.2",
|
|
42
|
+
"@typescript-eslint/eslint-plugin": "7.16.0",
|
|
43
|
+
"@typescript-eslint/parser": "7.16.0",
|
|
44
|
+
"chai": "4.4.1",
|
|
45
|
+
"eslint": "8.57.0",
|
|
46
|
+
"eslint-config-prettier": "9.1.0",
|
|
47
|
+
"hardhat": "3.7.0",
|
|
48
|
+
"husky": "9.1.1",
|
|
49
|
+
"prettier": "3.3.3",
|
|
50
|
+
"prettier-plugin-solidity": "1.3.1",
|
|
51
|
+
"ts-node": "10.9.2",
|
|
52
|
+
"typescript": "5.5.3"
|
|
53
|
+
},
|
|
54
|
+
"overrides": {
|
|
55
|
+
"diff": "8.0.3",
|
|
56
|
+
"serialize-javascript": "7.0.5",
|
|
57
|
+
"ws": "8.20.1"
|
|
58
|
+
},
|
|
59
|
+
"engines": {
|
|
60
|
+
"node": ">=22.0.0"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { network } from "hardhat";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { log } from "./lib/logger.js";
|
|
5
|
+
|
|
6
|
+
async function main() {
|
|
7
|
+
const connection = await network.create();
|
|
8
|
+
const { ethers } = connection;
|
|
9
|
+
const [owner] = await ethers.getSigners();
|
|
10
|
+
const chainId = Number((await ethers.provider.getNetwork()).chainId);
|
|
11
|
+
const deployment = JSON.parse(
|
|
12
|
+
await fs.readFile(path.join("deployments", `${chainId}.json`), "utf8")
|
|
13
|
+
) as { contracts: { AccountFactory: string } };
|
|
14
|
+
|
|
15
|
+
const factory = await ethers.getContractAt("AccountFactory", deployment.contracts.AccountFactory);
|
|
16
|
+
const salt = 0n;
|
|
17
|
+
const predicted = await factory.getAccountAddress(owner.address, salt);
|
|
18
|
+
const tx = await factory.createAccount(owner.address, salt);
|
|
19
|
+
await tx.wait();
|
|
20
|
+
|
|
21
|
+
log.info("account:created", {
|
|
22
|
+
owner: owner.address,
|
|
23
|
+
smartAccount: predicted
|
|
24
|
+
});
|
|
25
|
+
await connection.close();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
main().catch((error) => {
|
|
29
|
+
log.error("account:create:failed", {
|
|
30
|
+
error: error instanceof Error ? error.message : String(error)
|
|
31
|
+
});
|
|
32
|
+
process.exitCode = 1;
|
|
33
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { network } from "hardhat";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { log } from "./lib/logger.js";
|
|
5
|
+
|
|
6
|
+
async function main() {
|
|
7
|
+
const connection = await network.create();
|
|
8
|
+
const { ethers } = connection;
|
|
9
|
+
const [deployer] = await ethers.getSigners();
|
|
10
|
+
log.info("deploy:start", { deployer: deployer.address });
|
|
11
|
+
|
|
12
|
+
const EntryPoint = await ethers.getContractFactory("EntryPoint");
|
|
13
|
+
const entryPoint = await EntryPoint.deploy();
|
|
14
|
+
await entryPoint.waitForDeployment();
|
|
15
|
+
|
|
16
|
+
const AccountFactory = await ethers.getContractFactory("AccountFactory");
|
|
17
|
+
const accountFactory = await AccountFactory.deploy(await entryPoint.getAddress());
|
|
18
|
+
await accountFactory.waitForDeployment();
|
|
19
|
+
|
|
20
|
+
const Token = await ethers.getContractFactory("Token");
|
|
21
|
+
const token = await Token.deploy(deployer.address);
|
|
22
|
+
await token.waitForDeployment();
|
|
23
|
+
|
|
24
|
+
const deployment = {
|
|
25
|
+
network: (await ethers.provider.getNetwork()).name,
|
|
26
|
+
chainId: Number((await ethers.provider.getNetwork()).chainId),
|
|
27
|
+
deployer: deployer.address,
|
|
28
|
+
contracts: {
|
|
29
|
+
EntryPoint: await entryPoint.getAddress(),
|
|
30
|
+
AccountFactory: await accountFactory.getAddress(),
|
|
31
|
+
Token: await token.getAddress()
|
|
32
|
+
},
|
|
33
|
+
creator: "{{creator}}",
|
|
34
|
+
signature: "{{signature}}"
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
await fs.mkdir("deployments", { recursive: true });
|
|
38
|
+
await fs.writeFile(
|
|
39
|
+
path.join("deployments", `${deployment.chainId}.json`),
|
|
40
|
+
JSON.stringify(deployment, null, 2)
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
log.info("deploy:complete", deployment);
|
|
44
|
+
await connection.close();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
main().catch((error) => {
|
|
48
|
+
log.error("deploy:failed", { error: error instanceof Error ? error.message : String(error) });
|
|
49
|
+
process.exitCode = 1;
|
|
50
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { JsonRpcProvider } from "ethers";
|
|
2
|
+
import { env } from "./env.js";
|
|
3
|
+
|
|
4
|
+
export type BundlerClient = {
|
|
5
|
+
sendUserOperation(userOperation: unknown, entryPoint: string): Promise<string>;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function createBundlerClient(): BundlerClient | undefined {
|
|
9
|
+
if (!env.BUNDLER_RPC_URL) return undefined;
|
|
10
|
+
const provider = new JsonRpcProvider(env.BUNDLER_RPC_URL);
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
async sendUserOperation(userOperation, entryPoint) {
|
|
14
|
+
return provider.send("eth_sendUserOperation", [userOperation, entryPoint]) as Promise<string>;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
const EnvSchema = z.object({
|
|
5
|
+
PRIVATE_KEY: z.string().optional(),
|
|
6
|
+
SEPOLIA_RPC_URL: z.string().url().optional().default("http://127.0.0.1:8545"),
|
|
7
|
+
MAINNET_RPC_URL: z.string().url().optional().default("http://127.0.0.1:8545"),
|
|
8
|
+
ETHERSCAN_API_KEY: z.string().optional().default(""),
|
|
9
|
+
REPORT_GAS: z
|
|
10
|
+
.string()
|
|
11
|
+
.optional()
|
|
12
|
+
.default("false")
|
|
13
|
+
.transform((value) => value === "true"),
|
|
14
|
+
BUNDLER_RPC_URL: z.string().url().optional(),
|
|
15
|
+
PAYMASTER_RPC_URL: z.string().url().optional()
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const env = EnvSchema.parse(process.env);
|