Tickets
Tezos tickets are authenticated quantities issued by contracts or users. Tickets can be stored in contracts and in native Tezos accounts. A ticket of type sp.ticket[t]
has three elements:
Its ticketer, which is the contract that issued the ticket
Its contents of type
t
, also known as the wrapped value or payload, which can be any data typeIts amount of type
sp.nat
, which is an arbitrary non-zero positive number that represents a quantity or value for the ticket
A ticket's ticketer and contents cannot be changed.
Tickets themselves cannot be duplicated, but you can split one ticket into multiple tickets by creating duplicate tickets each with a portion of the original ticket's amount. The new tickets have the same ticketer and contents, and the sum of their amounts is always the amount of the original ticket. Similarly, you can join tickets with matching ticketers and contents into a single ticket with the sum of the joined tickets' amounts.
You cannot read the contents of a ticket directly; you must use sp.read_ticket
to access it.
- sp.ticket(contents: t, amount: sp.nat) → sp.ticket[t]
Create a ticket with the given contents and amount. The ticketer is always the contract's address via
sp.self_address
.
To create a ticket for use in a test scenario, use sp.test_ticket
, as described in Testing tickets. Tickets created with the sp.test_ticket
function are useful only in test scenarios; you cannot use these tickets in real transactions because doing so would allow you to create a false ticket.
- sp.read_ticket(ticket: sp.ticket[t]) → sp.pair[sp.record(ticketer=sp.address, contents=t, amount=sp.nat), sp.ticket[t]]
Reads the contents of a ticket and returns a pair of:
The ticket data, itself a record of the ticket's ticketer, contents, and amount.
A copy of the original ticket that can still be used.
This example creates a ticket and then reads it:
smartpy# Create ticket ticket_contents = (sp.int(5), "hello") ticket = sp.ticket(ticket_contents, 2) # Read ticket (ticket_data, new_ticket) = sp.read_ticket(ticket) assert ticket_data.contents == ticket_contents assert ticket_data.amount == 2 assert ticket_data.ticketer == sp.self_address
WARNING
Note that reading a ticket with
sp.read_ticket
consumes it, destroying the original ticket. To preserve the ticket or access it again, you must use the copy that the function returns, or else it is destroyed.Because reading a ticket consumes it, you must take special care when tickets are stored in maps. Reading a map can consume the tickets in the map. For this reason, when you work with a map of tickets, use
sp.get_and_update
instead ofsp.update_map
and retain the returned map to avoid consuming the tickets in it.This example uses
sp.update_map
incorrectly:smartpynew_map = sp.update_map(ticket_sender, sp.Some(new_ticket), self.data.ticket_map) self.data.ticket_map = new_map # Error: Variable cannot be used twice because it contains a ticket.
To work with tickets in a map without consuming them, use
sp.get_and_update
to get a copy of the map. This example usessp.get_and_update
to retrieve one ticket from a map and read it without consuming the map of tickets:smartpy# Get a ticket from the map and update storage with its value @sp.entrypoint def set_amount(self, ticketer_address): with sp.modify_record(self.data) as data: # Get the matching value from the map and a new copy of the map (matching_ticket, new_map) = sp.get_and_update( ticketer_address, None, data.ticket_map ) # Read the matching ticket (ticket_data, new_ticket) = sp.read_ticket(matching_ticket.unwrap_some()) data.ticket_amount = ticket_data.amount # Re-insert the new ticket into the map to avoid consuming the tickets new_map[ticketer_address] = new_ticket data.ticket_map = new_map
Joining and splitting tickets
- sp.join_tickets(t1: sp.ticket[t], t2: sp.ticket[t]) → sp.ticket[t]
Merges two tickets into one by adding their amounts. Fails if the tickets differ in their ticketer or contents.
- sp.split_ticket(ticket: sp.ticket[t], amount1: sp.nat, amount2: sp.nat) → sp.pair[sp.ticket[t], sp.ticket[t]]
Splits a ticket into two parts with the specified amounts. Fails if
amount1 + amount2
is not equal to the original ticket's amount.
For example, this code creates a ticket, splits it, and joins the split tickets:
# Create ticket
ticket_contents = (sp.int(5), "hello")
big_ticket = sp.ticket(ticket_contents, 100)
# Split ticket
(small_ticket_1, small_ticket_2) = sp.split_ticket(big_ticket, 70, 30)
# Verify ticket amounts
(data_1, small_ticket_1_new) = sp.read_ticket(small_ticket_1)
assert data_1.contents == ticket_contents
assert data_1.amount == 70
(data_2, small_ticket_2_new) = sp.read_ticket(small_ticket_2)
assert data_2.contents == ticket_contents
assert data_2.amount == 30
## Join tickets
joined_ticket = sp.join_tickets(small_ticket_1_new, small_ticket_2_new)
(data_joined, joined_ticket_new) = sp.read_ticket(joined_ticket)
assert data_joined.amount == 100
Transferring tickets
To transfer a ticket, send it as a parameter to an entrypoint with sp.transfer
as usual:
contract_opt = sp.contract(
sp.ticket[sp.pair[sp.int, sp.string]], contract_address, entrypoint="accept_ticket"
)
match contract_opt:
case Some(contract):
sp.transfer(ticket, sp.mutez(0), contract)
case None:
sp.trace("Failed to find contract")