Implement ETH send and QR code receive
All checks were successful
check / check (push) Successful in 22s

Send: stores mnemonic/private key with wallet data, derives
signing key from mnemonic + address index via ethers HDNodeWallet,
constructs transaction with parseEther, broadcasts via
sendTransaction, waits for confirmation, shows block number
and tx hash. ENS resolution in To field preserved.

Receive: QR code rendered to canvas via qrcode library (1.5.4).
Shows scannable QR above the full address text.

README updated with qrcode dependency and TODO progress.
This commit is contained in:
2026-02-25 18:17:23 +07:00
parent fc3f0e00c8
commit bfecddf2f7
5 changed files with 151 additions and 15 deletions

View File

@@ -288,6 +288,9 @@
Share this address with the sender. Make sure you only use
this address to receive Ethereum tokens.
</p>
<div class="flex justify-center mb-3">
<canvas id="receive-qr"></canvas>
</div>
<div
id="receive-address"
class="border border-border p-2 break-all select-all mb-3"

View File

@@ -5,8 +5,10 @@ const {
Wallet,
JsonRpcProvider,
formatEther,
parseEther,
} = require("ethers");
const { getTopTokenPrices } = require("../shared/tokens");
const QRCode = require("qrcode");
const DEBUG = true;
const DEBUG_MNEMONIC =
@@ -145,6 +147,22 @@ function formatUsd(amount) {
);
}
// Get an ethers Wallet (signer) for the currently selected address
function getSignerForCurrentAddress() {
const wallet = state.wallets[state.selectedWallet];
const addrIndex = state.selectedAddress;
if (wallet.type === "hd") {
const node = HDNodeWallet.fromPhrase(
wallet.mnemonic,
"",
BIP44_ETH_BASE,
);
return node.deriveChild(addrIndex);
} else {
return new Wallet(wallet.privateKey);
}
}
// -- balance fetching --
function getProvider() {
return new JsonRpcProvider(state.rpcUrl);
@@ -415,6 +433,7 @@ async function init() {
type: "hd",
name: "Wallet " + walletNum,
xpub: xpub,
mnemonic: mnemonic,
nextIndex: 1,
addresses: [
{ address: firstAddress, balance: "0.0000", tokens: [] },
@@ -444,6 +463,7 @@ async function init() {
addWalletAndGoToMain({
type: "key",
name: "Wallet " + walletNum,
privateKey: key,
addresses: [{ address: addr, balance: "0.0000", tokens: [] }],
});
});
@@ -485,7 +505,15 @@ async function init() {
$("btn-receive").addEventListener("click", () => {
const addr = currentAddress();
$("receive-address").textContent = addr ? addr.address : "";
const address = addr ? addr.address : "";
$("receive-address").textContent = address;
if (address) {
QRCode.toCanvas($("receive-qr"), address, {
width: 200,
margin: 2,
color: { dark: "#000000", light: "#ffffff" },
});
}
showView("receive");
});
@@ -530,13 +558,29 @@ async function init() {
return;
}
}
// TODO: prompt for password, decrypt key, construct and send transaction
const el = $("send-status");
el.textContent =
"Sending to " +
resolvedTo +
" (stub — password/signing not yet implemented)";
el.classList.remove("hidden");
const statusEl = $("send-status") || $("send-status");
statusEl.textContent = "Sending...";
statusEl.classList.remove("hidden");
try {
const signer = getSignerForCurrentAddress();
const provider = getProvider();
const connectedSigner = signer.connect(provider);
const tx = await connectedSigner.sendTransaction({
to: resolvedTo,
value: parseEther(amount),
});
statusEl.textContent = "Sent. Waiting for confirmation...";
const receipt = await tx.wait();
statusEl.textContent =
"Confirmed in block " +
receipt.blockNumber +
". Tx: " +
receipt.hash;
// Refresh balance after send
refreshBalances().then(() => renderWalletList());
} catch (e) {
statusEl.textContent = "Failed: " + (e.shortMessage || e.message);
}
});
$("btn-send-back").addEventListener("click", () => {