Repository: desmos-labs/desmos
Pull request #517 (2021-06-25 07:37:10)

Add sign command
<!-- < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < ☺
v                               ✰  Thanks for creating a PR! ✰    
v    Before smashing the submit button please review the checkboxes.
v    If a checkbox is n/a - please still include it but + a little note why
☺ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > >  -->

## Description
This PR adds the `sign` command to the `desmos` root command, allowing to sign any data. No changelog entry is required since this is part of the things that are needed in order to implement applications links

## Checklist
- [ ] Targeted PR against correct branch.
- [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work.
- [ ] Wrote unit tests.
- [ ] Wrote integration tests (simulation & CLI). 
- [ ] Updated the documentation. 
- [ ] Added an entry to the `CHANGELOG.md` file.
- [ ] Re-reviewed `Files changed` in the Github PR explorer.


codecov[bot]: # [Codecov](https://codecov.io/gh/desmos-labs/desmos/pull/517?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs) Report
> Merging [#517](https://codecov.io/gh/desmos-labs/desmos/pull/517?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs) (079cb12) into [master](https://codecov.io/gh/desmos-labs/desmos/commit/504d24e823bc7e1abce47de938f3c3e2cb59c310?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs) (504d24e) will **decrease** coverage by `2.60%`.
> The diff coverage is `64.51%`.

[![Impacted file tree graph](https://codecov.io/gh/desmos-labs/desmos/pull/517/graphs/tree.svg?width=650&height=150&src=pr&token=c87ulmWL91&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs)](https://codecov.io/gh/desmos-labs/desmos/pull/517?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs)

```diff
@@            Coverage Diff             @@
##           master     #517      +/-   ##
==========================================
- Coverage   84.94%   82.33%   -2.61%     
==========================================
  Files          89       91       +2     
  Lines        4284     4444     +160     
==========================================
+ Hits         3639     3659      +20     
- Misses        484      619     +135     
- Partials      161      166       +5     
```


| [Impacted Files](https://codecov.io/gh/desmos-labs/desmos/pull/517?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs) | Coverage Δ | |
|---|---|---|
| [app/desmos/cmd/root.go](https://codecov.io/gh/desmos-labs/desmos/pull/517/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs#diff-YXBwL2Rlc21vcy9jbWQvcm9vdC5nbw==) | `0.00% <0.00%> (ø)` | |
| [app/desmos/cmd/sign.go](https://codecov.io/gh/desmos-labs/desmos/pull/517/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs#diff-YXBwL2Rlc21vcy9jbWQvc2lnbi5nbw==) | `66.66% <66.66%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/desmos-labs/desmos/pull/517?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/desmos-labs/desmos/pull/517?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs). Last update [504d24e...079cb12](https://codecov.io/gh/desmos-labs/desmos/pull/517?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs).


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ed5abd3a..1fdf9aea 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@
 - Updated Cosmos SDK to `v0.42.6` ([#509](https://github.com/desmos-labs/desmos/issues/509))
 - Added the possibility to verify a profile with an external application ([#472](https://github.com/desmos-labs/desmos/issues/472)) 
 - Improved the posts query ([#499](https://github.com/desmos-labs/desmos/issues/499))
+- Added the post comments query ([#510](https://github.com/desmos-labs/desmos/pull/510))
 
 # Version 0.16.3
 ## Changes
diff --git a/app/desmos/cmd/root.go b/app/desmos/cmd/root.go
index 733cfb43..ff6f0da5 100644
--- a/app/desmos/cmd/root.go
+++ b/app/desmos/cmd/root.go
@@ -109,6 +109,7 @@ func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) {
 		rpc.StatusCommand(),
 		queryCommand(),
 		txCommand(),
+		GetSignCmd(),
 		keys.Commands(app.DefaultNodeHome),
 	)
 }
diff --git a/app/desmos/cmd/sign.go b/app/desmos/cmd/sign.go
new file mode 100644
index 00000000..5695d893
--- /dev/null
+++ b/app/desmos/cmd/sign.go
@@ -0,0 +1,76 @@
+package cmd
+
+import (
+	"encoding/hex"
+	"encoding/json"
+	"fmt"
+	"strings"
+
+	"github.com/cosmos/cosmos-sdk/client"
+	"github.com/cosmos/cosmos-sdk/client/flags"
+	"github.com/cosmos/cosmos-sdk/client/tx"
+	"github.com/spf13/cobra"
+)
+
+type SignatureData struct {
+	Address   string `json:"address"`
+	PubKey    string `json:"pub_key"`
+	Signature string `json:"signature"`
+	Value     string `json:"value"`
+}
+
+// GetSignCmd returns the command allowing to sign an arbitrary for later verification
+func GetSignCmd() *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "sign [value]",
+		Short: "Allows to sign the given value using the private key associated to the address or key specified using the --from flag",
+		RunE: func(cmd *cobra.Command, args []string) error {
+			clientCtx, err := client.GetClientTxContext(cmd)
+			if err != nil {
+				return err
+			}
+
+			f := cmd.Flags()
+			txFactory := tx.NewFactoryCLI(clientCtx, f)
+
+			from, _ := cmd.Flags().GetString(flags.FlagFrom)
+			_, fromName, _, err := client.GetFromFields(txFactory.Keybase(), from, clientCtx.GenerateOnly)
+			if err != nil {
+				return fmt.Errorf("error getting account from keybase: %w", err)
+			}
+
+			// Get the key from the keybase
+			key, err := txFactory.Keybase().Key(fromName)
+			if err != nil {
+				return err
+			}
+
+			// Sign the data with the private key
+			value := args[0]
+			bz, pubKey, err := txFactory.Keybase().Sign(key.GetName(), []byte(value))
+			if err != nil {
+				return err
+			}
+
+			// Build the signature data output
+			signatureData := SignatureData{
+				Address:   strings.ToLower(pubKey.Address().String()),
+				Signature: strings.ToLower(hex.EncodeToString(bz)),
+				PubKey:    strings.ToLower(hex.EncodeToString(pubKey.Bytes())),
+				Value:     value,
+			}
+
+			// Serialize the output as JSON and print it
+			bz, err = json.Marshal(&signatureData)
+			if err != nil {
+				return err
+			}
+
+			return clientCtx.PrintBytes(bz)
+		},
+	}
+
+	flags.AddTxFlagsToCmd(cmd)
+
+	return cmd
+}
diff --git a/docs/developers/queries/staging/comments.md b/docs/developers/queries/staging/comments.md
new file mode 100644
index 00000000..2c9d7cb0
--- /dev/null
+++ b/docs/developers/queries/staging/comments.md
@@ -0,0 +1,10 @@
+# Query the comments of a post
+This query allows you to get all the comments related to the given post ID. 
+
+**CLI**
+```bash
+desmos query posts post-comments [post-id]
+
+# Example
+# desmos query posts post-comments 301921ac3c8e623d8f35aef1886fea20849e49f08ec8ddfdd9b96feaf0c4fd15
+```
\ No newline at end of file
diff --git a/docs/developers/query-data.md b/docs/developers/query-data.md
index c5e1bcd1..349147b8 100644
--- a/docs/developers/query-data.md
+++ b/docs/developers/query-data.md
@@ -18,6 +18,7 @@ read the data stored inside it. Here you will find a list of all the query endpo
 - [Query the post's user answers](queries/staging/user-answers.md)
 - [Query registered reactions](queries/staging/reactions.md)
 - [Query the post's reports](queries/staging/reports.md)
+- [Query the post's comments](queries/staging/comments.md)
 
 ## Subspaces
 
diff --git a/proto/desmos/posts/v1beta1/posts.proto b/proto/desmos/posts/v1beta1/posts.proto
index 76de1061..9a96b74a 100644
--- a/proto/desmos/posts/v1beta1/posts.proto
+++ b/proto/desmos/posts/v1beta1/posts.proto
@@ -63,9 +63,6 @@ message Post {
       [ (gogoproto.moretags) = "yaml:\"poll_data,omitempty\"" ];
 }
 
-// CommentIDs wraps the list of comments of a post
-message CommentIDs { repeated string ids = 1; }
-
 // ___________________________________________________________________________________________________________________
 
 // CommentsState contains all the possible comments states
diff --git a/proto/desmos/posts/v1beta1/query.proto b/proto/desmos/posts/v1beta1/query.proto
index 8e8a2d54..5f15cfda 100644
--- a/proto/desmos/posts/v1beta1/query.proto
+++ b/proto/desmos/posts/v1beta1/query.proto
@@ -47,14 +47,20 @@ service Query {
   rpc Params(QueryParamsRequest) returns (QueryParamsResponse) {
     option (google.api.http).get = "/desmos/posts/v1beta1/posts/params";
   }
+
+  // PostComments queries the comments of the post having the given id
+  rpc PostComments(QueryPostCommentsRequest) returns (QueryPostCommentsResponse) {
+    option (google.api.http).get =
+      "/desmos/posts/v1beta1/posts/{post_id}/comments";
+  }
 }
 
 // ___________________________________________________________________________________________________________________
 
 // QueryPostsRequest is the request type for the Query/Posts RPC method.
 message QueryPostsRequest {
-  // SubspaceId represents the ID of the subspace to which to query the posts for.
-  // Providing an empty or invalid subspace id will return an error.
+  // SubspaceId represents the ID of the subspace to which to query the posts
+  // for. Providing an empty or invalid subspace id will return an error.
   string subspace_id = 1;
 
   // pagination defines an optional pagination for the request.
@@ -158,3 +164,22 @@ message QueryReportsResponse {
   repeated desmos.posts.v1beta1.Report reports = 1
       [ (gogoproto.nullable) = false ];
 }
+
+// ___________________________________________________________________________________________________________________
+
+// QueryPostCommentsRequest is the request type for the Query/PostComments RPC
+// method.
+message QueryPostCommentsRequest {
+  string post_id = 1;
+
+  // pagination defines an optional pagination for the request.
+  cosmos.base.query.v1beta1.PageRequest pagination = 2;
+}
+
+// QueryPostCommentsResponse is the response type for the Query/PostComments RPC
+// method.
+message QueryPostCommentsResponse {
+  repeated desmos.posts.v1beta1.Post comments = 1 [ (gogoproto.nullable) = false ];
+
+  cosmos.base.query.v1beta1.PageResponse pagination = 2;
+}
\ No newline at end of file
diff --git a/proto/desmos/profiles/v1beta1/query.proto b/proto/desmos/profiles/v1beta1/query.proto
index 9c45c812..c3e37eed 100644
--- a/proto/desmos/profiles/v1beta1/query.proto
+++ b/proto/desmos/profiles/v1beta1/query.proto
@@ -57,9 +57,33 @@ service Query {
         "/desmos/profiles/v1beta1/chain-links/{user}";
   }
 
+  // UserChainLink queries the chain link for the given user, chain name and
+  // target address
+  rpc UserChainLink(QueryUserChainLinkRequest)
+      returns (QueryUserChainLinkResponse) {
+    option (google.api.http).get =
+        "/desmos/profiles/v1beta1/chain-links/{user}/{chain_name}/{target}";
+  }
+
   // UserApplicationLinks queries application links for the given user
   rpc UserApplicationLinks(QueryUserApplicationLinksRequest)
       returns (QueryUserApplicationLinksResponse) {
     option (google.api.http).get = "/desmos/profiles/v1beta1/app-links/{user}";
   }
+
+  // UserApplicationLinks queries a single application link for a given user,
+  // searching via the application name and username
+  rpc UserApplicationLink(QueryUserApplicationLinkRequest)
+      returns (QueryUserApplicationLinkResponse) {
+    option (google.api.http).get =
+        "/desmos/profiles/v1beta1/app-links/{user}/{application}/{username}";
+  }
+
+  // ApplicationLinkByClientID queries a single application link for a given
+  // client id.
+  rpc ApplicationLinkByClientID(QueryApplicationLinkByClientIDRequest)
+      returns (QueryApplicationLinkByClientIDResponse) {
+    option (google.api.http).get =
+        "/desmos/profiles/v1beta1/app-links/clients/{client_id}";
+  }
 }
diff --git a/proto/desmos/profiles/v1beta1/query_app_links.proto b/proto/desmos/profiles/v1beta1/query_app_links.proto
index 6c06f44a..b7c961e4 100644
--- a/proto/desmos/profiles/v1beta1/query_app_links.proto
+++ b/proto/desmos/profiles/v1beta1/query_app_links.proto
@@ -10,8 +10,28 @@ import "cosmos/base/query/v1beta1/pagination.proto";
 
 option go_package = "github.com/desmos-labs/desmos/x/profiles/types";
 
-// QueryUserApplicationLinksRequest represent the request used when querying the
-// application links of a specific user
+// QueryUserApplicationLinkRequest represents the request used when querying an
+// application link using an application name and username for a given user
+message QueryUserApplicationLinkRequest {
+  // User contains the Desmos profile address associated for which the link
+  // should be searched for
+  string user = 1;
+  // Application represents the application name associated with the link
+  string application = 2;
+  // Username represents the username inside the application associated with the
+  // link
+  string username = 3;
+}
+
+// QueryUserApplicationLinkResponse represents the response to the query
+// allowing to get an application link for a specific user, searching via the
+// application name and username
+message QueryUserApplicationLinkResponse {
+  ApplicationLink link = 1 [ (gogoproto.nullable) = false ];
+}
+
+// QueryUserApplicationLinksRequest represents the request used when querying
+// the application links of a specific user
 message QueryUserApplicationLinksRequest {
   string user = 1;
 
@@ -27,3 +47,16 @@ message QueryUserApplicationLinksResponse {
   // Pagination defines the pagination response
   cosmos.base.query.v1beta1.PageResponse pagination = 2;
 }
+
+// QueryApplicationLinkByClientIDRequest contains the data of the request that
+// can be used to get an application link based on a client id
+message QueryApplicationLinkByClientIDRequest {
+  // ClientID represents the ID of the client to which search the link for
+  string client_id = 1;
+}
+
+// QueryApplicationLinkByClientIDResponse contains the data returned by the
+// request allowing to get an application link using a client id
+message QueryApplicationLinkByClientIDResponse {
+  ApplicationLink link = 1 [ (gogoproto.nullable) = false ];
+}
\ No newline at end of file
diff --git a/proto/desmos/profiles/v1beta1/query_chain_links.proto b/proto/desmos/profiles/v1beta1/query_chain_links.proto
index b0b4a802..9d68fb75 100644
--- a/proto/desmos/profiles/v1beta1/query_chain_links.proto
+++ b/proto/desmos/profiles/v1beta1/query_chain_links.proto
@@ -10,6 +10,24 @@ import "cosmos/base/query/v1beta1/pagination.proto";
 
 option go_package = "github.com/desmos-labs/desmos/x/profiles/types";
 
+// QueryUserChainLinkRequest represents the request that should be used in order
+// to retrieve the link associated with the provided user, for the given chain
+// and having the given target address
+message QueryUserChainLinkRequest {
+  // User represents the Desmos address of the user to which search the link for
+  string user = 1;
+  // ChainName contains the name of the chain to which search the link for
+  string chain_name = 2;
+  // Target must contain the external address to which query the link for
+  string target = 3;
+}
+
+// QueryUserChainLinkResponse contains the data that is returned when querying a
+// specific chain link
+message QueryUserChainLinkResponse {
+  ChainLink link = 1 [ (gogoproto.nullable) = false ];
+}
+
 // QueryUserChainLinksRequest is the request type for the
 // Query/UserChainLinks RPC endpoint
 message QueryUserChainLinksRequest {
diff --git a/x/profiles/handler.go b/x/profiles/handler.go
index ac4ddcbe..14cf4eef 100644
--- a/x/profiles/handler.go
+++ b/x/profiles/handler.go
@@ -64,6 +64,14 @@ func NewHandler(k keeper.Keeper) sdk.Handler {
 			res, err := msgServer.UnlinkChainAccount(sdk.WrapSDKContext(ctx), msg)
 			return sdk.WrapServiceResult(ctx, res, err)
 
+		case *types.MsgLinkApplication:
+			res, err := msgServer.LinkApplication(sdk.WrapSDKContext(ctx), msg)
+			return sdk.WrapServiceResult(ctx, res, err)
+
+		case *types.MsgUnlinkApplication:
+			res, err := msgServer.UnlinkApplication(sdk.WrapSDKContext(ctx), msg)
+			return sdk.WrapServiceResult(ctx, res, err)
+
 		default:
 			return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest,
 				"unrecognized %s message type: %v", types.ModuleName, msg.Type())
diff --git a/x/profiles/keeper/grpc_query.go b/x/profiles/keeper/grpc_query.go
index b7c634a7..7d4ae97f 100644
--- a/x/profiles/keeper/grpc_query.go
+++ b/x/profiles/keeper/grpc_query.go
@@ -140,6 +140,18 @@ func (k Keeper) UserChainLinks(ctx context.Context, request *types.QueryUserChai
 	return &types.QueryUserChainLinksResponse{Links: links, Pagination: pageRes}, nil
 }
 
+// UserChainLink implements the Query/UserChainLink gRPC method
+func (k Keeper) UserChainLink(ctx context.Context, request *types.QueryUserChainLinkRequest) (*types.QueryUserChainLinkResponse, error) {
+	sdkCtx := sdk.UnwrapSDKContext(ctx)
+
+	link, found := k.GetChainLink(sdkCtx, request.User, request.ChainName, request.Target)
+	if !found {
+		return nil, status.Error(codes.NotFound, "link not found")
+	}
+
+	return &types.QueryUserChainLinkResponse{Link: link}, nil
+}
+
 // UserApplicationLinks implements the Query/UserApplicationLinks gRPC method
 func (k Keeper) UserApplicationLinks(ctx context.Context, request *types.QueryUserApplicationLinksRequest) (*types.QueryUserApplicationLinksResponse, error) {
 	sdkCtx := sdk.UnwrapSDKContext(ctx)
@@ -168,3 +180,35 @@ func (k Keeper) UserApplicationLinks(ctx context.Context, request *types.QueryUs
 
 	return &types.QueryUserApplicationLinksResponse{Links: links, Pagination: pageRes}, nil
 }
+
+// UserApplicationLink implements the Query/UserApplicationLink gRPC method
+func (k Keeper) UserApplicationLink(ctx context.Context, request *types.QueryUserApplicationLinkRequest) (*types.QueryUserApplicationLinkResponse, error) {
+	sdkCtx := sdk.UnwrapSDKContext(ctx)
+
+	link, found, err := k.GetApplicationLink(sdkCtx, request.User, request.Application, request.Username)
+	if err != nil {
+		return nil, status.Error(codes.Internal, err.Error())
+	}
+
+	if !found {
+		return nil, status.Errorf(codes.NotFound, "link not found")
+	}
+
+	return &types.QueryUserApplicationLinkResponse{Link: link}, nil
+}
+
+// ApplicationLinkByClientID implements the Query/ApplicationLinkByClientID gRPC method
+func (k Keeper) ApplicationLinkByClientID(ctx context.Context, request *types.QueryApplicationLinkByClientIDRequest) (*types.QueryApplicationLinkByClientIDResponse, error) {
+	sdkCtx := sdk.UnwrapSDKContext(ctx)
+
+	link, err := k.GetApplicationLinkByClientID(sdkCtx, request.ClientId)
+	if err != nil {
+		if sdkerrors.ErrNotFound.Is(err) {
+			return nil, status.Errorf(codes.NotFound, "link for client id %s not found", request.ClientId)
+		}
+
+		return nil, status.Error(codes.Internal, err.Error())
+	}
+
+	return &types.QueryApplicationLinkByClientIDResponse{Link: link}, nil
+}
diff --git a/x/profiles/keeper/msg_server_app_link.go b/x/profiles/keeper/msg_server_app_link.go
index bed51338..348fd112 100644
--- a/x/profiles/keeper/msg_server_app_link.go
+++ b/x/profiles/keeper/msg_server_app_link.go
@@ -2,9 +2,12 @@ package keeper
 
 import (
 	"context"
+	"time"
 
 	sdk "github.com/cosmos/cosmos-sdk/types"
 
+	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
+
 	"github.com/desmos-labs/desmos/x/profiles/types"
 )
 
@@ -19,6 +22,18 @@ func (k Keeper) LinkApplication(
 		return nil, err
 	}
 
+	// See if the link already exists
+	link, found, err := k.GetApplicationLink(ctx, msg.Sender, msg.LinkData.Application, msg.LinkData.Username)
+	if err != nil {
+		return nil, err
+	}
+
+	if found && link.IsVerificationOngoing() {
+		return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest,
+			"verification process of link for application %s and username %s is already happening",
+			msg.LinkData.Application, msg.LinkData.Username)
+	}
+
 	if err := k.StartProfileConnection(
 		ctx, msg.LinkData, msg.CallData, user,
 		msg.SourcePort, msg.SourceChannel, msg.TimeoutHeight, msg.TimeoutTimestamp,
@@ -37,6 +52,7 @@ func (k Keeper) LinkApplication(
 			sdk.NewAttribute(types.AttributeKeyUser, msg.Sender),
 			sdk.NewAttribute(types.AttributeKeyApplicationName, msg.LinkData.Application),
 			sdk.NewAttribute(types.AttributeKeyApplicationUsername, msg.LinkData.Username),
+			sdk.NewAttribute(types.AttributeKeyApplicationLinkCreationTime, ctx.BlockTime().Format(time.RFC3339)),
 		),
 		sdk.NewEvent(
 			sdk.EventTypeMessage,
diff --git a/x/profiles/module_ibc.go b/x/profiles/module_ibc.go
index 4b345402..cfdbb310 100644
--- a/x/profiles/module_ibc.go
+++ b/x/profiles/module_ibc.go
@@ -186,35 +186,37 @@ func (am AppModule) OnRecvPacket(
 	}, ackBytes, nil
 }
 
-// PacketHandler represents a method that tries handling a packet, and returns types.ErrInvalidPacketData
-// if it cannot handle the contained data
-type PacketHandler = func(am AppModule, ctx sdk.Context, packet channeltypes.Packet) (channeltypes.Acknowledgement, error)
+// PacketHandler represents a method that tries handling a packet.
+// If the packet has been handled properly, handled will be true and the acknowledgment and error will
+// tell how the execution has ended.
+// If the packet cannot be handled properly, false will be returned instead as first value.
+type PacketHandler = func(
+	am AppModule, ctx sdk.Context, packet channeltypes.Packet,
+) (handled bool, ack channeltypes.Acknowledgement, err error)
 
-// HandlePacket handles the given packet by passing it to the provided packet handlers one by one until either one can
-// handle it, or they all return with types.ErrInvalidPacketData.
+// HandlePacket handles the given packet by passing it to the provided packet handlers one by one until
+// at least one of them can handle it.
+// If no handler supports the given packet, it returns types.ErrInvalidPacketData.
 func (am AppModule) HandlePacket(
 	ctx sdk.Context, packet channeltypes.Packet, packetHandlers ...PacketHandler,
 ) (channeltypes.Acknowledgement, error) {
-	var ack channeltypes.Acknowledgement
-	var err error
 	for _, handler := range packetHandlers {
-		ack, err = handler(am, ctx, packet)
-		if types.ErrInvalidPacketData.Is(err) {
-			continue
+		handled, ack, err := handler(am, ctx, packet)
+		if handled {
+			return ack, err
 		}
 	}
-	return ack, err
+	return channeltypes.Acknowledgement{}, sdkerrors.Wrapf(types.ErrInvalidPacketData, "%T", packet)
 }
 
 // handleLinkChainAccountPacketData tries handling athe given packet by deserializing the inner data
 // as a LinkChainAccountPacketData instance.
-// Returns ErrInvalidPacketData if the given packet data is not of such type.
 func handleLinkChainAccountPacketData(
 	am AppModule, ctx sdk.Context, packet channeltypes.Packet,
-) (channeltypes.Acknowledgement, error) {
+) (handled bool, ack channeltypes.Acknowledgement, err error) {
 	var packetData types.LinkChainAccountPacketData
 	if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &packetData); err != nil {
-		return channeltypes.Acknowledgement{}, sdkerrors.Wrapf(types.ErrInvalidPacketData, "%T", packet)
+		return false, channeltypes.Acknowledgement{}, nil
 	}
 
 	var acknowledgement channeltypes.Acknowledgement
@@ -226,14 +228,14 @@ func handleLinkChainAccountPacketData(
 		// Encode packet acknowledgment
 		packetAckBytes, err := packetAck.Marshal()
 		if err != nil {
-			return channeltypes.Acknowledgement{}, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
+			return true, channeltypes.Acknowledgement{}, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
 		}
 		acknowledgement = channeltypes.NewResultAcknowledgement(packetAckBytes)
 	}
 
 	address, err := types.UnpackAddressData(am.cdc, packetData.SourceAddress)
 	if err != nil {
-		return channeltypes.Acknowledgement{}, err
+		return true, channeltypes.Acknowledgement{}, err
 	}
 
 	ctx.EventManager().EmitEvent(
@@ -247,23 +249,22 @@ func handleLinkChainAccountPacketData(
 		),
 	)
 
-	return acknowledgement, nil
+	return true, acknowledgement, nil
 }
 
 // handleOracleRequestPacketData tries handling athe given packet by deserializing the inner data
 // as an OracleResponsePacketData instance.
-// Returns ErrInvalidPacketData if the given packet data is not of such type.
 func handleOracleRequestPacketData(
 	am AppModule, ctx sdk.Context, packet channeltypes.Packet,
-) (channeltypes.Acknowledgement, error) {
+) (handled bool, ack channeltypes.Acknowledgement, err error) {
 	var data oracletypes.OracleResponsePacketData
 	if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil {
-		return channeltypes.Acknowledgement{}, sdkerrors.Wrapf(types.ErrInvalidPacketData, "%T", packet)
+		return false, channeltypes.Acknowledgement{}, nil
 	}
 
 	acknowledgement := channeltypes.NewResultAcknowledgement([]byte{byte(1)})
 
-	err := am.keeper.OnRecvApplicationLinkPacketData(ctx, data)
+	err = am.keeper.OnRecvApplicationLinkPacketData(ctx, data)
 	if err != nil {
 		acknowledgement = channeltypes.NewErrorAcknowledgement(err.Error())
 	}
@@ -280,7 +281,7 @@ func handleOracleRequestPacketData(
 	)
 
 	// NOTE: acknowledgement will be written synchronously during IBC handler execution.
-	return acknowledgement, nil
+	return true, acknowledgement, nil
 }
 
 // -------------------------------------------------------------------------------------------------------------------
diff --git a/x/profiles/types/events.go b/x/profiles/types/events.go
index d5fb1af4..92eefd82 100644
--- a/x/profiles/types/events.go
+++ b/x/profiles/types/events.go
@@ -49,14 +49,15 @@ const (
 	EventTypesApplicationLinkSaved   = "application_link_saved"
 	EventTypeApplicationLinkDeleted  = "application_link_deleted"
 
-	AttributeKeyUser                = "user"
-	AttributeKeyApplicationName     = "application_name"
-	AttributeKeyApplicationUsername = "application_username"
-	AttributeKeyOracleID            = "oracle_id"
-	AttributeKeyClientID            = "client_id"
-	AttributeKeyRequestID           = "request_id"
-	AttributeKeyRequestKey          = "request_key"
-	AttributeKeyResolveStatus       = "resolve_status"
-	AttributeKeyAck                 = "acknowledgement"
-	AttributeKeyAckError            = "error"
+	AttributeKeyUser                        = "user"
+	AttributeKeyApplicationName             = "application_name"
+	AttributeKeyApplicationUsername         = "application_username"
+	AttributeKeyApplicationLinkCreationTime = "application_link_creation_time"
+	AttributeKeyOracleID                    = "oracle_id"
+	AttributeKeyClientID                    = "client_id"
+	AttributeKeyRequestID                   = "request_id"
+	AttributeKeyRequestKey                  = "request_key"
+	AttributeKeyResolveStatus               = "resolve_status"
+	AttributeKeyAck                         = "acknowledgement"
+	AttributeKeyAckError                    = "error"
 )
diff --git a/x/profiles/types/keys.go b/x/profiles/types/keys.go
index ef55428f..4038596d 100644
--- a/x/profiles/types/keys.go
+++ b/x/profiles/types/keys.go
@@ -36,8 +36,10 @@ const (
 
 	DoNotModify = "[do-not-modify]"
 
-	// IBC keys
-	IBCVersion = "ibc-profiles-1"
+	// IBCVersion represents the version of the IBC specification to be used.
+	// Currently we use ICS20-1 because we need to communicate with Band which uses the same version.
+	// TODO: Explore if it's possible to support multiple specifications at once, and see if we should create our own one.
+	IBCVersion = "ics20-1"
 	IBCPortID  = "ibc-profiles"
 )
 
@@ -48,7 +50,6 @@ var (
 	UsersBlocksStorePrefix        = []byte("users_blocks")
 	ChainLinksPrefix              = []byte("chain_links")
 	UserApplicationLinkPrefix     = []byte("user_application_link")
-	ApplicationLinkPrefix         = []byte("application_link")
 	ApplicationLinkClientIDPrefix = []byte("client_id")
 
 	// IBCPortKey defines the key to store the port ID in store
diff --git a/x/profiles/types/models_app_links.go b/x/profiles/types/models_app_links.go
index 46b46e17..bfb3f665 100644
--- a/x/profiles/types/models_app_links.go
+++ b/x/profiles/types/models_app_links.go
@@ -55,6 +55,11 @@ func (l ApplicationLink) Validate() error {
 	return nil
 }
 
+// IsVerificationOngoing tells whether the verification for the link is still ongoing
+func (l *ApplicationLink) IsVerificationOngoing() bool {
+	return l.State == ApplicationLinkStateInitialized || l.State == AppLinkStateVerificationStarted
+}
+
 // MustMarshalApplicationLink serializes the given application link using the provided BinaryMarshaler
 func MustMarshalApplicationLink(cdc codec.BinaryMarshaler, link ApplicationLink) []byte {
 	return cdc.MustMarshalBinaryBare(&link)
diff --git a/x/profiles/types/query.pb.go b/x/profiles/types/query.pb.go
index 3c3e01dd..eaf6fa57 100644
--- a/x/profiles/types/query.pb.go
+++ b/x/profiles/types/query.pb.go
@@ -35,43 +35,51 @@ func init() {
 }
 
 var fileDescriptor_5e0074f57a59f38d = []byte{
-	// 572 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x94, 0x3f, 0x6f, 0xd3, 0x40,
-	0x18, 0x87, 0x73, 0x48, 0x14, 0x71, 0x12, 0x48, 0x9c, 0x2a, 0xa1, 0x46, 0xc8, 0x08, 0x18, 0x4a,
-	0x9b, 0xda, 0xd7, 0x24, 0x15, 0x88, 0x6e, 0x14, 0x06, 0x06, 0x86, 0x52, 0x95, 0x85, 0x25, 0x3a,
-	0xbb, 0x17, 0xc7, 0xaa, 0xe3, 0xbb, 0xde, 0x9d, 0x11, 0x15, 0x62, 0x61, 0x66, 0x40, 0x62, 0xe1,
-	0x03, 0xb0, 0xb0, 0xf1, 0x05, 0x90, 0x3a, 0x32, 0x56, 0x62, 0x61, 0x44, 0x09, 0x1f, 0x04, 0xf9,
-	0xfe, 0xa4, 0x98, 0xca, 0x76, 0xd2, 0x2d, 0x76, 0x9e, 0xdf, 0xfb, 0x3e, 0xf7, 0xfa, 0xee, 0xe0,
-	0xbd, 0x03, 0x2a, 0xc7, 0x4c, 0x62, 0x2e, 0xd8, 0x30, 0x49, 0xa9, 0xc4, 0xaf, 0xbb, 0x21, 0x55,
-	0xa4, 0x8b, 0x8f, 0x72, 0x2a, 0x8e, 0x03, 0x2e, 0x98, 0x62, 0xe8, 0xa6, 0x81, 0x02, 0x07, 0x05,
-	0x16, 0x6a, 0x2f, 0xc7, 0x2c, 0x66, 0x9a, 0xc1, 0xc5, 0x2f, 0x83, 0xb7, 0x6f, 0xc5, 0x8c, 0xc5,
-	0x29, 0xc5, 0x84, 0x27, 0x98, 0x64, 0x19, 0x53, 0x44, 0x25, 0x2c, 0x93, 0xf6, 0xdf, 0x15, 0xfb,
-	0xaf, 0x7e, 0x0a, 0xf3, 0x21, 0x26, 0x99, 0xed, 0xd3, 0xee, 0xd4, 0xca, 0x0c, 0xec, 0x6b, 0x0b,
-	0x77, 0xeb, 0x61, 0x41, 0x53, 0xd3, 0x76, 0x94, 0x70, 0x39, 0x5f, 0xe4, 0x40, 0x91, 0x78, 0x20,
-	0xe8, 0x51, 0x4e, 0xa5, 0x72, 0x91, 0xf5, 0x06, 0x25, 0x22, 0xc8, 0xd8, 0xb1, 0xb8, 0x9e, 0x8d,
-	0x46, 0x24, 0xc9, 0x06, 0x69, 0x92, 0x1d, 0xba, 0x80, 0x5f, 0x1f, 0x20, 0x9c, 0x97, 0xf0, 0x95,
-	0x88, 0x15, 0xf8, 0xc0, 0x0c, 0xdc, 0x3c, 0x38, 0x4d, 0xf3, 0x84, 0x43, 0x22, 0xa9, 0x49, 0xcf,
-	0x6a, 0x71, 0x12, 0x27, 0x99, 0x1e, 0x85, 0x61, 0x7b, 0xdf, 0xaf, 0xc2, 0xcb, 0x2f, 0x0a, 0x04,
-	0x7d, 0x06, 0xf0, 0xca, 0xae, 0xe9, 0x8d, 0x36, 0x82, 0x8a, 0x8f, 0x1c, 0x68, 0xd6, 0x62, 0x7b,
-	0x66, 0x3a, 0x6d, 0x7f, 0x4e, 0x5a, 0x72, 0x96, 0x49, 0x7a, 0x77, 0xf3, 0xfd, 0xcf, 0x3f, 0x9f,
-	0x2e, 0xad, 0xa3, 0xfb, 0x95, 0x23, 0x9a, 0xbd, 0x78, 0x9b, 0x4b, 0x2a, 0xde, 0xa1, 0xaf, 0x00,
-	0x5e, 0x7b, 0xba, 0x4f, 0xe2, 0x7d, 0x41, 0x32, 0x39, 0xa4, 0x42, 0xa2, 0x5e, 0x7d, 0xcb, 0x12,
-	0xec, 0x34, 0xfb, 0x0b, 0x65, 0xac, 0x2c, 0xd6, 0xb2, 0x6b, 0x68, 0xb5, 0x52, 0x56, 0x6f, 0x14,
-	0x35, 0x33, 0xfb, 0x00, 0xe0, 0xd2, 0xae, 0xde, 0x08, 0xa8, 0xd3, 0x30, 0x17, 0x4d, 0x39, 0xbb,
-	0x8d, 0xf9, 0x60, 0xab, 0xb5, 0xaa, 0xb5, 0xee, 0xa0, 0xdb, 0xd5, 0x33, 0x34, 0x0e, 0x27, 0x00,
-	0xde, 0x78, 0x29, 0xa9, 0xd8, 0xfb, 0xf7, 0x04, 0xa0, 0x07, 0xf5, 0xcd, 0xce, 0x05, 0x9c, 0xe4,
-	0xc3, 0x85, 0x73, 0xd6, 0x77, 0x5b, 0xfb, 0x6e, 0xa1, 0x9e, 0xf3, 0x2d, 0x9d, 0xc8, 0x99, 0x74,
-	0xf9, 0xad, 0xfd, 0xfa, 0x5f, 0x00, 0x84, 0x45, 0xe5, 0x9d, 0x94, 0x45, 0x87, 0x12, 0xe1, 0x66,
-	0x07, 0x43, 0x3a, 0xe9, 0xcd, 0xf9, 0x03, 0xd6, 0xb6, 0xaf, 0x6d, 0x7d, 0xd4, 0xa9, 0xb7, 0x0d,
-	0x75, 0xca, 0x69, 0x7e, 0x03, 0xf0, 0x7a, 0x51, 0xeb, 0x49, 0x71, 0xb2, 0x9f, 0x17, 0x27, 0x15,
-	0xf5, 0x9b, 0x3b, 0x9f, 0xd1, 0x4e, 0x77, 0x6b, 0xb1, 0x50, 0x95, 0xf2, 0xb9, 0x0d, 0xa1, 0x6f,
-	0x1c, 0x5f, 0x5f, 0x21, 0x4e, 0xf9, 0x04, 0xc0, 0xe5, 0xa2, 0xde, 0x63, 0xce, 0xd3, 0x24, 0xd2,
-	0x2b, 0x34, 0xe2, 0x8f, 0x9a, 0x1d, 0xfe, 0xcf, 0x38, 0xfd, 0xed, 0x8b, 0x44, 0xed, 0x22, 0xba,
-	0x7a, 0x11, 0x1d, 0xb4, 0x56, 0xb9, 0x08, 0xc2, 0x79, 0x69, 0x09, 0x3b, 0xcf, 0x7e, 0x4c, 0x3c,
-	0x70, 0x3a, 0xf1, 0xc0, 0xef, 0x89, 0x07, 0x3e, 0x4e, 0xbd, 0xd6, 0xe9, 0xd4, 0x6b, 0xfd, 0x9a,
-	0x7a, 0xad, 0x57, 0x41, 0x9c, 0xa8, 0x51, 0x1e, 0x06, 0x11, 0x1b, 0xdb, 0x72, 0x7e, 0x4a, 0x42,
-	0xe9, 0x4a, 0xbf, 0x39, 0x2b, 0xae, 0x8e, 0x39, 0x95, 0xe1, 0x92, 0xbe, 0x10, 0xfb, 0x7f, 0x03,
-	0x00, 0x00, 0xff, 0xff, 0x9b, 0x3f, 0xc6, 0xb3, 0x05, 0x07, 0x00, 0x00,
+	// 697 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x95, 0x3f, 0x4f, 0x14, 0x4f,
+	0x18, 0xc7, 0x99, 0x5f, 0xf2, 0xc3, 0x64, 0x12, 0x48, 0x1c, 0x49, 0x0c, 0x17, 0x73, 0x46, 0x2d,
+	0x10, 0x8e, 0xdb, 0xe1, 0x38, 0x82, 0x40, 0x81, 0xe1, 0xa0, 0x10, 0x63, 0x81, 0x04, 0x1b, 0x9b,
+	0xcb, 0xec, 0x32, 0x2c, 0x1b, 0xf6, 0x76, 0x86, 0x9d, 0x39, 0x23, 0xb9, 0x5c, 0x63, 0x6d, 0x61,
+	0x62, 0xe3, 0x0b, 0xb0, 0xb1, 0xb3, 0xb7, 0x31, 0xb1, 0xb1, 0x24, 0x31, 0x31, 0x16, 0x16, 0x06,
+	0x7c, 0x21, 0x66, 0xe7, 0xcf, 0xe1, 0x82, 0xfb, 0xe7, 0xae, 0xbb, 0x99, 0xfd, 0x7e, 0x9f, 0xe7,
+	0xf3, 0x3c, 0x33, 0xcf, 0x1c, 0xbc, 0xb7, 0x4f, 0x45, 0x87, 0x09, 0xcc, 0x63, 0x76, 0x10, 0x84,
+	0x54, 0xe0, 0x17, 0x0d, 0x97, 0x4a, 0xd2, 0xc0, 0xc7, 0x5d, 0x1a, 0x9f, 0x38, 0x3c, 0x66, 0x92,
+	0xa1, 0x9b, 0x5a, 0xe4, 0x58, 0x91, 0x63, 0x44, 0x95, 0x29, 0x9f, 0xf9, 0x4c, 0x69, 0x70, 0xf2,
+	0x4b, 0xcb, 0x2b, 0xb7, 0x7c, 0xc6, 0xfc, 0x90, 0x62, 0xc2, 0x03, 0x4c, 0xa2, 0x88, 0x49, 0x22,
+	0x03, 0x16, 0x09, 0xf3, 0x75, 0xda, 0x7c, 0x55, 0x2b, 0xb7, 0x7b, 0x80, 0x49, 0x64, 0xf2, 0x54,
+	0x6a, 0xb9, 0x30, 0x6d, 0xb3, 0x6d, 0xc4, 0x8d, 0x7c, 0x71, 0x4c, 0x43, 0x9d, 0xf6, 0x30, 0xe0,
+	0xa2, 0x9c, 0x65, 0x5f, 0x12, 0xbf, 0x1d, 0xd3, 0xe3, 0x2e, 0x15, 0xd2, 0x5a, 0xe6, 0x0a, 0x90,
+	0x48, 0x4c, 0x3a, 0x56, 0x8b, 0xf3, 0xb5, 0xde, 0x21, 0x09, 0xa2, 0x76, 0x18, 0x44, 0x47, 0xd6,
+	0x50, 0xcf, 0x37, 0x10, 0xce, 0x53, 0xf2, 0x69, 0x8f, 0x25, 0xf2, 0xb6, 0x6e, 0xb8, 0x5e, 0x58,
+	0x4c, 0xbd, 0xc2, 0x2e, 0x11, 0x54, 0xbb, 0x07, 0xb1, 0x38, 0xf1, 0x83, 0x48, 0xb5, 0x42, 0x6b,
+	0x17, 0x3f, 0x4d, 0xc2, 0xff, 0x9f, 0x26, 0x12, 0xf4, 0x0e, 0xc0, 0x6b, 0x3b, 0x3a, 0x37, 0x9a,
+	0x77, 0x32, 0x0e, 0xd9, 0x51, 0x5a, 0x23, 0xdb, 0xd5, 0xdd, 0xa9, 0xd4, 0x4b, 0xaa, 0x05, 0x67,
+	0x91, 0xa0, 0x77, 0x17, 0x5e, 0x7d, 0xfb, 0xfd, 0xf6, 0xbf, 0x39, 0x74, 0x3f, 0xb3, 0x45, 0x83,
+	0x8d, 0x5e, 0x57, 0xd0, 0xb8, 0x8f, 0x3e, 0x00, 0x38, 0xb1, 0xb5, 0x47, 0xfc, 0xbd, 0x98, 0x44,
+	0xe2, 0x80, 0xc6, 0x02, 0x2d, 0xe6, 0xa7, 0x4c, 0x89, 0x2d, 0x66, 0x73, 0x28, 0x8f, 0x81, 0xc5,
+	0x0a, 0x76, 0x16, 0xcd, 0x64, 0xc2, 0xaa, 0x8b, 0x22, 0x07, 0x64, 0xaf, 0x01, 0x1c, 0xdf, 0x51,
+	0x17, 0x01, 0xd5, 0x0a, 0xfa, 0xa2, 0x54, 0x96, 0x6e, 0xbe, 0x9c, 0xd8, 0x60, 0xcd, 0x28, 0xac,
+	0x3b, 0xe8, 0x76, 0x76, 0x0f, 0x35, 0xc3, 0x67, 0x00, 0xaf, 0x3f, 0x13, 0x34, 0xde, 0xfd, 0x7b,
+	0x02, 0xd0, 0x72, 0x7e, 0xb2, 0x2b, 0x06, 0x0b, 0xf9, 0x60, 0x68, 0x9f, 0xe1, 0x5d, 0x53, 0xbc,
+	0x4b, 0x68, 0xd1, 0xf2, 0xa6, 0x26, 0x72, 0x00, 0x9d, 0xde, 0x35, 0xa7, 0xff, 0x1e, 0x40, 0x98,
+	0x44, 0x6e, 0x85, 0xcc, 0x3b, 0x12, 0x08, 0x17, 0x33, 0x68, 0xa5, 0x85, 0x5e, 0x28, 0x6f, 0x30,
+	0xb4, 0x4d, 0x45, 0x5b, 0x47, 0xb5, 0x7c, 0x5a, 0x57, 0xb9, 0x2c, 0xe6, 0x47, 0x00, 0x27, 0x93,
+	0x58, 0x9b, 0xc9, 0x64, 0x3f, 0x49, 0x26, 0x15, 0x35, 0x8b, 0x33, 0x5f, 0xa8, 0x2d, 0xee, 0xd2,
+	0x70, 0xa6, 0x2c, 0xe4, 0x2b, 0x17, 0x42, 0xbd, 0x38, 0x75, 0xf5, 0x84, 0x58, 0xe4, 0x2f, 0x00,
+	0x4e, 0xa4, 0xe2, 0x15, 0xcd, 0x55, 0x4a, 0x5c, 0x72, 0xae, 0x2e, 0x79, 0x0c, 0xef, 0xb6, 0xe2,
+	0xdd, 0x44, 0x1b, 0x43, 0xf0, 0xe2, 0x9e, 0x7e, 0x35, 0x23, 0xd2, 0xa1, 0x7d, 0xdc, 0x93, 0x24,
+	0xf6, 0xa9, 0xec, 0x27, 0x57, 0x7c, 0x2a, 0x49, 0xb2, 0xc1, 0x79, 0x18, 0x78, 0xea, 0x9c, 0x74,
+	0xfb, 0x57, 0x8b, 0xc1, 0x2e, 0x7b, 0x6c, 0x4d, 0x6b, 0xa3, 0x58, 0x4d, 0x69, 0x0d, 0x55, 0x5a,
+	0x0d, 0xcd, 0x66, 0x96, 0x46, 0x38, 0x4f, 0x1f, 0xc4, 0x77, 0x00, 0x6f, 0xfc, 0x23, 0x26, 0x5a,
+	0x19, 0x1a, 0xc3, 0x16, 0xb0, 0x3a, 0x82, 0xd3, 0xf0, 0x3f, 0x56, 0xfc, 0x5b, 0xa8, 0x55, 0x9a,
+	0x1f, 0xf7, 0xc8, 0x45, 0xac, 0xbe, 0xde, 0x54, 0x87, 0x84, 0x7e, 0x02, 0x38, 0x7d, 0x29, 0x4f,
+	0xeb, 0x64, 0x33, 0x0c, 0x68, 0x24, 0xb7, 0xb7, 0xd0, 0x7a, 0x3e, 0x64, 0xa6, 0xd1, 0x16, 0xf9,
+	0x70, 0x64, 0xbf, 0x29, 0x75, 0x5d, 0x95, 0xba, 0x82, 0x96, 0x4b, 0x94, 0xea, 0x29, 0xb3, 0xc0,
+	0x3d, 0xfd, 0xa3, 0x1d, 0xec, 0xf7, 0x5b, 0x8f, 0xbe, 0x9e, 0x55, 0xc1, 0xe9, 0x59, 0x15, 0xfc,
+	0x3a, 0xab, 0x82, 0x37, 0xe7, 0xd5, 0xb1, 0xd3, 0xf3, 0xea, 0xd8, 0x8f, 0xf3, 0xea, 0xd8, 0x73,
+	0xc7, 0x0f, 0xe4, 0x61, 0xd7, 0x75, 0x3c, 0xd6, 0x31, 0xb1, 0xeb, 0x21, 0x71, 0x85, 0xcd, 0xf3,
+	0xf2, 0x22, 0x93, 0x3c, 0xe1, 0x54, 0xb8, 0xe3, 0xea, 0xef, 0xb8, 0xf9, 0x27, 0x00, 0x00, 0xff,
+	0xff, 0xc5, 0x20, 0x1c, 0x8a, 0x83, 0x09, 0x00, 0x00,
 }
 
 // Reference imports to suppress errors if they are not otherwise used.
@@ -102,8 +110,17 @@ type QueryClient interface {
 	UserBlocks(ctx context.Context, in *QueryUserBlocksRequest, opts ...grpc.CallOption) (*QueryUserBlocksResponse, error)
 	// UserChainLinks queries chain links for the given user
 	UserChainLinks(ctx context.Context, in *QueryUserChainLinksRequest, opts ...grpc.CallOption) (*QueryUserChainLinksResponse, error)
+	// UserChainLink queries the chain link for the given user, chain name and
+	// target address
+	UserChainLink(ctx context.Context, in *QueryUserChainLinkRequest, opts ...grpc.CallOption) (*QueryUserChainLinkResponse, error)
 	// UserApplicationLinks queries application links for the given user
 	UserApplicationLinks(ctx context.Context, in *QueryUserApplicationLinksRequest, opts ...grpc.CallOption) (*QueryUserApplicationLinksResponse, error)
+	// UserApplicationLinks queries a single application link for a given user,
+	// searching via the application name and username
+	UserApplicationLink(ctx context.Context, in *QueryUserApplicationLinkRequest, opts ...grpc.CallOption) (*QueryUserApplicationLinkResponse, error)
+	// ApplicationLinkByClientID queries a single application link for a given
+	// client id.
+	ApplicationLinkByClientID(ctx context.Context, in *QueryApplicationLinkByClientIDRequest, opts ...grpc.CallOption) (*QueryApplicationLinkByClientIDResponse, error)
 }
 
 type queryClient struct {
@@ -168,6 +185,15 @@ func (c *queryClient) UserChainLinks(ctx context.Context, in *QueryUserChainLink
 	return out, nil
 }
 
+func (c *queryClient) UserChainLink(ctx context.Context, in *QueryUserChainLinkRequest, opts ...grpc.CallOption) (*QueryUserChainLinkResponse, error) {
+	out := new(QueryUserChainLinkResponse)
+	err := c.cc.Invoke(ctx, "/desmos.profiles.v1beta1.Query/UserChainLink", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
 func (c *queryClient) UserApplicationLinks(ctx context.Context, in *QueryUserApplicationLinksRequest, opts ...grpc.CallOption) (*QueryUserApplicationLinksResponse, error) {
 	out := new(QueryUserApplicationLinksResponse)
 	err := c.cc.Invoke(ctx, "/desmos.profiles.v1beta1.Query/UserApplicationLinks", in, out, opts...)
@@ -177,6 +203,24 @@ func (c *queryClient) UserApplicationLinks(ctx context.Context, in *QueryUserApp
 	return out, nil
 }
 
+func (c *queryClient) UserApplicationLink(ctx context.Context, in *QueryUserApplicationLinkRequest, opts ...grpc.CallOption) (*QueryUserApplicationLinkResponse, error) {
+	out := new(QueryUserApplicationLinkResponse)
+	err := c.cc.Invoke(ctx, "/desmos.profiles.v1beta1.Query/UserApplicationLink", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *queryClient) ApplicationLinkByClientID(ctx context.Context, in *QueryApplicationLinkByClientIDRequest, opts ...grpc.CallOption) (*QueryApplicationLinkByClientIDResponse, error) {
+	out := new(QueryApplicationLinkByClientIDResponse)
+	err := c.cc.Invoke(ctx, "/desmos.profiles.v1beta1.Query/ApplicationLinkByClientID", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
 // QueryServer is the server API for Query service.
 type QueryServer interface {
 	// Profile queries the profile of a specific user given their DTag or address.
@@ -195,8 +239,17 @@ type QueryServer interface {
 	UserBlocks(context.Context, *QueryUserBlocksRequest) (*QueryUserBlocksResponse, error)
 	// UserChainLinks queries chain links for the given user
 	UserChainLinks(context.Context, *QueryUserChainLinksRequest) (*QueryUserChainLinksResponse, error)
+	// UserChainLink queries the chain link for the given user, chain name and
+	// target address
+	UserChainLink(context.Context, *QueryUserChainLinkRequest) (*QueryUserChainLinkResponse, error)
 	// UserApplicationLinks queries application links for the given user
 	UserApplicationLinks(context.Context, *QueryUserApplicationLinksRequest) (*QueryUserApplicationLinksResponse, error)
+	// UserApplicationLinks queries a single application link for a given user,
+	// searching via the application name and username
+	UserApplicationLink(context.Context, *QueryUserApplicationLinkRequest) (*QueryUserApplicationLinkResponse, error)
+	// ApplicationLinkByClientID queries a single application link for a given
+	// client id.
+	ApplicationLinkByClientID(context.Context, *QueryApplicationLinkByClientIDRequest) (*QueryApplicationLinkByClientIDResponse, error)
 }
 
 // UnimplementedQueryServer can be embedded to have forward compatible implementations.
@@ -221,9 +274,18 @@ func (*UnimplementedQueryServer) UserBlocks(ctx context.Context, req *QueryUserB
 func (*UnimplementedQueryServer) UserChainLinks(ctx context.Context, req *QueryUserChainLinksRequest) (*QueryUserChainLinksResponse, error) {
 	return nil, status.Errorf(codes.Unimplemented, "method UserChainLinks not implemented")
 }
+func (*UnimplementedQueryServer) UserChainLink(ctx context.Context, req *QueryUserChainLinkRequest) (*QueryUserChainLinkResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method UserChainLink not implemented")
+}
 func (*UnimplementedQueryServer) UserApplicationLinks(ctx context.Context, req *QueryUserApplicationLinksRequest) (*QueryUserApplicationLinksResponse, error) {
 	return nil, status.Errorf(codes.Unimplemented, "method UserApplicationLinks not implemented")
 }
+func (*UnimplementedQueryServer) UserApplicationLink(ctx context.Context, req *QueryUserApplicationLinkRequest) (*QueryUserApplicationLinkResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method UserApplicationLink not implemented")
+}
+func (*UnimplementedQueryServer) ApplicationLinkByClientID(ctx context.Context, req *QueryApplicationLinkByClientIDRequest) (*QueryApplicationLinkByClientIDResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method ApplicationLinkByClientID not implemented")
+}
 
 func RegisterQueryServer(s grpc1.Server, srv QueryServer) {
 	s.RegisterService(&_Query_serviceDesc, srv)
@@ -337,6 +399,24 @@ func _Query_UserChainLinks_Handler(srv interface{}, ctx context.Context, dec fun
 	return interceptor(ctx, in, info, handler)
 }
 
+func _Query_UserChainLink_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(QueryUserChainLinkRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(QueryServer).UserChainLink(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/desmos.profiles.v1beta1.Query/UserChainLink",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(QueryServer).UserChainLink(ctx, req.(*QueryUserChainLinkRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
 func _Query_UserApplicationLinks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
 	in := new(QueryUserApplicationLinksRequest)
 	if err := dec(in); err != nil {
@@ -355,6 +435,42 @@ func _Query_UserApplicationLinks_Handler(srv interface{}, ctx context.Context, d
 	return interceptor(ctx, in, info, handler)
 }
 
+func _Query_UserApplicationLink_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(QueryUserApplicationLinkRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(QueryServer).UserApplicationLink(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/desmos.profiles.v1beta1.Query/UserApplicationLink",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(QueryServer).UserApplicationLink(ctx, req.(*QueryUserApplicationLinkRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _Query_ApplicationLinkByClientID_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(QueryApplicationLinkByClientIDRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(QueryServer).ApplicationLinkByClientID(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/desmos.profiles.v1beta1.Query/ApplicationLinkByClientID",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(QueryServer).ApplicationLinkByClientID(ctx, req.(*QueryApplicationLinkByClientIDRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
 var _Query_serviceDesc = grpc.ServiceDesc{
 	ServiceName: "desmos.profiles.v1beta1.Query",
 	HandlerType: (*QueryServer)(nil),
@@ -383,10 +499,22 @@ var _Query_serviceDesc = grpc.ServiceDesc{
 			MethodName: "UserChainLinks",
 			Handler:    _Query_UserChainLinks_Handler,
 		},
+		{
+			MethodName: "UserChainLink",
+			Handler:    _Query_UserChainLink_Handler,
+		},
 		{
 			MethodName: "UserApplicationLinks",
 			Handler:    _Query_UserApplicationLinks_Handler,
 		},
+		{
+			MethodName: "UserApplicationLink",
+			Handler:    _Query_UserApplicationLink_Handler,
+		},
+		{
+			MethodName: "ApplicationLinkByClientID",
+			Handler:    _Query_ApplicationLinkByClientID_Handler,
+		},
 	},
 	Streams:  []grpc.StreamDesc{},
 	Metadata: "desmos/profiles/v1beta1/query.proto",
diff --git a/x/profiles/types/query.pb.gw.go b/x/profiles/types/query.pb.gw.go
index 7b160c03..74d46187 100644
--- a/x/profiles/types/query.pb.gw.go
+++ b/x/profiles/types/query.pb.gw.go
@@ -337,6 +337,104 @@ func local_request_Query_UserChainLinks_0(ctx context.Context, marshaler runtime
 
 }
 
+func request_Query_UserChainLink_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+	var protoReq QueryUserChainLinkRequest
+	var metadata runtime.ServerMetadata
+
+	var (
+		val string
+		ok  bool
+		err error
+		_   = err
+	)
+
+	val, ok = pathParams["user"]
+	if !ok {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "user")
+	}
+
+	protoReq.User, err = runtime.String(val)
+
+	if err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "user", err)
+	}
+
+	val, ok = pathParams["chain_name"]
+	if !ok {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "chain_name")
+	}
+
+	protoReq.ChainName, err = runtime.String(val)
+
+	if err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "chain_name", err)
+	}
+
+	val, ok = pathParams["target"]
+	if !ok {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "target")
+	}
+
+	protoReq.Target, err = runtime.String(val)
+
+	if err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "target", err)
+	}
+
+	msg, err := client.UserChainLink(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
+	return msg, metadata, err
+
+}
+
+func local_request_Query_UserChainLink_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+	var protoReq QueryUserChainLinkRequest
+	var metadata runtime.ServerMetadata
+
+	var (
+		val string
+		ok  bool
+		err error
+		_   = err
+	)
+
+	val, ok = pathParams["user"]
+	if !ok {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "user")
+	}
+
+	protoReq.User, err = runtime.String(val)
+
+	if err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "user", err)
+	}
+
+	val, ok = pathParams["chain_name"]
+	if !ok {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "chain_name")
+	}
+
+	protoReq.ChainName, err = runtime.String(val)
+
+	if err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "chain_name", err)
+	}
+
+	val, ok = pathParams["target"]
+	if !ok {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "target")
+	}
+
+	protoReq.Target, err = runtime.String(val)
+
+	if err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "target", err)
+	}
+
+	msg, err := server.UserChainLink(ctx, &protoReq)
+	return msg, metadata, err
+
+}
+
 var (
 	filter_Query_UserApplicationLinks_0 = &utilities.DoubleArray{Encoding: map[string]int{"user": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
 )
@@ -409,6 +507,158 @@ func local_request_Query_UserApplicationLinks_0(ctx context.Context, marshaler r
 
 }
 
+func request_Query_UserApplicationLink_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+	var protoReq QueryUserApplicationLinkRequest
+	var metadata runtime.ServerMetadata
+
+	var (
+		val string
+		ok  bool
+		err error
+		_   = err
+	)
+
+	val, ok = pathParams["user"]
+	if !ok {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "user")
+	}
+
+	protoReq.User, err = runtime.String(val)
+
+	if err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "user", err)
+	}
+
+	val, ok = pathParams["application"]
+	if !ok {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "application")
+	}
+
+	protoReq.Application, err = runtime.String(val)
+
+	if err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "application", err)
+	}
+
+	val, ok = pathParams["username"]
+	if !ok {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "username")
+	}
+
+	protoReq.Username, err = runtime.String(val)
+
+	if err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "username", err)
+	}
+
+	msg, err := client.UserApplicationLink(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
+	return msg, metadata, err
+
+}
+
+func local_request_Query_UserApplicationLink_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+	var protoReq QueryUserApplicationLinkRequest
+	var metadata runtime.ServerMetadata
+
+	var (
+		val string
+		ok  bool
+		err error
+		_   = err
+	)
+
+	val, ok = pathParams["user"]
+	if !ok {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "user")
+	}
+
+	protoReq.User, err = runtime.String(val)
+
+	if err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "user", err)
+	}
+
+	val, ok = pathParams["application"]
+	if !ok {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "application")
+	}
+
+	protoReq.Application, err = runtime.String(val)
+
+	if err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "application", err)
+	}
+
+	val, ok = pathParams["username"]
+	if !ok {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "username")
+	}
+
+	protoReq.Username, err = runtime.String(val)
+
+	if err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "username", err)
+	}
+
+	msg, err := server.UserApplicationLink(ctx, &protoReq)
+	return msg, metadata, err
+
+}
+
+func request_Query_ApplicationLinkByClientID_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+	var protoReq QueryApplicationLinkByClientIDRequest
+	var metadata runtime.ServerMetadata
+
+	var (
+		val string
+		ok  bool
+		err error
+		_   = err
+	)
+
+	val, ok = pathParams["client_id"]
+	if !ok {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id")
+	}
+
+	protoReq.ClientId, err = runtime.String(val)
+
+	if err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err)
+	}
+
+	msg, err := client.ApplicationLinkByClientID(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
+	return msg, metadata, err
+
+}
+
+func local_request_Query_ApplicationLinkByClientID_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+	var protoReq QueryApplicationLinkByClientIDRequest
+	var metadata runtime.ServerMetadata
+
+	var (
+		val string
+		ok  bool
+		err error
+		_   = err
+	)
+
+	val, ok = pathParams["client_id"]
+	if !ok {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "client_id")
+	}
+
+	protoReq.ClientId, err = runtime.String(val)
+
+	if err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "client_id", err)
+	}
+
+	msg, err := server.ApplicationLinkByClientID(ctx, &protoReq)
+	return msg, metadata, err
+
+}
+
 // RegisterQueryHandlerServer registers the http handlers for service Query to "mux".
 // UnaryRPC     :call QueryServer directly.
 // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
