Flow Development on DappStarter: Smart Contracts and DappLib

July 5, 2021

DappStarter makes it easy for you to generate a full-stack source code Flow blockchain application. Once you clone the auto-generated project GitHub repository and open the project in your IDE such as VSCode, you’re probably wondering how the project is organized. Here’s your answer. 

In this document, I’ll lead you through the project structure and explain the purpose of key files. As a rule of thumb, if something is not described here, it is most likely something you can safely ignore.


Let’s get started with the directory structure, beginning from the root directory /packages


This directory is where you will do almost all of your coding. It includes three things:

  1. Dapplib: smart contract code, unit tests and the bridge between your client code and the blockchain.
  2. Client: Front-end code including UI harnesses for development and testing.
  3. Server: Server-side API for administrative function and event handling.

/packages/dapplib


The /packages/dapplib directory contains smart contract code and the JavaScript code that bridges your client and the blockchain. The packaged output of this directory is a JavaScript library that may be used with any web or mobile application and provides all the requisite functionality for interacting with your smart contracts.

Before we walk through the directory structure, it’s useful to step back and understand the DappStarter architecture. Much of the orchestration of DappStarter is handled by the Rhythm Toolkit, a collection of utility scripts located in the /packages/dapplib/src subdirectory. Here’s what each script does:




Now that you have some insights into the architecture, let’s explore the project directory structure.

/packages/dapplib/contracts


The /packages/dapplib/contracts contains all your smart contracts. The directory is watched and upon any change in files, the Flow emulator is restarted and all smart contracts are redeployed. In addition all test accounts are regenerated wiping out any prior state.


One important decision you will need to make is how to break up your smart contracts into files. It’s important to follow the convention established in DappStarter so the Rhythm deployment scripts work as expected:


Figure 1. This shows a very basic contracts directory structure. The Flow team’s implementation of NonFungibleToken resides within the Flow directory, while an ExampleNFT contract that I created is in the Project directory.


When deploying Cadence contracts, the order in which they are deployed is significant for imported contracts. You don’t have to worry about whether or not contracts that import other contracts get deployed before/after their dependency contracts. This will be automatically handled for you. 


This means if you have a contract named ExampleFungibleToken that gets imported inside ExampleNFT, you can place it in the Project directory as well. You can also create an arbitrary subdirectory structure within the Project directory. I like to use “imports,” but you can name these subdirectories anything you wish. This is shown in Figure 2.


Figure 2. An example of how to use a subdirectory if you would like to keep a more structured layout for contracts that import other contracts. Note that this is not necessary.


DappStarter makes importing smart contracts extremely easy using this format: 


import [EntityName] from [Source].[ContractName]


EntityName: Entity being imported

Source: Currently supported sources are “Flow,” “Decentology,” and “Project”

ContractName: Contract name


An example of this can be found in Figure 3. It isn’t necessary for a contract file to be named the same as the contract it contains, but this is a good convention to follow.


Figure 3. As you can see, our mint_nft transaction is importing the ExampleNFT contract from the /packages/dapplib/contracts/Project folder. Thus, our import statement is import ExampleNFT from Project.ExampleNFT.


If you want to know where exactly the smart contracts are deployed you can find this information in /packages/dapplib/src/dapp-config.json. This is a configuration file that is auto-generated and managed by DappStarter.

/packages/dapplib/interactions


The /packages/dapplib/interactions directory contains all your transactions and scripts. An example of a properly setup interactions directory is shown in Figure 4.


Figure 4. There are two transactions: mint_nft and transfer_nft. There is one script: get_ids. These will all be called from /packages/dapplib/src/dapp-lib.js, described later.


You can also create subdirectories within your transactions and scripts directories if you want a more structured layout. For example, let’s say we have transactions and scripts for both FungibleTokens and NonFungibleTokens. We can have a greater separation by adding subdirectories as shown in Figure 5.

