# NiceTry: A Frame Transaction-ready Post Quantum Wallet

> Post-quantum security on EVM chains is increasingly moving through account abstraction. As proposals mature, no other practical path has emerged.
While building NiceTry, the reference implementation of our Ephemeral Keys Protocol, we began testing frame transactions as a possible next step for AA.
This article explains how NiceTry works under frame transactions and how it differs from the ERC-4337 version.

- Author: Alessandro Baiocchi
- Published: May 21, 2026
- Tag: Post-Quantum
- URL: https://www.riva.xyz//blog/nicetry-a-frame-transaction-ready-post-quantum-wallet

---

# NiceTry: A Frame Transaction-ready Post Quantum Wallet 

## Introduction

When looking at Ethereum and other EVM chains, it seems clear that post-quantum security runs through account abstraction. Most teams working on these problems have converged on this, and as more concrete proposals appear, no other path looks viable.

As we build our post-quantum Ethereum wallet infra 'NiceTry', which is the reference implementation of our [Ephemeral Keys Protocol](https://github.com/RivaLabs-Core/Ephemeral-Keys-Protocol), we started experimenting with what could be the future of account abstraction: [frame transactions](https://x.com/riva_labs/status/2037174745460851000). 

In this article we cover how NiceTry works under frame transactions, and how the implementation differs from the canonical ERC-4337 version.

## What is NiceTry

NiceTry is a wallet infrastructure project that acts as the reference implementation of our ephemeral-key post-quantum protocol. 

Following the core design, the user's account address stays stable, while the signing key changes over time. Each transaction is authorized by a short-lived key, and successful validation must force the account to move to the next key. The [current NiceTry implementation](https://github.com/RivaLabs-Core/NiceTry) uses **FORS+C**, a hash-based signature, as the primary signature scheme.

In the ERC-4337 version, NiceTry is implemented as a smart account whose `validateUserOp` checks a FORS signature and rotates the account owner during validation. With frame transactions this logic changes, and instead of `UserOperation`, `EntryPoint`, and `validateUserOp`, the account expresses its validation logic inside the frame transaction model.

The NiceTry frame account design keeps the same security goal, but implements it differently to match the frame transaction's standard: validation checks the FORS signature, then requires the very next frame to rotate the signer.

## What are Frame Transactions

[EIP-8141](https://eips.ethereum.org/EIPS/eip-8141) introduces a new transaction type built from a sequence of **frames**. Instead of one transaction containing one top-level call, a frame transaction contains multiple framed calls, each with its own mode, target, gas limit, value, and calldata.

Conceptually, a frame transaction looks like this:

```text
tx = {
  sender,
  nonce,
  frames: [
    [mode, flags, target, gas_limit, value, data],
    ...
  ],
  fee fields
}
```

The modes relevant to NiceTry are:

```text
VERIFY   validate the transaction
SENDER   execute as the transaction sender
DEFAULT  execute through the frame transaction entry context
```

The important change is that account validation becomes native. A smart account can define its own validation logic in a `VERIFY` frame, instead of relying on an ECDSA signature over a normal transaction or on ERC-4337's `validateUserOp`.

This is the key difference for NiceTry: the `VERIFY` frame can approve execution, but it cannot directly rotate the signer because the `VERIFY` frame itself is bound to be static: it can execute any code that is necessary to verify the signature and read all necessary storage, but cannot change the state in doing so. Rotation has to modify the state by updating the contract's owner, therefore has to happen in a later execution frame, and validation must ensure that this later frame exists.

## ERC-4337 Account vs Frame Account

The ERC-4337 account and the frame account enforce the same high-level rule, but the execution model is different.

| Area | ERC-4337 NiceTry Account | Frame NiceTry Account |
| --- | --- | --- |
| Transaction object | `UserOperation` | Native frame transaction |
| Validation entry point | `validateUserOp` called by `EntryPoint` | `VERIFY` frame |
| Execution entry point | Account `execute` / calldata from the `UserOperation` | Later `SENDER` frames |
| Signature checked against | `userOpHash` | Frame transaction signature hash |
| Rotation timing | Can happen during account validation | Cannot happen in `VERIFY`; must happen in a later frame |
| Approval mechanism | Return value / EntryPoint validation semantics | `APPROVE` instruction |
| User execution dependency | EntryPoint executes after validation | `SENDER` frames execute only after approval |
| Frame inspection | Not applicable | Account can inspect the next frame before approval |

In ERC-4337, signer rotation can be part of `validateUserOp`. Validation happens before user execution, so if user execution later fails, the signer has still rotated.

In frame transactions a`VERIFY` frame is static, so the account cannot update `owner` during validation. If NiceTry only checked a FORS signature and called `APPROVE`, it would approve execution without forcing rotation, breaking the ephemeral-key invariant.

The frame account therefore uses a different pattern:

```text
Frame 0: VERIFY     -     check FORS signature and require Frame 1 to be rotation
Frame 1: SENDER     -     call rotateOwner(nextOwner)
Frame 2+: SENDER    -     user actions
```

## Rotation Invariant

The security invariant is:

```text
No frame transaction should be approved unless signer rotation is already scheduled as the next frame.
```

NiceTry enforces that invariant inside the frame account validation path.

During the `VERIFY` frame, the account first checks the FORS signature against the current `owner`. If the signature is invalid, validation fails.

If the signature is valid, the account does not immediately approve the transaction. It inspects the next frame and requires it to be the rotation frame.

The next frame must satisfy all of these checks:

```text
mode      == SENDER
target    == address(this)
value     == 0
atomic    == false
selector  == rotateOwner(address)
nextOwner != address(0)
```

In plain English: the frame immediately after validation must be a self-call from the account to `rotateOwner(address)`, with no ETH transfer, no atomic batching, and a nonzero replacement owner.

Only after those checks pass does validation approve execution and payment.

The resulting flow is:

```text
1. VERIFY frame runs on the NiceTry frame account.
2. Account verifies the FORS signature.
3. Account inspects frame N + 1.
4. Account confirms frame N + 1 is exactly rotateOwner(nextOwner).
5. Account approves execution/payment.
6. The next SENDER frame rotates the owner.
7. User execution frames run after rotation.
```

The interaction looks like this:

```mermaid
sequenceDiagram
    autonumber
    participant Tx as Frame transaction
    participant VM as Frame execution
    participant Account as NiceTry FrameAccount
    participant Verifier as FORS verifier
    participant UserCalls as User execution frames

    Tx->>VM: Start frame execution

    rect rgb(235, 245, 255)
        Note over VM,Verifier: VERIFY frame
        VM->>Account: Run VERIFY frame with FORS signature
        Account->>Verifier: Recover signer from txSigHash
        Verifier-->>Account: recovered signer
        Account->>Account: Compare recovered signer with current owner
        Account->>VM: Inspect frame N + 1
        VM-->>Account: mode, target, value, flags, calldata
        Account->>Account: Require SENDER self-call to rotateOwner(nextOwner)
        Account->>VM: APPROVE execution/payment
    end

    rect rgb(238, 248, 238)
        Note over VM,Verifier: Rotation SENDER frame
        VM->>Account: Run next SENDER frame
        Account->>Account: rotateOwner(nextOwner)
    end

    rect rgb(248, 248, 248)
        Note over VM,UserCalls: Later user frames
        VM->>UserCalls: Run user frames after rotation
    end
```

This interaction flow preserves the important property from the ERC-4337 design: even if later user execution fails, the signer rotation has already happened. The used FORS key is not left as the account's active key for a future transaction.

Therefore, the frame account is not relying on wallet discipline or off-chain convention, and it is not saying "_clients should include a rotation frame._" What it does is making rotation a validation requirement, and if the transaction does not put rotation immediately after verification, the account refuses to approve the transaction.

One small side effect of this change of paradigm is that, as of the current EIP-8141 spec state, this kind of account cannot be used as a paymaster, as it would not be able to add the rotation frame for itself to the user’s transaction.

## Conclusions

Frame transactions do not remove the need for smart account logic, instead, they move the account validation surface closer to the protocol. For NiceTry, that is exactly the interesting part: the same ephemeral-key invariant can be expressed without relying on ERC-4337's `EntryPoint` flow.

The main difference is that signer rotation cannot happen inside the `VERIFY` frame itself, because `VERIFY` execution is bound to be static. The account has to split validation and rotation into two phases: first prove that the transaction is authorized, then require that the next executable frame performs the rotation.

The resulting model is simple:

```text
VERIFY -> mandatory rotateOwner(nextOwner) -> user execution
```

We believe that native account abstraction is a step forward for Ethereum, eliminating the need for a heavy infrastructure surrounding current account abstraction. The current state of the EIP-8141 proposal brings some restrictions to the possibilities of the validation flow, which as we've shown in this article require a different account paradigm to keep invariants intact.
