feat: add market stats endpoint, wallet integration, and browser wallet link - Update devnet genesis timestamp to 1767000206 - Add market statistics endpoint with 24h volume, price change, and payment counts - Add wallet balance and info API endpoints in exchange router - Remove unused SessionDep dependencies from exchange endpoints - Integrate real AITBC wallet extension connection in trade-exchange UI - Add market data fetching with API fallback for price and volume display - Add cache-busting query
316 lines
9.7 KiB
JavaScript
316 lines
9.7 KiB
JavaScript
// Popup script for AITBC Wallet extension
|
|
let currentAccount = null;
|
|
let accounts = [];
|
|
|
|
// Load wallet data on popup open
|
|
document.addEventListener('DOMContentLoaded', async function() {
|
|
await loadWalletData();
|
|
updateUI();
|
|
|
|
// Check for pending connection request
|
|
const pending = await browser.storage.local.get(['pendingConnection']);
|
|
if (pending.pendingConnection) {
|
|
showConnectionDialog(pending.pendingConnection);
|
|
}
|
|
|
|
// Add event listeners
|
|
document.getElementById('createAccountBtn').addEventListener('click', createAccount);
|
|
document.getElementById('importAccountBtn').addEventListener('click', importAccount);
|
|
document.getElementById('sendTokensBtn').addEventListener('click', sendTokens);
|
|
document.getElementById('receiveTokensBtn').addEventListener('click', receiveTokens);
|
|
document.getElementById('viewOnExplorerBtn').addEventListener('click', viewOnExplorer);
|
|
document.getElementById('accountSelector').addEventListener('change', switchAccount);
|
|
});
|
|
|
|
// Load wallet data from storage
|
|
async function loadWalletData() {
|
|
const result = await browser.storage.local.get(['accounts', 'currentAccount']);
|
|
accounts = result.accounts || [];
|
|
currentAccount = result.currentAccount || null;
|
|
}
|
|
|
|
// Save wallet data to storage
|
|
async function saveWalletData() {
|
|
await browser.storage.local.set({
|
|
accounts: accounts,
|
|
currentAccount: currentAccount
|
|
});
|
|
}
|
|
|
|
// Update UI with current wallet state
|
|
function updateUI() {
|
|
const addressEl = document.getElementById('accountAddress');
|
|
const balanceEl = document.getElementById('balance');
|
|
const selectorEl = document.getElementById('accountSelector');
|
|
|
|
// Update account selector
|
|
selectorEl.innerHTML = '';
|
|
if (accounts.length === 0) {
|
|
const option = document.createElement('option');
|
|
option.value = '';
|
|
option.textContent = 'No accounts';
|
|
selectorEl.appendChild(option);
|
|
} else {
|
|
accounts.forEach((account, index) => {
|
|
const option = document.createElement('option');
|
|
option.value = index;
|
|
option.textContent = `Account ${index + 1} - ${account.address.substring(0, 20)}...`;
|
|
if (currentAccount && currentAccount.address === account.address) {
|
|
option.selected = true;
|
|
}
|
|
selectorEl.appendChild(option);
|
|
});
|
|
}
|
|
|
|
// Update current account display
|
|
if (currentAccount) {
|
|
addressEl.textContent = currentAccount.address;
|
|
balanceEl.textContent = `${currentAccount.balance || 0} AITBC`;
|
|
} else {
|
|
addressEl.textContent = 'Not connected';
|
|
balanceEl.textContent = '0 AITBC';
|
|
}
|
|
}
|
|
|
|
// Show connection dialog
|
|
function showConnectionDialog(pendingConnection) {
|
|
const dialog = document.createElement('div');
|
|
dialog.className = 'connection-dialog';
|
|
dialog.innerHTML = `
|
|
<div class="dialog-content">
|
|
<h3>Connection Request</h3>
|
|
<p>${pendingConnection.origin} wants to connect to your AITBC Wallet</p>
|
|
<p class="address">Address: ${pendingConnection.address}</p>
|
|
<div class="dialog-buttons">
|
|
<button id="approveConnection" class="approve-btn">Approve</button>
|
|
<button id="rejectConnection" class="reject-btn">Reject</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Add styles for the dialog
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
.connection-dialog {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: rgba(0, 0, 0, 0.8);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 1000;
|
|
}
|
|
.dialog-content {
|
|
background: white;
|
|
color: black;
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
max-width: 300px;
|
|
text-align: center;
|
|
}
|
|
.dialog-content h3 {
|
|
margin-top: 0;
|
|
}
|
|
.dialog-content .address {
|
|
font-family: monospace;
|
|
font-size: 12px;
|
|
word-break: break-all;
|
|
background: #f0f0f0;
|
|
padding: 5px;
|
|
border-radius: 4px;
|
|
margin: 10px 0;
|
|
}
|
|
.dialog-buttons {
|
|
display: flex;
|
|
gap: 10px;
|
|
justify-content: center;
|
|
margin-top: 20px;
|
|
}
|
|
.approve-btn {
|
|
background: #28a745;
|
|
color: white;
|
|
border: none;
|
|
padding: 8px 16px;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
}
|
|
.reject-btn {
|
|
background: #dc3545;
|
|
color: white;
|
|
border: none;
|
|
padding: 8px 16px;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
}
|
|
`;
|
|
|
|
document.head.appendChild(style);
|
|
document.body.appendChild(dialog);
|
|
|
|
// Handle button clicks
|
|
document.getElementById('approveConnection').addEventListener('click', async () => {
|
|
await browser.storage.local.set({
|
|
connectionResponse: {
|
|
id: pendingConnection.id,
|
|
approved: true
|
|
}
|
|
});
|
|
await browser.storage.local.remove(['pendingConnection']);
|
|
dialog.remove();
|
|
style.remove();
|
|
});
|
|
|
|
document.getElementById('rejectConnection').addEventListener('click', async () => {
|
|
await browser.storage.local.set({
|
|
connectionResponse: {
|
|
id: pendingConnection.id,
|
|
approved: false
|
|
}
|
|
});
|
|
await browser.storage.local.remove(['pendingConnection']);
|
|
dialog.remove();
|
|
style.remove();
|
|
});
|
|
}
|
|
|
|
// Switch to a different account
|
|
async function switchAccount() {
|
|
const selectorEl = document.getElementById('accountSelector');
|
|
const selectedIndex = parseInt(selectorEl.value);
|
|
|
|
if (isNaN(selectedIndex) || selectedIndex < 0 || selectedIndex >= accounts.length) {
|
|
return;
|
|
}
|
|
|
|
currentAccount = accounts[selectedIndex];
|
|
await saveWalletData();
|
|
updateUI();
|
|
}
|
|
|
|
// Create a new account
|
|
async function createAccount() {
|
|
// Generate a new private key and address
|
|
const privateKey = generatePrivateKey();
|
|
const address = await generateAddress(privateKey);
|
|
|
|
const newAccount = {
|
|
address: address,
|
|
privateKey: privateKey,
|
|
balance: 0,
|
|
created: new Date().toISOString()
|
|
};
|
|
|
|
accounts.push(newAccount);
|
|
currentAccount = newAccount;
|
|
await saveWalletData();
|
|
updateUI();
|
|
|
|
alert('New account created! Please save your private key securely.');
|
|
}
|
|
|
|
// Import account from private key
|
|
async function importAccount() {
|
|
const privateKey = prompt('Enter your private key:');
|
|
if (!privateKey) return;
|
|
|
|
try {
|
|
const address = await generateAddress(privateKey);
|
|
|
|
// Check if account already exists
|
|
const existing = accounts.find(a => a.address === address);
|
|
if (existing) {
|
|
currentAccount = existing;
|
|
} else {
|
|
currentAccount = {
|
|
address: address,
|
|
privateKey: privateKey,
|
|
balance: 0,
|
|
created: new Date().toISOString()
|
|
};
|
|
accounts.push(currentAccount);
|
|
}
|
|
|
|
await saveWalletData();
|
|
updateUI();
|
|
alert('Account imported successfully!');
|
|
} catch (error) {
|
|
alert('Invalid private key!');
|
|
}
|
|
}
|
|
|
|
// Send tokens
|
|
async function sendTokens() {
|
|
if (!currentAccount) {
|
|
alert('Please create or import an account first!');
|
|
return;
|
|
}
|
|
|
|
const to = prompt('Send to address:');
|
|
const amount = prompt('Amount:');
|
|
|
|
if (!to || !amount) return;
|
|
|
|
// In a real implementation, this would create and sign a transaction
|
|
alert(`Would send ${amount} AITBC to ${to}`);
|
|
}
|
|
|
|
// Receive tokens
|
|
function receiveTokens() {
|
|
if (!currentAccount) {
|
|
alert('Please create or import an account first!');
|
|
return;
|
|
}
|
|
|
|
alert(`Your receiving address:\n${currentAccount.address}`);
|
|
}
|
|
|
|
// View on explorer
|
|
function viewOnExplorer() {
|
|
if (!currentAccount) {
|
|
alert('Please create or import an account first!');
|
|
return;
|
|
}
|
|
|
|
browser.tabs.create({ url: `https://aitbc.bubuit.net/explorer/?address=${currentAccount.address}` });
|
|
}
|
|
|
|
// Generate a random private key (demo only)
|
|
function generatePrivateKey() {
|
|
const array = new Uint8Array(32);
|
|
crypto.getRandomValues(array);
|
|
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
|
|
}
|
|
|
|
// Generate address from private key (demo only)
|
|
async function generateAddress(privateKey) {
|
|
// In a real implementation, this would derive the address from the private key
|
|
// using the appropriate cryptographic algorithm
|
|
const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(privateKey));
|
|
return 'aitbc1' + Array.from(new Uint8Array(hash), b => b.toString(16).padStart(2, '0')).join('').substring(0, 40);
|
|
}
|
|
|
|
// Listen for connection requests from dApps
|
|
browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
if (request.method === 'connect') {
|
|
// Show connection dialog
|
|
const connected = confirm(`Allow this site to connect to your AITBC Wallet?`);
|
|
|
|
if (connected && currentAccount) {
|
|
sendResponse({
|
|
success: true,
|
|
address: currentAccount.address
|
|
});
|
|
} else {
|
|
sendResponse({
|
|
success: false,
|
|
error: 'User rejected connection'
|
|
});
|
|
}
|
|
}
|
|
|
|
return true; // Keep the message channel open for async response
|
|
});
|