-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathproposal.html
More file actions
258 lines (226 loc) · 13 KB
/
Copy pathproposal.html
File metadata and controls
258 lines (226 loc) · 13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Create Futarchy Proposal</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/ethers@5.7.2/dist/ethers.umd.min.js"></script>
<style>
/* Custom styles if needed, though Tailwind should cover most */
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</style>
</head>
<body class="bg-gray-100 text-gray-900 flex flex-col items-center min-h-screen p-4">
<div class="w-full max-w-2xl bg-white p-8 rounded-lg shadow-xl space-y-6">
<h1 class="text-3xl font-bold text-center text-indigo-600">Create Futarchy Proposal</h1>
<div class="space-y-2">
<button id="connectWallet" class="w-full bg-indigo-500 hover:bg-indigo-600 text-white font-semibold py-3 px-4 rounded-lg shadow transition duration-150 ease-in-out">Connect Wallet</button>
</div>
<div class="space-y-4">
<div>
<label for="contractAddress" class="block text-sm font-medium text-gray-700 mb-1">Contract Address</label>
<input id="contractAddress" value="0xe789e4A240d153AC55e32106821e785E71f6b792" class="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" placeholder="0x..." />
</div>
<div>
<label for="proposalName" class="block text-sm font-medium text-gray-700 mb-1">Proposal Name</label>
<input id="proposalName" class="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" placeholder="E.g., Will project X be completed by Y date?" />
</div>
<div>
<label for="companyToken" class="block text-sm font-medium text-gray-700 mb-1">Company Token Address</label>
<input id="companyToken" class="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" placeholder="0x... (Token representing shares/stake)" />
</div>
<div>
<label for="currencyToken" class="block text-sm font-medium text-gray-700 mb-1">Currency Token Address</label>
<input id="currencyToken" class="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" placeholder="0x... (Token for betting, e.g., xDAI, WETH)" />
</div>
<div>
<label for="proposalType" class="block text-sm font-medium text-gray-700 mb-1">Proposal Type</label>
<input id="proposalType" class="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" placeholder="e.g., milestone, feature, general" />
</div>
<div>
<label for="language" class="block text-sm font-medium text-gray-700 mb-1">Language</label>
<input id="language" value="en_US" class="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" placeholder="e.g., en_US" />
</div>
<div>
<label for="minBond" class="block text-sm font-medium text-gray-700 mb-1">Min Bond (in wei)</label>
<input id="minBond" value="10000000000000000000" type="text" class="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" placeholder="e.g., 10000000000000000000" />
</div>
<div>
<label for="openingTime" class="block text-sm font-medium text-gray-700 mb-1">Opening Time (Market Resolution)</label>
<input id="openingTime" type="datetime-local" class="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" />
</div>
</div>
<button id="submitProposal" class="w-full bg-green-500 hover:bg-green-600 text-white font-semibold py-3 px-4 rounded-lg shadow transition duration-150 ease-in-out" disabled>Submit Proposal</button>
<div id="status" class="mt-6 p-4 text-sm rounded-md"></div>
</div>
<script>
let provider, signer;
const statusDiv = document.getElementById('status');
const submitButton = document.getElementById('submitProposal');
const connectButton = document.getElementById('connectWallet');
function setStatus(message, type = 'info') {
statusDiv.innerHTML = message;
statusDiv.className = 'mt-6 p-4 text-sm rounded-md ';
if (type === 'error') {
statusDiv.classList.add('bg-red-100', 'text-red-700');
} else if (type === 'success') {
statusDiv.classList.add('bg-green-100', 'text-green-700');
} else {
statusDiv.classList.add('bg-blue-100', 'text-blue-700');
}
}
function setGnosiscanLink(txHash) {
statusDiv.innerHTML = `Proposal transaction submitted! <a href="https://gnosisscan.io/tx/${txHash}" target="_blank" rel="noopener noreferrer" class="font-medium text-indigo-600 hover:text-indigo-500 underline">View on Gnosiscan</a>. Waiting for confirmation...`;
statusDiv.className = 'mt-6 p-4 text-sm rounded-md bg-blue-100 text-blue-700';
}
document.addEventListener('DOMContentLoaded', () => {
submitButton.disabled = true;
connectButton.addEventListener('click', connectWallet);
});
async function connectWallet() {
console.log('Attempting to connect, window.ethereum:', window.ethereum);
const gnosisChainId = '0x64'; // 100 in decimal
const gnosisChainName = 'Gnosis Chain';
const gnosisRpcUrl = 'https://rpc.gnosischain.com';
const gnosisExplorerUrl = 'https://gnosisscan.io';
const gnosisNativeCurrency = { name: 'xDAI', symbol: 'xDAI', decimals: 18 };
if (window.ethereum) {
try {
provider = new ethers.providers.Web3Provider(window.ethereum);
await provider.send("eth_requestAccounts", []);
signer = provider.getSigner();
const address = await signer.getAddress();
const network = await provider.getNetwork();
const chainId = network.chainId;
if (chainId !== parseInt(gnosisChainId, 16)) {
setStatus(`Please switch to ${gnosisChainName}. Current network ID: ${chainId}`, 'info');
try {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: gnosisChainId }],
});
// Re-initialize provider and signer after network switch
provider = new ethers.providers.Web3Provider(window.ethereum);
signer = provider.getSigner();
// Verify network again
const newNetwork = await provider.getNetwork();
if (newNetwork.chainId !== parseInt(gnosisChainId, 16)) {
throw new Error(`Failed to switch to ${gnosisChainName}.`);
}
setStatus(`Switched to ${gnosisChainName}. Wallet: ${address}`, 'success');
} catch (switchError) {
// This error code indicates that the chain has not been added to MetaMask.
if (switchError.code === 4902) {
setStatus(`Looks like ${gnosisChainName} is not added to your MetaMask. Attempting to add...`, 'info');
try {
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [{
chainId: gnosisChainId,
chainName: gnosisChainName,
rpcUrls: [gnosisRpcUrl],
nativeCurrency: gnosisNativeCurrency,
blockExplorerUrls: [gnosisExplorerUrl],
}],
});
// Re-initialize provider and signer after adding and potentially switching
provider = new ethers.providers.Web3Provider(window.ethereum);
signer = provider.getSigner();
const newNetwork = await provider.getNetwork();
if (newNetwork.chainId !== parseInt(gnosisChainId, 16)) {
// User might have rejected the switch after adding
throw new Error(`Please manually switch to ${gnosisChainName} in MetaMask.`);
}
setStatus(`${gnosisChainName} added. Wallet: ${address}`, 'success');
} catch (addError) {
console.error('Failed to add Gnosis Chain:', addError);
setStatus(`Failed to add or switch to ${gnosisChainName}: ${addError.message || addError}`, 'error');
submitButton.disabled = true;
return;
}
} else {
console.error('Failed to switch network:', switchError);
setStatus(`Failed to switch network: ${switchError.message || switchError}`, 'error');
submitButton.disabled = true;
return;
}
}
} else {
setStatus('Wallet connected on ' + gnosisChainName + ': ' + address, 'success');
}
connectButton.textContent = 'Wallet Connected: ' + address.substring(0,6) + '...' + address.substring(address.length - 4);
connectButton.classList.remove('bg-indigo-500', 'hover:bg-indigo-600');
connectButton.classList.add('bg-gray-400', 'cursor-not-allowed');
connectButton.disabled = true;
submitButton.disabled = false;
} catch (e) {
console.error(e);
setStatus('Wallet connection process failed: ' + (e.message || e), 'error');
submitButton.disabled = true;
}
} else {
setStatus('MetaMask not detected. Please ensure it is installed and you are accessing this page via a local web server (http://localhost), not a file:/// URL.', 'error');
submitButton.disabled = true;
}
}
submitButton.addEventListener('click', async () => {
if (!signer) {
setStatus('Please connect your wallet first.', 'error');
return;
}
const contractAddress = document.getElementById('contractAddress').value.trim();
const proposalName = document.getElementById('proposalName').value.trim();
const companyToken = document.getElementById('companyToken').value.trim();
const currencyToken = document.getElementById('currencyToken').value.trim();
const proposalType = document.getElementById('proposalType').value.trim();
const language = document.getElementById('language').value.trim();
const minBond = document.getElementById('minBond').value.trim();
const openingTimeStr = document.getElementById('openingTime').value;
if (!contractAddress || !proposalName || !companyToken || !currencyToken || !proposalType || !language || !minBond || !openingTimeStr) {
setStatus('Please fill in all fields.', 'error');
return;
}
let openingTimeUnix;
try {
openingTimeUnix = Math.floor(new Date(openingTimeStr).getTime() / 1000);
if (isNaN(openingTimeUnix) || openingTimeUnix <= 0) {
throw new Error('Invalid date/time selected for opening time.');
}
} catch (e) {
setStatus('Invalid Opening Time format. Please select a valid date and time.', 'error');
return;
}
const abi = [
"function createProposal((string,address,address,string,string,uint256,uint32)) returns (uint256)"
];
const contract = new ethers.Contract(contractAddress, abi, signer);
try {
setStatus('Preparing transaction...', 'info');
submitButton.disabled = true;
submitButton.textContent = 'Submitting...';
const proposalData = [proposalName, companyToken, currencyToken, proposalType, language, ethers.BigNumber.from(minBond), openingTimeUnix];
console.log('Sending proposal data:', proposalData);
const tx = await contract.createProposal(proposalData);
setGnosiscanLink(tx.hash);
await tx.wait();
setStatus(`Proposal created successfully! Tx: ${tx.hash}. <a href="https://gnosisscan.io/tx/${tx.hash}" target="_blank" rel="noopener noreferrer" class="font-medium text-indigo-600 hover:text-indigo-500 underline">View on Gnosiscan</a>`, 'success');
} catch (e) {
console.error(e);
let errorMessage = 'Error creating proposal: ' + (e.reason || e.message || e);
if (e.data && e.data.message) { // More detailed revert reason from provider
errorMessage += ` (${e.data.message})`;
}
setStatus(errorMessage, 'error');
} finally {
submitButton.disabled = false;
submitButton.textContent = 'Submit Proposal';
}
});
</script>
</body>
</html>