Skip to content

Trust & security

The honest inventory of what you are trusting, what you are not, and what the contracts actually enforce. Nothing on this page is hedged.

What requires trust

These are real trust assumptions. There is no cryptographic construction that removes them, and no wording that makes them smaller than they are.

Custody of the physical item

A CS2 skin lives in Valve's database. No blockchain can hold one. Somebody has to operate the Steam accounts that hold deposited items, and that party can be compromised, compelled, or dishonest. If the items are lost, your token is a claim on something that is gone.

This is the foundational assumption of the entire product. Everything else on this page is smaller than it.

Mint authority

An allow-listed minter can create tokens. The contract validates the category byte and rejects duplicate ids — it does not, and cannot, validate that a real skin backs a new token. A compromised or dishonest minter can create tokens backed by nothing, diluting the redeemability of tokens that are backed.

What the minter role cannot do: move, freeze, or burn a token that already exists. Those require the token's owner.

Metadata hosting

tokenURI points at an HTTP base URI. Float, pattern, stickers, and images are served from a host the contract owner controls. That host can change what it serves or stop serving. Metadata is not on chain and is not signed.

Facts you can verify without trusting it: who owns a token (ownerOf), its category (categoryOf, a pure bit-shift), and whether it still exists. See Metadata.

The binding between token and item

"Token 42 is backed by that AK" is an operator assertion, recorded off-chain. The chain does not know what a skin is. The uniqueness constraint on the item fingerprint makes it impossible to tokenize the same item twice — it does not prove that any given item exists.

What does not require trust

Enforced by contract code on chain. LootFi cannot override these, and neither can anyone else.

GuaranteeEnforced by
Your token is yoursERC-721 ownership. There is no admin transfer, no clawback, no freeze function in the contract.
You can transfer ittransferFrom / safeTransferFrom. No allow-list of recipients, no operator permission needed.
A sale settles atomicallyfulfillOrder: payment splits and the NFT moves in one transaction, or the whole thing reverts.
The operator never holds your fundsNo escrow. Payment goes buyer → royalty/fee/seller directly, in the fill transaction.
The operator never holds your token while listedNo escrow. The token stays in your wallet until a fill.
You can cancel your own listingscancel(nonce) and setMinimumNonce, keyed by msg.sender. Nobody can cancel for you; nobody can stop you.
Nobody else can cancel your listingsSame — the mapping is keyed by msg.sender; there is no parameter to target another maker.
Only you can burn your tokendeTokenize requires direct ownership. Approved operators are rejected.
A token id is never reissued_mint reverts on a duplicate id.
A category can never be changedIt is bits in the token id. No function writes it.

Contract-level protections in the deployed code

LootMarket

ProtectionMechanism
ReentrancynonReentrant guard, plus strict CEI: the nonce is marked used before any external call, and the NFT transfer is last
Order replayPer-maker nonce marked used at fill (usedOrCancelled[maker][nonce])
Cross-chain replaychainId in the EIP-712 domain, read from block.chainid at fill time
Cross-contract replayverifyingContract in the domain — a signature for one market cannot be used on another
Nonce scopingPer maker. Your nonce space is yours; no global counter, no interference
Bulk invalidationsetMinimumNonce retires every order below a watermark in one transaction
Arbitrary collectionsCollection allow-list, owner-controlled
Arbitrary payment tokensCurrency allow-list; native ETH is address(0) and always accepted
Fee inflationMAX_PLATFORM_FEE_BPS = 1000 — a hard 10% cap as a compile-time constant. setPlatformFee reverts above it
Seller shortfallRoyalty + fee exceeding the price reverts with FeeExceedsPrice before any payout
Stale ownershipownerOf re-read at fill; a maker who no longer owns the token cannot have their order filled
Missing approvalApproval re-checked at fill
Non-standard ERC-20sSafeERC20 on every token movement
Failed payoutsExplicit reverts (PaymentFailed, RefundFailed) — value is never silently retained
Emergency stopwhenNotPaused on fills; owner-controlled

