Run Your First Scrypto Project
  • 06 May 2024
  • 9 Minutes to read
  • Dark
    Light
  • PDF

Run Your First Scrypto Project

  • Dark
    Light
  • PDF

Article Summary

"Hello, World!" examples usually provide the simplest possible piece of code to understand the basics of a new language. However, Scrypto isn't just a typical language – it is specialized for the management of assets on a decentralized network. So rather than just printing Hello, World! to a console, we'll hand out a HelloToken!

In this learning journey section, we'll walk you through running and interacting with Scrypto within the Radix Engine Simulator. By the end, you'll have locally deployed and used your first Scrypto "smart contract".

This section focuses on getting existing code working on your machine. In the Explain Your First Scrypto Project article, we will delve deeper into what the code is doing.

Before we begin

Make sure you have the Scrypto toolchain installed. If you do, let's get started!

Contents

Developing on Radix

A simple software development workflow might involve writing code, conducting local testing, deploying to a remote test environment for further testing, and finally deploying to the production environment. When it comes to creating smart contracts on Radix, we follow a similar process.

We use Scrypto to write the code and the Radix Engine Simulator (resim) for local testing. Once you're satisfied with your code, you can deploy it to our test network (Stokenet) and then to the main Radix Network (Mainnet).

Our development steps are:

  1. Create a package.
  2. Define code with the business logic
  3. Deploy the code package - using resim for local testing, Stokenet for testing, or Mainnet for production.
  4. Instantiate a component from the package.

Let's start these steps.

Creating a New Blueprint Package

Development starts by creating a package. We'll use the scrypto command line tool for this.

  • Open a new terminal in the directory where you'd like to create your new project and run the command below.

    scrypto new-package tutorial
    

This scaffolds a simple package containing a Hello blueprint.

Blueprints and Packages

Radix splits the concept of "smart contracts" into two parts:

  • A blueprint is the code that defines the behavior of the application and what state it will manage. Blueprints are instantiated into components, which then get their own address and own state. In object-oriented programming terms, you can think of it like a blueprint being a class definition and a component being an object which is an instance of that class.
  • Blueprints are always part of a package when deployed. Multiple blueprints may be grouped together in a single package.
  • Open the new package directory in an IDE/code editor with Rust support.
Choosing a code editor

VSCode is where we've focused most of our efforts on tooling so far.
Find the instructions on how to setup VSCode here.

  • Then open up src/lib.rs to see the source that we’re going to compile and deploy to the simulator.

    code tutorial/src/lib.rs
    

The Hello blueprint has one function which creates a new component with a supply of tokens (instantiate_hello), and one method to get one of those tokens from the component (free_token). A detailed explanation of this blueprint is in the next learning journey section.

So far we've completed steps;

  1. Create a package, and
  2. Define blueprints with the business logic.

Next we deploy the package in the Radix Engine Simulator.

Deploying the package locally via Radix Engine Simulator resim

On distributed ledgers like Radix, each action on the network incurs transaction fees. Transaction fees reflect the load each transaction puts on the network and are used to compensate the network validators.

To deploy our package, we will therefore need an account with enough XRD to cover the transaction fees. We can create both in resim.

Creating an Account

resim has simple commands to create and use accounts in the simulator environment.

  • Most resim commands use an existing account, so before we do anything else, lets create one.

    resim new-account
    

You should get a success message. At the bottom of the output you should see the created account component address, public/private key, and the NonFungibleGlobalId of your owner badge. e.g.:

A new account has been created!
Account component address: account_sim1c956qr3kxlgypxwst89j9yf24tjc7zxd4up38x37zr6q4jxdx9rhma
Public key: 03b9813c2244f864d3f886a8d8716aff5d234fab83a1c6f39d60999ddb01a15f98
Private key: 2ce29b819e212e999c8bd3ab05f529b7868a4667dfe6db03b257ab0560525635
Owner badge: resource_sim1nfzf2h73frult99zd060vfcml5kncq3mxpthusm9lkglvhsr0guahy:#1#
Set up as default account since you had none, you can change it using `set-default-account`.
Warning

