Skip to content

Commit

Permalink
🤗 Embrace the 1-Click: Add 1-Click enable and modify to swap review (#…
Browse files Browse the repository at this point in the history
…3954)

* feat: add 1click trade panel to review order

* feat: handle 1CT status change

* refactor: move formatSpendLimit into utils/formatter.ts

* feat: make 1CT remainig time translations configurable

without breaking existing usages ... :)

* feat: add active 1CT session info to review order

* feat: 1CT settings modal scaffold w/ placeholder modal

the diff is big, but the change is simple -> better to separate

* feat: use the 1CT settings modal

* docs: why 2 signs needed after enabling 1CT on swap review

* refactor: move edit into a separate component

* refactor: use consistent naming for one click trading related concerns

* refactor: move remaining spend limit fetch into hook

* refactor: move session periods translations to oneClickTrading block

these defintions are reused in the review order screens too

* feat: add optional change tracking to useOneClickTradingParams

* fix: wait for `oneClickTradingInfo` to load when using it as inital value

there can't be a 1CT session without account address
but if we return early in the `useOneClickSession`, the default value
will stop the loading flag which introduces bugs down the line

e.g. In `useSessionParams` we use the `info`/`oneClickTradingInfo` to
set the inital params for the `transaction1CTParams` which the user can
start modifying or sending

this should reflect the params that the user has set when activated with
the last session but instead it's always set to the default value

with this change we can reliably wait until the `loading` is set to
false and then start setting the inital value

* fix: use params changes for create/change/edit 1CT params flow

- with the panel the user can toggle on/off the state of the
`transaction1CTParams`
- the user can modify the params
  - for a new session: when clicking on the change button
  - for existing session: when clicking on the overview
- we should send a 1CT tx if
  - the user has turned it on/off
    - when turning on we should use
      - use the default params unless user changes them
      - the changed params if any
  - user has made changes to the params and
    - had the feature turned on and haven't turned it off
    - has the feture turned on
    - has turned on the feature

* fix: <p> shouldn't be inside <p>

* fix: keep all the changes in 1CT settings

previously the current state was overwritten by the new and only the
last change was marked with green

* feat: make trpc logs configurable in dev mode

* fix: on rollback use inital params first and current params as fallback

* fix: discard current changes on settings modal close

* fix: update user settings after removing 1CT session

the `shouldUseOneClickTrading` from account base store is used in the
calculation is `isOneClickTradingEnabled` which is the basis of the
enabled state checking for 1CT session

* refactor: use change tracking by default in `useSessionParams`

it's cheap and simplifies the code

* refactor: drop shouldShow prop

* feat: implement exceeded error state

* refactor: use static translation keys

and don't calculate them from props

* fix: linter problems

* feat: update useOneClickTradingParams to update when input changes

now we can change the session parameters in 2 places so we need the
transaction1CTParams that we receive from the hook to always reflect the
latest state

unless there are changes made to the draft value of course

* feat: disable confirm button until 1CT session start loads

* feat: close review modal after 1CT enable

otherwise the review ui starts changing and updating for the enabled 1CT
which is confusing and misleading for the user

* fix: linter happiness is important

* fix: coderabbit suggestions

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Revert "fix: coderabbit suggestions"

This reverts commit f014e48.

* fix: coderabbit suggestion

with proper syntax

* fix: add nextparam check from coderabbit

* docs: update docs to follow the api

* fix: don't use remove display on create rollback

* refactor: give price formatter with symbol a descriptive name

* fix: review comments 1st batch

* docs: info about why spreading changes is necessary

* fix: wrap sendSwapTx in callback

* fix: add signOptions to estimate-tx-fees queryKey

the gas amount needed for the tx is different when using one click
trading so we need to update the estimated tx fee

* feat: sign only once when enabling 1Ct through review order

the `useAsync` adds some delay in picking up mobx store changes so we
need to wait for it a bit for the changes to properly propagate and the
`useSwap` to update before we can start executing the actual swap tx

otherwise we either need to sign the tx manually or the broadcast will
fail

* refactor: rework mobx <> 1CT session reactivity to be push based

The useSwap hook was not updating instantly after the create1CTSession
mutation, but before the onSuccess callback. This caused the next swap
transaction to either fail or not get signed properly through 1CT.

This could be worked around with waiting for the changes to propagate,
but that solution can be error prone.

To implement a proper fix, the logic for handling updates has been moved
into the mobx store. The `useOneClickTradingSession` now uses mobx’s
reaction() to react immediately to changes and update the state as soon
as it changes. This ensures that the session info is always available
and up to date after change — eg after create1CT mutation but before the
`onSuccess` callback.

