Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions creator-keys/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -950,3 +950,57 @@ impl CreatorKeysContract {
})
}
}

/// Event name for batch buy completion.
pub const BATCH_BUY_COMPLETED_EVENT_NAME: Symbol = symbol_short!("bat_buy");

/// Stable batch buy completed event payload for downstream indexers.
#[derive(Clone, Debug, Eq, PartialEq)]
#[contracttype]
pub struct BatchBuyCompletedEvent {
pub buyer: Address,
pub total_price_paid: i128,
pub order_count: u32,
pub ledger: u32,
}

/// Shared batch buy completed event topics tuple.
pub fn batch_buy_completed_topics(buyer: &Address) -> (Symbol, Address) {
(BATCH_BUY_COMPLETED_EVENT_NAME, buyer.clone())
}

/// Event name for bonding curve migration.
pub const CURVE_MIGRATED_EVENT_NAME: Symbol = symbol_short!("curve_mig");

/// Stable curve migrated event payload for downstream indexers.
#[derive(Clone, Debug, Eq, PartialEq)]
#[contracttype]
pub struct CurveMigratedEvent {
pub admin: Address,
pub new_exponent: u32,
pub key_count: u32,
pub ledger: u32,
}

/// Shared curve migrated event topics tuple.
pub fn curve_migrated_topics(admin: &Address) -> (Symbol, Address) {
(CURVE_MIGRATED_EVENT_NAME, admin.clone())
}

/// Event name for royalty configuration update.
pub const ROYALTY_UPDATED_EVENT_NAME: Symbol = symbol_short!("roy_upd");

/// Stable royalty updated event payload for downstream indexers.
#[derive(Clone, Debug, Eq, PartialEq)]
#[contracttype]
pub struct RoyaltyUpdatedEvent {
pub creator: Address,
pub buy_fee_bps: u32,
pub sell_fee_bps: u32,
pub ledger: u32,
}

/// Shared royalty updated event topics tuple.
pub fn royalty_updated_topics(creator: &Address) -> (Symbol, Address) {
(ROYALTY_UPDATED_EVENT_NAME, creator.clone())
}
80 changes: 80 additions & 0 deletions creator-keys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,14 @@ pub mod constants {
DataKey::ReferralFeeBps
}

pub fn royalty_config(creator: &Address) -> DataKey {
DataKey::RoyaltyConfig(creator.clone())
}

pub fn curve_exponent(creator: &Address) -> DataKey {
DataKey::CurveExponent(creator.clone())
}

/// Absolute live-until ledger the contract last set for `creator`'s
/// profile key, used to decide whether to emit the TTL-extension event.
pub fn creator_ttl_live_until(creator: &Address) -> DataKey {
Expand Down Expand Up @@ -647,6 +655,12 @@ pub const DEFAULT_REFERRAL_FEE_BPS: u32 = 2000;
/// Maximum number of discount tiers allowed.
pub const MAX_DISCOUNT_TIERS: u32 = 5;

/// Maximum number of entries in a single batch buy call.
pub const MAX_BATCH_BUY_SIZE: usize = 5;

/// Maximum royalty fee basis points (5%).
pub const MAX_ROYALTY_BPS: u32 = 500;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[contracttype]
pub enum CurvePreset {
Expand Down Expand Up @@ -845,6 +859,23 @@ pub struct WhitelistStatus {
pub remaining_ledgers: u32,
}

/// Creator royalty configuration for buy and sell fees.
#[derive(Clone, Debug, Eq, PartialEq)]
#[contracttype]
pub struct RoyaltyConfig {
pub buy_fee_bps: u32,
pub sell_fee_bps: u32,
}

/// Result of a single order in a batch buy.
#[derive(Clone, Debug, Eq, PartialEq)]
#[contracttype]
pub struct BatchBuyOrderResult {
pub creator: Address,
pub quantity: u32,
pub price_paid: i128,
}

#[derive(Clone, Debug, PartialEq)]
#[contracttype]
pub struct CreatorProfile {
Expand Down Expand Up @@ -1420,6 +1451,14 @@ fn accrue_sell_trade_fees(env: &Env, creator: &Address, price: i128) -> Result<(
credit_protocol_fee_recipient_balance(env, protocol_fee)?;
}

if let Some(royalty) = read_royalty_config(env, creator) {
let royalty_amount = fee::apply_percentage_fee(price, royalty.sell_fee_bps)
.ok_or(ContractError::Overflow)?;
if royalty_amount > 0 {
credit_creator_fee_recipient_balance(env, creator, royalty_amount)?;
}
}

Ok(())
}

Expand Down Expand Up @@ -1486,12 +1525,35 @@ fn read_curve_slope(env: &Env) -> i128 {
.unwrap_or(0)
}

fn read_royalty_config(env: &Env, creator: &Address) -> Option<RoyaltyConfig> {
env.storage()
.persistent()
.get(&constants::storage::royalty_config(creator))
}

fn read_curve_exponent(env: &Env, creator: &Address) -> Option<u32> {
env.storage()
.persistent()
.get(&constants::storage::curve_exponent(creator))
}

fn compute_bonding_curve_price(
env: &Env,
creator: &Address,
base_price: i128,
supply: u32,
) -> Result<i128, ContractError> {
if let Some(exponent) = read_curve_exponent(env, creator) {
let slope = read_curve_slope(env);
let supply_exp = checked_pow_i128(supply as i128, exponent)?;
let supply_component = slope
.checked_mul(supply_exp)
.ok_or(ContractError::Overflow)?;
return base_price
.checked_add(supply_component)
.ok_or(ContractError::Overflow);
}

let preset = env
.storage()
.persistent()
Expand Down Expand Up @@ -1524,6 +1586,16 @@ fn compute_bonding_curve_price(
}
}

fn checked_pow_i128(base: i128, exp: u32) -> Result<i128, ContractError> {
let mut result: i128 = 1;
let mut _exp = exp;
while _exp > 0 {
result = result.checked_mul(base).ok_or(ContractError::Overflow)?;
_exp -= 1;
}
Ok(result)
}

fn zero_quote_response() -> QuoteResponse {
QuoteResponse {
price: 0,
Expand Down Expand Up @@ -2110,6 +2182,14 @@ impl CreatorKeysContract {
}
}

if let Some(royalty) = read_royalty_config(&env, &creator) {
let royalty_amount = fee::apply_percentage_fee(price, royalty.buy_fee_bps)
.ok_or(ContractError::Overflow)?;
if royalty_amount > 0 {
credit_creator_fee_recipient_balance(&env, &creator, royalty_amount)?;
}
}

let buy_event_data = events::KeysBoughtEvent {
buyer: buyer.clone(),
creator_id: creator.clone(),
Expand Down
Loading
Loading