Compare commits
1 Commits
feature/sh
...
3413e696db
| Author | SHA1 | Date | |
|---|---|---|---|
| 3413e696db |
35
README.md
35
README.md
@@ -213,22 +213,6 @@ create an address with the same visible characters and trick the user into
|
||||
sending funds to it. Showing the complete identifier defeats this class of
|
||||
attack.
|
||||
|
||||
#### Clipboard Policy
|
||||
|
||||
AutistMask never clears or overwrites the user's clipboard. When sensitive data
|
||||
such as a private key is copied, it is the user's responsibility to manage their
|
||||
clipboard afterwards. We deliberately avoid auto-clearing the clipboard for two
|
||||
reasons:
|
||||
|
||||
1. **User expectations**: silently modifying the clipboard violates the
|
||||
principle of least surprise. The user initiated the copy and knows the
|
||||
content is sensitive.
|
||||
2. **Data safety**: the user may have copied something else important in the
|
||||
intervening time. A timed clipboard clear would destroy that unrelated data.
|
||||
|
||||
The warning shown before revealing a private key makes it clear that the key is
|
||||
sensitive and that clipboard management is the user's responsibility.
|
||||
|
||||
#### Data Model
|
||||
|
||||
The core hierarchy is **Wallets → Addresses**:
|
||||
@@ -332,34 +316,15 @@ transitions.
|
||||
- Balance list: ETH + tracked ERC-20 tokens (4 decimal places, USD inline).
|
||||
Each balance row is clickable → **AddressToken**
|
||||
- Send / Receive / + Token buttons
|
||||
- "Show private key" button
|
||||
- Transaction list (with ENS resolution for counterparties)
|
||||
- **Transitions**:
|
||||
- Tap balance row → **AddressToken** (for that token)
|
||||
- "Send" → **Send**
|
||||
- "Receive" → **Receive**
|
||||
- "+ Token" → **AddToken**
|
||||
- "Show private key" → **ShowPrivateKey**
|
||||
- Tap transaction row → **TransactionDetail**
|
||||
- "Back" → **Home**
|
||||
|
||||
#### ShowPrivateKey
|
||||
|
||||
- **When**: User clicked "Show private key" on AddressDetail.
|
||||
- **Elements**:
|
||||
- "Back" button
|
||||
- Title: "Display Private Key"
|
||||
- Warning box (lock + money icons) explaining the key controls funds and
|
||||
that the user is responsible for clipboard management
|
||||
- Password input
|
||||
- "Display Private Key" button (with lock + money icons)
|
||||
- After reveal: private key in a read-only well (monospace, select-all),
|
||||
Copy button, Done button
|
||||
- **Transitions**:
|
||||
- "Display Private Key" (correct password) → reveals key in-place
|
||||
- "Copy" → copies key to clipboard
|
||||
- "Done" / "Back" → **AddressDetail** (key cleared from DOM)
|
||||
|
||||
#### AddressToken
|
||||
|
||||
- **When**: User clicked a specific token balance on AddressDetail.
|
||||
|
||||
@@ -93,13 +93,11 @@ function resetPopupUrl() {
|
||||
}
|
||||
}
|
||||
|
||||
// Open approval in a separate popup window.
|
||||
// This is the primary mechanism for tx/sign approvals (triggered programmatically,
|
||||
// not from a user gesture) and the fallback for site-connection approvals.
|
||||
// Fallback: open approval in a separate window (used when openPopup is unavailable)
|
||||
function openApprovalWindow(id) {
|
||||
const popupUrl = runtime.getURL("src/popup/index.html?approval=" + id);
|
||||
const popupWidth = 360;
|
||||
const popupHeight = 600;
|
||||
const popupWidth = 400;
|
||||
const popupHeight = 500;
|
||||
|
||||
windowsApi.getLastFocused((currentWin) => {
|
||||
const opts = {
|
||||
@@ -150,9 +148,7 @@ function requestApproval(origin, hostname) {
|
||||
}
|
||||
|
||||
// Open a tx-approval popup and return a promise that resolves with txHash or error.
|
||||
// Uses windows.create() directly because tx approvals are triggered programmatically
|
||||
// (from a dApp RPC call), not from a user gesture, so action.openPopup() is
|
||||
// unreliable in this context.
|
||||
// Uses the toolbar popup only — no fallback window.
|
||||
function requestTxApproval(origin, hostname, txParams) {
|
||||
return new Promise((resolve) => {
|
||||
const id = crypto.randomUUID();
|
||||
@@ -164,14 +160,27 @@ function requestTxApproval(origin, hostname, txParams) {
|
||||
type: "tx",
|
||||
};
|
||||
|
||||
openApprovalWindow(id);
|
||||
if (actionApi && typeof actionApi.setPopup === "function") {
|
||||
actionApi.setPopup({
|
||||
popup: "src/popup/index.html?approval=" + id,
|
||||
});
|
||||
}
|
||||
if (actionApi && typeof actionApi.openPopup === "function") {
|
||||
try {
|
||||
const result = actionApi.openPopup();
|
||||
if (result && typeof result.catch === "function") {
|
||||
result.catch(() => {});
|
||||
}
|
||||
} catch {
|
||||
// openPopup unsupported — user clicks toolbar icon
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Open a sign-approval popup and return a promise that resolves with { signature } or { error }.
|
||||
// Uses windows.create() directly because sign approvals are triggered programmatically
|
||||
// (from a dApp RPC call), not from a user gesture, so action.openPopup() is
|
||||
// unreliable in this context.
|
||||
// Uses the toolbar popup only — no fallback window. If openPopup() fails the
|
||||
// popup URL is still set, so the user can click the toolbar icon to respond.
|
||||
function requestSignApproval(origin, hostname, signParams) {
|
||||
return new Promise((resolve) => {
|
||||
const id = crypto.randomUUID();
|
||||
@@ -183,14 +192,27 @@ function requestSignApproval(origin, hostname, signParams) {
|
||||
type: "sign",
|
||||
};
|
||||
|
||||
openApprovalWindow(id);
|
||||
if (actionApi && typeof actionApi.setPopup === "function") {
|
||||
actionApi.setPopup({
|
||||
popup: "src/popup/index.html?approval=" + id,
|
||||
});
|
||||
}
|
||||
if (actionApi && typeof actionApi.openPopup === "function") {
|
||||
try {
|
||||
const result = actionApi.openPopup();
|
||||
if (result && typeof result.catch === "function") {
|
||||
result.catch(() => {});
|
||||
}
|
||||
} catch {
|
||||
// openPopup unsupported — user clicks toolbar icon
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Detect when an approval popup (browser-action) closes without a response.
|
||||
// TX and sign approvals now use windows.create() and are handled by the
|
||||
// windowsApi.onRemoved listener below, but we still handle site-connection
|
||||
// approval disconnects here.
|
||||
// TX and sign approvals are NOT auto-rejected on disconnect because toolbar
|
||||
// popups naturally close on focus loss and the user can reopen them.
|
||||
runtime.onConnect.addListener((port) => {
|
||||
if (port.name.startsWith("approval:")) {
|
||||
const id = port.name.split(":")[1];
|
||||
|
||||
@@ -13,26 +13,6 @@ if (typeof browser !== "undefined") {
|
||||
(document.head || document.documentElement).appendChild(script);
|
||||
}
|
||||
|
||||
// Send the persisted EIP-6963 provider UUID to the inpage script.
|
||||
// Generated once at install time and stored in chrome.storage.local.
|
||||
(function sendProviderUuid() {
|
||||
const storage =
|
||||
typeof browser !== "undefined"
|
||||
? browser.storage.local
|
||||
: chrome.storage.local;
|
||||
storage.get("eip6963Uuid", (items) => {
|
||||
let uuid = items?.eip6963Uuid;
|
||||
if (!uuid) {
|
||||
uuid = crypto.randomUUID();
|
||||
storage.set({ eip6963Uuid: uuid });
|
||||
}
|
||||
window.postMessage(
|
||||
{ type: "AUTISTMASK_PROVIDER_UUID", uuid },
|
||||
location.origin,
|
||||
);
|
||||
});
|
||||
})();
|
||||
|
||||
// Relay requests from the page to the background script
|
||||
window.addEventListener("message", (event) => {
|
||||
if (event.source !== window) return;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
const pending = {};
|
||||
|
||||
// Listen for responses from the content script
|
||||
window.addEventListener("message", function onUuid(event) {
|
||||
window.addEventListener("message", (event) => {
|
||||
if (event.source !== window) return;
|
||||
if (event.data?.type !== "AUTISTMASK_RESPONSE") return;
|
||||
const { id, result, error } = event.data;
|
||||
@@ -24,7 +24,7 @@
|
||||
});
|
||||
|
||||
// Listen for events pushed from the extension
|
||||
window.addEventListener("message", function onUuid(event) {
|
||||
window.addEventListener("message", (event) => {
|
||||
if (event.source !== window) return;
|
||||
if (event.data?.type !== "AUTISTMASK_EVENT") return;
|
||||
const { eventName, data } = event.data;
|
||||
@@ -134,7 +134,7 @@
|
||||
// Some dApps (wagmi) check this to confirm MetaMask-like behavior
|
||||
_metamask: {
|
||||
isUnlocked() {
|
||||
return Promise.resolve(provider.selectedAddress !== null);
|
||||
return Promise.resolve(true);
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -155,38 +155,21 @@
|
||||
"</svg>",
|
||||
);
|
||||
|
||||
let providerUuid = crypto.randomUUID(); // fallback until real UUID arrives
|
||||
|
||||
function buildProviderInfo() {
|
||||
return {
|
||||
uuid: providerUuid,
|
||||
name: "AutistMask",
|
||||
icon: ICON_SVG,
|
||||
rdns: "berlin.sneak.autistmask",
|
||||
};
|
||||
}
|
||||
const providerInfo = {
|
||||
uuid: "f3c5b2a1-8d4e-4f6a-9c7b-1e2d3a4b5c6d",
|
||||
name: "AutistMask",
|
||||
icon: ICON_SVG,
|
||||
rdns: "berlin.sneak.autistmask",
|
||||
};
|
||||
|
||||
function announceProvider() {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("eip6963:announceProvider", {
|
||||
detail: Object.freeze({
|
||||
info: buildProviderInfo(),
|
||||
provider,
|
||||
}),
|
||||
detail: Object.freeze({ info: providerInfo, provider }),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// Listen for the persisted UUID from the content script
|
||||
function onProviderUuid(event) {
|
||||
if (event.source !== window) return;
|
||||
if (event.data?.type !== "AUTISTMASK_PROVIDER_UUID") return;
|
||||
window.removeEventListener("message", onProviderUuid);
|
||||
providerUuid = event.data.uuid;
|
||||
announceProvider();
|
||||
}
|
||||
window.addEventListener("message", onProviderUuid);
|
||||
|
||||
window.addEventListener("eip6963:requestProvider", announceProvider);
|
||||
announceProvider();
|
||||
})();
|
||||
|
||||
@@ -307,15 +307,6 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<button
|
||||
id="btn-show-private-key"
|
||||
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer text-xs"
|
||||
>
|
||||
🔒 Show private key
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- transactions -->
|
||||
<div class="mt-3">
|
||||
<div class="border-b border-border pb-1 mb-1">
|
||||
@@ -327,77 +318,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ SHOW PRIVATE KEY ============ -->
|
||||
<div id="view-show-private-key" class="view hidden">
|
||||
<button
|
||||
id="btn-show-pk-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-2">Display Private Key</h2>
|
||||
|
||||
<!-- password prompt section -->
|
||||
<div id="show-pk-prompt">
|
||||
<div
|
||||
class="border border-border border-dashed p-3 mb-3 text-xs"
|
||||
>
|
||||
<p class="mb-1">
|
||||
🔒💰 Your private key controls this
|
||||
address and all its funds. Anyone who has it can
|
||||
spend your tokens.
|
||||
</p>
|
||||
<p>
|
||||
Do not share it. Do not paste it into websites. If
|
||||
you copy it, you are responsible for clearing your
|
||||
clipboard when you are done.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="block mb-1">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="show-pk-password"
|
||||
class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg"
|
||||
placeholder="Enter your password"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
id="show-pk-error"
|
||||
class="text-xs mb-2 border border-border border-dashed p-1 hidden"
|
||||
></div>
|
||||
<button
|
||||
id="btn-show-pk-reveal"
|
||||
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
|
||||
>
|
||||
🔒💰 Display Private Key
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- revealed key section -->
|
||||
<div id="show-pk-key-well" class="hidden">
|
||||
<div
|
||||
class="bg-well p-3 mx-1 mb-3 break-all font-mono text-xs select-all"
|
||||
>
|
||||
<span id="show-pk-key-value"></span>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
id="btn-show-pk-copy"
|
||||
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
<button
|
||||
id="btn-show-pk-done"
|
||||
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
|
||||
>
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ ADDRESS-TOKEN DETAIL VIEW ============ -->
|
||||
<div id="view-address-token" class="view hidden">
|
||||
<button
|
||||
@@ -457,7 +377,7 @@
|
||||
<!-- 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"
|
||||
class="bg-hover rounded-md mx-4 p-3 mb-3 text-xs hidden"
|
||||
></div>
|
||||
|
||||
<!-- token-filtered transactions -->
|
||||
@@ -788,7 +708,9 @@
|
||||
|
||||
<div class="bg-well p-3 mx-1 mb-3">
|
||||
<h3 class="font-bold mb-1">Wallets</h3>
|
||||
<div id="settings-wallet-list" class="mb-2"></div>
|
||||
<p class="text-xs text-muted mb-2">
|
||||
Add a new wallet from a recovery phrase or private key.
|
||||
</p>
|
||||
<button
|
||||
id="btn-main-add-wallet"
|
||||
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
|
||||
@@ -923,41 +845,6 @@
|
||||
</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 ============ -->
|
||||
<div id="view-settings-addtoken" class="view hidden">
|
||||
<button
|
||||
@@ -1033,13 +920,7 @@
|
||||
>
|
||||
< Back
|
||||
</button>
|
||||
<h2 id="tx-detail-heading" class="font-bold mb-2">
|
||||
Transaction
|
||||
</h2>
|
||||
<div id="tx-detail-type-section" class="mb-4 hidden">
|
||||
<div class="text-xs text-muted mb-1">Type</div>
|
||||
<div id="tx-detail-type" class="text-xs font-bold"></div>
|
||||
</div>
|
||||
<h2 class="font-bold mb-2">Transaction</h2>
|
||||
<div class="mb-4">
|
||||
<div class="text-xs text-muted mb-1">Status</div>
|
||||
<div id="tx-detail-status" class="text-xs"></div>
|
||||
@@ -1064,29 +945,6 @@
|
||||
<div class="text-xs text-muted mb-1">To</div>
|
||||
<div id="tx-detail-to" class="text-xs break-all"></div>
|
||||
</div>
|
||||
<div id="tx-detail-calldata-section" class="mb-4 hidden">
|
||||
<div
|
||||
id="tx-detail-calldata-well"
|
||||
class="mb-3 border border-border border-dashed p-2"
|
||||
>
|
||||
<div class="text-xs text-muted mb-1">Action</div>
|
||||
<div
|
||||
id="tx-detail-calldata-action"
|
||||
class="text-xs font-bold mb-2"
|
||||
></div>
|
||||
<div
|
||||
id="tx-detail-calldata-details"
|
||||
class="text-xs"
|
||||
></div>
|
||||
</div>
|
||||
<div id="tx-detail-rawdata-section" class="hidden">
|
||||
<div class="text-xs text-muted mb-1">Raw data</div>
|
||||
<div
|
||||
id="tx-detail-rawdata"
|
||||
class="text-xs break-all font-mono border border-border border-dashed p-2"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<div class="text-xs text-muted mb-1">Transaction hash</div>
|
||||
<div id="tx-detail-hash" class="text-xs break-all"></div>
|
||||
|
||||
@@ -19,7 +19,6 @@ const txStatus = require("./views/txStatus");
|
||||
const transactionDetail = require("./views/transactionDetail");
|
||||
const receive = require("./views/receive");
|
||||
const addToken = require("./views/addToken");
|
||||
const showPrivateKey = require("./views/showPrivateKey");
|
||||
const settings = require("./views/settings");
|
||||
const settingsAddToken = require("./views/settingsAddToken");
|
||||
const approval = require("./views/approval");
|
||||
@@ -57,7 +56,6 @@ const ctx = {
|
||||
showAddWalletView: () => addWallet.show(),
|
||||
showImportKeyView: () => importKey.show(),
|
||||
showAddressDetail: () => addressDetail.show(),
|
||||
showPrivateKey: () => showPrivateKey.show(),
|
||||
showAddressToken: () => addressToken.show(),
|
||||
showAddTokenView: () => addToken.show(),
|
||||
showConfirmTx: (txInfo) => confirmTx.show(txInfo),
|
||||
@@ -191,7 +189,7 @@ async function init() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const approvalId = params.get("approval");
|
||||
if (approvalId) {
|
||||
approval.show(approvalId);
|
||||
approval.show(parseInt(approvalId, 10));
|
||||
showView("approve-site");
|
||||
return;
|
||||
}
|
||||
@@ -214,7 +212,6 @@ async function init() {
|
||||
importKey.init(ctx);
|
||||
home.init(ctx);
|
||||
addressDetail.init(ctx);
|
||||
showPrivateKey.init(ctx);
|
||||
addressToken.init(ctx);
|
||||
send.init(ctx);
|
||||
confirmTx.init(ctx);
|
||||
|
||||
@@ -4,7 +4,6 @@ const {
|
||||
showFlash,
|
||||
balanceLinesForAddress,
|
||||
addressDotHtml,
|
||||
addressTitle,
|
||||
escapeHtml,
|
||||
truncateMiddle,
|
||||
} = require("./helpers");
|
||||
@@ -151,11 +150,11 @@ async function loadTransactions(address) {
|
||||
|
||||
loadedTxs = txs;
|
||||
|
||||
// Collect ALL unique addresses (from + to) for ENS resolution so
|
||||
// that reverse lookups work for every displayed address, not just
|
||||
// the ones that were originally entered as ENS names.
|
||||
// Collect unique counterparty addresses for ENS resolution.
|
||||
const counterparties = [
|
||||
...new Set(txs.flatMap((tx) => [tx.from, tx.to].filter(Boolean))),
|
||||
...new Set(
|
||||
txs.map((tx) => (tx.direction === "sent" ? tx.to : tx.from)),
|
||||
),
|
||||
];
|
||||
if (counterparties.length > 0) {
|
||||
try {
|
||||
@@ -186,19 +185,14 @@ function renderTransactions(txs) {
|
||||
let html = "";
|
||||
let i = 0;
|
||||
for (const tx of txs) {
|
||||
const counterparty =
|
||||
tx.direction === "sent" || tx.direction === "contract"
|
||||
? tx.to
|
||||
: tx.from;
|
||||
const counterparty = tx.direction === "sent" ? tx.to : tx.from;
|
||||
const ensName = ensNameMap.get(counterparty) || null;
|
||||
const title = addressTitle(counterparty, state.wallets);
|
||||
const dirLabel = tx.directionLabel;
|
||||
const amountStr = tx.value
|
||||
? escapeHtml(tx.value + " " + tx.symbol)
|
||||
: escapeHtml(tx.symbol);
|
||||
const maxAddr = Math.max(32, 36 - Math.max(0, amountStr.length - 10));
|
||||
const displayAddr =
|
||||
title || ensName || truncateMiddle(counterparty, maxAddr);
|
||||
const displayAddr = ensName || truncateMiddle(counterparty, maxAddr);
|
||||
const addrStr = escapeHtml(displayAddr);
|
||||
const dot = addressDotHtml(counterparty);
|
||||
const err = tx.isError ? " (failed)" : "";
|
||||
@@ -261,10 +255,6 @@ function init(_ctx) {
|
||||
});
|
||||
|
||||
$("btn-add-token").addEventListener("click", ctx.showAddTokenView);
|
||||
|
||||
$("btn-show-private-key").addEventListener("click", () => {
|
||||
ctx.showPrivateKey();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { init, show };
|
||||
|
||||
@@ -6,13 +6,11 @@ const {
|
||||
showView,
|
||||
showFlash,
|
||||
addressDotHtml,
|
||||
addressTitle,
|
||||
escapeHtml,
|
||||
truncateMiddle,
|
||||
balanceLine,
|
||||
} = require("./helpers");
|
||||
const { state, currentAddress, saveState } = require("../../shared/state");
|
||||
const { TOKEN_BY_ADDRESS } = require("../../shared/tokenList");
|
||||
const {
|
||||
formatUsd,
|
||||
getPrice,
|
||||
@@ -26,6 +24,7 @@ const { resolveEnsNames } = require("../../shared/ens");
|
||||
const { updateSendBalance, renderSendTokenSelect } = require("./send");
|
||||
const { log } = require("../../shared/log");
|
||||
const makeBlockie = require("ethereum-blockies-base64");
|
||||
const { TOKEN_BY_ADDRESS } = require("../../shared/tokenList");
|
||||
|
||||
let ctx;
|
||||
|
||||
@@ -214,10 +213,11 @@ async function loadTransactions(address, tokenId) {
|
||||
|
||||
loadedTxs = txs;
|
||||
|
||||
// Collect ALL unique addresses for ENS resolution so reverse
|
||||
// lookups work for every displayed address.
|
||||
// Collect unique counterparty addresses for ENS resolution
|
||||
const counterparties = [
|
||||
...new Set(txs.flatMap((tx) => [tx.from, tx.to].filter(Boolean))),
|
||||
...new Set(
|
||||
txs.map((tx) => (tx.direction === "sent" ? tx.to : tx.from)),
|
||||
),
|
||||
];
|
||||
if (counterparties.length > 0) {
|
||||
try {
|
||||
@@ -250,14 +250,12 @@ function renderTransactions(txs) {
|
||||
for (const tx of txs) {
|
||||
const counterparty = tx.direction === "sent" ? tx.to : tx.from;
|
||||
const ensName = ensNameMap.get(counterparty) || null;
|
||||
const title = addressTitle(counterparty, state.wallets);
|
||||
const dirLabel = tx.directionLabel;
|
||||
const amountStr = tx.value
|
||||
? escapeHtml(tx.value + " " + tx.symbol)
|
||||
: escapeHtml(tx.symbol);
|
||||
const maxAddr = Math.max(32, 36 - Math.max(0, amountStr.length - 10));
|
||||
const displayAddr =
|
||||
title || ensName || truncateMiddle(counterparty, maxAddr);
|
||||
const displayAddr = ensName || truncateMiddle(counterparty, maxAddr);
|
||||
const addrStr = escapeHtml(displayAddr);
|
||||
const dot = addressDotHtml(counterparty);
|
||||
const err = tx.isError ? " (failed)" : "";
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
const {
|
||||
$,
|
||||
addressDotHtml,
|
||||
addressTitle,
|
||||
escapeHtml,
|
||||
showView,
|
||||
} = require("./helpers");
|
||||
const { $, addressDotHtml, escapeHtml, showView } = require("./helpers");
|
||||
const { state, saveState } = require("../../shared/state");
|
||||
const { formatEther, formatUnits, Interface, toUtf8String } = require("ethers");
|
||||
const { ERC20_ABI } = require("../../shared/constants");
|
||||
@@ -28,15 +22,7 @@ function approvalAddressHtml(address) {
|
||||
const dot = addressDotHtml(address);
|
||||
const link = `https://etherscan.io/address/${address}`;
|
||||
const extLink = `<a href="${link}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>`;
|
||||
const title = addressTitle(address, state.wallets);
|
||||
let html = "";
|
||||
if (title) {
|
||||
html += `<div class="flex items-center font-bold">${dot}${escapeHtml(title)}</div>`;
|
||||
html += `<div class="break-all">${escapeHtml(address)}${extLink}</div>`;
|
||||
} else {
|
||||
html += `<div class="flex items-center">${dot}<span class="break-all">${escapeHtml(address)}</span>${extLink}</div>`;
|
||||
}
|
||||
return html;
|
||||
return `<div class="flex items-center">${dot}<span class="break-all">${escapeHtml(address)}</span>${extLink}</div>`;
|
||||
}
|
||||
|
||||
function formatTxValue(val) {
|
||||
@@ -467,4 +453,4 @@ function init(ctx) {
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { init, show, decodeCalldata };
|
||||
module.exports = { init, show };
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
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 };
|
||||
@@ -16,7 +16,6 @@ const VIEWS = [
|
||||
"import-key",
|
||||
"main",
|
||||
"address",
|
||||
"show-private-key",
|
||||
"address-token",
|
||||
"send",
|
||||
"confirm-tx",
|
||||
@@ -26,7 +25,6 @@ const VIEWS = [
|
||||
"receive",
|
||||
"add-token",
|
||||
"settings",
|
||||
"delete-wallet-confirm",
|
||||
"settings-addtoken",
|
||||
"transaction",
|
||||
"approve-site",
|
||||
|
||||
@@ -6,7 +6,6 @@ const {
|
||||
isoDate,
|
||||
timeAgo,
|
||||
addressDotHtml,
|
||||
addressTitle,
|
||||
escapeHtml,
|
||||
truncateMiddle,
|
||||
} = require("./helpers");
|
||||
@@ -103,17 +102,13 @@ function renderHomeTxList(ctx) {
|
||||
let html = "";
|
||||
let i = 0;
|
||||
for (const tx of homeTxs) {
|
||||
const counterparty =
|
||||
tx.direction === "sent" || tx.direction === "contract"
|
||||
? tx.to
|
||||
: tx.from;
|
||||
const counterparty = tx.direction === "sent" ? tx.to : tx.from;
|
||||
const dirLabel = tx.directionLabel;
|
||||
const amountStr = tx.value
|
||||
? escapeHtml(tx.value + " " + tx.symbol)
|
||||
: escapeHtml(tx.symbol);
|
||||
const title = addressTitle(counterparty, state.wallets);
|
||||
const maxAddr = Math.max(32, 36 - Math.max(0, amountStr.length - 10));
|
||||
const displayAddr = title || truncateMiddle(counterparty, maxAddr);
|
||||
const displayAddr = truncateMiddle(counterparty, maxAddr);
|
||||
const addrStr = escapeHtml(displayAddr);
|
||||
const dot = addressDotHtml(counterparty);
|
||||
const err = tx.isError ? " (failed)" : "";
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
// Send view: collect To, Amount, Token. Then go to confirmation.
|
||||
|
||||
const {
|
||||
$,
|
||||
showFlash,
|
||||
addressDotHtml,
|
||||
addressTitle,
|
||||
escapeHtml,
|
||||
} = require("./helpers");
|
||||
const { $, showFlash, addressDotHtml, escapeHtml } = require("./helpers");
|
||||
const { state, currentAddress } = require("../../shared/state");
|
||||
let ctx;
|
||||
const { getProvider } = require("../../shared/balances");
|
||||
@@ -50,15 +44,8 @@ function updateSendBalance() {
|
||||
const dot = addressDotHtml(addr.address);
|
||||
const link = `https://etherscan.io/address/${addr.address}`;
|
||||
const extLink = `<a href="${link}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>`;
|
||||
const title = addressTitle(addr.address, state.wallets);
|
||||
let fromHtml = "";
|
||||
if (title) {
|
||||
fromHtml += `<div class="flex items-center font-bold">${dot}${escapeHtml(title)}</div>`;
|
||||
if (addr.ensName) {
|
||||
fromHtml += `<div>${escapeHtml(addr.ensName)}</div>`;
|
||||
}
|
||||
fromHtml += `<div class="break-all">${escapeHtml(addr.address)}${extLink}</div>`;
|
||||
} else if (addr.ensName) {
|
||||
if (addr.ensName) {
|
||||
fromHtml += `<div class="flex items-center font-bold">${dot}${escapeHtml(addr.ensName)}</div>`;
|
||||
fromHtml += `<div class="break-all">${escapeHtml(addr.address)}${extLink}</div>`;
|
||||
} else {
|
||||
|
||||
@@ -2,7 +2,6 @@ const { $, showView, showFlash, escapeHtml } = require("./helpers");
|
||||
const { state, saveState } = require("../../shared/state");
|
||||
const { ETHEREUM_MAINNET_CHAIN_ID } = require("../../shared/constants");
|
||||
const { log, debugFetch } = require("../../shared/log");
|
||||
const deleteWallet = require("./deleteWallet");
|
||||
|
||||
const runtime =
|
||||
typeof browser !== "undefined" ? browser.runtime : chrome.runtime;
|
||||
@@ -66,68 +65,11 @@ 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() {
|
||||
$("settings-rpc").value = state.rpcUrl;
|
||||
$("settings-blockscout").value = state.blockscoutUrl;
|
||||
renderTrackedTokens();
|
||||
renderSiteLists();
|
||||
renderWalletListSettings();
|
||||
|
||||
showView("settings");
|
||||
}
|
||||
|
||||
@@ -141,8 +83,6 @@ function renderSiteLists() {
|
||||
}
|
||||
|
||||
function init(ctx) {
|
||||
deleteWallet.init(ctx);
|
||||
|
||||
$("btn-save-rpc").addEventListener("click", async () => {
|
||||
const url = $("settings-rpc").value.trim();
|
||||
if (!url) {
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
const { $, showView, showFlash, showError, hideError } = require("./helpers");
|
||||
const { state } = require("../../shared/state");
|
||||
const { decryptWithPassword } = require("../../shared/vault");
|
||||
const { getPrivateKeyForAddress } = require("../../shared/wallet");
|
||||
|
||||
let ctx;
|
||||
let revealed = false;
|
||||
|
||||
function show() {
|
||||
revealed = false;
|
||||
$("show-pk-password").value = "";
|
||||
$("show-pk-key-well").classList.add("hidden");
|
||||
$("show-pk-key-value").textContent = "";
|
||||
$("show-pk-prompt").classList.remove("hidden");
|
||||
hideError("show-pk-error");
|
||||
showView("show-private-key");
|
||||
}
|
||||
|
||||
function init(_ctx) {
|
||||
ctx = _ctx;
|
||||
|
||||
$("btn-show-pk-back").addEventListener("click", () => {
|
||||
clearKey();
|
||||
ctx.showAddressDetail();
|
||||
});
|
||||
|
||||
$("btn-show-pk-reveal").addEventListener("click", async () => {
|
||||
const pw = $("show-pk-password").value;
|
||||
if (!pw) {
|
||||
showError("show-pk-error", "Please enter your password.");
|
||||
return;
|
||||
}
|
||||
|
||||
const wallet = state.wallets[state.selectedWallet];
|
||||
let decryptedSecret;
|
||||
try {
|
||||
decryptedSecret = await decryptWithPassword(
|
||||
wallet.encryptedSecret,
|
||||
pw,
|
||||
);
|
||||
} catch (_e) {
|
||||
showError("show-pk-error", "Wrong password.");
|
||||
return;
|
||||
}
|
||||
|
||||
const privateKey = getPrivateKeyForAddress(
|
||||
wallet,
|
||||
state.selectedAddress,
|
||||
decryptedSecret,
|
||||
);
|
||||
|
||||
revealed = true;
|
||||
$("show-pk-prompt").classList.add("hidden");
|
||||
$("show-pk-key-well").classList.remove("hidden");
|
||||
$("show-pk-key-value").textContent = privateKey;
|
||||
hideError("show-pk-error");
|
||||
});
|
||||
|
||||
$("btn-show-pk-copy").addEventListener("click", () => {
|
||||
const key = $("show-pk-key-value").textContent;
|
||||
if (key) {
|
||||
navigator.clipboard.writeText(key);
|
||||
showFlash("Copied!");
|
||||
}
|
||||
});
|
||||
|
||||
$("btn-show-pk-done").addEventListener("click", () => {
|
||||
clearKey();
|
||||
ctx.showAddressDetail();
|
||||
});
|
||||
}
|
||||
|
||||
function clearKey() {
|
||||
revealed = false;
|
||||
$("show-pk-key-value").textContent = "";
|
||||
$("show-pk-password").value = "";
|
||||
}
|
||||
|
||||
module.exports = { init, show };
|
||||
@@ -13,8 +13,6 @@ const {
|
||||
} = require("./helpers");
|
||||
const { state } = require("../../shared/state");
|
||||
const makeBlockie = require("ethereum-blockies-base64");
|
||||
const { log, debugFetch } = require("../../shared/log");
|
||||
const { decodeCalldata } = require("./approval");
|
||||
|
||||
const EXT_ICON =
|
||||
`<span style="display:inline-block;width:10px;height:10px;margin-left:4px;vertical-align:middle">` +
|
||||
@@ -44,11 +42,11 @@ function txAddressHtml(address, ensName, title) {
|
||||
const extLink = `<a href="${link}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>`;
|
||||
let html = `<div class="mb-1">${blockie}</div>`;
|
||||
if (title) {
|
||||
html += `<div class="font-bold">${escapeHtml(title)}</div>`;
|
||||
html += `<div class="flex items-center font-bold">${dot}${escapeHtml(title)}</div>`;
|
||||
}
|
||||
if (ensName) {
|
||||
html +=
|
||||
`<div class="flex items-center">${dot}` +
|
||||
`<div class="flex items-center">${title ? "" : dot}` +
|
||||
copyableHtml(ensName, "") +
|
||||
extLink +
|
||||
`</div>` +
|
||||
@@ -57,7 +55,7 @@ function txAddressHtml(address, ensName, title) {
|
||||
`</div>`;
|
||||
} else {
|
||||
html +=
|
||||
`<div class="flex items-center">${dot}` +
|
||||
`<div class="flex items-center">${title ? "" : dot}` +
|
||||
copyableHtml(address, "break-all") +
|
||||
extLink +
|
||||
`</div>`;
|
||||
@@ -87,15 +85,9 @@ function show(tx) {
|
||||
fromEns: tx.fromEns || null,
|
||||
toEns: tx.toEns || null,
|
||||
directionLabel: tx.directionLabel || null,
|
||||
direction: tx.direction || null,
|
||||
isContractCall: tx.isContractCall || false,
|
||||
method: tx.method || null,
|
||||
},
|
||||
};
|
||||
render();
|
||||
if (tx.isContractCall || tx.direction === "contract") {
|
||||
loadCalldata(tx.hash, tx.to);
|
||||
}
|
||||
}
|
||||
|
||||
function render() {
|
||||
@@ -129,25 +121,6 @@ function render() {
|
||||
nativeEl.parentElement.classList.add("hidden");
|
||||
}
|
||||
|
||||
// Show type label for contract interactions (Swap, Execute, etc.)
|
||||
const typeSection = $("tx-detail-type-section");
|
||||
const typeEl = $("tx-detail-type");
|
||||
const headingEl = $("tx-detail-heading");
|
||||
if (tx.direction === "contract" && tx.directionLabel) {
|
||||
if (typeSection) {
|
||||
typeEl.textContent = tx.directionLabel;
|
||||
typeSection.classList.remove("hidden");
|
||||
}
|
||||
if (headingEl) headingEl.textContent = tx.directionLabel;
|
||||
} else {
|
||||
if (typeSection) typeSection.classList.add("hidden");
|
||||
if (headingEl) headingEl.textContent = "Transaction";
|
||||
}
|
||||
|
||||
// Hide calldata section by default; loadCalldata will show it if needed
|
||||
const calldataSection = $("tx-detail-calldata-section");
|
||||
if (calldataSection) calldataSection.classList.add("hidden");
|
||||
|
||||
$("tx-detail-time").textContent =
|
||||
isoDate(tx.timestamp) + " (" + timeAgo(tx.timestamp) + ")";
|
||||
$("tx-detail-status").textContent = tx.isError ? "Failed" : "Success";
|
||||
@@ -164,73 +137,6 @@ function render() {
|
||||
});
|
||||
}
|
||||
|
||||
async function loadCalldata(txHash, toAddress) {
|
||||
const section = $("tx-detail-calldata-section");
|
||||
const actionEl = $("tx-detail-calldata-action");
|
||||
const detailsEl = $("tx-detail-calldata-details");
|
||||
const wellEl = $("tx-detail-calldata-well");
|
||||
const rawSection = $("tx-detail-rawdata-section");
|
||||
const rawEl = $("tx-detail-rawdata");
|
||||
if (!section || !actionEl || !detailsEl) return;
|
||||
|
||||
try {
|
||||
const resp = await debugFetch(
|
||||
state.blockscoutUrl + "/transactions/" + txHash,
|
||||
);
|
||||
if (!resp.ok) return;
|
||||
const txData = await resp.json();
|
||||
const inputData = txData.raw_input || txData.input || null;
|
||||
if (!inputData || inputData === "0x") return;
|
||||
|
||||
const decoded = decodeCalldata(inputData, toAddress || "");
|
||||
if (decoded) {
|
||||
// Render decoded calldata matching approval view style
|
||||
actionEl.textContent = decoded.name;
|
||||
let detailsHtml = "";
|
||||
if (decoded.description) {
|
||||
detailsHtml += `<div class="mb-2">${escapeHtml(decoded.description)}</div>`;
|
||||
}
|
||||
for (const d of decoded.details || []) {
|
||||
detailsHtml += `<div class="mb-2">`;
|
||||
detailsHtml += `<div class="text-muted">${escapeHtml(d.label)}</div>`;
|
||||
if (d.address) {
|
||||
const dot = addressDotHtml(d.address);
|
||||
detailsHtml += `<div>${dot}${copyableHtml(d.value, "break-all")}</div>`;
|
||||
} else {
|
||||
detailsHtml += `<div class="font-bold">${escapeHtml(d.value)}</div>`;
|
||||
}
|
||||
detailsHtml += `</div>`;
|
||||
}
|
||||
detailsEl.innerHTML = detailsHtml;
|
||||
if (wellEl) wellEl.classList.remove("hidden");
|
||||
} else {
|
||||
// Unknown contract call — show method name in well
|
||||
const method = txData.method || "Unknown contract call";
|
||||
actionEl.textContent = method;
|
||||
detailsEl.innerHTML = "";
|
||||
if (wellEl) wellEl.classList.remove("hidden");
|
||||
}
|
||||
|
||||
// Always show raw data
|
||||
if (rawSection && rawEl) {
|
||||
rawEl.innerHTML = copyableHtml(inputData, "break-all");
|
||||
rawSection.classList.remove("hidden");
|
||||
}
|
||||
|
||||
section.classList.remove("hidden");
|
||||
|
||||
// Bind copy handlers for new elements
|
||||
section.querySelectorAll("[data-copy]").forEach((el) => {
|
||||
el.onclick = () => {
|
||||
navigator.clipboard.writeText(el.dataset.copy);
|
||||
showFlash("Copied!");
|
||||
};
|
||||
});
|
||||
} catch (e) {
|
||||
log.errorf("loadCalldata failed:", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
function init(_ctx) {
|
||||
ctx = _ctx;
|
||||
$("btn-tx-back").addEventListener("click", () => {
|
||||
|
||||
@@ -5,7 +5,6 @@ const {
|
||||
showView,
|
||||
showFlash,
|
||||
addressDotHtml,
|
||||
addressTitle,
|
||||
escapeHtml,
|
||||
} = require("./helpers");
|
||||
const { state, saveState } = require("../../shared/state");
|
||||
@@ -38,13 +37,6 @@ function toAddressHtml(address) {
|
||||
const dot = addressDotHtml(address);
|
||||
const link = `https://etherscan.io/address/${address}`;
|
||||
const extLink = `<a href="${link}" target="_blank" rel="noopener" class="inline-flex items-center">${EXT_ICON}</a>`;
|
||||
const title = addressTitle(address, state.wallets);
|
||||
if (title) {
|
||||
return (
|
||||
`<div class="flex items-center font-bold">${dot}${escapeHtml(title)}</div>` +
|
||||
`<div class="break-all">${escapeHtml(address)}${extLink}</div>`
|
||||
);
|
||||
}
|
||||
return `<div class="flex items-center">${dot}<span class="break-all">${escapeHtml(address)}</span>${extLink}</div>`;
|
||||
}
|
||||
|
||||
|
||||
@@ -123,27 +123,15 @@ async function refreshBalances(wallets, rpcUrl, blockscoutUrl, trackedTokens) {
|
||||
}),
|
||||
);
|
||||
|
||||
// ENS reverse lookup — only overwrite on success so that
|
||||
// transient RPC errors don't wipe a previously resolved name.
|
||||
// ENS reverse lookup
|
||||
updates.push(
|
||||
provider
|
||||
.lookupAddress(addr.address)
|
||||
.then((name) => {
|
||||
addr.ensName = name || null;
|
||||
log.debugf(
|
||||
"ENS reverse",
|
||||
addr.address,
|
||||
"->",
|
||||
addr.ensName,
|
||||
);
|
||||
})
|
||||
.catch((e) => {
|
||||
log.errorf(
|
||||
"ENS reverse failed",
|
||||
addr.address,
|
||||
e.message,
|
||||
);
|
||||
// Keep existing addr.ensName if we had one
|
||||
.catch(() => {
|
||||
addr.ensName = null;
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -204,10 +192,6 @@ async function lookupTokenInfo(contractAddress, rpcUrl) {
|
||||
name = symbol;
|
||||
}
|
||||
|
||||
// Truncate to prevent storage of excessively long values from RPC
|
||||
name = String(name).slice(0, 64);
|
||||
symbol = String(symbol).slice(0, 12);
|
||||
|
||||
log.infof("Token resolved:", symbol, "decimals", Number(decimals));
|
||||
return { name, symbol, decimals: Number(decimals) };
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ async function resolveEnsName(address, rpcUrl) {
|
||||
return name;
|
||||
} catch (e) {
|
||||
log.errorf("ENS reverse lookup failed", address, e.message);
|
||||
// Don't cache failures — let subsequent lookups retry
|
||||
setCache(address, null);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,21 +37,7 @@ function parseTx(tx, addrLower) {
|
||||
if (token) {
|
||||
symbol = token.symbol;
|
||||
}
|
||||
// Map known DEX methods to "Swap" for cleaner display
|
||||
const SWAP_METHODS = new Set([
|
||||
"execute",
|
||||
"swap",
|
||||
"swapExactTokensForTokens",
|
||||
"swapTokensForExactTokens",
|
||||
"swapExactETHForTokens",
|
||||
"swapTokensForExactETH",
|
||||
"swapExactTokensForETH",
|
||||
"swapETHForExactTokens",
|
||||
"multicall",
|
||||
]);
|
||||
const label = SWAP_METHODS.has(method)
|
||||
? "Swap"
|
||||
: method.charAt(0).toUpperCase() + method.slice(1);
|
||||
const label = method.charAt(0).toUpperCase() + method.slice(1);
|
||||
direction = "contract";
|
||||
directionLabel = label;
|
||||
value = "";
|
||||
@@ -153,18 +139,9 @@ async function fetchRecentTransactions(address, blockscoutUrl, count = 25) {
|
||||
|
||||
// When a token transfer shares a hash with a normal tx, the normal tx
|
||||
// is the contract call (0 ETH) and the token transfer has the real
|
||||
// amount and symbol. Replace the normal tx with the token transfer,
|
||||
// but preserve contract call metadata (direction, label, method) so
|
||||
// swaps and other contract interactions display correctly.
|
||||
// amount and symbol. Replace the normal tx with the token transfer.
|
||||
for (const tt of ttJson.items || []) {
|
||||
const parsed = parseTokenTransfer(tt, addrLower);
|
||||
const existing = txsByHash.get(parsed.hash);
|
||||
if (existing && existing.direction === "contract") {
|
||||
parsed.direction = "contract";
|
||||
parsed.directionLabel = existing.directionLabel;
|
||||
parsed.isContractCall = true;
|
||||
parsed.method = existing.method;
|
||||
}
|
||||
txsByHash.set(parsed.hash, parsed);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,18 +41,6 @@ function getSignerForAddress(walletData, addrIndex, decryptedSecret) {
|
||||
return new Wallet(decryptedSecret);
|
||||
}
|
||||
|
||||
function getPrivateKeyForAddress(walletData, addrIndex, decryptedSecret) {
|
||||
if (walletData.type === "hd") {
|
||||
const node = HDNodeWallet.fromPhrase(
|
||||
decryptedSecret,
|
||||
"",
|
||||
BIP44_ETH_PATH,
|
||||
);
|
||||
return node.deriveChild(addrIndex).privateKey;
|
||||
}
|
||||
return decryptedSecret;
|
||||
}
|
||||
|
||||
function isValidMnemonic(mnemonic) {
|
||||
return Mnemonic.isValidMnemonic(mnemonic);
|
||||
}
|
||||
@@ -63,6 +51,5 @@ module.exports = {
|
||||
hdWalletFromMnemonic,
|
||||
addressFromPrivateKey,
|
||||
getSignerForAddress,
|
||||
getPrivateKeyForAddress,
|
||||
isValidMnemonic,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user