-
Notifications
You must be signed in to change notification settings - Fork 423
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
🧹 (Dust to OSMO): Feature implementation #3913
Open
greg-nagy
wants to merge
15
commits into
osmosis-labs:stage
Choose a base branch
from
greg-nagy:dust-to-osmo
base: stage
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
6d9009e
feat: add dust to osmo feature flag
greg-nagy 44e725e
feat: add getUserDustAssets procedure
greg-nagy bf3f5c3
feat: add dust to osmo to portfolio page
greg-nagy c56853f
chore: add react query devtools
greg-nagy d28cfa1
feat: introduce generic queryClient
greg-nagy f611940
feat: add debounce config to useSwap
greg-nagy bc0d65d
feat: add isReadyToSwap to useSwap
greg-nagy e9809c8
feat: dust to osmo swap flow implementation
greg-nagy ccc7222
feat: sequential scheduling for correct account sequencing
greg-nagy 18d4f13
fix: don't refetch dust query
greg-nagy e3464e8
fix: add debounce time to swap to avoid account seq problems
greg-nagy 15750e7
fix: refresh the asset table to avoid stale assets
greg-nagy 6abfbdf
fix: stop loading if there is no dust
greg-nagy 559775b
feat: add in progress swap indicator
greg-nagy fc1e4b6
fix: prevent stale data flicker on 2nd press
greg-nagy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
236 changes: 236 additions & 0 deletions
236
packages/web/components/complex/dust-to-osmo/dust-to-osmo.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,236 @@ | ||
import { Dec } from "@keplr-wallet/unit"; | ||
import { useMutation, UseMutationResult } from "@tanstack/react-query"; | ||
import { useCallback, useEffect, useMemo, useState } from "react"; | ||
|
||
import { FallbackImg, Icon } from "~/components/assets"; | ||
import { Button } from "~/components/ui/button"; | ||
import { DefaultSlippage } from "~/config/swap"; | ||
import { useTranslation, useWalletSelect } from "~/hooks"; | ||
import { useSwap } from "~/hooks/use-swap"; | ||
import { useStore } from "~/stores"; | ||
import { HideDustUserSetting } from "~/stores/user-settings"; | ||
import { api } from "~/utils/trpc"; | ||
|
||
const maxSlippage = new Dec(DefaultSlippage); | ||
|
||
export function DustToOsmo({ onComplete }: { onComplete?: () => void }) { | ||
const { t } = useTranslation(); | ||
|
||
const { accountStore } = useStore(); | ||
const account = accountStore.getWallet(accountStore.osmosisChainId); | ||
const { isLoading: isLoadingWallet } = useWalletSelect(); | ||
|
||
const [convertDust, setConvertDust] = useState(false); | ||
|
||
const { | ||
data: dustAssets, | ||
isSuccess: isSuccessDust, | ||
isFetched: isFetchedDust, | ||
refetch: refetchDust, | ||
} = api.edge.assets.getUserDustAssets.useQuery( | ||
{ | ||
userOsmoAddress: account?.address ?? "", | ||
// TODO(Greg): the dust threshold has changed to 0.02. | ||
// Is it ok to use this or should I use the 0.01 which was specified? | ||
dustThreshold: HideDustUserSetting.DUST_THRESHOLD, | ||
}, | ||
{ | ||
enabled: !isLoadingWallet && Boolean(account?.address) && convertDust, | ||
cacheTime: 0, | ||
staleTime: 0, | ||
refetchOnWindowFocus: false, | ||
refetchOnMount: false, | ||
refetchOnReconnect: false, | ||
} | ||
); | ||
|
||
const handleConvertDustComplete = useCallback(() => { | ||
setConvertDust(false); | ||
// Refetch to ensure the dust assets are up to date | ||
refetchDust(); | ||
onComplete && onComplete(); | ||
}, [onComplete, refetchDust]); | ||
|
||
useEffect(() => { | ||
// Stop loading if there are no dust assets | ||
if (convertDust && isFetchedDust && dustAssets?.length === 0) { | ||
// Prevent a flash of the button if the dust assets are fetched quickly | ||
const timer = setTimeout(() => { | ||
setConvertDust(false); | ||
}, 500); | ||
|
||
return () => clearTimeout(timer); | ||
} | ||
}, [convertDust, dustAssets, isFetchedDust]); | ||
|
||
return ( | ||
<> | ||
<Button | ||
className="gap-2 !border !border-osmoverse-700 !py-2 !px-4 !text-wosmongton-200" | ||
variant="outline" | ||
size="lg-full" | ||
isLoading={convertDust} | ||
onClick={() => setConvertDust(true)} | ||
> | ||
<Icon id="dust-broom" className="h-6 w-6" /> | ||
{t("dustToOsmo.mainButton")} | ||
</Button> | ||
{convertDust && isSuccessDust && dustAssets.length > 0 && ( | ||
<SequentialSwapExecutor | ||
dustAssets={dustAssets} | ||
onComplete={handleConvertDustComplete} | ||
/> | ||
)} | ||
</> | ||
); | ||
} | ||
|
||
function SequentialSwapExecutor({ | ||
dustAssets, | ||
onComplete, | ||
}: { | ||
dustAssets: { | ||
coinDenom: string; | ||
amount: { toDec: () => { toString: () => string } }; | ||
}[]; | ||
onComplete: () => void; | ||
}) { | ||
const [swapTxStatuses, setSwapTxStatuses] = useState<{ | ||
[denom: string]: { | ||
status: UseMutationResult["status"]; | ||
denom: string; | ||
}; | ||
}>({}); | ||
|
||
const hasAllSwapsFinished = | ||
Object.keys(swapTxStatuses).length === dustAssets.length && | ||
Object.values(swapTxStatuses).every( | ||
({ status }) => status === "success" || status === "error" | ||
); | ||
|
||
const handleSwapTxStatusChange = useCallback( | ||
(denom: string, status: UseMutationResult["status"]) => | ||
setSwapTxStatuses((prev) => ({ ...prev, [denom]: { status, denom } })), | ||
[] | ||
); | ||
|
||
const activeSwapDenom = useMemo(() => { | ||
const runningSwapDenom = Object.values(swapTxStatuses).find( | ||
(s) => s.status === "loading" | ||
)?.denom; | ||
|
||
if (runningSwapDenom) { | ||
return runningSwapDenom; | ||
} | ||
|
||
const nextDenom = Object.values(swapTxStatuses).find( | ||
(s) => s.status === "idle" | ||
)?.denom; | ||
|
||
return nextDenom ?? ""; | ||
}, [swapTxStatuses]); | ||
|
||
useEffect(() => { | ||
// Notify the parent that we're done | ||
if (hasAllSwapsFinished) { | ||
onComplete(); | ||
} | ||
}, [hasAllSwapsFinished, onComplete]); | ||
|
||
return ( | ||
<> | ||
{dustAssets.map((dustAsset) => ( | ||
<SwapHandler | ||
key={dustAsset.coinDenom} | ||
fromDenom={dustAsset.coinDenom} | ||
amount={dustAsset.amount.toDec().toString()} | ||
onSendSwapTxStatusChange={handleSwapTxStatusChange} | ||
shouldExecuteSwap={activeSwapDenom === dustAsset.coinDenom} | ||
/> | ||
))} | ||
</> | ||
); | ||
} | ||
|
||
function SwapHandler({ | ||
fromDenom, | ||
amount, | ||
shouldExecuteSwap = false, | ||
onSendSwapTxStatusChange, | ||
}: { | ||
fromDenom: string; | ||
amount: string; | ||
shouldExecuteSwap: boolean; | ||
onSendSwapTxStatusChange: ( | ||
denom: string, | ||
status: UseMutationResult["status"] | ||
) => void; | ||
}) { | ||
const { | ||
fromAsset, | ||
toAsset, | ||
inAmountInput: { setAmount: setInAmount }, | ||
isReadyToSwap, | ||
sendTradeTokenInTx, | ||
} = useSwap({ | ||
initialFromDenom: fromDenom, | ||
initialToDenom: "OSMO", | ||
useQueryParams: false, | ||
maxSlippage, | ||
quoteType: "out-given-in", | ||
// Swap needs a bit of time to stabilize | ||
// otherwise account seq errors can happen | ||
inputDebounceMs: 100, | ||
}); | ||
|
||
useEffect(() => { | ||
setInAmount(amount); | ||
}, [setInAmount, amount]); | ||
|
||
const { | ||
mutate: sendSwapTx, | ||
isIdle: isSendSwapTxIdle, | ||
status: sendSwapTxStatus, | ||
} = useMutation(sendTradeTokenInTx); | ||
|
||
useEffect(() => { | ||
if (shouldExecuteSwap && isReadyToSwap && isSendSwapTxIdle) { | ||
sendSwapTx(); | ||
} | ||
}, [shouldExecuteSwap, isReadyToSwap, isSendSwapTxIdle, sendSwapTx]); | ||
|
||
useEffect(() => { | ||
onSendSwapTxStatusChange(fromDenom, sendSwapTxStatus); | ||
}, [fromDenom, sendSwapTxStatus, onSendSwapTxStatusChange]); | ||
|
||
return ( | ||
<> | ||
{shouldExecuteSwap && | ||
fromAsset?.amount?.currency.coinImageUrl && | ||
toAsset?.amount?.currency.coinImageUrl && ( | ||
<div className="flex lg:hidden items-center justify-end transition-colors duration-200 ease-in-out"> | ||
<FallbackImg | ||
alt={fromAsset?.amount?.denom} | ||
src={fromAsset?.amount?.currency.coinImageUrl} | ||
fallbacksrc="/icons/question-mark.svg" | ||
height={32} | ||
width={32} | ||
/> | ||
<Icon | ||
id="arrow-right" | ||
width={16} | ||
height={16} | ||
className="my-[8px] mx-[4px] text-osmoverse-500" | ||
/> | ||
<FallbackImg | ||
alt={toAsset?.amount?.denom} | ||
src={toAsset?.amount?.currency.coinImageUrl} | ||
fallbacksrc="/icons/question-mark.svg" | ||
height={32} | ||
width={32} | ||
/> | ||
</div> | ||
)} | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling for asset price fetching.
The filter assumes usdValue is always valid when defined. Consider adding error handling for potential failures in price fetching.
Also, consider adding a try-catch block to handle potential price fetching errors:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a filter above this that makes sure this is true. (The filter doesn't check the type assumption that the values are indeed
CoinPretty
orPricePretty
)The errors are ok to flow through the client.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@greg-nagy, thank you for the clarification. I understand that the filter above ensures
usdValue
is valid when defined, and that it's acceptable for errors to flow through to the client in this context. I'll keep this in mind for future reviews.✏️ Learnings added