Documentation

How XVaro works, in full

The derivation standard, the normalization rules, the API surface and the limits that apply to each of them.

updated September 2026

Overview

XVaro turns an email address into an on-chain account. The account exists before anyone signs up: it is derived, not created. Three things follow from that.

  • You can pay someone who has never heard of a wallet.
  • The recipient claims the account later, by proving the inbox is theirs.
  • The account is an ordinary EOA, so it works in every wallet on day one.

MDS-1 derivation standard

MDS-1 maps a canonical inbox to a secp256k1 key. It is deterministic, stateless, and published so that answers from the resolver can be checked.

typescript
canonical = normalize(email)          // see "Normalization"
eid       = sha256(canonical)

for (let i = 0; ; i++) {
  const sk = hkdf("sha512", ROOT,
    "xvaro/mds-1",                     // salt
    `acct:${canonical}:${i}`,           // info
    32);
  if (0n < toBigInt(sk) && toBigInt(sk) < SECP256K1_N) {
    return sk;                          // otherwise advance the counter
  }
}

account    = keccak256(pubkey(sk))[12:]
commitment = keccak256(`${eid}:${account.toLowerCase()}`)

Properties

  • Pure. No database read is needed to derive an address.
  • Portable. The output is a plain EOA — no factory, no proxy, no chain binding.
  • Stable. One inbox maps to exactly one account, forever. There is no rotation.

Normalization

Two spellings of the same mailbox must not produce two accounts. Normalization runs before derivation and is part of the standard.

ProviderRuleExample
gmail.com, googlemail.comDrop dots and +tags, unify the domainJ.Doe+shop@Gmail.com → jdoe@gmail.com
outlook, hotmail, live, msnDrop +tagsann+dev@outlook.com → ann@outlook.com
proton.me, protonmail.com, pm.meDrop +tagsx+a@proton.me → x@proton.me
yahoo, ymail, rocketmailDrop -tagsbo-news@yahoo.com → bo@yahoo.com
everything elseLowercase only — tags are preservedOps+ci@acme.com → ops+ci@acme.com

Custom domains keep their tags because many teams route + addresses to different mailboxes. Folding them would merge accounts that different people control.

Launching a coin that pays an inbox

XVaro launches on Varo (varo.rialto.xyz), the Rialto launchpad on Robinhood Chain. A Varo launch takes a list of fee_recipients — wallets and their shares of every trade's pool fee. XVaro fills that list with the MDS-1 accounts of email addresses. Nothing else is special: no wrapper contract, no custom token, no XVaro code between the pool and the money.

The flow

typescript
// 1. the launcher's wallet proves itself to Varo — a signature, no gas
const { message, nonce } = await varo.auth.challenge(wallet);
const token = await varo.auth.verify(nonce, wallet, personal_sign(message));

// 2. Varo signs a launch whose fee recipients are inboxes
const intent = await varo.intents.createToken({
  name, symbol, image_uri, quote_token: WETH,
  fee_recipients: [
    { wallet: mds1("nina@studio.com"), share_bps: 9000 },   // ← the inbox
    { wallet: mds1("treasury@xvaro.app"), share_bps: 1000 }, // ← XVaro
  ],
});

// 3. the same wallet sends it
await wallet.sendTransaction(intent.transaction);

Several recipients

Varo's recipient list is the split. Name up to six inboxes with their shares and each one becomes its own line, paid by the pool directly:

  • No contract of ours in the path — the pool pays each address on every trade.
  • Shares are readable from Varo's own record of the launch, so the people being paid never have to take our word for it.
  • XVaro's 10% is written in as one of the lines rather than taken at claim time — visible to everyone, including the recipients, before the launch happens.
  • Rounding dust goes to the largest inbox, never to us.

What the launcher pays

  • Varo's launch fee, if any, inside the transaction Varo prepares.
  • Gas — a launch creates the token, the pool and the position, about 6.6M gas.
  • Both come from the launcher's own wallet. XVaro holds nothing of theirs.

What the recipient does

Nothing, until they want the money. Fees accrue from the first trade. When they claim the inbox with a code, the fees are already sitting in the locker — and exporting the key removes XVaro from the picture entirely, since the locker pays the address, not us.

The recipients are fixed at mint: they cannot be changed afterwards, by us or by anyone.

What XVaro charges

ActionXVaroThird party
Deriving an account, receiving, claiming an inbox, exporting the keyfree
Transfers between accountsfreenetwork gas, at cost
Launching a coinfreeVaro's launch fee + gas, from your own wallet
Trading fees on a coin launched here10% of the pool feepaid by Varo to XVaro's own line
Claiming your fees — here or from your own walletfreenetwork gas, at cost

How the cut is taken

It is not a contract and not a hook in the token. When a coin is launched through XVaro, the treasury inbox is one of the fee recipients Varo records at mint, with a 10% share. Varo pays it on every trade the same way it pays the other inboxes; nothing is deducted from anyone at claim time.

  • The share is visible in Varo's own record of the launch before the coin exists.
  • Claiming through XVaro or from your own exported key makes no difference — what the locker owes your address is yours in full either way.
  • The treasury is itself an ordinary XVaro inbox account, not a special contract.

API

Authentication

Bearer keys, created in the developer console. Keys are shown once and stored hashed. Rate limit: 120 requests per minute per key.

bash
GET https://xvaro.app/api/v1/resolve?email=cfo@acme.com
Authorization: Bearer mk_live_…

200 OK
{
  "ok": true,
  "data": {
    "address": "0x7c3f…9a12",
    "canonical": "cfo@acme.com",
    "eid": "8f14e45f…",
    "commitment": "0x9ab3…",
    "derivation": "mds-1",
    "claimed": false,
    "self_custody": false,
    "chain": { "id": 4663, "name": "Robinhood Chain" }
  }
}

Session endpoints

  • POST /api/auth/start — issue a code for sign-in, claim or export.
  • POST /api/auth/verify — exchange a code for a session cookie.
  • POST /api/send — transfer to an inbox or an address.
  • POST /api/export — one-shot key release, requires a fresh export code.

Status and limits

SurfaceLimit
Resolver (public site)60 lookups / minute / IP
Resolver (API key)120 requests / minute / key
Code issuance8 / 10 minutes / IP, 5 / 15 minutes / inbox
Transfers30 / minute / account
Key export6 attempts / 15 minutes / account

Changelog

September 2026

  • MDS-1 resolver live on Robinhood Chain with published commitments.
  • Key export with v3 keystore and one-way severing of hosted signing.
  • Payment requests, contacts, audit log and scoped API keys.