ERC-8339: Two-Phase Asset Transfers

Hi all,

I am proposing a standard for two-phase transfers: the sender initiates, the asset is locked in
an escrow and bound to a named receiver, and nothing settles until that receiver accepts. Until
someone accepts, the sender can revoke at any time. After a deadline, the sender can reclaim.

Draft: Add ERC: Two-Phase Asset Transfers by muhammadaus · Pull Request #1882 · ethereum/ERCs · GitHub

The problem

A normal transfer is one-sided. The sender acts, the receiver does nothing, and a mistyped or
poisoned address owns the funds the moment the transaction lands. Address books and smart
wallets help the people who use them, but the root cause is that the receiver never has a say.

What the standard specifies

  • A standard escrow interface (ITwoPhaseEscrow) with one lifecycle: initiateTransfer
    acceptTransfer (receiver only) → settle, plus revokeTransfer (sender, any time while
    pending) and reclaimExpired (sender, after expiry).
  • It works with assets exactly as deployed today: native ETH and any ERC-20, ERC-721, or
    ERC-1155, using only the approval and transfer mechanics every token already has. No asset
    contract changes, ever. One escrow deployment and one wallet integration cover every asset
    class. Discoverable via ERC-165.
  • A mandatory second factor. The sender generates a throwaway secp256k1 key and hands it to
    the receiver off-chain, only after the receiver confirms the address is really theirs.
    Accepting requires the receiver’s own key AND a signature made with the secret key. This
    stops the one case receiver-acceptance alone does not: an active stranger at a wrong address
    who sees the pending transfer and accepts it. It is mandatory because users would skip an
    optional secret exactly when they misjudge that risk; a sender who wants no second factor
    can simply hand the secret over together with the transfer id.

Token-native interfaces are not part of this draft. Newly deployed tokens could build the same
lifecycle in directly, which makes things easier for their users: no approval step, no external
contract. That is a real option and I suggest standardizing it as a separate follow-up ERC. The
escrow comes first because it works with every asset that already exists today.

The design decision I most want feedback on

The obvious secret design (store a hash, receiver submits the raw preimage) is broken
in the mempool: the reveal code sits in calldata and is public to every mempool watcher the
moment the accept is broadcast, even if the transaction reverts. A receiver who submits from
the wrong account by mistake burns the secret publicly and gets nothing.

So the draft instead proves the secret by signature. The secret is a private key; the receiver signs keccak256(abi.encode(chainid, escrow, transferId, msg.sender)) with it. The raw key never appears on-chain, mined or reverted. An observed signature cannot be replayed (it names
the caller’s account) and cannot be inverted into the key. Cost is one ecrecover.

Prior art

ERC-1996 (Holdable Token), ERC-2020, and ERC-5528 touched adjacent ground as token extensions and stalled. This draft differs by standardizing the escrow instead, so it covers native ETH and every already-deployed token with zero changes to any of them, and by specifying the mempool-safe second factor.

Questions for the group

  1. Are the RECOMMENDED expiry bounds (10 minutes to 7 days) reasonable defaults?
  2. Any holes in the signature-based accept proof I have missed?

Reference implementation (Foundry, full test suite) is in the PR’s assets directory. Feedback
welcome.