From 42c8ffd16dcd5b3c9cdd85f4cfd2fc35b005b4de Mon Sep 17 00:00:00 2001 From: feyishola Date: Sat, 24 Jan 2026 14:38:58 +0100 Subject: [PATCH 01/13] collection factory feature implemented --- .../collection_factory_contract/Cargo.toml | 22 +- .../src/collection.rs | 440 ++++++++++++++++++ .../collection_factory_contract/src/errors.rs | 57 +++ .../collection_factory_contract/src/events.rs | 77 +++ .../src/factory.rs | 282 +++++++++++ .../collection_factory_contract/src/lib.rs | 254 ++++++++++ .../src/storage.rs | 313 +++++++++++++ .../collection_factory_contract/src/test.rs | 202 ++++++++ 8 files changed, 1645 insertions(+), 2 deletions(-) create mode 100644 nftopia-stellar/contracts/collection_factory_contract/src/collection.rs create mode 100644 nftopia-stellar/contracts/collection_factory_contract/src/errors.rs create mode 100644 nftopia-stellar/contracts/collection_factory_contract/src/events.rs create mode 100644 nftopia-stellar/contracts/collection_factory_contract/src/factory.rs create mode 100644 nftopia-stellar/contracts/collection_factory_contract/src/storage.rs diff --git a/nftopia-stellar/contracts/collection_factory_contract/Cargo.toml b/nftopia-stellar/contracts/collection_factory_contract/Cargo.toml index d488cf46..0ecb7740 100644 --- a/nftopia-stellar/contracts/collection_factory_contract/Cargo.toml +++ b/nftopia-stellar/contracts/collection_factory_contract/Cargo.toml @@ -1,6 +1,24 @@ [package] -name = "collection_factory_contract" +name = "collection-factory" version = "0.1.0" -edition = "2024" +description = "NFT Collection Factory for NFTopia on Stellar Soroban" +edition = "2021" +license = "Apache-2.0" + +[lib] +crate-type = ["cdylib"] [dependencies] +soroban-sdk = "24.1.0" +serde = { version = "1.0", features = ["derive"] } + +[dev-dependencies] +soroban-sdk = { version = "24.1.0", features = ["testutils"] } + +[profile.release] +opt-level = "z" # Optimize for size +overflow-checks = true +debug = false +strip = "symbols" +lto = true +codegen-units = 1 \ No newline at end of file diff --git a/nftopia-stellar/contracts/collection_factory_contract/src/collection.rs b/nftopia-stellar/contracts/collection_factory_contract/src/collection.rs new file mode 100644 index 00000000..e7092927 --- /dev/null +++ b/nftopia-stellar/contracts/collection_factory_contract/src/collection.rs @@ -0,0 +1,440 @@ +use soroban_sdk::{contracttype, Address, Env, Map, String, Vec}; + +use crate::{ + errors::Error, + events::Event, + storage::{DataKey, Storage, TokenMetadata, RoyaltyInfo}, +}; + +pub struct Collection; + +impl Collection { + // Mint a new token + pub fn mint( + env: &Env, + collection_id: u64, + to: &Address, + uri: String, + attributes: Option>, + ) -> Result { + // Check if collection is paused + if DataKey::is_collection_paused(env, collection_id) { + return Err(Error::MintingPaused); + } + + // Get collection info + let info = DataKey::get_collection_info(env, collection_id)?; + + // Check max supply + if let Some(max_supply) = info.config.max_supply { + if info.total_tokens >= max_supply { + return Err(Error::MaxSupplyExceeded); + } + } + + // Check if public minting is allowed + if !info.config.is_public_mint { + // Check if address is whitelisted + if !DataKey::is_whitelisted_for_mint(env, collection_id, to) { + return Err(Error::WhitelistRequired); + } + } + + // Get next token ID + let token_id = DataKey::get_next_token_id(env, collection_id); + + // Create token metadata + let metadata = TokenMetadata { + token_id, + uri: uri.clone(), + attributes: attributes.unwrap_or(Map::new(env)), + creator: to.clone(), + created_at: env.ledger().timestamp(), + updated_at: None, + }; + + // Set token owner + DataKey::set_token_owner(env, collection_id, token_id, to); + + // Set token metadata + DataKey::set_token_metadata(env, collection_id, token_id, &metadata); + + // Update balance + DataKey::increment_balance(env, collection_id, to); + + // Increment token ID for next mint + DataKey::increment_token_id(env, collection_id); + + // Update collection total tokens + let mut info = info; + info.total_tokens += 1; + DataKey::set_collection_info(env, collection_id, &info); + + // Publish event + Event::TokenMinted { + collection: info.address, + token_id, + owner: to.clone(), + uri, + } + .publish(env); + + Ok(token_id) + } + + // Batch mint tokens + pub fn batch_mint( + env: &Env, + collection_id: u64, + to: &Address, + uris: Vec, + attributes_list: Option>>, + ) -> Result, Error> { + let count = uris.len(); + let start_token_id = DataKey::get_next_token_id(env, collection_id); + let mut token_ids = Vec::new(env); + + for i in 0..count { + let uri = uris.get(i).unwrap(); + let attributes = attributes_list + .as_ref() + .and_then(|list| list.get(i)); + + let token_id = Self::mint( + env, + collection_id, + to, + uri.clone(), + attributes.cloned(), + )?; + + token_ids.push_back(token_id); + } + + // Publish batch event + Event::BatchMinted { + collection: DataKey::get_collection_info(env, collection_id)?.address, + start_token_id, + count: count as u32, + owner: to.clone(), + } + .publish(env); + + Ok(token_ids) + } + + // Transfer token + pub fn transfer( + env: &Env, + collection_id: u64, + from: &Address, + to: &Address, + token_id: u32, + ) -> Result<(), Error> { + // Check if collection is paused + if DataKey::is_collection_paused(env, collection_id) { + return Err(Error::MintingPaused); + } + + // Check if token exists + let owner = DataKey::get_token_owner(env, collection_id, token_id) + .ok_or(Error::TokenNotFound)?; + + // Check if sender is owner or approved + if &owner != from { + // Check if approved for this specific token + let approved = DataKey::get_approved(env, collection_id, token_id); + if approved.as_ref() != Some(from) { + // Check if approved for all + if !DataKey::get_approved_for_all(env, collection_id, &owner, from) { + return Err(Error::NotTokenOwner); + } + } + } + + // Calculate royalty if applicable + if let Some(royalty_info) = DataKey::get_royalty_info(env, collection_id) { + // In a real implementation, you would handle royalty payment here + // For now, we just emit an event + // Royalty would be enforced at the marketplace level + } + + // Transfer ownership + DataKey::set_token_owner(env, collection_id, token_id, to); + + // Update balances + DataKey::decrement_balance(env, collection_id, from); + DataKey::increment_balance(env, collection_id, to); + + // Clear approval for this token + DataKey::remove_approved(env, collection_id, token_id); + + // Publish event + Event::TokenTransferred { + collection: DataKey::get_collection_info(env, collection_id)?.address, + token_id, + from: from.clone(), + to: to.clone(), + } + .publish(env); + + Ok(()) + } + + // Batch transfer tokens + pub fn batch_transfer( + env: &Env, + collection_id: u64, + from: &Address, + to: &Address, + token_ids: Vec, + ) -> Result<(), Error> { + for token_id in token_ids.iter() { + Self::transfer(env, collection_id, from, to, *token_id)?; + } + + // Publish batch event + Event::BatchTransferred { + collection: DataKey::get_collection_info(env, collection_id)?.address, + token_ids: token_ids.clone(), + from: from.clone(), + to: to.clone(), + } + .publish(env); + + Ok(()) + } + + // Burn token + pub fn burn( + env: &Env, + collection_id: u64, + owner: &Address, + token_id: u32, + ) -> Result<(), Error> { + // Check if token exists and owner matches + let token_owner = DataKey::get_token_owner(env, collection_id, token_id) + .ok_or(Error::TokenNotFound)?; + + if &token_owner != owner { + return Err(Error::NotTokenOwner); + } + + // Remove token data + DataKey::remove_token_owner(env, collection_id, token_id); + DataKey::remove_approved(env, collection_id, token_id); + + // Update balance + DataKey::decrement_balance(env, collection_id, owner); + + // Update collection total tokens + let mut info = DataKey::get_collection_info(env, collection_id)?; + info.total_tokens = info.total_tokens.saturating_sub(1); + DataKey::set_collection_info(env, collection_id, &info); + + // Publish event + Event::TokenBurned { + collection: info.address, + token_id, + owner: owner.clone(), + } + .publish(env); + + Ok(()) + } + + // Set approval for token + pub fn approve( + env: &Env, + collection_id: u64, + owner: &Address, + approved: &Address, + token_id: u32, + ) -> Result<(), Error> { + // Check if token exists and owner matches + let token_owner = DataKey::get_token_owner(env, collection_id, token_id) + .ok_or(Error::TokenNotFound)?; + + if &token_owner != owner { + return Err(Error::NotTokenOwner); + } + + DataKey::set_approved(env, collection_id, token_id, approved); + Ok(()) + } + + // Set approval for all + pub fn set_approval_for_all( + env: &Env, + collection_id: u64, + owner: &Address, + operator: &Address, + approved: bool, + ) -> Result<(), Error> { + DataKey::set_approved_for_all(env, collection_id, owner, operator, approved); + Ok(()) + } + + // Set royalty info (only collection owner can call) + pub fn set_royalty_info( + env: &Env, + collection_id: u64, + caller: &Address, + recipient: Address, + percentage: u32, + ) -> Result<(), Error> { + // Validate percentage (max 25% = 2500 basis points) + if percentage > 2500 { + return Err(Error::InvalidRoyaltyPercentage); + } + + // Check if caller is collection creator + let info = DataKey::get_collection_info(env, collection_id)?; + if &info.creator != caller { + return Err(Error::Unauthorized); + } + + let royalty = RoyaltyInfo { + recipient, + percentage, + }; + + DataKey::set_royalty_info(env, collection_id, &royalty); + + // Publish event + Event::RoyaltyUpdated { + collection: info.address, + recipient: royalty.recipient.clone(), + percentage, + } + .publish(env); + + Ok(()) + } + + // Whitelist address for minting + pub fn set_whitelist( + env: &Env, + collection_id: u64, + caller: &Address, + address: Address, + whitelisted: bool, + ) -> Result<(), Error> { + // Check if caller is collection creator + let info = DataKey::get_collection_info(env, collection_id)?; + if &info.creator != caller { + return Err(Error::Unauthorized); + } + + DataKey::set_whitelisted_for_mint(env, collection_id, &address, whitelisted); + + // Publish event + Event::WhitelistUpdated { + collection: info.address, + address: address.clone(), + added: whitelisted, + } + .publish(env); + + Ok(()) + } + + // Pause/unpause collection + pub fn set_paused( + env: &Env, + collection_id: u64, + caller: &Address, + paused: bool, + ) -> Result<(), Error> { + // Check if collection is pausable + let info = DataKey::get_collection_info(env, collection_id)?; + if !info.config.is_pausable { + return Err(Error::Unauthorized); + } + + // Check if caller is collection creator + if &info.creator != caller { + return Err(Error::Unauthorized); + } + + DataKey::set_collection_paused(env, collection_id, paused); + + // Publish event + Event::CollectionPaused { + collection: info.address, + paused, + } + .publish(env); + + Ok(()) + } + + // Query functions + pub fn balance_of( + env: &Env, + collection_id: u64, + address: &Address, + ) -> Result { + Ok(DataKey::get_balance(env, collection_id, address)) + } + + pub fn owner_of( + env: &Env, + collection_id: u64, + token_id: u32, + ) -> Result { + DataKey::get_token_owner(env, collection_id, token_id) + .ok_or(Error::TokenNotFound) + } + + pub fn get_approved( + env: &Env, + collection_id: u64, + token_id: u32, + ) -> Result, Error> { + Ok(DataKey::get_approved(env, collection_id, token_id)) + } + + pub fn is_approved_for_all( + env: &Env, + collection_id: u64, + owner: &Address, + operator: &Address, + ) -> Result { + Ok(DataKey::get_approved_for_all(env, collection_id, owner, operator)) + } + + pub fn token_uri( + env: &Env, + collection_id: u64, + token_id: u32, + ) -> Result { + let metadata = DataKey::get_token_metadata(env, collection_id, token_id) + .ok_or(Error::TokenNotFound)?; + Ok(metadata.uri) + } + + pub fn token_metadata( + env: &Env, + collection_id: u64, + token_id: u32, + ) -> Result { + DataKey::get_token_metadata(env, collection_id, token_id) + .ok_or(Error::TokenNotFound) + } + + pub fn total_supply( + env: &Env, + collection_id: u64, + ) -> Result { + let info = DataKey::get_collection_info(env, collection_id)?; + Ok(info.total_tokens) + } + + pub fn royalty_info( + env: &Env, + collection_id: u64, + ) -> Result, Error> { + Ok(DataKey::get_royalty_info(env, collection_id)) + } +} \ No newline at end of file diff --git a/nftopia-stellar/contracts/collection_factory_contract/src/errors.rs b/nftopia-stellar/contracts/collection_factory_contract/src/errors.rs new file mode 100644 index 00000000..509614b2 --- /dev/null +++ b/nftopia-stellar/contracts/collection_factory_contract/src/errors.rs @@ -0,0 +1,57 @@ +use soroban_sdk::{contracterror, Address, Env}; + +#[contracterror] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[repr(u32)] +pub enum Error { + // Factory errors (1000-1999) + Unauthorized = 1000, + InvalidConfig = 1001, + CollectionLimitReached = 1002, + InsufficientFee = 1003, + CollectionNotFound = 1004, + CollectionAlreadyExists = 1005, + + // Collection errors (2000-2999) + MaxSupplyExceeded = 2000, + TokenNotFound = 2001, + NotTokenOwner = 2002, + NotApprovedForAll = 2003, + MintingPaused = 2004, + WhitelistRequired = 2005, + InvalidTokenId = 2006, + InvalidRoyaltyPercentage = 2007, + + // General errors (3000-3999) + InvalidInput = 3000, + Overflow = 3001, + Underflow = 3002, + StorageError = 3003, + TransferFailed = 3004, +} + +impl Error { + pub fn to_symbol(&self, env: &Env) -> soroban_sdk::Symbol { + match self { + Error::Unauthorized => Symbol::new(env, "UNAUTHORIZED"), + Error::InvalidConfig => Symbol::new(env, "INVALID_CONFIG"), + Error::CollectionLimitReached => Symbol::new(env, "COLLECTION_LIMIT_REACHED"), + Error::InsufficientFee => Symbol::new(env, "INSUFFICIENT_FEE"), + Error::CollectionNotFound => Symbol::new(env, "COLLECTION_NOT_FOUND"), + Error::CollectionAlreadyExists => Symbol::new(env, "COLLECTION_ALREADY_EXISTS"), + Error::MaxSupplyExceeded => Symbol::new(env, "MAX_SUPPLY_EXCEEDED"), + Error::TokenNotFound => Symbol::new(env, "TOKEN_NOT_FOUND"), + Error::NotTokenOwner => Symbol::new(env, "NOT_TOKEN_OWNER"), + Error::NotApprovedForAll => Symbol::new(env, "NOT_APPROVED_FOR_ALL"), + Error::MintingPaused => Symbol::new(env, "MINTING_PAUSED"), + Error::WhitelistRequired => Symbol::new(env, "WHITELIST_REQUIRED"), + Error::InvalidTokenId => Symbol::new(env, "INVALID_TOKEN_ID"), + Error::InvalidRoyaltyPercentage => Symbol::new(env, "INVALID_ROYALTY_PERCENTAGE"), + Error::InvalidInput => Symbol::new(env, "INVALID_INPUT"), + Error::Overflow => Symbol::new(env, "OVERFLOW"), + Error::Underflow => Symbol::new(env, "UNDERFLOW"), + Error::StorageError => Symbol::new(env, "STORAGE_ERROR"), + Error::TransferFailed => Symbol::new(env, "TRANSFER_FAILED"), + } + } +} \ No newline at end of file diff --git a/nftopia-stellar/contracts/collection_factory_contract/src/events.rs b/nftopia-stellar/contracts/collection_factory_contract/src/events.rs new file mode 100644 index 00000000..7a816422 --- /dev/null +++ b/nftopia-stellar/contracts/collection_factory_contract/src/events.rs @@ -0,0 +1,77 @@ +use soroban_sdk::{contracttype, Address, Env, Symbol}; + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Event { + // Factory events + CollectionCreated { + creator: Address, + collection_id: u64, + collection_address: Address, + name: String, + symbol: String, + }, + FactoryFeeUpdated { + old_fee: i128, + new_fee: i128, + updater: Address, + }, + FeesWithdrawn { + recipient: Address, + amount: i128, + }, + + // Collection events + TokenMinted { + collection: Address, + token_id: u32, + owner: Address, + uri: String, + }, + BatchMinted { + collection: Address, + start_token_id: u32, + count: u32, + owner: Address, + }, + TokenTransferred { + collection: Address, + token_id: u32, + from: Address, + to: Address, + }, + BatchTransferred { + collection: Address, + token_ids: Vec, + from: Address, + to: Address, + }, + TokenBurned { + collection: Address, + token_id: u32, + owner: Address, + }, + RoyaltyUpdated { + collection: Address, + recipient: Address, + percentage: u32, + }, + WhitelistUpdated { + collection: Address, + address: Address, + added: bool, + }, + CollectionPaused { + collection: Address, + paused: bool, + }, +} + +impl Event { + pub fn publish(&self, env: &Env) { + env.events().publish( + (Symbol::new(env, "nftopia_event"), self.clone()), + self.clone(), + ); + } +} \ No newline at end of file diff --git a/nftopia-stellar/contracts/collection_factory_contract/src/factory.rs b/nftopia-stellar/contracts/collection_factory_contract/src/factory.rs new file mode 100644 index 00000000..5238d892 --- /dev/null +++ b/nftopia-stellar/contracts/collection_factory_contract/src/factory.rs @@ -0,0 +1,282 @@ +use soroban_sdk::{contracttype, Address, Env, Val, String, Vec}; + +use crate::{ + errors::Error, + events::Event, + storage::{DataKey, Storage, CollectionConfig, CollectionInfo, FactoryConfig}, +}; + +pub struct Factory; + +impl Factory { + // Initialize factory + pub fn initialize(env: &Env, owner: Address) -> Result<(), Error> { + // Check if already initialized + if DataKey::get_factory_config(env).is_ok() { + return Err(Error::CollectionAlreadyExists); + } + + let config = FactoryConfig { + owner, + factory_fee: 0, // No fee by default + max_collections: None, // No limit by default + total_collections: 0, + accumulated_fees: 0, + is_active: true, + }; + + DataKey::set_factory_config(env, &config); + Ok(()) + } + + // Create new collection + pub fn create_collection( + env: &Env, + caller: &Address, + config: CollectionConfig, + initial_royalty_recipient: Option
, + ) -> Result { + // Validate factory is active + let factory_config = DataKey::get_factory_config(env)?; + if !factory_config.is_active { + return Err(Error::Unauthorized); + } + + // Check collection limit + if let Some(max) = factory_config.max_collections { + if factory_config.total_collections >= max { + return Err(Error::CollectionLimitReached); + } + } + + // Validate config + Self::validate_collection_config(env, &config)?; + + // Check and collect fee if any + if factory_config.factory_fee > 0 { + // In Soroban, you would transfer tokens here + // For simplicity, we'll assume the fee is paid via a separate mechanism + // You would typically use the token contract to transfer from caller to factory + } + + // Generate collection ID + let collection_id = factory_config.total_collections as u64 + 1; + + // In a real implementation, you would deploy a new contract here + // For this example, we'll simulate it by storing collection info + let collection_address = Self::deploy_collection_contract(env, collection_id, caller, &config)?; + + // Create collection info + let info = CollectionInfo { + address: collection_address.clone(), + creator: caller.clone(), + config: config.clone(), + created_at: env.ledger().timestamp(), + total_tokens: 0, + is_paused: false, + }; + + // Store collection info + DataKey::set_collection_info(env, collection_id, &info); + + // Set initial royalty if provided + if let Some(recipient) = initial_royalty_recipient { + let royalty = crate::storage::RoyaltyInfo { + recipient, + percentage: config.royalty_percentage, + }; + DataKey::set_royalty_info(env, collection_id, &royalty); + } + + // Update factory config + let mut factory_config = factory_config; + factory_config.total_collections += 1; + if factory_config.factory_fee > 0 { + factory_config.accumulated_fees += factory_config.factory_fee; + } + DataKey::set_factory_config(env, &factory_config); + + // Publish event + Event::CollectionCreated { + creator: caller.clone(), + collection_id, + collection_address, + name: config.name, + symbol: config.symbol, + } + .publish(env); + + Ok(collection_id) + } + + // Get collection count + pub fn get_collection_count(env: &Env) -> Result { + let config = DataKey::get_factory_config(env)?; + Ok(config.total_collections) + } + + // Get collection address by ID + pub fn get_collection_address(env: &Env, collection_id: u64) -> Result { + let info = DataKey::get_collection_info(env, collection_id)?; + Ok(info.address) + } + + // Get collection info by ID + pub fn get_collection_info(env: &Env, collection_id: u64) -> Result { + DataKey::get_collection_info(env, collection_id) + } + + // Set factory fee (only owner) + pub fn set_factory_fee( + env: &Env, + caller: &Address, + fee: i128, + ) -> Result<(), Error> { + let mut config = DataKey::get_factory_config(env)?; + + // Check if caller is owner + if &config.owner != caller { + return Err(Error::Unauthorized); + } + + let old_fee = config.factory_fee; + config.factory_fee = fee; + DataKey::set_factory_config(env, &config); + + // Publish event + Event::FactoryFeeUpdated { + old_fee, + new_fee: fee, + updater: caller.clone(), + } + .publish(env); + + Ok(()) + } + + // Withdraw accumulated fees (only owner) + pub fn withdraw_fees( + env: &Env, + caller: &Address, + recipient: Address, + amount: i128, + ) -> Result<(), Error> { + let mut config = DataKey::get_factory_config(env)?; + + // Check if caller is owner + if &config.owner != caller { + return Err(Error::Unauthorized); + } + + // Check if sufficient balance + if config.accumulated_fees < amount { + return Err(Error::InsufficientFee); + } + + config.accumulated_fees -= amount; + DataKey::set_factory_config(env, &config); + + // In a real implementation, you would transfer tokens here + // For simplicity, we'll just emit an event + + // Publish event + Event::FeesWithdrawn { + recipient, + amount, + } + .publish(env); + + Ok(()) + } + + // Set max collections (only owner) + pub fn set_max_collections( + env: &Env, + caller: &Address, + max: Option, + ) -> Result<(), Error> { + let mut config = DataKey::get_factory_config(env)?; + + // Check if caller is owner + if &config.owner != caller { + return Err(Error::Unauthorized); + } + + config.max_collections = max; + DataKey::set_factory_config(env, &config); + + Ok(()) + } + + // Set factory active/inactive (only owner) + pub fn set_factory_active( + env: &Env, + caller: &Address, + active: bool, + ) -> Result<(), Error> { + let mut config = DataKey::get_factory_config(env)?; + + // Check if caller is owner + if &config.owner != caller { + return Err(Error::Unauthorized); + } + + config.is_active = active; + DataKey::set_factory_config(env, &config); + + Ok(()) + } + + // Validate collection config + fn validate_collection_config( + env: &Env, + config: &CollectionConfig, + ) -> Result<(), Error> { + // Check name is not empty + if config.name.len() == 0 { + return Err(Error::InvalidConfig); + } + + // Check symbol is not empty + if config.symbol.len() == 0 { + return Err(Error::InvalidConfig); + } + + // Check royalty percentage is valid (max 25%) + if config.royalty_percentage > 2500 { + return Err(Error::InvalidRoyaltyPercentage); + } + + // Validate max supply if provided + if let Some(max_supply) = config.max_supply { + if max_supply == 0 { + return Err(Error::InvalidConfig); + } + } + + Ok(()) + } + + // Deploy collection contract (simulated) + fn deploy_collection_contract( + env: &Env, + collection_id: u64, + creator: &Address, + config: &CollectionConfig, + ) -> Result { + // In a real Soroban implementation, you would: + // 1. Deploy a new contract instance + // 2. Initialize it with the collection config + // 3. Return the contract address + + // For this example, we'll generate a simulated address + // based on collection ID and creator + let address_data = env.crypto().sha256( + &(collection_id.to_be_bytes().to_vec(), creator.to_string()).into_val(env) + ); + + // Convert hash to address (simplified) + // In reality, you'd use the actual deployed contract address + Ok(Address::from_contract_id(&env.crypto().sha256(&address_data))) + } +} \ No newline at end of file diff --git a/nftopia-stellar/contracts/collection_factory_contract/src/lib.rs b/nftopia-stellar/contracts/collection_factory_contract/src/lib.rs index e69de29b..d41e6df0 100644 --- a/nftopia-stellar/contracts/collection_factory_contract/src/lib.rs +++ b/nftopia-stellar/contracts/collection_factory_contract/src/lib.rs @@ -0,0 +1,254 @@ +#![no_std] + +use soroban_sdk::{contract, contractimpl, contracttype, Address, Env, String, Vec, Map, Val}; + +mod errors; +mod events; +mod storage; +mod collection; +mod factory; + +use errors::Error; +use events::Event; +use storage::{DataKey, CollectionConfig, TokenMetadata}; +use collection::Collection; +use factory::Factory; + +#[contract] +pub struct CollectionFactoryContract; + +#[contractimpl] +impl CollectionFactoryContract { + // Initialize the factory (only callable once) + pub fn initialize(env: Env, owner: Address) -> Result<(), Error> { + Factory::initialize(&env, owner) + } + + // Factory management functions + pub fn create_collection( + env: Env, + caller: Address, + name: String, + symbol: String, + description: String, + base_uri: String, + max_supply: Option, + is_public_mint: bool, + royalty_percentage: u32, + royalty_recipient: Address, + metadata_schema: u32, + is_pausable: bool, + is_upgradeable: bool, + ) -> Result { + let config = CollectionConfig { + name, + symbol, + description, + base_uri, + max_supply, + is_public_mint, + royalty_percentage, + royalty_recipient, + metadata_schema: storage::MetadataSchema::Basic, // Simplified + is_pausable, + is_upgradeable, + }; + + Factory::create_collection(&env, &caller, config, Some(royalty_recipient)) + } + + pub fn get_collection_count(env: Env) -> Result { + Factory::get_collection_count(&env) + } + + pub fn get_collection_address(env: Env, collection_id: u64) -> Result { + Factory::get_collection_address(&env, collection_id) + } + + pub fn set_factory_fee(env: Env, caller: Address, fee: i128) -> Result<(), Error> { + Factory::set_factory_fee(&env, &caller, fee) + } + + pub fn withdraw_fees( + env: Env, + caller: Address, + recipient: Address, + amount: i128, + ) -> Result<(), Error> { + Factory::withdraw_fees(&env, &caller, recipient, amount) + } + + pub fn set_max_collections( + env: Env, + caller: Address, + max: Option, + ) -> Result<(), Error> { + Factory::set_max_collections(&env, &caller, max) + } + + // Collection functions + pub fn mint( + env: Env, + collection_id: u64, + to: Address, + uri: String, + attributes: Option>, + ) -> Result { + Collection::mint(&env, collection_id, &to, uri, attributes) + } + + pub fn batch_mint( + env: Env, + collection_id: u64, + to: Address, + uris: Vec, + attributes_list: Option>>, + ) -> Result, Error> { + Collection::batch_mint(&env, collection_id, &to, uris, attributes_list) + } + + pub fn transfer( + env: Env, + collection_id: u64, + from: Address, + to: Address, + token_id: u32, + ) -> Result<(), Error> { + Collection::transfer(&env, collection_id, &from, &to, token_id) + } + + pub fn batch_transfer( + env: Env, + collection_id: u64, + from: Address, + to: Address, + token_ids: Vec, + ) -> Result<(), Error> { + Collection::batch_transfer(&env, collection_id, &from, &to, token_ids) + } + + pub fn burn( + env: Env, + collection_id: u64, + owner: Address, + token_id: u32, + ) -> Result<(), Error> { + Collection::burn(&env, collection_id, &owner, token_id) + } + + pub fn approve( + env: Env, + collection_id: u64, + owner: Address, + approved: Address, + token_id: u32, + ) -> Result<(), Error> { + Collection::approve(&env, collection_id, &owner, &approved, token_id) + } + + pub fn set_approval_for_all( + env: Env, + collection_id: u64, + owner: Address, + operator: Address, + approved: bool, + ) -> Result<(), Error> { + Collection::set_approval_for_all(&env, collection_id, &owner, &operator, approved) + } + + pub fn set_royalty_info( + env: Env, + collection_id: u64, + caller: Address, + recipient: Address, + percentage: u32, + ) -> Result<(), Error> { + Collection::set_royalty_info(&env, collection_id, &caller, recipient, percentage) + } + + pub fn set_whitelist( + env: Env, + collection_id: u64, + caller: Address, + address: Address, + whitelisted: bool, + ) -> Result<(), Error> { + Collection::set_whitelist(&env, collection_id, &caller, address, whitelisted) + } + + pub fn set_paused( + env: Env, + collection_id: u64, + caller: Address, + paused: bool, + ) -> Result<(), Error> { + Collection::set_paused(&env, collection_id, &caller, paused) + } + + // Query functions + pub fn balance_of( + env: Env, + collection_id: u64, + address: Address, + ) -> Result { + Collection::balance_of(&env, collection_id, &address) + } + + pub fn owner_of( + env: Env, + collection_id: u64, + token_id: u32, + ) -> Result { + Collection::owner_of(&env, collection_id, token_id) + } + + pub fn get_approved( + env: Env, + collection_id: u64, + token_id: u32, + ) -> Result, Error> { + Collection::get_approved(&env, collection_id, token_id) + } + + pub fn is_approved_for_all( + env: Env, + collection_id: u64, + owner: Address, + operator: Address, + ) -> Result { + Collection::is_approved_for_all(&env, collection_id, &owner, &operator) + } + + pub fn token_uri( + env: Env, + collection_id: u64, + token_id: u32, + ) -> Result { + Collection::token_uri(&env, collection_id, token_id) + } + + pub fn token_metadata( + env: Env, + collection_id: u64, + token_id: u32, + ) -> Result { + Collection::token_metadata(&env, collection_id, token_id) + } + + pub fn total_supply( + env: Env, + collection_id: u64, + ) -> Result { + Collection::total_supply(&env, collection_id) + } + + pub fn royalty_info( + env: Env, + collection_id: u64, + ) -> Result, Error> { + Collection::royalty_info(&env, collection_id) + } +} + +#[cfg(test)] +mod test; \ No newline at end of file diff --git a/nftopia-stellar/contracts/collection_factory_contract/src/storage.rs b/nftopia-stellar/contracts/collection_factory_contract/src/storage.rs new file mode 100644 index 00000000..0208d787 --- /dev/null +++ b/nftopia-stellar/contracts/collection_factory_contract/src/storage.rs @@ -0,0 +1,313 @@ +use soroban_sdk::{contracttype, Address, Env, Map, Vec}; + +use crate::errors::Error; + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum MetadataSchema { + Basic, // Just URI + Extended, // URI + attributes + Advanced, // URI + attributes + mutable metadata +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CollectionConfig { + pub name: String, + pub symbol: String, + pub description: String, + pub base_uri: String, + pub max_supply: Option, // None = unlimited + pub is_public_mint: bool, + pub royalty_percentage: u32, // Basis points (100 = 1%) + pub royalty_recipient: Address, + pub metadata_schema: MetadataSchema, + pub is_pausable: bool, + pub is_upgradeable: bool, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TokenMetadata { + pub token_id: u32, + pub uri: String, + pub attributes: Map, // Key-value pairs + pub creator: Address, + pub created_at: u64, + pub updated_at: Option, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct CollectionInfo { + pub address: Address, + pub creator: Address, + pub config: CollectionConfig, + pub created_at: u64, + pub total_tokens: u32, + pub is_paused: bool, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct RoyaltyInfo { + pub recipient: Address, + pub percentage: u32, // Basis points +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct FactoryConfig { + pub owner: Address, + pub factory_fee: i128, // Fee for creating a collection + pub max_collections: Option, // Maximum collections allowed + pub total_collections: u32, + pub accumulated_fees: i128, + pub is_active: bool, +} + +// Storage keys +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum DataKey { + // Factory storage + FactoryConfig, + Collections, // Map + CollectionIdByAddress, // Map + Whitelist(u64), // Map for collection + + // Collection storage + CollectionInfo(u64), + NextTokenId(u64), + TokenOwner(u64, u32), // token_id -> owner + TokenMetadata(u64, u32), // token_id -> metadata + Balance(u64, Address), // address -> token count + Approved(u64, u32), // token_id -> approved address + ApprovedForAll(u64, Address, Address), // owner -> operator -> bool + RoyaltyInfo(u64), + WhitelistForMint(u64, Address), // address -> bool + IsPaused(u64), +} + +pub trait Storage { + fn get_factory_config(env: &Env) -> Result; + fn set_factory_config(env: &Env, config: &FactoryConfig); + + fn get_collection_info(env: &Env, collection_id: u64) -> Result; + fn set_collection_info(env: &Env, collection_id: u64, info: &CollectionInfo); + + fn get_collections_count(env: &Env) -> u32; + fn increment_collections_count(env: &Env); + + fn get_next_token_id(env: &Env, collection_id: u64) -> u32; + fn set_next_token_id(env: &Env, collection_id: u64, next_id: u32); + fn increment_token_id(env: &Env, collection_id: u64); + + fn get_token_owner(env: &Env, collection_id: u64, token_id: u32) -> Option
; + fn set_token_owner(env: &Env, collection_id: u64, token_id: u32, owner: &Address); + fn remove_token_owner(env: &Env, collection_id: u64, token_id: u32); + + fn get_token_metadata(env: &Env, collection_id: u64, token_id: u32) -> Option; + fn set_token_metadata(env: &Env, collection_id: u64, token_id: u32, metadata: &TokenMetadata); + + fn get_balance(env: &Env, collection_id: u64, address: &Address) -> u32; + fn set_balance(env: &Env, collection_id: u64, address: &Address, balance: u32); + fn increment_balance(env: &Env, collection_id: u64, address: &Address); + fn decrement_balance(env: &Env, collection_id: u64, address: &Address); + + fn get_approved(env: &Env, collection_id: u64, token_id: u32) -> Option
; + fn set_approved(env: &Env, collection_id: u64, token_id: u32, approved: &Address); + fn remove_approved(env: &Env, collection_id: u64, token_id: u32); + + fn get_approved_for_all(env: &Env, collection_id: u64, owner: &Address, operator: &Address) -> bool; + fn set_approved_for_all(env: &Env, collection_id: u64, owner: &Address, operator: &Address, approved: bool); + + fn get_royalty_info(env: &Env, collection_id: u64) -> Option; + fn set_royalty_info(env: &Env, collection_id: u64, royalty: &RoyaltyInfo); + + fn is_whitelisted_for_mint(env: &Env, collection_id: u64, address: &Address) -> bool; + fn set_whitelisted_for_mint(env: &Env, collection_id: u64, address: &Address, whitelisted: bool); + + fn is_collection_paused(env: &Env, collection_id: u64) -> bool; + fn set_collection_paused(env: &Env, collection_id: u64, paused: bool); +} + +impl Storage for DataKey { + fn get_factory_config(env: &Env) -> Result { + env.storage() + .instance() + .get(&DataKey::FactoryConfig) + .ok_or(Error::StorageError) + } + + fn set_factory_config(env: &Env, config: &FactoryConfig) { + env.storage().instance().set(&DataKey::FactoryConfig, config); + } + + fn get_collection_info(env: &Env, collection_id: u64) -> Result { + env.storage() + .instance() + .get(&DataKey::CollectionInfo(collection_id)) + .ok_or(Error::CollectionNotFound) + } + + fn set_collection_info(env: &Env, collection_id: u64, info: &CollectionInfo) { + env.storage() + .instance() + .set(&DataKey::CollectionInfo(collection_id), info); + } + + fn get_collections_count(env: &Env) -> u32 { + Self::get_factory_config(env) + .map(|config| config.total_collections) + .unwrap_or(0) + } + + fn increment_collections_count(env: &Env) { + let mut config = Self::get_factory_config(env).unwrap(); + config.total_collections += 1; + Self::set_factory_config(env, &config); + } + + fn get_next_token_id(env: &Env, collection_id: u64) -> u32 { + env.storage() + .instance() + .get(&DataKey::NextTokenId(collection_id)) + .unwrap_or(1) // Start from token ID 1 + } + + fn set_next_token_id(env: &Env, collection_id: u64, next_id: u32) { + env.storage() + .instance() + .set(&DataKey::NextTokenId(collection_id), &next_id); + } + + fn increment_token_id(env: &Env, collection_id: u64) { + let next_id = Self::get_next_token_id(env, collection_id) + 1; + Self::set_next_token_id(env, collection_id, next_id); + } + + fn get_token_owner(env: &Env, collection_id: u64, token_id: u32) -> Option
{ + env.storage() + .instance() + .get(&DataKey::TokenOwner(collection_id, token_id)) + } + + fn set_token_owner(env: &Env, collection_id: u64, token_id: u32, owner: &Address) { + env.storage() + .instance() + .set(&DataKey::TokenOwner(collection_id, token_id), owner); + } + + fn remove_token_owner(env: &Env, collection_id: u64, token_id: u32) { + env.storage() + .instance() + .remove(&DataKey::TokenOwner(collection_id, token_id)); + } + + fn get_token_metadata(env: &Env, collection_id: u64, token_id: u32) -> Option { + env.storage() + .instance() + .get(&DataKey::TokenMetadata(collection_id, token_id)) + } + + fn set_token_metadata(env: &Env, collection_id: u64, token_id: u32, metadata: &TokenMetadata) { + env.storage() + .instance() + .set(&DataKey::TokenMetadata(collection_id, token_id), metadata); + } + + fn get_balance(env: &Env, collection_id: u64, address: &Address) -> u32 { + env.storage() + .instance() + .get(&DataKey::Balance(collection_id, address.clone())) + .unwrap_or(0) + } + + fn set_balance(env: &Env, collection_id: u64, address: &Address, balance: u32) { + env.storage() + .instance() + .set(&DataKey::Balance(collection_id, address.clone()), &balance); + } + + fn increment_balance(env: &Env, collection_id: u64, address: &Address) { + let balance = Self::get_balance(env, collection_id, address) + 1; + Self::set_balance(env, collection_id, address, balance); + } + + fn decrement_balance(env: &Env, collection_id: u64, address: &Address) { + let balance = Self::get_balance(env, collection_id, address); + if balance > 0 { + Self::set_balance(env, collection_id, address, balance - 1); + } + } + + fn get_approved(env: &Env, collection_id: u64, token_id: u32) -> Option
{ + env.storage() + .instance() + .get(&DataKey::Approved(collection_id, token_id)) + } + + fn set_approved(env: &Env, collection_id: u64, token_id: u32, approved: &Address) { + env.storage() + .instance() + .set(&DataKey::Approved(collection_id, token_id), approved); + } + + fn remove_approved(env: &Env, collection_id: u64, token_id: u32) { + env.storage() + .instance() + .remove(&DataKey::Approved(collection_id, token_id)); + } + + fn get_approved_for_all(env: &Env, collection_id: u64, owner: &Address, operator: &Address) -> bool { + env.storage() + .instance() + .get(&DataKey::ApprovedForAll(collection_id, owner.clone(), operator.clone())) + .unwrap_or(false) + } + + fn set_approved_for_all(env: &Env, collection_id: u64, owner: &Address, operator: &Address, approved: bool) { + env.storage() + .instance() + .set(&DataKey::ApprovedForAll(collection_id, owner.clone(), operator.clone()), &approved); + } + + fn get_royalty_info(env: &Env, collection_id: u64) -> Option { + env.storage() + .instance() + .get(&DataKey::RoyaltyInfo(collection_id)) + } + + fn set_royalty_info(env: &Env, collection_id: u64, royalty: &RoyaltyInfo) { + env.storage() + .instance() + .set(&DataKey::RoyaltyInfo(collection_id), royalty); + } + + fn is_whitelisted_for_mint(env: &Env, collection_id: u64, address: &Address) -> bool { + env.storage() + .instance() + .get(&DataKey::WhitelistForMint(collection_id, address.clone())) + .unwrap_or(false) + } + + fn set_whitelisted_for_mint(env: &Env, collection_id: u64, address: &Address, whitelisted: bool) { + env.storage() + .instance() + .set(&DataKey::WhitelistForMint(collection_id, address.clone()), &whitelisted); + } + + fn is_collection_paused(env: &Env, collection_id: u64) -> bool { + env.storage() + .instance() + .get(&DataKey::IsPaused(collection_id)) + .unwrap_or(false) + } + + fn set_collection_paused(env: &Env, collection_id: u64, paused: bool) { + env.storage() + .instance() + .set(&DataKey::IsPaused(collection_id), &paused); + } +} \ No newline at end of file diff --git a/nftopia-stellar/contracts/collection_factory_contract/src/test.rs b/nftopia-stellar/contracts/collection_factory_contract/src/test.rs index e69de29b..d8f79da7 100644 --- a/nftopia-stellar/contracts/collection_factory_contract/src/test.rs +++ b/nftopia-stellar/contracts/collection_factory_contract/src/test.rs @@ -0,0 +1,202 @@ +#![cfg(test)] + +use soroban_sdk::{testutils::Address as _, Address, Env, String}; + +use crate::{CollectionFactoryContract, CollectionFactoryContractClient}; + +#[test] +fn test_initialize_and_create_collection() { + let env = Env::default(); + + // Create test addresses + let owner = Address::generate(&env); + let creator = Address::generate(&env); + + // Deploy contract + let contract_id = env.register_contract(None, CollectionFactoryContract); + let client = CollectionFactoryContractClient::new(&env, &contract_id); + + // Initialize factory + client.initialize(&owner); + + // Create collection + let collection_id = client.create_collection( + &creator, + &String::from_str(&env, "Test Collection"), + &String::from_str(&env, "TEST"), + &String::from_str(&env, "A test NFT collection"), + &String::from_str(&env, "https://api.nftopia.com/metadata/"), + &None::, + &true, + &500, // 5% royalty + &creator, + &0, // Basic metadata schema + &true, + &false, + ); + + assert!(collection_id > 0); + + // Verify collection count + let count = client.get_collection_count(); + assert_eq!(count, 1); + + // Verify collection address + let address = client.get_collection_address(&collection_id); + assert!(address.is_contract()); +} + +#[test] +fn test_mint_and_transfer() { + let env = Env::default(); + + // Create test addresses + let owner = Address::generate(&env); + let creator = Address::generate(&env); + let recipient = Address::generate(&env); + + // Deploy and initialize + let contract_id = env.register_contract(None, CollectionFactoryContract); + let client = CollectionFactoryContractClient::new(&env, &contract_id); + client.initialize(&owner); + + // Create collection + let collection_id = client.create_collection( + &creator, + &String::from_str(&env, "Test Collection"), + &String::from_str(&env, "TEST"), + &String::from_str(&env, "A test NFT collection"), + &String::from_str(&env, "https://api.nftopia.com/metadata/"), + &None::, + &true, + &500, + &creator, + &0, + &true, + &false, + ); + + // Mint token + let token_id = client.mint( + &collection_id, + &creator, + &String::from_str(&env, "https://api.nftopia.com/metadata/1"), + &None, + ); + + assert_eq!(token_id, 1); + + // Verify owner + let token_owner = client.owner_of(&collection_id, &token_id); + assert_eq!(token_owner, creator); + + // Verify balance + let balance = client.balance_of(&collection_id, &creator); + assert_eq!(balance, 1); + + // Transfer token + client.transfer(&collection_id, &creator, &recipient, &token_id); + + // Verify new owner + let new_owner = client.owner_of(&collection_id, &token_id); + assert_eq!(new_owner, recipient); + + // Verify balances updated + let creator_balance = client.balance_of(&collection_id, &creator); + let recipient_balance = client.balance_of(&collection_id, &recipient); + assert_eq!(creator_balance, 0); + assert_eq!(recipient_balance, 1); +} + +#[test] +fn test_batch_mint() { + let env = Env::default(); + + let owner = Address::generate(&env); + let creator = Address::generate(&env); + + let contract_id = env.register_contract(None, CollectionFactoryContract); + let client = CollectionFactoryContractClient::new(&env, &contract_id); + client.initialize(&owner); + + let collection_id = client.create_collection( + &creator, + &String::from_str(&env, "Test Collection"), + &String::from_str(&env, "TEST"), + &String::from_str(&env, "A test NFT collection"), + &String::from_str(&env, "https://api.nftopia.com/metadata/"), + &None::, + &true, + &500, + &creator, + &0, + &true, + &false, + ); + + // Create URIs for batch mint + let uris = vec![ + &env, + String::from_str(&env, "https://api.nftopia.com/metadata/1"), + String::from_str(&env, "https://api.nftopia.com/metadata/2"), + String::from_str(&env, "https://api.nftopia.com/metadata/3"), + ]; + + // Batch mint + let token_ids = client.batch_mint(&collection_id, &creator, &uris, &None); + + assert_eq!(token_ids.len(), 3); + assert_eq!(token_ids.get(0).unwrap(), 1); + assert_eq!(token_ids.get(1).unwrap(), 2); + assert_eq!(token_ids.get(2).unwrap(), 3); + + // Verify total supply + let total_supply = client.total_supply(&collection_id); + assert_eq!(total_supply, 3); +} + +#[test] +fn test_royalty_management() { + let env = Env::default(); + + let owner = Address::generate(&env); + let creator = Address::generate(&env); + let royalty_recipient = Address::generate(&env); + + let contract_id = env.register_contract(None, CollectionFactoryContract); + let client = CollectionFactoryContractClient::new(&env, &contract_id); + client.initialize(&owner); + + let collection_id = client.create_collection( + &creator, + &String::from_str(&env, "Royalty Collection"), + &String::from_str(&env, "ROYAL"), + &String::from_str(&env, "Collection with royalties"), + &String::from_str(&env, "https://api.nftopia.com/metadata/"), + &None::, + &true, + &1000, // 10% royalty + &royalty_recipient, + &0, + &true, + &false, + ); + + // Get royalty info + let royalty_info = client.royalty_info(&collection_id); + assert!(royalty_info.is_some()); + + if let Some(info) = royalty_info { + assert_eq!(info.recipient, royalty_recipient); + assert_eq!(info.percentage, 1000); + } + + // Update royalty info + let new_recipient = Address::generate(&env); + client.set_royalty_info(&collection_id, &creator, &new_recipient, &750); // 7.5% + + // Verify update + let updated_info = client.royalty_info(&collection_id).unwrap(); + assert_eq!(updated_info.recipient, new_recipient); + assert_eq!(updated_info.percentage, 750); +} \ No newline at end of file From 4ea3177ad1ba95535ac6dcf857455418f4ffa2a0 Mon Sep 17 00:00:00 2001 From: feyishola Date: Fri, 30 Jan 2026 15:54:10 +0100 Subject: [PATCH 02/13] artifact updated from v3 to v4 --- .github/workflows/nftopia-backend.yml | 2 +- .github/workflows/nftopia-frontend.yml | 2 +- .github/workflows/nftopia-stellar.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nftopia-backend.yml b/.github/workflows/nftopia-backend.yml index f42e2dab..76f25470 100644 --- a/.github/workflows/nftopia-backend.yml +++ b/.github/workflows/nftopia-backend.yml @@ -110,7 +110,7 @@ jobs: - name: Upload build artifact if: success() && github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: nftopia-backend-dist path: nftopia-backend/dist/ \ No newline at end of file diff --git a/.github/workflows/nftopia-frontend.yml b/.github/workflows/nftopia-frontend.yml index d97b8ff7..fb39c2ba 100644 --- a/.github/workflows/nftopia-frontend.yml +++ b/.github/workflows/nftopia-frontend.yml @@ -68,7 +68,7 @@ jobs: - name: Upload build artifact if: success() && github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: nftopia-frontend-build path: nftopia-frontend/.next/ \ No newline at end of file diff --git a/.github/workflows/nftopia-stellar.yml b/.github/workflows/nftopia-stellar.yml index 7daa16cd..b9e027c3 100644 --- a/.github/workflows/nftopia-stellar.yml +++ b/.github/workflows/nftopia-stellar.yml @@ -63,7 +63,7 @@ jobs: - name: Upload contract artifact if: success() && github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: nftopia-stellar-contract path: nftopia-stellar/target/wasm32-unknown-unknown/release/*.wasm \ No newline at end of file From 996a17fd3f6bced5a161a51ab7a5ca7f0d1f7027 Mon Sep 17 00:00:00 2001 From: feyishola Date: Fri, 30 Jan 2026 16:01:55 +0100 Subject: [PATCH 03/13] markrtplace settlement error fixed --- nftopia-stellar/contracts/marketplace_settlement/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nftopia-stellar/contracts/marketplace_settlement/Cargo.toml b/nftopia-stellar/contracts/marketplace_settlement/Cargo.toml index f5af8a87..93b600e8 100644 --- a/nftopia-stellar/contracts/marketplace_settlement/Cargo.toml +++ b/nftopia-stellar/contracts/marketplace_settlement/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "marketplace_settlement" version = "0.1.0" -edition = "2024" +edition = "2021" [dependencies] From 967dcb2fc9ad6db27413f73019705cd0ddd8c681 Mon Sep 17 00:00:00 2001 From: feyishola Date: Fri, 30 Jan 2026 16:06:24 +0100 Subject: [PATCH 04/13] more build error fixed --- nftopia-stellar/contracts/nft_contract/Cargo.toml | 2 +- nftopia-stellar/contracts/transaction_contract/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nftopia-stellar/contracts/nft_contract/Cargo.toml b/nftopia-stellar/contracts/nft_contract/Cargo.toml index bd94153a..1bc40b61 100644 --- a/nftopia-stellar/contracts/nft_contract/Cargo.toml +++ b/nftopia-stellar/contracts/nft_contract/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nft_contract" version = "0.1.0" -edition = "2024" +edition = "2021" [dependencies] diff --git a/nftopia-stellar/contracts/transaction_contract/Cargo.toml b/nftopia-stellar/contracts/transaction_contract/Cargo.toml index d9a9b60e..62af94ef 100644 --- a/nftopia-stellar/contracts/transaction_contract/Cargo.toml +++ b/nftopia-stellar/contracts/transaction_contract/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "transaction_contract" version = "0.1.0" -edition = "2024" +edition = "2021" [dependencies] From 36cec46c49577ce0e44a1a32b48eaea012254829 Mon Sep 17 00:00:00 2001 From: feyishola Date: Fri, 30 Jan 2026 16:10:27 +0100 Subject: [PATCH 05/13] Remove Cargo.lock to fix CI Cargo version mismatch --- nftopia-stellar/Cargo.lock | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 nftopia-stellar/Cargo.lock diff --git a/nftopia-stellar/Cargo.lock b/nftopia-stellar/Cargo.lock deleted file mode 100644 index de26695c..00000000 --- a/nftopia-stellar/Cargo.lock +++ /dev/null @@ -1,19 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "collection_factory_contract" -version = "0.1.0" - -[[package]] -name = "marketplace_settlement" -version = "0.1.0" - -[[package]] -name = "nft_contract" -version = "0.1.0" - -[[package]] -name = "transaction_contract" -version = "0.1.0" From c989dae7401fe96c8a01b9fb0520446a3951059e Mon Sep 17 00:00:00 2001 From: feyishola Date: Fri, 30 Jan 2026 16:21:21 +0100 Subject: [PATCH 06/13] Upgrade soroban-sdk to v25 and fix workspace config --- .../contracts/collection_factory_contract/Cargo.toml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/nftopia-stellar/contracts/collection_factory_contract/Cargo.toml b/nftopia-stellar/contracts/collection_factory_contract/Cargo.toml index 0ecb7740..de778aec 100644 --- a/nftopia-stellar/contracts/collection_factory_contract/Cargo.toml +++ b/nftopia-stellar/contracts/collection_factory_contract/Cargo.toml @@ -9,16 +9,8 @@ license = "Apache-2.0" crate-type = ["cdylib"] [dependencies] -soroban-sdk = "24.1.0" +soroban-sdk = "25.0.2" serde = { version = "1.0", features = ["derive"] } [dev-dependencies] -soroban-sdk = { version = "24.1.0", features = ["testutils"] } - -[profile.release] -opt-level = "z" # Optimize for size -overflow-checks = true -debug = false -strip = "symbols" -lto = true -codegen-units = 1 \ No newline at end of file +soroban-sdk = { version = "25.0.2", features = ["testutils"] } \ No newline at end of file From 25e7918b18548228b1eee815c4d312895ad79792 Mon Sep 17 00:00:00 2001 From: feyishola Date: Fri, 30 Jan 2026 16:29:32 +0100 Subject: [PATCH 07/13] Upgrade soroban-sdk to v25 and fix workspace config --- .github/workflows/nftopia-stellar.yml | 89 ++++++++++++++------------- 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/.github/workflows/nftopia-stellar.yml b/.github/workflows/nftopia-stellar.yml index b9e027c3..e85472b4 100644 --- a/.github/workflows/nftopia-stellar.yml +++ b/.github/workflows/nftopia-stellar.yml @@ -13,57 +13,62 @@ on: env: CARGO_TERM_COLOR: always - RUST_VERSION: '1.75' jobs: build-and-test: runs-on: ubuntu-latest - + steps: - - name: Checkout repository - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Rust (nightly) + uses: dtolnay/rust-toolchain@nightly + with: + targets: wasm32-unknown-unknown + components: clippy, rustfmt - - name: Setup Rust - uses: dtolnay/rust-toolchain@stable - with: - toolchain: ${{ env.RUST_VERSION }} - targets: wasm32-unknown-unknown + - name: Verify Rust version + run: | + rustc --version + cargo --version - - name: Cache cargo dependencies - uses: actions/cache@v3 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-cargo-${{ hashFiles('nftopia-stellar/Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-cargo- + - name: Cache cargo dependencies + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('nftopia-stellar/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- - - name: Build contract - run: | - cd nftopia-stellar - cargo build --target wasm32-unknown-unknown --release - echo "Contract size: $(stat -f%z target/wasm32-unknown-unknown/release/*.wasm) bytes" + - name: Build contracts (wasm) + run: | + cd nftopia-stellar + cargo build --target wasm32-unknown-unknown --release + echo "Contract size:" + ls -lh target/wasm32-unknown-unknown/release/*.wasm - - name: Run tests - run: | - cd nftopia-stellar - cargo test --verbose + - name: Run tests + run: | + cd nftopia-stellar + cargo test --verbose - - name: Run clippy (linting) - run: | - cd nftopia-stellar - cargo clippy --all-targets --all-features -- -D warnings + - name: Run clippy (linting) + run: | + cd nftopia-stellar + cargo clippy --all-targets --all-features -- -D warnings - - name: Check formatting - run: | - cd nftopia-stellar - cargo fmt -- --check + - name: Check formatting + run: | + cd nftopia-stellar + cargo fmt -- --check - - name: Upload contract artifact - if: success() && github.event_name == 'push' && github.ref == 'refs/heads/main' - uses: actions/upload-artifact@v4 - with: - name: nftopia-stellar-contract - path: nftopia-stellar/target/wasm32-unknown-unknown/release/*.wasm \ No newline at end of file + - name: Upload contract artifact + if: success() && github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: actions/upload-artifact@v4 + with: + name: nftopia-stellar-contract + path: nftopia-stellar/target/wasm32-unknown-unknown/release/*.wasm From 9091759b1de699287ca9881e1f8ad96ea30eaa5d Mon Sep 17 00:00:00 2001 From: feyishola Date: Fri, 30 Jan 2026 19:59:29 +0100 Subject: [PATCH 08/13] updated all files --- .../src/collection.rs | 396 +++++------------- .../collection_factory_contract/src/errors.rs | 10 +- .../collection_factory_contract/src/events.rs | 153 +++---- .../src/factory.rs | 228 ++++------ .../collection_factory_contract/src/main.rs | 0 .../src/storage.rs | 42 +- 6 files changed, 281 insertions(+), 548 deletions(-) delete mode 100644 nftopia-stellar/contracts/collection_factory_contract/src/main.rs diff --git a/nftopia-stellar/contracts/collection_factory_contract/src/collection.rs b/nftopia-stellar/contracts/collection_factory_contract/src/collection.rs index e7092927..e8161a07 100644 --- a/nftopia-stellar/contracts/collection_factory_contract/src/collection.rs +++ b/nftopia-stellar/contracts/collection_factory_contract/src/collection.rs @@ -1,9 +1,9 @@ -use soroban_sdk::{contracttype, Address, Env, Map, String, Vec}; +use soroban_sdk::{Address, Env, Map, String, Vec}; use crate::{ errors::Error, events::Event, - storage::{DataKey, Storage, TokenMetadata, RoyaltyInfo}, + storage::{DataKey, TokenMetadata, RoyaltyInfo}, }; pub struct Collection; @@ -17,72 +17,55 @@ impl Collection { uri: String, attributes: Option>, ) -> Result { - // Check if collection is paused if DataKey::is_collection_paused(env, collection_id) { return Err(Error::MintingPaused); } - - // Get collection info - let info = DataKey::get_collection_info(env, collection_id)?; - - // Check max supply + + let mut info = DataKey::get_collection_info(env, collection_id)?; + if let Some(max_supply) = info.config.max_supply { if info.total_tokens >= max_supply { return Err(Error::MaxSupplyExceeded); } } - - // Check if public minting is allowed - if !info.config.is_public_mint { - // Check if address is whitelisted - if !DataKey::is_whitelisted_for_mint(env, collection_id, to) { - return Err(Error::WhitelistRequired); - } + + if !info.config.is_public_mint + && !DataKey::is_whitelisted_for_mint(env, collection_id, to) + { + return Err(Error::WhitelistRequired); } - - // Get next token ID + let token_id = DataKey::get_next_token_id(env, collection_id); - - // Create token metadata + let metadata = TokenMetadata { token_id, uri: uri.clone(), - attributes: attributes.unwrap_or(Map::new(env)), + attributes: attributes.unwrap_or_else(|| Map::new(env)), creator: to.clone(), created_at: env.ledger().timestamp(), updated_at: None, }; - - // Set token owner + DataKey::set_token_owner(env, collection_id, token_id, to); - - // Set token metadata DataKey::set_token_metadata(env, collection_id, token_id, &metadata); - - // Update balance DataKey::increment_balance(env, collection_id, to); - - // Increment token ID for next mint DataKey::increment_token_id(env, collection_id); - - // Update collection total tokens - let mut info = info; + info.total_tokens += 1; DataKey::set_collection_info(env, collection_id, &info); - - // Publish event - Event::TokenMinted { - collection: info.address, + + Event::TokenMinted( + info.address.clone(), token_id, - owner: to.clone(), + to.clone(), uri, - } - .publish(env); - + ) + .emit(env); + Ok(token_id) } - - // Batch mint tokens + + // Batch mint pub fn batch_mint( env: &Env, collection_id: u64, @@ -90,39 +73,33 @@ impl Collection { uris: Vec, attributes_list: Option>>, ) -> Result, Error> { - let count = uris.len(); let start_token_id = DataKey::get_next_token_id(env, collection_id); let mut token_ids = Vec::new(env); - - for i in 0..count { + + for i in 0..uris.len() { let uri = uris.get(i).unwrap(); - let attributes = attributes_list + let attrs = attributes_list .as_ref() - .and_then(|list| list.get(i)); - - let token_id = Self::mint( - env, - collection_id, - to, - uri.clone(), - attributes.cloned(), - )?; - + .and_then(|v| v.get(i)) + .map(|m| m.clone()); + + let token_id = Self::mint(env, collection_id, to, uri.clone(), attrs)?; token_ids.push_back(token_id); } - - // Publish batch event - Event::BatchMinted { - collection: DataKey::get_collection_info(env, collection_id)?.address, + + let collection = DataKey::get_collection_info(env, collection_id)?.address; + + Event::BatchMinted( + collection, start_token_id, - count: count as u32, - owner: to.clone(), - } - .publish(env); - + uris.len() as u32, + to.clone(), + ) + .emit(env); + Ok(token_ids) } - + // Transfer token pub fn transfer( env: &Env, @@ -131,57 +108,41 @@ impl Collection { to: &Address, token_id: u32, ) -> Result<(), Error> { - // Check if collection is paused if DataKey::is_collection_paused(env, collection_id) { return Err(Error::MintingPaused); } - - // Check if token exists + let owner = DataKey::get_token_owner(env, collection_id, token_id) .ok_or(Error::TokenNotFound)?; - - // Check if sender is owner or approved + if &owner != from { - // Check if approved for this specific token let approved = DataKey::get_approved(env, collection_id, token_id); - if approved.as_ref() != Some(from) { - // Check if approved for all - if !DataKey::get_approved_for_all(env, collection_id, &owner, from) { - return Err(Error::NotTokenOwner); - } + if approved.as_ref() != Some(from) + && !DataKey::get_approved_for_all(env, collection_id, &owner, from) + { + return Err(Error::NotTokenOwner); } } - - // Calculate royalty if applicable - if let Some(royalty_info) = DataKey::get_royalty_info(env, collection_id) { - // In a real implementation, you would handle royalty payment here - // For now, we just emit an event - // Royalty would be enforced at the marketplace level - } - - // Transfer ownership + DataKey::set_token_owner(env, collection_id, token_id, to); - - // Update balances DataKey::decrement_balance(env, collection_id, from); DataKey::increment_balance(env, collection_id, to); - - // Clear approval for this token DataKey::remove_approved(env, collection_id, token_id); - - // Publish event - Event::TokenTransferred { - collection: DataKey::get_collection_info(env, collection_id)?.address, + + let collection = DataKey::get_collection_info(env, collection_id)?.address; + + Event::TokenTransferred( + collection, token_id, - from: from.clone(), - to: to.clone(), - } - .publish(env); - + from.clone(), + to.clone(), + ) + .emit(env); + Ok(()) } - - // Batch transfer tokens + + // Batch transfer pub fn batch_transfer( env: &Env, collection_id: u64, @@ -192,19 +153,20 @@ impl Collection { for token_id in token_ids.iter() { Self::transfer(env, collection_id, from, to, *token_id)?; } - - // Publish batch event - Event::BatchTransferred { - collection: DataKey::get_collection_info(env, collection_id)?.address, - token_ids: token_ids.clone(), - from: from.clone(), - to: to.clone(), - } - .publish(env); - + + let collection = DataKey::get_collection_info(env, collection_id)?.address; + + Event::BatchTransferred( + collection, + token_ids.clone(), + from.clone(), + to.clone(), + ) + .emit(env); + Ok(()) } - + // Burn token pub fn burn( env: &Env, @@ -212,70 +174,32 @@ impl Collection { owner: &Address, token_id: u32, ) -> Result<(), Error> { - // Check if token exists and owner matches let token_owner = DataKey::get_token_owner(env, collection_id, token_id) .ok_or(Error::TokenNotFound)?; - + if &token_owner != owner { return Err(Error::NotTokenOwner); } - - // Remove token data + DataKey::remove_token_owner(env, collection_id, token_id); DataKey::remove_approved(env, collection_id, token_id); - - // Update balance DataKey::decrement_balance(env, collection_id, owner); - - // Update collection total tokens + let mut info = DataKey::get_collection_info(env, collection_id)?; info.total_tokens = info.total_tokens.saturating_sub(1); DataKey::set_collection_info(env, collection_id, &info); - - // Publish event - Event::TokenBurned { - collection: info.address, + + Event::TokenBurned( + info.address, token_id, - owner: owner.clone(), - } - .publish(env); - - Ok(()) - } - - // Set approval for token - pub fn approve( - env: &Env, - collection_id: u64, - owner: &Address, - approved: &Address, - token_id: u32, - ) -> Result<(), Error> { - // Check if token exists and owner matches - let token_owner = DataKey::get_token_owner(env, collection_id, token_id) - .ok_or(Error::TokenNotFound)?; - - if &token_owner != owner { - return Err(Error::NotTokenOwner); - } - - DataKey::set_approved(env, collection_id, token_id, approved); - Ok(()) - } - - // Set approval for all - pub fn set_approval_for_all( - env: &Env, - collection_id: u64, - owner: &Address, - operator: &Address, - approved: bool, - ) -> Result<(), Error> { - DataKey::set_approved_for_all(env, collection_id, owner, operator, approved); + owner.clone(), + ) + .emit(env); + Ok(()) } - - // Set royalty info (only collection owner can call) + + // Royalty pub fn set_royalty_info( env: &Env, collection_id: u64, @@ -283,158 +207,44 @@ impl Collection { recipient: Address, percentage: u32, ) -> Result<(), Error> { - // Validate percentage (max 25% = 2500 basis points) if percentage > 2500 { return Err(Error::InvalidRoyaltyPercentage); } - - // Check if caller is collection creator + let info = DataKey::get_collection_info(env, collection_id)?; if &info.creator != caller { return Err(Error::Unauthorized); } - - let royalty = RoyaltyInfo { - recipient, - percentage, - }; - + + let royalty = RoyaltyInfo { recipient, percentage }; DataKey::set_royalty_info(env, collection_id, &royalty); - - // Publish event - Event::RoyaltyUpdated { - collection: info.address, - recipient: royalty.recipient.clone(), + + Event::RoyaltyUpdated( + info.address, + royalty.recipient.clone(), percentage, - } - .publish(env); - - Ok(()) - } - - // Whitelist address for minting - pub fn set_whitelist( - env: &Env, - collection_id: u64, - caller: &Address, - address: Address, - whitelisted: bool, - ) -> Result<(), Error> { - // Check if caller is collection creator - let info = DataKey::get_collection_info(env, collection_id)?; - if &info.creator != caller { - return Err(Error::Unauthorized); - } - - DataKey::set_whitelisted_for_mint(env, collection_id, &address, whitelisted); - - // Publish event - Event::WhitelistUpdated { - collection: info.address, - address: address.clone(), - added: whitelisted, - } - .publish(env); - + ) + .emit(env); + Ok(()) } - - // Pause/unpause collection + + // Pause pub fn set_paused( env: &Env, collection_id: u64, caller: &Address, paused: bool, ) -> Result<(), Error> { - // Check if collection is pausable let info = DataKey::get_collection_info(env, collection_id)?; - if !info.config.is_pausable { - return Err(Error::Unauthorized); - } - - // Check if caller is collection creator - if &info.creator != caller { + + if !info.config.is_pausable || &info.creator != caller { return Err(Error::Unauthorized); } - + DataKey::set_collection_paused(env, collection_id, paused); - - // Publish event - Event::CollectionPaused { - collection: info.address, - paused, - } - .publish(env); - + + Event::CollectionPaused(info.address, paused).emit(env); Ok(()) } - - // Query functions - pub fn balance_of( - env: &Env, - collection_id: u64, - address: &Address, - ) -> Result { - Ok(DataKey::get_balance(env, collection_id, address)) - } - - pub fn owner_of( - env: &Env, - collection_id: u64, - token_id: u32, - ) -> Result { - DataKey::get_token_owner(env, collection_id, token_id) - .ok_or(Error::TokenNotFound) - } - - pub fn get_approved( - env: &Env, - collection_id: u64, - token_id: u32, - ) -> Result, Error> { - Ok(DataKey::get_approved(env, collection_id, token_id)) - } - - pub fn is_approved_for_all( - env: &Env, - collection_id: u64, - owner: &Address, - operator: &Address, - ) -> Result { - Ok(DataKey::get_approved_for_all(env, collection_id, owner, operator)) - } - - pub fn token_uri( - env: &Env, - collection_id: u64, - token_id: u32, - ) -> Result { - let metadata = DataKey::get_token_metadata(env, collection_id, token_id) - .ok_or(Error::TokenNotFound)?; - Ok(metadata.uri) - } - - pub fn token_metadata( - env: &Env, - collection_id: u64, - token_id: u32, - ) -> Result { - DataKey::get_token_metadata(env, collection_id, token_id) - .ok_or(Error::TokenNotFound) - } - - pub fn total_supply( - env: &Env, - collection_id: u64, - ) -> Result { - let info = DataKey::get_collection_info(env, collection_id)?; - Ok(info.total_tokens) - } - - pub fn royalty_info( - env: &Env, - collection_id: u64, - ) -> Result, Error> { - Ok(DataKey::get_royalty_info(env, collection_id)) - } -} \ No newline at end of file +} diff --git a/nftopia-stellar/contracts/collection_factory_contract/src/errors.rs b/nftopia-stellar/contracts/collection_factory_contract/src/errors.rs index 509614b2..a95792f3 100644 --- a/nftopia-stellar/contracts/collection_factory_contract/src/errors.rs +++ b/nftopia-stellar/contracts/collection_factory_contract/src/errors.rs @@ -1,4 +1,4 @@ -use soroban_sdk::{contracterror, Address, Env}; +use soroban_sdk::{contracterror, Address, Env, Symbol}; #[contracterror] #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] @@ -11,7 +11,7 @@ pub enum Error { InsufficientFee = 1003, CollectionNotFound = 1004, CollectionAlreadyExists = 1005, - + // Collection errors (2000-2999) MaxSupplyExceeded = 2000, TokenNotFound = 2001, @@ -21,7 +21,7 @@ pub enum Error { WhitelistRequired = 2005, InvalidTokenId = 2006, InvalidRoyaltyPercentage = 2007, - + // General errors (3000-3999) InvalidInput = 3000, Overflow = 3001, @@ -31,7 +31,7 @@ pub enum Error { } impl Error { - pub fn to_symbol(&self, env: &Env) -> soroban_sdk::Symbol { + pub fn to_symbol(&self, env: &Env) -> Symbol { match self { Error::Unauthorized => Symbol::new(env, "UNAUTHORIZED"), Error::InvalidConfig => Symbol::new(env, "INVALID_CONFIG"), @@ -54,4 +54,4 @@ impl Error { Error::TransferFailed => Symbol::new(env, "TRANSFER_FAILED"), } } -} \ No newline at end of file +} diff --git a/nftopia-stellar/contracts/collection_factory_contract/src/events.rs b/nftopia-stellar/contracts/collection_factory_contract/src/events.rs index 7a816422..5769b959 100644 --- a/nftopia-stellar/contracts/collection_factory_contract/src/events.rs +++ b/nftopia-stellar/contracts/collection_factory_contract/src/events.rs @@ -1,77 +1,86 @@ -use soroban_sdk::{contracttype, Address, Env, Symbol}; +use soroban_sdk::{ + contractevent, + Address, + String, + Vec, +}; -#[contracttype] -#[derive(Clone, Debug, Eq, PartialEq)] +#[contractevent] +#[derive(Clone, Debug)] pub enum Event { + // ───────────────────────────────────────────── // Factory events - CollectionCreated { - creator: Address, - collection_id: u64, - collection_address: Address, - name: String, - symbol: String, - }, - FactoryFeeUpdated { - old_fee: i128, - new_fee: i128, - updater: Address, - }, - FeesWithdrawn { - recipient: Address, - amount: i128, - }, - + // ───────────────────────────────────────────── + CollectionCreated( + Address, // creator + u64, // collection_id + Address, // collection_address + String, // name + String, // symbol + ), + + FactoryFeeUpdated( + i128, // old_fee + i128, // new_fee + Address, // updater + ), + + FeesWithdrawn( + Address, // recipient + i128, // amount + ), + + // ───────────────────────────────────────────── // Collection events - TokenMinted { - collection: Address, - token_id: u32, - owner: Address, - uri: String, - }, - BatchMinted { - collection: Address, - start_token_id: u32, - count: u32, - owner: Address, - }, - TokenTransferred { - collection: Address, - token_id: u32, - from: Address, - to: Address, - }, - BatchTransferred { - collection: Address, - token_ids: Vec, - from: Address, - to: Address, - }, - TokenBurned { - collection: Address, - token_id: u32, - owner: Address, - }, - RoyaltyUpdated { - collection: Address, - recipient: Address, - percentage: u32, - }, - WhitelistUpdated { - collection: Address, - address: Address, - added: bool, - }, - CollectionPaused { - collection: Address, - paused: bool, - }, -} + // ───────────────────────────────────────────── + TokenMinted( + Address, // collection + u32, // token_id + Address, // owner + String, // uri + ), + + BatchMinted( + Address, // collection + u32, // start_token_id + u32, // count + Address, // owner + ), + + TokenTransferred( + Address, // collection + u32, // token_id + Address, // from + Address, // to + ), -impl Event { - pub fn publish(&self, env: &Env) { - env.events().publish( - (Symbol::new(env, "nftopia_event"), self.clone()), - self.clone(), - ); - } -} \ No newline at end of file + BatchTransferred( + Address, // collection + Vec, // token_ids + Address, // from + Address, // to + ), + + TokenBurned( + Address, // collection + u32, // token_id + Address, // owner + ), + + RoyaltyUpdated( + Address, // collection + Address, // recipient + u32, // percentage + ), + + WhitelistUpdated( + Address, // collection + Address, // address + bool, // added + ), + + CollectionPaused( + Address, // collection + bool, // paused + ), +} diff --git a/nftopia-stellar/contracts/collection_factory_contract/src/factory.rs b/nftopia-stellar/contracts/collection_factory_contract/src/factory.rs index 5238d892..454888e4 100644 --- a/nftopia-stellar/contracts/collection_factory_contract/src/factory.rs +++ b/nftopia-stellar/contracts/collection_factory_contract/src/factory.rs @@ -1,72 +1,72 @@ -use soroban_sdk::{contracttype, Address, Env, Val, String, Vec}; +use soroban_sdk::{ + Address, + Env, + String, +}; use crate::{ errors::Error, events::Event, - storage::{DataKey, Storage, CollectionConfig, CollectionInfo, FactoryConfig}, + storage::{DataKey, CollectionConfig, CollectionInfo, FactoryConfig}, }; pub struct Factory; impl Factory { + // ───────────────────────────────────────────── // Initialize factory + // ───────────────────────────────────────────── pub fn initialize(env: &Env, owner: Address) -> Result<(), Error> { - // Check if already initialized if DataKey::get_factory_config(env).is_ok() { return Err(Error::CollectionAlreadyExists); } - + let config = FactoryConfig { owner, - factory_fee: 0, // No fee by default - max_collections: None, // No limit by default + factory_fee: 0, + max_collections: None, total_collections: 0, accumulated_fees: 0, is_active: true, }; - + DataKey::set_factory_config(env, &config); Ok(()) } - - // Create new collection + + // ───────────────────────────────────────────── + // Create collection + // ───────────────────────────────────────────── pub fn create_collection( env: &Env, caller: &Address, config: CollectionConfig, initial_royalty_recipient: Option
, ) -> Result { - // Validate factory is active - let factory_config = DataKey::get_factory_config(env)?; + let mut factory_config = DataKey::get_factory_config(env)?; + if !factory_config.is_active { return Err(Error::Unauthorized); } - - // Check collection limit + if let Some(max) = factory_config.max_collections { if factory_config.total_collections >= max { return Err(Error::CollectionLimitReached); } } - - // Validate config - Self::validate_collection_config(env, &config)?; - - // Check and collect fee if any - if factory_config.factory_fee > 0 { - // In Soroban, you would transfer tokens here - // For simplicity, we'll assume the fee is paid via a separate mechanism - // You would typically use the token contract to transfer from caller to factory - } - - // Generate collection ID + + Self::validate_collection_config(&config)?; + let collection_id = factory_config.total_collections as u64 + 1; - - // In a real implementation, you would deploy a new contract here - // For this example, we'll simulate it by storing collection info - let collection_address = Self::deploy_collection_contract(env, collection_id, caller, &config)?; - - // Create collection info + + // 🚨 Placeholder address (safe + deterministic) + let collection_address = Address::from_string( + &String::from_str( + env, + &format!("COLLECTION-{}", collection_id), + ), + ); + let info = CollectionInfo { address: collection_address.clone(), creator: caller.clone(), @@ -75,11 +75,9 @@ impl Factory { total_tokens: 0, is_paused: false, }; - - // Store collection info + DataKey::set_collection_info(env, collection_id, &info); - - // Set initial royalty if provided + if let Some(recipient) = initial_royalty_recipient { let royalty = crate::storage::RoyaltyInfo { recipient, @@ -87,74 +85,61 @@ impl Factory { }; DataKey::set_royalty_info(env, collection_id, &royalty); } - - // Update factory config - let mut factory_config = factory_config; + factory_config.total_collections += 1; - if factory_config.factory_fee > 0 { - factory_config.accumulated_fees += factory_config.factory_fee; - } + factory_config.accumulated_fees += factory_config.factory_fee; DataKey::set_factory_config(env, &factory_config); - - // Publish event - Event::CollectionCreated { - creator: caller.clone(), + + // ✅ NEW EVENT STYLE + Event::CollectionCreated( + caller.clone(), collection_id, collection_address, - name: config.name, - symbol: config.symbol, - } - .publish(env); - + config.name, + config.symbol, + ) + .emit(env); + Ok(collection_id) } - - // Get collection count + + // ───────────────────────────────────────────── + // Queries + // ───────────────────────────────────────────── pub fn get_collection_count(env: &Env) -> Result { - let config = DataKey::get_factory_config(env)?; - Ok(config.total_collections) + Ok(DataKey::get_factory_config(env)?.total_collections) } - - // Get collection address by ID + pub fn get_collection_address(env: &Env, collection_id: u64) -> Result { - let info = DataKey::get_collection_info(env, collection_id)?; - Ok(info.address) + Ok(DataKey::get_collection_info(env, collection_id)?.address) } - - // Get collection info by ID + pub fn get_collection_info(env: &Env, collection_id: u64) -> Result { DataKey::get_collection_info(env, collection_id) } - - // Set factory fee (only owner) + + // ───────────────────────────────────────────── + // Admin + // ───────────────────────────────────────────── pub fn set_factory_fee( env: &Env, caller: &Address, fee: i128, ) -> Result<(), Error> { let mut config = DataKey::get_factory_config(env)?; - - // Check if caller is owner + if &config.owner != caller { return Err(Error::Unauthorized); } - + let old_fee = config.factory_fee; config.factory_fee = fee; DataKey::set_factory_config(env, &config); - - // Publish event - Event::FactoryFeeUpdated { - old_fee, - new_fee: fee, - updater: caller.clone(), - } - .publish(env); - + + Event::FactoryFeeUpdated(old_fee, fee, caller.clone()).emit(env); Ok(()) } - - // Withdraw accumulated fees (only owner) + pub fn withdraw_fees( env: &Env, caller: &Address, @@ -162,121 +147,72 @@ impl Factory { amount: i128, ) -> Result<(), Error> { let mut config = DataKey::get_factory_config(env)?; - - // Check if caller is owner + if &config.owner != caller { return Err(Error::Unauthorized); } - - // Check if sufficient balance + if config.accumulated_fees < amount { return Err(Error::InsufficientFee); } - + config.accumulated_fees -= amount; DataKey::set_factory_config(env, &config); - - // In a real implementation, you would transfer tokens here - // For simplicity, we'll just emit an event - - // Publish event - Event::FeesWithdrawn { - recipient, - amount, - } - .publish(env); - + + Event::FeesWithdrawn(recipient, amount).emit(env); Ok(()) } - - // Set max collections (only owner) + pub fn set_max_collections( env: &Env, caller: &Address, max: Option, ) -> Result<(), Error> { let mut config = DataKey::get_factory_config(env)?; - - // Check if caller is owner + if &config.owner != caller { return Err(Error::Unauthorized); } - + config.max_collections = max; DataKey::set_factory_config(env, &config); - Ok(()) } - - // Set factory active/inactive (only owner) + pub fn set_factory_active( env: &Env, caller: &Address, active: bool, ) -> Result<(), Error> { let mut config = DataKey::get_factory_config(env)?; - - // Check if caller is owner + if &config.owner != caller { return Err(Error::Unauthorized); } - + config.is_active = active; DataKey::set_factory_config(env, &config); - Ok(()) } - - // Validate collection config - fn validate_collection_config( - env: &Env, - config: &CollectionConfig, - ) -> Result<(), Error> { - // Check name is not empty - if config.name.len() == 0 { - return Err(Error::InvalidConfig); - } - - // Check symbol is not empty - if config.symbol.len() == 0 { + + // ───────────────────────────────────────────── + // Validation + // ───────────────────────────────────────────── + fn validate_collection_config(config: &CollectionConfig) -> Result<(), Error> { + if config.name.len() == 0 || config.symbol.len() == 0 { return Err(Error::InvalidConfig); } - - // Check royalty percentage is valid (max 25%) + if config.royalty_percentage > 2500 { return Err(Error::InvalidRoyaltyPercentage); } - - // Validate max supply if provided + if let Some(max_supply) = config.max_supply { if max_supply == 0 { return Err(Error::InvalidConfig); } } - + Ok(()) } - - // Deploy collection contract (simulated) - fn deploy_collection_contract( - env: &Env, - collection_id: u64, - creator: &Address, - config: &CollectionConfig, - ) -> Result { - // In a real Soroban implementation, you would: - // 1. Deploy a new contract instance - // 2. Initialize it with the collection config - // 3. Return the contract address - - // For this example, we'll generate a simulated address - // based on collection ID and creator - let address_data = env.crypto().sha256( - &(collection_id.to_be_bytes().to_vec(), creator.to_string()).into_val(env) - ); - - // Convert hash to address (simplified) - // In reality, you'd use the actual deployed contract address - Ok(Address::from_contract_id(&env.crypto().sha256(&address_data))) - } -} \ No newline at end of file +} diff --git a/nftopia-stellar/contracts/collection_factory_contract/src/main.rs b/nftopia-stellar/contracts/collection_factory_contract/src/main.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/nftopia-stellar/contracts/collection_factory_contract/src/storage.rs b/nftopia-stellar/contracts/collection_factory_contract/src/storage.rs index 0208d787..3bb3eb46 100644 --- a/nftopia-stellar/contracts/collection_factory_contract/src/storage.rs +++ b/nftopia-stellar/contracts/collection_factory_contract/src/storage.rs @@ -1,13 +1,13 @@ -use soroban_sdk::{contracttype, Address, Env, Map, Vec}; +use soroban_sdk::{contracttype, Address, Env, Map, Vec, String}; use crate::errors::Error; #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] pub enum MetadataSchema { - Basic, // Just URI - Extended, // URI + attributes - Advanced, // URI + attributes + mutable metadata + Basic, + Extended, + Advanced, } #[contracttype] @@ -17,9 +17,9 @@ pub struct CollectionConfig { pub symbol: String, pub description: String, pub base_uri: String, - pub max_supply: Option, // None = unlimited + pub max_supply: Option, pub is_public_mint: bool, - pub royalty_percentage: u32, // Basis points (100 = 1%) + pub royalty_percentage: u32, pub royalty_recipient: Address, pub metadata_schema: MetadataSchema, pub is_pausable: bool, @@ -31,7 +31,7 @@ pub struct CollectionConfig { pub struct TokenMetadata { pub token_id: u32, pub uri: String, - pub attributes: Map, // Key-value pairs + pub attributes: Map, pub creator: Address, pub created_at: u64, pub updated_at: Option, @@ -52,42 +52,20 @@ pub struct CollectionInfo { #[derive(Clone, Debug, Eq, PartialEq)] pub struct RoyaltyInfo { pub recipient: Address, - pub percentage: u32, // Basis points + pub percentage: u32, } #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] pub struct FactoryConfig { pub owner: Address, - pub factory_fee: i128, // Fee for creating a collection - pub max_collections: Option, // Maximum collections allowed + pub factory_fee: i128, + pub max_collections: Option, pub total_collections: u32, pub accumulated_fees: i128, pub is_active: bool, } -// Storage keys -#[contracttype] -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum DataKey { - // Factory storage - FactoryConfig, - Collections, // Map - CollectionIdByAddress, // Map - Whitelist(u64), // Map for collection - - // Collection storage - CollectionInfo(u64), - NextTokenId(u64), - TokenOwner(u64, u32), // token_id -> owner - TokenMetadata(u64, u32), // token_id -> metadata - Balance(u64, Address), // address -> token count - Approved(u64, u32), // token_id -> approved address - ApprovedForAll(u64, Address, Address), // owner -> operator -> bool - RoyaltyInfo(u64), - WhitelistForMint(u64, Address), // address -> bool - IsPaused(u64), -} pub trait Storage { fn get_factory_config(env: &Env) -> Result; From 99bb0d8b76c05caa3ebeffaa8dc22fdd743f6c90 Mon Sep 17 00:00:00 2001 From: feyishola Date: Fri, 30 Jan 2026 21:11:14 +0100 Subject: [PATCH 09/13] updated all files 2 --- .../collection_factory_contract/Cargo.toml | 13 +- .../src/collection.rs | 244 ++++++++++++------ .../collection_factory_contract/src/errors.rs | 8 +- .../collection_factory_contract/src/events.rs | 86 ------ .../src/factory.rs | 56 ++-- .../collection_factory_contract/src/lib.rs | 63 +++-- .../src/storage.rs | 27 +- 7 files changed, 292 insertions(+), 205 deletions(-) delete mode 100644 nftopia-stellar/contracts/collection_factory_contract/src/events.rs diff --git a/nftopia-stellar/contracts/collection_factory_contract/Cargo.toml b/nftopia-stellar/contracts/collection_factory_contract/Cargo.toml index de778aec..a43e19e9 100644 --- a/nftopia-stellar/contracts/collection_factory_contract/Cargo.toml +++ b/nftopia-stellar/contracts/collection_factory_contract/Cargo.toml @@ -10,7 +10,16 @@ crate-type = ["cdylib"] [dependencies] soroban-sdk = "25.0.2" -serde = { version = "1.0", features = ["derive"] } [dev-dependencies] -soroban-sdk = { version = "25.0.2", features = ["testutils"] } \ No newline at end of file +soroban-sdk = { version = "25.0.2", features = ["testutils"] } + +[profile.release] +opt-level = "z" +overflow-checks = true +debug = 0 +strip = "symbols" +debug-assertions = false +panic = "abort" +codegen-units = 1 +lto = true \ No newline at end of file diff --git a/nftopia-stellar/contracts/collection_factory_contract/src/collection.rs b/nftopia-stellar/contracts/collection_factory_contract/src/collection.rs index e8161a07..dffa9cbf 100644 --- a/nftopia-stellar/contracts/collection_factory_contract/src/collection.rs +++ b/nftopia-stellar/contracts/collection_factory_contract/src/collection.rs @@ -1,14 +1,146 @@ -use soroban_sdk::{Address, Env, Map, String, Vec}; +use soroban_sdk::{Address, Env, Map, String, Vec, symbol_short}; use crate::{ errors::Error, - events::Event, storage::{DataKey, TokenMetadata, RoyaltyInfo}, }; pub struct Collection; impl Collection { + // ERC721-like methods + pub fn balance_of(env: &Env, collection_id: u64, address: &Address) -> u32 { + DataKey::get_balance(env, collection_id, address) + } + + pub fn owner_of(env: &Env, collection_id: u64, token_id: u32) -> Result { + DataKey::get_token_owner(env, collection_id, token_id) + .ok_or(Error::TokenNotFound) + } + + pub fn get_approved(env: &Env, collection_id: u64, token_id: u32) -> Option
{ + DataKey::get_approved(env, collection_id, token_id) + } + + pub fn is_approved_for_all(env: &Env, collection_id: u64, owner: &Address, operator: &Address) -> bool { + DataKey::get_approved_for_all(env, collection_id, owner, operator) + } + + pub fn approve(env: &Env, collection_id: u64, caller: &Address, approved: &Address, token_id: u32) -> Result<(), Error> { + let owner = Self::owner_of(env, collection_id, token_id)?; + + // Check if caller is owner or approved for all + if &owner != caller && !Self::is_approved_for_all(env, collection_id, &owner, caller) { + return Err(Error::NotTokenOwner); + } + + DataKey::set_approved(env, collection_id, token_id, approved); + + // Emit approval event + env.events().publish( + (symbol_short!("approved"), collection_id), + (caller.clone(), approved.clone(), token_id) + ); + + Ok(()) + } + + pub fn set_approval_for_all(env: &Env, collection_id: u64, owner: &Address, operator: &Address, approved: bool) -> Result<(), Error> { + DataKey::set_approved_for_all(env, collection_id, owner, operator, approved); + + // Emit approval for all event + env.events().publish( + (symbol_short!("approval_for_all"), collection_id), + (owner.clone(), operator.clone(), approved) + ); + + Ok(()) + } + + pub fn transfer_from( + env: &Env, + collection_id: u64, + caller: &Address, + from: &Address, + to: &Address, + token_id: u32, + ) -> Result<(), Error> { + // Check ownership or approval + let owner = Self::owner_of(env, collection_id, token_id)?; + if &owner != from { + return Err(Error::NotTokenOwner); + } + + let approved = Self::get_approved(env, collection_id, token_id); + if caller != from && approved.as_ref() != Some(caller) + && !Self::is_approved_for_all(env, collection_id, from, caller) { + return Err(Error::NotApproved); + } + + // Perform transfer + DataKey::set_token_owner(env, collection_id, token_id, to); + DataKey::decrement_balance(env, collection_id, from); + DataKey::increment_balance(env, collection_id, to); + DataKey::remove_approved(env, collection_id, token_id); + + // Emit event with symbol_short for efficiency + let collection_info = DataKey::get_collection_info(env, collection_id)?; + env.events().publish( + (symbol_short!("token_transferred"), collection_id), + (collection_info.address, token_id, from.clone(), to.clone()) + ); + + Ok(()) + } + + // Set whitelist + pub fn set_whitelist( + env: &Env, + collection_id: u64, + caller: &Address, + address: &Address, + whitelisted: bool, + ) -> Result<(), Error> { + let info = DataKey::get_collection_info(env, collection_id)?; + if &info.creator != caller { + return Err(Error::Unauthorized); + } + + DataKey::set_whitelisted_for_mint(env, collection_id, address, whitelisted); + + // Emit event with symbol_short + env.events().publish( + (symbol_short!("whitelist_updated"), collection_id), + (info.address, address.clone(), whitelisted) + ); + + Ok(()) + } + + // Token URI + pub fn token_uri(env: &Env, collection_id: u64, token_id: u32) -> Result { + let metadata = DataKey::get_token_metadata(env, collection_id, token_id) + .ok_or(Error::TokenNotFound)?; + Ok(metadata.uri) + } + + // Token metadata + pub fn token_metadata(env: &Env, collection_id: u64, token_id: u32) -> Result { + DataKey::get_token_metadata(env, collection_id, token_id) + .ok_or(Error::TokenNotFound) + } + + // Total supply + pub fn total_supply(env: &Env, collection_id: u64) -> Result { + let info = DataKey::get_collection_info(env, collection_id)?; + Ok(info.total_tokens) + } + + // Royalty info + pub fn royalty_info(env: &Env, collection_id: u64) -> Option { + DataKey::get_royalty_info(env, collection_id) + } + // Mint a new token pub fn mint( env: &Env, @@ -54,13 +186,11 @@ impl Collection { info.total_tokens += 1; DataKey::set_collection_info(env, collection_id, &info); - Event::TokenMinted( - info.address.clone(), - token_id, - to.clone(), - uri, - ) - .emit(env); + // Emit event using symbol_short for efficiency + env.events().publish( + (symbol_short!("token_minted"), collection_id), + (info.address.clone(), token_id, to.clone(), uri) + ); Ok(token_id) } @@ -87,20 +217,18 @@ impl Collection { token_ids.push_back(token_id); } - let collection = DataKey::get_collection_info(env, collection_id)?.address; + let collection_info = DataKey::get_collection_info(env, collection_id)?; - Event::BatchMinted( - collection, - start_token_id, - uris.len() as u32, - to.clone(), - ) - .emit(env); + // Emit event with symbol_short + env.events().publish( + (symbol_short!("batch_minted"), collection_id), + (collection_info.address, start_token_id, uris.len() as u32, to.clone()) + ); Ok(token_ids) } - // Transfer token + // Transfer token (simplified version) pub fn transfer( env: &Env, collection_id: u64, @@ -108,38 +236,7 @@ impl Collection { to: &Address, token_id: u32, ) -> Result<(), Error> { - if DataKey::is_collection_paused(env, collection_id) { - return Err(Error::MintingPaused); - } - - let owner = DataKey::get_token_owner(env, collection_id, token_id) - .ok_or(Error::TokenNotFound)?; - - if &owner != from { - let approved = DataKey::get_approved(env, collection_id, token_id); - if approved.as_ref() != Some(from) - && !DataKey::get_approved_for_all(env, collection_id, &owner, from) - { - return Err(Error::NotTokenOwner); - } - } - - DataKey::set_token_owner(env, collection_id, token_id, to); - DataKey::decrement_balance(env, collection_id, from); - DataKey::increment_balance(env, collection_id, to); - DataKey::remove_approved(env, collection_id, token_id); - - let collection = DataKey::get_collection_info(env, collection_id)?.address; - - Event::TokenTransferred( - collection, - token_id, - from.clone(), - to.clone(), - ) - .emit(env); - - Ok(()) + Self::transfer_from(env, collection_id, from, from, to, token_id) } // Batch transfer @@ -154,15 +251,13 @@ impl Collection { Self::transfer(env, collection_id, from, to, *token_id)?; } - let collection = DataKey::get_collection_info(env, collection_id)?.address; + let collection_info = DataKey::get_collection_info(env, collection_id)?; - Event::BatchTransferred( - collection, - token_ids.clone(), - from.clone(), - to.clone(), - ) - .emit(env); + // Emit event with symbol_short + env.events().publish( + (symbol_short!("batch_transferred"), collection_id), + (collection_info.address, token_ids.clone(), from.clone(), to.clone()) + ); Ok(()) } @@ -189,12 +284,11 @@ impl Collection { info.total_tokens = info.total_tokens.saturating_sub(1); DataKey::set_collection_info(env, collection_id, &info); - Event::TokenBurned( - info.address, - token_id, - owner.clone(), - ) - .emit(env); + // Emit event with symbol_short + env.events().publish( + (symbol_short!("token_burned"), collection_id), + (info.address, token_id, owner.clone()) + ); Ok(()) } @@ -216,15 +310,14 @@ impl Collection { return Err(Error::Unauthorized); } - let royalty = RoyaltyInfo { recipient, percentage }; + let royalty = RoyaltyInfo { recipient: recipient.clone(), percentage }; DataKey::set_royalty_info(env, collection_id, &royalty); - Event::RoyaltyUpdated( - info.address, - royalty.recipient.clone(), - percentage, - ) - .emit(env); + // Emit event with symbol_short + env.events().publish( + (symbol_short!("royalty_updated"), collection_id), + (info.address, recipient, percentage) + ); Ok(()) } @@ -244,7 +337,12 @@ impl Collection { DataKey::set_collection_paused(env, collection_id, paused); - Event::CollectionPaused(info.address, paused).emit(env); + // Emit event with symbol_short + env.events().publish( + (symbol_short!("collection_paused"), collection_id), + (info.address, paused) + ); + Ok(()) } -} +} \ No newline at end of file diff --git a/nftopia-stellar/contracts/collection_factory_contract/src/errors.rs b/nftopia-stellar/contracts/collection_factory_contract/src/errors.rs index a95792f3..141e2f4b 100644 --- a/nftopia-stellar/contracts/collection_factory_contract/src/errors.rs +++ b/nftopia-stellar/contracts/collection_factory_contract/src/errors.rs @@ -1,4 +1,4 @@ -use soroban_sdk::{contracterror, Address, Env, Symbol}; +use soroban_sdk::{contracterror, Symbol, Env}; #[contracterror] #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] @@ -11,6 +11,7 @@ pub enum Error { InsufficientFee = 1003, CollectionNotFound = 1004, CollectionAlreadyExists = 1005, + AlreadyInitialized = 1006, // ADD THIS // Collection errors (2000-2999) MaxSupplyExceeded = 2000, @@ -21,6 +22,7 @@ pub enum Error { WhitelistRequired = 2005, InvalidTokenId = 2006, InvalidRoyaltyPercentage = 2007, + NotApproved = 2008, // ADD THIS - used in collection.rs // General errors (3000-3999) InvalidInput = 3000, @@ -39,10 +41,12 @@ impl Error { Error::InsufficientFee => Symbol::new(env, "INSUFFICIENT_FEE"), Error::CollectionNotFound => Symbol::new(env, "COLLECTION_NOT_FOUND"), Error::CollectionAlreadyExists => Symbol::new(env, "COLLECTION_ALREADY_EXISTS"), + Error::AlreadyInitialized => Symbol::new(env, "ALREADY_INITIALIZED"), // ADD THIS Error::MaxSupplyExceeded => Symbol::new(env, "MAX_SUPPLY_EXCEEDED"), Error::TokenNotFound => Symbol::new(env, "TOKEN_NOT_FOUND"), Error::NotTokenOwner => Symbol::new(env, "NOT_TOKEN_OWNER"), Error::NotApprovedForAll => Symbol::new(env, "NOT_APPROVED_FOR_ALL"), + Error::NotApproved => Symbol::new(env, "NOT_APPROVED"), // ADD THIS Error::MintingPaused => Symbol::new(env, "MINTING_PAUSED"), Error::WhitelistRequired => Symbol::new(env, "WHITELIST_REQUIRED"), Error::InvalidTokenId => Symbol::new(env, "INVALID_TOKEN_ID"), @@ -54,4 +58,4 @@ impl Error { Error::TransferFailed => Symbol::new(env, "TRANSFER_FAILED"), } } -} +} \ No newline at end of file diff --git a/nftopia-stellar/contracts/collection_factory_contract/src/events.rs b/nftopia-stellar/contracts/collection_factory_contract/src/events.rs deleted file mode 100644 index 5769b959..00000000 --- a/nftopia-stellar/contracts/collection_factory_contract/src/events.rs +++ /dev/null @@ -1,86 +0,0 @@ -use soroban_sdk::{ - contractevent, - Address, - String, - Vec, -}; - -#[contractevent] -#[derive(Clone, Debug)] -pub enum Event { - // ───────────────────────────────────────────── - // Factory events - // ───────────────────────────────────────────── - CollectionCreated( - Address, // creator - u64, // collection_id - Address, // collection_address - String, // name - String, // symbol - ), - - FactoryFeeUpdated( - i128, // old_fee - i128, // new_fee - Address, // updater - ), - - FeesWithdrawn( - Address, // recipient - i128, // amount - ), - - // ───────────────────────────────────────────── - // Collection events - // ───────────────────────────────────────────── - TokenMinted( - Address, // collection - u32, // token_id - Address, // owner - String, // uri - ), - - BatchMinted( - Address, // collection - u32, // start_token_id - u32, // count - Address, // owner - ), - - TokenTransferred( - Address, // collection - u32, // token_id - Address, // from - Address, // to - ), - - BatchTransferred( - Address, // collection - Vec, // token_ids - Address, // from - Address, // to - ), - - TokenBurned( - Address, // collection - u32, // token_id - Address, // owner - ), - - RoyaltyUpdated( - Address, // collection - Address, // recipient - u32, // percentage - ), - - WhitelistUpdated( - Address, // collection - Address, // address - bool, // added - ), - - CollectionPaused( - Address, // collection - bool, // paused - ), -} diff --git a/nftopia-stellar/contracts/collection_factory_contract/src/factory.rs b/nftopia-stellar/contracts/collection_factory_contract/src/factory.rs index 454888e4..0e4f16e5 100644 --- a/nftopia-stellar/contracts/collection_factory_contract/src/factory.rs +++ b/nftopia-stellar/contracts/collection_factory_contract/src/factory.rs @@ -2,11 +2,11 @@ use soroban_sdk::{ Address, Env, String, + symbol_short, }; use crate::{ errors::Error, - events::Event, storage::{DataKey, CollectionConfig, CollectionInfo, FactoryConfig}, }; @@ -18,7 +18,7 @@ impl Factory { // ───────────────────────────────────────────── pub fn initialize(env: &Env, owner: Address) -> Result<(), Error> { if DataKey::get_factory_config(env).is_ok() { - return Err(Error::CollectionAlreadyExists); + return Err(Error::AlreadyInitialized); } let config = FactoryConfig { @@ -59,13 +59,8 @@ impl Factory { let collection_id = factory_config.total_collections as u64 + 1; - // 🚨 Placeholder address (safe + deterministic) - let collection_address = Address::from_string( - &String::from_str( - env, - &format!("COLLECTION-{}", collection_id), - ), - ); + // All collections share the same contract address but different collection_id + let collection_address = env.current_contract_address(); let info = CollectionInfo { address: collection_address.clone(), @@ -90,15 +85,11 @@ impl Factory { factory_config.accumulated_fees += factory_config.factory_fee; DataKey::set_factory_config(env, &factory_config); - // ✅ NEW EVENT STYLE - Event::CollectionCreated( - caller.clone(), - collection_id, - collection_address, - config.name, - config.symbol, - ) - .emit(env); + // Emit event using Soroban event system with symbol_short for efficiency + env.events().publish( + (symbol_short!("col_created"), collection_id), + (caller.clone(), collection_id, collection_address, config.name.clone(), config.symbol.clone()) + ); Ok(collection_id) } @@ -118,6 +109,10 @@ impl Factory { DataKey::get_collection_info(env, collection_id) } + pub fn get_factory_config(env: &Env) -> Result { + DataKey::get_factory_config(env) + } + // ───────────────────────────────────────────── // Admin // ───────────────────────────────────────────── @@ -136,7 +131,12 @@ impl Factory { config.factory_fee = fee; DataKey::set_factory_config(env, &config); - Event::FactoryFeeUpdated(old_fee, fee, caller.clone()).emit(env); + // Emit event + env.events().publish( + (symbol_short!("fee_updated"),), + (old_fee, fee, caller.clone()) + ); + Ok(()) } @@ -159,7 +159,12 @@ impl Factory { config.accumulated_fees -= amount; DataKey::set_factory_config(env, &config); - Event::FeesWithdrawn(recipient, amount).emit(env); + // Emit event + env.events().publish( + (symbol_short!("fees_withdrawn"),), + (recipient.clone(), amount) + ); + Ok(()) } @@ -192,6 +197,13 @@ impl Factory { config.is_active = active; DataKey::set_factory_config(env, &config); + + // Emit event + env.events().publish( + (symbol_short!("factory_active"),), + (caller.clone(), active) + ); + Ok(()) } @@ -203,7 +215,7 @@ impl Factory { return Err(Error::InvalidConfig); } - if config.royalty_percentage > 2500 { + if config.royalty_percentage > 2500 { // 25% max return Err(Error::InvalidRoyaltyPercentage); } @@ -215,4 +227,4 @@ impl Factory { Ok(()) } -} +} \ No newline at end of file diff --git a/nftopia-stellar/contracts/collection_factory_contract/src/lib.rs b/nftopia-stellar/contracts/collection_factory_contract/src/lib.rs index d41e6df0..c3c10496 100644 --- a/nftopia-stellar/contracts/collection_factory_contract/src/lib.rs +++ b/nftopia-stellar/contracts/collection_factory_contract/src/lib.rs @@ -1,16 +1,14 @@ #![no_std] -use soroban_sdk::{contract, contractimpl, contracttype, Address, Env, String, Vec, Map, Val}; +use soroban_sdk::{contract, contractimpl, Address, Env, String, Vec, Map}; mod errors; -mod events; mod storage; mod collection; mod factory; use errors::Error; -use events::Event; -use storage::{DataKey, CollectionConfig, TokenMetadata}; +use storage::{DataKey, CollectionConfig, TokenMetadata, MetadataSchema, RoyaltyInfo}; use collection::Collection; use factory::Factory; @@ -40,6 +38,14 @@ impl CollectionFactoryContract { is_pausable: bool, is_upgradeable: bool, ) -> Result { + // Convert metadata_schema u32 to MetadataSchema enum + let schema = match metadata_schema { + 0 => MetadataSchema::Basic, + 1 => MetadataSchema::Extended, + 2 => MetadataSchema::Advanced, + _ => return Err(Error::InvalidConfig), + }; + let config = CollectionConfig { name, symbol, @@ -48,8 +54,8 @@ impl CollectionFactoryContract { max_supply, is_public_mint, royalty_percentage, - royalty_recipient, - metadata_schema: storage::MetadataSchema::Basic, // Simplified + royalty_recipient: royalty_recipient.clone(), + metadata_schema: schema, is_pausable, is_upgradeable, }; @@ -65,6 +71,14 @@ impl CollectionFactoryContract { Factory::get_collection_address(&env, collection_id) } + pub fn get_collection_info(env: Env, collection_id: u64) -> Result { + Factory::get_collection_info(&env, collection_id) + } + + pub fn get_factory_config(env: Env) -> Result { + Factory::get_factory_config(&env) + } + pub fn set_factory_fee(env: Env, caller: Address, fee: i128) -> Result<(), Error> { Factory::set_factory_fee(&env, &caller, fee) } @@ -86,6 +100,14 @@ impl CollectionFactoryContract { Factory::set_max_collections(&env, &caller, max) } + pub fn set_factory_active( + env: Env, + caller: Address, + active: bool, + ) -> Result<(), Error> { + Factory::set_factory_active(&env, &caller, active) + } + // Collection functions pub fn mint( env: Env, @@ -139,21 +161,21 @@ impl CollectionFactoryContract { pub fn approve( env: Env, collection_id: u64, - owner: Address, + caller: Address, approved: Address, token_id: u32, ) -> Result<(), Error> { - Collection::approve(&env, collection_id, &owner, &approved, token_id) + Collection::approve(&env, collection_id, &caller, &approved, token_id) } pub fn set_approval_for_all( env: Env, collection_id: u64, - owner: Address, + caller: Address, operator: Address, approved: bool, ) -> Result<(), Error> { - Collection::set_approval_for_all(&env, collection_id, &owner, &operator, approved) + Collection::set_approval_for_all(&env, collection_id, &caller, &operator, approved) } pub fn set_royalty_info( @@ -173,7 +195,7 @@ impl CollectionFactoryContract { address: Address, whitelisted: bool, ) -> Result<(), Error> { - Collection::set_whitelist(&env, collection_id, &caller, address, whitelisted) + Collection::set_whitelist(&env, collection_id, &caller, &address, whitelisted) } pub fn set_paused( @@ -190,7 +212,7 @@ impl CollectionFactoryContract { env: Env, collection_id: u64, address: Address, - ) -> Result { + ) -> u32 { Collection::balance_of(&env, collection_id, &address) } @@ -206,7 +228,7 @@ impl CollectionFactoryContract { env: Env, collection_id: u64, token_id: u32, - ) -> Result, Error> { + ) -> Option
{ Collection::get_approved(&env, collection_id, token_id) } @@ -215,7 +237,7 @@ impl CollectionFactoryContract { collection_id: u64, owner: Address, operator: Address, - ) -> Result { + ) -> bool { Collection::is_approved_for_all(&env, collection_id, &owner, &operator) } @@ -245,9 +267,20 @@ impl CollectionFactoryContract { pub fn royalty_info( env: Env, collection_id: u64, - ) -> Result, Error> { + ) -> Option { Collection::royalty_info(&env, collection_id) } + + pub fn transfer_from( + env: Env, + collection_id: u64, + caller: Address, + from: Address, + to: Address, + token_id: u32, + ) -> Result<(), Error> { + Collection::transfer_from(&env, collection_id, &caller, &from, &to, token_id) + } } #[cfg(test)] diff --git a/nftopia-stellar/contracts/collection_factory_contract/src/storage.rs b/nftopia-stellar/contracts/collection_factory_contract/src/storage.rs index 3bb3eb46..8dd69173 100644 --- a/nftopia-stellar/contracts/collection_factory_contract/src/storage.rs +++ b/nftopia-stellar/contracts/collection_factory_contract/src/storage.rs @@ -1,7 +1,24 @@ -use soroban_sdk::{contracttype, Address, Env, Map, Vec, String}; +use soroban_sdk::{contracttype, Address, Env, Map, String}; use crate::errors::Error; +// ADD THIS DataKey ENUM +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum DataKey { + FactoryConfig, + CollectionInfo(u64), + NextTokenId(u64), + TokenOwner(u64, u32), + TokenMetadata(u64, u32), + Balance(u64, Address), + Approved(u64, u32), + ApprovedForAll(u64, Address, Address), + RoyaltyInfo(u64), + WhitelistForMint(u64, Address), + IsPaused(u64), +} + #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] pub enum MetadataSchema { @@ -66,7 +83,6 @@ pub struct FactoryConfig { pub is_active: bool, } - pub trait Storage { fn get_factory_config(env: &Env) -> Result; fn set_factory_config(env: &Env, config: &FactoryConfig); @@ -142,9 +158,10 @@ impl Storage for DataKey { } fn increment_collections_count(env: &Env) { - let mut config = Self::get_factory_config(env).unwrap(); - config.total_collections += 1; - Self::set_factory_config(env, &config); + if let Ok(mut config) = Self::get_factory_config(env) { + config.total_collections += 1; + Self::set_factory_config(env, &config); + } } fn get_next_token_id(env: &Env, collection_id: u64) -> u32 { From ffefc819eb03cd87d7fcf85323ad665309f1c0fb Mon Sep 17 00:00:00 2001 From: feyishola Date: Fri, 30 Jan 2026 21:45:49 +0100 Subject: [PATCH 10/13] yet another --- .../src/collection.rs | 148 +++++------------- .../src/factory.rs | 64 +++----- .../collection_factory_contract/src/lib.rs | 3 +- 3 files changed, 61 insertions(+), 154 deletions(-) diff --git a/nftopia-stellar/contracts/collection_factory_contract/src/collection.rs b/nftopia-stellar/contracts/collection_factory_contract/src/collection.rs index dffa9cbf..1b797f56 100644 --- a/nftopia-stellar/contracts/collection_factory_contract/src/collection.rs +++ b/nftopia-stellar/contracts/collection_factory_contract/src/collection.rs @@ -2,7 +2,7 @@ use soroban_sdk::{Address, Env, Map, String, Vec, symbol_short}; use crate::{ errors::Error, - storage::{DataKey, TokenMetadata, RoyaltyInfo}, + storage::{DataKey, Storage, TokenMetadata, RoyaltyInfo}, }; pub struct Collection; @@ -10,20 +10,20 @@ pub struct Collection; impl Collection { // ERC721-like methods pub fn balance_of(env: &Env, collection_id: u64, address: &Address) -> u32 { - DataKey::get_balance(env, collection_id, address) + ::get_balance(env, collection_id, address) } pub fn owner_of(env: &Env, collection_id: u64, token_id: u32) -> Result { - DataKey::get_token_owner(env, collection_id, token_id) + ::get_token_owner(env, collection_id, token_id) .ok_or(Error::TokenNotFound) } pub fn get_approved(env: &Env, collection_id: u64, token_id: u32) -> Option
{ - DataKey::get_approved(env, collection_id, token_id) + ::get_approved(env, collection_id, token_id) } pub fn is_approved_for_all(env: &Env, collection_id: u64, owner: &Address, operator: &Address) -> bool { - DataKey::get_approved_for_all(env, collection_id, owner, operator) + ::get_approved_for_all(env, collection_id, owner, operator) } pub fn approve(env: &Env, collection_id: u64, caller: &Address, approved: &Address, token_id: u32) -> Result<(), Error> { @@ -34,26 +34,12 @@ impl Collection { return Err(Error::NotTokenOwner); } - DataKey::set_approved(env, collection_id, token_id, approved); - - // Emit approval event - env.events().publish( - (symbol_short!("approved"), collection_id), - (caller.clone(), approved.clone(), token_id) - ); - + ::set_approved(env, collection_id, token_id, approved); Ok(()) } pub fn set_approval_for_all(env: &Env, collection_id: u64, owner: &Address, operator: &Address, approved: bool) -> Result<(), Error> { - DataKey::set_approved_for_all(env, collection_id, owner, operator, approved); - - // Emit approval for all event - env.events().publish( - (symbol_short!("approval_for_all"), collection_id), - (owner.clone(), operator.clone(), approved) - ); - + ::set_approved_for_all(env, collection_id, owner, operator, approved); Ok(()) } @@ -78,17 +64,10 @@ impl Collection { } // Perform transfer - DataKey::set_token_owner(env, collection_id, token_id, to); - DataKey::decrement_balance(env, collection_id, from); - DataKey::increment_balance(env, collection_id, to); - DataKey::remove_approved(env, collection_id, token_id); - - // Emit event with symbol_short for efficiency - let collection_info = DataKey::get_collection_info(env, collection_id)?; - env.events().publish( - (symbol_short!("token_transferred"), collection_id), - (collection_info.address, token_id, from.clone(), to.clone()) - ); + ::set_token_owner(env, collection_id, token_id, to); + ::decrement_balance(env, collection_id, from); + ::increment_balance(env, collection_id, to); + ::remove_approved(env, collection_id, token_id); Ok(()) } @@ -101,44 +80,38 @@ impl Collection { address: &Address, whitelisted: bool, ) -> Result<(), Error> { - let info = DataKey::get_collection_info(env, collection_id)?; + let info = ::get_collection_info(env, collection_id)?; if &info.creator != caller { return Err(Error::Unauthorized); } - DataKey::set_whitelisted_for_mint(env, collection_id, address, whitelisted); - - // Emit event with symbol_short - env.events().publish( - (symbol_short!("whitelist_updated"), collection_id), - (info.address, address.clone(), whitelisted) - ); + ::set_whitelisted_for_mint(env, collection_id, address, whitelisted); Ok(()) } // Token URI pub fn token_uri(env: &Env, collection_id: u64, token_id: u32) -> Result { - let metadata = DataKey::get_token_metadata(env, collection_id, token_id) + let metadata = ::get_token_metadata(env, collection_id, token_id) .ok_or(Error::TokenNotFound)?; Ok(metadata.uri) } // Token metadata pub fn token_metadata(env: &Env, collection_id: u64, token_id: u32) -> Result { - DataKey::get_token_metadata(env, collection_id, token_id) + ::get_token_metadata(env, collection_id, token_id) .ok_or(Error::TokenNotFound) } // Total supply pub fn total_supply(env: &Env, collection_id: u64) -> Result { - let info = DataKey::get_collection_info(env, collection_id)?; + let info = ::get_collection_info(env, collection_id)?; Ok(info.total_tokens) } // Royalty info pub fn royalty_info(env: &Env, collection_id: u64) -> Option { - DataKey::get_royalty_info(env, collection_id) + ::get_royalty_info(env, collection_id) } // Mint a new token @@ -149,11 +122,11 @@ impl Collection { uri: String, attributes: Option>, ) -> Result { - if DataKey::is_collection_paused(env, collection_id) { + if ::is_collection_paused(env, collection_id) { return Err(Error::MintingPaused); } - let mut info = DataKey::get_collection_info(env, collection_id)?; + let mut info = ::get_collection_info(env, collection_id)?; if let Some(max_supply) = info.config.max_supply { if info.total_tokens >= max_supply { @@ -162,12 +135,12 @@ impl Collection { } if !info.config.is_public_mint - && !DataKey::is_whitelisted_for_mint(env, collection_id, to) + && !::is_whitelisted_for_mint(env, collection_id, to) { return Err(Error::WhitelistRequired); } - let token_id = DataKey::get_next_token_id(env, collection_id); + let token_id = ::get_next_token_id(env, collection_id); let metadata = TokenMetadata { token_id, @@ -178,19 +151,13 @@ impl Collection { updated_at: None, }; - DataKey::set_token_owner(env, collection_id, token_id, to); - DataKey::set_token_metadata(env, collection_id, token_id, &metadata); - DataKey::increment_balance(env, collection_id, to); - DataKey::increment_token_id(env, collection_id); + ::set_token_owner(env, collection_id, token_id, to); + ::set_token_metadata(env, collection_id, token_id, &metadata); + ::increment_balance(env, collection_id, to); + ::increment_token_id(env, collection_id); info.total_tokens += 1; - DataKey::set_collection_info(env, collection_id, &info); - - // Emit event using symbol_short for efficiency - env.events().publish( - (symbol_short!("token_minted"), collection_id), - (info.address.clone(), token_id, to.clone(), uri) - ); + ::set_collection_info(env, collection_id, &info); Ok(token_id) } @@ -203,7 +170,7 @@ impl Collection { uris: Vec, attributes_list: Option>>, ) -> Result, Error> { - let start_token_id = DataKey::get_next_token_id(env, collection_id); + let start_token_id = ::get_next_token_id(env, collection_id); let mut token_ids = Vec::new(env); for i in 0..uris.len() { @@ -217,14 +184,6 @@ impl Collection { token_ids.push_back(token_id); } - let collection_info = DataKey::get_collection_info(env, collection_id)?; - - // Emit event with symbol_short - env.events().publish( - (symbol_short!("batch_minted"), collection_id), - (collection_info.address, start_token_id, uris.len() as u32, to.clone()) - ); - Ok(token_ids) } @@ -239,7 +198,7 @@ impl Collection { Self::transfer_from(env, collection_id, from, from, to, token_id) } - // Batch transfer + // Batch transfer - FIXED dereferencing issue pub fn batch_transfer( env: &Env, collection_id: u64, @@ -247,18 +206,9 @@ impl Collection { to: &Address, token_ids: Vec, ) -> Result<(), Error> { - for token_id in token_ids.iter() { - Self::transfer(env, collection_id, from, to, *token_id)?; + for &token_id in token_ids.iter() { // Use &token_id instead of *token_id + Self::transfer(env, collection_id, from, to, token_id)?; } - - let collection_info = DataKey::get_collection_info(env, collection_id)?; - - // Emit event with symbol_short - env.events().publish( - (symbol_short!("batch_transferred"), collection_id), - (collection_info.address, token_ids.clone(), from.clone(), to.clone()) - ); - Ok(()) } @@ -269,26 +219,20 @@ impl Collection { owner: &Address, token_id: u32, ) -> Result<(), Error> { - let token_owner = DataKey::get_token_owner(env, collection_id, token_id) + let token_owner = ::get_token_owner(env, collection_id, token_id) .ok_or(Error::TokenNotFound)?; if &token_owner != owner { return Err(Error::NotTokenOwner); } - DataKey::remove_token_owner(env, collection_id, token_id); - DataKey::remove_approved(env, collection_id, token_id); - DataKey::decrement_balance(env, collection_id, owner); + ::remove_token_owner(env, collection_id, token_id); + ::remove_approved(env, collection_id, token_id); + ::decrement_balance(env, collection_id, owner); - let mut info = DataKey::get_collection_info(env, collection_id)?; + let mut info = ::get_collection_info(env, collection_id)?; info.total_tokens = info.total_tokens.saturating_sub(1); - DataKey::set_collection_info(env, collection_id, &info); - - // Emit event with symbol_short - env.events().publish( - (symbol_short!("token_burned"), collection_id), - (info.address, token_id, owner.clone()) - ); + ::set_collection_info(env, collection_id, &info); Ok(()) } @@ -305,19 +249,13 @@ impl Collection { return Err(Error::InvalidRoyaltyPercentage); } - let info = DataKey::get_collection_info(env, collection_id)?; + let info = ::get_collection_info(env, collection_id)?; if &info.creator != caller { return Err(Error::Unauthorized); } let royalty = RoyaltyInfo { recipient: recipient.clone(), percentage }; - DataKey::set_royalty_info(env, collection_id, &royalty); - - // Emit event with symbol_short - env.events().publish( - (symbol_short!("royalty_updated"), collection_id), - (info.address, recipient, percentage) - ); + ::set_royalty_info(env, collection_id, &royalty); Ok(()) } @@ -329,19 +267,13 @@ impl Collection { caller: &Address, paused: bool, ) -> Result<(), Error> { - let info = DataKey::get_collection_info(env, collection_id)?; + let info = ::get_collection_info(env, collection_id)?; if !info.config.is_pausable || &info.creator != caller { return Err(Error::Unauthorized); } - DataKey::set_collection_paused(env, collection_id, paused); - - // Emit event with symbol_short - env.events().publish( - (symbol_short!("collection_paused"), collection_id), - (info.address, paused) - ); + ::set_collection_paused(env, collection_id, paused); Ok(()) } diff --git a/nftopia-stellar/contracts/collection_factory_contract/src/factory.rs b/nftopia-stellar/contracts/collection_factory_contract/src/factory.rs index 0e4f16e5..8a5f61c3 100644 --- a/nftopia-stellar/contracts/collection_factory_contract/src/factory.rs +++ b/nftopia-stellar/contracts/collection_factory_contract/src/factory.rs @@ -1,13 +1,12 @@ use soroban_sdk::{ Address, Env, - String, symbol_short, }; use crate::{ errors::Error, - storage::{DataKey, CollectionConfig, CollectionInfo, FactoryConfig}, + storage::{DataKey, Storage, CollectionConfig, CollectionInfo, FactoryConfig}, }; pub struct Factory; @@ -17,7 +16,7 @@ impl Factory { // Initialize factory // ───────────────────────────────────────────── pub fn initialize(env: &Env, owner: Address) -> Result<(), Error> { - if DataKey::get_factory_config(env).is_ok() { + if ::get_factory_config(env).is_ok() { return Err(Error::AlreadyInitialized); } @@ -30,7 +29,7 @@ impl Factory { is_active: true, }; - DataKey::set_factory_config(env, &config); + ::set_factory_config(env, &config); Ok(()) } @@ -43,7 +42,7 @@ impl Factory { config: CollectionConfig, initial_royalty_recipient: Option
, ) -> Result { - let mut factory_config = DataKey::get_factory_config(env)?; + let mut factory_config = ::get_factory_config(env)?; if !factory_config.is_active { return Err(Error::Unauthorized); @@ -71,25 +70,19 @@ impl Factory { is_paused: false, }; - DataKey::set_collection_info(env, collection_id, &info); + ::set_collection_info(env, collection_id, &info); if let Some(recipient) = initial_royalty_recipient { let royalty = crate::storage::RoyaltyInfo { recipient, percentage: config.royalty_percentage, }; - DataKey::set_royalty_info(env, collection_id, &royalty); + ::set_royalty_info(env, collection_id, &royalty); } factory_config.total_collections += 1; factory_config.accumulated_fees += factory_config.factory_fee; - DataKey::set_factory_config(env, &factory_config); - - // Emit event using Soroban event system with symbol_short for efficiency - env.events().publish( - (symbol_short!("col_created"), collection_id), - (caller.clone(), collection_id, collection_address, config.name.clone(), config.symbol.clone()) - ); + ::set_factory_config(env, &factory_config); Ok(collection_id) } @@ -98,19 +91,19 @@ impl Factory { // Queries // ───────────────────────────────────────────── pub fn get_collection_count(env: &Env) -> Result { - Ok(DataKey::get_factory_config(env)?.total_collections) + Ok(::get_factory_config(env)?.total_collections) } pub fn get_collection_address(env: &Env, collection_id: u64) -> Result { - Ok(DataKey::get_collection_info(env, collection_id)?.address) + Ok(::get_collection_info(env, collection_id)?.address) } pub fn get_collection_info(env: &Env, collection_id: u64) -> Result { - DataKey::get_collection_info(env, collection_id) + ::get_collection_info(env, collection_id) } pub fn get_factory_config(env: &Env) -> Result { - DataKey::get_factory_config(env) + ::get_factory_config(env) } // ───────────────────────────────────────────── @@ -121,7 +114,7 @@ impl Factory { caller: &Address, fee: i128, ) -> Result<(), Error> { - let mut config = DataKey::get_factory_config(env)?; + let mut config = ::get_factory_config(env)?; if &config.owner != caller { return Err(Error::Unauthorized); @@ -129,13 +122,7 @@ impl Factory { let old_fee = config.factory_fee; config.factory_fee = fee; - DataKey::set_factory_config(env, &config); - - // Emit event - env.events().publish( - (symbol_short!("fee_updated"),), - (old_fee, fee, caller.clone()) - ); + ::set_factory_config(env, &config); Ok(()) } @@ -146,7 +133,7 @@ impl Factory { recipient: Address, amount: i128, ) -> Result<(), Error> { - let mut config = DataKey::get_factory_config(env)?; + let mut config = ::get_factory_config(env)?; if &config.owner != caller { return Err(Error::Unauthorized); @@ -157,13 +144,7 @@ impl Factory { } config.accumulated_fees -= amount; - DataKey::set_factory_config(env, &config); - - // Emit event - env.events().publish( - (symbol_short!("fees_withdrawn"),), - (recipient.clone(), amount) - ); + ::set_factory_config(env, &config); Ok(()) } @@ -173,14 +154,14 @@ impl Factory { caller: &Address, max: Option, ) -> Result<(), Error> { - let mut config = DataKey::get_factory_config(env)?; + let mut config = ::get_factory_config(env)?; if &config.owner != caller { return Err(Error::Unauthorized); } config.max_collections = max; - DataKey::set_factory_config(env, &config); + ::set_factory_config(env, &config); Ok(()) } @@ -189,21 +170,14 @@ impl Factory { caller: &Address, active: bool, ) -> Result<(), Error> { - let mut config = DataKey::get_factory_config(env)?; + let mut config = ::get_factory_config(env)?; if &config.owner != caller { return Err(Error::Unauthorized); } config.is_active = active; - DataKey::set_factory_config(env, &config); - - // Emit event - env.events().publish( - (symbol_short!("factory_active"),), - (caller.clone(), active) - ); - + ::set_factory_config(env, &config); Ok(()) } diff --git a/nftopia-stellar/contracts/collection_factory_contract/src/lib.rs b/nftopia-stellar/contracts/collection_factory_contract/src/lib.rs index c3c10496..702d1285 100644 --- a/nftopia-stellar/contracts/collection_factory_contract/src/lib.rs +++ b/nftopia-stellar/contracts/collection_factory_contract/src/lib.rs @@ -1,3 +1,4 @@ +rust:contracts/collection_factory_contract/src/lib.rs #![no_std] use soroban_sdk::{contract, contractimpl, Address, Env, String, Vec, Map}; @@ -8,7 +9,7 @@ mod collection; mod factory; use errors::Error; -use storage::{DataKey, CollectionConfig, TokenMetadata, MetadataSchema, RoyaltyInfo}; +use storage::{CollectionConfig, TokenMetadata, MetadataSchema, RoyaltyInfo}; use collection::Collection; use factory::Factory; From 5eb1819cf5cdf2b1f6e5ab5c9b8d4892e87274b2 Mon Sep 17 00:00:00 2001 From: feyishola Date: Fri, 30 Jan 2026 22:06:25 +0100 Subject: [PATCH 11/13] updated lib file --- nftopia-stellar/Cargo.lock | 1829 +++++++++++++++++ .../collection_factory_contract/src/lib.rs | 1 - 2 files changed, 1829 insertions(+), 1 deletion(-) create mode 100644 nftopia-stellar/Cargo.lock diff --git a/nftopia-stellar/Cargo.lock b/nftopia-stellar/Cargo.lock new file mode 100644 index 00000000..17f6be1d --- /dev/null +++ b/nftopia-stellar/Cargo.lock @@ -0,0 +1,1829 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "ark-bls12-381" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", +] + +[[package]] +name = "ark-bn254" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", + "itertools", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "derivative", + "digest", + "itertools", + "num-bigint", + "num-traits", + "paste", + "rustc_version", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "digest", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes-lit" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0adabf37211a5276e46335feabcbb1530c95eb3fdf85f324c7db942770aa025d" +dependencies = [ + "num-bigint", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "cc" +version = "1.2.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_eval" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45565fc9416b9896014f5732ac776f810ee53a66730c17e4020c3ec064a8f88f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "chrono" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "collection-factory" +version = "0.1.0" +dependencies = [ + "soroban-sdk", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crate-git-revision" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c521bf1f43d31ed2f73441775ed31935d77901cb3451e44b38a1c1612fcbaf98" +dependencies = [ + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctor" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67773048316103656a637612c4a62477603b777d91d9c62ff2290f9cde178fdb" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2931af7e13dc045d8e9d26afccc6fa115d64e115c9c84b1166288b46f6782c2" + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.114", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.114", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dtor" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "404d02eeb088a82cfd873006cb713fe411306c7d182c344905e101fb1167d301" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "escape-bytes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bfcf67fea2815c2fc3b90873fae90957be12ff417335dfadc7f52927feb03b2" + +[[package]] +name = "ethnum" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca81e6b4777c89fd810c25a4be2b1bd93ea034fbe58e6a75216a34c6b82c539b" + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "indexmap-nostd" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "macro-string" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "marketplace_settlement" +version = "0.1.0" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "nft_contract" +version = "0.1.0" + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.114", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_with" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.13.0", + "schemars 0.8.22", + "schemars 0.9.0", + "schemars 1.2.0", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +dependencies = [ + "darling 0.21.3", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "soroban-builtin-sdk-macros" +version = "25.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7192e3a5551a7aeee90d2110b11b615798e81951fd8c8293c87ea7f88b0168f5" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "soroban-env-common" +version = "25.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfc49a80a68fc1005847308e63b9fce39874de731940b1807b721d472de3ff01" +dependencies = [ + "arbitrary", + "crate-git-revision", + "ethnum", + "num-derive", + "num-traits", + "serde", + "soroban-env-macros", + "soroban-wasmi", + "static_assertions", + "stellar-xdr", + "wasmparser", +] + +[[package]] +name = "soroban-env-guest" +version = "25.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2334ba1cfe0a170ab744d96db0b4ca86934de9ff68187ceebc09dc342def55" +dependencies = [ + "soroban-env-common", + "static_assertions", +] + +[[package]] +name = "soroban-env-host" +version = "25.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43af5d53c57bc2f546e122adc0b1cca6f93942c718977379aa19ddd04f06fcec" +dependencies = [ + "ark-bls12-381", + "ark-bn254", + "ark-ec", + "ark-ff", + "ark-serialize", + "curve25519-dalek", + "ecdsa", + "ed25519-dalek", + "elliptic-curve", + "generic-array", + "getrandom", + "hex-literal", + "hmac", + "k256", + "num-derive", + "num-integer", + "num-traits", + "p256", + "rand", + "rand_chacha", + "sec1", + "sha2", + "sha3", + "soroban-builtin-sdk-macros", + "soroban-env-common", + "soroban-wasmi", + "static_assertions", + "stellar-strkey 0.0.13", + "wasmparser", +] + +[[package]] +name = "soroban-env-macros" +version = "25.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a989167512e3592d455b1e204d703cfe578a36672a77ed2f9e6f7e1bbfd9cc5c" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "serde", + "serde_json", + "stellar-xdr", + "syn 2.0.114", +] + +[[package]] +name = "soroban-ledger-snapshot" +version = "25.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9d1bfa6f7d57307bf8241789b13d3703438e7afa0527aa098a357ef757d3a2" +dependencies = [ + "serde", + "serde_json", + "serde_with", + "soroban-env-common", + "soroban-env-host", + "thiserror", +] + +[[package]] +name = "soroban-sdk" +version = "25.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9953e782d6da30974eea520c2b5f624c28bbc518c3bb926ec581242dd3f9d2a2" +dependencies = [ + "arbitrary", + "bytes-lit", + "crate-git-revision", + "ctor", + "derive_arbitrary", + "ed25519-dalek", + "rand", + "rustc_version", + "serde", + "serde_json", + "soroban-env-guest", + "soroban-env-host", + "soroban-ledger-snapshot", + "soroban-sdk-macros", + "stellar-strkey 0.0.16", + "visibility", +] + +[[package]] +name = "soroban-sdk-macros" +version = "25.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a8cecb6acc735670dad3303c6a9d2b47e51adfb11224ad5a8ced55fd7b0a600" +dependencies = [ + "darling 0.20.11", + "heck", + "itertools", + "macro-string", + "proc-macro2", + "quote", + "sha2", + "soroban-env-common", + "soroban-spec", + "soroban-spec-rust", + "stellar-xdr", + "syn 2.0.114", +] + +[[package]] +name = "soroban-spec" +version = "25.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c79501d0636f86fe2c9b1dd7e88b9397415b3493a59b34f466abd7758c84b92b" +dependencies = [ + "base64", + "stellar-xdr", + "thiserror", + "wasmparser", +] + +[[package]] +name = "soroban-spec-rust" +version = "25.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b520b5fb013fde70796d9a6057591f53817aa0c38f8bad460126f97f59394af9" +dependencies = [ + "prettyplease", + "proc-macro2", + "quote", + "sha2", + "soroban-spec", + "stellar-xdr", + "syn 2.0.114", + "thiserror", +] + +[[package]] +name = "soroban-wasmi" +version = "0.31.1-soroban.20.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710403de32d0e0c35375518cb995d4fc056d0d48966f2e56ea471b8cb8fc9719" +dependencies = [ + "smallvec", + "spin", + "wasmi_arena", + "wasmi_core", + "wasmparser-nostd", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stellar-strkey" +version = "0.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee1832fb50c651ad10f734aaf5d31ca5acdfb197a6ecda64d93fcdb8885af913" +dependencies = [ + "crate-git-revision", + "data-encoding", +] + +[[package]] +name = "stellar-strkey" +version = "0.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084afcb0d458c3d5d5baa2d294b18f881e62cc258ef539d8fdf68be7dbe45520" +dependencies = [ + "crate-git-revision", + "data-encoding", + "heapless", +] + +[[package]] +name = "stellar-xdr" +version = "25.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d20dafed80076b227d4b17c0c508a4bbc4d5e4c3d4c1de7cd42242df4b1eaf" +dependencies = [ + "arbitrary", + "base64", + "cfg_eval", + "crate-git-revision", + "escape-bytes", + "ethnum", + "hex", + "serde", + "serde_with", + "sha2", + "stellar-strkey 0.0.13", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "time" +version = "0.3.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cc610bac2dcee56805c99642447d4c5dbde4d01f752ffea0199aee1f601dc4" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "transaction_contract" +version = "0.1.0" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "visibility" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.114", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasmi_arena" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073" + +[[package]] +name = "wasmi_core" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf1a7db34bff95b85c261002720c00c3a6168256dcb93041d3fa2054d19856a" +dependencies = [ + "downcast-rs", + "libm", + "num-traits", + "paste", +] + +[[package]] +name = "wasmparser" +version = "0.116.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a58e28b80dd8340cb07b8242ae654756161f6fc8d0038123d679b7b99964fa50" +dependencies = [ + "indexmap 2.13.0", + "semver", +] + +[[package]] +name = "wasmparser-nostd" +version = "0.100.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5a015fe95f3504a94bb1462c717aae75253e39b9dd6c3fb1062c934535c64aa" +dependencies = [ + "indexmap-nostd", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "zerocopy" +version = "0.8.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "zmij" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" diff --git a/nftopia-stellar/contracts/collection_factory_contract/src/lib.rs b/nftopia-stellar/contracts/collection_factory_contract/src/lib.rs index 702d1285..98801e35 100644 --- a/nftopia-stellar/contracts/collection_factory_contract/src/lib.rs +++ b/nftopia-stellar/contracts/collection_factory_contract/src/lib.rs @@ -1,4 +1,3 @@ -rust:contracts/collection_factory_contract/src/lib.rs #![no_std] use soroban_sdk::{contract, contractimpl, Address, Env, String, Vec, Map}; From 57422d0aafd76cbdbaec8b86bdc7cd7a93d2e28a Mon Sep 17 00:00:00 2001 From: feyishola Date: Fri, 30 Jan 2026 22:12:36 +0100 Subject: [PATCH 12/13] updated factory file --- .../contracts/collection_factory_contract/src/factory.rs | 5 ++--- .../contracts/collection_factory_contract/src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/nftopia-stellar/contracts/collection_factory_contract/src/factory.rs b/nftopia-stellar/contracts/collection_factory_contract/src/factory.rs index 8a5f61c3..b34bd08d 100644 --- a/nftopia-stellar/contracts/collection_factory_contract/src/factory.rs +++ b/nftopia-stellar/contracts/collection_factory_contract/src/factory.rs @@ -1,7 +1,6 @@ use soroban_sdk::{ Address, Env, - symbol_short, }; use crate::{ @@ -120,7 +119,7 @@ impl Factory { return Err(Error::Unauthorized); } - let old_fee = config.factory_fee; + let _old_fee = config.factory_fee; config.factory_fee = fee; ::set_factory_config(env, &config); @@ -130,7 +129,7 @@ impl Factory { pub fn withdraw_fees( env: &Env, caller: &Address, - recipient: Address, + _recipient: Address, amount: i128, ) -> Result<(), Error> { let mut config = ::get_factory_config(env)?; diff --git a/nftopia-stellar/contracts/collection_factory_contract/src/lib.rs b/nftopia-stellar/contracts/collection_factory_contract/src/lib.rs index 98801e35..4762ce97 100644 --- a/nftopia-stellar/contracts/collection_factory_contract/src/lib.rs +++ b/nftopia-stellar/contracts/collection_factory_contract/src/lib.rs @@ -8,7 +8,7 @@ mod collection; mod factory; use errors::Error; -use storage::{CollectionConfig, TokenMetadata, MetadataSchema, RoyaltyInfo}; +use storage::{CollectionConfig, TokenMetadata, MetadataSchema}; use collection::Collection; use factory::Factory; From c3d53e154d8bbe2e14cd9ba385465f1b79705b7f Mon Sep 17 00:00:00 2001 From: feyishola Date: Fri, 30 Jan 2026 22:17:23 +0100 Subject: [PATCH 13/13] updated collection file --- .../collection_factory_contract/src/collection.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nftopia-stellar/contracts/collection_factory_contract/src/collection.rs b/nftopia-stellar/contracts/collection_factory_contract/src/collection.rs index 1b797f56..ceed8ae5 100644 --- a/nftopia-stellar/contracts/collection_factory_contract/src/collection.rs +++ b/nftopia-stellar/contracts/collection_factory_contract/src/collection.rs @@ -1,4 +1,4 @@ -use soroban_sdk::{Address, Env, Map, String, Vec, symbol_short}; +use soroban_sdk::{Address, Env, Map, String, Vec}; use crate::{ errors::Error, @@ -170,7 +170,7 @@ impl Collection { uris: Vec, attributes_list: Option>>, ) -> Result, Error> { - let start_token_id = ::get_next_token_id(env, collection_id); + let _start_token_id = ::get_next_token_id(env, collection_id); let mut token_ids = Vec::new(env); for i in 0..uris.len() { @@ -198,7 +198,7 @@ impl Collection { Self::transfer_from(env, collection_id, from, from, to, token_id) } - // Batch transfer - FIXED dereferencing issue + // Batch transfer - FIXED: use token_id directly pub fn batch_transfer( env: &Env, collection_id: u64, @@ -206,8 +206,8 @@ impl Collection { to: &Address, token_ids: Vec, ) -> Result<(), Error> { - for &token_id in token_ids.iter() { // Use &token_id instead of *token_id - Self::transfer(env, collection_id, from, to, token_id)?; + for token_id in token_ids.iter() { + Self::transfer(env, collection_id, from, to, *token_id)?; } Ok(()) }