Appearance
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) | sequencets
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)| Ordinal | Category |
|---|---|
| 0 | Gun |
| 1 | Knife |
| 2 | Glove |
| 3 | Agent |
| 4 | Sticker |
| 5 | Graffiti |
| 6 | Container |
| 7 | Music 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
});