LootSkins

ProtectionMechanism
Unauthorised mintingonlyMinter allow-list, separate from ownership
Invalid token idsCategory byte ≥ 8 reverts with InvalidCategory on both mint and categoryOf
Duplicate ids_mint reverts
Unauthorised burnsdeTokenize requires direct ownership; operator approval deliberately rejected
Batch abuseMAX_BATCH = 50 on both mintBatch and deTokenize; empty batches revert
Underpaid withdraw feeInsufficientFee(required, provided)
Burn attributionDetokenized.owner is msg.sender, which — because only the direct owner can burn — is always the true owner
Log recoverabilityDetokenized.tokenIds is in the data field, not indexed, so ids stay decodable

Admin powers and blast radius

Both contracts are UUPS-upgradeable and owner-administered. Concretely, the owner can:

  • Upgrade either implementation.
  • Add and remove minters (updateMinter).
  • Change the metadata base URI (updateBaseURI).
  • Change the default royalty receiver and rate (setDefaultRoyalty).
  • Change the withdraw fee (setWithdrawFee) and collect accrued fees.
  • Change the platform fee, up to the 10% cap, and the fee recipient.
  • Change the collection and currency allow-lists.
  • Pause and unpause marketplace fills.

Full detail, and the current holder of each role, on Admin & upgrades.

Ownership and the minter set are both transferable (transferOwnership, updateMinter), which is what makes moving to a multisig possible without redeploying.

The operator-approval consequence, stated honestly

To list gaslessly, a seller grants setApprovalForAll to the market contract. That approval persists until revoked.

An upgrade of the market contract could therefore move approved tokens. A new implementation runs at the same address, and every approval already granted to that address applies to it. Nothing about "the code you approved" is pinned.

This is not specific to LootFi — it is the standard tradeoff behind every gasless-listing marketplace, and the reason approvals are worth understanding rather than clicking through. It is also the strongest argument for moving ownership to a multisig, which is the stated intent.

What you can do about it right now:

  • Revoke when you are done selling. setApprovalForAll(market, false) costs one transaction and removes the exposure entirely. Your tokens stay yours; only listing stops working until you re-approve.
  • Approve per token instead of for all. approve(market, tokenId) grants the market rights to exactly one token. More transactions, much smaller surface.
  • Watch the owner address. Ownership transfers and upgrades are on-chain events. You can monitor them without asking anyone's permission.

Things worth knowing that are not vulnerabilities

  • Listings do not escrow. A listed token can be transferred or burned by its owner. The listing then simply cannot fill. This is by design and is why listing is free.
  • Off-chain order status is advisory. A listing marked invalid off-chain can still fill on chain if the invalidating condition was reversed. The contract decides; LootFi's records only describe.
  • Cancellation costs gas. It is an on-chain transaction because it has to be — a cancellation that existed only in LootFi's records would not stop a buyer who already holds your signature.
  • Smart-contract wallets cannot list. Signature verification is ECDSA.recover with no ERC-1271 fallback. Contract wallets can buy, hold, transfer, and burn — they cannot be makers. See Marketplace.
  • Metadata can change after minting. Stat enrichment fills in float, pattern and stickers as they resolve. The item identity never changes. See Metadata.
  • A returned skin has its own Steam hold. Valve's rule about a freshly traded item, not a LootFi policy.

Responsible disclosure

If you have found a security issue in the contracts, the backend, or the frontend, please report it privately first. Public disclosure of an unpatched issue in a system holding other people's property puts those people at risk, and there is no upside to it that a coordinated disclosure does not also provide.

Contact the team through the official channels linked from the LootFi application. Please include:

  • What you found, and the impact if exploited.
  • Reproduction steps, and a transaction hash if the issue is on chain.
  • Whether anything has been disclosed elsewhere, and any timeline you intend to hold us to.

Please do not test against other people's tokens, listings, or deposits. If a proof of concept requires touching live state, use your own.

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