App.renderWallet = function() {
var self = this;
return Promise.all([
api('/api/wallet/balance').catch(function() { return { balance: '0', currency: 'ETH', tokens: [], network: 'Unknown' }; }),
api('/api/wallet/address').catch(function() { return { address: '', chain_id: 1, network: 'Unknown' }; }),
api('/api/stats/transactions?hours=168').catch(function() { return { transactions: [] }; }),
api('/api/services/swaps?limit=10').catch(function() { return { swap_tasks: [] }; }),
api('/api/services/tax-payouts?limit=10').catch(function() { return { tax_tasks: [] }; })
]).then(function(arr) {
var bal = arr[0], addr = arr[1], txs = (arr[2].transactions || []), swaps = (arr[3].swap_tasks || []), taxTasks = (arr[4].tax_tasks || []);
var addrStr = (addr.address != null && addr.address !== '') ? addr.address : (addr.note || '\u2014');
var network = bal.network || addr.network || 'Unknown';
var chainId = addr.chain_id || bal.chain_id || 1;
var tokens = bal.tokens || [];
txs = txs.filter(function(t) { return t.amount && t.amount !== 0; });
var txRows = txs.map(function(t) {
var meta = {};
try { if (t.metadata_json) meta = JSON.parse(t.metadata_json); } catch(e) {}
var label = t.tx_type || '';
if (meta.category) label += ' \u00b7 ' + meta.category;
var amtStr = (typeof t.amount === 'number' ? t.amount.toFixed(2) : t.amount) + ' ' + (t.currency || '');
var dateStr = t.created_at || '';
if (dateStr.length > 19) dateStr = dateStr.substring(0, 19).replace('T', ' ');
return '<tr><td>' + esc(label) + '</td><td style="font-family:var(--mono)">' + esc(amtStr) + '</td><td style="color:var(--muted)">' + esc(dateStr) + '</td></tr>';
}).join('');
// Network & address card
var networkBadge = '<span class="badge success" style="font-size:0.625rem;vertical-align:middle">' + esc(network) + '</span>';
var addrSection = '<div class="card" style="margin-bottom:1rem"><div class="card-title">Wallet ' + networkBadge + '</div><div style="font-size:0.6875rem;color:var(--muted);margin-bottom:0.5rem">Chain ID: ' + chainId + '</div><div class="card-mono" style="word-break:break-all" id="wallet-addr">' + esc(addrStr) + '</div><div style="display:flex;gap:0.5rem;margin-top:8px"><button class="btn secondary" id="copy-address" style="font-size:0.75rem;padding:0.3rem 0.75rem">Copy</button></div><p style="font-size:0.6875rem;color:var(--muted);margin-top:0.5rem">Wallet settings are immutable at runtime. Update `roboticus.toml` and restart to change address or chain.</p></div>';
// Token balances card
var tokenRows = '';
if (tokens.length > 0) {
tokens.forEach(function(t) {
var icon = self._TOKEN_ICONS[t.symbol] || '\u25cb';
var bal = t.balance || 0;
var formatted = t.formatted || (bal === 0 ? '0' : String(bal));
var nativeBadge = t.is_native ? ' <span class="badge" style="font-size:0.5rem;padding:1px 4px;opacity:0.6">gas</span>' : '';
var contractLink = '';
if (t.contract) {
var explorerUrl = self._explorerUrl(chainId, t.contract);
contractLink = ' <a href="' + explorerUrl + '" target="_blank" rel="noopener" style="font-size:0.5625rem;color:var(--accent);text-decoration:none" title="' + esc(t.contract) + '">\u2197</a>';
}
var dimClass = bal === 0 ? ' style="opacity:0.4"' : '';
tokenRows += '<div class="settings-row"' + dimClass + '><div style="display:flex;align-items:center;gap:0.5rem;min-width:120px"><span style="font-size:1.1rem;width:1.5rem;text-align:center">' + icon + '</span><div><div style="font-weight:600;font-size:0.8125rem">' + esc(t.symbol) + nativeBadge + contractLink + '</div><div style="font-size:0.625rem;color:var(--muted)">' + esc(t.name) + '</div></div></div><div style="text-align:right;font-family:var(--font-mono);font-size:0.875rem;font-weight:500">' + esc(formatted) + '</div></div>';
});
} else {
tokenRows = '<div style="color:var(--muted);font-size:0.75rem;padding:0.5rem 0">No token data available</div>';
}
var balancesCard = '<div class="card" style="margin-bottom:1rem"><div class="card-title">Balances</div>' + tokenRows + '</div>';
// Treasury card
var treasury = bal.treasury || {};
var treasuryCard = '<div class="card" style="margin-bottom:1rem"><div class="card-title">Treasury Policy</div>' +
'<div class="settings-row"><div class="settings-label">Per-payment cap</div><div style="font-family:var(--font-mono)">$' + (treasury.per_payment_cap || 0).toFixed(2) + '</div></div>' +
'<div style="font-size:0.75rem;color:var(--muted);padding:0 0 0.5rem 0">Maximum amount deducted per single service call or tool execution</div>' +
'<div class="settings-row"><div class="settings-label">Daily inference budget</div><div style="font-family:var(--font-mono)">$' + (treasury.daily_inference_budget || 0).toFixed(2) + '</div></div>' +
'<div style="font-size:0.75rem;color:var(--muted);padding:0 0 0.5rem 0">Total daily spend limit across all LLM inference calls</div>' +
'<div class="settings-row"><div class="settings-label">Minimum reserve</div><div style="font-family:var(--font-mono)">$' + (treasury.minimum_reserve || 0).toFixed(2) + '</div></div>' +
'<div style="font-size:0.75rem;color:var(--muted);padding:0 0 0.5rem 0">Balance threshold below which the agent pauses spending</div>' +
'</div>';
var revenueSwapCard = self._renderRevenueSwapSummaryCard(treasury.revenue_swap || {});
var seedReadinessCard = self._renderSeedExerciseReadinessCard(bal.seed_exercise_readiness || {});
var seedProgressCard = self._renderSeedExerciseProgressCard(bal.seed_exercise_progress || {});
var seedPlanCard = self._renderSeedExercisePlanCard(bal.seed_exercise_plan || {});
var strategySummaryCard = self._renderRevenueStrategySummaryCard(bal.revenue_strategy_summary || []);
var feedbackSummaryCard = self._renderRevenueFeedbackSummaryCard(bal.revenue_feedback_summary || []);
var swapTasksCard = self._renderRevenueSwapTasksCard(swaps);
var taxTasksCard = self._renderRevenueTaxTasksCard(taxTasks);
var guidanceBanner = '<div class="card" style="margin-bottom:1rem;background:linear-gradient(135deg,var(--surface) 0%,rgba(96,165,250,0.08) 100%);border-left:3px solid var(--accent)">'
+ '<div style="font-weight:600;margin-bottom:0.5rem">Wallet Overview</div>'
+ '<div style="font-size:0.8125rem;color:var(--muted);line-height:1.5">'
+ 'This page shows your agent\'s treasury balance and transaction history. '
+ 'The wallet is used for on-chain operations and service payments. '
+ '<strong>Balance</strong> reflects current holdings across configured networks. '
+ '<strong>Transactions</strong> show recent on-chain activity. '
+ 'To adjust spending limits, visit <a href="#settings" data-page="settings" style="color:var(--accent)">Settings</a> \u2192 Wallet section.'
+ '</div></div>';
return guidanceBanner + addrSection + balancesCard + treasuryCard + revenueSwapCard + seedReadinessCard + seedProgressCard + seedPlanCard + strategySummaryCard + feedbackSummaryCard + swapTasksCard + taxTasksCard +
'<div class="card-title">Transaction history</div><div class="table-wrap"><table><thead><tr><th>Type</th><th>Amount</th><th>Date</th></tr></thead><tbody>' + txRows + '</tbody></table></div>';
});
};
App._explorerUrl = function(chainId, contract) {
var base = { 1: 'https://etherscan.io', 8453: 'https://basescan.org', 42161: 'https://arbiscan.io', 10: 'https://optimistic.etherscan.io', 137: 'https://polygonscan.com' };
return (base[chainId] || 'https://basescan.org') + '/token/' + contract;
};
App._explorerUrl = function(chainId, contract) {
var base = { 1: 'https://etherscan.io', 8453: 'https://basescan.org', 42161: 'https://arbiscan.io', 10: 'https://optimistic.etherscan.io', 137: 'https://polygonscan.com' };
return (base[chainId] || 'https://basescan.org') + '/token/' + contract;
};