Skip to content

Commit e1ef0b7

Browse files
committed
feat: setup wallet config for signing
1 parent fafb0fd commit e1ef0b7

File tree

4 files changed

+474
-49
lines changed

4 files changed

+474
-49
lines changed
Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
<template>
2+
<div class="bg-indigo-50 p-4 rounded-lg border border-indigo-200 mb-4">
3+
<h2 class="text-lg font-semibold mb-3 text-indigo-800">
4+
Wallet Configuration
5+
</h2>
6+
<p class="text-sm text-gray-600 mb-4">
7+
Configure the funding source for account deployment and transactions.
8+
For automated tests, Anvil accounts are used by default.
9+
</p>
10+
11+
<div class="space-y-3">
12+
<!-- Network Detection -->
13+
<div class="p-3 bg-white rounded border border-indigo-200">
14+
<div class="text-sm">
15+
<strong>Detected Environment:</strong>
16+
<span
17+
:class="isAnvil ? 'text-green-600' : 'text-blue-600'"
18+
class="ml-2"
19+
>
20+
{{ isAnvil ? 'Anvil (Local Testnet)' : 'Custom Network' }}
21+
</span>
22+
</div>
23+
<div
24+
v-if="rpcUrl"
25+
class="text-xs text-gray-600 mt-1"
26+
>
27+
RPC: {{ rpcUrl }}
28+
</div>
29+
</div>
30+
31+
<!-- Wallet Source Selection -->
32+
<div>
33+
<label class="block text-sm font-medium mb-2">Funding Source:</label>
34+
<div class="space-y-2">
35+
<label class="flex items-center">
36+
<input
37+
v-model="config.source"
38+
type="radio"
39+
value="anvil"
40+
class="mr-2"
41+
:disabled="!isAnvil"
42+
>
43+
<span :class="!isAnvil ? 'text-gray-400' : ''">
44+
Anvil Test Account (Automated Testing)
45+
</span>
46+
</label>
47+
<label class="flex items-center">
48+
<input
49+
v-model="config.source"
50+
type="radio"
51+
value="private-key"
52+
class="mr-2"
53+
>
54+
<span>Private Key (Manual Entry)</span>
55+
</label>
56+
<label class="flex items-center">
57+
<input
58+
v-model="config.source"
59+
type="radio"
60+
value="browser-wallet"
61+
class="mr-2"
62+
>
63+
<span>Browser Wallet (MetaMask, etc.)</span>
64+
</label>
65+
</div>
66+
</div>
67+
68+
<!-- Anvil Account Selection -->
69+
<div
70+
v-if="config.source === 'anvil'"
71+
class="pl-6 border-l-2 border-indigo-300"
72+
>
73+
<label class="block text-sm font-medium mb-1">Anvil Account:</label>
74+
<select
75+
v-model="config.anvilAccountIndex"
76+
class="w-full px-3 py-2 border border-gray-300 rounded text-sm"
77+
>
78+
<option
79+
v-for="i in 10"
80+
:key="i - 1"
81+
:value="i - 1"
82+
>
83+
Account #{{ i - 1 }} ({{ anvilAddresses[i - 1] }})
84+
</option>
85+
</select>
86+
<p class="text-xs text-gray-500 mt-1">
87+
Select which Anvil test account to use for funding
88+
</p>
89+
</div>
90+
91+
<!-- Private Key Entry -->
92+
<div
93+
v-if="config.source === 'private-key'"
94+
class="pl-6 border-l-2 border-indigo-300"
95+
>
96+
<label class="block text-sm font-medium mb-1">Private Key:</label>
97+
<input
98+
v-model="config.privateKey"
99+
type="password"
100+
placeholder="0x..."
101+
class="w-full px-3 py-2 border border-gray-300 rounded text-sm font-mono"
102+
>
103+
<p class="text-xs text-gray-500 mt-1">
104+
Enter your private key (will not be stored)
105+
</p>
106+
<p class="text-xs text-red-600 mt-1">
107+
⚠️ Only use test accounts! Never enter mainnet private keys.
108+
</p>
109+
</div>
110+
111+
<!-- Browser Wallet Connection -->
112+
<div
113+
v-if="config.source === 'browser-wallet'"
114+
class="pl-6 border-l-2 border-indigo-300"
115+
>
116+
<button
117+
v-if="!connectedAddress"
118+
:disabled="connecting"
119+
class="w-full px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700 disabled:opacity-50"
120+
@click="connectBrowserWallet"
121+
>
122+
{{ connecting ? 'Connecting...' : 'Connect Wallet' }}
123+
</button>
124+
125+
<div
126+
v-else
127+
class="space-y-2"
128+
>
129+
<div class="p-3 bg-green-50 rounded border border-green-300">
130+
<div class="text-sm font-medium text-green-800">
131+
Connected
132+
</div>
133+
<code class="text-xs text-green-600 block mt-1">
134+
{{ connectedAddress }}
135+
</code>
136+
</div>
137+
<button
138+
class="w-full px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600"
139+
@click="disconnectWallet"
140+
>
141+
Disconnect
142+
</button>
143+
</div>
144+
145+
<p
146+
v-if="walletError"
147+
class="text-xs text-red-600 mt-2"
148+
>
149+
{{ walletError }}
150+
</p>
151+
</div>
152+
153+
<!-- Current Configuration Summary -->
154+
<div class="p-3 bg-white rounded border border-indigo-200">
155+
<div class="text-sm font-medium mb-2">
156+
Current Configuration:
157+
</div>
158+
<div class="text-xs space-y-1">
159+
<div>
160+
<strong>Source:</strong>
161+
{{
162+
config.source === 'anvil'
163+
? 'Anvil Test Account'
164+
: config.source === 'private-key'
165+
? 'Private Key'
166+
: 'Browser Wallet'
167+
}}
168+
</div>
169+
<div v-if="config.source === 'anvil'">
170+
<strong>Account:</strong> #{{ config.anvilAccountIndex }}
171+
</div>
172+
<div v-if="config.source === 'private-key' && config.privateKey">
173+
<strong>Private Key:</strong> {{ config.privateKey.substring(0, 10) }}...
174+
</div>
175+
<div v-if="config.source === 'browser-wallet' && connectedAddress">
176+
<strong>Address:</strong> {{ connectedAddress.substring(0, 10) }}...
177+
</div>
178+
<div
179+
v-if="isReady"
180+
class="text-green-600 mt-2"
181+
>
182+
✓ Ready to use
183+
</div>
184+
<div
185+
v-else
186+
class="text-orange-600 mt-2"
187+
>
188+
⚠ Configuration incomplete
189+
</div>
190+
</div>
191+
</div>
192+
</div>
193+
</div>
194+
</template>
195+
196+
<script setup>
197+
import { computed, ref, watch, onMounted } from "vue";
198+
199+
// Props
200+
const props = defineProps({
201+
modelValue: {
202+
type: Object,
203+
required: true,
204+
},
205+
});
206+
207+
// Emits
208+
const emit = defineEmits(["update:modelValue"]);
209+
210+
// Local state for two-way binding
211+
const config = computed({
212+
get: () => props.modelValue,
213+
set: (value) => emit("update:modelValue", value),
214+
});
215+
216+
// Wallet connection state
217+
const connecting = ref(false);
218+
const connectedAddress = ref("");
219+
const walletError = ref("");
220+
const rpcUrl = ref("");
221+
const isAnvil = ref(true);
222+
223+
// Anvil test account addresses
224+
const anvilAddresses = [
225+
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", // Account #0
226+
"0x70997970C51812dc3A010C7d01b50e0d17dc79C8", // Account #1
227+
"0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", // Account #2
228+
"0x90F79bf6EB2c4f870365E785982E1f101E93b906", // Account #3
229+
"0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65", // Account #4
230+
"0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", // Account #5
231+
"0x976EA74026E726554dB657fA54763abd0C3a0aa9", // Account #6
232+
"0x14dC79964da2C08b23698B3D3cc7Ca32193d9955", // Account #7
233+
"0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f", // Account #8
234+
"0xa0Ee7A142d267C1f36714E4a8F75612F20a79720", // Account #9
235+
];
236+
237+
// Check if configuration is ready to use
238+
const isReady = computed(() => {
239+
if (config.value.source === "anvil") {
240+
return isAnvil.value && config.value.anvilAccountIndex !== null;
241+
} else if (config.value.source === "private-key") {
242+
return config.value.privateKey && config.value.privateKey.startsWith("0x") && config.value.privateKey.length === 66;
243+
} else if (config.value.source === "browser-wallet") {
244+
return connectedAddress.value !== "";
245+
}
246+
return false;
247+
});
248+
249+
// Watch for changes to emit ready state
250+
watch(isReady, (ready) => {
251+
config.value.isReady = ready;
252+
if (config.value.source === "browser-wallet") {
253+
config.value.connectedAddress = connectedAddress.value;
254+
}
255+
});
256+
257+
/**
258+
* Connect to browser wallet (MetaMask, etc.)
259+
*/
260+
async function connectBrowserWallet() {
261+
connecting.value = true;
262+
walletError.value = "";
263+
264+
try {
265+
// Check if ethereum provider is available
266+
if (!window.ethereum) {
267+
throw new Error("No Ethereum wallet detected. Please install MetaMask or another Web3 wallet.");
268+
}
269+
270+
// Request account access
271+
const accounts = await window.ethereum.request({
272+
method: "eth_requestAccounts",
273+
});
274+
275+
if (accounts.length === 0) {
276+
throw new Error("No accounts found. Please unlock your wallet.");
277+
}
278+
279+
connectedAddress.value = accounts[0];
280+
281+
// Listen for account changes
282+
window.ethereum.on("accountsChanged", handleAccountsChanged);
283+
284+
// eslint-disable-next-line no-console
285+
console.log("Connected to wallet:", connectedAddress.value);
286+
} catch (err) {
287+
// eslint-disable-next-line no-console
288+
console.error("Failed to connect wallet:", err);
289+
walletError.value = err.message;
290+
} finally {
291+
connecting.value = false;
292+
}
293+
}
294+
295+
/**
296+
* Disconnect wallet
297+
*/
298+
function disconnectWallet() {
299+
connectedAddress.value = "";
300+
if (window.ethereum) {
301+
window.ethereum.removeListener("accountsChanged", handleAccountsChanged);
302+
}
303+
}
304+
305+
/**
306+
* Handle account changes from wallet
307+
*/
308+
function handleAccountsChanged(accounts) {
309+
if (accounts.length === 0) {
310+
// User disconnected wallet
311+
disconnectWallet();
312+
} else {
313+
connectedAddress.value = accounts[0];
314+
// eslint-disable-next-line no-console
315+
console.log("Wallet account changed:", connectedAddress.value);
316+
}
317+
}
318+
319+
/**
320+
* Detect network environment
321+
*/
322+
async function detectEnvironment() {
323+
try {
324+
const response = await fetch("/contracts.json");
325+
if (response.ok) {
326+
const contracts = await response.json();
327+
rpcUrl.value = contracts.rpcUrl || "";
328+
isAnvil.value = rpcUrl.value.includes("localhost:8545") || rpcUrl.value.includes("127.0.0.1:8545");
329+
330+
// Auto-select appropriate source
331+
if (isAnvil.value && config.value.source === "browser-wallet") {
332+
config.value.source = "anvil";
333+
} else if (!isAnvil.value && config.value.source === "anvil") {
334+
config.value.source = "browser-wallet";
335+
}
336+
}
337+
} catch (err) {
338+
// eslint-disable-next-line no-console
339+
console.warn("Could not load contracts.json:", err);
340+
}
341+
}
342+
343+
// Initialize on mount
344+
onMounted(() => {
345+
detectEnvironment();
346+
});
347+
</script>

0 commit comments

Comments
 (0)