Skip to content

plan to add state to accounts for expiring and soft deleting#960

Open
gudnuf wants to merge 7 commits intomasterfrom
docs/account-state-lifecycle
Open

plan to add state to accounts for expiring and soft deleting#960
gudnuf wants to merge 7 commits intomasterfrom
docs/account-state-lifecycle

Conversation

@gudnuf
Copy link
Copy Markdown
Contributor

@gudnuf gudnuf commented Mar 26, 2026

Summary

Design spec for adding active → expired → deleted lifecycle to wallet.accounts. For review/discussion before implementation.

  • Auto-expiry: pg_cron flips active → expired hourly when expires_at passes
  • Soft delete: Client-initiated RPC, hidden via restrictive RLS policy
  • One-way transitions: Enforced by DB trigger (no reactivation)
  • Partial unique index: Scoped to state = 'active' so expired/deleted accounts don't block new ones at the same mint

What this enables

  • Expired offer accounts stop showing as active without app-code changes to every query
  • Users can delete old offer accounts
  • New offers at the same mint work even after prior account expired

Open questions

  • Delete UI placement (hook is specced, UX is separate)
  • Expired balance recovery (proofs may still be swappable, separate feature)

No code changes — spec only.

gudnuf added 4 commits March 20, 2026 10:18
Two bugs with v2 keysets: getDecodedToken fails without keyset IDs
for resolution, and re-encoded tokens have truncated IDs that can't
be looked up. Fix uses dependency-injected keyset resolver with
cache-first strategy, plus raw token string passthrough in paste/scan.
- extractCashuTokenString validates via getTokenMetadata (not just regex)
- extractCashuToken uses try/catch like cashu.me for v2 detection
- Preserves immediate validation UX in paste/scan handlers
4 tasks: token functions + tests, resolver factory, paste/scan handlers,
route clientLoaders. Includes v2-specific tests with round-trip verification.
Design doc for adding active/expired/deleted lifecycle to wallet.accounts.
Covers DB migration (enum, transition trigger, partial unique index, RLS,
pg_cron auto-expiry), app-layer changes, and data flow diagrams.

For review/discussion before implementation.
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 26, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agicash Ready Ready Preview, Comment Mar 26, 2026 8:35am

Request Review

@supabase
Copy link
Copy Markdown

supabase bot commented Mar 26, 2026

This pull request has been ignored for the connected project hrebgkfhjpkbxpztqqke because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

gudnuf added 2 commits March 26, 2026 01:25
No longer needed — account state lifecycle replaces this approach.
Client-side expiry in upsert_user_with_accounts transitions stale accounts
before returning them. pg_cron remains as background cleanup for offline users.
Each DB function's WHERE clause only matches valid source states.
soft_delete_account gains AND state != 'deleted' guard.

### 6. Realtime handling for deleted accounts

The `ACCOUNT_UPDATED` handler must detect `state === 'deleted'` in the broadcast payload and call `accountCache.remove(id)` rather than `accountCache.update(account)`.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this makes sense because of what claude said in 1, but I'll look more when I build

### Soft delete DB function

```sql
create or replace function "wallet"."soft_delete_account"(p_account_id uuid)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should it be called soft delete or just delete?

if not found then
raise exception
using
hint = 'NOT_FOUND',
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if not found because account is already deleted should this return a different error message that its already deleted?


This runs inside the existing transaction, before the `accounts_with_proofs` CTE that fetches accounts. The client receives already-expired accounts with `state = 'expired'` — no second round-trip needed.

### pg_cron job for auto-expiry
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking we should keep this around for when the user doesn't open the app for a long time, but do you think its needed

### `gift-cards.tsx` -- Simplify filter

```typescript
function useActiveOffers() {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is from code I haven't committed yet btw

@gudnuf gudnuf requested a review from jbojcic1 March 26, 2026 18:45
@orveth orveth assigned ditto-agent and gudnuf and unassigned ditto-agent and gudnuf Mar 27, 2026
@orveth orveth requested review from ditto-agent and removed request for jbojcic1 March 28, 2026 09:20
@gudnuf gudnuf changed the title Account state lifecycle spec plan to add state to accounts for expiring and soft deleting Mar 28, 2026
@orveth orveth added the plan Plan or spec document label Mar 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

plan Plan or spec document

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants