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

feat: enable the spacewalk option from menu on pendulum network #347

Merged
Show file tree
Hide file tree
Changes from all 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
131 changes: 66 additions & 65 deletions src/NodeInfoProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,18 @@ import { rpc } from '@pendulum-chain/types';
import { ApiPromise, WsProvider } from '@polkadot/api';
import { createContext } from 'preact';
import { useContext, useEffect, useState } from 'preact/hooks';
import { toast } from 'react-toastify';
import { ToastMessage, showToast } from './shared/showToast';

async function createApiPromise(provider: WsProvider) {
return ApiPromise.create(
options({
provider,
rpc,
throwOnConnect: true,
throwOnUnknown: true,
}),
);
}

export interface NodeInfoProviderInterface {
bestNumberFinalize?: number;
Expand All @@ -21,19 +32,58 @@ const NodeInfoContext = createContext({
setState: {} as Dispatch<SetStateAction<Partial<NodeInfoProviderInterface>>>,
});

const NodeInfoProvider = ({
children,
tenantRPC,
value = {},
}: {
children: ReactNode;
tenantRPC?: string;
value?: Partial<NodeInfoProviderInterface>;
}) => {
const [state, setState] = useState(value);
const NodeInfoProvider = ({ children, tenantRPC }: { children: ReactNode; tenantRPC?: string }) => {
const [state, setState] = useState({} as Partial<NodeInfoProviderInterface>);
const [currentTenantRPC, setCurrentTenantRPC] = useState<string | undefined>(undefined);
const [pendingInitiationPromise, setPendingInitiationPromise] = useState<Promise<unknown> | undefined>(undefined);

async function updateStateWithChainProperties(api: ApiPromise) {
const bestNumberFinalize = await api.derive.chain.bestNumber();
const chainProperties = await api.registry.getChainProperties();
const ss58Format = chainProperties?.get('ss58Format').toString();
const tokenDecimals = Number(
chainProperties
?.get('tokenDecimals')
.toString()
.replace(/[\\[\]]/g, ''),
);
const tokenSymbol = chainProperties
?.get('tokenSymbol')
.toString()
.replace(/[\\[\]]/g, '');

setState((prevState) => ({
...prevState,
bestNumberFinalize: Number(bestNumberFinalize),
ss58Format: Number(ss58Format),
tokenDecimals,
tokenSymbol,
// TODO: same as for the api we could create a common interface for fetching data from indexer (swap assets, pools, other info)
// and pass the instance based on tenant to this context to be used in Swap, Pools components...
api,
}));
}

async function updateStateWithSystemInfo(api: ApiPromise) {
const [chain, nodeName, nodeVersion] = await Promise.all([
api.rpc.system.chain(),
api.rpc.system.name(),
api.rpc.system.version(),
]);

setState((prevState) => ({
...prevState,
chain: chain.toString(),
nodeName: nodeName.toString(),
nodeVersion: nodeVersion.toString(),
}));
}

async function handleConnectionError(error: Error) {
console.error('Error while connecting to the node:', error);
showToast(ToastMessage.NODE_CONNECTION_ERROR);
}

useEffect(() => {
let disconnect: () => void = () => undefined;

Expand All @@ -43,72 +93,23 @@ const NodeInfoProvider = ({
}

const connect = async () => {
console.log('connecting to', tenantRPC);

const provider = new WsProvider(tenantRPC, false);
await provider.connect();
const api = await ApiPromise.create(
options({
provider,
rpc,
// These are necessary so that the promise throws the error
throwOnConnect: true,
throwOnUnknown: true,
}),
);

const bestNumberFinalize = await api.derive.chain.bestNumber();
const chainProperties = await api.registry.getChainProperties();
const ss58Format = chainProperties?.get('ss58Format').toString();
const tokenDecimals = Number(
chainProperties
?.get('tokenDecimals')
.toString()
.replace(/[\\[\]]/g, ''),
);
const tokenSymbol = chainProperties
?.get('tokenSymbol')
.toString()
.replace(/[\\[\]]/g, '');

setState((prevState) => ({
...prevState,
bestNumberFinalize: Number(bestNumberFinalize),
ss58Format: Number(ss58Format),
tokenDecimals,
tokenSymbol,
// TODO: same as for the api we could create a common interface for fetching data from indexer (swap assets, pools, other info)
// and pass the instance based on tenant to this context to be used in Swap, Pools components...
api,
}));

const [chain, nodeName, nodeVersion] = await Promise.all([
api.rpc.system.chain(),
api.rpc.system.name(),
api.rpc.system.version(),
]);

setState((prevState) => ({
...prevState,
chain: chain.toString(),
nodeName: nodeName.toString(),
nodeVersion: nodeVersion.toString(),
}));

const api = await createApiPromise(provider);
await updateStateWithChainProperties(api);
await updateStateWithSystemInfo(api);

disconnect = () => {
api.disconnect();
};
};

console.log('pendingInitiationPromise', pendingInitiationPromise);
if (!pendingInitiationPromise) {
// We need this promise based approach to prevent race conditions when the user switches between tenants very quickly.
// Otherwise, it might happen that the connection to the first endpoint takes longer and resolves later than
// the connection to the second endpoint which would make us end up with a connection to the outdated endpoint.
const promise = connect().catch((error) => {
console.error('Error while connecting to the node:', error);
toast('Error while connecting to the node. Refresh the page to re-connect.', { type: toast.TYPE.ERROR });
});
const promise = connect().catch(handleConnectionError);
setPendingInitiationPromise(promise);
} else {
pendingInitiationPromise.then(() => {
Expand Down
6 changes: 2 additions & 4 deletions src/components/Layout/links.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const links: Links = ({ tenantName }) => [
props: {
className: ({ isActive } = {}) => (isActive ? 'active' : ''),
},
prefix: <DashboardIcon />
prefix: <DashboardIcon />,
},
{
link: 'https://app.zenlink.pro/',
Expand All @@ -58,8 +58,6 @@ export const links: Links = ({ tenantName }) => [
className: ({ isActive } = {}) => (isActive ? 'active' : tenantName === TenantName.Pendulum ? 'active' : ''),
},
prefix: <SpacewalkIcon />,
disabled: tenantName === TenantName.Pendulum,
suffix: tenantName === TenantName.Pendulum ? <ComingSoonTag /> : null,
submenu: [
{
link: './spacewalk/bridge',
Expand All @@ -77,7 +75,7 @@ export const links: Links = ({ tenantName }) => [
props: {
className: ({ isActive } = {}) => (isActive ? 'active' : ''),
},
prefix: <StakingIcon />
prefix: <StakingIcon />,
},
{
link: `https://${tenantName}.polkassembly.io/`,
Expand Down
2 changes: 1 addition & 1 deletion src/components/Selector/VaultSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ChevronDownIcon } from '@heroicons/react/20/solid';
import { Button, Dropdown } from 'react-daisyui';
import { convertCurrencyToStellarAsset } from '../../helpers/spacewalk';
import { ExtendedRegistryVault } from '../../hooks/spacewalk/vaultRegistry';
import { ExtendedRegistryVault } from '../../hooks/spacewalk/useVaultRegistryPallet';
import { nativeToDecimal } from '../../shared/parseNumbers/metric';
import { PublicKey } from '../PublicKey';

Expand Down
2 changes: 1 addition & 1 deletion src/components/TransferCountdown/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { SpacewalkPrimitivesIssueIssueRequest, SpacewalkPrimitivesRedeemRedeemRe
import { DateTime } from 'luxon';
import { useEffect, useMemo, useState } from 'preact/compat';
import { calculateDeadline } from '../../helpers/spacewalk';
import { useSecurityPallet } from '../../hooks/spacewalk/security';
import { useSecurityPallet } from '../../hooks/spacewalk/useSecurityPallet';

interface TransferCountdownProps {
request: SpacewalkPrimitivesIssueIssueRequest | SpacewalkPrimitivesRedeemRedeemRequest;
Expand Down
4 changes: 2 additions & 2 deletions src/components/Wallet/WalletConnect/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { WalletConnectModal } from '@walletconnect/modal';
import UniversalProvider from '@walletconnect/universal-provider';
import { SessionTypes } from '@walletconnect/types';
import { useCallback, useEffect, useState } from 'preact/compat';
import { toast } from 'react-toastify';
import logo from '../../../assets/wallet-connect.svg';
import { config } from '../../../config';
import { chainIds, walletConnectConfig } from '../../../config/walletConnect';
import { useGlobalState } from '../../../GlobalStateProvider';
import { walletConnectService } from '../../../services/walletConnect';
import { ToastMessage, showToast } from '../../../shared/showToast';

const WalletConnect = () => {
const [loading, setLoading] = useState(false);
Expand Down Expand Up @@ -61,7 +61,7 @@ const WalletConnect = () => {

//@eslint-disable-next-line no-explicit-any
} catch (error: any) {
toast(error, { type: 'error' });
showToast(ToastMessage.ERROR, error);
} finally {
setLoading(false);
}
Expand Down
23 changes: 15 additions & 8 deletions src/helpers/spacewalk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ export function convertCurrencyToStellarAsset(currency: SpacewalkPrimitivesCurre
} else {
return null;
}
} catch (e) {
console.error('Error converting currency to stellar asset', e);
} catch {
return null;
}
}
Expand Down Expand Up @@ -113,13 +112,21 @@ export function currencyToString(currency: SpacewalkPrimitivesCurrencyId, tenant
if (stellarAsset.isStellarNative) {
return 'XLM';
} else if (stellarAsset.isAlphaNum4) {
const code = tryConvertCodeToAscii(stellarAsset.asAlphaNum4.code);
const issuer = convertRawHexKeyToPublicKey(stellarAsset.asAlphaNum4.issuer.toHex());
return `${code}:${issuer.publicKey()}`;
try {
const code = tryConvertCodeToAscii(stellarAsset.asAlphaNum4.code);
const issuer = convertRawHexKeyToPublicKey(stellarAsset.asAlphaNum4.issuer.toHex());
return `${code}:${issuer.publicKey()}`;
} catch {
return 'Unknown';
}
} else if (stellarAsset.isAlphaNum12) {
const code = tryConvertCodeToAscii(stellarAsset.asAlphaNum12.code);
const issuer = convertRawHexKeyToPublicKey(stellarAsset.asAlphaNum12.issuer.toHex());
return `${code}:${issuer.publicKey()}`;
try {
const code = tryConvertCodeToAscii(stellarAsset.asAlphaNum12.code);
const issuer = convertRawHexKeyToPublicKey(stellarAsset.asAlphaNum12.issuer.toHex());
return `${code}:${issuer.publicKey()}`;
} catch {
return 'Unknown';
}
} else {
return 'Unknown';
}
Expand Down
4 changes: 2 additions & 2 deletions src/helpers/transaction.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { toast } from 'react-toastify';
import { config } from '../config';
import { ToastMessage, showToast } from '../shared/showToast';

export const transactionErrorToast = (err: unknown) => {
const cancelled = String(err).startsWith('Error: Cancelled');
toast(cancelled ? 'Transaction cancelled' : 'Transaction failed', { type: 'error' });
showToast(ToastMessage.ERROR, cancelled ? 'Transaction cancelled' : 'Transaction failed');
};

const { slippage, deadline } = config.transaction.settings;
Expand Down
4 changes: 0 additions & 4 deletions src/hooks/spacewalk/index.tsx

This file was deleted.

27 changes: 16 additions & 11 deletions src/hooks/spacewalk/useBridgeSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { Asset } from 'stellar-sdk';
import { convertCurrencyToStellarAsset } from '../../helpers/spacewalk';
import { stringifyStellarAsset } from '../../helpers/stellar';
import { BridgeContext } from '../../pages/bridge';
import { ExtendedRegistryVault, useVaultRegistryPallet } from './vaultRegistry';
import { ExtendedRegistryVault, useVaultRegistryPallet } from './useVaultRegistryPallet';
import { ToastMessage, showToast } from '../../shared/showToast';

export interface BridgeSettings {
selectedVault?: ExtendedRegistryVault;
Expand All @@ -29,17 +30,21 @@ function useBridgeSettings(): BridgeSettings {

useEffect(() => {
const combinedVaults: ExtendedRegistryVault[] = [];
Promise.all([getVaultsWithIssuableTokens(), getVaultsWithRedeemableTokens()]).then((data) => {
getVaults().forEach((vaultFromRegistry: any) => {
const vaultWithIssuable = data[0]?.find(([id, _]) => id.eq(vaultFromRegistry.id));
const vaultWithRedeemable = data[1]?.find(([id, _]) => id.eq(vaultFromRegistry.id));
const extended: ExtendedRegistryVault = vaultFromRegistry;
extended.issuableTokens = vaultWithIssuable ? vaultWithIssuable[1] : undefined;
extended.redeemableTokens = vaultWithRedeemable ? vaultWithRedeemable[1] : undefined;
combinedVaults.push(extended);
Promise.all([getVaultsWithIssuableTokens(), getVaultsWithRedeemableTokens()])
.then((data) => {
getVaults().forEach((vaultFromRegistry: any) => {
const vaultWithIssuable = data[0]?.find(([id, _]) => id.eq(vaultFromRegistry.id));
const vaultWithRedeemable = data[1]?.find(([id, _]) => id.eq(vaultFromRegistry.id));
const extended: ExtendedRegistryVault = vaultFromRegistry;
extended.issuableTokens = vaultWithIssuable ? vaultWithIssuable[1] : undefined;
extended.redeemableTokens = vaultWithRedeemable ? vaultWithRedeemable[1] : undefined;
combinedVaults.push(extended);
});
setExtendedVaults(combinedVaults);
})
.catch(() => {
showToast(ToastMessage.BRIDGE_CONNECTION_ERROR);
});
setExtendedVaults(combinedVaults);
});
}, [getVaults, setExtendedVaults, getVaultsWithIssuableTokens, getVaultsWithRedeemableTokens]);

const wrappedAssets = useMemo(() => {
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { VaultRegistryVault } from '@polkadot/types/lookup';
import { useEffect, useMemo, useState } from 'preact/hooks';
import { useNodeInfoState } from '../../NodeInfoProvider';
import { convertRawHexKeyToPublicKey } from '../../helpers/stellar';
import { isEmpty } from 'lodash';

export interface ExtendedRegistryVault extends VaultRegistryVault {
issuableTokens?: Balance;
Expand Down Expand Up @@ -58,6 +59,9 @@ export function useVaultRegistryPallet() {
if (!api) {
return undefined;
}
if (isEmpty(api.rpc.vaultRegistry)) {
throw new Error('Vault Registry does not exist');
}
return await api.rpc.vaultRegistry.getVaultsWithIssuableTokens();
},
async getVaultsWithRedeemableTokens() {
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion src/hooks/useBalances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { addSuffix, currencyToString } from '../helpers/spacewalk';
import { useNodeInfoState } from '../NodeInfoProvider';
import { PortfolioAsset } from '../pages/dashboard/PortfolioColumns';
import { nativeToDecimal } from '../shared/parseNumbers/metric';
import { useVaultRegistryPallet } from './spacewalk/vaultRegistry';
import { useVaultRegistryPallet } from './spacewalk/useVaultRegistryPallet';
import { usePriceFetcher } from './usePriceFetcher';

function useBalances() {
Expand Down
6 changes: 3 additions & 3 deletions src/hooks/useClipboard.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useMemo } from 'preact/hooks';
import { toast } from 'react-toastify';
import { ToastMessage, showToast } from '../shared/showToast';

export function useClipboard() {
return useMemo(
Expand All @@ -8,10 +8,10 @@ export function useClipboard() {
try {
await navigator.clipboard.writeText(value);
const message = notificationMessage || `Copied ${value} to clipboard`;
toast(message, { type: 'info' });
showToast(ToastMessage.INFO, message);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
toast(error, { type: 'error' });
showToast(ToastMessage.ERROR, error);
}
},
}),
Expand Down
2 changes: 1 addition & 1 deletion src/pages/bridge/FeeBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { SubmittableExtrinsic } from '@polkadot/api/promise/types';
import Big from 'big.js';
import { useCallback, useEffect, useMemo, useState } from 'preact/compat';
import { Asset } from 'stellar-sdk';
import { useFeePallet } from '../../hooks/spacewalk/fee';
import { useFeePallet } from '../../hooks/spacewalk/useFeePallet';
import { nativeStellarToDecimal, nativeToDecimal } from '../../shared/parseNumbers/metric';

interface FeeBoxProps {
Expand Down
4 changes: 2 additions & 2 deletions src/pages/bridge/Issue/ConfirmationDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { CopyableAddress, PublicKey } from '../../../components/PublicKey';
import TransferCountdown from '../../../components/TransferCountdown';
import { calculateDeadline, convertCurrencyToStellarAsset, deriveShortenedRequestId } from '../../../helpers/spacewalk';
import { convertRawHexKeyToPublicKey } from '../../../helpers/stellar';
import { RichIssueRequest } from '../../../hooks/spacewalk/issue';
import { useSecurityPallet } from '../../../hooks/spacewalk/security';
import { RichIssueRequest } from '../../../hooks/spacewalk/useIssuePallet';
import { useSecurityPallet } from '../../../hooks/spacewalk/useSecurityPallet';
import { nativeStellarToDecimal } from '../../../shared/parseNumbers/metric';

interface ConfirmationDialogProps {
Expand Down
Loading
Loading