feat(WALM-264): permanent V1 memory deletion — flag-gated UI + backend#378
Open
harrymove-ctrl wants to merge 7 commits into
Open
feat(WALM-264): permanent V1 memory deletion — flag-gated UI + backend#378harrymove-ctrl wants to merge 7 commits into
harrymove-ctrl wants to merge 7 commits into
Conversation
User-facing delete for old V1 memories, off by default behind
ENABLE_MEMORY_DELETION (server) + VITE_ENABLE_MEMORY_DELETION (app).
Backend: POST /api/delete-memories (protected_routes) accepts the
user-signed sponsored delete PTB {digest, signature, blobIds}, submits
it via the sidecar sponsor-execute path, and only after on-chain
success deletes the matching vector_entries rows (delete_by_blob_id,
owner-scoped from verified auth — never the body). sponsor.rs's sidecar
call is extracted into call_sidecar_sponsor_execute, shared with the
existing /sponsor/execute proxy. cleanup_expired_blob remains the
self-heal backstop if row-delete fails after a landed tx.
Frontend: /cleanup page enumerates the wallet's on-chain Walrus Blob
objects (walrusClient.getBlobType — no hardcoded package), converts
blob_id u256→base64url to match the relayer DB, builds the delete PTB
(deleteBlob + transferObjects of reclaimed Storage) in batches of 950
per Sui PTB caps, sponsors via POST /sponsor, and stops after the
wallet signature — the backend owns submit + DB cleanup. Confirm
dialog and copy state plainly that deletion is permanent, never
recoverable, never migrated. Plus the dismissible old-memories banner
and a user guide (docs/fundamentals/deleting-old-memories).
Verified: cargo tests 296 pass (5 pre-existing failures on clean dev),
app tsc + vite build + asset verification pass.
fetchAccountIdForOwner read the registry Table id as registryJson.accounts.id,
which is a plain string on gRPC json but Move's nested UID shape
({ id: { id } }) on JSON-RPC even after field unwrapping. The JSON-RPC
path then passed an object as getDynamicFieldObject's parentId and every
account lookup failed with 'Invalid params' — SetupWizard treated real
accounts as missing and tried create_account, which aborts on-chain with
EAccountAlreadyExists. Regression from the PR #355 compat layer, hidden
until now because testnet runs the gRPC path.
utils/api.ts still signed the legacy '{ts}.{method}.{path}.{bodySha}'
message with no x-nonce header, so the relayer's auth middleware
rejected every call with 426 (unsupported legacy SDK). Sign the current
'{ts}.{method}.{path}.{bodySha}.{nonce}.{account_id}' message and send
x-nonce, matching SDK v0.4+ — unblocks the delete-memories UI's calls
to /api/memory-blob-ids and /api/delete-memories.
Move the banner from AppContent into the Dashboard page (below the navbar, above the header) so it shows where the cleanup section lives and inherits the dash-shell layout, with the standard alert icon.
…ce (#351) Mainnet writes were failing 100% at the register_sponsor phase with an Enoki dry_run_failed -> MoveAbort in 0x2::coin::destroy_zero (ENonZero). Root cause is not Enoki: @mysten/walrus '#withWal' pre-funds an *exact* WAL payment computed from the client's cached systemState price, then asserts the coin is empty via coin::destroy_zero. When mainnet storage/write price drifts down between the cached read and execution, the contract deducts less WAL than we split off and the leftover trips destroy_zero. Verified on prod: on-chain price moved 71464->70922 within ~15 min, and a fresh relayer boot served 6 writes before the same client flipped to 100% destroy_zero at the next price move. Two gaps let this fail hard instead of self-healing: - The sidecar's auto-refresh only fired on isMoveAbortBalanceSplit ('balance' + 'split'); this message says destroy_zero, so refreshWalrusClient never ran. - The Rust worker swept it into the MoveAbort -> Permanent catch, so Apalis Dead-marked every write with no retry. Fix: - enoki.ts: add isMoveAbortWalDestroyZero detector. - walrus-upload.ts: refreshWalrusClient() on the destroy_zero abort so the retry rebuilds against the live price. - jobs.rs: classify the register destroy_zero abort as Transient (retryable), disjoint from the balance::split gas-budget path. - config.ts: drop WALRUS_CLIENT_MAX_AGE_MS default 30m -> 60s so the cached price tracks a live-drifting mainnet price (still env-overridable). Tests: new TS detector suite (7) + Rust classification test using the verbatim prod error. Full suites green (85 TS, 40 jobs).
The sidecar hardcoded getJsonRpcFullnodeUrl(mainnet) and ignored the SUI_RPC_URL env, so it could not be pointed at a dedicated RPC when the public fullnode pool degrades. Observed today: the public pool served stale reads (a blob certified at 00:49:14 read back as 'does not exist' at 00:49:49, 35s later), failing uploads at get_blob/certify/metadata. Honor an explicit SUI_RPC_URL override so ops can switch to a healthy RPC via env; fall back to the network default.
ducnmm
approved these changes
Jul 10, 2026
Collaborator
Style Guide AuditAudited 1 file(s) against the Sui Documentation Style Guide. 19 violation(s) found. All must be fixed before merge.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Implements WALM-264 per the agreed plan: a UI-only delete feature for old V1 memories — the app programmatically builds the
system::delete_blobPTB from the user's blobs, the user only signs, and the backend submits the tx + deletes the DB rows in the same call. Everything ships off by default behindENABLE_MEMORY_DELETION(server) +VITE_ENABLE_MEMORY_DELETION(app) — nothing is user-visible until rollout is agreed.Flow (plan §1)
Tickets
POST /api/delete-memoriesinprotected_routes, owner fromExtension(auth)(never the body). Submits first, aborts everything if the tx doesn't land;cleanup_expired_blobremains the self-heal backstop.sponsor.rs's sidecar call extracted intocall_sidecar_sponsor_execute, shared with the existing/sponsor/executeproxy.POST /api/memory-blob-ids(authenticated, same flag gate) exposing the owner'svector_entriesblob_id set — realizes "get the set from the V1 relayer" for the browser; mixed V1/V2 wallets never see non-memory blobs in the delete list.CleanupSectioncard in the Dashboard (mirrors the Delegate keys card — same Card shell, table classes, 52px header pills). Transport-aware enumeration (gRPClistOwnedObjects/ JSON-RPCgetOwnedObjects), u256→base64url blob-id conversion matching the relayer DB, batch loop resumable mid-run, confirm dialog with the plain-words permanent-deletion warning (§0/§5.3).docs/fundamentals/deleting-old-memories.md(registered in docs.json) + in-UI warning copy.Also included (found while testing on mainnet)
fetchAccountIdForOwnerread the Table id as a string; JSON-RPC returns{ id: { id } }, so every lookup on the JSON-RPC path failed withInvalid paramsand SetupWizard triedcreate_accountfor wallets that already have accounts (abortsEAccountAlreadyExists). Regression from the feat(relayer): gRPC write path for the JSON-RPC sunset (2026-07-31) #355 compat layer, hidden until now because testnet runs gRPC. This one matters for any mainnet deploy.utils/api.tsstill signed the legacy 4-part message with nox-nonce, so the auth middleware rejected it with 426; now signs the current 6-part message like SDK v0.4+.Verified
cargo test: 296 pass (5 pre-existing failures on clean dev, unrelated);cargo check --testscleantsc -b+ vite build +verify-dist-assetspassBefore enabling the flag (not blockers for this merge)
system::delete_blobto the Enoki Portal sponsor allowlist (testnet + mainnet apps) — Enoki currently rejects the sponsor dry-run with "not part of an allow-listed move call target".0x158a78f0…per §6 once the allowlist is in.