All checks were successful
check / check (push) Successful in 26s
Address rows on Home now carry an [x] control on wallets that derive their addresses from an extended key and hold more than one; it opens a confirmation screen before anything is removed. Removing an address destroys nothing, and the copy says so: the address stays derivable from key material the wallet still holds, any funds at it stay where they are, and importing the recovery phrase brings it back. That is also why the screen is not password-gated, unlike delete-wallet — a password gates the disclosure or destruction of a secret, and this does neither. A balance is surfaced as a warning line, never as a refusal. The state transition lives next to the wallet one in src/shared/walletDelete.js and shares its address comparison, site-permission cleanup and broadcast, so the rules match one level down: the last address of a wallet is never removable, the selection moves only when it was the address removed, an index after the splice is decremented, a selection in another wallet is untouched, and AUTISTMASK_ACTIVE_CHANGED is broadcast when the active address moves so a connected site stops being told about an address the user removed. The wallet's derivation counter is a high-water mark and is not rewound, so "+" derives a fresh index rather than handing back the address just removed.
271 lines
7.1 KiB
JavaScript
271 lines
7.1 KiB
JavaScript
// AutistMask popup entry point.
|
|
// Loads state, initializes views, triggers first render.
|
|
|
|
const { state, saveState, loadState } = require("../shared/state");
|
|
const { setRuntimeDebug } = require("../shared/log");
|
|
const { refreshPrices } = require("../shared/prices");
|
|
const { refreshBalances } = require("../shared/balances");
|
|
const {
|
|
$,
|
|
showView,
|
|
updateDebugBanner,
|
|
setRenderMain,
|
|
pushCurrentView,
|
|
goBack,
|
|
clearViewStack,
|
|
} = require("./views/helpers");
|
|
const { applyTheme } = require("./theme");
|
|
// Views that can be fully re-rendered from persisted state. All others fall
|
|
// back to the nearest restorable parent; see the module for why the
|
|
// secret-bearing views are absent.
|
|
const { RESTORABLE_VIEWS } = require("./restorableViews");
|
|
|
|
const home = require("./views/home");
|
|
const welcome = require("./views/welcome");
|
|
const addWallet = require("./views/addWallet");
|
|
const addressDetail = require("./views/addressDetail");
|
|
const addressToken = require("./views/addressToken");
|
|
const send = require("./views/send");
|
|
const confirmTx = require("./views/confirmTx");
|
|
const txStatus = require("./views/txStatus");
|
|
const transactionDetail = require("./views/transactionDetail");
|
|
const receive = require("./views/receive");
|
|
const addToken = require("./views/addToken");
|
|
const settings = require("./views/settings");
|
|
const settingsAddToken = require("./views/settingsAddToken");
|
|
const deleteAddress = require("./views/deleteAddress");
|
|
const approval = require("./views/approval");
|
|
|
|
function renderWalletList() {
|
|
home.render(ctx);
|
|
}
|
|
|
|
let refreshInFlight = false;
|
|
|
|
async function doRefreshAndRender() {
|
|
if (refreshInFlight) return;
|
|
refreshInFlight = true;
|
|
try {
|
|
await Promise.all([
|
|
refreshPrices(),
|
|
refreshBalances(
|
|
state.wallets,
|
|
state.rpcUrl,
|
|
state.blockscoutUrl,
|
|
state.trackedTokens,
|
|
),
|
|
]);
|
|
state.lastBalanceRefresh = Date.now();
|
|
await saveState();
|
|
renderWalletList();
|
|
} finally {
|
|
refreshInFlight = false;
|
|
}
|
|
}
|
|
|
|
const ctx = {
|
|
renderWalletList,
|
|
doRefreshAndRender,
|
|
showAddWalletView: () => {
|
|
pushCurrentView();
|
|
addWallet.show();
|
|
},
|
|
showAddressDetail: () => {
|
|
pushCurrentView();
|
|
addressDetail.show();
|
|
},
|
|
showAddressToken: () => {
|
|
pushCurrentView();
|
|
addressToken.show();
|
|
},
|
|
showAddTokenView: () => {
|
|
pushCurrentView();
|
|
addToken.show();
|
|
},
|
|
showConfirmTx: (txInfo) => {
|
|
pushCurrentView();
|
|
confirmTx.show(txInfo);
|
|
},
|
|
showReceive: () => {
|
|
pushCurrentView();
|
|
receive.show();
|
|
},
|
|
showTransactionDetail: (tx) => {
|
|
pushCurrentView();
|
|
transactionDetail.show(tx);
|
|
},
|
|
showSettingsView: () => {
|
|
pushCurrentView();
|
|
settings.show();
|
|
},
|
|
showSettingsAddTokenView: () => {
|
|
pushCurrentView();
|
|
settingsAddToken.show();
|
|
},
|
|
showDeleteAddress: (walletIdx, addrIdx) => {
|
|
pushCurrentView();
|
|
deleteAddress.show(walletIdx, addrIdx);
|
|
},
|
|
};
|
|
|
|
function needsAddress(view) {
|
|
return (
|
|
view === "address" ||
|
|
view === "address-token" ||
|
|
view === "receive" ||
|
|
view === "transaction"
|
|
);
|
|
}
|
|
|
|
function hasValidAddress() {
|
|
return (
|
|
state.selectedWallet !== null &&
|
|
state.selectedAddress !== null &&
|
|
state.wallets[state.selectedWallet] &&
|
|
state.wallets[state.selectedWallet].addresses[state.selectedAddress]
|
|
);
|
|
}
|
|
|
|
function restoreView() {
|
|
const view = state.currentView;
|
|
if (!view || !RESTORABLE_VIEWS.has(view)) {
|
|
return fallbackView();
|
|
}
|
|
|
|
if (needsAddress(view) && !hasValidAddress()) {
|
|
return fallbackView();
|
|
}
|
|
|
|
if (view === "address-token" && !state.selectedToken) {
|
|
return fallbackView();
|
|
}
|
|
|
|
switch (view) {
|
|
case "address":
|
|
addressDetail.show();
|
|
break;
|
|
case "address-token":
|
|
addressToken.show();
|
|
break;
|
|
case "receive":
|
|
receive.show();
|
|
break;
|
|
case "settings":
|
|
settings.show();
|
|
break;
|
|
case "settings-addtoken":
|
|
settingsAddToken.show();
|
|
break;
|
|
case "confirm-tx":
|
|
if (state.viewData && state.viewData.pendingTx) {
|
|
confirmTx.restore();
|
|
} else {
|
|
fallbackView();
|
|
}
|
|
break;
|
|
case "transaction":
|
|
if (state.viewData && state.viewData.tx) {
|
|
transactionDetail.render();
|
|
} else {
|
|
fallbackView();
|
|
}
|
|
break;
|
|
case "success-tx":
|
|
if (state.viewData && state.viewData.hash) {
|
|
txStatus.renderSuccess();
|
|
} else {
|
|
fallbackView();
|
|
}
|
|
break;
|
|
case "error-tx":
|
|
if (state.viewData && state.viewData.message) {
|
|
txStatus.renderError();
|
|
} else {
|
|
fallbackView();
|
|
}
|
|
break;
|
|
default:
|
|
fallbackView();
|
|
break;
|
|
}
|
|
}
|
|
|
|
function fallbackView() {
|
|
renderWalletList();
|
|
showView("main");
|
|
}
|
|
|
|
async function init() {
|
|
await loadState();
|
|
applyTheme(state.theme);
|
|
|
|
// Sync runtime debug flag from persisted state before first render
|
|
setRuntimeDebug(state.debugMode);
|
|
|
|
// Create the debug/testnet banner if needed (uses runtime debug state)
|
|
updateDebugBanner();
|
|
|
|
// Auto-default active address
|
|
if (
|
|
state.activeAddress === null &&
|
|
state.wallets.length > 0 &&
|
|
state.wallets[0].addresses.length > 0
|
|
) {
|
|
state.activeAddress = state.wallets[0].addresses[0].address;
|
|
await saveState();
|
|
}
|
|
|
|
// Always init approval and txStatus — they may run in the approval popup window
|
|
approval.init(ctx);
|
|
txStatus.init(ctx);
|
|
|
|
// Check for approval mode
|
|
const params = new URLSearchParams(window.location.search);
|
|
const approvalId = params.get("approval");
|
|
if (approvalId) {
|
|
approval.show(approvalId);
|
|
showView("approve-site");
|
|
return;
|
|
}
|
|
|
|
$("btn-settings").addEventListener("click", () => {
|
|
if (
|
|
!document
|
|
.getElementById("view-settings")
|
|
.classList.contains("hidden")
|
|
) {
|
|
goBack();
|
|
return;
|
|
}
|
|
pushCurrentView();
|
|
settings.show();
|
|
});
|
|
|
|
setRenderMain(renderWalletList);
|
|
|
|
welcome.init(ctx);
|
|
addWallet.init(ctx);
|
|
home.init(ctx);
|
|
addressDetail.init(ctx);
|
|
addressToken.init(ctx);
|
|
send.init(ctx);
|
|
confirmTx.init(ctx);
|
|
transactionDetail.init(ctx);
|
|
receive.init(ctx);
|
|
addToken.init(ctx);
|
|
settings.init(ctx);
|
|
settingsAddToken.init(ctx);
|
|
deleteAddress.init(ctx);
|
|
|
|
if (!state.hasWallet) {
|
|
showView("welcome");
|
|
} else {
|
|
renderWalletList();
|
|
restoreView();
|
|
doRefreshAndRender();
|
|
setInterval(doRefreshAndRender, 10000);
|
|
}
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", init);
|