Merge branch 'main' into fix/swap-display
All checks were successful
check / check (push) Successful in 22s
All checks were successful
check / check (push) Successful in 22s
This commit is contained in:
@@ -358,12 +358,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- token contract details (ERC-20 only) -->
|
|
||||||
<div
|
|
||||||
id="address-token-contract-info"
|
|
||||||
class="border-b border-border-light pb-2 mb-2 text-xs hidden"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<!-- actions -->
|
<!-- actions -->
|
||||||
<div class="flex gap-2 mb-3">
|
<div class="flex gap-2 mb-3">
|
||||||
<button
|
<button
|
||||||
@@ -380,6 +374,12 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- token contract details (ERC-20 only) -->
|
||||||
|
<div
|
||||||
|
id="address-token-contract-info"
|
||||||
|
class="bg-hover rounded-md mx-1 p-3 mb-3 text-xs hidden"
|
||||||
|
></div>
|
||||||
|
|
||||||
<!-- token-filtered transactions -->
|
<!-- token-filtered transactions -->
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<div class="border-b border-border pb-1 mb-1">
|
<div class="border-b border-border pb-1 mb-1">
|
||||||
@@ -708,9 +708,7 @@
|
|||||||
|
|
||||||
<div class="bg-well p-3 mx-1 mb-3">
|
<div class="bg-well p-3 mx-1 mb-3">
|
||||||
<h3 class="font-bold mb-1">Wallets</h3>
|
<h3 class="font-bold mb-1">Wallets</h3>
|
||||||
<p class="text-xs text-muted mb-2">
|
<div id="settings-wallet-list" class="mb-2"></div>
|
||||||
Add a new wallet from a recovery phrase or private key.
|
|
||||||
</p>
|
|
||||||
<button
|
<button
|
||||||
id="btn-main-add-wallet"
|
id="btn-main-add-wallet"
|
||||||
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
|
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
|
||||||
@@ -845,6 +843,41 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ============ DELETE WALLET CONFIRM ============ -->
|
||||||
|
<div id="view-delete-wallet-confirm" class="view hidden">
|
||||||
|
<button
|
||||||
|
id="btn-delete-wallet-back"
|
||||||
|
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer mb-2"
|
||||||
|
>
|
||||||
|
< Back
|
||||||
|
</button>
|
||||||
|
<h2 class="font-bold mb-3">Delete Wallet</h2>
|
||||||
|
<p class="text-xs mb-3">
|
||||||
|
Deleting
|
||||||
|
<strong id="delete-wallet-name"></strong> is permanent. Any
|
||||||
|
funds will be unrecoverable without your recovery phrase.
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
id="delete-wallet-flash"
|
||||||
|
class="text-xs text-red-500 mb-2 hidden"
|
||||||
|
></div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="block mb-1">Password</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="delete-wallet-password"
|
||||||
|
class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg"
|
||||||
|
placeholder="Enter your password to confirm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
id="btn-delete-wallet-confirm"
|
||||||
|
class="border border-border text-red-500 px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
|
||||||
|
>
|
||||||
|
Confirm Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- ============ SETTINGS: ADD TOKEN ============ -->
|
<!-- ============ SETTINGS: ADD TOKEN ============ -->
|
||||||
<div id="view-settings-addtoken" class="view hidden">
|
<div id="view-settings-addtoken" class="view hidden">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ async function init() {
|
|||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
const approvalId = params.get("approval");
|
const approvalId = params.get("approval");
|
||||||
if (approvalId) {
|
if (approvalId) {
|
||||||
approval.show(parseInt(approvalId, 10));
|
approval.show(approvalId);
|
||||||
showView("approve-site");
|
showView("approve-site");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const {
|
|||||||
balanceLine,
|
balanceLine,
|
||||||
} = require("./helpers");
|
} = require("./helpers");
|
||||||
const { state, currentAddress, saveState } = require("../../shared/state");
|
const { state, currentAddress, saveState } = require("../../shared/state");
|
||||||
|
const { TOKEN_BY_ADDRESS } = require("../../shared/tokenList");
|
||||||
const {
|
const {
|
||||||
formatUsd,
|
formatUsd,
|
||||||
getPrice,
|
getPrice,
|
||||||
@@ -142,30 +143,24 @@ function show() {
|
|||||||
const tokenHolders = tb && tb.holders != null ? tb.holders : null;
|
const tokenHolders = tb && tb.holders != null ? tb.holders : null;
|
||||||
const dot = addressDotHtml(tokenId);
|
const dot = addressDotHtml(tokenId);
|
||||||
const tokenLink = `https://etherscan.io/token/${escapeHtml(tokenId)}`;
|
const tokenLink = `https://etherscan.io/token/${escapeHtml(tokenId)}`;
|
||||||
let infoHtml =
|
const knownToken = TOKEN_BY_ADDRESS.get(tokenId.toLowerCase());
|
||||||
`<div class="font-bold mb-1">Token Contract</div>` +
|
const projectUrl = knownToken && knownToken.url ? knownToken.url : null;
|
||||||
`<div class="flex items-center mb-1">${dot}` +
|
let infoHtml = `<div class="font-bold mb-2">Contract Address</div>`;
|
||||||
|
infoHtml +=
|
||||||
|
`<div class="flex items-center mb-2">${dot}` +
|
||||||
`<span class="break-all underline decoration-dashed cursor-pointer" id="address-token-contract-copy" data-copy="${escapeHtml(tokenId)}">${escapeHtml(tokenId)}</span>` +
|
`<span class="break-all underline decoration-dashed cursor-pointer" id="address-token-contract-copy" data-copy="${escapeHtml(tokenId)}">${escapeHtml(tokenId)}</span>` +
|
||||||
`<a href="${tokenLink}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>` +
|
`<a href="${tokenLink}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>` +
|
||||||
`</div>`;
|
`</div>`;
|
||||||
const details = [];
|
|
||||||
if (tokenName)
|
if (tokenName)
|
||||||
details.push(`<span class="text-muted">Name:</span> ${tokenName}`);
|
infoHtml += `<div class="mb-1"><span class="text-muted">Name:</span> ${tokenName}</div>`;
|
||||||
if (tokenSymbol)
|
if (tokenSymbol)
|
||||||
details.push(
|
infoHtml += `<div class="mb-1"><span class="text-muted">Symbol:</span> ${tokenSymbol}</div>`;
|
||||||
`<span class="text-muted">Symbol:</span> ${tokenSymbol}`,
|
|
||||||
);
|
|
||||||
if (tokenDecimals != null)
|
if (tokenDecimals != null)
|
||||||
details.push(
|
infoHtml += `<div class="mb-1"><span class="text-muted">Decimals:</span> ${tokenDecimals}</div>`;
|
||||||
`<span class="text-muted">Decimals:</span> ${tokenDecimals}`,
|
|
||||||
);
|
|
||||||
if (tokenHolders != null)
|
if (tokenHolders != null)
|
||||||
details.push(
|
infoHtml += `<div class="mb-1"><span class="text-muted">Holders:</span> ${Number(tokenHolders).toLocaleString()}</div>`;
|
||||||
`<span class="text-muted">Holders:</span> ${Number(tokenHolders).toLocaleString()}`,
|
if (projectUrl)
|
||||||
);
|
infoHtml += `<div class="mb-1"><span class="text-muted">Website:</span> <a href="${escapeHtml(projectUrl)}" target="_blank" rel="noopener" class="underline decoration-dashed">${escapeHtml(projectUrl)}</a></div>`;
|
||||||
if (details.length > 0) {
|
|
||||||
infoHtml += `<div class="flex flex-wrap gap-x-3 gap-y-1">${details.join("")}</div>`;
|
|
||||||
}
|
|
||||||
contractInfo.innerHTML = infoHtml;
|
contractInfo.innerHTML = infoHtml;
|
||||||
contractInfo.classList.remove("hidden");
|
contractInfo.classList.remove("hidden");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
90
src/popup/views/deleteWallet.js
Normal file
90
src/popup/views/deleteWallet.js
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
const { $, showView, showFlash } = require("./helpers");
|
||||||
|
const { state, saveState } = require("../../shared/state");
|
||||||
|
const { decryptWithPassword } = require("../../shared/vault");
|
||||||
|
|
||||||
|
let deleteWalletIndex = null;
|
||||||
|
let ctx = null;
|
||||||
|
|
||||||
|
function show(walletIdx) {
|
||||||
|
deleteWalletIndex = walletIdx;
|
||||||
|
const wallet = state.wallets[walletIdx];
|
||||||
|
$("delete-wallet-name").textContent =
|
||||||
|
wallet.name || "Wallet " + (walletIdx + 1);
|
||||||
|
$("delete-wallet-password").value = "";
|
||||||
|
$("delete-wallet-flash").textContent = "";
|
||||||
|
$("delete-wallet-flash").classList.add("hidden");
|
||||||
|
showView("delete-wallet-confirm");
|
||||||
|
}
|
||||||
|
|
||||||
|
function init(_ctx) {
|
||||||
|
ctx = _ctx;
|
||||||
|
|
||||||
|
$("btn-delete-wallet-back").addEventListener("click", () => {
|
||||||
|
deleteWalletIndex = null;
|
||||||
|
ctx.showSettingsView();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("btn-delete-wallet-confirm").addEventListener("click", async () => {
|
||||||
|
const pw = $("delete-wallet-password").value;
|
||||||
|
if (!pw) {
|
||||||
|
$("delete-wallet-flash").textContent =
|
||||||
|
"Please enter your password.";
|
||||||
|
$("delete-wallet-flash").classList.remove("hidden");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deleteWalletIndex === null) {
|
||||||
|
$("delete-wallet-flash").textContent =
|
||||||
|
"No wallet selected for deletion.";
|
||||||
|
$("delete-wallet-flash").classList.remove("hidden");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const walletIdx = deleteWalletIndex;
|
||||||
|
const wallet = state.wallets[walletIdx];
|
||||||
|
|
||||||
|
// Verify password against the wallet's encrypted data
|
||||||
|
try {
|
||||||
|
await decryptWithPassword(wallet.encryptedSecret, pw);
|
||||||
|
} catch (_e) {
|
||||||
|
$("delete-wallet-flash").textContent = "Wrong password.";
|
||||||
|
$("delete-wallet-flash").classList.remove("hidden");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect addresses to clean up from allowedSites/deniedSites
|
||||||
|
const addresses = (wallet.addresses || []).map((a) => a.address);
|
||||||
|
|
||||||
|
// Remove wallet
|
||||||
|
state.wallets.splice(walletIdx, 1);
|
||||||
|
|
||||||
|
// Clean up site permissions for deleted addresses
|
||||||
|
for (const addr of addresses) {
|
||||||
|
delete state.allowedSites[addr];
|
||||||
|
delete state.deniedSites[addr];
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteWalletIndex = null;
|
||||||
|
|
||||||
|
if (state.wallets.length === 0) {
|
||||||
|
// No wallets left — reset selection and show welcome
|
||||||
|
state.selectedWallet = null;
|
||||||
|
state.selectedAddress = null;
|
||||||
|
state.activeAddress = null;
|
||||||
|
await saveState();
|
||||||
|
showView("welcome");
|
||||||
|
} else {
|
||||||
|
// Switch to first wallet if deleted wallet was active
|
||||||
|
state.selectedWallet = 0;
|
||||||
|
state.selectedAddress = 0;
|
||||||
|
state.activeAddress =
|
||||||
|
state.wallets[0].addresses[0]?.address || null;
|
||||||
|
await saveState();
|
||||||
|
ctx.renderWalletList();
|
||||||
|
ctx.showSettingsView();
|
||||||
|
showFlash("Wallet deleted.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { init, show };
|
||||||
@@ -25,6 +25,7 @@ const VIEWS = [
|
|||||||
"receive",
|
"receive",
|
||||||
"add-token",
|
"add-token",
|
||||||
"settings",
|
"settings",
|
||||||
|
"delete-wallet-confirm",
|
||||||
"settings-addtoken",
|
"settings-addtoken",
|
||||||
"transaction",
|
"transaction",
|
||||||
"approve-site",
|
"approve-site",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ const { $, showView, showFlash, escapeHtml } = require("./helpers");
|
|||||||
const { state, saveState } = require("../../shared/state");
|
const { state, saveState } = require("../../shared/state");
|
||||||
const { ETHEREUM_MAINNET_CHAIN_ID } = require("../../shared/constants");
|
const { ETHEREUM_MAINNET_CHAIN_ID } = require("../../shared/constants");
|
||||||
const { log, debugFetch } = require("../../shared/log");
|
const { log, debugFetch } = require("../../shared/log");
|
||||||
|
const deleteWallet = require("./deleteWallet");
|
||||||
|
|
||||||
const runtime =
|
const runtime =
|
||||||
typeof browser !== "undefined" ? browser.runtime : chrome.runtime;
|
typeof browser !== "undefined" ? browser.runtime : chrome.runtime;
|
||||||
@@ -65,11 +66,68 @@ function renderTrackedTokens() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderWalletListSettings() {
|
||||||
|
const container = $("settings-wallet-list");
|
||||||
|
if (state.wallets.length === 0) {
|
||||||
|
container.innerHTML = '<p class="text-xs text-muted">No wallets.</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let html = "";
|
||||||
|
state.wallets.forEach((wallet, idx) => {
|
||||||
|
const name = escapeHtml(wallet.name || "Wallet " + (idx + 1));
|
||||||
|
html += `<div class="flex justify-between items-center text-xs py-1 border-b border-border-light">`;
|
||||||
|
html += `<span class="settings-wallet-name cursor-pointer underline decoration-dashed" data-idx="${idx}">${name}</span>`;
|
||||||
|
html += `<button class="btn-delete-wallet border border-border px-1 hover:bg-fg hover:text-bg cursor-pointer" data-idx="${idx}">[x]</button>`;
|
||||||
|
html += `</div>`;
|
||||||
|
});
|
||||||
|
container.innerHTML = html;
|
||||||
|
container.querySelectorAll(".btn-delete-wallet").forEach((btn) => {
|
||||||
|
btn.addEventListener("click", () => {
|
||||||
|
const idx = parseInt(btn.dataset.idx, 10);
|
||||||
|
deleteWallet.show(idx);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Inline rename on click
|
||||||
|
container.querySelectorAll(".settings-wallet-name").forEach((span) => {
|
||||||
|
span.addEventListener("click", () => {
|
||||||
|
const idx = parseInt(span.dataset.idx, 10);
|
||||||
|
const wallet = state.wallets[idx];
|
||||||
|
const input = document.createElement("input");
|
||||||
|
input.type = "text";
|
||||||
|
input.className =
|
||||||
|
"border border-border p-0 text-xs bg-bg text-fg w-full";
|
||||||
|
input.value = wallet.name || "Wallet " + (idx + 1);
|
||||||
|
span.replaceWith(input);
|
||||||
|
input.focus();
|
||||||
|
input.select();
|
||||||
|
const finish = async () => {
|
||||||
|
const val = input.value.trim();
|
||||||
|
if (val && val !== wallet.name) {
|
||||||
|
wallet.name = val;
|
||||||
|
await saveState();
|
||||||
|
}
|
||||||
|
renderWalletListSettings();
|
||||||
|
};
|
||||||
|
input.addEventListener("blur", finish);
|
||||||
|
input.addEventListener("keydown", (e) => {
|
||||||
|
if (e.key === "Enter") input.blur();
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
input.value = wallet.name || "Wallet " + (idx + 1);
|
||||||
|
input.blur();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function show() {
|
function show() {
|
||||||
$("settings-rpc").value = state.rpcUrl;
|
$("settings-rpc").value = state.rpcUrl;
|
||||||
$("settings-blockscout").value = state.blockscoutUrl;
|
$("settings-blockscout").value = state.blockscoutUrl;
|
||||||
renderTrackedTokens();
|
renderTrackedTokens();
|
||||||
renderSiteLists();
|
renderSiteLists();
|
||||||
|
renderWalletListSettings();
|
||||||
|
|
||||||
showView("settings");
|
showView("settings");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +141,8 @@ function renderSiteLists() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function init(ctx) {
|
function init(ctx) {
|
||||||
|
deleteWallet.init(ctx);
|
||||||
|
|
||||||
$("btn-save-rpc").addEventListener("click", async () => {
|
$("btn-save-rpc").addEventListener("click", async () => {
|
||||||
const url = $("settings-rpc").value.trim();
|
const url = $("settings-rpc").value.trim();
|
||||||
if (!url) {
|
if (!url) {
|
||||||
|
|||||||
Reference in New Issue
Block a user