Figure 5. DappStarter allows you to separate our transactions and scripts into subdirectories if you desire a more structured approach. Here we have separated our transactions and scripts into fungibletoken and nft subdirectories and filled in some dummy .cdc files for our example.


In order to make development faster and easier, DappStarter takes a different approach to using transactions and scripts in client JavaScript. Without DappStarter, you would use the Flow FCL library and add the transaction and script Cadence language code within your JavaScript client-side code. This can be confusing and hard to maintain. 


DappStarter solves this problem by including a hot-loading transpiler that watches the interactions subdirectory and auto-generates /packages/dapplib/src/dapp-scripts.js and /packages/dapplib/src/dapp-transactions.js. These script files can be called from any JavaScript code and will execute the Cadence transaction or script automatically. If you modify transactions/scripts inside your interactions/ directory, they will be auto-transpiled. This means you do not have to restart your Dapp when updating code.


Understanding DappLib Methods


As mentioned above, dapp-lib.js contains auto-generated code based on the modules you selected when creating your project. It is also where you can add additional methods for calling scripts and transactions you define for interacting with your smart contract. Let’s take a look at the specific steps to do this — Figure 6 shows how to set up a simple Javascript method inside dapp-lib.js that will be used to call the mint_nft.cdc transaction. 


Figure 6. This shows how to set up a Javascript method called mintNFT that will be called from the client. This will call the mint_nft.cdc transaction and pass in the recipient parameter that is accessed from the data object argument.


Here’s what’s happening in the code:

  1. A data argument is passed by the calling client. For this example, we passed in two properties: admin and recipient.
  2. config is used to access information in dapp-config.js, which holds all the information about where the contracts are deployed, the service wallet, and more. 
  3. We use the Blockchain.post() method to call a transaction. If this were a script, this would instead be Blockchain.get(). The definitions for both of these methods can be found in /packages/dapplib/src/blockchain.js.
  4. The proposer key-value pair inside the roles object is used as the proposer, signer, and authenticator. On Flow, this equates to the AuthAccount in a transaction’s prepare phase. (If the code you are seeing is different, it is likely because the planned Flow Wallet Discovery enhancement for DappStarter has been completed. Look for an updated version of this article for details.)
  5. On Line 22, “mint_nft” is the name of the transaction or script file inside your interactions directory. NOTE: If your transaction/script is in a subdirectory, you must prefix this value with the name of that subdirectory followed by a “_”. For example, if the subdirectory is named “nft,” we would write ‘nft_mint_nft’ on line 22.
  6. The object following the name of your transaction/script contains the arguments you pass in to the transaction/script. Keys are the name of the argument, and values are an object that specifies a value and type. You most often get the value from your data object, and types are Flow-specific types that can be found here: https://github.com/onflow/flow-js-sdk/tree/master/packages/types
  7. The return object has 3 keys:
  1. type - this is the format of the result. If you are calling a transaction, this should stay DappLib.DAPP_RESULT_TX_HASH. If this is a script, and you are returning an Array, Number, or something else, you can specify this by using DAPP_RESULT_ARRAY, DAPP_RESULT_BIG_NUMBER, or any other option in the “DAPP LIBRARY” section of the dapp-lib.js file.
  2. label - the label that will render next to your result in the UI Harness
  3. result - if you are running a transaction, this should stay result.callData.transactionId. If you are calling a script, you will most likely want to change this to result.callData, however you can console.log result to see what you would like to return. Any result value will get rendered in the UI Harness if you have used the right type.

You may be wondering how dapp-lib.js is actually calling these transactions/scripts. This process is shown in Figure 7.

Figure 7. This shows the chain of events when we want to execute a transaction/script.


In this article, I provided details on developing and using Flow Smart Contracts with DappStarter. I also reviewed the blockchain interaction architecture used by DappLib and covered some sample code. In Part 2, I’ll cover the DappStarter client libraries, UI Harness and provide sample code for interacting with DappLib.

Start buiilding with DappStarter and join our Discord!

💌
Join the newsletter & get updates to your inbox.
Your information has been saved.
Looks like we're having trouble