Skip to content

Commit 88463a8

Browse files
SOV-4444: Rune pools convert updates (#1009)
* chore: add new component FilterPill * chore: update Convert Page, added filters by categories * Create many-candles-confess.md * chore: update component name * chore: update component * chore: close dropdown after click on token and keep open if category clicked * chore: filter our wbtc from rsk network * chore: fix mobile button view * chore: include wbtc and tbtc in btc category * chore: fix category
1 parent c20bcb3 commit 88463a8

11 files changed

Lines changed: 401 additions & 45 deletions

File tree

.changeset/many-candles-confess.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"frontend": patch
3+
"@sovryn/ui": patch
4+
---
5+
6+
SOV-4444: Rune pools convert updates

apps/frontend/src/app/5_pages/ConvertPage/ConvertPage.constants.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { SmartRouter, smartRoutes } from '@sovryn/sdk';
55
import { RSK_CHAIN_ID } from '../../../config/chains';
66

77
import { COMMON_SYMBOLS } from '../../../utils/asset';
8+
import { CategoryType } from './ConvertPage.types';
89

910
export const FIXED_RATE_ROUTES = ['MyntBasset', 'MyntFixedRate'];
1011

@@ -48,3 +49,10 @@ export const DEFAULT_SWAP_ENTRIES: Partial<Record<ChainIds, string>> = {
4849
[ChainIds.BOB_TESTNET]: COMMON_SYMBOLS.ETH,
4950
[ChainIds.SEPOLIA]: COMMON_SYMBOLS.ETH,
5051
};
52+
53+
export const CATEGORY_TOKENS: Record<CategoryType, string[]> = {
54+
[CategoryType.Stablecoins]: SMART_ROUTER_STABLECOINS,
55+
[CategoryType.BTC]: [COMMON_SYMBOLS.BTC],
56+
[CategoryType.Runes]: ['POWA'],
57+
[CategoryType.All]: [],
58+
};

apps/frontend/src/app/5_pages/ConvertPage/ConvertPage.tsx

Lines changed: 186 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import {
2424
IconNames,
2525
Paragraph,
2626
ParagraphSize,
27-
Select,
2827
SelectOption,
2928
SimpleTable,
3029
SimpleTableRow,
@@ -51,12 +50,18 @@ import {
5150
} from '../../../utils/asset';
5251
import { removeTrailingZerosFromString } from '../../../utils/helpers';
5352
import { decimalic, fromWei } from '../../../utils/math';
54-
import { FIXED_MYNT_RATE, FIXED_RATE_ROUTES } from './ConvertPage.constants';
53+
import {
54+
CATEGORY_TOKENS,
55+
FIXED_MYNT_RATE,
56+
FIXED_RATE_ROUTES,
57+
} from './ConvertPage.constants';
5558
import {
5659
DEFAULT_SWAP_ENTRIES,
5760
SMART_ROUTER_STABLECOINS,
5861
SWAP_ROUTES,
5962
} from './ConvertPage.constants';
63+
import { CategoryType } from './ConvertPage.types';
64+
import { AssetDropdownWithFilters } from './components/AssetDropdownWithFilters/AssetDropdownWithFilters';
6065
import { useConversionMaintenance } from './hooks/useConversionMaintenance';
6166
import { useGetMaximumAvailableAmount } from './hooks/useGetMaximumAvailableAmount';
6267
import { useHandleConversion } from './hooks/useHandleConversion';
@@ -76,23 +81,56 @@ const ConvertPage: FC = () => {
7681
[currentChainId],
7782
);
7883

84+
const [sourceCategories, setSourceCategories] = useState<CategoryType[]>([
85+
CategoryType.All,
86+
]);
87+
88+
const [destinationCategories, setDestinationCategories] = useState<
89+
CategoryType[]
90+
>([CategoryType.All]);
91+
7992
const tokensToOptions = useCallback(
8093
(
8194
addresses: string[],
8295
chain: ChainId,
8396
callback: (options: SelectOption<string>[]) => void,
84-
) =>
97+
categories: CategoryType[],
98+
) => {
8599
Promise.all(
86100
addresses
87101
.filter(
88-
// filter out WBTC token on rsk chain
102+
// filter out WBTC token on RSK chain
89103
item =>
90-
findAsset('WBTC', RSK_CHAIN_ID).address !== item.toLowerCase(),
104+
findAsset('WBTC', RSK_CHAIN_ID).address.toLowerCase() !==
105+
item.toLowerCase(),
91106
)
92107
.map(address => smartRouter.getTokenDetails(address, chain)),
93-
).then(tokens =>
108+
).then(tokens => {
109+
const tokensWithCategories = tokens.map(token => {
110+
const category = Object.keys(CATEGORY_TOKENS).find(type =>
111+
CATEGORY_TOKENS[type].includes(token.symbol),
112+
) as CategoryType;
113+
return { ...token, category };
114+
});
115+
116+
const filteredTokens = tokensWithCategories.filter(token => {
117+
if (categories.includes(CategoryType.BTC)) {
118+
if (
119+
token.symbol.toUpperCase() === CategoryType.BTC ||
120+
token.symbol.includes(CategoryType.BTC)
121+
) {
122+
return true;
123+
}
124+
}
125+
126+
return (
127+
categories.includes(token.category) ||
128+
categories.includes(CategoryType.All)
129+
);
130+
});
131+
94132
callback(
95-
tokens.map(token => ({
133+
filteredTokens.map(token => ({
96134
value: token.symbol,
97135
label: (
98136
<AssetRenderer
@@ -103,17 +141,31 @@ const ConvertPage: FC = () => {
103141
/>
104142
),
105143
})),
106-
),
107-
),
144+
);
145+
});
146+
},
108147
[smartRouter],
109148
);
110149

111150
const { account } = useAccount();
112151
const [searchParams, setSearchParams] = useSearchParams();
113152
const fromToken = searchParams.get('from') || '';
114153
const toToken = searchParams.get('to') || '';
154+
const categoryFromToken = searchParams.get('categoryFrom') || '';
155+
const categoryToToken = searchParams.get('categoryTo') || '';
115156
const { balance: myntBalance } = useAssetBalance(MYNT_TOKEN);
116157

158+
const parsedSourceCategories = useMemo(
159+
() =>
160+
categoryFromToken ? categoryFromToken.split(',') : [CategoryType.All],
161+
[categoryFromToken],
162+
);
163+
164+
const parsedDestinationCategories = useMemo(
165+
() => (categoryToToken ? categoryToToken.split(',') : [CategoryType.All]),
166+
[categoryToToken],
167+
);
168+
117169
const [slippageTolerance, setSlippageTolerance] = useState('0.5');
118170

119171
const [priceInQuote, setPriceQuote] = useState(false);
@@ -138,6 +190,28 @@ const ConvertPage: FC = () => {
138190

139191
const [sourceToken, setSourceToken] = useState<string>(defaultSourceToken);
140192

193+
const handleCategorySelect = useCallback(
194+
(
195+
category: CategoryType,
196+
setCategories: React.Dispatch<React.SetStateAction<CategoryType[]>>,
197+
) =>
198+
setCategories(prevCategories =>
199+
category === CategoryType.All
200+
? [CategoryType.All]
201+
: prevCategories.includes(category)
202+
? prevCategories.length === 1
203+
? [CategoryType.All]
204+
: prevCategories.filter(prevCategory => prevCategory !== category)
205+
: [
206+
...prevCategories.filter(
207+
prevCategory => prevCategory !== CategoryType.All,
208+
),
209+
category,
210+
],
211+
),
212+
[],
213+
);
214+
141215
const [showAdvancedSettings, setShowAdvancedSettings] = useState(true);
142216

143217
const [tokenOptions, setTokenOptions] = useState<SelectOption<string>[]>([]);
@@ -157,8 +231,15 @@ const ConvertPage: FC = () => {
157231
useEffect(() => {
158232
smartRouter
159233
.getEntries(currentChainId)
160-
.then(tokens => tokensToOptions(tokens, currentChainId, setTokenOptions));
161-
}, [currentChainId, smartRouter, tokensToOptions]);
234+
.then(tokens =>
235+
tokensToOptions(
236+
tokens,
237+
currentChainId,
238+
setTokenOptions,
239+
sourceCategories,
240+
),
241+
);
242+
}, [currentChainId, smartRouter, tokensToOptions, sourceCategories]);
162243

163244
useEffect(() => {
164245
(async () => {
@@ -168,15 +249,26 @@ const ConvertPage: FC = () => {
168249
);
169250
smartRouter
170251
.getDestination(currentChainId, sourceTokenDetails.address)
171-
.then(tokens => {
172-
tokensToOptions(tokens, currentChainId, setDestinationTokenOptions);
173-
});
252+
.then(tokens =>
253+
tokensToOptions(
254+
tokens,
255+
currentChainId,
256+
setDestinationTokenOptions,
257+
destinationCategories,
258+
),
259+
);
174260

175261
if (sourceToken === MYNT_TOKEN) {
176262
setDestinationToken(COMMON_SYMBOLS.SOV);
177263
}
178264
})();
179-
}, [currentChainId, smartRouter, sourceToken, tokensToOptions]);
265+
}, [
266+
currentChainId,
267+
smartRouter,
268+
sourceToken,
269+
tokensToOptions,
270+
destinationCategories,
271+
]);
180272

181273
const sourceTokenOptions = useMemo(
182274
() =>
@@ -331,18 +423,6 @@ const ConvertPage: FC = () => {
331423
setHasQuoteError(false);
332424
}, []);
333425

334-
const getAssetRenderer = useCallback(
335-
(token: string) => (
336-
<AssetRenderer
337-
showAssetLogo
338-
asset={token}
339-
chainId={currentChainId}
340-
assetClassName="font-medium"
341-
/>
342-
),
343-
[currentChainId],
344-
);
345-
346426
const { handleSubmit } = useHandleConversion(
347427
sourceToken,
348428
destinationToken,
@@ -410,7 +490,26 @@ const ConvertPage: FC = () => {
410490
if (toToken) {
411491
setDestinationToken(toToken);
412492
}
413-
}, [fromToken, toToken]);
493+
if (categoryFromToken) {
494+
const sourceCategories = parsedSourceCategories.map(
495+
category => CategoryType[category] || CategoryType.All,
496+
);
497+
setSourceCategories(sourceCategories);
498+
}
499+
if (categoryToToken) {
500+
const destinationCategories = parsedDestinationCategories.map(
501+
category => CategoryType[category] || CategoryType.All,
502+
);
503+
setDestinationCategories(destinationCategories);
504+
}
505+
}, [
506+
fromToken,
507+
toToken,
508+
categoryFromToken,
509+
categoryToToken,
510+
parsedSourceCategories,
511+
parsedDestinationCategories,
512+
]);
414513

