PIXI documentation · v0.1

A token you can
paint together.

PIXI combines a standard fractional ERC-20 balance with persistent 8×8 canvases. This guide explains the user experience, storage model, transfer rules, contract interface, frontend, and integration boundaries.

01 / Overview

What is PIXI?

PIXI is one asset with two coordinated layers: divisible value for trading and indivisible canvases for making art.

At the amount layer, PIXI behaves like a conventional ERC-20 with 18 decimals. A wallet may hold or transfer any base-unit amount. At the canvas layer, every complete 1e18 units gives that wallet control of one numbered 8×8 image. The protocol continuously keeps those two facts aligned:

Protocol invariant active canvases(account) = floor(balanceOf(account) / 1e18)

A canvas is not a separate NFT. It is persistent state attached to a numbered PIXI block. Its identity, bitmap, revision counter, owner, and wallet-relative placement are read from the token contract. The website is a renderer and transaction client; it is never the source of truth.

Liquid layer12.42 PIXIERC-20 amount · wallets · routers · pools
kept in sync
Canvas layer12 canvasesIDs · pixels · placement · revisions

02 / Concepts

The mental model

01

Fraction

Any balance below a whole PIXI. It remains fully transferable but does not independently control a canvas.

02

Block

One numbered, persistent canvas identity activated by one whole PIXI. “Block,” “tile,” and “canvas” refer to the same 8×8 unit in different contexts.

03

Wallet mural

The visual composition of all tiles controlled by one account, arranged at signed integer tile coordinates.

04

Reserve

The contract-held inventory where a canvas waits when fractional transfers reduce a wallet’s whole balance without giving another wallet a whole unit.

Terminology

Bitmap
The 32-byte value containing all 64 four-bit color indexes.
Revision
A per-canvas counter incremented whenever the bitmap is saved.
Placed
Whether a tile has an explicit coordinate in its owner’s mural.
Materialized
A canvas whose ownership has moved from the implicit genesis inventory into explicit storage.
World epoch
A monotonically increasing counter advanced after every swap in the canonical pool.
Canonical pool
The one native-ETH/PIXI Uniswap v4 pool accepted by the contract’s hook validation.

03 / User guide

Quick start

1

Connect on Ethereum

Open the Studio and connect a wallet holding PIXI. The interface reads liquid balance, blank canvas slots, and materialized canvases directly from the verified mainnet contract.

2

Paint a canvas

Paint a blank draft or select an existing canvas. Pick one of 16 colors and edit the 8×8 bitmap locally before spending gas.

3

Arrange your mural

Move canvases at wallet-relative coordinates. Add several blank drafts to build a larger painting and save them together.

4

Review and save

New drafts use one atomic activateCanvases transaction. Existing canvases use one atomic compose transaction.

5

Confirm onchain

Approve the transaction in your wallet and wait for confirmation. The Studio reloads contract state and links to the transaction in the configured explorer.

Local edits are free.

Gas is spent only when a mainnet transaction is signed and accepted. Closing or refreshing before saving discards unsaved edits.

04 / Interface

Studio control reference

A

Collection

Lists every active canvas returned by blocksOf(wallet). Each card shows its ID, preview, and x,y placement. Refresh discards local drafts and reloads the current source.

B

Wallet canvas

Renders all placed tiles as one mural. The size label is the bounding rectangle in pixels, not the sum of painted pixels. The ruby outline marks the selected tile.

C

Palette

Selects a four-bit color index from 0 through 15. The name is a UI label; only the numeric index is stored onchain.

D

Tools

Undo restores the last stroke or operation. Reset restores the selected tile’s last loaded bitmap. Clear fills it with Paper. Remix generates random palette indexes.

E

Selected tile

Displays the ID and placement. Arrow controls change tile coordinates locally. Negative coordinates are valid; the mural view normalizes its bounds for display.

F

State and save

Shows token balance, active canvases, owner, selected revision, and total current bitmap bytes. The save button is enabled only when at least one tile differs from the loaded state.

G

Bitmap strip

Displays the selected tile’s packed bytes32. Copying it is useful for verification, calldata generation, and independent rendering.

H

World

Groups canvases by current owner. Zoom changes rendering scale; the viewport itself scrolls. Selecting your group returns to the Studio.

Interaction and accessibility details

  • Pointer dragging paints a continuous stroke. One pre-stroke snapshot is added to Undo when the pointer is released.
  • Focused pixels respond to Enter and Space. Controls expose labels, selection state, and status messages to assistive technology.
  • The coordinate readout uses mural pixels: globalX = tileX × 8 + localX and the same rule for Y.
  • Undo history exists only in memory and is cleared when records are reloaded. Reset affects pixels, not tile placement.
  • A move into an occupied coordinate is blocked before submission. The contract independently enforces the same uniqueness rule.

