Bionttestnet
Address

octzuh9c…VGcqL7

octzuh9cEUKSd5iieVN8KUe4cdistvEtb3KbcrJLYVGcqL7
State
OCT balance
2OCT
wallet balance
Type
Contract
smart contract on chain
Chain-level
View on Octrascan · devnet
raw txs · nonce · pubkey
Pipoke (no profile)
this wallet has not registered a Pipoke profile
Contract
balance2 OCT
version1.0 Rehovot
code hash5c1bc1…aefcff
History
live · 20s · last 0
Source · ABI · Bytecode
✓ verified
expand →
✓ verified
contract Xcollection {

  struct TokenInfo {
    owner:        address
    minted_epoch: int
  }

  event Initialized(owner: address, name: string, max_supply: int, injection_cap: int)
  event Minted(minter: address, token_id: int, phase: int, price: int)
  event GenesisMint(minter: address, token_id: int, genesis_id: int, genesis_minted_count: int, injection_cap: int)
  event Airdropped(recipient: address, token_id: int)
  event Transferred(token_id: int, from: address, to: address)
  event Approved(token_id: int, approved: address)
  event ApprovalRevoked(token_id: int)
  event OperatorSet(owner: address, operator: address, approved: int)
  event Revealed(base_uri: string)
  event BaseUriUpdated(base_uri: string)
  event PhaseOverrideSet(phase: int)
  event PhaseOverrideCleared()
  event InjectionSwept(slots_swept: int, new_public_cap: int)
  event ProceedsClaimed(recipient: address, amount: int)
  event XpectrumWalletUpdated(new_wallet: address)
  event OwnershipTransferred(old_owner: address, new_owner: address)

  const PLATFORM_FEE_BPS: int = 250
  const MAX_ROYALTY_BPS:  int = 1000
  const MAX_INJECTION:    int = 222
  const INJECTION_BPS:    int = 2000
  const MIN_PHASE_EPOCHS: int = 360
  const MAX_GTD_EPOCHS:   int = 259200
  const MAX_FCFS_EPOCHS:  int = 259200

  state {
    owner:                  address
    admin:                  address
    xpectrum_wallet:        address

    name:                   string
    symbol:                 string
    base_uri:               string
    unrevealed_uri:         string
    revealed:               int

    royalty_bps:            int
    royalty_receiver:       address

    max_supply:             int
    total_minted:           int

    max_per_wallet:         int
    minted_per_wallet:      map[address]int

    initialized:            int
    paused:                 int

    phase_override_active:  int
    phase_override_value:   int

    gtd_price:              int
    gtd_start_epoch:        int
    gtd_end_epoch:          int
    gtd_cap:                int
    gtd_minted:             int
    gtd_wallet_cap:         int
    gtd_minted_by:          map[address]int

    fcfs_price:             int
    fcfs_start_epoch:       int
    fcfs_end_epoch:         int
    fcfs_cap:               int
    fcfs_minted:            int
    fcfs_wallet_cap:        int
    fcfs_minted_by:         map[address]int

    public_price:           int
    public_start_epoch:     int
    public_end_epoch:       int
    public_cap:             int
    public_minted:          int
    public_wallet_cap:      int
    public_minted_by:       map[address]int

    xlist_addr:             address
    genesis_contract:       address
    injection_cap:          int
    genesis_minted_count:   int
    used_genesis_ids:       map[int]int
    injection_swept:        int
    airdropped_count:       int

    balances:               map[address]int
    tokens:                 map[int]TokenInfo
    approvals:              map[int]address
    operator_approvals:     map[address]map[address]int

    reserve:                int
    pending_proceeds:       map[address]int
  }

  constructor() {
    self.owner                 = caller
    self.initialized           = 0
    self.revealed              = 0
    self.total_minted          = 0
    self.paused                = 0
    self.phase_override_active = 0
    self.phase_override_value  = 3
    self.reserve               = 0
    self.genesis_minted_count  = 0
    self.injection_swept       = 0
    self.airdropped_count      = 0
  }

  private fn only_initialized() {
    require(self.initialized == 1, "xcollection: not initialized")
  }

  private fn only_owner_or_admin() {
    require(caller == self.owner || caller == self.admin, "xcollection: not authorized")
  }

  fn init(
    collection_name:           string,
    collection_symbol:         string,
    collection_unrevealed_uri: string,
    collection_max_supply:     int,
    collection_royalty_bps:    int,
    collection_max_per_wallet: int,
    xpectrum_addr:             address,
    creator:                   address,
    xlist:                     address,
    genesis:                   address,
    gtd_wallet:                int,
    fcfs_wallet:               int,
    pub_wallet:                int
  ): bool {
    require(self.initialized == 0,                            "xcollection: already initialized")
    require(caller == self.owner,                             "xcollection: not authorized")
    require(is_address(xpectrum_addr) && xpectrum_addr != 0, "xcollection: invalid platform addr")
    require(is_address(creator) && creator != 0,             "xcollection: invalid creator")
    require(is_address(xlist) && xlist != 0,                 "xcollection: invalid xlist addr")
    require(is_address(genesis) && genesis != 0,             "xcollection: invalid genesis addr")
    require(collection_max_supply > 0,                       "xcollection: invalid supply")
    require(
      collection_royalty_bps >= 0 &&
      collection_royalty_bps <= MAX_ROYALTY_BPS,
      "xcollection: invalid royalty"
    )
    require(collection_max_per_wallet > 0, "xcollection: invalid wallet cap")
    require(gtd_wallet > 0 && fcfs_wallet > 0 && pub_wallet > 0, "xcollection: wallet caps must be > 0")

    let raw_injection = collection_max_supply * INJECTION_BPS / 10000
    let cap = raw_injection
    if raw_injection > MAX_INJECTION {
      cap = MAX_INJECTION
    }

    self.name              = collection_name
    self.symbol            = collection_symbol
    self.unrevealed_uri    = collection_unrevealed_uri
    self.max_supply        = collection_max_supply
    self.royalty_bps       = collection_royalty_bps
    self.max_per_wallet    = collection_max_per_wallet
    self.admin             = xpectrum_addr
    self.xpectrum_wallet   = xpectrum_addr
    self.royalty_receiver  = creator
    self.xlist_addr        = xlist
    self.genesis_contract  = genesis
    self.injection_cap     = cap
    self.gtd_wallet_cap    = gtd_wallet
    self.fcfs_wallet_cap   = fcfs_wallet
    self.public_wallet_cap = pub_wallet
    self.initialized       = 1
    self.owner             = creator

    emit Initialized(creator, collection_name, collection_max_supply, cap)
    return true
  }

  view fn get_current_phase(): int {
    if self.phase_override_active == 1 {
      return self.phase_override_value
    }
    if epoch >= self.gtd_start_epoch && epoch < self.gtd_end_epoch {
      return 0
    }
    if epoch >= self.fcfs_start_epoch && epoch < self.fcfs_end_epoch {
      return 1
    }
    if epoch >= self.public_start_epoch && epoch < self.public_end_epoch {
      return 2
    }
    return 3
  }

  fn mint(quantity: int): bool {
    only_initialized()
    require(self.paused == 0,  "xcollection: minting paused")
    require(quantity > 0,      "xcollection: zero quantity")
    require(quantity <= 5,     "xcollection: max 5 per tx")
    require(
      self.total_minted + quantity <= self.max_supply,
      "xcollection: exceeds max supply"
    )
    require(
      self.minted_per_wallet[caller] + quantity <= self.max_per_wallet,
      "xcollection: global wallet cap exceeded"
    )

    let phase = get_current_phase()
    require(phase != 3, "xcollection: minting closed")

    let price = 0

    if phase == 0 {
      let wl = call(self.xlist_addr, "is_whitelisted", 0, caller)
      require(wl == 1, "xcollection: not on GTD list — use genesis_mint if you hold a genesis NFT")
      require(
        self.gtd_minted_by[caller] + quantity <= self.gtd_wallet_cap,
        "xcollection: GTD phase wallet cap exceeded"
      )
      require(
        self.gtd_minted + quantity <= self.gtd_cap,
        "xcollection: GTD cap reached"
      )
      price = self.gtd_price
      self.gtd_minted            += quantity
      self.gtd_minted_by[caller] += quantity
    }

    if phase == 1 {
      let wl = call(self.xlist_addr, "is_whitelisted", 1, caller)
      require(wl == 1, "xcollection: not on FCFS list")
      require(
        self.fcfs_minted_by[caller] + quantity <= self.fcfs_wallet_cap,
        "xcollection: FCFS phase wallet cap exceeded"
      )
      require(
        self.fcfs_minted + quantity <= self.fcfs_cap,
        "xcollection: FCFS cap reached"
      )
      price = self.fcfs_price
      self.fcfs_minted            += quantity
      self.fcfs_minted_by[caller] += quantity
    }

    if phase == 2 {
      require(
        self.public_minted_by[caller] + quantity <= self.public_wallet_cap,
        "xcollection: public phase wallet cap exceeded"
      )
      require(
        self.public_minted + quantity <= self.public_cap,
        "xcollection: public cap reached"
      )
      price = self.public_price
      self.public_minted            += quantity
      self.public_minted_by[caller] += quantity
    }

    let cost = price * quantity
    require(value >= cost, "xcollection: insufficient payment")

    let fee              = cost * PLATFORM_FEE_BPS / 10000
    let creator_proceeds = cost - fee

    self.reserve                                += value
    self.pending_proceeds[self.owner]           += creator_proceeds
    self.pending_proceeds[self.xpectrum_wallet] += fee

    self.minted_per_wallet[caller] += quantity
    self.balances[caller]          += quantity

    let start_id      = self.total_minted
    self.total_minted += quantity

    let idx = 0
    for idx in 0..quantity {
      self.tokens[start_id + idx].owner        = caller
      self.tokens[start_id + idx].minted_epoch = epoch
      emit Minted(caller, start_id + idx, phase, price)
      emit Transferred(start_id + idx, 0, caller)
    }

    if value > cost {
      let refund = value - cost
      self.reserve -= refund
      transfer(caller, refund)
    }

    return true
  }

  fn genesis_mint(genesis_id: int): bool {
    only_initialized()
    require(self.paused == 0, "xcollection: minting paused")
    require(
      self.total_minted + 1 <= self.max_supply,
      "xcollection: exceeds max supply"
    )
    require(
      self.minted_per_wallet[caller] + 1 <= self.max_per_wallet,
      "xcollection: global wallet cap exceeded"
    )

    let phase = get_current_phase()
    require(phase == 0, "xcollection: genesis mint only available during GTD phase")
    require(self.injection_swept == 0, "xcollection: genesis injection already swept")

    require(self.used_genesis_ids[genesis_id] == 0, "xcollection: genesis token already used")
    require(
      self.genesis_minted_count < self.injection_cap,
      "xcollection: genesis injection full"
    )
    require(
      self.gtd_minted_by[caller] + 1 <= self.gtd_wallet_cap,
      "xcollection: GTD phase wallet cap exceeded"
    )

    let token_owner = call(self.genesis_contract, "owner_of", genesis_id)
    require(token_owner == caller, "xcollection: caller does not own this genesis token")

    self.used_genesis_ids[genesis_id]  = 1
    self.genesis_minted_count         += 1
    self.gtd_minted_by[caller]        += 1
    self.gtd_minted                   += 1

    let cost = self.gtd_price
    require(value >= cost, "xcollection: insufficient payment")

    let fee              = cost * PLATFORM_FEE_BPS / 10000
    let creator_proceeds = cost - fee

    self.reserve                                += value
    self.pending_proceeds[self.owner]           += creator_proceeds
    self.pending_proceeds[self.xpectrum_wallet] += fee

    self.minted_per_wallet[caller] += 1
    self.balances[caller]          += 1

    let token_id      = self.total_minted
    self.total_minted += 1

    self.tokens[token_id].owner        = caller
    self.tokens[token_id].minted_epoch = epoch

    emit GenesisMint(caller, token_id, genesis_id, self.genesis_minted_count, self.injection_cap)
    emit Transferred(token_id, 0, caller)

    if value > cost {
      let refund = value - cost
      self.reserve -= refund
      transfer(caller, refund)
    }

    return true
  }

  fn sweep_injection(): bool {
    only_initialized()
    only_owner_or_admin()
    require(self.injection_swept == 0,   "xcollection: injection already swept")
    require(
      epoch >= self.gtd_end_epoch || get_current_phase() > 0,
      "xcollection: GTD phase not yet ended"
    )

    let unclaimed = self.injection_cap - self.genesis_minted_count

    if unclaimed > 0 {
      self.public_cap += unclaimed
    }
    self.injection_cap    = 0
    self.injection_swept  = 1

    emit InjectionSwept(unclaimed, self.public_cap)
    return true
  }

  fn airdrop(
    addr_0:  address, addr_1:  address, addr_2:  address, addr_3:  address,
    addr_4:  address, addr_5:  address, addr_6:  address, addr_7:  address,
    addr_8:  address, addr_9:  address, addr_10: address, addr_11: address,
    addr_12: address, addr_13: address, addr_14: address, addr_15: address,
    addr_16: address, addr_17: address, addr_18: address, addr_19: address,
    count:   int
  ): bool {
    only_initialized()
    require(caller == self.owner,     "xcollection: only owner")
    require(count > 0 && count <= 20, "xcollection: count must be 1-20")
    require(
      self.total_minted + count <= self.max_supply,
      "xcollection: exceeds max supply"
    )

    let actual = 0

    if count > 0  && is_address(addr_0)  && addr_0  != 0 { let tid = self.total_minted  self.total_minted += 1  actual += 1  self.tokens[tid].owner = addr_0   self.tokens[tid].minted_epoch = epoch  self.balances[addr_0]  += 1  emit Minted(addr_0,  tid, 3, 0)  emit Airdropped(addr_0,  tid)  emit Transferred(tid, 0, addr_0)  }
    if count > 1  && is_address(addr_1)  && addr_1  != 0 { let tid = self.total_minted  self.total_minted += 1  actual += 1  self.tokens[tid].owner = addr_1   self.tokens[tid].minted_epoch = epoch  self.balances[addr_1]  += 1  emit Minted(addr_1,  tid, 3, 0)  emit Airdropped(addr_1,  tid)  emit Transferred(tid, 0, addr_1)  }
    if count > 2  && is_address(addr_2)  && addr_2  != 0 { let tid = self.total_minted  self.total_minted += 1  actual += 1  self.tokens[tid].owner = addr_2   self.tokens[tid].minted_epoch = epoch  self.balances[addr_2]  += 1  emit Minted(addr_2,  tid, 3, 0)  emit Airdropped(addr_2,  tid)  emit Transferred(tid, 0, addr_2)  }
    if count > 3  && is_address(addr_3)  && addr_3  != 0 { let tid = self.total_minted  self.total_minted += 1  actual += 1  self.tokens[tid].owner = addr_3   self.tokens[tid].minted_epoch = epoch  self.balances[addr_3]  += 1  emit Minted(addr_3,  tid, 3, 0)  emit Airdropped(addr_3,  tid)  emit Transferred(tid, 0, addr_3)  }
    if count > 4  && is_address(addr_4)  && addr_4  != 0 { let tid = self.total_minted  self.total_minted += 1  actual += 1  self.tokens[tid].owner = addr_4   self.tokens[tid].minted_epoch = epoch  self.balances[addr_4]  += 1  emit Minted(addr_4,  tid, 3, 0)  emit Airdropped(addr_4,  tid)  emit Transferred(tid, 0, addr_4)  }
    if count > 5  && is_address(addr_5)  && addr_5  != 0 { let tid = self.total_minted  self.total_minted += 1  actual += 1  self.tokens[tid].owner = addr_5   self.tokens[tid].minted_epoch = epoch  self.balances[addr_5]  += 1  emit Minted(addr_5,  tid, 3, 0)  emit Airdropped(addr_5,  tid)  emit Transferred(tid, 0, addr_5)  }
    if count > 6  && is_address(addr_6)  && addr_6  != 0 { let tid = self.total_minted  self.total_minted += 1  actual += 1  self.tokens[tid].owner = addr_6   self.tokens[tid].minted_epoch = epoch  self.balances[addr_6]  += 1  emit Minted(addr_6,  tid, 3, 0)  emit Airdropped(addr_6,  tid)  emit Transferred(tid, 0, addr_6)  }
    if count > 7  && is_address(addr_7)  && addr_7  != 0 { let tid = self.total_minted  self.total_minted += 1  actual += 1  self.tokens[tid].owner = addr_7   self.tokens[tid].minted_epoch = epoch  self.balances[addr_7]  += 1  emit Minted(addr_7,  tid, 3, 0)  emit Airdropped(addr_7,  tid)  emit Transferred(tid, 0, addr_7)  }
    if count > 8  && is_address(addr_8)  && addr_8  != 0 { let tid = self.total_minted  self.total_minted += 1  actual += 1  self.tokens[tid].owner = addr_8   self.tokens[tid].minted_epoch = epoch  self.balances[addr_8]  += 1  emit Minted(addr_8,  tid, 3, 0)  emit Airdropped(addr_8,  tid)  emit Transferred(tid, 0, addr_8)  }
    if count > 9  && is_address(addr_9)  && addr_9  != 0 { let tid = self.total_minted  self.total_minted += 1  actual += 1  self.tokens[tid].owner = addr_9   self.tokens[tid].minted_epoch = epoch  self.balances[addr_9]  += 1  emit Minted(addr_9,  tid, 3, 0)  emit Airdropped(addr_9,  tid)  emit Transferred(tid, 0, addr_9)  }
    if count > 10 && is_address(addr_10) && addr_10 != 0 { let tid = self.total_minted  self.total_minted += 1  actual += 1  self.tokens[tid].owner = addr_10  self.tokens[tid].minted_epoch = epoch  self.balances[addr_10] += 1  emit Minted(addr_10, tid, 3, 0)  emit Airdropped(addr_10, tid)  emit Transferred(tid, 0, addr_10) }
    if count > 11 && is_address(addr_11) && addr_11 != 0 { let tid = self.total_minted  self.total_minted += 1  actual += 1  self.tokens[tid].owner = addr_11  self.tokens[tid].minted_epoch = epoch  self.balances[addr_11] += 1  emit Minted(addr_11, tid, 3, 0)  emit Airdropped(addr_11, tid)  emit Transferred(tid, 0, addr_11) }
    if count > 12 && is_address(addr_12) && addr_12 != 0 { let tid = self.total_minted  self.total_minted += 1  actual += 1  self.tokens[tid].owner = addr_12  self.tokens[tid].minted_epoch = epoch  self.balances[addr_12] += 1  emit Minted(addr_12, tid, 3, 0)  emit Airdropped(addr_12, tid)  emit Transferred(tid, 0, addr_12) }
    if count > 13 && is_address(addr_13) && addr_13 != 0 { let tid = self.total_minted  self.total_minted += 1  actual += 1  self.tokens[tid].owner = addr_13  self.tokens[tid].minted_epoch = epoch  self.balances[addr_13] += 1  emit Minted(addr_13, tid, 3, 0)  emit Airdropped(addr_13, tid)  emit Transferred(tid, 0, addr_13) }
    if count > 14 && is_address(addr_14) && addr_14 != 0 { let tid = self.total_minted  self.total_minted += 1  actual += 1  self.tokens[tid].owner = addr_14  self.tokens[tid].minted_epoch = epoch  self.balances[addr_14] += 1  emit Minted(addr_14, tid, 3, 0)  emit Airdropped(addr_14, tid)  emit Transferred(tid, 0, addr_14) }
    if count > 15 && is_address(addr_15) && addr_15 != 0 { let tid = self.total_minted  self.total_minted += 1  actual += 1  self.tokens[tid].owner = addr_15  self.tokens[tid].minted_epoch = epoch  self.balances[addr_15] += 1  emit Minted(addr_15, tid, 3, 0)  emit Airdropped(addr_15, tid)  emit Transferred(tid, 0, addr_15) }
    if count > 16 && is_address(addr_16) && addr_16 != 0 { let tid = self.total_minted  self.total_minted += 1  actual += 1  self.tokens[tid].owner = addr_16  self.tokens[tid].minted_epoch = epoch  self.balances[addr_16] += 1  emit Minted(addr_16, tid, 3, 0)  emit Airdropped(addr_16, tid)  emit Transferred(tid, 0, addr_16) }
    if count > 17 && is_address(addr_17) && addr_17 != 0 { let tid = self.total_minted  self.total_minted += 1  actual += 1  self.tokens[tid].owner = addr_17  self.tokens[tid].minted_epoch = epoch  self.balances[addr_17] += 1  emit Minted(addr_17, tid, 3, 0)  emit Airdropped(addr_17, tid)  emit Transferred(tid, 0, addr_17) }
    if count > 18 && is_address(addr_18) && addr_18 != 0 { let tid = self.total_minted  self.total_minted += 1  actual += 1  self.tokens[tid].owner = addr_18  self.tokens[tid].minted_epoch = epoch  self.balances[addr_18] += 1  emit Minted(addr_18, tid, 3, 0)  emit Airdropped(addr_18, tid)  emit Transferred(tid, 0, addr_18) }
    if count > 19 && is_address(addr_19) && addr_19 != 0 { let tid = self.total_minted  self.total_minted += 1  actual += 1  self.tokens[tid].owner = addr_19  self.tokens[tid].minted_epoch = epoch  self.balances[addr_19] += 1  emit Minted(addr_19, tid, 3, 0)  emit Airdropped(addr_19, tid)  emit Transferred(tid, 0, addr_19) }

    self.airdropped_count += actual
    require(actual == count, "xcollection: address mismatch — all addresses up to count must be valid and non-zero")

    return true
  }

  fn transfer(to: address, token_id: int): bool {
    only_initialized()
    require(token_id >= 0 && token_id < self.total_minted, "xcollection: token not found")
    require(is_address(to) && to != 0,                     "xcollection: invalid address")

    let current_owner = self.tokens[token_id].owner
    require(caller == current_owner, "xcollection: not owner")
    require(to != current_owner,     "xcollection: already owner")

    self.tokens[token_id].owner   = to
    self.balances[current_owner] -= 1
    self.balances[to]            += 1
    self.approvals[token_id]      = 0

    emit Transferred(token_id, current_owner, to)
    return true
  }

  fn transfer_from(from: address, to: address, token_id: int): bool {
    only_initialized()
    require(token_id >= 0 && token_id < self.total_minted, "xcollection: token not found")
    require(is_address(to) && to != 0,                     "xcollection: invalid address")

    let current_owner = self.tokens[token_id].owner
    require(from == current_owner, "xcollection: from is not owner")
    require(to != current_owner,   "xcollection: already owner")

    let approved = self.approvals[token_id]
    let is_op    = self.operator_approvals[from][caller]
    require(
      caller == current_owner ||
      caller == approved      ||
      is_op == 1,
      "xcollection: not authorized"
    )

    self.tokens[token_id].owner   = to
    self.balances[from]          -= 1
    self.balances[to]            += 1
    self.approvals[token_id]      = 0

    emit Transferred(token_id, from, to)
    return true
  }

  fn approve(approved: address, token_id: int): bool {
    only_initialized()
    require(token_id >= 0 && token_id < self.total_minted, "xcollection: token not found")
    require(is_address(approved) && approved != 0,         "xcollection: use revoke_approval to clear")

    let current_owner = self.tokens[token_id].owner
    let is_op         = self.operator_approvals[current_owner][caller]
    require(caller == current_owner || is_op == 1,         "xcollection: not authorized")

    self.approvals[token_id] = approved
    emit Approved(token_id, approved)
    return true
  }

  fn revoke_approval(token_id: int): bool {
    only_initialized()
    require(token_id >= 0 && token_id < self.total_minted, "xcollection: token not found")

    let current_owner = self.tokens[token_id].owner
    let is_op         = self.operator_approvals[current_owner][caller]
    require(caller == current_owner || is_op == 1,         "xcollection: not authorized")

    self.approvals[token_id] = 0
    emit ApprovalRevoked(token_id)
    return true
  }

  fn set_operator(operator: address, approved: int): bool {
    only_initialized()
    require(operator != caller,                    "xcollection: cannot self-operate")
    require(is_address(operator) && operator != 0, "xcollection: invalid operator")
    require(approved == 0 || approved == 1,         "xcollection: approved must be 0 or 1")

    self.operator_approvals[caller][operator] = approved
    emit OperatorSet(caller, operator, approved)
    return true
  }

  fn claim_proceeds(): bool {
    only_initialized()
    let amount = self.pending_proceeds[caller]
    require(amount > 0, "xcollection: nothing to claim")

    self.pending_proceeds[caller] = 0
    self.reserve                 -= amount
    transfer(caller, amount)

    emit ProceedsClaimed(caller, amount)
    return true
  }

  fn reveal(uri: string): bool {
    only_initialized()
    only_owner_or_admin()
    self.base_uri = uri
    self.revealed = 1
    emit Revealed(uri)
    return true
  }

  fn update_base_uri(uri: string): bool {
    only_initialized()
    only_owner_or_admin()
    self.base_uri = uri
    emit BaseUriUpdated(uri)
    return true
  }

  fn set_unrevealed_uri(uri: string): bool {
    only_initialized()
    only_owner_or_admin()
    self.unrevealed_uri = uri
    return true
  }

  fn update_phase_prices(gtd: int, fcfs: int, pub_price: int): bool {
    only_initialized()
    only_owner_or_admin()
    require(gtd >= 0 && fcfs >= 0 && pub_price >= 0, "xcollection: invalid price")
    self.gtd_price    = gtd
    self.fcfs_price   = fcfs
    self.public_price = pub_price
    return true
  }

  fn update_phase_caps(gtd_cap: int, fcfs_cap: int, pub_cap: int): bool {
    only_initialized()
    only_owner_or_admin()
    require(gtd_cap >= 0 && fcfs_cap >= 0 && pub_cap >= 0, "xcollection: invalid cap")
    require(gtd_cap >= self.gtd_minted,    "xcollection: cannot lower GTD cap below minted amount")
    require(fcfs_cap >= self.fcfs_minted,  "xcollection: cannot lower FCFS cap below minted amount")
    require(pub_cap >= self.public_minted, "xcollection: cannot lower public cap below minted amount")
    let active_injection = self.injection_cap
    require(
      gtd_cap + active_injection + fcfs_cap + pub_cap + self.airdropped_count <= self.max_supply,
      "xcollection: caps plus airdrops exceed max supply"
    )
    self.gtd_cap    = gtd_cap
    self.fcfs_cap   = fcfs_cap
    self.public_cap = pub_cap
    return true
  }

  fn update_phase_wallet_caps(gtd_wallet: int, fcfs_wallet: int, pub_wallet: int): bool {
    only_initialized()
    only_owner_or_admin()
    require(gtd_wallet > 0 && fcfs_wallet > 0 && pub_wallet > 0, "xcollection: wallet caps must be > 0")
    self.gtd_wallet_cap    = gtd_wallet
    self.fcfs_wallet_cap   = fcfs_wallet
    self.public_wallet_cap = pub_wallet
    return true
  }

  fn update_phase_epochs(
    gtd_start:  int,
    gtd_end:    int,
    fcfs_start: int,
    fcfs_end:   int,
    pub_start:  int,
    pub_end:    int
  ): bool {
    only_initialized()
    only_owner_or_admin()

    let gtd_duration  = gtd_end - gtd_start
    let fcfs_duration = fcfs_end - fcfs_start
    let pub_duration  = pub_end - pub_start

    require(gtd_duration >= MIN_PHASE_EPOCHS, "xcollection: GTD phase too short (min 1 hour)")
    require(gtd_duration <= MAX_GTD_EPOCHS,   "xcollection: GTD phase too long (max 30 days)")
    require(pub_duration >= MIN_PHASE_EPOCHS, "xcollection: public phase too short (min 1 hour)")
    require(fcfs_end >= fcfs_start,           "xcollection: invalid FCFS range")
    require(fcfs_start >= gtd_end,            "xcollection: FCFS must start after GTD")
    require(pub_start >= fcfs_end,            "xcollection: public must start after FCFS")

    if fcfs_start < fcfs_end {
      require(
        fcfs_duration >= MIN_PHASE_EPOCHS && fcfs_duration <= MAX_FCFS_EPOCHS,
        "xcollection: FCFS phase duration invalid"
      )
    }

    self.gtd_start_epoch    = gtd_start
    self.gtd_end_epoch      = gtd_end
    self.fcfs_start_epoch   = fcfs_start
    self.fcfs_end_epoch     = fcfs_end
    self.public_start_epoch = pub_start
    self.public_end_epoch   = pub_end
    return true
  }

  fn set_phase_override(phase_num: int): bool {
    only_initialized()
    only_owner_or_admin()
    require(phase_num >= 0 && phase_num <= 3, "xcollection: invalid phase")
    self.phase_override_active = 1
    self.phase_override_value  = phase_num
    emit PhaseOverrideSet(phase_num)
    return true
  }

  fn clear_phase_override(): bool {
    only_initialized()
    only_owner_or_admin()
    self.phase_override_active = 0
    self.phase_override_value  = 3
    emit PhaseOverrideCleared()
    return true
  }

  fn pause(): bool {
    only_initialized()
    only_owner_or_admin()
    self.paused = 1
    return true
  }

  fn unpause(): bool {
    only_initialized()
    only_owner_or_admin()
    self.paused = 0
    return true
  }

  fn set_max_per_wallet(max_val: int): bool {
    only_initialized()
    only_owner_or_admin()
    require(max_val > 0, "xcollection: invalid cap")
    self.max_per_wallet = max_val
    return true
  }

  fn update_xpectrum_wallet(new_wallet: address): bool {
    only_initialized()
    require(caller == self.admin,                      "xcollection: only admin")
    require(is_address(new_wallet) && new_wallet != 0, "xcollection: invalid address")
    self.xpectrum_wallet = new_wallet
    emit XpectrumWalletUpdated(new_wallet)
    return true
  }

  fn update_admin(new_admin: address): bool {
    only_initialized()
    require(caller == self.admin,                     "xcollection: only admin")
    require(is_address(new_admin) && new_admin != 0,  "xcollection: invalid address")
    self.admin = new_admin
    return true
  }

  fn update_royalty_receiver(new_receiver: address): bool {
    only_initialized()
    require(
      caller == self.royalty_receiver || caller == self.owner,
      "xcollection: not authorized"
    )
    require(is_address(new_receiver) && new_receiver != 0, "xcollection: invalid address")
    self.royalty_receiver = new_receiver
    return true
  }

  fn transfer_ownership(new_owner: address): bool {
    only_initialized()
    require(caller == self.owner,                    "xcollection: not owner")
    require(is_address(new_owner) && new_owner != 0, "xcollection: invalid address")
    let old    = self.owner
    self.owner = new_owner
    emit OwnershipTransferred(old, new_owner)
    return true
  }

  fn update_xlist(new_xlist: address): bool {
    only_initialized()
    require(caller == self.admin,                    "xcollection: only admin")
    require(is_address(new_xlist) && new_xlist != 0, "xcollection: invalid address")
    self.xlist_addr = new_xlist
    return true
  }

  view fn owner_of(token_id: int): address {
    require(token_id >= 0 && token_id < self.total_minted, "xcollection: token not found")
    return self.tokens[token_id].owner
  }

  view fn creator_of(token_id: int): address {
    return self.royalty_receiver
  }

  view fn royalty_of(token_id: int): int {
    require(token_id >= 0 && token_id < self.total_minted, "xcollection: token not found")
    return self.royalty_bps
  }

  view fn token_uri(token_id: int): string {
    require(token_id >= 0 && token_id < self.total_minted, "xcollection: token not found")
    if self.revealed == 1 {
      let uri = self.base_uri
      uri = concat(uri, to_string(token_id))
      uri = concat(uri, ".json")
      return uri
    }
    return self.unrevealed_uri
  }

  view fn collection_of(token_id: int): string {
    return self.name
  }

  view fn balance_of(addr: address): int {
    return self.balances[addr]
  }

  view fn get_approved(token_id: int): address {
    return self.approvals[token_id]
  }

  view fn total_supply(): int {
    return self.total_minted
  }

  view fn collection_name(): string {
    return self.name
  }

  view fn symbol(): string {
    return self.symbol
  }

  view fn is_approved_or_owner(token_id: int, addr: address): int {
    require(addr != 0,                                      "xcollection: zero address")
    require(token_id >= 0 && token_id < self.total_minted, "xcollection: token not found")

    let current_owner = self.tokens[token_id].owner
    if addr == current_owner {
      return 1
    }
    if self.approvals[token_id] == addr {
      return 1
    }
    if self.operator_approvals[current_owner][addr] == 1 {
      return 1
    }
    return 0
  }

  view fn get_minted_by_wallet(addr: address): int {
    return self.minted_per_wallet[addr]
  }

  view fn get_pending_proceeds(addr: address): int {
    return self.pending_proceeds[addr]
  }

  view fn get_reserve(): int {
    return self.reserve
  }

  view fn is_genesis_id_used(genesis_id: int): int {
    return self.used_genesis_ids[genesis_id]
  }

  view fn get_genesis_status(): string {
    let out = to_string(self.genesis_minted_count)
    out = concat(out, "|")
    out = concat(out, to_string(self.injection_cap))
    out = concat(out, "|")
    out = concat(out, to_string(self.genesis_contract))
    out = concat(out, "|")
    out = concat(out, to_string(self.injection_swept))
    return out
  }

  view fn get_token_info(token_id: int): string {
    require(token_id >= 0 && token_id < self.total_minted, "xcollection: token not found")

    let uri = self.unrevealed_uri
    if self.revealed == 1 {
      uri = concat(self.base_uri, to_string(token_id))
      uri = concat(uri, ".json")
    }

    let out = to_string(token_id)
    out = concat(out, "|")
    out = concat(out, to_string(self.tokens[token_id].owner))
    out = concat(out, "|")
    out = concat(out, to_string(self.royalty_receiver))
    out = concat(out, "|")
    out = concat(out, self.name)
    out = concat(out, "|")
    out = concat(out, to_string(self.royalty_bps))
    out = concat(out, "|")
    out = concat(out, to_string(self.tokens[token_id].minted_epoch))
    out = concat(out, "|")
    out = concat(out, uri)
    return out
  }

  view fn get_contract_info(): string {
    let out = self.name
    out = concat(out, "|")
    out = concat(out, self.symbol)
    out = concat(out, "|")
    out = concat(out, to_string(self.total_minted))
    out = concat(out, "|")
    out = concat(out, to_string(self.max_supply))
    out = concat(out, "|")
    out = concat(out, to_string(self.royalty_bps))
    out = concat(out, "|")
    out = concat(out, to_string(self.owner))
    out = concat(out, "|")
    out = concat(out, to_string(self.revealed))
    return out
  }

  view fn get_phase_info(): string {
    let out = to_string(self.gtd_price)
    out = concat(out, "|")
    out = concat(out, to_string(self.gtd_start_epoch))
    out = concat(out, "|")
    out = concat(out, to_string(self.gtd_end_epoch))
    out = concat(out, "|")
    out = concat(out, to_string(self.gtd_cap))
    out = concat(out, "|")
    out = concat(out, to_string(self.gtd_minted))
    out = concat(out, "|")
    out = concat(out, to_string(self.gtd_wallet_cap))
    out = concat(out, "|")
    out = concat(out, to_string(self.fcfs_price))
    out = concat(out, "|")
    out = concat(out, to_string(self.fcfs_start_epoch))
    out = concat(out, "|")
    out = concat(out, to_string(self.fcfs_end_epoch))
    out = concat(out, "|")
    out = concat(out, to_string(self.fcfs_cap))
    out = concat(out, "|")
    out = concat(out, to_string(self.fcfs_minted))
    out = concat(out, "|")
    out = concat(out, to_string(self.fcfs_wallet_cap))
    out = concat(out, "|")
    out = concat(out, to_string(self.public_price))
    out = concat(out, "|")
    out = concat(out, to_string(self.public_start_epoch))
    out = concat(out, "|")
    out = concat(out, to_string(self.public_end_epoch))
    out = concat(out, "|")
    out = concat(out, to_string(self.public_cap))
    out = concat(out, "|")
    out = concat(out, to_string(self.public_minted))
    out = concat(out, "|")
    out = concat(out, to_string(self.public_wallet_cap))
    out = concat(out, "|")
    out = concat(out, to_string(self.injection_cap))
    out = concat(out, "|")
    out = concat(out, to_string(self.genesis_minted_count))
    return out
  }
}