Skip to content

feat(shade): event ticketing — payment, minting, royalty resale#257

Merged
codebestia merged 2 commits into
ShadeProtocol:mainfrom
Depo-dev:feat/ticketing-system
Apr 29, 2026
Merged

feat(shade): event ticketing — payment, minting, royalty resale#257
codebestia merged 2 commits into
ShadeProtocol:mainfrom
Depo-dev:feat/ticketing-system

Conversation

@Depo-dev
Copy link
Copy Markdown
Contributor

Summary

Implements the Shade contract's event ticketing system end to end: paid event creation, on-chain payment via Shade, NFT-style ticket minting, and royalty-enforced secondary resale.

Closes #246 (Event Creation Logic), #247 (Shade Payment Integration), #248 (Ticket Minting Logic), #254 (Royalty Fee on Resale).

What landed

  • Event creation (Implement Event Creation Logic #246): extended Event with event_date and royalty_bps. create_event validates ticket_price > 0, capacity > 0, royalty_bps <= 10_000, event_date >= ledger_timestamp, and that the token is platform-accepted.
  • Payment integration (Integrate Shade Payment Gateway for Ticket Purchases #247): purchase_ticket now actually transfers ticket_price - fee to the merchant account and the fee to the platform account, then records merchant analytics — using the same primitives as pay_invoice. Validations run before any state mutation.
  • Ticket minting (Implement Ticket Minting Logic #248): introduced Ticket { id, event_id, owner, minted_at }. Each purchase mints a unique ticket, persists it under DataKey::Ticket(id), and indexes it per-event (EventTickets) and per-user (UserTickets).
  • Resale royalty (Implement Royalty Fee Calculation on Ticket Resale #254): resell_ticket(seller, buyer, ticket_id, resale_price) splits the resale price into royalty = price * royalty_bps / 10_000 to the organizer and the remainder to the seller, then transfers ownership. Royalty math uses checked_mul to catch overflow.
  • Errors: 10 new variants (EventNotFound, EventSoldOut, InvalidCapacity, InvalidEventDate, InvalidRoyaltyBps, TicketNotFound, NotTicketOwner, TicketEventMismatch, InvalidResalePrice — plus reuse of existing payment errors).
  • Events emitted: EventCreatedEvent, TicketPurchasedEvent, TicketResoldEvent.

Tests

contracts/shade/src/tests/test_event_tickets.rs covers:

  • ✅ Create event stores all fields
  • ✅ Reject zero price / zero capacity / royalty > 100% / past event date
  • ✅ Purchase moves funds and mints a ticket; user index updated; sold counter incremented
  • ✅ Platform fee routed correctly when configured
  • ✅ Sold-out rejection
  • ✅ Missing-event rejection
  • ✅ Resale splits royalty to organizer + remainder to seller; ownership and indexes updated
  • ✅ Zero-royalty resale pays seller in full
  • ✅ Reject non-owner seller / zero resale price / unknown ticket id

Repo health fixes bundled in (main was red on cargo check)

These were already broken on main before this PR — folding the minimal fixes in so the lib compiles and CI can run:

  • components/admin.rs: replaced std::vec::Vec (the crate is #![no_std]) with an in-place insertion sort on soroban_sdk::Vec.
  • shade.rs: removed four impl methods (get_daily_volume, get_weekly_volume, get_merchant_daily_volume, get_merchant_weekly_volume) that were not declared in ShadeTrait and delegated to nonexistent admin_component functions.
  • interface.rs: imported PaymentPayload (used by validate_payment_payload but missing from the use list).
  • types.rs: moved FiatPricing above Invoice so the macro expansion for Option<FiatPricing> resolves cleanly.

Test plan

  • cargo check -p shade clean (was failing on main pre-PR)
  • CI runs cargo test --workspace --all-features on Linux
  • CI clippy + fmt

Implements the Shade contract's event ticketing system end to end so
organizers can run paid, NFT-style ticketed events with enforced
secondary-market royalties.

Closes ShadeProtocol#246 — Event Creation Logic
Closes ShadeProtocol#247 — Shade Payment Integration
Closes ShadeProtocol#248 — Ticket Minting Logic
Closes ShadeProtocol#254 — Royalty Fee on Resale

What landed:

- Event model gains `event_date` and `royalty_bps` so organizers control
  when the event happens and what royalty applies on resale; create_event
  validates ticket_price > 0, capacity > 0, royalty_bps <= 10_000, event
  date in the future, and that the token is on the platform whitelist.
- purchase_ticket now actually moves funds: it transfers `price - fee`
  from buyer to merchant_account, routes the fee to the platform account,
  records the payment in merchant analytics, and only then mints the
  ticket. Capacity and event existence are enforced before any state
  mutation, so failure leaves the contract untouched.
- Ticket records (id, event_id, owner, minted_at) are persisted, indexed
  per-event and per-user, so callers can list a buyer's holdings without
  scanning all events.
- resell_ticket lets the current owner sell to a new buyer at a chosen
  price; the contract splits resale_price between the organizer
  (royalty_bps / 10_000) and the seller, transfers ownership, and updates
  both parties' user-ticket indexes. Uses checked multiplication so a
  pathological resale_price cannot overflow the royalty calculation.
- Adds 10 error variants (EventNotFound, EventSoldOut, InvalidCapacity,
  InvalidEventDate, InvalidRoyaltyBps, TicketNotFound, NotTicketOwner,
  TicketEventMismatch, InvalidResalePrice, plus reuse of existing
  payment errors) so failure modes are distinguishable.
- Emits EventCreatedEvent, TicketPurchasedEvent, TicketResoldEvent for
  off-chain indexers.

Tests (contracts/shade/src/tests/test_event_tickets.rs) cover: full create
+ purchase + ticket lookup + buyer index, fee routing to the platform
account, sold-out rejection, missing-event rejection, invalid
inputs (zero price, zero capacity, royalty > 100%, past date), full resale
royalty split, zero-royalty resale, non-owner reseller rejection, zero
resale price, and unknown ticket id.

Repo cleanups bundled in (main was failing `cargo check`):
- contracts/shade/src/components/admin.rs: replace `std::vec::Vec` with
  an in-place insertion sort on `soroban_sdk::Vec` so the no_std crate
  compiles.
- contracts/shade/src/shade.rs: drop four impl methods
  (get_daily_volume, get_weekly_volume, get_merchant_daily_volume,
  get_merchant_weekly_volume) that were not declared in the trait and
  delegated to nonexistent admin_component functions.
- contracts/shade/src/interface.rs: import PaymentPayload (used by
  validate_payment_payload but missing from the use list).
- contracts/shade/src/types.rs: define FiatPricing before Invoice so the
  Option<FiatPricing> field resolves cleanly through the contracttype
  macro expansion.
@drips-wave
Copy link
Copy Markdown

drips-wave Bot commented Apr 28, 2026

@Depo-dev 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

Copy link
Copy Markdown
Collaborator

@codebestia codebestia left a comment

Choose a reason for hiding this comment

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

LGTM!
Thank you for your contribution!

@codebestia codebestia merged commit ffec3e3 into ShadeProtocol:main Apr 29, 2026
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 Event Creation Logic

2 participants