@@ -535,6 +785,26 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv
 
 	})
 
+	mux.Handle("GET", pattern_Query_UserChainLink_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
+		ctx, cancel := context.WithCancel(req.Context())
+		defer cancel()
+		inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
+		rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+		resp, md, err := local_request_Query_UserChainLink_0(rctx, inboundMarshaler, server, req, pathParams)
+		ctx = runtime.NewServerMetadataContext(ctx, md)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+
+		forward_Query_UserChainLink_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+	})
+
 	mux.Handle("GET", pattern_Query_UserApplicationLinks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
 		ctx, cancel := context.WithCancel(req.Context())
 		defer cancel()
@@ -555,6 +825,46 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv
 
 	})
 
+	mux.Handle("GET", pattern_Query_UserApplicationLink_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
+		ctx, cancel := context.WithCancel(req.Context())
+		defer cancel()
+		inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
+		rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+		resp, md, err := local_request_Query_UserApplicationLink_0(rctx, inboundMarshaler, server, req, pathParams)
+		ctx = runtime.NewServerMetadataContext(ctx, md)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+
+		forward_Query_UserApplicationLink_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+	})
+
+	mux.Handle("GET", pattern_Query_ApplicationLinkByClientID_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
+		ctx, cancel := context.WithCancel(req.Context())
+		defer cancel()
+		inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
+		rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+		resp, md, err := local_request_Query_ApplicationLinkByClientID_0(rctx, inboundMarshaler, server, req, pathParams)
+		ctx = runtime.NewServerMetadataContext(ctx, md)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+
+		forward_Query_ApplicationLinkByClientID_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+	})
+
 	return nil
 }
 
