Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/covenant-gov/pacto-app/llms.txt

Use this file to discover all available pages before exploring further.

Pacto stores all account data under a dedicated directory named after the account’s full npub1… string, located inside the Tauri app_data_dir. This means every Nostr identity gets its own isolated filesystem subtree — the app database, the MLS engine database, and cached media are all scoped to one account and cannot bleed into another. Switching accounts points the runtime at a different directory; logout deletes only the current account’s directory.

Directory layout

Path (under Tauri app_data_dir)Role
<npub>/Profile directory — account_manager::get_profile_directory
<npub>/vector.dbPrimary app database — get_database_path
<npub>/mls/MLS directory — get_mls_directory
<npub>/mls/vector-mls.dbMDK engine database (cryptographic MLS state)
Account discovery scans for npub1* subfolders under app_data_dir and validates the stored keys — see list_accounts in account_manager.rs.

vector.db schema

The authoritative CREATE TABLE statements live in SQL_SCHEMA in src-tauri/src/account_manager.rs. They are applied by init_profile_database for new accounts. run_migrations in the same file adds columns and tables for existing installations without losing data.

Core tables

TableRole
profilesContacts and users: npub, names, avatar URLs, evm_address, cached paths
chatsConversation metadata: chat_identifier (npub or hex group id), chat_type, participants JSON
messagesLegacy/parallel message storage (encrypted content field)
eventsPrimary flat storage for Nostr-shaped events: kind, content, tags JSON, chat_id, pending/failed flags, wrapper_event_id
settingsKey-value store: pkey, evm_pkey, evm_address, and other account-level settings
mls_groupsMLS group metadata: wire id, engine id, name, eviction flag, timestamps
mls_keypackagesKey package cache for members and devices
mls_event_cursorsSync cursors per group — last seen kind 444 event for backfill
squad_safeSquad/network id → Safe address mapping
Indexes are defined inline with each table in SQL_SCHEMA. Duplicate index definitions may also appear inside run_migrations blocks for tables introduced after the initial schema.

Encryption vs plaintext

account_manager.rs documents the storage intent clearly: message content and secrets are stored encrypted where noted; profiles and indexing metadata are stored plaintext for performance and search. The exact encrypt/decrypt call sites live in crypto.rs, db.rs, and lib.rs. This split means you can query the profiles table for display names and avatars without any decryption step, but reading events.content for a DM requires the PIN-derived key to be present.

Message encryption

Direct message content and similar sensitive event rows are encrypted before being written to SQLite — the value stored in events.content is not the raw NIP-44 relay ciphertext; it is a second layer of encryption applied locally. Write path (db.rssave_event):
internal_encrypt(event.content.clone(), None).await
Read path (loading events for the UI):
internal_decrypt(event.content, None).await
Both functions live in crypto.rs and use the global ENCRYPTION_KEY — a 32-byte key derived from the user’s PIN via Argon2. Passing password: None tells the function to use the cached in-memory key. If decryption fails (wrong PIN, key not set after a hot logout/login, or genuinely corrupt row), callers substitute the literal string [Decryption failed] so the UI renders a row rather than crashing. ENCRYPTION_KEY lifecycle: The key is stored in a OnceCell in lib.rs. Setting it twice in one process lifetime can fail or leave stale behavior. After logout, if the process stays alive and the user logs in again, ensure PIN entry re-derives and resets the key consistently. Test these flows:
  1. Logout → login same npub + same PIN
  2. Logout → login different npub
  3. [Decryption failed] should appear only for genuinely corrupt rows or a wrong PIN — never for a fresh account after a full wipe.

Account isolation

What logout deletes

LocationRemoved on logout?
AppData/<npub>/ (profile dir: vector.db, mls/, keys, chats, events)Yes — current account only
Downloads/Documents vector/ folder (attachments)Yes
app_data_dir()/cache/No — only cleared by explicit “Clear storage”
Legacy AppData/mls/Yes (compatibility cleanup)
Other npubs’ profile directoriesNo — multi-account data is not touched
The logout command in lib.rs locks STATE, calls close_db_connection() (required on Windows before file deletion), removes the profile directory with remove_dir_all, clears the Nostr client, and clears CURRENT_ACCOUNT. It does not restart the process.

Multi-account support

switch_account(handle, npub) points the runtime at another existing npub’s directory without deleting any data. login(import_key) opens a new profile directory and loads its database. Multiple accounts can coexist on disk; only one is active in the runtime at a time.

Frontend localStorage scoping

Frontend state that persists across sessions — navigation prefs, last-opened squad/channel — uses npub-scoped localStorage keys built with persistenceKey(prefix). On logout, clearAccountState (in src/lib/utils/clear-account-state.ts) resets domain stores and invite accept state before or alongside invoke('logout'). Unscoped keys can leak the previous account’s navigation state until cleared.

Canonical code locations

ConcernLocation
SQL_SCHEMA + init_profile_database, run_migrationssrc-tauri/src/account_manager.rs
Connection pool, most queries and #[tauri::command] DB entry pointssrc-tauri/src/db.rs
Event row shape / kindssrc-tauri/src/stored_event.rs
Profile row / Nostr profile mergesrc-tauri/src/profile.rs
vector-mls.db is owned exclusively by the MDK engine. Do not open it with an external SQLite tool and modify its contents. Any manual edit to the ratchet tree state, epoch secrets, or key packages will corrupt the engine’s view of group membership and make MLS groups unrecoverable for that account.
Naming note: Comments in the Rust source sometimes say “Vector database” or reference a “Vector” app. The shipped application name is Pacto — the files, paths, and database names are the same. This is a historical artifact from an earlier project name and does not indicate a separate system.

Build docs developers (and LLMs) love