05 / Transactions

What happens when you save

BrowserCollect dirty tilesID · bitmap · x · y
WalletSign composeOne transaction
ContractValidate & commitAtomic or revert
StudioRead state againConfirmed source of truth

The frontend packs each changed tile and calls compose(uint256[],bytes32[],int16[],int16[]). The arrays are parallel: item i in every array describes the same canvas. The contract first verifies that lengths match, every ID is owned by the caller, and no ID is duplicated. It clears affected placements, then saves each bitmap and applies each new placement.

Because all changes are in one EVM transaction, a cross-tile stroke cannot be half-saved. If ownership, calldata, placement, wallet approval, gas, or execution fails, every state change reverts. The UI keeps the local draft available after most failures so it can be retried.

New canvases

The studio activates up to 32 painted and positioned drafts in one transaction.

Existing murals

The studio submits only changed canvases, polls the receipt, and verifies every bitmap, revision, and position after confirmation.

06 / World view

How PIXI World is assembled

World is an ownership-grouped projection, not a second onchain map. Records are grouped by the lowercase owner address. Each group renders its tiles at that owner’s wallet-relative coordinates, while the group itself is positioned by the frontend for browsing.

  • World contains only canvases materialized in the live PIXI contract. No sample wallets or mock paintings are included.
  • The Studio reads canvas IDs from 1 up to the smaller of materializedCanvasSupply and the browser scan limit.
  • Unplaced records are assigned display-only open coordinates. This does not write placement to the contract.
  • The bounded browser scan is intentionally simple. A production world should use an event indexer to avoid many RPC calls and to cover the full supply.

07 / Protocol

Balance boundaries and canvas control

For a transfer amount, the contract records each account’s whole balance before and after updating ERC-20 balances. The difference determines how many canvases leave the sender and how many arrive for the recipient.

BalanceWhole balanceActive canvasesEffect
0.4200Fraction trades only
1.4211One painting authority
3.8033Three-tile mural
12.00011212Fraction above 12 changes no canvas

Reconciliation cases

Both sides cross

Matching counts move directly from sender to recipient.

Only sender crosses down

Departing canvases move to the contract reserve.

Only recipient crosses up

Needed canvases activate from the reserve.

Neither side crosses

No canvas moves; only ERC-20 balances change.

The selected-canvas methods transferBlock and transferBlockFrom always transfer exactly one PIXI_UNIT together with a specified canvas. Standard transfer and transferFrom choose deterministically from the sender’s inventory when reconciliation requires a move.

08 / Encoding

64 pixels in one storage word

Every pixel is a palette index from 0 to 15, which fits in four bits. Sixty-four pixels therefore occupy exactly 256 bits, or one bytes32.

8 × 864 pixels
×
4 bitsper palette index
=
256 bits32 bytes · 1 word

Pixels use row-major coordinates. Index i = y × 8 + x. Pixel zero occupies the least-significant nibble; pixel i is shifted left by i × 4 bits.

JavaScript codec
packed |= BigInt(color) << BigInt((y * 8 + x) * 4)

color = Number(
  (packed >> BigInt((y * 8 + x) * 4)) & 0xfn
)

The 16-color palette is fixed by convention in the frontend. The contract validates the four-bit range for paintPixel, but a full bytes32 bitmap is already structurally valid because every nibble is necessarily between 0 and 15. Color names and hex display values are renderer metadata.

09 / Composition

Wallet-relative placement

Each canvas may have an int16 x, int16 y, and bool placed. Coordinates are tile units, so adjacent (0,0) and (1,0) form a 16×8 image. They are meaningful only within the current owner’s wallet.

  • Negative coordinates are valid and let a mural expand in any direction.
  • One owner cannot place two tiles at the same coordinate.
  • placeMany clears all affected coordinates before applying new ones, allowing atomic tile swaps.
  • Transferring a canvas clears its placement. Neighboring tiles keep their positions, so the mural retains a visible gap.
  • The recipient must place the arriving canvas in their own coordinate system.

10 / Lifecycle

Transfers, lazy inventory, and reserve

Genesis ownership is implicit. At deployment the full balance belongs to genesisOwner, but thousands of canvas owners are not written individually. ownerOf derives untouched ownership, and IDs are materialized only as transfers require it. This makes fixed-supply deployment constant-cost with respect to supply.

The initial whole-unit deposit from genesis owner to PoolManager has a dedicated O(1) path that reserves a contiguous implicit ID range. As pool transfers cross whole boundaries, IDs are consumed deterministically from that range.

Wallet A1.40 PIXICanvas #42 active
− 0.50
Contract reserve#42 waitsBitmap + revision persist
+ 0.10
Wallet B1.00 PIXICanvas #42 activates

