diff --git a/src/app/common/transactions/stacks/broadcast-transaction.ts b/src/app/common/transactions/stacks/broadcast-transaction.ts deleted file mode 100644 index 33550cff45e..00000000000 --- a/src/app/common/transactions/stacks/broadcast-transaction.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { broadcastRawTransaction } from '@stacks/transactions'; - -import { logger } from '@shared/logger'; - -import { getErrorMessage } from '@app/common/get-error-message'; -import { delay } from '@app/common/utils'; -import { validateTxId } from '@app/common/validation/validate-tx-id'; - -async function simulateShortDelayToAvoidUndefinedTabId() { - await delay(1000); -} - -interface BroadcastTransactionOptions { - txRaw: string; - serialized: Uint8Array; - isSponsored: boolean; - attachment?: string; - networkUrl: string; -} -export async function broadcastStacksTransaction(options: BroadcastTransactionOptions) { - const { txRaw, serialized, isSponsored, attachment, networkUrl } = options; - - if (isSponsored) { - await simulateShortDelayToAvoidUndefinedTabId(); - return { txRaw }; - } - - const response = await broadcastRawTransaction( - serialized, - `${networkUrl}/v2/transactions`, - attachment ? Buffer.from(attachment, 'hex') : undefined - ); - - if ('error' in response) { - logger.error(`Error broadcasting raw transaction`, response); - const message = getErrorMessage(response.reason as any); - throw new Error(`${message} - ${(response as any).error}`); - } else if (!validateTxId(response.txid)) { - logger.error(`Error broadcasting raw transaction`, response); - throw new Error('Invalid txid for transaction'); - } - - return { - txId: response.txid, - txRaw, - }; -} diff --git a/src/app/components/fees-row/fees-row.tsx b/src/app/components/fees-row/fees-row.tsx index c57bd1746c3..3f4d9f0b520 100644 --- a/src/app/components/fees-row/fees-row.tsx +++ b/src/app/components/fees-row/fees-row.tsx @@ -63,14 +63,17 @@ export function FeesRow({ }, [feeHelper, defaultFeeValue, feeTypeHelper]); useEffect(() => { + if (isSponsored) { + void feeHelper.setValue(0); + return; + } + if (!defaultFeeValue && hasFeeEstimates && !feeField.value && !isCustom) { void feeHelper.setValue( convertAmountToBaseUnit(fees.estimates[FeeTypes.Middle].fee).toString() ); void feeTypeHelper.setValue(FeeTypes[FeeTypes.Middle]); - } - if (isSponsored) { - void feeHelper.setValue(0); + return; } }, [ defaultFeeValue, diff --git a/src/app/features/stacks-transaction-request/hooks/use-stacks-broadcast-transaction.tsx b/src/app/features/stacks-transaction-request/hooks/use-stacks-broadcast-transaction.tsx index 3aaf8850c3f..0faef75e0f4 100644 --- a/src/app/features/stacks-transaction-request/hooks/use-stacks-broadcast-transaction.tsx +++ b/src/app/features/stacks-transaction-request/hooks/use-stacks-broadcast-transaction.tsx @@ -2,22 +2,33 @@ import { useMemo, useState } from 'react'; import toast from 'react-hot-toast'; import { useNavigate } from 'react-router-dom'; -import { StacksTransaction } from '@stacks/transactions'; +import { AuthType, StacksTransaction } from '@stacks/transactions'; +import { finalizeTxSignature } from '@shared/actions/finalize-tx-signature'; import { logger } from '@shared/logger'; import { CryptoCurrencies } from '@shared/models/currencies.model'; import { RouteUrls } from '@shared/route-urls'; import { isError, isString } from '@shared/utils'; +import { useDefaultRequestParams } from '@app/common/hooks/use-default-request-search-params'; import { LoadingKeys } from '@app/common/hooks/use-loading'; import { useSubmitTransactionCallback } from '@app/common/hooks/use-submit-stx-transaction'; +import { stacksTransactionToHex } from '@app/common/transactions/stacks/transaction.utils'; +import { delay } from '@app/common/utils'; +import { useTransactionRequest } from '@app/store/transactions/requests.hooks'; import { useSignStacksTransaction } from '@app/store/transactions/transaction.hooks'; import { useStacksTransactionSummary } from './use-stacks-transaction-summary'; +async function simulateShortDelayToAvoidUndefinedTabId() { + await delay(1000); +} + export function useStacksBroadcastTransaction(token: CryptoCurrencies, decimals?: number) { const signStacksTransaction = useSignStacksTransaction(); const [isBroadcasting, setIsBroadcasting] = useState(false); + const { tabId } = useDefaultRequestParams(); + const requestToken = useTransactionRequest(); const { formSentSummaryTxState } = useStacksTransactionSummary(token); const navigate = useNavigate(); @@ -26,14 +37,26 @@ export function useStacksBroadcastTransaction(token: CryptoCurrencies, decimals? }); return useMemo(() => { - function handlePreviewSuccess(txId: string, signedTx: StacksTransaction) { - navigate( - RouteUrls.SentStxTxSummary.replace(':symbol', token.toLowerCase()).replace( - ':txId', - `${txId}` - ), - formSentSummaryTxState(txId, signedTx, decimals) - ); + function handlePreviewSuccess(signedTx: StacksTransaction, txId?: string) { + if (requestToken && tabId) { + finalizeTxSignature({ + requestPayload: requestToken, + tabId, + data: { + txRaw: stacksTransactionToHex(signedTx), + txId, + }, + }); + } + if (txId) { + navigate( + RouteUrls.SentStxTxSummary.replace(':symbol', token.toLowerCase()).replace( + ':txId', + `${txId}` + ), + formSentSummaryTxState(txId, signedTx, decimals) + ); + } } async function broadcastTransactionAction(signedTx: StacksTransaction) { @@ -44,16 +67,22 @@ export function useStacksBroadcastTransaction(token: CryptoCurrencies, decimals? } try { setIsBroadcasting(true); - await broadcastTransactionFn({ - onError(e: Error | string) { - const message = isString(e) ? e : e.message; - navigate(RouteUrls.TransactionBroadcastError, { state: { message } }); - }, - onSuccess(txId) { - handlePreviewSuccess(txId, signedTx); - }, - replaceByFee: false, - })(signedTx); + const isSponsored = signedTx.auth?.authType === AuthType.Sponsored; + if (isSponsored) { + await simulateShortDelayToAvoidUndefinedTabId(); + handlePreviewSuccess(signedTx); + } else { + await broadcastTransactionFn({ + onError(e: Error | string) { + const message = isString(e) ? e : e.message; + navigate(RouteUrls.TransactionBroadcastError, { state: { message } }); + }, + onSuccess(txId) { + handlePreviewSuccess(signedTx, txId); + }, + replaceByFee: false, + })(signedTx); + } } catch (e) { navigate(RouteUrls.TransactionBroadcastError, { state: { message: isError(e) ? e.message : 'Unknown error' }, @@ -67,6 +96,7 @@ export function useStacksBroadcastTransaction(token: CryptoCurrencies, decimals? try { if (!unsignedTx) return; const signedTx = await signStacksTransaction(unsignedTx); + // TODO: Maybe better error handling here? if (!signedTx) return; await broadcastTransactionAction(signedTx); } catch (e) {} @@ -84,5 +114,7 @@ export function useStacksBroadcastTransaction(token: CryptoCurrencies, decimals? token, formSentSummaryTxState, decimals, + requestToken, + tabId, ]); } diff --git a/src/app/features/stacks-transaction-request/stacks-transaction-signer.tsx b/src/app/features/stacks-transaction-request/stacks-transaction-signer.tsx index aa1207aaccf..96fdb31a3f1 100644 --- a/src/app/features/stacks-transaction-request/stacks-transaction-signer.tsx +++ b/src/app/features/stacks-transaction-request/stacks-transaction-signer.tsx @@ -1,7 +1,7 @@ import { Outlet, useLocation, useNavigate } from 'react-router-dom'; import { StacksTransaction } from '@stacks/transactions'; -import { Formik } from 'formik'; +import { Formik, FormikHelpers } from 'formik'; import { Flex } from 'leather-styles/jsx'; import * as yup from 'yup'; @@ -43,7 +43,7 @@ interface StacksTransactionSignerProps { disableNonceSelection?: boolean; isMultisig: boolean; onCancel(): void; - onSignStacksTransaction(fee: number, nonce: number): void; + onSignStacksTransaction(fee: number, nonce: number): Promise; } export function StacksTransactionSigner({ stacksTransaction, @@ -66,9 +66,14 @@ export function StacksTransactionSigner({ void analytics.track('view_transaction_signing'), [analytics]; }); - const onSubmit = async (values: StacksTransactionFormValues) => { - onSignStacksTransaction(stxToMicroStx(values.fee).toNumber(), Number(values.nonce)); - }; + async function onSubmit( + values: StacksTransactionFormValues, + formikHelpers: FormikHelpers + ) { + formikHelpers.setSubmitting(true); + await onSignStacksTransaction(stxToMicroStx(values.fee).toNumber(), Number(values.nonce)); + formikHelpers.setSubmitting(false); + } if (!transactionRequest) return null; diff --git a/src/app/features/stacks-transaction-request/submit-action.tsx b/src/app/features/stacks-transaction-request/submit-action.tsx index 10fd16699f6..85c7b757881 100644 --- a/src/app/features/stacks-transaction-request/submit-action.tsx +++ b/src/app/features/stacks-transaction-request/submit-action.tsx @@ -6,14 +6,13 @@ import { StacksTransactionFormValues } from '@shared/models/form.model'; import { isEmpty } from '@shared/utils'; import { useDrawers } from '@app/common/hooks/use-drawers'; -import { LoadingKeys, useLoading } from '@app/common/hooks/use-loading'; import { useTransactionError } from '@app/features/stacks-transaction-request/hooks/use-transaction-error'; import { Button } from '@app/ui/components/button/button'; export function SubmitAction() { - const { handleSubmit, values, validateForm } = useFormikContext(); + const { handleSubmit, values, validateForm, isSubmitting } = + useFormikContext(); const { isShowingHighFeeConfirmation, setIsShowingHighFeeConfirmation } = useDrawers(); - const { isLoading } = useLoading(LoadingKeys.SUBMIT_TRANSACTION_REQUEST); const error = useTransactionError(); const isDisabled = !!error || Number(values.fee) < 0; @@ -29,7 +28,7 @@ export function SubmitAction() { return (