Address
octaNNhs…NNEhgC
octaNNhsFYn9JFbktDg9k7GbpB1BFEKAkcCbxRJgbNNEhgC
State
OCT balance
0OCT
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
History
live · 20s · last 0Source · ABI · Bytecode✓ verifiedexpand →
Source · ABI · Bytecode
✓ verified✓ verified
contract Xcollection {
struct TokenInfo {
owner: address
minted_epoch: int
}
event Initialized(owner: address, name: string, max_supply: int)
event Minted(minter: address, token_id: int, phase: int, price: 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 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
state {
owner: address // the collection creator
admin: address // the Xpectrum operator (can assist but not withdraw)
xpectrum_wallet: address // where platform fees land
name: string
symbol: string
base_uri: string // set on reveal
unrevealed_uri: string // shown before reveal
revealed: int // 0 = hidden, 1 = live
royalty_bps: int
royalty_receiver: address // usually the creator
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 // 3 = closed by default
gtd_price: int
gtd_start_epoch: int
gtd_end_epoch: int
gtd_cap: int
gtd_minted: int
fcfs_price: int
fcfs_start_epoch: int
fcfs_end_epoch: int
fcfs_cap: int
fcfs_minted: int
public_price: int
public_start_epoch: int
public_end_epoch: int
public_cap: int
public_minted: int
xlist_addr: address
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
}
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, // operator wallet / fee destination
creator: address, // becomes the collection owner
xlist: address // the Xlist contract for this drop
): 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(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")
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.initialized = 1
self.owner = creator
emit Initialized(creator, collection_name, collection_max_supply)
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: 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")
require(self.gtd_minted + quantity <= self.gtd_cap, "xcollection: GTD cap reached")
price = self.gtd_price
self.gtd_minted += 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 + quantity <= self.fcfs_cap, "xcollection: FCFS cap reached")
price = self.fcfs_price
self.fcfs_minted += quantity
}
if phase == 2 {
require(self.public_minted + quantity <= self.public_cap, "xcollection: public cap reached")
price = self.public_price
self.public_minted += 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 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: int, fcfs: int, pub_cap: int): bool {
only_initialized()
only_owner_or_admin()
require(gtd >= 0 && fcfs >= 0 && pub_cap >= 0, "xcollection: invalid cap")
require(gtd + fcfs + pub_cap <= self.max_supply, "xcollection: caps exceed max supply")
self.gtd_cap = gtd
self.fcfs_cap = fcfs
self.public_cap = pub_cap
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()
require(gtd_end >= gtd_start, "xcollection: invalid GTD range")
require(fcfs_end >= fcfs_start, "xcollection: invalid FCFS range")
require(pub_end >= pub_start, "xcollection: invalid public range")
require(fcfs_start >= gtd_end, "xcollection: FCFS must start after GTD")
require(pub_start >= fcfs_end, "xcollection: public must start after FCFS")
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 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.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.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))
return out
}
}