415514
useEffect(() => {
416515
if (hasMyntBalance && fromToken === MYNT_TOKEN) {
@@ -435,10 +534,51 @@ const ConvertPage: FC = () => {
435534
urlParams.delete('to');
436535
}
437536

438-
if (toToken !== destinationToken || fromToken !== sourceToken) {
537+
if (sourceToken === destinationToken) {
538+
urlParams.delete('to');
539+
}
540+
541+
if (
542+
sourceCategories.length > 0 &&
543+
!sourceCategories.includes(CategoryType.All)
544+
) {
545+
urlParams.set('categoryFrom', sourceCategories.join(','));
546+
} else {
547+
urlParams.delete('categoryFrom');
548+
}
549+
550+
if (
551+
destinationCategories.length > 0 &&
552+
!destinationCategories.includes(CategoryType.All)
553+
) {
554+
urlParams.set('categoryTo', destinationCategories.join(','));
555+
} else {
556+
urlParams.delete('categoryTo');
557+
}
558+
559+
if (sourceToken === destinationToken) {
560+
setDestinationToken('');
561+
}
562+
563+
if (
564+
toToken !== destinationToken ||
565+
fromToken !== sourceToken ||
566+
categoryToToken !== destinationCategories.join(',') ||
567+
categoryFromToken !== sourceCategories.join(',')
568+
) {
439569
setSearchParams(new URLSearchParams(urlParams));
440570
}
441-
}, [sourceToken, destinationToken, setSearchParams, toToken, fromToken]);
571+
}, [
572+
sourceToken,
573+
destinationToken,
574+
setSearchParams,
575+
toToken,
576+
fromToken,
577+
sourceCategories,
578+
destinationCategories,
579+
categoryToToken,
580+
categoryFromToken,
581+
]);
442582

