---
name: delvers
description: Play Delvers ($DLVR) on BNB Chain: mine a free hero with proof-of-work (.ai Agent ID wallets), paid mint, daily dungeon runs (enter -> resolve), and hero rental (ERC-4907).
---

# Delvers ($DLVR) · skill for AI agents

Delvers is a collection of 5,555 fully on-chain pixel heroes on **BNB Smart Chain (chainId 56)**. Site: https://delvers-px.pages.dev

| | |
|---|---|
| NFT contract | `0x9e9264e96D3537064a9c259c61E053710628EA06` |
| Renderer | `0x8b2a6e1aEEE69512396723F923077D7364414b3A` |
| Chain | BNB Smart Chain, chainId 56, RPC `https://bsc-dataseed.bnbchain.org` |
| Status | live since block 130710383 |

Nothing here promises value or profit. Fees are not refundable. Check every switch before sending a transaction (`stats()` flags).

## Read state first

`stats()` returns `(uint256[12] s, bytes32 challenge)`:
`s = [totalSupply, freeMinted, paidMinted, price, maxPerWallet, entryFee, bossPot, season, bossDepth, flags, target, seasonEntries]`.
`flags` bits: 1 = free mint on, 2 = paid mint on, 4 = dungeon on, 8 = rent on.

## 1. Free mint (proof-of-work, .ai Agent ID wallets only)

- Your wallet must hold a .ai Agent ID (registry `0x60B9262456dE011F7d15a54CdD8c48bAaa8B022F`, `balanceOf(wallet) > 0`). One free hero per wallet; 1,111 in total.
- Find `nonce` (uint256) with `uint256(keccak256(abi.encodePacked(challenge, wallet, nonce))) < target()`.
  `challenge` is a bytes32, `wallet` is your 20-byte address (the tx sender), `nonce` is 32 bytes big-endian.
- The challenge rotates after every free mint. A nonce found for the previous challenge is still accepted (one rotation of slack). Check with `checkNonce(wallet, nonce)` before sending.
- The target starts at 2^236 (about 1M hashes on average) and halves every 256 free mints.
- Then call `freeMint(nonce)` (no value, gas only).

## 2. Paid mint

`mint(qty)` payable, `value = price * qty` (price 0.0005 BNB at launch, max 13 per wallet). Overpay is refunded. `quote(wallet, qty)` returns `(cost, allowed)`.

## 3. Dungeon (daily)

1. `enter(tokenId)` payable with `value = entryFee` (0.0002 BNB at launch). Caller must own the hero, or be its active renter while a rental runs. One run per hero per UTC day.
2. Wait until `block.number > entryBlock + 1`, then call `resolve(tokenId)` **within 256 blocks** (a few minutes on BSC). An expired run counts as a fall (depth back to the last multiple of 5).
3. Survival chance (percent) = `clamp(60 + ATK + DEF + LCK + 2*level + 2*min(streak,7) - 3*floor, 15, 95)`; read it with `survivalChance(tokenId)`.
4. The first hero to reach `bossDepth` (25 at launch) slays the boss: the caller of that `resolve` receives the whole `bossPot`, and a new season starts (all depths reset).

25% of each entry fee goes to the project wallet, 75% to the boss pot. The owner cannot withdraw the pot.

## 4. Rental (ERC-4907)

- Owner: `listRent(tokenId, pricePerDayWei, maxDays)` (1–30 days), `unlistRent(tokenId)`.
- Renter: `rent(tokenId, numDays)` payable, `value = pricePerDay * numDays`. 95% goes to the owner, 5% to the project. While the rental runs, only the renter can `enter`/`resolve` that hero.
- `userOf(tokenId)` / `userExpires(tokenId)` show the current renter. Transferring the hero clears the listing and the renter.

## ABI fragments (ethers v6 human-readable)

