diff --git a/docs/docs/02-guide/00-introduction.md b/docs/docs/02-guide/00-introduction.md index 05d104a31e..95c131d283 100644 --- a/docs/docs/02-guide/00-introduction.md +++ b/docs/docs/02-guide/00-introduction.md @@ -16,22 +16,26 @@ Welcome to the Ignite Developer Tutorials, your gateway to mastering blockchain - **Hello World Tutorial**: Engage in the excitement of blockchain development by making your blockchain respond with "Hello, World!" This includes learning to scaffold a Cosmos SDK query and modify keeper methods. -- **Blog Tutorial**: Step into decentralized applications (dApps) with the ability to write and read blog posts on your blockchain. This tutorial covers everything from defining new types in protocol buffer files to writing and reading data from the store. +- **Debugging a Blockchain**: Develop essential skills in debugging to maintain efficient and effective blockchain development. -- **DeFi Loan Tutorial**: Dive into Decentralized Finance (DeFi) by building a blockchain for managing loans. Gain insights into CRUD logic, module method integration, and token transaction management. +- **Running in a Docker Container**: Learn how to use Docker to containerize your blockchain environment, ensuring consistency and portability across development stages. -- **Token Factory Tutorial**: Master the creation and management of digital assets on your blockchain by building a token factory module, learning module development, CRUD operations without delete functionality, and native denomination integration. +- **Chain Simulation**: Understand the importance and method of simulating blockchain environments for testing and validating functionality under various scenarios. -- **Inter-blockchain Communication (IBC) Basics**: Explore the interconnected world of blockchains with the IBC protocol. Learn how to scaffold an IBC-enabled module, manage IBC packets, and configure a built-in IBC relayer. +If you want to learn more about creating modules and how to work with your blockchain: -- **Interchange Module**: Advance your IBC knowledge by building a module for decentralized token exchanges and order books. +Visit the Tutorials at [tutorials.ignite.com](https://tutorials.ignite.com). -- **Debugging a Blockchain**: Develop essential skills in debugging to maintain efficient and effective blockchain development. +You will find our evergreen tutorials like: -- **Running in a Docker Container**: Learn how to use Docker to containerize your blockchain environment, ensuring consistency and portability across development stages. +- How to build a Blog -- **Chain Simulation**: Understand the importance and method of simulating blockchain environments for testing and validating functionality under various scenarios. +- Create a DeFi Loan Module + +- How to create a Rollkit Sovereign Rollup Blockchain + +- Use Ignite Extensions, like Hermes or add CosmWasm to your Blockchain. -Each tutorial builds upon the previous, enhancing your understanding and skills progressively. By completing these tutorials, you will gain a robust understanding of blockchain principles, the Cosmos SDK, and practical experience in developing and managing blockchain projects. +- And many more! -Embark on your journey to become a proficient blockchain developer with Ignite's Developer Tutorials! +Embark on your journey to become a proficient Blockchain Developer with Ignite's Developer Tutorials! diff --git a/docs/docs/02-guide/04-blog.md b/docs/docs/02-guide/04-blog.md deleted file mode 100644 index df8b1e6522..0000000000 --- a/docs/docs/02-guide/04-blog.md +++ /dev/null @@ -1,403 +0,0 @@ ---- -description: Explore the essentials of module development while creating a dynamic blogging platform on your blockchain, where users can seamlessly submit and access blog posts, gaining practical experience in decentralized application functionalities. -title: Blog tutorial ---- - -# Build a Blog on a Blockchain with Ignite CLI - -## Introduction - -This tutorial guides you through creating a blog application as a Cosmos SDK blockchain using Ignite CLI. You'll learn how to set up types, messages, queries, and write logic for creating, reading, updating, and deleting blog posts. - -## Creating the Blog Blockchain - -1. **Initialize the Blockchain:** - -```bash -ignite scaffold chain blog -cd blog -``` - -2. **Define the Post Type:** - -```bash -ignite scaffold type post title body creator id:uint -``` -This step creates a Post type with title (string), body (string), creator (string), and id (unsigned integer) fields. - -## Implementing CRUD operations - -**Creating Posts** - -1. **Scaffold Create Message** - -```bash -ignite scaffold message create-post title body --response id:uint -``` - -This message allows users to create posts with a title and body. - -2. **Append Posts to the Store:** - -Create the file `x/blog/keeper/post.go`. - -Implement `AppendPost` and the following functions in `x/blog/keeper/post.go` to add posts to the store. - -```go title="x/blog/keeper/post.go" -package keeper - -import ( - "encoding/binary" - - "cosmossdk.io/store/prefix" - "github.com/cosmos/cosmos-sdk/runtime" - sdk "github.com/cosmos/cosmos-sdk/types" - - "blog/x/blog/types" -) - -func (k Keeper) AppendPost(ctx sdk.Context, post types.Post) uint64 { - count := k.GetPostCount(ctx) - post.Id = count - storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) - store := prefix.NewStore(storeAdapter, types.KeyPrefix(types.PostKey)) - appendedValue := k.cdc.MustMarshal(&post) - store.Set(GetPostIDBytes(post.Id), appendedValue) - k.SetPostCount(ctx, count+1) - return count -} - -func (k Keeper) GetPostCount(ctx sdk.Context) uint64 { - storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) - store := prefix.NewStore(storeAdapter, []byte{}) - byteKey := types.KeyPrefix(types.PostCountKey) - bz := store.Get(byteKey) - if bz == nil { - return 0 - } - return binary.BigEndian.Uint64(bz) -} - -func GetPostIDBytes(id uint64) []byte { - bz := make([]byte, 8) - binary.BigEndian.PutUint64(bz, id) - return bz -} - -func (k Keeper) SetPostCount(ctx sdk.Context, count uint64) { - storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) - store := prefix.NewStore(storeAdapter, []byte{}) - byteKey := types.KeyPrefix(types.PostCountKey) - bz := make([]byte, 8) - binary.BigEndian.PutUint64(bz, count) - store.Set(byteKey, bz) -} - -func (k Keeper) GetPost(ctx sdk.Context, id uint64) (val types.Post, found bool) { - storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) - store := prefix.NewStore(storeAdapter, types.KeyPrefix(types.PostKey)) - b := store.Get(GetPostIDBytes(id)) - if b == nil { - return val, false - } - k.cdc.MustUnmarshal(b, &val) - return val, true -} -``` - -3. **Add Post key prefix:** - -Add the `PostKey` and `PostCountKey` functions to the `x/blog/types/keys.go` file: - -```go title="x/blog/types/keys.go" - // PostKey is used to uniquely identify posts within the system. - // It will be used as the beginning of the key for each post, followed bei their unique ID - PostKey = "Post/value/" - - // This key will be used to keep track of the ID of the latest post added to the store. - PostCountKey = "Post/count/" -``` - -4. **Update Create Post:** - -Update the `x/blog/keeper/msg_server_create_post.go` file with the `CreatePost` function: - -```go title="x/blog/keeper/msg_server_create_post.go" -package keeper - -import ( - "context" - - sdk "github.com/cosmos/cosmos-sdk/types" - - "blog/x/blog/types" -) - -func (k msgServer) CreatePost(goCtx context.Context, msg *types.MsgCreatePost) (*types.MsgCreatePostResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - var post = types.Post{ - Creator: msg.Creator, - Title: msg.Title, - Body: msg.Body, - } - id := k.AppendPost( - ctx, - post, - ) - return &types.MsgCreatePostResponse{ - Id: id, - }, nil -} -``` - -**Updating Posts** - -1. **Scaffold Update Message:** - -```bash -ignite scaffold message update-post title body id:uint -``` - -This command allows for updating existing posts specified by their ID. - -2. **Update Logic** - -Implement `SetPost` in `x/blog/keeper/post.go` for updating posts in the store. - -```go title="x/blog/keeper/post.go" -func (k Keeper) SetPost(ctx sdk.Context, post types.Post) { - storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) - store := prefix.NewStore(storeAdapter, types.KeyPrefix(types.PostKey)) - b := k.cdc.MustMarshal(&post) - store.Set(GetPostIDBytes(post.Id), b) -} -``` - -Refine the `UpdatePost` function in `x/blog/keeper/msg_server_update_post.go`. - -```go title="x/blog/keeper/msg_server_update_post.go" -package keeper - -import ( - "context" - "fmt" - - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - - "blog/x/blog/types" -) - -func (k msgServer) UpdatePost(goCtx context.Context, msg *types.MsgUpdatePost) (*types.MsgUpdatePostResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - var post = types.Post{ - Creator: msg.Creator, - Id: msg.Id, - Title: msg.Title, - Body: msg.Body, - } - val, found := k.GetPost(ctx, msg.Id) - if !found { - return nil, errorsmod.Wrap(sdkerrors.ErrKeyNotFound, fmt.Sprintf("key %d doesn't exist", msg.Id)) - } - if msg.Creator != val.Creator { - return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "incorrect owner") - } - k.SetPost(ctx, post) - return &types.MsgUpdatePostResponse{}, nil -} -``` - -**Deleting Posts** - -1. **Scaffold Delete Message:** - -```bash -ignite scaffold message delete-post id:uint -``` - -This command enables the deletion of posts by their ID. - -2. **Delete Logic:** - -Implement RemovePost in `x/blog/keeper/post.go` to delete posts from the store. - -```go title="x/blog/keeper/post.go" -func (k Keeper) RemovePost(ctx sdk.Context, id uint64) { - storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) - store := prefix.NewStore(storeAdapter, types.KeyPrefix(types.PostKey)) - store.Delete(GetPostIDBytes(id)) -} -``` - -Add the according logic to `x/blog/keeper/msg_server_delete_post`. - -```go title="x/blog/keeper/msg_server_delete_post.go" -package keeper - -import ( - "context" - "fmt" - - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - - "blog/x/blog/types" -) - -func (k msgServer) DeletePost(goCtx context.Context, msg *types.MsgDeletePost) (*types.MsgDeletePostResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - val, found := k.GetPost(ctx, msg.Id) - if !found { - return nil, errorsmod.Wrap(sdkerrors.ErrKeyNotFound, fmt.Sprintf("key %d doesn't exist", msg.Id)) - } - if msg.Creator != val.Creator { - return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "incorrect owner") - } - k.RemovePost(ctx, msg.Id) - return &types.MsgDeletePostResponse{}, nil -} -``` - -**Reading Posts** - -1. **Scaffold Query Messages:** - -```bash title="proto/blog/blog/query.proto" -ignite scaffold query show-post id:uint --response post:Post -ignite scaffold query list-post --response post:Post --paginated -``` - -These queries allow for retrieving a single post by ID and listing all posts with pagination. - -2. **Query Implementation:** - -Implement `ShowPost` in `x/blog/keeper/query_show_post.go`. - -```go title="x/blog/keeper/query_show_post.go" -package keeper - -import ( - "context" - - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - "blog/x/blog/types" -) - -func (k Keeper) ShowPost(goCtx context.Context, req *types.QueryShowPostRequest) (*types.QueryShowPostResponse, error) { - if req == nil { - return nil, status.Error(codes.InvalidArgument, "invalid request") - } - - ctx := sdk.UnwrapSDKContext(goCtx) - post, found := k.GetPost(ctx, req.Id) - if !found { - return nil, sdkerrors.ErrKeyNotFound - } - - return &types.QueryShowPostResponse{Post: post}, nil -} -``` - -Implement `ListPost` in `x/blog/keeper/query_list_post.go`. - -```go title="x/blog/keeper/query_list_post.go" -package keeper - -import ( - "context" - - "cosmossdk.io/store/prefix" - "github.com/cosmos/cosmos-sdk/runtime" - "github.com/cosmos/cosmos-sdk/types/query" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - "blog/x/blog/types" -) - -func (k Keeper) ListPost(ctx context.Context, req *types.QueryListPostRequest) (*types.QueryListPostResponse, error) { - if req == nil { - return nil, status.Error(codes.InvalidArgument, "invalid request") - } - - storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx)) - store := prefix.NewStore(storeAdapter, types.KeyPrefix(types.PostKey)) - - var posts []types.Post - pageRes, err := query.Paginate(store, req.Pagination, func(key []byte, value []byte) error { - var post types.Post - if err := k.cdc.Unmarshal(value, &post); err != nil { - return err - } - - posts = append(posts, post) - return nil - }) - - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - - return &types.QueryListPostResponse{Post: posts, Pagination: pageRes}, nil -} -``` - -3. **Proto Implementation:** - -Add a `repeated` keyword to return a list of posts in `QueryListPostResponse` and include the option -`[(gogoproto.nullable) = false]` in `QueryShowPostResponse` and `QueryListPostResponse` to generate the field without a pointer. - -```proto title="proto/blog/blog/query.proto" -message QueryShowPostResponse { - Post post = 1 [(gogoproto.nullable) = false]; -} - -message QueryListPostResponse { - // highlight-next-line - repeated Post post = 1 [(gogoproto.nullable) = false]; - cosmos.base.query.v1beta1.PageResponse pagination = 2; -} -``` - -**Interacting with the Blog** - -1. **Create a Post:** - -```bash -blogd tx blog create-post hello world --from alice --chain-id blog -``` - -2. **View a Post:** - -```bash -blogd q blog show-post 0 -``` - -3. **List All Posts:** - -```bash -blogd q blog list-post -``` - -4. **Update a Post:** - -```bash -blogd tx blog update-post "Hello" "Cosmos" 0 --from alice --chain-id blog -``` - -5. **Delete a Post:** - -```bash -blogd tx blog delete-post 0 --from alice --chain-id blog -``` - -## Conclusion - -Congratulations on completing the Blog tutorial! You've successfully built a functional blockchain application using Ignite and Cosmos SDK. This tutorial equipped you with the skills to generate code for key blockchain operations and implement business-specific logic in a blockchain context. Continue developing your skills and expanding your blockchain applications with the next tutorials. diff --git a/docs/docs/02-guide/06-ibc.md b/docs/docs/02-guide/04-ibc.md similarity index 100% rename from docs/docs/02-guide/06-ibc.md rename to docs/docs/02-guide/04-ibc.md diff --git a/docs/docs/02-guide/08-debug.md b/docs/docs/02-guide/05-debug.md similarity index 100% rename from docs/docs/02-guide/08-debug.md rename to docs/docs/02-guide/05-debug.md diff --git a/docs/docs/02-guide/05-loan.md b/docs/docs/02-guide/05-loan.md deleted file mode 100644 index b2a65f5d06..0000000000 --- a/docs/docs/02-guide/05-loan.md +++ /dev/null @@ -1,483 +0,0 @@ -# DeFi Loan - -## Introduction - -Decentralized finance (DeFi) is a rapidly growing sector that is transforming the way we think about financial instruments and provides an array of inventive financial products and services. These include lending, borrowing, spot trading, margin trading, and flash loans, all of which are available to anyone possessing an internet connection. - -A DeFi loan represents a financial contract where the borrower is granted a certain asset, like currency or digital tokens. -In return, the borrower agrees to pay an additional fee and repay the loan within a set period of time. -To secure a loan, the borrower provides collateral that the lender can claim in the event of default. - -## You Will Learn - -In this tutorial you will learn how to: - -- **Scaffold a DeFi Module:** Learn how to use Ignite CLI to scaffold the basic structure of a DeFi module tailored for loan services. -- **Implement Loan Transactions:** Walk through coding the logic for initiating, managing, and closing loans. -- **Create Custom Tokens:** Understand how to create and manage custom tokens within your DeFi ecosystem, vital for lending and borrowing mechanisms. -- **Integrate Interest Rate Models:** Dive into implementing interest rate models to calculate loan interests dynamically. -- **Ensure Security and Compliance:** Focus on security, ensure your DeFi module is resistant to common vulnerabilities by validating inputs. -- **Test and Debug:** Learn effective strategies for testing your DeFi module and debugging issues that arise during development. - -## Setup and Scaffold - -1. **Create a New Blockchain:** - -```bash -ignite scaffold chain loan --no-module && cd loan -``` - -Notice the `--no-module` flag, in the next step we make sure the `bank` dependency is included with scaffolding the module. - -2. **Create a Module:** - -Create a new "loan" module that is based on the standard Cosmos SDK `bank` module. - -```bash -ignite scaffold module loan --dep bank -``` - -3. **Define the loan Module:** - -The "list" scaffolding command is used to generate files that implement the logic for storing and interacting with data stored as a list in the blockchain state. - -```bash -ignite scaffold list loan amount fee collateral deadline state borrower lender --no-message -``` - -4. **Scaffold the Messages:** - -Scaffold the code for handling the messages for requesting, approving, repaying, liquidating, and cancelling loans. - -- Handling Loan Requests - -```bash -ignite scaffold message request-loan amount fee collateral deadline -``` - -- Approving and Canceling Loans - -```bash -ignite scaffold message approve-loan id:uint -``` - -```bash -ignite scaffold message cancel-loan id:uint -``` - -- Repaying and Liquidating Loans - -```bash -ignite scaffold message repay-loan id:uint -``` - -```bash -ignite scaffold message liquidate-loan id:uint -``` - -## Additional Features - -- **Extend the BankKeeper:** - -Ignite takes care of adding the `bank` keeper, but you still need to tell the loan module which bank methods you will be using. You will be using three methods: `SendCoins`, `SendCoinsFromAccountToModule`, and `SendCoinsFromModuleToAccount`. -Remove the `SpendableCoins` function from the `BankKeeper`. - -Add these to the `Bankkeeper` interface. - -```go title="x/loan/types/expected_keepers.go" -package types - -import ( - "context" - - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// AccountKeeper defines the expected interface for the Account module. -type AccountKeeper interface { - GetAccount(context.Context, sdk.AccAddress) sdk.AccountI // only used for simulation - // Methods imported from account should be defined here -} - -// BankKeeper defines the expected interface for the Bank module. -type BankKeeper interface { - // SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins - SendCoins(ctx context.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error - SendCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error - SendCoinsFromModuleToAccount(ctx context.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error -} - -// ParamSubspace defines the expected Subspace interface for parameters. -type ParamSubspace interface { - Get(context.Context, []byte, interface{}) - Set(context.Context, []byte, interface{}) -} -``` - -- **Implement basic Validation to ensure proper loan requests:** - -When a loan is created, a certain message input validation is required. You want to throw error messages in case the end user tries impossible inputs. - -The `ValidateBasic` function plays a crucial role in maintaining the security and compliance of loan input parameters. By implementing comprehensive input validations, you enhance the security of your application. It's important to rigorously verify all user inputs to ensure they align with the established standards and rules of your platform. - -```go title="x/loan/types/message_request_loan.go" -import ( - "strconv" - - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" -) - -func (msg *MsgRequestLoan) ValidateBasic() error { - _, err := sdk.AccAddressFromBech32(msg.Creator) - if err != nil { - return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) - } - amount, _ := sdk.ParseCoinsNormalized(msg.Amount) - if !amount.IsValid() { - return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "amount is not a valid Coins object") - } - if amount.Empty() { - return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "amount is empty") - } - fee, _ := sdk.ParseCoinsNormalized(msg.Fee) - if !fee.IsValid() { - return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "fee is not a valid Coins object") - } - deadline, err := strconv.ParseInt(msg.Deadline, 10, 64) - if err != nil { - return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "deadline is not an integer") - } - if deadline <= 0 { - return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "deadline should be a positive integer") - } - collateral, _ := sdk.ParseCoinsNormalized(msg.Collateral) - if !collateral.IsValid() { - return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "collateral is not a valid Coins object") - } - if collateral.Empty() { - return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "collateral is empty") - } - return nil -} -``` - -## Using the Platform - -1. **As a Borrower:** - -Implement `RequestLoan` keeper method that will be called whenever a user requests a loan. `RequestLoan` creates a new loan; Set terms like amount, fee, collateral, and repayment deadline. The collateral from the borrower's account is sent to a module account, and adds the loan to the blockchain's store. - -Replace your scaffolded templates with the following code. - -```go title="x/loan/keeper/msg_server_request_loan.go" -package keeper - -import ( - "context" - - sdk "github.com/cosmos/cosmos-sdk/types" - - "loan/x/loan/types" -) - -func (k msgServer) RequestLoan(goCtx context.Context, msg *types.MsgRequestLoan) (*types.MsgRequestLoanResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - var loan = types.Loan{ - Amount: msg.Amount, - Fee: msg.Fee, - Collateral: msg.Collateral, - Deadline: msg.Deadline, - State: "requested", - Borrower: msg.Creator, - } - borrower, err := sdk.AccAddressFromBech32(msg.Creator) - if err != nil { - panic(err) - } - collateral, err := sdk.ParseCoinsNormalized(loan.Collateral) - if err != nil { - panic(err) - } - sdkError := k.bankKeeper.SendCoinsFromAccountToModule(ctx, borrower, types.ModuleName, collateral) - if sdkError != nil { - return nil, sdkError - } - k.AppendLoan(ctx, loan) - return &types.MsgRequestLoanResponse{}, nil -} -``` - -As a borrower, you have the option to cancel a loan you have created if you no longer want to proceed with it. However, this action is only possible if the loan's current status is marked as "requested". - -```go title="x/loan/keeper/msg_server_cancel_loan.go" -package keeper - -import ( - "context" - - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - - "loan/x/loan/types" -) - -func (k msgServer) CancelLoan(goCtx context.Context, msg *types.MsgCancelLoan) (*types.MsgCancelLoanResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - loan, found := k.GetLoan(ctx, msg.Id) - if !found { - return nil, errorsmod.Wrapf(sdkerrors.ErrKeyNotFound, "key %d doesn't exist", msg.Id) - } - if loan.Borrower != msg.Creator { - return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Cannot cancel: not the borrower") - } - if loan.State != "requested" { - return nil, errorsmod.Wrapf(types.ErrWrongLoanState, "%v", loan.State) - } - borrower, _ := sdk.AccAddressFromBech32(loan.Borrower) - collateral, _ := sdk.ParseCoinsNormalized(loan.Collateral) - err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, borrower, collateral) - if err != nil { - return nil, err - } - loan.State = "cancelled" - k.SetLoan(ctx, loan) - return &types.MsgCancelLoanResponse{}, nil -} -``` - -2. **As a Lender:** - -Approve loan requests and liquidate loans if borrowers fail to repay. - -```go title="x/loan/keeper/msg_server_approve_loan.go" -package keeper - -import ( - "context" - - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - - "loan/x/loan/types" -) - -func (k msgServer) ApproveLoan(goCtx context.Context, msg *types.MsgApproveLoan) (*types.MsgApproveLoanResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - loan, found := k.GetLoan(ctx, msg.Id) - if !found { - return nil, errorsmod.Wrapf(sdkerrors.ErrKeyNotFound, "key %d doesn't exist", msg.Id) - } - if loan.State != "requested" { - return nil, errorsmod.Wrapf(types.ErrWrongLoanState, "%v", loan.State) - } - lender, _ := sdk.AccAddressFromBech32(msg.Creator) - borrower, _ := sdk.AccAddressFromBech32(loan.Borrower) - amount, err := sdk.ParseCoinsNormalized(loan.Amount) - if err != nil { - return nil, errorsmod.Wrap(types.ErrWrongLoanState, "Cannot parse coins in loan amount") - } - err = k.bankKeeper.SendCoins(ctx, lender, borrower, amount) - if err != nil { - return nil, err - } - loan.Lender = msg.Creator - loan.State = "approved" - k.SetLoan(ctx, loan) - return &types.MsgApproveLoanResponse{}, nil -} -``` - -```go title="x/loan/keeper/msg_server_repay_loan.go" -package keeper - -import ( - "context" - - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - - "loan/x/loan/types" -) - -func (k msgServer) RepayLoan(goCtx context.Context, msg *types.MsgRepayLoan) (*types.MsgRepayLoanResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - loan, found := k.GetLoan(ctx, msg.Id) - if !found { - return nil, errorsmod.Wrapf(sdkerrors.ErrKeyNotFound, "key %d doesn't exist", msg.Id) - } - if loan.State != "approved" { - return nil, errorsmod.Wrapf(types.ErrWrongLoanState, "%v", loan.State) - } - lender, _ := sdk.AccAddressFromBech32(loan.Lender) - borrower, _ := sdk.AccAddressFromBech32(loan.Borrower) - if msg.Creator != loan.Borrower { - return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Cannot repay: not the borrower") - } - amount, _ := sdk.ParseCoinsNormalized(loan.Amount) - fee, _ := sdk.ParseCoinsNormalized(loan.Fee) - collateral, _ := sdk.ParseCoinsNormalized(loan.Collateral) - err := k.bankKeeper.SendCoins(ctx, borrower, lender, amount) - if err != nil { - return nil, err - } - err = k.bankKeeper.SendCoins(ctx, borrower, lender, fee) - if err != nil { - return nil, err - } - err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, borrower, collateral) - if err != nil { - return nil, err - } - loan.State = "repayed" - k.SetLoan(ctx, loan) - return &types.MsgRepayLoanResponse{}, nil -} -``` - -```go title="x/loan/keeper/msg_server_liquidate_loan.go" -package keeper - -import ( - "context" - "strconv" - - errorsmod "cosmossdk.io/errors" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - - "loan/x/loan/types" -) - -func (k msgServer) LiquidateLoan(goCtx context.Context, msg *types.MsgLiquidateLoan) (*types.MsgLiquidateLoanResponse, error) { - ctx := sdk.UnwrapSDKContext(goCtx) - loan, found := k.GetLoan(ctx, msg.Id) - if !found { - return nil, errorsmod.Wrapf(sdkerrors.ErrKeyNotFound, "key %d doesn't exist", msg.Id) - } - if loan.Lender != msg.Creator { - return nil, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "Cannot liquidate: not the lender") - } - if loan.State != "approved" { - return nil, errorsmod.Wrapf(types.ErrWrongLoanState, "%v", loan.State) - } - lender, _ := sdk.AccAddressFromBech32(loan.Lender) - collateral, _ := sdk.ParseCoinsNormalized(loan.Collateral) - deadline, err := strconv.ParseInt(loan.Deadline, 10, 64) - if err != nil { - panic(err) - } - if ctx.BlockHeight() < deadline { - return nil, errorsmod.Wrap(types.ErrDeadline, "Cannot liquidate before deadline") - } - err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, lender, collateral) - if err != nil { - return nil, err - } - loan.State = "liquidated" - k.SetLoan(ctx, loan) - return &types.MsgLiquidateLoanResponse{}, nil -} -``` - -Add the custom errors `ErrWrongLoanState` and `ErrDeadline`: - -```go title="x/loan/types/errors.go" -package types - -import ( - sdkerrors "cosmossdk.io/errors" -) - -var ( - ErrWrongLoanState = sdkerrors.Register(ModuleName, 2, "wrong loan state") - ErrDeadline = sdkerrors.Register(ModuleName, 3, "deadline") -) -``` - -## Testing the Application - -- **Add Test Tokens:** - -Configure config.yml to add tokens (e.g., 10000foocoin) to test accounts. - -```bash title="config.yml" -version: 1 -accounts: - - name: alice - coins: - - 20000token - - 10000foocoin - - 200000000stake - - name: bob - coins: - - 10000token - - 100000000stake -client: - openapi: - path: docs/static/openapi.yml -faucet: - name: bob - coins: - - 5token - - 100000stake -validators: - - name: alice - bonded: 100000000stake -``` - -- **Start the Blockchain:** - -```bash -ignite chain serve -``` - -If everything works successful, you should see the `Blockchain is running` message in the Terminal. - -- **Request a loan:** - -In a new terminal window, request a loan of `1000token` with `100token` as a fee and `1000foocoin` as a collateral from Alice's account. The deadline is set to `500` blocks: - -```bash -loand tx loan request-loan 1000token 100token 1000foocoin 500 --from alice --chain-id loan -``` - -- **Approve the loan:** - -```bash -loand tx loan approve-loan 0 --from bob --chain-id loan -``` - -- **Repay a loan:** - -```bash -loand tx loan repay-loan 0 --from alice --chain-id loan -``` - -- **Liquidate a loan:** - -```bash -loand tx loan request-loan 1000token 100token 1000foocoin 20 --from alice --chain-id loan -y -loand tx loan approve-loan 1 --from bob --chain-id loan -y -loand tx loan liquidate-loan 1 --from bob --chain-id loan -y -``` - -At any state in the process, use `q list loan` to see the active state of all loans. - -```bash -loand q loan list-loan -``` - -Query the blockchain for balances to confirm they have changed according to your transactions. - -```bash -loand q bank balances $(loand keys show alice -a) -``` - -## Conclusion - -This tutorial outlines the process of setting up a decentralized platform for digital asset loans using blockchain technology. By following these steps, you can create a DeFi platform that allows users to engage in secure and transparent lending and borrowing activities. diff --git a/docs/docs/02-guide/09-docker.md b/docs/docs/02-guide/06-docker.md similarity index 100% rename from docs/docs/02-guide/09-docker.md rename to docs/docs/02-guide/06-docker.md diff --git a/docs/docs/02-guide/10-simapp.md b/docs/docs/02-guide/07-simapp.md similarity index 100% rename from docs/docs/02-guide/10-simapp.md rename to docs/docs/02-guide/07-simapp.md