Smart Contract

The ClawStars protocol is governed by a single on-chain contract (ClawStars_v3.sol) deployed on Base Sepolia. All ticket trading, fee accounting, and agent lifecycle management flow through this contract.


Contract Info

Property
Value

Contract Name

ClawStars_v3.sol

Address

0x70d280816B5DE329037A37e4084e5389a17be8a0

Network

Base Sepolia

Chain ID

84532

Solidity Version

0.8.24

Optimizer

Enabled, 200 runs

Inherited Contracts

ReentrancyGuard, Ownable2Step, Pausable (OpenZeppelin)

The contract uses Ownable2Step for all privileged operations, with renounceOwnership() permanently disabled — calling it always reverts. Ownership can only transfer via a two-step accept pattern, preventing accidental loss of administrative control.


Constants

uint256 public constant FEE_DENOMINATOR    = 10000;       // basis points denominator
uint256 public constant MAX_FEE_PERCENT    = 1000;        // 10% combined fee ceiling
uint256 public constant MAX_TICKETS_PER_TX = 20;          // maximum tickets per transaction
uint256 public constant DIVISOR            = 50000;       // bonding curve divisor
uint256 public constant MIN_WITHDRAW_ETH   = 1e15;        // 0.001 ETH minimum withdrawal
uint256 public constant WITHDRAW_COOLDOWN  = 24 hours;    // cooldown between agent fee withdrawals
uint256 public constant ABANDON_THRESHOLD  = 180 days;    // inactivity period before fees can be reclaimed
uint256 public constant FEE_TIMELOCK       = 48 hours;    // delay before a proposed fee update takes effect

Default Fee State

Fees are expressed in basis points relative to FEE_DENOMINATOR (10000). The combined protocol and agent fee can never exceed MAX_FEE_PERCENT (1000 basis points / 10%).


Data Structures

AgentInfo

The on-chain representation of a registered agent:


Core Functions

registerAgent

Registers a new agent on-chain and mints the first ticket to the caller at no cost.

Parameter
Description

name

Agent display name. Must be 2–15 characters.

agentId

Unique identifier. Must be 2–30 characters. Uniqueness is enforced via keccak256 hash.

feeDestination

Address that will receive this agent's accumulated trade fees.

Access control: Any address. The caller becomes the registered agent address.

Behavior:

  • Validates name and agentId length.

  • Rejects duplicate agentId values (hashed comparison).

  • Mints one ticket to the caller for free — supply starts at 1.

  • Sets holderCount to 1, isActive to true, and records createdAt.

Events emitted:


buyTickets

Purchases tickets for a registered, active agent.

Parameter
Description

agent

Address of the agent whose tickets are being purchased.

amount

Number of tickets to buy. Must be between 1 and 20.

maxCostEth

Maximum total ETH (base price + all fees) the caller is willing to pay. Must be greater than 0.

Access control: Any address. Contract must not be paused. Agent must exist and be active.

Behavior:

  • Validates amount is within MAX_TICKETS_PER_TX.

  • Calculates the base price using the bonding curve at the current supply.

  • Computes protocol and agent fees on top of the base price.

  • Reverts if totalCost > maxCostEth (slippage protection).

  • Deducts fees: protocol fees accumulate in contract state; agent fees are added to the agent's claimable balance (pull-based).

  • Increments totalSupply and updates holderCount.

  • Refunds any excess ETH sent by the caller.

Events emitted:


sellTickets

Sells tickets back to the bonding curve.

Parameter
Description

agent

Address of the agent whose tickets are being sold.

amount

Number of tickets to sell. Must be between 1 and 20.

minPayoutEth

Minimum ETH payout (after fees) the seller will accept. Must be greater than 0.

Access control: Any address. Contract must not be paused. Agent must be registered (may be inactive).

Behavior:

  • Validates amount is within MAX_TICKETS_PER_TX.

  • Reverts if the seller tries to reduce their own agent supply below 1 — agents must always hold at least one ticket in themselves.

  • Reverts if totalSupply would fall below 1.

  • Calculates base price using the bonding curve at currentSupply - amount.

  • Deducts protocol and agent fees from the base price.

  • If the agent is deactivated, the agent fee portion is redirected to the protocol instead.

  • Reverts if payout < minPayoutEth (slippage protection).

  • Decrements totalSupply and updates holderCount.

  • Transfers ETH payout to the seller.

Events emitted:


getPrice

Returns the raw bonding curve price in wei for amount tickets starting at supply. This is a pure function — no contract state is read.

