Problem
randomnessClose correctly closes the randomness account + wSOL ATA and deactivates the per-randomness LUT, but the LUT rent cannot be recovered due to Solana's Address Lookup Table 512-slot cooldown requirement.
We're building Savage Protocol, a fully on-chain jackpot game on Solana (~144 rounds/day). Each round uses Switchboard On-Demand randomness. The unrecoverable LUT rent costs us ~0.28 SOL/day (~102 SOL/year).
Root Cause
Solana ALT closure requires a two-step process:
DeactivateLookupTable → start cooldown
- Wait ~512 slots (~3-4 minutes)
CloseLookupTable → recover rent
randomnessClose performs step 1 (deactivate) in a single CPI call. But step 3 (close) fails because the cooldown hasn't elapsed yet within the same instruction.
Calling randomnessClose a second time after cooldown fails with error 3007 because the randomness account is already closed and can't be deserialized.
Calling CloseLookupTable directly is impossible because the LUT authority is lutSigner (a PDA of the Switchboard program), and only the Switchboard program can sign for it.
Devnet Proof
We tested the full lifecycle on devnet:
| Step |
Result |
randomnessInit |
✅ Created randomness + ATA + LUT (152 bytes, 1,948,800 lamports) |
randomnessClose (1st call) |
✅ Closed randomness + ATA, deactivated LUT |
| Wait 512+ slots |
✅ Cooldown passed |
randomnessClose (2nd call) |
❌ Error 3007 — randomness account already closed |
Direct CloseLookupTable |
❌ Missing signature for lutSigner PDA |
Evidence on devnet:
- LUT:
69oknVfLSvapF4vv172ovw56PEmcPeEr8LhJzEPUV82m — deactivated at slot 454464732, still holding 1,948,800 lamports
- Randomness:
3UCNMCcmMzqGQV2XBK3fiTBFRwCnYERrkzmjqckZHB6i — successfully closed
Additional Finding
randomnessReveal does not reference the LUT at all (no lut, lutSigner, or addressLookupTableProgram in its account list). The LUT is created during randomnessInit but never actually used by any subsequent instruction in the commit-reveal flow.
Suggested Solutions (any would work)
Option A: randomnessCloseLut instruction
A new instruction that takes randomness_pubkey + lut_slot as parameters (for PDA derivation only, no account data needed), derives lutSigner, and CPI-calls CloseLookupTable on a deactivated LUT.
Option B: Make randomnessClose idempotent
On second call, skip the already-closed randomness/ATA accounts and only process the LUT closure if it's deactivated and cooldown has passed.
Option C: skip_lut flag in randomnessInit
Since randomnessReveal doesn't use the LUT, allow callers to opt out of LUT creation entirely.
Impact
| Metric |
Value |
| LUT rent per round |
1,948,800 lamports (0.00195 SOL) |
| Rounds per day |
~144 |
| Daily cost |
~0.28 SOL |
| Annual cost |
~102 SOL |
Happy to test any fix on devnet immediately. Thank you!
Problem
randomnessClosecorrectly closes the randomness account + wSOL ATA and deactivates the per-randomness LUT, but the LUT rent cannot be recovered due to Solana's Address Lookup Table 512-slot cooldown requirement.We're building Savage Protocol, a fully on-chain jackpot game on Solana (~144 rounds/day). Each round uses Switchboard On-Demand randomness. The unrecoverable LUT rent costs us ~0.28 SOL/day (~102 SOL/year).
Root Cause
Solana ALT closure requires a two-step process:
DeactivateLookupTable→ start cooldownCloseLookupTable→ recover rentrandomnessCloseperforms step 1 (deactivate) in a single CPI call. But step 3 (close) fails because the cooldown hasn't elapsed yet within the same instruction.Calling
randomnessClosea second time after cooldown fails with error 3007 because the randomness account is already closed and can't be deserialized.Calling
CloseLookupTabledirectly is impossible because the LUT authority islutSigner(a PDA of the Switchboard program), and only the Switchboard program can sign for it.Devnet Proof
We tested the full lifecycle on devnet:
randomnessInitrandomnessClose(1st call)randomnessClose(2nd call)CloseLookupTableEvidence on devnet:
69oknVfLSvapF4vv172ovw56PEmcPeEr8LhJzEPUV82m— deactivated at slot 454464732, still holding 1,948,800 lamports3UCNMCcmMzqGQV2XBK3fiTBFRwCnYERrkzmjqckZHB6i— successfully closedAdditional Finding
randomnessRevealdoes not reference the LUT at all (nolut,lutSigner, oraddressLookupTableProgramin its account list). The LUT is created duringrandomnessInitbut never actually used by any subsequent instruction in the commit-reveal flow.Suggested Solutions (any would work)
Option A:
randomnessCloseLutinstructionA new instruction that takes
randomness_pubkey+lut_slotas parameters (for PDA derivation only, no account data needed), deriveslutSigner, and CPI-callsCloseLookupTableon a deactivated LUT.Option B: Make
randomnessCloseidempotentOn second call, skip the already-closed randomness/ATA accounts and only process the LUT closure if it's deactivated and cooldown has passed.
Option C:
skip_lutflag inrandomnessInitSince
randomnessRevealdoesn't use the LUT, allow callers to opt out of LUT creation entirely.Impact
Happy to test any fix on devnet immediately. Thank you!