Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: swap max for non native assets #5923

Closed
wants to merge 7 commits into from
Closed

fix: swap max for non native assets #5923

wants to merge 7 commits into from

Conversation

herbig
Copy link

@herbig herbig commented Dec 21, 2023

Description

Brings back the existing Max button in the TradeAssetInput component, for ERC20 tokens. This allows users to easily fill the input field with their max balance.

This was originally removed to simplify the logic for calculating gas costs, but since ERC20 tokens are not used for paying native gas it is not an issue to bring it back for those.

Pull Request Type

  • 🐛 Bug fix (Non-breaking Change: Fixes an issue)
  • 🛠️ Chore (Non-breaking Change: Doc updates, pkg upgrades, typos, etc..)
  • 💅 New Feature (Breaking/Non-breaking Change)

Issue (if applicable)

closes #5828

Risk

This change adds a listener to the Max button, causing it to appear, and fills the input field with the total balance, for non-native (ERC20) tokens only.

This is low-risk, it is unlikely there are unintended consequences to this. Users are generally familiar with the placement and function of a Max button, so should understand what it is doing.

Testing

Engineering

  1. View the swap (Trade/Bridge) feature at http://localhost:3000/#/trade
  2. Confirm that the Max button appears when swapping FROM an ERC20 token to anything else.
  3. Confirm that clicking the Max button inputs the value displayed in the top right (Balance) into the input field.
  4. Confirm that the Max button DOES NOT appear when swapping from a native currency (e.g. ETH).

Operations

This is user facing. Testing can be the same as above.

Screenshots (if applicable)

Screenshot 2023-12-21 at 2 25 58 PM

@herbig herbig requested a review from a team as a code owner December 21, 2023 19:27
} else if (onPercentOptionClick) {
onPercentOptionClick(option)
}
}, [onPercentOptionClick, onMaxClick, option])
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed the logic slightly, since onPercentOptionClick is now optional

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since both are now optional, we could end up in a situation where we forget to pass onMaxClick, and the regular onClick handler gets fired, which could have very different logic as the max one

Copy link
Member

@0xApotheosis 0xApotheosis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid start. A few initial thoughts:

  • We'll want to use the ASSET_NAMESPACE constant from the caip package, rather than the erc20 string literal.
  • Whilst the ticket AC does indeed say we only want to handle erc20s, we'll also likely want to handle bep20s (Binance Smart Chain), too. You'll notice non-native BSC assets do not currently support send max in this branch.
  • We might want to look at using the isEvmChainId and isNativeEvmAsset helpers to achieve this in a more maintainable way.
  • A few lint issues to fix up 🙏

@herbig
Copy link
Author

herbig commented Dec 21, 2023

Solid start. A few initial thoughts:

* We'll want to use the `ASSET_NAMESPACE` constant from the `caip` package, rather than the `erc20` string literal.

* Whilst the ticket AC does indeed say we only want to handle `erc20`s, we'll also likely want to handle `bep20`s (Binance Smart Chain), too. You'll notice non-native BSC assets do not currently support send max in this branch.

* We might want to look at using the `isEvmChainId` and `isNativeEvmAsset` helpers to achieve this in a more maintainable way.

* A few lint issues to fix up 🙏

thanks @0xApotheosis those helper functions are great, and good call about bep20, this should now be handled

@woodenfurniture
Copy link
Member

Hey @herbig thanks for the PR! 🍻

Copy link
Contributor

@gomesalexandre gomesalexandre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid start indeed, thanks a lot for the contribution @herbig!

Following on the great review from @0xApotheosis, I've added a few comments, as this implementation is sane for the most part at runtime but potentially brings some major type safety risks.

From a functional perspective, the max button is indeed available for ERC-20 sell assets:

image

It looks like the scientific notation is used however, versus precision in the send modal, see:

image image

You can see the send modal implementation for reference BigNumber.toPrecision() is what you want here:

setValue(SendFormFields.CryptoAmount, cryptoHumanBalance.toPrecision())

Fiat max. is also happy.

image

And confirmed the max. button is only shown for EVM tokens.

@@ -41,15 +45,15 @@ export const PercentOptionsButton: React.FC<MaxButtonProps> = ({

type MaxButtonGroupProps = {
isDisabled?: boolean
onClick: (args: number) => void
onPercentOptionClick?: (args: number) => void
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is a shared component, we absolutely want a discriminated union here - one or the other should always be present: if we ever get to rendering this component, we should have an event handler for it.

} else if (onPercentOptionClick) {
onPercentOptionClick(option)
}
}, [onPercentOptionClick, onMaxClick, option])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since both are now optional, we could end up in a situation where we forget to pass onMaxClick, and the regular onClick handler gets fired, which could have very different logic as the max one

