AI agents: Logbook can be used programmatically. Start at https://logbook.zone/llms.txt. Remote MCP server: https://logbook.zone/mcp. Read-only REST API: https://logbook.zone/api/v1. Local MCP server: npx -y @logbookprotocol/mcp.

Logbook Beta on Sui Testnet. Learn more →
Documentation

Learn Logbook

Everything you need to create blockchain-verified surveys, understand the technology, and get the most out of Logbook.

DocsArchitecture
13 min read

Technical Architecture

How Logbook works under the hood.

#System Overview

Logbook consists of five components:

  1. 1Frontend: Next.js web application hosted on Cloudflare Pages
  2. 2Smart Contract: a Move package of two modules on the Sui blockchain (currently Testnet): logbook for campaigns and groups for groups
  3. 3Seal key servers: servers that Logbook does not run, which release decryption keys according to the contract's rules
  4. 4Walrus: decentralized storage for the files a campaign is about and for documents about a campaign (AI report, results certificate)
  5. 5Edge API routes: a thin server layer for gas sponsoring, Google sign-in (zkLogin) and AI features
There is no application database. Campaigns, responses, groups and memberships live on-chain, and documents live on Walrus with their hashes on-chain. The server side keeps only a cache of those documents, the files of the lists of groups (see Data Storage), daily quota counters, integrator API keys and remote MCP sessions (Cloudflare storage: KV, and D1 for the counters where it is set up).

#Frontend

#Technology Stack

  • Framework: Next.js 16 with React 19
  • Styling: Tailwind CSS 4
  • State Management: Zustand for forms, React state and hooks for data
  • Blockchain SDK: @mysten/sui 2.x, @mysten/dapp-kit-react for wallets, @mysten/seal for encryption, @mysten/enoki for zkLogin

#Key Features

  • Client-side rendering: pages read the blockchain directly from the browser
  • Encryption and decryption happen only in the browser (WebCrypto + Seal)
  • Data refresh via polling with a user-selectable interval
  • Responsive design with a mobile AI assistant

#Reading the Blockchain

Sui's JSON-RPC API has been retired, so the app uses two transports:
  • gRPC to a full node for current state: objects, balances, epochs, transaction execution
  • GraphQL to the indexer for history: events and past transactions

#Smart Contract

#Location

Deployed on Sui Testnet (mainnet is planned). See the Smart Contract page for the package ID.

#Objects

  • Campaign: title, questions, privacy settings, address list, encrypted content key. Its id is derived from the creator's address and a random nonce, so it is known before the campaign exists. A campaign for the members of groups also holds its participants: the groups and the moment they were frozen
  • Response: one child object per respondent, holding plaintext or encrypted answers
  • Group: owner, admins, how people join, who counts once, the fingerprint of its list, its parent and the groups under it, and the campaigns that reach it. Its id is derived like a campaign's
  • Member shards: up to 16 objects per group that hold the member records and the member cards, so that many people can join at the same time
  • Key shards: up to 16 objects of a community's top group that record which address holds which seat or check
  • Registry: the shared parent every campaign id and every group id is derived from
  • Config: a shared object with the contract version that every function checks; it also holds the addresses of the Logbook treasury and the public key of the check server (empty until the publisher sets it)

#Functions

  • create_campaign, update_campaign, delete_campaign
  • submit_response: record a participant's answers
  • update_allowlist: add and remove addresses of an address-list campaign in one call
  • set_documents, set_document_storage, attach_report, detach_report: the files a campaign is about and the reports about it
  • seal_approve: the access policy that Seal key servers evaluate
  • migrate: retires the previous package version after an upgrade
  • The module groups: creating and setting up a group, joining and leaving, applications, invitations, lists, member cards, admins and ownership, the tree of groups, and campaigns for the members of groups with their own response and key functions (see Smart Contract)
Campaigns close automatically at their end time; there is no separate finalize step. An ended campaign cannot be edited or reopened.

#Encryption Layer (Seal)

Private campaigns use Seal, threshold encryption for Sui:

  1. 1The browser encrypts data for an identity that starts with the campaign id and says what is protected: the content key, answers shared by an audience, one respondent's answers, or report keys
  2. 2To decrypt, the browser signs a short-lived session message (wallet signature, or silently with the zkLogin key)
  3. 3Each key server simulates the contract's seal_approve function for the requesting address (on password campaigns, together with the proof of the password)
  4. 4If the policy passes, the server returns its key share; 2 of 3 shares are needed on Testnet
