From e108fc1f341a091884bc686c45d6954a4d9098d2 Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Thu, 2 Jan 2025 16:53:05 -0300 Subject: [PATCH 01/44] substract min balance for the asset, assethub --- src/hooks/useInputTokenBalance.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/hooks/useInputTokenBalance.ts b/src/hooks/useInputTokenBalance.ts index f1b7eb505..fbc5fafce 100644 --- a/src/hooks/useInputTokenBalance.ts +++ b/src/hooks/useInputTokenBalance.ts @@ -44,10 +44,15 @@ const useAssetHubBalance = (assetId?: number): string | undefined => { const getBalance = async () => { try { const { api } = assetHubNode; + const assetInfo = await api.query.assets.asset(assetId); + const { minBalance: rawMinBalance } = assetInfo.toJSON() as { minBalance: number }; + const accountInfo = await api.query.assets.account(assetId, walletAccount.address); const rawBalance = (accountInfo.toJSON() as { balance?: number })?.balance ?? 0; - const formattedBalance = nativeToDecimal(rawBalance, USDC_DECIMALS).toFixed(2, 0).toString(); + + const offrampableBalance = rawBalance > 0 ? rawBalance - rawMinBalance : 0; + const formattedBalance = nativeToDecimal(offrampableBalance, USDC_DECIMALS).toFixed(2, 0).toString(); setBalance(formattedBalance); } catch (error) { console.error('Failed to fetch AssetHub balance:', error); From 81c7bbf5f8c5bdc551557ce4bf650410559704d9 Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Thu, 2 Jan 2025 18:15:28 -0300 Subject: [PATCH 02/44] explicitly wait for xcm sent confirmation event --- src/services/phases/polkadot/assethub.ts | 13 +++++ .../phases/polkadot/eventListener.tsx | 49 ++++++++++++++++++- src/services/phases/polkadot/eventParsers.tsx | 10 ++++ 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/services/phases/polkadot/assethub.ts b/src/services/phases/polkadot/assethub.ts index 8232160c4..8d08986a0 100644 --- a/src/services/phases/polkadot/assethub.ts +++ b/src/services/phases/polkadot/assethub.ts @@ -7,6 +7,7 @@ import Big from 'big.js'; import { ExecutionContext, OfframpingState } from '../../offrampingFlow'; import { waitUntilTrue } from '../../../helpers/function'; import { getRawInputBalance } from './ephemeral'; +import { EventListener } from './eventListener'; export function createAssethubAssetTransfer(assethubApi: ApiPromise, receiverAddress: string, rawAmount: string) { const receiverId = u8aToHex(decodeAddress(receiverAddress)); @@ -33,6 +34,13 @@ export async function executeAssetHubXCM(state: OfframpingState, context: Execut const { assetHubNode, walletAccount, setOfframpSigningPhase } = context; const { pendulumEphemeralAddress } = state; + const { pendulumNode } = context; + const { ss58Format, api } = pendulumNode; + + // We wait for up to 1 minute. XCM event should appear on the same block. + const maxWaitingTimeMinutes = 1; + const maxWaitingTimeMs = maxWaitingTimeMinutes * 60 * 1000; + if (!walletAccount) { throw new Error('Wallet account not available'); } @@ -54,6 +62,11 @@ export async function executeAssetHubXCM(state: OfframpingState, context: Execut const tx = createAssethubAssetTransfer(assetHubNode.api, pendulumEphemeralAddress, inputAmount.raw); context.setOfframpSigningPhase('started'); const { hash } = await tx.signAndSend(walletAccount.address, { signer: walletAccount.signer as Signer }); + + const eventListener = EventListener.getEventListener(api); + await eventListener.waitForXcmSentEvent(walletAccount.address, maxWaitingTimeMs); + eventListener.unsubscribe(); + setOfframpSigningPhase?.('finished'); return { ...state, assetHubXcmTransactionHash: hash.toString() }; } diff --git a/src/services/phases/polkadot/eventListener.tsx b/src/services/phases/polkadot/eventListener.tsx index 5c7066eee..d2f066ca9 100644 --- a/src/services/phases/polkadot/eventListener.tsx +++ b/src/services/phases/polkadot/eventListener.tsx @@ -3,7 +3,7 @@ import { ApiPromise } from '@polkadot/api'; -import { parseEventRedeemExecution } from './eventParsers'; +import { parseEventRedeemExecution, parseEventXcmSent } from './eventParsers'; interface IPendingEvent { filter: any; @@ -13,8 +13,11 @@ interface IPendingEvent { export class EventListener { static eventListeners = new Map(); + private unsubscribeHandle: (() => void) | null = null; + pendingIssueEvents: IPendingEvent[] = []; pendingRedeemEvents: IPendingEvent[] = []; + pendingXcmSentEvents: IPendingEvent[] = []; api: ApiPromise | undefined = undefined; @@ -33,7 +36,7 @@ export class EventListener { } async initEventSubscriber() { - this.api!.query.system.events((events) => { + this.unsubscribeHandle = await this.api!.query.system.events((events) => { events.forEach((event) => { this.processEvents(event, this.pendingIssueEvents); this.processEvents(event, this.pendingRedeemEvents); @@ -67,6 +70,33 @@ export class EventListener { }); } + waitForXcmSentEvent(originAddress: string, maxWaitingTimeMs: number) { + const filter = (event: any) => { + if (event.event.section === 'polkadotXcm' && event.event.method === 'Sent') { + const eventParsed = parseEventXcmSent(event); + if (eventParsed.originAddress == originAddress) { + console.log('event', event); + return eventParsed; + } + } + return null; + }; + + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject(new Error(`Max waiting time exceeded for XCM Sent event from origin: ${originAddress}`)); + }, maxWaitingTimeMs); + + this.pendingXcmSentEvents.push({ + filter, + resolve: (event) => { + clearTimeout(timeout); + resolve(event); + }, + }); + }); + } + processEvents(event: any, pendingEvents: IPendingEvent[]) { pendingEvents.forEach((pendingEvent, index) => { const matchedEvent = pendingEvent.filter(event); @@ -77,4 +107,19 @@ export class EventListener { } }); } + + unsubscribe() { + if (this.unsubscribeHandle) { + this.unsubscribeHandle(); + this.unsubscribeHandle = null; + } + + this.pendingIssueEvents = []; + this.pendingRedeemEvents = []; + this.pendingXcmSentEvents = []; + + EventListener.eventListeners.delete(this.api!); + + this.api = undefined; + } } diff --git a/src/services/phases/polkadot/eventParsers.tsx b/src/services/phases/polkadot/eventParsers.tsx index 861ed22d4..97173b5bc 100644 --- a/src/services/phases/polkadot/eventParsers.tsx +++ b/src/services/phases/polkadot/eventParsers.tsx @@ -1,6 +1,7 @@ // @todo: remove no-explicit-any /* eslint-disable @typescript-eslint/no-explicit-any */ import Big from 'big.js'; +import { encodeAddress } from '@polkadot/util-crypto'; import { stellarHexToPublic, hexToString } from './convert'; @@ -54,6 +55,15 @@ export function parseEventRedeemExecution(event: any) { return mappedData; } +export function parseEventXcmSent(event: any) { + const rawEventData = JSON.parse(event.event.data.toString()); + const PENDULUM_ADDRESS_PREFIX = 56; + const mappedData = { + originAddress: encodeAddress(rawEventData[0].interior.X1[0].AccountId32.id.toString(), PENDULUM_ADDRESS_PREFIX), + }; + return mappedData; +} + function extractStellarAssetInfo(data: any) { if ('stellarNative' in data.stellar) { return { From 76377251ecae7ca9cb166b70298ebd0baca170ae Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Fri, 3 Jan 2025 10:44:31 -0300 Subject: [PATCH 03/44] improve UI on log in --- src/components/SignIn/index.tsx | 37 +++++++++++++++++++++++++++++---- src/contexts/network.tsx | 2 +- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/components/SignIn/index.tsx b/src/components/SignIn/index.tsx index de567715d..e35d0b06a 100644 --- a/src/components/SignIn/index.tsx +++ b/src/components/SignIn/index.tsx @@ -1,4 +1,5 @@ -import { FC } from 'preact/compat'; +import { FC, useState } from 'preact/compat'; +import { useEffect } from 'react'; import { Modal } from 'react-daisyui'; interface SignInModalProps { @@ -8,10 +9,22 @@ interface SignInModalProps { } export const SignInModal: FC = ({ signingPending, closeModal, handleSignIn }) => { + const [waitingForWallet, setWaitingForWallet] = useState(false); + + const onSignMessage = () => { + setWaitingForWallet(true); + handleSignIn(); + }; + if (!signingPending) { return null; } + // We reset the waiting state when the signing process is finished, or failed. + useEffect(() => { + setWaitingForWallet(false); + }, [signingPending]); + return ( @@ -21,11 +34,27 @@ export const SignInModal: FC = ({ signingPending, closeModal, -

Please sign the message to log-in

+ {waitingForWallet ? ( +
+ Proceed to your wallet to complete the signing process. +
+ ) : ( +

Please sign the message to log in.

+ )}
- + */} From 0bc5aacb80215b368a8ed42c38f78974471bea5d Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Mon, 13 Jan 2025 10:14:18 -0300 Subject: [PATCH 08/44] reuse signing box for login --- src/components/SignIn/index.tsx | 69 ----------------------------- src/components/SigningBox/index.tsx | 41 ++++++++++------- src/hooks/offramp/useMainProcess.ts | 1 - src/hooks/useSignChallenge.ts | 27 +++++------ src/pages/swap/index.tsx | 8 +--- src/services/offrampingFlow.ts | 4 +- src/types/offramp.ts | 2 +- 7 files changed, 41 insertions(+), 111 deletions(-) delete mode 100644 src/components/SignIn/index.tsx diff --git a/src/components/SignIn/index.tsx b/src/components/SignIn/index.tsx deleted file mode 100644 index cc5eaf19e..000000000 --- a/src/components/SignIn/index.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { FC } from 'preact/compat'; -import { useEffect, useState } from 'preact/hooks'; -import { Modal } from 'react-daisyui'; - -interface SignInModalProps { - signingPending: boolean; - closeModal: () => void; - handleSignIn: () => void; -} - -export const SignInModal: FC = ({ signingPending, closeModal, handleSignIn }) => { - const [waitingForWallet, setWaitingForWallet] = useState(true); - - // const onSignMessage = () => { - // setWaitingForWallet(true); - // handleSignIn(); - // }; - - if (!signingPending) { - return null; - } - - // Legacy setWaitingForWallet to true on signingPending change. - // Confirm button then triggers immediate handleSignIn. - useEffect(() => { - setWaitingForWallet(true); - if (signingPending) { - handleSignIn(); - } - }, [signingPending]); - - return ( - - - Sign In - - - - {waitingForWallet ? ( -
- Proceed to your wallet to complete the signing process. -
- ) : ( -

Please sign the message to log in.

- )} -
- - {/* */} - - -
- ); -}; diff --git a/src/components/SigningBox/index.tsx b/src/components/SigningBox/index.tsx index f7f1e27a2..793839002 100644 --- a/src/components/SigningBox/index.tsx +++ b/src/components/SigningBox/index.tsx @@ -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'; @@ -12,11 +12,12 @@ 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 = { @@ -24,6 +25,7 @@ const EVM_PROGRESS_CONFIG: ProgressStep = { approved: '50', signed: '75', finished: '100', + login: '0', }; const NON_EVM_PROGRESS_CONFIG: ProgressStep = { @@ -31,11 +33,12 @@ const NON_EVM_PROGRESS_CONFIG: ProgressStep = { 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 = { @@ -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; }; @@ -88,17 +91,25 @@ export const SigningBox: FC = ({ step }) => { -
- -
+ {step !== 'login' && ( +
+ +
+ )} -
- -

- Waiting for signature {getSignatureNumber(step)}/{maxSignatures} -

-
+ {step !== 'login' && ( +
+ +

+ Waiting for signature {getSignatureNumber(step)}/{maxSignatures} +

+
+ )} ); diff --git a/src/hooks/offramp/useMainProcess.ts b/src/hooks/offramp/useMainProcess.ts index 400bdfa9f..12f0b4405 100644 --- a/src/hooks/offramp/useMainProcess.ts +++ b/src/hooks/offramp/useMainProcess.ts @@ -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; diff --git a/src/hooks/useSignChallenge.ts b/src/hooks/useSignChallenge.ts index 6b56a68eb..2e8ddf5b8 100644 --- a/src/hooks/useSignChallenge.ts +++ b/src/hooks/useSignChallenge.ts @@ -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; @@ -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<{ @@ -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; @@ -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 => { const stored = checkStoredSignature(); @@ -125,9 +121,6 @@ export function useSiweSignature() { }, [storageKey, signMessage]); return { - signingPending, - handleSign, - handleCancel, checkAndWaitForSignature, forceRefreshAndWaitForSignature, }; diff --git a/src/pages/swap/index.tsx b/src/pages/swap/index.tsx index 962e9f396..e10be812d 100644 --- a/src/pages/swap/index.tsx +++ b/src/pages/swap/index.tsx @@ -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 { @@ -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'; @@ -78,7 +76,6 @@ export const SwapPage = () => { const [cachedId, setCachedId] = useState(undefined); const { trackEvent } = useEventsContext(); const { selectedNetwork, setNetworkSelectorDisabled } = useNetwork(); - const { signingPending, handleSign, handleCancel } = useSiweContext(); const [termsAnimationKey, setTermsAnimationKey] = useState(0); @@ -369,7 +366,7 @@ export const SwapPage = () => { from, selectedNetwork, fromAmountString, - requiresSquidRouter: selectedNetwork === Networks.Polygon, + requiresSquidRouter: isNetworkEVM(selectedNetwork), setOfframpInitiating, setInitializeFailed, handleOnSubmit, @@ -379,7 +376,6 @@ export const SwapPage = () => { const main = (
-
void; + setOfframpSigningPhase: (n: OfframpSigningPhase) => void; trackEvent: (event: TrackableEvent) => void; pendulumNode: { ss58Format: number; api: ApiPromise; decimals: number }; assetHubNode: { api: ApiPromise }; diff --git a/src/types/offramp.ts b/src/types/offramp.ts index 305299ffd..4dcbeecc8 100644 --- a/src/types/offramp.ts +++ b/src/types/offramp.ts @@ -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; From f7c14d9347e473f735d1741ae42a2734e7e0a2d4 Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Mon, 13 Jan 2025 10:38:55 -0300 Subject: [PATCH 09/44] better error handling on wallet rejections --- src/hooks/offramp/useSubmitOfframp.ts | 4 +++- src/pages/swap/index.tsx | 16 +++++++++++++++- src/services/anchor/sep10/index.ts | 10 +++++++++- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/hooks/offramp/useSubmitOfframp.ts b/src/hooks/offramp/useSubmitOfframp.ts index a4ffc881d..327a8ef35 100644 --- a/src/hooks/offramp/useSubmitOfframp.ts +++ b/src/hooks/offramp/useSubmitOfframp.ts @@ -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(); } diff --git a/src/pages/swap/index.tsx b/src/pages/swap/index.tsx index e10be812d..e582d8638 100644 --- a/src/pages/swap/index.tsx +++ b/src/pages/swap/index.tsx @@ -96,6 +96,15 @@ export const SwapPage = () => { ); }, []); + const clearInitializeErrors = useCallback(() => { + if (!initializeFailedMessage) return; + if (initializeFailedMessage.includes('Please reload')) { + return window.location.reload(); + } else { + setInitializeFailedMessage(null); + } + }, [initializeFailedMessage, setInitializeFailedMessage]); + useEffect(() => { const initialize = async () => { try { @@ -405,7 +414,12 @@ export const SwapPage = () => {
{(initializeFailedMessage || apiInitializeFailed) && ( -

{initializeFailedMessage}

+
+

{initializeFailedMessage}

+ +
)}
diff --git a/src/services/anchor/sep10/index.ts b/src/services/anchor/sep10/index.ts index 7a1740e2d..bd0e41ca8 100644 --- a/src/services/anchor/sep10/index.ts +++ b/src/services/anchor/sep10/index.ts @@ -56,7 +56,15 @@ 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 + if ((error as Error).message.includes('User rejected the request')) { + throw new Error('User rejected sign request'); + } + throw new Error('Failed to sign login challenge'); + } } const { masterClientSignature, clientSignature, clientPublic } = await sep10SignaturesWithLoginRefresh( From 932d4a7d98d3b4f8e2986a19ab6529cefa06e06a Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Mon, 13 Jan 2025 10:55:24 -0300 Subject: [PATCH 10/44] log signature login raw error --- src/services/anchor/sep10/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/anchor/sep10/index.ts b/src/services/anchor/sep10/index.ts index bd0e41ca8..6412cbd0a 100644 --- a/src/services/anchor/sep10/index.ts +++ b/src/services/anchor/sep10/index.ts @@ -60,6 +60,7 @@ export async function sep10( 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')) { throw new Error('User rejected sign request'); } From 9b96dfa1d18450f4a7891145ee811d874386769b Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Mon, 13 Jan 2025 11:03:00 -0300 Subject: [PATCH 11/44] support different error message after wallet rejection --- src/pages/swap/index.tsx | 2 +- src/services/anchor/sep10/index.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pages/swap/index.tsx b/src/pages/swap/index.tsx index 889482d68..dd68888d4 100644 --- a/src/pages/swap/index.tsx +++ b/src/pages/swap/index.tsx @@ -100,7 +100,7 @@ export const SwapPage = () => { const clearInitializeErrors = useCallback(() => { if (!initializeFailedMessage) return; - if (initializeFailedMessage.includes('Please reload')) { + if (initializeFailedMessage.includes('Hang tight')) { return window.location.reload(); } else { setInitializeFailedMessage(null); diff --git a/src/services/anchor/sep10/index.ts b/src/services/anchor/sep10/index.ts index 6412cbd0a..77d98bc85 100644 --- a/src/services/anchor/sep10/index.ts +++ b/src/services/anchor/sep10/index.ts @@ -61,7 +61,11 @@ export async function sep10( } 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')) { + 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'); From f41851f98f518ee2c05e41d54d34d2c90b2e27d1 Mon Sep 17 00:00:00 2001 From: Marcel Ebert Date: Thu, 16 Jan 2025 13:07:59 +0100 Subject: [PATCH 12/44] Enable progress bar and footer also for `login` phase --- src/components/SigningBox/index.tsx | 30 +++++++++++------------------ 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/src/components/SigningBox/index.tsx b/src/components/SigningBox/index.tsx index 793839002..443fac9c8 100644 --- a/src/components/SigningBox/index.tsx +++ b/src/components/SigningBox/index.tsx @@ -25,7 +25,7 @@ const EVM_PROGRESS_CONFIG: ProgressStep = { approved: '50', signed: '75', finished: '100', - login: '0', + login: '15', }; const NON_EVM_PROGRESS_CONFIG: ProgressStep = { @@ -33,7 +33,7 @@ const NON_EVM_PROGRESS_CONFIG: ProgressStep = { finished: '100', signed: '0', approved: '0', - login: '0', + login: '15', }; const EVM_SIGNATURE_CONFIG: SignatureConfig = { @@ -91,25 +91,17 @@ export const SigningBox: FC = ({ step }) => { - {step !== 'login' && ( -
- -
- )} +
+ +
- {step !== 'login' && ( -
- -

- Waiting for signature {getSignatureNumber(step)}/{maxSignatures} -

-
- )} +
+ +

+ Waiting for signature {getSignatureNumber(step)}/{maxSignatures} +

+
); From efb3ff907d9f6b5b1ccfef7013eddbaeb4a8ab2b Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Thu, 16 Jan 2025 16:53:48 +0100 Subject: [PATCH 13/44] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f00ddcdeb..1df7db8b2 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ --- -PendulumPay is a gateway for cross-border payments. It is built on top of the Pendulum blockchain. +Vortex is a gateway for cross-border payments. It is built on top of the Pendulum blockchain. ## Run From 66406a6fd3b2be5448014c36e4c3ca0e5b299b53 Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Thu, 16 Jan 2025 16:54:26 +0100 Subject: [PATCH 14/44] Remove Vortex logo from Navbar --- src/components/Navbar/index.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/components/Navbar/index.tsx b/src/components/Navbar/index.tsx index 8e0b97e16..7babbb140 100644 --- a/src/components/Navbar/index.tsx +++ b/src/components/Navbar/index.tsx @@ -103,10 +103,6 @@ export const Navbar = () => { return (
- - Vortex Logo - Alpha - From 2e02810faa0d907b0c61ea84d24a553a797f2bff Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Thu, 16 Jan 2025 16:54:52 +0100 Subject: [PATCH 15/44] Update UserBalance icon --- src/assets/wallet-bifold-outline.svg | 1 + src/assets/wallet.svg | 7 ------ src/components/UserBalance/index.tsx | 36 +++++++++++++--------------- src/pages/swap/index.tsx | 3 --- 4 files changed, 17 insertions(+), 30 deletions(-) create mode 100644 src/assets/wallet-bifold-outline.svg delete mode 100644 src/assets/wallet.svg diff --git a/src/assets/wallet-bifold-outline.svg b/src/assets/wallet-bifold-outline.svg new file mode 100644 index 000000000..2197cdbf8 --- /dev/null +++ b/src/assets/wallet-bifold-outline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/wallet.svg b/src/assets/wallet.svg deleted file mode 100644 index a4be7376d..000000000 --- a/src/assets/wallet.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/src/components/UserBalance/index.tsx b/src/components/UserBalance/index.tsx index f9fe9248c..30de770a1 100644 --- a/src/components/UserBalance/index.tsx +++ b/src/components/UserBalance/index.tsx @@ -1,7 +1,7 @@ import { InputTokenDetails } from '../../constants/tokenConfig'; import { useInputTokenBalance } from '../../hooks/useInputTokenBalance'; import { useVortexAccount } from '../../hooks/useVortexAccount'; -import wallet from '../../assets/wallet.svg'; +import wallet from '../../assets/wallet-bifold-outline.svg'; interface UserBalanceProps { token: InputTokenDetails; @@ -19,24 +19,20 @@ export const UserBalance = ({ token, onClick }: UserBalanceProps) => { const showMaxButton = Number(inputTokenBalance) !== 0; return ( -

- <> -

- Available - - {inputTokenBalance} {token.assetSymbol} - - {showMaxButton && ( - - )} -
- -

+
+ Available +

+ {inputTokenBalance} {token.assetSymbol} +

+ {showMaxButton && ( + + )} +
); }; diff --git a/src/pages/swap/index.tsx b/src/pages/swap/index.tsx index 5f9ab62c6..43801b4ba 100644 --- a/src/pages/swap/index.tsx +++ b/src/pages/swap/index.tsx @@ -1,4 +1,3 @@ - import Big from 'big.js'; import { useEffect, useMemo, useRef, useState, useCallback } from 'preact/hooks'; import { ApiPromise } from '@polkadot/api'; @@ -61,8 +60,6 @@ import { TrustedBy } from '../../components/TrustedBy'; import { WhyVortex } from '../../components/WhyVortex'; import { usePolkadotWalletState } from '../../contexts/polkadotWallet'; - - export const SwapPage = () => { const formRef = useRef(null); const feeComparisonRef = useRef(null); From ee9220e789bbcb2e0db464d8bad579f9390ca512 Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Thu, 16 Jan 2025 17:12:22 +0100 Subject: [PATCH 16/44] make buttons in Navbar same height --- src/components/NetworkSelector/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/NetworkSelector/index.tsx b/src/components/NetworkSelector/index.tsx index c8c4e9dac..e909d5d33 100644 --- a/src/components/NetworkSelector/index.tsx +++ b/src/components/NetworkSelector/index.tsx @@ -15,7 +15,7 @@ interface NetworkButtonProps { const NetworkButton = ({ selectedNetwork, isOpen, onClick, disabled }: NetworkButtonProps) => ( Date: Thu, 16 Jan 2025 19:14:49 +0100 Subject: [PATCH 17/44] relocate payment images --- src/assets/payments/sepa.svg | 7 +++- src/components/PoweredBy/index.tsx | 55 ++++++++++++++++++++++++++---- src/components/TrustedBy/index.tsx | 13 +------ 3 files changed, 56 insertions(+), 19 deletions(-) diff --git a/src/assets/payments/sepa.svg b/src/assets/payments/sepa.svg index 06fca5066..919a48f3e 100644 --- a/src/assets/payments/sepa.svg +++ b/src/assets/payments/sepa.svg @@ -1 +1,6 @@ - \ No newline at end of file + + + + + + diff --git a/src/components/PoweredBy/index.tsx b/src/components/PoweredBy/index.tsx index eefa701b7..94148a31c 100644 --- a/src/components/PoweredBy/index.tsx +++ b/src/components/PoweredBy/index.tsx @@ -1,12 +1,55 @@ import satoshipayLogo from '../../assets/logo/satoshipay.svg'; +import MASTERCARD from '../../assets/payments/mastercard.svg'; +import SEPA from '../../assets/payments/sepa.svg'; +import VISA from '../../assets/payments/visa.svg'; +import vortexLogo from '../../assets/logo/blue.svg'; +interface ImageProps { + src: string; + alt: string; + comingSoon?: boolean; +} + +const paymentImages = [ + { src: SEPA, alt: 'SEPA logo' }, + { src: MASTERCARD, alt: 'Mastercard logo', comingSoon: true }, + { src: VISA, alt: 'Visa logo', comingSoon: true }, +]; + +const Image = ({ src, alt, comingSoon }: ImageProps) => ( +
+ {alt} + {/* {comingSoon &&
Coming soon
} */} +
+); + +const ImageList = ({ images }: { images: ImageProps[] }) => ( +
+ {images.map((img) => ( + + ))} +
+); export function PoweredBy() { return ( -
-

Powered by

- - Satoshipay - -
+
+ +
+

Powered by

+ Satoshipay +
+

+ A + + Satoshipay + + Company +

+
); } diff --git a/src/components/TrustedBy/index.tsx b/src/components/TrustedBy/index.tsx index 1e22ed979..eb791d959 100644 --- a/src/components/TrustedBy/index.tsx +++ b/src/components/TrustedBy/index.tsx @@ -4,10 +4,6 @@ import STELLAR from '../../assets/trustedby/stellar.svg'; import NABLA from '../../assets/trustedby/nabla.svg'; import WEB3 from '../../assets/trustedby/web3.svg'; -import MASTERCARD from '../../assets/payments/mastercard.svg'; -import SEPA from '../../assets/payments/sepa.svg'; -import VISA from '../../assets/payments/visa.svg'; - interface ImageProps { src: string; alt: string; @@ -38,18 +34,11 @@ export const TrustedBy = () => { { src: WEB3, alt: 'Web3 Foundation logo' }, ]; - const paymentImages = [ - { src: SEPA, alt: 'SEPA logo' }, - { src: MASTERCARD, alt: 'Mastercard logo', comingSoon: true }, - { src: VISA, alt: 'Visa logo', comingSoon: true }, - ]; - return ( -
+

Trusted by

-
); From ede37a736bed9f7a3ef68a720fa292f060eeb373 Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Thu, 16 Jan 2025 20:01:54 +0100 Subject: [PATCH 18/44] fix display asset icons on mobile --- src/components/buttons/AssetButton/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/buttons/AssetButton/index.tsx b/src/components/buttons/AssetButton/index.tsx index e33f38de2..36666fcfd 100644 --- a/src/components/buttons/AssetButton/index.tsx +++ b/src/components/buttons/AssetButton/index.tsx @@ -12,12 +12,12 @@ export function AssetButton({ assetIcon, tokenSymbol, onClick }: AssetButtonProp return (
-
+

Your quote ({exchangeRate})

From a1267aac306ee01b7357a02c9bd0ae7bd03c1ff2 Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Thu, 16 Jan 2025 20:04:00 +0100 Subject: [PATCH 20/44] add init animation for form --- src/pages/swap/index.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/pages/swap/index.tsx b/src/pages/swap/index.tsx index 3bef4abc0..45b836b6f 100644 --- a/src/pages/swap/index.tsx +++ b/src/pages/swap/index.tsx @@ -1,6 +1,7 @@ import Big from 'big.js'; import { useEffect, useMemo, useRef, useState, useCallback } from 'preact/hooks'; import { ApiPromise } from '@polkadot/api'; +import { motion } from 'framer-motion'; import { calculateTotalReceive, FeeCollapse } from '../../components/FeeCollapse'; import { PoolSelectorModal } from '../../components/InputKeys/SelectionModal'; @@ -375,7 +376,13 @@ export const SwapPage = () => {
- +

Sell Crypto

@@ -450,7 +457,7 @@ export const SwapPage = () => {

- +
{showCompareFees && fromToken && fromAmount && toToken && ( Date: Thu, 16 Jan 2025 20:04:15 +0100 Subject: [PATCH 21/44] adjust spacing for PoweredBy form section --- src/components/PoweredBy/index.tsx | 21 +++++++++++---------- src/components/UserBalance/index.tsx | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/components/PoweredBy/index.tsx b/src/components/PoweredBy/index.tsx index 94148a31c..c80176328 100644 --- a/src/components/PoweredBy/index.tsx +++ b/src/components/PoweredBy/index.tsx @@ -6,24 +6,27 @@ import vortexLogo from '../../assets/logo/blue.svg'; interface ImageProps { src: string; alt: string; + additionalClass?: string; comingSoon?: boolean; } const paymentImages = [ - { src: SEPA, alt: 'SEPA logo' }, + { src: SEPA, alt: 'SEPA logo', additionalClass: 'h-6' }, { src: MASTERCARD, alt: 'Mastercard logo', comingSoon: true }, { src: VISA, alt: 'Visa logo', comingSoon: true }, ]; -const Image = ({ src, alt, comingSoon }: ImageProps) => ( -
- {alt} - {/* {comingSoon &&
Coming soon
} */} +const Image = ({ src, alt, comingSoon, additionalClass }: ImageProps) => ( +
+ {alt} + {comingSoon && ( +
Coming soon
+ )}
); const ImageList = ({ images }: { images: ImageProps[] }) => ( -
+
{images.map((img) => ( ))} @@ -34,12 +37,11 @@ export function PoweredBy() { return (
-
+

Powered by

Satoshipay
-

- A +

Satoshipay - Company

); diff --git a/src/components/UserBalance/index.tsx b/src/components/UserBalance/index.tsx index 30de770a1..9d293d6d0 100644 --- a/src/components/UserBalance/index.tsx +++ b/src/components/UserBalance/index.tsx @@ -13,7 +13,7 @@ export const UserBalance = ({ token, onClick }: UserBalanceProps) => { const inputTokenBalance = useInputTokenBalance({ fromToken: token }); if (isDisconnected || inputTokenBalance === undefined) { - return <>; + return
; } const showMaxButton = Number(inputTokenBalance) !== 0; From 826105ed472cf5f34b305602323782dc0154fae2 Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Thu, 16 Jan 2025 20:11:19 +0100 Subject: [PATCH 22/44] animate TrustedBy --- src/components/TrustedBy/index.tsx | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/components/TrustedBy/index.tsx b/src/components/TrustedBy/index.tsx index eb791d959..0e829c36f 100644 --- a/src/components/TrustedBy/index.tsx +++ b/src/components/TrustedBy/index.tsx @@ -3,6 +3,7 @@ import POLKADOT from '../../assets/trustedby/polkadot.svg'; import STELLAR from '../../assets/trustedby/stellar.svg'; import NABLA from '../../assets/trustedby/nabla.svg'; import WEB3 from '../../assets/trustedby/web3.svg'; +import { motion } from 'framer-motion'; interface ImageProps { src: string; @@ -12,15 +13,32 @@ interface ImageProps { const Image = ({ src, alt, comingSoon }: ImageProps) => (
- {alt} + {comingSoon &&
Coming soon
}
); const ImageList = ({ images }: { images: ImageProps[] }) => (
- {images.map((img) => ( - + {images.map((img, index) => ( + + + ))}
); @@ -36,7 +54,7 @@ export const TrustedBy = () => { return (
-

Trusted by

+ Trusted by
From 591190b485b8bb1cd092362e576b0dbc60aebc9a Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Thu, 16 Jan 2025 17:19:40 -0300 Subject: [PATCH 23/44] reset sep variables also when changing network --- src/contexts/network.tsx | 3 +++ src/pages/swap/index.tsx | 1 + src/stores/sep24Store.ts | 5 +++-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/contexts/network.tsx b/src/contexts/network.tsx index bab197598..5458dac9b 100644 --- a/src/contexts/network.tsx +++ b/src/contexts/network.tsx @@ -5,6 +5,7 @@ import { useLocalStorage, LocalStorageKeys } from '../hooks/useLocalStorage'; import { WALLETCONNECT_ASSETHUB_ID } from '../constants/constants'; import { useOfframpActions } from '../stores/offrampStore'; import { getNetworkId, isNetworkEVM, Networks } from '../helpers/networks'; +import { useSep24Actions } from '../stores/sep24Store'; interface NetworkContextType { walletConnectPolkadotSelectedNetworkId: string; @@ -36,11 +37,13 @@ export const NetworkProvider = ({ children }: NetworkProviderProps) => { const [networkSelectorDisabled, setNetworkSelectorDisabled] = useState(false); const { resetOfframpState } = useOfframpActions(); + const { cleanup: cleanupSep24Variables } = useSep24Actions(); const { switchChain } = useSwitchChain(); const setSelectedNetwork = useCallback( (network: Networks) => { resetOfframpState(); + cleanupSep24Variables(); setSelectedNetworkState(network); setSelectedNetworkLocalStorage(network); diff --git a/src/pages/swap/index.tsx b/src/pages/swap/index.tsx index 5eb56f448..c7bc24fc1 100644 --- a/src/pages/swap/index.tsx +++ b/src/pages/swap/index.tsx @@ -1,6 +1,7 @@ import Big from 'big.js'; import { useEffect, useMemo, useRef, useState, useCallback } from 'preact/hooks'; import { ApiPromise } from '@polkadot/api'; +import { Fragment } from 'preact'; import { calculateTotalReceive, FeeCollapse } from '../../components/FeeCollapse'; import { PoolSelectorModal } from '../../components/InputKeys/SelectionModal'; diff --git a/src/stores/sep24Store.ts b/src/stores/sep24Store.ts index 9520227ff..3072a918e 100644 --- a/src/stores/sep24Store.ts +++ b/src/stores/sep24Store.ts @@ -36,13 +36,14 @@ const useSep24Store = create()((set, get) => ({ setExecutionInput: (input) => set({ executionInput: input }), setUrlInterval: (interval) => set({ urlInterval: interval }), - reset: () => + reset: () => { set({ anchorSessionParams: undefined, initialResponse: undefined, executionInput: undefined, urlInterval: undefined, - }), + }); + }, cleanup: () => { const { urlInterval } = get(); From 3f6551fcb7a47cceea6facd42688325cb32b602b Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Thu, 16 Jan 2025 19:12:14 -0300 Subject: [PATCH 24/44] sign finished animation --- src/components/SigningBox/index.tsx | 44 +++++++++++++++++++++++++---- src/hooks/useSignChallenge.ts | 2 +- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/components/SigningBox/index.tsx b/src/components/SigningBox/index.tsx index 443fac9c8..60546855e 100644 --- a/src/components/SigningBox/index.tsx +++ b/src/components/SigningBox/index.tsx @@ -1,5 +1,5 @@ import { Progress } from 'react-daisyui'; -import { FC } from 'preact/compat'; +import { FC, useRef, useState } from 'preact/compat'; import accountBalanceWalletIcon from '../../assets/account-balance-wallet.svg'; import { OfframpSigningPhase } from '../../types/offramp'; @@ -60,19 +60,49 @@ interface SigningBoxProps { const isValidStep = (step: OfframpSigningPhase | undefined, network: Networks): step is OfframpSigningPhase => { if (!step) return false; - if (step === 'finished') return false; + if (step === 'finished') return true; if (!isNetworkEVM(network) && (step === 'approved' || step === 'signed')) return false; return true; }; +const increaseProgressValueTo = (from: number, to: number, setProgressValueDisplay: (value: number) => void) => { + setProgressValueDisplay(from); + if (from === to) return; + + setTimeout(() => { + increaseProgressValueTo(from + 1, to, setProgressValueDisplay); + }, 10); +}; + export const SigningBox: FC = ({ step }) => { const { selectedNetwork } = useNetwork(); + const [progressValueDisplay, setProgressValueDisplay] = useState(0); + const initialMaxSignaturesRef = useRef(0); + const initialSignatureNumberRef = useRef(0); if (!isValidStep(step, selectedNetwork)) return null; - const progressValue = getProgressConfig(selectedNetwork)[step]; + if (step === 'finished') { + increaseProgressValueTo(progressValueDisplay, 100, setProgressValueDisplay); + } else { + setProgressValueDisplay(Number(getProgressConfig(selectedNetwork)[step])); + } + + if (progressValueDisplay == 100) { + return null; + } + const { maxSignatures, getSignatureNumber } = getSignatureConfig(selectedNetwork); + // If it is login step, signatureNumber is 0 and maxSignatures is 1, for any network + // Finished will display the last signature number and maxSignatures + const signatureNumber = + step === 'login' ? 1 : step === 'finished' ? initialMaxSignaturesRef.current : Number(getSignatureNumber(step)); + initialSignatureNumberRef.current = signatureNumber; + const maxSignaturesDisplay = + step === 'login' ? 1 : step === 'finished' ? initialMaxSignaturesRef.current : maxSignatures; + initialMaxSignaturesRef.current = maxSignaturesDisplay; + return (
@@ -92,14 +122,18 @@ export const SigningBox: FC = ({ step }) => {
- +

- Waiting for signature {getSignatureNumber(step)}/{maxSignatures} + Waiting for signature {initialSignatureNumberRef.current}/{initialMaxSignaturesRef.current}

diff --git a/src/hooks/useSignChallenge.ts b/src/hooks/useSignChallenge.ts index 2e8ddf5b8..06f5094c8 100644 --- a/src/hooks/useSignChallenge.ts +++ b/src/hooks/useSignChallenge.ts @@ -101,7 +101,7 @@ export function useSiweSignature() { signPromise.reject(new Error('Signing failed: ' + errorMessage)); } finally { setSignPromise(null); - setOfframpSigningPhase?.(undefined); + setOfframpSigningPhase?.('finished'); } }, [address, storageKey, signPromise, setSignPromise, getMessageSignature]); From b1520d5dacabc6973dd3dc9675929522aa3fb149 Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Fri, 17 Jan 2025 10:10:55 -0300 Subject: [PATCH 25/44] improve animation code --- src/components/SigningBox/index.tsx | 76 ++++++++++++++--------------- 1 file changed, 37 insertions(+), 39 deletions(-) diff --git a/src/components/SigningBox/index.tsx b/src/components/SigningBox/index.tsx index 60546855e..c92089749 100644 --- a/src/components/SigningBox/index.tsx +++ b/src/components/SigningBox/index.tsx @@ -1,11 +1,12 @@ import { Progress } from 'react-daisyui'; -import { FC, useRef, useState } from 'preact/compat'; +import { FC, StateUpdater, useRef, useState } from 'preact/compat'; import accountBalanceWalletIcon from '../../assets/account-balance-wallet.svg'; import { OfframpSigningPhase } from '../../types/offramp'; import { isNetworkEVM, Networks } from '../../helpers/networks'; import { useNetwork } from '../../contexts/network'; import { Spinner } from '../Spinner'; +import { useEffect } from 'react'; type ProgressStep = { started: string; @@ -16,8 +17,8 @@ type ProgressStep = { }; type SignatureConfig = { - maxSignatures: number; - getSignatureNumber: (step: OfframpSigningPhase) => string; + maxSignatures: (step: OfframpSigningPhase) => number; + getSignatureNumber: (step: OfframpSigningPhase) => number; }; const EVM_PROGRESS_CONFIG: ProgressStep = { @@ -37,13 +38,13 @@ const NON_EVM_PROGRESS_CONFIG: ProgressStep = { }; const EVM_SIGNATURE_CONFIG: SignatureConfig = { - maxSignatures: 2, - getSignatureNumber: (step: OfframpSigningPhase) => (step === 'started' ? '1' : '2'), + maxSignatures: (step: OfframpSigningPhase) => (step === 'login' ? 1 : 2), + getSignatureNumber: (step: OfframpSigningPhase) => (step === 'started' || 'login' ? 1 : 2), }; const NON_EVM_SIGNATURE_CONFIG: SignatureConfig = { - maxSignatures: 1, - getSignatureNumber: () => '1', + maxSignatures: () => 1, + getSignatureNumber: () => 1, }; const getProgressConfig = (network: Networks): ProgressStep => { @@ -65,43 +66,44 @@ const isValidStep = (step: OfframpSigningPhase | undefined, network: Networks): return true; }; -const increaseProgressValueTo = (from: number, to: number, setProgressValueDisplay: (value: number) => void) => { - setProgressValueDisplay(from); - if (from === to) return; - - setTimeout(() => { - increaseProgressValueTo(from + 1, to, setProgressValueDisplay); - }, 10); -}; - export const SigningBox: FC = ({ step }) => { const { selectedNetwork } = useNetwork(); - const [progressValueDisplay, setProgressValueDisplay] = useState(0); + const [progressValue, setProgressValue] = useState(0); const initialMaxSignaturesRef = useRef(0); const initialSignatureNumberRef = useRef(0); if (!isValidStep(step, selectedNetwork)) return null; - if (step === 'finished') { - increaseProgressValueTo(progressValueDisplay, 100, setProgressValueDisplay); - } else { - setProgressValueDisplay(Number(getProgressConfig(selectedNetwork)[step])); - } + useEffect(() => { + if (step === 'finished') { + const animateProgress = () => { + setProgressValue((prev) => { + if (prev >= 100) return 100; + return prev + 1; + }); + }; - if (progressValueDisplay == 100) { - return null; - } + const intervalId = setInterval(animateProgress, 5); + return () => clearInterval(intervalId); + } else { + setProgressValue(Number(getProgressConfig(selectedNetwork)[step])); + } + }, [step, selectedNetwork]); + + if (progressValue === 100) return null; const { maxSignatures, getSignatureNumber } = getSignatureConfig(selectedNetwork); - // If it is login step, signatureNumber is 0 and maxSignatures is 1, for any network - // Finished will display the last signature number and maxSignatures - const signatureNumber = - step === 'login' ? 1 : step === 'finished' ? initialMaxSignaturesRef.current : Number(getSignatureNumber(step)); - initialSignatureNumberRef.current = signatureNumber; - const maxSignaturesDisplay = - step === 'login' ? 1 : step === 'finished' ? initialMaxSignaturesRef.current : maxSignatures; - initialMaxSignaturesRef.current = maxSignaturesDisplay; + useEffect(() => { + if (step !== 'finished') { + initialMaxSignaturesRef.current = maxSignatures(step); + initialSignatureNumberRef.current = getSignatureNumber(step); + } + }, [step, maxSignatures, getSignatureNumber]); + + const signatureNumber = step === 'finished' ? initialSignatureNumberRef.current : Number(getSignatureNumber(step)); + + const maxSignaturesDisplay = step === 'finished' ? initialMaxSignaturesRef.current : maxSignatures(step); return (
@@ -122,18 +124,14 @@ export const SigningBox: FC = ({ step }) => {
- +

- Waiting for signature {initialSignatureNumberRef.current}/{initialMaxSignaturesRef.current} + Waiting for signature {signatureNumber}/{maxSignaturesDisplay}

From 9ae32d7a10f7bd4d07d941973d4e06ff365db8a6 Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Fri, 17 Jan 2025 10:41:23 -0300 Subject: [PATCH 26/44] show toast error on sign rejection --- src/hooks/offramp/useSubmitOfframp.ts | 10 +++++----- src/hooks/useSignChallenge.ts | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/hooks/offramp/useSubmitOfframp.ts b/src/hooks/offramp/useSubmitOfframp.ts index 327a8ef35..00cddac0f 100644 --- a/src/hooks/offramp/useSubmitOfframp.ts +++ b/src/hooks/offramp/useSubmitOfframp.ts @@ -19,6 +19,8 @@ import { useOfframpActions, useOfframpStarted, useOfframpState } from '../../sto import { ExecutionInput } from './useMainProcess'; import { useSep24Actions } from '../../stores/sep24Store'; +import { showToast, ToastMessage } from '../../helpers/notifications'; + export const useSubmitOfframp = () => { const { selectedNetwork } = useNetwork(); const { switchChainAsync, switchChain } = useSwitchChain(); @@ -124,12 +126,10 @@ export const useSubmitOfframp = () => { setOfframpInitiating(false); } } catch (error) { - console.error('Error initializing the offramping process', error); + console.error('Error initializing the offramping process', (error as Error).message); // 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.'); // 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. + if ((error as Error).message.includes('User rejected')) { + showToast(ToastMessage.ERROR, 'You must sign the login request to offramp Agentinian Peso'); } else { setInitializeFailed(); } diff --git a/src/hooks/useSignChallenge.ts b/src/hooks/useSignChallenge.ts index 06f5094c8..48f59c666 100644 --- a/src/hooks/useSignChallenge.ts +++ b/src/hooks/useSignChallenge.ts @@ -96,12 +96,13 @@ export function useSiweSignature() { localStorage.setItem(storageKey, JSON.stringify(signatureData)); signPromise.resolve(); + setOfframpSigningPhase?.('finished'); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); signPromise.reject(new Error('Signing failed: ' + errorMessage)); + setOfframpSigningPhase?.(undefined); } finally { setSignPromise(null); - setOfframpSigningPhase?.('finished'); } }, [address, storageKey, signPromise, setSignPromise, getMessageSignature]); From 17ebe3d2c91cab223ceef8996fa7dfa09daf5b55 Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Fri, 17 Jan 2025 11:02:57 -0300 Subject: [PATCH 27/44] typo, small sign box improvement for assethub --- src/hooks/offramp/useSubmitOfframp.ts | 2 +- src/services/phases/polkadot/assethub.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/hooks/offramp/useSubmitOfframp.ts b/src/hooks/offramp/useSubmitOfframp.ts index 00cddac0f..8336066ea 100644 --- a/src/hooks/offramp/useSubmitOfframp.ts +++ b/src/hooks/offramp/useSubmitOfframp.ts @@ -129,7 +129,7 @@ export const useSubmitOfframp = () => { console.error('Error initializing the offramping process', (error as Error).message); // Display error message, differentiating between user rejection and other errors if ((error as Error).message.includes('User rejected')) { - showToast(ToastMessage.ERROR, 'You must sign the login request to offramp Agentinian Peso'); + showToast(ToastMessage.ERROR, 'You must sign the login request to offramp Argentine Peso'); } else { setInitializeFailed(); } diff --git a/src/services/phases/polkadot/assethub.ts b/src/services/phases/polkadot/assethub.ts index 8232160c4..83cac6735 100644 --- a/src/services/phases/polkadot/assethub.ts +++ b/src/services/phases/polkadot/assethub.ts @@ -40,8 +40,6 @@ export async function executeAssetHubXCM(state: OfframpingState, context: Execut throw new Error('AssetHub node not available'); } - setOfframpSigningPhase?.('started'); - const didInputTokenArrivedOnPendulum = async () => { const inputBalanceRaw = await getRawInputBalance(state, context); return inputBalanceRaw.gt(Big(0)); From 67d4903f0fe292c0a4a4303d43d1e52fd3c21ed4 Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Fri, 17 Jan 2025 11:08:11 -0300 Subject: [PATCH 28/44] rollback on useless change --- src/stores/sep24Store.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/stores/sep24Store.ts b/src/stores/sep24Store.ts index 3072a918e..9520227ff 100644 --- a/src/stores/sep24Store.ts +++ b/src/stores/sep24Store.ts @@ -36,14 +36,13 @@ const useSep24Store = create()((set, get) => ({ setExecutionInput: (input) => set({ executionInput: input }), setUrlInterval: (interval) => set({ urlInterval: interval }), - reset: () => { + reset: () => set({ anchorSessionParams: undefined, initialResponse: undefined, executionInput: undefined, urlInterval: undefined, - }); - }, + }), cleanup: () => { const { urlInterval } = get(); From 8d55f7324b071f8b3cdfbed1cd1804226477313b Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Fri, 17 Jan 2025 11:23:13 -0300 Subject: [PATCH 29/44] fix lint errors --- src/components/SigningBox/index.tsx | 24 ++++++++++++------------ src/contexts/network.tsx | 2 +- src/hooks/useSignChallenge.ts | 4 ++-- src/pages/swap/index.tsx | 12 ------------ 4 files changed, 15 insertions(+), 27 deletions(-) diff --git a/src/components/SigningBox/index.tsx b/src/components/SigningBox/index.tsx index c92089749..d28de7323 100644 --- a/src/components/SigningBox/index.tsx +++ b/src/components/SigningBox/index.tsx @@ -1,5 +1,5 @@ import { Progress } from 'react-daisyui'; -import { FC, StateUpdater, useRef, useState } from 'preact/compat'; +import { FC, useRef, useState } from 'preact/compat'; import accountBalanceWalletIcon from '../../assets/account-balance-wallet.svg'; import { OfframpSigningPhase } from '../../types/offramp'; @@ -39,7 +39,7 @@ const NON_EVM_PROGRESS_CONFIG: ProgressStep = { const EVM_SIGNATURE_CONFIG: SignatureConfig = { maxSignatures: (step: OfframpSigningPhase) => (step === 'login' ? 1 : 2), - getSignatureNumber: (step: OfframpSigningPhase) => (step === 'started' || 'login' ? 1 : 2), + getSignatureNumber: (step: OfframpSigningPhase) => (step === 'started' || step === 'login' ? 1 : 2), }; const NON_EVM_SIGNATURE_CONFIG: SignatureConfig = { @@ -72,7 +72,14 @@ export const SigningBox: FC = ({ step }) => { const initialMaxSignaturesRef = useRef(0); const initialSignatureNumberRef = useRef(0); - if (!isValidStep(step, selectedNetwork)) return null; + const { maxSignatures, getSignatureNumber } = getSignatureConfig(selectedNetwork); + + useEffect(() => { + if (step !== 'finished' && isValidStep(step, selectedNetwork)) { + initialMaxSignaturesRef.current = maxSignatures(step); + initialSignatureNumberRef.current = getSignatureNumber(step); + } + }, [step, selectedNetwork, maxSignatures, getSignatureNumber]); useEffect(() => { if (step === 'finished') { @@ -86,21 +93,14 @@ export const SigningBox: FC = ({ step }) => { const intervalId = setInterval(animateProgress, 5); return () => clearInterval(intervalId); } else { + if (!isValidStep(step, selectedNetwork)) return; setProgressValue(Number(getProgressConfig(selectedNetwork)[step])); } }, [step, selectedNetwork]); + if (!isValidStep(step, selectedNetwork)) return null; if (progressValue === 100) return null; - const { maxSignatures, getSignatureNumber } = getSignatureConfig(selectedNetwork); - - useEffect(() => { - if (step !== 'finished') { - initialMaxSignaturesRef.current = maxSignatures(step); - initialSignatureNumberRef.current = getSignatureNumber(step); - } - }, [step, maxSignatures, getSignatureNumber]); - const signatureNumber = step === 'finished' ? initialSignatureNumberRef.current : Number(getSignatureNumber(step)); const maxSignaturesDisplay = step === 'finished' ? initialMaxSignaturesRef.current : maxSignatures(step); diff --git a/src/contexts/network.tsx b/src/contexts/network.tsx index 5458dac9b..9a4cea0d4 100644 --- a/src/contexts/network.tsx +++ b/src/contexts/network.tsx @@ -52,7 +52,7 @@ export const NetworkProvider = ({ children }: NetworkProviderProps) => { switchChain({ chainId: getNetworkId(network) }); } }, - [switchChain, setSelectedNetworkLocalStorage, resetOfframpState], + [switchChain, setSelectedNetworkLocalStorage, resetOfframpState, cleanupSep24Variables], ); // Only run on first render diff --git a/src/hooks/useSignChallenge.ts b/src/hooks/useSignChallenge.ts index 48f59c666..f243df827 100644 --- a/src/hooks/useSignChallenge.ts +++ b/src/hooks/useSignChallenge.ts @@ -58,7 +58,7 @@ export function useSiweSignature() { setSignPromise({ resolve, reject }); setOfframpSigningPhase?.('login'); }); - }, [setSignPromise, signPromise]); + }, [setOfframpSigningPhase, setSignPromise, signPromise]); const handleSign = useCallback(async () => { if (!address || !signPromise) return; @@ -104,7 +104,7 @@ export function useSiweSignature() { } finally { setSignPromise(null); } - }, [address, storageKey, signPromise, setSignPromise, getMessageSignature]); + }, [address, storageKey, signPromise, setSignPromise, getMessageSignature, setOfframpSigningPhase]); useEffect(() => { if (signPromise) handleSign(); diff --git a/src/pages/swap/index.tsx b/src/pages/swap/index.tsx index c7bc24fc1..4b7286806 100644 --- a/src/pages/swap/index.tsx +++ b/src/pages/swap/index.tsx @@ -94,15 +94,6 @@ 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 { @@ -408,9 +399,6 @@ export const SwapPage = () => { {(initializeFailedMessage || apiInitializeFailed) && (

{initializeFailedMessage}

-
)} From 680218be44ae8b519514aa4508a29d3503ce2fd8 Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Fri, 17 Jan 2025 11:33:37 -0300 Subject: [PATCH 30/44] move user reject detection to signChallenge component --- src/hooks/useSignChallenge.ts | 10 +++++++++- src/services/anchor/sep10/index.ts | 15 +-------------- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/hooks/useSignChallenge.ts b/src/hooks/useSignChallenge.ts index f243df827..c950c1884 100644 --- a/src/hooks/useSignChallenge.ts +++ b/src/hooks/useSignChallenge.ts @@ -99,8 +99,16 @@ export function useSiweSignature() { setOfframpSigningPhase?.('finished'); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); - signPromise.reject(new Error('Signing failed: ' + errorMessage)); setOfframpSigningPhase?.(undefined); + + if ( + (error as Error).message.includes('User rejected the request') || + (error as Error).message.includes('Signing failed: Cancelled') + ) { + // First case Assethub, second case EVM + return signPromise.reject(new Error('Signing failed: User rejected sign request')); + } + return signPromise.reject(new Error('Signing failed: Failed to sign login challenge. ' + errorMessage)); } finally { setSignPromise(null); } diff --git a/src/services/anchor/sep10/index.ts b/src/services/anchor/sep10/index.ts index 77d98bc85..7a1740e2d 100644 --- a/src/services/anchor/sep10/index.ts +++ b/src/services/anchor/sep10/index.ts @@ -56,20 +56,7 @@ export async function sep10( const transactionSigned = await fetchAndValidateChallenge(webAuthEndpoint, urlParams, signingKey); if (usesMemo) { - 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'); - } + await checkAndWaitForSignature(); } const { masterClientSignature, clientSignature, clientPublic } = await sep10SignaturesWithLoginRefresh( From c80dbdfe62680916c28071117c842c6fc67d75b7 Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Mon, 20 Jan 2025 11:25:19 +0100 Subject: [PATCH 31/44] Improve SigningBox animation --- src/assets/account-balance-wallet-blue.svg | 3 + src/components/SigningBox/index.tsx | 199 ++++++++++----------- 2 files changed, 97 insertions(+), 105 deletions(-) create mode 100644 src/assets/account-balance-wallet-blue.svg diff --git a/src/assets/account-balance-wallet-blue.svg b/src/assets/account-balance-wallet-blue.svg new file mode 100644 index 000000000..2f55a2d12 --- /dev/null +++ b/src/assets/account-balance-wallet-blue.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/SigningBox/index.tsx b/src/components/SigningBox/index.tsx index d28de7323..7c350d224 100644 --- a/src/components/SigningBox/index.tsx +++ b/src/components/SigningBox/index.tsx @@ -1,140 +1,129 @@ -import { Progress } from 'react-daisyui'; -import { FC, useRef, useState } from 'preact/compat'; -import accountBalanceWalletIcon from '../../assets/account-balance-wallet.svg'; +import { FC, useState, useEffect } from 'preact/compat'; +import { motion, AnimatePresence, useSpring, useTransform } from 'framer-motion'; +import accountBalanceWalletIcon from '../../assets/account-balance-wallet-blue.svg'; import { OfframpSigningPhase } from '../../types/offramp'; -import { isNetworkEVM, Networks } from '../../helpers/networks'; +import { isNetworkEVM } from '../../helpers/networks'; import { useNetwork } from '../../contexts/network'; import { Spinner } from '../Spinner'; -import { useEffect } from 'react'; - -type ProgressStep = { - started: string; - signed: string; - finished: string; - approved: string; - login: string; -}; - -type SignatureConfig = { - maxSignatures: (step: OfframpSigningPhase) => number; - getSignatureNumber: (step: OfframpSigningPhase) => number; -}; - -const EVM_PROGRESS_CONFIG: ProgressStep = { - started: '25', - approved: '50', - signed: '75', - finished: '100', - login: '15', -}; - -const NON_EVM_PROGRESS_CONFIG: ProgressStep = { - started: '33', - finished: '100', - signed: '0', - approved: '0', - login: '15', -}; -const EVM_SIGNATURE_CONFIG: SignatureConfig = { - maxSignatures: (step: OfframpSigningPhase) => (step === 'login' ? 1 : 2), - getSignatureNumber: (step: OfframpSigningPhase) => (step === 'started' || step === 'login' ? 1 : 2), +type ProgressConfig = { + [key in OfframpSigningPhase]: number; }; -const NON_EVM_SIGNATURE_CONFIG: SignatureConfig = { - maxSignatures: () => 1, - getSignatureNumber: () => 1, +const PROGRESS_CONFIGS: Record<'EVM' | 'NON_EVM', ProgressConfig> = { + EVM: { + started: 25, + approved: 50, + signed: 75, + finished: 100, + login: 15, + }, + NON_EVM: { + started: 33, + finished: 100, + signed: 0, + approved: 0, + login: 15, + }, }; -const getProgressConfig = (network: Networks): ProgressStep => { - return isNetworkEVM(network) ? EVM_PROGRESS_CONFIG : NON_EVM_PROGRESS_CONFIG; -}; - -const getSignatureConfig = (network: Networks): SignatureConfig => { - return isNetworkEVM(network) ? EVM_SIGNATURE_CONFIG : NON_EVM_SIGNATURE_CONFIG; +const getSignatureDetails = (step: OfframpSigningPhase, isEVM: boolean) => { + if (!isEVM) return { max: 1, current: 1 }; + if (step === 'login') return { max: 1, current: 1 }; + if (step === 'started') return { max: 2, current: 1 }; + return { max: 2, current: 2 }; }; interface SigningBoxProps { step?: OfframpSigningPhase; } -const isValidStep = (step: OfframpSigningPhase | undefined, network: Networks): step is OfframpSigningPhase => { +const isValidStep = (step: OfframpSigningPhase | undefined, isEVM: boolean): step is OfframpSigningPhase => { if (!step) return false; if (step === 'finished') return true; - if (!isNetworkEVM(network) && (step === 'approved' || step === 'signed')) return false; + if (!isEVM && (step === 'approved' || step === 'signed')) return false; return true; }; export const SigningBox: FC = ({ step }) => { const { selectedNetwork } = useNetwork(); - const [progressValue, setProgressValue] = useState(0); - const initialMaxSignaturesRef = useRef(0); - const initialSignatureNumberRef = useRef(0); + const isEVM = isNetworkEVM(selectedNetwork); + const progressConfig = isEVM ? PROGRESS_CONFIGS.EVM : PROGRESS_CONFIGS.NON_EVM; - const { maxSignatures, getSignatureNumber } = getSignatureConfig(selectedNetwork); + const progressValue = useSpring(0, { + stiffness: 60, + damping: 15, + restDelta: 0.001, + }); + const displayProgress = useTransform(progressValue, Math.round); - useEffect(() => { - if (step !== 'finished' && isValidStep(step, selectedNetwork)) { - initialMaxSignaturesRef.current = maxSignatures(step); - initialSignatureNumberRef.current = getSignatureNumber(step); - } - }, [step, selectedNetwork, maxSignatures, getSignatureNumber]); + const [signatureState, setSignatureState] = useState({ max: 0, current: 0 }); + const [shouldExit, setShouldExit] = useState(false); useEffect(() => { + if (!isValidStep(step, isEVM)) return; + if (step === 'finished') { - const animateProgress = () => { - setProgressValue((prev) => { - if (prev >= 100) return 100; - return prev + 1; - }); - }; - - const intervalId = setInterval(animateProgress, 5); - return () => clearInterval(intervalId); - } else { - if (!isValidStep(step, selectedNetwork)) return; - setProgressValue(Number(getProgressConfig(selectedNetwork)[step])); + progressValue.set(100); + setTimeout(() => setShouldExit(true), 3500); + return; } - }, [step, selectedNetwork]); - if (!isValidStep(step, selectedNetwork)) return null; - if (progressValue === 100) return null; + progressValue.set(progressConfig[step]); + setSignatureState(getSignatureDetails(step, isEVM)); + }, [step, isEVM, progressConfig, progressValue]); - const signatureNumber = step === 'finished' ? initialSignatureNumberRef.current : Number(getSignatureNumber(step)); - - const maxSignaturesDisplay = step === 'finished' ? initialMaxSignaturesRef.current : maxSignatures(step); + if (!isValidStep(step, isEVM) || shouldExit) return null; return ( -
-
-
-

Action Required

-
- -
-
-
- wallet account button -
-
-

Please sign the transaction in

-

your connected wallet to proceed

-
-
- -
- + + {!isValidStep(step, isEVM) || shouldExit ? null : ( + +
+ +

Action Required

+
+ +
+ +
+ wallet account button +
+
+

Please sign the transaction in

+

your connected wallet to proceed

+
+
+ + +
+ +
+
+
+ + + +

+ Waiting for signature {signatureState.current}/{signatureState.max} +

+
-
- -
- -

- Waiting for signature {signatureNumber}/{maxSignaturesDisplay} -

-
-
-
+ + )} + ); }; From 4fd8653478a2c7f3520f8cd2d35f3775f850f4a5 Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Mon, 20 Jan 2025 15:11:46 +0100 Subject: [PATCH 32/44] Improve footer spacing --- src/components/Navbar/index.tsx | 4 ++-- src/components/PoweredBy/index.tsx | 6 +++--- src/components/WhyVortex/index.tsx | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/Navbar/index.tsx b/src/components/Navbar/index.tsx index 7babbb140..6a0b49892 100644 --- a/src/components/Navbar/index.tsx +++ b/src/components/Navbar/index.tsx @@ -82,12 +82,12 @@ const MobileMenuList: FC = ({ showMenu, closeMenu }) => ( const Links = () => (
    {links.map((link) => ( -
  • +
  • {link.title} diff --git a/src/components/PoweredBy/index.tsx b/src/components/PoweredBy/index.tsx index c80176328..aa11b3afb 100644 --- a/src/components/PoweredBy/index.tsx +++ b/src/components/PoweredBy/index.tsx @@ -26,7 +26,7 @@ const Image = ({ src, alt, comingSoon, additionalClass }: ImageProps) => ( ); const ImageList = ({ images }: { images: ImageProps[] }) => ( -
    +
    {images.map((img) => ( ))} @@ -46,9 +46,9 @@ export function PoweredBy() { href="https://satoshipay.io" target="_blank" rel="noopener noreferrer" - className="transition hover:opacity-80" + className="flex gap-1 text-xs transition hover:opacity-80" > - Satoshipay + A Satoshipay Company

    diff --git a/src/components/WhyVortex/index.tsx b/src/components/WhyVortex/index.tsx index 1a4fda8e8..132860222 100644 --- a/src/components/WhyVortex/index.tsx +++ b/src/components/WhyVortex/index.tsx @@ -13,7 +13,7 @@ const features: Feature[] = [ { icon: DOLLAR, title: 'Lower Fees', - description: 'Offramping fees at just 0.5%, well below market average.', + description: 'Offramping fees as low as 0.25%.', subtext: 'Keep more of what you earn.', }, { From 4d91fdf5559a4b7e71396d80de2e45dbf13f7d27 Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Mon, 20 Jan 2025 14:49:18 -0300 Subject: [PATCH 33/44] return IP --- signer-service/src/api/routes/v1/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/signer-service/src/api/routes/v1/index.js b/signer-service/src/api/routes/v1/index.js index 063b4afc9..db40eece3 100644 --- a/signer-service/src/api/routes/v1/index.js +++ b/signer-service/src/api/routes/v1/index.js @@ -82,4 +82,6 @@ router.use('/rating', ratingRoutes); */ router.use('/siwe', siweRoutes); +router.use('/ip', (request, response) => response.send(request.ip)); + module.exports = router; From 9e9e45d8c3008ca95828f63e19a371aff5ea14d0 Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Mon, 20 Jan 2025 16:01:22 -0300 Subject: [PATCH 34/44] trust all proxies --- signer-service/src/config/express.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/signer-service/src/config/express.js b/signer-service/src/config/express.js index 43ea97b13..42abaa34b 100644 --- a/signer-service/src/config/express.js +++ b/signer-service/src/config/express.js @@ -33,7 +33,21 @@ app.use( // enable rate limiting // Set number of expected proxies -app.set('trust proxy', rateLimitNumberOfProxies); +app.set('trust proxy', true); + +// Add this before your rate limiter +app.use((req, res, next) => { + console.log({ + 'Raw Socket IP': req.socket.remoteAddress, + 'Express req.ip': req.ip, + 'X-Forwarded-For': req.headers['x-forwarded-for'], + 'X-Real-IP': req.headers['x-real-ip'], + 'All Headers': req.headers, + 'Trust Proxy Setting': app.get('trust proxy'), + }); + next(); +}); + // Define rate limiter const limiter = rateLimit({ windowMs: rateLimitWindowMinutes * 60 * 1000, From ea8c6c4b1714fd6147a342fa7ee157687fa6586e Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Mon, 20 Jan 2025 16:04:20 -0300 Subject: [PATCH 35/44] remove auth headers --- signer-service/src/config/express.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/signer-service/src/config/express.js b/signer-service/src/config/express.js index 42abaa34b..d2cd889d1 100644 --- a/signer-service/src/config/express.js +++ b/signer-service/src/config/express.js @@ -35,14 +35,16 @@ app.use( // Set number of expected proxies app.set('trust proxy', true); -// Add this before your rate limiter app.use((req, res, next) => { + delete filteredHeaders['authorization']; + delete filteredHeaders['cookie']; + console.log({ 'Raw Socket IP': req.socket.remoteAddress, 'Express req.ip': req.ip, 'X-Forwarded-For': req.headers['x-forwarded-for'], 'X-Real-IP': req.headers['x-real-ip'], - 'All Headers': req.headers, + 'Filtered Headers': filteredHeaders, 'Trust Proxy Setting': app.get('trust proxy'), }); next(); From 1a3119e21e3aedb9826eed0ce4629cb08116cc0e Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Mon, 20 Jan 2025 18:51:56 -0300 Subject: [PATCH 36/44] remove filteredHeaders --- signer-service/src/config/express.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/signer-service/src/config/express.js b/signer-service/src/config/express.js index d2cd889d1..82070b7b8 100644 --- a/signer-service/src/config/express.js +++ b/signer-service/src/config/express.js @@ -36,9 +36,6 @@ app.use( app.set('trust proxy', true); app.use((req, res, next) => { - delete filteredHeaders['authorization']; - delete filteredHeaders['cookie']; - console.log({ 'Raw Socket IP': req.socket.remoteAddress, 'Express req.ip': req.ip, From 90431aab395f92c969f757d134e60284cab55efc Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Mon, 20 Jan 2025 18:53:05 -0300 Subject: [PATCH 37/44] remove filtered headers, testing log --- signer-service/src/config/express.js | 1 - 1 file changed, 1 deletion(-) diff --git a/signer-service/src/config/express.js b/signer-service/src/config/express.js index 82070b7b8..de34b1bc2 100644 --- a/signer-service/src/config/express.js +++ b/signer-service/src/config/express.js @@ -41,7 +41,6 @@ app.use((req, res, next) => { 'Express req.ip': req.ip, 'X-Forwarded-For': req.headers['x-forwarded-for'], 'X-Real-IP': req.headers['x-real-ip'], - 'Filtered Headers': filteredHeaders, 'Trust Proxy Setting': app.get('trust proxy'), }); next(); From 5d5d74a457405c248dbae6fdeed689e8ea31feae Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Tue, 21 Jan 2025 11:39:33 -0300 Subject: [PATCH 38/44] tweak signing box animation --- src/components/SigningBox/index.tsx | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/components/SigningBox/index.tsx b/src/components/SigningBox/index.tsx index 7c350d224..e5e9e6ff6 100644 --- a/src/components/SigningBox/index.tsx +++ b/src/components/SigningBox/index.tsx @@ -1,5 +1,5 @@ import { FC, useState, useEffect } from 'preact/compat'; -import { motion, AnimatePresence, useSpring, useTransform } from 'framer-motion'; +import { motion, AnimatePresence } from 'framer-motion'; import accountBalanceWalletIcon from '../../assets/account-balance-wallet-blue.svg'; import { OfframpSigningPhase } from '../../types/offramp'; @@ -41,7 +41,7 @@ interface SigningBoxProps { const isValidStep = (step: OfframpSigningPhase | undefined, isEVM: boolean): step is OfframpSigningPhase => { if (!step) return false; - if (step === 'finished') return true; + if (step === 'finished' || step === 'login') return true; if (!isEVM && (step === 'approved' || step === 'signed')) return false; return true; }; @@ -51,30 +51,26 @@ export const SigningBox: FC = ({ step }) => { const isEVM = isNetworkEVM(selectedNetwork); const progressConfig = isEVM ? PROGRESS_CONFIGS.EVM : PROGRESS_CONFIGS.NON_EVM; - const progressValue = useSpring(0, { - stiffness: 60, - damping: 15, - restDelta: 0.001, - }); - const displayProgress = useTransform(progressValue, Math.round); - + const [progress, setProgress] = useState(0); const [signatureState, setSignatureState] = useState({ max: 0, current: 0 }); const [shouldExit, setShouldExit] = useState(false); useEffect(() => { if (!isValidStep(step, isEVM)) return; + if (step !== 'finished' && shouldExit) { + setShouldExit(false); + } + if (step === 'finished') { - progressValue.set(100); - setTimeout(() => setShouldExit(true), 3500); + setProgress(100); + setTimeout(() => setShouldExit(true), 2500); return; } - progressValue.set(progressConfig[step]); + setProgress(progressConfig[step]); setSignatureState(getSignatureDetails(step, isEVM)); - }, [step, isEVM, progressConfig, progressValue]); - - if (!isValidStep(step, isEVM) || shouldExit) return null; + }, [step, isEVM, progressConfig, shouldExit]); return ( @@ -108,7 +104,7 @@ export const SigningBox: FC = ({ step }) => {
    From f5af08a2292d34ec32c89805e631fe453658880f Mon Sep 17 00:00:00 2001 From: gianfra-t <96739519+gianfra-t@users.noreply.github.com> Date: Tue, 21 Jan 2025 13:54:14 -0300 Subject: [PATCH 39/44] Update src/hooks/offramp/useSubmitOfframp.ts Co-authored-by: Marcel Ebert --- src/hooks/offramp/useSubmitOfframp.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/offramp/useSubmitOfframp.ts b/src/hooks/offramp/useSubmitOfframp.ts index 8336066ea..057b40c42 100644 --- a/src/hooks/offramp/useSubmitOfframp.ts +++ b/src/hooks/offramp/useSubmitOfframp.ts @@ -129,7 +129,7 @@ export const useSubmitOfframp = () => { console.error('Error initializing the offramping process', (error as Error).message); // Display error message, differentiating between user rejection and other errors if ((error as Error).message.includes('User rejected')) { - showToast(ToastMessage.ERROR, 'You must sign the login request to offramp Argentine Peso'); + showToast(ToastMessage.ERROR, 'You must sign the login request to be able to sell Argentine Peso'); } else { setInitializeFailed(); } From 1857a960699c622c77007e6d87affe55bc7c47e6 Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Wed, 22 Jan 2025 14:37:01 +0100 Subject: [PATCH 40/44] show currency names in the dropdown --- src/components/InputKeys/PoolListItem/index.tsx | 4 +++- src/components/InputKeys/SelectionModal.tsx | 5 +++-- src/constants/tokenConfig.ts | 3 +++ src/pages/swap/index.tsx | 1 + 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/components/InputKeys/PoolListItem/index.tsx b/src/components/InputKeys/PoolListItem/index.tsx index 57e6fc010..eaab3a250 100644 --- a/src/components/InputKeys/PoolListItem/index.tsx +++ b/src/components/InputKeys/PoolListItem/index.tsx @@ -9,6 +9,7 @@ interface PoolListItemProps { isSelected?: boolean; onSelect: (tokenType: T) => void; assetIcon: AssetIconType; + name?: string; } export function PoolListItem({ @@ -17,6 +18,7 @@ export function PoolListItem({ isSelected, onSelect, assetIcon, + name, }: PoolListItemProps) { const tokenIcon = useGetAssetIcon(assetIcon); @@ -43,7 +45,7 @@ export function PoolListItem({ {tokenSymbol} - {tokenSymbol} + {name || tokenSymbol} ); diff --git a/src/components/InputKeys/SelectionModal.tsx b/src/components/InputKeys/SelectionModal.tsx index ac9c18635..d7996b8fc 100644 --- a/src/components/InputKeys/SelectionModal.tsx +++ b/src/components/InputKeys/SelectionModal.tsx @@ -13,7 +13,7 @@ interface PoolSelectorModalProps ext } interface PoolListProps { - definitions: { assetSymbol: string; type: T; assetIcon: AssetIconType }[]; + definitions: { assetSymbol: string; type: T; assetIcon: AssetIconType; name?: string }[]; onSelect: (tokenType: InputTokenType | OutputTokenType) => void; selected: InputTokenType | OutputTokenType; } @@ -46,7 +46,7 @@ function PoolList({ onSelect, defini placeholder="Find by name or address" />
    - {definitions.map(({ assetIcon, assetSymbol, type }) => ( + {definitions.map(({ assetIcon, assetSymbol, type, name }) => ( ({ onSelect, defini tokenType={type} tokenSymbol={assetSymbol} assetIcon={assetIcon} + name={name} /> ))}
    diff --git a/src/constants/tokenConfig.ts b/src/constants/tokenConfig.ts index 7e758df4d..00f60dfc3 100644 --- a/src/constants/tokenConfig.ts +++ b/src/constants/tokenConfig.ts @@ -39,6 +39,7 @@ export type InputTokenType = 'usdc' | 'usdce' | 'usdt'; export interface Fiat { assetIcon: AssetIconType; symbol: string; + name: string; } export interface OutputTokenDetails { @@ -261,6 +262,7 @@ export const OUTPUT_TOKEN_CONFIG: Record = fiat: { assetIcon: 'eur', symbol: 'EUR', + name: 'Euro', }, stellarAsset: { code: { @@ -286,6 +288,7 @@ export const OUTPUT_TOKEN_CONFIG: Record = fiat: { assetIcon: 'ars', symbol: 'ARS', + name: 'Argentine Peso', }, stellarAsset: { code: { diff --git a/src/pages/swap/index.tsx b/src/pages/swap/index.tsx index 04ad5bc77..71d3aa381 100644 --- a/src/pages/swap/index.tsx +++ b/src/pages/swap/index.tsx @@ -295,6 +295,7 @@ export const SwapPage = () => { type: key as OutputTokenType, assetSymbol: value.fiat.symbol, assetIcon: value.fiat.assetIcon, + name: value.fiat.name, })); const modals = ( From 8622107c356f57caa865fa4bc0fa856e2dae6118 Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Thu, 23 Jan 2025 11:37:14 -0300 Subject: [PATCH 41/44] do not show insufficient balance, max withdrawal error if wallet is not connected --- src/pages/swap/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/swap/index.tsx b/src/pages/swap/index.tsx index 71d3aa381..302cbc6b2 100644 --- a/src/pages/swap/index.tsx +++ b/src/pages/swap/index.tsx @@ -65,7 +65,7 @@ export const SwapPage = () => { const feeComparisonRef = useRef(null); const pendulumNode = usePendulumNode(); const [api, setApi] = useState(null); - const { address } = useVortexAccount(); + const { isDisconnected, address } = useVortexAccount(); const [initializeFailedMessage, setInitializeFailedMessage] = useState(null); const [apiInitializeFailed, setApiInitializeFailed] = useState(false); const [_, setIsReady] = useState(false); @@ -253,6 +253,8 @@ export const SwapPage = () => { ); function getCurrentErrorMessage() { + if (isDisconnected) return; + if (typeof userInputTokenBalance === 'string') { if (Big(userInputTokenBalance).lt(fromAmount ?? 0) && walletAccount) { trackEvent({ event: 'form_error', error_message: 'insufficient_balance' }); From 8569b1aff7112fe8b23f0e405a6b1fcb96c6ccea Mon Sep 17 00:00:00 2001 From: Prayag <52105313+prayagd@users.noreply.github.com> Date: Thu, 23 Jan 2025 20:45:10 +0530 Subject: [PATCH 42/44] Updated the max error amount --- src/constants/tokenConfig.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants/tokenConfig.ts b/src/constants/tokenConfig.ts index 00f60dfc3..3345fd34b 100644 --- a/src/constants/tokenConfig.ts +++ b/src/constants/tokenConfig.ts @@ -277,7 +277,7 @@ export const OUTPUT_TOKEN_CONFIG: Record = vaultAccountId: '6dgJM1ijyHFEfzUokJ1AHq3z3R3Z8ouc8B5SL9YjMRUaLsjh', erc20WrapperAddress: '6eNUvRWCKE3kejoyrJTXiSM7NxtWi37eRXTnKhGKPsJevAj5', minWithdrawalAmountRaw: '10000000000000', - maxWithdrawalAmountRaw: '10000000000000000', + maxWithdrawalAmountRaw: '5000000000000000', offrampFeesBasisPoints: 25, usesMemo: false, supportsClientDomain: true, From bd06679b8b87adf9b930f36f2afa565b6df465a6 Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Thu, 23 Jan 2025 13:27:41 -0300 Subject: [PATCH 43/44] add custom rpcs for all evm chains --- src/wagmiConfig.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/wagmiConfig.ts b/src/wagmiConfig.ts index c9f0e358d..a14605b21 100644 --- a/src/wagmiConfig.ts +++ b/src/wagmiConfig.ts @@ -5,10 +5,14 @@ import { WagmiAdapter } from '@reown/appkit-adapter-wagmi'; import { createAppKit } from '@reown/appkit/react'; // If we have an Alchemy API key, we can use it to fetch data from Polygon, otherwise use the default endpoint -// TODO we need to add better RPCs because metamask warns about unkown ones (defaults). Avalanche, base, etc. const transports = config.alchemyApiKey ? { [polygon.id]: http(`https://polygon-mainnet.g.alchemy.com/v2/${config.alchemyApiKey}`), + [mainnet.id]: http(`https://eth-mainnet.g.alchemy.com/v2/${config.alchemyApiKey}`), + [bsc.id]: http(`https://bnb-mainnet.g.alchemy.com/v2/${config.alchemyApiKey}`), + [arbitrum.id]: http(`https://arb-mainnet.g.alchemy.com/v2/${config.alchemyApiKey}`), + [base.id]: http(`https://base-mainnet.g.alchemy.com/v2/${config.alchemyApiKey}`), + [avalanche.id]: http(`https://avax-mainnet.g.alchemy.com/v2/${config.alchemyApiKey}`), } : { [polygon.id]: http(''), From 9a7ad255a65b37c4a6cdab35e6e388bf1c443413 Mon Sep 17 00:00:00 2001 From: Gianfranco Date: Thu, 23 Jan 2025 14:17:36 -0300 Subject: [PATCH 44/44] define empty transports if no alchemy key --- src/wagmiConfig.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/wagmiConfig.ts b/src/wagmiConfig.ts index a14605b21..ec5eeb284 100644 --- a/src/wagmiConfig.ts +++ b/src/wagmiConfig.ts @@ -16,6 +16,11 @@ const transports = config.alchemyApiKey } : { [polygon.id]: http(''), + [mainnet.id]: http(''), + [bsc.id]: http(''), + [arbitrum.id]: http(''), + [base.id]: http(''), + [avalanche.id]: http(''), }; // 2. Create a metadata object - optional