|
| 1 | +--- |
| 2 | +sidebar_position: 4 |
| 3 | +--- |
| 4 | + |
| 5 | +# Handling tickets in Deku |
| 6 | + |
| 7 | +Unlike Tezos, Deku does not have an native currency, but tickets. These tickets are directly owned |
| 8 | +by the accounts, and can be transferred between accounts or directly sent to smart contracts using |
| 9 | +the client. In this chapter, we show how to mint and send tickets on Deku. |
| 10 | + |
| 11 | +## Minting tickets in Deku |
| 12 | + |
| 13 | +Here is an example, written in JSLigo, of a smart contract that expects `bytes` and mints a `byte |
| 14 | +ticket` before sending it back to the sender: |
| 15 | + |
| 16 | +```jsligo |
| 17 | +type storage = unit; |
| 18 | +
|
| 19 | +const transfer_ticket = (ticket:ticket<bytes>, target: address) => { |
| 20 | + return match(Tezos.get_contract_opt (target), { |
| 21 | + None: () => (failwith("Incorrect address")), |
| 22 | + Some: (c:contract<unit>) => { |
| 23 | + return Tezos.transaction (ticket, (0 as mutez), c); |
| 24 | + } |
| 25 | + }); |
| 26 | +}; |
| 27 | +
|
| 28 | +const main = (payload: bytes, storage: storage) => { |
| 29 | + let ticket = Tezos.create_ticket (payload, (100 as nat)); |
| 30 | + let op = transfer_ticket(ticket, Tezos.get_sender ()); |
| 31 | + return [list([op]), storage]; |
| 32 | +} |
| 33 | +``` |
| 34 | + |
| 35 | +This contract is fairly straightforward: the `storage` does not change, and the `main` function simply takes the bytes `payload` from the sender, uses it to mint 100 tickets and send those to the sender. As on Tezos, implicit accounts are represented as contracts of the `unit` type. |
| 36 | + |
| 37 | +Here is how to compile and originate the contract on Deku: |
| 38 | + |
| 39 | +```bash |
| 40 | +$ ligo compile contract ticket.jsligo > ticket.tz |
| 41 | +$ deku-cli originate wallet.json ticket.tz 'Unit' |
| 42 | +operation hash: Do3UPjj84fPZnsR43shUUUhaj3pRJjbXrketwNThEwjJctAUgHAE |
| 43 | +Contract originated at address DK1JxMLmohbyKXbGhQWLJZyktSsZKGxaM6jz |
| 44 | +``` |
| 45 | + |
| 46 | +This contract can now be called to mint some tickets: |
| 47 | + |
| 48 | +```bash |
| 49 | +$ deku-cli invoke --endpoint http://0.0.0.0:8080 wallet.json DK1JxMLmohbyKXbGhQWLJZyktSsZKGxaM6jz '0x' |
| 50 | +operation hash: Do2MZ9egKWCtEXz7MLVcDFGvB19CHaAiaJg5nXU8we5ey4HXL8UJ |
| 51 | +$ deku-cli show-balance tz1Vhy8BWXdQNm6wNFi36hWj4iSoGqBrMB4v |
| 52 | +[ |
| 53 | + { |
| 54 | + ticket: { ticketer: 'DK1JxMLmohbyKXbGhQWLJZyktSsZKGxaM6jz', data: '' }, |
| 55 | + amount: 100 |
| 56 | + } |
| 57 | +] |
| 58 | +``` |
| 59 | +
|
| 60 | +Fresh tickets, nice! |
| 61 | +
|
| 62 | +## Consuming tickets in a smart contract |
| 63 | +
|
| 64 | +Let's take a look at tickets consumption by smart contracts. Here's an example of contract that |
| 65 | +waits |
| 66 | +for a ticket and an address, stores the ticket, and sends the ticket to the user when they request |
| 67 | +it. |
| 68 | +
|
| 69 | +```jsligo |
| 70 | +type deposit = option<[address, ticket<bytes>]>; |
| 71 | + |
| 72 | +type storage = { |
| 73 | + deposit: deposit |
| 74 | +}; |
| 75 | + |
| 76 | +type entrypoint = |
| 77 | + | ["Deposit", ticket<bytes>, address] |
| 78 | + | ["Withdraw"] |
| 79 | + |
| 80 | +const transfer_ticket = (ticket:ticket<bytes>, target: address) => { |
| 81 | + return match(Tezos.get_contract_opt (target), { |
| 82 | + None: () => (failwith("Incorrect address")), |
| 83 | + Some: (c:contract<unit>) => { |
| 84 | + return Tezos.transaction (ticket, (0 as mutez), c); |
| 85 | + } |
| 86 | + }); |
| 87 | +}; |
| 88 | + |
| 89 | +const main = (action: entrypoint, storage: storage) => { |
| 90 | + return match(action, { |
| 91 | + Deposit: (deposit:[ticket<bytes>, address]) => { |
| 92 | + let [ticket, to_] = deposit; |
| 93 | + assert_none(storage.deposit); |
| 94 | + let [[_ticketer, [_data, _amount]], ticket] = Tezos.read_ticket(ticket); |
| 95 | + let deposit = Some([to_, ticket]); |
| 96 | + return [list([]), {deposit}]; |
| 97 | + }, |
| 98 | + Withdraw: () => { |
| 99 | + let sender = Tezos.get_sender(); |
| 100 | + let [address, ticket] = Option.unopt_with_error( |
| 101 | + storage.deposit, "Nothing to withdraw." |
| 102 | + ); |
| 103 | + assert_with_error(address == sender, "You're not allowed to withdraw"); |
| 104 | + let transaction = transfer_ticket(ticket, address); |
| 105 | + return [list([transaction]), {deposit:None()}]; |
| 106 | + } |
| 107 | + }); |
| 108 | +} |
| 109 | +``` |
| 110 | +First, notice that this contract's storage is more involved than the previous one, as we need to |
| 111 | +store an address and a ticket so that another user can retrieve it. The `main` function waits for a |
| 112 | +deposit when the storage is `None`, and a withdraw otherwise. |
| 113 | +
|
| 114 | +Let's name this contract `vault.jsligo`. Compilation and origination are the same as before: |
| 115 | +
|
| 116 | +```bash |
| 117 | +$ ligo compile contract vault.jsligo > vault.tz |
| 118 | +$ deku-cli originate wallet.json vault.tz 'None' |
| 119 | +operation hash: Do2p2Le3he2kwnng7SKcizNq3715FcsaysCohFrPgVd9zFNfwc56 |
| 120 | +Contract originated at address DK141PdoGDp9gZea9okEiPJfL3Y9z5GhhZ5T |
| 121 | +``` |
| 122 | +
|
| 123 | +First, let's inspect this contract's storage: |
| 124 | +
|
| 125 | +```bash |
| 126 | +$ deku-cli show-storage DK141PdoGDp9gZea9okEiPJfL3Y9z5GhhZ5T |
| 127 | +{ none: true } |
| 128 | +``` |
| 129 | +
|
| 130 | +This prints the VM's state for this contract, which does not follow Tezos and Michelson's |
| 131 | +conventions. However it seems to match the value we provided when we originated — so far, so good. |
| 132 | +
|
| 133 | +Let's make a deposit. Unfortunately, we can't ask the Ligo compiler to produce the command line |
| 134 | +argument for us, nor we can use the `invoke-ligo` endpoint: indeed, for the moment there is no way |
| 135 | +to represent a ticket expression in Ligo to give to these commands! We thus have to write it by |
| 136 | +hand. |
| 137 | +
|
| 138 | +We know we want to call the first entrypoint, so the expression has to start with `Left`. We also |
| 139 | +want to give two arguments: the ticket itself (with an amount) and the deposit address, so we'll |
| 140 | +have to use `Pair`s. The ticket is designated by the minting contract (called the "ticketer"), the |
| 141 | +data bytes and an amount. Using the ticket from the previous contract, we get the following |
| 142 | +Michelson expression: |
| 143 | +
|
| 144 | +``` |
| 145 | +'Left (Pair (Pair "DK1JxMLmohbyKXbGhQWLJZyktSsZKGxaM6jz" 0x 10) "tz1Vhy8BWXdQNm6wNFi36hWj4iSoGqBrMB4v")' |
| 146 | +``` |
| 147 | +
|
| 148 | +As noted in the previous chapter though, in order for Deku to accept to change our balance, we have |
| 149 | +to explicitely give permission to handle this ticket, by writing its information again after the |
| 150 | +`invoke` argument. Finally this gives us the following command: |
| 151 | +
|
| 152 | +```bash |
| 153 | +$ deku-cli invoke wallet.json DK141PdoGDp9gZea9okEiPJfL3Y9z5GhhZ5T \ |
| 154 | + 'Left (Pair (Pair "DK1JxMLmohbyKXbGhQWLJZyktSsZKGxaM6jz" 0x 10) \ |
| 155 | + "tz1Vhy8BWXdQNm6wNFi36hWj4iSoGqBrMB4v")' \ |
| 156 | + '(Pair "DK1JxMLmohbyKXbGhQWLJZyktSsZKGxaM6jz" 0x 10)' |
| 157 | +``` |
| 158 | +
|
| 159 | +This command can be made shorter using a variable: |
| 160 | +
|
| 161 | +```bash |
| 162 | +$ TICKET='(Pair "DK1JxMLmohbyKXbGhQWLJZyktSsZKGxaM6jz" 0x 10)' |
| 163 | +$ deku-cli invoke wallet.json DK141PdoGDp9gZea9okEiPJfL3Y9z5GhhZ5T \ |
| 164 | + "Left (Pair $TICKET \"tz1Vhy8BWXdQNm6wNFi36hWj4iSoGqBrMB4v\")" "$TICKET" |
| 165 | +``` |
| 166 | +
|
| 167 | +Let's check out balance to see if the deposit worked: |
| 168 | +
|
| 169 | +```bash |
| 170 | +$ deku-cli show-balance tz1Vhy8BWXdQNm6wNFi36hWj4iSoGqBrMB4v |
| 171 | +[ |
| 172 | + { |
| 173 | + ticket: { ticketer: 'DK1JxMLmohbyKXbGhQWLJZyktSsZKGxaM6jz', data: '' }, |
| 174 | + amount: 90 |
| 175 | + }z |
| 176 | +] |
| 177 | +``` |
0 commit comments