# Fast-Forward Nonce

> Advance a personal account's memo-instruction nonce when it is stuck after a partial or abandoned direct-mint flow, using the 0xE1 opcode with Viem.

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.

Source: https://dev.flare.network/smart-accounts/guides/typescript-viem/fast-forward-nonce-ts

This guide walks through advancing a personal account's **memo-instruction nonce** when it is stuck after a partial or abandoned [direct mint](/fassets/direct-minting) flow.

The recovery path uses the memo opcode **`0xE1` (Fast-forward nonce)** to jump the nonce forward on Flare **without** executing a user operation. A small amount of FXRP is minted alongside the flag because fee-only direct mints revert on-chain.

Read the [Custom Instruction TypeScript guide](/smart-accounts/guides/typescript-viem/custom-instruction-ts) first if you are not familiar with [`getNonce`](/smart-accounts/reference/IMasterAccountController#getnonce) and the `0xFE` hash-commitment flow. If FXRP is still stuck at the [Core Vault](/fassets/core-vault) because the mint never finalized, complete the [`0xE0` recovery](/smart-accounts/guides/typescript-viem/recover-stuck-mint-transaction-ts) before using `0xE1`.

The full code is available on [GitHub](https://github.com/flare-foundation/flare-viem-starter/blob/main/src/fast-forward-nonce.ts).

info

The code in this guide is set up for the Coston2 testnet. Despite that, we refer to the network as Flare and its currency as FLR, rather than Coston2 and C2FLR.

## Usage of `0xE1`[​](#usage-of-0xe1 "Direct link to usage-of-0xe1")

`0xE1` solves a specific stuck state: FXRP was minted (or the payment was recovered), but the memo-instruction nonce did not advance because the original `PackedUserOperation` was never executed.

Situation

Action

Stuck payment **not yet minted** on Flare (`isTransactionIdUsed == false`)

Use [`0xE0`](/smart-accounts/guides/typescript-viem/recover-stuck-mint-transaction-ts) first — not `0xE1`

After `0xE0` recovery, nonce still points at an abandoned UserOp

Use `0xE1` to skip past it before sending a fresh `0xFE` or `0xFF` instruction

Nonce already advanced by a successful [`UserOperationExecuted`](/smart-accounts/reference/IMasterAccountController#useroperationexecuted)

Read [`getNonce`](/smart-accounts/reference/IMasterAccountController#getnonce) and build the next UserOp — `0xE1` is not needed

A common trigger is [`0xE0` skip-memo recovery](/smart-accounts/custom-instruction#recovery-after-a-failed-mint): the controller mints FXRP from the stuck payment but deliberately skips the original user operation, leaving the nonce unchanged.

## Memo-Instruction Nonce[​](#memo-instruction-nonce "Direct link to Memo-Instruction Nonce")

Each XRPL-linked personal account maintains a monotonic **memo-instruction nonce** exposed by [`getNonce(personalAccount)`](/smart-accounts/reference/IMasterAccountController#getnonce).

Every `0xFE` or `0xFF` user operation must embed `PackedUserOperation.nonce == getNonce(personalAccount)`. On successful execution, the contract emits [`UserOperationExecuted`](/smart-accounts/reference/IMasterAccountController#useroperationexecuted) and increments the nonce.

When a direct mint finalizes without running the user operation — for example after `0xE0` recovery or when an executor never submitted the original payment — the nonce stays at the abandoned slot. Any new payment built with the current `getNonce` still references that slot semantically, even though the original UserOp will never run.

`0xE1` explicitly jumps the counter forward (for example `N → N+1`) so you can build a fresh user operation.

## Flow Diagram[​](#flow-diagram "Direct link to Flow Diagram")

The canonical flow has two steps:

1.  **User side** — XRPL `Payment` to the [direct minting payment address](/fassets/reference/IAssetManager#directmintingpaymentaddress) with a `0xE1` memo (requires a positive net mint).
2.  **Executor side** — finalize via [`executeDirectMintingWithData`](/fassets/reference/IAssetManager#executedirectmintingwithdata) with `_data = "0x"` because no user-operation bytes are needed.

## Memo Layout[​](#memo-layout "Direct link to Memo Layout")

The `0xE1` fast-forward instruction uses the same 42-byte header shape as `0xE0`, `0xFE`, and `0xFF`:

`[0xE1 | walletId(1B) | executorFeeUBA(8B) | newNonce(32B)]`

`sendFastForwardNonceInstruction` encodes this memo and sends an XRPL `Payment`:

```
fastForward = await sendFastForwardNonceInstruction({  label: "fast-forward-nonce",  newNonce: targetNewNonce,  personalAccount,  xrplClient,  xrplWallet,  netMintAmountXrp: 1,});
```

The gross XRP amount equals the net mint plus minting and executor fees, computed by the `computeDirectMintingPaymentAmountXrp` function.

No destination tags

XRPL payments targeting smart accounts must not use a destination tag.

## On-Chain Rules[​](#on-chain-rules "Direct link to On-Chain Rules")

Before sending the XRPL payment, validate the target nonce client-side with `assertValidNonceIncrease`:

-   `newNonce` must be **strictly greater** than the current nonce.
-   The jump `newNonce - currentNonce` must not exceed `type(uint32).max`.

On-chain, an invalid increase reverts with [`InvalidNonceIncrease`](/smart-accounts/reference/IMasterAccountController#invalidnonceincrease).

The payment must carry a **positive net mint** amount. Fee-only direct mints revert — the `0xE1` flag must ride on a payment that mints FXRP.

On success, the receipt contains a [`NonceIncreased`](/smart-accounts/reference/IMasterAccountController#nonceincreased) event and **no** [`UserOperationExecuted`](/smart-accounts/reference/IMasterAccountController#useroperationexecuted) event.

## Finalize on Flare[​](#finalize-on-flare "Direct link to Finalize on Flare")

The executor fetches an FDC [`XRPPayment`](/fdc/attestation-types/xrp-payment) proof for the `0xE1` payment and calls `executeDirectMintingWithData` with empty `_data`:

```
({ receipt } = await executeDirectMintingWithData({  xrplTransactionHash: fastForward.xrplTransactionHash,  data: "0x",  value: 0n,  xrplClient,  label: "fast-forward-executor",  reuseExistingMint: true,}));
```

Verify the nonce advance from the receipt and on-chain state:

```
const nonceIncreased = findNonceIncreased(  receipt,  personalAccount,  targetNewNonce,);const nonceAfter = await getNonce(personalAccount);
```

After `0xE1`, build your next `0xFE` or `0xFF` user operation with `nonce == nonceAfter`.

## Full Recovery Context (Demo)[​](#full-recovery-context-demo "Direct link to Full Recovery Context (Demo)")

The example script can run an end-to-end demonstration on Coston2:

1.  **Abandoned `0xFE` payment** — send a direct-mint payment with a user operation but deliberately skip the executor call so XRP sits at the Core Vault.
2.  **`0xE0` recovery** — recover FXRP from the stuck payment without running the UserOp (nonce unchanged).
3.  **`0xE1` fast-forward** — advance the nonce past the abandoned slot.

For step 2 only, see the dedicated [Recover Stuck Mint Transaction guide](/smart-accounts/guides/typescript-viem/recover-stuck-mint-transaction-ts).

## Relayer Edge Cases[​](#relayer-edge-cases "Direct link to Relayer Edge Cases")

The script handles race conditions common on testnet:

1.  **`0xE1` payment already finalized** — if a relayer minted the payment while you were waiting, `isStuckTransactionIdUsed` returns `true` for the `0xE1` tx ID and the script loads the existing receipt via `findDirectMintingReceiptForTransactionId` instead of resubmitting.
    
2.  **Stuck payment already minted before `0xE0`** — if the original payment was finalized during recovery, the script skips the `0xE0` path and proceeds directly to `0xE1` when appropriate.
    

Both paths use `reuseExistingMint: true` so `PaymentAlreadyConfirmed` errors from duplicate submissions are handled gracefully.

## Full Script[​](#full-script "Direct link to Full Script")

The repository with the example is available on [GitHub](https://github.com/flare-foundation/flare-viem-starter). Helper functions live in the `src/utils` directory.

<details>
<summary>src/fast-forward-nonce.ts</summary>

src/fast-forward-nonce.ts

src/fast-forward-nonce.ts

```
/** * Fast-forward the personal account's memo-instruction nonce using opcode `0xE1`. * * `0xE1` advances `getNonce(personalAccount)` when it is stuck after a partial or * abandoned direct-mint flow — for example after `0xE0` skip-memo recovery, which * mints FXRP without running the original UserOp and leaves the nonce unchanged. * * Memo layout (42 bytes): *   [0xE1 | walletId(1B) | executorFeeUBA(8B) | newNonce(32B)] * * On-chain rules: *   - `newNonce` must be strictly greater than the current nonce. *   - The jump `newNonce - currentNonce` must not exceed `type(uint32).max`. *   - Emits `NonceIncreased(personalAccount, newNonce)` — no UserOp runs. * * Canonical flow: *   1. USER SIDE      — XRPL payment with 0xE1 memo (requires positive net mint). *   2. EXECUTOR SIDE  — finalize via `executeDirectMintingWithData(proof, "0x")`. * * When to use `0xE1` vs `0xE0`: *   - Stuck payment not yet minted (`isTransactionIdUsed == false`) → use *     `recover-direct-mint-transaction.ts` (`0xE0`) first, not this script. *   - After `0xE0` recovery, nonce still points at an abandoned UserOp → use `0xE1` *     to skip past it before sending a fresh `0xFE`/`0xFF` instruction. *   - Nonce already advanced by a successful `UserOperationExecuted` → just read *     `getNonce` and build the next UserOp; `0xE1` is not needed. * * Usage: *   # Full demo: create abandoned 0xFE payment → 0xE0 recover → 0xE1 fast-forward *   pnpm run script src/fast-forward-nonce.ts * *   # 0xE1 only (no demo setup; advances current nonce by 1) *   E1_ONLY=1 pnpm run script src/fast-forward-nonce.ts * *   # Resume with an existing stuck payment (runs 0xE0 then 0xE1) *   STUCK_XRPL_TX_HASH=<hash> STUCK_USER_OP_DATA=0x... pnpm run script src/fast-forward-nonce.ts * *   # Resume after the 0xE0 recovery payment was already sent *   STUCK_XRPL_TX_HASH=<hash> RECOVERY_XRPL_TX_HASH=<hash> pnpm run script src/fast-forward-nonce.ts * *   # Resume 0xE1 only (0xE0 already completed) *   FAST_FORWARD_XRPL_TX_HASH=<hash> TARGET_NEW_NONCE=<n> pnpm run script src/fast-forward-nonce.ts * * Required in .env: XRPL_SEED, XRPL_TESTNET_RPC_URL, PRIVATE_KEY (executor calls on Flare). */import { encodeFunctionData, type Address } from "viem";import { Client, Wallet } from "xrpl";import { abi as checkpointAbi } from "./abis/Checkpoint";import { computeDirectMintingPaymentAmountXrp } from "./utils/fassets";import { getXrpBalance } from "./utils/xrpl";import {  assertValidNonceIncrease,  diagnoseStuckDirectMint,  executeDirectMintingWithData,  findDirectMintingReceiptForTransactionId,  findIgnoreMemoSet,  findNonceIncreased,  getNonce,  getPersonalAccountAddress,  isStuckTransactionIdUsed,  logPersonalAccountFxrpCredit,  normalizeXrplTransactionId,  sendFastForwardNonceInstruction,  sendHashInstruction,  sendSkipMemoInstruction,  type Call,} from "./utils/smart-accounts";// Net XRP for demo stuck payment only (ignored when STUCK_XRPL_TX_HASH is set).const DEMO_STUCK_MINT_XRP = 10;// Net XRP for the 0xE0 recovery payment.const RECOVERY_NET_MINT_XRP = 1;// Net XRP for the 0xE1 fast-forward payment.const NET_MINT_XRP = 1;const XRPL_SEED = process.env.XRPL_SEED!;function parseTargetNewNonce(  envValue: string | undefined,  currentNonce: bigint,): bigint {  if (envValue?.trim()) {    return BigInt(envValue.trim());  }  console.warn(    "TARGET_NEW_NONCE not set — using current nonce + 1. " +      "This costs a small XRP direct mint plus executor gas on Flare.\n",  );  return currentNonce + 1n;}async function runE0Recovery({  stuckXrplTxHash,  stuckUserOpData,  personalAccount,  xrplClient,  xrplWallet,  recoveryNetMintAmountXrp,}: {  stuckXrplTxHash: string;  stuckUserOpData: `0x${string}` | undefined;  personalAccount: Address;  xrplClient: Client;  xrplWallet: Wallet;  recoveryNetMintAmountXrp: number;}): Promise<void> {  const diagnosis = await diagnoseStuckDirectMint({    stuckXrplTxHash,    personalAccount,  });  if (diagnosis.transactionIdUsed) {    console.log(      "Stuck transaction already minted on Flare — skipping 0xE0 (nonce may already be recoverable via 0xE1).\n",    );    return;  }  const recoveryPaymentAmountXrp = await computeDirectMintingPaymentAmountXrp({    netMintAmountXrp: recoveryNetMintAmountXrp,  });  const xrpBalance = await getXrpBalance(xrplWallet.address, xrplClient);  const existingRecoveryXrplTxHash = process.env.RECOVERY_XRPL_TX_HASH?.trim();  if (!existingRecoveryXrplTxHash && xrpBalance < recoveryPaymentAmountXrp) {    throw new Error(      `Insufficient XRP for 0xE0 recovery payment on ${xrplWallet.address}: ` +        `have ${xrpBalance} XRP, need ${recoveryPaymentAmountXrp} XRP`,    );  }  console.log("=== RECOVERY: 0xE0 skip-memo flow ===\n");  console.log(    "0xE0 recovers FXRP from the stuck payment but does not run the UserOp — " +      "the memo nonce stays at",    diagnosis.nonce,    "\n",  );  let skipMemo: { xrplTransactionHash: string; targetTxId: `0x${string}` };  if (existingRecoveryXrplTxHash) {    console.log(      "Using existing recovery XRPL payment from RECOVERY_XRPL_TX_HASH:",      existingRecoveryXrplTxHash,      "\n",    );    skipMemo = {      xrplTransactionHash: existingRecoveryXrplTxHash,      targetTxId: normalizeXrplTransactionId(stuckXrplTxHash),    };  } else {    skipMemo = await sendSkipMemoInstruction({      label: "skip-memo",      targetXrplTxHash: stuckXrplTxHash,      personalAccount,      xrplClient,      xrplWallet,      recoveryNetMintAmountXrp,    });  }  const targetTxId = skipMemo.targetTxId;  const recoveryPaymentTxId = normalizeXrplTransactionId(    skipMemo.xrplTransactionHash,  );  const skipMemoExecutorLabel = "skip-memo-executor";  let recoveryReceipt;  if (await isStuckTransactionIdUsed(recoveryPaymentTxId)) {    console.log(      "Recovery XRPL payment already minted on Flare — loading existing receipt.\n",    );    recoveryReceipt = await findDirectMintingReceiptForTransactionId(      recoveryPaymentTxId,      {        label: skipMemoExecutorLabel,      },    );  } else {    ({ receipt: recoveryReceipt } = await executeDirectMintingWithData({      xrplTransactionHash: skipMemo.xrplTransactionHash,      data: "0x",      value: 0n,      xrplClient,      label: skipMemoExecutorLabel,      reuseExistingMint: true,    }));  }  const ignoreMemoSet = findIgnoreMemoSet(    recoveryReceipt,    personalAccount,    targetTxId,  );  console.log("IgnoreMemoSet event:", ignoreMemoSet, "\n");  logPersonalAccountFxrpCredit({    label: skipMemoExecutorLabel,    receipt: recoveryReceipt,    personalAccount,    xrplTransactionId: recoveryPaymentTxId,  });  const stillStuck = !(await isStuckTransactionIdUsed(diagnosis.targetTxId));  if (!stillStuck) {    console.log(      "Stuck transaction was minted during recovery — loading existing receipt.\n",    );    const stuckReceipt = await findDirectMintingReceiptForTransactionId(      diagnosis.targetTxId,      {        label: "stuck-retry",      },    );    logPersonalAccountFxrpCredit({      label: "stuck-retry",      receipt: stuckReceipt,      personalAccount,      xrplTransactionId: diagnosis.targetTxId,    });  } else {    const { receipt: stuckReceipt } = await executeDirectMintingWithData({      xrplTransactionHash: stuckXrplTxHash,      data: stuckUserOpData ?? "0x",      value: 0n,      xrplClient,      label: "stuck-retry",      reuseExistingMint: true,    });    logPersonalAccountFxrpCredit({      label: "stuck-retry",      receipt: stuckReceipt,      personalAccount,      xrplTransactionId: diagnosis.targetTxId,    });  }  const nonceAfterE0 = await getNonce(personalAccount);  console.log(    "Memo nonce after 0xE0 recovery (unchanged — UserOp was skipped):",    nonceAfterE0,    "\n",  );}async function runE1FastForward({  targetNewNonce,  personalAccount,  xrplClient,  xrplWallet,  netMintAmountXrp,}: {  targetNewNonce: bigint;  personalAccount: Address;  xrplClient: Client;  xrplWallet: Wallet;  netMintAmountXrp: number;}): Promise<void> {  const currentNonce = await getNonce(personalAccount);  console.log("Current memo nonce:", currentNonce, "\n");  console.log("Target new nonce:", targetNewNonce, "\n");  assertValidNonceIncrease(currentNonce, targetNewNonce);  const paymentAmountXrp = await computeDirectMintingPaymentAmountXrp({    netMintAmountXrp,  });  const xrpBalance = await getXrpBalance(xrplWallet.address, xrplClient);  console.log("XRPL wallet XRP balance:", xrpBalance, "\n");  const existingFastForwardXrplTxHash =    process.env.FAST_FORWARD_XRPL_TX_HASH?.trim();  if (!existingFastForwardXrplTxHash && xrpBalance < paymentAmountXrp) {    throw new Error(      `Insufficient XRP for 0xE1 payment on ${xrplWallet.address}: ` +        `have ${xrpBalance} XRP, need ${paymentAmountXrp} XRP`,    );  }  console.log("=== FAST-FORWARD NONCE: 0xE1 flow ===\n");  let fastForward: { xrplTransactionHash: string; newNonce: bigint };  if (existingFastForwardXrplTxHash) {    console.log(      "Using existing XRPL payment from FAST_FORWARD_XRPL_TX_HASH:",      existingFastForwardXrplTxHash,      "\n",    );    fastForward = {      xrplTransactionHash: existingFastForwardXrplTxHash,      newNonce: targetNewNonce,    };  } else {    fastForward = await sendFastForwardNonceInstruction({      label: "fast-forward-nonce",      newNonce: targetNewNonce,      personalAccount,      xrplClient,      xrplWallet,      netMintAmountXrp,    });  }  const fastForwardTxId = normalizeXrplTransactionId(    fastForward.xrplTransactionHash,  );  const executorLabel = "fast-forward-executor";  let receipt;  if (await isStuckTransactionIdUsed(fastForwardTxId)) {    console.log(      "0xE1 XRPL payment already minted on Flare — loading existing receipt.\n",    );    receipt = await findDirectMintingReceiptForTransactionId(fastForwardTxId, {      label: executorLabel,    });  } else {    ({ receipt } = await executeDirectMintingWithData({      xrplTransactionHash: fastForward.xrplTransactionHash,      data: "0x",      value: 0n,      xrplClient,      label: executorLabel,      reuseExistingMint: true,    }));  }  const nonceIncreased = findNonceIncreased(    receipt,    personalAccount,    targetNewNonce,  );  console.log("NonceIncreased event:", nonceIncreased, "\n");  const nonceAfter = await getNonce(personalAccount);  console.log("Memo nonce after fast-forward:", nonceAfter, "\n");  if (nonceAfter !== targetNewNonce) {    throw new Error(      `Expected nonce ${targetNewNonce} after 0xE1, but getNonce returned ${nonceAfter}`,    );  }  console.log(    "Nonce fast-forward complete. Build your next 0xFE or 0xFF UserOp with nonce:",    nonceAfter,    "\n",  );}async function main({  demoStuckMintAmountXrp,  recoveryNetMintAmountXrp,  netMintAmountXrp,  xrplSeed,}: {  demoStuckMintAmountXrp: number;  recoveryNetMintAmountXrp: number;  netMintAmountXrp: number;  xrplSeed: string;}) {  // --- Step 1: Setup XRPL client, wallet, and personal account ---------------  const xrplClient = new Client(process.env.XRPL_TESTNET_RPC_URL!);  const xrplWallet = Wallet.fromSeed(xrplSeed);  const personalAccount = await getPersonalAccountAddress(xrplWallet.address);  console.log("Personal account address:", personalAccount, "\n");  const e1Only = process.env.E1_ONLY === "1";  const fastForwardResume = process.env.FAST_FORWARD_XRPL_TX_HASH?.trim();  // --- Step 2: 0xE1-only shortcut (E1_ONLY or FAST_FORWARD_XRPL_TX_HASH) ---  if (fastForwardResume || e1Only) {    const currentNonce = await getNonce(personalAccount);    const targetNewNonce = parseTargetNewNonce(      process.env.TARGET_NEW_NONCE,      currentNonce,    );    await runE1FastForward({      targetNewNonce,      personalAccount,      xrplClient,      xrplWallet,      netMintAmountXrp,    });    return;  }  // --- Step 3: Resolve stuck payment (demo or STUCK_XRPL_TX_HASH) ------------  let stuckXrplTxHash = process.env.STUCK_XRPL_TX_HASH?.trim();  let stuckUserOpData = process.env.STUCK_USER_OP_DATA?.trim() as    | `0x${string}`    | undefined;  const nonceBeforeDemo = await getNonce(personalAccount);  console.log("Memo nonce before demo:", nonceBeforeDemo, "\n");  if (!stuckXrplTxHash) {    console.log("=== DEMO: creating abandoned 0xFE payment ===\n");    console.log(      "Sending a 0xFE direct-mint payment with UserOp nonce",      nonceBeforeDemo,      "but deliberately not calling the executor — XRP sits at the Core Vault.\n",    );    const checkpointAddress = "0xEE6D54382aA623f4D16e856193f5f8384E487002";    const customInstruction: Call[] = [      {        target: checkpointAddress,        value: 0n,        data: encodeFunctionData({          abi: checkpointAbi,          functionName: "passCheckpoint",          args: [],        }),      },    ];    const stuckPaymentAmountXrp = await computeDirectMintingPaymentAmountXrp({      netMintAmountXrp: demoStuckMintAmountXrp,    });    const recoveryPaymentAmountXrp = await computeDirectMintingPaymentAmountXrp(      {        netMintAmountXrp: recoveryNetMintAmountXrp,      },    );    const e1PaymentAmountXrp = await computeDirectMintingPaymentAmountXrp({      netMintAmountXrp,    });    const totalXrpNeeded =      stuckPaymentAmountXrp + recoveryPaymentAmountXrp + e1PaymentAmountXrp;    const xrpBalance = await getXrpBalance(xrplWallet.address, xrplClient);    console.log("XRPL wallet XRP balance:", xrpBalance, "\n");    if (xrpBalance < totalXrpNeeded) {      throw new Error(        `Insufficient XRP for full demo on ${xrplWallet.address}: ` +          `have ${xrpBalance} XRP, need ${totalXrpNeeded} XRP ` +          `(stuck ${stuckPaymentAmountXrp} + recovery ${recoveryPaymentAmountXrp} + 0xE1 ${e1PaymentAmountXrp})`,      );    }    const userSide = await sendHashInstruction({      label: "abandoned-demo",      customInstruction,      amountXrp: stuckPaymentAmountXrp,      personalAccount,      xrplClient,      xrplWallet,    });    stuckXrplTxHash = userSide.xrplTransactionHash;    stuckUserOpData = userSide.data;    console.log(      "Abandoned XRPL transaction hash (save for resume):",      stuckXrplTxHash,      "\n",    );    console.log("UserOp nonce embedded in that payment:", userSide.nonce, "\n");    if (userSide.nonce !== nonceBeforeDemo) {      throw new Error(        `Demo UserOp nonce ${userSide.nonce} does not match pre-demo nonce ${nonceBeforeDemo}`,      );    }  } else {    console.log(      "=== Using existing stuck payment from STUCK_XRPL_TX_HASH ===\n",    );    console.log("Stuck XRPL transaction hash:", stuckXrplTxHash, "\n");    if (!stuckUserOpData) {      console.warn(        "STUCK_USER_OP_DATA not set — assuming 0xFF stuck flow. " +          "For 0xFE stuck payments, set STUCK_USER_OP_DATA to the original PackedUserOperation bytes.\n",      );    }  }  // --- Step 4: 0xE0 skip-memo recovery ---------------------------------------  await runE0Recovery({    stuckXrplTxHash,    stuckUserOpData,    personalAccount,    xrplClient,    xrplWallet,    recoveryNetMintAmountXrp,  });  // --- Step 5: Compute target nonce for fast-forward ---------------------------  const nonceAfterE0 = await getNonce(personalAccount);  const targetNewNonce = process.env.TARGET_NEW_NONCE?.trim()    ? BigInt(process.env.TARGET_NEW_NONCE.trim())    : nonceAfterE0 + 1n;  console.log(    "After 0xE0 the nonce is still",    nonceAfterE0,    "— the abandoned UserOp at nonce",    nonceBeforeDemo,    "was never executed.",    "Fast-forwarding to",    targetNewNonce,    "with 0xE1.\n",  );  // --- Step 6: 0xE1 fast-forward nonce ---------------------------------------  await runE1FastForward({    targetNewNonce,    personalAccount,    xrplClient,    xrplWallet,    netMintAmountXrp,  });}void main({  demoStuckMintAmountXrp: DEMO_STUCK_MINT_XRP,  recoveryNetMintAmountXrp: RECOVERY_NET_MINT_XRP,  netMintAmountXrp: NET_MINT_XRP,  xrplSeed: XRPL_SEED,})  .then(() => process.exit(0))  .catch((error) => {    console.error(error);    process.exit(1);  });
```

</details>
<details>
<summary>Expected Output</summary>

Expected Output

```
Personal account address: 0xFd2f0eb6b9fA4FE5bb1F7B26fEE3c647ed103d9FMemo nonce before demo: 58=== DEMO: creating abandoned 0xFE payment ===[abandoned-demo] current nonce: 58nAbandoned XRPL transaction hash (save for resume): FEAC1ABDB81809293E023DB9345715FA3A27949B23132AB9A2417D8F99A876E9UserOp nonce embedded in that payment: 58n=== RECOVERY: 0xE0 skip-memo flow ===0xE0 recovers FXRP from the stuck payment but does not run the UserOp — the memo nonce stays at 58[skip-memo] 0xE0 skip-memo targeting: 0xfeac1abdb81809293e023db9345715fa3a27949b23132ab9a2417d8f99a876e9IgnoreMemoSet event: { ... }Memo nonce after 0xE0 recovery (unchanged — UserOp was skipped): 58After 0xE0 the nonce is still 58 — the abandoned UserOp at nonce 58 was never executed. Fast-forwarding to 59 with 0xE1.=== FAST-FORWARD NONCE: 0xE1 flow ===Current memo nonce: 58Target new nonce: 59[fast-forward-nonce] 0xE1 fast-forward nonce to: 59n[fast-forward-nonce] memo (42 bytes): 0xE1000000000000000000000000000000000000000000000000000000000000003bNonceIncreased event: {args: {personalAccount: '0xFd2f0eb6b9fA4FE5bb1F7B26fEE3c647ed103d9F',newNonce: 59n}}Memo nonce after fast-forward: 59Nonce fast-forward complete. Build your next 0xFE or 0xFF UserOp with nonce: 59
```

</details>

Exact fee and amount values depend on live `AssetManagerFXRP` parameters.

What's next

-   Walk through the happy-path `0xFE` flow in the [Custom Instruction TypeScript guide](/smart-accounts/guides/typescript-viem/custom-instruction-ts).
-   If FXRP is still stuck at the Core Vault, start with the [Recover Stuck Mint Transaction guide](/smart-accounts/guides/typescript-viem/recover-stuck-mint-transaction-ts) (`0xE0`) before fast-forwarding the nonce.
