Add launcher wallet balance and address utility actions

This commit is contained in:
Joshua 2026-02-18 20:59:01 -08:00
parent 18a0d6fe29
commit 96cbba87ca
3 changed files with 122 additions and 0 deletions

View File

@ -37,6 +37,7 @@ Top-level control surface:
6. Pull-first updates feed + support ticket action
7. Identity assurance visibility (`none` / `crypto_direct_unattested` / `sponsored_unattested` / `onramp_attested`)
8. Explicit operator-visible mode toggles (`Human mode` / `Auto mode`) synced to governance `operation_mode`
9. Wallet utility actions (`Refresh balances`, `Copy address`) with native + USDC balance visibility
Advanced integration controls (collapsible):

View File

@ -12,6 +12,8 @@ const state = {
walletSessionToken: "",
walletSessionExpiresAt: "",
walletSessionRefreshInFlight: null,
walletBalanceNative: "",
walletBalanceUSDC: "",
};
function nowISO() {
@ -77,6 +79,12 @@ function sessionSummary() {
return `active (exp ${state.walletSessionExpiresAt})`;
}
function walletBalanceSummary() {
const native = state.walletBalanceNative || "-- ETH";
const usdc = state.walletBalanceUSDC || "-- USDC";
return `${native} | ${usdc}`;
}
function clearWalletSession(reason, payload = {}) {
if (!state.walletSessionToken && !state.walletSessionExpiresAt) {
return;
@ -184,6 +192,41 @@ function utf8ToHex(value) {
.join("")}`;
}
function normalizeHexQuantity(value) {
const raw = String(value || "").trim().toLowerCase();
if (!raw.startsWith("0x")) {
return null;
}
return raw;
}
function formatUnitsHex(hexValue, decimals, precision = 6) {
const hex = normalizeHexQuantity(hexValue);
if (!hex) {
return null;
}
let bigint;
try {
bigint = BigInt(hex);
} catch {
return null;
}
const unit = 10n ** BigInt(Math.max(0, Number(decimals) || 0));
const whole = bigint / unit;
const fraction = bigint % unit;
if (fraction === 0n) {
return whole.toString();
}
const padded = fraction.toString().padStart(Number(decimals), "0");
const trimmed = padded.slice(0, Math.max(1, precision)).replace(/0+$/, "");
return trimmed ? `${whole.toString()}.${trimmed}` : whole.toString();
}
function encodeAddressWord(address) {
const normalized = normalizedAddress(address).replace(/^0x/, "");
return normalized.padStart(64, "0");
}
function logLine(label, payload) {
const log = $("log");
const line = `[${nowISO()}] ${label}\n${JSON.stringify(payload, null, 2)}\n\n`;
@ -216,6 +259,7 @@ function refreshActionLocks(statusPayload) {
function refreshOverview(statusPayload) {
const currentWallet = wallet();
setSummary("summaryWallet", currentWallet || "not connected");
setSummary("summaryFunds", walletBalanceSummary());
setSummary("summarySession", sessionSummary());
refreshModeUI();
if (statusPayload && typeof statusPayload === "object") {
@ -453,9 +497,74 @@ async function onConnectWallet() {
provider_chain_id: providerChainID,
});
}
try {
await onRefreshBalances();
} catch (err) {
logLine("wallet balance refresh warning", { error: String(err) });
}
refreshOverview();
}
async function onRefreshBalances() {
const address = normalizedAddress(requireWallet());
const provider = await requireProvider();
const nativeHex = await provider.request({
method: "eth_getBalance",
params: [address, "latest"],
});
const nativeDisplay = formatUnitsHex(nativeHex, 18, 6);
if (nativeDisplay) {
state.walletBalanceNative = `${nativeDisplay} ETH`;
}
const usdcToken = normalizedAddress($("usdcTokenAddress")?.value || "");
if (usdcToken && /^0x[a-f0-9]{40}$/.test(usdcToken)) {
const balanceOfSelector = "0x70a08231";
const decimalsSelector = "0x313ce567";
const balanceCallData = `${balanceOfSelector}${encodeAddressWord(address)}`;
const usdcHex = await provider.request({
method: "eth_call",
params: [{ to: usdcToken, data: balanceCallData }, "latest"],
});
let decimals = 6;
try {
const decimalsHex = await provider.request({
method: "eth_call",
params: [{ to: usdcToken, data: decimalsSelector }, "latest"],
});
const parsed = Number(BigInt(String(decimalsHex || "0x6")));
if (Number.isFinite(parsed) && parsed >= 0 && parsed <= 36) {
decimals = parsed;
}
} catch {
// keep default 6 when token decimals call is unavailable
}
const usdcDisplay = formatUnitsHex(usdcHex, decimals, 2);
if (usdcDisplay) {
state.walletBalanceUSDC = `${usdcDisplay} USDC`;
}
} else if (usdcToken) {
state.walletBalanceUSDC = "invalid token";
}
refreshOverview();
logLine("wallet balances refreshed", {
wallet: address,
native: state.walletBalanceNative || "",
usdc: state.walletBalanceUSDC || "",
});
}
async function onCopyWallet() {
const address = requireWallet();
if (!navigator?.clipboard?.writeText) {
throw new Error("clipboard API unavailable");
}
await navigator.clipboard.writeText(address);
setFlowStatus("wallet address copied");
logLine("wallet copied", { wallet: address });
}
async function onSignIntent() {
if (!state.lastIntent) {
throw new Error("create intent before signing");
@ -1119,6 +1228,8 @@ bind("btnQuickConnect", onQuickConnect);
bind("btnQuickActivate", onQuickActivate);
bind("btnQuickRefresh", onQuickRefresh);
bind("btnQuickInstallStatus", onQuickInstallStatus);
bind("btnRefreshBalances", onRefreshBalances);
bind("btnCopyWallet", onCopyWallet);
bind("btnModeHuman", onModeHuman);
bind("btnModeAuto", onModeAuto);
bind("btnConnectWallet", onConnectWallet);

View File

@ -20,6 +20,8 @@
<button id="btnQuickActivate">Activate membership</button>
<button id="btnQuickRefresh">Refresh status + feed</button>
<button id="btnQuickInstallStatus">Governance status</button>
<button id="btnRefreshBalances">Refresh balances</button>
<button id="btnCopyWallet">Copy address</button>
<button id="btnModeHuman" class="mode-active">Human mode</button>
<button id="btnModeAuto">Auto mode</button>
</div>
@ -33,6 +35,10 @@
<h3>Session</h3>
<p id="summarySession">none</p>
</article>
<article class="stat">
<h3>Wallet Funds</h3>
<p id="summaryFunds">-- ETH | -- USDC</p>
</article>
<article class="stat">
<h3>Membership</h3>
<p id="summaryMembership">unknown</p>
@ -96,6 +102,10 @@
Chain ID
<input id="chainId" type="number" value="84532" />
</label>
<label>
USDC token address
<input id="usdcTokenAddress" value="0x036cbd53842c5426634e7929541ec2318f3dcf7e" />
</label>
</div>
</section>