Architecture
How Plinth stores forms and submissions, where the trust boundaries fall, and what the browser, Sui, Walrus, and Seal each do.
Layers
The browser is the only authority that reads, encrypts, and submits. The serverless routes provided by Next.js are limited to thin proxies for CORS and rate friendly aggregator reads. No Plinth managed database stores form data.
Browser
|
Next.js App Router
|
+----------+----------+
| | |
Sui RPC Walrus Seal
(mainnet) (HTTP) (key servers)
| | |
+----------+----------+
|
Sui mainnet
Walrus storageOnchain data model
The Move package plinth defines four modules.
form_registry
A single shared FormRegistry object indexes every form by slug. Each entry stores the form owner, the schema blob ID on Walrus, the schema version, the admin allowlist address pointer, and the creation epoch. The registry is shared, but only the form owner can update or retire a form.
public struct Form has key {
id: UID,
slug: String,
owner: address,
schema_blob_id: String,
schema_version: u64,
admin_allowlist_id: ID,
created_at_epoch: u64,
retired: bool,
}submission
Each submission is its own Sui object owned by the submitter address. It records the parent form ID, the submission blob ID on Walrus, the encryption envelope ID, the submitted at epoch, and a revoked flag. The submitter can call revoke to mark the submission tombstoned. The form owner can call prioritize, note, and set_status only against the index, not against the submission object itself, so submitter ownership is preserved.
public struct Submission has key, store {
id: UID,
form_id: ID,
submitter: address,
blob_id: String,
seal_envelope_id: String,
submitted_at_epoch: u64,
revoked: bool,
}admin
AdminAllowlist is a shared object listing wallet addresses that can decrypt the sensitive fields of a form. The form owner holds an AdminCap and can add or remove entries. A second cap, OwnerCap, can transfer ownership of the form to a different wallet. Both caps are independent objects so they can be rotated or held in cold storage.
events
Every public action emits an event so indexers and aggregators can build read views without polling.
| Event | Emitted on |
|---|---|
| FormCreated | New form published |
| FormUpdated | Schema or admin allowlist changed |
| FormRetired | Form taken offline |
| SubmissionCreated | New submission written |
| SubmissionRevoked | Submitter revokes their submission |
| SubmissionAnnotated | Admin sets note, priority, or status |
Walrus blob layout
Schema blob
{
"schemaVersion": 1,
"name": "Bug reports for Plinth",
"description": "Report a bug. Plain language is fine.",
"fields": [
{
"id": "title",
"type": "rich_text",
"label": "Short summary",
"required": true,
"encrypted": false
},
{
"id": "severity",
"type": "dropdown",
"label": "Severity",
"required": true,
"options": ["low", "medium", "high"],
"encrypted": false
},
{
"id": "wallet_email",
"type": "rich_text",
"label": "Contact email",
"required": false,
"encrypted": true
}
],
"adminAllowlist": [
"0xdd9ae8b9038f1149eb3791ac0a698105bb248af883f2c684f9d8f837f71fd2da"
]
}Submission blob
{
"schemaVersion": 1,
"formId": "0x...",
"submitter": "0x...",
"submittedAtEpoch": 142,
"fields": {
"title": "Form reordering breaks on Safari",
"severity": "high",
"wallet_email": {
"ciphertext": "base64",
"envelopeId": "seal-...",
"policy": "form-admin-allowlist"
}
}
}Encrypted fields carry a Seal envelope identifier that resolves to a key policy. The key policy is enforced by Seal key servers and requires the requesting wallet to be on the form admin allowlist.
Seal key hierarchy
Form key policy
|
resolves to allowlist of
|
+---------+---------+
| |
Form owner Admin allowlist
| |
OwnerCap AdminCap holdersWhen a public submitter encrypts a sensitive field, the client retrieves the public key bundle for the form policy from Seal and encrypts client side. When an admin wants to read, the client requests a session key from Seal key servers, signs a wallet challenge, and retrieves the data key only if the wallet is on the form allowlist and the policy has not been revoked.
Trust boundaries
| Boundary | Trusted to |
|---|---|
| Browser of submitter | Encrypt before upload, sign Sui transaction |
| Sui mainnet validators | Honor object ownership and allowlist updates |
| Walrus storage nodes | Persist blob, return blob on read |
For the full threat model and capability semantics, see the Security page.