Aegis: sends name their wallet, amounts with spaces are refused, BCMR cleanup
- send, sendToken and consolidate now require the wallet id the panel reviewed them for; a missing id used to skip the check, and the Solana token send sent none, so a selection change under the PIN pad sent from another wallet. - "1 0" was read as 10: a space inside an amount is now refused. - A BCMR registry list saved before https was enforced is filtered on read, and names lose the soft hyphen, Arabic letter mark, Mongolian vowel separator, line/paragraph separators and Unicode tag characters too.
This commit is contained in:
parent
7447d57e96
commit
916a748889
3 changed files with 23 additions and 8 deletions
|
|
@ -1806,6 +1806,10 @@ function registerPanelMessages(api) {
|
|||
api.onMessage("sendToken", async (p, m) => {
|
||||
fromPanel(m);
|
||||
requirePanelTxPin();
|
||||
// Same rule as send: the token send names the wallet it was reviewed for.
|
||||
if (!p || !p.walletId || String(p.walletId) !== selectedWalletId()) {
|
||||
throw new Error("the selected wallet changed — review the send and try again");
|
||||
}
|
||||
const rt = requireSelected();
|
||||
if (rt.entry.chain !== "sol") throw new Error("token send is Solana-only");
|
||||
const plan = await rt.adapter.planTokenTransfer(p || {});
|
||||
|
|
@ -1834,7 +1838,10 @@ function registerPanelMessages(api) {
|
|||
// The panel names the wallet its form was built for. If the selection
|
||||
// moved since (another surface, a late state push), refuse rather than
|
||||
// send this amount from a different wallet.
|
||||
if (p && p.walletId && String(p.walletId) !== selectedWalletId()) {
|
||||
// Required, not optional: a send that does not say which wallet it was
|
||||
// reviewed for cannot be checked against the selection.
|
||||
if (!p || !p.walletId) throw new Error("send names no wallet — review the send and try again");
|
||||
if (String(p.walletId) !== selectedWalletId()) {
|
||||
throw new Error("the selected wallet changed — review the send and try again");
|
||||
}
|
||||
const rt = requireSelected();
|
||||
|
|
@ -1936,7 +1943,7 @@ function registerPanelMessages(api) {
|
|||
// The destination is the wallet the PREVIEW was drawn for, not whatever
|
||||
// is selected by the time the button is pressed: a preview painted for A
|
||||
// followed by a switch to B used to sweep everything into B.
|
||||
if (p && p.destinationWalletId && String(p.destinationWalletId) !== destId) {
|
||||
if (!p || !p.destinationWalletId || String(p.destinationWalletId) !== destId) {
|
||||
throw new Error("the selected wallet changed since this preview — reopen Consolidate");
|
||||
}
|
||||
const destEntry = walletEntries().find((w) => w.id === destId);
|
||||
|
|
|
|||
|
|
@ -46,8 +46,11 @@ const DEFAULT_REGISTRIES = [
|
|||
module.exports = function makeBcmr({ storage, log = () => {} }) {
|
||||
|
||||
function registryList() {
|
||||
// Filtered on read as well: a list saved before setRegistries enforced
|
||||
// https still carries its http entries.
|
||||
const custom = storage.get("bcmr/registries", null);
|
||||
if (Array.isArray(custom) && custom.length) return custom;
|
||||
const clean = Array.isArray(custom) ? custom.filter((r) => r && typeof r.url === "string" && /^https:\/\//i.test(r.url)) : [];
|
||||
if (clean.length) return clean;
|
||||
return DEFAULT_REGISTRIES.slice();
|
||||
}
|
||||
function setRegistries(list) {
|
||||
|
|
@ -164,10 +167,13 @@ module.exports = function makeBcmr({ storage, log = () => {} }) {
|
|||
//
|
||||
// A local name wins over the registry: the user typed it for this exact
|
||||
// category, which is better evidence than a third-party document.
|
||||
// Control, bidi-override, zero-width and BOM characters. Built from code
|
||||
// Control, bidi-override, zero-width and BOM characters, plus the soft
|
||||
// hyphen, the Arabic letter mark, the Mongolian vowel separator, the
|
||||
// line/paragraph separators and the Unicode tag block. Built from code
|
||||
// points so no invisible character has to live in this source file.
|
||||
const rng = (a, b) => String.fromCharCode(a) + "-" + String.fromCharCode(b);
|
||||
const INVISIBLE = new RegExp("[" + rng(0x00, 0x1f) + rng(0x7f, 0x9f) + rng(0x200b, 0x200f) + rng(0x202a, 0x202e) + rng(0x2060, 0x2069) + rng(0xfeff, 0xfeff) + "]", "g");
|
||||
const rng = (a, b) => String.fromCodePoint(a) + "-" + String.fromCodePoint(b);
|
||||
const INVISIBLE = new RegExp("[" + rng(0x00, 0x1f) + rng(0x7f, 0x9f) + rng(0xad, 0xad) + rng(0x61c, 0x61c) + rng(0x180e, 0x180e)
|
||||
+ rng(0x200b, 0x200f) + rng(0x2028, 0x202e) + rng(0x2060, 0x2069) + rng(0xfeff, 0xfeff) + rng(0xe0000, 0xe007f) + "]", "gu");
|
||||
function cleanText(v, max) {
|
||||
if (typeof v !== "string") return null;
|
||||
const s = v.replace(INVISIBLE, "").trim().slice(0, max);
|
||||
|
|
|
|||
|
|
@ -4135,8 +4135,10 @@ function amountDecimals() { return sendAsset ? Number(sendAsset.decimals) || 0 :
|
|||
// guess: the old `.replace(/,/g, "")` turned a decimal comma ("0,5") into 5,
|
||||
// and Number() accepted "1e3", "0x10" and "-0.5".
|
||||
function parseAmountText(text) {
|
||||
let raw = String(text == null ? "" : text).trim().replace(/\s/g, "");
|
||||
let raw = String(text == null ? "" : text).trim();
|
||||
if (!raw) return { empty: true };
|
||||
// Space inside the number is refused, not deleted: "1 0" used to become 10.
|
||||
if (/\s/.test(raw)) return { error: "Remove the space from the amount" };
|
||||
if (/^\d{1,3}(,\d{3})+\.\d*$/.test(raw) || /^\d{1,3}(,\d{3}){2,}$/.test(raw)) {
|
||||
raw = raw.replace(/,/g, ""); // 1,234.5 · 1,234,567
|
||||
} else if (/^\d{1,3},\d{3}$/.test(raw)) {
|
||||
|
|
@ -4610,7 +4612,7 @@ $("sendBtn").addEventListener("click", async () => {
|
|||
try {
|
||||
const isToken = !!(req.mint && lastPlan._token);
|
||||
const r = isToken
|
||||
? await S.invoke("sendToken", { mint: req.mint, to: req.to, amount: req.amount })
|
||||
? await S.invoke("sendToken", { walletId: req.walletId, mint: req.mint, to: req.to, amount: req.amount })
|
||||
: await S.invoke("send", { walletId: req.walletId, to: req.to, amount: req.amount, feeRate: req.feeRate, sendMax: req.sendMax, memo: req.memo });
|
||||
// A broadcast that returned no txid is still a broadcast: never report
|
||||
// it as a failure and leave a filled form inviting a second send.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue