authors | start_date | published_date | version | summary | |
---|---|---|---|---|---|
|
2024/01/14 |
N/A |
1.0.0-draft.0 |
The Ticketto Protocol is a decentralised protocol to easily and securely issue, hold, and transfer tokens to grant access to events. |
Version: 1.0.0-draft.0 (2024/01/14)
Authors:
The Ticketto Protocol is a decentralised protocol to easily and securely issue, hold, and transfer tokens to grant access to events.
When organising an event, one of the crucial steps (aside from picking a venue and deciding the content that will be shown) is determining which people will attend to it, and how to verify people are granted to do so. This is an already solved problem: you either decide a closed list of attendees, or issue tickets and distribute among attendees through various means (like selling, giving away, or a combination of both).
Centralised ticketing systems overall tend to lack have of some (if not all) issues related to credibility, protection against fraud, falsification, and control over secondary markets (when intended).
NFTs provide the necessary tools to circumvent those issues. Also, providing a publicly auditable solution that helps other stakeholders (like venue owners and event promotors) gain credibility by giving information of the status of an event success in terms of ticket issuance/selling.
Finally, some tickets might grant multiple instances of acccess to an event, such a club membership or a ticket for an online conference.
This protocol aims to define a ticketing system based on NFTs, helping to solve the aforementioned issues.
- Event: A gathering, both physical and virtual. On-chain, events are usually defined as nonfungible collections.
- Ticket: A token used to get access to the event. On-chain, tickets are usually defined as nonfungible items (a.k.a. NFTs).
- Attendance: The act of marking a timestamp on a ticket, indicating it has been used for having gotten access to an event. Tickets might support single or multiple attendances, depending on the rules applied to it.
- Deferred transfer: The act of transferring a ticket to an unknown account, via a commit-reveal scheme.
- Event organisers: An account that creates an handles the state of an event.
- Event promoters: An entity (person, company, etc.) enabled to sell event tickets and receive a referral fee in exchange.
- Ticket holders: An account that owns a ticket.
- Event attendee: A ticket holder, that intend to get access to an event.
- Entrance operators: An entity (application, person) in charge of granting or denying access to an event.
- Ticket claimer: An account that owns a commit message enabling them to receive a ticket that is pending to be transfered via deferred transfer.
- Ticket resellers: A ticket holder that is interested in reselling their own ticket on a secondary market.
- Venue owners: An entity that owns the venue where an event will be held.
- Events Module: Allows handling operations of an event.
- Tickets Module: Allows handling actions related to owning a ticket, such as registering an attendance, deferred transfering and safely selling a ticket using a fungible asset.
- Attendances Module: Allows handling actions related to attending an event with a ticket, such as registering an attendance.
Enables some restrictions for the ticket at issuance time
#[derive(Default)]
struct TicketRestrictions {
cannot_resale: bool;
cannot_transfer: bool;
}
Sets the behaviour for attending with a ticket. May be Single
(i.e. entering a concert), Multiple
(i.e. a fast pass with a limit of n
usages), or Unlimited
(a membership on a night club, or a day pass at a hotel) with an optional expiration date.
enum AttendancePolicy {
Single,
Multiple { max: u32, maybe_until: Option<Timestamp> },
Unlimited { maybe_until: Option<Timestamp> },
}
Returns whether an event attendee is able to get access to an event, depending on the attributes (attendance_type, attendances, expiration_date, etc.) marked on their ticket.
fn can_attend(
event: EventId,
ticket: TicketId
) -> bool
sequenceDiagram
actor Attendee
participant Tickets
participant Nfts
participant Env
Attendee ->> Tickets: can_attend
Tickets ->> Nfts: do_get_attribute "attendance_type"
activate Nfts
Nfts ->> Tickets: attendance_type
deactivate Nfts
Tickets ->> Env: block_timestamp
activate Env
Env ->> Tickets: now
deactivate Env
break when there's no attendance_type
Tickets ->> Attendee: TicketFormatError
end
alt attendance_type is Unlimited
Tickets ->> Attendee: now <= until
else
Tickets ->> Nfts: do_get_attribute "attentances"
activate Nfts
Nfts --) Tickets: maybe_attendances
deactivate Nfts
alt attendance_type is Single
Nfts ->> Attendee: true if no attendances
else attendance_type is Multiple
Nfts ->> Attendee: true if max < attendances and now <= until
end
end
Note over Attendee,Nfts: Do the same for other properties, depending on the rules defined for the ticket
An organiser calls up a method called create_event
, passing a definition of the event (max_capacity
and (optional) metadata
).
Then, an account on behalf of the protocol (a.k.a. the issuer) is assigned as admin, and the organiser is assigned as owner. This is done, so it's easier to the protocol can execute permissioned actions over collections and items.
fn create_event(
origin: AccountId,
capacity: MaxCapacity,
maybe_metadata: Option<Vec<u8>>,
) -> Result<EventId>;
sequenceDiagram
actor Organiser
participant Events
participant Nfts
actor Issuer
Organiser ->> Events: create_event
Events ->> Nfts: do_create_collection
activate Nfts
Nfts --) Events: Created
Nfts --) Issuer: assign as issuer / admin / freezer
Nfts --) Organiser: assign as owner
Nfts ->> Events: CollectionId
deactivate Nfts
par
opt request includes metadata
Events ->> Nfts: do_set_metadata
activate Nfts
Nfts --) Events: CollectionMetadataSet
deactivate Nfts
end
and
Events ->> Nfts: do_set_max_supply
activate Nfts
Nfts --) Events: CollectionMaxSupplyset
deactivate Nfts
end
Events ->> Organiser: CollectionId
An organiser issues a ticket, including the attendance_type
, and optionally stating some attributes
to it and/or metadata
.
If desired by the event organiser, a ticket can be immediately minted on behalf of a holder.
It's possible for a ticket to be restricted (for selling, such as always free tickets, or transferring, like scolarship-class tickets).
Finally, organiser can also set a price
, and mark it as for_sale
(if this option is true, and the price is set as None
, a default price ).
fn issue_ticket(
origin: AccountId,
event: EventId,
attendance_type: AttendancePolicy,
maybe_holder: Option<AccountId>,
maybe_attributes: Option<Vec<ItemAttribute>>,
maybe_metadata: Option<Vec<u8>>,
maybe_restrictions: Option<TicketRestrictions>,
maybe_price: Option<{ asset: AssetId, amount: Balance }>,
for_sale: bool,
) -> Result<TicketId>;
sequenceDiagram
actor Organiser
participant Tickets
participant Nfts
actor Holder
actor Issuer
Organiser ->> Tickets: issue_ticket
Tickets ->> Nfts: do_mint_item
activate Nfts
Nfts --) Tickets: Issued
alt there is a holder
Nfts --) Holder: assign as owner
else
Nfts --) Organiser: assign as owner
end
Nfts ->> Tickets: TicketId
deactivate Nfts
opt request includes metadata
Tickets ->> Nfts: do_set_metadata
activate Nfts
Nfts --) Tickets: ItemMetadataSet
deactivate Nfts
end
opt request includes restrictions
note over Tickets,Nfts: Consider restrictions might also make items be locked for transfer
loop every restriction
Tickets ->> Nfts: do_set_attribute
activate Nfts
Nfts --) Tickets: AttributeSet
deactivate Nfts
end
end
opt request includes price
Tickets ->> Nfts: do_set_attribute
activate Nfts
Nfts --) Tickets: AttributeSet
deactivate Nfts
end
alt item is for_sale
Tickets ->> Nfts: do_item_lock_transfer
Tickets ->> Nfts: set_transfer_delegate
activate Nfts
Nfts --) Tickets: ItemTransferLocked
Nfts --) Issuer: assign as transfer delegate
deactivate Nfts
end
Tickets ->> Organiser: TicketId
A ticket holder can mark a ticket as for_sale
and set a price
at any moment, unless the ticket is marked as cannot_resale
.
fn sell_ticket(
origin: AccountId,
event: EventId,
ticket: TicketId,
price: { asset: AssetId, amount: Balance }
) -> Result<()>;
At any point, the holder may choose to stop selling the ticket.
fn withdraw_sell(
origin: AccountId,
event: EventId,
ticket: TicketId,
) -> Result<()>;
sequenceDiagram
actor Seller
participant Tickets
participant Nfts
actor Issuer
Seller ->> Tickets: sell_ticket
Tickets ->> Nfts: do_get_attribute "restrict_resell"
activate Nfts
Nfts ->> Tickets: restrict_resell
deactivate Nfts
break restrict_resell exists
Tickets ->> Seller: CannotSell
end
Tickets ->> Nfts: do_set_attribute "price"
activate Nfts
Nfts -->> Tickets: AttributeSet
deactivate Nfts
Tickets ->> Nfts: do_set_attribute "for_sale"
activate Nfts
Nfts -->> Tickets: AttributeSet
deactivate Nfts
Tickets ->> Nfts: do_item_lock_transfer
Tickets ->> Nfts: do_item_set_transfer_delegate
activate Nfts
Nfts -->> Tickets: ItemTransferLocked
Nfts --) Issuer: assign as transfer delegate
deactivate Nfts
Tickets -->> Seller: SetForSale
Seller ->> Tickets: withdraw_sell
Tickets ->> Nfts: do_get_attribute "for_sale"
activate Nfts
Nfts -->> Tickets: for_sale
deactivate Nfts
break if not for sale
Tickets ->> Seller: NotForSale
end
Tickets ->> Nfts: do_item_unlock_transfer
Tickets ->> Nfts: do_item_clear_transfer_delegate
activate Nfts
Nfts -->> Tickets: ItemTransferUnlocked
Nfts --) Issuer: cleared as transfer delegate
deactivate Nfts
Tickets ->> Nfts: do_clear_attribute "for_sale"
activate Nfts
Nfts -->> Tickets: AttributeSet
deactivate Nfts
When a ticket is marked as for_sale
, an account holding the amount requested in the price would be able to execute this call and get the ticket transferrd in exchange to the specified funds. Once sold, the protocol will modify the ticket to remove the price
and the for_sale
flag.
fn buy_ticket(
origin: AccountId,
event: EventId,
ticket: TicketId,
) -> Result<()>;
sequenceDiagram
actor Buyer
participant Tickets
participant Nfts
actor Seller
Buyer ->> Tickets: buy_ticket
Tickets ->> Tickets: do_get_attribute "for_sale"
activate Tickets
Tickets ->> Tickets: for_sale
deactivate Tickets
break ticket is not for sale
Tickets ->> Buyer: NotForSale
end
Tickets ->> Assets: do_transfer_asset
activate Tickets
break if Buyer does not have enough funds
Assets ->> Tickets: BalanceLow
Tickets ->> Buyer: BalanceLow
end
Buyer --> Assets: [withdraws]
Assets -->> Seller: [deposits]
Assets -->> Seller: Transferred
Assets -->> Buyer: Transferred
deactivate Tickets
Tickets ->> Nfts: do_transfer_item
activate Nfts
Nfts -->> Buyer: assigned as owner
Nfts -->> Seller: unset as owner
Nfts -->> Tickets: ItemTransferred
deactivate Nfts
Tickets -->> Buyer: TicketSold
Tickets -->> Seller: TicketSold
When a ticket is not marked as restrict_transfer
, its ticket holder may transfer it to another account, without the receiver needing to accept the transfer.
fn transfer_ticket(
origin: AccountId,
event: EventId,
ticket: TicketId,
receiver: AccountId,
) -> Result<()>;
sequenceDiagram
actor Sender
participant Tickets
participant Nfts
actor Receiver
Sender ->> Tickets: transfer_ticket
break if Ticket is resitrcted for transfer
Tickets ->> Sender: CannotTransfer
end
Tickets ->> Nfts: do_transfer_item
activate Nfts
Nfts -->> Receiver: assigned as owner
Nfts -->> Sender: unset as owner
Nfts -->> Tickets: ItemTransferred
deactivate Nfts
Tickets -->> Sender: TicketTransferred
Tickets -->> Receiver: TicketTransferredsequenceDiagram
When a ticket is not marked as restrict_transfer
, its ticket holder may commit for transfer using a commit-reveal scheme. This is so a receiver that initially doesn't own an account is able to claim it later.
The scheme starts by calling defer_transfer
, with the commit
message, and an optional expiration
date.
fn defer_transfer(
origin: AccountId,
event: EventId,
ticket: TicketId,
commit_message: Vec<u8>,
maybe_expiration: Optional<Timestamp>,
) -> Result<()>;
Once the receiver has created an account, they can call claim_deferred_transfer
to reveal the commitment, using a claim
message, receiving the transfered ticket as a result.
fn claim_deferred_transfer(
origin: AccountId,
event: EventId,
ticket: TicketId,
claim_message: Vec<u8>,
) -> Result<()>;
It's possible, at any moment, that the ticket holder cancels the deferred transferring, thus unlocking the item for transfer to another party (or even, selling it).
fn cancel_deferred_transfer(
origin: AccountId,
event: EventId,
ticket: TicketId,
) -> Result<()>;
sequenceDiagram
actor Sender
participant Tickets
participant Nfts
actor Issuer
actor Claimer
Sender ->> Tickets: defer_transfer
break if Ticket is resitrcted for transfer
Tickets ->> Sender: CannotTransfer
end
Tickets ->> Nfts: do_item_lock_transfer
Tickets ->> Nfts: do_set_transfer_delegate
activate Nfts
Nfts --) Tickets: ItemTransferLocked
Nfts --) Issuer: assign as transfer delegate
deactivate Nfts
Tickets ->> Tickets: set_transfer_claim
alt
Claimer ->> Tickets: claim_deferred_transfer
activate Tickets
Tickets ->> Nfts: do_transfer_item
activate Nfts
Nfts -->> Issuer: unset as transfer delegate
Nfts -->> Sender: unset as owner
Nfts -->> Claimer: assigned as owner
Nfts --) Tickets: ItemTransferred
deactivate Nfts
Tickets -->> Sender: TicketTransferred
Tickets -->> Claimer: TicketTransferred
deactivate Tickets
else Sender withdraws the transfer
Sender ->> Tickets: withrdraw_deferred_transfer
Tickets ->> Nfts: do_item_unlock_transfer
Tickets ->> Nfts: do_clear_transfer_delegate
activate Nfts
Nfts --) Tickets: ItemTransferUnlocked
Nfts --) Issuer: unassign as transfer delegate
deactivate Nfts
Tickets ->> Tickets: clear_transfer_claim
Claimer ->> Tickets: claim_deferred_transfer
Tickets ->> Claimer: NoTransferInPlace
end
The main use case on this protocol. grants ticket holder to get access to an event using a ticket.
fn mark_attendance(
origin: AccountId,
event: EventId,
ticket: TicketId,
) -> Result<()>;
sequenceDiagram
actor Attendee
participant Attendances
participant Tickets
participant Nfts
Attendee ->> Attendances: mark_attendance
Attendances ->> Tickets: can_attend "event, ticket"
activate Tickets
Tickets ->> Attendances: can_attend
deactivate Tickets
break cannot attend
Attendances ->> Attendee: CannotAttend
end
Attendances ->> Nfts: do_set_attribute "attendances"
activate Nfts
Nfts -->> Attendances: AttributeSet
deactivate Nfts
Attendances -->> Attendee: AttendanceMarked