All checks were successful
check / check (push) Successful in 28s
An xprv wallet imported before non-master keys were refused holds a key whose depth is greater than zero. Its addresses were derived by applying the Ethereum path beneath that key, so they are not the addresses the key produces under the standard path, and signing for them now throws — on the send screen, with no explanation. Detect it at wallet-list render time instead. An xprv wallet stores the neutered node four levels below the imported key, so a master import stores a depth-4 xpub and a depth-d import stores depth d + 4; the stored xpub is therefore an exact read on the imported key's depth and needs no password. The wallet list renders a named explanation under the wallet's name, the "+" button is withheld, and send, dapp transaction approval, dapp message signing and private-key export all refuse before asking for a password. getSignerForAddress remains the backstop and now says why in a sentence. The copy states what is true and nothing more: the addresses do descend from the key that was imported, so it neither promises the funds are safe nor implies anything was lost. The wallet is not deleted or rewritten.
699 lines
24 KiB
JavaScript
699 lines
24 KiB
JavaScript
const {
|
|
$,
|
|
addressTitle,
|
|
escapeHtml,
|
|
showView,
|
|
showError,
|
|
hideError,
|
|
renderAddressHtml,
|
|
attachCopyHandlers,
|
|
} = require("./helpers");
|
|
const { state, saveState, currentNetwork } = require("../../shared/state");
|
|
const {
|
|
formatEther,
|
|
formatUnits,
|
|
getBytes,
|
|
Interface,
|
|
toUtf8String,
|
|
} = require("ethers");
|
|
const { getPrice, formatUsd } = require("../../shared/prices");
|
|
const { ERC20_ABI } = require("../../shared/constants");
|
|
const { TOKEN_BY_ADDRESS } = require("../../shared/tokenList");
|
|
const { decryptWithPassword } = require("../../shared/vault");
|
|
const { getSignerForAddress } = require("../../shared/wallet");
|
|
const { walletDefect } = require("../../shared/walletDefects");
|
|
const { getProvider } = require("../../shared/balances");
|
|
const txStatus = require("./txStatus");
|
|
const uniswap = require("../../shared/uniswap");
|
|
const runtime =
|
|
typeof browser !== "undefined" ? browser.runtime : chrome.runtime;
|
|
|
|
const erc20Iface = new Interface(ERC20_ABI);
|
|
|
|
function approvalAddressHtml(address) {
|
|
const title = addressTitle(address, state.wallets);
|
|
return renderAddressHtml(address, { title });
|
|
}
|
|
|
|
function formatTxValue(val) {
|
|
const parts = val.split(".");
|
|
if (parts.length === 1) return val + ".0000";
|
|
const dec = (parts[1] + "0000").slice(0, 4);
|
|
return parts[0] + "." + dec;
|
|
}
|
|
|
|
function tokenLabel(address) {
|
|
const t = TOKEN_BY_ADDRESS.get(address.toLowerCase());
|
|
return t ? t.symbol : null;
|
|
}
|
|
|
|
// Try to decode calldata using known ABIs.
|
|
// Returns { name, description, details } or null.
|
|
function decodeCalldata(data, toAddress) {
|
|
if (!data || data === "0x" || data.length < 10) return null;
|
|
|
|
// Try ERC-20 (approve / transfer)
|
|
try {
|
|
const parsed = erc20Iface.parseTransaction({ data });
|
|
if (parsed) {
|
|
const token = TOKEN_BY_ADDRESS.get(toAddress.toLowerCase());
|
|
const tokenSymbol = token ? token.symbol : null;
|
|
const tokenDecimals = token ? token.decimals : 18;
|
|
const contractLabel = tokenSymbol
|
|
? tokenSymbol + " (" + toAddress + ")"
|
|
: toAddress;
|
|
|
|
if (parsed.name === "approve") {
|
|
const spender = parsed.args[0];
|
|
const rawAmount = parsed.args[1];
|
|
const maxUint = BigInt(
|
|
"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
|
);
|
|
const isUnlimited = rawAmount === maxUint;
|
|
const amountRaw = isUnlimited
|
|
? "Unlimited"
|
|
: formatTxValue(formatUnits(rawAmount, tokenDecimals));
|
|
const amountStr = isUnlimited
|
|
? "Unlimited"
|
|
: amountRaw + (tokenSymbol ? " " + tokenSymbol : "");
|
|
|
|
return {
|
|
name: "Token Approval",
|
|
description: tokenSymbol
|
|
? "Approve spending of your " + tokenSymbol
|
|
: "Approve spending of an ERC-20 token",
|
|
details: [
|
|
{
|
|
label: "Token",
|
|
value: contractLabel,
|
|
address: toAddress,
|
|
isToken: true,
|
|
},
|
|
{
|
|
label: "Spender",
|
|
value: spender,
|
|
address: spender,
|
|
},
|
|
{
|
|
label: "Amount",
|
|
value: amountStr,
|
|
rawValue: amountRaw,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
if (parsed.name === "transfer") {
|
|
const to = parsed.args[0];
|
|
const rawAmount = parsed.args[1];
|
|
const amountRaw = formatTxValue(
|
|
formatUnits(rawAmount, tokenDecimals),
|
|
);
|
|
const amountStr =
|
|
amountRaw + (tokenSymbol ? " " + tokenSymbol : "");
|
|
|
|
return {
|
|
name: "Token Transfer",
|
|
description: tokenSymbol
|
|
? "Transfer " + tokenSymbol
|
|
: "Transfer ERC-20 token",
|
|
details: [
|
|
{
|
|
label: "Token",
|
|
value: contractLabel,
|
|
address: toAddress,
|
|
isToken: true,
|
|
},
|
|
{ label: "Recipient", value: to, address: to },
|
|
{
|
|
label: "Amount",
|
|
value: amountStr,
|
|
rawValue: amountRaw,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
}
|
|
} catch {
|
|
// Not ERC-20 — fall through
|
|
}
|
|
|
|
// Try Uniswap Universal Router
|
|
const routerResult = uniswap.decode(data, toAddress);
|
|
if (routerResult) return routerResult;
|
|
|
|
return null;
|
|
}
|
|
|
|
function showPhishingWarning(elementId, isPhishing) {
|
|
const el = $(elementId);
|
|
if (!el) return;
|
|
// The background script performs the authoritative phishing domain check
|
|
// and passes the result via the isPhishingDomain flag.
|
|
if (isPhishing) {
|
|
el.classList.remove("hidden");
|
|
} else {
|
|
el.classList.add("hidden");
|
|
}
|
|
}
|
|
|
|
function showTxApproval(details) {
|
|
showPhishingWarning(
|
|
"approve-tx-phishing-warning",
|
|
details.isPhishingDomain,
|
|
);
|
|
|
|
pendingTxParams = details.txParams;
|
|
|
|
const toAddr = details.txParams.to;
|
|
const token = toAddr ? TOKEN_BY_ADDRESS.get(toAddr.toLowerCase()) : null;
|
|
const ethValue = formatEther(details.txParams.value || "0");
|
|
|
|
// Build txInfo for status screens
|
|
pendingTxDetails = {
|
|
from: state.activeAddress,
|
|
to: toAddr || "",
|
|
amount: formatTxValue(ethValue),
|
|
token: "ETH",
|
|
tokenSymbol: token ? token.symbol : null,
|
|
};
|
|
|
|
// If this is an ERC-20 call, try to extract the real recipient and amount
|
|
const decoded = decodeCalldata(details.txParams.data, toAddr || "");
|
|
if (decoded && decoded.details) {
|
|
let decodedTokenAddr = null;
|
|
let decodedTokenSymbol = null;
|
|
for (const d of decoded.details) {
|
|
if (d.label === "Recipient" && d.address) {
|
|
pendingTxDetails.to = d.address;
|
|
}
|
|
if (d.label === "Amount") {
|
|
pendingTxDetails.amount = d.rawValue || d.value;
|
|
}
|
|
if (d.label === "Token In" && d.isToken && d.address) {
|
|
const t = TOKEN_BY_ADDRESS.get(d.address.toLowerCase());
|
|
if (t) {
|
|
decodedTokenAddr = d.address;
|
|
decodedTokenSymbol = t.symbol;
|
|
}
|
|
}
|
|
}
|
|
if (token) {
|
|
pendingTxDetails.token = toAddr;
|
|
pendingTxDetails.tokenSymbol = token.symbol;
|
|
} else if (decodedTokenAddr) {
|
|
pendingTxDetails.token = decodedTokenAddr;
|
|
pendingTxDetails.tokenSymbol = decodedTokenSymbol;
|
|
}
|
|
}
|
|
|
|
// Carry decoded calldata info through to success/error views
|
|
if (decoded) {
|
|
pendingTxDetails.decoded = {
|
|
name: decoded.name,
|
|
description: decoded.description,
|
|
details: decoded.details,
|
|
};
|
|
}
|
|
|
|
$("approve-tx-hostname").textContent = details.hostname;
|
|
$("approve-tx-from").innerHTML = approvalAddressHtml(state.activeAddress);
|
|
|
|
// Show token symbol next to contract address if known
|
|
const symbol = toAddr ? tokenLabel(toAddr) : null;
|
|
if (toAddr) {
|
|
let toHtml = "";
|
|
if (symbol) {
|
|
toHtml += `<div class="font-bold mb-1">${escapeHtml(symbol)}</div>`;
|
|
}
|
|
toHtml += approvalAddressHtml(toAddr);
|
|
$("approve-tx-to").innerHTML = toHtml;
|
|
} else {
|
|
$("approve-tx-to").innerHTML = escapeHtml("(contract creation)");
|
|
}
|
|
|
|
const ethValueFormatted = formatTxValue(
|
|
formatEther(details.txParams.value || "0"),
|
|
);
|
|
const ethPrice = getPrice("ETH");
|
|
const ethUsd = ethPrice ? parseFloat(ethValueFormatted) * ethPrice : null;
|
|
const usdStr = formatUsd(ethUsd);
|
|
$("approve-tx-value").textContent =
|
|
ethValueFormatted + " ETH" + (usdStr ? " (" + usdStr + ")" : "");
|
|
|
|
// Decode calldata (reuse decoded from above)
|
|
const decodedEl = $("approve-tx-decoded");
|
|
if (decoded) {
|
|
$("approve-tx-action").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) {
|
|
if (d.isToken) {
|
|
detailsHtml += `<div class="font-bold">${escapeHtml(tokenLabel(d.address) || "Unknown token")}</div>`;
|
|
}
|
|
detailsHtml += approvalAddressHtml(d.address);
|
|
} else {
|
|
detailsHtml += `<div class="font-bold">${escapeHtml(d.value)}</div>`;
|
|
}
|
|
detailsHtml += `</div>`;
|
|
}
|
|
$("approve-tx-decoded-details").innerHTML = detailsHtml;
|
|
decodedEl.classList.remove("hidden");
|
|
} else {
|
|
decodedEl.classList.add("hidden");
|
|
}
|
|
|
|
// Always show raw data when present
|
|
if (details.txParams.data && details.txParams.data !== "0x") {
|
|
$("approve-tx-data").textContent = details.txParams.data;
|
|
$("approve-tx-data-section").classList.remove("hidden");
|
|
} else {
|
|
$("approve-tx-data-section").classList.add("hidden");
|
|
}
|
|
|
|
$("approve-tx-password").value = "";
|
|
hideError("approve-tx-error");
|
|
|
|
showView("approve-tx");
|
|
attachCopyHandlers("view-approve-tx");
|
|
gateOnWalletDefect("approve-tx-error", "btn-approve-tx");
|
|
}
|
|
|
|
function decodeHexMessage(hex) {
|
|
try {
|
|
const bytes = Uint8Array.from(
|
|
hex
|
|
.slice(2)
|
|
.match(/.{1,2}/g)
|
|
.map((b) => parseInt(b, 16)),
|
|
);
|
|
return toUtf8String(bytes);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function formatTypedDataHtml(jsonStr) {
|
|
try {
|
|
const data = JSON.parse(jsonStr);
|
|
let html = "";
|
|
|
|
if (data.domain) {
|
|
html += `<div class="mb-2"><div class="text-muted">Domain</div>`;
|
|
for (const [key, val] of Object.entries(data.domain)) {
|
|
html += `<div><span class="text-muted">${escapeHtml(key)}:</span> ${escapeHtml(String(val))}</div>`;
|
|
}
|
|
html += `</div>`;
|
|
}
|
|
|
|
if (data.primaryType) {
|
|
html += `<div class="mb-2"><div class="text-muted">Primary type</div>`;
|
|
html += `<div class="font-bold">${escapeHtml(data.primaryType)}</div></div>`;
|
|
}
|
|
|
|
if (data.message) {
|
|
html += `<div class="mb-2"><div class="text-muted">Message</div>`;
|
|
for (const [key, val] of Object.entries(data.message)) {
|
|
const display =
|
|
typeof val === "object" ? JSON.stringify(val) : String(val);
|
|
html += `<div><span class="text-muted">${escapeHtml(key)}:</span> <span class="break-all">${escapeHtml(display)}</span></div>`;
|
|
}
|
|
html += `</div>`;
|
|
}
|
|
|
|
return html;
|
|
} catch {
|
|
return `<div class="break-all">${escapeHtml(jsonStr)}</div>`;
|
|
}
|
|
}
|
|
|
|
function showSignApproval(details) {
|
|
showPhishingWarning(
|
|
"approve-sign-phishing-warning",
|
|
details.isPhishingDomain,
|
|
);
|
|
|
|
const sp = details.signParams;
|
|
pendingSignParams = sp;
|
|
|
|
$("approve-sign-hostname").textContent = details.hostname;
|
|
$("approve-sign-from").innerHTML = approvalAddressHtml(sp.from);
|
|
|
|
const isTyped =
|
|
sp.method === "eth_signTypedData_v4" ||
|
|
sp.method === "eth_signTypedData";
|
|
$("approve-sign-type").textContent = isTyped
|
|
? "Typed data (EIP-712)"
|
|
: "Personal message";
|
|
|
|
if (isTyped) {
|
|
$("approve-sign-message").innerHTML = formatTypedDataHtml(sp.typedData);
|
|
} else {
|
|
const decoded = decodeHexMessage(sp.message);
|
|
if (decoded !== null) {
|
|
$("approve-sign-message").textContent = decoded;
|
|
} else {
|
|
$("approve-sign-message").textContent = sp.message;
|
|
}
|
|
}
|
|
|
|
// Display danger warning for eth_sign (raw hash signing)
|
|
const warningEl = $("approve-sign-danger-warning");
|
|
if (warningEl) {
|
|
if (sp.dangerWarning) {
|
|
warningEl.textContent = sp.dangerWarning;
|
|
warningEl.style.visibility = "visible";
|
|
} else {
|
|
warningEl.textContent = "";
|
|
warningEl.style.visibility = "hidden";
|
|
}
|
|
}
|
|
|
|
$("approve-sign-password").value = "";
|
|
hideError("approve-sign-error");
|
|
$("btn-approve-sign").disabled = false;
|
|
$("btn-approve-sign").classList.remove("text-muted");
|
|
|
|
showView("approve-sign");
|
|
attachCopyHandlers("view-approve-sign");
|
|
gateOnWalletDefect("approve-sign-error", "btn-approve-sign");
|
|
}
|
|
|
|
function show(id) {
|
|
approvalId = id;
|
|
runtime.connect({ name: "approval:" + id });
|
|
runtime.sendMessage({ type: "AUTISTMASK_GET_APPROVAL", id }, (details) => {
|
|
if (!details) {
|
|
window.close();
|
|
return;
|
|
}
|
|
if (details.type === "tx") {
|
|
showTxApproval(details);
|
|
return;
|
|
}
|
|
if (details.type === "sign") {
|
|
showSignApproval(details);
|
|
return;
|
|
}
|
|
// Site connection approval
|
|
showPhishingWarning(
|
|
"approve-site-phishing-warning",
|
|
details.isPhishingDomain,
|
|
);
|
|
$("approve-hostname").textContent = details.hostname;
|
|
$("approve-address").innerHTML = approvalAddressHtml(
|
|
state.activeAddress,
|
|
);
|
|
attachCopyHandlers("view-approve-site");
|
|
$("approve-remember").checked = state.rememberSiteChoice;
|
|
});
|
|
}
|
|
|
|
let approvalId = null;
|
|
let pendingTxDetails = null;
|
|
// The exact parameters shown to the user, kept so the popup signs what it
|
|
// displayed rather than re-fetching anything at approval time. Both are
|
|
// repopulated by show() when the popup is closed and reopened.
|
|
let pendingTxParams = null;
|
|
let pendingSignParams = null;
|
|
|
|
// Approve buttons stay disabled and muted while the popup derives the key and
|
|
// signs, which is slow enough (Argon2id) that a double click is likely.
|
|
function setTxButtonBusy(busy) {
|
|
$("btn-approve-tx").disabled = busy;
|
|
$("btn-approve-tx").classList.toggle("text-muted", busy);
|
|
}
|
|
|
|
function setSignButtonBusy(busy) {
|
|
$("btn-approve-sign").disabled = busy;
|
|
$("btn-approve-sign").classList.toggle("text-muted", busy);
|
|
}
|
|
|
|
// Say so on the approval screen itself, and disable the approve button, when
|
|
// the active address belongs to a wallet whose keys cannot be derived. Without
|
|
// this the screen would take a password and fail after deriving it. Reject
|
|
// stays available; the wallet is not touched. Returns true when it gated.
|
|
function gateOnWalletDefect(errorId, buttonId) {
|
|
const active = findActiveWallet();
|
|
const defect = active ? walletDefect(active.wallet) : null;
|
|
if (!defect) return false;
|
|
showError(errorId, defect.shortMessage);
|
|
$(buttonId).disabled = true;
|
|
$(buttonId).classList.add("text-muted");
|
|
return true;
|
|
}
|
|
|
|
// Locate the wallet and the address index owning the currently active
|
|
// address. Returns null when no wallet holds it.
|
|
function findActiveWallet() {
|
|
for (const wallet of state.wallets) {
|
|
for (let i = 0; i < wallet.addresses.length; i++) {
|
|
if (wallet.addresses[i].address === state.activeAddress) {
|
|
return { wallet, addrIndex: i };
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function init(ctx) {
|
|
$("approve-remember").addEventListener("change", async () => {
|
|
state.rememberSiteChoice = $("approve-remember").checked;
|
|
await saveState();
|
|
});
|
|
|
|
$("btn-approve").addEventListener("click", () => {
|
|
const remember = $("approve-remember").checked;
|
|
runtime.sendMessage({
|
|
type: "AUTISTMASK_APPROVAL_RESPONSE",
|
|
id: approvalId,
|
|
approved: true,
|
|
remember,
|
|
});
|
|
window.close();
|
|
});
|
|
|
|
$("btn-reject").addEventListener("click", () => {
|
|
const remember = $("approve-remember").checked;
|
|
runtime.sendMessage({
|
|
type: "AUTISTMASK_APPROVAL_RESPONSE",
|
|
id: approvalId,
|
|
approved: false,
|
|
remember,
|
|
});
|
|
window.close();
|
|
});
|
|
|
|
$("btn-approve-tx").addEventListener("click", async () => {
|
|
let password = $("approve-tx-password").value;
|
|
if (!password) {
|
|
showError("approve-tx-error", "Please enter your password.");
|
|
return;
|
|
}
|
|
hideError("approve-tx-error");
|
|
setTxButtonBusy(true);
|
|
|
|
const active = findActiveWallet();
|
|
if (!active) {
|
|
password = null;
|
|
showError(
|
|
"approve-tx-error",
|
|
"No wallet was found for the active address.",
|
|
);
|
|
setTxButtonBusy(false);
|
|
return;
|
|
}
|
|
|
|
const defect = walletDefect(active.wallet);
|
|
if (defect) {
|
|
password = null;
|
|
showError("approve-tx-error", defect.shortMessage);
|
|
setTxButtonBusy(false);
|
|
return;
|
|
}
|
|
|
|
// Decrypt here, in the popup. The password must never cross the
|
|
// extension messaging boundary; only the signed transaction does.
|
|
let decryptedSecret;
|
|
try {
|
|
decryptedSecret = await decryptWithPassword(
|
|
active.wallet.encryptedSecret,
|
|
password,
|
|
);
|
|
} catch {
|
|
showError(
|
|
"approve-tx-error",
|
|
"That password is incorrect. Please try again.",
|
|
);
|
|
setTxButtonBusy(false);
|
|
return;
|
|
} finally {
|
|
// Best-effort: drop the password as soon as the key derivation
|
|
// is done. Note that JS strings are immutable; this clears the
|
|
// reference but the original string may persist until GC.
|
|
password = null;
|
|
}
|
|
|
|
const payload = {
|
|
type: "AUTISTMASK_TX_RESPONSE",
|
|
id: approvalId,
|
|
approved: true,
|
|
};
|
|
try {
|
|
const signer = getSignerForAddress(
|
|
active.wallet,
|
|
active.addrIndex,
|
|
decryptedSecret,
|
|
);
|
|
const provider = getProvider(state.rpcUrl);
|
|
const connected = signer.connect(provider);
|
|
// This is the sequence ethers' own sendTransaction() runs
|
|
// internally, so nonce, gas, fee and chain id population are
|
|
// identical to when the background did the signing.
|
|
const populated =
|
|
await connected.populateTransaction(pendingTxParams);
|
|
delete populated.from;
|
|
payload.rawSignedTx = await connected.signTransaction(populated);
|
|
} catch (e) {
|
|
payload.error =
|
|
e.shortMessage || e.message || "Transaction signing failed.";
|
|
} finally {
|
|
// Best-effort: clear the decrypted secret after use, with the
|
|
// same immutability caveat as the password above.
|
|
decryptedSecret = null;
|
|
}
|
|
|
|
runtime.sendMessage(payload, (response) => {
|
|
if (response && response.txHash) {
|
|
txStatus.showWait(pendingTxDetails, response.txHash);
|
|
} else {
|
|
const msg =
|
|
(response && response.error) || "Transaction failed.";
|
|
txStatus.showError(pendingTxDetails, null, msg);
|
|
}
|
|
});
|
|
});
|
|
|
|
$("btn-reject-tx").addEventListener("click", () => {
|
|
runtime.sendMessage({
|
|
type: "AUTISTMASK_TX_RESPONSE",
|
|
id: approvalId,
|
|
approved: false,
|
|
});
|
|
window.close();
|
|
});
|
|
|
|
$("btn-approve-sign").addEventListener("click", async () => {
|
|
let password = $("approve-sign-password").value;
|
|
if (!password) {
|
|
showError("approve-sign-error", "Please enter your password.");
|
|
return;
|
|
}
|
|
hideError("approve-sign-error");
|
|
setSignButtonBusy(true);
|
|
|
|
const active = findActiveWallet();
|
|
if (!active) {
|
|
password = null;
|
|
showError(
|
|
"approve-sign-error",
|
|
"No wallet was found for the active address.",
|
|
);
|
|
setSignButtonBusy(false);
|
|
return;
|
|
}
|
|
|
|
const defect = walletDefect(active.wallet);
|
|
if (defect) {
|
|
password = null;
|
|
showError("approve-sign-error", defect.shortMessage);
|
|
setSignButtonBusy(false);
|
|
return;
|
|
}
|
|
|
|
// Decrypt here, in the popup. The password must never cross the
|
|
// extension messaging boundary; only the signature does.
|
|
let decryptedSecret;
|
|
try {
|
|
decryptedSecret = await decryptWithPassword(
|
|
active.wallet.encryptedSecret,
|
|
password,
|
|
);
|
|
} catch {
|
|
showError(
|
|
"approve-sign-error",
|
|
"That password is incorrect. Please try again.",
|
|
);
|
|
setSignButtonBusy(false);
|
|
return;
|
|
} finally {
|
|
// Best-effort: drop the password as soon as the key derivation
|
|
// is done. Note that JS strings are immutable; this clears the
|
|
// reference but the original string may persist until GC.
|
|
password = null;
|
|
}
|
|
|
|
const payload = {
|
|
type: "AUTISTMASK_SIGN_RESPONSE",
|
|
id: approvalId,
|
|
approved: true,
|
|
};
|
|
try {
|
|
const signer = getSignerForAddress(
|
|
active.wallet,
|
|
active.addrIndex,
|
|
decryptedSecret,
|
|
);
|
|
const sp = pendingSignParams;
|
|
if (sp.method === "personal_sign" || sp.method === "eth_sign") {
|
|
payload.signature = await signer.signMessage(
|
|
getBytes(sp.message),
|
|
);
|
|
} else {
|
|
// eth_signTypedData_v4 / eth_signTypedData
|
|
const typedData = JSON.parse(sp.typedData);
|
|
const { domain, types, message } = typedData;
|
|
// ethers handles EIP712Domain internally
|
|
delete types.EIP712Domain;
|
|
payload.signature = await signer.signTypedData(
|
|
domain,
|
|
types,
|
|
message,
|
|
);
|
|
}
|
|
} catch (e) {
|
|
payload.error = e.shortMessage || e.message || "Signing failed.";
|
|
} finally {
|
|
// Best-effort: clear the decrypted secret after use, with the
|
|
// same immutability caveat as the password above.
|
|
decryptedSecret = null;
|
|
}
|
|
|
|
runtime.sendMessage(payload, (response) => {
|
|
if (response && response.signature) {
|
|
window.close();
|
|
} else {
|
|
const msg = (response && response.error) || "Signing failed.";
|
|
showError("approve-sign-error", msg);
|
|
setSignButtonBusy(false);
|
|
}
|
|
});
|
|
});
|
|
|
|
$("btn-reject-sign").addEventListener("click", () => {
|
|
runtime.sendMessage({
|
|
type: "AUTISTMASK_SIGN_RESPONSE",
|
|
id: approvalId,
|
|
approved: false,
|
|
});
|
|
window.close();
|
|
});
|
|
}
|
|
|
|
module.exports = { init, show, decodeCalldata };
|