Drop and Replace
What is Drop and Replace?
If fees change and your user operation gets stuck in the mempool, you can use drop and replace to resend the user operation with higher fees.
Here’s a quick example of how to use the useDropAndReplaceUserOperation()
hook to drop and replace a user operation.
import React from "react";
import { class ViewView, const Pressable: React.ForwardRefExoticComponent<PressableProps & React.RefAttributes<View>>Pressable, class TextText } from "react-native";
import {
function useDropAndReplaceUserOperation<TEntryPointVersion extends GetEntryPointFromAccount<TAccount>, TAccount extends SupportedAccounts = SupportedAccounts>({ client, ...mutationArgs }: UseDropAndReplaceUserOperationArgs<TEntryPointVersion, TAccount>): UseDropAndReplaceUserOperationResult<TEntryPointVersion, TAccount>Custom hook that handles the drop and replace user operation for a given client and mutation arguments.
useDropAndReplaceUserOperation,
function useSendUserOperation<TEntryPointVersion extends GetEntryPointFromAccount<TAccount>, TAccount extends SupportedAccounts = SupportedAccounts>(params: UseSendUserOperationArgs<TEntryPointVersion, TAccount>): UseSendUserOperationResult<TEntryPointVersion, TAccount>A hook that returns functions for sending user operations. You can also optionally wait for a user operation to be mined and get the transaction hash before returning using waitForTx
. Like any method that takes a smart account client, throws an error if client undefined or is signer not authenticated.
useSendUserOperation,
function useSmartAccountClient<TChain extends Chain | undefined = Chain | undefined, TAccount extends SupportedAccountTypes | undefined = "ModularAccountV2">(args: UseSmartAccountClientProps<TChain, TAccount>): UseSmartAccountClientResult<TChain, SupportedAccount<TAccount extends undefined ? "ModularAccountV2" : TAccount>>useSmartAccountClient,
} from "@account-kit/react-native";
export function function ComponentWithDropAndReplaceUO(): JSX.ElementComponentWithDropAndReplaceUO() {
const { const client: {
account: ModularAccountV2<AlchemySigner>;
batch?: {
multicall?: boolean | Prettify<MulticallBatchOptions> | undefined;
} | undefined;
... 84 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
} | undefinedclient } = useSmartAccountClient<Chain | undefined, "ModularAccountV2">(args: UseSmartAccountClientProps<Chain | undefined, "ModularAccountV2">): UseSmartAccountClientResult<Chain | undefined, ModularAccountV2<...>>useSmartAccountClient({});
const { const sendUserOperationAsync: UseMutateAsyncFunction<SendUserOperationWithEOA<keyof EntryPointRegistryBase<unknown>>, Error, SendUserOperationParameters<SupportedAccounts>, unknown>sendUserOperationAsync, const isSendingUserOperation: booleanisSendingUserOperation } =
useSendUserOperation<keyof EntryPointRegistryBase<unknown>, SupportedAccounts>(params: UseSendUserOperationArgs<keyof EntryPointRegistryBase<unknown>, SupportedAccounts>): UseSendUserOperationResult<...>A hook that returns functions for sending user operations. You can also optionally wait for a user operation to be mined and get the transaction hash before returning using waitForTx
. Like any method that takes a smart account client, throws an error if client undefined or is signer not authenticated.
useSendUserOperation({
client: {
account: SupportedAccounts;
batch?: {
multicall?: boolean | Prettify<MulticallBatchOptions> | undefined;
} | undefined;
... 84 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
} | {
...;
} | {
...;
} | {
...;
} | undefinedclient,
});
const { const dropAndReplaceUserOperation: UseMutateFunction<SendUserOperationResult<keyof EntryPointRegistryBase<unknown>>, Error, DropAndReplaceUserOperationParameters<SupportedAccounts>, unknown>dropAndReplaceUserOperation, const isDroppingAndReplacingUserOperation: booleanisDroppingAndReplacingUserOperation } =
useDropAndReplaceUserOperation<keyof EntryPointRegistryBase<unknown>, SupportedAccounts>({ client, ...mutationArgs }: UseDropAndReplaceUserOperationArgs<keyof EntryPointRegistryBase<unknown>, SupportedAccounts>): UseDropAndReplaceUserOperationResult<...>Custom hook that handles the drop and replace user operation for a given client and mutation arguments.
useDropAndReplaceUserOperation({
client: {
account: SupportedAccounts;
batch?: {
multicall?: boolean | Prettify<MulticallBatchOptions> | undefined;
} | undefined;
... 84 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
} | {
...;
} | {
...;
} | {
...;
} | undefinedclient,
onSuccess?: ((data: SendUserOperationResult<keyof EntryPointRegistryBase<unknown>>, variables: DropAndReplaceUserOperationParameters<SupportedAccounts>, context: unknown) => Promise<unknown> | unknown) | undefinedonSuccess: ({ hash: `0x${string}`hash, request: UserOperationRequest<keyof EntryPointRegistryBase<unknown>>request }) => {
// [optional] Do something with the hash and request
},
onError?: ((error: Error, variables: DropAndReplaceUserOperationParameters<SupportedAccounts>, context: unknown) => Promise<unknown> | unknown) | undefinedonError: (error: Errorerror) => {
// [optional] Do something with the error
},
// [optional] ...additional mutationArgs
});
return (
<class ViewView>
<const Pressable: React.ForwardRefExoticComponent<PressableProps & React.RefAttributes<View>>Pressable
PressableProps.onPress?: ((event: GestureResponderEvent) => void) | null | undefinedCalled when a single tap gesture is detected.
onPress={async () => {
const { const request: UserOperationRequest<keyof EntryPointRegistryBase<unknown>> | undefinedrequest } = await const sendUserOperationAsync: (variables: SendUserOperationParameters<SupportedAccounts>, options?: MutateOptions<SendUserOperationWithEOA<keyof EntryPointRegistryBase<unknown>>, Error, SendUserOperationParameters<...>, unknown> | undefined) => Promise<...>sendUserOperationAsync({
uo: UserOperationCallData | BatchUserOperationCallDatauo: {
target: `0x${string}`target: "0xTARGET_ADDRESS",
data: `0x${string}`data: "0x",
value?: bigint | undefinedvalue: 0n,
},
});
const dropAndReplaceUserOperation: (variables: DropAndReplaceUserOperationParameters<SupportedAccounts>, options?: MutateOptions<SendUserOperationResult<keyof EntryPointRegistryBase<unknown>>, Error, DropAndReplaceUserOperationParameters<...>, unknown> | undefined) => voiddropAndReplaceUserOperation({
uoToDrop: UserOperationRequest<keyof EntryPointRegistryBase<unknown>>uoToDrop: const request: UserOperationRequest<keyof EntryPointRegistryBase<unknown>> | undefinedrequest,
});
}}
PressableProps.disabled?: boolean | null | undefinedWhether the press behavior is disabled.
disabled={const isSendingUserOperation: booleanisSendingUserOperation || const isDroppingAndReplacingUserOperation: booleanisDroppingAndReplacingUserOperation}
>
<class ViewView>
<class TextText>
{const isSendingUserOperation: booleanisSendingUserOperation
? "Sending..."
: const isDroppingAndReplacingUserOperation: booleanisDroppingAndReplacingUserOperation
? "Replacing..."
: "Send then Replace UO"}
</class TextText>
</class ViewView>
</const Pressable: React.ForwardRefExoticComponent<PressableProps & React.RefAttributes<View>>Pressable>
</class ViewView>
);
}
You can also build a more complex retry logic in a case you want more control over how many times you want to retry a failed user operation.