Zorrito

How it works
Zorrito

The no-loss lottery on Stellar.

๐Ÿ’ก The idea is simple: you put money into a pool, the pool lends it on Blend and earns interest, and every round that interest is drawn among the savers who are in. One saver wins everyone's yield. Nobody else loses anything: their capital is still there and they withdraw it whenever they want.

How it works

1
You deposit USDC

Your deposit goes straight into Blend and starts earning at once. There is no minimum. Every dollar, and every second it stays in, adds odds. If you hold XLM or USDT0, the app swaps them for USDC at the door. (The testnet pool uses XLM.)

2
The pool earns

Everyone's capital is lent on Blend, Stellar's lending market. The interest borrowers pay accumulates as the prize.

3
The timer runs out

When the week is up (10 minutes in the test pool), the round closes and the draw fires automatically. The prize and everyone's odds are frozen at that instant.

4
drand decides

The contract fixes in advance which drand random number will decide: one that does not exist yet. When drand publishes it, the contract verifies the signature before picking.

5
One wins, nobody loses

The whole prize goes to the winner's wallet in the same transaction. Everyone else keeps exactly what they put in, and a new round is already running.


๐ŸŽฏ How odds are calculated

Your weight in the draw is how much you put in, for how long it stayed in during the round. It is the same proportion in which your money earned the prize: whoever contributed more to the yield has more chances to take it.

weight = ฮฃ (deposit ร— seconds it was in the round)
P(win) = your weight รท the pool's total weight

Example: two people with 100 USDC. One joins on Monday, the other on Saturday. The first has 6 times the odds, because her money earned 6 times more. Jumping in at the last minute with a lot of money barely counts, and that is on purpose: there is no way to "buy" the draw at the last moment.

Withdrawing does not erase what you already accrued. If you had money in for three days and take it out, those three days still count for that round's draw.

๐Ÿ”ฅ The daily streak

Once a day (UTC) you can mark "I saved today". Every day in a row adds weight to the current round, and each day adds more than the last:

bonus on day k = deposit ร— (round length) ร— k / 28

Since 1+2+โ€ฆ+7 = 28, seven days in a row add exactly one full round of weight: whoever marks every day doubles their odds compared to someone with the same money who marks none. Skipping a day goes back to day 1. The bonus belongs to the round in which it is marked; the next round starts from zero, like everything else.

Days in a rowExtra weight accumulatedOdds vs. not marking
13.6 % of a roundร—1.04
321 %ร—1.21
554 %ร—1.54
7 ๐Ÿ†100 %ร—2

๐Ÿค Referrals

Your invite link is your address. Whoever joins through it, on their first deposit, declares you as their referrer, and from then on you weigh as if you held 10 % of their capital, for as long as they are in. If they withdraw, the bonus goes down with them.

