Flow Developers

Flow is a fast, secure, and developer-friendly blockchain built to support the next generation of games, apps, and the digital assets that power them.

Start Here

Composable Resources: Kitty Hats

In this tutorial, we're going to walkthrough how resources can own other resources by creating, deploying, and moving composable NFTs.


Open the starter code for this tutorial in the Flow Playground: https://play.onflow.org/068f7218-98b7-4674-bf13-774155d145f0
The tutorial will be asking you do take various actions to interact with this code.

Instructions that require you to take action are always included in a callout box like this one. These highlighted actions are all that you need to do to get your code running, but reading the rest is necessary to understand the language's design.

Resources owning other resources is a powerful feature in the world of blockchain and smart contracts. To showcase how this feature works on Flow, this tutorial will take you through these steps with a composable NFT:

  1. Deploy the Kitty and KittyHat definitions to account 0x01
  2. Create a Kitty and two KittyHats and store them in your account
  3. Move the Kitties and Hats around to see how composable NFTs function on Flow

Before proceeding with this tutorial, we recommend following the instructions in Getting Started and Hello, World! to learn about the Playground and Cadence.

For additional support, see the Playground Manual

Resources Owning Resources


The NFT collections talked about in Non-Fungible Tokens are examples of resources that own other resources. We have a resource, the NFT collection, that has ownership of the NFT resources that are stored within it. The owner and anyone with a reference can move these resources around, but they still belong to the collection while they are in it and the code defined in the collection has ultimate control over the resources.

When the collection is moved or destroyed, all of the NFTs inside of it are moved or destroyed with it.

If the owner of the collection transferred the whole collection resource to another user's account, all of the tokens will move to the other user's account with it. The tokens don't stay in the original owner's account. This is like handing someone your wallet instead of just a dollar bill. It isn't a common action, but certainly is possible.

References cannot be created for resources that are stored in other resources. The owning resource has control over it and therefore controls the type of access that external calls have on the stored resource.

Resources Owning Resources: An Example


The NFT collection is a simple example of how resources can own other resources, but innovative and powerful versions can be made.

An important feature of CryptoKitties (and other applications on the Ethereum blockchain) is that any developer can make new experiences around the existing application. Even though the original contract didn't include specific support for CryptoKitty accessories (like hats), an independent developer was still able to make hats that Kitties from the original contract could use.

Here is a basic example of how we can replicate this feature in Cadence:

Open the account 0x01 tab which has the contract named KittyVerse.cdc
Deploy the code to account 0x01

// KittyVerse.cdc

access(all) contract KittyVerse {

    access(all) resource KittyHat {
        access(all) let id: Int
        access(all) let name: String

        init(id: Int, name: String) {
            self.id = id
            self.name = name
        }

        // An example of a function someone might put in their hat resource
        access(all) fun tipHat(): String {
            if self.name == "Cowboy Hat" {
                return "Howdy Y'all"
            } else if self.name == "Top Hat" {
                return "Greetings, fellow aristocats!"
            } 

            return "Hello"
        }
    }

    access(all) fun createHat(id: Int, name: String): @KittyHat {
        return <-create KittyHat(id: id, name: name)
    }

    access(all) resource Kitty {

        access(all) let id: Int

        // place where the Kitty hats are stored
        access(all) let items: @{String: KittyHat}

        init(newID: Int) {
            self.id = newID
            self.items <- {}
        }

        destroy() {
            destroy self.items
        }
    }

    access(all) fun createKitty(): @Kitty {
        return <-create Kitty(newID: 1)
    }
}

These definitions show how a Kitty resource could own hats.

The hats are stored in a variable in the Kitty resource.

// place where the Kitty hats are stored
access(all) let items: <-{String: KittyHat}

A Kitty owner can take the hats off the Kitty and transfer them individually. Or the owner can transfer a Kitty that owns a hat, and the hat will go along with the Kitty.

Here is a transaction to create a Kitty and a KittyHat, store the hat in the Kitty, then store it in your account storage.

  • Open Transaction1.cdc
  • Select account 0x01 as the only signer
  • Send the transaction by clicking the Send button
    Transaction1.cdc should contain the following code:
// Transaction1.cdc

import KittyVerse from 0x01

transaction {

    prepare(acct: AuthAccount) {

        // create the Kitty object
        let kitty <- KittyVerse.createKitty()

        // create the KittyHat objects
        let hat1 <- KittyVerse.createHat(id: 1, name: "Cowboy Hat")
        let hat2 <- KittyVerse.createHat(id: 2, name: "Top Hat")

        // Put the hat on the cat!
        let oldCowboyHat <- kitty.items["Cowboy Hat"] <- hat1
        destroy oldCowboyHat
        let oldTopHat <- kitty.items["Top Hat"] <- hat2
        destroy oldTopHat

        log("The Cat has the Hats")

        // Store the Kitty in storage
        let oldKitty <- acct.storage[KittyVerse.Kitty] <- kitty
        destroy oldKitty
    }
}

You should see an output that looks something like this:

> "The Cat has the Hats"

Now we can run a transaction to move the Kitty along with its hat, remove the cowboy hat from the Kitty, then make the Kitty tip its hat.

  • Open Transaction2.cdc
  • Select account 0x01 as the only signer
  • Send the transaction
    Transaction2.cdc should contain the following code:
// Transaction2.cdc

import KittyVerse from 0x01

transaction {

    prepare(acct: AuthAccount) {

        // move the Kitty out of storage, which moves its hat along with it
        let kittyOpt <- acct.storage[KittyVerse.Kitty] <- nil
        let kitty <- kittyOpt ?? panic("Kitty doesn't exist!")

        // take the cowboy hat off the Kitty
        let cowboyHatOpt <- kitty.items.remove(key: "Cowboy Hat")
        let cowboyHat <- cowboyHatOpt ?? panic("cowboy hat doesn't exist!")

        // Tip the cowboy hat
        log(cowboyHat.tipHat())
        destroy cowboyHat

        // Tip the top hat that is on the Kitty
        log(kitty.items["Top Hat"]?.tipHat())

        // move the Kitty to storage
        // which moves its hat along with it
        let oldKitty <- acct.storage[KittyVerse.Kitty] <- kitty
        destroy oldKitty
    }
}

You should see something like this output:

> "Howdy Y'all"
> "Greetings, fellow aristocats!"

Whenever the Kitty is moved, its hats are implicitly moved along with it. This is because the hats are owned by the Kitty.

The Future is Meow!


The above is a simple example of composable resources. We had to explicitly say that a Kitty could own a Hat in this example, but in the near future, Cadence will support more powerful ways of achieving resource composability where developers can declare types that separate resources can own even if the owning resource never specified the ownership possibility in the first place.

After these tutorials, you should have enough info to start making some simple smart contracts of your own. Don't hesitate to reach out to the Flow team on our Discord chat channel if you have any questions!

Practice what you're learned in the Flow Playground!

Updated 7 days ago


Composable Resources: Kitty Hats


Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.