Cudos Blast is a Node.js CLI (command line interface) tool for working with the Cudos blockchain. You can scaffold, compile and test your Rust smart contracts. JavaScript and Rust testing is supported.
Utilizing blast.config.js
it provides a possibility for deploying and interacting with them on a specified network (local, test or public).
By using this tool you can also spin up a local Cudos node
and interact with it.
- Installation
- Help and version
- Initializing a project
- Compiling smart contracts
- Running Rust tests
- Testing contracts with JavaScript
- Interacting with a Cudos node
- Deploying smart contracts, interacting with them and running custom script files
- Creating a custom task
- Network
- Managing accounts
Make sure you have Node.js installed. Docker is also required.
Prerequisite | Minimum version | Recommended version |
---|---|---|
Node.js | 14.15.0 | 16.10.0 |
npm | 6.9.0 | 7.24.0 |
Docker engine | 19.03.13 | 20.10.12 |
Docker compose | 1.27.4 | 1.29.2 |
For Windows users we recommend using Windows Subsystem for Linux (WSL). To avoid permission issues with
WSL
, you may have to changenpm
default directory.
Cudos Blast can be used through a local installation in your project or by installing it globally.
Create an npm project by going to an empty folder, then run
npm init
and follow the instructions. Once your project is ready, run
npm install cudos-blast
.
To use your local installation of Cudos Blast, use npx blast
.
You can let npm completely manage Cudos Blast package just by using npx cudos-blast
to directly run commands. That way you will always be using the latest version of Cudos Blast so it is possible to have future compatibility issues. We recommend running
npm install cudos-blast -g
to install globally and using Cudos Blast by blast
as all the examples in this guide do.
Run --help
or help
on any blast
command to show all available subcommands, parameters and additional information.
blast --help
blast help
blast node --help
blast node start help
You can display cudos-blast
version number using --version
.
blast --version
To scaffold a sample project navigate to empty directory (or your npm project for local cudos-blast installation) and run
blast init
You can also specify the full directory of the project using optional parameter --dir
or -d
blast init --dir /Your/Location/Here
The project is now ready to work with the Cudos blockchain. It contains sample smart contracts and scripts to deploy or interact. The new project's npm
dependencies are automatically installed.
Make sure to initialize a new project in a directory other than the local repository folder, or else
cudos-blast
will break and the repository have to be cloned again.
Also, allblast
commands are designed to be executed from the project root directory.
To compile all smart contracts run
blast compile
The contracts have to be in {project_root}/contracts/
folder. Cudos Blast comes with sample contracts you can use. All contracts are compiled in alphabetical order and as a Rust workspace. If you want to add more folders to compile, all you have to do is edit the base {project_root}/Cargo.toml
file and add your folder under members
. The compilation is done using rust-optimizer and the artifacts in {project_root}/artifacts/
folder are optimized for deployment.
Rust tests are organized by the Rust convention for writing tests. You can check them in their corresponding contracts in {project_root}/contracts/{contract_name}/
. To run smart contracts' Rust tests:
blast rusttest
To run the Rust tests without printing cargo log messages use --quiet
or -q
blast rusttest -q
Cudos Blast uses Jest framework for testing. Test files must be in {project_root}/tests/
folder. You can use the provided sample test as a template or make one or more tests of your own. You can run the default local node in order to deploy or interact with the smart contracts in your tests. To connect to a non-local Cudos node such as testnet or your own node, follow these instructions.
describe('alpha contract', () => {
const MSG_INIT = { count: 13 }
const MSG_INCREMENT = { increment: {} }
const MSG_RESET = { reset: { count: 1 } }
const QUERY_GET_COUNT = { get_count: {} }
let alice, bob, contract
// deploying alpha contract once before test cases
beforeAll(async () => {
// 'bre' is available in global context
[alice, bob] = await bre.getSigners()
contract = await bre.getContractFactory('alpha')
await contract.deploy(MSG_INIT, 'alpha', { signer: bob })
})
// positive test case
test('increment count', async () => {
await contract.execute(MSG_INCREMENT, alice)
return expect(contract.query(QUERY_GET_COUNT))
.resolves.toEqual({ count: 14 })
})
// ...
// negative test case
test('reset count from user throws unauthorized', () => {
return expect(contract.execute(MSG_RESET, alice))
.rejects.toThrow('Unauthorized')
})
})
Run all test files with
blast test
blast test -n testnet
You can also run the tests with disabled console logging and show only essential test result information. To do this use --silent
or -s
blast test --silent
You can interact with a local Cudos node
with blast node
command.
To start a fresh local Cudos node run
blast node start
or you can show the node logging output in current terminal window. To do this use --log
or -l
.
blast node start -l
To see how to manage local node accounts go here.
To stop a running node run
blast node stop
To check whether any Cudos node is online or offline run
blast node status
blast node status -n testnet
More information about connecting to a non-local Cudos node here.
You can use the supplied {project_root}/scripts/deploy.js
to deploy a sample smart contract.
async function main () {
// functions such as 'getSigners' and 'getContractFactory' are available in global context
const [alice, bob] = await bre.getSigners()
// get contract object of 'alpha' contract in 'contracts/alpha'
const contract = await bre.getContractFactory('alpha')
// define instantiate message for the contract
// in this message you can set called function and its parameters
const MSG_INIT = { count: 13 }
// deploying the contract with bob as a signer (default signer would be alice)
const deploy = await contract.deploy(MSG_INIT, 'alpha', { signer: bob })
// get useful info such as contractAddress from deploy transaction
const contractAddress = deploy.instantiateTx.contractAddress
// printing contract address so it can be copied and used in other scripts such as interact.js
console.log(`Contract deployed at: ${contractAddress}`)
}
// ...
Deploy the contract by running the script:
blast run scripts/deploy.js
When the contract is deployed, its address will be printed. Then you can edit {project_root}/scripts/interact.js
with the new address
async function main() {
const [alice, bob] = await bre.getSigners()
// replace the address with the new one from your deployed smart contract
const contract = await bre.getContractFromAddress('cudos1uul3yzm2lgskp3dxpj0zg558hppxk6pt8t00qe')
// ...
and run the script to interact with the deployed smart contract.
blast run scripts/interact.js
When running scripts through blast run
the bre
object in injected. It provides various useful functions to interact with cudos blockchain network. You can also require
the cudos-blast
library to access the same functions and enable your code editor's intellisense.
const bre = require('cudos-blast')
You are free to use these sample files as templates or create your own custom .js
scripts. You can specify your own script file path.
blast run scripts/myCustomScript.js
blast run newFolder/anotherScripts/myCustomScript.js
Here is a list of functions you can use in your scripts.
Function | Descripton | Sample usage |
---|---|---|
async getSigners() | If the local node is used: Returns an array of predefined accounts ({project_root}/local-accounts.json ) including the auto generated additional accounts.For other networks: returns an array of user-defined private accounts from {project_root}/private-accounts.json . |
const [alice, bob] = await bre.getSigners() |
async getContractFactory(contractLabel) | Returns an instance of a new contract by its label. | const contract = await bre.getContractFactory('alpha') |
async getContractFromCodeId(codeId) | Returns an instance of a contract whose code is uploaded but not instantiated. | const contract = await bre.getContractFromCodeId(123) |
async getContractFromAddress(contractAddress) | Returns an instance of an on-chain contract by its address. | const contract = await bre.getContractFromAddress('cudos1uul3yzm2lgskp3dxpj0zg558hppxk6pt8t00qe') |
You can get an instance of a contract (e.g. with getContractFactory()
). Here is the functionality such an instance of a contract can offer.
Function | Descripton | Sample usage |
---|---|---|
async uploadCode(options = { signer: null, gasLimit: null, gasMultiplier: null }) | Uploads the contract's source code on the network so it can be optimally used to instantiate a contract multiple times with different initial state. | const uploadTx = await contract.uploadCode() |
async instantiate(msg, label, options = { signer: null, funds: null, gasLimit: null, gasMultiplier: null }) | Instantiates an uploaded contract with given initMsg and label . Can be used for undeployed as well as already deployed contracts. The new instantiated contract does not override the current contract object, and therefore it is designed to be accessed by its address. |
const instantiateTx = await contract.instantiate(MSG_INIT) |
async deploy(msg, label, options = { signer: null, funds: null }) | Deploys the conttract with the given initMsg . You cannot use deploy on an instance ot contract whose code is already uploaded. |
const deployTxs = await contract.deploy(MSG_INIT, { label: 'myLabel' }) |
async execute(msg, signer = null, options = { gasLimit: null, gasMultiplier: null }) | Executes a transaction within the contract with the given message. | const result = await contract.execute(MSG_INCREMENT, alice) |
async query(queryMsg, signer = null) | Executes a query within the contract with the given message. | const count = await contract.query(QUERY_GET_COUNT) |
getAddress() | Returns the address of a deployed contract or null if undeployed. | const address = contract.getAddress() |
getCodeId() | Returns the code ID of an uploaded contract or null if unuploaded. | const codeId = contract.getCodeId() |
getLabel() | Returns the label of the contract or null if undeployed. | const label = contract.getLabel() |
getCreator() | Returns the address of the contract's creator or null if unuploaded. | const label = contract.getCreator() |
Options object | Descripton |
---|---|
options = { signer } | The signer to execute the functionality with. The default signer is the first one returned by getSigners() . |
options = { funds } | The amount of tokens to fund the newly created contract. |
options = { gasLimit } | The maximum limit of gas a transaction can consume. Defaults to "auto". |
options = { gasMultiplier } | gasLimit multiplier. Defaults to "auto" or 1.3. gasMultiplier is taken into consideration only when auto gasLimit is used. |
- You can run your scripts on a different node. More information here
- You can set a custom address prefix under
addressPrefix
inblast.config.js
. The default prefix iscudos
blast run scripts/myCustomScript.js -n testnet
- You can automatically fund smart contracts with tokens in your scripts
async function main () {
const [alice, bob] = await getSigners()
const contract = await getContractFactory('alpha')
const MSG_INIT = { count: 13 }
const deploy = await contract.deploy(MSG_INIT, 'alpha', { signer: bob, fund: 123 })
// ...
- Gas fees are calculated and applied per transaction. Note that
deploy()
function submits two separate transactions (upload code + instantiate), and therefore autogasLimit
andgasMultiplier
are used. - You can specify gas price from
blast.config.js
. It is used in format<amount>acudos
Cudos Blast allows the creation of custom tasks that can easily run commonly used operations or help manage your workflow. This guide shows you how to create a sample task to print a parameter from the CLI.
Let's add the following line in our blast.config.js
outside of the scope of module.exports
:
require('cudos-blast/utilities/task.js')
task("print", "Prints a custom parameter").setAction(async () => {});
It is a good practice to split your code into several files and require
them from the config file for more complex tasks.
After adding it, you should be able to see the task and its description in blast --help
.
Usage: blast <command> [arguments] [command options]
Commands:
blast init Create a sample project
blast compile Compile the smart contracts in the workspace in
alphabetical order
blast test Run the JavaScript tests
blast rusttest Run smart contracts rust tests
blast node Manage a local CUDOS node
blast run <scriptFilePath> Run a single script
blast keys Manage node accounts (keys)
blast print Prints a custom parameter
Options:
--version Show version number [boolean]
--help Show help [boolean]
now let's add a param to our task
require('cudos-blast/utilities/task.js')
task("print", "Prints a custom parameter")
.addParam("param", "Our custom parameter")
.setAction(async (argv) => {
console.log(`Printing our param... ${argv.param}`)
});
module.exports.config = {
// ...
Now we can simply invoke it by running:
blast print --param "important thing to print"
You can add as many parameteres with .addParam()
as you need.
You should know that every task must end with .setAction()
so it can take it's place.
You can connect to the default local node as well as a public one or you can use your own Cudos node. To do that, add a {custom_name}: {node_url}
to networks
field in blast.config.js
, then call the run
, test
or node status
command with --network
or -n
followed by {custom_name}
. If no network is passed, blast commands connect to the default local node.
Here are Cudos nodes you can use to connect to Cudos network:
Chain ID | URL |
---|---|
cudos-network | http://localhost:26657 |
Chain ID | URL |
---|---|
cudos-testnet-public-3 | https://sentry1.gcp-uscentral1.cudos.org:36657 |
Chain ID | URL |
---|---|
cudos-1 | https://rpc.cudos.org |
By default local Cudos node starts with 10 predefined accounts funded with acudos
. You can set how many additional random accounts to load when starting a local node in blast.config.js
under additionalAccounts
. If any additional accounts are added, customAccountBalances
field must be set for the amount of tokens that these accounts will be funded with. Predefined and additionally generated accounts are written in {project_root}/local-accounts.json
. Another way to manage custom accounts is through blast keys
command.
You can put your private accounts in {project_root}/private-accounts.json
. Initializing a new project automatically adds this file to .gitignore
. Make sure you keep private-accounts.json
in .gitignore
in order to prevent accidentally committing and exposing your private accounts.. The private-accounts.json
file is mainly meant to have existing accounts in other networks (testnet, mainnet, etc). If you also want to have these accounts in your local environment, make sure you add them to your local node keyring.
To list all accounts in the local node key storage run
blast keys ls
To add a new account named myAccount1
to the local node key storage run
blast keys add myAccount1
After adding the new account, it is automatically funded with acudos
tokens from the default local node faucet.
To remove an account from the node key storage run
blast keys rm myAccount1
You can skip the delete confirmation with --force
or -f
blast keys rm myAccount1 -f
You can fund an account with additional tokens. To specify tokens amount use --tokens
or -t
.
blast keys fund myAccount1 --tokens 1000000
The tokens are funded from the default local node faucet in acudos
.