Writer is an onchain writing platform. Content is permanently stored on Optimism through smart contracts.
WriterFactory:0xF532F814a7A7291440Fc27AbB8CC845Ab86709c8
ColorRegistry:0xA083Ff0E24F616AfD12755d7169256334669d4DD

Factory contract that deploys Writer + WriterStorage pairs using CREATE2 for deterministic addresses.
create(title, admin, managers, salt)§Deploy a new Writer and WriterStorage contract pair.
Parameters
Returns
(address writerAddress, address storeAddress)Events
WriterCreated(writerAddress, storeAddress, admin, title, managers)computeWriterStorageAddress(salt)§Pre-compute the address a WriterStorage would be deployed to with the given salt.
Access: View
Parameters
Returns
addresscomputeWriterAddress(title, admin, managers, salt)§Pre-compute the address a Writer would be deployed to with the given parameters.
Access: View
Parameters
Returns
address
Main logic contract for managing entries with role-based access control.
getEntryCount()§Returns the total number of entries.
Access: View
Returns
uint256getEntryIds()§Returns an array of all entry IDs.
Access: View
Returns
uint256[]getEntry(id)§Returns the full entry struct including chunks, author, and timestamps.
Access: View
Parameters
Returns
Entry { createdAtBlock, updatedAtBlock, chunks[], totalChunks, receivedChunks, author }getEntryContent(id)§Returns the concatenated content of all chunks for an entry.
Access: View
Parameters
Returns
stringgetEntryChunk(id, index)§Returns a specific chunk's content.
Access: View
Parameters
Returns
stringcreateWithChunk(chunkCount, content)§Create a new entry with the first chunk of content. Caller becomes the entry author.
Access: WRITER_ROLE
Parameters
Returns
(uint256 entryId, Entry entry)Events
EntryCreated(id, author)ChunkReceived(author, id, index, content)createWithChunkWithSig(signature, nonce, chunkCount, content)§Create a new entry with the first chunk via EIP-712 signature. Signer becomes the entry author.
Access: Signer must have WRITER_ROLE
Parameters
Returns
(uint256 entryId, Entry entry)Events
EntryCreated(id, author)ChunkReceived(author, id, index, content)addChunk(id, index, content)§Add a chunk to an existing entry at a specific index.
Access: Author + WRITER_ROLE
Parameters
Returns
EntryEvents
ChunkReceived(author, id, index, content)addChunkWithSig(signature, nonce, id, index, content)§Add a chunk to an existing entry via EIP-712 signature.
Access: Signer must be author + WRITER_ROLE
Parameters
Returns
EntryEvents
ChunkReceived(author, id, index, content)update(id, totalChunks, content)§Replace an entry's content. Clears all previous chunks and sets new content.
Access: Author + WRITER_ROLE
Parameters
Returns
EntryEvents
EntryUpdated(id, author)ChunkReceived(author, id, index, content)updateWithSig(signature, nonce, id, totalChunks, content)§Replace an entry's content via EIP-712 signature.
Access: Signer must be author + WRITER_ROLE
Parameters
Events
EntryUpdated(id, author)ChunkReceived(author, id, index, content)remove(id)§Delete an entry.
Access: Author + WRITER_ROLE
Parameters
Events
EntryRemoved(id, author)removeWithSig(signature, nonce, id)§Delete an entry via EIP-712 signature.
Access: Signer must be author + WRITER_ROLE
Parameters
Events
EntryRemoved(id, author)setTitle(newTitle)§Update the writer's title.
Access: DEFAULT_ADMIN_ROLE
Parameters
Events
TitleSet(title)setTitleWithSig(signature, nonce, newTitle)§Update the writer's title via EIP-712 signature.
Access: Signer must have DEFAULT_ADMIN_ROLE
Parameters
Events
TitleSet(title)replaceAdmin(newAdmin)§Transfer admin role to a new address. Revokes admin from the caller.
Access: DEFAULT_ADMIN_ROLE
Parameters

Storage contract that holds all entry data. Only the Writer logic contract can modify state, enforced by the onlyLogic modifier.
Entry struct§The data structure for each entry stored onchain.
Parameters

