Getting Started with Abstract — How to create a Modular App (outdated)

Abstract Contact
Abstract Money
Published in
4 min readNov 18, 2022

Note: this article is outdated and will be updated shortly.

This guide will walk you through the steps to create a CosmWasm smart-contract module using Abstract. You will be able to leverage other modules, interact with OSes, and deploy your contract all from one repository.

Overview

Scaffold Generation

Open a terminal window and run the following command to generate the scaffold app on your machine:

If you don’t yet have it installed you can install it with cargo install cargo-generate

cargo generate gh:Abstract-OS/app-module-template \
--branch template \
--name <module-name>

The above will clone the template repository into <module-name> which will be the name of your Abstract module.

All message structs will be prefixed with this module name as well. Ex: ModuleNameInstantiateMsg. This is to differentiate from the actual InstantiateMsg (the true entry point) imported from abstract_os.

This name will also be the base name for most module-specific variables in the app. From here-on-out, the examples in this guide will be using “balancer”. This name will be the base name for most app-specific variables in the app as mentioned above.

Project Structure

Open the module-name folder, where you will find the following folders:

  • contracts: your CosmWasm smart contracts
  • interfaces: Rust deployment interfaces for your smart contracts
  • scripts: scripts for deploying your module to Abstract Version Control and for writing tests

Now that you have a fresh template, you are ready to start writing your module.

cargo fmt # cleanup any template residuals
cargo build

Contract Structure

All the following files are referenced from contracts/src directory. If you’ve ever written a CosmWasm smart contract before, this should be pretty standard aside from the contract.rs entry point.

msg.rs

Message declarations for your module. Here you will find the entry-point msg declarations as well as their responses.

error.rs

Human-readable error declarations for the module.

state.rs

The actual state stored on-chain for your smart-contract. Item and Map are both from the cw-storage-plus library, which provides powerful storage abstractions.

Commonly-used state like the Admin and Contract Version are already provided natively by the Abstract-SDK and exposed to your handlers (details below 👀)

contract.rs

This file serves as the overall entry point into your module. Abstract provides a wrapper around the CosmWasm entry points, and exposes their configuration to you through the AppContract type. Your module-specific types for error, messages, and receive are then all encapsulated into a single new type: <ModuleName>App.

pub type BalancerApp = AppContract<
BalancerError,
BalancerExecuteMsg,
BalancerInstantiateMsg,
BalancerQueryMsg,
BalancerMigrateMsg,
>;

Further down, we can see that our APP is an instance of this <ModuleName>App which declares each of the handlers for instantiate, query, execute, migrate, and replies.

const APP: BalancerApp = BalApp::new(MODULE_NAME, MODULE_VERSION)
.with_instantiate(handlers::instantiate_handler)
.with_query(handlers::query_handler)
.with_execute(handlers::execute_handler)
.with_migrate(handlers::migrate_handler)
.with_replies(&[(EXAMPLE_REPLY_ID, handlers::example_reply_handler)])
.with_dependencies(&[EXCHANGE]);

The dependencies specify which modules your users need to have installed in their OS to interact with the module. By default, the template declares the abstract:dex module as a dependency.

Finally, a Rust macro takes the type and const declaration of your app to export the actual CosmWasm entry points from your contract.

export_endpoints!(APP, BalancerApp);

handlers

All of the handlers for the messages sent to your contract (declared in msg.rs) are held within the handlers directory, and the appropriate messages can be handled via match statement.

Below is the ExecuteMsg handler for Equilibrium’s balancer module.

pub fn execute_handler(
deps: DepsMut,
_env: Env,
info: MessageInfo,
balancer: BalancerApp,
msg: BalancerExecuteMsg,
) -> BalancerResult {
match msg {
BalancerExecuteMsg::Rebalance {} => rebalance(deps, info, balancer),
BalancerExecuteMsg::UpdateAssetWeights { to_add, to_remove } => {
update_asset_weights(deps, info, balancer, to_add, to_remove)
}
BalancerExecuteMsg::UpdateConfig { deviation, dex } => {
update_config(deps, info, balancer, deviation, dex)
}
}

The <ModuleName>App acts as an adapter between your contract and the Abstract SDK, exposing Abstract Name System, other module functionality, asset value calculation, and much more. Read more in the Abstract docs.

Deployment

Interfaces

Within interfaces is a template.rs file that declares the deployment interface for your module. By default, the new function should be enough to deploy the contract but you can add additional functions to perform more advanced actions. See abstract-boot for more examples.

Scripts

To make your module available for other OSes to install on a local, Testnet, or Mainnet network, you can deploy your module directly from the template repo.

1. Configure Deployment Env

Rename the example env

# in the root
mv .env.example .env

Configure the variables (this example uses uni-5 Juno testnet)

# human-readable name of the chain
CHAIN="juno"
# name of the deployment to be exported
DEPLOYMENT="v0.0.1"
# "local" | "testnet" | "mainnet"
NETWORK="testnet"

# Mnemonic of the account that will be used to deploy the contracts (admin)
TEST_MNEMONIC=""
# Abstract version control address for app deployment
VERSION_CONTROL_ADDRESS=junoxxx

The Abstract version control address can be found on https://beta.abstract.money/deployments (check our discord for when it is available)

2. Configure the Deployment Network

In src/bin/deploy_app.rs, configure the network to instantiate the daemon to be UNI_5 for Juno testnet or JUNO_1 for Juno mainnet. Other available networks are exported from boot_core (docs).

use boot_core::{instantiate_daemon_env, networks::juno::{JUNO_DAEMON, UNI_5, JUNO_1}};

pub fn deploy_app() -> anyhow::Result<()> {
let network = UNI_5;
//...
}

3. Deploy to Version Control

Run the following command:

cargo deploy

…and your module will be registered to Abstract Version Control. You can verify this by going to https://beta.abstract.money/ans (soon) and checking the registered modules.

Need help?

Connect with us on Discord! We’d be happy to help you with any questions, feedback, or suggestions.

References

--

--