The session data is stored as regular state, so any components using
this hook can rely on react’s built-in reactivity without needing to
work with mobx observables directly.

Finally, a useEffect is used to clean up resources when the component
unmounts.

* feat: use the updated 1CT session

* feat: before sending swap tx wait for networkfee to update

when enabling 1CT through the review order modal rarely it can happen
that the networkfee is refetching when the `sendTradeTokenInTx` is
called which can result in low gas fee for the tx

this safety measure gives networkfee a 1s safety buffer with 100ms
rechecks to update

* fix: add feature flag and new wallet checks

- don't show 1CT for new wallets that are without funding
- use the feature flag from launch darkly

* feat: read 1CT over spend params from simulation error

for some high volume tokens — eg OSMO — the token value is not included
in the spend limit

the source of truth for the tokens is the contract so we need to rely on
its error when running a simulation

* feat: remove 1CT spend limit error from swap tool

handling this concern is moved to the review order modal where the user
will be able to increase the spend limit during the swap flow

note: there was no option for the user to manually sign if 1CT is
enabled and there is spend limit error (they could proceed to the review
order modal, but there the confirm button was disabled)

if they want to proceed with the swap they need to increase the spend
limit

* feat: use swap simulation result in checking if swap would exceed limit

* fix: typo

* fixup! feat: read 1CT over spend params from simulation error

* fix: rabbit haunts the past

* Revert "refactor: rework mobx <> 1CT session reactivity to be push based"

This reverts commit 2758eef.

* Revert "feat: use the updated 1CT session"

This reverts commit 747dbc3.

* fix: typo

* docs: explain the refetch flow for 1CT session

* fix: don't include `isEnabled` in changes in 1CT settings standalone mode

isEnabled as a change is requied to be tracked in the review order modal
flow but in standalone mode when the modal is opened through the profile
the enabled toggle is treated differently from other parameter changes
and has its own lifecycle

* fixup! feat: sign only once when enabling 1Ct through review order

* fix: convert uusdc to usdc from over spend error

* fix: coderabbit

* fix: coderabbit

* Revert "fix: coderabbit"

This reverts commit f8ec361.

* fix: send log event on 1CT sesssion start from review order

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
  • Loading branch information
