feat(shade): event ticketing — payment, minting, royalty resale#257
Merged
Conversation
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.
|
@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! 🚀 |
codebestia
approved these changes
Apr 29, 2026
Collaborator
codebestia
left a comment
There was a problem hiding this comment.
LGTM!
Thank you for your contribution!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
Eventwithevent_dateandroyalty_bps.create_eventvalidatesticket_price > 0,capacity > 0,royalty_bps <= 10_000,event_date >= ledger_timestamp, and that the token is platform-accepted.purchase_ticketnow actually transfersticket_price - feeto the merchant account and the fee to the platform account, then records merchant analytics — using the same primitives aspay_invoice. Validations run before any state mutation.Ticket { id, event_id, owner, minted_at }. Each purchase mints a unique ticket, persists it underDataKey::Ticket(id), and indexes it per-event (EventTickets) and per-user (UserTickets).resell_ticket(seller, buyer, ticket_id, resale_price)splits the resale price intoroyalty = price * royalty_bps / 10_000to the organizer and the remainder to the seller, then transfers ownership. Royalty math useschecked_multo catch overflow.EventCreatedEvent,TicketPurchasedEvent,TicketResoldEvent.Tests
contracts/shade/src/tests/test_event_tickets.rscovers:Repo health fixes bundled in (main was red on
cargo check)These were already broken on
mainbefore this PR — folding the minimal fixes in so the lib compiles and CI can run:components/admin.rs: replacedstd::vec::Vec(the crate is#![no_std]) with an in-place insertion sort onsoroban_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 inShadeTraitand delegated to nonexistentadmin_componentfunctions.interface.rs: importedPaymentPayload(used byvalidate_payment_payloadbut missing from the use list).types.rs: movedFiatPricingaboveInvoiceso the macro expansion forOption<FiatPricing>resolves cleanly.Test plan
cargo check -p shadeclean (was failing on main pre-PR)cargo test --workspace --all-featureson Linux