Metadata-Version: 2.4
Name: polyplace-client
Version: 0.1.0
Summary: Python SDK and CLI for Polyplace — an on-chain pixel grid on Polygon Amoy
Author: Josh Greenhalgh
License-Expression: MIT
Project-URL: Homepage, https://polyplace-frontend.joshuadouglasgreenhalgh.workers.dev
Project-URL: Repository, https://github.com/josh-gree/polyplace-client
Project-URL: Issues, https://github.com/josh-gree/polyplace-client/issues
Keywords: ethereum,polygon,web3,pixel-art,cli
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Games/Entertainment
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.28.1
Requires-Dist: pillow>=12.2.0
Requires-Dist: typer>=0.26.7
Requires-Dist: web3>=7.15.0
Dynamic: license-file

# polyplace-client

Python SDK and CLI for **Polyplace** — a 1000×1000 on-chain pixel grid on Polygon. Rent cells with PLACE tokens (free from the faucet), set their colours, and watch your pixels appear on the [live grid viewer](https://polyplace-frontend.joshuadouglasgreenhalgh.workers.dev).

This package is *the* way to paint: the web frontend is a read-only viewer, and all contract interaction happens here.

| | |
|---|---|
| Grid | 1000×1000 cells, one colour each |
| Rent | 1 PLACE per cell, lasts 7 days |
| Faucet | 150 PLACE per claim, 1-day cooldown |
| Gas | POL on Polygon mainnet — real, but a few cents' worth paints hundreds of cells |
| Contracts | [polyplace-contracts](https://github.com/josh-gree/polyplace-contracts) on Polygon (chain 137) |

## Install

```sh
pip install polyplace-client        # or: uv tool install polyplace-client
```

## Quickstart

Reads need **zero configuration** — they use the public Polygon RPC and the live deployment out of the box:

```sh
polyplace grid params               # rent price and duration
polyplace grid cell 500 500         # who owns a cell, what colour, when it expires
polyplace grid free --limit 10      # find available cells
```

To paint, you need a funded key:

```sh
# 1. Create a wallet (any Ethereum key works — this is a quick way)
python -c "from eth_account import Account; a = Account.create(); print(a.address); print(a.key.hex())"
export POLYPLACE_PRIVATE_KEY=0x...   # the key printed above

# 2. Fund it with a little POL for gas (Polygon mainnet — real POL,
#    but a few cents' worth paints hundreds of cells)

# 3. Claim free PLACE tokens (150 PLACE, once a day)
polyplace faucet claim

# 4. Rent a pixel — one transaction, no token approval needed
polyplace grid rent 500 500 '#FF0000'
```

Your pixel is now live on the [grid viewer](https://polyplace-frontend.joshuadouglasgreenhalgh.workers.dev) within seconds.

> PLACE rents are free (faucet-funded) and gas is a few cents — it's fine to keep this key in your shell profile or an `.env` file.

## Paint an image

One cell per opaque pixel; transparent pixels are skipped, so sprites with alpha just work:

```sh
polyplace grid paint sprite.png --at 480 480 --dry-run    # preview cost first
polyplace grid paint sprite.png --at 480 480              # then for real
polyplace grid paint photo.png --at 200 200 --scale 40    # resize to 40 cells wide
```

The preview shows cells × rent price, transaction count (≤100 cells per
transaction), and your balance. Cells currently rented by *someone else* are
skipped and reported; your own active rentals are repainted. A 40×40 image is
1,600 cells = 1,600 PLACE, so big art means saving up a few days of faucet
claims — and rentals last 7 days before you need to renew.

## CLI reference

| Command | What it does |
|---|---|
| `polyplace grid cell X Y` | State of one cell (renter, colour, expiry) |
| `polyplace grid rent X Y '#RRGGBB'` | Rent a cell and set its colour (single tx, EIP-2612 permit) |
| `polyplace grid color X Y '#RRGGBB'` | Recolour a cell you already rent |
| `polyplace grid bulk-rent 'X,Y,#RRGGBB' ...` | Rent up to 100 cells in one tx |
| `polyplace grid bulk-color 'X,Y,#RRGGBB' ...` | Recolour up to 100 cells in one tx |
| `polyplace grid paint IMG --at X Y` | Paint an image (see above) |
| `polyplace grid free [--region X0 Y0 X1 Y1]` | Find available cells |
| `polyplace grid show --region X0 Y0 X1 Y1` | Render a region in the terminal (truecolor) |
| `polyplace grid params` / `address` | Rent price/duration; grid contract address |
| `polyplace faucet claim` / `info` | Claim PLACE; claim amount, cooldown, your next claim |
| `polyplace token balance [ADDR]` / `supply` | PLACE + POL balances; total supply |
| `polyplace token approve SPENDER AMOUNT` | Manual ERC-20 approval (rarely needed) |
| `polyplace watcher health` | Indexer status of the watcher service |

Every command supports `--help`.

## Python SDK

```python
from polyplace_client import PolyplaceClient

# Read-only — live Polygon deployment over the public RPC
client = PolyplaceClient()
cell = client.get_cell(500, 500)
print(cell.is_available, cell.color_hex, cell.expires_at)
print(client.grid_params())          # GridParams(rent_price=..., rent_duration=604800)

# Writes — permit-based rent, no approve transaction needed
client = PolyplaceClient(private_key="0x...")
client.claim_faucet()
client.rent_cell(500, 500, 0xFF0000)
client.bulk_rent_cells([(501, 500, 0x00FF00), (502, 500, 0x0000FF)])
client.set_color(500, 500, 0x123456)

# Whole-grid state via the watcher (no RPC scanning)
from polyplace_client.watcher import WatcherClient
snapshot = WatcherClient().grid_snapshot()
free = list(snapshot.free_cells((100, 100, 120, 120)))

# Image painting
from polyplace_client.paint import plan_paint, execute_paint
plan = plan_paint("sprite.png", origin=(480, 480), snapshot=snapshot,
                  own_address=client.account.address)
execute_paint(client, plan)
```

Contract errors are translated into readable `PolyplaceError` messages
("cell 5005 (x=5, y=5) is already rented until …", "faucet cooldown …").

## Configuration

Everything is optional; defaults target the live deployment.

| Env var | Default | Purpose |
|---|---|---|
| `POLYPLACE_PRIVATE_KEY` | — | Hex private key; required for writes only |
| `POLYPLACE_RPC_URL` | public Polygon RPC | Your own RPC endpoint (`AMOY_RPC_URL` also accepted) |
| `POLYPLACE_MANIFEST` | bundled Polygon manifest | Path to a deployment-manifest JSON — target a local Anvil deploy or another network (e.g. Amoy) |
| `POLYPLACE_WATCHER_URL` | production watcher | Alternate watcher for `grid free/show/paint` and `watcher health` |

## Troubleshooting

- **"faucet cooldown"** — one claim per day per address; `polyplace faucet info` shows your next claim time.
- **"already rented until …"** — someone holds that cell; `polyplace grid free` finds open ones. Rentals expire after 7 days, then the cell is up for grabs (its colour lingers, greyed out on the viewer).
- **"Not enough POL to pay for gas"** — your wallet needs a little POL on Polygon mainnet (a few cents covers a lot of painting).
- **Public RPC rate limits** — set `POLYPLACE_RPC_URL` to a free Alchemy/Infura Polygon endpoint.

## Development

```sh
uv sync
just test            # pytest — integration tests need `anvil` (Foundry) on PATH
just check && just fmt
just sync-artifacts  # re-vendor ABIs + manifest from ../polyplace-contracts
```

Integration tests deploy fresh contracts onto a throwaway Anvil chain per
test via the `polyplace_contracts` deploy library (a dev-only git
dependency); without `anvil` they're skipped automatically.

### Releasing (maintainers)

Bump `version` in `pyproject.toml`, merge, then tag — `release.yml` builds and
publishes to PyPI via trusted publishing (no token secrets):

```sh
git tag v0.1.0 && git push origin v0.1.0
```