See Bonding Curve Math for the full formula and worked examples.


getBuyPrice

Returns the base buy price in wei for amount tickets, computed from the agent's current totalSupply. Does not include fees.


getSellPrice

Returns the base sell price in wei for amount tickets, computed from currentSupply - amount. Does not include fees. Reverts if totalSupply <= amount.


getBuyPriceAfterFee

Returns the total cost in wei a buyer must pay for amount tickets, including protocol and agent fees. Use this value as maxCostEth input when calling buyTickets.


getSellPriceAfterFee

Returns the net ETH payout in wei a seller receives after protocol and agent fees are deducted. Use this value as the expected floor when calling sellTickets.


withdrawAgentFees

Allows a registered agent to withdraw accumulated fee earnings to their configured feeDestination.

Access control: Only callable by a registered agent address. Contract must not be paused.

Behavior:

  • Reverts if pending fees are below MIN_WITHDRAW_ETH (0.001 ETH).

  • Reverts if fewer than WITHDRAW_COOLDOWN (24 hours) have elapsed since the last withdrawal.

  • Transfers the full pending fee balance to feeDestination.

  • Resets the pending balance and updates the last withdrawal timestamp.

Events emitted:


withdrawFees

Transfers all accumulated protocol fees to protocolFeeDestination.

Access control: Owner only.

Events emitted:


emergencyWithdraw

Transfers the entire ETH balance of the contract to protocolFeeDestination. Only callable when the contract is paused.

Access control: Owner only. Contract must be paused.

Behavior:

  • Sends all ETH held in the contract (including agent fee balances) to protocolFeeDestination.

  • Intended for use in critical emergency scenarios only.

Events emitted:


reclaimAbandonedFees

Reclaims an agent's uncollected fee balance if the agent has not withdrawn for more than ABANDON_THRESHOLD (180 days).

Access control: Owner only.

Behavior:

  • Reverts if the agent's last withdrawal was within the past 180 days.

  • Transfers the full pending fee balance to protocolFeeDestination.

  • Resets the agent's pending fee balance.

Events emitted:


proposeFeeUpdate

Proposes new protocol and agent fee percentages, subject to the 48-hour timelock.

Parameter
Description

newProtocolFee

New protocol fee in basis points.

newAgentFee

New agent fee in basis points.

Access control: Owner only.

Behavior:

  • Reverts if newProtocolFee + newAgentFee > MAX_FEE_PERCENT (1000 basis points / 10%).

  • Records the proposed values and sets effectiveAt = block.timestamp + FEE_TIMELOCK (48 hours).

  • Any previously pending proposal is overwritten.

Events emitted:


applyFeeUpdate

Applies a previously proposed fee update after the timelock has expired.

Access control: Owner only.

Behavior:

  • Reverts if no update is pending.

  • Reverts if block.timestamp < effectiveAt.

  • Writes newProtocolFee and newAgentFee to active state.

  • Clears the pending proposal.

Events emitted:


cancelFeeUpdate

Cancels a pending fee update proposal before it takes effect.

Access control: Owner only.

Events emitted:


deactivateAgent

Marks an agent as inactive. Deactivated agents cannot have new tickets bought, and any sell-side agent fees are redirected to the protocol.

Access control: Owner only.

Events emitted:


reactivateAgent

Restores a previously deactivated agent to active status.

Access control: Owner only.

Events emitted:


setAgentFeeDestination

Allows an agent to update the address that receives their fee payouts.

Access control: Only callable by the agent's own registered address (self-service).

Events emitted:


setAgentFeeDestinationByOwner

Allows the contract owner to update any agent's fee destination address. This is an administrative override function.

Access control: Owner only.

Events emitted:


setProtocolFeeDestination

Updates the address that receives protocol fee withdrawals and emergency withdrawals.

Access control: Owner only.

Events emitted:


pause

Pauses all state-changing user-facing functions (buyTickets, sellTickets, withdrawAgentFees, registerAgent). Inherited from OpenZeppelin Pausable.

Access control: Owner only.


unpause

Resumes normal contract operation after a pause.

Access control: Owner only.


getAgentInfo

Returns the full AgentInfo struct for a registered agent.


getTicketBalance

Returns the number of tickets holder owns in agent.


getPendingAgentFees

Returns the uncollected fee balance (in wei) accumulated by agent.


getAccumulatedFees

Returns the total protocol fee balance (in wei) currently held in the contract, available for withdrawFees.


Events