Do not attempt to use this private key on the main network! It is seeded from a predictable value and stored in plaintext.

  • If you do not see the line about setting your default account, then you already created an account previously. Either reset your entire simulator with the resim reset command and try again, or set this new account as your default account with the following command:

    resim set-default-account <ACCOUNT_ADDRESS> <PRIVATE_KEY> <OWNER_BADGE>
    

Displaying the Account

  • You can look at the resources in your new account by running:

    resim show <ACCOUNT_ADDRESS>
    
resim show

The show command can be used with any account component, component or resource address.

You will get something like this:

Component Address: account_sim1c956qr3kxlgypxwst89j9yf24tjc7zxd4up38x37zr6q4jxdx9rhma
Blueprint ID: { package_address: package_sim1pkgxxxxxxxxxaccntxxxxxxxxxx000929625493xxxxxxxxxrn8jm6, blueprint_name: "Account" }
Owned Fungible Resources: 1
└─ resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3: 10000 Radix (XRD)
Owned Non-fungibles Resources: 1
└─ resource_sim1nfzf2h73frult99zd060vfcml5kncq3mxpthusm9lkglvhsr0guahy: 1 Owner Badge
   └─ #1#
Metadata: 0

Note the Owned Fungible Resources: section, which indicates that the account already holds 1000 XRD, which will be more than enough to deploy our first package.

Deploying the Package

  • To publish/deploy our package, we need to make sure our terminal has navigated to the project root directory containing the Cargo.toml file:

    cd tutorial
    
  • Then we use the radix engine simulator to publish/deploy our package locally

    resim publish .
    

Once this finishes you should see the published package’s address at the end of the output. e.g.

Success! New Package: package_sim1pk3cmat8st4ja2ms8mjqy2e9ptk8y6cx40v4qnfrkgnxcp2krkpr92
  • Store the address for the last of our 4 development steps, instantiating a component from our package

Component Instantiation

  • Now our package is deployed "on ledger" in the simulator, we're going to create a component by calling the instantiate_hello function on the Hello blueprint.

    resim call-function <PACKAGE_ADDRESS> Hello instantiate_hello
    
You can find an example and explanation of the function call output by clicking here

The instantiate_hello output should look something like this:

Transaction Status: COMMITTED SUCCESS
Transaction Cost: 0.65177966419 XRD
├─ Network execution: 0.30327475 XRD, 6065495 execution cost units
├─ Network finalization: 0.1260127 XRD, 2520254 finalization cost units
├─ Tip: 0 XRD
├─ Network Storage: 0.22249221419 XRD
└─ Royalties: 0 XRD
Logs: 0
Events: 7
├─ Emitter: Method { node: internal_vault_sim1tz9uaalv8g3ahmwep2trlyj2m3zn7rstm9pwessa3k56me2fcduq2u, module_id: Main }
   Event: LockFeeEvent {
     amount: Decimal("5000"),
   }
├─ Emitter: Method { node: resource_sim1t4h3kupr5l95w6ufpuysl0afun0gfzzw7ltmk7y68ks5ekqh4cpx9w, module_id: Main }
   Event: MintFungibleResourceEvent {
     amount: Decimal("1000"),
   }
├─ Emitter: Method { node: resource_sim1t4h3kupr5l95w6ufpuysl0afun0gfzzw7ltmk7y68ks5ekqh4cpx9w, module_id: Main }
   Event: VaultCreationEvent {
     vault_id: NodeId(hex("5855080b052e56996aeedf0045f0ab8b2d6081e2bec248684ba4b5c2b03e")),
   }
├─ Emitter: Method { node: internal_vault_sim1tp2sszc99etfj6hwmuqytu9t3vkkpq0zhmpys6zt5j6u9vp7u6438n, module_id: Main }
   Event: DepositEvent {
     amount: Decimal("1000"),
   }
├─ Emitter: Method { node: internal_vault_sim1tz9uaalv8g3ahmwep2trlyj2m3zn7rstm9pwessa3k56me2fcduq2u, module_id: Main }
   Event: PayFeeEvent {
     amount: Decimal("0.65177966419"),
   }
├─ Emitter: Method { node: internal_vault_sim1tpsesv77qvw782kknjks9g3x2msg8cc8ldshk28pkf6m6lkhun3sel, module_id: Main }
   Event: DepositEvent {
     amount: Decimal("0.325889832095"),
   }
