1"use strict";(globalThis.webpackChunkmy_website=globalThis.webpackChunkmy_website||[]).push([[1258],{15680(e,n,t){t.d(n,{xA:()=>l,yg:()=>m});var r=t(96540);function o(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function i(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);n&&(r=r.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,r)}return t}function a(e){for(var n=1;n<arguments.length;n++){var t=null!=arguments[n]?arguments[n]:{};n%2?i(Object(t),!0).forEach((function(n){o(e,n,t[n])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(t)):i(Object(t)).forEach((function(n){Object.defineProperty(e,n,Object.getOwnPropertyDescriptor(t,n))}))}return e}function s(e,n){if(null==e)return{};var t,r,o=function(e,n){if(null==e)return{};var t,r,o={},i=Object.keys(e);for(r=0;r<i.length;r++)t=i[r],n.indexOf(t)>=0||(o[t]=e[t]);return o}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r<i.length;r++)t=i[r],n.indexOf(t)>=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(o[t]=e[t])}return o}var c=r.createContext({}),d=function(e){var n=r.useContext(c),t=n;return e&&(t="function"==typeof e?e(n):a(a({},n),e)),t},l=function(e){var n=d(e.components);return r.createElement(c.Provider,{value:n},e.children)},u={inlineCode:"code",wrapper:function(e){var n=e.children;return r.createElement(r.Fragment,{},n)}},p=r.forwardRef((function(e,n){var t=e.components,o=e.mdxType,i=e.originalType,c=e.parentName,l=s(e,["components","mdxType","originalType","parentName"]),p=d(t),m=o,g=p["".concat(c,".").concat(m)]||p[m]||u[m]||i;return t?r.createElement(g,a(a({ref:n},l),{},{components:t})):r.createElement(g,a({ref:n},l))}));function m(e,n){var t=arguments,o=n&&n.mdxType;if("string"==typeof e||o){var i=t.length,a=new Array(i);a[0]=p;var s={};for(var c in n)hasOwnProperty.call(n,c)&&(s[c]=n[c]);s.originalType=e,s.mdxType="string"==typeof e?e:o,a[1]=s;for(var d=2;d<i;d++)a[d]=t[d];return r.createElement.apply(null,a)}return r.createElement.apply(null,t)}p.displayName="MDXCreateElement"},6863(e,n,t){t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>a,default:()=>u,frontMatter:()=>i,metadata:()=>s,toc:()=>d});var r=t(58168),o=(t(96540),t(15680));const i={order:5,description:"Mint vouchers and lock and unlock native token from a blockchain."},a="Mint and Burn Vouchers",s={unversionedId:"guide/interchange/mint-and-burn-voucher",id:"version-v0.25/guide/interchange/mint-and-burn-voucher",title:"Mint and Burn Vouchers",description:"Mint vouchers and lock and unlock native token from a blockchain.",source:"@site/versioned_docs/version-v0.25/guide/08-interchange/05-mint-and-burn-voucher.md",sourceDirName:"guide/08-interchange",slug:"/guide/interchange/mint-and-burn-voucher",permalink:"/v0.25/guide/interchange/mint-and-burn-voucher",draft:!1,tags:[],version:"v0.25",sidebarPosition:5,frontMatter:{order:5,description:"Mint vouchers and lock and unlock native token from a blockchain."},sidebar:"tutorialSidebar",previous:{title:"Implement the Order Books",permalink:"/v0.25/guide/interchange/creating-order-books"},next:{title:"Create Sell Orders",permalink:"/v0.25/guide/interchange/creating-sell-orders"}},c={},d=[{value:"Create the SafeBurn Function to Burn Vouchers or Lock Tokens",id:"create-the-safeburn-function-to-burn-vouchers-or-lock-tokens",level:2},{value:"SaveVoucherDenom",id:"savevoucherdenom",level:2},{value:"Implement an OriginalDenom Function",id:"implement-an-originaldenom-function",level:3},{value:"Implement a SafeMint Function",id:"implement-a-safemint-function",level:3},{value:"Implement a <code>MintTokens</code> Function",id:"implement-a-minttokens-function",level:4},{value:"Summary",id:"summary",level:2}],l={toc:d};
1function u(e){let{components:n,...t}=e;return(0,o.yg)("wrapper",(0,r.A)({},l,t,{components:n,mdxType:"MDXLayout"}),(0,o.yg)("h1",{id:"mint-and-burn-vouchers"},"Mint and Burn Vouchers"),(0,o.yg)("p",null,"In this chapter, you learn about vouchers. The ",(0,o.yg)("inlineCode",{parentName:"p"},"dex")," module implementation mints vouchers and locks and unlocks native token from a blockchain."),(0,o.yg)("p",null,"There is a lot to learn from this ",(0,o.yg)("inlineCode",{parentName:"p"},"dex")," module implementation:"),(0,o.yg)("ul",null,(0,o.yg)("li",{parentName:"ul"},"You work with the ",(0,o.yg)("inlineCode",{parentName:"li"},"bank")," keeper and use several methods it offers."),(0,o.yg)("li",{parentName:"ul"},"You interact with another module and use the module account to lock tokens.")),(0,o.yg)("p",null,"This implementation can teach you how to use various interactions with module accounts or minting, locking or burning tokens."),(0,o.yg)("h2",{id:"create-the-safeburn-function-to-burn-vouchers-or-lock-tokens"},"Create the SafeBurn Function to Burn Vouchers or Lock Tokens"),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"SafeBurn")," function burns tokens if they are IBC vouchers (have an ",(0,o.yg)("inlineCode",{parentName:"p"},"ibc/")," prefix) and locks tokens if they are native to the chain."),(0,o.yg)("p",null,"Create a new ",(0,o.yg)("inlineCode",{parentName:"p"},"x/dex/keeper/mint.go")," file:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-go"},'// x/dex/keeper/mint.go\n\npackage keeper\n\nimport (\n "fmt"\n "strings"\n\n sdkmath "cosmossdk.io/math"\n sdk "github.com/cosmos/cosmos-sdk/types"\n ibctransfertypes "github.com/cosmos/ibc-go/v5/modules/apps/transfer/types"\n\n "interchange/x/dex/types"\n)\n\n// isIBCToken checks if the token came from the IBC module\n// Each IBC token starts with an ibc/ denom, the check is rather simple\nfunc isIBCToken(denom string) bool {\n return strings.HasPrefix(denom, "ibc/")\n}\n\nfunc (k Keeper) SafeBurn(ctx sdk.Context, port string, channel string, sender sdk.AccAddress, denom string, amount int32) error {\n if isIBCToken(denom) {\n // Burn the tokens\n if err := k.BurnTokens(ctx, sender, sdk.NewCoin(denom, sdkmath.NewInt(int64(amount)))); err != nil {\n return err\n }\n } else {\n // Lock the tokens\n if err := k.LockTokens(ctx, port, channel, sender, sdk.NewCoin(denom, sdkmath.NewInt(int64(amount)))); err != nil {\n return err\n }\n }\n\n return nil\n}\n')),(0,o.yg)("p",null,"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."),(0,o.yg)("p",null,"Now, implement the ",(0,o.yg)("inlineCode",{parentName:"p"},"BurnTokens")," keeper method as used in the previous function. The ",(0,o.yg)("inlineCode",{parentName:"p"},"bankKeeper")," has a useful function for this:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-go"},'// x/dex/keeper/mint.go\n\nfunc (k Keeper) BurnTokens(ctx sdk.Context, sender sdk.AccAddress, tokens sdk.Coin) error {\n // transfer the coins to the module account and burn them\n if err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, types.ModuleName, sdk.NewCoins(tokens)); err != nil {\n return err\n }\n\n if err := k.bankKeeper.BurnCoins(\n ctx, types.ModuleName, sdk.NewCoins(tokens),\n ); err != nil {\n // NOTE: should not happen as the module account was\n // retrieved on the step above and it has enough balance\n // to burn.\n panic(fmt.Sprintf("cannot burn coins after a successful send to a module account: %v", err))\n }\n\n return nil\n}\n')),(0,o.yg)("p",null,"Implement the ",(0,o.yg)("inlineCode",{parentName:"p"},"LockTokens")," keeper method. "),(0,o.yg)("p",null,"To lock token from a native chain, you can send the native token to the Escrow Address:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-go"}
1,"// x/dex/keeper/mint.go\n\nfunc (k Keeper) LockTokens(ctx sdk.Context, sourcePort string, sourceChannel string, sender sdk.AccAddress, tokens sdk.Coin) error {\n // create the escrow address for the tokens\n escrowAddress := ibctransfertypes.GetEscrowAddress(sourcePort, sourceChannel)\n\n // escrow source tokens. It fails if balance insufficient\n if err := k.bankKeeper.SendCoins(\n ctx, sender, escrowAddress, sdk.NewCoins(tokens),\n ); err != nil {\n return err\n }\n\n return nil\n}\n")),(0,o.yg)("p",null,(0,o.yg)("inlineCode",{parentName:"p"},"BurnTokens")," and ",(0,o.yg)("inlineCode",{parentName:"p"},"LockTokens")," use ",(0,o.yg)("inlineCode",{parentName:"p"},"SendCoinsFromAccountToModule"),", ",(0,o.yg)("inlineCode",{parentName:"p"},"BurnCoins"),", and ",(0,o.yg)("inlineCode",{parentName:"p"},"SendCoins")," keeper methods of the ",(0,o.yg)("inlineCode",{parentName:"p"},"bank")," module. "),(0,o.yg)("p",null,"To start using these function from the ",(0,o.yg)("inlineCode",{parentName:"p"},"dex")," module, first add them to the ",(0,o.yg)("inlineCode",{parentName:"p"},"BankKeeper")," interface in the ",(0,o.yg)("inlineCode",{parentName:"p"},"x/dex/types/expected_keepers.go")," file."),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-go"},'// x/dex/types/expected_keepers.go\n\npackage types\n\nimport sdk "github.com/cosmos/cosmos-sdk/types"\n\n// BankKeeper defines the expected bank keeper\ntype BankKeeper interface {\n //...\n SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error\n BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error\n SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error\n}\n')),(0,o.yg)("h2",{id:"savevoucherdenom"},"SaveVoucherDenom"),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"SaveVoucherDenom")," function saves the voucher denom to be able to convert it back later."),(0,o.yg)("p",null,"Create a new ",(0,o.yg)("inlineCode",{parentName:"p"},"x/dex/keeper/denom.go")," file:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-go"},'// x/dex/keeper/denom.go\n\npackage keeper\n\nimport (\n sdk "github.com/cosmos/cosmos-sdk/types"\n ibctransfertypes "github.com/cosmos/ibc-go/v5/modules/apps/transfer/types"\n\n "interchange/x/dex/types"\n)\n\nfunc (k Keeper) SaveVoucherDenom(ctx sdk.Context, port string, channel string, denom string) {\n voucher := VoucherDenom(port, channel, denom)\n\n // Store the origin denom\n _, saved := k.GetDenomTrace(ctx, voucher)\n if !saved {\n k.SetDenomTrace(ctx, types.DenomTrace{\n Index: voucher,\n Port: port,\n Channel: channel,\n Origin: denom,\n })\n }\n}\n')),(0,o.yg)("p",null,"Finally, the last function to implement is the ",(0,o.yg)("inlineCode",{parentName:"p"},"VoucherDenom")," function that returns the voucher of the denom from the port ID and channel ID:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-go"},'// x/dex/keeper/denom.go\n\nfunc VoucherDenom(port string, channel string, denom string) string {\n // since SendPacket did not prefix the denomination, we must prefix denomination here\n sourcePrefix := ibctransfertypes.GetDenomPrefix(port, channel)\n\n // NOTE: sourcePrefix contains the trailing "/"\n prefixedDenom := sourcePrefix + denom\n\n // construct the denomination trace from the full raw denomination\n denomTrace := ibctransfertypes.ParseDenomTrace(prefixedDenom)\n voucher := denomTrace.IBCDenom()\n return voucher[:16]\n}\n')),(0,o.yg)("h3",{id:"implement-an-originaldenom-function"},"Implement an OriginalDenom Function"),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"OriginalDenom")," function returns back the original denom of the voucher. "),(0,o.yg)("p",null,"False is returned if the port ID and channel ID provided are not the origins of the voucher:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-go"},'// x/dex/keeper/denom.go\n\nfunc (k Keeper) OriginalDenom(ctx sdk.Context, port string, channel string, voucher string) (string, bool) {\n trace, exist := k.GetDenomTrace(ctx, voucher)\n if exist {\n // Check if original port and channel\n if trace.Port == port && trace.Channel == channel {\n return trace.Origin, true\n }\n }\n\n // Not the original chain\n return "", false\n}\n')),(0,o.yg)("h3",{id:"implement-a-safemint-function"},"Implement a SafeMint Function"),(0,o.yg)("p",null,"If a token is an IBC token (has an ",(0,o.yg)("inlineCode",{parentName:"p"},"ibc/")," prefix), the ",(0,o.yg)("inlineCode",{parentName:"p"},"SafeMint")," function mints IBC token with ",(0,o.yg)("inlineCode",{parentName:"p"},"MintTokens"),". Otherwise, it unlocks native token with ",(0,o.yg)("inlineCode",{parentName:"p"},"UnlockTokens"),"."),(0,o.yg)("p",null,"Go back to the ",(0,o.yg)("inlineCode",{parentName:"p"},"x/dex/keeper/mint.go")," file and add the following code:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-go"},"// x/dex/keeper/mint.go\n\nfunc (k Keeper) SafeMint(ctx sdk.Context, port string, channel string, receiver sdk.AccAddress, denom string, amount int32) error {\n if isIBCToken(denom) {\n // Mint IBC tokens\n if err := k.MintTokens(ctx, receiver, sdk.NewCoin(denom, sdkmath.NewInt(int64(amount)))); err != nil {\n return err\n }\n } else {\n // Unlock native tokens\n if err := k.UnlockTokens(\n ctx,\n port,\n channel,\n receiver,\n sdk.NewCoin(denom, sdkmath.NewInt(int64(amount))),\n ); err != nil {\n return err\n }\n }\n\n return nil\n}\n")),(0,o.yg)("h4",{id:"implement-a-minttokens-function"},"Implement a ",(0,o.yg)("inlineCode",{parentName:"h4"},"MintTokens")," Function"),(0,o.yg)("p",null,"You can use the ",(0,o.yg)("inlineCode",{parentName:"p"},"bankKeeper")," function again to MintCoins. These token will then be sent to the receiver account:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-go"}
1,'// x/dex/keeper/mint.go\n\nfunc (k Keeper) MintTokens(ctx sdk.Context, receiver sdk.AccAddress, tokens sdk.Coin) error {\n // mint new tokens if the source of the transfer is the same chain\n if err := k.bankKeeper.MintCoins(\n ctx, types.ModuleName, sdk.NewCoins(tokens),\n ); err != nil {\n return err\n }\n\n // send to receiver\n if err := k.bankKeeper.SendCoinsFromModuleToAccount(\n ctx, types.ModuleName, receiver, sdk.NewCoins(tokens),\n ); err != nil {\n panic(fmt.Sprintf("unable to send coins from module to account despite previously minting coins to module account: %v", err))\n }\n\n return nil\n}\n')),(0,o.yg)("p",null,"Finally, add the function to unlock token after they are sent back to the native blockchain:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-go"},"// x/dex/keeper/mint.go\n\nfunc (k Keeper) UnlockTokens(ctx sdk.Context, sourcePort string, sourceChannel string, receiver sdk.AccAddress, tokens sdk.Coin) error {\n // create the escrow address for the tokens\n escrowAddress := ibctransfertypes.GetEscrowAddress(sourcePort, sourceChannel)\n\n // escrow source tokens. It fails if balance insufficient\n if err := k.bankKeeper.SendCoins(\n ctx, escrowAddress, receiver, sdk.NewCoins(tokens),\n ); err != nil {\n return err\n }\n\n return nil\n}\n")),(0,o.yg)("p",null,"The ",(0,o.yg)("inlineCode",{parentName:"p"},"MintTokens")," function uses two keeper methods from the ",(0,o.yg)("inlineCode",{parentName:"p"},"bank")," module: ",(0,o.yg)("inlineCode",{parentName:"p"},"MintCoins")," and ",(0,o.yg)("inlineCode",{parentName:"p"},"SendCoinsFromModuleToAccount"),".\nTo import these methods, add their signatures to the ",(0,o.yg)("inlineCode",{parentName:"p"},"BankKeeper")," interface in the ",(0,o.yg)("inlineCode",{parentName:"p"},"x/dex/types/expected_keepers.go")," file:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-go"},"// x/dex/types/expected_keepers.go\n\ntype BankKeeper interface {\n // ...\n MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error\n SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error\n}\n")),(0,o.yg)("h2",{id:"summary"},"Summary"),(0,o.yg)("p",null,"You finished the mint and burn voucher logic."),(0,o.yg)("p",null,"It is a good time to make another git commit to save the state of your work:"),(0,o.yg)("pre",null,(0,o.yg)("code",{parentName:"pre",className:"language-bash"},'git add .\ngit commit -m "Add Mint and Burn Voucher"\n')),(0,o.yg)("p",null,"In the next chapter, you look into creating sell orders."))}u.isMDXComponent=!0}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.