@@ -716,6 +1026,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie
 
 	})
 
+	mux.Handle("GET", pattern_Query_UserChainLink_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
+		ctx, cancel := context.WithCancel(req.Context())
+		defer cancel()
+		inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
+		rctx, err := runtime.AnnotateContext(ctx, mux, req)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+		resp, md, err := request_Query_UserChainLink_0(rctx, inboundMarshaler, client, req, pathParams)
+		ctx = runtime.NewServerMetadataContext(ctx, md)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+
+		forward_Query_UserChainLink_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+	})
+
 	mux.Handle("GET", pattern_Query_UserApplicationLinks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
 		ctx, cancel := context.WithCancel(req.Context())
 		defer cancel()
@@ -736,6 +1066,46 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie
 
 	})
 
+	mux.Handle("GET", pattern_Query_UserApplicationLink_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
+		ctx, cancel := context.WithCancel(req.Context())
+		defer cancel()
+		inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
+		rctx, err := runtime.AnnotateContext(ctx, mux, req)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+		resp, md, err := request_Query_UserApplicationLink_0(rctx, inboundMarshaler, client, req, pathParams)
+		ctx = runtime.NewServerMetadataContext(ctx, md)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+
+		forward_Query_UserApplicationLink_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+	})
+
+	mux.Handle("GET", pattern_Query_ApplicationLinkByClientID_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
+		ctx, cancel := context.WithCancel(req.Context())
+		defer cancel()
+		inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
+		rctx, err := runtime.AnnotateContext(ctx, mux, req)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+		resp, md, err := request_Query_ApplicationLinkByClientID_0(rctx, inboundMarshaler, client, req, pathParams)
+		ctx = runtime.NewServerMetadataContext(ctx, md)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+
+		forward_Query_ApplicationLinkByClientID_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+	})
+
 	return nil
 }
 
@@ -752,7 +1122,13 @@ var (
 
 	pattern_Query_UserChainLinks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"desmos", "profiles", "v1beta1", "chain-links", "user"}, "", runtime.AssumeColonVerbOpt(true)))
 
+	pattern_Query_UserChainLink_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 1, 0, 4, 1, 5, 5, 1, 0, 4, 1, 5, 6}, []string{"desmos", "profiles", "v1beta1", "chain-links", "user", "chain_name", "target"}, "", runtime.AssumeColonVerbOpt(true)))
+
 	pattern_Query_UserApplicationLinks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"desmos", "profiles", "v1beta1", "app-links", "user"}, "", runtime.AssumeColonVerbOpt(true)))
+
+	pattern_Query_UserApplicationLink_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 1, 0, 4, 1, 5, 5, 1, 0, 4, 1, 5, 6}, []string{"desmos", "profiles", "v1beta1", "app-links", "user", "application", "username"}, "", runtime.AssumeColonVerbOpt(true)))
+
+	pattern_Query_ApplicationLinkByClientID_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 2, 4, 1, 0, 4, 1, 5, 5}, []string{"desmos", "profiles", "v1beta1", "app-links", "clients", "client_id"}, "", runtime.AssumeColonVerbOpt(true)))
 )
 
 var (
@@ -768,5 +1144,11 @@ var (
 
 	forward_Query_UserChainLinks_0 = runtime.ForwardResponseMessage
 
+	forward_Query_UserChainLink_0 = runtime.ForwardResponseMessage
+
 	forward_Query_UserApplicationLinks_0 = runtime.ForwardResponseMessage
+
+	forward_Query_UserApplicationLink_0 = runtime.ForwardResponseMessage
+
+	forward_Query_ApplicationLinkByClientID_0 = runtime.ForwardResponseMessage
 )
diff --git a/x/profiles/types/query_app_links.go b/x/profiles/types/query_app_links.go
index afaab0ff..a8bca994 100644
--- a/x/profiles/types/query_app_links.go
+++ b/x/profiles/types/query_app_links.go
@@ -11,3 +11,19 @@ func NewQueryUserApplicationLinksRequest(user string, pageReq *query.PageRequest
 		Pagination: pageReq,
 	}
 }
+
+// NewQueryUserApplicationLinkRequest returns a new QueryUserApplicationLinkRequest instance
+func NewQueryUserApplicationLinkRequest(user, application, username string) *QueryUserApplicationLinkRequest {
+	return &QueryUserApplicationLinkRequest{
+		User:        user,
+		Application: application,
+		Username:    username,
+	}
+}
+
+// NewQueryApplicationLinkByClientIDRequest returns a new QueryApplicationLinkByClientIDRequest instance
+func NewQueryApplicationLinkByClientIDRequest(clientID string) *QueryApplicationLinkByClientIDRequest {
+	return &QueryApplicationLinkByClientIDRequest{
+		ClientId: clientID,
+	}
+}
diff --git a/x/profiles/types/query_app_links.pb.go b/x/profiles/types/query_app_links.pb.go
index 8331d1c2..97387ebb 100644
--- a/x/profiles/types/query_app_links.pb.go
+++ b/x/profiles/types/query_app_links.pb.go
@@ -27,8 +27,122 @@ var _ = math.Inf
 // proto package needs to be updated.
 const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
 