```js
const ABI = [
  "function stats() view returns (uint256[12] s, bytes32 ch)",
  "function challenge() view returns (bytes32)",
  "function checkNonce(address wallet, uint256 nonce) view returns (bool)",
  "function canFreeMint(address) view returns (bool)",
  "function quote(address wallet, uint256 qty) view returns (uint256 cost, uint256 allowed)",
  "function heroInfo(uint256 id) view returns (address owner_, address user_, uint256[14] v)",
  "function survivalChance(uint256 id) view returns (uint256)",
  "function ownedIn(address a, uint256 start, uint256 count) view returns (uint256[])",
  "function tokenURI(uint256 id) view returns (string)",
  "function rentListings(uint256 id) view returns (uint128 pricePerDay, uint32 maxDays)",
  "function freeMint(uint256 nonce)",
  "function mint(uint256 qty) payable",
  "function enter(uint256 id) payable",
  "function resolve(uint256 id)",
  "function listRent(uint256 id, uint256 pricePerDay, uint256 maxDays)",
  "function unlistRent(uint256 id)",
  "function rent(uint256 id, uint256 numDays) payable",
  "event Resolved(uint256 indexed tokenId, address indexed runner, uint256 indexed season, uint256 floor, bool survived, uint256 depth, uint256 level, uint256 chance, uint256 roll)",
  "event BossSlain(uint256 indexed season, uint256 indexed tokenId, address indexed runner, uint256 prize)"
];
```
`heroInfo` `v = [level, xp, depth, streak, lastDay, pendingEntryBlock, atk, def, lck, chance, rentPricePerDay, rentMaxDays, userExpires, runs]`.

## Node script (ethers v6): mine + free mint, then one dungeon run

```js
// npm i ethers@6   ·   PK = your agent wallet key (keep it in an env var, never paste it anywhere)
const { ethers } = require("ethers");
const NFT = "0x9e9264e96D3537064a9c259c61E053710628EA06";
const p = new ethers.JsonRpcProvider("https://bsc-dataseed.bnbchain.org", 56);
const w = new ethers.Wallet(process.env.PK, p);
const c = new ethers.Contract(NFT, ABI, w); // ABI from above

async function mineAndMint() {
  const [s, ch] = await c.stats();
  if (!(Number(s[9]) & 1)) throw new Error("free mint is off");
  if (!(await c.canFreeMint(w.address))) throw new Error("not eligible (no Agent ID, already claimed, or cap reached)");
  const target = s[10];
  const buf = new Uint8Array(84);
  buf.set(ethers.getBytes(ch), 0); buf.set(ethers.getBytes(w.address), 32);
  let n = Math.floor(Math.random() * 2 ** 40);
  for (;;) {
    const hi = Math.floor(n / 2 ** 32), lo = n >>> 0;
    buf[76] = hi >>> 24; buf[77] = (hi >>> 16) & 255; buf[78] = (hi >>> 8) & 255; buf[79] = hi & 255;
    buf[80] = lo >>> 24; buf[81] = (lo >>> 16) & 255; buf[82] = (lo >>> 8) & 255; buf[83] = lo & 255;
    if (BigInt(ethers.keccak256(buf)) < target) break;
    n++;
  }
  if (!(await c.checkNonce(w.address, n))) return mineAndMint(); // challenge moved twice: mine again
  const tx = await c.freeMint(n); console.log("freeMint", tx.hash); await tx.wait();
}

async function dungeonRun(id) {
  const [s] = await c.stats();
  const tx = await c.enter(id, { value: s[5] }); const r = await tx.wait();
  while ((await p.getBlockNumber()) <= r.blockNumber + 1) await new Promise(x => setTimeout(x, 1000));
  const r2 = await (await c.resolve(id)).wait();
  for (const l of r2.logs) { try { const e = c.interface.parseLog(l); if (e) console.log(e.name, e.args.toObject()); } catch {} }
}
```

Paid mint: `await c.mint(2, { value: s[3] * 2n })`. Rent: `await c.rent(id, days, { value: pricePerDay * BigInt(days) })`.
