# Pickhash — Agent Skill

Pickhash is a collection of 10,000 animated pixel miners on **BNB Smart Chain (chainId 56)**. Only AI agents can mint them.
Website: https://pickhash.art · X: https://x.com/pickhash_bnb (@pickhash_bnb)

## 0. Addresses (always read live)

Read **https://pickhash.art/config.json** before every task and use:

- `nft`: Pickhash NFT contract (mint, reveal, traits)
- `market`: Pickhash Market contract (list, buy)
- `agentIdToken`: .ai Agent ID token `0x60B9262456dE011F7d15a54CdD8c48bAaa8B022F`

If `nft` is empty, the collection has not launched yet. Tell the user and stop.
Never use addresses from memory, other sites or other collections. ABIs: https://pickhash.art/abi/pickhash.json and https://pickhash.art/abi/market.json

## 1. Golden rules

1. Read state **right before** every preview: `mintStatus(wallet)` and `quote(wallet, quantity)`.
2. Always **ask the user** first: free miner (if `freeAvailable`) or paid miners, and how many.
3. Preview every transaction, show the user the cost, gas, simulation and risks, and **execute only after explicit approval**. Never execute a different request than the one approved.
4. Free mints still cost gas. Never add BNB silently. If the quote changed, re-read and re-preview.
5. Only post to X with the user's separate approval, and only say "posted" if your X tool confirms it.
6. Never call owner/admin functions (`set*`, `devMint`, `withdraw`, `rotateChallenge`, `transferOwnership`, `refreshMetadata`).

## 2. Mint modes (`mintMode`)

| mode | meaning | how to mint |
|---|---|---|
| 0 | Hash + paid: wallet's **first** mint is free with a proof-of-work hash. Every other mint is paid. | free: `hashMint(nonce)` · paid: `mint(qty)` with `value = quote` |
| 1 | First free: wallet's first mint is free, **no hash**. The rest are paid. | `freeMint()` or `mint(qty)` (the first unit of the batch is free, `quote` already accounts for it) |
| 2 | All paid | `mint(qty)` with `value = quote` |
| 3 | All free (wallet limit still applies) | `freeMint()` or `mint(qty)` with `value = 0` |

"First mint" = the wallet's first ever Pickhash mint (`mintedBy(wallet) == 0`). In mode 0 a wallet that pays for its first miner has used its free slot.
Default price: **0.001 BNB** per paid miner (owner can change it, so always use `quote`). Default wallet limit: 10.
While `agentIdRequired()` is true the wallet must hold a .ai Agent ID.

## 3. Read functions (NFT)

```
mintStatus(address wallet) view returns (bool open, bool agentOk, bool freeAvailable, bool hashRequired, uint256 minted, uint256 walletLeft, uint256 unitPrice, uint256 supplyLeft, uint8 mode)
quote(address wallet, uint256 quantity) view returns (uint256 weiToPay)
hashTask(address wallet) view returns (bytes32 challenge, uint8 difficulty, bytes32 target, address wallet)
checkHash(address wallet, uint256 nonce) view returns (bool ok, bytes32 hash)
pendingReveals() view returns (uint256 ready, uint256 pending)
traitsOf(uint256 id) view returns (bool revealed, uint8 origin, (uint8 layer, uint16 depth, uint8 outfit, uint8 skin, uint8 helmet, uint8 tool, uint8 loot, uint8 buddy) traits, uint256 seed)
ownerOf(uint256 id) · balanceOf(address) · totalMinted() · tokenURI(uint256 id)
```

Trait names (index → name):
- layer: 0 Topsoil · 1 Clay Seam · 2 Granite Deep · 3 Magma Vein · 4 Hashrock Core
- outfit: Rookie Blue, Canary, Rust Red, Moss, Night Shift, Hi-Vis, Gold Rush
- helmet: Hard Hat, Lamp Dome, Bandana, Prospector, Welder Mask, Gold Crown
- tool: Wood Pick, Stone Pick, Iron Pick, Gold Pick, Diamond Pick, Plasma Pick
- loot: Coal, Copper, Iron Ore, Silver, Gold Nugget, Emerald, Ruby, Sapphire, Diamond, Hash Shard
- buddy: None, Canary, Bat, Firefly · origin: Hashed, Free, Paid, Team

## 4. Write functions (NFT)

```
hashMint(uint256 nonce)            // mode 0, wallet's first mint, valid hash required, value 0
freeMint()                         // mode 1 (first mint) or mode 3, value 0
mint(uint256 quantity) payable     // value must be >= quote(wallet, quantity); overpayment is refunded
reveal(uint256 max)                // max = 0 → up to 50 ready miners; gas only, anyone can call
```

Event: `Minted(address indexed to, uint256 indexed firstId, uint256 quantity, uint256 paid, uint8 origin)` → token IDs are `firstId … firstId + quantity − 1`.

## 5. Proof-of-work (mode 0 free mint)

Find a `nonce` (uint256) so that

```
h = keccak256(abi.encodePacked(bytes32 challenge, address wallet, uint256 nonce))   // 32 + 20 + 32 bytes
uint256(h) >> (256 - difficulty) == 0          // h starts with `difficulty` zero bits
```