443583
useEffect(() => {
444584
if (!account) {
@@ -491,14 +631,14 @@ const ConvertPage: FC = () => {
491631
dataAttribute="convert-from-amount"
492632
placeholder="0"
493633
/>
494-
495-
<Select
496-
value={sourceToken}
497-
onChange={onSourceTokenChange}
498-
options={sourceTokenOptions}
499-
labelRenderer={() => getAssetRenderer(sourceToken)}
500-
className="min-w-[6.7rem]"
501-
menuClassName="max-h-[10rem] sm:max-h-[12rem]"
634+
<AssetDropdownWithFilters
635+
token={sourceToken}
636+
selectedCategories={sourceCategories}
637+
tokenOptions={sourceTokenOptions}
638+
onCategorySelect={category =>
639+
handleCategorySelect(category, setSourceCategories)
640+
}
641+
onTokenChange={onSourceTokenChange}
502642
dataAttribute="convert-from-asset"
503643
/>
504644
</div>
@@ -540,12 +680,14 @@ const ConvertPage: FC = () => {
540680
className="w-full flex-grow-0 flex-shrink"
541681
dataAttribute="convert-to-amount"
542682
/>
543-
<Select
544-
value={destinationToken}
545-
onChange={onDestinationTokenChange}
546-
options={destinationTokenOptions}
547-
className="min-w-[6.7rem]"
548-
menuClassName="max-h-[10rem] sm:max-h-[12rem]"
683+
<AssetDropdownWithFilters
684+
token={destinationToken}
685+
selectedCategories={destinationCategories}
686+
tokenOptions={destinationTokenOptions}
687+
onCategorySelect={category =>
688+
handleCategorySelect(category, setDestinationCategories)
689+
}
690+
onTokenChange={onDestinationTokenChange}
549691
dataAttribute="convert-to-asset"
550692
/>
551693
</div>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export enum CategoryType {
2+
All = 'All',
3+
Stablecoins = 'Stablecoins',
4+
BTC = 'BTC',
5+
Runes = 'Runes',
6+
}

0 commit comments

Comments
 (0)