└─ Emitter: Method { node: resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3, module_id: Main }
   Event: BurnFungibleResourceEvent {
     amount: Decimal("0.325889832095"),
   }
Outputs: 3
├─ Unit
├─ Reference("component_sim1crkp7q8sfhg7xa0xvqtdjezltj3hams2hrk4ztzqs2c90sy0cslv6a")
└─ Enum::[0]
Balance Changes: 3
├─ Vault: internal_vault_sim1tz9uaalv8g3ahmwep2trlyj2m3zn7rstm9pwessa3k56me2fcduq2u
   ResAddr: resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3
   Change: -0.65177966419
├─ Vault: internal_vault_sim1tp2sszc99etfj6hwmuqytu9t3vkkpq0zhmpys6zt5j6u9vp7u6438n
   ResAddr: resource_sim1t4h3kupr5l95w6ufpuysl0afun0gfzzw7ltmk7y68ks5ekqh4cpx9w
   Change: 1000
└─ Vault: internal_vault_sim1tpsesv77qvw782kknjks9g3x2msg8cc8ldshk28pkf6m6lkhun3sel
   ResAddr: resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3
   Change: 0.325889832095
New Entities: 2
└─ Component: component_sim1crkp7q8sfhg7xa0xvqtdjezltj3hams2hrk4ztzqs2c90sy0cslv6a
└─ Resource: resource_sim1t4h3kupr5l95w6ufpuysl0afun0gfzzw7ltmk7y68ks5ekqh4cpx9w

This reads as:

CaptionDescription
Transaction StatusTransaction was successful.
Transaction CostThis sections describes the fees you had to pay to execute this transaction. Read more about fees here.
LogsThere were no logs.
EventsThe transaction first executed a call to lock the fees. Then it executed one CallFunction instruction, that called the instantiate_hello function which in turn issued a CallMethod instruction. The events emitted show us what happened as a result of the manifest instructions.
Outputs and Balance ChangesThe new instantiated ComponentAddress and information about resource movement is displayed here
New EntitiesThere were two new entities created: the Hello component and the HelloToken resource.

This creates two new entities with different addresses:

  • a new HelloToken with a ResourceAddress,
  • and your fresh Hello component with a ComponentAddress. Store the component address for later use.

We have now completed all the development steps.

  1. Create a package.
  2. Define code with the business logic
  3. Deploy the code package to resim.
  4. Instantiate a component from the package.

Using the Hello Component in resim

Now that we have our component in our local environment, we can interact with the it.

  • Let's call the free_token method.

    resim call-method <COMPONENT_ADDRESS> free_token
    

This uses resim call-method rather than the just used resim call-function since we are now calling a method on an instantiated component instead of a function on a blueprint.

You can find an example and explanation of the method call output by clicking here

The free_token output should look something like this:

Transaction Status: COMMITTED SUCCESS
Transaction Cost: 0.43160387771 XRD
├─ Network execution: 0.30021485 XRD, 6004297 execution cost units
├─ Network finalization: 0.0363077 XRD, 726154 finalization cost units
├─ Tip: 0 XRD
├─ Network Storage: 0.09508132771 XRD
└─ Royalties: 0 XRD
Logs: 1
└─ [INFO ] My balance is: 1000 HelloToken. Now giving away a token!
Events: 8
├─ Emitter: Method { node: internal_vault_sim1tz9uaalv8g3ahmwep2trlyj2m3zn7rstm9pwessa3k56me2fcduq2u, module_id: Main }
   Event: LockFeeEvent {
     amount: Decimal("5000"),
   }
├─ Emitter: Method { node: internal_vault_sim1tp2sszc99etfj6hwmuqytu9t3vkkpq0zhmpys6zt5j6u9vp7u6438n, module_id: Main }
   Event: WithdrawEvent {
     amount: Decimal("1"),
   }
├─ Emitter: Method { node: resource_sim1t4h3kupr5l95w6ufpuysl0afun0gfzzw7ltmk7y68ks5ekqh4cpx9w, module_id: Main }
   Event: VaultCreationEvent {
     vault_id: NodeId(hex("5825b3cabab992e4d46fda2ff9dad46a8b2021785ca645d0e2986f0824a2")),
   }
├─ Emitter: Method { node: internal_vault_sim1tqjm8j46hxfwf4r0mghlnkk5d29jqgtctjnyt58znphssf9zm6gyg5, module_id: Main }
   Event: DepositEvent {
     amount: Decimal("1"),
   }