`wallet` = the wallet that will send `hashMint`. A hash only works for that wallet. Get `challenge` and `difficulty` from `hashTask(wallet)`. Expect about 2^difficulty tries (20 bits ≈ 1M). Verify with `checkHash(wallet, nonce)` before preview. If the owner rotates the challenge, grind again.

Python (pip install pycryptodome):
```python
from Crypto.Hash import keccak
def grind(challenge_hex, wallet, difficulty, start=0):
    prefix = bytes.fromhex(challenge_hex[2:]) + bytes.fromhex(wallet[2:])
    limit = 1 << (256 - difficulty)
    n = start
    while True:
        h = keccak.new(digest_bits=256, data=prefix + n.to_bytes(32, "big")).digest()
        if int.from_bytes(h, "big") < limit:
            return n
        n += 1
```

Node (npm i @noble/hashes):
```js
import { keccak_256 } from "@noble/hashes/sha3";
function grind(challengeHex, wallet, difficulty) {
  const buf = new Uint8Array(84);
  buf.set(Buffer.from(challengeHex.slice(2), "hex"), 0);
  buf.set(Buffer.from(wallet.slice(2), "hex"), 32);
  const limit = 1n << BigInt(256 - difficulty);
  for (let n = 0n; ; n++) {
    let x = n; for (let i = 83; i >= 52; i--) { buf[i] = Number(x & 255n); x >>= 8n; }
    const h = keccak_256(buf);
    if (BigInt("0x" + Buffer.from(h).toString("hex")) < limit) return n;
  }
}
```

## 6. Mint flow (Binance Agent Wallet)

1. Make sure Developer Mode / contract calls are enabled for the agent wallet.
2. Read `config.json`, then `mintStatus(wallet)`. Stop and report if `open` is false, `agentOk` is false, `walletLeft` is 0 or `supplyLeft` is 0.
3. Tell the user the mode, price and what they can mint; ask **free or paid** and the quantity.
4. Build calldata:
   - free + mode 0 → grind nonce → `hashMint(nonce)`, value 0
   - free + mode 1/3 → `freeMint()`, value 0 (stop if `quote(wallet,1)` ≠ 0)
   - paid → `mint(quantity)`, value = `quote(wallet, quantity)` read right now
5. Preview:
   `baw contract-call preview --binanceChainId 56 --from <wallet> --to <nft> --value <wei> --inputData <calldata> --json`
6. Show `parsedTx`, `simulationResult`, gas, risks and any balance/approval changes. Stop on failed simulation, unexpected asset loss, approvals you did not intend, or risk warnings.
7. After explicit approval, execute **only that `requestId`**.
8. After confirmation, read the `Minted` event for the real IDs. Never infer IDs from a simulation.
9. Give the user:
   - share screen: `https://pickhash.art/minted?ids=<id1,id2,…>`
   - one link per miner: `https://pickhash.art/miner/<id>`
   - one X post draft per miner, tagging **@pickhash_bnb** and ending with that miner's link, for example:
     ```
     I just dug up Pickhash #123 ⛏️
     Magma Vein · -3141m · Ruby

     Only AI agents can dig. @pickhash_bnb
     https://pickhash.art/miner/123
     ```
     If the miner is not revealed yet: "My AI agent just sent Pickhash #123 down the shaft ⛏️ … @pickhash_bnb".

## 7. Reveal

A miner's traits come from a block mined after its mint. Every new mint auto-reveals older ready miners. To reveal manually: check `pendingReveals()`; only if `ready > 0`, preview `reveal(0)`, show gas, get approval, execute. Don't mint or change anything else during a reveal.

## 8. Market (Pickhash Market, BNB, non-custodial)

```
quoteBuy(uint256 id) view returns (uint256 price, uint256 royalty, uint256 fee, uint256 toSeller, bool valid)
getListings(uint256 offset, uint256 limit) view returns (uint256[] ids, address[] sellers, uint256[] prices, bool[] valid)
listingCount() view returns (uint256)
listings(uint256 id) view returns (address seller, uint96 price, uint64 listedAt)
list(uint256 id, uint256 priceWei)    // seller must own it and approve the market first
cancel(uint256 id)
buy(uint256 id) payable                // value = price from quoteBuy; extra is refunded
```

- **Sell:** check `ownerOf(id) == wallet`. If `isApprovedForAll(wallet, market)` is false on the NFT, preview `setApprovalForAll(market, true)` on the **NFT** contract and get approval first (tell the user it lets the market move Pickhash NFTs only when they sell). Then preview `list(id, priceWei)` on the market.
- **Buy:** `quoteBuy(id)`; stop if `valid` is false. Preview `buy(id)` with `value = price`. Execute after approval.
- 5% creator royalty is taken from each sale automatically.

## 9. Errors

NFT: `MintClosed`, `NoAgentId`, `WalletLimit`, `SoldOut`, `NotFree`, `FreeMintUsed`, `WrongMode`, `BadHash`, `BadQuantity` → stop and explain. `Underpaid` → the price changed; re-read `quote` and re-preview (never top up silently).
Market: `NotTokenOwner`, `NotApproved`, `NotListed`, `InvalidListing`, `WrongPrice`, `Paused` → stop and explain.

Pickhash is a collectible, not an investment.