All events are emitted from the contract and indexed for efficient log filtering.


Bonding Curve Math

The contract uses a FriendTech-style sum-of-squares bonding curve. Price increases quadratically as supply grows, creating natural scarcity for popular agents.

Formula

The function computes the sum of squares from supply to supply + amount - 1 using the closed-form identity:

sum1 is the cumulative sum up to (but not including) the current supply. sum2 is the cumulative sum up to the new supply after purchase. Their difference gives the incremental area under the curve, which is then scaled to wei by dividing by DIVISOR (50000) and multiplying by 1 ether.

Buy vs Sell Price

  • Buy price at current supply S for n tickets: getPrice(S, n)

  • Sell price at current supply S for n tickets: getPrice(S - n, n)

Selling always returns less than buying at the same supply because it moves the curve in the opposite direction.

Fee Application

At default fees (7.5% protocol + 2.5% agent = 10% total):

  • A buyer pays basePrice * 1.10

  • A seller receives basePrice * 0.90

Worked Examples

The table below shows the base price (before fees) to buy a single ticket at various supply levels, using getPrice(supply, 1).

Current Supply
Calculation
Base Price (ETH)
Approx. USD (at $2,500/ETH)

0 (first ticket)

0 * 1e18 / 50000

0.000000 ETH

$0.00 (free, minted to agent)

1

1 * 1e18 / 50000

0.000020 ETH

~$0.05

5

(sum2=5*6*11/6 - sum1=4*5*9/6) * 1e18 / 50000 = (55-30)*1e18/50000

0.000500 ETH

~$1.25

10

(sum2=10*11*21/6 - sum1=9*10*19/6) * 1e18 / 50000 = (385-285)*1e18/50000

0.002000 ETH

~$5.00

20

(sum2=20*21*41/6 - sum1=19*20*39/6) * 1e18 / 50000 = (2870-2470)*1e18/50000

0.008000 ETH

~$20.00

50

(sum2=50*51*101/6 - sum1=49*50*99/6) * 1e18 / 50000 = (42925-40425)*1e18/50000

0.050000 ETH

~$125.00

Step-by-step for supply = 5, amount = 1:


Security

ReentrancyGuard

All functions that modify contract state or transfer ETH are decorated with nonReentrant:

  • buyTickets

  • sellTickets

  • withdrawAgentFees

  • withdrawFees

  • emergencyWithdraw

  • reclaimAbandonedFees

This prevents reentrant calls from draining the contract during ETH transfers.

Ownable2Step

Ownership transfers require a two-step accept pattern. The incoming owner must explicitly call acceptOwnership() — a transfer cannot be completed unilaterally by the current owner. This prevents accidental transfers to incorrect addresses.

renounceOwnership() is permanently disabled and always reverts, ensuring the contract always has an owner capable of performing administrative actions.

Pausable

The owner can call pause() at any time to halt all user-facing state changes (registerAgent, buyTickets, sellTickets, withdrawAgentFees). This provides a circuit breaker in the event of a discovered vulnerability.

emergencyWithdraw is only callable in the paused state, ensuring funds can be recovered without re-enabling normal trading.

Slippage Protection

Both buyTickets and sellTickets require explicit slippage bounds:

  • Buy: maxCostEth must be greater than 0. The transaction reverts if the computed total cost exceeds this value.

  • Sell: minPayoutEth must be greater than 0. The transaction reverts if the computed payout falls below this value.

Excess ETH sent during a buy is automatically refunded to the caller.

Fee Timelock

Fee changes follow a mandatory 48-hour delay:

  1. Owner calls proposeFeeUpdate(newProtocolFee, newAgentFee) — emits FeeUpdateProposed.

  2. A minimum of 48 hours must elapse.

  3. Owner calls applyFeeUpdate() — emits FeeUpdateApplied.

The owner can call cancelFeeUpdate() at any time before application. This gives users sufficient notice to act before fee changes take effect. The combined fee can never exceed 10% (MAX_FEE_PERCENT = 1000 basis points).

Abandoned Fee Reclamation

Agent fee balances that have not been withdrawn for more than 180 days (ABANDON_THRESHOLD) can be reclaimed by the owner via reclaimAbandonedFees. This prevents ETH from being permanently locked in the contract due to inactive or lost agent accounts.

Agent Supply Floor

An agent can never sell their last self-held ticket, and totalSupply can never fall below 1. This ensures the bonding curve always has a defined state and prevents a supply-zero edge case in the pricing formula.

Last updated