---
order: 5
description: Mint vouchers and lock and unlock native token from a blockchain.
---
Mint and Burn Vouchers
In this chapter, you learn about vouchers. The dex
module implementation mints vouchers and locks and unlocks native token from a blockchain.
There is a lot to learn from this dex
module implementation:
This implementation can teach you how to use various interactions with module accounts or minting, locking or burning tokens.
Create the SafeBurn Function to Burn Vouchers or Lock Tokens
The SafeBurn
function burns tokens if they are IBC vouchers (have an ibc/
prefix) and locks tokens if they are native to the chain.
Create a new x/dex/keeper/mint.go
file:
package keeper
import (
"fmt"
"strings"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
ibctransfertypes "github.com/cosmos/ibc-go/v5/modules/apps/transfer/types"
"interchange/x/dex/types"
)
func isIBCToken(denom string) bool {
return strings.HasPrefix(denom, "ibc/")
}
func (k Keeper) SafeBurn(ctx sdk.Context, port string, channel string, sender sdk.AccAddress, denom string, amount int32) error {
if isIBCToken(denom) {
if err := k.BurnTokens(ctx, sender, sdk.NewCoin(denom, sdkmath.NewInt(int64(amount)))); err != nil {
return err
}
} else {
if err := k.LockTokens(ctx, port, channel, sender, sdk.NewCoin(denom, sdkmath.NewInt(int64(amount)))); err != nil {
return err
}
}
return nil
}
If the token comes from another blockchain as an IBC token, the burning method actually burns those IBC tokens on one chain and unlocks them on the other chain. The native token are locked away.
Now, implement the BurnTokens
keeper method as used in the previous function. The bankKeeper
has a useful function for this:
func (k Keeper) BurnTokens(ctx sdk.Context, sender sdk.AccAddress, tokens sdk.Coin) error {
if err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, types.ModuleName, sdk.NewCoins(tokens)); err != nil {
return err
}
if err := k.bankKeeper.BurnCoins(
ctx, types.ModuleName, sdk.NewCoins(tokens),
); err != nil {
panic(fmt.Sprintf("cannot burn coins after a successful send to a module account: %v", err))
}
return nil
}
Implement the LockTokens
keeper method.
To lock token from a native chain, you can send the native token to the Escrow Address:
func (k Keeper) LockTokens(ctx sdk.Context, sourcePort string, sourceChannel string, sender sdk.AccAddress, tokens sdk.Coin) error {
escrowAddress := ibctransfertypes.GetEscrowAddress(sourcePort, sourceChannel)
if err := k.bankKeeper.SendCoins(
ctx, sender, escrowAddress, sdk.NewCoins(tokens),
); err != nil {
return err
}
return nil
}
BurnTokens
and LockTokens
use SendCoinsFromAccountToModule
, BurnCoins
, and SendCoins
keeper methods of the bank
module.
To start using these function from the dex
module, first add them to the BankKeeper
interface in the x/dex/types/expected_keepers.go
file.
package types
import sdk "github.com/cosmos/cosmos-sdk/types"
type BankKeeper interface {
SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error
SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error
}
SaveVoucherDenom
The SaveVoucherDenom
function saves the voucher denom to be able to convert it back later.
Create a new x/dex/keeper/denom.go
file:
package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
ibctransfertypes "github.com/cosmos/ibc-go/v5/modules/apps/transfer/types"
"interchange/x/dex/types"
)
func (k Keeper) SaveVoucherDenom(ctx sdk.Context, port string, channel string, denom string) {
voucher := VoucherDenom(port, channel, denom)
_, saved := k.GetDenomTrace(ctx, voucher)
if !saved {
k.SetDenomTrace(ctx, types.DenomTrace{
Index: voucher,
Port: port,
Channel: channel,
Origin: denom,
})
}
}
Finally, the last function to implement is the VoucherDenom
function that returns the voucher of the denom from the port ID and channel ID:
func VoucherDenom(port string, channel string, denom string) string {
sourcePrefix := ibctransfertypes.GetDenomPrefix(port, channel)
prefixedDenom := sourcePrefix + denom
denomTrace := ibctransfertypes.ParseDenomTrace(prefixedDenom)
voucher := denomTrace.IBCDenom()
return voucher[:16]
}
Implement an OriginalDenom Function
The OriginalDenom
function returns back the original denom of the voucher.
False is returned if the port ID and channel ID provided are not the origins of the voucher:
func (k Keeper) OriginalDenom(ctx sdk.Context, port string, channel string, voucher string) (string, bool) {
trace, exist := k.GetDenomTrace(ctx, voucher)
if exist {
if trace.Port == port && trace.Channel == channel {
return trace.Origin, true
}
}
return "", false
}
Implement a SafeMint Function
If a token is an IBC token (has an ibc/
prefix), the SafeMint
function mints IBC token with MintTokens
. Otherwise, it unlocks native token with UnlockTokens
.
Go back to the x/dex/keeper/mint.go
file and add the following code:
func (k Keeper) SafeMint(ctx sdk.Context, port string, channel string, receiver sdk.AccAddress, denom string, amount int32) error {
if isIBCToken(denom) {
if err := k.MintTokens(ctx, receiver, sdk.NewCoin(denom, sdkmath.NewInt(int64(amount)))); err != nil {
return err
}
} else {
if err := k.UnlockTokens(
ctx,
port,
channel,
receiver,
sdk.NewCoin(denom, sdkmath.NewInt(int64(amount))),
); err != nil {
return err
}
}
return nil
}
Implement a MintTokens
Function
You can use the bankKeeper
function again to MintCoins. These token will then be sent to the receiver account:
func (k Keeper) MintTokens(ctx sdk.Context, receiver sdk.AccAddress, tokens sdk.Coin) error {
if err := k.bankKeeper.MintCoins(
ctx, types.ModuleName, sdk.NewCoins(tokens),
); err != nil {
return err
}
if err := k.bankKeeper.SendCoinsFromModuleToAccount(
ctx, types.ModuleName, receiver, sdk.NewCoins(tokens),
); err != nil {
panic(fmt.Sprintf("unable to send coins from module to account despite previously minting coins to module account: %v", err))
}
return nil
}
Finally, add the function to unlock token after they are sent back to the native blockchain:
func (k Keeper) UnlockTokens(ctx sdk.Context, sourcePort string, sourceChannel string, receiver sdk.AccAddress, tokens sdk.Coin) error {
escrowAddress := ibctransfertypes.GetEscrowAddress(sourcePort, sourceChannel)
if err := k.bankKeeper.SendCoins(
ctx, escrowAddress, receiver, sdk.NewCoins(tokens),
); err != nil {
return err
}
return nil
}
The MintTokens
function uses two keeper methods from the bank
module: MintCoins
and SendCoinsFromModuleToAccount
. To import these methods, add their signatures to the BankKeeper
interface in the x/dex/types/expected_keepers.go
file:
type BankKeeper interface {
MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error
SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
}
Summary
You finished the mint and burn voucher logic.
It is a good time to make another git commit to save the state of your work:
git add .
git commit -m "Add Mint and Burn Voucher"
In the next chapter, you look into creating sell orders.