-------------------------------------
| |
| · · · · · · ● · · |
| · · · · · · · · · |
| · ● · · · · · · · |
| · · · · ● · · · · |
| · · · · · · · · ● |
| · · ● · · · · · · |
| · · · · · · · ● · |
| · ● · · · ● · · · |
| · · · ● · · · · · |
| |
-------------------------------------
Leave observations on any ERC-721 or ERC-1155 artifact.
Observations is a minimal, permissionless protocol for annotating NFTs. Anyone can leave a text observation on any token — optionally pinned to specific coordinates on the artifact itself. All observations are emitted as events, making them free to write and easy to index.
Inspired by Sam Spratt's The Monument Game.
The core of the protocol is a single Solidity contract: Observations.sol.
Leave a text observation on a token.
function observe(
address collection,
uint256 tokenId,
uint64 parent,
bool update,
string calldata note,
uint8 viewType,
uint32 time
) external payableLeave an observation pinned to a specific location on the artifact.
function observeAt(
address collection,
uint256 tokenId,
uint64 parent,
bool update,
string calldata note,
int32 x,
int32 y,
uint8 viewType,
uint32 time
) external payableEvery observation emits a single event:
event Observation(
address indexed collection,
uint256 indexed tokenId,
address indexed observer,
uint64 id,
uint64 parent,
bool update,
string note,
bool located,
int32 x,
int32 y,
uint8 viewType,
uint32 time,
uint256 tip
)The collection and tokenId are indexed, so you can efficiently filter observations for a specific artifact. The observer is also indexed, so you can query all observations left by a given address. Each observation gets a sequential id scoped to the artifact.
Observations can be edited or deleted by their original author by submitting a new observation with parent set to the target observation's id and update=true.
- Edit:
observe(collection, tokenId, parent=targetId, update=true, "new note", ...)— replaces the content of the target observation. - Delete:
observe(collection, tokenId, parent=targetId, update=true, "", ...)— an empty note signals deletion.
Only the original observer of the target observation may edit or delete it. The update event is always recorded as its own observation (preserving full event history), but indexers should apply the update to the parent and exclude the update record from display. parent=0 with update=false is a regular top-level observation.
The contract tracks a lightweight Artifact record per token:
struct Artifact {
uint64 count; // total number of observations
uint128 firstBlock; // block of the first observation
}Readable via:
mapping(address => mapping(uint256 => Artifact)) public artifacts;This is the only on-chain state. Observation content lives in event logs.
This is a monorepo managed with pnpm workspaces.
| Package | Description | Status |
|---|---|---|
packages/contract |
Solidity contract, tests, and deployment | Ready |
packages/indexer |
Ponder-based indexer for all observations | In progress |
packages/ui |
UI components for creating and viewing observations | In progress |
Built with Hardhat 3 and viem.
cd packages/contract
npx hardhat testA Ponder indexer that watches for Observation events and stores them in a queryable database with a GraphQL API.
A Nuxt app providing embeddable components for hosting observation interfaces — create observations, browse observations on a given artifact, and view observation locations on token media.
pnpm install