From 853208773d1667257e562e4bce8e9397d4f53e44 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torsten=20St=C3=BCber?=
<15174476+TorstenStueber@users.noreply.github.com>
Date: Thu, 8 Aug 2024 11:55:29 -0300
Subject: [PATCH 1/2] 46 integrate google analytics query into the prototype
(#86)
* Add basic event tracking
* Add tracked events
---
index.html | 27 ++++++
src/components/FeeCollapse/index.tsx | 7 +-
src/components/NumericInput/index.tsx | 64 ++++++++------
src/contexts/events.tsx | 121 ++++++++++++++++++++++++++
src/hooks/useMainProcess.ts | 19 +++-
src/main.tsx | 19 ++--
6 files changed, 219 insertions(+), 38 deletions(-)
create mode 100644 src/contexts/events.tsx
diff --git a/index.html b/index.html
index 0d8addc4..2eaa699a 100644
--- a/index.html
+++ b/index.html
@@ -7,6 +7,22 @@
Vortex
+
+
+
+
+
+
+
+
+
diff --git a/src/components/FeeCollapse/index.tsx b/src/components/FeeCollapse/index.tsx
index 63158c1e..65de11bd 100644
--- a/src/components/FeeCollapse/index.tsx
+++ b/src/components/FeeCollapse/index.tsx
@@ -5,6 +5,7 @@ import LocalGasStationIcon from '@mui/icons-material/LocalGasStation';
import Big from 'big.js';
import { roundDownToSignificantDecimals } from '../../helpers/parseNumbers';
import { OUTPUT_TOKEN_CONFIG, OutputTokenType } from '../../constants/tokenConfig';
+import { useEventsContext } from '../../contexts/events';
const FEES_RATE = 0.05; // 0.5% fee rate
@@ -27,8 +28,12 @@ interface CollapseProps {
export const FeeCollapse: FC = ({ fromAmount, toAmount, toCurrency }) => {
const [isOpen, setIsOpen] = useState(false);
+ const { trackEvent } = useEventsContext();
- const toggleIsOpen = () => setIsOpen((state) => !state);
+ const toggleIsOpen = () => {
+ trackEvent({ event: 'click_details' });
+ setIsOpen((state) => !state);
+ };
const outputToken = toCurrency ? OUTPUT_TOKEN_CONFIG[toCurrency] : undefined;
diff --git a/src/components/NumericInput/index.tsx b/src/components/NumericInput/index.tsx
index 7e80615e..6de9743b 100644
--- a/src/components/NumericInput/index.tsx
+++ b/src/components/NumericInput/index.tsx
@@ -1,5 +1,6 @@
import { Input } from 'react-daisyui';
import { UseFormRegisterReturn } from 'react-hook-form';
+import { useEventsContext } from '../../contexts/events';
export function exceedsMaxDecimals(value: unknown, maxDecimals: number) {
if (value === undefined || value === null) return true;
@@ -53,31 +54,38 @@ export const NumericInput = ({
autoFocus,
disabled,
disableStyles = false,
-}: NumericInputProps) => (
-
- handleOnKeyPress(e, maxDecimals)}
- onInput={handleOnInput}
- pattern="^[0-9]*[.,]?[0-9]*$"
- placeholder="0.0"
- readOnly={readOnly}
- spellcheck="false"
- step="any"
- type="text"
- inputmode="decimal"
- value={defaultValue}
- autoFocus={autoFocus}
- disabled={disabled}
- {...register}
- />
-
-);
+}: NumericInputProps) => {
+ const { trackEvent } = useEventsContext();
+
+ return (
+
+ handleOnKeyPress(e, maxDecimals)}
+ onInput={(e: KeyboardEvent) => {
+ trackEvent({ event: 'amount_type' });
+ handleOnInput(e);
+ }}
+ pattern="^[0-9]*[.,]?[0-9]*$"
+ placeholder="0.0"
+ readOnly={readOnly}
+ spellcheck="false"
+ step="any"
+ type="text"
+ inputmode="decimal"
+ value={defaultValue}
+ autoFocus={autoFocus}
+ disabled={disabled}
+ {...register}
+ />
+
+ );
+};
diff --git a/src/contexts/events.tsx b/src/contexts/events.tsx
new file mode 100644
index 00000000..d737f5f9
--- /dev/null
+++ b/src/contexts/events.tsx
@@ -0,0 +1,121 @@
+import { createContext } from 'preact';
+import { PropsWithChildren, useCallback, useContext, useEffect, useRef, useState } from 'preact/compat';
+import { useAccount } from 'wagmi';
+import { INPUT_TOKEN_CONFIG, OUTPUT_TOKEN_CONFIG } from '../constants/tokenConfig';
+import { OfframpingState } from '../services/offrampingFlow';
+
+declare global {
+ interface Window {
+ dataLayer: Record[];
+ }
+}
+
+const UNIQUE_EVENT_TYPES = ['amount_type', 'click_details', 'click_support'];
+
+export interface AmountTypeEvent {
+ event: `amount_type`;
+}
+
+export interface ClickDetailsEvent {
+ event: 'click_details';
+}
+
+export interface WalletConnectEvent {
+ event: 'wallet_connect';
+ wallet_action: 'connect' | 'disconnect' | 'change';
+}
+
+export interface TransactionEvent {
+ event: 'transaction_confirmation' | 'kyc_completed' | 'transaction_success' | 'transaction_failure';
+ from_asset: string;
+ to_asset: string;
+ from_amount: string;
+ to_amount: string;
+}
+
+export interface ClickSupportEvent {
+ event: 'click_support';
+ transaction_status: 'success' | 'failure';
+}
+
+export type TrackableEvent =
+ | AmountTypeEvent
+ | ClickDetailsEvent
+ | WalletConnectEvent
+ | TransactionEvent
+ | ClickSupportEvent;
+
+type EventType = TrackableEvent['event'];
+
+type UseEventsContext = ReturnType;
+const useEvents = () => {
+ const [_, setTrackedEventTypes] = useState>(new Set());
+
+ const previousAddress = useRef<`0x${string}` | undefined>(undefined);
+ const { address } = useAccount();
+
+ const trackEvent = useCallback(
+ (event: TrackableEvent) => {
+ setTrackedEventTypes((trackedEventTypes) => {
+ if (UNIQUE_EVENT_TYPES.includes(event.event)) {
+ if (trackedEventTypes.has(event.event)) {
+ return trackedEventTypes;
+ } else {
+ trackedEventTypes = new Set(trackedEventTypes);
+ trackedEventTypes.add(event.event);
+ }
+ }
+ console.log('Push data layer', event);
+
+ window.dataLayer.push(event);
+
+ return trackedEventTypes;
+ });
+ },
+ [setTrackedEventTypes],
+ );
+
+ useEffect(() => {
+ const wasConnected = previousAddress.current !== undefined;
+ const isConnected = address !== undefined;
+
+ if (!isConnected) {
+ trackEvent({ event: 'wallet_connect', wallet_action: 'disconnect' });
+ } else {
+ trackEvent({ event: 'wallet_connect', wallet_action: wasConnected ? 'change' : 'connect' });
+ }
+
+ previousAddress.current = address;
+ }, [address]);
+
+ return {
+ trackEvent,
+ };
+};
+
+const Context = createContext(undefined);
+
+export const useEventsContext = () => {
+ const contextValue = useContext(Context);
+ if (contextValue === undefined) {
+ throw new Error('Context must be inside a Provider');
+ }
+
+ return contextValue;
+};
+
+export function EventsProvider({ children }: PropsWithChildren) {
+ const useEventsResult = useEvents();
+
+ return {children};
+}
+
+export function createTransactionEvent(type: TransactionEvent['event'], state: OfframpingState) {
+ return {
+ event: type,
+ from_asset: INPUT_TOKEN_CONFIG[state.inputTokenType].assetSymbol,
+ to_asset: OUTPUT_TOKEN_CONFIG[state.outputTokenType].stellarAsset.code.string,
+ from_amount: state.inputAmount.units,
+ to_amount: state.outputAmount.units,
+ };
+}
diff --git a/src/hooks/useMainProcess.ts b/src/hooks/useMainProcess.ts
index e8397a3f..562c7fe6 100644
--- a/src/hooks/useMainProcess.ts
+++ b/src/hooks/useMainProcess.ts
@@ -3,7 +3,7 @@ import { useState, useEffect, useCallback } from 'react';
// Configs, Types, constants
import { createStellarEphemeralSecret, sep24First } from '../services/anchor';
import { ExecutionInput } from '../types';
-import { OUTPUT_TOKEN_CONFIG } from '../constants/tokenConfig';
+import { INPUT_TOKEN_CONFIG, OUTPUT_TOKEN_CONFIG } from '../constants/tokenConfig';
import { fetchTomlValues, sep10, sep24Second } from '../services/anchor';
// Utils
@@ -20,6 +20,7 @@ import {
} from '../services/offrampingFlow';
import { EventStatus, GenericEvent } from '../components/GenericEvent';
import Big from 'big.js';
+import { createTransactionEvent, useEventsContext } from '../contexts/events';
export const useMainProcess = () => {
// EXAMPLE mocking states
@@ -39,12 +40,19 @@ export const useMainProcess = () => {
const [sep24Url, setSep24Url] = useState(undefined);
const [sep24Id, setSep24Id] = useState(undefined);
const wagmiConfig = useConfig();
+ const { trackEvent } = useEventsContext();
const [events, setEvents] = useState([]);
const updateHookStateFromState = (state: OfframpingState | undefined) => {
setOfframpingPhase(state?.phase);
setSep24Id(state?.sep24Id);
+
+ if (state?.phase === 'success') {
+ trackEvent(createTransactionEvent('transaction_success', state));
+ } else if (state?.phase === 'failure') {
+ trackEvent(createTransactionEvent('transaction_failure', state));
+ }
};
useEffect(() => {
@@ -64,6 +72,13 @@ export const useMainProcess = () => {
(async () => {
setOfframpingStarted(true);
+ trackEvent({
+ event: 'transaction_confirmation',
+ from_asset: INPUT_TOKEN_CONFIG[inputTokenType].assetSymbol,
+ to_asset: OUTPUT_TOKEN_CONFIG[outputTokenType].stellarAsset.code.string,
+ from_amount: amountInUnits,
+ to_amount: Big(minAmountOutUnits).round(2, 0).toFixed(2, 0),
+ });
try {
const stellarEphemeralSecret = createStellarEphemeralSecret();
@@ -99,6 +114,8 @@ export const useMainProcess = () => {
sepResult: secondSep24Response,
});
+ trackEvent(createTransactionEvent('kyc_completed', initialState));
+
updateHookStateFromState(initialState);
} catch (error) {
console.error('Some error occurred initializing the offramping process', error);
diff --git a/src/main.tsx b/src/main.tsx
index efc5752a..fb0083bd 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -14,6 +14,7 @@ import { App } from './app';
import defaultTheme from './theme';
import { GlobalState, GlobalStateContext, GlobalStateProvider } from './GlobalStateProvider';
import { wagmiConfig } from './wagmiConfig';
+import { EventsProvider } from './contexts/events';
const queryClient = new QueryClient();
@@ -24,14 +25,16 @@ render(
-
-
- {(globalState) => {
- const { tenantRPC, getThemeName = () => undefined } = globalState as GlobalState;
- return ;
- }}
-
-
+
+
+
+ {(globalState) => {
+ const { tenantRPC, getThemeName = () => undefined } = globalState as GlobalState;
+ return ;
+ }}
+
+
+
From bbfd121904d423734672f024cd17f64c80b5a1ff Mon Sep 17 00:00:00 2001
From: Kacper Szarkiewicz <43585069+Sharqiewicz@users.noreply.github.com>
Date: Thu, 8 Aug 2024 17:50:29 +0200
Subject: [PATCH 2/2] Signing dialog box (#87)
* add basic dialogbox component
* implement styles for signing dialog
* show SignerPopup when txs are pending
* fix imports
* improve signingPhase logic
* remove console log
---
.../src/api/services/stellar.service.js | 3 +-
src/components/ExchangeRate/index.tsx | 2 +-
src/components/GenericEvent.tsx | 2 +-
src/components/Nabla/useSwapForm.tsx | 8 +-
src/components/SigningBox/index.tsx | 61 +++++++++++++
src/hooks/useMainProcess.ts | 19 ++--
src/pages/progress/index.tsx | 5 +-
src/pages/swap/index.tsx | 88 +++----------------
src/services/evmTransactions.ts | 2 +-
src/services/offrampingFlow.ts | 6 +-
src/services/polkadot/ephemeral.tsx | 2 +-
src/services/squidrouter/process.ts | 23 +++--
tailwind.config.js | 2 +-
13 files changed, 121 insertions(+), 102 deletions(-)
create mode 100644 src/components/SigningBox/index.tsx
diff --git a/signer-service/src/api/services/stellar.service.js b/signer-service/src/api/services/stellar.service.js
index ffe632c1..850aeab2 100644
--- a/signer-service/src/api/services/stellar.service.js
+++ b/signer-service/src/api/services/stellar.service.js
@@ -6,7 +6,6 @@ const {
Networks,
Asset,
Memo,
- Transaction,
Account,
} = require('stellar-sdk');
const { HORIZON_URL, BASE_FEE } = require('../../constants/constants');
@@ -31,7 +30,7 @@ async function buildCreationStellarTx(fundingSecret, ephemeralAccountId, maxTime
const fundingSequence = fundingAccount.sequence;
// add a setOption oeration in order to make this a 2-of-2 multisig account where the
// funding account is a cosigner
- let createAccountTransaction = new TransactionBuilder(fundingAccount, {
+ const createAccountTransaction = new TransactionBuilder(fundingAccount, {
fee: BASE_FEE,
networkPassphrase: NETWORK_PASSPHRASE,
})
diff --git a/src/components/ExchangeRate/index.tsx b/src/components/ExchangeRate/index.tsx
index c3d4d305..188088ff 100644
--- a/src/components/ExchangeRate/index.tsx
+++ b/src/components/ExchangeRate/index.tsx
@@ -1,6 +1,6 @@
+import { FC } from 'preact/compat';
import { InputTokenDetails, OutputTokenDetails } from '../../constants/tokenConfig';
import { UseTokenOutAmountResult } from '../../hooks/nabla/useTokenAmountOut';
-import { FC } from 'preact/compat';
interface ExchangeRateProps {
fromToken?: InputTokenDetails;
diff --git a/src/components/GenericEvent.tsx b/src/components/GenericEvent.tsx
index 1715522d..3883c17e 100644
--- a/src/components/GenericEvent.tsx
+++ b/src/components/GenericEvent.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React from 'preact/compat';
export enum EventStatus {
Success = 'success',
diff --git a/src/components/Nabla/useSwapForm.tsx b/src/components/Nabla/useSwapForm.tsx
index 5696c527..9a666ef8 100644
--- a/src/components/Nabla/useSwapForm.tsx
+++ b/src/components/Nabla/useSwapForm.tsx
@@ -1,13 +1,13 @@
+import { yupResolver } from '@hookform/resolvers/yup';
import Big from 'big.js';
+import { useCallback, useMemo, useState } from 'preact/compat';
import { Resolver, useForm, useWatch } from 'react-hook-form';
-import { useState, useCallback, useMemo } from 'preact/compat';
-import { yupResolver } from '@hookform/resolvers/yup';
-import { INPUT_TOKEN_CONFIG, InputTokenType, OUTPUT_TOKEN_CONFIG, OutputTokenType } from '../../constants/tokenConfig';
import { storageKeys } from '../../constants/localStorage';
+import { INPUT_TOKEN_CONFIG, InputTokenType, OUTPUT_TOKEN_CONFIG, OutputTokenType } from '../../constants/tokenConfig';
import { debounce } from '../../helpers/function';
-import schema, { SwapFormValues } from './schema';
import { storageService } from '../../services/storage/local';
+import schema, { SwapFormValues } from './schema';
interface SwapSettings {
from: string;
diff --git a/src/components/SigningBox/index.tsx b/src/components/SigningBox/index.tsx
new file mode 100644
index 00000000..af37718c
--- /dev/null
+++ b/src/components/SigningBox/index.tsx
@@ -0,0 +1,61 @@
+import AccountBalanceWalletOutlinedIcon from '@mui/icons-material/AccountBalanceWalletOutlined';
+import { Progress } from 'react-daisyui';
+import { FC } from 'preact/compat';
+
+import { SigningPhase } from '../../hooks/useMainProcess';
+
+const progressValues: Record = {
+ started: '25',
+ approved: '50',
+ signed: '75',
+ finished: '100',
+};
+
+function getProgressValue(step: SigningPhase): string {
+ return progressValues[step] || '0';
+}
+
+function getSignatureNumber(step: SigningPhase): string {
+ return step === 'started' ? '1' : '2';
+}
+
+interface SigningBoxProps {
+ step?: SigningPhase;
+}
+
+export const SigningBox: FC = ({ step }) => {
+ if (!step) return <>>;
+ if (step !== 'started' && step !== 'approved' && step !== 'signed') return <>>;
+
+ return (
+
+
+
+
+
+
+
+
Please sign the transaction in
+
your connected wallet to proceed
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/hooks/useMainProcess.ts b/src/hooks/useMainProcess.ts
index 562c7fe6..344d9558 100644
--- a/src/hooks/useMainProcess.ts
+++ b/src/hooks/useMainProcess.ts
@@ -1,4 +1,4 @@
-import { useState, useEffect, useCallback } from 'react';
+import { useState, useEffect, useCallback } from 'preact/compat';
// Configs, Types, constants
import { createStellarEphemeralSecret, sep24First } from '../services/anchor';
@@ -22,6 +22,8 @@ import { EventStatus, GenericEvent } from '../components/GenericEvent';
import Big from 'big.js';
import { createTransactionEvent, useEventsContext } from '../contexts/events';
+export type SigningPhase = 'started' | 'approved' | 'signed' | 'finished';
+
export const useMainProcess = () => {
// EXAMPLE mocking states
@@ -39,12 +41,18 @@ export const useMainProcess = () => {
const [offrampingPhase, setOfframpingPhase] = useState(undefined);
const [sep24Url, setSep24Url] = useState(undefined);
const [sep24Id, setSep24Id] = useState(undefined);
+
+ const [signingPhase, setSigningPhase] = useState(undefined);
+
const wagmiConfig = useConfig();
const { trackEvent } = useEventsContext();
- const [events, setEvents] = useState([]);
+ const [, setEvents] = useState([]);
const updateHookStateFromState = (state: OfframpingState | undefined) => {
+ if (state?.phase === 'success' || state?.phase === 'failure') {
+ setSigningPhase(undefined);
+ }
setOfframpingPhase(state?.phase);
setSep24Id(state?.sep24Id);
@@ -123,7 +131,7 @@ export const useMainProcess = () => {
}
})();
},
- [],
+ [offrampingPhase, offrampingStarted],
);
const finishOfframping = useCallback(() => {
@@ -136,10 +144,10 @@ export const useMainProcess = () => {
useEffect(() => {
(async () => {
- const nextState = await advanceOfframpingState({ renderEvent: addEvent, wagmiConfig });
+ const nextState = await advanceOfframpingState({ renderEvent: addEvent, wagmiConfig, setSigningPhase });
updateHookStateFromState(nextState);
})();
- }, [offrampingPhase]);
+ }, [offrampingPhase, wagmiConfig]);
return {
handleOnSubmit,
@@ -148,5 +156,6 @@ export const useMainProcess = () => {
offrampingStarted,
sep24Id,
finishOfframping,
+ signingPhase,
};
};
diff --git a/src/pages/progress/index.tsx b/src/pages/progress/index.tsx
index 4d226ad2..b3774f39 100644
--- a/src/pages/progress/index.tsx
+++ b/src/pages/progress/index.tsx
@@ -1,5 +1,4 @@
import { useEffect } from 'preact/hooks';
-import { useNavigate } from 'react-router-dom';
import { ExclamationCircleIcon } from '@heroicons/react/20/solid';
import { Box } from '../../components/Box';
import { BaseLayout } from '../../layouts';
@@ -9,15 +8,13 @@ const handleTabClose = (event: Event) => {
};
export const ProgressPage = () => {
- const navigate = useNavigate();
-
useEffect(() => {
window.addEventListener('beforeunload', handleTabClose);
return () => {
window.removeEventListener('beforeunload', handleTabClose);
};
- }, [navigate]);
+ }, []);
const main = (
diff --git a/src/pages/swap/index.tsx b/src/pages/swap/index.tsx
index 08aba742..7309c213 100644
--- a/src/pages/swap/index.tsx
+++ b/src/pages/swap/index.tsx
@@ -1,5 +1,4 @@
import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
-import { useAccount } from 'wagmi';
import Big from 'big.js';
import { ArrowDownIcon } from '@heroicons/react/20/solid';
@@ -13,7 +12,7 @@ import { PoolSelectorModal } from '../../components/InputKeys/SelectionModal';
import { ExchangeRate } from '../../components/ExchangeRate';
import { AssetNumericInput } from '../../components/AssetNumericInput';
import { SwapSubmitButton } from '../../components/buttons/SwapSubmitButton';
-import { BankDetails } from './sections/BankDetails';
+import { SigningBox } from '../../components/SigningBox';
import { config } from '../../config';
import { INPUT_TOKEN_CONFIG, InputTokenType, OUTPUT_TOKEN_CONFIG, OutputTokenType } from '../../constants/tokenConfig';
import { BaseLayout } from '../../layouts';
@@ -31,16 +30,11 @@ const Arrow = () => (
);
export const SwapPage = () => {
- const [isSubmitButtonDisabled, setIsSubmitButtonDisabled] = useState(true);
- const [isExchangeSectionSubmitted, setIsExchangeSectionSubmitted] = useState(false);
- const [isExchangeSectionSubmittedError, setIsExchangeSectionSubmittedError] = useState(false);
const [isQuoteSubmitted, setIsQuoteSubmitted] = useState(false);
const formRef = useRef(null);
const [api, setApi] = useState(null);
- const { isDisconnected } = useAccount();
-
useEffect(() => {
const initializeApiManager = async () => {
const manager = await getApiManagerInstance();
@@ -52,7 +46,8 @@ export const SwapPage = () => {
}, []);
// Main process hook
- const { handleOnSubmit, finishOfframping, offrampingStarted, sep24Url, sep24Id, offrampingPhase } = useMainProcess();
+ const { handleOnSubmit, finishOfframping, offrampingStarted, sep24Url, sep24Id, offrampingPhase, signingPhase } =
+ useMainProcess();
const {
tokensModal: [modalType, setModalType],
@@ -63,19 +58,11 @@ export const SwapPage = () => {
fromAmountString,
from,
to,
- reset,
} = useSwapForm();
const fromToken = from ? INPUT_TOKEN_CONFIG[from] : undefined;
const toToken = to ? OUTPUT_TOKEN_CONFIG[to] : undefined;
- useEffect(() => {
- if (form.formState.isDirty && isExchangeSectionSubmitted && isDisconnected) {
- setIsExchangeSectionSubmitted(false);
- reset();
- }
- }, [form.formState.isDirty, isDisconnected, isExchangeSectionSubmitted, reset]);
-
const tokenOutData = useTokenOutAmount({
wantsSwap: true,
api,
@@ -92,31 +79,10 @@ export const SwapPage = () => {
const inputAmountIsStable =
tokenOutData.actualAmountInRaw !== undefined && BigInt(tokenOutData.actualAmountInRaw) > 0n;
- // Check only the first part of the form (without Bank Details)
- const isFormValidWithoutBankDetails = useMemo(() => {
- const errors = form.formState.errors;
- const noErrors = !errors.from && !errors.to && !errors.fromAmount && !errors.toAmount;
- const isValid =
- Boolean(from) && Boolean(to) && Boolean(fromAmount) && Boolean(tokenOutData.data?.amountOut.preciseString);
-
- return noErrors && isValid;
- }, [form.formState.errors, from, fromAmount, to, tokenOutData.data?.amountOut.preciseString]);
-
function onSubmit(e: Event) {
e.preventDefault();
- if (isSubmitButtonDisabled || !inputAmountIsStable || tokenOutData.actualAmountInRaw === undefined) return;
-
- if (!isExchangeSectionSubmitted) {
- if (isFormValidWithoutBankDetails) {
- setIsExchangeSectionSubmittedError(false);
- setIsExchangeSectionSubmitted(true);
- } else {
- setIsExchangeSectionSubmittedError(true);
- }
-
- return;
- }
+ if (!inputAmountIsStable || tokenOutData.actualAmountInRaw === undefined) return;
if (fromAmount === undefined) {
console.log('Input amount is undefined');
@@ -140,12 +106,6 @@ export const SwapPage = () => {
});
}
- useEffect(() => {
- if (isExchangeSectionSubmitted) {
- formRef.current && formRef.current.scrollIntoView({ behavior: 'smooth', block: 'end' });
- }
- }, [isExchangeSectionSubmitted]);
-
useEffect(() => {
if (tokenOutData.data) {
const toAmount = tokenOutData.data.amountOut.preciseBigDecimal.round(2, 0);
@@ -157,20 +117,6 @@ export const SwapPage = () => {
}
}, [form, tokenOutData.data]);
- // Check if the Submit button should be enabled
- useEffect(() => {
- // Validate only the first part of the form (without Bank Details)
- if (!isExchangeSectionSubmitted && isFormValidWithoutBankDetails) {
- setIsSubmitButtonDisabled(false);
- }
- // Validate the whole form (with Bank Details)
- else if (isExchangeSectionSubmitted /*&& form.formState.isValid*/) {
- setIsSubmitButtonDisabled(false);
- } else {
- setIsSubmitButtonDisabled(true);
- }
- }, [form.formState, form.formState.isValid, isExchangeSectionSubmitted, isFormValidWithoutBankDetails]);
-
const ReceiveNumericInput = useMemo(
() => (
{
);
function getCurrentErrorMessage() {
- if (isExchangeSectionSubmittedError) {
- return 'You must first enter the amount you wish to withdraw.';
- }
-
const amountOut = tokenOutData.data?.amountOut;
if (amountOut !== undefined && toToken !== undefined) {
@@ -242,7 +184,7 @@ export const SwapPage = () => {
setModalType(undefined)}
isLoading={false}
@@ -257,12 +199,13 @@ export const SwapPage = () => {
return ;
}
- if (offrampingPhase !== undefined || offrampingStarted) {
+ if ((offrampingPhase !== undefined || offrampingStarted) && signingPhase === 'finished') {
return ;
}
const main = (
+
diff --git a/src/services/evmTransactions.ts b/src/services/evmTransactions.ts
index 95c744d8..a9ae5e54 100644
--- a/src/services/evmTransactions.ts
+++ b/src/services/evmTransactions.ts
@@ -2,5 +2,5 @@ import { waitForTransactionReceipt } from '@wagmi/core';
import { Config } from 'wagmi';
export async function waitForEvmTransaction(hash: `0x${string}`, wagmiConfig: Config) {
- const result = await waitForTransactionReceipt(wagmiConfig, { hash });
+ await waitForTransactionReceipt(wagmiConfig, { hash });
}
diff --git a/src/services/offrampingFlow.ts b/src/services/offrampingFlow.ts
index f6c9b2c8..50aa408f 100644
--- a/src/services/offrampingFlow.ts
+++ b/src/services/offrampingFlow.ts
@@ -12,6 +12,7 @@ import { executeSpacewalkRedeem } from './polkadot';
import { fetchSigningServiceAccountId } from './signingService';
import { Keypair } from 'stellar-sdk';
import { storageService } from './storage/local';
+import { SigningPhase } from '../hooks/useMainProcess';
export type OfframpingPhase =
| 'squidRouter'
@@ -88,6 +89,7 @@ const STATE_ADVANCEMENT_HANDLERS: Record void;
}
const OFFRAMPING_STATE_LOCAL_STORAGE_KEY = 'offrampingState';
@@ -196,8 +198,8 @@ export async function advanceOfframpingState(context: ExecutionContext): Promise
let newState: OfframpingState | undefined;
try {
newState = await STATE_ADVANCEMENT_HANDLERS[phase](state, context);
- } catch (error) {
- if ((error as any)?.message === 'Wallet not connected') {
+ } catch (error: unknown) {
+ if ((error as Error)?.message === 'Wallet not connected') {
// TODO: transmit error to caller
console.error('Wallet not connected. Try to connect wallet');
return state;
diff --git a/src/services/polkadot/ephemeral.tsx b/src/services/polkadot/ephemeral.tsx
index 4ede7f49..738e5850 100644
--- a/src/services/polkadot/ephemeral.tsx
+++ b/src/services/polkadot/ephemeral.tsx
@@ -1,7 +1,7 @@
import { Keyring } from '@polkadot/api';
import { mnemonicGenerate } from '@polkadot/util-crypto';
import { getApiManagerInstance } from './polkadotApi';
-import { getPendulumCurrencyId, INPUT_TOKEN_CONFIG, OUTPUT_TOKEN_CONFIG } from '../../constants/tokenConfig';
+import { getPendulumCurrencyId, INPUT_TOKEN_CONFIG } from '../../constants/tokenConfig';
import Big from 'big.js';
import { ExecutionContext, OfframpingState } from '../offrampingFlow';
import { waitForEvmTransaction } from '../evmTransactions';
diff --git a/src/services/squidrouter/process.ts b/src/services/squidrouter/process.ts
index 4162973b..71284ca0 100644
--- a/src/services/squidrouter/process.ts
+++ b/src/services/squidrouter/process.ts
@@ -1,14 +1,17 @@
import { writeContract, sendTransaction, getAccount } from '@wagmi/core';
+import { Keyring } from '@polkadot/api';
-import { ExecutionContext, OfframpingState } from '../offrampingFlow';
-import erc20ABI from '../../contracts/ERC20';
import { INPUT_TOKEN_CONFIG } from '../../constants/tokenConfig';
-import { getRouteTransactionRequest } from './route';
-import { waitForEvmTransaction } from '../evmTransactions';
-import { Keyring } from '@polkadot/api';
+import erc20ABI from '../../contracts/ERC20';
import { getApiManagerInstance } from '../polkadot/polkadotApi';
+import { ExecutionContext, OfframpingState } from '../offrampingFlow';
+import { waitForEvmTransaction } from '../evmTransactions';
+import { getRouteTransactionRequest } from './route';
-export async function squidRouter(state: OfframpingState, { wagmiConfig }: ExecutionContext): Promise {
+export async function squidRouter(
+ state: OfframpingState,
+ { wagmiConfig, setSigningPhase }: ExecutionContext,
+): Promise {
const inputToken = INPUT_TOKEN_CONFIG[state.inputTokenType];
const fromTokenErc20Address = inputToken.erc20AddressSourceChain;
@@ -32,6 +35,8 @@ export async function squidRouter(state: OfframpingState, { wagmiConfig }: Execu
console.log('Asking for approval of', transactionRequest?.target, fromTokenErc20Address, state.inputAmount.units);
+ setSigningPhase?.('started');
+
const approvalHash = await writeContract(wagmiConfig, {
abi: erc20ABI,
address: fromTokenErc20Address,
@@ -39,6 +44,8 @@ export async function squidRouter(state: OfframpingState, { wagmiConfig }: Execu
args: [transactionRequest?.target, state.inputAmount.raw],
});
+ setSigningPhase?.('approved');
+
await waitForEvmTransaction(approvalHash, wagmiConfig);
const swapHash = await sendTransaction(wagmiConfig, {
@@ -48,9 +55,13 @@ export async function squidRouter(state: OfframpingState, { wagmiConfig }: Execu
gas: BigInt(transactionRequest.gasLimit) * BigInt(2),
});
+ setSigningPhase?.('signed');
+
const axelarScanLink = 'https://axelarscan.io/gmp/' + swapHash;
console.log(`Squidrouter Swap Initiated! Check Axelarscan for details: ${axelarScanLink}`);
+ setSigningPhase?.('finished');
+
return {
...state,
squidRouterApproveHash: approvalHash,
diff --git a/tailwind.config.js b/tailwind.config.js
index 08c6468f..b88c0dc6 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -53,7 +53,7 @@ module.exports = {
// Undefined colors will be chosen by daisyUI automatically.
{
pendulum: {
- primary: '#907EA0',
+ primary: '#0F4DC0',
'primary-content': '#fff',
secondary: '#F4F5F6',
'secondary-content': '#58667E',