Hi! ๐ My name is Phill and I am Blockchain Tech Lead at The Fabricant and a Community Rep for Flow. You can find me on:
Discord: Phill#1854
Email: fullstackpho@protonmail.com
Github: github.com/ph0ph0
Twitter: twitter.com/fullstackpho
๐ Intro
Shell scripts can be incredibly useful for automating repetitive tasks and speeding up development workflows. In this post, which is intended for experienced Flow developers, I'll show you how to use shell scripts to simplify and accelerate building dApps and smart contracts on the Flow blockchain with Cadence.
An important part of smart contract development is writing automated tests such as unit tests and integration tests. This involves using a testing framework that runs your tests, and your tests call scripts, transactions and smart contract functions. The testing framework then has methods that can be used to automatically validate the state/output of the calls.
During the developmental cycle, developers often manually test their contracts, not to replace automated tests, but to complement them. The automated tests ensure the code's functionality and security, while manual tests facilitate immediate checks, letting developers verify functions and outputs during the coding phase.
๐ก The Value of Shell Scripts in Smart Contract Development
Manual testing is useful during the iteration phase when the developer is coding the contract and needs to quickly check that the functions work as expected. The developer can easily spin up the emulator, deploy their new contract, run scripts/transactions and then determine if their logic needs some modifications.
Manual testing in this way allows you to test 'closer to the code' since you are directly running the scripts/transactions yourself and not relying on a third-party testing tool/framework to run and validate the code. Therefore, if you do encounter any bugs, you can be certain that they are in your code and not in the codebase of the testing tool.
In practice, a developer may write a function with a specific state change and/or output in mind. Let's imagine that this function is a mint function, that when called, deposits an NFT into the user's account. To mint an NFT, the user's address must be added to an access list by the contract Admin. The minting state of the contract must also be set to 'open' so that users can mint NFTs, alongside various other configurations. The buyers of the NFT must be funded FLOW so that they can make their purchase.
To manually test their function, the developer must therefore start the emulator, deploy their contract, add the user's address to the access list, run the other config functions, fund the buyer's accounts, and only then can they test their mint function. If the developer is running these scripts and transactions via the CLI, then getting to the state required before minting can be laborious and hinder iteration.
๐จโ๐ป Creating a Shell Script for Efficient Testing
On Flow, it is possible to save the emulator state so that you can reload that state as a starting point from which to run your code. However, during iteration, you may change state variables, functions, outputs etc so that previously saved states are invalidated or no longer useful for what you are trying to achieve.
Shell scripts are useful because they allow you to chain together commands and control program flow, allowing you to consistently reach a defined system state in the emulator.
To revisit our previous example of minting an NFT, the developer can write a single shell script that deploys the contract and runs the necessary transactions to achieve the required state before minting the NFT. Running this script is much quicker than typing out the individual CLI commands for running each script and transaction.
So how do we create our shell script to quickly and easily run a series of transactions for us? For this example, we will use the scenario described above, where we need to run several transactions to achieve a particular state before minting NFTs. This demonstration is a hypothetical scenario, in which the developer has created a Flow NFT project containing all of the necessary scripts and transactions for them to iterate on their contract code.
In a Flow project, we create a file called setup_nft_minting.sh
. The file extension, .sh
, indicates that it is a shell file. Inside this file, we have the following code:
# 1) Deploy contracts
flow project deploy
# 2) Create emulator-account-1
flow accounts create --key 5af4720a97c1fa50aa80b72d68c7bbc0779d198e9fe5e2b84d49ba852f20601fa23aa547635ec5e5d7d59a4da2cde5813577e2087a081303d85d27d59c401c5e
# 3) Create emulator-account-2
flow accounts create --key 5af4720a97c1fa50aa80b72d68c7bbc0779d198e9fe5e2b84d49ba852f20601fa23aa547635ec5e5d7d59a4da2cde5813577e2087a081303d85d27d59c401c5e
# 4) Mint FLOW into accounts
flow transactions send ./cadence/transactions/fungibletoken/flow/mint_flow.cdc 0x179b6b1cb6755e31 1000.0
flow transactions send ./cadence/transactions/fungibletoken/flow/mint_flow.cdc 0xf8d6e0586b0a20c7 1000.0
flow transactions send ./cadence/transactions/fungibletoken/flow/mint_flow.cdc 0x01cf0e2f2f715450 1000.0
# 5) Run various config functions
flow transactions send ./cadence/transactions/nft/run_config.cdc
# 6) Add all 3 addresses to access list
flow transactions send ./cadence/transactions/nft/add_addresses_to_access_list.cdc '{0x179b6b1cb6755e31: true, 0xf8d6e0586b0a20c7: true, 0x01cf0e2f2f715450: true}'
# 7) Open minting in nft contract
flow transactions send ./cadence/transactions/nft/set_minting_is_open.cdc true
# 8) Mint NFT
flow transactions send ./cadence/transactions/nft/mint_nft.cdc --signer emulator-account-1
So as you can see from the commands in the shell script, the following will occur in the emulator:
The contracts in the project are deployed.
Create emulator-account-1. You will need the associated JSON for this account in your
flow.json
file, which will allow you to sign transactions for this account.Create emulator-account-2. You will need the associated JSON for this account in your
flow.json
file, which will allow you to sign transactions for this account.Next, we mint FLOW into the default emulator account and our emulator-account-1 and 2. This will allow them to pay for minting the NFTs.
In this step, we are running the transactions that are needed to setup the contract.
Our NFT contract in this scenario has an access list. Therefore we are adding the addresses to the list.
In order for users to mint, we must open minting in the contract.
Finally, we can call our mint function.
Before we can run this script, we must first grant permission for the script to be executed. On a Mac, you can achieve this with:
chmod 755 ./setup_nft_minting.sh
With the permissions updated, we can now run the script.
Open up a terminal instance, and run
flow emulator
to start the emulator.Open another terminal instance. I usually use 'split terminal' in VS Code for this, so that the left window runs the emulator, and the right window is where i can run my scripts.
Run the script in the new terminal instance window using:
./setup_nft_minting.sh
When running the script, you will see the output in both the emulator terminal window and the window that the script is running in. Once the mint_nft
transaction has run, we can check the output in the terminal. Did the transaction run successfully? Was the contract state updated as expected? Was the correct event emitted? These are a few examples of what we can look at to determine if we need to make any changes to our logic. An example output is given below:
On the left, we can see the running emulator, and on the right, we can see the final successful transaction ๐ช
Using shell scripts in this way allows you to quickly iterate during development by efficiently checking that functionality works as expected!
To view an example repository that demonstrates the use of shell scripts in Flow development, please visit this github repo below. Inside you will find a shell script that runs similar transactions to those given above, alongside the steps in how to setup and use the repo ๐
https://github.com/ph0ph0/FlowShellScriptsTutorial
๐๏ธ Running Multiple Transactions Sequentially
You may come across a scenario where you need to run many transactions sequentially. For example, you may not have access to infrastructure that can sign multiple transactions simultaneously using a single account, so that all transactions are in-flight at the same time. You may alternatively have a situation where the transactions must be run in a defined order.
If the transactions must be run sequentially, one option is to type each CLI command into the terminal, wait for the transaction to execute, and then type the next command. Obviously, this is incredibly inefficient, so this is something that we should avoid.
One such situation where you may need to run many separate transactions might be when sending NFTs to other users, and you don't have access to infrastructure that allows you to sign and send multiple transactions simultaneously.
For example, before your NFT project opened minting publicly, your team minted 1000 NFTs and stored these in a project-owned archive wallet. Minting is now closed, and your team would like to send these 1000 NFTs to 1000 different addresses. This can quickly and easily be achieved using a shell script.
Your team has provided you with an excel sheet containing all 1000 addresses that should receive an NFT, as well as the ID of the NFT that each address should receive.
Your first step would be to copy both columns into a file in VS Code called transfer_nfts.sh
(feel free to choose a more descriptive filename for your project). VS Code is smart enough to keep horizontally adjacent cells on the same line, and vertically adjacent cells on different lines, so the file should look something like this (only 3 lines are shown for brevity):
0x123 1
0x456 2
0x789 3
...
Where the 0x
values on the left are the addresses and the integer values on the right are the NFT IDs.
Let's assume that your transfer_nft Flow transaction takes two parameters, the address to transfer the NFT to, and the NFT ID of the NFT to transfer.
Using the multi-cursor functionality of VS Code, we can place a cursor on every line in the file. Click on the first line on the left of the first address (in other words, the start of the first line).
On Mac, while pressing 'alt + shift', click on the left of the address on the last line (start of the last line). You will now have a cursor at the start of every line.
Write your transfer transaction CLI command, which will now appear on every line. Your file will look something like this:
flow transactions send ./cadence/transactions/nft/transfer_nft 0x123 1
flow transactions send ./cadence/transactions/nft/transfer_nft 0x456 2
flow transactions send ./cadence/transactions/nft/transfer_nft 0x789 3
Whilst still using the multi-cursor function, navigate to the end of each line by pressing the right keyboard arrow (or cmd + right arrow).
Now we can add our signer and network flags. Your file will look something like this:
flow transactions send ./cadence/transactions/nft/transfer_nft 0x123 1 --signer admin-account -n mainnet
flow transactions send ./cadence/transactions/nft/transfer_nft 0x456 2 --signer admin-account -n mainnet
flow transactions send ./cadence/transactions/nft/transfer_nft 0x789 3 --signer admin-account -n mainnet
It would be useful to save the output to a file so that we can ensure that every transfer was successful. We can do that in shell scripts using the "append redirection" operator, >>
. This is used to append the output of a command to a file and if the file does not exist, it will be created. The output is appended to the file during the execution of the script, so we can also use the output file to monitor progress.
After the signer and network flags, update each file to include the redirection operator and a filename for the output to be saved to. Your shell script should now look like this:
flow transactions send ./cadence/transactions/nft/transfer_nft 0x123 1 --signer admin-account -n mainnet >> nft_transfer_output.log
flow transactions send ./cadence/transactions/nft/transfer_nft 0x456 2 --signer admin-account -n mainnet >> nft_transfer_output.log
flow transactions send ./cadence/transactions/nft/transfer_nft 0x789 3 --signer admin-account -n mainnet >> nft_transfer_output.log
Before we can run this script, we must first grant permission for the script to be executed. On a Mac, you can achieve this with:
chmod 755 ./transfer_nfts.sh
After checking that all of the parameters are correct (we don't want to send NFTs to the wrong addresses!), we can run the shell script with the following command (NOTE: It would be wise to run this script against testnet first using testnet addresses!):
./transfer_nfts.sh
We can open the nft_transfer_output.log
file to monitor progress and/or check it at the end to ensure that all transfers were successful!
๐งโโ๏ธ Conclusion
Shell scripting serves as a powerful tool in the developer's arsenal, especially for those working with Flow blockchain and Cadence to develop dApps and smart contracts.
It streamlines the cumbersome process of repetitive manual tasks, ensuring both efficiency and accuracy.
This article demonstrated the immense value of shell scripts in automating setups and running multiple transactions, shedding light on practical scenarios such as minting and transferring NFTs. We only covered a few examples here, but I'm sure you and your team can come up with many more situations where using shell scripts when building on Flow might be useful!