Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -356,3 +356,6 @@ playwright/chrome-extensions/keplr/
playwright/yarn.lock

debug_container.dot

.gocache
AGENTS.md
1 change: 1 addition & 0 deletions evm-e2e/.entrypoint.addr
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0x31cB3eE2aC481dbF1fc85a3Ab1Aad925Af4b3b5a
1 change: 1 addition & 0 deletions evm-e2e/.passkeyfactory.addr
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0xF4434B66C39dA329a7230Faf919Ea65d3aCeB0AA
38 changes: 38 additions & 0 deletions evm-e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Localnet has JSON RPC enabled by default.

```bash
npm install
(cd passkey-sdk && npm install)
```

### Configure environment in `.env` file
Expand Down Expand Up @@ -58,3 +59,40 @@ test/contract_send_nibi.test.ts:
Ran 6 tests across 4 files. [38.08s]

```

### Deploy PasskeyAccountFactory on Nibiru localnet

Assumes:
- Nibiru localnet running with the P-256 precompile at `0x0000000000000000000000000000000000000100`.
- An ERC-4337 EntryPoint already deployed; pass its address via `ENTRY_POINT`.

```bash
# Optional: provide QX/QY (0x-prefixed 32-byte coords) to create the first account.
ENTRY_POINT=0xYourEntryPoint \
QX=0x... \
QY=0x... \
npx hardhat run scripts/deploy-passkey.js --network localhost
```

### Run an ERC-4337 bundler against Nibiru RPC

Uses a Stackup-style Docker image with a temp config. Required env: `RPC_URL`, `ENTRY_POINT`, `CHAIN_ID`. Optional:
`PRIVATE_KEY` (bundler signer), `BUNDLER_PORT` (default 4337), `BUNDLER_IMAGE` (default
`ghcr.io/stackup-wallet/stackup-bundler:latest`).

```bash
RPC_URL=http://127.0.0.1:8545 \
ENTRY_POINT=0x... \
CHAIN_ID=12345 \
npm run bundler
```

### Passkey ERC-4337 test coverage

The `bun test` suite now exercises the passkey ERC-4337 flow end-to-end. During the run it:

- Builds the `passkey-sdk`, deploys a fresh `EntryPointV06` + `PasskeyAccountFactory`, and funds the dev bundler key.
- Starts the local bundler from `passkey-sdk/dist/local-bundler.js` on port `14437`.
- Executes the CLI passkey script against that bundler to prove a full user operation.

Ensure `node`, `npm`, and `tsup` dependencies are installed (`npm install` in both `evm-e2e/` and `evm-e2e/passkey-sdk/`) and that port `14437` is free before running `bun test` or `just test-e2e`.
11 changes: 11 additions & 0 deletions evm-e2e/contracts/passkey/EntryPointV06.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.24;

import "@account-abstraction/contracts/core/EntryPoint.sol";

/// @notice Thin wrapper that tags EntryPoint v0.6 with a version string for bundlers that require it.
contract EntryPointV06 is EntryPoint {
function entryPointVersion() external pure returns (string memory) {
return "0.6.0";
}
}
86 changes: 86 additions & 0 deletions evm-e2e/contracts/passkey/PasskeyAccount.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {UserOperation, SIG_VALIDATION_FAILED} from "./UserOperation.sol";
import {IEntryPoint} from "./interfaces/IEntryPoint.sol";

/// @notice Minimal ERC-4337-style account secured by a P-256 pubkey (raw r,s signatures).
/// @dev Uses Nibiru RIP-7212 precompile at 0x...0100. Signature format: abi.encode(r,s).
contract PasskeyAccount {
IEntryPoint public entryPoint;
bytes32 public qx;
bytes32 public qy;
uint256 public nonce;
bool private initialized;

event Executed(address indexed target, uint256 value, bytes data);

modifier onlyEntryPoint() {
require(msg.sender == address(entryPoint), "not entrypoint");
_;
}

function initialize(address _entryPoint, bytes32 _qx, bytes32 _qy) external {
require(!initialized, "initialized");
require(_entryPoint != address(0), "entrypoint=0");
initialized = true;
entryPoint = IEntryPoint(_entryPoint);
qx = _qx;
qy = _qy;
}

receive() external payable {}

function validateUserOp(
UserOperation calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds
) external onlyEntryPoint returns (uint256 validationData) {
if (userOp.nonce != nonce) return SIG_VALIDATION_FAILED;
uint256 verified = _verify(userOpHash, userOp.signature);
if (verified != 1) return SIG_VALIDATION_FAILED;
nonce++;
if (missingAccountFunds > 0) {
entryPoint.depositTo{value: missingAccountFunds}(address(this));
}
return 0;
}

function execute(address to, uint256 value, bytes calldata data) external onlyEntryPoint {
(bool ok, ) = to.call{value: value}(data);
require(ok, "exec failed");
emit Executed(to, value, data);
}

function _verify(bytes32 hash, bytes calldata signature) internal view returns (uint256) {
if (signature.length != 64) return 0;
(bytes32 r, bytes32 s) = abi.decode(signature, (bytes32, bytes32));
bytes32 digest = sha256(abi.encodePacked(hash));
bytes memory input = abi.encodePacked(digest, r, s, qx, qy);
(bool ok, bytes memory out) = address(0x100).staticcall(input);
if (!ok || out.length != 32) {
return 0;
}
return uint256(bytes32(out));
}
}

/// @notice Factory deploying cheap PasskeyAccount minimal-proxy clones.
contract PasskeyAccountFactory {
address public immutable IMPLEMENTATION;
address public immutable ENTRY_POINT;

event AccountCreated(address indexed account, bytes32 qx, bytes32 qy);

constructor(address _entryPoint) {
ENTRY_POINT = _entryPoint;
IMPLEMENTATION = address(new PasskeyAccount());
}

function createAccount(bytes32 _qx, bytes32 _qy) external returns (address account) {
account = Clones.clone(IMPLEMENTATION);
PasskeyAccount(payable(account)).initialize(ENTRY_POINT, _qx, _qy);
emit AccountCreated(account, _qx, _qy);
}
}
18 changes: 18 additions & 0 deletions evm-e2e/contracts/passkey/UserOperation.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

struct UserOperation {
address sender;
uint256 nonce;
bytes initCode;
bytes callData;
uint256 callGasLimit;
uint256 verificationGasLimit;
uint256 preVerificationGas;
uint256 maxFeePerGas;
uint256 maxPriorityFeePerGas;
bytes paymasterAndData;
bytes signature;
}

uint256 constant SIG_VALIDATION_FAILED = 1;
12 changes: 12 additions & 0 deletions evm-e2e/contracts/passkey/interfaces/IEntryPoint.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {UserOperation} from "../UserOperation.sol";

interface IEntryPoint {
function handleOps(UserOperation[] calldata ops, address payable beneficiary) external;

function depositTo(address account) external payable;

function balanceOf(address account) external view returns (uint256);
}
13 changes: 0 additions & 13 deletions evm-e2e/hardhat.config.js

This file was deleted.

23 changes: 23 additions & 0 deletions evm-e2e/hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { HardhatUserConfig } from "hardhat/config"
import "@nomicfoundation/hardhat-toolbox"
import "@typechain/hardhat"

const config: HardhatUserConfig = {
solidity: {
version: "0.8.24",
settings: {
optimizer: {
enabled: true,
runs: 100,
},
},
},
typechain: {
outDir: "types",
target: "ethers-v6",
alwaysGenerateOverloads: false,
dontOverrideCompile: false,
},
}

export default config
1 change: 1 addition & 0 deletions evm-e2e/justfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ setup:
install:
#!/usr/bin/env bash
npm install
(cd passkey-sdk && npm install)
npx hardhat typechain

# Runs the E2E tests
Expand Down
24 changes: 13 additions & 11 deletions evm-e2e/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion evm-e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"@nibiruchain/evm-core": "^0.0.7",
"@nibiruchain/solidity": "^0.0.4",
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
"@openzeppelin/contracts": "^5.1.0",
"@openzeppelin/contracts": "^4.9.6",
"@typechain/ethers-v6": "^0.5.1",
"@typechain/hardhat": "^9.1.0",
"@types/bun": "^1.2.5",
Expand All @@ -34,5 +34,11 @@
"ts-jest": "^29.2.4",
"typechain": "^8.3.2",
"typescript": "^5.8.2"
},
"scripts": {
"bundler": "bash scripts/start-bundler.sh"
},
"dependencies": {
"@account-abstraction/contracts": "^0.6.0"
}
}
Loading