Read the collection state
Everything below is a free view call against mainnet. If you'd rather query indexed history (acquisitions, bids, settles, referral credits) over GraphQL instead of the chain, the protocol runs a public indexer; see Indexer.
The headline numbers
# Traits permanently collected (0..111)
cast call 0x59607d4d92a57EAa8544b2AdE7b014F8785AAf34 "collectedCount()(uint256)" --rpc-url https://ethereum-rpc.publicnode.com
# The live bid, in wei
cast call 0xC8ED01ffd957f5a62b1526d58E309c8bf2BB4A4c "bidBalance()(uint256)" --rpc-url https://ethereum-rpc.publicnode.com
# Punks vaulted so far
cast call 0x3614692b8C8B22890D66a5DfcBc6F6eAdEdE036f "lockedPunkCount()(uint256)" --rpc-url https://ethereum-rpc.publicnode.com
# Proofs minted so far (0..111)
cast call 0x3614692b8C8B22890D66a5DfcBc6F6eAdEdE036f "totalProofsMinted()(uint256)" --rpc-url https://ethereum-rpc.publicnode.com
# Is the work complete (all 111 trait bits collected)?
cast call 0x59607d4d92a57EAa8544b2AdE7b014F8785AAf34 "isComplete()(bool)" --rpc-url https://ethereum-rpc.publicnode.comPermanentCollection: the records core
Address: 0x59607d4d92a57EAa8544b2AdE7b014F8785AAf34. Full reference: PermanentCollection.
Coverage masks and counters
| View | Returns |
|---|---|
collectedMask() -> uint256 |
The canonical 111-bit completion mask. Monotonic; bit i set means trait i is permanently collected |
collectedCount() -> uint256 |
Population count of collectedMask (0..111) |
uncollectedMask() -> uint256 |
Complement within the full set |
pendingMask() -> uint256 |
Bits that are uncollected AND targeted by an in-flight return auction |
isComplete() -> bool |
True iff collectedMask equals the full 111-bit set |
isCollected(uint8 traitId) -> bool |
Single-bit read of collectedMask |
isPending(uint8 traitId) -> bool |
True iff uncollected and at least one in-flight return auction targets it |
pendingTraitCount(uint8) -> uint16 |
Per-trait in-flight counter (0 or 1 by protocol invariant) |
attemptCount(uint8) -> uint256 |
How many acquisitions have ever targeted this trait; drives the return auction reserve |
firstVaultedPunk(uint8 traitId) -> (uint16 punkId, bool exists) |
The Punk whose vaulting collected the trait; exists false while uncollected |
traitCarrierCount(uint8) -> uint16 |
How many of the 10,000 Punks carry the trait in the sealed dataset |
Targeting
| View | Returns |
|---|---|
canonicalTargetOf(uint16 punkId) -> uint8 |
The trait an acquisition of this Punk would record right now: the rarest uncollected, non-pending trait it carries (ties break to the lowest bit). Reverts NoEligibleTarget(punkId) if the Punk carries nothing targetable |
soleCarrierConstraint(uint16 punkId) -> (bool required, uint8 requiredTraitId) |
Whether the sole-carrier guard constrains this Punk (true only for #8348 while trait bit 23 is uncollected) |
newBitsFor(uint16 punkId) -> uint256 / newBitsCountFor(uint16) -> uint256 |
Mask (and count) of the Punk's traits not yet collected |
The acquisitions log (append-only)
| View | Returns |
|---|---|
acquisitionCount() -> uint256 |
Total rows ever recorded; monotonic |
getAcquisition(uint256 idx) -> Acquisition |
Row by 0-based index; page with acquisitionCount() |
getAcquisitionFor(uint16 punkId) -> Acquisition |
The Punk's current row; reverts NotRecorded(punkId) if never acquired |
acquisitionIndexOf(uint16 punkId) -> uint256 |
0-based index of the Punk's current row; reverts NotRecorded |
isRecorded(uint16 punkId) -> bool |
Whether the Punk has ever been acquired |
originalSellerOf(uint16 punkId) -> address |
The address that gave up the Punk (the future Proof recipient); zero if unrecorded |
custodyOf(uint16 punkId) -> Custody |
Current custody: 0 None (never acquired), 1 InReturnAuction, 2 ReturnedToMarket, 3 Vaulted (terminal) |
The Acquisition struct fields, in ABI order: punkId (uint16), targetTraitId (uint8), mask (uint256), pendingMaskAtAcquisition (uint256), acquirer (address), originalSeller (address), priceWei (uint256), acquiredAtBlock (uint256), custody (uint8).
Patron: the live bid
Address: 0xC8ED01ffd957f5a62b1526d58E309c8bf2BB4A4c. Full reference: Patron.
bidBalance() -> uint256 is the live bid: the adapter-accounted ETH that acceptBid / acceptListing pay from. Use it, not the raw contract balance, which can exceed the bid if ETH was force-sent. accountedLiveBidWei() returns the same value.
ReturnAuctionModule: per-Punk sale state
Address: 0x2ced1CA0BC7996c3dFB3D4E685d9d9E7405A7aCe. Full reference: ReturnAuctionModule.
| View | Returns |
|---|---|
getSale(uint16 punkId) -> ReturnAuction |
Full sale struct: acquisitionCost (uint128), highBidWei (uint128), highBidder (address), startedAt (uint64), endsAt (uint64), reserveWei (uint128), targetTraitId (uint8), settled (bool). All-zero if no sale ever started |
reserveOf(uint16) -> uint256 |
Bid floor, snapshotted at sale start |
highBidOf(uint16) -> uint128 / highBidderOf(uint16) -> address |
Current winning bid and bidder (0 / zero address before the first bid) |
endsAt(uint16) -> uint64 / startedAt(uint16) -> uint64 |
Deadline (anti-snipe extensions included) and start time |
isLive(uint16) -> bool |
Currently accepting bids |
isSettleable(uint16) -> bool |
settle(punkId) would succeed right now |
referrerOfHighBid(uint16) -> address |
Referrer attached to the current high bid |
pendingRefund(address) -> uint256 |
Queued outbid refunds awaiting withdrawRefund() |
Enumerate sales from the ReturnAuctionStarted(punkId, acquisitionCost, reserveWei, startedAt, endsAt) event log; a settled slot is reusable, so always confirm with the live views.
PunkVault: Proofs and the Title
Address: 0x3614692b8C8B22890D66a5DfcBc6F6eAdEdE036f. Full reference: PunkVault.
The vault is the ERC721 issuer of the 111 Proofs (token ids 0..110, tokenId == traitId) and the Vault Title (token id 111).
| View | Returns |
|---|---|
totalProofsMinted() -> uint256 |
Proofs minted so far, caps at 111 |
isProofMinted(uint8 traitId) -> bool |
Whether the trait's Proof exists (false for ids >= 111) |
proofMeta(uint256 tokenId) -> (uint16 punkId, uint8 traitId, uint16 sequence, uint64 mintedAtBlock) |
Per-Proof metadata, frozen at mint. sequence is the 1-based collection order, which diverges from tokenId because traits vault in arbitrary order |
proofsMintedMask() -> uint256 |
Bitmap of minted Proofs, bit traitId; one read instead of 111 |
lockedPunkCount() -> uint256 / isLocked(uint16) -> bool |
Vaulted-Punk counter and per-Punk flag |
titleMinted() -> bool / titleOwner() -> address |
Title state; titleOwner is zero before the mint |
totalSupply() -> uint256 |
Minted Proofs plus the Title if minted |
ownerOf(uint256) / tokenURI(uint256) |
Standard ERC721; tokenURI reverts UnknownTokenId for an unminted Proof id |
# Metadata of the Proof for trait 23
cast call 0x3614692b8C8B22890D66a5DfcBc6F6eAdEdE036f "proofMeta(uint256)(uint16,uint8,uint16,uint64)" 23 --rpc-url https://ethereum-rpc.publicnode.comDecoding the 111-bit masks
Every mask (collectedMask, pendingMask, uncollectedMask, per-Punk trait masks, proofsMintedMask) uses the same convention: bit i corresponds to trait id i, for i in 0..110. The full set is (1 << 111) - 1.
Plain BigInt:
const TRAIT_COUNT = 111;
/** Trait ids present in a mask. */
function traitIdsOf(mask: bigint): number[] {
const ids: number[] = [];
for (let i = 0; i < TRAIT_COUNT; i++) {
if ((mask >> BigInt(i)) & 1n) ids.push(i);
}
return ids;
}
const has = (mask: bigint, traitId: number) => ((mask >> BigInt(traitId)) & 1n) === 1n;
const count = (mask: bigint) => traitIdsOf(mask).length;Reading and decoding with viem:
import {createPublicClient, http, parseAbi} from 'viem';
import {mainnet} from 'viem/chains';
const client = createPublicClient({chain: mainnet, transport: http('https://ethereum-rpc.publicnode.com')});
const abi = parseAbi([
'function collectedMask() view returns (uint256)',
'function pendingMask() view returns (uint256)',
'function canonicalTargetOf(uint16 punkId) view returns (uint8)',
]);
const collected = await client.readContract({
address: '0x59607d4d92a57EAa8544b2AdE7b014F8785AAf34',
abi,
functionName: 'collectedMask',
});
console.log('collected traits:', traitIdsOf(collected)); // e.g. [3, 17, 54]And with cast, printing the mask as hex for eyeballing:
cast call 0x59607d4d92a57EAa8544b2AdE7b014F8785AAf34 "collectedMask()(uint256)" --rpc-url https://ethereum-rpc.publicnode.com | cast to-hexTrait names for each id come from the sealed PunksData contract at 0x9cF9C8eA737A7d5157d3F4282aCe30880a7A117C, or from the indexer's trait entities.