Skip to content

Read token state

Everything you can learn about a LootFi token without an API key. Addresses: Deployments.

Core reads

solidity
function ownerOf(uint256 tokenId) external view returns (address);
function balanceOf(address owner) external view returns (uint256);
function tokenURI(uint256 tokenId) external view returns (string);
function categoryOf(uint256 tokenId) external pure returns (uint8);
function royaltyInfo(uint256 tokenId, uint256 salePrice)
    external view returns (address receiver, uint256 amount);
function withdrawFee() external view returns (uint256);
function baseURI() external view returns (string);
function supportsInterface(bytes4 interfaceId) external view returns (bool);
ts
import { publicClient } from "./chain";

const [owner, uri, category] = await Promise.all([
  publicClient.readContract({ address: LOOT_SKINS, abi, functionName: "ownerOf",    args: [tokenId] }),
  publicClient.readContract({ address: LOOT_SKINS, abi, functionName: "tokenURI",   args: [tokenId] }),
  publicClient.readContract({ address: LOOT_SKINS, abi, functionName: "categoryOf", args: [tokenId] }),
]);

categoryOf is pure — a bit-shift with no storage access. It returns a value for any id, including one that was never minted, so it is not an existence check.

Decoding a token id

tokenId = (category << 248) | sequence
ts
export function decodeTokenId(tokenId: bigint) {
  return {
    category: Number(tokenId >> 248n),
    sequence: tokenId & ((1n << 248n) - 1n),
  };
}
solidity
uint8   category = uint8(tokenId >> 248);
uint256 sequence = tokenId & ((uint256(1) << 248) - 1);
python
category = token_id >> 248
sequence = token_id & ((1 << 248) - 1)
OrdinalCategory
0Gun
1Knife
2Glove
3Agent
4Sticker
5Graffiti
6Container
7Music Kit

Gun is ordinal 0

if (category) is false for every gun — the most common item type. Test category != null / !== undefined, never truthiness. The same trap applies to paintSeed: 0, which is a real and sometimes valuable pattern.

Full layout and rationale: Token IDs.

Detecting a burned token

A burned token has no owner:

ts
try {
  await publicClient.readContract({ ...skins, functionName: "ownerOf", args: [tokenId] });
  // exists
} catch {
  // burned, or never minted — these are indistinguishable by this call alone
}

tokenURI reverts the same way. To distinguish burned from never minted, look for the Detokenized event, which is the authoritative burn record — see Events & indexing.

Enumerating the collection

LootSkins is not ERC-721Enumerable

There is no totalSupply(), no tokenByIndex(), and no tokenOfOwnerByIndex(). Do not build against them.

Replay Transfer logs from the deploy block (19419826) and fold them into an ownership map. A mint is a Transfer from the zero address; a burn is a Transfer to it. That is the same approach LootFi uses internally, and it is exact rather than approximate.

Can this token be listed or sold?

fulfillOrder accepts either approval form, so check both:

ts
const [approvedAll, approvedOne] = await Promise.all([
  publicClient.readContract({ ...skins, functionName: "isApprovedForAll", args: [owner, LOOT_MARKET] }),
  publicClient.readContract({ ...skins, functionName: "getApproved",      args: [tokenId] }),
]);

const marketCanMove =
  approvedAll || approvedOne.toLowerCase() === LOOT_MARKET.toLowerCase();

Checking only isApprovedForAll will report perfectly fillable orders as stale.

Is a specific order still fillable?

Mirror the contract's own checks rather than guessing:

ts
const [owner, used, minNonce] = await Promise.all([
  publicClient.readContract({ ...skins,  functionName: "ownerOf",         args: [order.tokenId] }),
  publicClient.readContract({ ...market, functionName: "usedOrCancelled", args: [order.maker, order.nonce] }),
  publicClient.readContract({ ...market, functionName: "minimumNonce",    args: [order.maker] }),
]);

const fillable =
  BigInt(order.expiry) > BigInt(Math.floor(Date.now() / 1000)) &&
  owner.toLowerCase() === order.maker.toLowerCase() &&
  !used &&
  BigInt(order.nonce) >= minNonce &&
  marketCanMove;

Better still, simulate the actual call — see Fill an order.

Interface support

ts
await publicClient.readContract({
  ...skins, functionName: "supportsInterface", args: ["0x80ac58cd"],  // ERC-721
});
await publicClient.readContract({
  ...skins, functionName: "supportsInterface", args: ["0x2a55205a"],  // ERC-2981
});

LootFi is not affiliated with, endorsed by, or sponsored by Valve Corporation. Counter-Strike and Steam are trademarks of Valve Corporation.