WriterDocs

Writer is an onchain writing platform. Content is permanently stored on Optimism through smart contracts.

Smart Contracts§

WriterFactory§

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

title : string Name of the writer/publication
admin : address Admin address for the writer
managers : address[] Addresses granted the WRITER role
salt : bytes32 Salt for deterministic deployment

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

salt : bytes32 Deployment salt

Returns

address
computeWriterAddress(title, admin, managers, salt)§

Pre-compute the address a Writer would be deployed to with the given parameters.

Access: View

Parameters

title : string Writer title
admin : address Admin address
managers : address[] Manager addresses
salt : bytes32 Deployment salt

Returns

address

Writer§

Main logic contract for managing entries with role-based access control.

Reading§

getEntryCount()§

Returns the total number of entries.

Access: View

Returns

uint256
getEntryIds()§

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

id : uint256 Entry ID

Returns

Entry { createdAtBlock, updatedAtBlock, chunks[], totalChunks, receivedChunks, author }
getEntryContent(id)§

Returns the concatenated content of all chunks for an entry.

Access: View

Parameters

id : uint256 Entry ID

Returns

string
getEntryChunk(id, index)§

Returns a specific chunk's content.

Access: View

Parameters

id : uint256 Entry ID
index : uint256 Chunk index

Returns

string

Writing§

createWithChunk(chunkCount, content)§

Create a new entry with the first chunk of content. Caller becomes the entry author.

Access: WRITER_ROLE

Parameters

chunkCount : uint256 Total number of chunks for this entry
content : string First chunk content

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

signature : bytes EIP-712 typed data signature
nonce : uint256 Unique nonce for replay protection
chunkCount : uint256 Total number of chunks
content : string First chunk content

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

id : uint256 Entry ID
index : uint256 Chunk index
content : string Chunk content

Returns

Entry

Events

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

signature : bytes EIP-712 typed data signature
nonce : uint256 Unique nonce for replay protection
id : uint256 Entry ID
index : uint256 Chunk index
content : string Chunk content

Returns

Entry

Events

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

id : uint256 Entry ID
totalChunks : uint256 New total chunks
content : string New first chunk content

Returns

Entry

Events

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

signature : bytes EIP-712 typed data signature
nonce : uint256 Unique nonce for replay protection
id : uint256 Entry ID
totalChunks : uint256 New total chunks
content : string New first chunk content

Events

EntryUpdated(id, author)ChunkReceived(author, id, index, content)
remove(id)§

Delete an entry.

Access: Author + WRITER_ROLE

Parameters

id : uint256 Entry ID

Events

EntryRemoved(id, author)
removeWithSig(signature, nonce, id)§

Delete an entry via EIP-712 signature.

Access: Signer must be author + WRITER_ROLE

Parameters

signature : bytes EIP-712 typed data signature
nonce : uint256 Unique nonce for replay protection
id : uint256 Entry ID

Events

EntryRemoved(id, author)

Administration§

setTitle(newTitle)§

Update the writer's title.

Access: DEFAULT_ADMIN_ROLE

Parameters

newTitle : string New title

Events

TitleSet(title)
setTitleWithSig(signature, nonce, newTitle)§

Update the writer's title via EIP-712 signature.

Access: Signer must have DEFAULT_ADMIN_ROLE

Parameters

signature : bytes EIP-712 typed data signature
nonce : uint256 Unique nonce for replay protection
newTitle : string New title

Events

TitleSet(title)
replaceAdmin(newAdmin)§

Transfer admin role to a new address. Revokes admin from the caller.

Access: DEFAULT_ADMIN_ROLE

Parameters

newAdmin : address New admin address

WriterStorage§

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

createdAtBlock : uint256 Block number when entry was created
updatedAtBlock : uint256 Block number of last update
chunks : string[] Array of content chunks
totalChunks : uint256 Expected total number of chunks
receivedChunks : uint256 Number of chunks received so far
author : address Address of the entry author

ColorRegistry§

Simple registry mapping user addresses to their chosen hex color.

setHex(hexColor)§

Set your color directly.

Parameters

hexColor : bytes32 Color in bytes32 format

Events

HexSet(user, hexColor)
setHexWithSig(signature, nonce, hexColor)§

Set your color via EIP-712 signature.

Parameters

signature : bytes EIP-712 signature
nonce : uint256 Unique nonce for replay protection
hexColor : bytes32 Color in bytes32 format

Events

HexSet(user, hexColor)
getPrimary(user)§

Get a user's hex color.

Access: View

Parameters

user : address User address

Returns

bytes32

API§

Public read endpoints. Write endpoints exist but are restricted to authenticated frontend clients (Privy bearer token required) and are intentionally not documented here.

Base URL:

https://api.writer.place

Writers§

GET/writer/public§

List all public writers.

Response

{ writers: Writer[] }
GET/writer/:address§

Get a specific writer and all its entries.

Parameters

address : address Writer contract address

Response

{ writer: Writer }
GET/manager/:address§

Get all writers managed by an address.

Parameters

address : address Manager wallet address

Response

{ writers: Writer[] }

Entries§

GET/writer/:address/entry/:id§

Get a confirmed entry by its onchain ID.

Parameters

address : address Writer contract address
id : bigint Onchain entry ID

Response

{ entry: Entry }
GET/writer/:address/entry/pending/:id§

Get a pending entry before onchain confirmation.

Parameters

address : address Writer contract address
id : string Database entry ID

Response

{ entry: Entry }

User§

GET/me/:address§

Get user data for an address.

Parameters

address : address User wallet address

Response

{ user: User }

Content Encoding§

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

Format Prefixes§

The version prefix at the start of the stored content string indicates how to decode it.

br:

Public entry. Brotli compressed, Base64 encoded. No encryption.

br:GxoAAI2pVgqN...

enc:v3:br:

Private entry, current format. AES-GCM encrypted with v3 key, Brotli compressed.

enc:v3:br:A7f3kQ9x...

enc:v2:br:

Deprecated

enc:br:

Deprecated

Compression§

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

Encryption§

Private entries are encrypted after compression using AES-GCM with a 128-bit key and 12-byte random IV.

compressed content → AES-GCM encrypt → prepend IV → base64 encode

The encryption key is deterministically derived from a wallet signature:

  1. User signs a fixed message with personal_sign
  2. Signature is hashed with Keccak-256
  3. First 16 bytes of the hash become the AES key

The key never leaves the client. Only the entry author can decrypt their private entries — the server and contract store opaque ciphertext.

V1, V2, and V3 keys differ only in the message signed during key derivation. V3 is the current default and includes a security warning to only sign on writer.place. V1 and V2 are supported for backward compatibility with older entries. A migration tool is available in the app to re-encrypt legacy entries with the V3 key.

Decoding§

To read an entry, reverse the pipeline based on the prefix.

br:

strip prefix → base64 decode → brotli decompress

enc:v3:br:

strip prefix → base64 decode → AES-GCM decrypt (v3 key) → brotli decompress

enc:v2:br:

Deprecated

enc:br:

Deprecated

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.

Writer