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 2026Overview
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.
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.
| Provider | Rule | Example |
|---|---|---|
| gmail.com, googlemail.com | Drop dots and +tags, unify the domain | J.Doe+shop@Gmail.com → jdoe@gmail.com |
| outlook, hotmail, live, msn | Drop +tags | ann+dev@outlook.com → ann@outlook.com |
| proton.me, protonmail.com, pm.me | Drop +tags | x+a@proton.me → x@proton.me |
| yahoo, ymail, rocketmail | Drop -tags | bo-news@yahoo.com → bo@yahoo.com |
| everything else | Lowercase only — tags are preserved | Ops+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
// 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
| Action | XVaro | Third party |
|---|---|---|
| Deriving an account, receiving, claiming an inbox, exporting the key | free | — |
| Transfers between accounts | free | network gas, at cost |
| Launching a coin | free | Varo's launch fee + gas, from your own wallet |
| Trading fees on a coin launched here | 10% of the pool fee | paid by Varo to XVaro's own line |
| Claiming your fees — here or from your own wallet | free | network 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.
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 forsign-in,claimorexport.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
| Surface | Limit |
|---|---|
| Resolver (public site) | 60 lookups / minute / IP |
| Resolver (API key) | 120 requests / minute / key |
| Code issuance | 8 / 10 minutes / IP, 5 / 15 minutes / inbox |
| Transfers | 30 / minute / account |
| Key export | 6 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.
