Skip to main content

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 statusPrivate alternativeRagequit's role
ACTIVEWithdraw or transfer, both keep the label privateUnnecessary, and would expose the label for no reason
PENDINGNone yet, the note is awaiting attestationGet the value out before attestation, or wait
REJECTEDNone, the ASP refused the depositThe 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 Ragequit event always publishes the note's commitment, its label, and the owner's address, whatever the note's status. A deposit note links straight back to its Deposited event through the matching commitment. A note received through transfers has no such match, but its label is 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: ragequit is the one PoolVault entry 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 with PPYieldTokenZap.redeemToUnderlying, or exit to the aToken with PPYieldToken.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 note
Circuit 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.