WalletRead the docsLaunch app
New: verify in your browser

Recompute any closed batch from its ciphertexts and the drand beacon

The swap protocol

XMR and ETH change hands with no bridge and no wrapped coin. One secret per side ties the Monero key to the Ethereum contract.

Keys on two curves

Monero signs on ed25519. Ethereum checks keys on secp256k1. Each side draws one secret scalar below 2^252, which is a valid scalar on both curves. On ed25519 the secret gives a public key that becomes half of the swap's Monero spend key. On secp256k1 it gives an Ethereum address that the contract stores as a commitment.

The XMR is locked to a shared address whose public spend key is A + B (one half from each side) and whose view key is the sum of two view key shares, so both sides can watch it. Neither side can spend it alone. Whoever learns both secrets a and b holds the full spend key a + b.

The DLEq proof

Before locking anything, each side must be sure that the Ethereum commitment of the other really comes from the same secret as their Monero half. A cross-group discrete log equality proof shows it without revealing the secret. The secret is split into 252 bits. Each bit gets a Pedersen commitment on both curves, with blinding factors that cancel out in the weighted sum, and a two-member ring signature with one challenge shared by both curves proves each committed bit is 0 or 1 on both at once. Summing the commitments gives back both public keys.

Our implementation uses @noble/curves, takes about 4 seconds to prove and 3 to check in a laptop browser, and weighs about 125 KB. You can run it in the Swaps tab and check a counterparty's proof file there.

Step by step

ETH holder (wants XMR)XmrSwap / MoneroXMR holder (wants ETH)keys + DLEq proofsnewSwap{value}send XMR to shared addresssetReady()claim(b), reveals bsweep XMR with a + b
Happy path, top to bottom
  1. Both sides exchange half-keys, view key shares and DLEq proofs, and check them.
  2. The ETH holder calls newSwap with the ETH, the XMR holder's commitment (claim key), their own commitment (refund key) and two durations.
  3. The XMR holder sends the XMR to the shared address.
  4. The ETH holder sees it confirmed with the shared view key and calls setReady.
  5. The XMR holder calls claim with secret b. The contract checks that b matches the claim key and pays the ETH. The secret is now public in the transaction.
  6. The ETH holder reads b from the chain and sweeps the XMR with a + b.

Timeouts and refunds

Two times bound every swap. Before t0, if the swap is not marked ready, the ETH holder can refund. From t0 (or from ready) until t1, only the XMR holder can claim. After t1, only the ETH holder can refund. A refund reveals secret a, which lets the XMR holder take the XMR back from the shared address with a + b. Either way one secret becomes public, and each side ends up with their own coin back or the other side's.

The contract

XmrSwap.sol checks a secret against its commitment with the ecrecover precompile, which makes the check one call. It stores only a stage per swap hash. It takes 0.3% of the ETH leg on claim (0.1% when either party holds the holder threshold), never on refund, and enforces a per-swap ETH cap while in alpha. The admin can change the fee recipient, the cap and the holder token, and cannot touch locked ETH. Seventeen Foundry tests cover claim, refund, the timeouts, wrong secrets, the cap and the discount.

The contract is not deployed yet. Until it is, a filled order is a published match and no funds move.