mirror of
https://github.com/zhigang1992/wallet.git
synced 2026-04-28 20:55:30 +08:00
refactor(tx-signing): remove implicit signing of transactions from send-form
This commit is contained in:
@@ -82,7 +82,6 @@ export function useSignIn() {
|
||||
setIsIdle,
|
||||
]
|
||||
);
|
||||
|
||||
const handleSetSeed = useCallback(
|
||||
async (value: string, trim?: boolean) => {
|
||||
const trimmed = trim ? value.trim() : value;
|
||||
|
||||
@@ -38,23 +38,17 @@ function getErrorMessage(
|
||||
|
||||
const timeForApiToUpdate = 250;
|
||||
|
||||
interface UseSubmitTransactionCallbackArgs {
|
||||
replaceByFee?: boolean;
|
||||
onClose: () => void;
|
||||
interface UseSubmitTransactionArgs {
|
||||
loadingKey: string;
|
||||
}
|
||||
export function useSubmitTransactionCallback({
|
||||
replaceByFee,
|
||||
onClose,
|
||||
loadingKey,
|
||||
}: UseSubmitTransactionCallbackArgs) {
|
||||
interface UseSubmitTransactionCallbackArgs {
|
||||
replaceByFee?: boolean;
|
||||
onClose(): void;
|
||||
}
|
||||
export function useSubmitTransactionCallback({ loadingKey }: UseSubmitTransactionArgs) {
|
||||
const refreshAccountData = useRefreshAllAccountData();
|
||||
const changeScreen = useChangeScreen();
|
||||
<<<<<<< HEAD
|
||||
const { setLatestNonce } = useWallet();
|
||||
=======
|
||||
const { doSetLatestNonce } = useWallet();
|
||||
>>>>>>> 3be3b2d29 (refactor(tx-signing): use unsigned serialised txs for fee estimation)
|
||||
const { setIsLoading, setIsIdle } = useLoading(loadingKey);
|
||||
const stacksNetwork = useCurrentStacksNetworkState();
|
||||
const { setActiveTabActivity } = useHomeTabs();
|
||||
@@ -62,47 +56,46 @@ export function useSubmitTransactionCallback({
|
||||
const externalTxid = useCurrentAccountTxIds();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
return useCallback<(tx: StacksTransaction) => Promise<void>>(
|
||||
async transaction => {
|
||||
setIsLoading();
|
||||
const nonce = !replaceByFee && transaction.auth.spendingCondition?.nonce.toNumber();
|
||||
try {
|
||||
const response = await broadcastTransaction(transaction, stacksNetwork);
|
||||
if (typeof response !== 'string') {
|
||||
toast.error(getErrorMessage(response.reason));
|
||||
onClose();
|
||||
setIsIdle();
|
||||
} else {
|
||||
const txid = `0x${response}`;
|
||||
if (!externalTxid.includes(txid)) {
|
||||
await setLocalTxs({
|
||||
rawTx: transaction.serialize().toString('hex'),
|
||||
timestamp: todaysIsoDate(),
|
||||
txid,
|
||||
});
|
||||
return useCallback(
|
||||
({ replaceByFee, onClose }: UseSubmitTransactionCallbackArgs) =>
|
||||
async (transaction: StacksTransaction) => {
|
||||
setIsLoading();
|
||||
const nonce = !replaceByFee && transaction.auth.spendingCondition?.nonce.toNumber();
|
||||
try {
|
||||
const response = await broadcastTransaction(transaction, stacksNetwork);
|
||||
if (typeof response !== 'string') {
|
||||
toast.error(getErrorMessage(response.reason));
|
||||
onClose();
|
||||
setIsIdle();
|
||||
} else {
|
||||
const txid = `0x${response}`;
|
||||
if (!externalTxid.includes(txid)) {
|
||||
await setLocalTxs({
|
||||
rawTx: transaction.serialize().toString('hex'),
|
||||
timestamp: todaysIsoDate(),
|
||||
txid,
|
||||
});
|
||||
}
|
||||
if (nonce) await setLatestNonce(nonce);
|
||||
toast.success('Transaction submitted!');
|
||||
void analytics.track('broadcast_transaction');
|
||||
onClose();
|
||||
setIsIdle();
|
||||
changeScreen(RouteUrls.Home);
|
||||
// switch active tab to activity
|
||||
setActiveTabActivity();
|
||||
await refreshAccountData(timeForApiToUpdate);
|
||||
}
|
||||
if (nonce) await setLatestNonce(nonce);
|
||||
toast.success('Transaction submitted!');
|
||||
void analytics.track('broadcast_transaction');
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
toast.error('Something went wrong');
|
||||
onClose();
|
||||
setIsIdle();
|
||||
changeScreen(RouteUrls.Home);
|
||||
// switch active tab to activity
|
||||
setActiveTabActivity();
|
||||
await refreshAccountData(timeForApiToUpdate);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
toast.error('Something went wrong');
|
||||
onClose();
|
||||
setIsIdle();
|
||||
}
|
||||
},
|
||||
},
|
||||
[
|
||||
setIsLoading,
|
||||
replaceByFee,
|
||||
stacksNetwork,
|
||||
onClose,
|
||||
setIsIdle,
|
||||
externalTxid,
|
||||
setLatestNonce,
|
||||
@@ -116,18 +109,19 @@ export function useSubmitTransactionCallback({
|
||||
}
|
||||
|
||||
interface UseHandleSubmitTransactionArgs {
|
||||
transaction: StacksTransaction | null;
|
||||
onClose: () => void;
|
||||
loadingKey: string;
|
||||
}
|
||||
interface UseHandleSubmitTransactionReturnFn {
|
||||
transaction: StacksTransaction;
|
||||
replaceByFee?: boolean;
|
||||
onClose(): void;
|
||||
}
|
||||
export function useHandleSubmitTransaction({
|
||||
transaction,
|
||||
onClose,
|
||||
loadingKey,
|
||||
replaceByFee = false,
|
||||
}: UseHandleSubmitTransactionArgs) {
|
||||
const callback = useSubmitTransactionCallback({ onClose, loadingKey, replaceByFee });
|
||||
if (transaction) return () => callback(transaction);
|
||||
return () => null;
|
||||
export function useHandleSubmitTransaction({ loadingKey }: UseHandleSubmitTransactionArgs) {
|
||||
const broadcastTxCallback = useSubmitTransactionCallback({ loadingKey });
|
||||
|
||||
return useCallback(
|
||||
({ transaction, onClose, replaceByFee = false }: UseHandleSubmitTransactionReturnFn) =>
|
||||
broadcastTxCallback({ onClose, replaceByFee })(transaction),
|
||||
[broadcastTxCallback]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ function generateSignedStxTransferTx(args: GenerateSignedStxTransferTxArgs) {
|
||||
return makeSTXTokenTransfer(options);
|
||||
}
|
||||
|
||||
export type GenerateSignedTransactionOptions = GenerateSignedTxArgs<
|
||||
type GenerateSignedTransactionOptions = GenerateSignedTxArgs<
|
||||
ContractCallPayload | STXTransferPayload | ContractDeployPayload
|
||||
>;
|
||||
export async function generateSignedTransaction(options: GenerateSignedTransactionOptions) {
|
||||
|
||||
@@ -119,10 +119,9 @@ function generateUnsignedStxTransferTx(args: GenerateUnsignedStxTransferTxArgs)
|
||||
return makeUnsignedSTXTokenTransfer(options);
|
||||
}
|
||||
|
||||
type GenerateUnsignedTransactionOptions = GenerateUnsignedTxArgs<
|
||||
export type GenerateUnsignedTransactionOptions = GenerateUnsignedTxArgs<
|
||||
ContractCallPayload | STXTransferPayload | ContractDeployPayload
|
||||
>;
|
||||
|
||||
export async function generateUnsignedTransaction(options: GenerateUnsignedTransactionOptions) {
|
||||
const { txData, publicKey, nonce, fee = 0 } = options;
|
||||
const isValid = isTransactionTypeSupported(txData.txType);
|
||||
|
||||
@@ -5,12 +5,15 @@ import { useCurrentNetwork } from '@common/hooks/use-current-network';
|
||||
import { useDrawers } from '@common/hooks/use-drawers';
|
||||
import { SpaceBetween } from '@components/space-between';
|
||||
import { Caption } from '@components/typography';
|
||||
import { useTxByteSizeState, useTxForSettingsState } from '@store/transactions/transaction.hooks';
|
||||
import {
|
||||
useTxByteSizeState,
|
||||
useUnsignedTxForSettingsState,
|
||||
} from '@store/transactions/transaction.hooks';
|
||||
|
||||
export function ShowEditNonceAction(): JSX.Element {
|
||||
const { isTestnet, name } = useCurrentNetwork();
|
||||
const { showEditNonce, setShowEditNonce } = useDrawers();
|
||||
const [tx] = useTxForSettingsState();
|
||||
const [tx] = useUnsignedTxForSettingsState();
|
||||
const [, setTxBytes] = useTxByteSizeState();
|
||||
|
||||
return (
|
||||
|
||||
@@ -4,14 +4,14 @@ import { Button, Stack } from '@stacks/ui';
|
||||
|
||||
import { LoadingKeys, useLoading } from '@common/hooks/use-loading';
|
||||
import { useDrawers } from '@common/hooks/use-drawers';
|
||||
import { useTxForSettingsState } from '@store/transactions/transaction.hooks';
|
||||
import { useUnsignedTxForSettingsState } from '@store/transactions/transaction.hooks';
|
||||
|
||||
import { EditNonceField } from './edit-nonce-field';
|
||||
|
||||
export function EditNonceFormInner(): JSX.Element {
|
||||
const { setFieldValue, handleSubmit } = useFormikContext();
|
||||
const { isLoading } = useLoading(LoadingKeys.EDIT_NONCE_DRAWER);
|
||||
const [transaction] = useTxForSettingsState();
|
||||
const [transaction] = useUnsignedTxForSettingsState();
|
||||
const nonce = transaction?.auth.spendingCondition?.nonce.toNumber();
|
||||
const { setShowEditNonce } = useDrawers();
|
||||
|
||||
|
||||
@@ -6,14 +6,14 @@ import { Button, Stack } from '@stacks/ui';
|
||||
import { LoadingKeys, useLoading } from '@common/hooks/use-loading';
|
||||
import { nonceSchema } from '@common/validation/nonce-schema';
|
||||
import { useCustomNonce } from '@store/transactions/nonce.hooks';
|
||||
import { useTxForSettingsState } from '@store/transactions/transaction.hooks';
|
||||
import { useUnsignedTxForSettingsState } from '@store/transactions/transaction.hooks';
|
||||
|
||||
import { EditNonceFormInner } from './edit-nonce-form-inner';
|
||||
import { EditNonceField } from './edit-nonce-field';
|
||||
|
||||
// Not sure what this is doing?
|
||||
const SuspenseOnMount = ({ onMountCallback, isEnabled }: any) => {
|
||||
const [tx] = useTxForSettingsState();
|
||||
const [tx] = useUnsignedTxForSettingsState();
|
||||
|
||||
useEffect(() => {
|
||||
if (tx && isEnabled) {
|
||||
|
||||
16
src/features/fee-row/components/transaction-fee.tsx
Normal file
16
src/features/fee-row/components/transaction-fee.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import { AuthType } from '@stacks/transactions';
|
||||
|
||||
import { useUnsignedTxForSettingsState } from '@store/transactions/transaction.hooks';
|
||||
|
||||
interface TransactionFeeProps {
|
||||
fee: number | string;
|
||||
}
|
||||
export function TransactionFee(props: TransactionFeeProps): JSX.Element | null {
|
||||
const { fee } = props;
|
||||
/** @deprecated */
|
||||
const [transaction] = useUnsignedTxForSettingsState();
|
||||
const isSponsored = transaction?.auth?.authType === AuthType.Sponsored;
|
||||
|
||||
return <>{isSponsored ? '🎉 sponsored' : fee} STX</>;
|
||||
}
|
||||
@@ -5,12 +5,12 @@ import { Button, Stack } from '@stacks/ui';
|
||||
import { LoadingKeys, useLoading } from '@common/hooks/use-loading';
|
||||
|
||||
interface SendTokensConfirmActionsProps {
|
||||
onSubmit: () => void;
|
||||
onUserConfirmBroadcast: () => void;
|
||||
transaction: StacksTransaction | undefined;
|
||||
}
|
||||
|
||||
export function SendTokensConfirmActions(props: SendTokensConfirmActionsProps): JSX.Element {
|
||||
const { onSubmit: handleSubmit, transaction } = props;
|
||||
const { onUserConfirmBroadcast: handleSubmit, transaction } = props;
|
||||
const { isLoading } = useLoading(LoadingKeys.CONFIRM_DRAWER);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,42 +1,32 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { Flex, Stack } from '@stacks/ui';
|
||||
import React from 'react';
|
||||
import { Stack } from '@stacks/ui';
|
||||
|
||||
import { useDrawers } from '@common/hooks/use-drawers';
|
||||
import { BaseDrawer, BaseDrawerProps } from '@components/drawer';
|
||||
import { LoadingKeys } from '@common/hooks/use-loading';
|
||||
|
||||
import { SpaceBetween } from '@components/space-between';
|
||||
import { Caption } from '@components/typography';
|
||||
import { TransactionFee } from '@components/fee-row/components/transaction-fee';
|
||||
import { useHandleSubmitTransaction } from '@common/hooks/use-submit-stx-transaction';
|
||||
import {
|
||||
useLocalTransactionInputsState,
|
||||
useTxForSettingsState,
|
||||
useUnsignedTxForSettingsState,
|
||||
} from '@store/transactions/transaction.hooks';
|
||||
import { useFeeEstimationsState } from '@store/transactions/fees.hooks';
|
||||
|
||||
import { SendTokensConfirmActions } from './send-tokens-confirm-actions';
|
||||
import { SendTokensConfirmDetails } from './send-tokens-confirm-details';
|
||||
import { isTxSponsored } from '@common/transactions/transaction-utils';
|
||||
|
||||
export function SendTokensConfirmDrawer(props: BaseDrawerProps) {
|
||||
const { isShowing, onClose } = props;
|
||||
interface SendTokensConfirmDrawerProps extends BaseDrawerProps {
|
||||
onUserSelectBroadcastTransaction(): void;
|
||||
}
|
||||
export function SendTokensConfirmDrawer(props: SendTokensConfirmDrawerProps) {
|
||||
const { isShowing, onClose, onUserSelectBroadcastTransaction } = props;
|
||||
|
||||
const [txData] = useLocalTransactionInputsState();
|
||||
const [transaction] = useTxForSettingsState();
|
||||
const [transaction] = useUnsignedTxForSettingsState();
|
||||
const { showEditNonce } = useDrawers();
|
||||
const [, setFeeEstimations] = useFeeEstimationsState();
|
||||
const isSponsored = transaction ? isTxSponsored(transaction) : false;
|
||||
|
||||
const broadcastTransaction = useHandleSubmitTransaction({
|
||||
transaction: transaction || null,
|
||||
onClose,
|
||||
loadingKey: LoadingKeys.CONFIRM_DRAWER,
|
||||
});
|
||||
|
||||
const broadcastTransactionAction = useCallback(async () => {
|
||||
await broadcastTransaction();
|
||||
setFeeEstimations([]);
|
||||
}, [broadcastTransaction, setFeeEstimations]);
|
||||
|
||||
if (!isShowing || !transaction || !txData) return null;
|
||||
|
||||
return (
|
||||
@@ -53,16 +43,14 @@ export function SendTokensConfirmDrawer(props: BaseDrawerProps) {
|
||||
nonce={transaction?.auth.spendingCondition?.nonce.toNumber()}
|
||||
/>
|
||||
<SpaceBetween>
|
||||
<Caption>
|
||||
<Flex>Fees</Flex>
|
||||
</Caption>
|
||||
<Caption>Fees</Caption>
|
||||
<Caption>
|
||||
<TransactionFee isSponsored={isSponsored} fee={txData.fee} />
|
||||
</Caption>
|
||||
</SpaceBetween>
|
||||
<SendTokensConfirmActions
|
||||
onSubmit={() => broadcastTransactionAction()}
|
||||
transaction={transaction}
|
||||
onUserConfirmBroadcast={() => onUserSelectBroadcastTransaction()}
|
||||
/>
|
||||
</Stack>
|
||||
</BaseDrawer>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useEffect } from 'react';
|
||||
|
||||
import {
|
||||
useLocalTransactionInputsState,
|
||||
useTxForSettingsState,
|
||||
useUnsignedTxForSettingsState,
|
||||
} from '@store/transactions/transaction.hooks';
|
||||
import { LoadingKeys, useLoading } from '@common/hooks/use-loading';
|
||||
|
||||
@@ -12,7 +12,7 @@ interface ShowDelayProps {
|
||||
isShowing: boolean;
|
||||
}
|
||||
export const ShowDelay = ({ setShowing, beginShow, isShowing }: ShowDelayProps) => {
|
||||
const [tx] = useTxForSettingsState();
|
||||
const [tx] = useUnsignedTxForSettingsState();
|
||||
const [txData] = useLocalTransactionInputsState();
|
||||
const { setIsIdle } = useLoading(LoadingKeys.SEND_TOKENS_FORM);
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { memo, Suspense, useState } from 'react';
|
||||
import React, { memo, Suspense, useCallback, useState } from 'react';
|
||||
import { Formik } from 'formik';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
import { useSelectedAsset } from '@common/hooks/use-selected-asset';
|
||||
import { LoadingKeys, useLoading } from '@common/hooks/use-loading';
|
||||
@@ -10,12 +11,19 @@ import { Header } from '@components/header';
|
||||
import { useChangeScreen } from '@common/hooks/use-change-screen';
|
||||
import { HighFeeDrawer } from '@features/high-fee-drawer/high-fee-drawer';
|
||||
import { useSendFormValidation } from '@pages/send-tokens/hooks/use-send-form-validation';
|
||||
import { useLocalTransactionInputsState } from '@store/transactions/transaction.hooks';
|
||||
import {
|
||||
useLocalTransactionInputsState,
|
||||
useSendFormUnsignedTxState,
|
||||
useSignTransactionSoftwareWallet,
|
||||
} from '@store/transactions/transaction.hooks';
|
||||
|
||||
import { SendTokensConfirmDrawer } from './components/send-tokens-confirm-drawer/send-tokens-confirm-drawer';
|
||||
import { SendFormInner } from './components/send-form-inner';
|
||||
import { ShowDelay } from './components/show-delay';
|
||||
import { useResetNonceCallback } from './hooks/use-reset-nonce-callback';
|
||||
import { useHandleSubmitTransaction } from '@common/hooks/use-submit-stx-transaction';
|
||||
import { useFeeEstimationsState } from '@store/transactions/fees.hooks';
|
||||
import { logger } from '@common/logger';
|
||||
|
||||
function SendTokensFormBase() {
|
||||
const changeScreen = useChangeScreen();
|
||||
@@ -25,16 +33,52 @@ function SendTokensFormBase() {
|
||||
const [assetError, setAssetError] = useState<string | undefined>(undefined);
|
||||
const { selectedAsset } = useSelectedAsset();
|
||||
const sendFormSchema = useSendFormValidation({ setAssetError });
|
||||
const [, setTxData] = useLocalTransactionInputsState();
|
||||
const [_txData, setTxData] = useLocalTransactionInputsState();
|
||||
const [beginShow, setBeginShow] = useState(false);
|
||||
const resetNonceCallback = useResetNonceCallback();
|
||||
const [, setFeeEstimations] = useFeeEstimationsState();
|
||||
const transaction = useSendFormUnsignedTxState();
|
||||
|
||||
const handleConfirmDrawerOnClose = (setSubmitting: (value: boolean) => void) => {
|
||||
const signSoftwareWalletTx = useSignTransactionSoftwareWallet();
|
||||
|
||||
const handleConfirmDrawerOnClose = useCallback(() => {
|
||||
setShowing(false);
|
||||
setSubmitting(false);
|
||||
setTxData(null);
|
||||
setIsIdle();
|
||||
resetNonceCallback();
|
||||
}, [resetNonceCallback, setIsIdle, setTxData]);
|
||||
|
||||
const broadcastTransactionFn = useHandleSubmitTransaction({
|
||||
loadingKey: LoadingKeys.CONFIRM_DRAWER,
|
||||
});
|
||||
|
||||
const broadcastTransactionAction = useCallback(async () => {
|
||||
if (!transaction) {
|
||||
logger.error('Cannot broadcast transaction, no tx in state');
|
||||
toast.error('Unable to broadcast transaction');
|
||||
return;
|
||||
}
|
||||
const signedTx = signSoftwareWalletTx(transaction);
|
||||
await broadcastTransactionFn({
|
||||
transaction: signedTx,
|
||||
onClose() {
|
||||
handleConfirmDrawerOnClose();
|
||||
},
|
||||
});
|
||||
setFeeEstimations([]);
|
||||
}, [
|
||||
broadcastTransactionFn,
|
||||
handleConfirmDrawerOnClose,
|
||||
setFeeEstimations,
|
||||
signSoftwareWalletTx,
|
||||
transaction,
|
||||
]);
|
||||
|
||||
const initalValues = {
|
||||
amount: '',
|
||||
recipient: '',
|
||||
fee: '',
|
||||
memo: '',
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -42,39 +86,25 @@ function SendTokensFormBase() {
|
||||
header={<Header title="Send" onClose={() => changeScreen(RouteUrls.PopupHome)} />}
|
||||
>
|
||||
<Formik
|
||||
initialValues={{
|
||||
amount: '',
|
||||
recipient: '',
|
||||
fee: '',
|
||||
memo: '',
|
||||
}}
|
||||
initialErrors={{}}
|
||||
onSubmit={values => {
|
||||
if (
|
||||
selectedAsset &&
|
||||
values.amount &&
|
||||
values.recipient &&
|
||||
values.recipient !== '' &&
|
||||
values.fee
|
||||
) {
|
||||
if (!assetError) {
|
||||
setTxData({
|
||||
amount: values.amount,
|
||||
fee: values.fee,
|
||||
memo: values.memo,
|
||||
recipient: values.recipient,
|
||||
});
|
||||
setIsLoading();
|
||||
setBeginShow(true);
|
||||
}
|
||||
}
|
||||
}}
|
||||
initialValues={initalValues}
|
||||
validateOnChange={false}
|
||||
validateOnBlur={false}
|
||||
validateOnMount={false}
|
||||
validationSchema={sendFormSchema}
|
||||
onSubmit={values => {
|
||||
if (selectedAsset && !assetError) {
|
||||
setTxData({
|
||||
amount: values.amount,
|
||||
fee: values.fee,
|
||||
memo: values.memo,
|
||||
recipient: values.recipient,
|
||||
});
|
||||
setIsLoading();
|
||||
setBeginShow(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{formik => (
|
||||
{() => (
|
||||
<>
|
||||
{!showHighFeeConfirmation && beginShow && (
|
||||
<Suspense fallback={<></>}>
|
||||
@@ -84,12 +114,14 @@ function SendTokensFormBase() {
|
||||
<Suspense fallback={<></>}>
|
||||
<SendFormInner assetError={assetError} />
|
||||
</Suspense>
|
||||
<Suspense fallback={<></>}>
|
||||
<SendTokensConfirmDrawer
|
||||
isShowing={isShowing && !showEditNonce}
|
||||
onClose={() => handleConfirmDrawerOnClose(formik.setSubmitting)}
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
<SendTokensConfirmDrawer
|
||||
isShowing={isShowing && !showEditNonce}
|
||||
onClose={() => handleConfirmDrawerOnClose()}
|
||||
onUserSelectBroadcastTransaction={async () => {
|
||||
await broadcastTransactionAction();
|
||||
}}
|
||||
/>
|
||||
<HighFeeDrawer />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -16,11 +16,7 @@ export const useReplaceByFeeSubmitCallBack = () => {
|
||||
const [, setTxId] = useRawTxIdState();
|
||||
|
||||
const submitTransaction = useSubmitTransactionCallback({
|
||||
onClose: () => {
|
||||
setTxId(null);
|
||||
},
|
||||
loadingKey: LoadingKeys.INCREASE_FEE_DRAWER,
|
||||
replaceByFee: true,
|
||||
});
|
||||
|
||||
return useAtomCallback<void, { fee: number; nonce: number }>(
|
||||
@@ -28,9 +24,14 @@ export const useReplaceByFeeSubmitCallBack = () => {
|
||||
async get => {
|
||||
const signedTx = await get(rawSignedTxState, true);
|
||||
if (!signedTx) return;
|
||||
await submitTransaction(signedTx);
|
||||
await submitTransaction({
|
||||
onClose: () => {
|
||||
setTxId(null);
|
||||
},
|
||||
replaceByFee: true,
|
||||
})(signedTx);
|
||||
},
|
||||
[submitTransaction]
|
||||
[setTxId, submitTransaction]
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
import { serializePayload } from '@stacks/transactions/dist/payload';
|
||||
|
||||
import { stxToMicroStx, validateStacksAddress } from '@common/stacks-utils';
|
||||
import { generateSignedTransaction } from '@common/transactions/generate-signed-txs';
|
||||
import { stacksTransactionToHex, whenChainId } from '@common/transactions/transaction-utils';
|
||||
import { currentNetworkState, currentStacksNetworkState } from '@store/network/networks';
|
||||
import { currentAccountNonceState } from '@store/accounts/nonce';
|
||||
@@ -25,9 +24,8 @@ import { currentAccountState, currentAccountStxAddressState } from '@store/accou
|
||||
import { requestTokenPayloadState } from '@store/transactions/requests';
|
||||
import { postConditionsState } from '@store/transactions/post-conditions';
|
||||
import { localStacksTransactionInputsState } from '@store/transactions/local-transactions';
|
||||
import { localTransactionState } from '@store/transactions/local-transactions';
|
||||
import { sendFormUnsignedTxState } from '@store/transactions/local-transactions';
|
||||
import { generateUnsignedTransaction } from '@common/transactions/generate-unsigned-txs';
|
||||
|
||||
import { customNonceState } from './nonce.hooks';
|
||||
|
||||
export const pendingTransactionState = atom<
|
||||
@@ -45,30 +43,6 @@ export const pendingTransactionState = atom<
|
||||
|
||||
export const transactionAttachmentState = atom(get => get(pendingTransactionState)?.attachment);
|
||||
|
||||
/** @deprecated */
|
||||
const signedStacksTransactionBaseState = atom(get => {
|
||||
const account = get(currentAccountState);
|
||||
const txData = get(pendingTransactionState);
|
||||
const stxAddress = get(currentAccountStxAddressState);
|
||||
const nonce = get(currentAccountNonceState);
|
||||
const customNonce = get(customNonceState);
|
||||
if (!account || !txData || !stxAddress || typeof nonce === 'undefined') return;
|
||||
const txNonce = typeof customNonce === 'number' ? customNonce : nonce;
|
||||
if (
|
||||
txData.txType === TransactionTypes.ContractCall &&
|
||||
!validateStacksAddress(txData.contractAddress)
|
||||
) {
|
||||
return { transaction: undefined, options: {} };
|
||||
}
|
||||
const options = {
|
||||
fee: txData.fee,
|
||||
senderKey: account.stxPrivateKey,
|
||||
nonce: txNonce,
|
||||
txData,
|
||||
};
|
||||
return generateSignedTransaction(options).then(transaction => ({ transaction, options }));
|
||||
});
|
||||
|
||||
const unsignedStacksTransactionBaseState = atom(get => {
|
||||
const account = get(currentAccountState);
|
||||
const txData = get(pendingTransactionState);
|
||||
@@ -93,46 +67,29 @@ const unsignedStacksTransactionBaseState = atom(get => {
|
||||
return generateUnsignedTransaction(options).then(transaction => ({ transaction, options }));
|
||||
});
|
||||
|
||||
/** @deprecated */
|
||||
const signedStacksTransactionState = atom(get => {
|
||||
const { transaction, options } = get(signedStacksTransactionBaseState);
|
||||
if (!transaction) return;
|
||||
return generateSignedTransaction({ ...options });
|
||||
});
|
||||
|
||||
const unsignedStacksTransactionState = atom(get => {
|
||||
export const unsignedStacksTransactionState = atom(get => {
|
||||
const { transaction, options } = get(unsignedStacksTransactionBaseState);
|
||||
if (!transaction) return;
|
||||
return generateUnsignedTransaction({ ...options });
|
||||
});
|
||||
|
||||
/** @deprecated */
|
||||
export const signedTransactionState = atom(get => {
|
||||
const signedTransaction = get(signedStacksTransactionState);
|
||||
if (!signedTransaction) return;
|
||||
const serialized = signedTransaction.serialize();
|
||||
const txRaw = stacksTransactionToHex(signedTransaction);
|
||||
export function prepareTxDetailsForBroadcast(tx: StacksTransaction) {
|
||||
const serialized = tx.serialize();
|
||||
const txRaw = stacksTransactionToHex(tx);
|
||||
|
||||
return {
|
||||
serialized,
|
||||
isSponsored: signedTransaction?.auth?.authType === AuthType.Sponsored,
|
||||
nonce: signedTransaction?.auth.spendingCondition?.nonce.toNumber(),
|
||||
fee: signedTransaction?.auth.spendingCondition?.fee?.toNumber(),
|
||||
isSponsored: tx.auth?.authType === AuthType.Sponsored,
|
||||
nonce: tx.auth.spendingCondition?.nonce.toNumber(),
|
||||
fee: tx.auth.spendingCondition?.fee?.toNumber(),
|
||||
txRaw,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export const unsignedTransactionState = atom(get => {
|
||||
const unsignedTransaction = get(unsignedStacksTransactionState);
|
||||
if (!unsignedTransaction) return;
|
||||
const serialized = unsignedTransaction.serialize();
|
||||
const txRaw = stacksTransactionToHex(unsignedTransaction);
|
||||
return {
|
||||
serialized,
|
||||
isSponsored: unsignedTransaction?.auth?.authType === AuthType.Sponsored,
|
||||
nonce: unsignedTransaction?.auth.spendingCondition?.nonce.toNumber(),
|
||||
fee: unsignedTransaction?.auth.spendingCondition?.fee?.toNumber(),
|
||||
txRaw,
|
||||
};
|
||||
return prepareTxDetailsForBroadcast(unsignedTransaction);
|
||||
});
|
||||
|
||||
export const serializedUnsignedTransactionPayloadState = atom<string>(get => {
|
||||
@@ -168,35 +125,23 @@ export const transactionBroadcastErrorState = atom<string | null>(null);
|
||||
// like it could easily be error-prone. Say this value doesn't get reset when it should.
|
||||
// The effect could be calamitous. A user would be changing settings for a stale, cached
|
||||
// transaction they've long forgotten about.
|
||||
/** @deprecated */
|
||||
export const txForSettingsState = atom(get =>
|
||||
get(pendingTransactionState) ? get(signedStacksTransactionState) : get(localTransactionState)
|
||||
get(pendingTransactionState) ? get(unsignedStacksTransactionState) : get(sendFormUnsignedTxState)
|
||||
);
|
||||
|
||||
// Using txForSettingsState which should be refactored
|
||||
// with new transaction signing flow.
|
||||
export const serializedTransactionPayloadState = atom<string>(get => {
|
||||
const transaction = get(txForSettingsState);
|
||||
const transaction = get(sendFormUnsignedTxState);
|
||||
if (!transaction) return '';
|
||||
const serializedTxPayload = serializePayload(transaction.payload);
|
||||
return serializedTxPayload.toString('hex');
|
||||
});
|
||||
|
||||
// Using txForSettingsState which should be refactored
|
||||
// with new transaction signing flow.
|
||||
export const estimatedTransactionByteLengthState = atom<number | null>(get => {
|
||||
const transaction = get(txForSettingsState);
|
||||
const transaction = get(sendFormUnsignedTxState);
|
||||
if (!transaction) return null;
|
||||
const serializedTx = transaction.serialize();
|
||||
return serializedTx.byteLength;
|
||||
});
|
||||
|
||||
export const txByteSize = atom<number | null>(null);
|
||||
|
||||
// dev tooling
|
||||
postConditionsState.debugLabel = 'postConditionsState';
|
||||
pendingTransactionState.debugLabel = 'pendingTransactionState';
|
||||
transactionAttachmentState.debugLabel = 'transactionAttachmentState';
|
||||
signedStacksTransactionState.debugLabel = 'signedStacksTransactionState';
|
||||
signedTransactionState.debugLabel = 'signedTransactionState';
|
||||
transactionNetworkVersionState.debugLabel = 'transactionNetworkVersionState';
|
||||
transactionBroadcastErrorState.debugLabel = 'transactionBroadcastErrorState';
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
createEmptyAddress,
|
||||
noneCV,
|
||||
PostConditionMode,
|
||||
pubKeyfromPrivKey,
|
||||
publicKeyToString,
|
||||
serializeCV,
|
||||
someCV,
|
||||
standardPrincipalCVFromAddress,
|
||||
@@ -16,10 +18,6 @@ import {
|
||||
|
||||
import { ftUnshiftDecimals, stxToMicroStx } from '@common/stacks-utils';
|
||||
import { TransactionFormValues } from '@common/transactions/transaction-utils';
|
||||
import {
|
||||
generateSignedTransaction,
|
||||
GenerateSignedTransactionOptions,
|
||||
} from '@common/transactions/generate-signed-txs';
|
||||
import { makeFungibleTokenTransferState } from '@store/transactions/fungible-token-transfer';
|
||||
import { selectedAssetStore } from '@store/assets/asset-search';
|
||||
import { makePostCondition } from '@store/transactions/transaction.hooks';
|
||||
@@ -27,13 +25,17 @@ import { currentAccountState, currentAccountStxAddressState } from '@store/accou
|
||||
import { currentStacksNetworkState } from '@store/network/networks';
|
||||
import { currentAccountNonceState } from '@store/accounts/nonce';
|
||||
import { customNonceState } from '@store/transactions/nonce.hooks';
|
||||
import {
|
||||
generateUnsignedTransaction,
|
||||
GenerateUnsignedTransactionOptions,
|
||||
} from '@common/transactions/generate-unsigned-txs';
|
||||
|
||||
// This is the form state so can likely be removed from global store when we
|
||||
// refactor transaction signing. Leaving for now to avoid conflicts but deprecating.
|
||||
/** @deprecated */
|
||||
export const localStacksTransactionInputsState = atom<TransactionFormValues | null>(null);
|
||||
|
||||
const stxTokenTransferTransactionState = atom(get => {
|
||||
const stxTokenTransferUnsignedTxState = atom(get => {
|
||||
const txData = get(localStacksTransactionInputsState);
|
||||
const address = get(currentAccountStxAddressState);
|
||||
const customNonce = get(customNonceState);
|
||||
@@ -48,10 +50,9 @@ const stxTokenTransferTransactionState = atom(get => {
|
||||
);
|
||||
|
||||
if (!account || typeof nonce === 'undefined') return;
|
||||
const senderKey = account.stxPrivateKey;
|
||||
const txNonce = typeof customNonce === 'number' ? customNonce : nonce;
|
||||
const options: GenerateSignedTransactionOptions = {
|
||||
senderKey,
|
||||
const options: GenerateUnsignedTransactionOptions = {
|
||||
publicKey: publicKeyToString(pubKeyfromPrivKey(account.stxPrivateKey)),
|
||||
nonce: txNonce,
|
||||
txData: {
|
||||
txType: TransactionTypes.STXTransfer,
|
||||
@@ -67,16 +68,16 @@ const stxTokenTransferTransactionState = atom(get => {
|
||||
} as STXTransferPayload,
|
||||
};
|
||||
|
||||
return generateSignedTransaction(options).then(transaction => {
|
||||
return generateUnsignedTransaction(options).then(transaction => {
|
||||
if (!transaction) return;
|
||||
return generateSignedTransaction({
|
||||
return generateUnsignedTransaction({
|
||||
...options,
|
||||
fee: stxToMicroStx(txData?.fee || 0).toNumber(),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const ftTokenTransferTransactionState = atom(get => {
|
||||
const ftTokenTransferUnsignedTxState = atom(get => {
|
||||
const txData = get(localStacksTransactionInputsState);
|
||||
const address = get(currentAccountStxAddressState);
|
||||
const customNonce = get(customNonceState);
|
||||
@@ -88,16 +89,8 @@ const ftTokenTransferTransactionState = atom(get => {
|
||||
const selectedAsset = get(selectedAssetStore);
|
||||
|
||||
if (!assetTransferState || !selectedAsset || !account) return;
|
||||
const {
|
||||
balances,
|
||||
network,
|
||||
senderKey,
|
||||
assetName,
|
||||
contractAddress,
|
||||
contractName,
|
||||
nonce,
|
||||
stxAddress,
|
||||
} = assetTransferState;
|
||||
const { balances, network, assetName, contractAddress, contractName, nonce, stxAddress } =
|
||||
assetTransferState;
|
||||
|
||||
const functionName = 'transfer';
|
||||
|
||||
@@ -149,31 +142,28 @@ const ftTokenTransferTransactionState = atom(get => {
|
||||
postConditions,
|
||||
postConditionMode: PostConditionMode.Deny,
|
||||
network,
|
||||
// Dummy public key to satisfy types
|
||||
// This isn't a good parttern to follow, but much of this
|
||||
// code will have to change with Ledger code anyway
|
||||
publicKey: '',
|
||||
publicKey: publicKeyToString(pubKeyfromPrivKey(account.stxPrivateKey)),
|
||||
},
|
||||
senderKey,
|
||||
publicKey: publicKeyToString(pubKeyfromPrivKey(account.stxPrivateKey)),
|
||||
nonce: txNonce,
|
||||
} as const;
|
||||
|
||||
return generateSignedTransaction(options).then(transaction => {
|
||||
return generateUnsignedTransaction(options).then(transaction => {
|
||||
if (!transaction) return;
|
||||
return generateSignedTransaction({
|
||||
return generateUnsignedTransaction({
|
||||
...options,
|
||||
fee: stxToMicroStx(txData?.fee || 0).toNumber(),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const localTransactionIsStxTransferState = atom(get => {
|
||||
const isSendFormSendingStx = atom(get => {
|
||||
const selectedAsset = get(selectedAssetStore);
|
||||
return selectedAsset?.type === 'stx';
|
||||
});
|
||||
|
||||
export const localTransactionState = atom(get => {
|
||||
return get(localTransactionIsStxTransferState)
|
||||
? get(stxTokenTransferTransactionState)
|
||||
: get(ftTokenTransferTransactionState);
|
||||
export const sendFormUnsignedTxState = atom(get => {
|
||||
return get(isSendFormSendingStx)
|
||||
? get(stxTokenTransferUnsignedTxState)
|
||||
: get(ftTokenTransferUnsignedTxState);
|
||||
});
|
||||
|
||||
@@ -48,9 +48,3 @@ export const transactionRequestStxAddressState = atom(
|
||||
);
|
||||
|
||||
export const transactionRequestNetwork = atom(get => get(requestTokenPayloadState)?.network);
|
||||
|
||||
requestTokenPayloadState.debugLabel = 'requestTokenPayloadState';
|
||||
requestTokenOriginState.debugLabel = 'requestTokenOriginState';
|
||||
transactionRequestValidationState.debugLabel = 'transactionRequestValidationState';
|
||||
transactionRequestStxAddressState.debugLabel = 'transactionRequestStxAddressState';
|
||||
transactionRequestNetwork.debugLabel = 'transactionRequestNetwork';
|
||||
|
||||
@@ -4,9 +4,11 @@ import { useAtom } from 'jotai';
|
||||
import { useAtomCallback, useAtomValue, waitForAll } from 'jotai/utils';
|
||||
import {
|
||||
createAssetInfo,
|
||||
createStacksPrivateKey,
|
||||
FungibleConditionCode,
|
||||
makeStandardFungiblePostCondition,
|
||||
PostCondition,
|
||||
TransactionSigner,
|
||||
} from '@stacks/transactions';
|
||||
|
||||
import { todaysIsoDate } from '@common/date-utils';
|
||||
@@ -16,24 +18,30 @@ import { broadcastTransaction } from '@common/transactions/broadcast-transaction
|
||||
import { logger } from '@common/logger';
|
||||
import { currentAccountState } from '@store/accounts';
|
||||
import { currentNetworkState } from '@store/network/networks';
|
||||
import { localStacksTransactionInputsState } from '@store/transactions/local-transactions';
|
||||
import {
|
||||
localStacksTransactionInputsState,
|
||||
sendFormUnsignedTxState,
|
||||
} from '@store/transactions/local-transactions';
|
||||
import { currentAccountLocallySubmittedTxsState } from '@store/accounts/account-activity';
|
||||
|
||||
import { postConditionsState } from './post-conditions';
|
||||
import { requestTokenState } from './requests';
|
||||
import { useCurrentAccount } from '@store/accounts/account.hooks';
|
||||
import { StacksTransaction } from '@stacks/connect/node_modules/@stacks/transactions';
|
||||
import {
|
||||
estimatedTransactionByteLengthState,
|
||||
estimatedUnsignedTransactionByteLengthState,
|
||||
prepareTxDetailsForBroadcast,
|
||||
pendingTransactionState,
|
||||
serializedTransactionPayloadState,
|
||||
serializedUnsignedTransactionPayloadState,
|
||||
signedTransactionState,
|
||||
transactionAttachmentState,
|
||||
transactionBroadcastErrorState,
|
||||
txByteSize,
|
||||
txForSettingsState,
|
||||
unsignedStacksTransactionState,
|
||||
unsignedTransactionState,
|
||||
} from './index';
|
||||
import { postConditionsState } from './post-conditions';
|
||||
import { requestTokenState } from './requests';
|
||||
|
||||
export function usePendingTransaction() {
|
||||
return useAtomValue(pendingTransactionState);
|
||||
@@ -63,31 +71,47 @@ export function useEstimatedTransactionByteLengthState() {
|
||||
return useAtomValue(estimatedTransactionByteLengthState);
|
||||
}
|
||||
|
||||
export function useSignTransactionSoftwareWallet() {
|
||||
const account = useCurrentAccount();
|
||||
if (!account) throw new Error('Cannot sign a transaction without an account');
|
||||
return useCallback(
|
||||
(tx: StacksTransaction) => {
|
||||
const signer = new TransactionSigner(tx);
|
||||
signer.signOrigin(createStacksPrivateKey(account.stxPrivateKey));
|
||||
return tx;
|
||||
},
|
||||
[account.stxPrivateKey]
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: @kyranjamie
|
||||
// only used for signed transactions, not send form
|
||||
export function useTransactionBroadcast() {
|
||||
const { setLatestNonce } = useWallet();
|
||||
const signSoftwareWalletTx = useSignTransactionSoftwareWallet();
|
||||
|
||||
return useAtomCallback(
|
||||
useCallback(
|
||||
async (get, set) => {
|
||||
const { account, signedTransaction, attachment, requestToken, network } = await get(
|
||||
const { account, unsignedStacksTransaction, attachment, requestToken, network } = await get(
|
||||
waitForAll({
|
||||
// TODO: @kyranjamie
|
||||
// Need to replace this mechanism to so that this broadcast hook
|
||||
// doesn't implictly read the stxPrivateKey to create a signed tx
|
||||
signedTransaction: signedTransactionState,
|
||||
account: currentAccountState,
|
||||
attachment: transactionAttachmentState,
|
||||
requestToken: requestTokenState,
|
||||
network: currentNetworkState,
|
||||
unsignedStacksTransaction: unsignedStacksTransactionState,
|
||||
}),
|
||||
true
|
||||
);
|
||||
if (!account || !requestToken || !signedTransaction) {
|
||||
|
||||
if (!account || !requestToken || !unsignedStacksTransaction) {
|
||||
set(transactionBroadcastErrorState, 'No pending transaction found.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { isSponsored, serialized, txRaw, nonce } = signedTransaction;
|
||||
const signedTx = signSoftwareWalletTx(unsignedStacksTransaction);
|
||||
const { isSponsored, serialized, txRaw, nonce } = prepareTxDetailsForBroadcast(signedTx);
|
||||
const result = await broadcastTransaction({
|
||||
isSponsored,
|
||||
serialized,
|
||||
@@ -110,7 +134,7 @@ export function useTransactionBroadcast() {
|
||||
if (error instanceof Error) set(transactionBroadcastErrorState, error.message);
|
||||
}
|
||||
},
|
||||
[setLatestNonce]
|
||||
[setLatestNonce, signSoftwareWalletTx]
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -134,8 +158,26 @@ export function makePostCondition(options: PostConditionsOptions): PostCondition
|
||||
);
|
||||
}
|
||||
|
||||
export const useLocalTransactionInputsState = () => useAtom(localStacksTransactionInputsState);
|
||||
export function useLocalTransactionInputsState() {
|
||||
return useAtom(localStacksTransactionInputsState);
|
||||
}
|
||||
|
||||
export const useTxForSettingsState = () => useAtom(txForSettingsState);
|
||||
export function useSendFormUnsignedTxState() {
|
||||
return useAtomValue(sendFormUnsignedTxState);
|
||||
}
|
||||
|
||||
export const useTxByteSizeState = () => useAtom(txByteSize);
|
||||
/**
|
||||
* @deprecated
|
||||
* Do not use implicit state-driven atom to get a "active" `StacksTransaction`
|
||||
* This atom uses the presence of `pendingTransaction` to determine whether the
|
||||
* "current" tx is either from a dApp, or the send form.
|
||||
*
|
||||
* Instead, be explicit when dealing with transaction broadcasts.
|
||||
*/
|
||||
export function useUnsignedTxForSettingsState() {
|
||||
return useAtom(txForSettingsState);
|
||||
}
|
||||
|
||||
export function useTxByteSizeState() {
|
||||
return useAtom(txByteSize);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ const baseConfig = require('./webpack.config.base');
|
||||
|
||||
const config = {
|
||||
...baseConfig,
|
||||
devtool: 'eval-source-map',
|
||||
devtool: 'eval-cheap-module-source-map',
|
||||
mode: 'development',
|
||||
output: {
|
||||
...baseConfig.output,
|
||||
|
||||
@@ -16025,7 +16025,12 @@ typeforce@^1.11.3, typeforce@^1.11.5:
|
||||
resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.18.0.tgz#d7416a2c5845e085034d70fcc5b6cc4a90edbfdc"
|
||||
integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==
|
||||
|
||||
typescript@4.4.4, typescript@^4.1.2:
|
||||
typescript@4.5.2:
|
||||
version "4.5.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.2.tgz#8ac1fba9f52256fdb06fb89e4122fa6a346c2998"
|
||||
integrity sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==
|
||||
|
||||
typescript@^4.1.2:
|
||||
version "4.4.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c"
|
||||
integrity sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==
|
||||
|
||||
Reference in New Issue
Block a user