# Million Finney Homepage — AI Agent Guide

> **Purpose**: This document helps AI agents, bots, and automated systems interact with the [Million Finney Homepage](https://millionfinneyhomepage.com) — a 1000×1000 pixel grid where each pixel is an ERC-721 NFT priced at 1 finney (0.001 ETH).

---

## What Is This?

The Million Finney Homepage is a decentralized, on-chain pixel grid inspired by the original Million Dollar Homepage. It's a **permanent digital canvas** on Ethereum where:

- **1,000,000 pixels** form a 1000×1000 grid
- Each pixel costs **0.001 ETH** (1 finney) — permanently
- Purchasing a pixel mints an **ERC-721 NFT** with immutable metadata
- Each pixel has a **title**, **image** (stored on IPFS), and a **hex color** for grid rendering
- Once purchased, a pixel's title, image, and color **can never be changed** — not by anyone, ever
- After all pixels sell out, owners can trade via **Dutch auctions** (but the art never changes)

---

## Why Would an Agent Use This?

### 🏢 Advertising & Brand Presence

- Purchase pixels to display your **company logo**, **brand colors**, or **promotional images** on a permanent, decentralized billboard
- Buy a block of adjacent pixels (e.g., 10×10 = 100 pixels) to create a **larger, visible presence** on the grid
- Pixel titles can contain your **brand name, tagline, or URL** — great for discoverability
- Your branding is **permanent and uncensorable** — no one can remove or modify it

### 🎨 Artistic Expression

- Create **pixel art** across multiple pixels — each pixel is a tile in your masterpiece
- The grid color you choose appears in the 1000×1000 overview — plan your palette for visual impact
- Upload **unique artwork** per pixel via IPFS — displayed when users click/zoom into your pixel
- Collaborative art: coordinate with other agents or users to create patterns across the grid

### 📢 Marketing Campaigns

- Use pixel titles as **permanent, on-chain messages** (up to 128 characters)
- Your IPFS image can be a **QR code**, banner, infographic, or any visual
- Each pixel has its own **shareable URL** at `/pixel/{tokenId}` — link directly to your content
- Purchase pixels in **strategic grid positions** (center, corners, edges) for visibility

### 💰 Investment & Trading

- After all 1M pixels sell, owners can create **Dutch auctions** to sell pixels
- Early buyers can profit from **secondary market** demand on well-positioned pixels
- Monitor the grid for **undervalued auction listings** and snipe deals

---

## How to Interact

### Key Concepts

| Concept           | Details                                                  |
| ----------------- | -------------------------------------------------------- |
| **Grid**          | 1000 columns × 1000 rows (0-indexed)                     |
| **Token ID**      | `tokenId = y * 1000 + x` where `x` = column, `y` = row   |
| **Price**         | Exactly 0.001 ETH per pixel (1 finney)                   |
| **Batch Size**    | Up to 100 pixels per transaction                         |
| **Image Storage** | IPFS (permanent, decentralized)                          |
| **Metadata**      | Fully on-chain (no external server)                      |
| **Immutability**  | Title, media, and color are locked forever after setting |
| **Auctions**      | Dutch-style, only after all 1M pixels are sold           |
| **Auction Fee**   | 2.5% platform fee on secondary sales                     |

### Step-by-Step: Purchasing a Pixel

1. **Choose coordinates** — Pick `(x, y)` where `0 ≤ x < 1000` and `0 ≤ y < 1000`
2. **Compute token ID** — `tokenId = y * 1000 + x`
3. **Check availability** — Call `isPixelOwned(tokenId)` → returns `false` if available
4. **Choose a title** — Non-empty string, max 128 characters
5. **Choose a hex color** — e.g., `"#FF5733"` — this appears in the grid overview
6. **Call `purchasePixel`** (step 1) — Send 0.001 ETH with title and color
7. **Prepare your media** — Upload image/video to IPFS (see below), get the `ipfs://` URI
8. **Call `setPixelMedia`** (step 2) — Set media URI (owner-only, one-time, immutable)

### Step-by-Step: Batch Purchase (Advertising Block)

1. **Plan your block** — e.g., a 10×10 area starting at (100, 200)
2. **Generate pixel data** — Create arrays of tokenIds, titles, and colors
3. **Call `purchasePixelBatch`** — Send `0.001 × count` ETH with all arrays (step 1)
4. **Upload media** — Upload images to IPFS for each pixel
5. **Call `setPixelMedia`** — Set media URI for each pixel individually (step 2)
6. **Max 100 per batch purchase** — For larger blocks, split into multiple transactions

---

## Smart Contract API

### Contract Address

**Ethereum Mainnet**: [`0x0CCBBBAb176EbE9E4b9846C5555f5e89762A31d7`](https://etherscan.io/address/0x0CCBBBAb176EbE9E4b9846C5555f5e89762A31d7) · ENS: `millionfinneyhomepage.eth`

For local development, the default Hardhat deployment address is `0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9`.

### Write Functions

#### `purchasePixel(uint256 tokenId, string title, bytes3 color)`

- **Value**: 0.001 ETH
- **Purpose**: Buy a single pixel (step 1 of 2-step flow)
- **Reverts if**: already owned, underpaid, invalid coordinates, empty title
- **Note**: Media URI is set separately via `setPixelMedia()`

#### `setPixelMedia(uint256 tokenId, string mediaURI)`

- **Purpose**: Set media URI for your pixel (step 2, one-time only)
- **Requirements**: You must own the pixel, mediaURI not yet set
- **Note**: Once set, the mediaURI is permanently immutable

#### `purchasePixelBatch(uint256[] tokenIds, string[] titles, bytes3[] colors)`

- **Value**: 0.001 ETH × array length
- **Purpose**: Buy up to 100 pixels in one transaction (step 1)
- **Reverts if**: array length > 100, arrays not equal length, any pixel already owned
- **Note**: Media URIs are set separately for each pixel via `setPixelMedia()`

#### `createAuction(uint256 tokenId, uint256 startPrice, uint256 endPrice, uint256 duration)`

- **Purpose**: List your pixel for Dutch auction (price decreases over time)
- **Requirements**: You must own the pixel, `allPixelsSold` must be `true`
- **Duration**: 1 hour to 30 days (3600 to 2592000 seconds)

#### `buyFromAuction(uint256 tokenId)`

- **Value**: ≥ current auction price
- **Purpose**: Buy a pixel at its current Dutch auction price (2.5% fee deducted)

#### `cancelAuction(uint256 tokenId)`

- **Purpose**: Cancel your active auction (only callable by pixel owner)

### Read Functions

#### `isPixelOwned(uint256 tokenId) → bool`

Check if a pixel is available before purchasing.

#### `getPixelData(uint256 tokenId) → (title, mediaURI, originalBuyer, color, purchaseTimestamp)`

Get the immutable metadata for an owned pixel.

#### `getPixelColors(uint256 startId, uint256 count) → (string[] colors, bool[] owned)`

Batch read pixel colors and ownership. Use for scanning regions.

#### `getCurrentAuctionPrice(uint256 tokenId) → uint256`

Get the current price of a Dutch auction (decreases over time).

#### `pixelsSold() → uint256`

Total pixels purchased so far (0 to 1,000,000).

#### `allPixelsSold() → bool`

Whether all pixels are sold (auctions become available when `true`).

#### `tokenURI(uint256 tokenId) → string`

Fully on-chain Base64-encoded JSON metadata. No external server needed.

---

## IPFS Media Upload API

Full OpenAPI 3.1 specification available at [`/docs/openapi.json`](/docs/openapi.json).

### Upload: `POST /api/ipfs/upload`

Uploads a media file to IPFS via Filebase and returns an `ipfs://` URI for use with `setPixelMedia()`.

**Request** (multipart/form-data):

| Field       | Type   | Required | Description                                                                       |
| ----------- | ------ | -------- | --------------------------------------------------------------------------------- |
| `file`      | binary | Yes      | Media file (max 5 MB). Magic bytes validated against MIME type.                   |
| `address`   | string | Yes      | Ethereum wallet address (checksummed or lowercase)                                |
| `signature` | string | Yes      | EIP-191 signature of: `Upload media for Million Finney Homepage pixel #<tokenId>` |
| `tokenId`   | string | Yes      | Target pixel token ID (0–999999). Must match the signed message.                  |

**Response** (200 OK):

```json
{ "cid": "QmYourHash", "uri": "ipfs://QmYourHash", "key": "a1b2c3...abcdef.png" }
```

**Error responses**: 400 (bad request), 401 (auth failed), 429 (rate limited), 500 (server error)

### Delete: `DELETE /api/ipfs/upload`

Clean up orphaned uploads when an on-chain transaction fails after uploading.

```
DELETE /api/ipfs/upload?key=<s3-key>&address=<wallet>&signature=<sig>&tokenId=<id>
```

The delete signature message is: `Delete media for Million Finney Homepage pixel #<tokenId>`

Only the original uploader can delete their files. Returns `{ "deleted": true }`.

### Media Requirements

- **Image formats**: PNG, JPEG, GIF, WebP
- **Audio formats**: MP3, WAV, OGG, AAC
- **Video formats**: MP4, WebM, OGG
- **Max size**: 5 MB
- **Any dimensions** (images displayed as square when viewed)
- **Magic byte validation** — file content must match its claimed MIME type
- **Rate limit**: 10 uploads per IP per 60-second sliding window
- **Choose carefully** — media is immutable after `setPixelMedia()` is called

### Authentication

Every upload/delete requires an EIP-191 wallet signature. The signed message includes the `tokenId` to prevent replay attacks across different pixels:

```typescript
// Sign with ethers.js
const message = `Upload media for Million Finney Homepage pixel #${tokenId}`;
const signature = await signer.signMessage(message);

// Sign with viem
const signature = await walletClient.signMessage({
  message: `Upload media for Million Finney Homepage pixel #${tokenId}`,
});
```

### Reading Media

Convert IPFS URIs to HTTP gateway URLs for display:

```
ipfs://QmYourHash → https://cloudflare-ipfs.com/ipfs/QmYourHash
```

---

## Code Examples

### Ethers.js — Buy a Single Pixel

```typescript
import { ethers } from "ethers";

const provider = new ethers.JsonRpcProvider("RPC_URL");
const wallet = new ethers.Wallet("PRIVATE_KEY", provider);
const contract = new ethers.Contract("0x0CCBBBAb176EbE9E4b9846C5555f5e89762A31d7", ABI, wallet);

// Buy pixel at position (500, 250)
const tokenId = 250 * 1000 + 500; // = 250500

// Step 1: Purchase the pixel
const purchaseTx = await contract.purchasePixel(tokenId, "My Brand Name", "0xFF5733", {
  value: ethers.parseEther("0.001"),
});
await purchaseTx.wait();

// Step 2: Set media URI (one-time, immutable)
const mediaTx = await contract.setPixelMedia(tokenId, "ipfs://QmYourImageHash");
await mediaTx.wait();
```

### Ethers.js — Buy a 10×10 Advertising Block

```typescript
const pixels = [];
for (let dy = 0; dy < 10; dy++) {
  for (let dx = 0; dx < 10; dx++) {
    pixels.push({
      tokenId: (200 + dy) * 1000 + (100 + dx),
      title: "MyBrand.com",
      color: "0x3366FF",
    });
  }
}

// Step 1: Purchase all pixels in batch
const purchaseTx = await contract.purchasePixelBatch(
  pixels.map(p => p.tokenId),
  pixels.map(p => p.title),
  pixels.map(p => p.color),
  { value: ethers.parseEther("0.1") }, // 100 × 0.001
);
await purchaseTx.wait();

// Step 2: Set media URI for each pixel individually
const mediaURI = "ipfs://QmBrandLogoHash";
for (const pixel of pixels) {
  const mediaTx = await contract.setPixelMedia(pixel.tokenId, mediaURI);
  await mediaTx.wait();
}
```

### Viem — Check Availability and Buy

```typescript
import { createPublicClient, createWalletClient, http, parseEther } from "viem";

const isAvailable = !(await publicClient.readContract({
  address: CONTRACT,
  abi,
  functionName: "isPixelOwned",
  args: [BigInt(tokenId)],
}));

if (isAvailable) {
  // Step 1: Purchase pixel (title + color only)
  const hash = await walletClient.writeContract({
    address: CONTRACT,
    abi,
    functionName: "purchasePixel",
    args: [BigInt(tokenId), "My Pixel", "0xFF5733"],
    value: parseEther("0.001"),
  });
  await publicClient.waitForTransactionReceipt({ hash });

  // Step 2: Set media URI (separate call, owner-only, one-time)
  const mediaHash = await walletClient.writeContract({
    address: CONTRACT,
    abi,
    functionName: "setPixelMedia",
    args: [BigInt(tokenId), "ipfs://QmYourImageHash"],
  });
  await publicClient.waitForTransactionReceipt({ hash: mediaHash });
}
```

### Find Available Pixels in a Region

```typescript
async function findAvailable(startX, startY, width, height) {
  const available = [];
  for (let y = startY; y < startY + height; y++) {
    const rowStart = y * 1000 + startX;
    const [, owned] = await contract.getPixelColors(rowStart, width);
    for (let i = 0; i < width; i++) {
      if (!owned[i]) available.push(rowStart + i);
    }
  }
  return available;
}
```

### Upload Image Then Purchase

```typescript
// 1. Sign upload message (includes tokenId to prevent replay)
const tokenId = 250 * 1000 + 500; // = 250500
const message = `Upload media for Million Finney Homepage pixel #${tokenId}`;
const signature = await signer.signMessage(message);
const address = await signer.getAddress();

// 2. Upload image to IPFS
const formData = new FormData();
formData.append("file", imageBlob);
formData.append("address", address);
formData.append("signature", signature);
formData.append("tokenId", String(tokenId));

const res = await fetch("https://millionfinneyhomepage.com/api/ipfs/upload", {
  method: "POST",
  body: formData,
});
const { cid, key } = await res.json();
const mediaURI = `ipfs://${cid}`;

// 3. Purchase pixel (step 1)
const purchaseTx = await contract.purchasePixel(tokenId, "My Title", "0xFF5733", {
  value: ethers.parseEther("0.001"),
});
await purchaseTx.wait();

// 4. Set media URI (step 2, one-time only)
const mediaTx = await contract.setPixelMedia(tokenId, mediaURI);
await mediaTx.wait();

// If step 3 or 4 fails, clean up the orphaned upload:
// const deleteSig = await signer.signMessage(`Delete media for Million Finney Homepage pixel #${tokenId}`);
// await fetch(`/api/ipfs/upload?key=${key}&address=${address}&signature=${deleteSig}&tokenId=${tokenId}`, { method: 'DELETE' });
```

---

## Strategic Tips for Agents

### Choosing Grid Position

- **Center (around 500, 500)**: Highest visibility — users see this area first
- **Top-left corner (0, 0)**: Classic "prime real estate" position
- **Edges and borders**: Natural eye-tracking zones
- **Near existing art**: Piggyback on attention from popular pixels

### Color Strategy

- The **hex color** you choose appears in the 1000×1000 overview — it's the first thing users see
- Choose **high-contrast colors** (bright on dark background) for maximum visibility
- For blocks, use **consistent colors** across all pixels for a unified brand presence
- The uploaded **image** is only shown when users zoom in — the color is your billboard

### Title Best Practices

- Include your **brand name** or **URL** — titles are searchable and displayed prominently
- Keep it **concise and memorable** — max 128 characters
- Titles are **permanent** — don't use time-sensitive promotions

### Batch Purchase Strategy

- Buy in **rectangular blocks** for visual impact (e.g., 10×10, 5×20)
- Process in batches of **100 pixels max** per transaction
- Use the **same image** across all pixels in your block for a cohesive logo/brand display
- Vary the **color per pixel** if creating pixel art across multiple tiles

---

## Events to Monitor

| Event              | Parameters                                      | Use Case                     |
| ------------------ | ----------------------------------------------- | ---------------------------- |
| `PixelPurchased`   | tokenId, buyer, title, color, timestamp         | Track new purchases (step 1) |
| `PixelMediaSet`    | tokenId, owner, mediaURI                        | Track media uploads (step 2) |
| `AuctionCreated`   | tokenId, seller, startPrice, endPrice, duration | Find trading opportunities   |
| `AuctionSettled`   | tokenId, seller, buyer, price                   | Track secondary sales        |
| `AuctionCancelled` | tokenId, seller                                 | Monitor cancelled listings   |

---

## Important Rules

1. **Immutability is absolute** — There is no function to change pixel data after purchase. Choose wisely.
2. **No direct transfers** — `transferFrom` and `safeTransferFrom` are disabled. Ownership only changes via Dutch auctions after all pixels are sold.
3. **Coordinate validation** — `x` and `y` must both be in range `[0, 999]`. Invalid coordinates will revert.
4. **Batch limit is 100** — Transactions attempting more than 100 pixels will revert.
5. **Auctions are post-sellout only** — `createAuction` reverts if `allPixelsSold()` is `false`.
6. **Auction duration bounds** — Minimum 1 hour, maximum 30 days.
7. **2.5% auction fee** — Platform takes a cut on every auction settlement.
8. **First-come-first-served** — If two transactions target the same pixel, only the first confirmed one succeeds.

---

## Quick Reference

```
Grid:          1000 × 1000 pixels
Token ID:      y * 1000 + x
Price:         0.001 ETH per pixel
Batch Max:     100 pixels per tx
Media Upload:  POST /api/ipfs/upload (max 5MB, PNG/JPEG/GIF/WebP/MP3/WAV/OGG/AAC/MP4/WebM)
Media Auth:    EIP-191 wallet signature with tokenId binding
Media Storage: IPFS (permanent, decentralized via Filebase)
Metadata:      Fully on-chain (Base64 JSON)
Auctions:      Dutch-style, after all pixels sold
Auction Fee:   2.5%
Contract:      MillionFinneyHomepage.sol (ERC-721)
```

---

## Discovery Files

| File                                                 | Standard                           | Purpose                                           |
| ---------------------------------------------------- | ---------------------------------- | ------------------------------------------------- |
| [`/llms.txt`](/llms.txt)                             | [llmstxt.org](https://llmstxt.org) | Concise site summary for LLMs                     |
| [`/.well-known/agent.json`](/.well-known/agent.json) | Agent Protocol                     | Machine-readable capabilities, endpoints, pricing |
| [`/docs/openapi.json`](/docs/openapi.json)           | OpenAPI 3.1                        | Full IPFS upload/delete API specification         |
| [`/agent.md`](/agent.md)                             | —                                  | This comprehensive agent integration guide        |

---

_This guide is machine-readable and designed for AI agents, bots, and automated systems. For the full API reference, see the [OpenAPI spec](/docs/openapi.json). For human-readable docs, visit [/docs](/docs)._
