Skip to content

fix: Delegate button silently fails when LPT allowance is partially approved #737

Description

@rickstaa

Problem

On the delegating widget, entering an amount that is below the wallet balance but above the current LPT allowance renders an enabled Delegate button that does nothing when clicked, no wallet prompt, no error. Smaller amounts work, so it looks like an arbitrary cap.

Root cause

/api/account-balance returns balance and allowance as raw wei strings (pages/api/account-balance/[address].tsx:44-47), but components/DelegatingWidget/Delegate.tsx:170-183 compares them against amount, which is the human-readable LPT input:

const sufficientBalance           = Number(tokenBalance)      >= amount;  // 1.4e19 >= 5
const sufficientTransferAllowance = Number(transferAllowance) >= amount;  // 1.1e17 >= 5

Both are true for any realistic input, so:

  1. showApproveFlow (:186) is false whenever allowance is non-zero, and the Approve/Delegate pair is never shown.
  2. useSimulateContract for bondWithHint (:136) reverts on the real ERC-20 allowance check, leaving bondWithHintConfig undefined.
  3. onDelegate (:206-214) throws No config for bond with hint into catch { console.error(e) }, so the click is a no-op.

sufficientBalance is broken the same way, so the "Insufficient Balance" guard at :228 can never fire either.

Why it went unnoticed

Only wallets with a partial non-zero allowance are affected. With allowance exactly 0, Number(transferAllowance) > 0 is false, the approve flow shows correctly, and the explorer grants MAX_UINT256 — so the normal path never hits this. Users land in the broken state when a wallet (Rabby, MetaMask) overrides the unlimited approval with an exact amount.

InputBox.tsx:51 correctly applies fromWei for display, which is why the balance shown is right while the comparison is wrong.

Introduced

989c09b (2022-08-31, #165). Before that commit, Footer.tsx passed formatted values:

const tokenBalance      = account && parseFloat(Utils.fromWei(account.tokenBalance));
const transferAllowance = account && parseFloat(Utils.fromWei(account.allowance));

#165 switched them to the raw wei values from the new /api/account-balance route without reintroducing fromWei, leaving the comparisons unchanged.

Proposed fix

Compare in wei using BigInt:

const amountWei = useMemo(() => {
  try {
    return amount ? parseEther(amount) : 0n;
  } catch {
    return 0n;
  }
}, [amount]);

const sufficientBalance = useMemo(
  () => amountWei > 0n && BigInt(tokenBalance ?? 0) >= amountWei,
  [amountWei, tokenBalance]
);

const sufficientTransferAllowance = useMemo(
  () => amountWei > 0n && BigInt(transferAllowance ?? 0) >= amountWei,
  [amountWei, transferAllowance]
);

Also disable the Delegate button while bondWithHintConfig is undefined (or surface the error) so a failed simulation can never present as a working button.

Workaround

Set the allowance to 0 (wallet or revoke.cash); the approve flow then reappears and grants unlimited approval correctly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions