Skip to content

feat: implement batch ticket minting and purchasing optimizations - #163

Merged
DioChuks merged 4 commits into
BuidlZone-Labs:mainfrom
Myart352:feat/147-batch-ticket-minting
Aug 4, 2026
Merged

feat: implement batch ticket minting and purchasing optimizations#163
DioChuks merged 4 commits into
BuidlZone-Labs:mainfrom
Myart352:feat/147-batch-ticket-minting

Conversation

@Myart352

@Myart352 Myart352 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Closes #147

Summary

Adds batch_register_for_event and batch_mint_ticket functions to allow users to purchase multiple tickets in a single atomic transaction, reducing network fees and improving UX for group bookings.

Changes

Event Contract (contracts/event/src/lib.rs)

  • Added batch_register_for_event(env, nonce, attendee, event_id, tier_id, count, is_verified, email_hash) that:
    • Validates batch capacity (max 50 tickets per call)
    • Checks tier availability for the full batch
    • Enforces max_tickets_per_user limit
    • Validates free claim limits and cooldowns for zero-price tiers
    • Processes payment as a single call with the total price
    • Calls batch_mint_ticket on the ticket contract
    • Updates counts atomically

Ticket Contract (contracts/ticket/src/lib.rs)

  • Added batch_mint_ticket(env, event_id, organizer, owner, count) that:
    • Mints up to 50 tickets in a single call
    • Accumulates owner and event ticket lists efficiently
    • Returns the ID of the first ticket minted

Key Design Decisions

  • 50 ticket batch limit prevents excessive gas consumption and DoS vectors
  • Single payment call for paid tickets reduces cross-contract calls
  • Pre-validation of all limits before any state mutation ensures atomicity
  • Backward compatible — existing single-ticket functions unchanged

Summary by CodeRabbit

  • New Features
    • Added batch registration for purchasing and minting 1–100 event tickets in a single transaction.
    • Added batch ticket minting for authorized callers, including sequential ticket creation and ownership tracking.
    • Added validation for event availability, ticket limits, capacity, pricing, and eligibility during batch registration.

@drips-wave

drips-wave Bot commented Jul 29, 2026

Copy link
Copy Markdown

@Myart352 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@DioChuks, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 53 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9e5547a6-4037-49cc-99b4-6572d08d396a

📥 Commits

Reviewing files that changed from the base of the PR and between b6ba435 and b1a8d0e.

📒 Files selected for processing (1)
  • contracts/event/src/lib.rs
📝 Walkthrough

Walkthrough

Changes

Batch registration and batch ticket minting now support atomic operations for 1–100 tickets. The flows validate authorization, capacity, privacy, per-user limits, payment rules, and cooldowns before updating ticket and event state.

Batch Ticket Purchase

Layer / File(s) Summary
Batch ticket minting
contracts/ticket/src/lib.rs
batch_mint_ticket validates the count and caller, creates sequential tickets, updates indexes and counters, emits mint events, and returns ticket IDs.
Batch event registration
contracts/event/src/lib.rs
batch_register_for_event validates registration rules, charges paid tickets once, invokes batch minting, updates event and tier totals, and emits a registration event.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Attendee
  participant EventContract
  participant PaymentsContract
  participant TicketContract
  Attendee->>EventContract: Request batch registration
  EventContract->>PaymentsContract: Charge combined price
  EventContract->>TicketContract: Mint batch tickets
  TicketContract-->>EventContract: Return ticket IDs
  EventContract-->>Attendee: Update registration and emit event
Loading

Possibly related PRs

