Hello, NFT!

From Wikipedia,

A non-fungible token (NFT) is a unique and non-interchangeable unit of data stored on a digital ledger (blockchain). NFTs can be associated with easily-reproducible items such as photos, videos, audio, and other types of digital files as unique items

In this example, we will show you how to build a ticket vending machine in Scrypto.

You can find the code for this example on github here.

Blueprint and Component

The blueprint we’re building is called HelloNft. Each HelloNft manages the following resources and data.

struct HelloNft {
    /// A vault that holds all available tickets.
    available_tickets: Vault,
    /// The price for each ticket.
    ticket_price: Decimal,
    /// A vault for collecting payments.
    collected_xrd: Vault,
}

The available_tickets contains non-fungible Ticket resource. Both fungible and non-fungible resources are stored in a Vault.

Creating NFT Units

In our example, the supply of NFT units are fixed, and we allocate the resource upfront.

First, we create a struct that will represent the data associated with each ticket NFT.

#[derive(NftData)]
pub struct Ticket {
    pub row: u32,
    pub column: u32,
}

Then, we prepare the data for each NFT unit (every ticket is associated with a specific row and column number).

let mut tickets = Vec::new();
for row in 1..5 {
    for column in 1..5 {
        tickets.push((Uuid::generate(), Ticket { row, column }));
    }
}

Then, the whole vector of NFT data is passed to ResourceBuilder as the initial supply.

let ticket_bucket: Bucket = ResourceBuilder::new_non_fungible()
    .metadata("name", "Ticket")
    .initial_supply_non_fungible(tickets);

After that, we get a bucket of NFT units stored in ticket_bucket. We can put everything together and instantiate our HelloNFT component with a vault containing the tickets like this:

pub fn new(price: Decimal) -> Component {
    // Prepare ticket NFT data
    let mut tickets = Vec::new();
    for row in 1..5 {
        for column in 1..5 {
            tickets.push((Uuid::generate(), Ticket { row, column }));
        }
    }

    // Creates a fixed supply of NFTs.
    let ticket_bucket = ResourceBuilder::new_non_fungible()
        .metadata("name", "Ticket")
        .initial_supply_non_fungible(tickets);

    // Instantiate our component
    Self {
        available_tickets: Vault::with_bucket(ticket_bucket),
        ticket_price: price,
        collected_xrd: Vault::new(RADIX_TOKEN),
    }
    .instantiate()
}

Allowing Callers to Buy Tickets

A HelloNft component exposes three public methods:

  • buy_ticket: allowing caller to buy one ticket;

  • buy_ticket_by_id: allowing caller to buy one specific ticket;

  • available_ticket_ids: returns the IDs of all available tickets.

The workflow of buy_ticket and buy_ticket_by_id is very similar.

pub fn buy_ticket(&mut self, payment: Bucket) -> (Bucket, Bucket) {
    // Take our price out of the payment bucket
    self.collected_xrd.put(payment.take(self.ticket_price));

    // Take any ticket
    let ticket = self.available_tickets.take(1);

    // Return the ticket and change
    (ticket, payment)
}

pub fn buy_ticket_by_id(&mut self, id: u128, payment: Bucket) -> (Bucket, Bucket) {
    // Take our price out of the payment bucket
    self.collected_xrd.put(payment.take(self.ticket_price));

    // Take the specific ticket
    let ticket = self.available_tickets.take_nft(id);

    // Return the ticket and change
    (ticket, payment)
}

Both involves:

  1. Taking a payment according to pre-defined price and putting it into the collected_xrd vault;

  2. Taking a ticket from the available_tickets vault:

    • take(1) returns one NFT unit;

    • take_nft(id) returns the specified NFT unit.

  3. Returning the ticket and payment change.

To write the available_ticket_ids method, you can use the get_nft_ids() method on the tickets vault:

pub fn available_ticket_ids(&self) -> Vec<u128> {
    self.available_tickets.get_nft_ids()
}

Buy That Ticket

  1. Create a new account, and save the account address

    resim new-account
  2. Publish the package, and save the package address

    resim publish .
  3. Call the new function to instantiate a component with a ticket price of 5 XRD, and save the component address

    resim call-function <PACKAGE_ADDRESS> HelloNft new 5
  4. Call the available_ticket_ids method

    resim call-method <COMPONENT_ADDRESS> available_ticket_ids
  5. Call the buy_ticket_by_id method

    resim call-method <COMPONENT_ADDRESS> buy_ticket_by_id <TICKET_ID> "100,030000000000000000000000000000000000000000000000000004"
  6. Check out our balance

    resim show <ACCOUNT_ADDRESS>