base weight = your capital + min(10 % of your referrals' capital, 50 % of your capital)

The cap at half of your own capital is what makes inventing referrals pointless: with no money of your own in, there is no bonus, and with money in, the bonus is never worth more than half of it. Bringing friends adds up; bringing empty accounts does not.


๐ŸŽฒ How the randomness works

Picking a winner on-chain is hard: the chain is deterministic and public, so any value known at draw time can be influenced by whoever builds the block. Zorrito uses nothing from the chain as a source of randomness. It uses drand, a public randomness beacon run by about 20 organisations (Cloudflare, Protocol Labs, EPFL, Kudelski, among others) that publishes a signed number every 3 seconds.

๐Ÿ”’ Phase 1 โ€” Close: a number that does not exist yet is fixed

When the round closes, the contract computes which drand round corresponds to 10 minutes in the future and stores it. At that moment nobody, not even drand, knows what value it will have. It also freezes the prize, the total weight and an accumulated entropy from all the round's deposits.

โœ๏ธ Phase 2 โ€” Draw: the signature is verified on-chain

When drand publishes that round, the signature is brought to the contract. The contract verifies with BLS12-381, inside Soroban itself, that the signature is valid for that exact round and for the drand public key it has stored. If it does not verify, there is no draw. If it does, the seed is:

seed = sha256(entropy โ€– signature โ€– drand_round)
winner = whoever lands on seed mod total_weight
๐Ÿ›ก๏ธ Why nobody can bias it. To pick the winner you would have to know the drand signature before it exists, and that requires corrupting a majority of the organisations that run the network. A Stellar validator cannot: the contract uses no ledger data as randomness. Neither can we: none of our keys is involved in the draw. And anyone can recompute it from what stays on-chain.

Approaches compared

ApproachWho can influence itStatus
Ledger timestamp or hashThe validator building the blockRejected
Commit-reveal with a keeperThe keeper, by withholding the revealRejected: liveness
VRF oracleThe oracle operatorNone on Stellar
drand + BLS verification on-chain โœ“Nobody short of a drand majorityWhat runs today

BLS12-381 verification inside the contract is possible because Stellar added it as a native host function in Protocol 22. Zorrito uses it to verify drand quicknet signatures: a pairing_check between the signature, the message hash, the G2 generator and the group public key.


๐Ÿค– Nobody has to be there

The draw fires on its own when the timer runs out: a keeper running as a serverless function closes the round and brings the drand signature, and so does every visit to the app that finds an expired round or a pending draw. Both actions are permissionless: there are no roles or privileged keys, and whoever performs them only pays the fee. If the keeper disappears, the pool does not get stuck: the first person to open the app unblocks it.

And if drand stopped publishing, the only thing that would not happen is the draw. Capital can still be withdrawn, at any time, with or without a pending draw.


๐Ÿงช Try it without waiting a week

The pool on the home page runs on mainnet with weekly rounds. To watch the whole cycle in minutes there is a test pool on testnet, with 10-minute rounds and test XLM: stellar.zorrito.app/test. Same contract, same Blend (its testnet pool) and same drand. Free testnet XLM from the Stellar Laboratory, or directly with Cosmos Wallet, which funds a testnet account on creation.


๐ŸŒŠ Blend: where the prize comes from

Blend is Stellar's lending protocol. The pool deposits everyone's capital as Supply in a Blend v2 pool (the USDC reserve of the Fixed pool on mainnet; XLM on TestnetV2 on testnet): a position that lends and earns interest but is not used as collateral, so it carries no debt and cannot be liquidated.

  • The interest is paid by borrowers in the pool. The APY the app shows is what Blend is paying right now.
  • The contract talks to Blend on its own, through its own adapter. Nobody holds the key to that position: only the pool can deposit and withdraw.
  • The prize is the difference between what is in Blend and the capital deposited. If the pool earned nothing, the prize is 0 and nobody loses.

๐Ÿ“ˆ Built for a million accounts

Drawing among many participants with different weights is expensive if you have to walk through them. Zorrito keeps the weights in a Fenwick tree over the contract's storage, with room for 220 accounts (just over a million). Depositing, withdrawing and drawing always touch ~20 entries, whether there are 2 participants or 1,000,000.

Time is handled with algebra, not loops: each account's weight is deposit ร— T โˆ’ b, where b is adjusted on every move. That way everyone's weight advances with the clock without anyone having to update it.


๐Ÿ›  Tech stack

ContractsRust + Soroban (soroban-sdk 27). Three contracts: the pool, the Blend adapter and a mock yield source for tests. 60 tests, including BLS verification with our own keys, the streak, referrals, Blend's rounding and the pool operating against Blend's real bytecode.
Randomnessdrand quicknet (bls-unchained-g1-rfc9380), verified on-chain with Stellar's BLS12-381 host functions. Point decompression on the client with @noble/curves.
YieldBlend v2: the USDC reserve of the Fixed pool on mainnet, the XLM reserve of TestnetV2 on testnet. Integrated with contractimport! of the official WASMs.
Currency swapTo join with XLM or USDT0: simultaneous quotes from the Soroswap router (simulating router_get_amounts_out) and the classic DEX (Horizon's /paths/strict-send), then a swap through whichever pays more, with swap_exact_tokens_for_tokens or a pathPaymentStrictSend. In the user's wallet, never in the pool.
WebNext.js 16, Tailwind 4, @stellar/stellar-sdk 17, Stellar Wallets Kit (Freighter, xBull, Lobstr) plus a custom module for Cosmos Wallet. Deployed on Vercel.
KeeperServerless function on Vercel (/api/keeper), triggered by cron and by every visit. Permissionless: anyone can run another one.

๐Ÿ“‹ Contracts

Zorrito pool ยท mainnet ยท USDC ยท weeklyCBPOMGHGCWH2QMG4V4FTZKGBCEN7K37R2OIDGD5VWBAKYTOWG7CDCGGA
View on stellar.expert โ†—
Blend adapter ยท mainnetCD5XQWHFSW427KOQAMAXBMMM6X4BIH6AXZUP76PBB6SWYSSAA4D53MKC
View on stellar.expert โ†—
Blend v2 pool ยท mainnet (Fixed)CAJJZSGMMM3PD7N33TAPHGBUGTB43OC73HVIK2L2G6BNGGGYOSSYBXBD
View on stellar.expert โ†—
Soroswap router ยท mainnetCAG5LRYQ5JVEUI5TEID72EYOVX44TTUJT5BQR2J6J77FH65PCCFAJDDH
View on stellar.expert โ†—
USDT0 (Tether via LayerZero) ยท mainnetCBSJZEIO5C7KC2SF3MKSNXXJSW5G3VTNBX4ATMKUI3B2MR4JKM4R26YF
View on stellar.expert โ†—
Test pool ยท testnet ยท XLM ยท 10 minCDNKUQX5YT5JYDF2UB3NZXI7UFKRKUTU7W23P42TLXUTGY4WE5IZI5X2
View on stellar.expert โ†—
Blend adapter ยท testnetCCHLQA7SGZAEVGLAFUL4Y6DMBG7ZUZYSZZ7AGN44GNNJCCJ6VECV7ISA
View on stellar.expert โ†—
Blend v2 pool ยท testnet (TestnetV2)CCEBVDYM32YNYCVNRXQKDFFPISJJCV557CDZEIRBEE4NCV4KHPQ44HGF
View on stellar.expert โ†—
drand quicknet ยท group public key (G2)83cf0f2896adee7eb8b5f01fcad39122โ€ฆa6e9c76a4bc09e76eae8991ef5ece45a
View on drand โ†—

Open source: github.com/artugrande/zorrito. The tests, the deploy scripts and this very site are there.


๐Ÿ›ก๏ธ Security and risks, no makeup

LOW
Nobody can pick the winner

Randomness comes from drand and is verified in the contract. There is no admin key in the pool: no role can pause it, change the draw or touch funds. The adapter has an admin that only serves to set, once, which contract is the pool.

LOW
Your capital depends on nobody

Withdrawing needs no permission, has no penalty and works even with a pending draw, even if the keeper is down and even if drand stops publishing.

MEDIUM
Blend risk: liquidity

The capital is lent out. If the Blend pool has almost all of its liquidity borrowed, a withdrawal can fail until someone repays or deposits. This is the real risk behind "nobody loses": no capital is lost, but you may have to wait. Blend mitigates it with rates that rise with utilisation.

MEDIUM
Blend risk: protocol

A bug in Blend affects the pool like any other lender. Blend v2 is audited and is the most used lending protocol on Stellar, but the risk is not zero.

LOW
drand liveness

If drand does not publish, there is no draw until it is back. Capital can still be withdrawn. drand has been publishing every 3 seconds for years with more than 20 operators.

LOW
Storage rent on Stellar

Persistent entries expire unless someone extends them. The contract extends its own on every use; accounts idle for months need someone to extend them (anyone can). Automating it in the keeper is pending.

MEDIUM
New contract, not audited

Built from scratch during the hackathon. The mainnet pool has a capital cap fixed in the contract precisely for this reason: it limits how much can be inside until an audit says it can be raised. Do not put in money you cannot afford to lose.


โ“ Questions

Can I lose money?

No. The draw only distributes the interest the pool earned. Your deposit is yours and you withdraw it whenever you want. The only thing that can happen is having to wait if Blend is short on liquidity at that moment.

What do I get if I do not win?

Nothing, and you lose nothing. It is the same money you had, but with a shot at a prize every round instead of a small interest. If you prefer the guaranteed interest, Blend is right there.

Why did the person with the most money not win?

Because weight is money times time, not just money. Someone with less money who joined earlier can weigh more. And even with more weight, it is a draw: more odds is not certainty.

Can I verify a draw?

Yes. Every draw emits an event with the drand round, the signature and the total weight. With that and the round's deposits, anyone can recompute the seed and the winner.

What if the round is not closed on time?

Nothing bad. It closes when the keeper or a visit to the app closes it, and until then the pool keeps earning. The draw is never lost, only delayed.

Why USDC on mainnet and XLM on testnet?

Because the prize comes from what people borrow, and on Stellar people borrow USDC: the USDC reserve of the Fixed pool is at 80 % utilisation and pays ~8 % a year to lenders; the XLM reserve sits at 0.1 % and pays 0 %. On testnet there is no easy USDC and XLM needs no trustline, so the test pool uses XLM. The contract is the same with any token Blend accepts.

Will there be a USDT0 pool?

Today you can join with USDT0 and it is swapped to USDC at the door. A pool that holds USDT0 directly needs Blend to lend it, because that is where the prize comes from. USDT0 arrived on Stellar in September 2026 and has no reserve in any Blend pool yet. The day it does, it is one more entry in the configuration: the contract does not know which token it is.

What about Blend's 5-dollar minimum?

It does not apply. That minimum is for collateral, and the pool contract only checks it when a position has debt: it exists so that nobody leaves a loan with collateral too small to be worth liquidating. Zorrito only lends (non-collateral Supply) and never borrows, so the pool never evaluates that minimum on its position. Besides, the pool holds a single position on Blend that aggregates everyone's deposits: whoever puts in 0.50 USDC does not open their own position, they add to the pool's. Tested on mainnet with deposits under a dollar.

Do I need anything in my wallet to join?

USDC and a little XLM for fees. If your wallet does not accept USDC yet, the app offers to add it with one tap: it is Stellar's trustline, done once. Freighter, Cosmos Wallet, xBull and Lobstr all work.

Can I join with XLM or USDT0?

Yes, with both. Pick the currency next to the amount, the app gets quotes from the two places where swaps happen on Stellar, Soroswap and the classic DEX (the same one Freighter and Lobstr use), and shows you whichever gives more USDC at that moment. You sign twice: the swap and the deposit. The swap goes through your wallet, not through the pool: the pool receives USDC as always and your capital is in dollars from the first second. Up to 0.5 % less than the quote is accepted if the price moves between looking and signing; if it moves more, the whole swap fails and nothing happens. Withdrawals are always in USDC.

Can I have referrals without putting money in?

You can, but they add nothing: the referral bonus is worth at most half of your own capital, and with no capital it is worth zero.

โ† Back to the app