Ragequit
Ragequit publicly recovers a note's full value to its owner via on-chain transfer, regardless of ASP approval. It exists to guarantee self-custody at all times, even when the ASP refuses to attest a deposit.
When to use it
Ragequit is for a note you cannot spend normally, or cannot wait to spend. It always recovers the full value, but it exposes the note on-chain and the recovered value has no anonymity set, so use it only when there is no private alternative.
| Note status | Private alternative | Ragequit's role |
|---|---|---|
ACTIVE | Withdraw or transfer, both keep the label private | Unnecessary, and would expose the label for no reason |
PENDING | None yet, the note is awaiting attestation | Get the value out before attestation, or wait |
REJECTED | None, the ASP refused the deposit | The designed recovery path |
How it works
- Owner-only: the call must come from the note's current owner, the keystore-registered address the note is bound to. The proof requires keystore membership, so the owner must be registered first. Because notes are transferable, the owner is whoever holds the note now, not necessarily the original depositor.
- Destination binding: the funds always return to the owner's address. The circuit forces this, so the recovery cannot be redirected to any other address.
- Exposes the note on-chain: the
Ragequitevent always publishes the note's commitment, itslabel, and the owner's address, whatever the note's status. A deposit note links straight back to itsDepositedevent through the matching commitment. A note received through transfers has no such match, but itslabelis the deposit's lineage identifier, so anyone holding the label-to-deposit mapping (the ASP) can still tie it to the origin deposit. Either way, the recovered value has no anonymity set. - Any unspent status: ragequit works on any unspent commitment, whatever its status.
- Gas and value: the caller pays gas in ETH, and the note's full value moves in one step. There is no partial ragequit.
- Works while paused:
ragequitis the onePoolVaultentry point without a pause guard, so a paused pool cannot trap funds. - A yield note pays shares: ragequit pays the note's
tokenId, so a yield-share note lands as ppUSDC in your wallet, not USDC. Unwrap it afterwards withPPYieldTokenZap.redeemToUnderlying, or exit to the aToken withPPYieldToken.redeem, which never touches Aave.
How to use it
const allNotes = (await session.exportAccount()).notes;
const stuckNote = allNotes.find(
(n) => n.status === "PENDING" || n.status === "REJECTED",
);
if (!stuckNote) throw new Error("no PENDING or REJECTED note to recover");
const result = await session.rageQuit({
commitment: stuckNote.commitment,
});
console.log("ragequit tx:", result.txHash);
Ragequit is a last-resort path. It exists so the protocol remains self-custodial and never freezes funds, regardless of ASP approval.
Behind the scenes
SDK call
session.rageQuit({commitment})Contract method
PoolVault.ragequit(...)Contract gate
require(msg.sender == _proof.ownerAddress()): caller must be the keystore-registered owner of the noteCircuit
ragequit Groth16: proves keystore membership and knowledge of the noteSecret and the nullifying / revocable keys. It does not use depositSecret, which lives only in the deposit circuit.On-chain effect Note commitment marked as exited and value transferred to the note's recorded
ownerAddress. The Ragequit event emits the ragequitter (current owner), not the original depositor.