Simple registry mapping user addresses to their chosen hex color.
setHex(hexColor)§Set your color directly.
Parameters
Events
HexSet(user, hexColor)setHexWithSig(signature, nonce, hexColor)§Set your color via EIP-712 signature.
Parameters
Events
HexSet(user, hexColor)getPrimary(user)§Get a user's hex color.
Access: View
Parameters
Returns
bytes32Public read endpoints are available to any client. Browser write endpoints are restricted to authenticated frontend clients (Privy bearer token required); programmatic agent writes should use the x402 endpoints documented below.
https://api.writer.place
/writer/public§List all public writers.
Response
{ writers: Writer[] }/writer/:address§Get a specific writer and all its entries.
Parameters
Response
{ writer: Writer }https://writer.place/writer/:address.md§Fetch a Writer Place summary as Markdown, including frontmatter metadata and public entry links. The canonical Place URL also supports content negotiation with Accept: text/markdown.
Parameters
Response
text/markdown; charset=utf-8/manager/:address§Get all writers managed by an address.
Parameters
Response
{ writers: Writer[] }
/writer/:address/entry/:id§Get a confirmed entry by its onchain ID.
Parameters
Response
{ entry: Entry }https://writer.place/writer/:address/:id.md§Fetch a public/plaintext entry as Markdown with provenance frontmatter from the web app. The canonical HTML entry URL also supports content negotiation with Accept: text/markdown. Encrypted entries return 403 because the server does not have wallet-derived decryption keys.
Parameters
Response
text/markdown; charset=utf-8/writer/:address/entry/pending/:id§Get a pending entry before onchain confirmation.
Parameters
Response
{ entry: Entry }
/me/:address§Get user data for an address.
Parameters
Response
{ user: User }
Agent-oriented write endpoints use x402 payments instead of Privy browser auth. See /agents.md for operational guidance and safety policy.
x402 endpoints return a payment challenge when payment is required. The x402 payer must match the action signer: for Place creation the payer must equal the requested admin address; for entry creates, updates, and deletes the payer must equal the recovered EIP-712 signer.
agent prepares request → x402 payment → EIP-712 signature where needed → relay transaction → pending response → indexer confirmation
/x402/factory/create§Create a new Writer Place. The x402 payer becomes the admin and sole manager.
Auth: x402 payment; payer must equal address
Request Body
Response
{ writer: Writer & { transactionId: string, createdAtHash: null } }/x402/writer/:address/entry/createWithChunk§Create an entry in a Writer Place using an EIP-712 CreateWithChunk signature. The content is the final encoded content string, not necessarily raw markdown.
Auth: x402 payment; payer must equal recovered entry author
Parameters
Request Body
Response
{ pending: { transactionId: string, author: address } }/x402/writer/:address/entry/:id/update§Update an entry using an EIP-712 Update signature. The content is the full replacement encoded content string.
Auth: x402 payment; payer must equal recovered entry author
Parameters
Request Body
Response
{ pending: { transactionId: string, author: address } }/x402/writer/:address/entry/:id/delete§Delete an entry using an EIP-712 Remove signature. Deletion updates Writer state; it does not erase historical blockchain data.
Auth: x402 payment; payer must equal recovered remover/signing author
Parameters
Request Body
Response
{ pending: { transactionId: string, signer: address } }Writer Places can be fetched as markdown using /writer/:address.md, or from the canonical Place URL with Accept: text/markdown. Public entries can be fetched as markdown with provenance frontmatter using /writer/:address/:id.md, or from the canonical entry URL with Accept: text/markdown. Private entries are returned by the API as opaque encoded content and must be decrypted client-side with the author wallet.
Machine-readable discovery is available at /.well-known/writer-agent.json. These docs are available as markdown at /docs.md, or from /docs with Accept: text/markdown. Public Place discovery is available at /explore.md, or from /explore with Accept: text/markdown. OpenAPI is available at /openapi.json.
Agent safety guidance is published at /agents.md.
Entry content goes through a multi-step encoding pipeline before being stored onchain. The content & chunkContent fields in API requests contain the final encoded string, not raw markdown.
markdown → compress → encrypt (optional) → prefix → store
The version prefix at the start of the stored content string indicates how the official Writer frontend decodes it.
Writer contracts store entry content as strings and do not enforce a specific encryption scheme. The prefixes documented here describe the format used by Writer's frontend. Other clients may store different formats, but compatibility with Writer's frontend requires following this convention.
no prefixPublic entry. Raw markdown. Compatible with Writer's UI, but larger onchain than Brotli-compressed content.
# We're just living on the edge of somebody else's civilization, like fleas on a dog's back. If the dog drowns, the fleas drown, too.
br:Public entry. Brotli compressed, Base64 encoded. No encryption.
br:GxoAAI2pVgqN...
enc:v5:br:Private entry, current format. Brotli compressed, then AES-GCM encrypted with the v5 per-writer key.
enc:v5:br:A7f3kQ9x...
Older private prefixes including enc:v4:br:, enc:v3:br:, enc:v2:br:, and enc:br: are legacy formats supported for backwards compatibility. The app can migrate older entries to the current enc:v5:br: format.

All content is compressed with Brotli at quality level 11 (maximum), then Base64 encoded. This reduces onchain storage costs.
markdown → UTF-8 encode → brotli compress (quality 11) → base64 encode

Private entries are encrypted after compression using AES-GCM with a 256-bit key and 12-byte random IV.
compressed content → AES-GCM encrypt → prepend IV → base64 encode
For current private entries, the encryption key is derived locally from an EIP-712 wallet signature bound to the Writer's storage ID:
eth_signTypedData_v4writer.place encryption, version 1Writer:enc:v5:AES-256-GCMThe key never leaves the client. Writer's server, database, and smart contracts store opaque ciphertext and cannot decrypt private entry content.
Private means content-encrypted, not invisible. Onchain metadata such as writer address, author address, timestamps, entry count, and approximate content size may still be public.
Users are responsible for wallet access. If the author wallet is lost, private entries encrypted for that wallet may be unrecoverable. Only sign Writer encryption messages on writer.place.
The format is intentionally deterministic and self-custodial: anyone with the onchain ciphertext, the author wallet, and this derivation spec can rebuild a reader without Writer's frontend or backend.
V5 is the current default. V1, V2, V3, and V4 are supported only for backward compatibility with older entries. A migration tool is available in the app to re-encrypt legacy entries with the current V5 format.

To read an entry, reverse the pipeline based on the prefix.
br:strip prefix → base64 decode → brotli decompress
enc:v5:br:strip prefix → base64 decode IV + ciphertext → AES-GCM decrypt (v5 key) → brotli decompress
Public entries are decoded server-side and returned as plaintext in the decompressed field. Private entries are returned as the raw encoded string and decrypted client-side using the author's wallet.