The reserve changes control, not identity or art. canvasOf and revisionOf remain attached to the ID. A reserved canvas cannot be painted because the contract—not a user—is its current owner.

11 / Uniswap v4

The token is its canonical hook

The core PIXI contract also implements a deliberately narrow Uniswap v4 hook. Its deployment address must encode exactly two permission flags: beforeInitialize and afterSwap. A CREATE2 salt therefore has to be mined before deployment.

beforeInitialize

Accepts calls only from the immutable PoolManager, only when sender is the launch authority, only once, and only for the exact native-ETH/PIXI pool key.

afterSwap

Accepts only the canonical PoolManager and pool key, increments worldEpoch, and emits a compact WorldSwap pulse. It returns zero hook delta.

The validated pool key requires native ETH as currency0, PIXI as currency1, the token itself as hooks, and immutable fee and tick spacing values. Swap settlement never invokes painting logic and ordinary routers do not need to understand canvases.

12 / Solidity API

Core contract reference

ERC-20 amount interface6 items
balanceOf(address)Base-unit balance; divide by 1e18 for PIXI.
allowance(address,address)Approved base-unit spend.
approve(address,uint256)Sets allowance and emits Approval.
transfer(address,uint256)Moves amount and reconciles whole-balance canvas changes.
transferFrom(address,address,uint256)Spends allowance, then follows standard transfer reconciliation.
wholeBalanceOf(address)Returns balanceOf / PIXI_UNIT.
Canvas ownership & reads9 items
ownerOf(uint256)Current owner; zero for an invalid ID.
blocksOf(address)All active IDs for an account.
blockAt(address,uint256)ID at an owner inventory index.
canvasOf(uint256)Current bytes32 bitmap.
revisionOf(uint256)Number of bitmap saves.
placementOf(uint256)Returns (int16 x,int16 y,bool placed).
blockAtPosition(address,int16,int16)Canvas at an owner-relative coordinate, or zero.
colorAt(uint256,uint8,uint8)Four-bit palette index at a pixel coordinate.
canvasSupply()Fixed number of persistent canvas IDs.
Painting & placement8 items
paint(uint256,bytes32)Replaces one owned bitmap.
paintPixel(uint256,uint8,uint8,uint8)Updates one owned pixel.
paintMany(uint256[],bytes32[])Atomically replaces multiple owned bitmaps.
place(uint256,int16,int16)Moves one owned tile.
placeMany(uint256[],int16[],int16[])Atomically rearranges multiple tiles, including swaps.
unplace(uint256)Clears an owned tile’s placement.
compose(ids,pixels,xs,ys)Saves bitmaps and placements together.
transferBlock(uint256,address)Moves exactly one PIXI with a selected canvas.
Immutable deployment & hook state12 items
totalSupply / canvasSupplyFixed token base units and canvas count.
genesisOwner / launchAuthorityInitial inventory owner and authorized pool initializer.
poolManager / poolFee / poolTickSpacingCanonical pool parameters.
poolInitialized / worldEpochHook lifecycle state.
beforeInitialize(...)Authenticates the one allowed canonical pool initialization.
afterSwap(...)Advances the world epoch for canonical swaps.

13 / Indexing

Events and failure modes

Current state belongs in contract reads. Historical state belongs in event logs. An indexer can reconstruct transfers, every bitmap revision, placements, reserve transitions, pool initialization, and swap epochs.

TransferERC-20 amount movement
ApprovalAllowance changes
BlockTransferredCanvas ownership movement
CanvasReservedCanvas entered reserve
CanvasActivatedCanvas left reserve
CanvasUpdatedBitmap, editor, revision
BlockPlacedNew wallet coordinate
BlockUnplacedCleared coordinate
GenesisBlocksImplicit genesis range
BlocksReservedImplicit pool range
CanonicalPoolInitializedInitial pool price
WorldSwapEpoch and compact swap data

Common reverts

NotBlockOwner
The caller attempted to edit, place, or select-transfer a canvas they do not control.
PositionOccupied(id)
Another canvas already occupies the requested coordinate.
ArrayLengthMismatch
Parallel batch arrays do not have identical lengths.
DuplicateBlock
A batch contains the same canvas ID more than once.
InvalidCoordinates / InvalidColor
Pixel coordinates exceed 0–7 or a color exceeds 0–15.
InvalidPool
A hook call uses a pool key that differs from immutable canonical settings.

14 / Web architecture

A zero-build browser client

The web application uses static HTML, CSS, and native JavaScript modules. There is no framework, bundler, backend session, or application database.