Suggested reviewers: diochuks, depo-dev, codeze-us

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the feature but omits most required template sections, including tests, security, privacy, storage, and acceptance sign-off. Complete the required sections, document test coverage and acceptance criteria, and state storage, privacy, security, and cross-contract impacts.
Linked Issues check ⚠️ Warning The batch entrypoints and limit enforcement address the core issue, but no tests or documentation updates are shown for the required acceptance criteria. Add tests for atomicity, limits, boundaries, and regressions, then update documentation and reconcile the stated 50-ticket limit with the implementation.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies batch ticket minting and purchasing as the primary changes.
Out of Scope Changes check ✅ Passed The changes are limited to batch registration and batch minting in the two files named by issue #147.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Myart352
Myart352 force-pushed the feat/147-batch-ticket-minting branch from 7105769 to 39409df Compare July 30, 2026 09:17
@DioChuks
DioChuks self-requested a review August 3, 2026 16:20
@DioChuks

DioChuks commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@DioChuks

DioChuks commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

@Myart352 pls fix clippy & format

- Allow clippy::too_many_arguments on batch_register_for_event, whose flat
  argument list intentionally mirrors register_for_event.
- Apply cargo fmt to the batch_mint_ticket call site.
@DioChuks

DioChuks commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (4)
contracts/ticket/src/lib.rs (2)

78-80: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The batch size limit 100 is hard-coded in two contracts. Both files validate the same bound with a separate literal, so the two limits can drift apart. If the event contract allows a larger count than the ticket contract accepts, batch_mint_ticket rejects the call after the event contract has already validated capacity and computed the payment amount. Define the limit once in a shared location and reference it from both call sites.

  • contracts/ticket/src/lib.rs#L78-L80: replace the literal 100 in batch_mint_ticket with the shared constant.
  • contracts/event/src/lib.rs#L957-L959: replace the literal 100 in batch_register_for_event with the same shared constant.

Also align the documentation. The PR description states a maximum of 50 tickets per call, while both contracts enforce 100.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/ticket/src/lib.rs` around lines 78 - 80, Define one shared
batch-size limit and use it in batch_mint_ticket at
contracts/ticket/src/lib.rs:78-80 and batch_register_for_event at
contracts/event/src/lib.rs:957-959, replacing both hard-coded 100 literals.
Update the PR documentation to state the enforced maximum of 100 tickets per
call.

93-123: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared single-ticket mint body.

Lines 94-119 duplicate mint_ticket lines 39-66 exactly, apart from the ID variable. Two copies of the ticket construction, persistence, indexing, and event emission will drift. A future change to Ticket fields or to the index helpers must be applied twice.

Extract a private helper and call it from both entry points.

♻️ Proposed refactor
fn create_ticket(
    env: &Env,
    ticket_id: u64,
    event_id: &Symbol,
    organizer: &Address,
    owner: &Address,
) {
    let ticket = Ticket {
        ticket_id,
        event_id: event_id.clone(),
        organizer: organizer.clone(),
        owner: owner.clone(),
        issued_at: env.ledger().timestamp(),
        status: TicketStatus::Valid,
        is_transferable: true,
        is_used: false,
    };

    env.storage()
        .persistent()
        .set(&DataKey::Ticket(ticket_id), &ticket);

    storage::add_owner_ticket(env, owner, ticket_id);
    storage::add_event_ticket(env, event_id, ticket_id);

    events::emit_ticket_minted(
        env,
        ticket_id,
        ticket.event_id,
        ticket.owner,
        ticket.organizer,
        ticket.issued_at,
    );
}

Then the loop becomes:

         for _ in 0..count {
-            let ticket = Ticket {
-                ticket_id: next_id,
-                event_id: event_id.clone(),
-                organizer: organizer.clone(),
-                owner: owner.clone(),
-                issued_at: env.ledger().timestamp(),
-                status: TicketStatus::Valid,
-                is_transferable: true,
-                is_used: false,
-            };
-
-            env.storage()
-                .persistent()
-                .set(&DataKey::Ticket(next_id), &ticket);
-
-            storage::add_owner_ticket(&env, &owner, next_id);
-            storage::add_event_ticket(&env, &event_id, next_id);
-
-            events::emit_ticket_minted(
-                &env,
-                next_id,
-                ticket.event_id.clone(),
-                ticket.owner.clone(),
-                ticket.organizer.clone(),
-                ticket.issued_at,
-            );
-
+            create_ticket(&env, next_id, &event_id, &organizer, &owner);
             ticket_ids.push_back(next_id);
             next_id += 1;
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/ticket/src/lib.rs` around lines 93 - 123, Extract the duplicated
single-ticket construction, persistence, indexing, and event emission into a
private helper such as create_ticket, parameterized by env, ticket_id, event_id,
organizer, and owner. Replace the existing body in both mint_ticket entry
points, including the count loop, with calls to this helper while preserving ID
sequencing and ticket_ids collection.
contracts/event/src/lib.rs (2)