greg-nagy and coderabbitai[bot] authored Nov 25, 2024
1 parent 8179a7f commit 11449ab
Show file tree
Hide file tree
Showing 38 changed files with 2,290 additions and 867 deletions.
6 changes: 3 additions & 3 deletions packages/stores/src/account/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ export class AccountStore<Injects extends Record<string, any>[] = []> {
makeObservable(this);

autorun(async () => {
const isOneClickTradingEnabled = await this.getShouldUseOneClickTrading();
const isOneClickTradingEnabled = await this.isOneClickTradingEnabled();
const oneClickTradingInfo = await this.getOneClickTradingInfo();
const hasUsedOneClickTrading = await this.getHasUsedOneClickTrading();
runInAction(() => {
Expand Down Expand Up @@ -1472,7 +1472,7 @@ export class AccountStore<Injects extends Record<string, any>[] = []> {
}: {
messages: readonly EncodeObject[];
}): Promise<boolean> {
const isOneClickTradingEnabled = await this.isOneCLickTradingEnabled();
const isOneClickTradingEnabled = await this.isOneClickTradingEnabled();
const oneClickTradingInfo = await this.getOneClickTradingInfo();

if (!oneClickTradingInfo || !isOneClickTradingEnabled) {
Expand Down Expand Up @@ -1535,7 +1535,7 @@ export class AccountStore<Injects extends Record<string, any>[] = []> {
});
}

async isOneCLickTradingEnabled(): Promise<boolean> {
async isOneClickTradingEnabled(): Promise<boolean> {
const oneClickTradingInfo = await this.getOneClickTradingInfo();

if (isNil(oneClickTradingInfo)) return false;
Expand Down
5 changes: 4 additions & 1 deletion packages/web/.env
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,7 @@ TWITTER_API_URL=https://api.twitter.com/
BLOCKAID_BASE_URL=http://api.blockaid.io:80
# BLOCKAID_API_KEY=

NEXT_PUBLIC_SPEND_LIMIT_CONTRACT_ADDRESS=osmo10xqv8rlpkflywm92k5wdmplzy7khtasl9c2c08psmvlu543k724sy94k74
NEXT_PUBLIC_SPEND_LIMIT_CONTRACT_ADDRESS=osmo10xqv8rlpkflywm92k5wdmplzy7khtasl9c2c08psmvlu543k724sy94k74

# Disable TRPC logs in development
# NEXT_PUBLIC_TRPC_LOGS=off
54 changes: 53 additions & 1 deletion packages/web/components/alert/__tests__/prettify.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Dec } from "@keplr-wallet/unit";
import cases from "jest-in-case";

import { isOverspendErrorMessage, isRejectedTxErrorMessage } from "../prettify";
import {
getParametersFromOverspendErrorMessage,
isOverspendErrorMessage,
isRejectedTxErrorMessage,
} from "../prettify";

cases(
"isOverspendErrorMessage",
Expand Down Expand Up @@ -45,6 +50,53 @@ cases(
]
);

cases(
"getParametersFromOverspendErrorMessage",
({ message, result }) => {
expect(getParametersFromOverspendErrorMessage(message)).toEqual(result);
},
[
{
name: "should extract parameters from valid overspend error message",
message:
"Fetch error. Spend limit error: Overspend: 2000000 has been spent but limit is 1000000.",
result: {
wouldSpendTotal: new Dec("2000000", 6),
limit: new Dec("1000000", 6),
},
},
{
name: "should extract parameters from complex overspend error message",
message:
"Fetch error. execution blocked by authenticator (account = osmo1sh8lreekwcytxpqr6lxmw5cl7kdrfsdfat2ujlvz, authenticator id = 208, msg index = 0, msg type url = /osmosis.poolmanager.v1beta1.MsgSwapExactAmountIn): Spend limit error: Overspend: 50065777 has been spent but limit is 1000000: execute wasm contract failed",
result: {
wouldSpendTotal: new Dec("50065777", 6),
limit: new Dec("1000000", 6),
},
},
{
name: "should handle empty message",
message: "",
result: undefined,
},
{
name: "should handle undefined message",
message: undefined,
result: undefined,
},
{
name: "should return undefined for non-overspend error message",
message: "execution succeeded",
result: undefined,
},
{
name: "should return undefined for invalid overspend error format",
message: "Spend limit error: Invalid format",
result: undefined,
},
]
);

cases(
"isRejectedTxErrorMessage",
({ message, result }) => {
Expand Down
30 changes: 29 additions & 1 deletion packages/web/components/alert/prettify.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AppCurrency } from "@keplr-wallet/types";
import { CoinPretty, Int } from "@keplr-wallet/unit";
import { CoinPretty, Dec, Int } from "@keplr-wallet/unit";
import {
isInsufficientFeeError,
isSlippageErrorMessage,
Expand Down Expand Up @@ -27,6 +27,34 @@ const regexRejectedTx = /Request rejected/;
const regexOverspendError =
/Spend limit error: Overspend: (\d+) has been spent but limit is (\d+)/;

export function getParametersFromOverspendErrorMessage(
message: string | undefined
): { wouldSpendTotal: Dec; limit: Dec } | undefined {
if (!message) return;

const match = message.match(regexOverspendError);
if (!match) return;

const [, wouldSpendTotal, limit] = match;

if (!wouldSpendTotal || !limit) return;

try {
// Validate that extracted values are valid numbers
if (isNaN(Number(wouldSpendTotal)) || isNaN(Number(limit))) {
return;
}

return {
wouldSpendTotal: new Dec(wouldSpendTotal, 6),
limit: new Dec(limit, 6),
};
} catch (error) {
console.error("Failed to parse overspend error parameters:", error);
return;
}
}

export function isOverspendErrorMessage({
message,
}: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import { humanizeTime } from "~/utils/date";

export const OneClickTradingRemainingTime: FunctionComponent<{
className?: string;
}> = ({ className }) => {
useShortTimeUnits?: boolean;
}> = ({ className, useShortTimeUnits }) => {
const { oneClickTradingInfo, isOneClickTradingExpired } =
useOneClickTradingSession();
const { t } = useTranslation();
Expand All @@ -24,7 +25,8 @@ export const OneClickTradingRemainingTime: FunctionComponent<{
humanizeTime(
dayjs.unix(
unixNanoSecondsToSeconds(oneClickTradingInfo.sessionPeriod.end)
)
),
useShortTimeUnits
)
);
};
Expand All @@ -39,7 +41,7 @@ export const OneClickTradingRemainingTime: FunctionComponent<{
);

return () => clearInterval(intervalId);
}, [oneClickTradingInfo]);
}, [oneClickTradingInfo, useShortTimeUnits]);

if (isOneClickTradingExpired) {
return (
Expand Down
Loading

0 comments on commit 11449ab

Please sign in to comment.