-// QueryUserApplicationLinksRequest represent the request used when querying the
-// application links of a specific user
+// QueryUserApplicationLinkRequest represents the request used when querying an
+// application link using an application name and username for a given user
+type QueryUserApplicationLinkRequest struct {
+	// User contains the Desmos profile address associated for which the link
+	// should be searched for
+	User string `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"`
+	// Application represents the application name associated with the link
+	Application string `protobuf:"bytes,2,opt,name=application,proto3" json:"application,omitempty"`
+	// Username represents the username inside the application associated with the
+	// link
+	Username string `protobuf:"bytes,3,opt,name=username,proto3" json:"username,omitempty"`
+}
+
+func (m *QueryUserApplicationLinkRequest) Reset()         { *m = QueryUserApplicationLinkRequest{} }
+func (m *QueryUserApplicationLinkRequest) String() string { return proto.CompactTextString(m) }
+func (*QueryUserApplicationLinkRequest) ProtoMessage()    {}
+func (*QueryUserApplicationLinkRequest) Descriptor() ([]byte, []int) {
+	return fileDescriptor_18d2a2b45fd238cb, []int{0}
+}
+func (m *QueryUserApplicationLinkRequest) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *QueryUserApplicationLinkRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_QueryUserApplicationLinkRequest.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *QueryUserApplicationLinkRequest) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_QueryUserApplicationLinkRequest.Merge(m, src)
+}
+func (m *QueryUserApplicationLinkRequest) XXX_Size() int {
+	return m.Size()
+}
+func (m *QueryUserApplicationLinkRequest) XXX_DiscardUnknown() {
+	xxx_messageInfo_QueryUserApplicationLinkRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_QueryUserApplicationLinkRequest proto.InternalMessageInfo
+
+func (m *QueryUserApplicationLinkRequest) GetUser() string {
+	if m != nil {
+		return m.User
+	}
+	return ""
+}
+
+func (m *QueryUserApplicationLinkRequest) GetApplication() string {
+	if m != nil {
+		return m.Application
+	}
+	return ""
+}
+
+func (m *QueryUserApplicationLinkRequest) GetUsername() string {
+	if m != nil {
+		return m.Username
+	}
+	return ""
+}
+
+// QueryUserApplicationLinkResponse represents the response to the query
+// allowing to get an application link for a specific user, searching via the
+// application name and username
+type QueryUserApplicationLinkResponse struct {
+	Link ApplicationLink `protobuf:"bytes,1,opt,name=link,proto3" json:"link"`
+}
+
+func (m *QueryUserApplicationLinkResponse) Reset()         { *m = QueryUserApplicationLinkResponse{} }
+func (m *QueryUserApplicationLinkResponse) String() string { return proto.CompactTextString(m) }
+func (*QueryUserApplicationLinkResponse) ProtoMessage()    {}
+func (*QueryUserApplicationLinkResponse) Descriptor() ([]byte, []int) {
+	return fileDescriptor_18d2a2b45fd238cb, []int{1}
+}
+func (m *QueryUserApplicationLinkResponse) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *QueryUserApplicationLinkResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_QueryUserApplicationLinkResponse.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *QueryUserApplicationLinkResponse) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_QueryUserApplicationLinkResponse.Merge(m, src)
+}
+func (m *QueryUserApplicationLinkResponse) XXX_Size() int {
+	return m.Size()
+}
+func (m *QueryUserApplicationLinkResponse) XXX_DiscardUnknown() {
+	xxx_messageInfo_QueryUserApplicationLinkResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_QueryUserApplicationLinkResponse proto.InternalMessageInfo
+
+func (m *QueryUserApplicationLinkResponse) GetLink() ApplicationLink {
+	if m != nil {
+		return m.Link
+	}
+	return ApplicationLink{}
+}
+
+// QueryUserApplicationLinksRequest represents the request used when querying
+// the application links of a specific user
 type QueryUserApplicationLinksRequest struct {
 	User string `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"`
 	// Pagination defines an optional pagination for the request
@@ -39,7 +153,7 @@ func (m *QueryUserApplicationLinksRequest) Reset()         { *m = QueryUserAppli
 func (m *QueryUserApplicationLinksRequest) String() string { return proto.CompactTextString(m) }
 func (*QueryUserApplicationLinksRequest) ProtoMessage()    {}
 func (*QueryUserApplicationLinksRequest) Descriptor() ([]byte, []int) {
-	return fileDescriptor_18d2a2b45fd238cb, []int{0}
+	return fileDescriptor_18d2a2b45fd238cb, []int{2}
 }
 func (m *QueryUserApplicationLinksRequest) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -94,7 +208,7 @@ func (m *QueryUserApplicationLinksResponse) Reset()         { *m = QueryUserAppl
 func (m *QueryUserApplicationLinksResponse) String() string { return proto.CompactTextString(m) }
 func (*QueryUserApplicationLinksResponse) ProtoMessage()    {}
 func (*QueryUserApplicationLinksResponse) Descriptor() ([]byte, []int) {
-	return fileDescriptor_18d2a2b45fd238cb, []int{1}
+	return fileDescriptor_18d2a2b45fd238cb, []int{3}
 }
 func (m *QueryUserApplicationLinksResponse) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -137,9 +251,108 @@ func (m *QueryUserApplicationLinksResponse) GetPagination() *query.PageResponse
 	return nil
 }
 
+// QueryApplicationLinkByClientIDRequest contains the data of the request that
+// can be used to get an application link based on a client id
+type QueryApplicationLinkByClientIDRequest struct {
+	// ClientID represents the ID of the client to which search the link for
+	ClientId string `protobuf:"bytes,1,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
+}
+
+func (m *QueryApplicationLinkByClientIDRequest) Reset()         { *m = QueryApplicationLinkByClientIDRequest{} }
+func (m *QueryApplicationLinkByClientIDRequest) String() string { return proto.CompactTextString(m) }
+func (*QueryApplicationLinkByClientIDRequest) ProtoMessage()    {}
+func (*QueryApplicationLinkByClientIDRequest) Descriptor() ([]byte, []int) {
+	return fileDescriptor_18d2a2b45fd238cb, []int{4}
+}
+func (m *QueryApplicationLinkByClientIDRequest) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *QueryApplicationLinkByClientIDRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_QueryApplicationLinkByClientIDRequest.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *QueryApplicationLinkByClientIDRequest) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_QueryApplicationLinkByClientIDRequest.Merge(m, src)
+}
+func (m *QueryApplicationLinkByClientIDRequest) XXX_Size() int {
+	return m.Size()
+}
+func (m *QueryApplicationLinkByClientIDRequest) XXX_DiscardUnknown() {
+	xxx_messageInfo_QueryApplicationLinkByClientIDRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_QueryApplicationLinkByClientIDRequest proto.InternalMessageInfo
+
+func (m *QueryApplicationLinkByClientIDRequest) GetClientId() string {
+	if m != nil {
+		return m.ClientId
+	}
+	return ""
+}
+
+// QueryApplicationLinkByClientIDResponse contains the data returned by the
+// request allowing to get an application link using a client id
+type QueryApplicationLinkByClientIDResponse struct {
+	Link ApplicationLink `protobuf:"bytes,1,opt,name=link,proto3" json:"link"`
+}
+
+func (m *QueryApplicationLinkByClientIDResponse) Reset() {
+	*m = QueryApplicationLinkByClientIDResponse{}
+}
+func (m *QueryApplicationLinkByClientIDResponse) String() string { return proto.CompactTextString(m) }
+func (*QueryApplicationLinkByClientIDResponse) ProtoMessage()    {}
+func (*QueryApplicationLinkByClientIDResponse) Descriptor() ([]byte, []int) {
+	return fileDescriptor_18d2a2b45fd238cb, []int{5}
+}
+func (m *QueryApplicationLinkByClientIDResponse) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *QueryApplicationLinkByClientIDResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_QueryApplicationLinkByClientIDResponse.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *QueryApplicationLinkByClientIDResponse) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_QueryApplicationLinkByClientIDResponse.Merge(m, src)
+}
+func (m *QueryApplicationLinkByClientIDResponse) XXX_Size() int {
+	return m.Size()
+}
+func (m *QueryApplicationLinkByClientIDResponse) XXX_DiscardUnknown() {
+	xxx_messageInfo_QueryApplicationLinkByClientIDResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_QueryApplicationLinkByClientIDResponse proto.InternalMessageInfo
+
+func (m *QueryApplicationLinkByClientIDResponse) GetLink() ApplicationLink {
+	if m != nil {
+		return m.Link
+	}
+	return ApplicationLink{}
+}
+
 func init() {
+	proto.RegisterType((*QueryUserApplicationLinkRequest)(nil), "desmos.profiles.v1beta1.QueryUserApplicationLinkRequest")
+	proto.RegisterType((*QueryUserApplicationLinkResponse)(nil), "desmos.profiles.v1beta1.QueryUserApplicationLinkResponse")
 	proto.RegisterType((*QueryUserApplicationLinksRequest)(nil), "desmos.profiles.v1beta1.QueryUserApplicationLinksRequest")
 	proto.RegisterType((*QueryUserApplicationLinksResponse)(nil), "desmos.profiles.v1beta1.QueryUserApplicationLinksResponse")
+	proto.RegisterType((*QueryApplicationLinkByClientIDRequest)(nil), "desmos.profiles.v1beta1.QueryApplicationLinkByClientIDRequest")
+	proto.RegisterType((*QueryApplicationLinkByClientIDResponse)(nil), "desmos.profiles.v1beta1.QueryApplicationLinkByClientIDResponse")
 }
 
 func init() {
@@ -147,30 +360,113 @@ func init() {
 }
 
 var fileDescriptor_18d2a2b45fd238cb = []byte{
-	// 368 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x91, 0xc1, 0x4e, 0xfa, 0x40,
-	0x10, 0xc6, 0xbb, 0xff, 0x3f, 0x9a, 0x58, 0x6e, 0x8d, 0x89, 0xd0, 0x98, 0x5a, 0x39, 0x68, 0x63,
-	0xc2, 0x6e, 0xc0, 0x27, 0x90, 0x18, 0xf5, 0xe0, 0x41, 0x9b, 0x78, 0xf1, 0x42, 0xb6, 0xb0, 0xd4,
-	0xc6, 0xd2, 0x59, 0xba, 0x5b, 0x23, 0x17, 0x9f, 0xc1, 0x67, 0xf1, 0x29, 0x38, 0x72, 0xf4, 0x64,
-	0x0c, 0xbc, 0x88, 0xe9, 0xee, 0x22, 0x84, 0x88, 0xde, 0x66, 0xf2, 0x7d, 0xfb, 0xcd, 0x6f, 0x76,
-	0xec, 0x66, 0x9f, 0x89, 0x21, 0x08, 0xc2, 0x73, 0x18, 0x24, 0x29, 0x13, 0xe4, 0xa9, 0x15, 0x31,
-	0x49, 0x5b, 0x64, 0x54, 0xb0, 0x7c, 0xdc, 0xa5, 0x9c, 0x77, 0xd3, 0x24, 0x7b, 0x14, 0x98, 0xe7,
-	0x20, 0xc1, 0xd9, 0xd3, 0x76, 0xbc, 0xb0, 0x63, 0x63, 0x77, 0x77, 0x63, 0x88, 0x41, 0x79, 0x48,
-	0x59, 0x69, 0xbb, 0xbb, 0x1f, 0x03, 0xc4, 0x29, 0x23, 0x94, 0x27, 0x84, 0x66, 0x19, 0x48, 0x2a,
-	0x13, 0xc8, 0x4c, 0x98, 0x5b, 0x37, 0xaa, 0xea, 0xa2, 0x62, 0x40, 0x68, 0x36, 0x36, 0x12, 0xde,
-	0x84, 0x35, 0x84, 0x3e, 0x4b, 0xc5, 0x3a, 0x97, 0x5b, 0xef, 0x41, 0xe9, 0xef, 0x6a, 0x02, 0xdd,
-	0x18, 0xe9, 0x44, 0x77, 0x24, 0xa2, 0x82, 0xe9, 0xad, 0xbe, 0xc3, 0x38, 0x8d, 0x93, 0x4c, 0x21,
-	0x69, 0x6f, 0xe3, 0xc5, 0xf6, 0x6f, 0x4b, 0xc7, 0x9d, 0x60, 0xf9, 0x19, 0xe7, 0x69, 0xd2, 0x53,
-	0xea, 0x75, 0x39, 0x29, 0x64, 0xa3, 0x82, 0x09, 0xe9, 0x38, 0x76, 0xa5, 0x10, 0x2c, 0xaf, 0x21,
-	0x1f, 0x05, 0x3b, 0xa1, 0xaa, 0x9d, 0x0b, 0xdb, 0x5e, 0x66, 0xd5, 0xfe, 0xf9, 0x28, 0xa8, 0xb6,
-	0x8f, 0xb0, 0xc1, 0x28, 0x07, 0x63, 0x35, 0x78, 0xf1, 0x5b, 0xf8, 0x86, 0xc6, 0xcc, 0xe4, 0x85,
-	0x2b, 0x2f, 0x1b, 0x6f, 0xc8, 0x3e, 0xfc, 0x05, 0x40, 0x70, 0xc8, 0x04, 0x73, 0xce, 0xed, 0x2d,
-	0xb5, 0x7b, 0x0d, 0xf9, 0xff, 0x83, 0x6a, 0x3b, 0xc0, 0x1b, 0x8e, 0x82, 0xd7, 0x12, 0x3a, 0x95,
-	0xc9, 0xc7, 0x81, 0x15, 0xea, 0xc7, 0xce, 0xe5, 0x0f, 0xcc, 0xc7, 0x7f, 0x32, 0x6b, 0x84, 0x55,
-	0xe8, 0xce, 0xd5, 0x64, 0xe6, 0xa1, 0xe9, 0xcc, 0x43, 0x9f, 0x33, 0x0f, 0xbd, 0xce, 0x3d, 0x6b,
-	0x3a, 0xf7, 0xac, 0xf7, 0xb9, 0x67, 0xdd, 0xe3, 0x38, 0x91, 0x0f, 0x45, 0x84, 0x7b, 0x30, 0x24,
-	0x9a, 0xb1, 0x99, 0xd2, 0x48, 0x98, 0x9a, 0x3c, 0x2f, 0xcf, 0x2b, 0xc7, 0x9c, 0x89, 0x68, 0x5b,
-	0x5d, 0xe1, 0xf4, 0x2b, 0x00, 0x00, 0xff, 0xff, 0x04, 0x99, 0x2a, 0xde, 0x95, 0x02, 0x00, 0x00,
+	// 459 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x53, 0xd1, 0x6e, 0xd3, 0x30,
+	0x14, 0x6d, 0x58, 0x41, 0x9b, 0xfb, 0x16, 0x21, 0xd1, 0x05, 0x94, 0x95, 0x48, 0x8c, 0x09, 0x69,
+	0xb6, 0x36, 0xbe, 0x80, 0x52, 0x01, 0x93, 0x78, 0x80, 0x4a, 0xbc, 0xf0, 0x52, 0x39, 0xc9, 0x6d,
+	0xb0, 0xe6, 0xd8, 0x5e, 0xec, 0x20, 0xf2, 0xc2, 0x37, 0xf0, 0x2d, 0x7c, 0xc5, 0x1e, 0xf7, 0xc8,
+	0x13, 0x42, 0xed, 0x8f, 0xa0, 0xd8, 0x5e, 0x3b, 0x2a, 0xb2, 0x4a, 0x88, 0x37, 0x5f, 0x9f, 0x73,
+	0xef, 0x3d, 0xe7, 0x58, 0x46, 0xc7, 0x39, 0xe8, 0x52, 0x6a, 0xa2, 0x2a, 0x39, 0x67, 0x1c, 0x34,
+	0xf9, 0x7c, 0x92, 0x82, 0xa1, 0x27, 0xe4, 0xa2, 0x86, 0xaa, 0x99, 0x51, 0xa5, 0x66, 0x9c, 0x89,
+	0x73, 0x8d, 0x55, 0x25, 0x8d, 0x0c, 0x1f, 0x38, 0x3a, 0xbe, 0xa6, 0x63, 0x4f, 0x8f, 0xee, 0x17,
+	0xb2, 0x90, 0x96, 0x43, 0xda, 0x93, 0xa3, 0x47, 0x8f, 0x0a, 0x29, 0x0b, 0x0e, 0x84, 0x2a, 0x46,
+	0xa8, 0x10, 0xd2, 0x50, 0xc3, 0xa4, 0xf0, 0xc3, 0xa2, 0x7d, 0x8f, 0xda, 0x2a, 0xad, 0xe7, 0x84,
+	0x8a, 0xc6, 0x43, 0xb8, 0x4b, 0x56, 0x29, 0x73, 0xe0, 0x7a, 0x53, 0x57, 0xb4, 0x9f, 0xc9, 0x96,
+	0x3f, 0x73, 0x0a, 0x5c, 0xe1, 0xa1, 0x67, 0xae, 0x22, 0x29, 0xd5, 0xe0, 0x5c, 0xad, 0x86, 0x29,
+	0x5a, 0x30, 0x61, 0x25, 0x39, 0x6e, 0xa2, 0xd1, 0xc1, 0xfb, 0x96, 0xf1, 0x41, 0x43, 0xf5, 0x42,
+	0x29, 0xce, 0x32, 0x8b, 0xbe, 0x65, 0xe2, 0x7c, 0x0a, 0x17, 0x35, 0x68, 0x13, 0x86, 0xa8, 0x5f,
+	0x6b, 0xa8, 0x86, 0xc1, 0x28, 0x38, 0xda, 0x9b, 0xda, 0x73, 0x38, 0x42, 0x03, 0xba, 0x66, 0x0f,
+	0xef, 0x58, 0xe8, 0xe6, 0x55, 0x18, 0xa1, 0xdd, 0x96, 0x29, 0x68, 0x09, 0xc3, 0x1d, 0x0b, 0xaf,
+	0xea, 0x64, 0x8e, 0x46, 0xdd, 0x4b, 0xb5, 0x92, 0x42, 0x43, 0x38, 0x46, 0xfd, 0xd6, 0xae, 0xdd,
+	0x3a, 0x38, 0x3d, 0xc2, 0x1d, 0xcf, 0x80, 0x37, 0xfa, 0xc7, 0xfd, 0xcb, 0x9f, 0x07, 0xbd, 0xa9,
+	0xed, 0x4d, 0xbe, 0x76, 0xef, 0xd1, 0xb7, 0xb9, 0x7b, 0x85, 0xd0, 0x3a, 0x28, 0x6b, 0x6e, 0x70,
+	0x7a, 0x88, 0x7d, 0xc6, 0x6d, 0xaa, 0xd8, 0xa6, 0xba, 0xd2, 0xf0, 0x8e, 0x16, 0xe0, 0xe7, 0x4d,
+	0x6f, 0x74, 0x26, 0xdf, 0x03, 0xf4, 0xf8, 0x16, 0x01, 0xde, 0xe9, 0x04, 0xdd, 0xb5, 0x0f, 0x3b,
+	0x0c, 0x46, 0x3b, 0xff, 0x60, 0xd5, 0x35, 0x87, 0xaf, 0xff, 0xa2, 0xf9, 0xe9, 0x56, 0xcd, 0x4e,
+	0xc2, 0x1f, 0xa2, 0x27, 0xe8, 0x89, 0xd5, 0xbc, 0xb9, 0xad, 0x79, 0xc9, 0x19, 0x08, 0x73, 0x36,
+	0xb9, 0x4e, 0xee, 0x21, 0xda, 0xcb, 0xec, 0xd5, 0x8c, 0xe5, 0x3e, 0xbe, 0x5d, 0x77, 0x71, 0x96,
+	0x27, 0x1c, 0x1d, 0x6e, 0x9b, 0xf2, 0xff, 0x1e, 0x7a, 0xfc, 0xe6, 0x72, 0x11, 0x07, 0x57, 0x8b,
+	0x38, 0xf8, 0xb5, 0x88, 0x83, 0x6f, 0xcb, 0xb8, 0x77, 0xb5, 0x8c, 0x7b, 0x3f, 0x96, 0x71, 0xef,
+	0x23, 0x2e, 0x98, 0xf9, 0x54, 0xa7, 0x38, 0x93, 0x25, 0x71, 0x93, 0x8f, 0x39, 0x4d, 0xb5, 0x3f,
+	0x93, 0x2f, 0xeb, 0xff, 0x66, 0x1a, 0x05, 0x3a, 0xbd, 0x67, 0xbf, 0xc5, 0xf3, 0xdf, 0x01, 0x00,
+	0x00, 0xff, 0xff, 0xe8, 0x31, 0x86, 0x83, 0x26, 0x04, 0x00, 0x00,
+}
+
+func (m *QueryUserApplicationLinkRequest) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *QueryUserApplicationLinkRequest) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *QueryUserApplicationLinkRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.Username) > 0 {
+		i -= len(m.Username)
+		copy(dAtA[i:], m.Username)
+		i = encodeVarintQueryAppLinks(dAtA, i, uint64(len(m.Username)))
+		i--
+		dAtA[i] = 0x1a
+	}
+	if len(m.Application) > 0 {
+		i -= len(m.Application)
+		copy(dAtA[i:], m.Application)
+		i = encodeVarintQueryAppLinks(dAtA, i, uint64(len(m.Application)))
+		i--
+		dAtA[i] = 0x12
+	}
+	if len(m.User) > 0 {
+		i -= len(m.User)
+		copy(dAtA[i:], m.User)
+		i = encodeVarintQueryAppLinks(dAtA, i, uint64(len(m.User)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *QueryUserApplicationLinkResponse) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *QueryUserApplicationLinkResponse) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *QueryUserApplicationLinkResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	{
+		size, err := m.Link.MarshalToSizedBuffer(dAtA[:i])
+		if err != nil {
+			return 0, err
+		}
+		i -= size
+		i = encodeVarintQueryAppLinks(dAtA, i, uint64(size))
+	}
+	i--
+	dAtA[i] = 0xa
+	return len(dAtA) - i, nil
 }
 
 func (m *QueryUserApplicationLinksRequest) Marshal() (dAtA []byte, err error) {
@@ -264,6 +560,69 @@ func (m *QueryUserApplicationLinksResponse) MarshalToSizedBuffer(dAtA []byte) (i
 	return len(dAtA) - i, nil
 }
 
+func (m *QueryApplicationLinkByClientIDRequest) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *QueryApplicationLinkByClientIDRequest) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *QueryApplicationLinkByClientIDRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.ClientId) > 0 {
+		i -= len(m.ClientId)
+		copy(dAtA[i:], m.ClientId)
+		i = encodeVarintQueryAppLinks(dAtA, i, uint64(len(m.ClientId)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *QueryApplicationLinkByClientIDResponse) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *QueryApplicationLinkByClientIDResponse) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *QueryApplicationLinkByClientIDResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	{
+		size, err := m.Link.MarshalToSizedBuffer(dAtA[:i])
+		if err != nil {
+			return 0, err
+		}
+		i -= size
+		i = encodeVarintQueryAppLinks(dAtA, i, uint64(size))
+	}
+	i--
+	dAtA[i] = 0xa
+	return len(dAtA) - i, nil
+}
+
 func encodeVarintQueryAppLinks(dAtA []byte, offset int, v uint64) int {
 	offset -= sovQueryAppLinks(v)
 	base := offset
@@ -275,6 +634,38 @@ func encodeVarintQueryAppLinks(dAtA []byte, offset int, v uint64) int {
 	dAtA[offset] = uint8(v)
 	return base
 }
+func (m *QueryUserApplicationLinkRequest) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.User)
+	if l > 0 {
+		n += 1 + l + sovQueryAppLinks(uint64(l))
+	}
+	l = len(m.Application)
+	if l > 0 {
+		n += 1 + l + sovQueryAppLinks(uint64(l))
+	}
+	l = len(m.Username)
+	if l > 0 {
+		n += 1 + l + sovQueryAppLinks(uint64(l))
+	}
+	return n
+}
+
+func (m *QueryUserApplicationLinkResponse) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = m.Link.Size()
+	n += 1 + l + sovQueryAppLinks(uint64(l))
+	return n
+}
+
 func (m *QueryUserApplicationLinksRequest) Size() (n int) {
 	if m == nil {
 		return 0
@@ -311,16 +702,269 @@ func (m *QueryUserApplicationLinksResponse) Size() (n int) {
 	return n
 }
 
-func sovQueryAppLinks(x uint64) (n int) {
-	return (math_bits.Len64(x|1) + 6) / 7
-}
-func sozQueryAppLinks(x uint64) (n int) {
-	return sovQueryAppLinks(uint64((x << 1) ^ uint64((int64(x) >> 63))))
-}
-func (m *QueryUserApplicationLinksRequest) Unmarshal(dAtA []byte) error {
-	l := len(dAtA)
-	iNdEx := 0
-	for iNdEx < l {
+func (m *QueryApplicationLinkByClientIDRequest) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.ClientId)
+	if l > 0 {
+		n += 1 + l + sovQueryAppLinks(uint64(l))
+	}
+	return n
+}
+
+func (m *QueryApplicationLinkByClientIDResponse) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = m.Link.Size()
+	n += 1 + l + sovQueryAppLinks(uint64(l))
+	return n
+}
+
+func sovQueryAppLinks(x uint64) (n int) {
+	return (math_bits.Len64(x|1) + 6) / 7
+}
+func sozQueryAppLinks(x uint64) (n int) {
+	return sovQueryAppLinks(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (m *QueryUserApplicationLinkRequest) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowQueryAppLinks
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: QueryUserApplicationLinkRequest: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: QueryUserApplicationLinkRequest: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field User", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowQueryAppLinks
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthQueryAppLinks
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthQueryAppLinks
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.User = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Application", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowQueryAppLinks
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthQueryAppLinks
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthQueryAppLinks
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Application = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 3:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Username", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowQueryAppLinks
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthQueryAppLinks
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthQueryAppLinks
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Username = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipQueryAppLinks(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthQueryAppLinks
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *QueryUserApplicationLinkResponse) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowQueryAppLinks
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: QueryUserApplicationLinkResponse: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: QueryUserApplicationLinkResponse: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Link", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowQueryAppLinks
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthQueryAppLinks
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthQueryAppLinks
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := m.Link.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipQueryAppLinks(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthQueryAppLinks
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *QueryUserApplicationLinksRequest) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
 		preIndex := iNdEx
 		var wire uint64
 		for shift := uint(0); ; shift += 7 {
@@ -555,6 +1199,171 @@ func (m *QueryUserApplicationLinksResponse) Unmarshal(dAtA []byte) error {
 	}
 	return nil
 }
+func (m *QueryApplicationLinkByClientIDRequest) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowQueryAppLinks
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: QueryApplicationLinkByClientIDRequest: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: QueryApplicationLinkByClientIDRequest: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ClientId", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowQueryAppLinks
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthQueryAppLinks
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthQueryAppLinks
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.ClientId = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipQueryAppLinks(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthQueryAppLinks
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *QueryApplicationLinkByClientIDResponse) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowQueryAppLinks
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: QueryApplicationLinkByClientIDResponse: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: QueryApplicationLinkByClientIDResponse: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Link", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowQueryAppLinks
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthQueryAppLinks
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthQueryAppLinks
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := m.Link.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipQueryAppLinks(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthQueryAppLinks
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
 func skipQueryAppLinks(dAtA []byte) (n int, err error) {
 	l := len(dAtA)
 	iNdEx := 0
diff --git a/x/profiles/types/query_chain_links.pb.go b/x/profiles/types/query_chain_links.pb.go
index 98515741..c6bcef8d 100644
--- a/x/profiles/types/query_chain_links.pb.go
+++ b/x/profiles/types/query_chain_links.pb.go
@@ -27,6 +27,118 @@ var _ = math.Inf
 // proto package needs to be updated.
 const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
 
+// QueryUserChainLinkRequest represents the request that should be used in order
+// to retrieve the link associated with the provided user, for the given chain
+// and having the given target address
+type QueryUserChainLinkRequest struct {
+	// User represents the Desmos address of the user to which search the link for
+	User string `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"`
+	// ChainName contains the name of the chain to which search the link for
+	ChainName string `protobuf:"bytes,2,opt,name=chain_name,json=chainName,proto3" json:"chain_name,omitempty"`
+	// Target must contain the external address to which query the link for
+	Target string `protobuf:"bytes,3,opt,name=target,proto3" json:"target,omitempty"`
+}
+
+func (m *QueryUserChainLinkRequest) Reset()         { *m = QueryUserChainLinkRequest{} }
+func (m *QueryUserChainLinkRequest) String() string { return proto.CompactTextString(m) }
+func (*QueryUserChainLinkRequest) ProtoMessage()    {}
+func (*QueryUserChainLinkRequest) Descriptor() ([]byte, []int) {
+	return fileDescriptor_c44c4be38a628772, []int{0}
+}
+func (m *QueryUserChainLinkRequest) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *QueryUserChainLinkRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_QueryUserChainLinkRequest.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *QueryUserChainLinkRequest) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_QueryUserChainLinkRequest.Merge(m, src)
+}
+func (m *QueryUserChainLinkRequest) XXX_Size() int {
+	return m.Size()
+}
+func (m *QueryUserChainLinkRequest) XXX_DiscardUnknown() {
+	xxx_messageInfo_QueryUserChainLinkRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_QueryUserChainLinkRequest proto.InternalMessageInfo
+
+func (m *QueryUserChainLinkRequest) GetUser() string {
+	if m != nil {
+		return m.User
+	}
+	return ""
+}
+
+func (m *QueryUserChainLinkRequest) GetChainName() string {
+	if m != nil {
+		return m.ChainName
+	}
+	return ""
+}
+
+func (m *QueryUserChainLinkRequest) GetTarget() string {
+	if m != nil {
+		return m.Target
+	}
+	return ""
+}
+
+// QueryUserChainLinkResponse contains the data that is returned when querying a
+// specific chain link
+type QueryUserChainLinkResponse struct {
+	Link ChainLink `protobuf:"bytes,1,opt,name=link,proto3" json:"link"`
+}
+
+func (m *QueryUserChainLinkResponse) Reset()         { *m = QueryUserChainLinkResponse{} }
+func (m *QueryUserChainLinkResponse) String() string { return proto.CompactTextString(m) }
+func (*QueryUserChainLinkResponse) ProtoMessage()    {}
+func (*QueryUserChainLinkResponse) Descriptor() ([]byte, []int) {
+	return fileDescriptor_c44c4be38a628772, []int{1}
+}
+func (m *QueryUserChainLinkResponse) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *QueryUserChainLinkResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_QueryUserChainLinkResponse.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *QueryUserChainLinkResponse) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_QueryUserChainLinkResponse.Merge(m, src)
+}
+func (m *QueryUserChainLinkResponse) XXX_Size() int {
+	return m.Size()
+}
+func (m *QueryUserChainLinkResponse) XXX_DiscardUnknown() {
+	xxx_messageInfo_QueryUserChainLinkResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_QueryUserChainLinkResponse proto.InternalMessageInfo
+
+func (m *QueryUserChainLinkResponse) GetLink() ChainLink {
+	if m != nil {
+		return m.Link
+	}
+	return ChainLink{}
+}
+
 // QueryUserChainLinksRequest is the request type for the
 // Query/UserChainLinks RPC endpoint
 type QueryUserChainLinksRequest struct {
@@ -39,7 +151,7 @@ func (m *QueryUserChainLinksRequest) Reset()         { *m = QueryUserChainLinksR
 func (m *QueryUserChainLinksRequest) String() string { return proto.CompactTextString(m) }
 func (*QueryUserChainLinksRequest) ProtoMessage()    {}
 func (*QueryUserChainLinksRequest) Descriptor() ([]byte, []int) {
-	return fileDescriptor_c44c4be38a628772, []int{0}
+	return fileDescriptor_c44c4be38a628772, []int{2}
 }
 func (m *QueryUserChainLinksRequest) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -94,7 +206,7 @@ func (m *QueryUserChainLinksResponse) Reset()         { *m = QueryUserChainLinks
 func (m *QueryUserChainLinksResponse) String() string { return proto.CompactTextString(m) }
 func (*QueryUserChainLinksResponse) ProtoMessage()    {}
 func (*QueryUserChainLinksResponse) Descriptor() ([]byte, []int) {
-	return fileDescriptor_c44c4be38a628772, []int{1}
+	return fileDescriptor_c44c4be38a628772, []int{3}
 }
 func (m *QueryUserChainLinksResponse) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -138,6 +250,8 @@ func (m *QueryUserChainLinksResponse) GetPagination() *query.PageResponse {
 }
 
 func init() {
+	proto.RegisterType((*QueryUserChainLinkRequest)(nil), "desmos.profiles.v1beta1.QueryUserChainLinkRequest")
+	proto.RegisterType((*QueryUserChainLinkResponse)(nil), "desmos.profiles.v1beta1.QueryUserChainLinkResponse")
 	proto.RegisterType((*QueryUserChainLinksRequest)(nil), "desmos.profiles.v1beta1.QueryUserChainLinksRequest")
 	proto.RegisterType((*QueryUserChainLinksResponse)(nil), "desmos.profiles.v1beta1.QueryUserChainLinksResponse")
 }
@@ -147,30 +261,111 @@ func init() {
 }
 
 var fileDescriptor_c44c4be38a628772 = []byte{
-	// 368 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x92, 0xcf, 0x4e, 0xea, 0x40,
-	0x14, 0xc6, 0x3b, 0xf7, 0x72, 0x6f, 0x62, 0xd9, 0x35, 0x26, 0x42, 0x35, 0x95, 0xb0, 0x50, 0x62,
-	0xe2, 0x8c, 0xe0, 0xde, 0x05, 0x26, 0xea, 0xc2, 0x85, 0x36, 0x71, 0xe3, 0x86, 0x4c, 0xe1, 0x50,
-	0x1a, 0xcb, 0x9c, 0xd2, 0x69, 0x0d, 0xbc, 0x85, 0x4f, 0xe1, 0xb3, 0xb0, 0x64, 0xe9, 0xca, 0x18,
-	0x78, 0x11, 0xd3, 0x99, 0xe1, 0x4f, 0x22, 0xc4, 0xdd, 0x9c, 0x9c, 0xf3, 0x7d, 0xdf, 0x6f, 0x66,
-	0x8e, 0xcd, 0x7a, 0x20, 0x87, 0x28, 0x59, 0x92, 0x62, 0x3f, 0x8a, 0x41, 0xb2, 0xd7, 0x66, 0x00,
-	0x19, 0x6f, 0xb2, 0x51, 0x0e, 0xe9, 0xa4, 0xd3, 0x1d, 0xf0, 0x48, 0x74, 0xe2, 0x48, 0xbc, 0x48,
-	0x9a, 0xa4, 0x98, 0xa1, 0x73, 0xa0, 0x05, 0x74, 0x29, 0xa0, 0x46, 0xe0, 0xee, 0x87, 0x18, 0xa2,
-	0x9a, 0x61, 0xc5, 0x49, 0x8f, 0xbb, 0x47, 0x21, 0x62, 0x18, 0x03, 0xe3, 0x49, 0xc4, 0xb8, 0x10,
-	0x98, 0xf1, 0x2c, 0x42, 0x61, 0xcc, 0xdc, 0xaa, 0xe9, 0xaa, 0x2a, 0xc8, 0xfb, 0x8c, 0x8b, 0x89,
-	0x69, 0x5d, 0xec, 0x02, 0x1b, 0x62, 0x0f, 0x62, 0xf9, 0x93, 0xcc, 0xad, 0x76, 0xb1, 0x50, 0x74,
-	0x34, 0x83, 0x2e, 0x4c, 0xeb, 0x4c, 0x57, 0x2c, 0xe0, 0x12, 0xf4, 0xcd, 0x56, 0x76, 0x09, 0x0f,
-	0x23, 0xa1, 0xa0, 0xf4, 0x6c, 0x7d, 0x6c, 0xbb, 0x8f, 0xc5, 0xc4, 0x93, 0x84, 0xf4, 0xba, 0x08,
-	0xb9, 0x2f, 0x32, 0x7c, 0x18, 0xe5, 0x20, 0x33, 0xc7, 0xb1, 0x4b, 0xb9, 0x84, 0xb4, 0x42, 0x6a,
-	0xa4, 0xb1, 0xe7, 0xab, 0xb3, 0x73, 0x63, 0xdb, 0x6b, 0x97, 0xca, 0x9f, 0x1a, 0x69, 0x94, 0x5b,
-	0x27, 0xd4, 0x00, 0x14, 0x91, 0x54, 0x45, 0x2e, 0x5f, 0x8a, 0x3e, 0xf0, 0x10, 0x8c, 0x9f, 0xbf,
-	0xa1, 0xac, 0xbf, 0x13, 0xfb, 0x70, 0x6b, 0xb4, 0x4c, 0x50, 0x48, 0x70, 0xae, 0xec, 0x7f, 0xea,
-	0xbe, 0x15, 0x52, 0xfb, 0xdb, 0x28, 0xb7, 0xea, 0x74, 0xc7, 0x57, 0xd0, 0x95, 0xb6, 0x5d, 0x9a,
-	0x7e, 0x1e, 0x5b, 0xbe, 0x96, 0x39, 0xb7, 0x5b, 0x38, 0x4f, 0x7f, 0xe5, 0xd4, 0xe1, 0x9b, 0xa0,
-	0xed, 0xbb, 0xe9, 0xdc, 0x23, 0xb3, 0xb9, 0x47, 0xbe, 0xe6, 0x1e, 0x79, 0x5b, 0x78, 0xd6, 0x6c,
-	0xe1, 0x59, 0x1f, 0x0b, 0xcf, 0x7a, 0xa6, 0x61, 0x94, 0x0d, 0xf2, 0x80, 0x76, 0x71, 0x68, 0x36,
-	0xeb, 0x3c, 0xe6, 0x81, 0x5c, 0x6e, 0xd9, 0x78, 0xfd, 0x9d, 0xd9, 0x24, 0x01, 0x19, 0xfc, 0x57,
-	0x6f, 0x7e, 0xf9, 0x1d, 0x00, 0x00, 0xff, 0xff, 0x0a, 0xd8, 0xa6, 0x8b, 0x87, 0x02, 0x00, 0x00,
+	// 420 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x53, 0xbd, 0xae, 0xd3, 0x30,
+	0x14, 0x4e, 0xb8, 0xe5, 0x4a, 0xd7, 0x77, 0x8b, 0x10, 0xa4, 0x01, 0xc2, 0x55, 0x06, 0x40, 0x48,
+	0xd8, 0xb4, 0xac, 0x88, 0xa1, 0x48, 0xc0, 0x80, 0x10, 0x44, 0x62, 0xe9, 0x52, 0x39, 0xed, 0xa9,
+	0x6b, 0x35, 0xb1, 0xd3, 0xd8, 0x41, 0xed, 0x5b, 0xf0, 0x14, 0x3c, 0x4b, 0xc7, 0x8e, 0x4c, 0x08,
+	0xb5, 0x2f, 0x82, 0x62, 0xbb, 0x3f, 0x12, 0x29, 0x88, 0xcd, 0x27, 0xe7, 0x7c, 0xe7, 0xfb, 0x89,
+	0x8d, 0xc8, 0x04, 0x54, 0x21, 0x15, 0x29, 0x2b, 0x39, 0xe5, 0x39, 0x28, 0xf2, 0xb5, 0x97, 0x81,
+	0xa6, 0x3d, 0xb2, 0xa8, 0xa1, 0x5a, 0x8d, 0xc6, 0x33, 0xca, 0xc5, 0x28, 0xe7, 0x62, 0xae, 0x70,
+	0x59, 0x49, 0x2d, 0x83, 0x7b, 0x16, 0x80, 0xf7, 0x00, 0xec, 0x00, 0xd1, 0x1d, 0x26, 0x99, 0x34,
+	0x33, 0xa4, 0x39, 0xd9, 0xf1, 0xe8, 0x01, 0x93, 0x92, 0xe5, 0x40, 0x68, 0xc9, 0x09, 0x15, 0x42,
+	0x6a, 0xaa, 0xb9, 0x14, 0x6e, 0x59, 0xd4, 0x75, 0x5d, 0x53, 0x65, 0xf5, 0x94, 0x50, 0xb1, 0x72,
+	0xad, 0x17, 0xe7, 0x84, 0x15, 0x72, 0x02, 0xb9, 0xfa, 0x53, 0x59, 0xd4, 0x1d, 0xcb, 0x06, 0x31,
+	0xb2, 0x1a, 0x6c, 0xe1, 0x5a, 0xcf, 0x6c, 0x45, 0x32, 0xaa, 0xc0, 0x3a, 0x3b, 0xac, 0x2b, 0x29,
+	0xe3, 0xc2, 0x88, 0xb2, 0xb3, 0xc9, 0x14, 0x75, 0x3f, 0x37, 0x13, 0x5f, 0x14, 0x54, 0x6f, 0x1a,
+	0x92, 0x0f, 0x5c, 0xcc, 0x53, 0x58, 0xd4, 0xa0, 0x74, 0x10, 0xa0, 0x4e, 0xad, 0xa0, 0x0a, 0xfd,
+	0x1b, 0xff, 0xe9, 0x55, 0x6a, 0xce, 0xc1, 0x43, 0x84, 0xac, 0x18, 0x41, 0x0b, 0x08, 0x6f, 0x99,
+	0xce, 0x95, 0xf9, 0xf2, 0x91, 0x16, 0x10, 0xdc, 0x45, 0x97, 0x9a, 0x56, 0x0c, 0x74, 0x78, 0x61,
+	0x5a, 0xae, 0x4a, 0x86, 0x28, 0x6a, 0xe3, 0x51, 0xa5, 0x14, 0x0a, 0x82, 0x57, 0xa8, 0xd3, 0x78,
+	0x33, 0x44, 0xd7, 0xfd, 0x04, 0x9f, 0x49, 0x1d, 0x1f, 0x90, 0x83, 0xce, 0xfa, 0xe7, 0x23, 0x2f,
+	0x35, 0xa8, 0x64, 0xd9, 0xb6, 0x5b, 0xfd, 0xcd, 0xc4, 0x5b, 0x84, 0x8e, 0x49, 0x18, 0x13, 0xd7,
+	0xfd, 0xc7, 0xd8, 0x85, 0xd8, 0xc4, 0x86, 0x4d, 0x6c, 0x07, 0xde, 0x4f, 0x94, 0x81, 0xdb, 0x97,
+	0x9e, 0x20, 0x93, 0xef, 0x3e, 0xba, 0xdf, 0x4a, 0xed, 0x7c, 0xbd, 0x46, 0xb7, 0xcd, 0x3f, 0x0b,
+	0xfd, 0x9b, 0x8b, 0xff, 0x32, 0x66, 0x61, 0xc1, 0xbb, 0x16, 0x9d, 0x4f, 0xfe, 0xa9, 0xd3, 0x92,
+	0x9f, 0x0a, 0x1d, 0xbc, 0x5f, 0x6f, 0x63, 0x7f, 0xb3, 0x8d, 0xfd, 0x5f, 0xdb, 0xd8, 0xff, 0xb6,
+	0x8b, 0xbd, 0xcd, 0x2e, 0xf6, 0x7e, 0xec, 0x62, 0x6f, 0x88, 0x19, 0xd7, 0xb3, 0x3a, 0xc3, 0x63,
+	0x59, 0xb8, 0xd7, 0xf1, 0x3c, 0xa7, 0x99, 0xda, 0xbf, 0x94, 0xe5, 0xf1, 0x4a, 0xea, 0x55, 0x09,
+	0x2a, 0xbb, 0x34, 0xf7, 0xe6, 0xe5, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0x63, 0x42, 0xc8, 0xc6,
+	0x4b, 0x03, 0x00, 0x00,
+}
+
+func (m *QueryUserChainLinkRequest) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *QueryUserChainLinkRequest) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *QueryUserChainLinkRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.Target) > 0 {
+		i -= len(m.Target)
+		copy(dAtA[i:], m.Target)
+		i = encodeVarintQueryChainLinks(dAtA, i, uint64(len(m.Target)))
+		i--
+		dAtA[i] = 0x1a
+	}
+	if len(m.ChainName) > 0 {
+		i -= len(m.ChainName)
+		copy(dAtA[i:], m.ChainName)
+		i = encodeVarintQueryChainLinks(dAtA, i, uint64(len(m.ChainName)))
+		i--
+		dAtA[i] = 0x12
+	}
+	if len(m.User) > 0 {
+		i -= len(m.User)
+		copy(dAtA[i:], m.User)
+		i = encodeVarintQueryChainLinks(dAtA, i, uint64(len(m.User)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *QueryUserChainLinkResponse) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *QueryUserChainLinkResponse) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *QueryUserChainLinkResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	{
+		size, err := m.Link.MarshalToSizedBuffer(dAtA[:i])
+		if err != nil {
+			return 0, err
+		}
+		i -= size
+		i = encodeVarintQueryChainLinks(dAtA, i, uint64(size))
+	}
+	i--
+	dAtA[i] = 0xa
+	return len(dAtA) - i, nil
 }
 
 func (m *QueryUserChainLinksRequest) Marshal() (dAtA []byte, err error) {
@@ -275,6 +470,38 @@ func encodeVarintQueryChainLinks(dAtA []byte, offset int, v uint64) int {
 	dAtA[offset] = uint8(v)
 	return base
 }
+func (m *QueryUserChainLinkRequest) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.User)
+	if l > 0 {
+		n += 1 + l + sovQueryChainLinks(uint64(l))
+	}
+	l = len(m.ChainName)
+	if l > 0 {
+		n += 1 + l + sovQueryChainLinks(uint64(l))
+	}
+	l = len(m.Target)
+	if l > 0 {
+		n += 1 + l + sovQueryChainLinks(uint64(l))
+	}
+	return n
+}
+
+func (m *QueryUserChainLinkResponse) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = m.Link.Size()
+	n += 1 + l + sovQueryChainLinks(uint64(l))
+	return n
+}
+
 func (m *QueryUserChainLinksRequest) Size() (n int) {
 	if m == nil {
 		return 0
@@ -317,6 +544,235 @@ func sovQueryChainLinks(x uint64) (n int) {
 func sozQueryChainLinks(x uint64) (n int) {
 	return sovQueryChainLinks(uint64((x << 1) ^ uint64((int64(x) >> 63))))
 }
+func (m *QueryUserChainLinkRequest) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowQueryChainLinks
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: QueryUserChainLinkRequest: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: QueryUserChainLinkRequest: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field User", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowQueryChainLinks
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthQueryChainLinks
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthQueryChainLinks
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.User = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ChainName", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowQueryChainLinks
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthQueryChainLinks
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthQueryChainLinks
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.ChainName = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 3:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Target", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowQueryChainLinks
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthQueryChainLinks
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthQueryChainLinks
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Target = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipQueryChainLinks(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthQueryChainLinks
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *QueryUserChainLinkResponse) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowQueryChainLinks
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: QueryUserChainLinkResponse: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: QueryUserChainLinkResponse: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Link", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowQueryChainLinks
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthQueryChainLinks
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthQueryChainLinks
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := m.Link.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipQueryChainLinks(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthQueryChainLinks
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
 func (m *QueryUserChainLinksRequest) Unmarshal(dAtA []byte) error {
 	l := len(dAtA)
 	iNdEx := 0
diff --git a/x/staging/posts/client/cli/query.go b/x/staging/posts/client/cli/query.go
index 46d3ddcf..e1f0b038 100644
--- a/x/staging/posts/client/cli/query.go
+++ b/x/staging/posts/client/cli/query.go
@@ -252,3 +252,39 @@ func GetCmdQueryParams() *cobra.Command {
 
 	return cmd
 }
+
+// GetCmdQueryPostComments returns the command allowing to query the comments of a post
+func GetCmdQueryPostComments() *cobra.Command {
+	cmd := &cobra.Command{
+		Use:   "post-comments [post-id]",
+		Short: "Retrieve tha comments of the post with the given id",
+		Args:  cobra.ExactArgs(1),
+		RunE: func(cmd *cobra.Command, args []string) error {
+			clientCtx, err := client.GetClientQueryContext(cmd)
+			if err != nil {
+				return err
+			}
+			queryClient := types.NewQueryClient(clientCtx)
+
+			pageReq, err := client.ReadPageRequest(cmd.Flags())
+			if err != nil {
+				return err
+			}
+
+			res, err := queryClient.PostComments(
+				context.Background(),
+				&types.QueryPostCommentsRequest{PostId: args[0], Pagination: pageReq},
+			)
+			if err != nil {
+				return err
+			}
+
+			return clientCtx.PrintProto(res)
+		},
+	}
+
+	flags.AddQueryFlagsToCmd(cmd)
+	flags.AddPaginationFlagsToCmd(cmd, types.QueryPostComments)
+
+	return cmd
+}
diff --git a/x/staging/posts/keeper/grpc_query.go b/x/staging/posts/keeper/grpc_query.go
index 0736fcd9..66cede8a 100644
--- a/x/staging/posts/keeper/grpc_query.go
+++ b/x/staging/posts/keeper/grpc_query.go
@@ -138,3 +138,37 @@ func (k Keeper) Params(goCtx context.Context, _ *types.QueryParamsRequest) (*typ
 	params := k.GetParams(ctx)
 	return &types.QueryParamsResponse{Params: params}, nil
 }
+
+// PostComments implements the Query/PostComments gRPC method
+func (k Keeper) PostComments(
+	goCtx context.Context, req *types.QueryPostCommentsRequest,
+) (*types.QueryPostCommentsResponse, error) {
+	if !types.IsValidPostID(req.PostId) {
+		return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "invalid post id: %s", req.PostId)
+	}
+
+	ctx := sdk.UnwrapSDKContext(goCtx)
+	_, found := k.GetPost(ctx, req.PostId)
+	if !found {
+		return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "post with id %s not found", req.PostId)
+	}
+
+	store := ctx.KVStore(k.storeKey)
+	commetsStore := prefix.NewStore(store, types.PostCommentsPrefix(req.PostId))
+
+	var comments []types.Post
+	pageRes, err := query.FilteredPaginate(commetsStore, req.Pagination, func(key []byte, value []byte, accumulate bool) (bool, error) {
+		// it assumes that the comment must exist
+		comment, _ := k.GetPost(ctx, string(value))
+		if accumulate {
+			comments = append(comments, comment)
+		}
+		return true, nil
+	})
+
+	if err != nil {
+		return nil, status.Error(codes.Internal, err.Error())
+	}
+
+	return &types.QueryPostCommentsResponse{Comments: comments, Pagination: pageRes}, nil
+}
diff --git a/x/staging/posts/keeper/keeper.go b/x/staging/posts/keeper/keeper.go
index cbcc896a..6f740244 100644
--- a/x/staging/posts/keeper/keeper.go
+++ b/x/staging/posts/keeper/keeper.go
@@ -69,12 +69,9 @@ func (k Keeper) SavePost(ctx sdk.Context, post types.Post) {
 
 	// Save the comments to the parent post, if it is valid
 	if types.IsValidPostID(post.ParentID) {
-		parentCommentsKey := types.PostCommentsStoreKey(post.ParentID)
-
-		var commentsIDs types.CommentIDs
-		k.cdc.MustUnmarshalBinaryBare(store.Get(parentCommentsKey), &commentsIDs)
-		if editedIDs, appended := commentsIDs.AppendIfMissing(post.PostID); appended {
-			store.Set(parentCommentsKey, k.cdc.MustMarshalBinaryBare(&editedIDs))
+		commentKey := types.CommentsStoreKey(post.ParentID, post.PostID)
+		if !store.Has(commentKey) {
+			store.Set(commentKey, []byte(post.PostID))
 		}
 	}
 }
@@ -97,17 +94,6 @@ func (k Keeper) GetPost(ctx sdk.Context, id string) (post types.Post, found bool
 	return post, true
 }
 
-// GetPostChildrenIDs returns the IDs of all the children posts associated to the post
-// having the given postID
-// nolint: interfacer
-func (k Keeper) GetPostChildrenIDs(ctx sdk.Context, postID string) []string {
-	store := ctx.KVStore(k.storeKey)
-
-	var ids types.CommentIDs
-	k.cdc.MustUnmarshalBinaryBare(store.Get(types.PostCommentsStoreKey(postID)), &ids)
-	return ids.Ids
-}
-
 // GetPosts returns the list of all the posts that are stored into the current state
 func (k Keeper) GetPosts(ctx sdk.Context) []types.Post {
 	var posts []types.Post
diff --git a/x/staging/posts/keeper/msgs_server.go b/x/staging/posts/keeper/msgs_server.go
index 9560b9d4..7ac2478a 100644
--- a/x/staging/posts/keeper/msgs_server.go
+++ b/x/staging/posts/keeper/msgs_server.go
@@ -93,6 +93,11 @@ func (k msgServer) CreatePost(goCtx context.Context, msg *types.MsgCreatePost) (
 			return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest,
 				"post with id %s does not allow comments", parentPost.PostID)
 		}
+
+		if parentPost.Subspace != post.Subspace {
+			return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest,
+				"the subspace of the comment is not same as the parent post with id %s", parentPost.PostID)
+		}
 	}
 
 	// Save the post
diff --git a/x/staging/posts/simulation/decoder.go b/x/staging/posts/simulation/decoder.go
index 7a7b7607..017857d7 100644
--- a/x/staging/posts/simulation/decoder.go
+++ b/x/staging/posts/simulation/decoder.go
@@ -21,18 +21,18 @@ func NewDecodeStore(cdc codec.Marshaler) func(kvA, kvB kv.Pair) string {
 			cdc.MustUnmarshalBinaryBare(kvB.Value, &postB)
 			return fmt.Sprintf("PostA: %s\nPostB: %s\n", postA.String(), postB.String())
 
-		case bytes.HasPrefix(kvA.Key, types.PostCommentsStorePrefix):
-			var commentsA, commentsB types.CommentIDs
-			cdc.MustUnmarshalBinaryBare(kvA.Value, &commentsA)
-			cdc.MustUnmarshalBinaryBare(kvB.Value, &commentsB)
-			return fmt.Sprintf("CommentsA: %s\nCommentsB: %s\n", commentsA, commentsB)
-
 		case bytes.HasPrefix(kvA.Key, types.PostReactionsStorePrefix):
 			var postReactionsA, postReactionsB types.PostReactions
 			cdc.MustUnmarshalBinaryBare(kvA.Value, &postReactionsA)
 			cdc.MustUnmarshalBinaryBare(kvB.Value, &postReactionsB)
 			return fmt.Sprintf("PostReactionsA: %s\nPostReactionsB: %s\n", postReactionsA, postReactionsB)
 
+		case bytes.HasPrefix(kvA.Key, types.CommentsStorePrefix):
+			var commentIDA, commentIDB string
+			commentIDA = string(kvA.Value)
+			commentIDB = string(kvA.Value)
+			return fmt.Sprintf("CommentA: %s\nCommentB: %s\n", commentIDA, commentIDB)
+
 		case bytes.HasPrefix(kvA.Key, types.ReactionsStorePrefix):
 			var reactionA, reactionB types.RegisteredReaction
 			cdc.MustUnmarshalBinaryBare(kvA.Value, &reactionA)
diff --git a/x/staging/posts/types/keys.go b/x/staging/posts/types/keys.go
index 48a0b6e2..ff14bc38 100644
--- a/x/staging/posts/types/keys.go
+++ b/x/staging/posts/types/keys.go
@@ -25,6 +25,7 @@ const (
 	QuerierRoute             = ModuleName
 	QueryPost                = "post"
 	QueryPosts               = "posts"
+	QueryPostComments        = "post-comments"
 	QueryReports             = "reports"
 	QueryUserAnswers         = "user-answers"
 	QueryRegisteredReactions = "registered-reactions"
@@ -38,7 +39,7 @@ var (
 	ModuleAddress = authtypes.NewModuleAddress(ModuleName)
 
 	PostStorePrefix          = []byte("post")
-	PostCommentsStorePrefix  = []byte("comments")
+	CommentsStorePrefix      = []byte("comments")
 	SubspacePostPrefix       = []byte("subspace")
 	PostReactionsStorePrefix = []byte("p_reactions")
 	ReactionsStorePrefix     = []byte("reactions")
@@ -61,9 +62,14 @@ func PostStoreKey(id string) []byte {
 	return append(PostStorePrefix, []byte(id)...)
 }
 
-// PostCommentsStoreKey turns an id to a key used to store a post's comments into the posts store
-func PostCommentsStoreKey(id string) []byte {
-	return append(PostCommentsStorePrefix, []byte(id)...)
+// PostCommentsPrefix returns the prefix used to store all the comments for the parent post having the given id
+func PostCommentsPrefix(postID string) []byte {
+	return append(CommentsStorePrefix, []byte(postID)...)
+}
+
+// CommentsStoreKey returns the store key used to store the comment containing the given data
+func CommentsStoreKey(parentID, commentID string) []byte {
+	return append(PostCommentsPrefix(parentID), []byte(commentID)...)
 }
 
 // SubspacePostPrefix returns the prefix used to store all the posts present inside the subspace having the given id
diff --git a/x/staging/posts/types/posts.go b/x/staging/posts/types/posts.go
index 7998a213..aa8c3ae9 100644
--- a/x/staging/posts/types/posts.go
+++ b/x/staging/posts/types/posts.go
@@ -118,19 +118,6 @@ func IsValidCommentsState(commentsState CommentsState) bool {
 
 // ___________________________________________________________________________________________________________________
 
-// AppendIfMissing appends the given id to the ids slice, if not present yet.
-// If appended, returns the new slice and true. Otherwise, returns the original slice and false.
-func (ids CommentIDs) AppendIfMissing(id string) (CommentIDs, bool) {
-	for _, existing := range ids.Ids {
-		if existing == id {
-			return ids, false
-		}
-	}
-	return CommentIDs{Ids: append(ids.Ids, id)}, true
-}
-
-// ___________________________________________________________________________________________________________________
-
 // NewAttachment builds a new Attachment instance with the provided data
 func NewAttachment(uri, mimeType string, tags []string) Attachment {
 	return Attachment{
diff --git a/x/staging/posts/types/posts.pb.go b/x/staging/posts/types/posts.pb.go
index 659b69e0..cd3ce31e 100644
--- a/x/staging/posts/types/posts.pb.go
+++ b/x/staging/posts/types/posts.pb.go
@@ -184,51 +184,6 @@ func (m *Post) GetPollData() *PollData {
 	return nil
 }
 
-// CommentIDs wraps the list of comments of a post
-type CommentIDs struct {
-	Ids []string `protobuf:"bytes,1,rep,name=ids,proto3" json:"ids,omitempty"`
-}
-
-func (m *CommentIDs) Reset()         { *m = CommentIDs{} }
-func (m *CommentIDs) String() string { return proto.CompactTextString(m) }
-func (*CommentIDs) ProtoMessage()    {}
-func (*CommentIDs) Descriptor() ([]byte, []int) {
-	return fileDescriptor_46085d0cf4c5bade, []int{1}
-}
-func (m *CommentIDs) XXX_Unmarshal(b []byte) error {
-	return m.Unmarshal(b)
-}
-func (m *CommentIDs) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
-	if deterministic {
-		return xxx_messageInfo_CommentIDs.Marshal(b, m, deterministic)
-	} else {
-		b = b[:cap(b)]
-		n, err := m.MarshalToSizedBuffer(b)
-		if err != nil {
-			return nil, err
-		}
-		return b[:n], nil
-	}
-}
-func (m *CommentIDs) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_CommentIDs.Merge(m, src)
-}
-func (m *CommentIDs) XXX_Size() int {
-	return m.Size()
-}
-func (m *CommentIDs) XXX_DiscardUnknown() {
-	xxx_messageInfo_CommentIDs.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_CommentIDs proto.InternalMessageInfo
-
-func (m *CommentIDs) GetIds() []string {
-	if m != nil {
-		return m.Ids
-	}
-	return nil
-}
-
 // Attachment contains the information representing any type of file provided
 // with a post. This file can be an image or a multimedia file (vocals, video,
 // documents, etc.).
@@ -242,7 +197,7 @@ func (m *Attachment) Reset()         { *m = Attachment{} }
 func (m *Attachment) String() string { return proto.CompactTextString(m) }
 func (*Attachment) ProtoMessage()    {}
 func (*Attachment) Descriptor() ([]byte, []int) {
-	return fileDescriptor_46085d0cf4c5bade, []int{2}
+	return fileDescriptor_46085d0cf4c5bade, []int{1}
 }
 func (m *Attachment) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -303,7 +258,7 @@ func (m *Attribute) Reset()         { *m = Attribute{} }
 func (m *Attribute) String() string { return proto.CompactTextString(m) }
 func (*Attribute) ProtoMessage()    {}
 func (*Attribute) Descriptor() ([]byte, []int) {
-	return fileDescriptor_46085d0cf4c5bade, []int{3}
+	return fileDescriptor_46085d0cf4c5bade, []int{2}
 }
 func (m *Attribute) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -349,7 +304,6 @@ func (m *Attribute) GetValue() string {
 func init() {
 	proto.RegisterEnum("desmos.posts.v1beta1.CommentsState", CommentsState_name, CommentsState_value)
 	proto.RegisterType((*Post)(nil), "desmos.posts.v1beta1.Post")
-	proto.RegisterType((*CommentIDs)(nil), "desmos.posts.v1beta1.CommentIDs")
 	proto.RegisterType((*Attachment)(nil), "desmos.posts.v1beta1.Attachment")
 	proto.RegisterType((*Attribute)(nil), "desmos.posts.v1beta1.Attribute")
 }
@@ -357,62 +311,61 @@ func init() {
 func init() { proto.RegisterFile("desmos/posts/v1beta1/posts.proto", fileDescriptor_46085d0cf4c5bade) }
 
 var fileDescriptor_46085d0cf4c5bade = []byte{
-	// 869 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x55, 0x3f, 0x6f, 0xdb, 0x46,
-	0x14, 0x17, 0x23, 0xc7, 0x92, 0x4e, 0x88, 0x2b, 0x5c, 0x95, 0x82, 0x15, 0x0c, 0x1e, 0xc1, 0x02,
-	0x85, 0xd0, 0xc6, 0x24, 0x62, 0x77, 0x4a, 0xdb, 0xc1, 0xb4, 0x54, 0x40, 0xad, 0x1d, 0xab, 0xb4,
-	0x8c, 0x00, 0xed, 0x20, 0x9c, 0xc4, 0x0b, 0x73, 0x08, 0xa9, 0x23, 0x74, 0xa7, 0xb8, 0xfa, 0x06,
-	0x45, 0xa6, 0x8c, 0x5d, 0x02, 0x04, 0xe8, 0x50, 0xa0, 0x5f, 0xa2, 0xe8, 0x16, 0x74, 0xca, 0xd8,
-	0x89, 0x29, 0xe4, 0xa5, 0xf0, 0xa8, 0x4f, 0x50, 0x1c, 0x79, 0x14, 0x65, 0x43, 0x41, 0x87, 0x6c,
-	0x8f, 0xef, 0xfd, 0x7e, 0xbf, 0xf7, 0xe7, 0xde, 0x93, 0x80, 0xe9, 0x13, 0x1e, 0x31, 0xee, 0xc4,
-	0x8c, 0x0b, 0xee, 0x3c, 0xbb, 0x3f, 0x22, 0x02, 0xdf, 0xcf, 0xbe, 0xec, 0x78, 0xca, 0x04, 0x83,
-	0xcd, 0x0c, 0x61, 0x67, 0x3e, 0x85, 0x68, 0x35, 0x03, 0x16, 0xb0, 0x14, 0xe0, 0x48, 0x2b, 0xc3,
-	0xb6, 0x50, 0xc0, 0x58, 0x10, 0x12, 0x27, 0xfd, 0x1a, 0xcd, 0x1e, 0x3b, 0x82, 0x46, 0x84, 0x0b,
-	0x1c, 0xc5, 0x0a, 0xf0, 0xae, 0x74, 0x61, 0xa8, 0xd2, 0x59, 0x7f, 0x55, 0xc0, 0x56, 0x9f, 0x71,
-	0x01, 0xbf, 0x04, 0x15, 0x89, 0x1a, 0x52, 0x5f, 0xd7, 0x4c, 0xad, 0x5d, 0x73, 0xad, 0x45, 0x82,
-	0xb6, 0x65, 0xa8, 0xd7, 0xb9, 0x4a, 0x50, 0x1e, 0x5c, 0x26, 0xa8, 0x36, 0xc7, 0x51, 0xf8, 0xc0,
-	0xa2, 0xbe, 0xe5, 0x6d, 0x4b, 0x6f, 0xcf, 0x87, 0xdf, 0x82, 0x5a, 0x8c, 0xa7, 0x64, 0x92, 0xd2,
-	0x6f, 0xa5, 0xf4, 0xbd, 0x45, 0x82, 0xaa, 0xfd, 0xd4, 0x99, 0x0a, 0x14, 0x80, 0x65, 0x82, 0x1a,
-	0x99, 0xc4, 0xca, 0x65, 0x79, 0xd5, 0xcc, 0xee, 0xf9, 0xf0, 0x1e, 0xa8, 0x44, 0x84, 0x73, 0x1c,
-	0x10, 0xbd, 0x9c, 0x2a, 0xc1, 0x65, 0x82, 0x76, 0x32, 0x82, 0x0a, 0x58, 0x5e, 0x0e, 0x81, 0x7d,
-	0x50, 0x19, 0x4f, 0x09, 0x16, 0xc4, 0xd7, 0xb7, 0x4c, 0xad, 0x5d, 0xdf, 0x6f, 0xd9, 0xd9, 0x50,
-	0xec, 0x7c, 0x28, 0xf6, 0x20, 0x1f, 0x8a, 0xdb, 0x7a, 0x9d, 0xa0, 0x52, 0xa1, 0xa6, 0x88, 0xd6,
-	0x8b, 0xb7, 0x48, 0xf3, 0x72, 0x19, 0xf8, 0x08, 0xd4, 0x43, 0xcc, 0xc5, 0x90, 0xf8, 0x54, 0xaa,
-	0xde, 0x7e, 0x2f, 0x55, 0x20, 0xa5, 0xba, 0xa9, 0x12, 0xbc, 0x00, 0x3b, 0x63, 0x16, 0x45, 0x64,
-	0x22, 0xf8, 0x90, 0x0b, 0x2c, 0x88, 0xbe, 0x6d, 0x6a, 0xed, 0x9d, 0xfd, 0x4f, 0xec, 0x4d, 0x4f,
-	0x6e, 0x1f, 0x29, 0xec, 0x99, 0x84, 0xba, 0x9f, 0x5f, 0x25, 0xe8, 0x06, 0x7d, 0x99, 0xa0, 0xbb,
-	0x2a, 0xe5, 0x35, 0xbf, 0xe5, 0xdd, 0x19, 0xaf, 0x73, 0xa1, 0x03, 0xaa, 0x7c, 0x36, 0xe2, 0x31,
-	0x1e, 0x13, 0xbd, 0x92, 0x8e, 0xf4, 0xc3, 0x65, 0x82, 0x3e, 0xc8, 0xb8, 0x79, 0xc4, 0xf2, 0x56,
-	0x20, 0xf8, 0x9b, 0x06, 0xee, 0x62, 0xdf, 0xa7, 0x82, 0xb2, 0x09, 0x0e, 0x87, 0x58, 0x88, 0x29,
-	0x1d, 0xcd, 0x04, 0xe1, 0x7a, 0xd5, 0x2c, 0xb7, 0xeb, 0xfb, 0x68, 0x73, 0xc5, 0x87, 0x39, 0xce,
-	0xfd, 0x5e, 0x8e, 0xe4, 0x2a, 0x41, 0x68, 0xa3, 0xca, 0x3d, 0x16, 0x51, 0x41, 0xa2, 0x58, 0xcc,
-	0x97, 0x09, 0xfa, 0x34, 0x2b, 0xe3, 0x7f, 0x80, 0x96, 0xd7, 0x2c, 0x10, 0xab, 0x3c, 0x5c, 0x2e,
-	0x4b, 0x3a, 0x6f, 0x36, 0xd5, 0x6b, 0x37, 0x97, 0x45, 0x05, 0x2c, 0x2f, 0x87, 0xc0, 0x0b, 0x50,
-	0xc7, 0x42, 0xe0, 0xf1, 0x93, 0x74, 0x38, 0x3a, 0x48, 0x9b, 0x31, 0xdf, 0xd9, 0x8c, 0x02, 0xba,
-	0x07, 0xea, 0x81, 0x77, 0x55, 0xa9, 0x85, 0xc4, 0x5a, 0x81, 0xbf, 0xbf, 0x45, 0xf5, 0x82, 0xc3,
-	0xbd, 0xf5, 0x4c, 0xf0, 0x47, 0x50, 0x93, 0x47, 0x37, 0xf4, 0xb1, 0xc0, 0x7a, 0x3d, 0xdd, 0x28,
-	0x63, 0x73, 0xda, 0x3e, 0x0b, 0xc3, 0x0e, 0x16, 0xd8, 0x35, 0x96, 0x09, 0x6a, 0xa9, 0x33, 0xc9,
-	0xa9, 0xeb, 0xf3, 0xa8, 0xc6, 0x0a, 0xf9, 0xa0, 0xfa, 0xcb, 0x2b, 0xa4, 0xfd, 0xfb, 0x0a, 0x69,
-	0x96, 0x01, 0x80, 0xda, 0x9a, 0x5e, 0x87, 0xc3, 0x06, 0x28, 0x53, 0x9f, 0xeb, 0x9a, 0x59, 0x6e,
-	0xd7, 0x3c, 0x69, 0x5a, 0x7f, 0x6a, 0x00, 0x14, 0x35, 0x42, 0x1b, 0x94, 0x67, 0x53, 0xaa, 0xce,
-	0x7d, 0x77, 0x91, 0xa0, 0xf2, 0xb9, 0xd7, 0xbb, 0x4a, 0x90, 0xf4, 0x2e, 0x13, 0x04, 0xb2, 0xec,
-	0xb3, 0x29, 0xb5, 0x3c, 0xe9, 0x92, 0x57, 0x1e, 0xd1, 0x88, 0x0c, 0xc5, 0x3c, 0x26, 0xeb, 0x57,
-	0x7e, 0x42, 0x23, 0x32, 0x98, 0xc7, 0x44, 0x5e, 0xf9, 0x0a, 0x50, 0x5c, 0xf9, 0xca, 0x65, 0x79,
-	0xd5, 0x48, 0x41, 0xe1, 0x1e, 0xd8, 0x12, 0x38, 0xe0, 0x7a, 0x59, 0x56, 0xe7, 0x7e, 0x5c, 0xec,
-	0xb2, 0xf4, 0xae, 0xf7, 0x99, 0xc2, 0xd6, 0x7a, 0xfc, 0x1a, 0xd4, 0x56, 0xef, 0x2f, 0x5b, 0x7c,
-	0x4a, 0xe6, 0x59, 0x07, 0x9e, 0x34, 0x61, 0x13, 0xdc, 0x7e, 0x86, 0xc3, 0x99, 0xaa, 0xcf, 0xcb,
-	0x3e, 0x0a, 0xfa, 0x67, 0x7f, 0x68, 0xe0, 0xce, 0xb5, 0xcb, 0x82, 0x5f, 0x81, 0xd6, 0xd1, 0xe9,
-	0xc9, 0x49, 0xf7, 0xe1, 0xe0, 0x6c, 0x78, 0x36, 0x38, 0x1c, 0x74, 0x87, 0xe7, 0x0f, 0xcf, 0xfa,
-	0xdd, 0xa3, 0xde, 0x37, 0xbd, 0x6e, 0xa7, 0x51, 0x6a, 0xed, 0x3e, 0x7f, 0x69, 0xea, 0xd7, 0x28,
-	0xe7, 0x13, 0x1e, 0x93, 0x31, 0x7d, 0x4c, 0x89, 0x0f, 0xbf, 0x00, 0x1f, 0xdd, 0x60, 0x1f, 0x1e,
-	0x1f, 0x9f, 0x3e, 0xea, 0x76, 0x1a, 0x5a, 0x4b, 0x7f, 0xfe, 0xd2, 0x6c, 0x5e, 0x63, 0x1e, 0x86,
-	0x21, 0xbb, 0xd8, 0xc8, 0x72, 0x8f, 0x4f, 0x8f, 0xbe, 0xeb, 0x76, 0x1a, 0xb7, 0x36, 0xb0, 0xdc,
-	0x90, 0x8d, 0x9f, 0x12, 0xbf, 0xb5, 0xf5, 0xf3, 0xaf, 0x46, 0xc9, 0x3d, 0x79, 0xbd, 0x30, 0xb4,
-	0x37, 0x0b, 0x43, 0xfb, 0x67, 0x61, 0x68, 0x2f, 0x2e, 0x8d, 0xd2, 0x9b, 0x4b, 0xa3, 0xf4, 0xf7,
-	0xa5, 0x51, 0xfa, 0xe1, 0x20, 0xa0, 0xe2, 0xc9, 0x6c, 0x64, 0x8f, 0x59, 0xe4, 0x64, 0xcb, 0xb5,
-	0x17, 0xe2, 0x11, 0x57, 0xb6, 0xf3, 0x93, 0xc3, 0x05, 0x0e, 0xe8, 0x24, 0x50, 0x7f, 0x07, 0xf2,
-	0x49, 0xf8, 0x68, 0x3b, 0xfd, 0x45, 0x3b, 0xf8, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x64, 0xed, 0xaf,
-	0x71, 0x9a, 0x06, 0x00, 0x00,
+	// 853 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x94, 0x31, 0x6f, 0xdb, 0x46,
+	0x14, 0xc7, 0xc5, 0xc8, 0xb1, 0xa4, 0x13, 0xe2, 0x0a, 0x57, 0xa5, 0x60, 0x09, 0x83, 0x47, 0xb0,
+	0x40, 0x21, 0xb4, 0x31, 0x89, 0xd8, 0x9d, 0xd2, 0x76, 0x30, 0x2d, 0x15, 0x50, 0x6b, 0xc7, 0x2a,
+	0x2d, 0x23, 0x40, 0x3b, 0x08, 0x27, 0xf1, 0xc2, 0x10, 0x21, 0x75, 0x84, 0xee, 0x14, 0x57, 0xdf,
+	0xa0, 0xc8, 0x94, 0xb1, 0x4b, 0x80, 0x00, 0x1d, 0x0a, 0xf4, 0x4b, 0x14, 0xdd, 0x82, 0x4e, 0x19,
+	0x3b, 0x31, 0x85, 0xbc, 0x14, 0x1e, 0xf9, 0x09, 0x8a, 0x23, 0x8f, 0xa2, 0x6c, 0x28, 0xe8, 0x90,
+	0xed, 0xf8, 0xee, 0xf7, 0xff, 0xbf, 0x77, 0xef, 0xee, 0x11, 0x18, 0x1e, 0x61, 0x11, 0x65, 0x76,
+	0x4c, 0x19, 0x67, 0xf6, 0xb3, 0xfb, 0x63, 0xc2, 0xf1, 0xfd, 0xfc, 0xcb, 0x8a, 0x67, 0x94, 0x53,
+	0xd8, 0xce, 0x09, 0x2b, 0x8f, 0x49, 0x42, 0x6b, 0xfb, 0xd4, 0xa7, 0x19, 0x60, 0x8b, 0x55, 0xce,
+	0x6a, 0xc8, 0xa7, 0xd4, 0x0f, 0x89, 0x9d, 0x7d, 0x8d, 0xe7, 0x8f, 0x6d, 0x1e, 0x44, 0x84, 0x71,
+	0x1c, 0xc5, 0x12, 0x78, 0x57, 0xba, 0x30, 0x94, 0xe9, 0xcc, 0xbf, 0x6a, 0x60, 0x6b, 0x40, 0x19,
+	0x87, 0x5f, 0x82, 0x9a, 0xa0, 0x46, 0x81, 0xa7, 0x2a, 0x86, 0xd2, 0x69, 0x38, 0xe6, 0x32, 0x41,
+	0xdb, 0x62, 0xab, 0xdf, 0xbd, 0x4a, 0x50, 0xb1, 0x99, 0x26, 0xa8, 0xb1, 0xc0, 0x51, 0xf8, 0xc0,
+	0x0c, 0x3c, 0xd3, 0xdd, 0x16, 0xd1, 0xbe, 0x07, 0xbf, 0x05, 0x8d, 0x18, 0xcf, 0xc8, 0x34, 0x93,
+	0xdf, 0xca, 0xe4, 0x7b, 0xcb, 0x04, 0xd5, 0x07, 0x59, 0x30, 0x33, 0x28, 0x81, 0x34, 0x41, 0xad,
+	0xdc, 0x62, 0x15, 0x32, 0xdd, 0x7a, 0xbe, 0xee, 0x7b, 0xf0, 0x1e, 0xa8, 0x45, 0x84, 0x31, 0xec,
+	0x13, 0xb5, 0x9a, 0x39, 0xc1, 0x34, 0x41, 0x3b, 0xb9, 0x40, 0x6e, 0x98, 0x6e, 0x81, 0xc0, 0x01,
+	0xa8, 0x4d, 0x66, 0x04, 0x73, 0xe2, 0xa9, 0x5b, 0x86, 0xd2, 0x69, 0xee, 0x6b, 0x56, 0xde, 0x14,
+	0xab, 0x68, 0x8a, 0x35, 0x2c, 0x9a, 0xe2, 0x68, 0xaf, 0x13, 0x54, 0x29, 0xdd, 0xa4, 0xd0, 0x7c,
+	0xf1, 0x16, 0x29, 0x6e, 0x61, 0x03, 0x1f, 0x81, 0x66, 0x88, 0x19, 0x1f, 0x11, 0x2f, 0x10, 0xae,
+	0xb7, 0xdf, 0xcb, 0x15, 0x08, 0xab, 0x5e, 0xe6, 0x04, 0x2f, 0xc0, 0xce, 0x84, 0x46, 0x11, 0x99,
+	0x72, 0x36, 0x62, 0x1c, 0x73, 0xa2, 0x6e, 0x1b, 0x4a, 0x67, 0x67, 0xff, 0x13, 0x6b, 0xd3, 0x95,
+	0x5b, 0x47, 0x92, 0x3d, 0x13, 0xa8, 0xf3, 0xf9, 0x55, 0x82, 0x6e, 0xc8, 0xd3, 0x04, 0xdd, 0x95,
+	0x29, 0xaf, 0xc5, 0x4d, 0xf7, 0xce, 0x64, 0x5d, 0x0b, 0x6d, 0x50, 0x67, 0xf3, 0x31, 0x8b, 0xf1,
+	0x84, 0xa8, 0xb5, 0xac, 0xa5, 0x1f, 0xa6, 0x09, 0xfa, 0x20, 0xd7, 0x16, 0x3b, 0xa6, 0xbb, 0x82,
+	0xe0, 0x6f, 0x0a, 0xb8, 0x8b, 0x3d, 0x2f, 0xe0, 0x01, 0x9d, 0xe2, 0x70, 0x84, 0x39, 0x9f, 0x05,
+	0xe3, 0x39, 0x27, 0x4c, 0xad, 0x1b, 0xd5, 0x4e, 0x73, 0x1f, 0x6d, 0xae, 0xf8, 0xb0, 0xe0, 0x9c,
+	0xef, 0x45, 0x4b, 0xae, 0x12, 0x84, 0x36, 0xba, 0xdc, 0xa3, 0x51, 0xc0, 0x49, 0x14, 0xf3, 0x45,
+	0x9a, 0xa0, 0x4f, 0xf3, 0x32, 0xfe, 0x07, 0x34, 0xdd, 0x76, 0x49, 0xac, 0xf2, 0x30, 0xf1, 0x58,
+	0xb2, 0x7e, 0xd3, 0x99, 0xda, 0xb8, 0xf9, 0x58, 0xe4, 0x86, 0xe9, 0x16, 0x08, 0xbc, 0x00, 0x4d,
+	0xcc, 0x39, 0x9e, 0x3c, 0xc9, 0x9a, 0xa3, 0x82, 0xec, 0x30, 0xc6, 0x3b, 0x0f, 0x23, 0x41, 0xe7,
+	0x40, 0x5e, 0xf0, 0xae, 0x2c, 0xb5, 0xb4, 0x58, 0x2b, 0xf0, 0xf7, 0xb7, 0xa8, 0x59, 0x6a, 0x98,
+	0xbb, 0x9e, 0x09, 0xfe, 0x08, 0x1a, 0x62, 0xe8, 0x46, 0x1e, 0xe6, 0x58, 0x6d, 0x66, 0x2f, 0x4a,
+	0xdf, 0x9c, 0x76, 0x40, 0xc3, 0xb0, 0x8b, 0x39, 0x76, 0xf4, 0x34, 0x41, 0x9a, 0x1c, 0x93, 0x42,
+	0xba, 0xde, 0x8f, 0x7a, 0x2c, 0xc9, 0x07, 0xf5, 0x5f, 0x5e, 0x21, 0xe5, 0xdf, 0x57, 0x48, 0x31,
+	0xff, 0x54, 0x00, 0x28, 0x6b, 0x80, 0x16, 0xa8, 0xce, 0x67, 0x81, 0x1c, 0xe7, 0xdd, 0x65, 0x82,
+	0xaa, 0xe7, 0x6e, 0xff, 0x2a, 0x41, 0x22, 0x9a, 0x26, 0x08, 0xe4, 0xee, 0xf3, 0x59, 0x60, 0xba,
+	0x22, 0x24, 0xa6, 0x38, 0x0a, 0x22, 0x32, 0xe2, 0x8b, 0x98, 0xac, 0x4f, 0xf1, 0x49, 0x10, 0x91,
+	0xe1, 0x22, 0x26, 0x62, 0x8a, 0x57, 0x40, 0x39, 0xc5, 0xab, 0x90, 0xe9, 0xd6, 0x23, 0x89, 0xc2,
+	0x3d, 0xb0, 0xc5, 0xb1, 0xcf, 0xd4, 0xaa, 0x51, 0xed, 0x34, 0x9c, 0x8f, 0xcb, 0xb7, 0x2a, 0xa2,
+	0xeb, 0xe7, 0xc8, 0xb0, 0xb5, 0x33, 0x7c, 0x0d, 0x1a, 0xab, 0xfb, 0x85, 0x2d, 0x50, 0x7d, 0x4a,
+	0x16, 0xf9, 0x09, 0x5c, 0xb1, 0x84, 0x6d, 0x70, 0xfb, 0x19, 0x0e, 0xe7, 0xb2, 0x3e, 0x37, 0xff,
+	0x28, 0xe5, 0x9f, 0xfd, 0xa1, 0x80, 0x3b, 0xd7, 0x26, 0x07, 0x7e, 0x05, 0xb4, 0xa3, 0xd3, 0x93,
+	0x93, 0xde, 0xc3, 0xe1, 0xd9, 0xe8, 0x6c, 0x78, 0x38, 0xec, 0x8d, 0xce, 0x1f, 0x9e, 0x0d, 0x7a,
+	0x47, 0xfd, 0x6f, 0xfa, 0xbd, 0x6e, 0xab, 0xa2, 0xed, 0x3e, 0x7f, 0x69, 0xa8, 0xd7, 0x24, 0xe7,
+	0x53, 0x16, 0x93, 0x49, 0xf0, 0x38, 0x20, 0x1e, 0xfc, 0x02, 0x7c, 0x74, 0x43, 0x7d, 0x78, 0x7c,
+	0x7c, 0xfa, 0xa8, 0xd7, 0x6d, 0x29, 0x9a, 0xfa, 0xfc, 0xa5, 0xd1, 0xbe, 0xa6, 0x3c, 0x0c, 0x43,
+	0x7a, 0xb1, 0x51, 0xe5, 0x1c, 0x9f, 0x1e, 0x7d, 0xd7, 0xeb, 0xb6, 0x6e, 0x6d, 0x50, 0x39, 0x21,
+	0x9d, 0x3c, 0x25, 0x9e, 0xb6, 0xf5, 0xf3, 0xaf, 0x7a, 0xc5, 0x39, 0x79, 0xbd, 0xd4, 0x95, 0x37,
+	0x4b, 0x5d, 0xf9, 0x67, 0xa9, 0x2b, 0x2f, 0x2e, 0xf5, 0xca, 0x9b, 0x4b, 0xbd, 0xf2, 0xf7, 0xa5,
+	0x5e, 0xf9, 0xe1, 0xc0, 0x0f, 0xf8, 0x93, 0xf9, 0xd8, 0x9a, 0xd0, 0xc8, 0xce, 0x1f, 0xcf, 0x5e,
+	0x88, 0xc7, 0x4c, 0xae, 0xed, 0x9f, 0x6c, 0xc6, 0xb1, 0x1f, 0x4c, 0x7d, 0xf9, 0xbb, 0x17, 0x57,
+	0xc2, 0xc6, 0xdb, 0xd9, 0x1f, 0xeb, 0xe0, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x4f, 0xc2, 0xa7,
+	0x09, 0x7a, 0x06, 0x00, 0x00,
 }
 
 func (this *Post) Equal(that interface{}) bool {
@@ -660,38 +613,6 @@ func (m *Post) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	return len(dAtA) - i, nil
 }
 
-func (m *CommentIDs) Marshal() (dAtA []byte, err error) {
-	size := m.Size()
-	dAtA = make([]byte, size)
-	n, err := m.MarshalToSizedBuffer(dAtA[:size])
-	if err != nil {
-		return nil, err
-	}
-	return dAtA[:n], nil
-}
-
-func (m *CommentIDs) MarshalTo(dAtA []byte) (int, error) {
-	size := m.Size()
-	return m.MarshalToSizedBuffer(dAtA[:size])
-}
-
-func (m *CommentIDs) MarshalToSizedBuffer(dAtA []byte) (int, error) {
-	i := len(dAtA)
-	_ = i
-	var l int
-	_ = l
-	if len(m.Ids) > 0 {
-		for iNdEx := len(m.Ids) - 1; iNdEx >= 0; iNdEx-- {
-			i -= len(m.Ids[iNdEx])
-			copy(dAtA[i:], m.Ids[iNdEx])
-			i = encodeVarintPosts(dAtA, i, uint64(len(m.Ids[iNdEx])))
-			i--
-			dAtA[i] = 0xa
-		}
-	}
-	return len(dAtA) - i, nil
-}
-
 func (m *Attachment) Marshal() (dAtA []byte, err error) {
 	size := m.Size()
 	dAtA = make([]byte, size)
@@ -838,21 +759,6 @@ func (m *Post) Size() (n int) {
 	return n
 }
 
-func (m *CommentIDs) Size() (n int) {
-	if m == nil {
-		return 0
-	}
-	var l int
-	_ = l
-	if len(m.Ids) > 0 {
-		for _, s := range m.Ids {
-			l = len(s)
-			n += 1 + l + sovPosts(uint64(l))
-		}
-	}
-	return n
-}
-
 func (m *Attachment) Size() (n int) {
 	if m == nil {
 		return 0
@@ -1298,88 +1204,6 @@ func (m *Post) Unmarshal(dAtA []byte) error {
 	}
 	return nil
 }
-func (m *CommentIDs) Unmarshal(dAtA []byte) error {
-	l := len(dAtA)
-	iNdEx := 0
-	for iNdEx < l {
-		preIndex := iNdEx
-		var wire uint64
-		for shift := uint(0); ; shift += 7 {
-			if shift >= 64 {
-				return ErrIntOverflowPosts
-			}
-			if iNdEx >= l {
-				return io.ErrUnexpectedEOF
-			}
-			b := dAtA[iNdEx]
-			iNdEx++
-			wire |= uint64(b&0x7F) << shift
-			if b < 0x80 {
-				break
-			}
-		}
-		fieldNum := int32(wire >> 3)
-		wireType := int(wire & 0x7)
-		if wireType == 4 {
-			return fmt.Errorf("proto: CommentIDs: wiretype end group for non-group")
-		}
-		if fieldNum <= 0 {
-			return fmt.Errorf("proto: CommentIDs: illegal tag %d (wire type %d)", fieldNum, wire)
-		}
-		switch fieldNum {
-		case 1:
-			if wireType != 2 {
-				return fmt.Errorf("proto: wrong wireType = %d for field Ids", wireType)
-			}
-			var stringLen uint64
-			for shift := uint(0); ; shift += 7 {
-				if shift >= 64 {
-					return ErrIntOverflowPosts
-				}
-				if iNdEx >= l {
-					return io.ErrUnexpectedEOF
-				}
-				b := dAtA[iNdEx]
-				iNdEx++
-				stringLen |= uint64(b&0x7F) << shift
-				if b < 0x80 {
-					break
-				}
-			}
-			intStringLen := int(stringLen)
-			if intStringLen < 0 {
-				return ErrInvalidLengthPosts
-			}
-			postIndex := iNdEx + intStringLen
-			if postIndex < 0 {
-				return ErrInvalidLengthPosts
-			}
-			if postIndex > l {
-				return io.ErrUnexpectedEOF
-			}
-			m.Ids = append(m.Ids, string(dAtA[iNdEx:postIndex]))
-			iNdEx = postIndex
-		default:
-			iNdEx = preIndex
-			skippy, err := skipPosts(dAtA[iNdEx:])
-			if err != nil {
-				return err
-			}
-			if (skippy < 0) || (iNdEx+skippy) < 0 {
-				return ErrInvalidLengthPosts
-			}
-			if (iNdEx + skippy) > l {
-				return io.ErrUnexpectedEOF
-			}
-			iNdEx += skippy
-		}
-	}
-
-	if iNdEx > l {
-		return io.ErrUnexpectedEOF
-	}
-	return nil
-}
 func (m *Attachment) Unmarshal(dAtA []byte) error {
 	l := len(dAtA)
 	iNdEx := 0
diff --git a/x/staging/posts/types/query.pb.go b/x/staging/posts/types/query.pb.go
index 2556365a..964bfeb1 100644
--- a/x/staging/posts/types/query.pb.go
+++ b/x/staging/posts/types/query.pb.go
@@ -33,8 +33,8 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
 
 // QueryPostsRequest is the request type for the Query/Posts RPC method.
 type QueryPostsRequest struct {
-	// SubspaceId represents the ID of the subspace to which to query the posts for.
-	// Providing an empty or invalid subspace id will return an error.
+	// SubspaceId represents the ID of the subspace to which to query the posts
+	// for. Providing an empty or invalid subspace id will return an error.
 	SubspaceId string `protobuf:"bytes,1,opt,name=subspace_id,json=subspaceId,proto3" json:"subspace_id,omitempty"`
 	// pagination defines an optional pagination for the request.
 	Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"`
@@ -596,6 +596,115 @@ func (m *QueryReportsResponse) GetReports() []Report {
 	return nil
 }
 
+// QueryPostCommentsRequest is the request type for the Query/PostComments RPC
+// method.
+type QueryPostCommentsRequest struct {
+	PostId string `protobuf:"bytes,1,opt,name=post_id,json=postId,proto3" json:"post_id,omitempty"`
+	// pagination defines an optional pagination for the request.
+	Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"`
+}
+
+func (m *QueryPostCommentsRequest) Reset()         { *m = QueryPostCommentsRequest{} }
+func (m *QueryPostCommentsRequest) String() string { return proto.CompactTextString(m) }
+func (*QueryPostCommentsRequest) ProtoMessage()    {}
+func (*QueryPostCommentsRequest) Descriptor() ([]byte, []int) {
+	return fileDescriptor_b6258583fb7a3a9d, []int{12}
+}
+func (m *QueryPostCommentsRequest) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *QueryPostCommentsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_QueryPostCommentsRequest.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *QueryPostCommentsRequest) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_QueryPostCommentsRequest.Merge(m, src)
+}
+func (m *QueryPostCommentsRequest) XXX_Size() int {
+	return m.Size()
+}
+func (m *QueryPostCommentsRequest) XXX_DiscardUnknown() {
+	xxx_messageInfo_QueryPostCommentsRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_QueryPostCommentsRequest proto.InternalMessageInfo
+
+func (m *QueryPostCommentsRequest) GetPostId() string {
+	if m != nil {
+		return m.PostId
+	}
+	return ""
+}
+
+func (m *QueryPostCommentsRequest) GetPagination() *query.PageRequest {
+	if m != nil {
+		return m.Pagination
+	}
+	return nil
+}
+
+// QueryPostCommentsResponse is the response type for the Query/PostComments RPC
+// method.
+type QueryPostCommentsResponse struct {
+	Comments   []Post              `protobuf:"bytes,1,rep,name=comments,proto3" json:"comments"`
+	Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"`
+}
+
+func (m *QueryPostCommentsResponse) Reset()         { *m = QueryPostCommentsResponse{} }
+func (m *QueryPostCommentsResponse) String() string { return proto.CompactTextString(m) }
+func (*QueryPostCommentsResponse) ProtoMessage()    {}
+func (*QueryPostCommentsResponse) Descriptor() ([]byte, []int) {
+	return fileDescriptor_b6258583fb7a3a9d, []int{13}
+}
+func (m *QueryPostCommentsResponse) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *QueryPostCommentsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_QueryPostCommentsResponse.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *QueryPostCommentsResponse) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_QueryPostCommentsResponse.Merge(m, src)
+}
+func (m *QueryPostCommentsResponse) XXX_Size() int {
+	return m.Size()
+}
+func (m *QueryPostCommentsResponse) XXX_DiscardUnknown() {
+	xxx_messageInfo_QueryPostCommentsResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_QueryPostCommentsResponse proto.InternalMessageInfo
+
+func (m *QueryPostCommentsResponse) GetComments() []Post {
+	if m != nil {
+		return m.Comments
+	}
+	return nil
+}
+
+func (m *QueryPostCommentsResponse) GetPagination() *query.PageResponse {
+	if m != nil {
+		return m.Pagination
+	}
+	return nil
+}
+
 func init() {
 	proto.RegisterType((*QueryPostsRequest)(nil), "desmos.posts.v1beta1.QueryPostsRequest")
 	proto.RegisterType((*QueryPostsResponse)(nil), "desmos.posts.v1beta1.QueryPostsResponse")
@@ -609,65 +718,71 @@ func init() {
 	proto.RegisterType((*QueryParamsResponse)(nil), "desmos.posts.v1beta1.QueryParamsResponse")
 	proto.RegisterType((*QueryReportsRequest)(nil), "desmos.posts.v1beta1.QueryReportsRequest")
 	proto.RegisterType((*QueryReportsResponse)(nil), "desmos.posts.v1beta1.QueryReportsResponse")
+	proto.RegisterType((*QueryPostCommentsRequest)(nil), "desmos.posts.v1beta1.QueryPostCommentsRequest")
+	proto.RegisterType((*QueryPostCommentsResponse)(nil), "desmos.posts.v1beta1.QueryPostCommentsResponse")
 }
 
 func init() { proto.RegisterFile("desmos/posts/v1beta1/query.proto", fileDescriptor_b6258583fb7a3a9d) }
 
 var fileDescriptor_b6258583fb7a3a9d = []byte{
-	// 840 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x56, 0xc1, 0x6e, 0xd3, 0x4a,
-	0x14, 0xcd, 0xb4, 0x69, 0xf2, 0x3a, 0xd9, 0xbc, 0x37, 0x8d, 0xd4, 0xc8, 0xaf, 0xc4, 0xc1, 0x94,
-	0x26, 0xad, 0x14, 0x9b, 0xa6, 0xb4, 0xaa, 0x2a, 0x16, 0xd0, 0x05, 0xa8, 0x0b, 0x44, 0x6b, 0xc1,
-	0x86, 0x4d, 0x35, 0x4e, 0x06, 0x63, 0x29, 0x89, 0x5d, 0x8f, 0x03, 0x54, 0x80, 0x90, 0x58, 0x20,
-	0x60, 0x05, 0x42, 0x48, 0x2c, 0x2b, 0x21, 0x7e, 0x80, 0x0f, 0x60, 0xdd, 0x15, 0xaa, 0xc4, 0x86,
-	0x55, 0x85, 0x5a, 0x16, 0x88, 0x25, 0x5f, 0x80, 0x3c, 0x33, 0x76, 0x9d, 0xc6, 0x71, 0x42, 0x55,
-	0xb1, 0x73, 0x66, 0xce, 0xbd, 0xf7, 0xdc, 0x73, 0x7d, 0x8f, 0x03, 0x4b, 0x0d, 0x42, 0x5b, 0x36,
-	0xd5, 0x1c, 0x9b, 0x7a, 0x54, 0xbb, 0x37, 0x6f, 0x10, 0x0f, 0xcf, 0x6b, 0x5b, 0x1d, 0xe2, 0x6e,
-	0xab, 0x8e, 0x6b, 0x7b, 0x36, 0xca, 0x73, 0x84, 0xca, 0x10, 0xaa, 0x40, 0x48, 0x79, 0xd3, 0x36,
-	0x6d, 0x06, 0xd0, 0xfc, 0x27, 0x8e, 0x95, 0xa6, 0x4c, 0xdb, 0x36, 0x9b, 0x44, 0xc3, 0x8e, 0xa5,
-	0xe1, 0x76, 0xdb, 0xf6, 0xb0, 0x67, 0xd9, 0x6d, 0x2a, 0x6e, 0x65, 0x71, 0xcb, 0x7e, 0x19, 0x9d,
-	0x3b, 0x9a, 0x67, 0xb5, 0x08, 0xf5, 0x70, 0xcb, 0x11, 0x80, 0xb9, 0xba, 0xcd, 0xc8, 0x18, 0x98,
-	0x12, 0xce, 0x21, 0x64, 0xe4, 0x60, 0xd3, 0x6a, 0xb3, 0x6c, 0x02, 0x1b, 0x4f, 0x9c, 0x93, 0xe4,
-	0x88, 0xe9, 0x58, 0x84, 0x4b, 0x70, 0x3d, 0x4a, 0xaa, 0x5f, 0x9e, 0x66, 0x33, 0x40, 0x9c, 0x8d,
-	0x47, 0x60, 0x17, 0xb7, 0x92, 0x21, 0x2e, 0x71, 0x6c, 0xd7, 0xe3, 0x10, 0xe5, 0x11, 0xfc, 0x6f,
-	0xc3, 0xef, 0x68, 0xdd, 0x87, 0xe8, 0x64, 0xab, 0x43, 0xa8, 0x87, 0x64, 0x98, 0xa3, 0x1d, 0x83,
-	0x3a, 0xb8, 0x4e, 0x36, 0xad, 0x46, 0x01, 0x94, 0x40, 0x65, 0x5c, 0x87, 0xc1, 0xd1, 0x5a, 0x03,
-	0x5d, 0x85, 0xf0, 0xa8, 0xf3, 0xc2, 0x48, 0x09, 0x54, 0x72, 0xb5, 0x19, 0x95, 0xcb, 0xa4, 0xfa,
-	0x32, 0xa9, 0x7c, 0x54, 0xa2, 0xa4, 0xba, 0x8e, 0x4d, 0x22, 0x92, 0xeb, 0x91, 0x48, 0xe5, 0x2d,
-	0x80, 0x28, 0x5a, 0x9e, 0x3a, 0x76, 0x9b, 0x12, 0xb4, 0x04, 0xc7, 0x18, 0xe5, 0x02, 0x28, 0x8d,
-	0x56, 0x72, 0x35, 0x49, 0x8d, 0x9b, 0xb5, 0xea, 0xc7, 0xac, 0xa6, 0x77, 0xf7, 0xe5, 0x94, 0xce,
-	0xe1, 0xe8, 0x5a, 0x0c, 0xad, 0xf2, 0x40, 0x5a, 0xbc, 0x68, 0x17, 0xaf, 0x1b, 0xf0, 0xdf, 0x90,
-	0x56, 0x20, 0xca, 0x3c, 0xcc, 0xfa, 0x55, 0x42, 0x41, 0x56, 0x0b, 0x3f, 0xf7, 0xe5, 0xe0, 0xe8,
-	0xd7, 0xbe, 0x3c, 0xbe, 0x8d, 0x5b, 0xcd, 0x15, 0xc5, 0x6a, 0x28, 0x7a, 0xc6, 0x3f, 0x5d, 0x6b,
-	0xac, 0xfc, 0xf3, 0x7c, 0x47, 0x4e, 0xfd, 0xd8, 0x91, 0x53, 0xca, 0x5a, 0x44, 0xe6, 0xb0, 0xcd,
-	0x8b, 0x30, 0xed, 0x03, 0x59, 0xba, 0x61, 0xba, 0x64, 0x68, 0xe5, 0x23, 0x80, 0x93, 0x2c, 0xd7,
-	0x2d, 0x4a, 0xdc, 0x2b, 0x6d, 0x7a, 0x9f, 0xb8, 0xf4, 0xe4, 0x1c, 0x11, 0x82, 0xe9, 0x0e, 0x25,
-	0x2e, 0x53, 0x6b, 0x5c, 0x67, 0xcf, 0xc7, 0xc6, 0x3b, 0x7a, 0xd2, 0xf1, 0x46, 0xfa, 0xff, 0x00,
-	0x60, 0xa1, 0x97, 0xb4, 0xd0, 0xe1, 0x32, 0xcc, 0x62, 0x7e, 0x24, 0x06, 0x5e, 0x8a, 0x97, 0xe2,
-	0x28, 0x56, 0x08, 0x12, 0x84, 0x9d, 0xde, 0xe0, 0x5f, 0x02, 0x28, 0x33, 0x9e, 0x3a, 0x31, 0x2d,
-	0xea, 0x11, 0x97, 0x34, 0xf4, 0x60, 0x33, 0xff, 0xfa, 0x76, 0x7c, 0x06, 0xb0, 0xd4, 0x9f, 0x8c,
-	0x10, 0x0f, 0xc3, 0xbc, 0x1b, 0x5e, 0x6f, 0x86, 0x36, 0x22, 0x94, 0xac, 0xc4, 0x2b, 0xd9, 0x9b,
-	0x50, 0x28, 0x3a, 0xe1, 0xf6, 0x96, 0x3a, 0x3d, 0x75, 0xf3, 0xc1, 0xb6, 0x33, 0x93, 0x12, 0x2d,
-	0x2b, 0x1b, 0x70, 0xa2, 0xeb, 0x54, 0x34, 0xb6, 0x02, 0x33, 0xdc, 0xcc, 0xc4, 0x7e, 0x4c, 0xf5,
-	0xd9, 0x0f, 0x86, 0x11, 0xf4, 0x45, 0x84, 0xb2, 0x2c, 0x52, 0xea, 0xcc, 0xea, 0xc2, 0xc9, 0x4d,
-	0x1e, 0x5b, 0x8f, 0x98, 0x45, 0xbd, 0x09, 0xf3, 0xdd, 0x91, 0x82, 0xcd, 0x25, 0x98, 0xe5, 0xbe,
-	0x19, 0x28, 0x3b, 0xd5, 0x4f, 0x59, 0x1f, 0x14, 0xbc, 0x9f, 0x22, 0xa4, 0xf6, 0x2e, 0x0b, 0xc7,
-	0x58, 0x5a, 0xf4, 0x04, 0x8e, 0x31, 0xaf, 0x43, 0xe5, 0xf8, 0xf8, 0x1e, 0x33, 0x96, 0x2a, 0x83,
-	0x81, 0x9c, 0xa3, 0x72, 0xee, 0xe9, 0x97, 0xef, 0x6f, 0x46, 0xce, 0xa0, 0xff, 0xb5, 0xfe, 0x1f,
-	0x21, 0xf4, 0x0c, 0xc0, 0xb4, 0x1f, 0x86, 0x66, 0x06, 0xe4, 0x0d, 0xea, 0x97, 0x07, 0xe2, 0x44,
-	0xf9, 0x2a, 0x2b, 0x5f, 0x46, 0xe7, 0x13, 0xca, 0x6b, 0x0f, 0xc5, 0x00, 0x1e, 0xa3, 0xd7, 0x00,
-	0x66, 0x85, 0xca, 0x68, 0x36, 0xa1, 0x46, 0xf7, 0x0c, 0xa5, 0xb9, 0x61, 0xa0, 0x82, 0x91, 0xc6,
-	0x18, 0xcd, 0xa2, 0xb2, 0x96, 0xf0, 0x21, 0x8c, 0x72, 0x7a, 0x0f, 0x60, 0x2e, 0xe2, 0x50, 0xa8,
-	0x9a, 0x50, 0xac, 0xd7, 0x7e, 0x25, 0x75, 0x58, 0xb8, 0xe0, 0xb7, 0xc8, 0xf8, 0x69, 0xa8, 0x3a,
-	0x94, 0x62, 0x5a, 0xe0, 0x76, 0x9f, 0x00, 0x9c, 0x88, 0xb1, 0x04, 0xb4, 0x98, 0x28, 0x4d, 0x3f,
-	0x3f, 0x93, 0x96, 0xfe, 0x34, 0x4c, 0xb0, 0x5f, 0x66, 0xec, 0x6b, 0xe8, 0x42, 0x12, 0xfb, 0x23,
-	0x3f, 0xa9, 0x86, 0xde, 0x84, 0x5e, 0x00, 0x98, 0xe1, 0x7b, 0x8b, 0x12, 0xdf, 0xee, 0xa8, 0x4d,
-	0x48, 0xb3, 0x43, 0x20, 0x05, 0xb3, 0x39, 0xc6, 0x6c, 0x1a, 0x29, 0x49, 0xcc, 0xb8, 0x55, 0xac,
-	0x5e, 0xdf, 0x3d, 0x28, 0x82, 0xbd, 0x83, 0x22, 0xf8, 0x76, 0x50, 0x04, 0xaf, 0x0e, 0x8b, 0xa9,
-	0xbd, 0xc3, 0x62, 0xea, 0xeb, 0x61, 0x31, 0x75, 0x7b, 0xc1, 0xb4, 0xbc, 0xbb, 0x1d, 0x43, 0xad,
-	0xdb, 0x2d, 0x91, 0xa7, 0xda, 0xc4, 0x06, 0x0d, 0x72, 0x3e, 0xd0, 0xa8, 0xe7, 0x5b, 0x9b, 0x29,
-	0xf2, 0x79, 0xdb, 0x0e, 0xa1, 0x46, 0x86, 0xfd, 0xad, 0x5a, 0xf8, 0x1d, 0x00, 0x00, 0xff, 0xff,
-	0x95, 0x02, 0xc6, 0xff, 0xc1, 0x0a, 0x00, 0x00,
+	// 909 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x56, 0xb1, 0x6f, 0xf3, 0x44,
+	0x14, 0xcf, 0x7d, 0x5f, 0x9a, 0xb4, 0x17, 0x06, 0xb8, 0x46, 0xfa, 0x82, 0x29, 0x71, 0x30, 0x1f,
+	0x4d, 0x5a, 0x29, 0x76, 0x9b, 0xd2, 0xaa, 0xaa, 0x18, 0xa0, 0x48, 0xa0, 0x0e, 0x88, 0xd6, 0x82,
+	0x85, 0xa5, 0xba, 0x24, 0x87, 0xb1, 0x14, 0xc7, 0xae, 0xcf, 0x01, 0xaa, 0x82, 0x90, 0x18, 0x10,
+	0x30, 0x81, 0x10, 0x7b, 0x05, 0x42, 0xec, 0xfc, 0x01, 0x0c, 0x4c, 0x9d, 0x50, 0x25, 0x16, 0xa6,
+	0x0a, 0xb5, 0x0c, 0x88, 0x91, 0xbf, 0x00, 0xf9, 0xee, 0xd9, 0x75, 0x1a, 0xc7, 0x31, 0x55, 0xc4,
+	0xd6, 0x9e, 0x7f, 0xef, 0xbd, 0xdf, 0xfd, 0xde, 0x7b, 0xbf, 0x0b, 0x6e, 0xf4, 0x19, 0x77, 0x5c,
+	0x6e, 0x78, 0x2e, 0x0f, 0xb8, 0xf1, 0xfe, 0x66, 0x97, 0x05, 0x74, 0xd3, 0x38, 0x19, 0x31, 0xff,
+	0x54, 0xf7, 0x7c, 0x37, 0x70, 0x49, 0x55, 0x22, 0x74, 0x81, 0xd0, 0x01, 0xa1, 0x54, 0x2d, 0xd7,
+	0x72, 0x05, 0xc0, 0x08, 0xff, 0x92, 0x58, 0x65, 0xc5, 0x72, 0x5d, 0x6b, 0xc0, 0x0c, 0xea, 0xd9,
+	0x06, 0x1d, 0x0e, 0xdd, 0x80, 0x06, 0xb6, 0x3b, 0xe4, 0xf0, 0x55, 0x85, 0xaf, 0xe2, 0xbf, 0xee,
+	0xe8, 0x5d, 0x23, 0xb0, 0x1d, 0xc6, 0x03, 0xea, 0x78, 0x00, 0x58, 0xef, 0xb9, 0x82, 0x4c, 0x97,
+	0x72, 0x26, 0x39, 0xc4, 0x8c, 0x3c, 0x6a, 0xd9, 0x43, 0x91, 0x0d, 0xb0, 0xe9, 0xc4, 0x25, 0x49,
+	0x89, 0x78, 0x9c, 0x8a, 0xf0, 0x19, 0xed, 0x25, 0x49, 0x4d, 0xcb, 0x33, 0x18, 0x44, 0x88, 0xe7,
+	0xd2, 0x11, 0xd4, 0xa7, 0x4e, 0x36, 0xc4, 0x67, 0x9e, 0xeb, 0x07, 0x12, 0xa2, 0x7d, 0x84, 0x9f,
+	0x3a, 0x0a, 0x6f, 0x74, 0x18, 0x42, 0x4c, 0x76, 0x32, 0x62, 0x3c, 0x20, 0x2a, 0xae, 0xf0, 0x51,
+	0x97, 0x7b, 0xb4, 0xc7, 0x8e, 0xed, 0x7e, 0x0d, 0x35, 0x50, 0x6b, 0xc9, 0xc4, 0xd1, 0xd1, 0x41,
+	0x9f, 0xbc, 0x86, 0xf1, 0xed, 0xcd, 0x6b, 0x0f, 0x1a, 0xa8, 0x55, 0xe9, 0xac, 0xea, 0x52, 0x26,
+	0x3d, 0x94, 0x49, 0x97, 0xad, 0x82, 0x92, 0xfa, 0x21, 0xb5, 0x18, 0x24, 0x37, 0x13, 0x91, 0xda,
+	0xb7, 0x08, 0x93, 0x64, 0x79, 0xee, 0xb9, 0x43, 0xce, 0xc8, 0x0e, 0x5e, 0x10, 0x94, 0x6b, 0xa8,
+	0xf1, 0xb0, 0x55, 0xe9, 0x28, 0x7a, 0x5a, 0xaf, 0xf5, 0x30, 0x66, 0xbf, 0x78, 0x71, 0xa5, 0x16,
+	0x4c, 0x09, 0x27, 0xaf, 0xa7, 0xd0, 0x6a, 0xce, 0xa4, 0x25, 0x8b, 0x8e, 0xf1, 0x7a, 0x13, 0x3f,
+	0x19, 0xd3, 0x8a, 0x44, 0xd9, 0xc4, 0xe5, 0xb0, 0x4a, 0x2c, 0xc8, 0x7e, 0xed, 0xef, 0x2b, 0x35,
+	0x3a, 0xfa, 0xe7, 0x4a, 0x5d, 0x3a, 0xa5, 0xce, 0x60, 0x4f, 0xb3, 0xfb, 0x9a, 0x59, 0x0a, 0x4f,
+	0x0f, 0xfa, 0x7b, 0x8b, 0x9f, 0x9f, 0xab, 0x85, 0xbf, 0xce, 0xd5, 0x82, 0x76, 0x90, 0x90, 0x39,
+	0xbe, 0xe6, 0x8b, 0xb8, 0x18, 0x02, 0x45, 0xba, 0x3c, 0xb7, 0x14, 0x68, 0xed, 0x27, 0x84, 0x1f,
+	0x89, 0x5c, 0x6f, 0x73, 0xe6, 0xbf, 0x32, 0xe4, 0x1f, 0x30, 0x9f, 0xdf, 0x9f, 0x23, 0x21, 0xb8,
+	0x38, 0xe2, 0xcc, 0x17, 0x6a, 0x2d, 0x99, 0xe2, 0xef, 0x3b, 0xed, 0x7d, 0x78, 0xdf, 0xf6, 0x26,
+	0xee, 0xff, 0x03, 0xc2, 0xb5, 0x49, 0xd2, 0xa0, 0xc3, 0xcb, 0xb8, 0x4c, 0xe5, 0x11, 0x34, 0xbc,
+	0x91, 0x2e, 0xc5, 0x6d, 0x2c, 0x08, 0x12, 0x85, 0xcd, 0xaf, 0xf1, 0x5f, 0x22, 0xac, 0x0a, 0x9e,
+	0x26, 0xb3, 0x6c, 0x1e, 0x30, 0x9f, 0xf5, 0xcd, 0x68, 0x33, 0xff, 0xf7, 0xed, 0xf8, 0x15, 0xe1,
+	0xc6, 0x74, 0x32, 0x20, 0x1e, 0xc5, 0x55, 0x3f, 0xfe, 0x7c, 0x1c, 0xdb, 0x08, 0x28, 0xd9, 0x4a,
+	0x57, 0x72, 0x32, 0x21, 0x28, 0xba, 0xec, 0x4f, 0x96, 0x9a, 0x9f, 0xba, 0xd5, 0x68, 0xdb, 0x85,
+	0x49, 0xc1, 0x95, 0xb5, 0x23, 0xbc, 0x3c, 0x76, 0x0a, 0x17, 0xdb, 0xc3, 0x25, 0x69, 0x66, 0xb0,
+	0x1f, 0x2b, 0x53, 0xf6, 0x43, 0x60, 0x80, 0x3e, 0x44, 0x68, 0xbb, 0x90, 0xd2, 0x14, 0x56, 0x17,
+	0x77, 0xee, 0xd1, 0x9d, 0xf5, 0x48, 0x59, 0xd4, 0xb7, 0x70, 0x75, 0x3c, 0x12, 0xd8, 0xbc, 0x84,
+	0xcb, 0xd2, 0x37, 0x23, 0x65, 0x57, 0xa6, 0x29, 0x1b, 0x82, 0xa2, 0xf9, 0x84, 0x10, 0xed, 0x0c,
+	0xa6, 0x3f, 0x5c, 0xe6, 0x57, 0x5d, 0xc7, 0x61, 0xc3, 0xd9, 0xa4, 0xe6, 0x36, 0x46, 0xdf, 0x21,
+	0xfc, 0x74, 0x4a, 0xf5, 0xf8, 0x62, 0x8b, 0x3d, 0x38, 0xcb, 0x6d, 0xb7, 0x71, 0xc4, 0xdc, 0x46,
+	0xa3, 0xf3, 0xcb, 0x22, 0x5e, 0x10, 0x24, 0xc9, 0x27, 0x78, 0x41, 0xbc, 0x06, 0xa4, 0x99, 0xce,
+	0x63, 0xe2, 0xb9, 0x52, 0x5a, 0xb3, 0x81, 0xb2, 0xa2, 0xf6, 0xfc, 0xa7, 0xbf, 0xfd, 0xf9, 0xcd,
+	0x83, 0x67, 0xc9, 0x33, 0xc6, 0xf4, 0x67, 0x9a, 0x7c, 0x86, 0x70, 0x31, 0x0c, 0x23, 0xab, 0x33,
+	0xf2, 0x46, 0xf5, 0x9b, 0x33, 0x71, 0x50, 0xbe, 0x2d, 0xca, 0x37, 0xc9, 0x0b, 0x19, 0xe5, 0x8d,
+	0x33, 0x98, 0x86, 0x8f, 0xc9, 0xd7, 0x08, 0x97, 0x61, 0x0e, 0xc9, 0x5a, 0x46, 0x8d, 0xf1, 0x29,
+	0x57, 0xd6, 0xf3, 0x40, 0x81, 0x91, 0x21, 0x18, 0xad, 0x91, 0xa6, 0x91, 0xf1, 0x53, 0x21, 0xc9,
+	0xe9, 0x7b, 0x84, 0x2b, 0x09, 0x0f, 0x27, 0xed, 0x8c, 0x62, 0x93, 0x0f, 0x94, 0xa2, 0xe7, 0x85,
+	0x03, 0xbf, 0x6d, 0xc1, 0xcf, 0x20, 0xed, 0x5c, 0x8a, 0x19, 0xd1, 0x7b, 0xf0, 0x33, 0xc2, 0xcb,
+	0x29, 0xa6, 0x49, 0xb6, 0x33, 0xa5, 0x99, 0xe6, 0xf8, 0xca, 0xce, 0x7f, 0x0d, 0x03, 0xf6, 0xbb,
+	0x82, 0x7d, 0x87, 0x6c, 0x64, 0xb1, 0xbf, 0x75, 0xdc, 0x76, 0xec, 0xde, 0xe4, 0x0b, 0x84, 0x4b,
+	0xd2, 0xd9, 0x48, 0xe6, 0x74, 0x27, 0x8d, 0x54, 0x59, 0xcb, 0x81, 0x04, 0x66, 0xeb, 0x82, 0xd9,
+	0x63, 0xa2, 0x65, 0x31, 0x93, 0x66, 0x4a, 0x7e, 0x44, 0xf8, 0x89, 0xa4, 0x75, 0x10, 0x7d, 0xc6,
+	0xbc, 0xdf, 0x71, 0x38, 0xc5, 0xc8, 0x8d, 0x07, 0x76, 0x3b, 0x82, 0xdd, 0x06, 0xd1, 0xf3, 0x75,
+	0x3d, 0x72, 0xa3, 0xfd, 0x37, 0x2e, 0xae, 0xeb, 0xe8, 0xf2, 0xba, 0x8e, 0xfe, 0xb8, 0xae, 0xa3,
+	0xaf, 0x6e, 0xea, 0x85, 0xcb, 0x9b, 0x7a, 0xe1, 0xf7, 0x9b, 0x7a, 0xe1, 0x9d, 0x2d, 0xcb, 0x0e,
+	0xde, 0x1b, 0x75, 0xf5, 0x9e, 0xeb, 0x40, 0xce, 0xf6, 0x80, 0x76, 0x79, 0x94, 0xff, 0x43, 0x83,
+	0x07, 0xa1, 0x17, 0x59, 0x90, 0x3b, 0x38, 0xf5, 0x18, 0xef, 0x96, 0xc4, 0x4f, 0xe4, 0xad, 0x7f,
+	0x03, 0x00, 0x00, 0xff, 0xff, 0x7f, 0x75, 0xcc, 0x35, 0x8d, 0x0c, 0x00, 0x00,
 }
 
 // Reference imports to suppress errors if they are not otherwise used.
@@ -694,6 +809,8 @@ type QueryClient interface {
 	RegisteredReactions(ctx context.Context, in *QueryRegisteredReactionsRequest, opts ...grpc.CallOption) (*QueryRegisteredReactionsResponse, error)
 	// Params queries the posts module params
 	Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error)
+	// PostComments queries the comments of the post having the given id
+	PostComments(ctx context.Context, in *QueryPostCommentsRequest, opts ...grpc.CallOption) (*QueryPostCommentsResponse, error)
 }
 
 type queryClient struct {
@@ -758,6 +875,15 @@ func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts .
 	return out, nil
 }
 
+func (c *queryClient) PostComments(ctx context.Context, in *QueryPostCommentsRequest, opts ...grpc.CallOption) (*QueryPostCommentsResponse, error) {
+	out := new(QueryPostCommentsResponse)
+	err := c.cc.Invoke(ctx, "/desmos.posts.v1beta1.Query/PostComments", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
 // QueryServer is the server API for Query service.
 type QueryServer interface {
 	// Posts queries all the stored posts
@@ -772,6 +898,8 @@ type QueryServer interface {
 	RegisteredReactions(context.Context, *QueryRegisteredReactionsRequest) (*QueryRegisteredReactionsResponse, error)
 	// Params queries the posts module params
 	Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error)
+	// PostComments queries the comments of the post having the given id
+	PostComments(context.Context, *QueryPostCommentsRequest) (*QueryPostCommentsResponse, error)
 }
 
 // UnimplementedQueryServer can be embedded to have forward compatible implementations.
@@ -796,6 +924,9 @@ func (*UnimplementedQueryServer) RegisteredReactions(ctx context.Context, req *Q
 func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) {
 	return nil, status.Errorf(codes.Unimplemented, "method Params not implemented")
 }
+func (*UnimplementedQueryServer) PostComments(ctx context.Context, req *QueryPostCommentsRequest) (*QueryPostCommentsResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method PostComments not implemented")
+}
 
 func RegisterQueryServer(s grpc1.Server, srv QueryServer) {
 	s.RegisterService(&_Query_serviceDesc, srv)
@@ -909,6 +1040,24 @@ func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interf
 	return interceptor(ctx, in, info, handler)
 }
 
+func _Query_PostComments_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(QueryPostCommentsRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(QueryServer).PostComments(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/desmos.posts.v1beta1.Query/PostComments",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(QueryServer).PostComments(ctx, req.(*QueryPostCommentsRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
 var _Query_serviceDesc = grpc.ServiceDesc{
 	ServiceName: "desmos.posts.v1beta1.Query",
 	HandlerType: (*QueryServer)(nil),
@@ -937,6 +1086,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{
 			MethodName: "Params",
 			Handler:    _Query_Params_Handler,
 		},
+		{
+			MethodName: "PostComments",
+			Handler:    _Query_PostComments_Handler,
+		},
 	},
 	Streams:  []grpc.StreamDesc{},
 	Metadata: "desmos/posts/v1beta1/query.proto",
@@ -1408,6 +1561,97 @@ func (m *QueryReportsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	return len(dAtA) - i, nil
 }
 
+func (m *QueryPostCommentsRequest) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *QueryPostCommentsRequest) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *QueryPostCommentsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.Pagination != nil {
+		{
+			size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i])
+			if err != nil {
+				return 0, err
+			}
+			i -= size
+			i = encodeVarintQuery(dAtA, i, uint64(size))
+		}
+		i--
+		dAtA[i] = 0x12
+	}
+	if len(m.PostId) > 0 {
+		i -= len(m.PostId)
+		copy(dAtA[i:], m.PostId)
+		i = encodeVarintQuery(dAtA, i, uint64(len(m.PostId)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *QueryPostCommentsResponse) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *QueryPostCommentsResponse) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *QueryPostCommentsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.Pagination != nil {
+		{
+			size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i])
+			if err != nil {
+				return 0, err
+			}
+			i -= size
+			i = encodeVarintQuery(dAtA, i, uint64(size))
+		}
+		i--
+		dAtA[i] = 0x12
+	}
+	if len(m.Comments) > 0 {
+		for iNdEx := len(m.Comments) - 1; iNdEx >= 0; iNdEx-- {
+			{
+				size, err := m.Comments[iNdEx].MarshalToSizedBuffer(dAtA[:i])
+				if err != nil {
+					return 0, err
+				}
+				i -= size
+				i = encodeVarintQuery(dAtA, i, uint64(size))
+			}
+			i--
+			dAtA[i] = 0xa
+		}
+	}
+	return len(dAtA) - i, nil
+}
+
 func encodeVarintQuery(dAtA []byte, offset int, v uint64) int {
 	offset -= sovQuery(v)
 	base := offset
@@ -1603,6 +1847,42 @@ func (m *QueryReportsResponse) Size() (n int) {
 	return n
 }
 
+func (m *QueryPostCommentsRequest) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.PostId)
+	if l > 0 {
+		n += 1 + l + sovQuery(uint64(l))
+	}
+	if m.Pagination != nil {
+		l = m.Pagination.Size()
+		n += 1 + l + sovQuery(uint64(l))
+	}
+	return n
+}
+
+func (m *QueryPostCommentsResponse) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if len(m.Comments) > 0 {
+		for _, e := range m.Comments {
+			l = e.Size()
+			n += 1 + l + sovQuery(uint64(l))
+		}
+	}
+	if m.Pagination != nil {
+		l = m.Pagination.Size()
+		n += 1 + l + sovQuery(uint64(l))
+	}
+	return n
+}
+
 func sovQuery(x uint64) (n int) {
 	return (math_bits.Len64(x|1) + 6) / 7
 }
@@ -2819,6 +3099,244 @@ func (m *QueryReportsResponse) Unmarshal(dAtA []byte) error {
 	}
 	return nil
 }
+func (m *QueryPostCommentsRequest) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowQuery
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: QueryPostCommentsRequest: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: QueryPostCommentsRequest: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field PostId", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowQuery
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthQuery
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthQuery
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.PostId = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowQuery
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthQuery
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthQuery
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.Pagination == nil {
+				m.Pagination = &query.PageRequest{}
+			}
+			if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipQuery(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthQuery
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *QueryPostCommentsResponse) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowQuery
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: QueryPostCommentsResponse: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: QueryPostCommentsResponse: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Comments", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowQuery
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthQuery
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthQuery
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Comments = append(m.Comments, Post{})
+			if err := m.Comments[len(m.Comments)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowQuery
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthQuery
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthQuery
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.Pagination == nil {
+				m.Pagination = &query.PageResponse{}
+			}
+			if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipQuery(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthQuery
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
 func skipQuery(dAtA []byte) (n int, err error) {
 	l := len(dAtA)
 	iNdEx := 0
diff --git a/x/staging/posts/types/query.pb.gw.go b/x/staging/posts/types/query.pb.gw.go
index 93fc8e3b..e7ff4a92 100644
--- a/x/staging/posts/types/query.pb.gw.go
+++ b/x/staging/posts/types/query.pb.gw.go
@@ -301,6 +301,78 @@ func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshal
 
 }
 
+var (
+	filter_Query_PostComments_0 = &utilities.DoubleArray{Encoding: map[string]int{"post_id": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
+)
+
+func request_Query_PostComments_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+	var protoReq QueryPostCommentsRequest
+	var metadata runtime.ServerMetadata
+
+	var (
+		val string
+		ok  bool
+		err error
+		_   = err
+	)
+
+	val, ok = pathParams["post_id"]
+	if !ok {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "post_id")
+	}
+
+	protoReq.PostId, err = runtime.String(val)
+
+	if err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "post_id", err)
+	}
+
+	if err := req.ParseForm(); err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
+	}
+	if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_PostComments_0); err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
+	}
+
+	msg, err := client.PostComments(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
+	return msg, metadata, err
+
+}
+
+func local_request_Query_PostComments_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+	var protoReq QueryPostCommentsRequest
+	var metadata runtime.ServerMetadata
+
+	var (
+		val string
+		ok  bool
+		err error
+		_   = err
+	)
+
+	val, ok = pathParams["post_id"]
+	if !ok {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "post_id")
+	}
+
+	protoReq.PostId, err = runtime.String(val)
+
+	if err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "post_id", err)
+	}
+
+	if err := req.ParseForm(); err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
+	}
+	if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_PostComments_0); err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
+	}
+
+	msg, err := server.PostComments(ctx, &protoReq)
+	return msg, metadata, err
+
+}
+
 // RegisterQueryHandlerServer registers the http handlers for service Query to "mux".
 // UnaryRPC     :call QueryServer directly.
 // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
