Created
November 30, 2025 09:13
-
-
Save hydrogenbond007/f983c621d035432beda03b1d5f51072f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Halo Privacy Token Interface</title> | |
| <script src="https://cdn.jsdelivr.net/npm/ethers@5.7.2/dist/ethers.umd.min.js"></script> | |
| <style> | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| max-width: 800px; | |
| margin: 50px auto; | |
| padding: 20px; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: #333; | |
| } | |
| .container { | |
| background: white; | |
| padding: 30px; | |
| border-radius: 15px; | |
| box-shadow: 0 10px 30px rgba(0,0,0,0.2); | |
| } | |
| h1 { | |
| color: #667eea; | |
| text-align: center; | |
| margin-bottom: 10px; | |
| } | |
| .subtitle { | |
| text-align: center; | |
| color: #666; | |
| margin-bottom: 30px; | |
| } | |
| .section { | |
| margin: 20px 0; | |
| padding: 20px; | |
| background: #f8f9fa; | |
| border-radius: 10px; | |
| border-left: 4px solid #667eea; | |
| } | |
| button { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| border: none; | |
| padding: 12px 24px; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| font-size: 16px; | |
| font-weight: bold; | |
| width: 100%; | |
| margin: 10px 0; | |
| transition: transform 0.2s; | |
| } | |
| button:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); | |
| } | |
| button:disabled { | |
| background: #ccc; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| input, select { | |
| width: 100%; | |
| padding: 12px; | |
| margin: 10px 0; | |
| border: 2px solid #e0e0e0; | |
| border-radius: 8px; | |
| font-size: 14px; | |
| box-sizing: border-box; | |
| } | |
| input:focus, select:focus { | |
| outline: none; | |
| border-color: #667eea; | |
| } | |
| .status { | |
| padding: 15px; | |
| margin: 10px 0; | |
| border-radius: 8px; | |
| font-weight: bold; | |
| } | |
| .success { | |
| background: #d4edda; | |
| color: #155724; | |
| border: 1px solid #c3e6cb; | |
| } | |
| .error { | |
| background: #f8d7da; | |
| color: #721c24; | |
| border: 1px solid #f5c6cb; | |
| } | |
| .info { | |
| background: #d1ecf1; | |
| color: #0c5460; | |
| border: 1px solid #bee5eb; | |
| } | |
| .warning { | |
| background: #fff3cd; | |
| color: #856404; | |
| border: 1px solid #ffeeba; | |
| } | |
| .balance { | |
| font-size: 24px; | |
| font-weight: bold; | |
| color: #667eea; | |
| text-align: center; | |
| margin: 20px 0; | |
| } | |
| .address { | |
| font-family: monospace; | |
| font-size: 12px; | |
| word-break: break-all; | |
| background: #e9ecef; | |
| padding: 8px; | |
| border-radius: 5px; | |
| } | |
| .hidden { | |
| display: none; | |
| } | |
| label { | |
| font-weight: bold; | |
| color: #555; | |
| display: block; | |
| margin-top: 15px; | |
| } | |
| .tx-link { | |
| color: #667eea; | |
| text-decoration: none; | |
| font-weight: bold; | |
| } | |
| .tx-link:hover { | |
| text-decoration: underline; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>🔐 Halo Privacy Token</h1> | |
| <div class="subtitle">Move tokens to private storage with TEE encryption</div> | |
| <!-- Connection Section --> | |
| <div class="section"> | |
| <h3>1️⃣ Connect Wallet</h3> | |
| <button id="connectBtn" onclick="connectWallet()">Connect MetaMask</button> | |
| <div id="walletInfo" class="hidden"> | |
| <p><strong>Connected:</strong></p> | |
| <div class="address" id="userAddress"></div> | |
| </div> | |
| </div> | |
| <!-- Balance Section --> | |
| <div class="section hidden" id="balanceSection"> | |
| <h3>2️⃣ Check Balances</h3> | |
| <button onclick="loadBalances()">Refresh Balances</button> | |
| <div id="balances"> | |
| <p><strong>Public Balance:</strong> <span id="publicBalance">-</span> tokens</p> | |
| <p><strong>Private Balance:</strong> <span id="privateBalance">-</span></p> | |
| </div> | |
| </div> | |
| <!-- Move to Private Section --> | |
| <div class="section hidden" id="privateSection"> | |
| <h3>3️⃣ Move to Private Storage</h3> | |
| <p class="info">This will encrypt your tokens using the TEE and store them privately on-chain.</p> | |
| <label>Amount to Move Private:</label> | |
| <input type="number" id="privateAmount" placeholder="e.g., 100" min="0" step="1"> | |
| <button onclick="moveToPrivate()">Move to Private Storage</button> | |
| </div> | |
| <!-- Private Transfer Section --> | |
| <div class="section hidden" id="transferSection"> | |
| <h3>4️⃣ Private Transfer</h3> | |
| <p class="info">Transfer privately encrypted tokens to another address.</p> | |
| <label>Recipient Address:</label> | |
| <input type="text" id="recipientAddress" placeholder="0x..."> | |
| <label>Amount:</label> | |
| <input type="number" id="transferAmount" placeholder="e.g., 50" min="0" step="1"> | |
| <button onclick="privateTransfer()">Send Private Transfer</button> | |
| </div> | |
| <!-- Status Messages --> | |
| <div id="statusMsg"></div> | |
| </div> | |
| <script> | |
| // Configuration | |
| const CONFIG = { | |
| rpcUrl: 'http://64.34.84.209:34141', | |
| chainId: 999999, | |
| contractAddress: '0x2210899f4Dd9944bF1b26836330aefEDD4050508', | |
| teeUrl: 'https://309216b1009d13f2ce79215e39c8bdd9974c6bd4-8080.dstack-pha-prod7.phala.network' | |
| }; | |
| // Contract ABI (simplified) | |
| const CONTRACT_ABI = [ | |
| "function balanceOf(address) view returns (uint256)", | |
| "function getPrivateBalance(address) view returns (bytes)", | |
| "function hasPrivateBalance(address) view returns (bool)", | |
| "function depositToPrivate(bytes encryptedBalance, uint64 timestamp, bytes signature)", | |
| "function privateTransfer(address to, bytes encryptedAmount, bytes signature)" | |
| ]; | |
| let provider, signer, contract, userAddress; | |
| // Connect Wallet | |
| async function connectWallet() { | |
| try { | |
| if (!window.ethereum) { | |
| showStatus('Please install MetaMask!', 'error'); | |
| return; | |
| } | |
| // Request account access | |
| await window.ethereum.request({ method: 'eth_requestAccounts' }); | |
| provider = new ethers.providers.Web3Provider(window.ethereum); | |
| signer = provider.getSigner(); | |
| userAddress = await signer.getAddress(); | |
| // Check network | |
| const network = await provider.getNetwork(); | |
| if (network.chainId !== CONFIG.chainId) { | |
| showStatus(`Please switch to Halo Privacy Testnet (Chain ID: ${CONFIG.chainId})`, 'warning'); | |
| return; | |
| } | |
| // Initialize contract | |
| contract = new ethers.Contract(CONFIG.contractAddress, CONTRACT_ABI, signer); | |
| // Update UI | |
| document.getElementById('userAddress').textContent = userAddress; | |
| document.getElementById('walletInfo').classList.remove('hidden'); | |
| document.getElementById('balanceSection').classList.remove('hidden'); | |
| document.getElementById('privateSection').classList.remove('hidden'); | |
| document.getElementById('transferSection').classList.remove('hidden'); | |
| document.getElementById('connectBtn').textContent = '✅ Connected'; | |
| document.getElementById('connectBtn').disabled = true; | |
| showStatus('Wallet connected successfully!', 'success'); | |
| // Load balances | |
| await loadBalances(); | |
| } catch (error) { | |
| showStatus('Error connecting wallet: ' + error.message, 'error'); | |
| } | |
| } | |
| // Load Balances | |
| async function loadBalances() { | |
| try { | |
| showStatus('Loading balances...', 'info'); | |
| // Get public balance | |
| const publicBal = await contract.balanceOf(userAddress); | |
| const publicBalFormatted = ethers.utils.formatEther(publicBal); | |
| document.getElementById('publicBalance').textContent = parseFloat(publicBalFormatted).toFixed(2); | |
| // Get private balance | |
| try { | |
| const privateBal = await contract.getPrivateBalance(userAddress); | |
| if (privateBal && privateBal !== '0x') { | |
| document.getElementById('privateBalance').textContent = | |
| privateBal.substring(0, 20) + '... (encrypted)'; | |
| } else { | |
| document.getElementById('privateBalance').textContent = 'Not set'; | |
| } | |
| } catch (e) { | |
| document.getElementById('privateBalance').textContent = 'Not set'; | |
| } | |
| showStatus('Balances loaded!', 'success'); | |
| } catch (error) { | |
| showStatus('Error loading balances: ' + error.message, 'error'); | |
| } | |
| } | |
| // Move to Private Storage | |
| async function moveToPrivate() { | |
| try { | |
| const amount = document.getElementById('privateAmount').value; | |
| if (!amount || parseFloat(amount) <= 0) { | |
| showStatus('Please enter a valid amount', 'error'); | |
| return; | |
| } | |
| showStatus('Step 1/3: Getting encryption from TEE...', 'info'); | |
| // Call TEE to get encrypted balance | |
| const teeResponse = await fetch(`${CONFIG.teeUrl}/initialize`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| token: CONFIG.contractAddress, | |
| user: userAddress, | |
| amount: amount + '.0' | |
| }) | |
| }); | |
| const teeData = await teeResponse.json(); | |
| if (!teeData.encrypted_balance || !teeData.signature) { | |
| throw new Error('TEE failed to encrypt: ' + JSON.stringify(teeData)); | |
| } | |
| showStatus('Step 2/3: TEE encrypted your balance. Sending transaction...', 'info'); | |
| // Call depositToPrivate | |
| const tx = await contract.depositToPrivate( | |
| '0x' + teeData.encrypted_balance, | |
| teeData.timestamp, | |
| '0x' + teeData.signature | |
| ); | |
| showStatus('Step 3/3: Waiting for confirmation...', 'info'); | |
| const receipt = await tx.wait(); | |
| showStatus( | |
| `✅ Success! ${amount} tokens moved to private storage. TX: ${receipt.transactionHash}`, | |
| 'success' | |
| ); | |
| // Refresh balances | |
| await loadBalances(); | |
| } catch (error) { | |
| showStatus('Error: ' + error.message, 'error'); | |
| console.error(error); | |
| } | |
| } | |
| // Private Transfer | |
| async function privateTransfer() { | |
| try { | |
| const recipient = document.getElementById('recipientAddress').value; | |
| const amount = document.getElementById('transferAmount').value; | |
| if (!recipient || !ethers.utils.isAddress(recipient)) { | |
| showStatus('Please enter a valid recipient address', 'error'); | |
| return; | |
| } | |
| if (!amount || parseFloat(amount) <= 0) { | |
| showStatus('Please enter a valid amount', 'error'); | |
| return; | |
| } | |
| showStatus('Step 1/3: Getting TEE encryption...', 'info'); | |
| // Get encrypted amount from TEE | |
| const teeResponse = await fetch(`${CONFIG.teeUrl}/initialize`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| token: CONFIG.contractAddress, | |
| user: recipient, | |
| amount: amount + '.0' | |
| }) | |
| }); | |
| const teeData = await teeResponse.json(); | |
| if (!teeData.encrypted_balance || !teeData.signature) { | |
| throw new Error('TEE failed to encrypt'); | |
| } | |
| showStatus('Step 2/3: Sending private transfer transaction...', 'info'); | |
| // Call privateTransfer | |
| const tx = await contract.privateTransfer( | |
| recipient, | |
| '0x' + teeData.encrypted_balance, | |
| '0x' + teeData.signature | |
| ); | |
| showStatus('Step 3/3: Waiting for confirmation...', 'info'); | |
| const receipt = await tx.wait(); | |
| showStatus( | |
| `✅ Private transfer successful! ${amount} tokens sent to ${recipient}. TX: ${receipt.transactionHash}`, | |
| 'success' | |
| ); | |
| // Refresh balances | |
| await loadBalances(); | |
| } catch (error) { | |
| showStatus('Error: ' + error.message, 'error'); | |
| console.error(error); | |
| } | |
| } | |
| // Show Status Message | |
| function showStatus(message, type) { | |
| const statusDiv = document.getElementById('statusMsg'); | |
| statusDiv.innerHTML = `<div class="status ${type}">${message}</div>`; | |
| setTimeout(() => { | |
| if (type === 'success' || type === 'info') { | |
| statusDiv.innerHTML = ''; | |
| } | |
| }, 10000); | |
| } | |
| // Handle account changes | |
| if (window.ethereum) { | |
| window.ethereum.on('accountsChanged', (accounts) => { | |
| if (accounts.length === 0) { | |
| location.reload(); | |
| } else { | |
| location.reload(); | |
| } | |
| }); | |
| window.ethereum.on('chainChanged', () => { | |
| location.reload(); | |
| }); | |
| } | |
| </script> | |
| </body> | |
| </html> | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment