Skip to content

Commit

Permalink
swap in extension (#19689)
Browse files Browse the repository at this point in the history
## Description 

Describe the changes or additions included in this PR.

## Test plan 

How did you test the new or updated feature?

---

## Release notes

Check each box that your changes affect. If none of the boxes relate to
your changes, release notes aren't required.

For each box you select, include information after the relevant heading
that describes the impact of your changes that a user might notice and
any actions they must take to implement updates.

- [ ] Protocol: 
- [ ] Nodes (Validators and Full nodes): 
- [ ] Indexer: 
- [ ] JSON-RPC: 
- [ ] GraphQL: 
- [ ] CLI: 
- [ ] Rust SDK:
- [ ] REST API:
  • Loading branch information
plam-ml authored Oct 4, 2024
1 parent fedfb0d commit a6539b1
Show file tree
Hide file tree
Showing 18 changed files with 848 additions and 851 deletions.
2 changes: 1 addition & 1 deletion apps/core/src/utils/transaction/getBalanceChangeSummary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export type BalanceChange = {
export type BalanceChangeByOwner = Record<string, BalanceChange[]>;
export type BalanceChangeSummary = BalanceChangeByOwner | null;

function getOwnerAddress(owner: ObjectOwner): string {
export function getOwnerAddress(owner: ObjectOwner): string {
if (typeof owner === 'object') {
if ('AddressOwner' in owner) {
return owner.AddressOwner;
Expand Down
22 changes: 18 additions & 4 deletions apps/wallet/src/ui/app/helpers/filterAndSortTokenBalances.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { USDC_TYPE_ARG } from '_pages/swap/utils';
import { type CoinBalance } from '@mysten/sui/client';
import { SUI_TYPE_ARG } from '@mysten/sui/utils';

// Sort tokens by symbol and total balance
// Move this to the API backend
// Filter out tokens with zero balance
export function filterAndSortTokenBalances(tokens: CoinBalance[]) {
return tokens
.filter((token) => Number(token.totalBalance) > 0)
.sort((a, b) =>
(getCoinSymbol(a.coinType) + Number(a.totalBalance)).localeCompare(
.sort((a, b) => {
if (a.coinType === SUI_TYPE_ARG) {
return -1;
}
if (b.coinType === SUI_TYPE_ARG) {
return 1;
}
if (a.coinType === USDC_TYPE_ARG) {
return -1;
}
if (b.coinType === USDC_TYPE_ARG) {
return 1;
}
return (getCoinSymbol(a.coinType) + Number(a.totalBalance)).localeCompare(
getCoinSymbol(b.coinType) + Number(b.totalBalance),
),
);
);
});
}

export function getCoinSymbol(coinTypeArg: string) {
Expand Down
13 changes: 13 additions & 0 deletions apps/wallet/src/ui/app/hooks/useSupportedCoins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0
import { useAppsBackend } from '@mysten/core';
import { useQuery } from '@tanstack/react-query';

export function useSupportedCoins() {
const { request } = useAppsBackend();

return useQuery({
queryKey: ['supported-coins-apps-backend'],
queryFn: async () => request<{ supported: string[] }>('swap/coins'),
});
}
68 changes: 68 additions & 0 deletions apps/wallet/src/ui/app/hooks/useValidSwapTokensList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0
import { useActiveAddress } from '_app/hooks/useActiveAddress';
import { useGetAllBalances } from '_app/hooks/useGetAllBalances';
import { useRecognizedPackages } from '_app/hooks/useRecognizedPackages';
import { useSupportedCoins } from '_app/hooks/useSupportedCoins';
import { type CoinBalance } from '@mysten/sui/client';
import {
normalizeStructTag,
normalizeSuiObjectId,
parseStructTag,
SUI_TYPE_ARG,
} from '@mysten/sui/utils';
import { useMemo } from 'react';

export function filterTokenBalances(tokens: CoinBalance[]) {
return tokens.filter(
(token) => Number(token.totalBalance) > 0 || token.coinType === SUI_TYPE_ARG,
);
}

export function useValidSwapTokensList() {
const address = useActiveAddress();
const { data, isLoading: isSupportedCoinsLoading } = useSupportedCoins();
const { data: rawCoinBalances, isLoading: isGetAllBalancesLoading } = useGetAllBalances(
address || '',
);
const packages = useRecognizedPackages();
const normalizedPackages = useMemo(
() => packages.map((id) => normalizeSuiObjectId(id)),
[packages],
);

const supported = useMemo(
() =>
data?.supported.filter((type) => normalizedPackages.includes(parseStructTag(type).address)),
[data, normalizedPackages],
);

const coinBalances = useMemo(
() => (rawCoinBalances ? filterTokenBalances(rawCoinBalances) : null),
[rawCoinBalances],
);

const validSwaps = useMemo(
() =>
supported?.sort((a, b) => {
const suiType = normalizeStructTag(SUI_TYPE_ARG);
const balanceA = BigInt(
coinBalances?.find(
(balance) => normalizeStructTag(balance.coinType) === normalizeStructTag(a),
)?.totalBalance ?? 0,
);
const balanceB = BigInt(
coinBalances?.find(
(balance) => normalizeStructTag(balance.coinType) === normalizeStructTag(b),
)?.totalBalance ?? 0,
);
return a === suiType ? -1 : b === suiType ? 1 : Number(balanceB - balanceA);
}) ?? [],
[supported, coinBalances],
);

return {
isLoading: isSupportedCoinsLoading || isGetAllBalancesLoading,
data: validSwaps,
};
}
4 changes: 2 additions & 2 deletions apps/wallet/src/ui/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { useAppDispatch, useAppSelector } from '_hooks';
import { UsdcPromo } from '_pages/home/usdc-promo/UsdcPromo';
import { SwapPage } from '_pages/swap';
import { FromAssets } from '_pages/swap/FromAssets';
import { CoinsSelectionPage } from '_pages/swap/CoinsSelectionPage';
import { setNavVisibility } from '_redux/slices/app';
import { isLedgerAccountSerializedUI } from '_src/background/accounts/LedgerAccount';
import { persistableStorage } from '_src/shared/analytics/amplitude';
Expand Down Expand Up @@ -178,7 +178,7 @@ const App = () => {
<Route path="send/select" element={<CoinsSelectorPage />} />
<Route path="stake/*" element={<Staking />} />
<Route path="swap/*" element={<SwapPage />} />
<Route path="swap/from-assets" element={<FromAssets />} />
<Route path="swap/coins-select" element={<CoinsSelectionPage />} />
<Route path="tokens/*" element={<TokenDetailsPage />} />
<Route path="transactions/:status?" element={<TransactionBlocksPage />} />
<Route path="*" element={<Navigate to="/tokens" replace={true} />} />
Expand Down
40 changes: 15 additions & 25 deletions apps/wallet/src/ui/app/pages/home/tokens/TokensDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,7 @@ import Alert from '_components/alert';
import { CoinIcon } from '_components/coin-icon';
import Loading from '_components/loading';
import { filterAndSortTokenBalances } from '_helpers';
import {
useAllowedSwapCoinsList,
useAppSelector,
useCoinsReFetchingConfig,
useSortedCoinsByCategories,
} from '_hooks';
import { useAppSelector, useCoinsReFetchingConfig, useSortedCoinsByCategories } from '_hooks';
import { UsdcPromoBanner } from '_pages/home/usdc-promo/UsdcPromoBanner';
import {
DELEGATED_STAKES_QUERY_REFETCH_INTERVAL,
Expand Down Expand Up @@ -116,12 +111,9 @@ export function TokenRow({
const params = new URLSearchParams({
type: coinBalance.coinType,
});
const allowedSwapCoinsList = useAllowedSwapCoinsList();

const balanceInUsd = useBalanceInUSD(coinBalance.coinType, coinBalance.totalBalance);

const isRenderSwapButton = allowedSwapCoinsList.includes(coinType);

const coinMetadataOverrides = useCoinMetadataOverrides();
return (
<Tag
Expand Down Expand Up @@ -161,24 +153,22 @@ export function TokenRow({
>
Send
</TokenRowButton>
{isRenderSwapButton && (
<TokenRowButton
coinBalance={coinBalance}
to={`/swap?${params.toString()}`}
onClick={() => {
ampli.clickedSwapCoin({
coinType: coinBalance.coinType,
totalBalance: Number(formatted),
sourceFlow: 'TokenRow',
});
}}
>
Swap
</TokenRowButton>
)}
<TokenRowButton
coinBalance={coinBalance}
to={`/swap?${params.toString()}`}
onClick={() => {
ampli.clickedSwapCoin({
coinType: coinBalance.coinType,
totalBalance: Number(formatted),
sourceFlow: 'TokenRow',
});
}}
>
Swap
</TokenRowButton>
</div>
) : (
<div className="flex gap-1 items-start">
<div className="flex flex-wrap gap-1 items-start">
<Text variant="subtitleSmall" weight="semibold" color="gray-90">
{symbol}
</Text>
Expand Down
4 changes: 2 additions & 2 deletions apps/wallet/src/ui/app/pages/home/usdc-promo/UsdcPromo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function UsdcPromo() {
const [searchParams] = useSearchParams();
const fromCoinType = searchParams.get('type');
const presetAmount = searchParams.get('presetAmount');
const { promoBannerSheetTitle, promoBannerSheetContent } = useUsdcPromo();
const { promoBannerSheetTitle, promoBannerSheetContent, ctaLabel } = useUsdcPromo();

return (
<div className="flex flex-col items-center gap-6">
Expand All @@ -33,7 +33,7 @@ export function UsdcPromo() {
</Text>
</div>
<Button
text="Swap wUSDC"
text={ctaLabel}
onClick={() => {
ampli.clickedSwapCoin({
sourceFlow: 'UsdcPromoBanner',
Expand Down
2 changes: 2 additions & 0 deletions apps/wallet/src/ui/app/pages/home/usdc-promo/useUsdcPromo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type WalletUsdcPromo = {
promoBannerText: string;
promoBannerSheetTitle: string;
promoBannerSheetContent: string;
ctaLabel: string;
wrappedUsdcList: string[];
};

Expand All @@ -19,6 +20,7 @@ export function useUsdcPromo() {
promoBannerText: '',
promoBannerSheetTitle: '',
promoBannerSheetContent: '',
ctaLabel: '',
wrappedUsdcList: [],
});

Expand Down
49 changes: 28 additions & 21 deletions apps/wallet/src/ui/app/pages/swap/AssetData.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,58 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0
import { useActiveAccount } from '_app/hooks/useActiveAccount';
import { Heading } from '_app/shared/heading';
import { Text } from '_app/shared/text';
import { ButtonOrLink } from '_app/shared/utils/ButtonOrLink';
import { CoinIcon } from '_components/coin-icon';
import { DescriptionItem } from '_pages/approval-request/transaction-request/DescriptionList';
import { useGetBalance } from '_pages/swap/utils';
import { useCoinMetadata } from '@mysten/core';
import { ChevronDown16 } from '@mysten/icons';

export function AssetData({
tokenBalance,
coinType,
symbol,
to,
onClick,
disabled,
}: {
tokenBalance: string;
coinType: string;
symbol: string;
to?: string;
onClick?: () => void;
disabled?: boolean;
}) {
const activeAccount = useActiveAccount();
const currentAddress = activeAccount?.address;

const { data: balance } = useGetBalance({
coinType,
owner: currentAddress,
});

const { data: coinMetadata } = useCoinMetadata(coinType);

return (
<DescriptionItem
title={
<div className="flex gap-1 items-center">
<CoinIcon coinType={coinType} size="sm" />
<ButtonOrLink
disabled={disabled}
onClick={onClick}
to={to}
className="flex gap-1 items-center no-underline outline-none border-transparent bg-transparent p-0"
>
<Heading variant="heading6" weight="semibold" color="hero-dark">
{symbol}
</Heading>
{!disabled && <ChevronDown16 className="h-4 w-4 text-hero-dark" />}
</ButtonOrLink>
</div>
<ButtonOrLink
disabled={disabled}
onClick={onClick}
to={to}
className="flex gap-1 items-center no-underline outline-none border-transparent bg-transparent p-0"
>
{!!coinType && <CoinIcon coinType={coinType} size="md" />}
<Heading variant="heading6" weight="semibold" color="hero-dark">
{coinMetadata?.symbol || 'Select coin'}
</Heading>
{!disabled && <ChevronDown16 className="h-4 w-4 text-hero-dark" />}
</ButtonOrLink>
}
>
{!!tokenBalance && (
<div className="flex gap-1">
{!!balance && (
<div className="flex flex-wrap gap-1 justify-end">
<div className="text-bodySmall font-medium text-hero-darkest/40">Balance</div>{' '}
<Text variant="bodySmall" weight="medium" color="steel-darker">
{tokenBalance} {symbol}
{balance?.formatted} {coinMetadata?.symbol}
</Text>
</div>
)}
Expand Down
Loading

0 comments on commit a6539b1

Please sign in to comment.