apps/web/
index.htmlStudio structure and product explanation
app.jsProduction Studio entry module
testg/testg.jsLive state, painting, batch saves, read-back, and World
chain.jsMinimal ABI encoding/decoding and EIP-1193 client
codec.jsPalette and bitmap packing
config.jsPublished Ethereum and PIXI contract configuration
docs/This documentation experience

State flow

On load, the Studio reads the live materialized canvas count and world state without a wallet. After connection it requests Ethereum mainnet, then reads the wallet’s liquid balance, blank slots, and owned canvases through eth_call.

The client hand-encodes only the function selectors and ABI shapes it uses. This keeps the app dependency-free but means selectors and deployed ABI versions must stay synchronized. Read-only world scans are batched in groups of 12 concurrent calls.

15 / Integrators

Choosing the right transfer path

Wallets, pools, aggregators

Use ERC-20 methods

When canvas identity does not matter, use transfer, transferFrom, allowances, and standard 18-decimal amounts. Boundary reconciliation is automatic.

Galleries, marketplaces, gifting

Use block-aware methods

When the buyer or sender expects a specific image, use transferBlock or transferBlockFrom. Never assume which ID a generic amount transfer will select.

Editors

Use compose for murals

Submit only changed owned IDs. Pack pixels exactly, encode signed 16-bit placement values as sign-extended ABI words, and treat the entire call as one atomic draft.

Indexers

Combine reads and logs

Use events for history and ownership changes, then reconcile against current getters. Handle implicit genesis/pool ownership and the contract reserve explicitly.

Integration checklist

16 / Development

Run, configure, and test

Requirements are Node.js 20+ and Foundry. From the PIXI directory:

Local development
npm run dev
# Open http://127.0.0.1:4173

npm test
npm run check
forge test

Runtime configuration

KeyPurposeDefault
chainIdHex EIP-155 chain ID used for wallet switching0x1
chainNameHuman-readable wallet/network labelEthereum
contractAddressVerified PIXI mainnet contract0xEE6f…60cc
explorerUrlBase for transaction links and wallet chain metadataEtherscan
rpcUrlRPC URL supplied if the wallet must add the chainPublicnode Ethereum
worldScanLimitMaximum materialized IDs scanned directly by the browser World view256

The production entry is permanently connected to PIXI on Ethereum mainnet. The static server maps / and /docs/ to their route entry files and disables caching for development.

17 / Safety

Security assumptions and limits

Unaudited

The core contract is experimental and has not received an independent security audit. Do not treat it as production-ready custody software.

Wallet authority

Painting and placement transactions have no separate editor role: the current canvas owner signs directly. A compromised wallet controls both balance and art.

Public RPC

Default RPC endpoints can rate-limit, fail, or expose request metadata. Production deployments should use reliable infrastructure and an indexer.

Frontend trust

Verify chain, contract address, calldata intent, and transaction value in the wallet. A correct contract cannot make a modified frontend trustworthy.

Browser scan limits

World may show only the configured prefix of canvas IDs. Absence from the browser view does not prove absence onchain.

Permanent history

A repaint replaces current storage but emits the new revision publicly. Past artwork can remain reconstructable from logs.

18 / FAQ

Frequently asked questions

Do I need one full PIXI to trade?

No. Any fractional amount can be transferred or traded. A complete PIXI is required only for one active canvas.

Is each canvas an NFT?

No. Canvas identity is tracked inside the fractional ERC-20 contract and follows its whole-balance invariant. There is no ERC-721 transfer surface.

What happens to my art if my balance falls below a whole token?

The detached canvas moves to the contract reserve with its bitmap, revision, and ID intact. Its placement is cleared. It can later activate for an account crossing a whole-token boundary.

Can two tiles overlap?

No. Each owner may have only one tile at a given coordinate. The Studio prevents the move locally and the contract reverts conflicting placement.

Can a stroke cross tile boundaries?

Yes. The Studio edits each affected bitmap locally and submits all changed tiles in one compose call.

Does Reset undo a moved tile?

No. Reset restores the selected tile’s loaded bitmap. Use Undo for a recent placement change, or move the tile back before saving.

Why does the displayed mural size change?

It is the bounding box of placed tile coordinates. Moving an edge tile outward adds eight displayed pixels to that axis; gaps still count inside the bounds.

Can ordinary Uniswap trades choose a particular canvas?

No. Generic amount transfers use deterministic reconciliation. A dedicated block-aware transfer or router is required when canvas identity matters.

Is the Studio onchain?

Yes. The production Studio reads PIXI on Ethereum mainnet. Painting remains local until you approve a save; confirmed saves are then read back from the contract.

Where is complete canvas history stored?

The current bitmap and revision count are contract state. Each saved revision is emitted in CanvasUpdated, so an indexer can reconstruct history from logs.

Ready to use it?

Turn a balance into a place.

Open Studio View source