Skip to content

Commit 1f5bcf6

Browse files
committed
refactor: extract browser wallet into dedicated crate
1 parent 9322228 commit 1f5bcf6

40 files changed

+4109
-1261
lines changed

Cargo.lock

Lines changed: 190 additions & 74 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ members = [
2424
"crates/macros/",
2525
"crates/test-utils/",
2626
"crates/lint/",
27+
"crates/wallets/",
28+
"crates/browser-wallet/",
2729
]
2830
resolver = "2"
2931

@@ -200,6 +202,7 @@ foundry-evm-traces = { path = "crates/evm/traces" }
200202
foundry-macros = { path = "crates/macros" }
201203
foundry-test-utils = { path = "crates/test-utils" }
202204
foundry-wallets = { path = "crates/wallets" }
205+
foundry-browser-wallet = { path = "crates/browser-wallet" }
203206
foundry-linking = { path = "crates/linking" }
204207

205208
# solc & compilation utilities

crates/browser-wallet/Cargo.toml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
[package]
2+
name = "foundry-browser-wallet"
3+
version.workspace = true
4+
edition.workspace = true
5+
license.workspace = true
6+
homepage.workspace = true
7+
repository.workspace = true
8+
exclude.workspace = true
9+
10+
[lints]
11+
workspace = true
12+
13+
[dependencies]
14+
alloy-primitives.workspace = true
15+
alloy-signer = { workspace = true, features = ["eip712"] }
16+
alloy-rpc-types.workspace = true
17+
alloy-consensus.workspace = true
18+
alloy-network.workspace = true
19+
alloy-sol-types.workspace = true
20+
alloy-dyn-abi.workspace = true
21+
foundry-common.workspace = true
22+
23+
axum.workspace = true
24+
tokio = { workspace = true, features = ["net", "rt-multi-thread", "macros"] }
25+
tower.workspace = true
26+
tower-http = { workspace = true, features = ["cors", "set-header"] }
27+
uuid = { workspace = true, features = ["v4", "serde"] }
28+
hex = "0.4"
29+
parking_lot.workspace = true
30+
serde = { workspace = true, features = ["derive"] }
31+
serde_json.workspace = true
32+
thiserror.workspace = true
33+
async-trait.workspace = true
34+
tracing.workspace = true
35+
webbrowser = "1.0"
36+
37+
[dev-dependencies]
38+
tokio = { workspace = true, features = ["test-util"] }
39+
reqwest = { workspace = true, features = ["json"] }
40+
alloy-signer-local.workspace = true
41+
alloy-provider.workspace = true

