Skip to Content
⚔ BattleMeme docs · early preview · expect rough edges
ContractsClaimManager

ClaimManager

Holds the winner-token reserve and registry of loser→winner conversion ratios. Loser holders call claim() to burn their bag and receive winner tokens at the rate locked in at elimination time. No deadline — the window stays open forever.

Source: contracts/src/core/ClaimManager.sol · ReentrancyGuard. Singleton — one ClaimManager handles every battle’s claims.

Roles

CallerWhat they can do
orchestrator (set by owner)registerLosingToken at battle finalize — pre-funds the winner reserve and records the ratio
anyone holding loser tokensclaim(loserToken, amount) — burns their bag, receives winner tokens
anyoneAll view functions (previewClaim, losingTokenInfo)

Public API

function registerLosingToken(address loserToken, address winnerToken, uint256 ratio) external; function claim(address loserToken, uint256 amount) external returns (uint256 winnerAmount); function previewClaim(address loserToken, uint256 amount) external view returns (uint256); function losingTokenInfo(address loserToken) external view returns (address winnerToken, uint256 ratio);

Conversion ratio

The ratio is scaled by 1e18:

winnerAmount = burnedLoser × ratio / 1e18

Where, at finalize:

ratio = (winnerPriceQ96 × 1e18) / loserPriceQ96

winnerPriceQ96 and loserPriceQ96 are sqrtPriceX96² snapshots — the winner’s at finalize, the loser’s at elimination.

The result is USD-fair at the moment of elimination: being eliminated late (after the loser pumped) costs less than being eliminated early.

claim flow

function claim(address loserToken, uint256 amount) external returns (uint256 winnerAmount);
  1. Look up (winnerToken, ratio) for loserToken. Reverts UnknownToken if not registered.
  2. Compute winnerAmount = amount × ratio / 1e18. Reverts NothingToClaim if 0.
  3. Approve check — caller must have approve(ClaimManager, ≥ amount) on the loser token.
  4. hook.burnLoserForClaim(loserToken, msg.sender, amount) — burns the caller’s loser balance via the hook’s privileged path (LOST tokens are otherwise frozen).
  5. Transfer winnerAmount of the winner token from this contract’s reserve to msg.sender.

Reverts InsufficientWinnerSupply only in pathological cases where the reserve has been drained (e.g. if orchestrator.registerLosingToken math underflowed).

Events

event LosingTokenRegistered(address indexed loserToken, address indexed winnerToken, uint256 ratio); event Claimed(address indexed loserToken, address indexed user, uint256 burnedAmount, uint256 winnerAmount);

Indexers typically subscribe to:

  • LosingTokenRegistered — to render claim-available cards on /portfolio
  • Claimed — for activity feed + analytics

Errors

ErrorCauseFix
AlreadyRegisteredRe-registering the same loser tokenEach loser is registered exactly once at finalize
UnknownTokenclaim on a token that wasn’t registeredBattle hasn’t been finalized yet, or token wasn’t part of any battle
NotOrchestratorNon-orchestrator called registerLosingTokenSetup mistake — set orchestrator via owner
NothingToClaimamount × ratio / 1e18 == 0Increase amount (dust burn), or check previewClaim first
InsufficientWinnerSupplyReserve too low (shouldn’t happen if registerLosingToken ran with the right amount)Audit the orchestrator finalize trace

Common usage patterns

Preview a claim before submitting

const preview = await publicClient.readContract({ address: ADDRESSES.ClaimManager, abi: ClaimManagerAbi, functionName: "previewClaim", args: [loserToken, userLoserBalance], }); // preview = winner tokens the caller will receive (in wei)

Full claim flow (with allowance)

const balance = await publicClient.readContract({ address: loserToken, abi: BattleMemeTokenAbi, functionName: "balanceOf", args: [account], }); // 1. Approve ClaimManager (one-time max) await walletClient.writeContract({ address: loserToken, abi: BattleMemeTokenAbi, functionName: "approve", args: [ADDRESSES.ClaimManager, (1n << 256n) - 1n], }); // 2. Claim const tx = await walletClient.writeContract({ address: ADDRESSES.ClaimManager, abi: ClaimManagerAbi, functionName: "claim", args: [loserToken, balance], });

Resolve winner from a loser token

const [winnerToken, ratio] = await publicClient.readContract({ address: ADDRESSES.ClaimManager, abi: ClaimManagerAbi, functionName: "losingTokenInfo", args: [loserToken], }); // winnerToken == 0x0 → not registered yet (battle not finalized).

See also: Claim rewards, BattleOrchestrator.