Add launcher wallet balance and address utility actions
This commit is contained in:
parent
18a0d6fe29
commit
96cbba87ca
@ -37,6 +37,7 @@ Top-level control surface:
|
|||||||
6. Pull-first updates feed + support ticket action
|
6. Pull-first updates feed + support ticket action
|
||||||
7. Identity assurance visibility (`none` / `crypto_direct_unattested` / `sponsored_unattested` / `onramp_attested`)
|
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`
|
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):
|
Advanced integration controls (collapsible):
|
||||||
|
|
||||||
|
|||||||
111
app/app.js
111
app/app.js
@ -12,6 +12,8 @@ const state = {
|
|||||||
walletSessionToken: "",
|
walletSessionToken: "",
|
||||||
walletSessionExpiresAt: "",
|
walletSessionExpiresAt: "",
|
||||||
walletSessionRefreshInFlight: null,
|
walletSessionRefreshInFlight: null,
|
||||||
|
walletBalanceNative: "",
|
||||||
|
walletBalanceUSDC: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
function nowISO() {
|
function nowISO() {
|
||||||
@ -77,6 +79,12 @@ function sessionSummary() {
|
|||||||
return `active (exp ${state.walletSessionExpiresAt})`;
|
return `active (exp ${state.walletSessionExpiresAt})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function walletBalanceSummary() {
|
||||||
|
const native = state.walletBalanceNative || "-- ETH";
|
||||||
|
const usdc = state.walletBalanceUSDC || "-- USDC";
|
||||||
|
return `${native} | ${usdc}`;
|
||||||
|
}
|
||||||
|
|
||||||
function clearWalletSession(reason, payload = {}) {
|
function clearWalletSession(reason, payload = {}) {
|
||||||
if (!state.walletSessionToken && !state.walletSessionExpiresAt) {
|
if (!state.walletSessionToken && !state.walletSessionExpiresAt) {
|
||||||
return;
|
return;
|
||||||
@ -184,6 +192,41 @@ function utf8ToHex(value) {
|
|||||||
.join("")}`;
|
.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) {
|
function logLine(label, payload) {
|
||||||
const log = $("log");
|
const log = $("log");
|
||||||
const line = `[${nowISO()}] ${label}\n${JSON.stringify(payload, null, 2)}\n\n`;
|
const line = `[${nowISO()}] ${label}\n${JSON.stringify(payload, null, 2)}\n\n`;
|
||||||
@ -216,6 +259,7 @@ function refreshActionLocks(statusPayload) {
|
|||||||
function refreshOverview(statusPayload) {
|
function refreshOverview(statusPayload) {
|
||||||
const currentWallet = wallet();
|
const currentWallet = wallet();
|
||||||
setSummary("summaryWallet", currentWallet || "not connected");
|
setSummary("summaryWallet", currentWallet || "not connected");
|
||||||
|
setSummary("summaryFunds", walletBalanceSummary());
|
||||||
setSummary("summarySession", sessionSummary());
|
setSummary("summarySession", sessionSummary());
|
||||||
refreshModeUI();
|
refreshModeUI();
|
||||||
if (statusPayload && typeof statusPayload === "object") {
|
if (statusPayload && typeof statusPayload === "object") {
|
||||||
@ -453,9 +497,74 @@ async function onConnectWallet() {
|
|||||||
provider_chain_id: providerChainID,
|
provider_chain_id: providerChainID,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
await onRefreshBalances();
|
||||||
|
} catch (err) {
|
||||||
|
logLine("wallet balance refresh warning", { error: String(err) });
|
||||||
|
}
|
||||||
refreshOverview();
|
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() {
|
async function onSignIntent() {
|
||||||
if (!state.lastIntent) {
|
if (!state.lastIntent) {
|
||||||
throw new Error("create intent before signing");
|
throw new Error("create intent before signing");
|
||||||
@ -1119,6 +1228,8 @@ bind("btnQuickConnect", onQuickConnect);
|
|||||||
bind("btnQuickActivate", onQuickActivate);
|
bind("btnQuickActivate", onQuickActivate);
|
||||||
bind("btnQuickRefresh", onQuickRefresh);
|
bind("btnQuickRefresh", onQuickRefresh);
|
||||||
bind("btnQuickInstallStatus", onQuickInstallStatus);
|
bind("btnQuickInstallStatus", onQuickInstallStatus);
|
||||||
|
bind("btnRefreshBalances", onRefreshBalances);
|
||||||
|
bind("btnCopyWallet", onCopyWallet);
|
||||||
bind("btnModeHuman", onModeHuman);
|
bind("btnModeHuman", onModeHuman);
|
||||||
bind("btnModeAuto", onModeAuto);
|
bind("btnModeAuto", onModeAuto);
|
||||||
bind("btnConnectWallet", onConnectWallet);
|
bind("btnConnectWallet", onConnectWallet);
|
||||||
|
|||||||
@ -20,6 +20,8 @@
|
|||||||
<button id="btnQuickActivate">Activate membership</button>
|
<button id="btnQuickActivate">Activate membership</button>
|
||||||
<button id="btnQuickRefresh">Refresh status + feed</button>
|
<button id="btnQuickRefresh">Refresh status + feed</button>
|
||||||
<button id="btnQuickInstallStatus">Governance status</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="btnModeHuman" class="mode-active">Human mode</button>
|
||||||
<button id="btnModeAuto">Auto mode</button>
|
<button id="btnModeAuto">Auto mode</button>
|
||||||
</div>
|
</div>
|
||||||
@ -33,6 +35,10 @@
|
|||||||
<h3>Session</h3>
|
<h3>Session</h3>
|
||||||
<p id="summarySession">none</p>
|
<p id="summarySession">none</p>
|
||||||
</article>
|
</article>
|
||||||
|
<article class="stat">
|
||||||
|
<h3>Wallet Funds</h3>
|
||||||
|
<p id="summaryFunds">-- ETH | -- USDC</p>
|
||||||
|
</article>
|
||||||
<article class="stat">
|
<article class="stat">
|
||||||
<h3>Membership</h3>
|
<h3>Membership</h3>
|
||||||
<p id="summaryMembership">unknown</p>
|
<p id="summaryMembership">unknown</p>
|
||||||
@ -96,6 +102,10 @@
|
|||||||
Chain ID
|
Chain ID
|
||||||
<input id="chainId" type="number" value="84532" />
|
<input id="chainId" type="number" value="84532" />
|
||||||
</label>
|
</label>
|
||||||
|
<label>
|
||||||
|
USDC token address
|
||||||
|
<input id="usdcTokenAddress" value="0x036cbd53842c5426634e7929541ec2318f3dcf7e" />
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user