Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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, StandardizedBalance } from '@orb-labs/orby-core';
import { WalletAvatar } from '../../WalletAvatar';
import { WalletDisplayName } from '../../WalletDisplayName';
import { TransactionSimulation } from '../TransactionSimulation';
Expand All @@ -35,6 +37,10 @@ export function TransactionConfirmationView({
customAllowanceValueBase,
onOpenAllowanceForm,
onGasbackReady,
selectedGasToken,
setSelectedGasToken,
operationSet,
fungibleTokens,
}: {
formId: string;
title: React.ReactNode;
Expand All @@ -49,6 +55,10 @@ export function TransactionConfirmationView({
customAllowanceValueBase?: string;
onOpenAllowanceForm?: () => void;
onGasbackReady: null | ((value: number) => void);
selectedGasToken?: GasTokenInput;
setSelectedGasToken?: (gasToken?: GasTokenInput) => void;
operationSet?: OperationSet;
fungibleTokens?: StandardizedBalance[] | undefined;
}) {
const { preferences, query } = usePreferences();

Expand Down Expand Up @@ -127,6 +137,7 @@ export function TransactionConfirmationView({
address={wallet.address}
transaction={transaction}
txInterpretQuery={txInterpretQuery}
operationSet={operationSet}
/>
<Spacer height={20} />
<React.Suspense
Expand Down Expand Up @@ -162,12 +173,17 @@ export function TransactionConfirmationView({
}
: null
}
selectedGasToken={selectedGasToken}
setSelectedGasToken={setSelectedGasToken}
/>
) : txInterpretQuery.data?.action?.transaction.fee ? (
<AddressActionNetworkFee
chain={chain.toString()}
networkFee={txInterpretQuery.data?.action?.transaction.fee}
isLoading={txInterpretQuery.isLoading}
selectedGasToken={selectedGasToken}
selectGasToken={setSelectedGasToken}
fungibleTokens={fungibleTokens}
/>
) : null}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { solFromBase64 } from 'src/modules/solana/transactions/create';
import { createChain } from 'src/modules/networks/Chain';
import { NetworkId } from 'src/modules/networks/NetworkId';
import type { MultichainTransaction } from 'src/shared/types/MultichainTransaction';
import type { OperationSet } from '@orb-labs/orby-core';
import { AddressActionDetails } from '../AddressActionDetails';

export function TransactionSimulation({
Expand All @@ -37,12 +38,14 @@ export function TransactionSimulation({
txInterpretQuery,
customAllowanceValueBase,
onOpenAllowanceForm,
operationSet,
}: {
address: string;
transaction: MultichainTransaction;
txInterpretQuery: ReturnType<typeof useInterpretTxBasedOnEligibility>;
customAllowanceValueBase?: string;
onOpenAllowanceForm?: () => void;
operationSet?: OperationSet;
}) {
const advancedDialogRef = useRef<HTMLDialogElementInterface | null>(null);
const toastRef = useRef<PopoverToastHandle>(null);
Expand Down Expand Up @@ -155,6 +158,7 @@ export function TransactionSimulation({
transaction={transaction}
addressAction={addressAction}
onCopyData={() => toastRef.current?.showToast()}
operationSet={operationSet}
/>
</>
);
Expand Down
194 changes: 184 additions & 10 deletions src/ui/pages/BridgeForm/BridgeForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,21 @@ 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, 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 { useOrbyGetOperationsToSignTransactionOrSignTypedData } from 'src/ui/shared/hooks/useOrbyGetOperationsToSignTransactionOrSignTypedData';
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';
Expand Down Expand Up @@ -414,6 +426,14 @@ function BridgeFormComponent() {
useErrorBoundary: true,
});

const [submitOperationSetIsLoading, setSubmitOperationSetIsLoading] =
useState(false);
const [submitOperationSuccessful, setSubmitOperationSuccessful] =
useState(false);
const [
submitOperationFinalOperationStatus,
setSubmitOperationFinalOperationStatus,
] = useState<OperationStatus | undefined>(undefined);
const [userFormState, setUserFormState] =
useSearchParamsObj<BridgeFormState>();

Expand Down Expand Up @@ -601,16 +621,87 @@ 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<GasTokenInput>({
name: 'Native Token',
standardizedTokenId: undefined,
isDefault: true,
});

const { fungibleTokens } = useGetFungibleTokenPortfolio(undefined, chainId);
const [allowanceBase, setAllowanceBase] = useState<string | null>(null);

useEffect(
() => setAllowanceBase(null),
[inputChain, inputAmount, inputFungibleId]
);

const {
operationSet,
operationSetError,
operationSetLoading,
virtualNode,
aggregateFee,
} = useOrbyGetOperationsToSignTransactionOrSignTypedData(
{
evm: selectedQuote?.transactionSwap?.evm ?? undefined,
solana: selectedQuote?.transactionSwap?.solana ?? undefined,
typedData: undefined,
},
wallet ? wallet : undefined,
isOrbyEnabled,
selectedGasToken,
chainId
);

const selectGasToken = useCallback(
(gasToken?: GasTokenInput) => {
if (gasToken) {
setSelectedGasToken(gasToken);
}
},
[setSelectedGasToken]
);

const {
mutate: sendApproveTransaction,
data: approveHash = null,
Expand Down Expand Up @@ -738,6 +829,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<number | null>(null);
const handleGasbackReady = useCallback((value: number) => {
gasbackValueRef.current = value;
Expand All @@ -756,26 +901,37 @@ 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 (
<SuccessState
hash={result.evm?.hash ?? ensureSolanaResult(result).signature}
hash={hash}
explorer={selectedQuote?.contractMetadata?.explorer ?? null}
inputPosition={inputPosition}
outputPosition={outputPosition}
formState={snapshotRef.current}
gasbackValue={gasbackValueRef.current}
onDone={() => {
sendTransactionMutation.reset();
setSubmitOperationSuccessful(false);
setSubmitOperationFinalOperationStatus(undefined);
setSubmitOperationSetIsLoading(false);
snapshotRef.current = null;
gasbackValueRef.current = null;
navigate('/overview/history');
Expand Down Expand Up @@ -852,6 +1008,9 @@ function BridgeFormComponent() {
},
}}
onGasbackReady={handleGasbackReady}
selectedGasToken={selectedGasToken}
setSelectedGasToken={selectGasToken}
operationSet={operationSet}
/>
</ViewLoadingSuspense>
);
Expand Down Expand Up @@ -934,7 +1093,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');
}
Expand Down Expand Up @@ -1050,6 +1209,11 @@ function BridgeFormComponent() {
onQuoteIdChange={(quoteId) => setUserQuoteId(quoteId)}
/>
{selectedQuote ? <ZerionFeeLine quote={selectedQuote} /> : null}
{operationSetError ? (
<UIText kind="body/regular" color="var(--negative-500)">
{operationSetError}
</UIText>
) : null}
{isEthereumAddress(address) &&
currentTransaction?.evm &&
spendChain ? (
Expand Down Expand Up @@ -1079,6 +1243,8 @@ function BridgeFormComponent() {
setUserFormState((state) => ({ ...state, ...partial }));
}}
gasback={gasbackEstimation}
selectedGasToken={selectedGasToken}
setSelectedGasToken={selectGasToken}
/>
</React.Suspense>
) : null}
Expand All @@ -1087,6 +1253,11 @@ function BridgeFormComponent() {
<NetworkFeeLineInfo
networkFee={selectedQuote.networkFee}
isLoading={quotesData.isPreviousData}
selectedGasToken={selectedGasToken}
selectGasToken={selectGasToken}
aggregateFee={aggregateFee}
operationSet={operationSet}
fungibleTokens={fungibleTokens}
/>
) : null}
</VStack>
Expand Down Expand Up @@ -1194,7 +1365,9 @@ function BridgeFormComponent() {
Boolean(
(selectedQuote && !selectedQuote.transactionSwap) ||
quotesData.error
)
) ||
operationSetLoading ||
submitOperationSetIsLoading
}
holdToSign={false}
>
Expand All @@ -1207,9 +1380,10 @@ function BridgeFormComponent() {
}}
>
{hint ||
(quotesData.isLoading
(quotesData.isLoading || operationSetLoading
? 'Fetching offers'
: sendTransactionMutation.isLoading
: sendTransactionMutation.isLoading ||
submitOperationSetIsLoading
? 'Sending...'
: 'Send')}
</span>
Expand Down
Loading
Loading