From 03d12777e9073bfff792ed52c7115082648961ce Mon Sep 17 00:00:00 2001 From: felimadu Date: Fri, 18 Jul 2025 20:50:20 -0400 Subject: [PATCH 1/3] using orby for in-wallet gas and single-click --- .../TransactionConfirmationView.tsx | 11 + .../TransactionSimulation.tsx | 4 + src/ui/pages/BridgeForm/BridgeForm.tsx | 229 ++++++++++++++++- src/ui/pages/SendForm/SendForm.tsx | 199 ++++++++++++++- .../SendTransactionConfirmation.tsx | 11 + .../pages/SendTransaction/SendTransaction.tsx | 1 - .../TransactionConfiguration.tsx | 27 +- src/ui/pages/SignTypedData/SignTypedData.tsx | 1 - src/ui/pages/SwapForm/SwapForm.tsx | 230 +++++++++++++++++- 9 files changed, 678 insertions(+), 35 deletions(-) diff --git a/src/ui/components/address-action/TransactionConfirmationView/TransactionConfirmationView.tsx b/src/ui/components/address-action/TransactionConfirmationView/TransactionConfirmationView.tsx index 409959e0da..d161d46a1d 100644 --- a/src/ui/components/address-action/TransactionConfirmationView/TransactionConfirmationView.tsx +++ b/src/ui/components/address-action/TransactionConfirmationView/TransactionConfirmationView.tsx @@ -18,6 +18,8 @@ import type { MultichainTransaction } from 'src/shared/types/MultichainTransacti import { SecurityStatusBackground } from 'src/ui/shared/security-check'; import { VStack } from 'src/ui/ui-kit/VStack'; import { AddressActionNetworkFee } from 'src/ui/pages/SendTransaction/TransactionConfiguration/TransactionConfiguration'; +import type { GasTokenInput } from 'src/ui/pages/SendTransaction/NetworkFee/NetworkFee'; +import type { OperationSet } from '@orb-labs/orby-core'; import { WalletAvatar } from '../../WalletAvatar'; import { WalletDisplayName } from '../../WalletDisplayName'; import { TransactionSimulation } from '../TransactionSimulation'; @@ -35,6 +37,9 @@ export function TransactionConfirmationView({ customAllowanceValueBase, onOpenAllowanceForm, onGasbackReady, + selectedGasToken, + setSelectedGasToken, + operationSet, }: { formId: string; title: React.ReactNode; @@ -49,6 +54,9 @@ export function TransactionConfirmationView({ customAllowanceValueBase?: string; onOpenAllowanceForm?: () => void; onGasbackReady: null | ((value: number) => void); + selectedGasToken?: GasTokenInput; + setSelectedGasToken?: (gasToken?: GasTokenInput) => void; + operationSet?: OperationSet; }) { const { preferences, query } = usePreferences(); @@ -127,6 +135,7 @@ export function TransactionConfirmationView({ address={wallet.address} transaction={transaction} txInterpretQuery={txInterpretQuery} + operationSet={operationSet} /> ) : txInterpretQuery.data?.action?.transaction.fee ? ( ; customAllowanceValueBase?: string; onOpenAllowanceForm?: () => void; + operationSet?: OperationSet; }) { const advancedDialogRef = useRef(null); const toastRef = useRef(null); @@ -155,6 +158,7 @@ export function TransactionSimulation({ transaction={transaction} addressAction={addressAction} onCopyData={() => toastRef.current?.showToast()} + operationSet={operationSet} /> ); diff --git a/src/ui/pages/BridgeForm/BridgeForm.tsx b/src/ui/pages/BridgeForm/BridgeForm.tsx index 8e77c58a85..ac32926d71 100644 --- a/src/ui/pages/BridgeForm/BridgeForm.tsx +++ b/src/ui/pages/BridgeForm/BridgeForm.tsx @@ -92,9 +92,25 @@ import { ensureSolanaResult } from 'src/modules/shared/transactions/helpers'; import { isSolanaAddress } from 'src/modules/solana/shared'; import { isEthereumAddress } from 'src/shared/isEthereumAddress'; import { getAddressType } from 'src/shared/wallet/classifiers'; +import { useIsOrbyEnabled } from 'src/shared/core/useIsOrbyEnabled'; +import { + useGetFungibleTokenPortfolio, + useGetOperationsToSignTransactionOrSignTypedData, + useOrby, +} from '@orb-labs/orby-react'; +import type { OperationStatus } from '@orb-labs/orby-core'; +import { OperationStatusType } from '@orb-labs/orby-core'; +import { + signTransaction, + signTypedData, + signUserOperation, +} from 'src/shared/core/orb'; +import _ from 'lodash'; +import { useOperationSetError } from 'src/ui/shared/hooks/useOperationSetError'; import { TransactionConfiguration } from '../SendTransaction/TransactionConfiguration'; import { ApproveHintLine } from '../SwapForm/ApproveHintLine'; import { txErrorToMessage } from '../SendTransaction/shared/transactionErrorToMessage'; +import type { GasTokenInput } from '../SendTransaction/NetworkFee/NetworkFee'; import { getQuotesErrorMessage } from '../SwapForm/Quotes/getQuotesErrorMessage'; import { getPopularTokens } from '../SwapForm/shared/getPopularTokens'; import { usePosition } from '../SwapForm/shared/usePosition'; @@ -414,6 +430,14 @@ function BridgeFormComponent() { useErrorBoundary: true, }); + const [submitOperationSetIsLoading, setSubmitOperationSetIsLoading] = + useState(false); + const [submitOperationSuccessful, setSubmitOperationSuccessful] = + useState(false); + const [ + submitOperationFinalOperationStatus, + setSubmitOperationFinalOperationStatus, + ] = useState(undefined); const [userFormState, setUserFormState] = useSearchParamsObj(); @@ -601,8 +625,59 @@ function BridgeFormComponent() { inputNetwork?.supports_sponsored_transactions, }); - const currentTransaction = - selectedQuote?.transactionApprove || selectedQuote?.transactionSwap || null; + const { accountCluster } = useOrby(); + const { networks } = useNetworks( + inputChain ? [inputChain.toString()] : undefined + ); + + const chain = useMemo(() => { + if (inputChain) { + return createChain(inputChain); + } + return null; + }, [inputChain]); + + const chainId = useMemo(() => { + const id = + inputChain == 'solana' + ? 101 + : chain && networks + ? networks.getChainId(chain) + : null; + + if (id) { + return BigInt(id); + } + return undefined; + }, [chain, inputChain, networks]); + + const isOrbyEnabled = useIsOrbyEnabled(chainId); + + const currentTransaction = useMemo(() => { + if (isOrbyEnabled) { + return selectedQuote?.transactionSwap || null; + } else { + return ( + selectedQuote?.transactionApprove || + selectedQuote?.transactionSwap || + null + ); + } + }, [selectedQuote, isOrbyEnabled]); + + const [selectedGasToken, setSelectedGasToken] = useState({ + name: 'Native Token', + standardizedTokenId: undefined, + isDefault: true, + }); + + const gasToken = useMemo(() => { + return selectedGasToken?.standardizedTokenId + ? { standardizedTokenId: selectedGasToken.standardizedTokenId } + : undefined; + }, [selectedGasToken]); + + const { fungibleTokens } = useGetFungibleTokenPortfolio(undefined, chainId); const [allowanceBase, setAllowanceBase] = useState(null); @@ -611,6 +686,57 @@ function BridgeFormComponent() { [inputChain, inputAmount, inputFungibleId] ); + const orbyParams = useMemo(() => { + if (!isOrbyEnabled) { + return { data: '0x' }; + } else if (selectedQuote?.transactionSwap?.evm) { + return { + data: selectedQuote?.transactionSwap?.evm?.data as string, + to: selectedQuote?.transactionSwap?.evm?.to as string, + value: selectedQuote?.transactionSwap?.evm?.value + ? BigInt(selectedQuote?.transactionSwap?.evm?.value?.toString()) + : undefined, + entrypointAccountAddress: wallet?.address as string, + chainId, + gasToken, + }; + } else if (selectedQuote?.transactionSwap?.solana) { + return { + data: selectedQuote?.transactionSwap?.solana as string, + entrypointAccountAddress: wallet?.address as string, + chainId: BigInt(101), + gasToken, + }; + } else { + return { data: '0x' }; + } + }, [selectedQuote, isOrbyEnabled, chainId, wallet, gasToken]); + + const { + operationSet, + virtualNode, + aggregateFee, + isLoading: OperationSetLoading, + } = useGetOperationsToSignTransactionOrSignTypedData( + orbyParams?.data, + orbyParams?.to, + orbyParams?.value, + orbyParams?.entrypointAccountAddress, + orbyParams?.chainId, + orbyParams.gasToken + ); + + const operationSetError = useOperationSetError(operationSet, isOrbyEnabled); + + const selectGasToken = useCallback( + (gasToken?: GasTokenInput) => { + if (gasToken) { + setSelectedGasToken(gasToken); + } + }, + [setSelectedGasToken] + ); + const { mutate: sendApproveTransaction, data: approveHash = null, @@ -738,6 +864,60 @@ function BridgeFormComponent() { }, }); + const operationStatusesUpdated = useCallback( + async ( + statusSummary: OperationStatusType, + finalTransactionStatus?: OperationStatus, + _statuses?: OperationStatus[] + ) => { + if ( + [OperationStatusType.SUCCESSFUL, OperationStatusType.PENDING].includes( + statusSummary + ) + ) { + setSubmitOperationSetIsLoading(false); + setSubmitOperationSuccessful(true); + setSubmitOperationFinalOperationStatus(finalTransactionStatus); + } + }, + [] + ); + + const submitTransaction = useCallback( + async (interpretationAction: AddressAction | null) => { + if (isOrbyEnabled) { + if (accountCluster && virtualNode && virtualNode) { + setSubmitOperationSetIsLoading(true); + const { operationResponses } = await virtualNode.sendOperationSet( + accountCluster, + operationSet, + signTransaction, + signUserOperation, + signTypedData + ); + + const ids = operationResponses + ?.map((op) => op.id) + .filter((id) => !_.isUndefined(id)); + virtualNode?.subscribeToOperationStatuses( + ids, + operationStatusesUpdated + ); + } + } else { + sendTransaction(interpretationAction); + } + }, + [ + accountCluster, + operationSet, + virtualNode, + operationStatusesUpdated, + isOrbyEnabled, + sendTransaction, + ] + ); + const gasbackValueRef = useRef(null); const handleGasbackReady = useCallback((value: number) => { gasbackValueRef.current = value; @@ -756,19 +936,27 @@ function BridgeFormComponent() { [outputEcosystem] ); - if (sendTransactionMutation.isSuccess) { - const result = sendTransactionMutation.data; + if (sendTransactionMutation.isSuccess || submitOperationSuccessful) { invariant( - inputPosition && outputPosition && result, + inputPosition && + outputPosition && + (sendTransactionMutation.data || submitOperationFinalOperationStatus), 'Missing Form State View values' ); invariant( snapshotRef.current, 'State snapshot must be taken before submit' ); + + const hash: string = + submitOperationFinalOperationStatus?.hash ?? + sendTransactionMutation.data?.evm?.hash ?? + ensureSolanaResult(sendTransactionMutation.data as SignTransactionResult) + .signature; + return ( { sendTransactionMutation.reset(); + setSubmitOperationSuccessful(false); + setSubmitOperationFinalOperationStatus(undefined); + setSubmitOperationSetIsLoading(false); snapshotRef.current = null; gasbackValueRef.current = null; navigate('/overview/history'); @@ -852,6 +1043,9 @@ function BridgeFormComponent() { }, }} onGasbackReady={handleGasbackReady} + selectedGasToken={selectedGasToken} + setSelectedGasToken={selectGasToken} + operationSet={operationSet} /> ); @@ -934,7 +1128,7 @@ function BridgeFormComponent() { if (submitType === 'approve') { sendApproveTransaction(interpretationAction); } else if (submitType === 'bridge') { - sendTransaction(interpretationAction); + submitTransaction(interpretationAction); } else { throw new Error('Must set a submit_type to form'); } @@ -1050,6 +1244,11 @@ function BridgeFormComponent() { onQuoteIdChange={(quoteId) => setUserQuoteId(quoteId)} /> {selectedQuote ? : null} + {operationSetError ? ( + + {operationSetError} + + ) : null} {isEthereumAddress(address) && currentTransaction?.evm && spendChain ? ( @@ -1079,6 +1278,8 @@ function BridgeFormComponent() { setUserFormState((state) => ({ ...state, ...partial })); }} gasback={gasbackEstimation} + selectedGasToken={selectedGasToken} + setSelectedGasToken={selectGasToken} /> ) : null} @@ -1087,6 +1288,11 @@ function BridgeFormComponent() { ) : null} @@ -1194,7 +1400,9 @@ function BridgeFormComponent() { Boolean( (selectedQuote && !selectedQuote.transactionSwap) || quotesData.error - ) + ) || + OperationSetLoading || + submitOperationSetIsLoading } holdToSign={false} > @@ -1207,9 +1415,10 @@ function BridgeFormComponent() { }} > {hint || - (quotesData.isLoading + (quotesData.isLoading || OperationSetLoading ? 'Fetching offers' - : sendTransactionMutation.isLoading + : sendTransactionMutation.isLoading || + submitOperationSetIsLoading ? 'Sending...' : 'Send')} diff --git a/src/ui/pages/SendForm/SendForm.tsx b/src/ui/pages/SendForm/SendForm.tsx index b59a827412..bb8001fe9e 100644 --- a/src/ui/pages/SendForm/SendForm.tsx +++ b/src/ui/pages/SendForm/SendForm.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useId, useMemo, useRef } from 'react'; +import React, { useCallback, useId, useMemo, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { hashQueryKey, useMutation, useQuery } from '@tanstack/react-query'; import type { AddressAction, AddressPosition } from 'defi-sdk'; @@ -30,7 +30,10 @@ import type { HTMLDialogElementInterface } from 'src/ui/ui-kit/ModalDialogs/HTML import { WalletAvatar } from 'src/ui/components/WalletAvatar'; import { NavigationTitle } from 'src/ui/components/NavigationTitle'; import { UnstyledLink } from 'src/ui/ui-kit/UnstyledLink'; -import { useNetworkConfig } from 'src/modules/networks/useNetworks'; +import { + useNetworkConfig, + useNetworks, +} from 'src/modules/networks/useNetworks'; import { getRootDomNode } from 'src/ui/shared/getRootDomNode'; import { usePreferences } from 'src/ui/features/preferences/usePreferences'; import { ViewLoadingSuspense } from 'src/ui/components/ViewLoading/ViewLoading'; @@ -55,10 +58,25 @@ import { Networks } from 'src/modules/networks/Networks'; import { useSearchParamsObj } from 'src/ui/shared/forms/useSearchParamsObj'; import { getDefaultChain } from 'src/ui/shared/forms/trading/getDefaultChain'; import { isMatchForEcosystem } from 'src/shared/wallet/shared'; +import { + useGetOperationsToSignTransactionOrSignTypedData, + useOrby, +} from '@orb-labs/orby-react'; +import type { OperationStatus } from '@orb-labs/orby-core'; +import { OperationStatusType } from '@orb-labs/orby-core'; +import { + signTransaction, + signTypedData, + signUserOperation, +} from 'src/shared/core/orb'; +import { useIsOrbyEnabled } from 'src/shared/core/useIsOrbyEnabled'; +import { useOperationSetError } from 'src/ui/shared/hooks/useOperationSetError'; +import _ from 'lodash'; import { TransactionConfiguration } from '../SendTransaction/TransactionConfiguration'; import { NetworkSelect } from '../Networks/NetworkSelect'; import { txErrorToMessage } from '../SendTransaction/shared/transactionErrorToMessage'; import { NetworkFeeLineInfo } from '../SendTransaction/TransactionConfiguration/TransactionConfiguration'; +import type { GasTokenInput } from '../SendTransaction/NetworkFee/NetworkFee'; import { SuccessState } from './SuccessState'; import { AddressInputWrapper } from './fieldsets/AddressInput'; import { updateRecentAddresses } from './fieldsets/AddressInput/updateRecentAddresses'; @@ -103,6 +121,14 @@ function SendFormComponent() { const { preferences, setPreferences } = usePreferences(); const { innerHeight } = useWindowSizeStore(); + const [submitOperationSetIsLoading, setSubmitOperationSetIsLoading] = + useState(false); + const [submitOperationSuccessful, setSubmitOperationSuccessful] = + useState(false); + const [ + submitOperationFinalOperationStatus, + setSubmitOperationFinalOperationStatus, + ] = useState(undefined); const [userFormState, setUserFormState] = useSearchParamsObj(); const handleChange = useCallback( @@ -169,6 +195,31 @@ function SendFormComponent() { const formId = useId(); const client = useDefiSdkClient(); + const { accountCluster } = useOrby(); + const { networks } = useNetworks(chain ? [chain.toString()] : undefined); + + const chainId = useMemo(() => { + const id = + chain?.value == 'solana' + ? 101 + : chain && networks + ? networks.getChainId(chain) + : null; + + if (id) { + return BigInt(id); + } + return undefined; + }, [chain, networks]); + + const isOrbyEnabled = useIsOrbyEnabled(chainId); + + const [selectedGasToken, setSelectedGasToken] = useState({ + name: 'Native Token', + standardizedTokenId: undefined, + isDefault: true, + }); + const { data: sendData, ...sendDataQuery } = useQuery({ suspense: false, keepPreviousData: true, @@ -275,16 +326,134 @@ function SendFormComponent() { [addressType] ); - if (sendTxMutation.isSuccess) { - const result = sendTxMutation.data; - invariant(result, 'Missing Form State View values'); + const gasToken = useMemo(() => { + return selectedGasToken?.standardizedTokenId + ? { standardizedTokenId: selectedGasToken.standardizedTokenId } + : undefined; + }, [selectedGasToken]); + + const orbyParams = useMemo(() => { + if (!isOrbyEnabled) { + return { data: '0x' }; + } else if (sendData?.transaction?.evm) { + return { + data: sendData?.transaction?.evm?.data as string, + to: sendData?.transaction?.evm?.to as string, + value: sendData?.transaction?.evm?.value + ? BigInt(sendData?.transaction?.evm?.value?.toString()) + : undefined, + entrypointAccountAddress: wallet?.address as string, + chainId, + gasToken, + }; + } else if (sendData?.transaction?.solana) { + return { + data: sendData?.transaction?.solana as string, + entrypointAccountAddress: wallet?.address as string, + chainId: BigInt(101), + gasToken, + }; + } else { + return { data: '0x' }; + } + }, [sendData, isOrbyEnabled, chainId, wallet, gasToken]); + + const { + operationSet, + virtualNode, + isLoading: OperationSetLoading, + } = useGetOperationsToSignTransactionOrSignTypedData( + orbyParams?.data, + orbyParams?.to, + orbyParams?.value, + orbyParams?.entrypointAccountAddress, + orbyParams?.chainId, + orbyParams.gasToken + ); + + const operationSetError = useOperationSetError(operationSet, isOrbyEnabled); + + const selectGasToken = useCallback( + (gasToken?: GasTokenInput) => { + if (gasToken) { + setSelectedGasToken(gasToken); + } + }, + [setSelectedGasToken] + ); + const operationStatusesUpdated = useCallback( + async ( + statusSummary: OperationStatusType, + finalTransactionStatus?: OperationStatus, + _statuses?: OperationStatus[] + ) => { + if ( + [OperationStatusType.SUCCESSFUL, OperationStatusType.PENDING].includes( + statusSummary + ) + ) { + setSubmitOperationSetIsLoading(false); + setSubmitOperationSuccessful(true); + setSubmitOperationFinalOperationStatus(finalTransactionStatus); + } + }, + [] + ); + + const submitTransaction = useCallback( + async (interpretationAction: AddressAction | null) => { + if (isOrbyEnabled) { + if (accountCluster && virtualNode && virtualNode) { + setSubmitOperationSetIsLoading(true); + const { operationResponses } = await virtualNode.sendOperationSet( + accountCluster, + operationSet, + signTransaction, + signUserOperation, + signTypedData + ); + + const ids = operationResponses + ?.map((op) => op.id) + .filter((id) => !_.isUndefined(id)); + virtualNode?.subscribeToOperationStatuses( + ids, + operationStatusesUpdated + ); + } + } else { + sendTxMutation.mutate(interpretationAction); + } + }, + [ + accountCluster, + operationSet, + virtualNode, + operationStatusesUpdated, + isOrbyEnabled, + sendTxMutation, + ] + ); + + if (sendTxMutation.isSuccess || submitOperationSuccessful) { + invariant( + sendTxMutation.data || submitOperationFinalOperationStatus, + 'Missing Form State View values' + ); invariant( snapshotRef.current, 'State snapshot must be taken before submit' ); + + const hash: string = + submitOperationFinalOperationStatus?.hash ?? + sendTxMutation.data?.evm?.hash ?? + ensureSolanaResult(sendTxMutation?.data as SignTransactionResult) + .signature; + return ( { - sendTxMutation.mutate(interpretationAction); + submitTransaction(interpretationAction); }); } }} @@ -488,6 +657,8 @@ function SendFormComponent() { setUserFormState((state) => ({ ...state, ...partial })); }} gasback={gasbackEstimation} + selectedGasToken={selectedGasToken} + setSelectedGasToken={selectGasToken} /> ) : addressType === 'solana' ? ( @@ -525,6 +696,9 @@ function SendFormComponent() { paymasterEligible={paymasterEligible} paymasterPossible={sendData.paymasterPossible} onGasbackReady={handleGasbackReady} + operationSet={operationSet} + selectedGasToken={selectedGasToken} + setSelectedGasToken={selectGasToken} /> @@ -542,12 +716,21 @@ function SendFormComponent() { ? txErrorToMessage(sendTxMutation.error) : null} + {operationSetError ? ( + + {operationSetError} + + ) : null} {wallet ? ( ) : null} diff --git a/src/ui/pages/SendForm/SendTransactionConfirmation/SendTransactionConfirmation.tsx b/src/ui/pages/SendForm/SendTransactionConfirmation/SendTransactionConfirmation.tsx index 36e7e187ce..8a86a28c04 100644 --- a/src/ui/pages/SendForm/SendTransactionConfirmation/SendTransactionConfirmation.tsx +++ b/src/ui/pages/SendForm/SendTransactionConfirmation/SendTransactionConfirmation.tsx @@ -5,8 +5,10 @@ import { walletPort } from 'src/ui/shared/channels'; import { createChain } from 'src/modules/networks/Chain'; import { invariant } from 'src/shared/invariant'; import type { MultichainTransaction } from 'src/shared/types/MultichainTransaction'; +import type { OperationSet } from '@orb-labs/orby-core'; import type { SendFormState } from '../shared/SendFormState'; import { toConfiguration } from '../shared/helpers'; +import type { GasTokenInput } from '../../SendTransaction/NetworkFee/NetworkFee'; export function SendTransactionConfirmation({ formId, @@ -15,6 +17,9 @@ export function SendTransactionConfirmation({ paymasterPossible, onGasbackReady, formState, + operationSet, + selectedGasToken, + setSelectedGasToken, }: { formId: string; transaction: MultichainTransaction; @@ -22,6 +27,9 @@ export function SendTransactionConfirmation({ paymasterEligible: boolean; paymasterPossible: boolean; onGasbackReady: null | ((value: number) => void); + operationSet?: OperationSet; + selectedGasToken?: GasTokenInput; + setSelectedGasToken?: (gasToken?: GasTokenInput) => void; }) { const { data: wallet } = useQuery({ queryKey: ['wallet/uiGetCurrentWallet'], @@ -56,6 +64,9 @@ export function SendTransactionConfirmation({ }, }} onGasbackReady={onGasbackReady} + operationSet={operationSet} + selectedGasToken={selectedGasToken} + setSelectedGasToken={setSelectedGasToken} /> ); } diff --git a/src/ui/pages/SendTransaction/SendTransaction.tsx b/src/ui/pages/SendTransaction/SendTransaction.tsx index fa717ba83d..0dfbc20e35 100644 --- a/src/ui/pages/SendTransaction/SendTransaction.tsx +++ b/src/ui/pages/SendTransaction/SendTransaction.tsx @@ -578,7 +578,6 @@ function SendTransactionContent({ const [allowanceQuantityBase, setAllowanceQuantityBase] = useState(''); const [submitTransactionIsLoading, setSubmitTransactionIsLoading] = useState(false); - const configureTransactionToBeSigned = useEvent( async ( transaction: IncomingTransaction, diff --git a/src/ui/pages/SendTransaction/TransactionConfiguration/TransactionConfiguration.tsx b/src/ui/pages/SendTransaction/TransactionConfiguration/TransactionConfiguration.tsx index 77392426a0..63758b73eb 100644 --- a/src/ui/pages/SendTransaction/TransactionConfiguration/TransactionConfiguration.tsx +++ b/src/ui/pages/SendTransaction/TransactionConfiguration/TransactionConfiguration.tsx @@ -34,7 +34,7 @@ import { baseToCommon } from 'src/shared/units/convert'; import { getDecimals } from 'src/modules/networks/asset'; import { getFungibleAsset } from 'src/modules/ethereum/transactions/actionAsset'; import { useGetFungibleTokenPortfolio } from '@orb-labs/orby-react'; -import type { StandardizedBalance } from '@orb-labs/orby-core'; +import type { OperationSet, StandardizedBalance } from '@orb-labs/orby-core'; import { useIsOrbyEnabled } from 'src/shared/core/useIsOrbyEnabled'; import { useNetworks } from 'src/modules/networks/useNetworks'; import { NetworkFee } from '../NetworkFee'; @@ -88,25 +88,44 @@ export function NetworkFeeLineInfo({ label, networkFee, isLoading, + selectedGasToken, + selectGasToken, + fungibleTokens, + aggregateFee, + operationSet, }: { label?: React.ReactNode; networkFee: NetworkFeeType; isLoading: boolean; + selectedGasToken?: GasTokenInput; + selectGasToken?: (gasToken?: GasTokenInput) => void; + fungibleTokens?: StandardizedBalance[] | undefined; + aggregateFee?: string; + operationSet?: OperationSet; }) { const { currency } = useCurrency(); + return ( {label !== undefined ? ( label ) : ( - Network Fee + Network Felix )} - + {selectedGasToken && operationSet ? ( + + ) : null} {isLoading ? : null} - {networkFee.amount.value != null + {aggregateFee + ? aggregateFee + : networkFee.amount.value != null ? formatCurrencyValueExtra( networkFee.amount.value, 'en', diff --git a/src/ui/pages/SignTypedData/SignTypedData.tsx b/src/ui/pages/SignTypedData/SignTypedData.tsx index f23c0a66d0..04b579f79f 100644 --- a/src/ui/pages/SignTypedData/SignTypedData.tsx +++ b/src/ui/pages/SignTypedData/SignTypedData.tsx @@ -659,7 +659,6 @@ function SignTypedDataContent({ selectedGasToken, chainId ? BigInt(chainId) : undefined ); - const { fungibleTokens } = useGetFungibleTokenPortfolio( undefined, chainId && isOrbyEnabled ? BigInt(chainId) : undefined diff --git a/src/ui/pages/SwapForm/SwapForm.tsx b/src/ui/pages/SwapForm/SwapForm.tsx index 4636fa7583..dccf8a393e 100644 --- a/src/ui/pages/SwapForm/SwapForm.tsx +++ b/src/ui/pages/SwapForm/SwapForm.tsx @@ -22,7 +22,10 @@ import { PageColumn } from 'src/ui/components/PageColumn'; import { WalletAvatar } from 'src/ui/components/WalletAvatar'; import { useAddressParams } from 'src/ui/shared/user-address/useAddressParams'; import { UnstyledLink } from 'src/ui/ui-kit/UnstyledLink'; -import { useNetworkConfig } from 'src/modules/networks/useNetworks'; +import { + useNetworkConfig, + useNetworks, +} from 'src/modules/networks/useNetworks'; import type { Chain } from 'src/modules/networks/Chain'; import { createChain } from 'src/modules/networks/Chain'; import { PageTop } from 'src/ui/components/PageTop'; @@ -92,12 +95,28 @@ import type { SignTransactionResult } from 'src/shared/types/SignTransactionResu import { ensureSolanaResult } from 'src/modules/shared/transactions/helpers'; import { isMatchForEcosystem } from 'src/shared/wallet/shared'; import { Networks } from 'src/modules/networks/Networks'; +import { useIsOrbyEnabled } from 'src/shared/core/useIsOrbyEnabled'; +import { + useGetFungibleTokenPortfolio, + useGetOperationsToSignTransactionOrSignTypedData, + useOrby, +} from '@orb-labs/orby-react'; +import type { OperationStatus } from '@orb-labs/orby-core'; +import { OperationStatusType } from '@orb-labs/orby-core'; +import { + signTransaction, + signTypedData, + signUserOperation, +} from 'src/shared/core/orb'; +import _ from 'lodash'; +import { useOperationSetError } from 'src/ui/shared/hooks/useOperationSetError'; import { NetworkSelect } from '../Networks/NetworkSelect'; import { TransactionConfiguration } from '../SendTransaction/TransactionConfiguration'; import { txErrorToMessage } from '../SendTransaction/shared/transactionErrorToMessage'; import { fromConfiguration, toConfiguration } from '../SendForm/shared/helpers'; import { NetworkFeeLineInfo } from '../SendTransaction/TransactionConfiguration/TransactionConfiguration'; import { TransactionWarning } from '../SendTransaction/TransactionWarnings/TransactionWarning'; +import { type GasTokenInput } from '../SendTransaction/NetworkFee/NetworkFee'; import { RateLine } from './Quotes'; import * as styles from './styles.module.css'; import { ApproveHintLine } from './ApproveHintLine'; @@ -314,6 +333,14 @@ function SwapFormComponent() { useErrorBoundary: true, }); + const [submitOperationSetIsLoading, setSubmitOperationSetIsLoading] = + useState(false); + const [submitOperationSuccessful, setSubmitOperationSuccessful] = + useState(false); + const [ + submitOperationFinalOperationStatus, + setSubmitOperationFinalOperationStatus, + ] = useState(undefined); const [userFormState, setUserFormState] = useSearchParamsObj(); const refetchInterval = usePositionsRefetchInterval(20000); @@ -453,8 +480,109 @@ function SwapFormComponent() { supportsSponsoredTransactions: network?.supports_sponsored_transactions, }); - const currentTransaction = - selectedQuote?.transactionApprove || selectedQuote?.transactionSwap || null; + const { accountCluster } = useOrby(); + const { networks } = useNetworks( + inputChain ? [inputChain.toString()] : undefined + ); + + const chain = useMemo(() => { + if (inputChain) { + return createChain(inputChain); + } + return null; + }, [inputChain]); + + const chainId = useMemo(() => { + const id = + inputChain == 'solana' + ? 101 + : chain && networks + ? networks.getChainId(chain) + : null; + + if (id) { + return BigInt(id); + } + return undefined; + }, [chain, inputChain, networks]); + + const isOrbyEnabled = useIsOrbyEnabled(chainId); + const { fungibleTokens } = useGetFungibleTokenPortfolio(undefined, chainId); + + const currentTransaction = useMemo(() => { + if (isOrbyEnabled) { + return selectedQuote?.transactionSwap || null; + } else { + return ( + selectedQuote?.transactionApprove || + selectedQuote?.transactionSwap || + null + ); + } + }, [selectedQuote, isOrbyEnabled]); + + const [selectedGasToken, setSelectedGasToken] = useState({ + name: 'Native Token', + standardizedTokenId: undefined, + isDefault: true, + }); + + const gasToken = useMemo(() => { + return selectedGasToken?.standardizedTokenId + ? { standardizedTokenId: selectedGasToken.standardizedTokenId } + : undefined; + }, [selectedGasToken]); + + const orbyParams = useMemo(() => { + if (!isOrbyEnabled) { + return { data: '0x' }; + } else if (selectedQuote?.transactionSwap?.evm) { + return { + data: selectedQuote?.transactionSwap?.evm?.data as string, + to: selectedQuote?.transactionSwap?.evm?.to as string, + value: selectedQuote?.transactionSwap?.evm?.value + ? BigInt(selectedQuote?.transactionSwap?.evm?.value?.toString()) + : undefined, + entrypointAccountAddress: wallet?.address as string, + chainId, + gasToken, + }; + } else if (selectedQuote?.transactionSwap?.solana) { + return { + data: selectedQuote?.transactionSwap?.solana as string, + entrypointAccountAddress: wallet?.address as string, + chainId: BigInt(101), + gasToken, + }; + } else { + return { data: '0x' }; + } + }, [selectedQuote, isOrbyEnabled, chainId, wallet, gasToken]); + + const { + operationSet, + virtualNode, + aggregateFee, + isLoading: OperationSetLoading, + } = useGetOperationsToSignTransactionOrSignTypedData( + orbyParams?.data, + orbyParams?.to, + orbyParams?.value, + orbyParams?.entrypointAccountAddress, + orbyParams?.chainId, + orbyParams.gasToken + ); + + const operationSetError = useOperationSetError(operationSet, isOrbyEnabled); + + const selectGasToken = useCallback( + (gasToken?: GasTokenInput) => { + if (gasToken) { + setSelectedGasToken(gasToken); + } + }, + [setSelectedGasToken] + ); const [allowanceBase, setAllowanceBase] = useState(null); @@ -587,6 +715,60 @@ function SwapFormComponent() { }, }); + const operationStatusesUpdated = useCallback( + async ( + statusSummary: OperationStatusType, + finalTransactionStatus?: OperationStatus, + _statuses?: OperationStatus[] + ) => { + if ( + [OperationStatusType.SUCCESSFUL, OperationStatusType.PENDING].includes( + statusSummary + ) + ) { + setSubmitOperationSetIsLoading(false); + setSubmitOperationSuccessful(true); + setSubmitOperationFinalOperationStatus(finalTransactionStatus); + } + }, + [] + ); + + const submitTransaction = useCallback( + async (interpretationAction: AddressAction | null) => { + if (isOrbyEnabled) { + if (accountCluster && virtualNode && virtualNode) { + setSubmitOperationSetIsLoading(true); + const { operationResponses } = await virtualNode.sendOperationSet( + accountCluster, + operationSet, + signTransaction, + signUserOperation, + signTypedData + ); + + const ids = operationResponses + ?.map((op) => op.id) + .filter((id) => !_.isUndefined(id)); + virtualNode?.subscribeToOperationStatuses( + ids, + operationStatusesUpdated + ); + } + } else { + sendTransaction(interpretationAction); + } + }, + [ + accountCluster, + operationSet, + virtualNode, + operationStatusesUpdated, + isOrbyEnabled, + sendTransaction, + ] + ); + const outputAmount = selectedQuote?.outputAmount.quantity || null; const priceImpact = useMemo(() => { @@ -605,19 +787,27 @@ function SwapFormComponent() { const navigate = useNavigate(); - if (sendTransactionMutation.isSuccess) { - const result = sendTransactionMutation.data; + if (sendTransactionMutation.isSuccess || submitOperationSuccessful) { invariant( - inputPosition && outputPosition && result, + inputPosition && + outputPosition && + (sendTransactionMutation.data || submitOperationFinalOperationStatus), 'Missing Form State View values' ); invariant( snapshotRef.current, 'State snapshot must be taken before submit' ); + + const hash: string = + submitOperationFinalOperationStatus?.hash ?? + sendTransactionMutation.data?.evm?.hash ?? + ensureSolanaResult(sendTransactionMutation.data as SignTransactionResult) + .signature; + return ( ); @@ -819,7 +1012,7 @@ function SwapFormComponent() { if (submitType === 'approve') { sendApproveTransaction(interpretationAction); } else if (submitType === 'swap') { - sendTransaction(interpretationAction); + submitTransaction(interpretationAction); } else { throw new Error('Must set a submit_type to form'); } @@ -939,6 +1132,8 @@ function SwapFormComponent() { setUserFormState((state) => ({ ...state, ...partial })); }} gasback={gasbackEstimation} + selectedGasToken={selectedGasToken} + setSelectedGasToken={selectGasToken} /> ) : null} @@ -947,6 +1142,11 @@ function SwapFormComponent() { ) : null} {selectedQuote?.protocolFee.percentage === 0 ? ( @@ -964,6 +1164,11 @@ function SwapFormComponent() { message={selectedQuote?.error?.message} /> ) : null} + {operationSetError ? ( + + {operationSetError} + + ) : null} {quotesData.done && priceImpact && !isApproveMode ? ( ) : null} @@ -1066,7 +1271,9 @@ function SwapFormComponent() { Boolean( (selectedQuote && !selectedQuote.transactionSwap) || quotesData.error - ) + ) || + OperationSetLoading || + submitOperationSetIsLoading } holdToSign={false} > @@ -1079,9 +1286,10 @@ function SwapFormComponent() { }} > {hint || - (quotesData.isLoading + (quotesData.isLoading || OperationSetLoading ? 'Fetching offers' - : sendTransactionMutation.isLoading + : sendTransactionMutation.isLoading || + submitOperationSetIsLoading ? 'Sending...' : 'Swap')} From a92c2adc86afb2d2e412b4ed84708df8ac6d2daa Mon Sep 17 00:00:00 2001 From: felimadu Date: Tue, 22 Jul 2025 23:26:23 -0400 Subject: [PATCH 2/3] refactoring --- .../TransactionConfirmationView.tsx | 7 +- src/ui/pages/BridgeForm/BridgeForm.tsx | 67 +++++------------ src/ui/pages/SendForm/SendForm.tsx | 71 ++++++------------- .../SendTransactionConfirmation.tsx | 5 +- src/ui/pages/SwapForm/SwapForm.tsx | 66 +++++------------ ...rationsToSignTransactionOrSignTypedData.ts | 3 +- 6 files changed, 67 insertions(+), 152 deletions(-) diff --git a/src/ui/components/address-action/TransactionConfirmationView/TransactionConfirmationView.tsx b/src/ui/components/address-action/TransactionConfirmationView/TransactionConfirmationView.tsx index d161d46a1d..dc4273a426 100644 --- a/src/ui/components/address-action/TransactionConfirmationView/TransactionConfirmationView.tsx +++ b/src/ui/components/address-action/TransactionConfirmationView/TransactionConfirmationView.tsx @@ -19,7 +19,7 @@ import { SecurityStatusBackground } from 'src/ui/shared/security-check'; import { VStack } from 'src/ui/ui-kit/VStack'; import { AddressActionNetworkFee } from 'src/ui/pages/SendTransaction/TransactionConfiguration/TransactionConfiguration'; import type { GasTokenInput } from 'src/ui/pages/SendTransaction/NetworkFee/NetworkFee'; -import type { OperationSet } from '@orb-labs/orby-core'; +import type { OperationSet, StandardizedBalance } from '@orb-labs/orby-core'; import { WalletAvatar } from '../../WalletAvatar'; import { WalletDisplayName } from '../../WalletDisplayName'; import { TransactionSimulation } from '../TransactionSimulation'; @@ -40,6 +40,7 @@ export function TransactionConfirmationView({ selectedGasToken, setSelectedGasToken, operationSet, + fungibleTokens, }: { formId: string; title: React.ReactNode; @@ -57,6 +58,7 @@ export function TransactionConfirmationView({ selectedGasToken?: GasTokenInput; setSelectedGasToken?: (gasToken?: GasTokenInput) => void; operationSet?: OperationSet; + fungibleTokens?: StandardizedBalance[] | undefined; }) { const { preferences, query } = usePreferences(); @@ -179,6 +181,9 @@ export function TransactionConfirmationView({ chain={chain.toString()} networkFee={txInterpretQuery.data?.action?.transaction.fee} isLoading={txInterpretQuery.isLoading} + selectedGasToken={selectedGasToken} + selectGasToken={setSelectedGasToken} + fungibleTokens={fungibleTokens} /> ) : null} diff --git a/src/ui/pages/BridgeForm/BridgeForm.tsx b/src/ui/pages/BridgeForm/BridgeForm.tsx index ac32926d71..9cd2f50a84 100644 --- a/src/ui/pages/BridgeForm/BridgeForm.tsx +++ b/src/ui/pages/BridgeForm/BridgeForm.tsx @@ -93,11 +93,7 @@ import { isSolanaAddress } from 'src/modules/solana/shared'; import { isEthereumAddress } from 'src/shared/isEthereumAddress'; import { getAddressType } from 'src/shared/wallet/classifiers'; import { useIsOrbyEnabled } from 'src/shared/core/useIsOrbyEnabled'; -import { - useGetFungibleTokenPortfolio, - useGetOperationsToSignTransactionOrSignTypedData, - useOrby, -} from '@orb-labs/orby-react'; +import { useGetFungibleTokenPortfolio, useOrby } from '@orb-labs/orby-react'; import type { OperationStatus } from '@orb-labs/orby-core'; import { OperationStatusType } from '@orb-labs/orby-core'; import { @@ -106,7 +102,7 @@ import { signUserOperation, } from 'src/shared/core/orb'; import _ from 'lodash'; -import { useOperationSetError } from 'src/ui/shared/hooks/useOperationSetError'; +import { useOrbyGetOperationsToSignTransactionOrSignTypedData } from 'src/ui/shared/hooks/useOrbyGetOperationsToSignTransactionOrSignTypedData'; import { TransactionConfiguration } from '../SendTransaction/TransactionConfiguration'; import { ApproveHintLine } from '../SwapForm/ApproveHintLine'; import { txErrorToMessage } from '../SendTransaction/shared/transactionErrorToMessage'; @@ -671,14 +667,7 @@ function BridgeFormComponent() { isDefault: true, }); - const gasToken = useMemo(() => { - return selectedGasToken?.standardizedTokenId - ? { standardizedTokenId: selectedGasToken.standardizedTokenId } - : undefined; - }, [selectedGasToken]); - const { fungibleTokens } = useGetFungibleTokenPortfolio(undefined, chainId); - const [allowanceBase, setAllowanceBase] = useState(null); useEffect( @@ -686,48 +675,24 @@ function BridgeFormComponent() { [inputChain, inputAmount, inputFungibleId] ); - const orbyParams = useMemo(() => { - if (!isOrbyEnabled) { - return { data: '0x' }; - } else if (selectedQuote?.transactionSwap?.evm) { - return { - data: selectedQuote?.transactionSwap?.evm?.data as string, - to: selectedQuote?.transactionSwap?.evm?.to as string, - value: selectedQuote?.transactionSwap?.evm?.value - ? BigInt(selectedQuote?.transactionSwap?.evm?.value?.toString()) - : undefined, - entrypointAccountAddress: wallet?.address as string, - chainId, - gasToken, - }; - } else if (selectedQuote?.transactionSwap?.solana) { - return { - data: selectedQuote?.transactionSwap?.solana as string, - entrypointAccountAddress: wallet?.address as string, - chainId: BigInt(101), - gasToken, - }; - } else { - return { data: '0x' }; - } - }, [selectedQuote, isOrbyEnabled, chainId, wallet, gasToken]); - const { operationSet, + operationSetError, + operationSetLoading, virtualNode, aggregateFee, - isLoading: OperationSetLoading, - } = useGetOperationsToSignTransactionOrSignTypedData( - orbyParams?.data, - orbyParams?.to, - orbyParams?.value, - orbyParams?.entrypointAccountAddress, - orbyParams?.chainId, - orbyParams.gasToken + } = useOrbyGetOperationsToSignTransactionOrSignTypedData( + { + evm: selectedQuote?.transactionSwap?.evm ?? undefined, + solana: selectedQuote?.transactionSwap?.solana ?? undefined, + typedData: undefined, + }, + wallet ? wallet : undefined, + isOrbyEnabled, + selectedGasToken, + chainId ); - const operationSetError = useOperationSetError(operationSet, isOrbyEnabled); - const selectGasToken = useCallback( (gasToken?: GasTokenInput) => { if (gasToken) { @@ -1401,7 +1366,7 @@ function BridgeFormComponent() { (selectedQuote && !selectedQuote.transactionSwap) || quotesData.error ) || - OperationSetLoading || + operationSetLoading || submitOperationSetIsLoading } holdToSign={false} @@ -1415,7 +1380,7 @@ function BridgeFormComponent() { }} > {hint || - (quotesData.isLoading || OperationSetLoading + (quotesData.isLoading || operationSetLoading ? 'Fetching offers' : sendTransactionMutation.isLoading || submitOperationSetIsLoading diff --git a/src/ui/pages/SendForm/SendForm.tsx b/src/ui/pages/SendForm/SendForm.tsx index bb8001fe9e..e98da7770d 100644 --- a/src/ui/pages/SendForm/SendForm.tsx +++ b/src/ui/pages/SendForm/SendForm.tsx @@ -58,10 +58,7 @@ import { Networks } from 'src/modules/networks/Networks'; import { useSearchParamsObj } from 'src/ui/shared/forms/useSearchParamsObj'; import { getDefaultChain } from 'src/ui/shared/forms/trading/getDefaultChain'; import { isMatchForEcosystem } from 'src/shared/wallet/shared'; -import { - useGetOperationsToSignTransactionOrSignTypedData, - useOrby, -} from '@orb-labs/orby-react'; +import { useGetFungibleTokenPortfolio, useOrby } from '@orb-labs/orby-react'; import type { OperationStatus } from '@orb-labs/orby-core'; import { OperationStatusType } from '@orb-labs/orby-core'; import { @@ -70,8 +67,8 @@ import { signUserOperation, } from 'src/shared/core/orb'; import { useIsOrbyEnabled } from 'src/shared/core/useIsOrbyEnabled'; -import { useOperationSetError } from 'src/ui/shared/hooks/useOperationSetError'; import _ from 'lodash'; +import { useOrbyGetOperationsToSignTransactionOrSignTypedData } from 'src/ui/shared/hooks/useOrbyGetOperationsToSignTransactionOrSignTypedData'; import { TransactionConfiguration } from '../SendTransaction/TransactionConfiguration'; import { NetworkSelect } from '../Networks/NetworkSelect'; import { txErrorToMessage } from '../SendTransaction/shared/transactionErrorToMessage'; @@ -213,6 +210,7 @@ function SendFormComponent() { }, [chain, networks]); const isOrbyEnabled = useIsOrbyEnabled(chainId); + const { fungibleTokens } = useGetFungibleTokenPortfolio(undefined, chainId); const [selectedGasToken, setSelectedGasToken] = useState({ name: 'Native Token', @@ -326,53 +324,24 @@ function SendFormComponent() { [addressType] ); - const gasToken = useMemo(() => { - return selectedGasToken?.standardizedTokenId - ? { standardizedTokenId: selectedGasToken.standardizedTokenId } - : undefined; - }, [selectedGasToken]); - - const orbyParams = useMemo(() => { - if (!isOrbyEnabled) { - return { data: '0x' }; - } else if (sendData?.transaction?.evm) { - return { - data: sendData?.transaction?.evm?.data as string, - to: sendData?.transaction?.evm?.to as string, - value: sendData?.transaction?.evm?.value - ? BigInt(sendData?.transaction?.evm?.value?.toString()) - : undefined, - entrypointAccountAddress: wallet?.address as string, - chainId, - gasToken, - }; - } else if (sendData?.transaction?.solana) { - return { - data: sendData?.transaction?.solana as string, - entrypointAccountAddress: wallet?.address as string, - chainId: BigInt(101), - gasToken, - }; - } else { - return { data: '0x' }; - } - }, [sendData, isOrbyEnabled, chainId, wallet, gasToken]); - const { operationSet, + operationSetError, + operationSetLoading, virtualNode, - isLoading: OperationSetLoading, - } = useGetOperationsToSignTransactionOrSignTypedData( - orbyParams?.data, - orbyParams?.to, - orbyParams?.value, - orbyParams?.entrypointAccountAddress, - orbyParams?.chainId, - orbyParams.gasToken + aggregateFee, + } = useOrbyGetOperationsToSignTransactionOrSignTypedData( + { + evm: sendData?.transaction?.evm, + solana: sendData?.transaction?.solana, + typedData: undefined, + }, + wallet ? wallet : undefined, + isOrbyEnabled, + selectedGasToken, + chainId ); - const operationSetError = useOperationSetError(operationSet, isOrbyEnabled); - const selectGasToken = useCallback( (gasToken?: GasTokenInput) => { if (gasToken) { @@ -669,6 +638,11 @@ function SendFormComponent() { isLoading={ sendDataQuery.isFetching || sendDataQuery.isPreviousData } + selectedGasToken={selectedGasToken} + selectGasToken={selectGasToken} + aggregateFee={aggregateFee} + operationSet={operationSet} + fungibleTokens={fungibleTokens} /> ) : sendDataQuery.isLoading ? (
@@ -699,6 +673,7 @@ function SendFormComponent() { operationSet={operationSet} selectedGasToken={selectedGasToken} setSelectedGasToken={selectGasToken} + fungibleTokens={fungibleTokens} /> @@ -729,7 +704,7 @@ function SendFormComponent() { disabled={ sendTxMutation.isLoading || submitOperationSetIsLoading || - OperationSetLoading + operationSetLoading } holdToSign={false} /> diff --git a/src/ui/pages/SendForm/SendTransactionConfirmation/SendTransactionConfirmation.tsx b/src/ui/pages/SendForm/SendTransactionConfirmation/SendTransactionConfirmation.tsx index 8a86a28c04..da0b0a4d22 100644 --- a/src/ui/pages/SendForm/SendTransactionConfirmation/SendTransactionConfirmation.tsx +++ b/src/ui/pages/SendForm/SendTransactionConfirmation/SendTransactionConfirmation.tsx @@ -5,7 +5,7 @@ import { walletPort } from 'src/ui/shared/channels'; import { createChain } from 'src/modules/networks/Chain'; import { invariant } from 'src/shared/invariant'; import type { MultichainTransaction } from 'src/shared/types/MultichainTransaction'; -import type { OperationSet } from '@orb-labs/orby-core'; +import type { OperationSet, StandardizedBalance } from '@orb-labs/orby-core'; import type { SendFormState } from '../shared/SendFormState'; import { toConfiguration } from '../shared/helpers'; import type { GasTokenInput } from '../../SendTransaction/NetworkFee/NetworkFee'; @@ -20,6 +20,7 @@ export function SendTransactionConfirmation({ operationSet, selectedGasToken, setSelectedGasToken, + fungibleTokens, }: { formId: string; transaction: MultichainTransaction; @@ -30,6 +31,7 @@ export function SendTransactionConfirmation({ operationSet?: OperationSet; selectedGasToken?: GasTokenInput; setSelectedGasToken?: (gasToken?: GasTokenInput) => void; + fungibleTokens?: StandardizedBalance[] | undefined; }) { const { data: wallet } = useQuery({ queryKey: ['wallet/uiGetCurrentWallet'], @@ -67,6 +69,7 @@ export function SendTransactionConfirmation({ operationSet={operationSet} selectedGasToken={selectedGasToken} setSelectedGasToken={setSelectedGasToken} + fungibleTokens={fungibleTokens} /> ); } diff --git a/src/ui/pages/SwapForm/SwapForm.tsx b/src/ui/pages/SwapForm/SwapForm.tsx index dccf8a393e..4dbf0eedf0 100644 --- a/src/ui/pages/SwapForm/SwapForm.tsx +++ b/src/ui/pages/SwapForm/SwapForm.tsx @@ -96,11 +96,7 @@ import { ensureSolanaResult } from 'src/modules/shared/transactions/helpers'; import { isMatchForEcosystem } from 'src/shared/wallet/shared'; import { Networks } from 'src/modules/networks/Networks'; import { useIsOrbyEnabled } from 'src/shared/core/useIsOrbyEnabled'; -import { - useGetFungibleTokenPortfolio, - useGetOperationsToSignTransactionOrSignTypedData, - useOrby, -} from '@orb-labs/orby-react'; +import { useGetFungibleTokenPortfolio, useOrby } from '@orb-labs/orby-react'; import type { OperationStatus } from '@orb-labs/orby-core'; import { OperationStatusType } from '@orb-labs/orby-core'; import { @@ -109,7 +105,7 @@ import { signUserOperation, } from 'src/shared/core/orb'; import _ from 'lodash'; -import { useOperationSetError } from 'src/ui/shared/hooks/useOperationSetError'; +import { useOrbyGetOperationsToSignTransactionOrSignTypedData } from 'src/ui/shared/hooks/useOrbyGetOperationsToSignTransactionOrSignTypedData'; import { NetworkSelect } from '../Networks/NetworkSelect'; import { TransactionConfiguration } from '../SendTransaction/TransactionConfiguration'; import { txErrorToMessage } from '../SendTransaction/shared/transactionErrorToMessage'; @@ -527,54 +523,24 @@ function SwapFormComponent() { isDefault: true, }); - const gasToken = useMemo(() => { - return selectedGasToken?.standardizedTokenId - ? { standardizedTokenId: selectedGasToken.standardizedTokenId } - : undefined; - }, [selectedGasToken]); - - const orbyParams = useMemo(() => { - if (!isOrbyEnabled) { - return { data: '0x' }; - } else if (selectedQuote?.transactionSwap?.evm) { - return { - data: selectedQuote?.transactionSwap?.evm?.data as string, - to: selectedQuote?.transactionSwap?.evm?.to as string, - value: selectedQuote?.transactionSwap?.evm?.value - ? BigInt(selectedQuote?.transactionSwap?.evm?.value?.toString()) - : undefined, - entrypointAccountAddress: wallet?.address as string, - chainId, - gasToken, - }; - } else if (selectedQuote?.transactionSwap?.solana) { - return { - data: selectedQuote?.transactionSwap?.solana as string, - entrypointAccountAddress: wallet?.address as string, - chainId: BigInt(101), - gasToken, - }; - } else { - return { data: '0x' }; - } - }, [selectedQuote, isOrbyEnabled, chainId, wallet, gasToken]); - const { operationSet, + operationSetError, + operationSetLoading, virtualNode, aggregateFee, - isLoading: OperationSetLoading, - } = useGetOperationsToSignTransactionOrSignTypedData( - orbyParams?.data, - orbyParams?.to, - orbyParams?.value, - orbyParams?.entrypointAccountAddress, - orbyParams?.chainId, - orbyParams.gasToken + } = useOrbyGetOperationsToSignTransactionOrSignTypedData( + { + evm: selectedQuote?.transactionSwap?.evm ?? undefined, + solana: selectedQuote?.transactionSwap?.solana ?? undefined, + typedData: undefined, + }, + wallet ? wallet : undefined, + isOrbyEnabled, + selectedGasToken, + chainId ); - const operationSetError = useOperationSetError(operationSet, isOrbyEnabled); - const selectGasToken = useCallback( (gasToken?: GasTokenInput) => { if (gasToken) { @@ -1272,7 +1238,7 @@ function SwapFormComponent() { (selectedQuote && !selectedQuote.transactionSwap) || quotesData.error ) || - OperationSetLoading || + operationSetLoading || submitOperationSetIsLoading } holdToSign={false} @@ -1286,7 +1252,7 @@ function SwapFormComponent() { }} > {hint || - (quotesData.isLoading || OperationSetLoading + (quotesData.isLoading || operationSetLoading ? 'Fetching offers' : sendTransactionMutation.isLoading || submitOperationSetIsLoading diff --git a/src/ui/shared/hooks/useOrbyGetOperationsToSignTransactionOrSignTypedData.ts b/src/ui/shared/hooks/useOrbyGetOperationsToSignTransactionOrSignTypedData.ts index b4e3c7477b..5af1b22da4 100644 --- a/src/ui/shared/hooks/useOrbyGetOperationsToSignTransactionOrSignTypedData.ts +++ b/src/ui/shared/hooks/useOrbyGetOperationsToSignTransactionOrSignTypedData.ts @@ -3,11 +3,12 @@ import type { IncomingTransactionWithChainId } from 'src/modules/ethereum/types/ import { useGetOperationsToSignTransactionOrSignTypedData } from '@orb-labs/orby-react'; import type { ExternallyOwnedAccount } from 'src/shared/types/ExternallyOwnedAccount'; import type { GasTokenInput } from 'src/ui/pages/SendTransaction/NetworkFee/NetworkFee'; +import type { TransactionEVM } from 'src/shared/types/Quote'; import { useOperationSetError } from './useOperationSetError'; export function useOrbyGetOperationsToSignTransactionOrSignTypedData( transaction: { - evm?: IncomingTransactionWithChainId | undefined; + evm?: IncomingTransactionWithChainId | TransactionEVM | undefined; solana?: string | undefined; typedData?: string | undefined; }, From b3a70d895d66f57e6b0e24e0740373112eb80941 Mon Sep 17 00:00:00 2001 From: felimadu Date: Tue, 22 Jul 2025 23:30:41 -0400 Subject: [PATCH 3/3] removing unneeded changes --- src/ui/pages/SendTransaction/SendTransaction.tsx | 1 + src/ui/pages/SignTypedData/SignTypedData.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/ui/pages/SendTransaction/SendTransaction.tsx b/src/ui/pages/SendTransaction/SendTransaction.tsx index 0dfbc20e35..fa717ba83d 100644 --- a/src/ui/pages/SendTransaction/SendTransaction.tsx +++ b/src/ui/pages/SendTransaction/SendTransaction.tsx @@ -578,6 +578,7 @@ function SendTransactionContent({ const [allowanceQuantityBase, setAllowanceQuantityBase] = useState(''); const [submitTransactionIsLoading, setSubmitTransactionIsLoading] = useState(false); + const configureTransactionToBeSigned = useEvent( async ( transaction: IncomingTransaction, diff --git a/src/ui/pages/SignTypedData/SignTypedData.tsx b/src/ui/pages/SignTypedData/SignTypedData.tsx index 04b579f79f..f23c0a66d0 100644 --- a/src/ui/pages/SignTypedData/SignTypedData.tsx +++ b/src/ui/pages/SignTypedData/SignTypedData.tsx @@ -659,6 +659,7 @@ function SignTypedDataContent({ selectedGasToken, chainId ? BigInt(chainId) : undefined ); + const { fungibleTokens } = useGetFungibleTokenPortfolio( undefined, chainId && isOrbyEnabled ? BigInt(chainId) : undefined