@@ -427,6 +499,26 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv
 
 	})
 
+	mux.Handle("GET", pattern_Query_PostComments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
+		ctx, cancel := context.WithCancel(req.Context())
+		defer cancel()
+		inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
+		rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+		resp, md, err := local_request_Query_PostComments_0(rctx, inboundMarshaler, server, req, pathParams)
+		ctx = runtime.NewServerMetadataContext(ctx, md)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+
+		forward_Query_PostComments_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+	})
+
 	return nil
 }
 
@@ -588,6 +680,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie
 
 	})
 
+	mux.Handle("GET", pattern_Query_PostComments_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
+		ctx, cancel := context.WithCancel(req.Context())
+		defer cancel()
+		inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
+		rctx, err := runtime.AnnotateContext(ctx, mux, req)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+		resp, md, err := request_Query_PostComments_0(rctx, inboundMarshaler, client, req, pathParams)
+		ctx = runtime.NewServerMetadataContext(ctx, md)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+
+		forward_Query_PostComments_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+	})
+
 	return nil
 }
 
@@ -603,6 +715,8 @@ var (
 	pattern_Query_RegisteredReactions_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 1, 2, 3}, []string{"desmos", "posts", "v1beta1", "registered-reactions"}, "", runtime.AssumeColonVerbOpt(true)))
 
 	pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 1, 2, 3}, []string{"desmos", "posts", "v1beta1", "params"}, "", runtime.AssumeColonVerbOpt(true)))
