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

Improve UI on log-in modal. #350

Open
wants to merge 9 commits into
base: polygon-prototype-staging
Choose a base branch
from
36 changes: 0 additions & 36 deletions src/components/SignIn/index.tsx

This file was deleted.

41 changes: 26 additions & 15 deletions src/components/SigningBox/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Progress } from 'react-daisyui';
import { FC } from 'preact/compat';
import accountBalanceWalletIcon from '../../assets/account-balance-wallet.svg';

import { SigningPhase } from '../../hooks/offramp/useMainProcess';
import { OfframpSigningPhase } from '../../types/offramp';
import { isNetworkEVM, Networks } from '../../helpers/networks';
import { useNetwork } from '../../contexts/network';
import { Spinner } from '../Spinner';
Expand All @@ -12,30 +12,33 @@ type ProgressStep = {
signed: string;
finished: string;
approved: string;
login: string;
};

type SignatureConfig = {
maxSignatures: number;
getSignatureNumber: (step: SigningPhase) => string;
getSignatureNumber: (step: OfframpSigningPhase) => string;
};

const EVM_PROGRESS_CONFIG: ProgressStep = {
started: '25',
approved: '50',
signed: '75',
finished: '100',
login: '0',
};

const NON_EVM_PROGRESS_CONFIG: ProgressStep = {
started: '33',
finished: '100',
signed: '0',
approved: '0',
login: '0',
};

const EVM_SIGNATURE_CONFIG: SignatureConfig = {
maxSignatures: 2,
getSignatureNumber: (step: SigningPhase) => (step === 'started' ? '1' : '2'),
getSignatureNumber: (step: OfframpSigningPhase) => (step === 'started' ? '1' : '2'),
};

