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: update skip / SEO #1654

Merged
merged 10 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified web-ui/bun.lockb
Binary file not shown.
157 changes: 127 additions & 30 deletions web-ui/components/Assets/modals/rewardsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ import {
MenuItem,
MenuList,
Box,
Checkbox,
} from '@chakra-ui/react';
import { useChain, useChains } from '@cosmos-kit/react';
import { SkipRouter, SKIP_API_URL } from '@skip-router/core';
import { SkipRouter, SKIP_API_URL, UserAddress } from '@skip-router/core';
import { ibc } from 'interchain-query';
import { useCallback, useState } from 'react';
import { FaInfoCircle } from 'react-icons/fa';
Expand All @@ -42,7 +43,21 @@ import { useSkipExecute } from '@/hooks/useSkipExecute';
import { shiftDigits } from '@/utils';

const RewardsModal = ({ address, isOpen, onClose }: { address: string; isOpen: boolean; onClose: () => void }) => {
const chains = useChains(['cosmoshub', 'osmosis', 'stargaze', 'juno', 'sommelier', 'regen', 'dydx', 'saga']);
const chains = useChains([
'cosmoshub',
'osmosis',
'stargaze',
'juno',
'sommelier',
'regen',
'dydx',
'agoric',
'axelar',
'saga',
'stride',
'noble',
'neutron',
]);

const { wallet } = useChain('quicksilver');

Expand All @@ -65,13 +80,32 @@ const RewardsModal = ({ address, isOpen, onClose }: { address: string; isOpen: b
const { balance, refetch } = useAllBalancesQuery('quicksilver', address);
const [isSigning, setIsSigning] = useState<boolean>(false);
const [isBottomVisible, setIsBottomVisible] = useState(true);
const [selectedAssets, setSelectedAssets] = useState<string[]>([]);
const [selectAll, setSelectAll] = useState<boolean>(false);

const handleScroll = useCallback((event: React.UIEvent<HTMLDivElement>) => {
const target = event.currentTarget;
const isBottom = target.scrollHeight - target.scrollTop <= target.clientHeight;
setIsBottomVisible(!isBottom);
}, []);

const handleCheckboxChange = (denom: string) => {
setSelectedAssets((prevSelectedAssets) =>
prevSelectedAssets.includes(denom) ? prevSelectedAssets.filter((item) => item !== denom) : [...prevSelectedAssets, denom],
);
};
// Toggle selection of all assets
// If currently all are selected, deselect all
// Otherwise, select all available assets
const handleSelectAllChange = () => {
if (selectAll) {
setSelectedAssets([]);
} else {
setSelectedAssets(tokenDetails.map((detail) => detail?.denom ?? ''));
}
setSelectAll(!selectAll);
};

const { assets: skipAssets } = useSkipAssets('quicksilver-2');
const balanceData = balance?.balances || [];

Expand All @@ -81,6 +115,7 @@ const RewardsModal = ({ address, isOpen, onClose }: { address: string; isOpen: b

return balanceData
.map((balanceItem) => {
// filter out native quick tokens
if (balanceItem.denom.startsWith('q') || balanceItem.denom.startsWith('aq') || balanceItem.denom.startsWith('uq')) {
return null;
}
Expand Down Expand Up @@ -133,25 +168,68 @@ const RewardsModal = ({ address, isOpen, onClose }: { address: string; isOpen: b
const { routesData } = useSkipRoutesData(osmosisRoutesDataObjects);

const executeRoute = useSkipExecute(skipClient);

// Helper function to get ordered addresses based on requiredChainAddresses
const getOrderedAddresses = (requiredChainAddresses: string[], allAddresses: UserAddress[]): UserAddress[] => {
return requiredChainAddresses.map((chainID) => {
const addressObj = allAddresses.find((addr) => addr.chainID === chainID);
if (!addressObj) {
console.error(`Address for chainID ${chainID} not found`);
chalabi2 marked this conversation as resolved.
Show resolved Hide resolved
return {} as UserAddress;
}
return addressObj;
});
};

// Helper function to format the token name
const formatTokenName = (originDenom: string) => {
if (originDenom.toLowerCase().startsWith('st')) {
if (originDenom.toLowerCase() === 'stinj') {
return `st${originDenom.slice(2).toUpperCase()}`;
}
return `st${originDenom.slice(3).toUpperCase()}`;
}
return originDenom.toLowerCase().startsWith('factory/')
? originDenom.split('/').pop()?.replace(/^u/, '').toUpperCase()
: originDenom.slice(1).toUpperCase();
};

// uses all the data gathered to create the ibc transactions for sending assets to osmosis.
const handleExecuteRoute = async () => {
setIsSigning(true);

const addresses = {
'quicksilver-2': address,
'osmosis-1': chains.osmosis.address,
'cosmoshub-4': chains.cosmoshub.address,
'stargaze-1': chains.stargaze.address,
'sommelier-3': chains.sommelier.address,
'regen-1': chains.regen.address,
'juno-1': chains.juno.address,
'dydx-mainnet-1': chains.dydx.address,
};

// Execute each route in sequence
for (const route of routesData) {
const allAddresses: UserAddress[] = [
{ chainID: 'quicksilver-2', address },
{ chainID: 'osmosis-1', address: chains.osmosis.address ?? '' },
{ chainID: 'cosmoshub-4', address: chains.cosmoshub.address ?? '' },
{ chainID: 'stargaze-1', address: chains.stargaze.address ?? '' },
{ chainID: 'sommelier-3', address: chains.sommelier.address ?? '' },
{ chainID: 'regen-1', address: chains.regen.address ?? '' },
{ chainID: 'juno-1', address: chains.juno.address ?? '' },
{ chainID: 'dydx-mainnet-1', address: chains.dydx.address ?? '' },
{ chainID: 'stride-1', address: chains.stride.address ?? '' },
{ chainID: 'noble-1', address: chains.noble.address ?? '' },
{ chainID: 'neutron-1', address: chains.neutron.address ?? '' },
{ chainID: 'axelar-dojo-1', address: chains.axelar.address ?? '' },
{ chainID: 'agoric-3', address: chains.agoric.address ?? '' },
];

// Check if any address is undefined
const undefinedAddresses = allAddresses.filter((addr) => !addr.address);
if (undefinedAddresses.length > 0) {
console.error('Some addresses are undefined:', undefinedAddresses);
setIsSigning(false);
return;
}
chalabi2 marked this conversation as resolved.
Show resolved Hide resolved

// Filter routesData to only include selected assets
const filteredRoutesData = routesData.filter((route) => selectedAssets.includes(route?.sourceAssetDenom ?? ''));

// Execute each route in sequence with ordered addresses
for (const route of filteredRoutesData) {
const orderedAddresses = getOrderedAddresses(route?.requiredChainAddresses ?? ([] as string[]), allAddresses);
try {
await executeRoute(route, addresses, refetch);
await executeRoute(route, orderedAddresses, refetch);
} catch (error) {
console.error('Error executing route:', error);
setIsSigning(false);
Expand All @@ -177,6 +255,10 @@ const RewardsModal = ({ address, isOpen, onClose }: { address: string; isOpen: b
return;
}

if (!selectedAssets.includes(tokenDetail?.denom ?? '')) {
continue;
}

const [_, channel] = tokenDetail?.trace.split('/') ?? '';
const sourcePort = 'transfer';
const sourceChannel = channel;
Expand All @@ -198,6 +280,11 @@ const RewardsModal = ({ address, isOpen, onClose }: { address: string; isOpen: b
'regen-1': 'regen',
'juno-1': 'juno',
'dydx-mainnet-1': 'dydx',
'stride-1': 'stride',
'noble-1': 'noble',
'neutron-1': 'neutron',
'axelar-dojo-1': 'axelar',
'agoric-3': 'agoric',
};

const getChainName = (chainId: string) => {
Expand Down Expand Up @@ -271,6 +358,14 @@ const RewardsModal = ({ address, isOpen, onClose }: { address: string; isOpen: b
<Table variant="simple" colorScheme="whiteAlpha" size="sm">
<Thead position="sticky" top={0} bg="rgb(32,32,32)" zIndex={1}>
<Tr>
<Th color="complimentary.900">
<Checkbox
isChecked={selectAll}
onChange={handleSelectAllChange}
colorScheme="complimentary"
borderColor="complimentary.900"
/>
</Th>
joe-bowman marked this conversation as resolved.
Show resolved Hide resolved
<Th color="complimentary.900">Token</Th>
<Th color="complimentary.900" isNumeric>
Amount
Expand All @@ -279,26 +374,27 @@ const RewardsModal = ({ address, isOpen, onClose }: { address: string; isOpen: b
</Thead>
<Tbody overflowY={'auto'} maxH="250px">
{tokenDetails.map((detail, index) => (
<Tr key={index}>
<Tr key={detail?.denom ?? index}>
<Td color="white">
<Checkbox
isChecked={selectedAssets.includes(detail?.denom ?? '')}
onChange={() => handleCheckboxChange(detail?.denom ?? '')}
colorScheme="complimentary"
borderColor="complimentary.900"
/>
</Td>
<Td color="white">
<HStack>
<Image w="32px" h="32px" alt={detail?.originDenom} src={detail?.logoURI} />
<Text fontSize={'large'}>
{detail?.originDenom
? detail.originDenom.toLowerCase().startsWith('factory/')
? (() => {
const lastSegment = detail.originDenom.split('/').pop() || '';
return lastSegment.startsWith('u') ? lastSegment.slice(1).toUpperCase() : lastSegment.toUpperCase();
})()
: detail.originDenom.slice(1).toUpperCase()
: ''}
</Text>
<Text fontSize={'large'}>{formatTokenName(detail?.originDenom ?? '')}</Text>
</HStack>
</Td>
<Td fontSize={'large'} color="white" isNumeric>
{Number(shiftDigits(detail?.amount ?? '', -Number(detail?.decimals)))
.toFixed(2)
.toString()}
{Number(Number(shiftDigits(detail?.amount ?? '', -Number(detail?.decimals))).toFixed(2)) <= 0.01
? '< 0.01'
: Number(shiftDigits(detail?.amount ?? '', -Number(detail?.decimals)))
.toFixed(2)
.toString()}
</Td>
</Tr>
))}
Expand Down Expand Up @@ -329,6 +425,7 @@ const RewardsModal = ({ address, isOpen, onClose }: { address: string; isOpen: b
size="sm"
w="160px"
variant="outline"
isDisabled={selectedAssets.length === 0}
onClick={() => (destination === 'osmosis' ? handleExecuteRoute() : onSubmitClick())}
>
{isSigning === true && <Spinner size="sm" />}
Expand Down
2 changes: 1 addition & 1 deletion web-ui/components/Staking/modals/stakingProcessModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ export const StakingProcessModal: React.FC<StakingModalProps> = ({ isOpen, onClo
feeAmount = '10000';
} else {
// Default case
const fixedMinGasPrice = fees?.find(({ denom }) => denom === mainDenom)?.average_gas_price ?? '';
const fixedMinGasPrice = fees?.find(({ denom }: { denom: string }) => denom === mainDenom)?.average_gas_price ?? '';
feeAmount = shiftDigits(fixedMinGasPrice, 6).toString();
}

Expand Down
17 changes: 16 additions & 1 deletion web-ui/components/wallet-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,22 @@ import {
} from '@/components';

export const WalletButton: React.FC = () => {
const chains = useChains(['quicksilver', 'cosmoshub', 'osmosis', 'stargaze', 'juno', 'sommelier', 'regen', 'umee', 'dydx']);
const chains = useChains([
'quicksilver',
'cosmoshub',
'osmosis',
'stargaze',
'juno',
'sommelier',
'regen',
'umee',
'dydx',
'stride',
'noble',
'axelar',
'saga',
'neutron',
]);

const { connect, openView, status, message, wallet, isWalletError } = chains.quicksilver;

Expand Down
13 changes: 9 additions & 4 deletions web-ui/hooks/useSkipExecute.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import { ToastId } from '@chakra-ui/react';
import {SkipRouter, TxStatusResponse} from '@skip-router/core';
import {RouteResponse, SkipRouter, TxStatusResponse} from '@skip-router/core';
import { useCallback } from 'react';

import { useToaster, ToastType } from './useToaster';

interface UserAddress {
chainID: string;
address: string;
}

export function useSkipExecute(skipClient: SkipRouter) {
if (!skipClient) {
throw new Error('SkipRouter is not initialized');
}

const toaster = useToaster();

const executeRoute = useCallback(async (route: any, userAddresses: any, refetch: () => void) => {
const executeRoute = useCallback(async (route: any, userAddresses: UserAddress[], refetch: () => void) => {
// Initialize with null and allow for the type to be null or ToastId
let broadcastToastId: ToastId | null = null;

Expand Down Expand Up @@ -74,15 +79,15 @@ export function useSkipMessages(skipClient: SkipRouter) {
if (!skipClient) {
throw new Error('SkipRouter is not initialized');
}
const skipMessages = useCallback(async (route: any) => {
const skipMessages = useCallback(async (route: RouteResponse, addressList: string[]) => {
return await skipClient.messages({
sourceAssetDenom: route.sourceAssetDenom,
sourceAssetChainID: route.sourceAssetChainID,
destAssetDenom: route.destAssetDenom,
destAssetChainID: route.destAssetChainID,
amountIn: route.amountIn,
amountOut: route.amountOut,
addressList: route.addressList,
addressList: addressList,
operations: route.operations,
});
}, []);
Expand Down
2 changes: 1 addition & 1 deletion web-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"@interchain-ui/react": "1.10.0",
"@osmonauts/lcd": "^1.0.3",
"@radix-ui/react-icons": "^1.3.0",
"@skip-router/core": "2.4.1",
"@skip-router/core": "5.0.0",
"@tanstack/react-query": "4.36.1",
"@tanstack/react-query-devtools": "4.36.1",
"@types/crypto-js": "^4.2.1",
Expand Down
43 changes: 42 additions & 1 deletion web-ui/pages/about.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,50 @@ const AboutPage = () => {
maxW="6xl"
>
<Head>
<title>About </title>
<title>About - Quicksilver Zone</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Interchain liquid staking hub. Secure your stake with the user-focused liquid staking." />
<meta name="keywords" content="staking, Quicksilver Protocol, crypto staking, earn rewards, DeFi, blockchain" />
<meta name="author" content="Quicksilver Zone" />
<link rel="icon" href="/img/favicon-main.png" />

<meta property="og:title" content="About - Quicksilver Zone" />
<meta
property="og:description"
content="Interchain liquid staking hub. Secure your stake with the user-focused liquid staking."
/>
<meta property="og:url" content="https://app.quicksilver.zone/about" />
<meta property="og:image" content="https://app.quicksilver.zone/img/staking-banner.png" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="Quicksilver Protocol" />

<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="About - Quicksilver Zone" />
<meta
name="twitter:description"
content="Interchain liquid staking hub. Secure your stake with the user-focused liquid staking."
/>
<meta name="twitter:image" content="https://app.quicksilver.zone/img/staking-banner.png" />
<meta name="twitter:site" content="@QuicksilverProtocol" />

<script type="application/ld+json">
{JSON.stringify({
'@context': 'https://schema.org',
'@type': 'WebPage',
name: 'About - Quicksilver Zone',
description: 'Interchain liquid staking hub. Secure your stake with the user focused liquid staking.',
url: 'https://app.quicksilver.zone/aho8t',
image: 'https://app.quicksilver.zone/img/staking-banner.png',
publisher: {
'@type': 'Organization',
name: 'Quicksilver Protocol',
logo: {
'@type': 'ImageObject',
url: 'https://app.quicksilver.zone/img/logo.png',
},
},
})}
</script>
</Head>
<VStack spacing={4} align="stretch" m={8}>
<Heading as="h1" color="white" size="xl" textAlign="left">
Expand Down
Loading
Loading