+
+	pattern_Query_PostComments_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 1, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"desmos", "posts", "v1beta1", "post_id", "comments"}, "", runtime.AssumeColonVerbOpt(true)))
 )
 
 var (
@@ -617,4 +731,6 @@ var (
 	forward_Query_RegisteredReactions_0 = runtime.ForwardResponseMessage
 
 	forward_Query_Params_0 = runtime.ForwardResponseMessage
+
+	forward_Query_PostComments_0 = runtime.ForwardResponseMessage
 )


Test patch:
diff --git a/app/desmos/cmd/sign_test.go b/app/desmos/cmd/sign_test.go
new file mode 100644
index 00000000..665a0e0b
--- /dev/null
+++ b/app/desmos/cmd/sign_test.go
@@ -0,0 +1,58 @@
+package cmd_test
+
+import (
+	"encoding/json"
+	"fmt"
+	"os"
+	"testing"
+
+	"github.com/cosmos/cosmos-sdk/client"
+	"github.com/cosmos/cosmos-sdk/client/flags"
+	"github.com/cosmos/cosmos-sdk/crypto/hd"
+	"github.com/cosmos/cosmos-sdk/crypto/keyring"
+	clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	"github.com/stretchr/testify/require"
+
+	"github.com/desmos-labs/desmos/app"
+	cmd "github.com/desmos-labs/desmos/app/desmos/cmd"
+)
+
+func TestGetSignCmd(t *testing.T) {
+	cfg := sdk.GetConfig()
+	app.SetupConfig(cfg)
+	cfg.Seal()
+
+	keyBase := keyring.NewInMemory()
+	algo := hd.Secp256k1
+	hdPath := sdk.GetConfig().GetFullFundraiserPath()
+
+	keyName := "test"
+	mnemonic := "clip toilet stairs jaguar baby over mosquito capital speed mule adjust eye print voyage verify smart open crack imitate auto gauge museum planet rebel"
+	_, err := keyBase.NewAccount(keyName, mnemonic, "", hdPath, algo)
+	require.NoError(t, err)
+
+	output := os.Stdout
+	clientCtx := client.Context{}.
+		WithKeyring(keyBase).
+		WithOutput(output)
+
+	out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd.GetSignCmd(), []string{
+		"This is my signed value",
+		fmt.Sprintf("--%s=%s", flags.FlagFrom, keyName),
+	})
+	require.NoError(t, err)
+
+	var data cmd.SignatureData
+	err = json.Unmarshal(out.Bytes(), &data)
+	require.NoError(t, err)
+
+	expected := cmd.SignatureData{
+		Address:   "d133e902b523aa568f0086609c958c83ac0c4fc1",
+		PubKey:    "03f3fa3e31c3c2833c92b83f26ef29397991acfeb0fbaad8864d047cdd6a0cc155",
+		Signature: "5d492db8df4a6f912188610395574d36cdbaadec00e64bc6341722e81fe38cae7d8b7e6d2896f1fba03e45dd2a9534cab205347f00e177de6796a46811cfa35f",
+		Value:     "This is my signed value",
+	}
+	require.Equal(t, expected, data)
+
+}
diff --git a/x/profiles/keeper/grpc_query_test.go b/x/profiles/keeper/grpc_query_test.go
index daffbf19..0303c2c5 100644
--- a/x/profiles/keeper/grpc_query_test.go
+++ b/x/profiles/keeper/grpc_query_test.go
@@ -282,6 +282,91 @@ func (suite *KeeperTestSuite) Test_UserChainLinks() {
 	}
 }
 
