Skip to content

feat(sdk-core): currency-safe CurrencyAmount comparisons - #697

Open
gomesalexandre wants to merge 1 commit into
Uniswap:mainfrom
gomesalexandre:feat_currencyamount_currency_safe_comparisons
Open

feat(sdk-core): currency-safe CurrencyAmount comparisons#697
gomesalexandre wants to merge 1 commit into
Uniswap:mainfrom
gomesalexandre:feat_currencyamount_currency_safe_comparisons

Conversation

@gomesalexandre

Copy link
Copy Markdown

closes #53

what

CurrencyAmount.greaterThan/equalTo/lessThan were never overridden, so they fell through to Fraction with no currency check. usdcAmount.greaterThan(daiAmount) silently compared raw numerators across two different currencies. The sibling arithmetic methods (add/subtract/multiply) already guard this with invariant(this.currency.equals(other.currency), 'CURRENCY').

how

The three comparison methods now apply that same invariant when the other operand is itself a CurrencyAmount, so a cross-currency comparison throws CURRENCY instead of returning a meaningless result.

I kept the base Fraction | BigintIsh signature rather than narrowing to CurrencyAmount<T> | 0 as the issue suggests, on purpose: the narrowing breaks legitimate existing call sites that compare against a raw amount. Concretely, v2-sdk's pair.reserve0.equalTo(ZERO) (where ZERO is a JSBI, not the number literal 0) fails to type-check under CurrencyAmount<T> | 0, and that raw-zero pattern is common. A runtime invariant delivers the currency safety the issue asks for without forcing a downstream migration.

The instanceof CurrencyAmount guard degrades gracefully: if other is a CurrencyAmount from a different copy of the package, it falls through to the base comparison (exactly the pre-fix behavior), never a wrong throw. Percent/Price extend Fraction (not CurrencyAmount), so they still compare as fractions.

testing

  • sdk-core: 19 tests in currencyAmount.test.ts pass (3 new: same-currency comparisons, raw-amount comparisons including a JSBI zero, and a cross-currency throw). tsc + eslint clean.
  • Ran the full suites of the downstream consumers against the rebuilt sdk-core: v2-sdk (103) and v3-sdk (338) pass with zero failures. (router-sdk/v4-sdk/universal-router-sdk fail only on pre-existing workspace module-resolution, unrelated to this change - their unbuilt sibling packages.)
  • Grepped the monorepo for native-vs-wrapped comparison sites (ETH.equals(WETH) is false, so those would newly throw): none exist.

Changeset included (minor), noting the behavior change.

greaterThan/equalTo/lessThan fell through to Fraction with no currency
check, so comparing amounts of different currencies silently compared
raw numerators (usdcAmount.greaterThan(daiAmount) returned a meaningless
result). add/subtract/multiply already guard this with a CURRENCY
invariant.

Override the three comparisons to apply the same invariant when the
other side is a CurrencyAmount, so a cross-currency comparison throws.
The base Fraction | BigintIsh signature is kept so existing callers
comparing against a raw amount (e.g. amount.equalTo(ZERO) with a JSBI
zero) keep working - narrowing to CurrencyAmount<T> | 0 as the issue
suggests breaks those legit call sites (verified against v2-sdk).

closes Uniswap#53
@gomesalexandre
gomesalexandre requested a review from a team as a code owner August 17, 2026 20:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add CurrencyAmount.{greaterThan,equalTo,lessThan}

1 participant