Most airdrops are static lists. Someone runs a script offchain, dumps the result into a CSV, generates a Merkle tree, and ships a contract that lets eligible wallets claim. It works, but it has a problem: the eligibility logic is invisible. Users have to trust that the team's snapshot was honest and that the script did what it was supposed to.
A fairdrop flips this. Instead of distributing a precomputed list, you publish the eligibility rule itself as a SQL query, run it against indexed onchain data, and let a zero-knowledge proof guarantee the result. The rule is the source of truth. Anyone can read it, audit it, and verify their own allocation.
This post walks through how we structured the SXT Genesis Validator Rewards fairdrop using this approach, the math behind it, the smart contract that resulted, and how to replicate the pattern for your own protocol.
Where the query data comes from
A fairdrop is only as good as the data the query reads. With Space and Time, that data can come from two places, and you can combine them freely inside the same SQL.
Onchain data, indexed by SXT. You point SXT at any smart contract on a supported network, select the events you care about, and SXT indexes those events into queryable tables. Every emitted event becomes a row, with columns derived from the event signature plus standard block metadata.
Offchain data, published as an SXT table. You can also write your own tables directly to SXT Chain (CSV upload, API insert, or programmatic writes) and query them the same way. This is what the Airdrop use case does when you upload a precomputed allocation list: the CSV becomes an onchain table, and the claim contract queries it.
Both sources are stored with cryptographic commitments signed by the SXT validator set, which is what allows Proof of SQL to produce a zero-knowledge proof that a query result was computed correctly over untampered data. From the query's perspective, onchain and offchain tables look the same.
What a fairdrop unlocks
Once your eligibility rule is a SQL query against verifiable data, you can use the same primitive for a lot more than a one-time token drop.
- Retroactive airdrops with conditional logic. Reward users who did something specific, at a specific time, and met some condition afterward. The Genesis Validator Rewards Program is exactly this: stake within 14 days, hold for 9 months, get a boosted payout.
- Loyalty and volume-based rewards. Reward users by activity, frequency, or volume rather than a static snapshot. The query can sum trade volume, count distinct days of activity, or weight contributions over time.
- Rewards for non-yield-bearing stablecoin holders. Stablecoins by design don't pay yield to holders, but a separate contract can. SXT covers this pattern explicitly in the User Rewards for Stablecoins writeup: index transfer and balance data, define a reward function over time-weighted holdings, and let users claim against a proven result.
- Continuous emissions tied to verifiable activity. Because the query is re-runnable, you can structure ongoing rewards rather than a one-shot drop. New activity gets reflected the next time someone claims.
The common thread: any rule you can express in SQL over indexed data is a candidate. The infrastructure does not change between use cases. The query does.
Setting up a fairdrop correctly
A few practical things to think about before you ship one of these.
Understand the distribution of every variable your query depends on. If your eligibility rule rewards "users who staked X tokens," it helps to know what the histogram of stake sizes looks like before you pick the value of X. Pull the data first, then choose. Pick the threshold blind and you either qualify almost nobody (which underspends the budget) or almost everybody (which spends it on dust wallets). The Query Workbench is good for this: run aggregations against the indexed tables and look at the shape of the data before you commit to a rule.
Cap it. Unbounded rules drain budgets fast: one large wallet, one loop, one unexpected user pattern. Bound the payout in time, per wallet, or in total, and put the bound in the SQL.
Test that the right amounts go to the right wallets. Run the query in the workbench before deploying. Spot-check the top recipients, the bottom recipients, and a few edge cases (users who staked right before the cutoff, users who unstaked just after). If a wallet gets an amount that surprises you, either the query is wrong or your mental model of the rule is wrong. Either way, you want to find that out before the contract is deployed and immutable.
Return raw token units. The standard Fairdrop contract expects a cumulative reward amount, not a human-formatted token amount. For an 18-decimal token, one full token is 1000000000000000000. For a 6-decimal token, one full token is 1000000.
Our use case: the SXT Genesis Validator Rewards math
The Genesis Validator Rewards Program rewards early stakers who staked within the first 14 days of SXT mainnet and kept their stake active for at least 9 months. Stakers who meet both conditions get their standard ~8% annualized rewards adjusted upward to an effective ~30% annualized.
We express the entire eligibility-and-payout rule as one SQL query:
SELECT CAST((SUM(STAKED) * CAST(SUM(UNSTAKED) = 0 AS BIGINT)) AS DECIMAL(75, 0)) * CAST(0.217 AS DECIMAL(75,3)) AS REWARDFROM ( SELECT CAST(SUM(AMOUNT) AS DECIMAL(75, 0)) AS STAKED, 0 AS UNSTAKED FROM STAKING_5DHZMDXQYB8ZAAYOPX4BKSSZKMK2DSPKJX5YYAH1Q8GELVL3.STAKED WHERE STAKER = $1 AND BLOCK_NUMBER < 22534698
UNION ALL
SELECT 0 AS STAKED, COUNT(*) AS UNSTAKED FROM STAKING_5DHZMDXQYB8ZAAYOPX4BKSSZKMK2DSPKJX5YYAH1Q8GELVL3.UNSTAKE_INITIATED WHERE STAKER = $1 AND BLOCK_NUMBER < 24408385)A few design choices worth surfacing:
- Two cutoffs, not one. The stake snapshot marks the end of the 14-day staking window. The unstake check extends roughly 1.87M blocks (about 9 months on Ethereum) past the snapshot, covering the full lock-up window. We use two different block numbers because we are measuring two different things: "did you stake in time?" and "did you stay staked long enough?"
- Unstaking stops eligibility. An unstake event during the genesis period voids the full reward.
- The
CAST(... = 0 AS BIGINT)trick. This is how we encode the indicator function in SQL. Booleans cast to integers give 1 or 0, and multiplying by the staked sum cleanly zeroes out ineligible wallets without aCASEbranch.
Translating the rule into a smart contract
Here is the path from "I have a query" to "I have a deployed contract that pays out against it."
Step 1: Index your contract's events
The query above reads from two tables, STAKED and UNSTAKE_INITIATED, which correspond to events emitted by the SXT staking contract on Ethereum. Before any of this works, those events need to be indexed into queryable tables on SXT Chain.
You do this from the Index a Contract page. You point SXT at your contract address, select which events to index, and SXT spins up the tables for you. Every event emitted from the contract becomes a row in the table. The schema is derived from your event signatures, so event Staked(address staker, uint256 amount) becomes a STAKED table with STAKER and AMOUNT columns plus the standard block metadata.
Step 2: Write and test the query in the workbench
Open the Query Workbench and iterate on the SQL against the live indexed tables. Try it with several known wallets. Check the output. Vary the block cutoffs and watch the totals move the way you expect.
One thing to get right at this stage: the query needs and expects exactly one parameter, and that parameter is the claimant's wallet address. The fairdrop contract will pass the caller's address into the query at claim time as $1, so every reference to the user in the SQL (in our case, WHERE STAKER = $1 in both subqueries) has to point at that same parameter.
The deployed Fairdrop contract expects the query result to be one row and one column: a nonnegative DECIMAL(75, 0) value representing the claimant's cumulative lifetime reward in reward-token base units. The contract subtracts totalClaimed[$1] and transfers only the unclaimed delta.
This is where you confirm that the variables in your formula are doing what you think. Check how many addresses would qualify versus disqualify. Compare your total expected payout to your funding budget.
Step 3: Compile the proof plan and deploy the query contract
Once the query is final, we compile the query into a proof plan. The plan is what the prover uses to generate a ZK proof that the SQL was executed correctly, and that proof can be verified onchain. The plan is embedded in your contract at deploy time.
A user calling claim() triggers the following flow:
- Your fairdrop contract emits a query request for its embedded query plan, and with the caller's address as the parameter ($1 in our SQL).
- SXT executes the SQL against the indexed tables and returns the result plus a zero-knowledge proof.
- SXT sends the proof to the onchain verifier contract and, if valid, it forwards the result to the fairdrop contract
- Your fairdrop contract receives the verified reward amount and transfers the corresponding tokens to the user
No backend, no admin, no offchain trust assumption beyond the validator set securing the data commitments.