Migrating a Coin to Coin Manager
The previous articles in this section showcase how to create a Coin
object with different constructors. Namely, create_currency
and create_regulated_currency
.
This article will show how an existing Coin
object can be migrated to CoinManager
. By doing so, the Coin
deployer and its end-users will gain extra-functionalites. Generally, we recommend initially creating a Coin
via CoinManager
to avoid the need for migration later on. However, if you have an existing Coin
and wish to migrate it to CoinManager
, this article will guide you through the process.
Pre-requisites
1. Create a Simple Coin
First, we will create a simple coin to be migrated CoinManager
.
- Create a new empty package called
my_coin
:
iota move new my_coin
- In
my_coin.move
, paste this simple moveCoin
module:
module my_coin::my_coin {
use iota::coin::{Self, TreasuryCap};
/// The type identifier of coin. The coin will have a type
/// tag of kind: `Coin<package_object::mycoin::MYCOIN>`
/// Make sure that the name of the type matches the module's name.
public struct MYCOIN has drop {}
/// Module initializer is called once on module publish. A treasury
/// cap is sent to the publisher, who then controls minting and burning
fun init(witness: MYCOIN, ctx: &mut TxContext) {
let (treasury, metadata) = coin::create_currency(witness, 6, b"MYCOIN", b"", b"", option::none(), ctx);
transfer::public_freeze_object(metadata);
transfer::public_transfer(treasury, tx_context::sender(ctx))
}
public fun mint(
treasury_cap: &mut TreasuryCap<MYCOIN>,
amount: u64,
recipient: address,
ctx: &mut TxContext,
) {
let coin = coin::mint(treasury_cap, amount, ctx);
transfer::public_transfer(coin, recipient)
}
}
- Deploy the
my_coin
package by running the following command:
iota client publish --gas-budget 100000000
- The console will respond with the transaction effects. You should pay attention to the created objects to retrieve the object IDs:
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Object Changes │
├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Created Objects: │
│ ┌── │
│ │ ObjectID: <OBJECT-ID> │
│ │ Sender: <SENDER-ADDR> │
│ │ Owner: Account Address ( <SENDER-ADDR> ) │
│ │ ObjectType: 0x2::coin::TreasuryCap<<PACKAGE-ID>::mycoin::MYCOIN> │
│ │ Version: 3 │
│ │ Digest: <DIGEST-HASH> │
│ └── │
│ ┌── │
│ │ ObjectID: <OBJECT-ID> │
│ │ Sender: <SENDER-ADDR> │
│ │ Owner: Account Address ( <SENDER-ADDR> ) │
│ │ ObjectType: 0x2::package::UpgradeCap │
│ │ Version: 3 │
│ │ Digest: <DIGEST-HASH> │
│ └── │
│ ┌── │
│ │ ObjectID: <OBJECT-ID> │
│ │ Sender: <SENDER-ADDR> │
│ │ Owner: Immutable │
│ │ ObjectType: 0x2::coin::CoinMetadata<<PACKAGE-ID>::mycoin::MYCOIN> │
│ │ Version: 3 │
│ │ Digest: <DIGEST-HASH> │
│ └── │
│ Mutated Objects: │
│ ┌── │
│ │ ObjectID: 0x1d60b04477b67ebe99af085d02cef4601765976d215313f7bcdab95d7d771b9b │
│ │ Sender: <SENDER-ADDR> │
│ │ Owner: Account Address ( <SENDER-ADDR> ) │
│ │ ObjectType: 0x2::coin::Coin<0x2::iota::IOTA> │
│ │ Version: 3 │
│ │ Digest: <DIGEST-HASH> │
│ └── │
│ Published Objects: │
│ ┌── │
│ │ PackageID: <PACKAGE-ID> │
│ │ Version: 1 │
│ │ Digest: <DIGEST-HASH> │
│ │ Modules: mycoin │
│ └── │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
2. Create a Coin Manager
- In the root folder, create a new package
my_coin_manager
:
iota move new my_coin_manager_manager
- In the created
my_coin_manager.move
, paste the following module:
module my_coin::exclusive_coin {
use iota::coin_manager;
use iota::event;
use iota::coin::{CoinMetadata, TreasuryCap};
/// Phantom parameter T can only be initialized in the `create_guardian`
/// function. But the types passed here must have `drop`.
public struct Guardian<phantom T: drop> has key, store {
id: UID
}
/// This type is the witness resource and is intended to be used only once.
public struct EXCLUSIVE_COIN has drop {}
/// The first argument of this function is an actual instance of the
/// type T with `drop` ability. It is dropped as soon as received.
public fun create_guardian<T: drop>(
_witness: T, ctx: &mut TxContext
): Guardian<T> {
Guardian { id: object::new(ctx) }
}
/// Module initializer is the best way to ensure that the
/// code is called only once. With `Witness` pattern it is
/// often the best practice.
fun init(witness: EXCLUSIVE_COIN, ctx: &mut TxContext) {
transfer::transfer(
create_guardian(witness, ctx),
tx_context::sender(ctx)
)
}
public fun migrate_to_manager<T, S: drop> (otw:Guardian<S> ,cap: TreasuryCap<T>, meta: &CoinMetadata<T>, ctx: &mut TxContext) {
transfer::public_freeze_object(otw);
let (cm_treasury_cap, manager) = coin_manager::new_with_immutable_metadata(cap, meta, ctx);
// Transfer the `CoinManagerTreasuryCap` to the creator of the `Coin`.
transfer::public_transfer(cm_treasury_cap, ctx.sender());
// Publicly share the `CoinManager` object for convenient usage by anyone interested.
transfer::public_share_object(manager);
}
}
Let's go quickly over it before publishing:
What you want to achieve is creating a single instance of CoinManager
. The most straightforward way to do it is in init()
, which is assured to be run only once.
If you were creating a CoinManager
from scratch, that would be relatively simple, as the init function would look like this:
fun init(witness: EXCLUSIVE_COIN, ctx: &mut TxContext) {
// Create a `Coin` type and have it managed.
let (cm_treasury_cap, cm_meta_cap, manager) = coin_manager::create(
witness,
0,
b"EXCL",
b"Exclusive Coin",
b"There are only 100, never any more.",
option::none(),
ctx
);
// Transfer the `CoinManagerTreasuryCap` to the creator of the `Coin`.
transfer::public_transfer(cm_treasury_cap, ctx.sender());
// Transfer the `CoinManagerMetadataCap` to the creator of the `Coin`.
transfer::public_transfer(cm_meta_cap, ctx.sender());
// Publicly share the `CoinManager` object for convenient usage by anyone interested.
transfer::public_share_object(manager);
}
However, since you are migrating from a Coin
, you will use another constructor for CoinManager
: CoinManager::new_with_immutable_metadata(cap,meta,ctx)
if the metadata is already frozen, and CoinManager::new(cap,meta,ctx)
if the metadata is mutable.
In that case, init
would have to take two extra parameters: the Coin
's CoinMetadata
object and the TreasuryCap
object. Iota's Move only allows TxContext
and an optional Witness. There is no means to pass in the extra argument.
You can use the One Time Witness Pattern
to resolve this.
You can use the init
function to ensure that only one instance of Guardian
is created. After that, you can pass Guardian to migrate_to_coin_manager
and freeze it to ensure the call succeeds only once.
Now, you should publish the CoinManager
:
iota client publish --gas-budget 100000000
If you check the console, you will see the created objects:
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Object Changes │
├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Created Objects: │
│ ┌── │
│ │ ObjectID: <OBJECT-ID> │
│ │ Sender: <SENDER-ADDR> │
│ │ Owner: Account Address ( <SENDER-ADDR> ) │
│ │ ObjectType: 0x2::package::UpgradeCap │
│ │ Version: 3 │
│ │ Digest: <DIGEST-HASH> │
│ └── │
│ ┌── │
│ │ ObjectID: <OBJECT-ID> │
│ │ Sender: <SENDER-ADDR> │
│ │ Owner: Account Address ( <SENDER-ADDR> ) │
│ │ ObjectType: <PACKAGE-ID>::exclusive_coin::Guardian<<PACKAGE-ID>::exclusive_coin::EXCLUSIVE_COIN> │
│ │ Version: 3 │
│ │ Digest: <DIGEST-HASH> │
│ └── │
│ Mutated Objects: │
│ ┌── │
│ │ ObjectID: <OBJECT-ID> │
│ │ Sender: <SENDER-ADDR> │
│ │ Owner: Account Address ( <SENDER-ADDR> ) │
│ │ ObjectType: 0x2::coin::Coin<0x2::iota::IOTA> │
│ │ Version: 3 │
│ │ Digest: <DIGEST-HASH> │
│ └── │
│ Published Objects: │
│ ┌── │
│ │ PackageID: <PACKAGE-ID> │
│ │ Version: 1 │
│ │ Digest: <DIGEST-HASH> │
│ │ Modules: exclusive_coin │
│ └── │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
When publishing this module, you will get the Guardian
object's ID. You should then pass this object to the migrate_to_manager
function alongside the Coin
's TreasuryCap
and CoinMetadata
objects.
iota client call --package <COIN-MANAGER-PACKAGE-ID> --module exclusive_coin --function migrate_to_manager --gas-budget 100000000 --type-args <MYCOIN-PACKAGE-ID>::mycoin::MYCOIN <COIN-MANAGER-PACKAGE-ID>::exclusive_coin::EXCLUSIVE_COIN --args <Guardian-OBJECT-ID> <TreasuryCap-OBJECT-ID> <CoinMetadata-OBJECT-ID>
The first time you run the command, the output should look similar to the following:
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Object Changes │
├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Created Objects: │
│ ┌── │
│ │ ObjectID: <OBJECT-ID> │
│ │ Sender: <SENDER-ADDR> │
│ │ Owner: Shared │
│ │ ObjectType: 0x2::coin_manager::CoinManager<<MYCOIN-PACKAGE-ID>::mycoin::MYCOIN> │
│ │ Version: 7 │
│ │ Digest: <DIGEST-HASH> │
│ └── │
│ ┌── │
│ │ ObjectID: <OBJECT-ID> │
│ │ Sender: <SENDER-ADDR> │
│ │ Owner: Account Address ( <SENDER-ADDR> ) │
│ │ ObjectType: 0x2::coin_manager::CoinManagerTreasuryCap<<MY-COIN-PACKAGE-ID>::mycoin::MYCOIN> │
│ │ Version: 7 │
│ │ Digest: <DIGEST-HASH> │
│ └── │
│ Mutated Objects: │
│ ┌── │
│ │ ObjectID: <OBJECT-ID> │
│ │ Sender: <SENDER-ADDR> │
│ │ Owner: Account Address ( <SENDER-ADDR> ) │
│ │ ObjectType: 0x2::coin::Coin<0x2::iota::IOTA> │
│ │ Version: 7 │
│ │ Digest: <DIGEST-HASH> │
│ └── │
│ ┌── │
│ │ ObjectID: <GUARDIAN-OBJECT-ID> │
│ │ Sender: <SENDER-ADDR> │
│ │ Owner: Immutable │
│ │ ObjectType: <COIN_MANAGER-PACKAGE-ID>::exclusive_coin::Guardian<<COIN_MANAGER-PACKAGE-ID>::exclusive_coin::EXCLUSIVE_COIN> │
│ │ Version: 7 │
│ │ Digest: <DIGEST-HASH> │
│ └── │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
This command is a one-time operation. If you try to run it again, you will get an error message.
Conclusion
This article demonstrated how to migrate a Coin
object to a CoinManager
. The process involves creating a CoinManager
and passing the Coin
's TreasuryCap
and CoinMetadata
objects to the migrate_to_manager
function. This function will create a CoinManager
object and transfer the CoinManagerTreasuryCap
to the Coin
's creator. The CoinManager
object is then shared publicly for convenient usage by anyone interested.
We used also used the One Time Witness Pattern
to ensure that the migrate_to_manager
function is called only once.