Skip to content

Instantly share code, notes, and snippets.

@hydrogenbond007
Created November 30, 2025 09:13
Show Gist options
  • Select an option

  • Save hydrogenbond007/f983c621d035432beda03b1d5f51072f to your computer and use it in GitHub Desktop.

Select an option

Save hydrogenbond007/f983c621d035432beda03b1d5f51072f to your computer and use it in GitHub Desktop.
<!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