diff --git a/creator-keys/src/lib.rs b/creator-keys/src/lib.rs
index 0f00497..8a36c0c 100644
--- a/creator-keys/src/lib.rs
+++ b/creator-keys/src/lib.rs
@@ -74,9 +74,11 @@ pub enum ContractError {
InsufficientSupply = 25,
SelfTransfer = 26,
ZeroTransferAmount = 27,
- InsufficientTreasuryBalance = 28,
- BatchClaimExceedsLimit = 29,
- InvalidCoCreatorShare = 30,
+ WhitelistOnly = 28,
+ WhitelistTooLarge = 29,
+ InsufficientTreasuryBalance = 30,
+ BatchClaimExceedsLimit = 31,
+ InvalidCoCreatorShare = 32,
}
pub mod fee {
@@ -313,6 +315,10 @@ pub mod constants {
DataKey::CoCreatorFeeBalance(creator.clone(), co_creator.clone())
}
+ pub fn whitelist(creator: &Address) -> DataKey {
+ DataKey::Whitelist(creator.clone())
+ }
+
pub fn creator(creator: &Address) -> DataKey {
creator_key(creator)
}
@@ -482,6 +488,9 @@ pub const CREATOR_TTL_LEDGERS: u32 = 6311520; // ~2 years at 5s per ledger
pub const HANDLE_LEN_MIN: u32 = 3;
pub const HANDLE_LEN_MAX: u32 = 32;
+/// Maximum number of addresses accepted in a creator whitelist.
+pub const MAX_WHITELIST_SIZE: u32 = 500;
+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[contracttype]
pub enum CurvePreset {
@@ -518,6 +527,7 @@ pub enum DataKey {
TreasuryBalance,
CoCreator(Address),
CoCreatorFeeBalance(Address, Address),
+ Whitelist(Address),
}
/// Time-locked key allocation for creator self-vesting.
@@ -543,6 +553,21 @@ pub struct CoCreatorConfig {
pub share_bps: u32,
}
+#[derive(Clone, Debug, PartialEq)]
+#[contracttype]
+pub struct WhitelistConfig {
+ pub addresses: soroban_sdk::Vec
,
+ pub window_ledgers: u32,
+}
+
+#[derive(Clone, Debug, PartialEq)]
+#[contracttype]
+pub struct WhitelistStatus {
+ pub active: bool,
+ pub expires_at_ledger: u32,
+ pub remaining_ledgers: u32,
+}
+
#[derive(Clone, Debug, PartialEq)]
#[contracttype]
pub struct CreatorProfile {
@@ -1159,6 +1184,55 @@ fn extend_creator_ttl(env: &Env, creator: &Address) {
}
}
+fn whitelist_status_for_profile(
+ env: &Env,
+ creator: &Address,
+ registered_at: u32,
+) -> WhitelistStatus {
+ let Some(config) = env
+ .storage()
+ .persistent()
+ .get::(&constants::storage::whitelist(creator))
+ else {
+ return WhitelistStatus {
+ active: false,
+ expires_at_ledger: 0,
+ remaining_ledgers: 0,
+ };
+ };
+ let expires_at_ledger = registered_at.saturating_add(config.window_ledgers);
+ let current = env.ledger().sequence();
+ let remaining_ledgers = expires_at_ledger.saturating_sub(current);
+ WhitelistStatus {
+ active: remaining_ledgers > 0,
+ expires_at_ledger,
+ remaining_ledgers,
+ }
+}
+
+fn enforce_whitelist_window(
+ env: &Env,
+ profile: &CreatorProfile,
+ creator: &Address,
+ buyer: &Address,
+) -> Result<(), ContractError> {
+ let status = whitelist_status_for_profile(env, creator, profile.registered_at);
+ if !status.active {
+ return Ok(());
+ }
+ let config: WhitelistConfig = env
+ .storage()
+ .persistent()
+ .get(&constants::storage::whitelist(creator))
+ .ok_or(ContractError::WhitelistOnly)?;
+ for address in config.addresses.iter() {
+ if address == *buyer {
+ return Ok(());
+ }
+ }
+ Err(ContractError::WhitelistOnly)
+}
+
#[contract]
pub struct CreatorKeysContract;
@@ -1189,6 +1263,7 @@ impl CreatorKeysContract {
max_supply: Option,
curve_preset: Option,
co_creator: Option,
+ whitelist: Option,
) -> Result<(), ContractError> {
creator.require_auth();
assert_not_paused(&env)?;
@@ -1197,6 +1272,11 @@ impl CreatorKeysContract {
if let Some(config) = co_creator.as_ref() {
validate_co_creator_config(&env, config)?;
}
+ if let Some(config) = whitelist.as_ref() {
+ if config.addresses.len() > MAX_WHITELIST_SIZE {
+ return Err(ContractError::WhitelistTooLarge);
+ }
+ }
let key = constants::storage::creator(&creator);
// Creator profile storage is a single source of truth keyed by creator address.
@@ -1262,6 +1342,12 @@ impl CreatorKeysContract {
.set(&constants::storage::co_creator(&creator), &config);
}
+ if let Some(config) = whitelist {
+ env.storage()
+ .persistent()
+ .set(&constants::storage::whitelist(&creator), &config);
+ }
+
let profile = CreatorProfile {
creator: creator.clone(),
handle,
@@ -1293,6 +1379,12 @@ impl CreatorKeysContract {
.persistent()
.extend_ttl(&co_creator_key, current_ledger, extend_to);
}
+ let whitelist_key = constants::storage::whitelist(&creator);
+ if env.storage().persistent().has(&whitelist_key) {
+ env.storage()
+ .persistent()
+ .extend_ttl(&whitelist_key, current_ledger, extend_to);
+ }
env.events().publish(
events::register_event_topics(&profile.creator),
@@ -1330,6 +1422,7 @@ impl CreatorKeysContract {
.ok_or(ContractError::KeyPriceNotSet)?;
let mut profile: CreatorProfile = read_registered_creator_profile(&env, &creator)?;
+ enforce_whitelist_window(&env, &profile, &creator, &buyer)?;
let price = compute_bonding_curve_price(&env, &creator, base_price, profile.supply)?;
assert_buy_price_slippage(price, max_price)?;
@@ -1743,6 +1836,18 @@ impl CreatorKeysContract {
read_key_balance(&env, &creator)
}
+ /// Read-only view: returns whitelist window state for a creator.
+ pub fn get_whitelist_status(env: Env, creator: Address) -> WhitelistStatus {
+ let Some(profile) = read_creator_profile(&env, &creator) else {
+ return WhitelistStatus {
+ active: false,
+ expires_at_ledger: 0,
+ remaining_ledgers: 0,
+ };
+ };
+ whitelist_status_for_profile(&env, &creator, profile.registered_at)
+ }
+
/// Read-only view: returns the current supply for a registered creator.
///
/// Fails with [`ContractError::NotRegistered`] if the creator does not exist.
diff --git a/creator-keys/src/test.rs b/creator-keys/src/test.rs
index 8811800..84bd6ac 100644
--- a/creator-keys/src/test.rs
+++ b/creator-keys/src/test.rs
@@ -19,7 +19,8 @@ fn test_register_creator_with_locked_allocation() {
claimed: false,
};
- client.register_creator(&creator, &handle, &Some(locked), &None, &None, &None);
+ client.register_creator(&creator, &handle, &Some(locked), &None, &None, &None,
+ &None);
let stored = client.get_locked_allocation(&creator).unwrap();
assert_eq!(stored.amount, 100);
@@ -47,7 +48,8 @@ fn test_register_creator_locked_allocation_reverts_past_ledger() {
claimed: false,
};
- let result = client.try_register_creator(&creator, &handle, &Some(locked), &None, &None, &None);
+ let result = client.try_register_creator(&creator, &handle, &Some(locked), &None, &None, &None,
+ &None);
assert_eq!(result, Err(Ok(ContractError::AllocationLocked)));
}
@@ -68,7 +70,8 @@ fn test_claim_locked_allocation_success() {
claimed: false,
};
- client.register_creator(&creator, &handle, &Some(locked), &None, &None, &None);
+ client.register_creator(&creator, &handle, &Some(locked), &None, &None, &None,
+ &None);
// Advance ledger past unlock
ledger_info.sequence_number = 250;
@@ -100,7 +103,8 @@ fn test_claim_locked_allocation_reverts_early() {
claimed: false,
};
- client.register_creator(&creator, &handle, &Some(locked), &None, &None, &None);
+ client.register_creator(&creator, &handle, &Some(locked), &None, &None, &None,
+ &None);
// Try to claim before unlock
let result = client.try_claim_locked_allocation(&creator);
@@ -124,7 +128,8 @@ fn test_claim_locked_allocation_reverts_double_claim() {
claimed: false,
};
- client.register_creator(&creator, &handle, &Some(locked), &None, &None, &None);
+ client.register_creator(&creator, &handle, &Some(locked), &None, &None, &None,
+ &None);
// Advance ledger past unlock
ledger_info.sequence_number = 250;
@@ -162,7 +167,8 @@ fn test_get_locked_allocation_returns_allocation_when_set() {
claimed: false,
};
- client.register_creator(&creator, &handle, &Some(locked), &None, &None, &None);
+ client.register_creator(&creator, &handle, &Some(locked), &None, &None, &None,
+ &None);
let result = client.get_locked_allocation(&creator).unwrap();
assert_eq!(result.amount, 100);
@@ -184,7 +190,8 @@ fn test_transfer_keys_basic() {
let recipient = Address::generate(&env);
client.set_key_price(&admin, &100i128);
- client.register_creator(&creator, &String::from_str(&env, "alice"), &None, &None, &None, &None);
+ client.register_creator(&creator, &String::from_str(&env, "alice"), &None, &None, &None, &None,
+ &None);
client.buy_key(&creator, &sender, &100i128, &None);
client.buy_key(&creator, &sender, &100i128, &None);
client.buy_key(&creator, &sender, &100i128, &None);
@@ -208,7 +215,8 @@ fn test_transfer_keys_sender_zeroed_out() {
let recipient = Address::generate(&env);
client.set_key_price(&admin, &100i128);
- client.register_creator(&creator, &String::from_str(&env, "alice"), &None, &None, &None, &None);
+ client.register_creator(&creator, &String::from_str(&env, "alice"), &None, &None, &None, &None,
+ &None);
client.buy_key(&creator, &sender, &100i128, &None);
client.transfer_keys(&creator, &sender, &recipient, &1);
@@ -230,7 +238,8 @@ fn test_transfer_keys_new_recipient() {
let recipient = Address::generate(&env);
client.set_key_price(&admin, &100i128);
- client.register_creator(&creator, &String::from_str(&env, "alice"), &None, &None, &None, &None);
+ client.register_creator(&creator, &String::from_str(&env, "alice"), &None, &None, &None, &None,
+ &None);
client.buy_key(&creator, &sender, &100i128, &None);
let supply_before = client.get_total_key_supply(&creator);
@@ -252,7 +261,8 @@ fn test_transfer_keys_self_transfer_reverts() {
let sender = Address::generate(&env);
client.set_key_price(&admin, &100i128);
- client.register_creator(&creator, &String::from_str(&env, "alice"), &None, &None, &None, &None);
+ client.register_creator(&creator, &String::from_str(&env, "alice"), &None, &None, &None, &None,
+ &None);
client.buy_key(&creator, &sender, &100i128, &None);
let result = client.try_transfer_keys(&creator, &sender, &sender, &1);
@@ -271,7 +281,8 @@ fn test_transfer_keys_zero_amount_reverts() {
let recipient = Address::generate(&env);
client.set_key_price(&admin, &100i128);
- client.register_creator(&creator, &String::from_str(&env, "alice"), &None, &None, &None, &None);
+ client.register_creator(&creator, &String::from_str(&env, "alice"), &None, &None, &None, &None,
+ &None);
client.buy_key(&creator, &sender, &100i128, &None);
let result = client.try_transfer_keys(&creator, &sender, &recipient, &0);
@@ -290,7 +301,8 @@ fn test_transfer_keys_insufficient_balance_reverts() {
let recipient = Address::generate(&env);
client.set_key_price(&admin, &100i128);
- client.register_creator(&creator, &String::from_str(&env, "alice"), &None, &None, &None, &None);
+ client.register_creator(&creator, &String::from_str(&env, "alice"), &None, &None, &None, &None,
+ &None);
client.buy_key(&creator, &sender, &100i128, &None);
let result = client.try_transfer_keys(&creator, &sender, &recipient, &2);
@@ -308,7 +320,8 @@ fn test_register_creator_with_max_supply() {
let creator = Address::generate(&env);
let handle = String::from_str(&env, "alice");
- client.register_creator(&creator, &handle, &None, &Some(1000), &None, &None);
+ client.register_creator(&creator, &handle, &None, &Some(1000), &None, &None,
+ &None);
let cap = client.get_max_supply(&creator).unwrap();
assert_eq!(cap, 1000);
@@ -323,7 +336,8 @@ fn test_register_creator_max_supply_zero_reverts() {
let creator = Address::generate(&env);
let handle = String::from_str(&env, "alice");
- let result = client.try_register_creator(&creator, &handle, &None, &Some(0), &None, &None);
+ let result = client.try_register_creator(&creator, &handle, &None, &Some(0), &None, &None,
+ &None);
assert_eq!(result, Err(Ok(ContractError::NotPositiveAmount)));
}
@@ -338,7 +352,8 @@ fn test_buy_exceeds_max_supply_reverts() {
let admin = Address::generate(&env);
let handle = String::from_str(&env, "alice");
- client.register_creator(&creator, &handle, &None, &Some(5), &None, &None);
+ client.register_creator(&creator, &handle, &None, &Some(5), &None, &None,
+ &None);
client.set_key_price(&admin, &100);
client.set_fee_config(&admin, &9000, &1000);
@@ -363,7 +378,8 @@ fn test_buy_within_max_supply_succeeds() {
let admin = Address::generate(&env);
let handle = String::from_str(&env, "alice");
- client.register_creator(&creator, &handle, &None, &Some(10), &None, &None);
+ client.register_creator(&creator, &handle, &None, &Some(10), &None, &None,
+ &None);
client.set_key_price(&admin, &100);
client.set_fee_config(&admin, &9000, &1000);
@@ -385,7 +401,8 @@ fn test_get_max_supply_returns_none_for_uncapped() {
let creator = Address::generate(&env);
let handle = String::from_str(&env, "alice");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None,
+ &None);
let cap = client.get_max_supply(&creator);
assert_eq!(cap, None);
@@ -484,7 +501,8 @@ fn test_update_creator_fee_recipient_success() {
let new_recipient = Address::generate(&env);
let handle = String::from_str(&env, "alice");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None,
+ &None);
client.update_creator_fee_recipient(&creator, &new_recipient);
let profile = client.get_creator(&creator);
@@ -502,7 +520,8 @@ fn test_update_creator_fee_recipient_unauthorized_reverts() {
let new_recipient = Address::generate(&env);
let handle = String::from_str(&env, "alice");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None,
+ &None);
let result = client.try_update_creator_fee_recipient(&unauthorized, &new_recipient);
// This should fail because unauthorized is not the current fee recipient
@@ -552,7 +571,8 @@ fn test_sell_key_accepts_exact_min_proceeds_boundary() {
client.set_key_price(&admin, &100);
client.set_fee_config(&admin, &9000, &1000);
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None,
+ &None);
client.buy_key(&creator, &seller, &100, &None);
client.buy_key(&creator, &seller, &100, &None);
@@ -580,7 +600,8 @@ fn test_sell_extends_creator_ttl_after_successful_sell() {
client.set_key_price(&admin, &100);
client.set_fee_config(&admin, &9000, &1000);
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None,
+ &None);
client.buy_key(&creator, &seller, &100, &None);
let creator_key = constants::storage::creator(&creator);
@@ -619,7 +640,8 @@ fn test_failed_sell_does_not_extend_creator_ttl() {
let handle = String::from_str(&env, "alice");
client.set_key_price(&admin, &100);
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None,
+ &None);
let creator_key = constants::storage::creator(&creator);
let mut ledger_info = env.ledger().get();
@@ -651,7 +673,8 @@ fn test_register_creator_without_optional_params_succeeds() {
let handle = String::from_str(&env, "alice");
// Registration with None for both optional params should work (backwards compatible)
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None,
+ &None);
let profile = client.get_creator(&creator);
assert_eq!(profile.supply, 0);
@@ -777,7 +800,8 @@ fn test_get_fee_config_persists_across_repeated_reads() {
let creator = Address::generate(&env);
let handle = String::from_str(&env, "alice");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None,
+ &None);
// Repeatedly read the fee config and verify stability
for _ in 0..5 {
@@ -803,7 +827,8 @@ fn test_register_creator() {
let creator = Address::generate(&env);
let handle = String::from_str(&env, "alice");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None,
+ &None);
let profile = client.get_creator(&creator);
assert_eq!(profile.handle, handle);
@@ -823,7 +848,8 @@ fn test_register_creator_persists_registration_metadata() {
let creator = Address::generate(&env);
let handle = String::from_str(&env, "alice");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None,
+ &None);
let profile = client.get_creator(&creator);
assert_eq!(profile.creator, creator);
@@ -843,10 +869,12 @@ fn test_duplicate_registration_fails() {
let creator = Address::generate(&env);
let handle = String::from_str(&env, "alice");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None,
+ &None);
// Second registration should fail with AlreadyRegistered error
- let result = client.try_register_creator(&creator, &handle, &None, &None, &None, &None);
+ let result = client.try_register_creator(&creator, &handle, &None, &None, &None, &None,
+ &None);
assert_eq!(result, Err(Ok(ContractError::AlreadyRegistered)));
assert_no_events(&env);
}
@@ -881,7 +909,8 @@ fn test_buy_key_success() {
let creator = Address::generate(&env);
let handle = String::from_str(&env, "alice");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None,
+ &None);
let buyer = Address::generate(&env);
let supply = client.buy_key(&creator, &buyer, &100, &None);
@@ -904,7 +933,8 @@ fn test_get_creator_holder_count_counts_unique_holders() {
let creator = Address::generate(&env);
let handle = String::from_str(&env, "alice");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None,
+ &None);
let holder_one = Address::generate(&env);
let holder_two = Address::generate(&env);
@@ -945,7 +975,8 @@ fn test_buy_key_insufficient_payment() {
let creator = Address::generate(&env);
let handle = String::from_str(&env, "alice");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None,
+ &None);
let buyer = Address::generate(&env);
let result = client.try_buy_key(&creator, &buyer, &99, &None);
@@ -1016,7 +1047,8 @@ fn test_get_key_balance_returns_zero_for_unregistered_wallet() {
let creator = Address::generate(&env);
let handle = String::from_str(&env, "alice");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None,
+ &None);
let unregistered_wallet = Address::generate(&env);
@@ -1115,7 +1147,8 @@ fn test_get_buy_quote_success() {
let creator = Address::generate(&env);
let handle = String::from_str(&env, "alice");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None,
+ &None);
let quote = client.get_buy_quote(&creator);
assert_eq!(quote.price, 1000);
@@ -1137,7 +1170,8 @@ fn test_get_sell_quote_success() {
let creator = Address::generate(&env);
let handle = String::from_str(&env, "alice");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None,
+ &None);
let buyer = Address::generate(&env);
client.buy_key(&creator, &buyer, &1000, &None);
@@ -1162,7 +1196,8 @@ fn test_get_sell_quote_fails_if_insufficient_balance() {
let creator = Address::generate(&env);
let handle = String::from_str(&env, "alice");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None,
+ &None);
let holder = Address::generate(&env); // Zero balance
let result = client.try_get_sell_quote(&creator, &holder);
@@ -1197,7 +1232,8 @@ fn test_get_quote_fails_if_fee_not_set() {
let creator = Address::generate(&env);
let handle = String::from_str(&env, "alice");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None,
+ &None);
let result = client.try_get_buy_quote(&creator);
assert_eq!(result, Err(Ok(ContractError::FeeConfigNotSet)));
@@ -1227,7 +1263,8 @@ fn test_get_creator_fee_recipient_success() {
let creator = Address::generate(&env);
let handle = String::from_str(&env, "alice");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None,
+ &None);
let recipient = client.get_creator_fee_recipient(&creator);
assert_eq!(recipient, creator);
@@ -1259,7 +1296,8 @@ fn test_quote_overflow_guards() {
let creator = Address::generate(&env);
let handle = String::from_str(&env, "alice");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None,
+ &None);
// Buy quote: price + fees (will overflow)
let result = client.try_get_buy_quote(&creator);
@@ -1336,7 +1374,8 @@ fn test_register_event_field_order_is_stable() {
let creator = Address::generate(&env);
let handle = String::from_str(&env, "alice");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None,
+ &None);
let all_events = env.events().all();
assert_eq!(
@@ -1398,7 +1437,8 @@ fn test_buy_event_topic_and_data_order_is_stable() {
let creator = Address::generate(&env);
let handle = String::from_str(&env, "bob");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None,
+ &None);
let buyer = Address::generate(&env);
client.buy_key(&creator, &buyer, &500, &None);
@@ -1464,7 +1504,8 @@ fn test_register_event_fee_adjacent_fields_are_zero_and_ordered_after_identity_f
let creator = Address::generate(&env);
let handle = String::from_str(&env, "carol");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None,
+ &None);
let all_events = env.events().all();
let (_contract_id, _topics, data): (
diff --git a/creator-keys/src/test_issues.rs b/creator-keys/src/test_issues.rs
index 1188ecd..d37a830 100644
--- a/creator-keys/src/test_issues.rs
+++ b/creator-keys/src/test_issues.rs
@@ -21,10 +21,10 @@ mod issue_tests {
let handle = String::from_str(env, "alice");
match cap {
Some(c) => {
- client.register_creator(&creator, &handle, &None, &Some(c), &None, &None);
+ client.register_creator(&creator, &handle, &None, &Some(c), &None, &None, &None);
}
None => {
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None, &None);
}
}
creator
diff --git a/creator-keys/tests/buy_event_buyer_address.rs b/creator-keys/tests/buy_event_buyer_address.rs
index ceb0535..c5c287e 100644
--- a/creator-keys/tests/buy_event_buyer_address.rs
+++ b/creator-keys/tests/buy_event_buyer_address.rs
@@ -32,6 +32,7 @@ fn test_buy_event_buyer_address_matches_caller() {
&None,
&None,
&None,
+ &None,
);
// Clear any prior events then perform the buy
@@ -90,6 +91,7 @@ fn test_buy_event_buyer_address_field_is_non_zero() {
&None,
&None,
&None,
+ &None,
);
client.buy_key(&creator, &buyer, &KEY_PRICE, &None);
diff --git a/creator-keys/tests/buy_key_event.rs b/creator-keys/tests/buy_key_event.rs
index 7484c64..f12fbc8 100644
--- a/creator-keys/tests/buy_key_event.rs
+++ b/creator-keys/tests/buy_key_event.rs
@@ -23,6 +23,7 @@ fn test_buy_key_event_includes_payment_amount() {
&None,
&None,
&None,
+ &None,
);
let supply = client.buy_key(&creator, &buyer, &150i128, &None);
assert_eq!(supply, 1);
@@ -56,6 +57,7 @@ fn test_buy_key_event_topics_include_creator_and_buyer() {
&None,
&None,
&None,
+ &None,
);
client.buy_key(&creator, &buyer, &200i128, &None);
diff --git a/creator-keys/tests/claim_locked_allocation_ledger_boundary.rs b/creator-keys/tests/claim_locked_allocation_ledger_boundary.rs
index c85c72e..0719d67 100644
--- a/creator-keys/tests/claim_locked_allocation_ledger_boundary.rs
+++ b/creator-keys/tests/claim_locked_allocation_ledger_boundary.rs
@@ -32,6 +32,7 @@ fn test_claim_locked_allocation_reverts_at_every_ledger_before_unlock() {
&None,
&None,
&None,
+ &None,
);
// Immediately after registration — must revert.
@@ -79,6 +80,7 @@ fn test_claim_locked_allocation_succeeds_at_unlock_ledger() {
&None,
&None,
&None,
+ &None,
);
// Advance to exactly unlock_ledger.
diff --git a/creator-keys/tests/claim_locked_allocation_non_creator_reverts.rs b/creator-keys/tests/claim_locked_allocation_non_creator_reverts.rs
index b65f71a..daf8d83 100644
--- a/creator-keys/tests/claim_locked_allocation_non_creator_reverts.rs
+++ b/creator-keys/tests/claim_locked_allocation_non_creator_reverts.rs
@@ -34,6 +34,7 @@ fn setup_creator_with_locked_allocation(
&None,
&None,
&None,
+ &None,
);
creator
}
diff --git a/creator-keys/tests/co_creator_revenue_split.rs b/creator-keys/tests/co_creator_revenue_split.rs
index f27f57c..5d57090 100644
--- a/creator-keys/tests/co_creator_revenue_split.rs
+++ b/creator-keys/tests/co_creator_revenue_split.rs
@@ -58,6 +58,7 @@ fn register_creator_with_co_creator(
&None,
&None,
&Some(config.clone()),
+ &None,
);
(creator, co_creator, config)
@@ -97,6 +98,7 @@ fn test_register_creator_rejects_invalid_co_creator_share_bps() {
&None,
&None,
&Some(config),
+ &None,
);
assert_eq!(result, Err(Ok(ContractError::InvalidCoCreatorShare)));
diff --git a/creator-keys/tests/contract_test_env/mod.rs b/creator-keys/tests/contract_test_env/mod.rs
index bfbfbf5..c6828f3 100644
--- a/creator-keys/tests/contract_test_env/mod.rs
+++ b/creator-keys/tests/contract_test_env/mod.rs
@@ -112,6 +112,7 @@ pub fn register_test_creator(
&None,
&None,
&None,
+ &None,
);
creator
}
@@ -144,6 +145,7 @@ pub fn register_test_creator_with_fee_config(
&None,
&None,
&None,
+ &None,
);
creator
}
diff --git a/creator-keys/tests/creator_detail_read_consistency.rs b/creator-keys/tests/creator_detail_read_consistency.rs
index 48a1ff5..f1f9a97 100644
--- a/creator-keys/tests/creator_detail_read_consistency.rs
+++ b/creator-keys/tests/creator_detail_read_consistency.rs
@@ -23,7 +23,7 @@ fn test_creator_details_identical_across_three_consecutive_reads() {
let handle = String::from_str(&env, "alice");
// Register creator to establish initial state
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None, &None);
// Perform three consecutive reads with NO state changes between them
let read1 = client.get_creator_details(&creator);
@@ -131,7 +131,7 @@ fn test_creator_details_no_storage_writes_during_reads() {
let creator = soroban_sdk::Address::generate(&env);
let handle = String::from_str(&env, "charlie");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None, &None);
// Use a sentinel holder address — no keys held, so balance stays 0.
let sentinel = soroban_sdk::Address::generate(&env);
diff --git a/creator-keys/tests/creator_details_view.rs b/creator-keys/tests/creator_details_view.rs
index e7ba28b..77b40f3 100644
--- a/creator-keys/tests/creator_details_view.rs
+++ b/creator-keys/tests/creator_details_view.rs
@@ -29,7 +29,7 @@ fn test_get_creator_details_registered_returns_correct_data() {
let creator = soroban_sdk::Address::generate(&env);
let handle = String::from_str(&env, "alice");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None, &None);
let details = client.get_creator_details(&creator);
assert!(details.is_registered);
diff --git a/creator-keys/tests/creator_fee_bps.rs b/creator-keys/tests/creator_fee_bps.rs
index 3e5109d..d177fb9 100644
--- a/creator-keys/tests/creator_fee_bps.rs
+++ b/creator-keys/tests/creator_fee_bps.rs
@@ -20,6 +20,7 @@ fn test_get_creator_fee_bps_returns_configured_value() {
&None,
&None,
&None,
+ &None,
);
client.set_fee_config(&admin, &9000u32, &1000u32);
@@ -43,6 +44,7 @@ fn test_get_creator_fee_bps_is_read_only() {
&None,
&None,
&None,
+ &None,
);
client.set_fee_config(&admin, &7500u32, &2500u32);
@@ -69,6 +71,7 @@ fn test_get_creator_fee_bps_tracks_fee_config_updates() {
&None,
&None,
&None,
+ &None,
);
client.set_fee_config(&admin, &9000u32, &1000u32);
diff --git a/creator-keys/tests/creator_fee_bps_invalid_reads.rs b/creator-keys/tests/creator_fee_bps_invalid_reads.rs
index 4e78167..e7f7f30 100644
--- a/creator-keys/tests/creator_fee_bps_invalid_reads.rs
+++ b/creator-keys/tests/creator_fee_bps_invalid_reads.rs
@@ -49,6 +49,7 @@ fn test_get_creator_fee_bps_fails_when_fee_config_not_set() {
&None,
&None,
&None,
+ &None,
);
let result = client.try_get_creator_fee_bps(&creator);
diff --git a/creator-keys/tests/creator_fee_config_view.rs b/creator-keys/tests/creator_fee_config_view.rs
index 711a629..7d3364b 100644
--- a/creator-keys/tests/creator_fee_config_view.rs
+++ b/creator-keys/tests/creator_fee_config_view.rs
@@ -28,7 +28,7 @@ fn test_get_creator_fee_config_registered_no_fee_config() {
let creator = soroban_sdk::Address::generate(&env);
let handle = String::from_str(&env, "test_creator");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None, &None);
let view = client.get_creator_fee_config(&creator);
@@ -50,7 +50,7 @@ fn test_get_creator_fee_config_registered_with_fee_config() {
let creator = soroban_sdk::Address::generate(&env);
let handle = String::from_str(&env, "test_creator");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None, &None);
client.set_fee_config(&admin, &9000u32, &1000u32);
let view = client.get_creator_fee_config(&creator);
@@ -73,7 +73,7 @@ fn test_get_creator_fee_config_is_read_only() {
let creator = soroban_sdk::Address::generate(&env);
let handle = String::from_str(&env, "test_creator");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None, &None);
client.set_fee_config(&admin, &8000u32, &2000u32);
let v1 = client.get_creator_fee_config(&creator);
@@ -97,7 +97,7 @@ fn test_get_creator_fee_config_updates_after_fee_reconfiguration() {
let creator = soroban_sdk::Address::generate(&env);
let handle = String::from_str(&env, "test_creator");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None, &None);
client.set_fee_config(&admin, &9000u32, &1000u32);
let v1 = client.get_creator_fee_config(&creator);
@@ -124,8 +124,8 @@ fn test_get_creator_fee_config_multiple_creators_independent() {
let handle1 = String::from_str(&env, "creator_one");
let handle2 = String::from_str(&env, "creator_two");
- client.register_creator(&creator1, &handle1, &None, &None, &None, &None);
- client.register_creator(&creator2, &handle2, &None, &None, &None, &None);
+ client.register_creator(&creator1, &handle1, &None, &None, &None, &None, &None);
+ client.register_creator(&creator2, &handle2, &None, &None, &None, &None, &None);
client.set_fee_config(&admin, &9000u32, &1000u32);
let view1 = client.get_creator_fee_config(&creator1);
diff --git a/creator-keys/tests/creator_fee_recipient.rs b/creator-keys/tests/creator_fee_recipient.rs
index 9563488..65394b1 100644
--- a/creator-keys/tests/creator_fee_recipient.rs
+++ b/creator-keys/tests/creator_fee_recipient.rs
@@ -20,6 +20,7 @@ fn test_get_creator_fee_recipient_returns_creator_address() {
&None,
&None,
&None,
+ &None,
);
assert_eq!(client.get_creator_fee_recipient(&creator), creator);
@@ -41,6 +42,7 @@ fn test_get_creator_fee_recipient_is_read_only() {
&None,
&None,
&None,
+ &None,
);
let first_read = client.get_creator_fee_recipient(&creator);
diff --git a/creator-keys/tests/creator_registration.rs b/creator-keys/tests/creator_registration.rs
index d16c0b9..73b0b1f 100644
--- a/creator-keys/tests/creator_registration.rs
+++ b/creator-keys/tests/creator_registration.rs
@@ -33,6 +33,7 @@ fn test_is_creator_registered_returns_true_after_registration() {
&None,
&None,
&None,
+ &None,
);
assert!(client.is_creator_registered(&creator));
@@ -54,6 +55,7 @@ fn test_is_creator_registered_is_read_only() {
&None,
&None,
&None,
+ &None,
);
// Multiple calls should return the same result without mutating state
@@ -82,6 +84,7 @@ fn test_is_creator_registered_different_creators_independent() {
&None,
&None,
&None,
+ &None,
);
assert!(client.is_creator_registered(&alice));
@@ -101,9 +104,9 @@ fn test_register_creator_duplicate_fails() {
let creator = Address::generate(&env);
let handle = String::from_str(&env, "alice");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None, &None);
// Second registration with the same address should fail with error
- let result = client.try_register_creator(&creator, &handle, &None, &None, &None, &None);
+ let result = client.try_register_creator(&creator, &handle, &None, &None, &None, &None, &None);
assert_eq!(result, Err(Ok(ContractError::AlreadyRegistered)));
}
@@ -124,6 +127,7 @@ fn test_register_creator_duplicate_different_handle_fails() {
&None,
&None,
&None,
+ &None,
);
// Re-registering with a different handle should still fail
let result = client.try_register_creator(
@@ -133,6 +137,7 @@ fn test_register_creator_duplicate_different_handle_fails() {
&None,
&None,
&None,
+ &None,
);
assert_eq!(result, Err(Ok(ContractError::AlreadyRegistered)));
}
@@ -155,6 +160,7 @@ fn test_register_creator_different_addresses_succeeds() {
&None,
&None,
&None,
+ &None,
);
client.register_creator(
&bob,
@@ -163,6 +169,7 @@ fn test_register_creator_different_addresses_succeeds() {
&None,
&None,
&None,
+ &None,
);
assert!(client.is_creator_registered(&alice));
@@ -186,6 +193,7 @@ fn test_register_creator_accepts_min_handle_length() {
&None,
&None,
&None,
+ &None,
);
assert!(client.is_creator_registered(&creator));
@@ -208,6 +216,7 @@ fn test_register_creator_accepts_max_handle_length() {
&None,
&None,
&None,
+ &None,
);
assert!(client.is_creator_registered(&creator));
@@ -230,6 +239,7 @@ fn test_register_creator_rejects_handle_shorter_than_min() {
&None,
&None,
&None,
+ &None,
);
assert_eq!(result, Err(Ok(ContractError::HandleTooShort)));
}
@@ -251,6 +261,7 @@ fn test_register_creator_rejects_handle_longer_than_max() {
&None,
&None,
&None,
+ &None,
);
assert_eq!(result, Err(Ok(ContractError::HandleTooLong)));
}
@@ -265,7 +276,8 @@ fn test_register_creator_rejects_invalid_characters_in_handle() {
let creator = Address::generate(&env);
let invalid_handle = String::from_str(&env, "Alice-01");
- let result = client.try_register_creator(&creator, &invalid_handle, &None, &None, &None, &None);
+ let result =
+ client.try_register_creator(&creator, &invalid_handle, &None, &None, &None, &None, &None);
assert_eq!(result, Err(Ok(ContractError::InvalidHandleCharacter)));
}
@@ -281,7 +293,7 @@ fn test_register_creator_max_length_handle_succeeds() {
let creator = Address::generate(&env);
let max_handle = String::from_str(&env, &"a".repeat(HANDLE_LEN_MAX as usize));
- client.register_creator(&creator, &max_handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &max_handle, &None, &None, &None, &None, &None);
assert!(client.is_creator_registered(&creator));
}
@@ -296,8 +308,15 @@ fn test_register_creator_handle_one_over_max_rejected() {
let creator = Address::generate(&env);
let over_max_handle = String::from_str(&env, &"a".repeat((HANDLE_LEN_MAX + 1) as usize));
- let result =
- client.try_register_creator(&creator, &over_max_handle, &None, &None, &None, &None);
+ let result = client.try_register_creator(
+ &creator,
+ &over_max_handle,
+ &None,
+ &None,
+ &None,
+ &None,
+ &None,
+ );
assert_eq!(result, Err(Ok(ContractError::HandleTooLong)));
}
diff --git a/creator-keys/tests/creator_supply.rs b/creator-keys/tests/creator_supply.rs
index 6ede717..231dee2 100644
--- a/creator-keys/tests/creator_supply.rs
+++ b/creator-keys/tests/creator_supply.rs
@@ -18,6 +18,7 @@ fn setup(env: &Env) -> (CreatorKeysContractClient<'_>, Address, Address) {
&None,
&None,
&None,
+ &None,
);
(client, admin, creator)
diff --git a/creator-keys/tests/creator_treasury_share.rs b/creator-keys/tests/creator_treasury_share.rs
index 76433c3..5cab019 100644
--- a/creator-keys/tests/creator_treasury_share.rs
+++ b/creator-keys/tests/creator_treasury_share.rs
@@ -20,6 +20,7 @@ fn test_get_creator_treasury_share_returns_configured_value() {
&None,
&None,
&None,
+ &None,
);
client.set_fee_config(&admin, &9000u32, &1000u32);
@@ -43,6 +44,7 @@ fn test_get_creator_treasury_share_is_read_only() {
&None,
&None,
&None,
+ &None,
);
client.set_fee_config(&admin, &8000u32, &2000u32);
diff --git a/creator-keys/tests/creator_treasury_share_invalid_reads.rs b/creator-keys/tests/creator_treasury_share_invalid_reads.rs
index 7379c00..7e78ca4 100644
--- a/creator-keys/tests/creator_treasury_share_invalid_reads.rs
+++ b/creator-keys/tests/creator_treasury_share_invalid_reads.rs
@@ -49,6 +49,7 @@ fn test_get_creator_treasury_share_fails_when_fee_config_not_set() {
&None,
&None,
&None,
+ &None,
);
let result = client.try_get_creator_treasury_share(&creator);
diff --git a/creator-keys/tests/curve_preset_storage.rs b/creator-keys/tests/curve_preset_storage.rs
index 36270d5..f82a77a 100644
--- a/creator-keys/tests/curve_preset_storage.rs
+++ b/creator-keys/tests/curve_preset_storage.rs
@@ -23,6 +23,7 @@ fn test_curve_preset_variants_and_error_handling() {
&None,
&Some(CurvePreset::Linear),
&None,
+ &None,
);
// Register creator with Quadratic preset
@@ -33,6 +34,7 @@ fn test_curve_preset_variants_and_error_handling() {
&None,
&Some(CurvePreset::Quadratic),
&None,
+ &None,
);
// Register creator with Flat preset
@@ -43,6 +45,7 @@ fn test_curve_preset_variants_and_error_handling() {
&None,
&Some(CurvePreset::Flat),
&None,
+ &None,
);
// Assert each returns the correct variant
diff --git a/creator-keys/tests/emergency_pause.rs b/creator-keys/tests/emergency_pause.rs
index 9027894..09de32d 100644
--- a/creator-keys/tests/emergency_pause.rs
+++ b/creator-keys/tests/emergency_pause.rs
@@ -151,6 +151,7 @@ fn test_register_creator_reverts_when_paused() {
&None,
&None,
&None,
+ &None,
);
assert_eq!(result, Err(Ok(ContractError::ProtocolPaused)));
}
@@ -206,6 +207,7 @@ fn test_pause_blocks_registration_not_reads() {
&None,
&None,
&None,
+ &None,
);
assert_eq!(result, Err(Ok(ContractError::ProtocolPaused)));
@@ -217,6 +219,7 @@ fn test_pause_blocks_registration_not_reads() {
// Unpause — creator_b registration must now succeed
client.unpause(&admin);
assert!(!client.get_is_paused());
+
let _ = client
.try_register_creator(
&creator_b,
@@ -225,6 +228,7 @@ fn test_pause_blocks_registration_not_reads() {
&None,
&None,
&None,
+ &None,
)
.unwrap();
}
diff --git a/creator-keys/tests/empty_handle_registration_regression.rs b/creator-keys/tests/empty_handle_registration_regression.rs
index ec5f9ed..ed3f1c8 100644
--- a/creator-keys/tests/empty_handle_registration_regression.rs
+++ b/creator-keys/tests/empty_handle_registration_regression.rs
@@ -22,6 +22,7 @@ fn test_register_creator_rejects_empty_handle() {
&None,
&None,
&None,
+ &None,
);
assert_eq!(result, Err(Ok(ContractError::HandleTooShort)));
diff --git a/creator-keys/tests/events.rs b/creator-keys/tests/events.rs
index 519ae9a..32e6917 100644
--- a/creator-keys/tests/events.rs
+++ b/creator-keys/tests/events.rs
@@ -48,6 +48,7 @@ impl<'a> EventFixture<'a> {
&None,
&None,
&None,
+ &None,
);
}
@@ -235,7 +236,7 @@ fn test_register_creator_event_data_is_indexer_friendly() {
fixture
.client
- .register_creator(&fixture.creator, &handle, &None, &None, &None, &None);
+ .register_creator(&fixture.creator, &handle, &None, &None, &None, &None, &None);
let events = env.events().all();
let last = events.last().unwrap();
diff --git a/creator-keys/tests/flat_curve_symmetry_regression.rs b/creator-keys/tests/flat_curve_symmetry_regression.rs
index 2f3077d..8b7d1d0 100644
--- a/creator-keys/tests/flat_curve_symmetry_regression.rs
+++ b/creator-keys/tests/flat_curve_symmetry_regression.rs
@@ -93,6 +93,7 @@ fn test_flat_curve_symmetry() {
&None,
&Some(CurvePreset::Flat),
&None,
+ &None,
);
let buyer = Address::generate(&env);
diff --git a/creator-keys/tests/get_locked_allocation_none.rs b/creator-keys/tests/get_locked_allocation_none.rs
index 854e3ed..903f68b 100644
--- a/creator-keys/tests/get_locked_allocation_none.rs
+++ b/creator-keys/tests/get_locked_allocation_none.rs
@@ -48,6 +48,7 @@ fn test_get_locked_allocation_returns_some_when_set() {
&None,
&None,
&None,
+ &None,
);
let result = client.get_locked_allocation(&creator);
diff --git a/creator-keys/tests/governance_polls.rs b/creator-keys/tests/governance_polls.rs
index ed65a67..0723ca1 100644
--- a/creator-keys/tests/governance_polls.rs
+++ b/creator-keys/tests/governance_polls.rs
@@ -27,6 +27,7 @@ fn creator_can_create_poll_and_view_empty_result() {
&None,
&None,
&None,
+ &None,
);
let question = String::from_str(&env, "Should we launch premium content?");
@@ -61,6 +62,7 @@ fn holder_vote_uses_liquid_key_balance_as_weight() {
&None,
&None,
&None,
+ &None,
);
let holder = Address::generate(&env);
@@ -99,6 +101,7 @@ fn changing_vote_before_expiry_updates_tally() {
&None,
&None,
&None,
+ &None,
);
let holder = Address::generate(&env);
@@ -140,6 +143,7 @@ fn vote_after_expiry_reverts_with_poll_expired() {
&None,
&None,
&None,
+ &None,
);
let holder = Address::generate(&env);
@@ -178,6 +182,7 @@ fn non_holder_vote_reverts_with_not_a_holder() {
&None,
&None,
&None,
+ &None,
);
let non_holder = Address::generate(&env);
@@ -210,6 +215,7 @@ fn invalid_vote_option_reverts() {
&None,
&None,
&None,
+ &None,
);
let holder = Address::generate(&env);
diff --git a/creator-keys/tests/holder_count_multiple_buyers.rs b/creator-keys/tests/holder_count_multiple_buyers.rs
index 93e90ae..ae5f37f 100644
--- a/creator-keys/tests/holder_count_multiple_buyers.rs
+++ b/creator-keys/tests/holder_count_multiple_buyers.rs
@@ -24,6 +24,7 @@ fn holder_count_tracks_distinct_buyers_and_decrements_on_exit() {
&None,
&None,
&None,
+ &None,
);
let buyer_a = Address::generate(&env);
diff --git a/creator-keys/tests/holder_count_unchanged_after_supply_cap_exceeded.rs b/creator-keys/tests/holder_count_unchanged_after_supply_cap_exceeded.rs
index 6429254..9996120 100644
--- a/creator-keys/tests/holder_count_unchanged_after_supply_cap_exceeded.rs
+++ b/creator-keys/tests/holder_count_unchanged_after_supply_cap_exceeded.rs
@@ -25,6 +25,7 @@ fn test_holder_count_unchanged_after_failed_buy_supply_cap_exceeded() {
&Some(10u32),
&None,
&None,
+ &None,
);
// First wallet buys 10 keys to fill the cap.
diff --git a/creator-keys/tests/holder_key_count_view.rs b/creator-keys/tests/holder_key_count_view.rs
index 46849ab..161bf60 100644
--- a/creator-keys/tests/holder_key_count_view.rs
+++ b/creator-keys/tests/holder_key_count_view.rs
@@ -16,6 +16,7 @@ fn setup_with_creator(env: &Env) -> (CreatorKeysContractClient<'_>, Address, Add
&None,
&None,
&None,
+ &None,
);
(client, creator, admin)
}
@@ -186,6 +187,7 @@ fn test_holder_key_count_view_zero_keys_different_creators() {
&None,
&None,
&None,
+ &None,
);
client.register_creator(
&creator_b,
@@ -194,6 +196,7 @@ fn test_holder_key_count_view_zero_keys_different_creators() {
&None,
&None,
&None,
+ &None,
);
// Holder buys keys only from creator A
diff --git a/creator-keys/tests/key_balance.rs b/creator-keys/tests/key_balance.rs
index bb4ff2e..2a1c20e 100644
--- a/creator-keys/tests/key_balance.rs
+++ b/creator-keys/tests/key_balance.rs
@@ -37,6 +37,7 @@ fn test_key_balance_increments_on_buy() {
&None,
&None,
&None,
+ &None,
);
assert_eq!(client.get_key_balance(&creator, &buyer), 0);
@@ -69,6 +70,7 @@ fn test_key_balance_is_per_buyer() {
&None,
&None,
&None,
+ &None,
);
client.buy_key(&creator, &buyer_a, &100i128, &None);
@@ -100,6 +102,7 @@ fn test_key_balance_is_per_creator() {
&None,
&None,
&None,
+ &None,
);
client.register_creator(
&creator_b,
@@ -108,6 +111,7 @@ fn test_key_balance_is_per_creator() {
&None,
&None,
&None,
+ &None,
);
client.buy_key(&creator_a, &buyer, &100i128, &None);
@@ -137,6 +141,7 @@ fn test_key_balance_zero_for_unregistered_creator_even_when_other_balances_exist
&None,
&None,
&None,
+ &None,
);
client.buy_key(®istered_creator, &buyer, &100i128, &None);
@@ -164,6 +169,7 @@ fn test_key_balance_zero_for_registered_creator_and_unseen_wallet() {
&None,
&None,
&None,
+ &None,
);
client.buy_key(&creator, &buyer_with_balance, &100i128, &None);
@@ -192,6 +198,7 @@ fn test_key_balance_returns_zero_for_uninitialized_holder() {
&None,
&None,
&None,
+ &None,
);
client.buy_key(&creator, &buyer_a, &100i128, &None);
client.buy_key(&creator, &buyer_a, &100i128, &None);
diff --git a/creator-keys/tests/key_name.rs b/creator-keys/tests/key_name.rs
index f0f6b06..f4f2055 100644
--- a/creator-keys/tests/key_name.rs
+++ b/creator-keys/tests/key_name.rs
@@ -13,7 +13,7 @@ fn test_get_key_name_success() {
let creator = soroban_sdk::Address::generate(&env);
let handle = String::from_str(&env, "alice");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None, &None);
let name = client.get_key_name(&creator);
assert_eq!(name, handle);
diff --git a/creator-keys/tests/key_supply.rs b/creator-keys/tests/key_supply.rs
index 225cf98..2a642ab 100644
--- a/creator-keys/tests/key_supply.rs
+++ b/creator-keys/tests/key_supply.rs
@@ -27,6 +27,7 @@ fn test_get_total_key_supply_returns_zero_for_new_creator() {
&None,
&None,
&None,
+ &None,
);
assert_eq!(client.get_total_key_supply(&creator), 0);
@@ -57,6 +58,7 @@ fn test_get_total_key_supply_increments_after_buy() {
&None,
&None,
&None,
+ &None,
);
assert_eq!(client.get_total_key_supply(&creator), 0);
@@ -83,6 +85,7 @@ fn test_get_total_key_supply_is_read_only() {
&None,
&None,
&None,
+ &None,
);
// Call multiple times — should not change state
@@ -110,6 +113,7 @@ fn test_buy_key_zero_payment_fails() {
&None,
&None,
&None,
+ &None,
);
let result = client.try_buy_key(&creator, &buyer, &0_i128, &None);
@@ -131,6 +135,7 @@ fn test_buy_key_negative_payment_fails() {
&None,
&None,
&None,
+ &None,
);
let result = client.try_buy_key(&creator, &buyer, &-50_i128, &None);
@@ -152,6 +157,7 @@ fn test_buy_key_positive_payment_succeeds() {
&None,
&None,
&None,
+ &None,
);
let supply = client.buy_key(&creator, &buyer, &100_i128, &None);
diff --git a/creator-keys/tests/key_symbol.rs b/creator-keys/tests/key_symbol.rs
index b9e3233..b2707e9 100644
--- a/creator-keys/tests/key_symbol.rs
+++ b/creator-keys/tests/key_symbol.rs
@@ -13,7 +13,7 @@ fn test_get_key_symbol_success() {
let creator = soroban_sdk::Address::generate(&env);
let handle = String::from_str(&env, "alice");
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None, &None);
let symbol = client.get_key_symbol(&creator);
assert_eq!(symbol, handle);
diff --git a/creator-keys/tests/keys_transferred_event_fields.rs b/creator-keys/tests/keys_transferred_event_fields.rs
index f177326..9bce28c 100644
--- a/creator-keys/tests/keys_transferred_event_fields.rs
+++ b/creator-keys/tests/keys_transferred_event_fields.rs
@@ -42,6 +42,7 @@ fn setup_transfer(
&None,
&None,
&None,
+ &None,
);
client.buy_key(&creator, &sender, &KEY_PRICE, &None);
client.transfer_keys(&creator, &sender, &recipient, &TRANSFER_AMOUNT);
diff --git a/creator-keys/tests/locked_allocation_bonding_curve_supply.rs b/creator-keys/tests/locked_allocation_bonding_curve_supply.rs
index 27fe019..95e828d 100644
--- a/creator-keys/tests/locked_allocation_bonding_curve_supply.rs
+++ b/creator-keys/tests/locked_allocation_bonding_curve_supply.rs
@@ -52,6 +52,7 @@ fn setup(
&None,
&None,
&None,
+ &None,
);
let creator_no_alloc = Address::generate(env);
@@ -62,6 +63,7 @@ fn setup(
&None,
&None,
&None,
+ &None,
);
(client, creator_with_alloc, creator_no_alloc)
diff --git a/creator-keys/tests/max_supply_zero_rejected.rs b/creator-keys/tests/max_supply_zero_rejected.rs
index fd6805f..0667b78 100644
--- a/creator-keys/tests/max_supply_zero_rejected.rs
+++ b/creator-keys/tests/max_supply_zero_rejected.rs
@@ -29,6 +29,7 @@ fn test_max_supply_zero_reverts_at_registration() {
&Some(0),
&None,
&None,
+ &None,
);
assert_eq!(
result,
@@ -55,6 +56,7 @@ fn test_no_creator_state_written_after_zero_supply_cap_rejection() {
&Some(0),
&None,
&None,
+ &None,
);
// Creator must not appear as registered
@@ -89,6 +91,7 @@ fn test_max_supply_one_accepted_as_minimum() {
&Some(1),
&None,
&None,
+ &None,
);
assert!(
result.is_ok(),
@@ -123,6 +126,7 @@ fn test_max_supply_none_accepted_no_cap() {
&None,
&None,
&None,
+ &None,
);
assert!(result.is_ok(), "max_supply: None must be accepted (no cap)");
assert!(client.is_creator_registered(&creator));
@@ -151,6 +155,7 @@ fn test_max_supply_two_accepted() {
&Some(2),
&None,
&None,
+ &None,
);
assert!(result.is_ok(), "max_supply: Some(2) must be accepted");
assert_eq!(client.get_max_supply(&creator), Some(2));
@@ -170,6 +175,7 @@ fn test_max_supply_large_value_accepted() {
&Some(1_000_000),
&None,
&None,
+ &None,
);
assert!(
result.is_ok(),
diff --git a/creator-keys/tests/protocol_fee_bps_read.rs b/creator-keys/tests/protocol_fee_bps_read.rs
index 7d05a7d..6b8daa0 100644
--- a/creator-keys/tests/protocol_fee_bps_read.rs
+++ b/creator-keys/tests/protocol_fee_bps_read.rs
@@ -86,6 +86,7 @@ fn test_get_protocol_fee_bps_persists_across_operations() {
&None,
&None,
&None,
+ &None,
);
client.set_key_price(&admin, &100);
client.buy_key(&creator, &buyer, &100, &None);
diff --git a/creator-keys/tests/protocol_state_version.rs b/creator-keys/tests/protocol_state_version.rs
index 551d7db..ab4d77f 100644
--- a/creator-keys/tests/protocol_state_version.rs
+++ b/creator-keys/tests/protocol_state_version.rs
@@ -109,6 +109,7 @@ fn test_get_protocol_state_version_increments_only_on_config_updates() {
&None,
&None,
&None,
+ &None,
);
client.buy_key(&creator, &buyer, &100i128, &None);
client.set_treasury_address(&admin, &Address::generate(&env));
diff --git a/creator-keys/tests/registration_event_details.rs b/creator-keys/tests/registration_event_details.rs
index 6eab4e5..ca52362 100644
--- a/creator-keys/tests/registration_event_details.rs
+++ b/creator-keys/tests/registration_event_details.rs
@@ -26,7 +26,7 @@ fn test_register_creator_event_field_values_match_fixtures() {
client.set_fee_config(&admin, &expected_creator_bps, &expected_protocol_bps);
// 3. Trigger registration
- client.register_creator(&creator, &handle, &None, &None, &None, &None);
+ client.register_creator(&creator, &handle, &None, &None, &None, &None, &None);
// 4. Capture the emitted event
let all_events = env.events().all();
@@ -91,6 +91,7 @@ fn test_register_creator_event_fields_update_with_fee_config() {
&None,
&None,
&None,
+ &None,
);
let event1: events::CreatorRegisteredEvent =
@@ -108,6 +109,7 @@ fn test_register_creator_event_fields_update_with_fee_config() {
&None,
&None,
&None,
+ &None,
);
let event2: events::CreatorRegisteredEvent =
diff --git a/creator-keys/tests/sell_event_seller_address.rs b/creator-keys/tests/sell_event_seller_address.rs
index 265851d..ad4711b 100644
--- a/creator-keys/tests/sell_event_seller_address.rs
+++ b/creator-keys/tests/sell_event_seller_address.rs
@@ -33,6 +33,7 @@ fn test_sell_event_seller_address_matches_caller() {
&None,
&None,
&None,
+ &None,
);
// Buyer purchases keys
@@ -94,6 +95,7 @@ fn test_sell_event_seller_address_field_is_non_zero() {
&None,
&None,
&None,
+ &None,
);
client.buy_key(&creator, &seller, &KEY_PRICE, &None);
client.sell_key(&creator, &seller, &None);
diff --git a/creator-keys/tests/test_register_creator.rs b/creator-keys/tests/test_register_creator.rs
index 0c38979..36cb4f6 100644
--- a/creator-keys/tests/test_register_creator.rs
+++ b/creator-keys/tests/test_register_creator.rs
@@ -13,7 +13,7 @@ fn test_register_creator_minimum_handle_length_success() {
let min_handle = "a".repeat(HANDLE_LEN_MIN as usize);
let handle = String::from_str(&env, &min_handle);
- let result = client.try_register_creator(&creator, &handle, &None, &None, &None, &None);
+ let result = client.try_register_creator(&creator, &handle, &None, &None, &None, &None, &None);
// Happy path: the function succeeds
assert_eq!(result, Ok(Ok(())));
@@ -34,7 +34,7 @@ fn test_register_creator_below_minimum_handle_length_fails() {
let short_handle = "a".repeat((HANDLE_LEN_MIN - 1) as usize);
let handle = String::from_str(&env, &short_handle);
- let result = client.try_register_creator(&creator, &handle, &None, &None, &None, &None);
+ let result = client.try_register_creator(&creator, &handle, &None, &None, &None, &None, &None);
// Error case: expected failure
assert_eq!(result, Err(Ok(ContractError::HandleTooShort)));
diff --git a/creator-keys/tests/total_supply_overflow.rs b/creator-keys/tests/total_supply_overflow.rs
index fcc2150..cc8d5fd 100644
--- a/creator-keys/tests/total_supply_overflow.rs
+++ b/creator-keys/tests/total_supply_overflow.rs
@@ -26,6 +26,7 @@ fn buy_at_max_supply_is_rejected_with_overflow_and_no_state_corruption() {
&None,
&None,
&None,
+ &None,
);
// Seed supply at the ceiling to simulate "many sequential buys" cheaply.
diff --git a/creator-keys/tests/transfer_keys.rs b/creator-keys/tests/transfer_keys.rs
index fcae9b8..67c9967 100644
--- a/creator-keys/tests/transfer_keys.rs
+++ b/creator-keys/tests/transfer_keys.rs
@@ -24,6 +24,7 @@ fn test_transfer_keys_sender_balance_decreases() {
&None,
&None,
&None,
+ &None,
);
client.buy_key(&creator, &sender, &100, &None);
client.buy_key(&creator, &sender, &100, &None);
@@ -55,6 +56,7 @@ fn test_transfer_keys_recipient_balance_increases() {
&None,
&None,
&None,
+ &None,
);
client.buy_key(&creator, &sender, &100, &None);
@@ -84,6 +86,7 @@ fn test_transfer_keys_total_supply_unchanged() {
&None,
&None,
&None,
+ &None,
);
client.buy_key(&creator, &sender, &100, &None);
client.buy_key(&creator, &sender, &100, &None);
@@ -112,6 +115,7 @@ fn test_transfer_keys_buy_quote_unchanged_after_transfer() {
&None,
&None,
&None,
+ &None,
);
client.buy_key(&creator, &sender, &100, &None);
client.buy_key(&creator, &sender, &100, &None);
@@ -147,6 +151,7 @@ fn test_transfer_keys_sell_quote_unchanged_after_transfer() {
&None,
&None,
&None,
+ &None,
);
client.buy_key(&creator, &sender, &100, &None);
client.buy_key(&creator, &sender, &100, &None);
@@ -178,6 +183,7 @@ fn test_transfer_keys_holder_count_unaffected_when_sender_zero_but_recipient_new
&None,
&None,
&None,
+ &None,
);
client.buy_key(&creator, &sender, &100, &None);
@@ -214,6 +220,7 @@ fn test_transfer_keys_holder_count_increments_when_recipient_new() {
&None,
&None,
&None,
+ &None,
);
client.buy_key(&creator, &sender_a, &100, &None);
client.buy_key(&creator, &sender_b, &100, &None);
@@ -247,6 +254,7 @@ fn test_transfer_keys_self_transfer_reverts() {
&None,
&None,
&None,
+ &None,
);
client.buy_key(&creator, &sender, &100, &None);
@@ -275,6 +283,7 @@ fn test_transfer_keys_zero_amount_reverts() {
&None,
&None,
&None,
+ &None,
);
client.buy_key(&creator, &sender, &100, &None);
@@ -303,6 +312,7 @@ fn test_transfer_keys_exceeding_balance_reverts() {
&None,
&None,
&None,
+ &None,
);
client.buy_key(&creator, &sender, &100, &None);
@@ -347,6 +357,7 @@ fn test_transfer_keys_self_transfer_sender_balance_unchanged() {
&None,
&None,
&None,
+ &None,
);
client.buy_key(&creator, &sender, &100, &None);
client.buy_key(&creator, &sender, &100, &None);
@@ -382,6 +393,7 @@ fn test_transfer_keys_self_transfer_total_supply_unchanged() {
&None,
&None,
&None,
+ &None,
);
client.buy_key(&creator, &sender, &100, &None);
client.buy_key(&creator, &sender, &100, &None);
@@ -419,6 +431,7 @@ fn test_transfer_keys_preserves_other_holders() {
&None,
&None,
&None,
+ &None,
);
client.buy_key(&creator, &sender, &100, &None);
client.buy_key(&creator, &bystander, &100, &None);
diff --git a/creator-keys/tests/transfer_keys_dividend_preservation.rs b/creator-keys/tests/transfer_keys_dividend_preservation.rs
index 745b521..8627bfe 100644
--- a/creator-keys/tests/transfer_keys_dividend_preservation.rs
+++ b/creator-keys/tests/transfer_keys_dividend_preservation.rs
@@ -27,6 +27,7 @@ fn test_transfer_keys_preserves_claimable_dividends() {
&None,
&Some(CurvePreset::Flat),
&None,
+ &None,
);
let wallet_a = Address::generate(&env);
diff --git a/creator-keys/tests/treasury_withdrawal.rs b/creator-keys/tests/treasury_withdrawal.rs
index da6fcbb..c0e3c7a 100644
--- a/creator-keys/tests/treasury_withdrawal.rs
+++ b/creator-keys/tests/treasury_withdrawal.rs
@@ -38,6 +38,7 @@ fn buy_one_key(env: &Env, client: &CreatorKeysContractClient<'_>) -> i128 {
&None,
&None,
&None,
+ &None,
);
client.buy_key(&creator, &buyer, &100i128, &None);
// 10% of 100 = 10 stroops
@@ -75,6 +76,7 @@ fn get_treasury_balance_accumulates_across_multiple_buys() {
&None,
&None,
&None,
+ &None,
);
let buyer1 = Address::generate(&env);
@@ -206,6 +208,7 @@ fn withdraw_treasury_multiple_partial_withdrawals_track_correctly() {
&None,
&None,
&None,
+ &None,
);
let buyer = Address::generate(&env);
diff --git a/creator-keys/tests/whitelist_window.rs b/creator-keys/tests/whitelist_window.rs
new file mode 100644
index 0000000..74c7141
--- /dev/null
+++ b/creator-keys/tests/whitelist_window.rs
@@ -0,0 +1,155 @@
+mod contract_test_env;
+
+use contract_test_env::{
+ compute_expected_buy_price, register_creator_keys, set_key_price_for_tests, test_env_with_auths,
+};
+use creator_keys::{ContractError, WhitelistConfig, MAX_WHITELIST_SIZE};
+use soroban_sdk::{
+ testutils::{Address as _, Ledger},
+ vec, Address, String, Vec,
+};
+
+fn register_whitelisted_creator(
+ env: &soroban_sdk::Env,
+ client: &creator_keys::CreatorKeysContractClient<'_>,
+ whitelist: Vec,
+ window_ledgers: u32,
+) -> Address {
+ let creator = Address::generate(env);
+ client.register_creator(
+ &creator,
+ &String::from_str(env, "alice"),
+ &None,
+ &None,
+ &None,
+ &None,
+ &Some(WhitelistConfig {
+ addresses: whitelist,
+ window_ledgers,
+ }),
+ );
+ creator
+}
+
+fn advance_ledgers(env: &soroban_sdk::Env, ledgers: u32) {
+ let mut ledger = env.ledger().get();
+ ledger.sequence_number += ledgers;
+ env.ledger().set(ledger);
+}
+
+#[test]
+fn test_non_whitelisted_wallet_cannot_buy_during_window() {
+ let env = test_env_with_auths();
+ let (client, _) = register_creator_keys(&env);
+ set_key_price_for_tests(&env, &client, 100);
+ let approved = Address::generate(&env);
+ let creator = register_whitelisted_creator(&env, &client, vec![&env, approved], 10);
+ let buyer = Address::generate(&env);
+
+ let result = client.try_buy_key(&creator, &buyer, &100, &None);
+
+ assert_eq!(result, Err(Ok(ContractError::WhitelistOnly)));
+ assert_eq!(client.get_total_key_supply(&creator), 0);
+}
+
+#[test]
+fn test_whitelisted_wallet_can_buy_during_window() {
+ let env = test_env_with_auths();
+ let (client, _) = register_creator_keys(&env);
+ set_key_price_for_tests(&env, &client, 100);
+ let buyer = Address::generate(&env);
+ let creator = register_whitelisted_creator(&env, &client, vec![&env, buyer.clone()], 10);
+
+ let supply = client.buy_key(&creator, &buyer, &100, &None);
+
+ assert_eq!(supply, 1);
+ assert_eq!(client.get_key_balance(&creator, &buyer), 1);
+}
+
+#[test]
+fn test_anyone_can_buy_after_window_expires() {
+ let env = test_env_with_auths();
+ let (client, _) = register_creator_keys(&env);
+ set_key_price_for_tests(&env, &client, 100);
+ let approved = Address::generate(&env);
+ let creator = register_whitelisted_creator(&env, &client, vec![&env, approved], 5);
+ advance_ledgers(&env, 5);
+ let public_buyer = Address::generate(&env);
+
+ let supply = client.buy_key(&creator, &public_buyer, &100, &None);
+
+ assert_eq!(supply, 1);
+}
+
+#[test]
+fn test_get_whitelist_status_tracks_active_and_expired_state() {
+ let env = test_env_with_auths();
+ let (client, _) = register_creator_keys(&env);
+ let buyer = Address::generate(&env);
+ let registered_at = env.ledger().sequence();
+ let creator = register_whitelisted_creator(&env, &client, vec![&env, buyer], 7);
+
+ let active = client.get_whitelist_status(&creator);
+ assert!(active.active);
+ assert_eq!(active.expires_at_ledger, registered_at + 7);
+ assert_eq!(active.remaining_ledgers, 7);
+
+ advance_ledgers(&env, 7);
+ let expired = client.get_whitelist_status(&creator);
+ assert!(!expired.active);
+ assert_eq!(expired.expires_at_ledger, registered_at + 7);
+ assert_eq!(expired.remaining_ledgers, 0);
+}
+
+#[test]
+fn test_whitelist_over_500_addresses_reverts_at_registration() {
+ let env = test_env_with_auths();
+ let (client, _) = register_creator_keys(&env);
+ let creator = Address::generate(&env);
+ let mut addresses = Vec::new(&env);
+ for _ in 0..=MAX_WHITELIST_SIZE {
+ addresses.push_back(Address::generate(&env));
+ }
+
+ let result = client.try_register_creator(
+ &creator,
+ &String::from_str(&env, "alice"),
+ &None,
+ &None,
+ &None,
+ &None,
+ &Some(WhitelistConfig {
+ addresses,
+ window_ledgers: 10,
+ }),
+ );
+
+ assert_eq!(result, Err(Ok(ContractError::WhitelistTooLarge)));
+ assert!(!client.is_creator_registered(&creator));
+}
+
+#[test]
+fn test_none_whitelist_allows_public_buy_immediately() {
+ let env = test_env_with_auths();
+ let (client, _) = register_creator_keys(&env);
+ set_key_price_for_tests(&env, &client, 100);
+ let creator = Address::generate(&env);
+ client.register_creator(
+ &creator,
+ &String::from_str(&env, "alice"),
+ &None,
+ &None,
+ &None,
+ &None,
+ &None,
+ );
+ let buyer = Address::generate(&env);
+
+ let quote = compute_expected_buy_price(0, 100);
+ let supply = client.buy_key(&creator, &buyer, "e, &None);
+ let status = client.get_whitelist_status(&creator);
+
+ assert_eq!(supply, 1);
+ assert!(!status.active);
+ assert_eq!(status.remaining_ledgers, 0);
+}