Comment on lines 55 to 58
const showMax = useMemo(
() => isEvmChainId(fromAssetId(assetId).chainId) && !isNativeEvmAsset(assetId),
[assetId],
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rendering of the button should be controlled by the presence of percentOptions, not that of the handler.

I believe a more semantic way to achieve this would be to pass the current default percentOptions ([1]) array as percentOptions if this condition is true, and an empty array otherwise.

This way, we would pass this consistently (at least for this component), and have the options rendering logic in a better place.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gomesalexandre is there an instance in the app where we currently display percentOptions that I could see? I couldn't find any but maybe I missed it. My initial thought was that if it wasn't being used the code should just be pulled out altogether and only support the Max button.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<TradeAssetInput itself is passed percentOptions. If you inspect consumers, eventually reaching <TradeInput />, you'll see this guy:

This is the prop that is controling which percent options are being displayed - showMax here is effectively redundant, as we're passing 1 (max) as an option from the parent, but then have children programmatically (not) render it

}, [balance, props])

const showMax = useMemo(
() => isEvmChainId(fromAssetId(assetId).chainId) && !isNativeEvmAsset(assetId),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also have a isToken method you may want to take a look at to keep things declarative, it effectively checks for "is non-native asset", which realistically means EVM chain tokens currently, though that could change in the future (as e.g Cosmos SDK chains can have tokens as wells, though we do not support those in the swapper) so the isEvmChainId() check is definitely welcomed here!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gomesalexandre hmm yeah maybe isToken is more future thinking, it would be better that if Cosmos support is added later on that the Max button will appear by default

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either or, that one is fine as-is as we most likely won't support tokens from non-EVM chains for trading in the future but we may. Would prefer it regardless as it reads nicer and is more declarative

@herbig
Copy link
Author

herbig commented Dec 22, 2023

It looks like the scientific notation is used however, versus precision in the send modal, see:

@gomesalexandre oh good catch, yeah I didn't see that since I only checked a few assets that would not have triggered scientific notation

maybe I'm missing something, but to me it seems both of those screenshots would be wrong.

Scientific notation in the Swap field isn't great UX, and I'll need to fix that.

But in the Send screen it looks as though you're sending 4.414, which isn't accurate.

Also so we're clear, the Max button in Send is already in production, and unrelated to Max here in the Swap, so I think we have a bug there in production. I've just tried prod Max and got this:

Screenshot 2023-12-22 at 1 39 47 PM

@gomesalexandre
Copy link
Contributor

It looks like the scientific notation is used however, versus precision in the send modal, see:

@gomesalexandre oh good catch, yeah I didn't see that since I only checked a few assets that would not have triggered scientific notation

maybe I'm missing something, but to me it seems both of those screenshots would be wrong.

Scientific notation in the Swap field isn't great UX, and I'll need to fix that.

The scientific notation should never be user-facing, we always want to have either full-precision when applicable, or trimmed decimal places depending on the screens and product spec. For things like sends and swapping, we want to ensure we display the actual amounts that users are going to send out of their wallet.

i.e when sending FOX, you can see that the balance (for display purposes only) has trimmed precision, but max. click actually inputs the full-precision amount, with 18 digits in the case of FOX:

image

But in the Send screen it looks as though you're sending 4.414, which isn't accurate.

Not sure what do you mean here @herbig? Clicking max/percent options will replace the input with the prorated one, with full asset precision, i.e in the screenshot above 115.618374110339184543 FOX (not e.g 115.61 FOX)

Also so we're clear, the Max button in Send is already in production, and unrelated to Max here in the Swap, so I think we have a bug there in production. I've just tried prod Max and got this:

That's correct as per our current flow, though definitely not the best UX: we do fees deduction (on native assets only obviously) so trying to send 0.0003 ETH would result in more fees than the actual balance, hence the negative value.

@0xean
Copy link
Contributor

0xean commented Jan 8, 2024

@herbig - thanks for the PR and work on this issue. @gomesalexandre is going to take it over from here since we haven't heard from you in a bit.

I would love to send some FOX tokens your way as a way to thank you for the work on this, if you want to DM me I can get that arranged. Thanks again for your contributions.

@gomesalexandre
Copy link
Contributor

Closing in favor of #5961

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.

swap max for non native assets
5 participants