+func (suite *KeeperTestSuite) Test_UserChainLink() {
+	var pubKey, err = sdk.GetPubKeyFromBech32(
+		sdk.Bech32PubKeyTypeAccPub,
+		"cosmospub1addwnpepqvryxhhqhw52c4ny5twtfzf3fsrjqhx0x5cuya0fylw0wu0eqptykeqhr4d",
+	)
+	suite.Require().NoError(err)
+
+	usecases := []struct {
+		name      string
+		store     func(ctx sdk.Context)
+		req       *types.QueryUserChainLinkRequest
+		shouldErr bool
+		expRes    *types.QueryUserChainLinkResponse
+	}{
+		{
+			name: "not found link returns error",
+			req: &types.QueryUserChainLinkRequest{
+				User:      "cosmos1y54exmx84cqtasvjnskf9f63djuuj68p7hqf47",
+				ChainName: "cosmos",
+				Target:    "cosmos1y54exmx84cqtasvjnskf9f63djuuj68p7hqf47",
+			},
+			shouldErr: true,
+		},
+		{
+			name: "existing chain link returns proper response",
+			store: func(ctx sdk.Context) {
+				address := "cosmos1y54exmx84cqtasvjnskf9f63djuuj68p7hqf47"
+				suite.ak.SetAccount(ctx, suite.CreateProfileFromAddress(address))
+
+				link := types.NewChainLink(
+					address,
+					types.NewBech32Address("cosmos1cjf97gpzwmaf30pzvaargfgr884mpp5ak8f7ns", "cosmos"),
+					types.NewProof(
+						pubKey,
+						"909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b",
+						"text",
+					),
+					types.NewChainConfig("cosmos"),
+					suite.testData.profile.CreationDate,
+				)
+				suite.Require().NoError(suite.k.SaveChainLink(ctx, link))
+			},
+			req: &types.QueryUserChainLinkRequest{
+				User:      "cosmos1y54exmx84cqtasvjnskf9f63djuuj68p7hqf47",
+				ChainName: "cosmos",
+				Target:    "cosmos1cjf97gpzwmaf30pzvaargfgr884mpp5ak8f7ns",
+			},
+			shouldErr: false,
+			expRes: &types.QueryUserChainLinkResponse{
+				Link: types.NewChainLink(
+					"cosmos1y54exmx84cqtasvjnskf9f63djuuj68p7hqf47",
+					types.NewBech32Address("cosmos1cjf97gpzwmaf30pzvaargfgr884mpp5ak8f7ns", "cosmos"),
+					types.NewProof(
+						pubKey,
+						"909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b",
+						"text",
+					),
+					types.NewChainConfig("cosmos"),
+					suite.testData.profile.CreationDate,
+				),
+			},
+		},
+	}
+
+	for _, uc := range usecases {
+		uc := uc
+		suite.Run(uc.name, func() {
+			ctx, _ := suite.ctx.CacheContext()
+			if uc.store != nil {
+				uc.store(ctx)
+			}
+
+			res, err := suite.k.UserChainLink(sdk.WrapSDKContext(ctx), uc.req)
+			if uc.shouldErr {
+				suite.Require().Error(err)
+			} else {
+				suite.Require().NoError(err)
+				suite.Require().NotNil(res)
+
+				suite.Require().Equal(uc.expRes, res)
+			}
+		})
+	}
+}
+
 func (suite *KeeperTestSuite) Test_UserRelationships() {
 	usecases := []struct {
 		name                string
@@ -458,3 +543,169 @@ func (suite *KeeperTestSuite) TestQueryServer_UserApplicationLinks() {
 		})
 	}
 }
