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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,11 @@ panic = "abort"
codegen-units = 1
lto = true

# Suppress PDB generation on Windows to prevent linker exhaustion (LNK1318)
# under disk-constrained environments. Setting debug = 0 disables the Program
# Database file that the MSVC linker writes alongside the test binary.
# [profile.test] inherits from [profile.dev], so setting debug = 0 here
# covers both `cargo build` and `cargo test` invocations.
[profile.dev]
debug = 0

80 changes: 77 additions & 3 deletions creator-keys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,15 +265,30 @@ pub struct ProtocolFeeView {

/// Stable, non-optional view of creator details.
///
/// Returned by [`CreatorKeysContract::get_creator_details`] for indexer-friendly consumption.
/// When `is_registered` is `false`, default values are returned for other fields.
/// Returned by [`CreatorKeysContract::get_creator_details`] and
/// [`CreatorKeysContract::get_creators_batch`] for indexer-friendly consumption.
/// When `is_registered` is `false`, default values are returned for all other fields,
/// including `registered_at: 0`.
///
/// # Field Stability
///
/// Fields are append-only. Do not reorder existing fields; the Soroban XDR encoder
/// serialises struct fields in declaration order and downstream indexers rely on
/// positional stability.
#[derive(Clone)]
#[contracttype]
pub struct CreatorDetailsView {
pub creator: Address,
pub handle: String,
pub supply: u32,
pub is_registered: bool,
/// Ledger sequence number at the time the creator registered.
///
/// Set to `env.ledger().sequence()` inside [`CreatorKeysContract::register_creator`].
/// Returns `0` for unregistered addresses so callers never receive an `Option`.
/// Clients can use this field to sort a marketplace grid chronologically without
/// maintaining a separate off-chain index.
pub registered_at: u32,
}
/// Stable, non-optional view of a creator's fee configuration.
///
Expand Down Expand Up @@ -358,6 +373,12 @@ pub struct CreatorProfile {
pub supply: u32,
pub holder_count: u32,
pub fee_recipient: Address,
/// Ledger sequence number captured at registration time via `env.ledger().sequence()`.
///
/// Stored as the last field so existing serialised profiles written before this
/// field was added deserialise correctly — the Soroban persistent storage layer
/// reads structs by field index, so appending is the only safe extension pattern.
pub registered_at: u32,
}

/// Reads a creator profile from storage, returning `None` for unregistered creators.
Expand Down Expand Up @@ -643,6 +664,7 @@ impl CreatorKeysContract {
supply: 0,
holder_count: 0,
fee_recipient: creator.clone(),
registered_at: env.ledger().sequence(),
};

let fee_config = read_protocol_fee_config(&env).unwrap_or(fee::FeeConfig {
Expand Down Expand Up @@ -811,7 +833,7 @@ impl CreatorKeysContract {
///
/// Returns a [`CreatorDetailsView`] regardless of registration status.
/// When the creator is not registered, `is_registered` is `false` and
/// default values are provided for other fields.
/// default values are provided for other fields, including `registered_at: 0`.
pub fn get_creator_details(env: Env, creator: Address) -> CreatorDetailsView {
let key = constants::storage::creator(&creator);
match env
Expand All @@ -824,15 +846,67 @@ impl CreatorKeysContract {
handle: profile.handle,
supply: profile.supply,
is_registered: true,
registered_at: profile.registered_at,
},
None => CreatorDetailsView {
creator,
handle: read_none_string(&env),
supply: 0,
is_registered: false,
registered_at: 0,
},
}
}

/// Read-only batch view: returns [`CreatorDetailsView`] for each address in `creators`.
///
/// Iterates the provided addresses in order and fetches each creator's profile
/// from persistent storage. The output `Vec` is the same length as the input and
/// preserves input order, so clients can zip the two slices without an extra sort.
///
/// Unregistered addresses never cause the call to fail: they produce a default
/// [`CreatorDetailsView`] with `is_registered: false` and `registered_at: 0`,
/// matching the single-address behaviour of [`get_creator_details`].
///
/// # Usage
///
/// ```text
/// let views = client.get_creators_batch(&vec![alice, bob, unknown]);
/// // views[0] → alice's details (is_registered: true)
/// // views[1] → bob's details (is_registered: true)
/// // views[2] → default view (is_registered: false, registered_at: 0)
/// ```
pub fn get_creators_batch(
env: Env,
creators: soroban_sdk::Vec<Address>,
) -> soroban_sdk::Vec<CreatorDetailsView> {
let mut results = soroban_sdk::Vec::new(&env);
for creator in creators.iter() {
let key = constants::storage::creator(&creator);
let view = match env
.storage()
.persistent()
.get::<DataKey, CreatorProfile>(&key)
{
Some(profile) => CreatorDetailsView {
creator: profile.creator,
handle: profile.handle,
supply: profile.supply,
is_registered: true,
registered_at: profile.registered_at,
},
None => CreatorDetailsView {
creator,
handle: read_none_string(&env),
supply: 0,
is_registered: false,
registered_at: 0,
},
};
results.push_back(view);
}
results
}
/// Read-only view: returns the protocol state version.
///
/// Returns a stable scalar value for clients and indexers to detect
Expand Down
1 change: 1 addition & 0 deletions creator-keys/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ fn test_read_key_balance_returns_registered_creator_supply() {
supply: 7,
holder_count: 3,
fee_recipient: creator.clone(),
registered_at: 0,
};

let supply = env.as_contract(&contract_id, || {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,14 @@
"u32": 0
}
},
{
"key": {
"symbol": "registered_at"
},
"val": {
"u32": 0
}
},
{
"key": {
"symbol": "supply"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,14 @@
"u32": 0
}
},
{
"key": {
"symbol": "registered_at"
},
"val": {
"u32": 0
}
},
{
"key": {
"symbol": "supply"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,14 @@
"u32": 1
}
},
{
"key": {
"symbol": "registered_at"
},
"val": {
"u32": 0
}
},
{
"key": {
"symbol": "supply"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,14 @@
"u32": 1
}
},
{
"key": {
"symbol": "registered_at"
},
"val": {
"u32": 0
}
},
{
"key": {
"symbol": "supply"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,14 @@
"u32": 1
}
},
{
"key": {
"symbol": "registered_at"
},
"val": {
"u32": 0
}
},
{
"key": {
"symbol": "supply"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,14 @@
"u32": 1
}
},
{
"key": {
"symbol": "registered_at"
},
"val": {
"u32": 0
}
},
{
"key": {
"symbol": "supply"
Expand Down Expand Up @@ -579,6 +587,14 @@
"u32": 1
}
},
{
"key": {
"symbol": "registered_at"
},
"val": {
"u32": 0
}
},
{
"key": {
"symbol": "supply"
Expand Down Expand Up @@ -665,6 +681,14 @@
"u32": 1
}
},
{
"key": {
"symbol": "registered_at"
},
"val": {
"u32": 0
}
},
{
"key": {
"symbol": "supply"
Expand Down Expand Up @@ -751,6 +775,14 @@
"u32": 1
}
},
{
"key": {
"symbol": "registered_at"
},
"val": {
"u32": 0
}
},
{
"key": {
"symbol": "supply"
Expand Down
Loading
Loading