Groups use the same mechanism with identities that start with the group id: the key of a private group (its name, description and card form), the two parts of a member card, the text of an application, and the admins' copy of a list. The policies are functions of the module groups (see Privacy in Groups).

No single key server can release a key on its own, and Logbook's servers never hold the keys, with one exception: the remote MCP connector decrypts on Logbook's edge for the account connected to it (see Clients and Where Keys Live). The key servers apply whatever policy the current package has, so the holder of the package's upgrade key could change who is served (see Security below). Each key server sees which address asks for which key. See Data Storage for which key protects what, and for what Logbook and others can see.

#Edge API Routes

Small stateless functions running on Cloudflare's edge:

  • /api/sponsor: step one of gas sponsorship: the treasury pays gas for Logbook transactions of Google users and of SDK integrators. It checks the transaction the client sent before it asks the chain anything: at most 4 calls, all into the Logbook package, with plain values or shared objects as inputs, and never the treasury as the sender. It then builds the transaction with the treasury as gas owner, paying from its address balance (never with a coin object, so a transaction that is prepared and never signed locks nothing), simulates it, and answers with the finished bytes and a ticket that is valid for about 5 minutes. A transaction that would fail is refused, not paid for. Only the IP address or the API key is charged for this step, never the sender's address
  • /api/sponsor/sign: step two. The client has checked that the bytes are the transaction it asked for and has signed them; this route checks the ticket and the sender's signature and answers with the treasury's signature. Only now are the sender's daily count and the SUI budgets charged, so nobody can use up the limits of an address they do not control. Daily quotas apply per sender address, per IP address (IPv6 per /64 and per /48) and per API key, and a cap on all sponsorship together can apply as well; an API key can be bound to the websites allowed to use it and limits each sender. A request whose quota cannot be checked is refused. The first route takes bodies of up to 512 KiB, the second up to about 200 KiB: enough for the largest transaction Sui executes (128 KiB), with its plain values in base64 in the first request and in the signed bytes of the second. The remote MCP connector, whose users OAuth has already authenticated, gets its sponsorship in one step inside the server, counted per address
  • Group transactions go through the same two routes with a check of their own. A transaction that calls the module groups may hold up to 280 commands, because joining, releasing a seat or creating a campaign for many groups takes many calls, and results of one call may feed the next. Before it signs, the treasury reads the groups the transaction names and applies the payer rule: an owner's or an admin's call is paid only on a group that was created with sponsored gas, a member's own call in any group. Group transactions have daily limits of their own (see Who Pays in Groups)
  • /api/zklogin/*: exchanges the Google code for a token, derives the Sui address and requests the zero-knowledge proof through Mysten's Enoki service
  • /api/generate-campaign and /api/analyze: AI campaign assistant and results analysis. Both pass what they receive, in plain text, to an external provider (DeepSeek): the assistant the chat and the form draft, the analysis the campaign's texts and the answers its creator confirmed. Wallet addresses are not sent to the AI. An analysis must be signed by the campaign creator; both routes have daily quotas.
  • /api/v1/*: public read-only REST API (see API & SDK)
  • /mcp with /oauth/* and /.well-known/oauth-*: the remote MCP server for AI agents and its OAuth 2.1 authorization server; sessions and tokens live in Cloudflare KV, the session's signing key encrypted with a server secret. Private data of the connected account is decrypted here
  • /api/oauth/*: the consent page's back end (prepares the zkLogin nonce, finishes the Google sign-in, issues the authorization code) and the Connected apps list on the account page, where sessions are ended
  • /api/documents: uploads the files a campaign is about (signed by the uploader, daily quotas per address, per network and overall) and serves cache copies by content hash, as downloads. /api/documents/keep and /api/reports/keep: after the transaction, the client asks the server to keep the cache copies the chain now commits to; the server reads the chain itself, and an upload the chain never commits to expires after two days
  • /api/reports: uploads campaign documents (AI report, results certificate) to Walrus for the creator and keeps a cache copy. Uploads require a signature of the campaign's on-chain creator; documents of private campaigns arrive already encrypted. The pointer and hash are written on-chain by the creator, not by the server
  • /api/groups/lists: stores the two files of a group's list: the published form (one fingerprint per seat, which holders need to take their seats) and the admins' encrypted copy. An upload is signed by an admin, and the server checks on-chain that the signer is the owner or an admin of the group or of its community's top group. Anyone can download both; the copy opens only for the group's admins. The group commits on-chain to the fingerprint of the list and to the hash of the copy

#Clients and Where Keys Live

The web app is one client of the protocol; the same libraries (interface/lib) are bundled into the TypeScript SDK and into the MCP tools, so the protocol logic exists once. What differs between clients is who holds the key that signs:

ClientSigns withKey lives
Web app, walletthe walletin the wallet
Web app, GooglezkLogin ephemeral keybrowser localStorage
SDKthe integrator's key or account callbackswherever the integrator keeps it
Local MCP serverthe agent's key, or a Google sessionon the user's machine
Remote MCP server (/mcp)a Google session created on the consent pageLogbook servers (KV, encrypted), revocable; private data is decrypted there
Draft linksnobody: the person who opens the link signs in the web app—

#Data Flow

#Creating a Campaign

  1. 1User fills the form in the frontend
  2. 2The browser picks a random nonce, which fixes the campaign id before the campaign exists
  3. 3For private campaigns it generates a content key, encrypts the content and seals the key with Seal for the campaign; for password campaigns it also derives the password and the public key that checks proofs of it, and wraps the key with the password
  4. 4Frontend builds the create_campaign transaction
  5. 5A wallet user signs and pays the gas. A Google user's browser asks the treasury to sponsor it: it gets the transaction back with the treasury's gas, checks that it is the same transaction, signs it with the zkLogin key, and the treasury then co-signs
  6. 6Campaign object is created on-chain

#Submitting a Response

  1. 1User selects answers
  2. 2If answers are not public, the browser encrypts them (content key or Seal), bound to the campaign and the respondent and padded to a multiple of 256 bytes
  3. 3Frontend builds the submit_response transaction, naming the content version and access epoch the answers were prepared for (on password campaigns with the proof of the password), and the user signs it
  4. 4The contract checks access, version and epoch and attaches a Response object to the campaign; a response prepared before an edit or a removal from the address list is refused and prepared again
  5. 5Public answers are validated and counted by the contract; encrypted ones are opaque to it

#Reading Results

  1. 1Frontend loads the Campaign object and its responses over gRPC
  2. 2Public answers are tallied straight from the chain and compared with the counts the contract keeps
  3. 3Encrypted answers are decrypted in the browser for users the policy allows, checked against the answer rules, then tallied client-side; malformed or copied answers are marked invalid and not counted
  4. 4Updates arrive via polling

#Security

#On-Chain

  • Move language prevents common vulnerabilities
  • One response per address enforced by contract
  • Only the creator can edit or delete a campaign, and only before the first response
  • The creator's changes are paid the way the campaign was created, so storage a sponsor paid for is refunded to that sponsor, never to someone else
  • Address lists and answer visibility are enforced by the contract through Seal, not by the website
  • The participants of a campaign for the members of groups are frozen at its creation: the contract accepts a response only from an address whose membership covers that moment, whatever happens to the groups afterwards
  • A group is changed only by its owner and admins, and paid the way it was created (see Who Pays in Groups)
  • The password of a password campaign is checked by the contract: responding and requesting keys need a signature by a key derived from the campaign key the password unlocks, valid for one address only
  • Public answers are validated on-chain, and every Seal identity starts with the campaign id, so one campaign's keys can never be obtained through another
  • Every function checks the version in the shared Config: after an upgrade that raises the version, the previous package version stops working, so a fix cannot be bypassed by calling old code
  • During the beta the package is upgradeable and Logbook holds its UpgradeCap with a single key (no multisig, no delay). An upgrade is a public transaction, but it can change any rule, including who may decrypt what: the holder of the UpgradeCap is part of what you trust, and could read everything that is encrypted by publishing such an upgrade

#Frontend

  • Wallet users: private keys never leave the wallet
  • Google users: a temporary (ephemeral) key pair, its ZK proof and the Google token are kept in the browser's localStorage and cleared on logout; the key is valid for up to 30 epochs (about 30 days)
  • Campaign content keys and Seal session keys live in sessionStorage (session keys expire after 15 minutes)
  • Campaign passwords are kept in localStorage for the signed-in account and removed on logout; a signed-out visitor's password lives in sessionStorage and ends with the tab
  • Campaign passwords, campaign drafts and answers pre-selected by a draft link travel in the link fragment (after #), which browsers never send to servers