crates/browser-wallet/README.md

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
# Browser Wallet
2+
3+
Browser wallet integration for Foundry tools, enabling interaction with MetaMask and other browser-based wallets.
4+
5+
## Overview
6+
7+
This crate provides a bridge between Foundry CLI tools and browser wallets using a local HTTP server. It implements support for:
8+
- Transaction sending via `cast send --browser`
9+
- Contract deployment via `forge create --browser`
10+
- Message signing (personal_sign and eth_signTypedData_v4) via `cast wallet sign --browser`
11+
12+
## Architecture
13+
14+
The browser wallet integration follows this flow:
15+
16+
1. CLI starts a local HTTP server on a random port
17+
2. Opens the user's browser to `http://localhost:PORT`
18+
3. Web interface connects to the browser wallet (e.g., MetaMask)
19+
4. CLI queues requests (transactions/signatures) for browser processing
20+
5. Browser polls for pending requests and prompts user for approval
21+
6. Results are returned to CLI via the HTTP API
22+
23+
## HTTP API Reference
24+
25+
All API endpoints follow JSON-RPC 2.0 conventions and are served from `http://localhost:PORT`.
26+
27+
### GET `/api/heartbeat`
28+
Health check endpoint to verify server is running.
29+
30+
**Response:**
31+
```json
32+
{
33+
"success": true,
34+
"data": {
35+
"status": "ok",
36+
"connected": true,
37+
"address": "0x1234..."
38+
}
39+
}
40+
```
41+
42+
### GET `/api/transaction/pending`
43+
Retrieve the next pending transaction for user approval.
44+
45+
**Response:**
46+
```json
47+
{
48+
"success": true,
49+
"data": {
50+
"id": "uuid-v4",
51+
"from": "0x1234...",
52+
"to": "0x5678...",
53+
"value": "0x1000000000000000",
54+
"data": "0x...",
55+
"chainId": "0x1"
56+
}
57+
}
58+
```
59+
60+
### POST `/api/transaction/response`
61+
Submit transaction approval/rejection result.
62+
63+
**Request:**
64+
```json
65+
{
66+
"id": "uuid-v4",
67+
"hash": "0xabcd...",
68+
"error": null
69+
}
70+
```
71+
72+
**Response:**
73+
```json
74+
{
75+
"success": true
76+
}
77+
```
78+
79+
### GET `/api/sign/pending`
80+
Retrieve pending message signing request.
81+
82+
**Response:**
83+
```json
84+
{
85+
"success": true,
86+
"data": {
87+
"id": "uuid-v4",
88+
"message": "Hello World",
89+
"address": "0x1234...",
90+
"type": "personal_sign"
91+
}
92+
}
93+
```
94+
95+
### POST `/api/sign/response`
96+
Submit message signing result.
97+
98+
**Request:**
99+
```json
100+
{
101+
"id": "uuid-v4",
102+
"signature": "0xabcd...",
103+
"error": null
104+
}
105+
```
106+
107+
**Response:**
108+
```json
109+
{
110+
"success": true
111+
}
112+
```
113+
114+
### GET `/api/network`
115+
Get current network configuration.
116+
117+
**Response:**
118+
```json
119+
{
120+
"success": true,
121+
"data": {
122+
"chainId": 1,
123+
"name": "mainnet"
124+
}
125+
}
126+
```
127+
128+
### POST `/api/account`
129+
Update connected wallet account status.
130+
131+
**Request:**
132+
```json
133+
{
134+
"address": "0x1234...",
135+
"chainId": 1
136+
}
137+
```
138+
139+
**Response:**
140+
```json
141+
{
142+
"success": true
143+
}
144+
```
145+
146+
## Message Types
147+
148+
### Transaction Request (`BrowserTransaction`)
149+
```rust
150+
{
151+
id: String, // Unique transaction ID
152+
from: Address, // Sender address
153+
to: Option<Address>, // Recipient (None for contract creation)
154+
value: U256, // ETH value to send
155+
data: Bytes, // Transaction data
156+
chainId: ChainId, // Network chain ID
157+
}
158+
```
159+
160+
### Sign Request (`SignRequest`)
161+
```rust
162+
{
163+
id: String, // Unique request ID
164+
message: String, // Message to sign
165+
address: Address, // Address to sign with
166+
type: SignType, // "personal_sign" or "sign_typed_data"
167+
}
168+
```
169+
170+
### Sign Type (`SignType`)
171+
- `personal_sign`: Standard message signing
172+
- `sign_typed_data`: EIP-712 typed data signing
173+
174+
## JavaScript API
175+
176+
The web interface uses the standard EIP-1193 provider interface:
177+
178+
```javascript
179+
// Connect wallet
180+
await window.ethereum.request({ method: 'eth_requestAccounts' });
181+
182+
// Send transaction
183+
const hash = await window.ethereum.request({
184+
method: 'eth_sendTransaction',
185+
params: [transaction]
186+
});
187+
188+
// Sign message
189+
const signature = await window.ethereum.request({
190+
method: 'personal_sign',
191+
params: [message, address]
192+
});
193+
194+
// Sign typed data
195+
const signature = await window.ethereum.request({
196+
method: 'eth_signTypedData_v4',
197+
params: [address, typedData]
198+
});
199+
```
200+
201+
## Security
202+
203+
- Server only accepts connections from localhost
204+
- Content Security Policy headers prevent XSS attacks
205+
- No sensitive data is stored; all operations are transient
206+
- Automatic timeout after 5 minutes of inactivity
207+
208+
## Standards Compliance
209+
210+
- [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193): Ethereum Provider JavaScript API
211+
- [EIP-712](https://eips.ethereum.org/EIPS/eip-712): Typed structured data hashing and signing
212+
- [JSON-RPC 2.0](https://www.jsonrpc.org/specification): Communication protocol
213+
214+
## Contributing
215+
216+
When adding new functionality:
217+
1. Update message types in `lib.rs`
218+
2. Add corresponding HTTP endpoints in `server.rs`
219+
3. Update JavaScript handlers in `assets/web/js/`
220+
4. Add integration tests in `tests/integration/`

crates/wallets/src/browser/assets/web/css/styles.css renamed to crates/browser-wallet/src/assets/web/css/styles.css

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,21 @@ h3 {
4949
font-size: 0.875rem;
5050
}
5151

52+
/* Loading state animation */
53+
@keyframes pulse {
54+
0%, 100% {
55+
opacity: 1;
56+
}
57+
50% {
58+
opacity: 0.5;
59+
}
60+
}
61+
62+
.status-value.initializing {
63+
animation: pulse 1.5s ease-in-out infinite;
64+
color: #60a5fa;
65+
}
66+
5267
.status {
5368
background: #27272a;
5469
border-radius: 8px;
@@ -249,4 +264,16 @@ h3 {
249264
color: #71717a;
250265
font-size: 0.75rem;
251266
margin-top: 2rem;
267+
}
268+
269+
.security-warning {
270+
background: #f59e0b1a;
271+
border: 1px solid #f59e0b;
272+
color: #f59e0b;
273+
padding: 1rem;
274+
border-radius: 8px;
275+
text-align: center;
276+
font-size: 0.875rem;
277+
margin-bottom: 1rem;
278+
font-weight: 500;
252279
}

crates/wallets/src/browser/assets/web/index.html renamed to crates/browser-wallet/src/assets/web/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ <h1>⚒️ Foundry Browser Wallet</h1>
1717
<div id="status-container" class="status">
1818
<div class="status-row">
1919
<span class="status-label">Status:</span>
20-
<span id="connection-status" class="status-value">Checking...</span>
20+
<span id="connection-status" class="status-value">Initializing...</span>
2121
</div>
2222
<div class="status-row">
2323
<span class="status-label">Account:</span>

0 commit comments

Comments
 (0)