+
+func (suite *KeeperTestSuite) TestQueryServer_UserApplicationLink() {
+	usecases := []struct {
+		name        string
+		store       func(ctx sdk.Context)
+		req         *types.QueryUserApplicationLinkRequest
+		shouldErr   bool
+		expResponse *types.QueryUserApplicationLinkResponse
+	}{
+		{
+			name:      "not found link returns error",
+			req:       types.NewQueryUserApplicationLinkRequest("user", "application", "username"),
+			shouldErr: true,
+		},
+		{
+			name: "valid request returns proper response",
+			store: func(ctx sdk.Context) {
+				profile := suite.CreateProfileFromAddress("cosmos1y54exmx84cqtasvjnskf9f63djuuj68p7hqf47")
+				suite.ak.SetAccount(ctx, profile)
+
+				suite.Require().NoError(suite.k.SaveApplicationLink(
+					ctx,
+					types.NewApplicationLink(
+						"cosmos1y54exmx84cqtasvjnskf9f63djuuj68p7hqf47",
+						types.NewData("twitter", "twitteruser"),
+						types.ApplicationLinkStateInitialized,
+						types.NewOracleRequest(
+							-1,
+							1,
+							types.NewOracleRequestCallData(
+								"twitter",
+								"7B22757365726E616D65223A22526963636172646F4D222C22676973745F6964223A223732306530303732333930613930316262383065353966643630643766646564227D",
+							),
+							"client_id",
+						),
+						nil,
+						time.Date(2020, 1, 1, 00, 00, 00, 000, time.UTC),
+					)),
+				)
+			},
+			req: types.NewQueryUserApplicationLinkRequest(
+				"cosmos1y54exmx84cqtasvjnskf9f63djuuj68p7hqf47",
+				"twitter",
+				"twitteruser",
+			),
+			shouldErr: false,
+			expResponse: &types.QueryUserApplicationLinkResponse{
+				Link: types.NewApplicationLink(
+					"cosmos1y54exmx84cqtasvjnskf9f63djuuj68p7hqf47",
+					types.NewData("twitter", "twitteruser"),
+					types.ApplicationLinkStateInitialized,
+					types.NewOracleRequest(
+						-1,
+						1,
+						types.NewOracleRequestCallData(
+							"twitter",
+							"7B22757365726E616D65223A22526963636172646F4D222C22676973745F6964223A223732306530303732333930613930316262383065353966643630643766646564227D",
+						),
+						"client_id",
+					),
+					nil,
+					time.Date(2020, 1, 1, 00, 00, 00, 000, time.UTC),
+				),
+			},
+		},
+	}
+
+	for _, uc := range usecases {
+		uc := uc
+		suite.Run(uc.name, func() {
+			ctx, _ := suite.ctx.CacheContext()
+			if uc.store != nil {
+				uc.store(ctx)
+			}
+
+			res, err := suite.k.UserApplicationLink(sdk.WrapSDKContext(ctx), uc.req)
+			if uc.shouldErr {
+				suite.Require().Error(err)
+			} else {
+				suite.Require().NoError(err)
+				suite.Require().Equal(uc.expResponse, res)
+			}
+		})
+	}
+}
+
+func (suite *KeeperTestSuite) TestQueryServer_ApplicationLinkByClientID() {
+	usecases := []struct {
+		name        string
+		store       func(ctx sdk.Context)
+		req         *types.QueryApplicationLinkByClientIDRequest
+		shouldErr   bool
+		expResponse *types.QueryApplicationLinkByClientIDResponse
+	}{
+		{
+			name:      "not found link returns error",
+			req:       types.NewQueryApplicationLinkByClientIDRequest("client_id"),
+			shouldErr: true,
+		},
+		{
+			name: "valid request returns proper response",
+			store: func(ctx sdk.Context) {
+				profile := suite.CreateProfileFromAddress("cosmos1y54exmx84cqtasvjnskf9f63djuuj68p7hqf47")
+				suite.ak.SetAccount(ctx, profile)
+
+				suite.Require().NoError(suite.k.SaveApplicationLink(
+					ctx,
+					types.NewApplicationLink(
+						"cosmos1y54exmx84cqtasvjnskf9f63djuuj68p7hqf47",
+						types.NewData("twitter", "twitteruser"),
+						types.ApplicationLinkStateInitialized,
+						types.NewOracleRequest(
+							-1,
+							1,
+							types.NewOracleRequestCallData(
+								"twitter",
+								"7B22757365726E616D65223A22526963636172646F4D222C22676973745F6964223A223732306530303732333930613930316262383065353966643630643766646564227D",
+							),
+							"client_id",
+						),
+						nil,
+						time.Date(2020, 1, 1, 00, 00, 00, 000, time.UTC),
+					)),
+				)
+			},
+			req:       types.NewQueryApplicationLinkByClientIDRequest("client_id"),
+			shouldErr: false,
+			expResponse: &types.QueryApplicationLinkByClientIDResponse{
+				Link: types.NewApplicationLink(
+					"cosmos1y54exmx84cqtasvjnskf9f63djuuj68p7hqf47",
+					types.NewData("twitter", "twitteruser"),
+					types.ApplicationLinkStateInitialized,
+					types.NewOracleRequest(
+						-1,
+						1,
+						types.NewOracleRequestCallData(
+							"twitter",
+							"7B22757365726E616D65223A22526963636172646F4D222C22676973745F6964223A223732306530303732333930613930316262383065353966643630643766646564227D",
+						),
+						"client_id",
+					),
+					nil,
+					time.Date(2020, 1, 1, 00, 00, 00, 000, time.UTC),
+				),
+			},
+		},
+	}
+
+	for _, uc := range usecases {
+		uc := uc
+		suite.Run(uc.name, func() {
+			ctx, _ := suite.ctx.CacheContext()
+			if uc.store != nil {
+				uc.store(ctx)
+			}
+
+			res, err := suite.k.ApplicationLinkByClientID(sdk.WrapSDKContext(ctx), uc.req)
+			if uc.shouldErr {
+				suite.Require().Error(err)
+			} else {
+				suite.Require().NoError(err)
+				suite.Require().Equal(uc.expResponse, res)
+			}
+		})
+	}
+}
diff --git a/x/staging/posts/client/cli/cli_test.go b/x/staging/posts/client/cli/cli_test.go
index e2bde12a..661c81d8 100644
--- a/x/staging/posts/client/cli/cli_test.go
+++ b/x/staging/posts/client/cli/cli_test.go
@@ -28,7 +28,7 @@ type IntegrationTestSuite struct {
 
 func TestIntegrationTestSuite(t *testing.T) {
 	//TODO restore this when out of staging
-	//suite.Run(t, new(IntegrationTestSuite))
+	// suite.Run(t, new(IntegrationTestSuite))
 }
 
 func (s *IntegrationTestSuite) SetupSuite() {
@@ -69,7 +69,7 @@ func (s *IntegrationTestSuite) SetupSuite() {
 			LastEdited:           creationDate.Add(1),
 			Subspace:             "4e188d9c17150037d5199bbdb91ae1eb2a78a15aca04cb35530cccb81494b36e",
 			CommentsState:        types.CommentsStateAllowed,
-			AdditionalAttributes: nil,
+			AdditionalAttributes: []types.Attribute{},
 			Creator:              "cosmos1y54exmx84cqtasvjnskf9f63djuuj68p7hqf47",
 			Attachments: types.NewAttachments(
 				types.NewAttachment(
@@ -89,6 +89,18 @@ func (s *IntegrationTestSuite) SetupSuite() {
 				AllowsAnswerEdits:     true,
 			},
 		},
+		{
+			PostID:               "29de02e105c68a60e45c289bff19fde745bca9c63c38f2095b59e8e8090ae1af",
+			ParentID:             "19de02e105c68a60e45c289bff19fde745bca9c63c38f2095b59e8e8090ae1af",
+			Message:              "Post message",
+			Created:              creationDate,
+			LastEdited:           creationDate.Add(1),
+			Subspace:             "4e188d9c17150037d5199bbdb91ae1eb2a78a15aca04cb35530cccb81494b36e",
+			CommentsState:        types.CommentsStateAllowed,
+			AdditionalAttributes: []types.Attribute{},
+			Attachments:          []types.Attachment{},
+			Creator:              "cosmos1y54exmx84cqtasvjnskf9f63djuuj68p7hqf47",
+		},
 	}
 	postsData.UsersPollAnswers = []types.UserAnswer{
 		types.NewUserAnswer(
@@ -254,6 +266,7 @@ func (s *IntegrationTestSuite) TestCmdQueryPosts() {
 						Created:              creationDate,
 						LastEdited:           creationDate.Add(1),
 						Subspace:             "4e188d9c17150037d5199bbdb91ae1eb2a78a15aca04cb35530cccb81494b36e",
+						CommentsState:        types.CommentsStateAllowed,
 						AdditionalAttributes: []types.Attribute{},
 						Creator:              "cosmos1y54exmx84cqtasvjnskf9f63djuuj68p7hqf47",
 						Attachments: types.NewAttachments(
@@ -274,6 +287,18 @@ func (s *IntegrationTestSuite) TestCmdQueryPosts() {
 							AllowsAnswerEdits:     true,
 						},
 					},
+					{
+						PostID:               "29de02e105c68a60e45c289bff19fde745bca9c63c38f2095b59e8e8090ae1af",
+						ParentID:             "19de02e105c68a60e45c289bff19fde745bca9c63c38f2095b59e8e8090ae1af",
+						Message:              "Post message",
+						Created:              creationDate,
+						LastEdited:           creationDate.Add(1),
+						Subspace:             "4e188d9c17150037d5199bbdb91ae1eb2a78a15aca04cb35530cccb81494b36e",
+						CommentsState:        types.CommentsStateAllowed,
+						AdditionalAttributes: []types.Attribute{},
+						Attachments:          []types.Attachment{},
+						Creator:              "cosmos1y54exmx84cqtasvjnskf9f63djuuj68p7hqf47",
+					},
 				},
 			},
 		},
@@ -295,6 +320,7 @@ func (s *IntegrationTestSuite) TestCmdQueryPosts() {
 				var response types.QueryPostsResponse
 				s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &response), out.String())
 				s.Require().NotEmpty(response.Posts)
+				s.Require().Equal(tc.expectedOutput.Posts, response.Posts)
 			}
 		})
 	}
@@ -562,6 +588,65 @@ func (s *IntegrationTestSuite) TestCmdQueryParams() {
 	}
 }
 
+func (s *IntegrationTestSuite) TestCmdQueryPostComments() {
+	val := s.network.Validators[0]
+
+	creationDate, err := time.Parse(time.RFC3339, "2020-01-01T15:15:00.000Z")
+	s.Require().NoError(err)
+
+	testCases := []struct {
+		name           string
+		args           []string
+		expectErr      bool
+		expectedOutput *types.QueryPostCommentsResponse
+	}{
+		{
+			name: "data is returned properly",
+			args: []string{
+				"19de02e105c68a60e45c289bff19fde745bca9c63c38f2095b59e8e8090ae1af",
+				fmt.Sprintf("--%s=json", tmcli.OutputFlag),
+			},
+			expectErr: false,
+			expectedOutput: &types.QueryPostCommentsResponse{
+				Comments: []types.Post{
+					{
+						PostID:               "29de02e105c68a60e45c289bff19fde745bca9c63c38f2095b59e8e8090ae1af",
+						ParentID:             "19de02e105c68a60e45c289bff19fde745bca9c63c38f2095b59e8e8090ae1af",
+						Message:              "Post message",
+						Created:              creationDate,
+						LastEdited:           creationDate.Add(1),
+						Subspace:             "4e188d9c17150037d5199bbdb91ae1eb2a78a15aca04cb35530cccb81494b36e",
+						CommentsState:        types.CommentsStateAllowed,
+						AdditionalAttributes: []types.Attribute{},
+						Attachments:          []types.Attachment{},
+						Creator:              "cosmos1y54exmx84cqtasvjnskf9f63djuuj68p7hqf47",
+					},
+				},
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		tc := tc
+
+		s.Run(tc.name, func() {
+			cmd := cli.GetCmdQueryPostComments()
+			clientCtx := val.ClientCtx
+			out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args)
+
+			if tc.expectErr {
+				s.Require().Error(err)
+			} else {
+				s.Require().NoError(err)
+
+				var response types.QueryPostCommentsResponse
+				s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &response), out.String())
+				s.Require().Equal(tc.expectedOutput.Comments, response.Comments)
+			}
+		})
+	}
+}
+
 // ___________________________________________________________________________________________________________________
 
 func (s *IntegrationTestSuite) TestCmdCreatePost() {
diff --git a/x/staging/posts/keeper/common_test.go b/x/staging/posts/keeper/common_test.go
index a4e4c973..e7d97e5b 100644
--- a/x/staging/posts/keeper/common_test.go
+++ b/x/staging/posts/keeper/common_test.go
@@ -67,6 +67,7 @@ type TestData struct {
 	registeredReaction     types.RegisteredReaction
 	post                   types.Post
 	subspace               subspacetypes.Subspace
+	otherSubspace          subspacetypes.Subspace
 }
 
 func (suite *KeeperTestSuite) SetupTest() {
@@ -169,6 +170,15 @@ func (suite *KeeperTestSuite) SetupTest() {
 		blockTime,
 	)
 
+	suite.testData.otherSubspace = subspacetypes.NewSubspace(
+		"5e188d9c17150037d5199bbdb91ae1eb2a78a15aca04cb35530cccb81494b36e",
+		"test",
+		suite.testData.postOwner,
+		suite.testData.postOwner,
+		subspacetypes.SubspaceTypeOpen,
+		blockTime,
+	)
+
 	suite.testData.postCreationDate = blockTime
 	suite.testData.postEndPollDate, _ = time.Parse(time.RFC3339, "2050-01-01T15:15:00.000Z")
 	suite.testData.postEndPollDateExpired, _ = time.Parse(time.RFC3339, "2019-01-01T01:15:00.000Z")
diff --git a/x/staging/posts/keeper/grpc_query_test.go b/x/staging/posts/keeper/grpc_query_test.go
index 7d7dddba..9699df5b 100644
--- a/x/staging/posts/keeper/grpc_query_test.go
+++ b/x/staging/posts/keeper/grpc_query_test.go
@@ -475,3 +475,126 @@ func (suite *KeeperTestSuite) Test_UserAnswers() {
 		})
 	}
 }
+
+func (suite *KeeperTestSuite) Test_PostComments() {
+	creationDate, err := time.Parse(time.RFC3339, "2020-01-01T15:15:00.000Z")
+	suite.Require().NoError(err)
+	pollEndDate, err := time.Parse(time.RFC3339, "2050-01-01T15:15:00.000Z")
+	suite.Require().NoError(err)
+	posts := []types.Post{
+		{
+			PostID:               "19de02e105c68a60e45c289bff19fde745bca9c63c38f2095b59e8e8090ae1af",
+			Message:              "Post message #desmos",
+			Created:              creationDate,
+			LastEdited:           creationDate.Add(1),
+			CommentsState:        types.CommentsStateAllowed,
+			Subspace:             "4e188d9c17150037d5199bbdb91ae1eb2a78a15aca04cb35530cccb81494b36e",
+			AdditionalAttributes: nil,
+			Creator:              "cosmos1y54exmx84cqtasvjnskf9f63djuuj68p7hqf47",
+			Attachments: types.NewAttachments(
+				types.NewAttachment(
+					"https://uri.com",
+					"text/plain",
+					[]string{"cosmos1y54exmx84cqtasvjnskf9f63djuuj68p7hqf47"},
+				),
+			),
+			PollData: &types.PollData{
+				Question: "poll?",
+				ProvidedAnswers: types.NewPollAnswers(
+					types.NewPollAnswer("1", "Yes"),
+					types.NewPollAnswer("2", "No"),
+				),
+				EndDate:               pollEndDate,
+				AllowsMultipleAnswers: true,
+				AllowsAnswerEdits:     true,
+			},
+		},
+		{
+			PostID:               "29de02e105c68a60e45c289bff19fde745bca9c63c38f2095b59e8e8090ae1af",
+			ParentID:             "19de02e105c68a60e45c289bff19fde745bca9c63c38f2095b59e8e8090ae1af",
+			Message:              "Post message",
+			Created:              creationDate.Add(2),
+			LastEdited:           creationDate.Add(2),
+			CommentsState:        types.CommentsStateAllowed,
+			Subspace:             "4e188d9c17150037d5199bbdb91ae1eb2a78a15aca04cb35530cccb81494b36e",
+			AdditionalAttributes: nil,
+			Creator:              "cosmos1ftkjv8njvkekk00ehwdfl5sst8zgdpenjfm4hs",
+		},
+		{
+			PostID:               "39de02e105c68a60e45c289bff19fde745bca9c63c38f2095b59e8e8090ae1af",
+			ParentID:             "19de02e105c68a60e45c289bff19fde745bca9c63c38f2095b59e8e8090ae1af",
+			Message:              "Post message",
+			Created:              creationDate.Add(2),
+			LastEdited:           creationDate.Add(2),
+			CommentsState:        types.CommentsStateAllowed,
+			Subspace:             "4e188d9c17150037d5199bbdb91ae1eb2a78a15aca04cb35530cccb81494b36e",
+			AdditionalAttributes: nil,
+			Creator:              "cosmos1ftkjv8njvkekk00ehwdfl5sst8zgdpenjfm4hs",
+		},
+	}
+
+	usecases := []struct {
+		name      string
+		store     func(ctx sdk.Context)
+		req       *types.QueryPostCommentsRequest
+		shouldErr bool
+		expLen    int
+	}{
+		{
+			name:      "invalid request return error",
+			req:       &types.QueryPostCommentsRequest{},
+			shouldErr: true,
+		},
+		{
+			name: "non existent post id returns error",
+			req: &types.QueryPostCommentsRequest{
+				PostId: "19de02e105c68a60e45c289bff19fde745bca9c63c38f2095b59e8e8090ae1af",
+			},
+			shouldErr: true,
+		},
+		{
+			name: "valid request returns properly",
+			store: func(ctx sdk.Context) {
+				for _, post := range posts {
+					suite.k.SavePost(ctx, post)
+				}
+			},
+			req: &types.QueryPostCommentsRequest{
+				PostId: "19de02e105c68a60e45c289bff19fde745bca9c63c38f2095b59e8e8090ae1af",
+			},
+			shouldErr: false,
+			expLen:    2,
+		},
+		{
+			name: "valid request with pagination returns properly",
+			store: func(ctx sdk.Context) {
+				for _, post := range posts {
+					suite.k.SavePost(ctx, post)
+				}
+			},
+			req: &types.QueryPostCommentsRequest{
+				PostId:     "19de02e105c68a60e45c289bff19fde745bca9c63c38f2095b59e8e8090ae1af",
+				Pagination: &query.PageRequest{Limit: 1},
+			},
+			shouldErr: false,
+			expLen:    1,
+		},
+	}
+	suite.SetupTest()
+	for _, uc := range usecases {
+		suite.Run(uc.name, func() {
+			ctx, _ := suite.ctx.CacheContext()
+			if uc.store != nil {
+				uc.store(ctx)
+			}
+			res, err := suite.k.PostComments(sdk.WrapSDKContext(ctx), uc.req)
+			if uc.shouldErr {
+				suite.Require().Error(err)
+			} else {
+				suite.Require().NoError(err)
+				suite.Require().NotNil(res)
+				suite.Require().Equal(uc.expLen, len(res.Comments))
+			}
+		})
+	}
+}
diff --git a/x/staging/posts/keeper/keeper_test.go b/x/staging/posts/keeper/keeper_test.go
index 22b841ba..0f264165 100644
--- a/x/staging/posts/keeper/keeper_test.go
+++ b/x/staging/posts/keeper/keeper_test.go
@@ -1,16 +1,31 @@
 package keeper_test
 
 import (
+	sdk "github.com/cosmos/cosmos-sdk/types"
 	"github.com/desmos-labs/desmos/x/staging/posts/types"
 )
 
+// getPostsCommentsIDs returns the comments of the post having the given id
+func (suite *KeeperTestSuite) getPostsCommentsIDs(ctx sdk.Context, postID string) []string {
+	store := ctx.KVStore(suite.storeKey)
+	iterator := sdk.KVStorePrefixIterator(store, types.PostCommentsPrefix(postID))
+	defer iterator.Close()
+
+	commentIDs := []string{}
+	for ; iterator.Valid(); iterator.Next() {
+		commentID := string(iterator.Value())
+		commentIDs = append(commentIDs, commentID)
+	}
+	return commentIDs
+}
+
 func (suite *KeeperTestSuite) TestKeeper_SavePost() {
 	tests := []struct {
-		name                 string
-		existingPosts        []types.Post
-		newPost              types.Post
-		expParentCommentsIDs []string
-		expLastID            string
+		name           string
+		existingPosts  []types.Post
+		newPost        types.Post
+		expCommentsIDs []string
+		expLastID      string
 	}{
 		{
 			name: "Post with ID already present",
@@ -34,6 +49,7 @@ func (suite *KeeperTestSuite) TestKeeper_SavePost() {
 				AdditionalAttributes: nil,
 				Creator:              suite.testData.post.Creator,
 			},
+			expCommentsIDs: []string{},
 		},
 		{
 			name: "Post which ID is not already present",
@@ -57,6 +73,7 @@ func (suite *KeeperTestSuite) TestKeeper_SavePost() {
 				AdditionalAttributes: nil,
 				Creator:              suite.testData.post.Creator,
 			},
+			expCommentsIDs: []string{},
 		},
 		{
 			name: "Post with valid parent ID",
@@ -81,7 +98,7 @@ func (suite *KeeperTestSuite) TestKeeper_SavePost() {
 				AdditionalAttributes: nil,
 				Creator:              suite.testData.post.Creator,
 			},
-			expParentCommentsIDs: []string{
+			expCommentsIDs: []string{
 				"f1b909289cd23188c19da17ae5d5a05ad65623b0fad756e5e03c8c936ca876fd",
 			},
 		},
@@ -109,6 +126,7 @@ func (suite *KeeperTestSuite) TestKeeper_SavePost() {
 				},
 				Creator: suite.testData.postOwner,
 			},
+			expCommentsIDs: []string{},
 		},
 		{
 			name: "Post with ID lesser ID than Last ID stored",
@@ -132,6 +150,7 @@ func (suite *KeeperTestSuite) TestKeeper_SavePost() {
 				AdditionalAttributes: nil,
 				Creator:              suite.testData.postOwner,
 			},
+			expCommentsIDs: []string{},
 		},
 		{
 			name:          "Post with medias is saved properly",
@@ -146,6 +165,7 @@ func (suite *KeeperTestSuite) TestKeeper_SavePost() {
 				Creator:              suite.testData.postOwner,
 				Attachments:          suite.testData.post.Attachments,
 			},
+			expCommentsIDs: []string{},
 		},
 		{
 			name:          "Post with poll data is saved properly",
@@ -160,12 +180,14 @@ func (suite *KeeperTestSuite) TestKeeper_SavePost() {
 				Creator:              suite.testData.postOwner,
 				PollData:             suite.testData.post.PollData,
 			},
+			expCommentsIDs: []string{},
 		},
 	}
 
 	for _, test := range tests {
 		test := test
 		suite.Run(test.name, func() {
+			suite.SetupTest()
 			store := suite.ctx.KVStore(suite.storeKey)
 			for _, post := range test.existingPosts {
 				store.Set(types.PostStoreKey(post.PostID), suite.cdc.MustMarshalBinaryBare(&post))
@@ -179,10 +201,9 @@ func (suite *KeeperTestSuite) TestKeeper_SavePost() {
 			suite.cdc.MustUnmarshalBinaryBare(store.Get(types.PostStoreKey(test.newPost.PostID)), &expected)
 			suite.True(expected.Equal(test.newPost))
 
-			// Check the parent comments
-			var wrapped types.CommentIDs
-			suite.cdc.MustUnmarshalBinaryBare(store.Get(types.PostCommentsStoreKey(test.newPost.ParentID)), &wrapped)
-			suite.Equal(test.expParentCommentsIDs, wrapped.Ids)
+			// Check the post comments
+			ids := suite.getPostsCommentsIDs(suite.ctx, test.newPost.ParentID)
+			suite.Equal(test.expCommentsIDs, ids)
 		})
 	}
 }
@@ -330,7 +351,7 @@ func (suite *KeeperTestSuite) TestKeeper_GetPostChildrenIDs() {
 				suite.k.SavePost(suite.ctx, p)
 			}
 
-			storedChildrenIDs := suite.k.GetPostChildrenIDs(suite.ctx, test.postID)
+			storedChildrenIDs := suite.getPostsCommentsIDs(suite.ctx, test.postID)
 			suite.Len(storedChildrenIDs, len(test.expChildrenIDs))
 
 			for _, id := range test.expChildrenIDs {
diff --git a/x/staging/posts/keeper/msgs_server_test.go b/x/staging/posts/keeper/msgs_server_test.go
index 5299fcd6..73edf60e 100644
--- a/x/staging/posts/keeper/msgs_server_test.go
+++ b/x/staging/posts/keeper/msgs_server_test.go
@@ -190,6 +190,33 @@ func (suite *KeeperTestSuite) TestMsgServer_CreatePost() {
 			),
 			expError: true,
 		},
+		{
+			name: "The subspace of the comment is not same as the parent post",
+			storedPosts: []types.Post{
+				{
+					PostID:               "b7c1193823638c3a65f4f1933e1c22928f710919fb86d01364024e407b3634af",
+					ParentID:             suite.testData.post.ParentID,
+					Message:              suite.testData.post.Message,
+					Created:              suite.testData.post.Created,
+					CommentsState:        types.CommentsStateAllowed,
+					Subspace:             suite.testData.post.Subspace,
+					AdditionalAttributes: suite.testData.post.AdditionalAttributes,
+					Creator:              suite.testData.post.Creator,
+					PollData:             suite.testData.post.PollData,
+				},
+			},
+			msg: types.NewMsgCreatePost(
+				suite.testData.post.Message,
+				"b7c1193823638c3a65f4f1933e1c22928f710919fb86d01364024e407b3634af",
+				suite.testData.post.CommentsState,
+				"5e188d9c17150037d5199bbdb91ae1eb2a78a15aca04cb35530cccb81494b36e",
+				suite.testData.post.AdditionalAttributes,
+				suite.testData.post.Creator,
+				suite.testData.post.Attachments,
+				suite.testData.post.PollData,
+			),
+			expError: true,
+		},
 	}
 
 	for _, test := range tests {
@@ -201,6 +228,9 @@ func (suite *KeeperTestSuite) TestMsgServer_CreatePost() {
 			err := suite.sk.SaveSubspace(suite.ctx, suite.testData.subspace, suite.testData.postOwner)
 			suite.Require().NoError(err)
 
+			err = suite.sk.SaveSubspace(suite.ctx, suite.testData.otherSubspace, suite.testData.postOwner)
+			suite.Require().NoError(err)
+
 			for _, post := range test.storedPosts {
 				suite.k.SavePost(suite.ctx, post)
 			}
diff --git a/x/staging/posts/simulation/decoder_test.go b/x/staging/posts/simulation/decoder_test.go
index c34c2461..c407eaac 100644
--- a/x/staging/posts/simulation/decoder_test.go
+++ b/x/staging/posts/simulation/decoder_test.go
@@ -25,7 +25,7 @@ func TestDecodeStore(t *testing.T) {
 
 	post := types.NewPost(
 		"e1ba4807a15d8579f79cfd90a07fc015e6125565c9271eb94aded0b2ebf86163",
-		"",
+		"h1ba4807a15d8579f79cfd90a07fc015e6125565c9271eb94aded0b2ebf86163",
 		"Post message",
 		types.CommentsStateAllowed,
 		"4e188d9c17150037d5199bbdb91ae1eb2a78a15aca04cb35530cccb81494b36e",
@@ -52,11 +52,8 @@ func TestDecodeStore(t *testing.T) {
 		address,
 	)
 
-	comments := types.CommentIDs{Ids: []string{
-		"19de02e105c68a60e45c289bff19fde745bca9c63c38f2095b59e8e8090ae1af",
-		"f1b909289cd23188c19da17ae5d5a05ad65623b0fad756e5e03c8c936ca876fd",
-		"4e188d9c17150037d5199bbdb91ae1eb2a78a15aca04cb35530cccb81494b36e",
-	}}
+	commentID := "g1ba4807a15d8579f79cfd90a07fc015e6125565c9271eb94aded0b2ebf86163"
+
 	postReactions := types.PostReactions{Reactions: []types.PostReaction{
 		types.NewPostReaction(":thumbsup:", "👍", address),
 		types.NewPostReaction("blue_heart:", "💙", address),
@@ -91,8 +88,8 @@ func TestDecodeStore(t *testing.T) {
 			Value: cdc.MustMarshalBinaryBare(&post),
 		},
 		{
-			Key:   types.PostCommentsStoreKey(post.PostID),
-			Value: cdc.MustMarshalBinaryBare(&comments),
+			Key:   types.CommentsStoreKey(post.PostID, commentID),
+			Value: []byte(commentID),
 		},
 		{
 			Key:   types.PostReactionsStoreKey(post.PostID),
@@ -113,9 +110,9 @@ func TestDecodeStore(t *testing.T) {
 		expectedLog string
 	}{
 		{"Post", fmt.Sprintf("PostA: %s\nPostB: %s\n", post.String(), post.String())},
-		{"Comments", fmt.Sprintf("CommentsA: %s\nCommentsB: %s\n", comments, comments)},
+		{"Comment", fmt.Sprintf("CommentA: %s\nCommentB: %s\n", commentID, commentID)},
 		{"PostReactions", fmt.Sprintf("PostReactionsA: %s\nPostReactionsB: %s\n", postReactions, postReactions)},
-		{"Reactions", fmt.Sprintf("ReactionA: %s\nReactionB: %s\n", registeredReaction, registeredReaction)},
+		{"Reaction", fmt.Sprintf("ReactionA: %s\nReactionB: %s\n", registeredReaction, registeredReaction)},
 		{"Report", fmt.Sprintf("ReportsA: %s\nReportsB: %s\n", reports, reports)},
 		{"other", ""},
 	}
diff --git a/x/staging/posts/types/posts_test.go b/x/staging/posts/types/posts_test.go
index 60282b80..d8e9556e 100644
--- a/x/staging/posts/types/posts_test.go
+++ b/x/staging/posts/types/posts_test.go
@@ -404,49 +404,6 @@ func Test_CommentsStateFromString(t *testing.T) {
 
 // ___________________________________________________________________________________________________________________
 
-func TestCommentIDs_AppendIfMissing(t *testing.T) {
-	usecases := []struct {
-		name        string
-		ids         types.CommentIDs
-		toAppend    string
-		expAppended bool
-		expSlice    types.CommentIDs
-	}{
-		{
-			name:        "id is appended correctly to empty slice",
-			ids:         types.CommentIDs{},
-			toAppend:    "1",
-			expAppended: true,
-			expSlice:    types.CommentIDs{Ids: []string{"1"}},
-		},
-		{
-			name:        "missing id is appended properly",
-			ids:         types.CommentIDs{Ids: []string{"1"}},
-			toAppend:    "2",
-			expAppended: true,
-			expSlice:    types.CommentIDs{Ids: []string{"1", "2"}},
-		},
-		{
-			name:        "present id is not appended",
-			ids:         types.CommentIDs{Ids: []string{"1"}},
-			toAppend:    "1",
-			expAppended: false,
-			expSlice:    types.CommentIDs{Ids: []string{"1"}},
-		},
-	}
-
-	for _, uc := range usecases {
-		uc := uc
-		t.Run(uc.name, func(t *testing.T) {
-			slice, appended := uc.ids.AppendIfMissing(uc.toAppend)
-			require.Equal(t, uc.expAppended, appended)
-			require.Equal(t, uc.expSlice, slice)
-		})
-	}
-}
-
-// ___________________________________________________________________________________________________________________
-
 func TestAttachments_Equal(t *testing.T) {
 	tests := []struct {
 		name      string