feat: a "Max" button on the Send screen (closes #198)
Max fills in a token's balance, cut down to the 18 decimal places the confirmation screen accepts, or for ETH the exact balance minus the fee reserve the confirmation screen's balance check gates on. An ETH fee estimate that finishes after the Send screen was left, or its address, holding, recipient or amount changed, fills nothing in. The confirmation screen works a max ETH amount out again from its own fee estimate and signs it with that estimate's fee fields, so a fee that rose before signing cannot push amount plus fee above the balance. validateTransfer() still gates every send, the check that ETH covers a token send's fee included. Where there is nothing to fill in, a flash message says why. Model: opus-5-5
This commit was merged in pull request #452.
This commit is contained in:
@@ -6,6 +6,8 @@ const {
|
||||
FEE_UNAVAILABLE,
|
||||
feeReserveWei,
|
||||
feeEstimateWei,
|
||||
maxEthAmount,
|
||||
maxTokenAmount,
|
||||
toFixedPoint,
|
||||
validateTransfer,
|
||||
} = require("../src/shared/txValidation");
|
||||
@@ -306,6 +308,72 @@ describe("feeEstimateWei", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// The amount the Send screen's Max fills in for ETH: the exact balance, as
|
||||
// balances.js stores it, minus the fee reserve. Never the four-decimal balance
|
||||
// the Send screen shows.
|
||||
describe("maxEthAmount", () => {
|
||||
// The Send screen shows this balance as 1.2345.
|
||||
const BALANCE = "1.234567890123456789";
|
||||
|
||||
test("is the exact balance minus the fee, to the wei", () => {
|
||||
expect(maxEthAmount(BALANCE, FEE)).toBe("1.234147890123456789");
|
||||
expect(
|
||||
parseEther(BALANCE) - parseEther(maxEthAmount(BALANCE, FEE)),
|
||||
).toBe(FEE);
|
||||
});
|
||||
|
||||
test("passes validateTransfer with exactly the fee left behind", () => {
|
||||
const r = validateTransfer({
|
||||
isErc20: false,
|
||||
amount: maxEthAmount(BALANCE, FEE),
|
||||
ethBalance: BALANCE,
|
||||
feeStatus: FEE_KNOWN,
|
||||
feeWei: FEE,
|
||||
});
|
||||
expect(r).toEqual({ canSend: true, codes: [] });
|
||||
});
|
||||
|
||||
test("is one wei when the balance is one wei more than the fee", () => {
|
||||
expect(maxEthAmount("0.000420000000000001", FEE)).toBe(
|
||||
"0.000000000000000001",
|
||||
);
|
||||
});
|
||||
|
||||
test("is null when the balance does not cover the fee", () => {
|
||||
expect(maxEthAmount("0.0001", FEE)).toBe(null);
|
||||
expect(maxEthAmount("0.0", FEE)).toBe(null);
|
||||
});
|
||||
|
||||
test("is null when the balance covers the fee and nothing more", () => {
|
||||
expect(maxEthAmount("0.00042", FEE)).toBe(null);
|
||||
});
|
||||
|
||||
test("is null on a balance or fee it cannot do exact arithmetic on", () => {
|
||||
expect(maxEthAmount(undefined, FEE)).toBe(null);
|
||||
expect(maxEthAmount("not a number", FEE)).toBe(null);
|
||||
expect(maxEthAmount(BALANCE, null)).toBe(null);
|
||||
expect(maxEthAmount(BALANCE, -1n)).toBe(null);
|
||||
expect(maxEthAmount(BALANCE, 420000000000000)).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
// The amount the Send screen's Max fills in for a token.
|
||||
describe("maxTokenAmount", () => {
|
||||
test("cuts a balance with more than 18 places down, never up", () => {
|
||||
const amount = maxTokenAmount("1234.567890123456789012999999");
|
||||
expect(amount).toBe("1234.567890123456789012");
|
||||
expect(toFixedPoint(amount)).not.toBe(null);
|
||||
});
|
||||
|
||||
test("leaves a balance with 18 places or fewer as it is", () => {
|
||||
expect(maxTokenAmount("0.123456789012345678")).toBe(
|
||||
"0.123456789012345678",
|
||||
);
|
||||
expect(maxTokenAmount("1.5")).toBe("1.5");
|
||||
expect(maxTokenAmount("100")).toBe("100");
|
||||
});
|
||||
});
|
||||
|
||||
// Everything that is not a usable fee blocks exactly as FEE_UNAVAILABLE does.
|
||||
// Each of these previously returned { canSend: true, codes: [] } — counting no
|
||||
// fee at all, on a full-balance send, in the direction that lets money out.
|
||||
|
||||
Reference in New Issue
Block a user