├─ Emitter: Method { node: account_sim1c956qr3kxlgypxwst89j9yf24tjc7zxd4up38x37zr6q4jxdx9rhma, module_id: Main }
   Event: DepositEvent::Fungible(
     ResourceAddress(Reference("resource_sim1t4h3kupr5l95w6ufpuysl0afun0gfzzw7ltmk7y68ks5ekqh4cpx9w"),
     Decimal("1"),
   )
├─ Emitter: Method { node: internal_vault_sim1tz9uaalv8g3ahmwep2trlyj2m3zn7rstm9pwessa3k56me2fcduq2u, module_id: Main }
   Event: PayFeeEvent {
     amount: Decimal("0.43160387771"),
   }
├─ Emitter: Method { node: internal_vault_sim1tpsesv77qvw782kknjks9g3x2msg8cc8ldshk28pkf6m6lkhun3sel, module_id: Main }
   Event: DepositEvent {
     amount: Decimal("0.215801938855"),
   }
└─ Emitter: Method { node: resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3, module_id: Main }
   Event: BurnFungibleResourceEvent {
     amount: Decimal("0.215801938855"),
   }
Outputs: 3
├─ Unit
├─ Own("internal_component_sim1lruex79e7m0tgvp852a0298xlmk5acltxnu8rp785yufcd26vcf5hg")
└─ Enum::[0]
Balance Changes: 4
├─ Vault: internal_vault_sim1tz9uaalv8g3ahmwep2trlyj2m3zn7rstm9pwessa3k56me2fcduq2u
   ResAddr: resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3
   Change: -0.43160387771
├─ Vault: internal_vault_sim1tp2sszc99etfj6hwmuqytu9t3vkkpq0zhmpys6zt5j6u9vp7u6438n
   ResAddr: resource_sim1t4h3kupr5l95w6ufpuysl0afun0gfzzw7ltmk7y68ks5ekqh4cpx9w
   Change: -1
├─ Vault: internal_vault_sim1tqjm8j46hxfwf4r0mghlnkk5d29jqgtctjnyt58znphssf9zm6gyg5
   ResAddr: resource_sim1t4h3kupr5l95w6ufpuysl0afun0gfzzw7ltmk7y68ks5ekqh4cpx9w
   Change: 1
└─ Vault: internal_vault_sim1tpsesv77qvw782kknjks9g3x2msg8cc8ldshk28pkf6m6lkhun3sel
   ResAddr: resource_sim1tknxxxxxxxxxradxrdxxxxxxxxx009923554798xxxxxxxxxakj8n3
   Change: 0.215801938855
New Entities: 0

This reads as:

CaptionDescription
Transaction statusTransaction was successful.
Transaction Fee and Cost UnitsThis sections describes the fees you had to pay to execute this transaction. Read more about fees here.
LogsThere was one log message. The message was: "[INFO ] My balance is: 1000 HelloToken. Now giving away a token!"
EventsThe transaction first locked the fees. Then it executed one CallMethod instruction, that called the free_token method which in turn issued another CallMethod instruction to withdraw and store the HelloToken in your account.
Outputs and Balance ChangesInformation about resource movement is displayed here

You'll now have a shiny new HelloToken in your account, and your Hello component has one less.

  • We can check this by using resim show on our account and Hello components.

    resim show <ACCOUNT_ADDRESS>
    
    resim show <COMPONENT_ADDRESS>
    

Final Notes

  • If you make changes to the structs within your code, then unfortunately you will have to run through the entire publish-instantiate-call flow from scratch, saving the new addresses as they appear. If you only make implementation changes then it is possible to update your package with:

    resim publish . --package-address <PACKAGE_ADDRESS>
    
  • At any point you can instantly get a clean slate in the simulator by running:

    resim reset
    

    You almost certainly need to do this if you switch to working on a different project.

Well done. You've finished this introductory tutorial on using resim for Scrypto. You can continuing your learning journey with our explanation of the package code in the next section, Explain Your First Scrypto Project.

Let us know if you find any section helpful or not by clicking one of the buttons below ⬇. You can also let us know about a typo or outdated information using the same buttons.


Was this article helpful?