The part people miss: a relayer is there for privacy, not convenience.
Picture withdrawing to a fresh wallet with no relayer. That wallet needs SOL for the fee, so you fund it from somewhere, and that funding transaction links the new wallet back to you. The pool did its job, and then a 0.006 SOL top-up undid it.
The relayer submits the withdrawal and pays the network fee up front, and the cost comes out of the withdrawn amount.
The trust question is fair. Can the relayer steal or redirect? No. The recipient address is part of what the ZK proof commits to, so changing it makes the proof invalid and the transaction fails. The worst a relayer can do is refuse to submit.
Why private withdrawals go through a relayer 🛰
Every Solana transaction needs a fee payer. If your fresh wallet pays its own fee, you first have to fund it with SOL, and that funding can link it right back to you.
So the relayer submits the withdrawal and covers the network fee. You pay it back from the withdrawn amount: about 0.006 SOL, plus the protocol’s 0.35%.
The relayer can’t redirect your funds. The proof locks the recipient.
A bit more detail on the blind signature part, since this is where people usually get suspicious.
Your browser makes a random token, multiplies it by a blinding factor only you know, and sends us the result. We sign that blinded value after checking your burn on-chain. You remove the blinding factor locally, and you’re left with a valid signature from us over a token we’ve never seen in clear.
At redemption we can confirm the signature is ours and that the token hasn’t been used before. What we can’t do is match it to the burn transaction, because the value we signed and the value you redeem look unrelated to us.
The burn itself stays public, and that’s fine. What’s private is which pass it turned into.
It’s the standard construction (RFC 9474), not something we invented. Using a standard was the point.
How a Moth Pass can’t be tied to the burn that created it 🔏
You burn $DMOTH on-chain (public)
Your browser “blinds” a random token before sending it to us
We sign it without seeing it (RSA blind signatures, RFC 9474)
You unblind it and get a valid, signed pass
When you redeem, we can verify our signature, but we can’t tell which burn it came from.
We know a pass is real. We don’t know whose it is.
From the dev side, the key line is that we hand back an unsigned transaction.
That one choice decides the security model. We build the transaction, the wallet shows it, and the user signs it like any other send. Our server never holds a key, so it can’t sign for you even if it wanted to, or if someone broke into it.
Screening runs before we build anything. If it can’t run, the wallet just gets an empty answer and our route doesn’t appear. There’s no half-state where a user sees an option that might fail later.
After signing, the wallet only polls status until the payment settles on Solana.
It’s still a design, not a shipped integration. But it’s the same pipeline the Duskmoth app runs today, with a different screen at the front.
Here’s the flow we’re sketching for a Duskmoth route inside a wallet. Nothing shipped, just the design.
The wallet asks us for a quote. We answer with the full cost, the estimated time, and which engine would run it: the ZK pool for SOL or the private rollup for USDC.
If the user picks our route, we screen the destination first. If screening can’t run, we return nothing and the wallet simply doesn’t offer us.
If it passes, we hand back an unsigned transaction. The user signs it in their own wallet like any other send. We never touch keys, and the wallet can check the status with us until it settles on Solana.
Same rules as the Duskmoth app, just a different front door.
Each line in this post is a concrete engineering requirement, not a slogan.
“Fail closed”: if the screening API times out or returns something we can’t parse, the answer is no route. A timeout never counts as a pass.
“Fees in full before confirm”: the quote includes the network fee, the engine fee and our fee, and execution is rejected if the real cost would exceed what you approved.
“Trust model labeled”: every engine carries a field that says zk or tee, and the UI has to render it. A ZK pool and a hardware rollup protect different things, and you should know which one you’re using.
“Receipt on Solscan”: if we can’t hand you a signature you can check, the payment isn’t done from our side.
If a wallet flow can’t carry all four, we don’t plug into it.
Before Duskmoth shows up inside anyone else’s wallet, a few things have to be true.
Screening has to work every time, and fail closed when it can’t. Fees have to be shown in full before the user confirms, with nothing added later. The trust model has to be labeled honestly, because a ZK pool and a hardware-based rollup are not the same thing, and users deserve to know which one they’re using. And every payment needs a receipt that can be checked on Solscan.
If we can’t meet all of that inside a wallet flow, we won’t ship it there. A rushed integration would hurt the wallet’s users and our reputation at the same time.
The “boring” fix here deserves a proper explanation, because it was the one users actually felt.
Settlement on the private rollup confirms differently from a normal Solana send. Our status checker was waiting for a receipt shape it expected, didn’t recognize the mainnet one, and left finished operations stuck on “pending.” Nothing was lost. The money had moved. The UI just didn’t believe it.
The worst way to fix that would be to mark anything old as done. We changed it so an operation only flips to confirmed when we can actually match the settlement receipt. If we can’t match it, it stays pending and you can check it yourself.
Withdrawal routing and the mobile bugs were smaller. Still beta, still small amounts. Read your receipts.
Update on the private USDC beta, a few days in.
It’s live on mainnet as a separate panel: private sends, deposits, withdrawals and payroll in USDC, running on @magicblock private rollup. You can also load a USDC plan straight from AI Pack.
The biggest thing we fixed this week was boring but important: some completed operations kept showing as pending because settlement receipts on mainnet weren’t being recognized. That’s sorted now, along with withdrawal routing and a few mobile layout bugs.
Still beta, still small amounts. But it’s running.
Opened a Telegram for Duskmoth.
If you’ve got questions about the stack, the engines, or what’s coming next, ask me there directly. I’ll share work-in-progress before it hits X.
https://t.co/NceqTvAXwo
Our Telegram is open
A place to talk private payments, ask questions, report bugs, and help shape what Duskmoth builds next.
→ talk directly with the dev
→ early looks at what we’re testing
→ feedback that actually shapes the roadmap
Join the discussion 👇
https://t.co/mvkBOqBQPh
Dev log: where Duskmoth’s infra is now, and where I’m taking it 🛠
Today
→ wallet signs every tx, unsigned txs verified client-side
→ AI Pack writes plans, never signs
→ screening fails closed, Moth Pass checked before execute
→ router picks the engine by asset: ZK pool for SOL, @magicblock private rollup for USDC (beta)
→ backups, cross-tab lock and check-only recovery so nobody gets paid twice
Next (exploring)
→ single-signature batches: N payments, one approval
→ split and timed payouts for payroll
→ a Route API so wallets can offer Duskmoth routes natively
→ multisig approval + more engines as they hit mainnet
The flow stays the same. The engines underneath get better.
I’ll post progress as each piece ships.
Spent the week shipping the MagicBlock beta. Here’s the full write-up of how it all fits together: the router, both privacy engines, and the recovery logic that stops double payments.
Questions welcome, I’ll answer in the replies 👇
This is the flow I’ve been sketching for Duskmoth payroll 👇
Start on mainnet → run privately → settle on mainnet, with one signature from the user.
Duskmoth handles the part people actually see: the plan, screening, full fees and one approval. A private rollup handles execution: split payouts, timed releases, settled back on Solana.
Exploring it now. Will share what works and what doesn’t.
MagicBlock v1.0 is live on Solana after a year and a half of building
Apps can run in real time, prove every outcome is fair, and keep payments private, without leaving Solana
Read the full Validator v1.0 release: https://t.co/Wb3vjQ0CUt