Code in Move [2] — Resource Account on Aptos

Thouny
5 min readDec 22, 2022

--

Diagram of interactions between accounts and resource accounts

In the last article, we wrote then published and interacted with a smart contract allowing to issue a Coin and then to mint and burn a certain quantity. However, only the owner of the module could perform these actions since it requires the mint and burn Capability which is a Resource existing only under the same account as the module, that of the owner!

Today we will see how to give access to this Resource (Capability) to any user so that he can mint a Coin amount directly in his wallet. And to do this we will need a tool specially designed for this purpose by the Aptos team: the Resource Account.

The Resource Account

First, let’s look at what this type of account consists of. This feature was implemented to allow developers to manage Resources and Modules independently of a so-called “user” account. This is usually used to store and isolate resources as well as publish modules as standalone accounts. This means that no private key can control them, the signer Capability can be stored in this account or in a third party account.

The diagram above details the interactions between the different types of accounts. You can see on the left the representation of the smart contract from the previous article. A 0xadm1n account deploys a module and it can upgrade it according to the Aptos rules. He can then issue a Coin (and thus create and store the mint and burn Capabilities), then mint or burn an amount. However another user 0xus3r will not be able to mint this Coin since it is 0xadm1n who has the Capability.

This problem can be solved by using a Resource Account. 0xadm1n creates a Resource Account (with the signer Capability) which will in turn publish the module. Then 0xadm1n can issue the Coin via 0xr3s0urc3 which will use its signer Capability to create the mint Capability. Finally 0xadm1n and 0xus3r will be able to call 0xr3s0urc3 to mint the Coin via its signer Capability. Besides, as the module does not belong to 0xadm1n he will not be able to upgrade it.

Modifying our Smart Contract

We will start from the module written in the last article, if you don’t have it you can get it here. The final code related to this tutorial can be found here.

Let’s start by adding these two Resources. ModuleData contains the parameters of the smart contract, i.e.: an admin address, the signer Capability of the Resource Account (that everyone will be able to use to sign transactions), on/off, and an EventHandle to store minting events.

struct CoinMintingEvent has drop, store {
receiver_addr: address,
amount: u64,
}

struct ModuleData has key {
admin_addr: address,
signer_cap: account::SignerCapability,
minting_enabled: bool,
coin_minting_event: event::EventHandle<CoinMintingEvent>,
}

We write this short function to check if the caller is indeed the admin.

fun assert_is_admin(addr: address) acquires ModuleData {
let admin = borrow_global<ModuleData>(@coin_mint).admin_addr;
assert!(addr == admin, error::permission_denied(ENOT_ADMIN));
}

This time, the init_module fonction will be used to retrieve the signer_cap after module publishing. @source is the address will use to deploy the smart contract, the Resource Account will be derived from it.

fun init_module(resource_acc: &signer) {
let signer_cap = resource_account::retrieve_resource_account_cap(resource_acc, @source);

move_to(resource_acc, ModuleData {
admin_addr: @admin,
signer_cap,
minting_enabled: false,
coin_minting_event: account::new_event_handle<CoinMintingEvent>(resource_acc),
});
}

The function for issuing the Coin is very similar to the one we used in the last tutorial. The only difference is that we create the signer from the signer capability that we have stored under the Resource Account above. This allows us to move the mint and burn Capabilities to the Resource Account (move_to requires the account signature).

public entry fun issue(admin: &signer) acquires ModuleData {
assert!(!exists<Capabilities>(@coin_mint), error::already_exists(EALREADY_COIN_CAP));
let addr = signer::address_of(admin);
assert_is_admin(addr);

let data = borrow_global<ModuleData>(@coin_mint);
let resource_signer = &account::create_signer_with_capability(&data.signer_cap);

let (burn_cap, freeze_cap, mint_cap) = coin::initialize<CoinCoin>(
resource_signer,
string::utf8(b"Coin Coin"),
string::utf8(b"Coin2"),
18,
true,
);
move_to(resource_signer, Capabilities {mint_cap, burn_cap});
coin::destroy_freeze_cap(freeze_cap);
}

Let’s add two more functions for smart contract management.

public entry fun enable_minting(admin: &signer, status: bool) acquires ModuleData {
let addr = signer::address_of(admin);
assert_is_admin(addr);
borrow_global_mut<ModuleData>(@coin_mint).minting_enabled = status;
}

public entry fun set_admin(admin: &signer, admin_addr: address) acquires ModuleData {
let addr = signer::address_of(admin);
assert_is_admin(addr);
borrow_global_mut<ModuleData>(@coin_mint).admin_addr = admin_addr;
}

Finally mint and burn functions follow the same principles as issue. We retrieve Capabilities from the Resource Account, which corresponds to @coin_mint named address, to be able to mint an amount of CoinCoin.

public entry fun mint(user: &signer, amount: u64) acquires Capabilities {
assert!(exists<Capabilities>(@coin_mint), error::permission_denied(ENO_COIN_CAP));

let mint_cap = &borrow_global<Capabilities>(@coin_mint).mint_cap;
let coins = coin::mint<CoinCoin>(amount, mint_cap);
coin::register<CoinCoin>(user);
coin::deposit<CoinCoin>(signer::address_of(user), coins);
}

The tricky part: Deployment

Creating the Resource Account and publishing the module has caused me some troubles and actually led me to write this article. I couldn’t find any docs or tutorials detailing the process. Fortunately, the Aptos team is pretty quick to help developers like us!

Start by commenting on the named address @coin_mint in Move.toml. This is the Resource Account that doesn’t exist yet. Next, we will create the resource account and publish our module under it in a single command line.

aptos move create-resource-account-and-publish-package --seed whatever_you_want --address-name coin_mint

Your terminal should prompt something like that, copy the address, this is the one of the Resource Account you will create after validating the transaction.

With it, you can interact with the smart contract like we did in the previous article.

Conclusion

This pattern is widely used for NFT minting or anything that necessitates Resource creation. In a few articles we will see the alternative offered by Mysten Labs within Sui Move.

The next tutorial in the “Code in Move” series will cover unit tests on Aptos so subscribe so you don’t miss it because testing is extremely important!

As always if you have any issues or concerns about this tutorial or Move in general, feel free to reach me on Twitter and follow me fore more content.

--

--

Thouny

Blockchain developer crafting educational and technical content on Web3 techs and philosophies. Digressions on life trying to make sense of it.