const NON_EVM_SIGNATURE_CONFIG: SignatureConfig = {
Expand All @@ -52,12 +55,12 @@ const getSignatureConfig = (network: Networks): SignatureConfig => {
};

interface SigningBoxProps {
step?: SigningPhase;
step?: OfframpSigningPhase;
}

const isValidStep = (step: SigningPhase | undefined, network: Networks): step is SigningPhase => {
const isValidStep = (step: OfframpSigningPhase | undefined, network: Networks): step is OfframpSigningPhase => {
if (!step) return false;
if (!['started', 'approved', 'signed'].includes(step)) return false;
if (step === 'finished') return false;
if (!isNetworkEVM(network) && (step === 'approved' || step === 'signed')) return false;
return true;
};
Expand Down Expand Up @@ -88,17 +91,25 @@ export const SigningBox: FC<SigningBoxProps> = ({ step }) => {
</div>
</div>

<div className="w-full pb-2.5">
<Progress value={progressValue} max="100" className="h-4 bg-white border progress-primary border-primary" />
</div>
{step !== 'login' && (
<div className="w-full pb-2.5">
<Progress
value={progressValue}
max="100"
className="h-4 bg-white border progress-primary border-primary"
/>
</div>
)}
</main>

<footer className="flex items-center justify-center bg-[#5E88D5] text-white rounded-b">
<Spinner />
<p className="ml-2.5 my-2 text-xs">
Waiting for signature {getSignatureNumber(step)}/{maxSignatures}
</p>
</footer>
{step !== 'login' && (
<footer className="flex items-center justify-center bg-[#5E88D5] text-white rounded-b">
<Spinner />
<p className="ml-2.5 my-2 text-xs">
Waiting for signature {getSignatureNumber(step)}/{maxSignatures}
</p>
</footer>
)}
</div>
</section>
);
Expand Down
1 change: 0 additions & 1 deletion src/hooks/offramp/useMainProcess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { useOfframpActions, useOfframpState } from '../../stores/offrampStore';
import { useSep24UrlInterval, useSep24InitialResponse } from '../../stores/sep24Store';
import { useSep24Actions } from '../../stores/sep24Store';
import { useAnchorWindowHandler } from './useSEP24/useAnchorWindowHandler';
export type SigningPhase = 'started' | 'approved' | 'signed' | 'finished';

export interface ExecutionInput {
inputTokenType: InputTokenType;
Expand Down
4 changes: 3 additions & 1 deletion src/hooks/offramp/useSubmitOfframp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,9 @@ export const useSubmitOfframp = () => {
console.error('Error initializing the offramping process', error);
// Display error message, differentiating between user rejection and other errors
if ((error as Error).message.includes('User rejected the request')) {
setInitializeFailed('Please switch to the correct network and try again.');
setInitializeFailed('Please switch to the correct network and try again.'); // Case: User rejected switching the network, relevant in Metamask.
} else if ((error as Error).message.includes('User rejected sign request')) {
setInitializeFailed('Please sign the login message to continue.'); // Case: User rejected signing the login challenge.
} else {
setInitializeFailed();
}
Expand Down
27 changes: 10 additions & 17 deletions src/hooks/useSignChallenge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { DEFAULT_LOGIN_EXPIRATION_TIME_HOURS } from '../constants/constants';
import { SIGNING_SERVICE_URL } from '../constants/constants';
import { storageKeys } from '../constants/localStorage';
import { useVortexAccount } from './useVortexAccount';
import { useOfframpActions } from '../stores/offrampStore';
import { useEffect } from 'react';

export interface SiweSignatureData {
signatureSet: boolean;
Expand All @@ -24,9 +26,8 @@ function createSiweMessage(address: string, nonce: string) {
}

export function useSiweSignature() {
const [signingPending, setSigningPending] = useState(false);
const { address, getMessageSignature } = useVortexAccount();

const { setOfframpSigningPhase } = useOfframpActions();
// Used to wait for the modal interaction and/or return of the
// signing promise.
const [signPromise, setSignPromise] = useState<{
Expand Down Expand Up @@ -55,9 +56,9 @@ export function useSiweSignature() {
if (signPromise) return;
return new Promise((resolve, reject) => {
setSignPromise({ resolve, reject });
setSigningPending(true);
setOfframpSigningPhase?.('login');
});
}, [setSigningPending, setSignPromise, signPromise]);
}, [setSignPromise, signPromise]);

const handleSign = useCallback(async () => {
if (!address || !signPromise) return;
Expand Down Expand Up @@ -99,19 +100,14 @@ export function useSiweSignature() {
const errorMessage = error instanceof Error ? error.message : String(error);
signPromise.reject(new Error('Signing failed: ' + errorMessage));
} finally {
setSigningPending(false);
setSignPromise(null);
setOfframpSigningPhase?.(undefined);
}
}, [address, storageKey, signPromise, setSigningPending, setSignPromise, getMessageSignature]);
}, [address, storageKey, signPromise, setSignPromise, getMessageSignature]);

// Handler for modal cancellation
const handleCancel = useCallback(() => {
if (signPromise) {
signPromise.reject(new Error('User cancelled'));
setSignPromise(null);
}
setSigningPending(false);
}, [signPromise, setSigningPending, setSignPromise]);
useEffect(() => {
if (signPromise) handleSign();
}, [signPromise, handleSign]);

const checkAndWaitForSignature = useCallback(async (): Promise<void> => {
const stored = checkStoredSignature();
Expand All @@ -125,9 +121,6 @@ export function useSiweSignature() {
}, [storageKey, signMessage]);

return {
signingPending,
handleSign,
handleCancel,
checkAndWaitForSignature,
forceRefreshAndWaitForSignature,
};
Expand Down
24 changes: 17 additions & 7 deletions src/pages/swap/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { ExchangeRate } from '../../components/ExchangeRate';
import { LabeledInput } from '../../components/LabeledInput';
import { UserBalance } from '../../components/UserBalance';
import { SigningBox } from '../../components/SigningBox';
import { SignInModal } from '../../components/SignIn';
import { PoweredBy } from '../../components/PoweredBy';

import {
Expand All @@ -30,11 +29,10 @@ import { config } from '../../config';
import { useEventsContext } from '../../contexts/events';
import { useNetwork } from '../../contexts/network';
import { usePendulumNode } from '../../contexts/polkadotNode';
import { useSiweContext } from '../../contexts/siwe';

import { multiplyByPowerOfTen, stringifyBigWithSignificantDecimals } from '../../helpers/contracts';
import { showToast, ToastMessage } from '../../helpers/notifications';
import { isNetworkEVM, Networks } from '../../helpers/networks';
import { isNetworkEVM } from '../../helpers/networks';

import { useInputTokenBalance } from '../../hooks/useInputTokenBalance';
import { useTokenOutAmount } from '../../hooks/nabla/useTokenAmountOut';
Expand Down Expand Up @@ -79,7 +77,6 @@ export const SwapPage = () => {
const [cachedId, setCachedId] = useState<string | undefined>(undefined);
const { trackEvent } = useEventsContext();
const { selectedNetwork, setNetworkSelectorDisabled } = useNetwork();
const { signingPending, handleSign, handleCancel } = useSiweContext();
const { walletAccount } = usePolkadotWalletState();

const [termsAnimationKey, setTermsAnimationKey] = useState(0);
Expand All @@ -101,6 +98,15 @@ export const SwapPage = () => {
);
}, []);

const clearInitializeErrors = useCallback(() => {
if (!initializeFailedMessage) return;
if (initializeFailedMessage.includes('Hang tight')) {
return window.location.reload();
} else {
setInitializeFailedMessage(null);
}
}, [initializeFailedMessage, setInitializeFailedMessage]);

useEffect(() => {
const initialize = async () => {
try {
Expand Down Expand Up @@ -368,7 +374,7 @@ export const SwapPage = () => {
from,
selectedNetwork,
fromAmountString,
requiresSquidRouter: selectedNetwork === Networks.Polygon,
requiresSquidRouter: isNetworkEVM(selectedNetwork),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to this change, but we need this after support of other EVM networks.

setOfframpInitiating,
setInitializeFailed,
handleOnSubmit,
Expand All @@ -378,7 +384,6 @@ export const SwapPage = () => {

const main = (
<main ref={formRef}>
<SignInModal signingPending={signingPending} closeModal={handleCancel} handleSignIn={handleSign} />
<SigningBox step={offrampSigningPhase} />
<form
className="max-w-2xl px-4 py-4 mx-4 my-8 rounded-lg shadow-custom md:mx-auto md:w-2/3 lg:w-3/5 xl:w-1/2"
Expand Down Expand Up @@ -408,7 +413,12 @@ export const SwapPage = () => {
</section>
<section className="flex justify-center w-full mt-5">
{(initializeFailedMessage || apiInitializeFailed) && (
<p className="text-red-600">{initializeFailedMessage}</p>
<div className="flex items-center gap-4">
<p className="text-red-600">{initializeFailedMessage}</p>
<button className="btn btn-vortex-primary px-4 py-2 text-xs" onClick={() => clearInitializeErrors()}>
Retry
</button>
</div>
)}
</section>
<section className="w-full mt-5">
Expand Down
15 changes: 14 additions & 1 deletion src/services/anchor/sep10/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,20 @@ export async function sep10(
const transactionSigned = await fetchAndValidateChallenge(webAuthEndpoint, urlParams, signingKey);

if (usesMemo) {
await checkAndWaitForSignature();
try {
await checkAndWaitForSignature();
} catch (error) {
// We must differentiate between user driven rejection and other errors
console.log((error as Error).message);
if (
(error as Error).message.includes('User rejected the request') ||
(error as Error).message.includes('Signing failed: Cancelled')
) {
// First case Assethub, second case EVM
throw new Error('User rejected sign request');
}
throw new Error('Failed to sign login challenge');
}
}

const { masterClientSignature, clientSignature, clientPublic } = await sep10SignaturesWithLoginRefresh(
Expand Down
4 changes: 2 additions & 2 deletions src/services/offrampingFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { decodeAddress } from '@polkadot/util-crypto';
import { ApiPromise } from '@polkadot/api';
import { u8aToHex } from '@polkadot/util';

import { SigningPhase } from '../hooks/offramp/useMainProcess';
import { OfframpSigningPhase } from '../types/offramp';
import { TrackableEvent } from '../contexts/events';
import { isNetworkEVM, Networks } from '../helpers/networks';
import { SepResult } from '../types/sep';
Expand Down Expand Up @@ -64,7 +64,7 @@ export type FinalOfframpingPhase = 'success';

export interface ExecutionContext {
wagmiConfig: Config;
setOfframpSigningPhase: (n: SigningPhase) => void;
setOfframpSigningPhase: (n: OfframpSigningPhase) => void;
trackEvent: (event: TrackableEvent) => void;
pendulumNode: { ss58Format: number; api: ApiPromise; decimals: number };
assetHubNode: { api: ApiPromise };
Expand Down
2 changes: 1 addition & 1 deletion src/types/offramp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { OfframpingState } from '../services/offrampingFlow';
import { InputTokenType, OutputTokenType } from '../constants/tokenConfig';
import { ISep24Intermediate, IAnchorSessionParams } from './sep';

export type OfframpSigningPhase = 'started' | 'approved' | 'signed' | 'finished';
export type OfframpSigningPhase = 'login' | 'started' | 'approved' | 'signed' | 'finished';

export interface OfframpExecutionInput {
inputTokenType: InputTokenType;
Expand Down
Loading