@terra-rebirth/terrain
v0.5.0
Published
Terra development environment
Downloads
1
Readme
Terrain
Terrain allows you to:
- Scaffold a template smart contract and frontend for app development.
- Dramatically simplify the development and deployment process.
Terrain is not:
- A fully-featured Terra command-line interface (CLI). If you need a fully-featured client, use terrad.
- A Light Client Daemon (LCD). You will still need an RPC endpoint to deploy your contract. LocalTerra is the recommended option for this.
Table of contents
- Terrain
- Table of contents
- Setup
- Getting Started
- Migrating CosmWasm Contracts on Terra
- Use Terrain Main Branch Locally
- Terrain Commands
Setup
Download LocalTerra
For testing purposes, we recommend to install and run LocalTerra on your personal computer. Instructions on how to get LocalTerra up and running can be found in the LocalTerra documentation.
Note: If you are using a Mac with an M1 chip, you might need to update your Docker Desktop due to the qemu bug.
Once all dependencies have been installed, do the following:
- Clone the LocalTerra repo.
git clone https://github.com/terra-money/LocalTerra.git
- Navigate to the newly created
LocalTerra
directory.
cd LocalTerra
- Spin up an instance of the environment with
docker-compose
.
docker-compose up
Setup Rust
While WASM smart contracts can be written in any programming language, it is strongly recommended that you utilize Rust, as it is the only language for which mature libraries and tooling exist for CosmWasm. To complete this tutorial, install the latest version of Rust by following the instructions here. Once Rust is installed on your computer, do the following:
- Set the default release channel used to update Rust to stable.
rustup default stable
- Add wasm as the compilation target.
rustup target add wasm32-unknown-unknown
- Install the necessary dependencies for generating contracts.
cargo install cargo-run-script
Install Node JS and NPM
To run Terrain, you will need to install Node.js and NPM. We recommend that you install Node.js v16 (LTS). Node Package Manager (NPM) is automatically installed along with your Node.js download.
Note: Use Node.js v16 (LTS) if you encounter the following error code: error:0308010C:digital envelope routines::unsupported
Getting Started
Now that you have completed the initial setup, generate your first smart contract using the procedure described below.
- Install the terrain package globally.
npm install -g @terra-money/terrain
Note: If you would like to install terrain locally, you can execute the command npm install @terra-money/terrain
, without the -g
flag, while in the directory in which you would like to be able to utilize the package. You can then execute any terrain commands by prefixing them with npx
. For example, to scaffold a new project named my-terra-dapp
with a locally installed terrain package, you would utilize the command npx terrain new my-terra-dapp
.
- Generate your smart contract and corresponding frontend templates.
terrain new my-terra-dapp
- Navigate to the new
my-terra-dapp
directory.
cd my-terra-dapp
- Install all necessary Node dependencies in your project.
npm install
Project Structure
The terrain new
command generates a project that contains a template smart contract called counter
and a corresponding frontend. Other supporting files are generated to provide further functionality. You may view the project structure below.
.
├── contracts # the smart contract directory
│ ├── counter # template smart contract
│ └── ...
├── frontend # template frontend application
├── lib # predefined task and console functions
├── tasks # predefined tasks
├── keys.terrain.js # keys for signing transactions
├── config.terrain.json # config for connections and contract deployments
└── refs.terrain.json # deployed code and contract references
Deployment
The terrain deploy
command does the following:
- Builds, optimizes and stores the wasm code on the blockchain.
- Instantiates the contract.
To deploy your new counter smart contract, run the following command in the terminal.
terrain deploy counter --signer test1
Note: You can also store the wasm code and instantiate the contract separately using the command terrain code:store CONTRACT
followed by terrain contract:instantiate CONTRACT
. In this case, you must also run the command terrain sync-refs
in your project directory to update the refs.terrain.json
file which references contract deployments on all networks.
In this case, we specify one of the preconfigured accounts with balances on LocalTerra, test1
, as our signer. The signer account will be responsible for paying the gas fee associated with deploying the contract to the Terra blockchain and will be assigned as the owner of the project.
You can also specify the network on which you would like to deploy your contract by adding the --network
flag. If the network is not specified, as is the case in our above example, your contract will be deployed to localterra
by default. You may also deploy to mainnet
, the live Terra blockchain, as well as testnet
, a network similar to mainnet used for testing.
Deploying on Testnet or Mainnet
The predefined accounts in the keys.terrain.js
file shown below can be utilized as signers on testnet
. We will demonstrate how to deploy your smart contract utilizing the preconfigured custom_tester_1
account. You may also add a personal account to the keys.terrain.js
file by adding the account name as well as its corresponding private key. You can then use that account as the signer specifying the account name after the --signer
flag in the terrain deploy
command.
Warning: Utilizing a personal account for deployment requires the use of a private key or mnemonic. These are private keys that are generated upon the creation of your personal wallet. Saving or utilizing these keys on your personal computer may expose them to malicious actors who could gain access to your personal wallet if they are able to obtain them. You can create a wallet solely for testing purposes to eliminate risk. Alternatively, you can store your private keys as secret enviroment variables which you can then reference utilizing process.env.SECRET_VAR
in keys.terrain.json
. Use your private key or mnemonic at your own discretion.
// can use `process.env.SECRET_MNEMONIC` or `process.env.SECRET_PRIV_KEY`
// to populate secret in CI environment instead of hardcoding
module.exports = {
custom_tester_1: {
mnemonic:
"shiver position copy catalog upset verify cheap library enjoy extend second peasant basic kit polar business document shrug pass chuckle lottery blind ecology stand",
},
custom_tester_2: {
privateKey: "fGl1yNoUnnNUqTUXXhxH9vJU0htlz9lWwBt3fQw+ixw=",
},
};
Prior to deploying your contract, ensure that your signer wallet contains the funds needed to pay for associated transaction fees. You can request funds from the Terra Testnet Faucet by submitting the wallet address of the account where you would like to receive the funds and clicking on the Send me tokens
button.
You can retrieve the wallet address associated with the custom_tester_1
account by utilizing the terrain console
in your terminal.
terrain console
terrain > wallets.custom_tester_1.key.accAddress
'terra1qd9fwwgnwmwlu2csv49fgtum3rgms64s8tcavp'
After you have received the Luna tokens from the Terra Testnet Faucet, query the balance of your account by utilizing the following command in the terrain console.
terrain > (await client.bank.balance(wallets.custom_tester_1.key.accAddress))[0]
Finally, exit the terrain console and deploy the counter
smart contract to testnet with the custom_tester_1
account as the signer.
terrain deploy counter --signer custom_tester_1 --network testnet
Initializing the Frontend Template
After deployment, the refs.terrain.json
file will be updated in the project directory as well as the frontend/src
directory. These files contain all contract references on all networks. This information is utilized by terrain's utility functions and also the frontend template. An example of refs.terrain.json
can be found below:
{
"localterra": {
"counter": {
"codeId": "1",
"contractAddresses": {
"default": "terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5"
}
}
},
"testnet": {
"counter": {
"codeId": "18160",
"contractAddresses": {
"default": "terra15faphq99pap3fr0dwk46826uqr2usve739l7ms"
}
}
}
}
Important: If you have initialized the contract without using the terrain deploy
command or have manually changed the refs.terrain.json
file in the project directory, you will need to sync the references to the frontend/src
directory in order to ensure frontend functionality. To do so, use the following command: terrain sync-refs
After you have synced the contract references, navigate to the frontend
directory and start the application.
- Navigate to the
frontend
directory.
cd frontend
- Start the application.
npm run start
Note: Switching networks in your Terra Station extension will result in a change in reference to the contract address which corresponds with the new network.
Run Contract Functions with Terrain
Once you have successfully deployed your project, you can interact with the deployed contract and the underlying blockchain by utilizing functions defined in the lib/index.js
file. You may also create your own abstractions in this file for querying or executing transactions. The default contents of the lib/index.js
file are presented below.
// lib/index.js
module.exports = ({ wallets, refs, config, client }) => ({
getCount: () => client.query("counter", { get_count: {} }),
increment: (signer = wallets.test1) =>
client.execute(signer, "counter", { increment: {} }),
});
You can call the functions defined above inside of the terrain console
. An example using the counter
smart contract is shown below.
terrain console
terrain > await lib.getCount()
{ count: 0 }
terrain > await lib.increment()
terrain > await lib.getCount()
{ count: 1 }
You may also specify which network you would like to interact with by utilizing the --network
flag with the terrain console
command.
terrain console --network NETWORK
Creating Tasks
You can also utilize the functions available inside of the lib/index.js
file to create tasks. Tasks are utilized in order to automate the execution of sequential functions or commands. An example task is provided for you in the tasks/example-with-lib.js
file in your project directory.
// tasks/example-with-lib.js
const { task } = require("@terra-money/terrain");
const lib = require("../lib");
task(async (env) => {
const { getCount, increment } = lib(env);
console.log("count 1 = ", await getCount());
await increment();
console.log("count 2 = ", await getCount());
});
To run the example task shown above, which is located in the tasks/example-with-lib.js
file, run the following command in the terminal.
terrain task:run example-with-lib
In order to create a new task, run the following command replacing <task-name>
with the desired name for your new task.
terrain task:new <task-name>
If you would like to utilize JavaScript in your functions or tasks, you can import Terra.js. The tasks/example-custom-logic.js
file contains an example of a task that utilizes Terra.js functionality. To learn more about Terra.js, view the Terra.js documentation.
// tasks/example-custom-logic.js
const { task, terrajs } = require("@terra-money/terrain");
// terrajs is basically re-exported terra.js (https://terra-money.github.io/terra.js/)
task(async ({ wallets, refs, config, client }) => {
console.log("creating new key");
const key = terrajs.MnemonicKey();
console.log("private key", key.privateKey.toString("base64"));
console.log("mnemonic", key.mnemonic);
});
As of Terrain 0.4.0 it is possible to deploy and instantiate contracts from tasks. This can be useful for multi-contract, or multi-stage deployments.
const { task } = require("@terra-money/terrain");
task(async ({ wallets, client, deploy }) => {
// First deploy the counter smart contract.
const counterCodeId = await deploy.storeCode(wallets.test1, "counter");
const counterAddress = await deploy.instantiate(
// Signer
wallets.test1,
// Contract name
"counter",
// Code ID
counterCodeId,
// Instance ID
"default",
// Contract admin
wallets.test1.key.accAddress
);
// Now deploy a CW20 with the counter contract set as the minter in instantiation.
const cw20CodeId = await deploy.storeCode(wallets.test1, "cw20-base");
const cw20Address = await deploy.instantiate(
wallets.test1,
"cw20-base",
cw20CodeId,
"default",
wallets.test1.key.accAddress,
// Custom instantiation message.
// with no message provided the default from config.terrain will be used.
{
name: "counter",
symbol: "CTR",
decimals: 6,
initial_balances: [],
mint: {
minter: counterAddress,
},
}
);
// Update the CW20 address in counter.
// Note: It's important to use the address returned by deploy.instantiate
// Refs are only read into memory at the start of the task.
await client.execute(wallets.test1, counterAddress, {
update_token: { token: cw20Address },
});
console.log(`CW20 Address: ${cw20Address}`);
});
Migrating CosmWasm Contracts on Terra
On Terra it is possible to initalize contracts as migratable. This functionallity allows the adminstrator to upload a new version of the contract, then send a migrate message to move to the new code.
This tutorial builds on top of the Terrain Quick Start Guide and walks you through a contract migration.
Adding MigrateMsg to the Contract
In order for a contract to be migratable, it must satisfy the following two requirements:
- The smart contract handles the
MigrateMsg
transaction. - The smart contract has an admininstrator set.
To implement support for MigrateMsg
, add the message to the msg.rs
file. To do so, navigate to msg.rs
and place the following code just above the InstantiateMsg
struct.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct MigrateMsg {}
With MigrateMsg
defined, update the contract.rs
file. First, update the import from crate::msg
to include MigrateMsg
.
use crate::msg::{CountResponse, ExecuteMsg, InstantiateMsg, QueryMsg, MigrateMsg};
Next, add the following method above instantiate
.
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult<Response> {
Ok(Response::default())
}
Migrating the Contract
In the previous Terrain tutorial, we deployed the contract, but did not initilize it as migratable.
After adding MigrateMsg to the smart contract, we can redeploy the contract and add the --set-signer-as-admin
flag. This allows the transaction signer to migrate the contract in the future.
terrain deploy counter --signer test1 --set-signer-as-admin
If you decide to make changes to the deployed contract, you can migrate to the updated code by executing the following command.
terrain contract:migrate counter --signer test1
Use Terrain Main Branch Locally
In some cases, the latest features or bug fixes may be integrated into the main branch of the Terrain Github repo, but not yet released to the corresponding npm package. Subsequently, you may want to use the latest version of Terrain available on Github before it has been released to npm. The below described method may also be utilized if you are interested in developing on and contributing to Terrain.
Warning: Features and bug fixes that are implemented on the latest version of Terrain may still be subject to testing. As such, you should only use the main branch of the Terrain github repo in exceptional circumstances. In all other cases, use the npm package.
To use the main branch of the Terrain repo on your local machine, do the following:
- Clone the repo.
git clone --branch main --depth 1 https://github.com/terra-money/terrain
- Navigate to the project folder.
cd terrain
- Inside the project folder, install all necessary node dependencies.
npm install
- Run the
npm link
command to link the project to your global terrain instance.
npm link
To unlink the terrain command from the cloned repository and revert back to the default functionality you can run the command below.
npm unlink terrain
Terrain Commands
terrain console
terrain contract:generateClient CONTRACT
terrain contract:instantiate CONTRACT
terrain contract:migrate CONTRACT
terrain contract:new NAME
terrain contract:store CONTRACT
terrain contract:updateAdmin CONTRACT ADMIN
terrain deploy CONTRACT
terrain help [COMMAND]
terrain new NAME
terrain sync-refs [FILE]
terrain task:new [TASK]
terrain task:run [TASK]
terrain test CONTRACT-NAME
terrain test:coverage [CONTRACT-NAME]
terrain wallet:new
terrain console
Start a repl console that provides context and convenient utilities to interact with the blockchain and your contracts.
USAGE
$ terrain console [--signer <value>] [--network <value>] [--config-path <value>] [--refs-path <value>]
[--keys-path <value>]
FLAGS
--config-path=<value> [default: config.terrain.json]
--keys-path=<value> [default: keys.terrain.js]
--network=<value> [default: localterra]
--refs-path=<value> [default: refs.terrain.json]
--signer=<value> [default: test1]
DESCRIPTION
Start a repl console that provides context and convenient utilities to interact with the blockchain and your
contracts.
See code: src/commands/console.ts
terrain contract:generateClient CONTRACT
Generate a Wallet Provider or Terra.js compatible TypeScript client.
USAGE
$ terrain contract:generateClient [CONTRACT] [--lib-path <value>] [--dest <value>] [--build-schema]
FLAGS
--build-schema
--dest=<value> [default: ./frontend/src/contract]
--lib-path=<value> [default: ./lib] location to place the generated client
DESCRIPTION
Generate a Wallet Provider or Terra.js compatible TypeScript client.
See code: src/commands/contract/generateClient.ts
terrain contract:instantiate CONTRACT
Instantiate the contract.
USAGE
$ terrain contract:instantiate [CONTRACT] [--signer <value>] [--set-signer-as-admin] [--network <value>] [--config-path
<value>] [--refs-path <value>] [--keys-path <value>] [--instance-id <value>] [--code-id <value>]
FLAGS
--code-id=<value> specfic codeId to instantiate
--config-path=<value> [default: ./config.terrain.json]
--instance-id=<value> [default: default]
--keys-path=<value> [default: ./keys.terrain.js]
--network=<value> [default: localterra]
--refs-path=<value> [default: ./refs.terrain.json]
--set-signer-as-admin set signer (deployer) as admin to allow migration.
--signer=<value> [default: test1]
DESCRIPTION
Instantiate the contract.
See code: src/commands/contract/instantiate.ts
terrain contract:migrate CONTRACT
Migrate the contract.
USAGE
$ terrain contract:migrate [CONTRACT] [--signer <value>] [--no-rebuild] [--network <value>] [--config-path <value>]
[--refs-path <value>] [--keys-path <value>] [--instance-id <value>] [--code-id <value>] [--arm64]
FLAGS
--arm64 use rust-optimizer-arm64 for optimization. Not recommended for production, but it will optimize
quicker on arm64 hardware during development.
--code-id=<value> target code id for migration
--config-path=<value> [default: ./config.terrain.json]
--instance-id=<value> [default: default]
--keys-path=<value> [default: ./keys.terrain.js]
--network=<value> [default: localterra]
--no-rebuild deploy the wasm bytecode as is.
--refs-path=<value> [default: ./refs.terrain.json]
--signer=<value> [default: test1]
DESCRIPTION
Migrate the contract.
See code: src/commands/contract/migrate.ts
terrain contract:new NAME
Generate new contract.
USAGE
$ terrain contract:new [NAME] [--path <value>] [--version <value>] [--authors <value>]
FLAGS
--authors=<value> [default: Terra Money <[email protected]>]
--path=<value> [default: ./contracts] path to keep the contracts
--version=<value> [default: 1.0-beta6]
DESCRIPTION
Generate new contract.
EXAMPLES
$ terrain code:new awesome_contract
$ terrain code:new awesome_contract --path path/to/dapp
$ terrain code:new awesome_contract --path path/to/dapp --authors "ExampleAuthor<[email protected]>"
See code: src/commands/contract/new.ts
terrain contract:store CONTRACT
Store code on chain.
USAGE
$ terrain contract:store [CONTRACT] [--signer <value>] [--no-rebuild] [--network <value>] [--config-path <value>]
[--refs-path <value>] [--keys-path <value>] [--code-id <value>]
FLAGS
--code-id=<value>
--config-path=<value> [default: ./config.terrain.json]
--keys-path=<value> [default: ./keys.terrain.js]
--network=<value> [default: localterra]
--no-rebuild deploy the wasm bytecode as is.
--refs-path=<value> [default: ./refs.terrain.json]
--signer=<value> [default: test1]
DESCRIPTION
Store code on chain.
See code: src/commands/contract/store.ts
terrain contract:updateAdmin CONTRACT ADMIN
Update the admin of a contract.
USAGE
$ terrain contract:updateAdmin [CONTRACT] [ADMIN] [--signer <value>] [--network <value>] [--config-path <value>]
[--refs-path <value>] [--keys-path <value>] [--instance-id <value>]
FLAGS
--config-path=<value> [default: ./config.terrain.json]
--instance-id=<value> [default: default]
--keys-path=<value> [default: ./keys.terrain.js]
--network=<value> [default: localterra]
--refs-path=<value> [default: ./refs.terrain.json]
--signer=<value> [default: test1]
DESCRIPTION
Update the admin of a contract.
See code: src/commands/contract/updateAdmin.ts
terrain deploy CONTRACT
Build wasm bytecode, store code on chain and instantiate.
USAGE
$ terrain deploy [CONTRACT] [--signer <value>] [--arm64] [--no-rebuild] [--set-signer-as-admin] [--network
<value>] [--config-path <value>] [--refs-path <value>] [--keys-path <value>] [--instance-id <value>]
[--admin-address <value>] [--frontend-refs-path <value>]
FLAGS
--admin-address=<value> set custom address as contract admin to allow migration.
--arm64 use rust-optimizer-arm64 for optimization. Not recommended for production, but it will
optimize quicker on arm64 hardware during development.
--config-path=<value> [default: ./config.terrain.json]
--frontend-refs-path=<value> [default: ./frontend/src/refs.terrain.json]
--instance-id=<value> [default: default] enable management of multiple instances of the same contract
--keys-path=<value> [default: ./keys.terrain.js]
--network=<value> [default: localterra]
--no-rebuild deploy the wasm bytecode as is.
--refs-path=<value> [default: ./refs.terrain.json]
--set-signer-as-admin set signer (deployer) as admin to allow migration.
--signer=<value> [default: test1]
DESCRIPTION
Build wasm bytecode, store code on chain and instantiate.
See code: src/commands/deploy.ts
terrain help [COMMAND]
display help for terrain
USAGE
$ terrain help [COMMAND] [--all]
ARGUMENTS
COMMAND command to show help for
FLAGS
--all see all commands in CLI
DESCRIPTION
display help for terrain
See code: @oclif/plugin-help
terrain new NAME
Create new dapp from template.
USAGE
$ terrain new [NAME] [--path <value>] [--framework react|vue|svelte|next|vite|lit] [--version <value>]
[--authors <value>]
FLAGS
--authors=<value> [default: Terra Money <[email protected]>]
--framework=<option> [default: react] Choose the frontend framework you want to use. Non-react framework options have
better wallet-provider support but less streamlined contract integration.
<options: react|vue|svelte|next|vite|lit>
--path=<value> [default: .] Path to create the workspace
--version=<value> [default: 1.0]
DESCRIPTION
Create new dapp from template.
EXAMPLES
$ terrain new awesome-dapp
$ terrain new awesome-dapp --path path/to/dapp
$ terrain new awesome-dapp --path path/to/dapp --authors "ExampleAuthor<[email protected]>"
$ terrain new awesome-dapp --path path/to/dapp --framework vue --authors "ExampleAuthor<[email protected]>"
See code: src/commands/new.ts
terrain sync-refs [FILE]
Sync configuration with frontend app.
USAGE
$ terrain sync-refs [FILE] [--refs-path <value>] [--dest <value>]
FLAGS
--dest=<value> [default: ./frontend/src/refs.terrain.json]
--refs-path=<value> [default: ./refs.terrain.json]
DESCRIPTION
Sync configuration with frontend app.
See code: src/commands/sync-refs.ts
terrain task:new [TASK]
create new task
USAGE
$ terrain task:new [TASK]
DESCRIPTION
create new task
See code: src/commands/task/new.ts
terrain task:run [TASK]
run predefined task
USAGE
$ terrain task:run [TASK] [--signer <value>] [--network <value>] [--config-path <value>] [--refs-path
<value>] [--keys-path <value>]
FLAGS
--config-path=<value> [default: config.terrain.json]
--keys-path=<value> [default: keys.terrain.js]
--network=<value> [default: localterra]
--refs-path=<value> [default: refs.terrain.json]
--signer=<value> [default: test1]
DESCRIPTION
run predefined task
See code: src/commands/task/run.ts
terrain test CONTRACT-NAME
Runs unit tests for a contract directory.
USAGE
$ terrain test [CONTRACT-NAME] [--no-fail-fast]
FLAGS
--no-fail-fast Run all tests regardless of failure.
DESCRIPTION
Runs unit tests for a contract directory.
EXAMPLES
$ terrain test counter
$ terrain test counter --no-fail-fast
See code: src/commands/test.ts
terrain test:coverage [CONTRACT-NAME]
Runs unit tests for a contract directory.
USAGE
$ terrain test:coverage [CONTRACT-NAME]
DESCRIPTION
Runs unit tests for a contract directory.
EXAMPLES
$ terrain test:coverage
$ terrain test:coverage counter
See code: src/commands/test/coverage.ts
terrain wallet:new
Generate a new wallet to use for signing contracts
USAGE
$ terrain wallet:new [--outfile <value>] [--index <value>]
FLAGS
--index=<value> key index to use, default value is 0
--outfile=<value> absolute path to store the mnemonic key to. If omitted, output to stdout
DESCRIPTION
Generate a new wallet to use for signing contracts
See code: src/commands/wallet/new.ts