1031-1033: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Return the minted ticket IDs to the caller.

batch_mint_ticket returns the generated IDs, but this code discards them into _ticket_ids. A client that buys 30 tickets has no way to learn the IDs from the return value. The client must scan get_tickets_by_owner and filter by event, as request_postponement_refund does at lines 681-690.

Change the return type to Result<soroban_sdk::Vec<u64>, EventError> and return the IDs.

♻️ Proposed change
-    ) -> Result<(), EventError> {
+    ) -> Result<soroban_sdk::Vec<u64>, EventError> {
         let ticket_client = TicketContractClient::new(&env, &ticket_contract);
-        let _ticket_ids =
+        let ticket_ids =
             ticket_client.batch_mint_ticket(&event.event_id, &event.organizer, &attendee, &count);
-        Ok(())
+        Ok(ticket_ids)
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/event/src/lib.rs` around lines 1031 - 1033, Update the surrounding
event purchase function containing the TicketContractClient::batch_mint_ticket
call to return Result<soroban_sdk::Vec<u64>, EventError>, propagate the
batch_mint_ticket result instead of assigning it to _ticket_ids, and ensure the
minted ticket IDs are returned to the caller while preserving the existing error
handling.

1037-1042: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Replace the free-claim increment loop with one write.

Lines 1038-1040 call storage::increment_free_claim_count once per ticket. Per contracts/event/src/storage.rs lines 361-368, each call reads the key, writes the key, and extends the TTL. At count = 100 that is 100 read-modify-write cycles on a single ledger entry, and it inflates the transaction footprint for no benefit.

Add a helper that sets the total once. The batch already read the prior value into existing at line 998 when a claim limit is configured.

♻️ Proposed refactor

Add to contracts/event/src/storage.rs:

pub fn add_free_claim_count(env: &Env, event_id: &Symbol, attendee: &Address, delta: u32) {
    let key = DataKey::FreeClaimCount(event_id.clone(), attendee.clone());
    let count: u32 = env.storage().persistent().get(&key).unwrap_or(0u32);
    env.storage().persistent().set(&key, &(count + delta));
    env.storage()
        .persistent()
        .extend_ttl(&key, TTL_THRESHOLD, TTL_BUMP);
}

Then:

         if tier.price == 0 {
-            for _ in 0..count {
-                storage::increment_free_claim_count(&env, &event_id, &attendee);
-            }
+            storage::add_free_claim_count(&env, &event_id, &attendee, count);
             storage::set_last_free_claim(&env, &event_id, &attendee, env.ledger().timestamp());
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/event/src/lib.rs` around lines 1037 - 1042, Replace the per-ticket
increment loop in the free-claim branch with a single storage update using a new
add_free_claim_count helper. Implement the helper in storage to read the current
FreeClaimCount, add the supplied count, write once, and extend its TTL; pass
count from the batch flow while preserving the existing last-free-claim
timestamp update.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@contracts/event/src/lib.rs`:
- Around line 961-980: The batch registration flow around
batch_register_for_event must explicitly handle an attendee’s existing
reservation instead of leaving tier.reserved incremented. Choose the intended
behavior: either validate and consume the reservation by decrementing the
matched tier’s reserved count and calling storage::remove_reservation, or reject
the batch call with EventError::InvalidInput when storage::has_reservation is
true; preserve normal batch registration for callers without reservations.
- Around line 990-992: Update the capacity check in the batch registration path
to include tier.reserved alongside tier.sold and count, matching the
reservation-aware checks used by register_for_event and reserve_ticket; return
TierSoldOut whenever the combined total reaches or exceeds tier.capacity.
- Line 1035: Update batch_register_for_event around save_registration so it
checks whether each attendee is already registered before saving; call
storage::save_registration only for new attendees, while still allowing repeated
purchases to proceed without rejecting existing attendees.
- Around line 982-984: Update batch_register_for_event’s max_tickets_per_user
validation to compare the attendee’s existing owned ticket total for this event
plus the requested count against the configured cap. Preserve the unlimited
behavior when max_tickets_per_user is zero and return EventError::InvalidInput
whenever the combined total exceeds the limit.

In `@contracts/ticket/src/lib.rs`:
- Around line 90-125: Update batch_mint_ticket so its maximum accepted count
stays within Soroban’s 200-write limit, accounting for the ticket and
owner/event index writes performed per iteration. Reduce the advertised count
cap to a safe upper bound, and add simulator coverage confirming the maximum
succeeds while exceeding it is rejected.

---

Nitpick comments:
In `@contracts/event/src/lib.rs`:
- Around line 1031-1033: Update the surrounding event purchase function
containing the TicketContractClient::batch_mint_ticket call to return
Result<soroban_sdk::Vec<u64>, EventError>, propagate the batch_mint_ticket
result instead of assigning it to _ticket_ids, and ensure the minted ticket IDs
are returned to the caller while preserving the existing error handling.
- Around line 1037-1042: Replace the per-ticket increment loop in the free-claim
branch with a single storage update using a new add_free_claim_count helper.
Implement the helper in storage to read the current FreeClaimCount, add the
supplied count, write once, and extend its TTL; pass count from the batch flow
while preserving the existing last-free-claim timestamp update.

In `@contracts/ticket/src/lib.rs`:
- Around line 78-80: Define one shared batch-size limit and use it in
batch_mint_ticket at contracts/ticket/src/lib.rs:78-80 and
batch_register_for_event at contracts/event/src/lib.rs:957-959, replacing both
hard-coded 100 literals. Update the PR documentation to state the enforced
maximum of 100 tickets per call.
- Around line 93-123: Extract the duplicated single-ticket construction,
persistence, indexing, and event emission into a private helper such as
create_ticket, parameterized by env, ticket_id, event_id, organizer, and owner.
Replace the existing body in both mint_ticket entry points, including the count
loop, with calls to this helper while preserving ID sequencing and ticket_ids
collection.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4cd82461-e854-4b95-b98c-58f1dcaf2d71

📥 Commits

Reviewing files that changed from the base of the PR and between 7ca1d48 and b6ba435.

📒 Files selected for processing (2)
  • contracts/event/src/lib.rs
  • contracts/ticket/src/lib.rs

Comment on lines +961 to +980
let mut event = storage::get_event(&env, &event_id)?;

if event.status != EventStatus::Active {
return Err(EventError::EventNotActive);
}

require_settleable_privacy(&env, &event_id)?;

let mut tier_index = None;
let mut req_price: Option<i128> = None;
for i in 0..event.tiers.len() {
let t = event.tiers.get(i).ok_or(EventError::TierNotFound)?;
if t.tier_id == tier_id {
tier_index = Some(i);
req_price = Some(t.price);
break;
}
}
let index = tier_index.ok_or(EventError::TierNotFound)?;
let tier = event.tiers.get(index).ok_or(EventError::TierNotFound)?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

The batch path ignores existing reservations, so tier.reserved leaks.

register_for_event lines 855-925 handle a caller who holds a reservation. It validates the expiry and tier, decrements tier.reserved, and calls storage::remove_reservation. batch_register_for_event performs none of these steps.

If an attendee reserves a ticket and then calls batch_register_for_event, the reservation record stays in storage and tier.reserved stays incremented. That reserved slot is never released, so the tier permanently loses sellable capacity. release_expired_reservation is the only remaining path, and it requires an explicit external call.

Decide the intended behavior and implement it. Two options are consistent:

  • Consume the reservation: validate it, decrement tier.reserved by one, and call storage::remove_reservation.
  • Reject the call: return EventError::InvalidInput when storage::has_reservation returns true, and require the attendee to use register_for_event.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/event/src/lib.rs` around lines 961 - 980, The batch registration
flow around batch_register_for_event must explicitly handle an attendee’s
existing reservation instead of leaving tier.reserved incremented. Choose the
intended behavior: either validate and consume the reservation by decrementing
the matched tier’s reserved count and calling storage::remove_reservation, or
reject the batch call with EventError::InvalidInput when
storage::has_reservation is true; preserve normal batch registration for callers
without reservations.

Comment on lines +982 to +984
if event.max_tickets_per_user > 0 && count > event.max_tickets_per_user {
return Err(EventError::InvalidInput);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Locate per-user ticket accounting helpers and existing max_tickets_per_user enforcement.
set -euo pipefail

# Find any storage helper that tracks a per-user ticket count for an event.
rg -nP -C3 'user_ticket|tickets_per_user|UserTicketCount|get_user_ticket' contracts/

# Find every place max_tickets_per_user is read, to see where the limit is enforced today.
rg -nP -C5 '\bmax_tickets_per_user\b' contracts/

# Inspect the existing per-attendee ticket lookup helper used elsewhere in the event contract.
ast-grep run --pattern 'fn has_valid_ticket_for_event($$$) { $$$ }' --lang rust contracts/event/src/

Repository: BuidlZone-Labs/zicket-contract

Length of output: 50386


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the batch registration path and relevant helpers without running repo code.
sed -n '930,1025p' contracts/event/src/lib.rs
printf '\n--- register_for_event nearby ---\n'
sed -n '830,920p' contracts/event/src/lib.rs
printf '\n--- helper definitions involving has_valid_ticket_for_event ---\n'
rg -n -C4 'pub.*fn has_valid_ticket_for_event|fn has_valid_ticket_for_event' contracts/event/src/lib.rs
printf '\n--- batch/register function signatures ---\n'
rg -n -C3 'pub fn (batch_register_for_event|register_for_event)|fn (batch_register_for_event|register_for_event)' contracts/event/src/lib.rs

Repository: BuidlZone-Labs/zicket-contract

Length of output: 7858


Enforce max_tickets_per_user against the attendee’s owned tickets.

batch_register_for_event rejects count > event.max_tickets_per_user, but repeated calls with count == event.max_tickets_per_user bypass the limit. Use a per-event owner total, including existing tickets, so one attendee cannot exceed the configured per-user cap.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/event/src/lib.rs` around lines 982 - 984, Update
batch_register_for_event’s max_tickets_per_user validation to compare the
attendee’s existing owned ticket total for this event plus the requested count
against the configured cap. Preserve the unlimited behavior when
max_tickets_per_user is zero and return EventError::InvalidInput whenever the
combined total exceeds the limit.

Comment thread contracts/event/src/lib.rs Outdated
Comment thread contracts/event/src/lib.rs Outdated
Comment on lines +90 to +125
let mut ticket_ids = soroban_sdk::Vec::new(&env);
let mut next_id = read_next_ticket_id(&env);

for _ in 0..count {
let ticket = Ticket {
ticket_id: next_id,
event_id: event_id.clone(),
organizer: organizer.clone(),
owner: owner.clone(),
issued_at: env.ledger().timestamp(),
status: TicketStatus::Valid,
is_transferable: true,
is_used: false,
};

env.storage()
.persistent()
.set(&DataKey::Ticket(next_id), &ticket);

storage::add_owner_ticket(&env, &owner, next_id);
storage::add_event_ticket(&env, &event_id, next_id);

events::emit_ticket_minted(
&env,
next_id,
ticket.event_id.clone(),
ticket.owner.clone(),
ticket.organizer.clone(),
ticket.issued_at,
);

ticket_ids.push_back(next_id);
next_id += 1;
}

write_next_ticket_id(&env, next_id);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

Soroban transaction resource limits max ledger entry writes per transaction

💡 Result:

On the Stellar network, Soroban transactions are subject to specific resource limits defined by network settings [1][2]. As of August 2026, the maximum number of ledger entry writes allowed per transaction is 200 [3]. These limits are part of a multidimensional resource model where transactions declare their required resources—including ledger entry accesses (reads and writes), ledger I/O (bytes read/written), and CPU instructions—within their footprint [1][4]. Key points regarding these limits: 1. Per-Transaction Limit: Current mainnet settings cap the number of ledger entries that can be written in a single transaction at 200 [3]. 2. Dynamic Configuration: These limits are not static hard-coded constants in the protocol itself but are instead network-defined settings that can be adjusted by validator consensus to reflect network capacity and performance needs [1][2][3]. 3. Verification: Because these values can change via validator vote, developers are encouraged to verify the current limits for a specific network (e.g., mainnet) by checking the active network settings using tools such as the Stellar Laboratory or by querying the network configuration [3]. 4. Context: The 200-entry write limit is designed to maintain ledger close times and prevent the execution of arbitrarily large transactions [1][2]. The limits are also balanced against ledger-wide capacity, with established ratios (often 5:1 for ledger-to-transaction limits) ensuring fair resource distribution [5]. For the most accurate and up-to-date values, you can inspect the active ConfigSettingContractLedgerCostV0 values on the network [6].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate lib.rs and storage.rs =="
fd -a 'lib\.rs|storage\.rs|events\.rs' contracts/ticket/src || true

echo "== outline ticket lib =="
ast-grep outline contracts/ticket/src/lib.rs || true

echo "== relevant ticket lib lines =="
nl -ba contracts/ticket/src/lib.rs | sed -n '1,180p'

echo "== relevant storage definitions =="
for f in $(fd 'storage\.rs|events\.rs' contracts/ticket/src); do
  echo "--- $f ---
"
  nl -ba "$f" | sed -n '1,240p'
done

echo "== search claim/count/mint functions =="
rg -n "pub fn|count|MAX|100|mint|add_owner_ticket|add_event_ticket|read_next_ticket_id|write_next_ticket_id|emit_ticket_minted" contracts/ticket/src

Repository: BuidlZone-Labs/zicket-contract

Length of output: 1036


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== lib imports and constants =="
awk '{printf "%6d  %s\n", NR, $0}' contracts/ticket/src/lib.rs | sed -n '1,180p'

echo "== storage outlines/contents =="
awk '{printf "%6d  %s\n", NR, $0}' contracts/ticket/src/storage.rs | sed -n '1,260p'

echo "== lib minting range =="
awk '{printf "%6d  %s\n", NR, $0}' contracts/ticket/src/lib.rs | sed -n '180,340p'

echo "== storage helper calls in lib =="
awk '{printf "%6d  %s\n", NR, $0}' contracts/ticket/src/lib.rs | sed -n '340,470p'

echo "== search relevant docs/constants in repo =="
rg -n "MAX|100|batch_mint|mint_ticket|ledger entry|ResourceLimits|max|ContractLedgerCost|MAX_LEDER|LEDER|LedgerEntry|Write|LedgerKey" . || true

Repository: BuidlZone-Labs/zicket-contract

Length of output: 50386


🌐 Web query:

Soroban extend_ttl counts as write ledger entry updates storage extension TTL

💡 Result:

In Soroban, calling extend_ttl is a specific operation used to manage the Time-To-Live (TTL) of ledger entries, and while it effectively updates the entry's lifespan, it is distinct from general data mutation operations like set or update [1][2][3]. When you invoke extend_ttl (or the ExtendFootprintTTLOp operation), the network performs a storage extension that incurs "ledger rent" fees [2][3][4]. These rent fees are calculated based on the duration of the extension, the size of the ledger entry, and current storage pricing parameters [3]. Key points regarding how extend_ttl functions: 1. Nature of the Operation: extend_ttl is a dedicated host function available in the Soroban SDK (for Persistent, Temporary, and Instance storage) used to ensure an entry remains on-chain [1][5][3][6]. It is not a standard data update; rather, it is a management operation specifically for state archival [2]. 2. Fee Structure: Because extend_ttl extends the lifecycle of data, it is subject to rent fees which are categorized as a "refundable" fee component in Soroban's resource management model [3]. 3. Execution: If the current TTL of an entry already exceeds the specified threshold, the extend_ttl call is a no-op and does not trigger a fee [2]. If the TTL is below the threshold, the operation extends the lifespan to the requested extend_to value, which constitutes a ledger update for the purpose of state management [1][2]. 4. Distinct from Data Mutation: While set or update operations modify the value associated with a key, extend_ttl modifies the metadata (the expiration ledger sequence) of the ledger entry [1][5][6][4]. Both types of operations require sufficient footprint resources in the transaction [2].

Citations:


🌐 Web query:

Soroban max ledger entry writes per transaction 2026 extend_ttl ledger entry writes

💡 Result:

In the Stellar Soroban network as of August 2026, the maximum number of ledger entry write operations allowed per transaction is governed by network configuration parameters, which can be adjusted via protocol upgrades [1][2][3]. According to recent protocol updates, such as those documented in SLP-0004, this limit has been set to 200 write entries per transaction [4]. Regarding the extend_ttl functionality: 1. Operations used to extend TTL, such as the host functions provided by env.storage().persistent().extend_ttl(...) or extend_contract_data host functions, are designed to update the expiration ledger sequence of existing ledger entries [5][6][7]. 2. These extension operations typically do not count against the standard "write ledger entries" transaction limit in the same way that creating new entries does [7]. Specifically, host functions for extending TTL explicitly state that they do not charge standard write fees (though they do charge rent fees if an extension occurs) [7]. 3. Because these operations do not modify the data content or create new entries but rather update the TTL metadata (live_until_ledger_seq), they are treated differently by the network's resource metering system [7]. Users should consult the latest ConfigSettingContractLedgerCostV0 structure for the definitive current tx_max_write_ledger_entries value on the specific network they are accessing, as these values are subject to change by validator consensus [1][3].

Citations:


Keep batch_mint_ticket under Soroban write limits.

Each loop iteration writes a ticket and two index/count entries. A count of 100 therefore touches ~300 ledger entries in one transaction, while Soroban caps write operations at 200. This can make the advertised maximum unusable because the batch reverts. Reduce the count cap or split batching across transactions, and test the upper bound with the contract simulator before merge.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@contracts/ticket/src/lib.rs` around lines 90 - 125, Update batch_mint_ticket
so its maximum accepted count stays within Soroban’s 200-write limit, accounting
for the ticket and owner/event index writes performed per iteration. Reduce the
advertised count cap to a safe upper bound, and add simulator coverage
confirming the maximum succeeds while exceeding it is rejected.

DioChuks and others added 2 commits August 4, 2026 18:20
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@DioChuks
DioChuks merged commit 633821a into BuidlZone-Labs:main Aug 4, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement Batch Ticket Minting and Purchasing Optimizations

2 participants