Repository: desmos-labs/desmos
Pull request #708 (2021-12-22 04:29:21)

feat: ADR-005: Support multisig chain link
## Description

Closes: #633 
This PR implements ADR-005.

<!-- Add a description of the changes that this PR introduces and the files that
are the most critical to review. -->

---

### Author Checklist

*All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.*

I have...

- [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] added `!` to the type prefix if API or client breaking change
- [x] targeted the correct branch (see [PR Targeting](https://github.com/desmos-labs/desmos/blob/master/CONTRIBUTING.md#pr-targeting))
- [ ] provided a link to the relevant issue or specification
- [ ] followed the guidelines for [building modules](https://docs.cosmos.network/v0.44/building-modules/intro.html)
- [x] included the necessary unit and integration [tests](https://github.com/desmos-labs/desmos/blob/master/CONTRIBUTING.md#testing)
- [x] added a changelog entry to `CHANGELOG.md`
- [ ] included comments for [documenting Go code](https://blog.golang.org/godoc)
- [ ] updated the relevant documentation or specification
- [ ] reviewed "Files changed" and left comments if necessary
- [ ] confirmed all CI checks have passed

### Reviewers Checklist

*All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.*

I have...

- [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] confirmed `!` in the type prefix if API or client breaking change
- [ ] confirmed all author checklist items have been addressed
- [ ] reviewed state machine logic
- [ ] reviewed API design and naming
- [ ] reviewed documentation is accurate
- [ ] reviewed tests and test coverage
- [ ] manually tested (if applicable)

dadamu: Hi, @RiccardoM @bragaz 
It seems that we can not use `SignatureDescriptor_Data` in the `Proof` directly, The error happens when `msg.GetSignBytes()` is called, which is 
```
--- FAIL: TestMsgLinkChainAccount_GetSignBytes (0.00s)
panic: Unregistered interface signing.isSignatureDescriptor_Data_Sum [recovered]
        panic: Unregistered interface signing.isSignatureDescriptor_Data_Sum
``` 
because `signing.isSignatureDescriptor_Data_Sum` doesn't register to the `AnimoCodec`. In addition, the interface is a private interface generated by proto automatically.

I propose that we can convert it to hex string from the proto, instead.

What do you think?
RiccardoM: @dadamu What if we use the `SignatureData` interface instead, and encode it using an `Any` instance the same way we do with `MsgLinkChainAccount#AddressData`? 
dadamu: @RiccardoM Unfortunately, `SignatureData` doesn't implement `proto.Message`. Besides, the structs of it are not the proto as well. 
So it can not be used in the `Proof` proto object.
RiccardoM: @dadamu I have just finished experimenting with this problem, and I think that we can do as follows: 

**1.** Create a new interface called `SignatureData` that extends `proto.Message` and has a `isSignatureDataMethod`
**2.** Create two new Proto definitions: `SingleSignatureData` and `MultiSignatureData` that contain the same fields of the Cosmos ones. The only difference is that our `MultiSignatureData` will have its `Signatures` field that is going to be an array of  `codectypes.Any` rather than an array of `signing.SignatureData`.
**3.** Add a method called `SignatureDataToCosmosSinatureData` that takes a `SignatureData` interface instance and returns a `signing.SignatureData` instance. 

Then, we only need to register the new `SignatureData` interface and its implementations inside our Amino instance and everything should work fine. 

This will allow us to use `proto.Any` as the `Signature` type inside the `Proof` object, and serialize it both using Amino and Protobuf.  

I have tried implementing all of this and pushed the changes to a new branch named `riccardo/adr-005-multisig-chainlink-impl-test`. You can take a look at it and pull the changes in your branch if you find them good enough 
dadamu: @RiccardoM Okay, got it. will extend the interface with the same way as `AddressData` to solve the issue.
codecov[bot]: # [Codecov](https://codecov.io/gh/desmos-labs/desmos/pull/708?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs) Report
> Merging [#708](https://codecov.io/gh/desmos-labs/desmos/pull/708?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs) (a9de5f0) into [master](https://codecov.io/gh/desmos-labs/desmos/commit/1a223427d1c4803f6c37f1010a717c98406118a2?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs) (1a22342) will **decrease** coverage by `5.58%`.
> The diff coverage is `64.23%`.

[![Impacted file tree graph](https://codecov.io/gh/desmos-labs/desmos/pull/708/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/708?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     #708      +/-   ##
==========================================
- Coverage   84.23%   78.65%   -5.59%     
==========================================
  Files          91       52      -39     
  Lines        6624     4286    -2338     
==========================================
- Hits         5580     3371    -2209     
+ Misses        833      739      -94     
+ Partials      211      176      -35     
```


| [Impacted Files](https://codecov.io/gh/desmos-labs/desmos/pull/708?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs) | Coverage Δ | |
|---|---|---|
| [x/profiles/client/cli/cli\_app\_links.go](https://codecov.io/gh/desmos-labs/desmos/pull/708/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs#diff-eC9wcm9maWxlcy9jbGllbnQvY2xpL2NsaV9hcHBfbGlua3MuZ28=) | `34.16% <ø> (ø)` | |
| [x/profiles/client/cli/cli\_params.go](https://codecov.io/gh/desmos-labs/desmos/pull/708/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs#diff-eC9wcm9maWxlcy9jbGllbnQvY2xpL2NsaV9wYXJhbXMuZ28=) | `70.00% <ø> (ø)` | |
| [x/profiles/client/cli/cli\_relationships.go](https://codecov.io/gh/desmos-labs/desmos/pull/708/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs#diff-eC9wcm9maWxlcy9jbGllbnQvY2xpL2NsaV9yZWxhdGlvbnNoaXBzLmdv) | `75.51% <ø> (ø)` | |
| [x/profiles/client/utils/utils.go](https://codecov.io/gh/desmos-labs/desmos/pull/708/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs#diff-eC9wcm9maWxlcy9jbGllbnQvdXRpbHMvdXRpbHMuZ28=) | `56.25% <ø> (ø)` | |
| [x/profiles/keeper/alias\_functions.go](https://codecov.io/gh/desmos-labs/desmos/pull/708/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs#diff-eC9wcm9maWxlcy9rZWVwZXIvYWxpYXNfZnVuY3Rpb25zLmdv) | `90.47% <ø> (ø)` | |
| [x/profiles/keeper/genesis.go](https://codecov.io/gh/desmos-labs/desmos/pull/708/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs#diff-eC9wcm9maWxlcy9rZWVwZXIvZ2VuZXNpcy5nbw==) | `84.61% <ø> (ø)` | |
| [x/profiles/keeper/invariants.go](https://codecov.io/gh/desmos-labs/desmos/pull/708/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs#diff-eC9wcm9maWxlcy9rZWVwZXIvaW52YXJpYW50cy5nbw==) | `100.00% <ø> (ø)` | |
| [x/profiles/keeper/keeper\_blocks.go](https://codecov.io/gh/desmos-labs/desmos/pull/708/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs#diff-eC9wcm9maWxlcy9rZWVwZXIva2VlcGVyX2Jsb2Nrcy5nbw==) | `85.50% <ø> (ø)` | |
| [x/profiles/keeper/keeper\_chain\_links.go](https://codecov.io/gh/desmos-labs/desmos/pull/708/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs#diff-eC9wcm9maWxlcy9rZWVwZXIva2VlcGVyX2NoYWluX2xpbmtzLmdv) | `84.74% <ø> (ø)` | |
| [x/profiles/keeper/keeper\_dtag\_transfers.go](https://codecov.io/gh/desmos-labs/desmos/pull/708/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs#diff-eC9wcm9maWxlcy9rZWVwZXIva2VlcGVyX2R0YWdfdHJhbnNmZXJzLmdv) | `89.09% <ø> (ø)` | |
| ... and [52 more](https://codecov.io/gh/desmos-labs/desmos/pull/708/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/desmos-labs/desmos/pull/708?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/708?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=desmos-labs). Last update [a4de414...a9de5f0](https://codecov.io/gh/desmos-labs/desmos/pull/708?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).

RiccardoM: @dadamu I've improved the code organization by splitting apart the various getters and creating a new builder interface. The multi-sig chain link test is however failing. Can you check why please? I didn't change pretty much anything except forcing the `Sequence` and `AccountNumber` to `0` when building the `SignatureData` instance
dadamu: @RiccardoM Fixed.

Patch:
diff --git a/.changeset/entries/a492eff6c4ec45c98cb555b14de9f27ba50da5b147ff05b7a4cb1707af73655c.yaml b/.changeset/entries/a492eff6c4ec45c98cb555b14de9f27ba50da5b147ff05b7a4cb1707af73655c.yaml
new file mode 100644
index 00000000..8fd9b43a
--- /dev/null
+++ b/.changeset/entries/a492eff6c4ec45c98cb555b14de9f27ba50da5b147ff05b7a4cb1707af73655c.yaml
@@ -0,0 +1,6 @@
+type: feat
+module: x/profiles
+pull_request: 708
+description: Added support for multisig chain links
+backward_compatible: false
+date: 2022-01-12T03:49:42.2638125Z
diff --git a/Makefile b/Makefile
index 396d0d75..ca786196 100644
--- a/Makefile
+++ b/Makefile
@@ -349,6 +349,12 @@ proto-update-deps:
 	@mkdir -p $(IBC_TYPES)/core/client/v1
 	@curl -sSL $(IBC_URL)/core/client/v1/client.proto > $(IBC_TYPES)/core/client/v1/client.proto
 
+	@mkdir -p $(COSMOS_TYPES)/tx/signing/v1beta1
+	@curl -sSL $(COSMOS_URL)/tx/signing/v1beta1/signing.proto > $(COSMOS_TYPES)/tx/signing/v1beta1/signing.proto
+
+	@mkdir -p $(COSMOS_TYPES)/crypto/multisig/v1beta1
+	@curl -sSL $(COSMOS_URL)/crypto/multisig/v1beta1/multisig.proto > $(COSMOS_TYPES)/crypto/multisig/v1beta1/multisig.proto
+
 ## Importing of tendermint protobuf definitions currently requires the
 ## use of `sed` in order to build properly with cosmos-sdk's proto file layout
 ## (which is the standard Buf.build FILE_LAYOUT)
diff --git a/app/desmos/cmd/chainlink/builder/builder.go b/app/desmos/cmd/chainlink/builder/builder.go
new file mode 100644
index 00000000..82789f97
--- /dev/null
+++ b/app/desmos/cmd/chainlink/builder/builder.go
@@ -0,0 +1,15 @@
+package builder
+
+import (
+	"github.com/desmos-labs/desmos/v2/app/desmos/cmd/chainlink/types"
+	"github.com/desmos-labs/desmos/v2/x/profiles/client/utils"
+)
+
+// ChainLinkJSONBuilder allows to build a ChainLinkJSON instance
+type ChainLinkJSONBuilder interface {
+	BuildChainLinkJSON(chain types.Chain) (utils.ChainLinkJSON, error)
+}
+
+// ChainLinkJSONBuilderProvider allows to provide the provider ChainLinkJSONBuilder implementation based on whether
+// it should create the JSON chain link for single or
+type ChainLinkJSONBuilderProvider func(isSingleAccount bool) ChainLinkJSONBuilder
diff --git a/app/desmos/cmd/chainlink/builder/multi/builder.go b/app/desmos/cmd/chainlink/builder/multi/builder.go
new file mode 100644
index 00000000..ac865de2
--- /dev/null
+++ b/app/desmos/cmd/chainlink/builder/multi/builder.go
@@ -0,0 +1,99 @@
+package multi
+
+import (
+	"encoding/hex"
+	"fmt"
+	"io/ioutil"
+
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	"github.com/cosmos/cosmos-sdk/types/tx/signing"
+	authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
+
+	"github.com/desmos-labs/desmos/v2/app"
+	"github.com/desmos-labs/desmos/v2/app/desmos/cmd/chainlink/getter"
+	"github.com/desmos-labs/desmos/v2/app/desmos/cmd/chainlink/types"
+	"github.com/desmos-labs/desmos/v2/x/profiles/client/utils"
+	profilestypes "github.com/desmos-labs/desmos/v2/x/profiles/types"
+)
+
+// AccountChainLinkJSONBuilder implements the ChainLinkJSONBuilder for multi signature accounts
+type AccountChainLinkJSONBuilder struct {
+	getter getter.MultiSignatureAccountReferenceGetter
+}
+
+// NewAccountChainLinkJSONBuilder returns a new AccountChainLinkJSONBuilder instance
+func NewAccountChainLinkJSONBuilder(getter getter.MultiSignatureAccountReferenceGetter) *AccountChainLinkJSONBuilder {
+	return &AccountChainLinkJSONBuilder{
+		getter: getter,
+	}
+}
+
+// BuildChainLinkJSON implements ChainLinkJSONBuilder
+func (b *AccountChainLinkJSONBuilder) BuildChainLinkJSON(chain types.Chain) (utils.ChainLinkJSON, error) {
+	txFilePath, err := b.getter.GetMultiSignedTxFilePath()
+	if err != nil {
+		return utils.ChainLinkJSON{}, err
+	}
+
+	signedChainID, err := b.getter.GetSignedChainID()
+	if err != nil {
+		return utils.ChainLinkJSON{}, err
+	}
+
+	encodingConfig := app.MakeTestEncodingConfig()
+	txCfg := encodingConfig.TxConfig
+
+	// Read the transaction file
+	bytes, err := ioutil.ReadFile(txFilePath)
+	if err != nil {
+		return utils.ChainLinkJSON{}, err
+	}
+
+	// Parse the transaction
+	parsedTx, err := txCfg.TxJSONDecoder()(bytes)
+	if err != nil {
+		return utils.ChainLinkJSON{}, err
+	}
+
+	// Get the sign mode
+	signMode := signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON
+
+	// Wrap the transaction inside a builder to make it easier to get the signatures
+	txBuilder, err := txCfg.WrapTxBuilder(parsedTx)
+	if err != nil {
+		return utils.ChainLinkJSON{}, err
+	}
+
+	sigs, err := txBuilder.GetTx().GetSignaturesV2()
+	if err != nil {
+		return utils.ChainLinkJSON{}, err
+	}
+
+	// Make sure there is only one signature for the multisig account
+	if len(sigs) != 1 {
+		return utils.ChainLinkJSON{}, fmt.Errorf("invalid number of signatures")
+	}
+
+	// Re-create the bytes that have been signed in order to produce the signature
+	signingData := authsigning.SignerData{AccountNumber: 0, Sequence: 0, ChainID: signedChainID}
+	value, err := txCfg.SignModeHandler().GetSignBytes(signMode, signingData, parsedTx)
+	if err != nil {
+		return utils.ChainLinkJSON{}, err
+	}
+
+	addr, err := sdk.Bech32ifyAddressBytes(chain.Prefix, sigs[0].PubKey.Address().Bytes())
+	if err != nil {
+		return utils.ChainLinkJSON{}, err
+	}
+
+	sigData, err := profilestypes.SignatureDataFromCosmosSignatureData(sigs[0].Data)
+	if err != nil {
+		return utils.ChainLinkJSON{}, err
+	}
+
+	return utils.NewChainLinkJSON(
+		profilestypes.NewBech32Address(addr, chain.Prefix),
+		profilestypes.NewProof(sigs[0].PubKey, sigData, hex.EncodeToString(value)),
+		profilestypes.NewChainConfig(chain.Name),
+	), err
+}
diff --git a/app/desmos/cmd/chainlink/builder/single/builder.go b/app/desmos/cmd/chainlink/builder/single/builder.go
new file mode 100644
index 00000000..f23a2ff5
--- /dev/null
+++ b/app/desmos/cmd/chainlink/builder/single/builder.go
@@ -0,0 +1,65 @@
+package single
+
+import (
+	"encoding/hex"
+
+	"github.com/cosmos/cosmos-sdk/crypto/hd"
+	"github.com/cosmos/cosmos-sdk/crypto/keyring"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	"github.com/cosmos/cosmos-sdk/types/tx/signing"
+
+	"github.com/desmos-labs/desmos/v2/app/desmos/cmd/chainlink/getter"
+	"github.com/desmos-labs/desmos/v2/app/desmos/cmd/chainlink/types"
+	"github.com/desmos-labs/desmos/v2/x/profiles/client/utils"
+	profilestypes "github.com/desmos-labs/desmos/v2/x/profiles/types"
+)
+
+const (
+	KeyName = "desmos_chain_link_account"
+)
+
+// AccountChainLinkJSONBuilder implements the ChainLinkJSONBuilder for single signature accounts
+type AccountChainLinkJSONBuilder struct {
+	getter getter.SingleSignatureAccountReferenceGetter
+}
+
+// NewAccountChainLinkJSONBuilder returns a new AccountChainLinkJSONBuilder instance
+func NewAccountChainLinkJSONBuilder(getter getter.SingleSignatureAccountReferenceGetter) *AccountChainLinkJSONBuilder {
+	return &AccountChainLinkJSONBuilder{
+		getter: getter,
+	}
+}
+
+// BuildChainLinkJSON implements ChainLinkJSONBuilder
+func (b *AccountChainLinkJSONBuilder) BuildChainLinkJSON(chain types.Chain) (utils.ChainLinkJSON, error) {
+	mnemonic, err := b.getter.GetMnemonic()
+	if err != nil {
+		return utils.ChainLinkJSON{}, err
+	}
+
+	// Create an in-memory keybase for signing
+	keyBase := keyring.NewInMemory()
+	_, err = keyBase.NewAccount(KeyName, mnemonic, "", chain.DerivationPath, hd.Secp256k1)
+	if err != nil {
+		return utils.ChainLinkJSON{}, err
+	}
+
+	// Generate the proof signing it with the key
+	key, _ := keyBase.Key(KeyName)
+	addr, _ := sdk.Bech32ifyAddressBytes(chain.Prefix, key.GetAddress())
+	value := []byte(addr)
+	sig, pubkey, err := keyBase.Sign(KeyName, value)
+	if err != nil {
+		return utils.ChainLinkJSON{}, err
+	}
+	sigData := &profilestypes.SingleSignatureData{
+		Mode:      signing.SignMode_SIGN_MODE_DIRECT,
+		Signature: sig,
+	}
+
+	return utils.NewChainLinkJSON(
+		profilestypes.NewBech32Address(addr, chain.Prefix),
+		profilestypes.NewProof(pubkey, sigData, hex.EncodeToString(value)),
+		profilestypes.NewChainConfig(chain.Name),
+	), nil
+}
diff --git a/app/desmos/cmd/chainlink/create_json.go b/app/desmos/cmd/chainlink/create_json.go
index 2583f84a..949716da 100644
--- a/app/desmos/cmd/chainlink/create_json.go
+++ b/app/desmos/cmd/chainlink/create_json.go
@@ -1,25 +1,23 @@
 package chainlink
 
 import (
-	"encoding/hex"
 	"fmt"
 	"io/ioutil"
 
-	chainlinktypes "github.com/desmos-labs/desmos/v2/app/desmos/cmd/chainlink/types"
+	"github.com/desmos-labs/desmos/v2/app/desmos/cmd/chainlink/builder"
+	chainlinktypes "github.com/desmos-labs/desmos/v2/app/desmos/cmd/chainlink/getter"
 
-	"github.com/cosmos/cosmos-sdk/crypto/hd"
-	"github.com/cosmos/cosmos-sdk/crypto/keyring"
-	sdk "github.com/cosmos/cosmos-sdk/types"
 	"github.com/spf13/cobra"
 
 	"github.com/desmos-labs/desmos/v2/app"
-	profilescliutils "github.com/desmos-labs/desmos/v2/x/profiles/client/utils"
-	profilestypes "github.com/desmos-labs/desmos/v2/x/profiles/types"
 )
 
 // GetCreateChainLinkJSON returns the command allowing to generate the chain link JSON
 // file that is required by the link-chain command
-func GetCreateChainLinkJSON(getter chainlinktypes.ChainLinkReferenceGetter) *cobra.Command {
+func GetCreateChainLinkJSON(
+	getter chainlinktypes.ChainLinkReferenceGetter,
+	provider builder.ChainLinkJSONBuilderProvider,
+) *cobra.Command {
 	return &cobra.Command{
 		Use:   "create-chain-link-json",
 		Short: "Start an interactive prompt to create a new chain link JSON object",
@@ -28,11 +26,17 @@ Once you have built the JSON object using this command, you can then run the fol
 
 desmos tx profiles link-chain [/path/to/json/file.json]
 
+--- Single signature accounts ---
 Note that this command will ask you the mnemonic that should be used to generate the private key of the address you want to link.
-The mnemonic is only used temporarily and never stored anywhere.`,
+The mnemonic is only used temporarily and never stored anywhere.
+
+--- Multi signature accounts ---
+If you have are using a multi-signature account, you will be required to provide the path to a signed transaction file. 
+That transaction must be signed as normal, except for the specified "account-number" and "sequence" values which should be both set to 0. 
+Providing an invalid transaction (either with an account-number or sequence not set to 0, or not signed correctly) will result in a failing linkage later on.
+`,
 		RunE: func(cmd *cobra.Command, args []string) error {
-			// Get the data
-			mnemonic, err := getter.GetMnemonic()
+			isSingleSignatureAccount, err := getter.IsSingleSignatureAccount()
 			if err != nil {
 				return err
 			}
@@ -42,59 +46,30 @@ The mnemonic is only used temporarily and never stored anywhere.`,
 				return err
 			}
 
-			filename, err := getter.GetFilename()
+			chainLinkJSON, err := provider(isSingleSignatureAccount).BuildChainLinkJSON(chain)
 			if err != nil {
 				return err
 			}
 
-			// Build che chain link JSON
-			chainLinkJSON, err := generateChainLinkJSON(mnemonic, chain)
+			// Marshal the chain link JSON
+			bz, err := app.MakeTestEncodingConfig().Marshaler.MarshalJSON(&chainLinkJSON)
 			if err != nil {
 				return err
 			}
 
-			cdc, _ := app.MakeCodecs()
-			bz, err := cdc.MarshalJSON(&chainLinkJSON)
+			// Write the chain link JSON to a file
+			filename, err := getter.GetFilename()
 			if err != nil {
 				return err
 			}
 
-			if filename != "" {
-				err = ioutil.WriteFile(filename, bz, 0600)
-				if err != nil {
-					return err
-				}
+			err = ioutil.WriteFile(filename, bz, 0600)
+			if err != nil {
+				return err
 			}
 
 			cmd.Println(fmt.Sprintf("Chain link JSON file stored at %s", filename))
-
 			return nil
 		},
 	}
 }
-
-// generateChainLinkJSON returns build a new ChainLinkJSON intance using the provided mnemonic and chain configuration
-func generateChainLinkJSON(mnemonic string, chain chainlinktypes.Chain) (profilescliutils.ChainLinkJSON, error) {
-	// Create an in-memory keybase for signing
-	keyBase := keyring.NewInMemory()
-	keyName := "chainlink"
-	_, err := keyBase.NewAccount(keyName, mnemonic, "", chain.DerivationPath, hd.Secp256k1)
-	if err != nil {
-		return profilescliutils.ChainLinkJSON{}, err
-	}
-
-	// Generate the proof signing it with the key
-	key, _ := keyBase.Key(keyName)
-	addr, _ := sdk.Bech32ifyAddressBytes(chain.Prefix, key.GetAddress())
-	value := []byte(addr)
-	sig, pubkey, err := keyBase.Sign(keyName, value)
-	if err != nil {
-		return profilescliutils.ChainLinkJSON{}, err
-	}
-
-	return profilescliutils.NewChainLinkJSON(
-		profilestypes.NewBech32Address(addr, chain.Prefix),
-		profilestypes.NewProof(pubkey, hex.EncodeToString(sig), hex.EncodeToString(value)),
-		profilestypes.NewChainConfig(chain.Name),
-	), nil
-}
diff --git a/app/desmos/cmd/chainlink/types/getter.go b/app/desmos/cmd/chainlink/getter/getter.go
similarity index 61%
rename from app/desmos/cmd/chainlink/types/getter.go
rename to app/desmos/cmd/chainlink/getter/getter.go
index 6c4e71f9..a6e2e5ae 100644
--- a/app/desmos/cmd/chainlink/types/getter.go
+++ b/app/desmos/cmd/chainlink/getter/getter.go
@@ -1,4 +1,4 @@
-package types
+package getter
 
 import (
 	"fmt"
@@ -6,56 +6,70 @@ import (
 	"path"
 	"strings"
 
-	"github.com/cosmos/go-bip39"
+	"github.com/desmos-labs/desmos/v2/app/desmos/cmd/chainlink/types"
 
 	"github.com/manifoldco/promptui"
 )
 
 // ChainLinkReferenceGetter allows to get all the data needed to generate a ChainLinkJSON instance
 type ChainLinkReferenceGetter interface {
-	// GetMnemonic returns the mnemonic
-	GetMnemonic() (string, error)
+
+	// IsSingleSignatureAccount returns if the target account is single signature account
+	IsSingleSignatureAccount() (bool, error)
 
 	// GetChain returns Chain instance
-	GetChain() (Chain, error)
+	GetChain() (types.Chain, error)
 
 	// GetFilename returns filename to save
 	GetFilename() (string, error)
 }
 
+// SingleSignatureAccountReferenceGetter allows to get all the data needed to generate a ChainLinkJSON interface for single signature account
+type SingleSignatureAccountReferenceGetter interface {
+	// GetMnemonic returns the mnemonic
+	GetMnemonic() (string, error)
+}
+
+// MultiSignatureAccountReferenceGetter allows to get all the data needed to generate a ChainLinkJSON interface for multi signature account
+type MultiSignatureAccountReferenceGetter interface {
+	// GetSignedChainID returns the chain id which is used to sign the multisigned tx file
+	GetSignedChainID() (string, error)
+
+	// GetMultiSignedTxFilePath returns the path of multisigned transaction file
+	GetMultiSignedTxFilePath() (string, error)
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
 // ChainLinkReferencePrompt is a ChainLinkReferenceGetter implemented with an interactive prompt
 type ChainLinkReferencePrompt struct {
 	ChainLinkReferenceGetter
-	cfg Config
+	cfg types.Config
 }
 
 // NewChainLinkReferencePrompt returns an instance implementing ChainLinkReferencePrompt
 func NewChainLinkReferencePrompt() *ChainLinkReferencePrompt {
 	return &ChainLinkReferencePrompt{
-		cfg: DefaultConfig(),
+		cfg: types.DefaultConfig(),
 	}
 }
 
-// GetMnemonic implements ChainLinkReferenceGetter
-func (cp ChainLinkReferencePrompt) GetMnemonic() (string, error) {
-	mnemonic, err := cp.getMnemonic()
-	if err != nil {
-		return "", err
-	}
-	return mnemonic, nil
+// IsSingleSignatureAccount implements ChainLinkReferenceGetter
+func (cp ChainLinkReferencePrompt) IsSingleSignatureAccount() (bool, error) {
+	return cp.isSingleSignatureAccount()
 }
 
 // GetChain implements ChainLinkReferenceGetter
-func (cp ChainLinkReferencePrompt) GetChain() (Chain, error) {
+func (cp ChainLinkReferencePrompt) GetChain() (types.Chain, error) {
 	chain, err := cp.selectChain()
 	if err != nil {
-		return Chain{}, err
+		return types.Chain{}, err
 	}
 
 	if chain.ID == "Other" {
 		newChain, err := cp.getCustomChain(chain)
 		if err != nil {
-			return Chain{}, err
+			return types.Chain{}, err
 		}
 		chain = newChain
 	}
@@ -69,28 +83,36 @@ func (cp ChainLinkReferencePrompt) GetFilename() (string, error) {
 	if err != nil {
 		return "", err
 	}
+
+	if filename == "" {
+		return "", fmt.Errorf("file name cannot be empty")
+	}
+
 	return filename, nil
 }
 
-// getMnemonic asks the user the mnemonic and then returns it
-func (cp ChainLinkReferencePrompt) getMnemonic() (string, error) {
-	prompt := promptui.Prompt{
-		Label:       "Please enter the mnemonic that should be used to generate the address you want to link",
-		HideEntered: true,
-		Validate: func(s string) error {
-			if strings.TrimSpace(s) == "" {
-				return fmt.Errorf("mnemonic cannot be empty or blank")
-			} else if _, err := bip39.MnemonicToByteArray(s); err != nil {
-				return fmt.Errorf("invalid mnemonic")
-			}
-			return nil
+// --------------------------------------------------------------------------------------------------------------------
+
+// isSingleSignatureAccount asks the user if the target of the account is single signature account, and then returns it
+func (cp ChainLinkReferencePrompt) isSingleSignatureAccount() (bool, error) {
+	prompt := promptui.Select{
+		Label: "Please select if the target account is a single signature account. (select no if it is multi signature account)",
+		Items: []string{"Yes", "No"},
+		Templates: &promptui.SelectTemplates{
+			Active:   "\U00002713 {{ . | cyan }}",
+			Inactive: "  {{ . | cyan }}",
+			Selected: "Module: \U00002713 {{ . | cyan }}",
 		},
 	}
-	return prompt.Run()
+	_, result, err := prompt.Run()
+	if err != nil {
+		return false, err
+	}
+	return result == "Yes", nil
 }
 
 // selectChain asks the user to select a predefined Chain instance, and returns it
-func (cp ChainLinkReferencePrompt) selectChain() (Chain, error) {
+func (cp ChainLinkReferencePrompt) selectChain() (types.Chain, error) {
 	cfg := cp.cfg
 	prompt := promptui.Select{
 		Label: "Select a target chain",
@@ -104,27 +126,27 @@ func (cp ChainLinkReferencePrompt) selectChain() (Chain, error) {
 
 	index, _, err := prompt.Run()
 	if err != nil {
-		return Chain{}, err
+		return types.Chain{}, err
 	}
 
 	return cfg.Chains[index], nil
 }
 
 // getCustomChain asks the user to input the data to build a custom Chain instance, and then returns it
-func (cp ChainLinkReferencePrompt) getCustomChain(chain Chain) (Chain, error) {
+func (cp ChainLinkReferencePrompt) getCustomChain(chain types.Chain) (types.Chain, error) {
 	chainName, err := cp.getChainName()
 	if err != nil {
-		return Chain{}, err
+		return types.Chain{}, err
 	}
 
 	prefix, err := cp.getBech32Prefix()
 	if err != nil {
-		return Chain{}, err
+		return types.Chain{}, err
 	}
 
 	derivationPath, err := cp.getDerivationPath()
 	if err != nil {
-		return Chain{}, err
+		return types.Chain{}, err
 	}
 
 	chain.Name = chainName
diff --git a/app/desmos/cmd/chainlink/getter/multi/getter.go b/app/desmos/cmd/chainlink/getter/multi/getter.go
new file mode 100644
index 00000000..2ec6ebe8
--- /dev/null
+++ b/app/desmos/cmd/chainlink/getter/multi/getter.go
@@ -0,0 +1,57 @@
+package multi
+
+import (
+	"fmt"
+	"os"
+	"path"
+	"strings"
+
+	"github.com/manifoldco/promptui"
+)
+
+// ChainLinkJSONInfoGetter implements MultiSignatureAccountReferenceGetter
+type ChainLinkJSONInfoGetter struct {
+}
+
+// NewChainLinkJSONInfoGetter returns a new ChainLinkJSONInfoGetter instance
+func NewChainLinkJSONInfoGetter() *ChainLinkJSONInfoGetter {
+	return &ChainLinkJSONInfoGetter{}
+}
+
+// getMultiSignedTxFile asks the user the path of the multisigned transaction file, and then returns it
+func (cp *ChainLinkJSONInfoGetter) getMultiSignedTxFile() (string, error) {
+	wd, err := os.Getwd()
+	if err != nil {
+		return "", err
+	}
+	prompt := promptui.Prompt{
+		Label:   "Please insert the path of multisigned tx file (fully qualified path)",
+		Default: path.Join(wd, "tx.json"),
+	}
+	return prompt.Run()
+}
+
+// GetMultiSignedTxFilePath implements MultiSignatureAccountReferenceGetter
+func (cp *ChainLinkJSONInfoGetter) GetMultiSignedTxFilePath() (string, error) {
+	return cp.getMultiSignedTxFile()
+}
+
+// getSignedChainID asks the user the chain id that is used to sign the transaction file, and then returns it
+func (cp *ChainLinkJSONInfoGetter) getSignedChainID() (string, error) {
+	prompt := promptui.Prompt{
+		Label:       "Please enter the chain id that is used to sign the multisigned transaction file",
+		HideEntered: true,
+		Validate: func(s string) error {
+			if strings.TrimSpace(s) == "" {
+				return fmt.Errorf("signed chain id cannot be empty or blank")
+			}
+			return nil
+		},
+	}
+	return prompt.Run()
+}
+
+// GetSignedChainID implements MultiSignatureAccountReferenceGetter
+func (cp *ChainLinkJSONInfoGetter) GetSignedChainID() (string, error) {
+	return cp.getSignedChainID()
+}
diff --git a/app/desmos/cmd/chainlink/getter/single/getter.go b/app/desmos/cmd/chainlink/getter/single/getter.go
new file mode 100644
index 00000000..71e75456
--- /dev/null
+++ b/app/desmos/cmd/chainlink/getter/single/getter.go
@@ -0,0 +1,44 @@
+package single
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/cosmos/go-bip39"
+	"github.com/manifoldco/promptui"
+)
+
+// ChainLinkJSONInfoGetter implements SingleSignatureAccountReferenceGetter
+type ChainLinkJSONInfoGetter struct {
+}
+
+// NewChainLinkJSONInfoGetter returns a new ChainLinkJSONInfoGetter instance
+func NewChainLinkJSONInfoGetter() *ChainLinkJSONInfoGetter {
+	return &ChainLinkJSONInfoGetter{}
+}
+
+// getMnemonic asks the user the mnemonic and then returns it
+func (cp *ChainLinkJSONInfoGetter) getMnemonic() (string, error) {
+	prompt := promptui.Prompt{
+		Label:       "Please enter the mnemonic that should be used to generate the address you want to link",
+		HideEntered: true,
+		Validate: func(s string) error {
+			if strings.TrimSpace(s) == "" {
+				return fmt.Errorf("mnemonic cannot be empty or blank")
+			} else if _, err := bip39.MnemonicToByteArray(s); err != nil {
+				return fmt.Errorf("invalid mnemonic")
+			}
+			return nil
+		},
+	}
+	return prompt.Run()
+}
+
+// GetMnemonic implements SingleSignatureAccountReferenceGetter
+func (cp *ChainLinkJSONInfoGetter) GetMnemonic() (string, error) {
+	mnemonic, err := cp.getMnemonic()
+	if err != nil {
+		return "", err
+	}
+	return mnemonic, nil
+}
diff --git a/app/desmos/cmd/chainlink/provider/provider.go b/app/desmos/cmd/chainlink/provider/provider.go
new file mode 100644
index 00000000..31db6b0a
--- /dev/null
+++ b/app/desmos/cmd/chainlink/provider/provider.go
@@ -0,0 +1,17 @@
+package provider
+
+import (
+	"github.com/desmos-labs/desmos/v2/app/desmos/cmd/chainlink/builder"
+	multibuilder "github.com/desmos-labs/desmos/v2/app/desmos/cmd/chainlink/builder/multi"
+	singlebuilder "github.com/desmos-labs/desmos/v2/app/desmos/cmd/chainlink/builder/single"
+	multigetter "github.com/desmos-labs/desmos/v2/app/desmos/cmd/chainlink/getter/multi"
+	singlegetter "github.com/desmos-labs/desmos/v2/app/desmos/cmd/chainlink/getter/single"
+)
+
+// DefaultChainLinkJSONBuilderProvider returns the default ChainLinkJSONBuilder provider implementation
+func DefaultChainLinkJSONBuilderProvider(isSingleAccount bool) builder.ChainLinkJSONBuilder {
+	if isSingleAccount {
+		return singlebuilder.NewAccountChainLinkJSONBuilder(singlegetter.NewChainLinkJSONInfoGetter())
+	}
+	return multibuilder.NewAccountChainLinkJSONBuilder(multigetter.NewChainLinkJSONInfoGetter())
+}
diff --git a/app/desmos/cmd/root.go b/app/desmos/cmd/root.go
index f092dd2d..8ae25876 100644
--- a/app/desmos/cmd/root.go
+++ b/app/desmos/cmd/root.go
@@ -7,8 +7,6 @@ import (
 
 	serverconfig "github.com/cosmos/cosmos-sdk/server/config"
 
-	chainlinktypes "github.com/desmos-labs/desmos/v2/app/desmos/cmd/chainlink/types"
-
 	"github.com/desmos-labs/desmos/v2/app/desmos/cmd/chainlink"
 	"github.com/desmos-labs/desmos/v2/app/desmos/cmd/sign"
 
@@ -44,6 +42,8 @@ import (
 	banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
 	cosmosgenutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli"
 
+	chainlinktypes "github.com/desmos-labs/desmos/v2/app/desmos/cmd/chainlink/getter"
+	chainlinkprovider "github.com/desmos-labs/desmos/v2/app/desmos/cmd/chainlink/provider"
 	genutilcli "github.com/desmos-labs/desmos/v2/x/genutil/client/cli"
 )
 
@@ -178,7 +178,10 @@ func initRootCmd(rootCmd *cobra.Command, encodingConfig params.EncodingConfig) {
 		queryCommand(),
 		txCommand(),
 		sign.GetSignCmd(),
-		chainlink.GetCreateChainLinkJSON(chainlinktypes.NewChainLinkReferencePrompt()),
+		chainlink.GetCreateChainLinkJSON(
+			chainlinktypes.NewChainLinkReferencePrompt(),
+			chainlinkprovider.DefaultChainLinkJSONBuilderProvider,
+		),
 		keys.Commands(app.DefaultNodeHome),
 	)
 }
diff --git a/proto/desmos/profiles/legacy/v230/models_chain_links.proto b/proto/desmos/profiles/legacy/v230/models_chain_links.proto
new file mode 100644
index 00000000..27c4a9f7
--- /dev/null
+++ b/proto/desmos/profiles/legacy/v230/models_chain_links.proto
@@ -0,0 +1,111 @@
+syntax = "proto3";
+package desmos.profiles.legacy.v230;
+
+import "gogoproto/gogo.proto";
+import "google/protobuf/any.proto";
+import "google/protobuf/timestamp.proto";
+import "cosmos_proto/cosmos.proto";
+
+option go_package = "github.com/desmos-labs/desmos/v2/x/profiles/legacy/v230";
+
+// ChainLink contains the data representing either an inter- or cross- chain
+// link
+message ChainLink {
+  option (gogoproto.goproto_getters) = false;
+  option (gogoproto.equal) = true;
+  option (gogoproto.goproto_stringer) = true;
+
+  // User defines the destination profile address to link
+  string user = 1 [ (gogoproto.moretags) = "yaml:\"user\"" ];
+
+  // Address contains the data of the external chain address to be connected
+  // with the Desmos profile
+  google.protobuf.Any address = 2 [
+    (gogoproto.moretags) = "yaml:\"address\"",
+    (cosmos_proto.accepts_interface) = "AddressData"
+  ];
+
+  // Proof contains the ownership proof of the external chain address
+  Proof proof = 3
+      [ (gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"proof\"" ];
+
+  // ChainConfig contains the configuration of the external chain
+  ChainConfig chain_config = 4 [
+    (gogoproto.nullable) = false,
+    (gogoproto.moretags) = "yaml:\"chain_config\""
+  ];
+
+  // CreationTime represents the time in which the link has been created
+  google.protobuf.Timestamp creation_time = 5 [
+    (gogoproto.stdtime) = true,
+    (gogoproto.moretags) = "yaml:\"creation_time\"",
+    (gogoproto.nullable) = false
+  ];
+}
+
+// ChainConfig contains the data of the chain with which the link is made.
+message ChainConfig {
+  option (gogoproto.goproto_getters) = false;
+  option (gogoproto.equal) = true;
+  option (gogoproto.goproto_stringer) = true;
+
+  string name = 1 [ (gogoproto.moretags) = "yaml:\"name\"" ];
+}
+
+// Proof contains all the data used to verify a signature when linking an
+// account to a profile
+message Proof {
+  option (gogoproto.goproto_getters) = false;
+  option (gogoproto.equal) = true;
+  option (gogoproto.goproto_stringer) = true;
+
+  // PubKey represents the public key associated with the address for which to
+  // prove the ownership
+  google.protobuf.Any pub_key = 1 [ (gogoproto.moretags) = "yaml:\"pub_key\"" ];
+
+  // Signature represents the hex-encoded signature of the PlainText value
+  string signature = 2 [ (gogoproto.moretags) = "yaml:\"signature\"" ];
+
+  // PlainText represents the hex-encoded value signed in order to produce the Signature
+  string plain_text = 3 [ (gogoproto.moretags) = "yaml:\"plain_text\"" ];
+}
+
+// Bech32Address represents a Bech32-encoded address
+message Bech32Address {
+  option (gogoproto.goproto_getters) = false;
+  option (gogoproto.equal) = true;
+  option (gogoproto.goproto_stringer) = true;
+  option (cosmos_proto.implements_interface) = "AddressData";
+
+  // Value represents the Bech-32 encoded address value
+  string value = 1 [ (gogoproto.moretags) = "yaml:\"value\"" ];
+
+  // Prefix represents the HRP of the Bech32 address
+  string prefix = 2 [ (gogoproto.moretags) = "yaml:\"prefix\"" ];
+}
+
+// Base58Address represents a Base58-encoded address
+message Base58Address {
+  option (gogoproto.goproto_getters) = false;
+  option (gogoproto.equal) = true;
+  option (gogoproto.goproto_stringer) = true;
+  option (cosmos_proto.implements_interface) = "AddressData";
+
+  // Value contains the Base58-encoded address
+  string value = 1 [ (gogoproto.moretags) = "yaml:\"value\"" ];
+}
+
+// HexAddress represents an Hex-encoded address
+// NOTE: Currently it only supports keccak256-uncompressed addresses
+message HexAddress {
+  option (gogoproto.goproto_getters) = false;
+  option (gogoproto.equal) = true;
+  option (gogoproto.goproto_stringer) = true;
+  option (cosmos_proto.implements_interface) = "AddressData";
+
+  // Value represents the hex address value
+  string value = 1 [ (gogoproto.moretags) = "yaml:\"value\"" ];
+
+  // Prefix represents the optional prefix used during address encoding (e.g. 0x)
+  string prefix = 2 [ (gogoproto.moretags) = "yaml:\"prefix\"" ];
+}
\ No newline at end of file
diff --git a/proto/desmos/profiles/v1beta1/client/cli.proto b/proto/desmos/profiles/v1beta1/client/cli.proto
index 4f0cd1a7..dcbabcdc 100644
--- a/proto/desmos/profiles/v1beta1/client/cli.proto
+++ b/proto/desmos/profiles/v1beta1/client/cli.proto
@@ -12,8 +12,8 @@ option go_package = "github.com/desmos-labs/desmos/v2/x/profiles/client/utils";
 // ChainLinkJSON contains the data required to create a ChainLink using the CLI
 // command
 message ChainLinkJSON {
-  option (gogoproto.equal) = true;
   option (gogoproto.goproto_stringer) = true;
+  option (gogoproto.equal) = true;
 
   // Address contains the data of the external chain address to be connected
   // with the Desmos profile
diff --git a/proto/desmos/profiles/v1beta1/models_chain_links.proto b/proto/desmos/profiles/v1beta1/models_chain_links.proto
index d6579bd5..f59c7c04 100644
--- a/proto/desmos/profiles/v1beta1/models_chain_links.proto
+++ b/proto/desmos/profiles/v1beta1/models_chain_links.proto
@@ -5,6 +5,8 @@ import "gogoproto/gogo.proto";
 import "google/protobuf/any.proto";
 import "google/protobuf/timestamp.proto";
 import "cosmos_proto/cosmos.proto";
+import "cosmos/tx/signing/v1beta1/signing.proto";
+import "cosmos/crypto/multisig/v1beta1/multisig.proto";
 
 option go_package = "github.com/desmos-labs/desmos/v2/x/profiles/types";
 
@@ -64,9 +66,11 @@ message Proof {
   google.protobuf.Any pub_key = 1 [ (gogoproto.moretags) = "yaml:\"pub_key\"" ];
 
   // Signature represents the hex-encoded signature of the PlainText value
-  string signature = 2 [ (gogoproto.moretags) = "yaml:\"signature\"" ];
+  google.protobuf.Any signature = 2
+      [ (gogoproto.moretags) = "yaml:\"signature\"" ];
 
-  // PlainText represents the hex-encoded value signed in order to produce the Signature
+  // PlainText represents the hex-encoded value signed in order to produce the
+  // Signature
   string plain_text = 3 [ (gogoproto.moretags) = "yaml:\"plain_text\"" ];
 }
 
@@ -106,6 +110,33 @@ message HexAddress {
   // Value represents the hex address value
   string value = 1 [ (gogoproto.moretags) = "yaml:\"value\"" ];
 
-  // Prefix represents the optional prefix used during address encoding (e.g. 0x)
+  // Prefix represents the optional prefix used during address encoding (e.g.
+  // 0x)
   string prefix = 2 [ (gogoproto.moretags) = "yaml:\"prefix\"" ];
+}
+
+// SingleSignatureData is the signature data for a single signer
+message SingleSignatureData {
+  option (gogoproto.goproto_getters) = false;
+  option (gogoproto.equal) = true;
+  option (cosmos_proto.implements_interface) = "SignatureData";
+
+  // Mode is the signing mode of the single signer
+  cosmos.tx.signing.v1beta1.SignMode mode = 1;
+
+  // Signature is the raw signature bytes
+  bytes signature = 2;
+}
+
+// MultiSignatureData is the signature data for a multisig public key
+message MultiSignatureData {
+  option (gogoproto.goproto_getters) = false;
+  option (gogoproto.equal) = true;
+  option (cosmos_proto.implements_interface) = "SignatureData";
+
+  // Bitarray specifies which keys within the multisig are signing
+  cosmos.crypto.multisig.v1beta1.CompactBitArray bit_array = 1;
+
+  // Signatures is the signatures of the multi-signature
+  repeated google.protobuf.Any signatures = 2;
 }
\ No newline at end of file
diff --git a/proto/desmos/profiles/v1beta1/msgs_profile.proto b/proto/desmos/profiles/v1beta1/msgs_profile.proto
index 2fe4d74b..13a8139d 100644
--- a/proto/desmos/profiles/v1beta1/msgs_profile.proto
+++ b/proto/desmos/profiles/v1beta1/msgs_profile.proto
@@ -24,13 +24,10 @@ message MsgSaveProfile {
 
   string bio = 3 [ (gogoproto.moretags) = "yaml:\"bio\"" ];
 
-  string profile_picture = 4 [
-    (gogoproto.moretags) = "yaml:\"profile_picture\""
-  ];
+  string profile_picture = 4
+      [ (gogoproto.moretags) = "yaml:\"profile_picture\"" ];
 
-  string cover_picture = 5 [
-    (gogoproto.moretags) = "yaml:\"cover_picture\""
-  ];
+  string cover_picture = 5 [ (gogoproto.moretags) = "yaml:\"cover_picture\"" ];
 
   string creator = 6 [ (gogoproto.moretags) = "yaml:\"creator\"" ];
 }
diff --git a/third_party/proto/cosmos/crypto/multisig/v1beta1/multisig.proto b/third_party/proto/cosmos/crypto/multisig/v1beta1/multisig.proto
new file mode 100644
index 00000000..bf671f17
--- /dev/null
+++ b/third_party/proto/cosmos/crypto/multisig/v1beta1/multisig.proto
@@ -0,0 +1,25 @@
+syntax = "proto3";
+package cosmos.crypto.multisig.v1beta1;
+
+import "gogoproto/gogo.proto";
+
+option go_package = "github.com/cosmos/cosmos-sdk/crypto/types";
+
+// MultiSignature wraps the signatures from a multisig.LegacyAminoPubKey.
+// See cosmos.tx.v1betata1.ModeInfo.Multi for how to specify which signers
+// signed and with which modes.
+message MultiSignature {
+  option (gogoproto.goproto_unrecognized) = true;
+  repeated bytes signatures               = 1;
+}
+
+// CompactBitArray is an implementation of a space efficient bit array.
+// This is used to ensure that the encoded data takes up a minimal amount of
+// space after proto encoding.
+// This is not thread safe, and is not intended for concurrent usage.
+message CompactBitArray {
+  option (gogoproto.goproto_stringer) = false;
+
+  uint32 extra_bits_stored = 1;
+  bytes  elems             = 2;
+}
diff --git a/third_party/proto/cosmos/tx/signing/v1beta1/signing.proto b/third_party/proto/cosmos/tx/signing/v1beta1/signing.proto
new file mode 100644
index 00000000..4c1be405
--- /dev/null
+++ b/third_party/proto/cosmos/tx/signing/v1beta1/signing.proto
@@ -0,0 +1,79 @@
+syntax = "proto3";
+package cosmos.tx.signing.v1beta1;
+
+import "cosmos/crypto/multisig/v1beta1/multisig.proto";
+import "google/protobuf/any.proto";
+
+option go_package = "github.com/cosmos/cosmos-sdk/types/tx/signing";
+
+// SignMode represents a signing mode with its own security guarantees.
+enum SignMode {
+  // SIGN_MODE_UNSPECIFIED specifies an unknown signing mode and will be
+  // rejected
+  SIGN_MODE_UNSPECIFIED = 0;
+
+  // SIGN_MODE_DIRECT specifies a signing mode which uses SignDoc and is
+  // verified with raw bytes from Tx
+  SIGN_MODE_DIRECT = 1;
+
+  // SIGN_MODE_TEXTUAL is a future signing mode that will verify some
+  // human-readable textual representation on top of the binary representation
+  // from SIGN_MODE_DIRECT
+  SIGN_MODE_TEXTUAL = 2;
+
+  // SIGN_MODE_LEGACY_AMINO_JSON is a backwards compatibility mode which uses
+  // Amino JSON and will be removed in the future
+  SIGN_MODE_LEGACY_AMINO_JSON = 127;
+}
+
+// SignatureDescriptors wraps multiple SignatureDescriptor's.
+message SignatureDescriptors {
+  // signatures are the signature descriptors
+  repeated SignatureDescriptor signatures = 1;
+}
+
+// SignatureDescriptor is a convenience type which represents the full data for
+// a signature including the public key of the signer, signing modes and the
+// signature itself. It is primarily used for coordinating signatures between
+// clients.
+message SignatureDescriptor {
+  // public_key is the public key of the signer
+  google.protobuf.Any public_key = 1;
+
+  Data data = 2;
+
+  // sequence is the sequence of the account, which describes the
+  // number of committed transactions signed by a given address. It is used to prevent
+  // replay attacks.
+  uint64 sequence = 3;
+
+  // Data represents signature data
+  message Data {
+    // sum is the oneof that specifies whether this represents single or multi-signature data
+    oneof sum {
+      // single represents a single signer
+      Single single = 1;
+
+      // multi represents a multisig signer
+      Multi multi = 2;
+    }
+
+    // Single is the signature data for a single signer
+    message Single {
+      // mode is the signing mode of the single signer
+      SignMode mode = 1;
+
+      // signature is the raw signature bytes
+      bytes signature = 2;
+    }
+
+    // Multi is the signature data for a multisig public key
+    message Multi {
+      // bitarray specifies which keys within the multisig are signing
+      cosmos.crypto.multisig.v1beta1.CompactBitArray bitarray = 1;
+
+      // signatures is the signatures of the multi-signature
+      repeated Data signatures = 2;
+    }
+  }
+}
diff --git a/x/profiles/legacy/v210/store.go b/x/profiles/legacy/v210/store.go
index a01347ab..0de4cfcb 100644
--- a/x/profiles/legacy/v210/store.go
+++ b/x/profiles/legacy/v210/store.go
@@ -6,6 +6,7 @@ import (
 	"github.com/cosmos/cosmos-sdk/codec"
 	sdk "github.com/cosmos/cosmos-sdk/types"
 
+	v230 "github.com/desmos-labs/desmos/v2/x/profiles/legacy/v230"
 	"github.com/desmos-labs/desmos/v2/x/profiles/types"
 )
 
@@ -68,9 +69,9 @@ func migrateChainLinks(store sdk.KVStore, cdc codec.BinaryCodec) error {
 	defer iterator.Close()
 
 	var keys [][]byte
-	var newLinks []types.ChainLink
+	var newLinks []v230.ChainLink
 	for ; iterator.Valid(); iterator.Next() {
-		var link types.ChainLink
+		var link v230.ChainLink
 		err := cdc.Unmarshal(iterator.Value(), &link)
 		if err != nil {
 			return err
@@ -86,7 +87,7 @@ func migrateChainLinks(store sdk.KVStore, cdc codec.BinaryCodec) error {
 	}
 
 	for index, link := range newLinks {
-		store.Set(keys[index], types.MustMarshalChainLink(cdc, link))
+		store.Set(keys[index], v230.MustMarshalChainLink(cdc, link))
 	}
 
 	return nil
diff --git a/x/profiles/legacy/v230/codec.go b/x/profiles/legacy/v230/codec.go
new file mode 100644
index 00000000..6dbc6cae
--- /dev/null
+++ b/x/profiles/legacy/v230/codec.go
@@ -0,0 +1,15 @@
+package v230
+
+import (
+	types "github.com/cosmos/cosmos-sdk/codec/types"
+)
+
+func RegisterInterfaces(registry types.InterfaceRegistry) {
+	registry.RegisterInterface(
+		"desmos.profiles.legacy.v230.AddressData",
+		(*AddressData)(nil),
+		&Bech32Address{},
+		&Base58Address{},
+		&HexAddress{},
+	)
+}
diff --git a/x/profiles/legacy/v230/models_chain_links.go b/x/profiles/legacy/v230/models_chain_links.go
new file mode 100644
index 00000000..f155315b
--- /dev/null
+++ b/x/profiles/legacy/v230/models_chain_links.go
@@ -0,0 +1,345 @@
+package v230
+
+import (
+	"bytes"
+	"encoding/hex"
+	"fmt"
+	"strings"
+	"time"
+
+	"github.com/cosmos/cosmos-sdk/types/bech32"
+	"github.com/tendermint/tendermint/crypto/tmhash"
+
+	"github.com/btcsuite/btcd/btcec"
+	"github.com/ethereum/go-ethereum/crypto"
+	"github.com/gogo/protobuf/proto"
+	"github.com/mr-tron/base58"
+
+	"github.com/cosmos/cosmos-sdk/codec"
+	codectypes "github.com/cosmos/cosmos-sdk/codec/types"
+	cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+)
+
+// NewChainConfig allows to build a new ChainConfig instance
+func NewChainConfig(name string) ChainConfig {
+	return ChainConfig{
+		Name: name,
+	}
+}
+
+// Validate checks the validity of the ChainConfig
+func (c ChainConfig) Validate() error {
+	if strings.TrimSpace(c.Name) == "" {
+		return fmt.Errorf("chain name cannot be empty or blank")
+	}
+	if c.Name != strings.ToLower(c.Name) {
+		return fmt.Errorf("chain name must be lowercase")
+	}
+	return nil
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+// NewProof is a constructor function for Proof
+// nolint:interfacer
+func NewProof(pubKey cryptotypes.PubKey, signature string, plainText string) Proof {
+	pubKeyAny, err := codectypes.NewAnyWithValue(pubKey)
+	if err != nil {
+		panic("failed to pack public key to any type")
+	}
+	return Proof{
+		PubKey:    pubKeyAny,
+		Signature: signature,
+		PlainText: plainText,
+	}
+}
+
+// Validate checks the validity of the Proof
+func (p Proof) Validate() error {
+	if p.PubKey == nil {
+		return fmt.Errorf("public key field cannot be nil")
+	}
+
+	_, err := hex.DecodeString(p.Signature)
+	if err != nil {
+		return fmt.Errorf("invalid hex-encoded signature")
+	}
+
+	if strings.TrimSpace(p.PlainText) == "" {
+		return fmt.Errorf("plain text cannot be empty or blank")
+	}
+
+	_, err = hex.DecodeString(p.PlainText)
+	if err != nil {
+		return fmt.Errorf("invalid hex-encoded plain text")
+	}
+
+	return nil
+}
+
+// Verify verifies the signature using the given plain text and public key.
+// It returns and error if something is invalid.
+func (p Proof) Verify(unpacker codectypes.AnyUnpacker, address AddressData) error {
+	var pubkey cryptotypes.PubKey
+	err := unpacker.UnpackAny(p.PubKey, &pubkey)
+	if err != nil {
+		return fmt.Errorf("failed to unpack the public key")
+	}
+
+	value, _ := hex.DecodeString(p.PlainText)
+
+	sig, _ := hex.DecodeString(p.Signature)
+	if !pubkey.VerifySignature(value, sig) {
+		return fmt.Errorf("failed to verify the signature")
+	}
+
+	valid, err := address.VerifyPubKey(pubkey)
+	if err != nil {
+		return err
+	}
+
+	if !valid {
+		return fmt.Errorf("invalid address and public key combination provided")
+	}
+
+	return nil
+}
+
+// UnpackInterfaces implements codectypes.UnpackInterfacesMessage
+func (p *Proof) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error {
+	var pubKey cryptotypes.PubKey
+	return unpacker.UnpackAny(p.PubKey, &pubKey)
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+// AddressData is an interface representing a generic external chain address
+type AddressData interface {
+	proto.Message
+
+	// Validate checks the validity of the AddressData
+	Validate() error
+
+	// GetValue returns the address value
+	GetValue() string
+
+	// VerifyPubKey verifies that the given public key is associated with this address data
+	VerifyPubKey(key cryptotypes.PubKey) (bool, error)
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+var _ AddressData = &Bech32Address{}
+
+// NewBech32Address returns a new Bech32Address instance
+func NewBech32Address(value, prefix string) *Bech32Address {
+	return &Bech32Address{Value: value, Prefix: prefix}
+}
+
+// Validate implements AddressData
+func (b Bech32Address) Validate() error {
+	if strings.TrimSpace(b.Value) == "" {
+		return fmt.Errorf("value cannot be empty or blank")
+	}
+
+	if strings.TrimSpace(b.Prefix) == "" {
+		return fmt.Errorf("prefix cannot be empty or blank")
+	}
+
+	_, err := sdk.GetFromBech32(b.Value, b.Prefix)
+	if err != nil {
+		return fmt.Errorf("invalid Bech32 value or wrong prefix")
+	}
+
+	return nil
+}
+
+// GetValue implements AddressData
+func (b Bech32Address) GetValue() string {
+	return b.Value
+}
+
+// VerifyPubKey implements AddressData
+func (b Bech32Address) VerifyPubKey(key cryptotypes.PubKey) (bool, error) {
+	_, bz, err := bech32.DecodeAndConvert(b.Value)
+	if err != nil {
+		return false, err
+	}
+	return bytes.Equal(bz, key.Address().Bytes()), nil
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+var _ AddressData = &Base58Address{}
+
+// NewBase58Address returns a new Base58Address instance
+func NewBase58Address(value string) *Base58Address {
+	return &Base58Address{Value: value}
+}
+
+// Validate implements AddressData
+func (b Base58Address) Validate() error {
+	if strings.TrimSpace(b.Value) == "" {
+		return fmt.Errorf("address cannot be empty or blank")
+	}
+
+	if _, err := base58.Decode(b.Value); err != nil {
+		return fmt.Errorf("invalid Base58 address")
+	}
+
+	return nil
+}
+
+// GetValue implements AddressData
+func (b Base58Address) GetValue() string {
+	return b.Value
+}
+
+// VerifyPubKey implements AddressData
+func (b Base58Address) VerifyPubKey(key cryptotypes.PubKey) (bool, error) {
+	bz, err := base58.Decode(b.Value)
+	return bytes.Equal(tmhash.SumTruncated(bz), key.Address().Bytes()), err
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+var _ AddressData = &HexAddress{}
+
+// NewHexAddress returns a new HexAddress instance
+func NewHexAddress(value, prefix string) *HexAddress {
+	return &HexAddress{Value: value, Prefix: prefix}
+}
+
+// Validate implements AddressData
+func (h HexAddress) Validate() error {
+	if strings.TrimSpace(h.Value) == "" {
+		return fmt.Errorf("value cannot be empty or blank")
+	}
+
+	if len(h.Value) <= len(h.Prefix) {
+		return fmt.Errorf("address cannot be smaller than prefix")
+	}
+
+	prefix, addrWithoutPrefix := h.Value[:len(h.Prefix)], h.Value[len(h.Prefix):]
+	if prefix != h.Prefix {
+		return fmt.Errorf("prefix does not match")
+	}
+
+	if _, err := hex.DecodeString(addrWithoutPrefix); err != nil {
+		return fmt.Errorf("invalid hex address")
+	}
+	return nil
+}
+
+// GetValue implements AddressData
+func (h HexAddress) GetValue() string {
+	return h.Value
+}
+
+// VerifyPubKey implements AddressData
+func (h HexAddress) VerifyPubKey(key cryptotypes.PubKey) (bool, error) {
+	addr := h.Value[len(h.Prefix):]
+	bz, err := hex.DecodeString(addr)
+	if err != nil {
+		return false, err
+	}
+	pubKey, err := btcec.ParsePubKey(key.Bytes(), btcec.S256())
+	if err != nil {
+		return false, err
+	}
+	uncompressedPubKey := pubKey.SerializeUncompressed()
+	return bytes.Equal(crypto.Keccak256(uncompressedPubKey[1:])[12:], bz), err
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+// UnpackAddressData deserializes the given any type value as an address data using the provided unpacker
+func UnpackAddressData(unpacker codectypes.AnyUnpacker, addressAny *codectypes.Any) (AddressData, error) {
+	var address AddressData
+	if err := unpacker.UnpackAny(addressAny, &address); err != nil {
+		return nil, err
+	}
+	return address, nil
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+// NewChainLink returns a new ChainLink instance
+// nolint:interfacer
+func NewChainLink(user string, address AddressData, proof Proof, chainConfig ChainConfig, creationTime time.Time) ChainLink {
+	addressAny, err := codectypes.NewAnyWithValue(address)
+	if err != nil {
+		panic("failed to pack address data to any type")
+	}
+	return ChainLink{
+		User:         user,
+		Address:      addressAny,
+		Proof:        proof,
+		ChainConfig:  chainConfig,
+		CreationTime: creationTime,
+	}
+}
+
+// GetAddressData returns the AddressData associated with this chain link
+func (link ChainLink) GetAddressData() AddressData {
+	return link.Address.GetCachedValue().(AddressData)
+}
+
+// Validate checks the validity of the ChainLink
+func (link ChainLink) Validate() error {
+	if _, err := sdk.AccAddressFromBech32(link.User); err != nil {
+		return fmt.Errorf("invalid creator address: %s", link.User)
+	}
+
+	if link.Address == nil {
+		return fmt.Errorf("address cannot be nil")
+	}
+
+	err := link.Proof.Validate()
+	if err != nil {
+		return err
+	}
+
+	err = link.ChainConfig.Validate()
+	if err != nil {
+		return err
+	}
+
+	if link.CreationTime.IsZero() {
+		return fmt.Errorf("creation time cannot be zero")
+	}
+
+	return nil
+}
+
+// UnpackInterfaces implements codectypes.UnpackInterfacesMessage
+func (link *ChainLink) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error {
+	if link.Address != nil {
+		var address AddressData
+		err := unpacker.UnpackAny(link.Address, &address)
+		if err != nil {
+			return err
+		}
+	}
+
+	err := link.Proof.UnpackInterfaces(unpacker)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// MustMarshalChainLink serializes the given chain link using the provided BinaryCodec
+func MustMarshalChainLink(cdc codec.BinaryCodec, link ChainLink) []byte {
+	return cdc.MustMarshal(&link)
+}
+
+// MustUnmarshalChainLink deserializes the given byte array as a chain link using
+// the provided BinaryCodec
+func MustUnmarshalChainLink(codec codec.BinaryCodec, bz []byte) ChainLink {
+	var link ChainLink
+	codec.MustUnmarshal(bz, &link)
+	return link
+}
diff --git a/x/profiles/legacy/v230/models_chain_links.pb.go b/x/profiles/legacy/v230/models_chain_links.pb.go
new file mode 100644
index 00000000..252a1eed
--- /dev/null
+++ b/x/profiles/legacy/v230/models_chain_links.pb.go
@@ -0,0 +1,1727 @@
+// Code generated by protoc-gen-gogo. DO NOT EDIT.
+// source: desmos/profiles/legacy/v230/models_chain_links.proto
+
+package v230
+
+import (
+	fmt "fmt"
+	types "github.com/cosmos/cosmos-sdk/codec/types"
+	_ "github.com/gogo/protobuf/gogoproto"
+	proto "github.com/gogo/protobuf/proto"
+	github_com_gogo_protobuf_types "github.com/gogo/protobuf/types"
+	_ "github.com/regen-network/cosmos-proto"
+	_ "google.golang.org/protobuf/types/known/timestamppb"
+	io "io"
+	math "math"
+	math_bits "math/bits"
+	time "time"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+var _ = time.Kitchen
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
+
+// ChainLink contains the data representing either an inter- or cross- chain
+// link
+type ChainLink struct {
+	// User defines the destination profile address to link
+	User string `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty" yaml:"user"`
+	// Address contains the data of the external chain address to be connected
+	// with the Desmos profile
+	Address *types.Any `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty" yaml:"address"`
+	// Proof contains the ownership proof of the external chain address
+	Proof Proof `protobuf:"bytes,3,opt,name=proof,proto3" json:"proof" yaml:"proof"`
+	// ChainConfig contains the configuration of the external chain
+	ChainConfig ChainConfig `protobuf:"bytes,4,opt,name=chain_config,json=chainConfig,proto3" json:"chain_config" yaml:"chain_config"`
+	// CreationTime represents the time in which the link has been created
+	CreationTime time.Time `protobuf:"bytes,5,opt,name=creation_time,json=creationTime,proto3,stdtime" json:"creation_time" yaml:"creation_time"`
+}
+
+func (m *ChainLink) Reset()         { *m = ChainLink{} }
+func (m *ChainLink) String() string { return proto.CompactTextString(m) }
+func (*ChainLink) ProtoMessage()    {}
+func (*ChainLink) Descriptor() ([]byte, []int) {
+	return fileDescriptor_85b730b6f73481b3, []int{0}
+}
+func (m *ChainLink) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *ChainLink) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_ChainLink.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 *ChainLink) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ChainLink.Merge(m, src)
+}
+func (m *ChainLink) XXX_Size() int {
+	return m.Size()
+}
+func (m *ChainLink) XXX_DiscardUnknown() {
+	xxx_messageInfo_ChainLink.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ChainLink proto.InternalMessageInfo
+
+// ChainConfig contains the data of the chain with which the link is made.
+type ChainConfig struct {
+	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty" yaml:"name"`
+}
+
+func (m *ChainConfig) Reset()         { *m = ChainConfig{} }
+func (m *ChainConfig) String() string { return proto.CompactTextString(m) }
+func (*ChainConfig) ProtoMessage()    {}
+func (*ChainConfig) Descriptor() ([]byte, []int) {
+	return fileDescriptor_85b730b6f73481b3, []int{1}
+}
+func (m *ChainConfig) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *ChainConfig) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_ChainConfig.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 *ChainConfig) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ChainConfig.Merge(m, src)
+}
+func (m *ChainConfig) XXX_Size() int {
+	return m.Size()
+}
+func (m *ChainConfig) XXX_DiscardUnknown() {
+	xxx_messageInfo_ChainConfig.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ChainConfig proto.InternalMessageInfo
+
+// Proof contains all the data used to verify a signature when linking an
+// account to a profile
+type Proof struct {
+	// PubKey represents the public key associated with the address for which to
+	// prove the ownership
+	PubKey *types.Any `protobuf:"bytes,1,opt,name=pub_key,json=pubKey,proto3" json:"pub_key,omitempty" yaml:"pub_key"`
+	// Signature represents the hex-encoded signature of the PlainText value
+	Signature string `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty" yaml:"signature"`
+	// PlainText represents the hex-encoded value signed in order to produce the Signature
+	PlainText string `protobuf:"bytes,3,opt,name=plain_text,json=plainText,proto3" json:"plain_text,omitempty" yaml:"plain_text"`
+}
+
+func (m *Proof) Reset()         { *m = Proof{} }
+func (m *Proof) String() string { return proto.CompactTextString(m) }
+func (*Proof) ProtoMessage()    {}
+func (*Proof) Descriptor() ([]byte, []int) {
+	return fileDescriptor_85b730b6f73481b3, []int{2}
+}
+func (m *Proof) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *Proof) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_Proof.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 *Proof) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Proof.Merge(m, src)
+}
+func (m *Proof) XXX_Size() int {
+	return m.Size()
+}
+func (m *Proof) XXX_DiscardUnknown() {
+	xxx_messageInfo_Proof.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Proof proto.InternalMessageInfo
+
+// Bech32Address represents a Bech32-encoded address
+type Bech32Address struct {
+	// Value represents the Bech-32 encoded address value
+	Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty" yaml:"value"`
+	// Prefix represents the HRP of the Bech32 address
+	Prefix string `protobuf:"bytes,2,opt,name=prefix,proto3" json:"prefix,omitempty" yaml:"prefix"`
+}
+
+func (m *Bech32Address) Reset()         { *m = Bech32Address{} }
+func (m *Bech32Address) String() string { return proto.CompactTextString(m) }
+func (*Bech32Address) ProtoMessage()    {}
+func (*Bech32Address) Descriptor() ([]byte, []int) {
+	return fileDescriptor_85b730b6f73481b3, []int{3}
+}
+func (m *Bech32Address) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *Bech32Address) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_Bech32Address.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 *Bech32Address) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Bech32Address.Merge(m, src)
+}
+func (m *Bech32Address) XXX_Size() int {
+	return m.Size()
+}
+func (m *Bech32Address) XXX_DiscardUnknown() {
+	xxx_messageInfo_Bech32Address.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Bech32Address proto.InternalMessageInfo
+
+// Base58Address represents a Base58-encoded address
+type Base58Address struct {
+	// Value contains the Base58-encoded address
+	Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty" yaml:"value"`
+}
+
+func (m *Base58Address) Reset()         { *m = Base58Address{} }
+func (m *Base58Address) String() string { return proto.CompactTextString(m) }
+func (*Base58Address) ProtoMessage()    {}
+func (*Base58Address) Descriptor() ([]byte, []int) {
+	return fileDescriptor_85b730b6f73481b3, []int{4}
+}
+func (m *Base58Address) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *Base58Address) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_Base58Address.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 *Base58Address) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Base58Address.Merge(m, src)
+}
+func (m *Base58Address) XXX_Size() int {
+	return m.Size()
+}
+func (m *Base58Address) XXX_DiscardUnknown() {
+	xxx_messageInfo_Base58Address.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Base58Address proto.InternalMessageInfo
+
+// HexAddress represents an Hex-encoded address
+// NOTE: Currently it only supports keccak256-uncompressed addresses
+type HexAddress struct {
+	// Value represents the hex address value
+	Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty" yaml:"value"`
+	// Prefix represents the optional prefix used during address encoding (e.g. 0x)
+	Prefix string `protobuf:"bytes,2,opt,name=prefix,proto3" json:"prefix,omitempty" yaml:"prefix"`
+}
+
+func (m *HexAddress) Reset()         { *m = HexAddress{} }
+func (m *HexAddress) String() string { return proto.CompactTextString(m) }
+func (*HexAddress) ProtoMessage()    {}
+func (*HexAddress) Descriptor() ([]byte, []int) {
+	return fileDescriptor_85b730b6f73481b3, []int{5}
+}
+func (m *HexAddress) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *HexAddress) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_HexAddress.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 *HexAddress) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_HexAddress.Merge(m, src)
+}
+func (m *HexAddress) XXX_Size() int {
+	return m.Size()
+}
+func (m *HexAddress) XXX_DiscardUnknown() {
+	xxx_messageInfo_HexAddress.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_HexAddress proto.InternalMessageInfo
+
+func init() {
+	proto.RegisterType((*ChainLink)(nil), "desmos.profiles.legacy.v230.ChainLink")
+	proto.RegisterType((*ChainConfig)(nil), "desmos.profiles.legacy.v230.ChainConfig")
+	proto.RegisterType((*Proof)(nil), "desmos.profiles.legacy.v230.Proof")
+	proto.RegisterType((*Bech32Address)(nil), "desmos.profiles.legacy.v230.Bech32Address")
+	proto.RegisterType((*Base58Address)(nil), "desmos.profiles.legacy.v230.Base58Address")
+	proto.RegisterType((*HexAddress)(nil), "desmos.profiles.legacy.v230.HexAddress")
+}
+
+func init() {
+	proto.RegisterFile("desmos/profiles/legacy/v230/models_chain_links.proto", fileDescriptor_85b730b6f73481b3)
+}
+
+var fileDescriptor_85b730b6f73481b3 = []byte{
+	// 639 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x54, 0xb1, 0x6f, 0xd3, 0x4e,
+	0x14, 0x8e, 0x7f, 0x6d, 0x5a, 0xe5, 0x92, 0xfc, 0x68, 0x8f, 0x20, 0x85, 0x56, 0xca, 0x95, 0x43,
+	0x42, 0x65, 0xa8, 0x8d, 0xd2, 0x22, 0x50, 0x27, 0xea, 0x32, 0x20, 0x81, 0x10, 0xb5, 0x3a, 0xb1,
+	0x44, 0x67, 0xe7, 0xe2, 0x58, 0xb5, 0x7d, 0x96, 0xcf, 0xae, 0x62, 0x09, 0x89, 0x95, 0xb1, 0x23,
+	0x63, 0xff, 0x02, 0x26, 0xfe, 0x02, 0xa6, 0x8a, 0xa9, 0x62, 0x62, 0x32, 0xa8, 0x5d, 0x98, 0xfd,
+	0x17, 0xa0, 0xbb, 0xb3, 0x9b, 0xd0, 0x42, 0x25, 0x26, 0xb6, 0xbb, 0xf7, 0xbe, 0xef, 0x7b, 0xef,
+	0xbe, 0xf7, 0x6c, 0xb0, 0x35, 0xa4, 0x3c, 0x60, 0xdc, 0x88, 0x62, 0x36, 0xf2, 0x7c, 0xca, 0x0d,
+	0x9f, 0xba, 0xc4, 0xc9, 0x8c, 0xc3, 0xfe, 0xe6, 0x03, 0x23, 0x60, 0x43, 0xea, 0xf3, 0x81, 0x33,
+	0x26, 0x5e, 0x38, 0xf0, 0xbd, 0xf0, 0x80, 0xeb, 0x51, 0xcc, 0x12, 0x06, 0x57, 0x15, 0x4b, 0xaf,
+	0x58, 0xba, 0x62, 0xe9, 0x82, 0xb5, 0xd2, 0x71, 0x99, 0xcb, 0x24, 0xce, 0x10, 0x27, 0x45, 0x59,
+	0xb9, 0xed, 0x32, 0xe6, 0xfa, 0xd4, 0x90, 0x37, 0x3b, 0x1d, 0x19, 0x24, 0xcc, 0xca, 0x14, 0xba,
+	0x9c, 0x4a, 0xbc, 0x80, 0xf2, 0x84, 0x04, 0x51, 0xc5, 0x75, 0x98, 0x28, 0x37, 0x50, 0xa2, 0xea,
+	0xa2, 0x52, 0xf8, 0xc3, 0x1c, 0x68, 0xec, 0x8a, 0xfe, 0x5e, 0x78, 0xe1, 0x01, 0xbc, 0x0b, 0xe6,
+	0x53, 0x4e, 0xe3, 0xae, 0xb6, 0xa6, 0xad, 0x37, 0xcc, 0x1b, 0x45, 0x8e, 0x9a, 0x19, 0x09, 0xfc,
+	0x6d, 0x2c, 0xa2, 0xd8, 0x92, 0x49, 0xb8, 0x07, 0x16, 0xc9, 0x70, 0x18, 0x53, 0xce, 0xbb, 0xff,
+	0xad, 0x69, 0xeb, 0xcd, 0x7e, 0x47, 0x57, 0x0d, 0xe8, 0x55, 0x03, 0xfa, 0x4e, 0x98, 0x99, 0x77,
+	0x8a, 0x1c, 0xfd, 0xaf, 0xd8, 0x25, 0x1c, 0x7f, 0xfe, 0xb8, 0xd1, 0xdc, 0x51, 0xe7, 0xa7, 0x24,
+	0x21, 0x56, 0xa5, 0x03, 0x5f, 0x82, 0x7a, 0x14, 0x33, 0x36, 0xea, 0xce, 0x49, 0x41, 0xac, 0x5f,
+	0xe3, 0x8f, 0xfe, 0x4a, 0x20, 0xcd, 0xce, 0x49, 0x8e, 0x6a, 0x45, 0x8e, 0x5a, 0xaa, 0x84, 0xa4,
+	0x63, 0x4b, 0xc9, 0xc0, 0x31, 0x68, 0x29, 0xd3, 0x1d, 0x16, 0x8e, 0x3c, 0xb7, 0x3b, 0x2f, 0x65,
+	0xd7, 0xaf, 0x95, 0x95, 0x2e, 0xec, 0x4a, 0xbc, 0xb9, 0x5a, 0x8a, 0xdf, 0x54, 0xe2, 0xb3, 0x5a,
+	0xd8, 0x6a, 0x3a, 0x53, 0x24, 0x24, 0xa0, 0xed, 0xc4, 0x94, 0x24, 0x1e, 0x0b, 0x07, 0xc2, 0xf6,
+	0x6e, 0x5d, 0x96, 0x5a, 0xb9, 0x62, 0xc9, 0x7e, 0x35, 0x13, 0x73, 0xad, 0x14, 0xef, 0x94, 0xe2,
+	0xb3, 0x74, 0x7c, 0xf4, 0x0d, 0x69, 0x56, 0xab, 0x8a, 0x09, 0xd2, 0x76, 0xeb, 0xdd, 0x31, 0xaa,
+	0xbd, 0x3f, 0x46, 0xda, 0x8f, 0x63, 0xa4, 0xe1, 0x27, 0xa0, 0x39, 0xd3, 0xa9, 0x98, 0x58, 0x48,
+	0x02, 0x7a, 0x75, 0x62, 0x22, 0x8a, 0x2d, 0x99, 0xbc, 0xa4, 0xf0, 0x49, 0x03, 0x75, 0xe9, 0x21,
+	0xdc, 0x01, 0x8b, 0x51, 0x6a, 0x0f, 0x0e, 0x68, 0x26, 0xf9, 0x7f, 0x9a, 0x24, 0x9c, 0x4e, 0xb2,
+	0x84, 0x63, 0x6b, 0x21, 0x4a, 0xed, 0xe7, 0x34, 0x83, 0x7d, 0xd0, 0xe0, 0x9e, 0x1b, 0x92, 0x24,
+	0x8d, 0xa9, 0x5c, 0x87, 0x86, 0xd9, 0x29, 0x72, 0xb4, 0xa4, 0xe0, 0x17, 0x29, 0x6c, 0x4d, 0x61,
+	0x70, 0x0b, 0x80, 0xc8, 0x17, 0x8e, 0x26, 0x74, 0x92, 0xc8, 0x91, 0x37, 0xcc, 0x5b, 0x45, 0x8e,
+	0x96, 0xcb, 0x1a, 0x17, 0x39, 0x6c, 0x35, 0xe4, 0x65, 0x9f, 0x4e, 0x92, 0x4b, 0x8f, 0x78, 0x0b,
+	0xda, 0x26, 0x75, 0xc6, 0x9b, 0xfd, 0x72, 0x9f, 0xe0, 0x3d, 0x50, 0x3f, 0x24, 0x7e, 0x5a, 0x39,
+	0xb1, 0x34, 0x5d, 0x0d, 0x19, 0xc6, 0x96, 0x4a, 0xc3, 0xfb, 0x60, 0x21, 0x8a, 0xe9, 0xc8, 0x9b,
+	0x94, 0xdd, 0x2e, 0x17, 0x39, 0x6a, 0x57, 0x3b, 0x24, 0xe2, 0xe2, 0x6d, 0xf2, 0xb0, 0xbd, 0x3a,
+	0x5b, 0xf1, 0xcb, 0xaf, 0xbb, 0x8b, 0xf7, 0x41, 0xdb, 0x24, 0x9c, 0x3e, 0x7c, 0xfc, 0x97, 0x0d,
+	0x5c, 0xaf, 0xfa, 0x06, 0x80, 0x67, 0x74, 0xf2, 0x8f, 0xde, 0x64, 0xee, 0x9d, 0x9c, 0xf5, 0xb4,
+	0xd3, 0xb3, 0x9e, 0xf6, 0xfd, 0xac, 0xa7, 0x1d, 0x9d, 0xf7, 0x6a, 0xa7, 0xe7, 0xbd, 0xda, 0xd7,
+	0xf3, 0x5e, 0xed, 0xf5, 0x23, 0xd7, 0x4b, 0xc6, 0xa9, 0xad, 0x3b, 0x2c, 0x30, 0xd4, 0x47, 0xb4,
+	0xe1, 0x13, 0x9b, 0x97, 0x67, 0xe3, 0xb0, 0x6f, 0x4c, 0x7e, 0xfb, 0x0b, 0xb4, 0x17, 0xe4, 0x26,
+	0x6d, 0xfe, 0x0c, 0x00, 0x00, 0xff, 0xff, 0x8c, 0x19, 0x03, 0xd0, 0x28, 0x05, 0x00, 0x00,
+}
+
+func (this *ChainLink) Equal(that interface{}) bool {
+	if that == nil {
+		return this == nil
+	}
+
+	that1, ok := that.(*ChainLink)
+	if !ok {
+		that2, ok := that.(ChainLink)
+		if ok {
+			that1 = &that2
+		} else {
+			return false
+		}
+	}
+	if that1 == nil {
+		return this == nil
+	} else if this == nil {
+		return false
+	}
+	if this.User != that1.User {
+		return false
+	}
+	if !this.Address.Equal(that1.Address) {
+		return false
+	}
+	if !this.Proof.Equal(&that1.Proof) {
+		return false
+	}
+	if !this.ChainConfig.Equal(&that1.ChainConfig) {
+		return false
+	}
+	if !this.CreationTime.Equal(that1.CreationTime) {
+		return false
+	}
+	return true
+}
+func (this *ChainConfig) Equal(that interface{}) bool {
+	if that == nil {
+		return this == nil
+	}
+
+	that1, ok := that.(*ChainConfig)
+	if !ok {
+		that2, ok := that.(ChainConfig)
+		if ok {
+			that1 = &that2
+		} else {
+			return false
+		}
+	}
+	if that1 == nil {
+		return this == nil
+	} else if this == nil {
+		return false
+	}
+	if this.Name != that1.Name {
+		return false
+	}
+	return true
+}
+func (this *Proof) Equal(that interface{}) bool {
+	if that == nil {
+		return this == nil
+	}
+
+	that1, ok := that.(*Proof)
+	if !ok {
+		that2, ok := that.(Proof)
+		if ok {
+			that1 = &that2
+		} else {
+			return false
+		}
+	}
+	if that1 == nil {
+		return this == nil
+	} else if this == nil {
+		return false
+	}
+	if !this.PubKey.Equal(that1.PubKey) {
+		return false
+	}
+	if this.Signature != that1.Signature {
+		return false
+	}
+	if this.PlainText != that1.PlainText {
+		return false
+	}
+	return true
+}
+func (this *Bech32Address) Equal(that interface{}) bool {
+	if that == nil {
+		return this == nil
+	}
+
+	that1, ok := that.(*Bech32Address)
+	if !ok {
+		that2, ok := that.(Bech32Address)
+		if ok {
+			that1 = &that2
+		} else {
+			return false
+		}
+	}
+	if that1 == nil {
+		return this == nil
+	} else if this == nil {
+		return false
+	}
+	if this.Value != that1.Value {
+		return false
+	}
+	if this.Prefix != that1.Prefix {
+		return false
+	}
+	return true
+}
+func (this *Base58Address) Equal(that interface{}) bool {
+	if that == nil {
+		return this == nil
+	}
+
+	that1, ok := that.(*Base58Address)
+	if !ok {
+		that2, ok := that.(Base58Address)
+		if ok {
+			that1 = &that2
+		} else {
+			return false
+		}
+	}
+	if that1 == nil {
+		return this == nil
+	} else if this == nil {
+		return false
+	}
+	if this.Value != that1.Value {
+		return false
+	}
+	return true
+}
+func (this *HexAddress) Equal(that interface{}) bool {
+	if that == nil {
+		return this == nil
+	}
+
+	that1, ok := that.(*HexAddress)
+	if !ok {
+		that2, ok := that.(HexAddress)
+		if ok {
+			that1 = &that2
+		} else {
+			return false
+		}
+	}
+	if that1 == nil {
+		return this == nil
+	} else if this == nil {
+		return false
+	}
+	if this.Value != that1.Value {
+		return false
+	}
+	if this.Prefix != that1.Prefix {
+		return false
+	}
+	return true
+}
+func (m *ChainLink) 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 *ChainLink) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *ChainLink) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	n1, err1 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.CreationTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.CreationTime):])
+	if err1 != nil {
+		return 0, err1
+	}
+	i -= n1
+	i = encodeVarintModelsChainLinks(dAtA, i, uint64(n1))
+	i--
+	dAtA[i] = 0x2a
+	{
+		size, err := m.ChainConfig.MarshalToSizedBuffer(dAtA[:i])
+		if err != nil {
+			return 0, err
+		}
+		i -= size
+		i = encodeVarintModelsChainLinks(dAtA, i, uint64(size))
+	}
+	i--
+	dAtA[i] = 0x22
+	{
+		size, err := m.Proof.MarshalToSizedBuffer(dAtA[:i])
+		if err != nil {
+			return 0, err
+		}
+		i -= size
+		i = encodeVarintModelsChainLinks(dAtA, i, uint64(size))
+	}
+	i--
+	dAtA[i] = 0x1a
+	if m.Address != nil {
+		{
+			size, err := m.Address.MarshalToSizedBuffer(dAtA[:i])
+			if err != nil {
+				return 0, err
+			}
+			i -= size
+			i = encodeVarintModelsChainLinks(dAtA, i, uint64(size))
+		}
+		i--
+		dAtA[i] = 0x12
+	}
+	if len(m.User) > 0 {
+		i -= len(m.User)
+		copy(dAtA[i:], m.User)
+		i = encodeVarintModelsChainLinks(dAtA, i, uint64(len(m.User)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *ChainConfig) 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 *ChainConfig) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *ChainConfig) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.Name) > 0 {
+		i -= len(m.Name)
+		copy(dAtA[i:], m.Name)
+		i = encodeVarintModelsChainLinks(dAtA, i, uint64(len(m.Name)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *Proof) 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 *Proof) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *Proof) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.PlainText) > 0 {
+		i -= len(m.PlainText)
+		copy(dAtA[i:], m.PlainText)
+		i = encodeVarintModelsChainLinks(dAtA, i, uint64(len(m.PlainText)))
+		i--
+		dAtA[i] = 0x1a
+	}
+	if len(m.Signature) > 0 {
+		i -= len(m.Signature)
+		copy(dAtA[i:], m.Signature)
+		i = encodeVarintModelsChainLinks(dAtA, i, uint64(len(m.Signature)))
+		i--
+		dAtA[i] = 0x12
+	}
+	if m.PubKey != nil {
+		{
+			size, err := m.PubKey.MarshalToSizedBuffer(dAtA[:i])
+			if err != nil {
+				return 0, err
+			}
+			i -= size
+			i = encodeVarintModelsChainLinks(dAtA, i, uint64(size))
+		}
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *Bech32Address) 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 *Bech32Address) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *Bech32Address) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.Prefix) > 0 {
+		i -= len(m.Prefix)
+		copy(dAtA[i:], m.Prefix)
+		i = encodeVarintModelsChainLinks(dAtA, i, uint64(len(m.Prefix)))
+		i--
+		dAtA[i] = 0x12
+	}
+	if len(m.Value) > 0 {
+		i -= len(m.Value)
+		copy(dAtA[i:], m.Value)
+		i = encodeVarintModelsChainLinks(dAtA, i, uint64(len(m.Value)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *Base58Address) 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 *Base58Address) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *Base58Address) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.Value) > 0 {
+		i -= len(m.Value)
+		copy(dAtA[i:], m.Value)
+		i = encodeVarintModelsChainLinks(dAtA, i, uint64(len(m.Value)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *HexAddress) 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 *HexAddress) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *HexAddress) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.Prefix) > 0 {
+		i -= len(m.Prefix)
+		copy(dAtA[i:], m.Prefix)
+		i = encodeVarintModelsChainLinks(dAtA, i, uint64(len(m.Prefix)))
+		i--
+		dAtA[i] = 0x12
+	}
+	if len(m.Value) > 0 {
+		i -= len(m.Value)
+		copy(dAtA[i:], m.Value)
+		i = encodeVarintModelsChainLinks(dAtA, i, uint64(len(m.Value)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func encodeVarintModelsChainLinks(dAtA []byte, offset int, v uint64) int {
+	offset -= sovModelsChainLinks(v)
+	base := offset
+	for v >= 1<<7 {
+		dAtA[offset] = uint8(v&0x7f | 0x80)
+		v >>= 7
+		offset++
+	}
+	dAtA[offset] = uint8(v)
+	return base
+}
+func (m *ChainLink) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.User)
+	if l > 0 {
+		n += 1 + l + sovModelsChainLinks(uint64(l))
+	}
+	if m.Address != nil {
+		l = m.Address.Size()
+		n += 1 + l + sovModelsChainLinks(uint64(l))
+	}
+	l = m.Proof.Size()
+	n += 1 + l + sovModelsChainLinks(uint64(l))
+	l = m.ChainConfig.Size()
+	n += 1 + l + sovModelsChainLinks(uint64(l))
+	l = github_com_gogo_protobuf_types.SizeOfStdTime(m.CreationTime)
+	n += 1 + l + sovModelsChainLinks(uint64(l))
+	return n
+}
+
+func (m *ChainConfig) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.Name)
+	if l > 0 {
+		n += 1 + l + sovModelsChainLinks(uint64(l))
+	}
+	return n
+}
+
+func (m *Proof) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.PubKey != nil {
+		l = m.PubKey.Size()
+		n += 1 + l + sovModelsChainLinks(uint64(l))
+	}
+	l = len(m.Signature)
+	if l > 0 {
+		n += 1 + l + sovModelsChainLinks(uint64(l))
+	}
+	l = len(m.PlainText)
+	if l > 0 {
+		n += 1 + l + sovModelsChainLinks(uint64(l))
+	}
+	return n
+}
+
+func (m *Bech32Address) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.Value)
+	if l > 0 {
+		n += 1 + l + sovModelsChainLinks(uint64(l))
+	}
+	l = len(m.Prefix)
+	if l > 0 {
+		n += 1 + l + sovModelsChainLinks(uint64(l))
+	}
+	return n
+}
+
+func (m *Base58Address) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.Value)
+	if l > 0 {
+		n += 1 + l + sovModelsChainLinks(uint64(l))
+	}
+	return n
+}
+
+func (m *HexAddress) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.Value)
+	if l > 0 {
+		n += 1 + l + sovModelsChainLinks(uint64(l))
+	}
+	l = len(m.Prefix)
+	if l > 0 {
+		n += 1 + l + sovModelsChainLinks(uint64(l))
+	}
+	return n
+}
+
+func sovModelsChainLinks(x uint64) (n int) {
+	return (math_bits.Len64(x|1) + 6) / 7
+}
+func sozModelsChainLinks(x uint64) (n int) {
+	return sovModelsChainLinks(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (m *ChainLink) 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 ErrIntOverflowModelsChainLinks
+			}
+			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: ChainLink: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: ChainLink: 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 ErrIntOverflowModelsChainLinks
+				}
+				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 ErrInvalidLengthModelsChainLinks
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			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 Address", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowModelsChainLinks
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.Address == nil {
+				m.Address = &types.Any{}
+			}
+			if err := m.Address.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 3:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Proof", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowModelsChainLinks
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := m.Proof.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 4:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ChainConfig", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowModelsChainLinks
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := m.ChainConfig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 5:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field CreationTime", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowModelsChainLinks
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.CreationTime, dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipModelsChainLinks(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *ChainConfig) 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 ErrIntOverflowModelsChainLinks
+			}
+			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: ChainConfig: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: ChainConfig: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowModelsChainLinks
+				}
+				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 ErrInvalidLengthModelsChainLinks
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Name = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipModelsChainLinks(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *Proof) 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 ErrIntOverflowModelsChainLinks
+			}
+			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: Proof: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: Proof: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field PubKey", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowModelsChainLinks
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.PubKey == nil {
+				m.PubKey = &types.Any{}
+			}
+			if err := m.PubKey.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Signature", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowModelsChainLinks
+				}
+				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 ErrInvalidLengthModelsChainLinks
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Signature = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 3:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field PlainText", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowModelsChainLinks
+				}
+				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 ErrInvalidLengthModelsChainLinks
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.PlainText = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipModelsChainLinks(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *Bech32Address) 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 ErrIntOverflowModelsChainLinks
+			}
+			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: Bech32Address: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: Bech32Address: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowModelsChainLinks
+				}
+				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 ErrInvalidLengthModelsChainLinks
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Value = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Prefix", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowModelsChainLinks
+				}
+				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 ErrInvalidLengthModelsChainLinks
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Prefix = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipModelsChainLinks(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *Base58Address) 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 ErrIntOverflowModelsChainLinks
+			}
+			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: Base58Address: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: Base58Address: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowModelsChainLinks
+				}
+				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 ErrInvalidLengthModelsChainLinks
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Value = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipModelsChainLinks(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *HexAddress) 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 ErrIntOverflowModelsChainLinks
+			}
+			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: HexAddress: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: HexAddress: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowModelsChainLinks
+				}
+				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 ErrInvalidLengthModelsChainLinks
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Value = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Prefix", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowModelsChainLinks
+				}
+				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 ErrInvalidLengthModelsChainLinks
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Prefix = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipModelsChainLinks(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func skipModelsChainLinks(dAtA []byte) (n int, err error) {
+	l := len(dAtA)
+	iNdEx := 0
+	depth := 0
+	for iNdEx < l {
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return 0, ErrIntOverflowModelsChainLinks
+			}
+			if iNdEx >= l {
+				return 0, io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= (uint64(b) & 0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		wireType := int(wire & 0x7)
+		switch wireType {
+		case 0:
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowModelsChainLinks
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				iNdEx++
+				if dAtA[iNdEx-1] < 0x80 {
+					break
+				}
+			}
+		case 1:
+			iNdEx += 8
+		case 2:
+			var length int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowModelsChainLinks
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				length |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if length < 0 {
+				return 0, ErrInvalidLengthModelsChainLinks
+			}
+			iNdEx += length
+		case 3:
+			depth++
+		case 4:
+			if depth == 0 {
+				return 0, ErrUnexpectedEndOfGroupModelsChainLinks
+			}
+			depth--
+		case 5:
+			iNdEx += 4
+		default:
+			return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
+		}
+		if iNdEx < 0 {
+			return 0, ErrInvalidLengthModelsChainLinks
+		}
+		if depth == 0 {
+			return iNdEx, nil
+		}
+	}
+	return 0, io.ErrUnexpectedEOF
+}
+
+var (
+	ErrInvalidLengthModelsChainLinks        = fmt.Errorf("proto: negative length found during unmarshaling")
+	ErrIntOverflowModelsChainLinks          = fmt.Errorf("proto: integer overflow")
+	ErrUnexpectedEndOfGroupModelsChainLinks = fmt.Errorf("proto: unexpected end of group")
+)
diff --git a/x/profiles/module.go b/x/profiles/module.go
index fd0d1a34..6a069717 100644
--- a/x/profiles/module.go
+++ b/x/profiles/module.go
@@ -22,6 +22,7 @@ import (
 
 	"github.com/desmos-labs/desmos/v2/x/profiles/client/cli"
 	"github.com/desmos-labs/desmos/v2/x/profiles/keeper"
+	v230 "github.com/desmos-labs/desmos/v2/x/profiles/legacy/v230"
 	"github.com/desmos-labs/desmos/v2/x/profiles/simulation"
 	"github.com/desmos-labs/desmos/v2/x/profiles/types"
 )
@@ -87,6 +88,7 @@ func (AppModuleBasic) GetQueryCmd() *cobra.Command {
 
 // RegisterInterfaces registers interfaces and implementations of the profiles module.
 func (AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) {
+	v230.RegisterInterfaces(registry)
 	types.RegisterInterfaces(registry)
 }
 
diff --git a/x/profiles/types/codec.go b/x/profiles/types/codec.go
index c05bb0f4..4772000e 100644
--- a/x/profiles/types/codec.go
+++ b/x/profiles/types/codec.go
@@ -31,6 +31,10 @@ func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
 	cdc.RegisterConcrete(&Base58Address{}, "desmos/Base58Address", nil)
 	cdc.RegisterConcrete(&HexAddress{}, "desmos/HexAddress", nil)
 
+	cdc.RegisterInterface((*SignatureData)(nil), nil)
+	cdc.RegisterConcrete(&SingleSignatureData{}, "desmos/SingleSignatureData", nil)
+	cdc.RegisterConcrete(&MultiSignatureData{}, "desmos/MultiSignatureData", nil)
+
 	cdc.RegisterConcrete(&Profile{}, "desmos/Profile", nil)
 }
 
@@ -45,6 +49,12 @@ func RegisterInterfaces(registry types.InterfaceRegistry) {
 		&Base58Address{},
 		&HexAddress{},
 	)
+	registry.RegisterInterface(
+		"desmos.profiles.v1beta1.Signature",
+		(*SignatureData)(nil),
+		&SingleSignatureData{},
+		&MultiSignatureData{},
+	)
 
 	registry.RegisterImplementations((*sdk.Msg)(nil),
 		&MsgSaveProfile{},
diff --git a/x/profiles/types/models_chain_links.go b/x/profiles/types/models_chain_links.go
index 66f6df9d..69a443a3 100644
--- a/x/profiles/types/models_chain_links.go
+++ b/x/profiles/types/models_chain_links.go
@@ -8,6 +8,7 @@ import (
 	"time"
 
 	"github.com/cosmos/cosmos-sdk/types/bech32"
+	signing "github.com/cosmos/cosmos-sdk/types/tx/signing"
 	"github.com/tendermint/tendermint/crypto/tmhash"
 
 	"github.com/btcsuite/btcd/btcec"
@@ -18,6 +19,7 @@ import (
 	"github.com/cosmos/cosmos-sdk/codec"
 	codectypes "github.com/cosmos/cosmos-sdk/codec/types"
 	cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
+	"github.com/cosmos/cosmos-sdk/crypto/types/multisig"
 	sdk "github.com/cosmos/cosmos-sdk/types"
 )
 
@@ -43,14 +45,20 @@ func (c ChainConfig) Validate() error {
 
 // NewProof is a constructor function for Proof
 // nolint:interfacer
-func NewProof(pubKey cryptotypes.PubKey, signature string, plainText string) Proof {
+func NewProof(pubKey cryptotypes.PubKey, signature SignatureData, plainText string) Proof {
 	pubKeyAny, err := codectypes.NewAnyWithValue(pubKey)
 	if err != nil {
 		panic("failed to pack public key to any type")
 	}
+
+	signatureAny, err := codectypes.NewAnyWithValue(signature)
+	if err != nil {
+		panic("failed to pack signature data to any type")
+	}
+
 	return Proof{
 		PubKey:    pubKeyAny,
-		Signature: signature,
+		Signature: signatureAny,
 		PlainText: plainText,
 	}
 }
@@ -61,16 +69,15 @@ func (p Proof) Validate() error {
 		return fmt.Errorf("public key field cannot be nil")
 	}
 
-	_, err := hex.DecodeString(p.Signature)
-	if err != nil {
-		return fmt.Errorf("invalid hex-encoded signature")
+	if p.Signature == nil {
+		return fmt.Errorf("signature field cannot be nil")
 	}
 
 	if strings.TrimSpace(p.PlainText) == "" {
 		return fmt.Errorf("plain text cannot be empty or blank")
 	}
 
-	_, err = hex.DecodeString(p.PlainText)
+	_, err := hex.DecodeString(p.PlainText)
 	if err != nil {
 		return fmt.Errorf("invalid hex-encoded plain text")
 	}
@@ -81,24 +88,57 @@ func (p Proof) Validate() error {
 // Verify verifies the signature using the given plain text and public key.
 // It returns and error if something is invalid.
 func (p Proof) Verify(unpacker codectypes.AnyUnpacker, address AddressData) error {
-	var pubkey cryptotypes.PubKey
-	err := unpacker.UnpackAny(p.PubKey, &pubkey)
+	value, err := hex.DecodeString(p.PlainText)
 	if err != nil {
-		return fmt.Errorf("failed to unpack the public key")
+		return fmt.Errorf("error while decoding proof text: %s", err)
 	}
 
-	value, _ := hex.DecodeString(p.PlainText)
+	var sigData SignatureData
+	err = unpacker.UnpackAny(p.Signature, &sigData)
+	if err != nil {
+		return fmt.Errorf("failed to unpack the signature")
+	}
+	// Convert the signature data to the Cosmos type
+	cosmosSigData, err := SignatureDataToCosmosSignatureData(unpacker, sigData)
+	if err != nil {
+		return err
+	}
+
+	// Verify the signature
+	var pubkey cryptotypes.PubKey
+	switch sigData := cosmosSigData.(type) {
+	case *signing.SingleSignatureData:
+		err = unpacker.UnpackAny(p.PubKey, &pubkey)
+		if err != nil {
+			return fmt.Errorf("failed to unpack the public key")
+		}
+		if !pubkey.VerifySignature(value, sigData.Signature) {
+			return fmt.Errorf("failed to verify the signature")
+		}
 
-	sig, _ := hex.DecodeString(p.Signature)
-	if !pubkey.VerifySignature(value, sig) {
-		return fmt.Errorf("failed to verify the signature")
+	case *signing.MultiSignatureData:
+		var multiPubkey multisig.PubKey
+		err = unpacker.UnpackAny(p.PubKey, &multiPubkey)
+		if err != nil {
+			return fmt.Errorf("failed to unpack the public key")
+		}
+		err = multiPubkey.VerifyMultisignature(
+			func(mode signing.SignMode) ([]byte, error) {
+				return value, nil
+			},
+			sigData,
+		)
+		if err != nil {
+			return err
+		}
+		pubkey = multiPubkey
 	}
 
+	// Verify the public key
 	valid, err := address.VerifyPubKey(pubkey)
 	if err != nil {
 		return err
 	}
-
 	if !valid {
 		return fmt.Errorf("invalid address and public key combination provided")
 	}
@@ -109,11 +149,114 @@ func (p Proof) Verify(unpacker codectypes.AnyUnpacker, address AddressData) erro
 // UnpackInterfaces implements codectypes.UnpackInterfacesMessage
 func (p *Proof) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error {
 	var pubKey cryptotypes.PubKey
-	return unpacker.UnpackAny(p.PubKey, &pubKey)
+	err := unpacker.UnpackAny(p.PubKey, &pubKey)
+	if err != nil {
+		return err
+	}
+
+	var signatureData SignatureData
+	return unpacker.UnpackAny(p.Signature, &signatureData)
 }
 
 // --------------------------------------------------------------------------------------------------------------------
 
+// SignatureData represents a generic single- or multi- signature data
+type SignatureData interface {
+	proto.Message
+
+	isSignatureData()
+}
+
+// SignatureDataToCosmosSignatureData allows to convert the given SignatureData to a Cosmos SignatureData instance
+// unpacking the proto.Any instance using the given unpacker.
+func SignatureDataToCosmosSignatureData(unpacker codectypes.AnyUnpacker, s SignatureData) (signing.SignatureData, error) {
+	switch data := s.(type) {
+	case *SingleSignatureData:
+		return &signing.SingleSignatureData{
+			Signature: data.Signature,
+			SignMode:  data.Mode,
+		}, nil
+
+	case *MultiSignatureData:
+		sigs, err := unpackSignatures(unpacker, data.Signatures)
+		if err != nil {
+			return nil, err
+		}
+
+		return &signing.MultiSignatureData{
+			BitArray:   data.BitArray,
+			Signatures: sigs,
+		}, nil
+	}
+
+	return nil, fmt.Errorf("signature type not supported: %T", s)
+}
+
+// SignatureDataFromCosmosSignatureData allows to create a SignatureData instance from the given Cosmos SignatureData
+func SignatureDataFromCosmosSignatureData(data signing.SignatureData) (SignatureData, error) {
+	switch data := data.(type) {
+	case *signing.SingleSignatureData:
+		return &SingleSignatureData{
+			Mode:      data.SignMode,
+			Signature: data.Signature,
+		}, nil
+	case *signing.MultiSignatureData:
+		sigAnys := make([]*codectypes.Any, len(data.Signatures))
+		for i, data := range data.Signatures {
+			sigData, err := SignatureDataFromCosmosSignatureData(data)
+			if err != nil {
+				return nil, err
+			}
+			sigAny, err := codectypes.NewAnyWithValue(sigData)
+			if err != nil {
+				return nil, err
+			}
+			sigAnys[i] = sigAny
+		}
+		return &MultiSignatureData{
+			BitArray:   data.BitArray,
+			Signatures: sigAnys,
+		}, nil
+	default:
+		return nil, fmt.Errorf("unexpected case %+v", data)
+	}
+}
+
+// unpackSignatures unpacks the given signatures using the provided unpacker
+func unpackSignatures(unpacker codectypes.AnyUnpacker, sigs []*codectypes.Any) ([]signing.SignatureData, error) {
+	var signatures = make([]signing.SignatureData, len(sigs))
+	for i, sig := range sigs {
+		var signatureData SignatureData
+		if err := unpacker.UnpackAny(sig, &signatureData); err != nil {
+			return nil, err
+		}
+
+		cosmosSigData, err := SignatureDataToCosmosSignatureData(unpacker, signatureData)
+		if err != nil {
+			return nil, err
+		}
+		signatures[i] = cosmosSigData
+	}
+
+	return signatures, nil
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+var _ SignatureData = &SingleSignatureData{}
+
+// isSignatureData implements SignatureData
+func (s *SingleSignatureData) isSignatureData() {}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+var _ SignatureData = &MultiSignatureData{}
+
+// isSignatureData implements SignatureData
+func (s *MultiSignatureData) isSignatureData() {}
+
+// --------------------------------------------------------------------------------------------------------------------
+
 // AddressData is an interface representing a generic external chain address
 type AddressData interface {
 	proto.Message
diff --git a/x/profiles/types/models_chain_links.pb.go b/x/profiles/types/models_chain_links.pb.go
index 654930d9..de506e4e 100644
--- a/x/profiles/types/models_chain_links.pb.go
+++ b/x/profiles/types/models_chain_links.pb.go
@@ -4,8 +4,11 @@
 package types
 
 import (
+	bytes "bytes"
 	fmt "fmt"
 	types "github.com/cosmos/cosmos-sdk/codec/types"
+	types1 "github.com/cosmos/cosmos-sdk/crypto/types"
+	signing "github.com/cosmos/cosmos-sdk/types/tx/signing"
 	_ "github.com/gogo/protobuf/gogoproto"
 	proto "github.com/gogo/protobuf/proto"
 	github_com_gogo_protobuf_types "github.com/gogo/protobuf/types"
@@ -123,8 +126,9 @@ type Proof struct {
 	// prove the ownership
 	PubKey *types.Any `protobuf:"bytes,1,opt,name=pub_key,json=pubKey,proto3" json:"pub_key,omitempty" yaml:"pub_key"`
 	// Signature represents the hex-encoded signature of the PlainText value
-	Signature string `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty" yaml:"signature"`
-	// PlainText represents the hex-encoded value signed in order to produce the Signature
+	Signature *types.Any `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty" yaml:"signature"`
+	// PlainText represents the hex-encoded value signed in order to produce the
+	// Signature
 	PlainText string `protobuf:"bytes,3,opt,name=plain_text,json=plainText,proto3" json:"plain_text,omitempty" yaml:"plain_text"`
 }
 
@@ -246,7 +250,8 @@ var xxx_messageInfo_Base58Address proto.InternalMessageInfo
 type HexAddress struct {
 	// Value represents the hex address value
 	Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty" yaml:"value"`
-	// Prefix represents the optional prefix used during address encoding (e.g. 0x)
+	// Prefix represents the optional prefix used during address encoding (e.g.
+	// 0x)
 	Prefix string `protobuf:"bytes,2,opt,name=prefix,proto3" json:"prefix,omitempty" yaml:"prefix"`
 }
 
@@ -283,6 +288,88 @@ func (m *HexAddress) XXX_DiscardUnknown() {
 
 var xxx_messageInfo_HexAddress proto.InternalMessageInfo
 
+// SingleSignatureData is the signature data for a single signer
+type SingleSignatureData struct {
+	// Mode is the signing mode of the single signer
+	Mode signing.SignMode `protobuf:"varint,1,opt,name=mode,proto3,enum=cosmos.tx.signing.v1beta1.SignMode" json:"mode,omitempty"`
+	// Signature is the raw signature bytes
+	Signature []byte `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"`
+}
+
+func (m *SingleSignatureData) Reset()         { *m = SingleSignatureData{} }
+func (m *SingleSignatureData) String() string { return proto.CompactTextString(m) }
+func (*SingleSignatureData) ProtoMessage()    {}
+func (*SingleSignatureData) Descriptor() ([]byte, []int) {
+	return fileDescriptor_1c1946212735e419, []int{6}
+}
+func (m *SingleSignatureData) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *SingleSignatureData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_SingleSignatureData.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 *SingleSignatureData) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_SingleSignatureData.Merge(m, src)
+}
+func (m *SingleSignatureData) XXX_Size() int {
+	return m.Size()
+}
+func (m *SingleSignatureData) XXX_DiscardUnknown() {
+	xxx_messageInfo_SingleSignatureData.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_SingleSignatureData proto.InternalMessageInfo
+
+// MultiSignatureData is the signature data for a multisig public key
+type MultiSignatureData struct {
+	// Bitarray specifies which keys within the multisig are signing
+	BitArray *types1.CompactBitArray `protobuf:"bytes,1,opt,name=bit_array,json=bitArray,proto3" json:"bit_array,omitempty"`
+	// Signatures is the signatures of the multi-signature
+	Signatures []*types.Any `protobuf:"bytes,2,rep,name=signatures,proto3" json:"signatures,omitempty"`
+}
+
+func (m *MultiSignatureData) Reset()         { *m = MultiSignatureData{} }
+func (m *MultiSignatureData) String() string { return proto.CompactTextString(m) }
+func (*MultiSignatureData) ProtoMessage()    {}
+func (*MultiSignatureData) Descriptor() ([]byte, []int) {
+	return fileDescriptor_1c1946212735e419, []int{7}
+}
+func (m *MultiSignatureData) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *MultiSignatureData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_MultiSignatureData.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 *MultiSignatureData) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_MultiSignatureData.Merge(m, src)
+}
+func (m *MultiSignatureData) XXX_Size() int {
+	return m.Size()
+}
+func (m *MultiSignatureData) XXX_DiscardUnknown() {
+	xxx_messageInfo_MultiSignatureData.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_MultiSignatureData proto.InternalMessageInfo
+
 func init() {
 	proto.RegisterType((*ChainLink)(nil), "desmos.profiles.v1beta1.ChainLink")
 	proto.RegisterType((*ChainConfig)(nil), "desmos.profiles.v1beta1.ChainConfig")
@@ -290,6 +377,8 @@ func init() {
 	proto.RegisterType((*Bech32Address)(nil), "desmos.profiles.v1beta1.Bech32Address")
 	proto.RegisterType((*Base58Address)(nil), "desmos.profiles.v1beta1.Base58Address")
 	proto.RegisterType((*HexAddress)(nil), "desmos.profiles.v1beta1.HexAddress")
+	proto.RegisterType((*SingleSignatureData)(nil), "desmos.profiles.v1beta1.SingleSignatureData")
+	proto.RegisterType((*MultiSignatureData)(nil), "desmos.profiles.v1beta1.MultiSignatureData")
 }
 
 func init() {
@@ -297,47 +386,57 @@ func init() {
 }
 
 var fileDescriptor_1c1946212735e419 = []byte{
-	// 633 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x54, 0xbd, 0x6e, 0xd3, 0x40,
-	0x1c, 0x8f, 0x69, 0xd3, 0x2a, 0x97, 0x04, 0x5a, 0x13, 0x44, 0x68, 0x25, 0x5f, 0x39, 0x10, 0x2a,
-	0x43, 0x6d, 0x9a, 0x82, 0x84, 0x3a, 0x51, 0x97, 0x01, 0x51, 0x06, 0xb0, 0x3a, 0xb1, 0x44, 0x67,
-	0xe7, 0xe2, 0x5a, 0xb5, 0x7d, 0x96, 0xef, 0x5c, 0x25, 0x12, 0x12, 0x2b, 0x63, 0x47, 0xc6, 0x4e,
-	0x3c, 0x01, 0x4f, 0xc0, 0x54, 0x31, 0x55, 0x4c, 0x4c, 0x06, 0xb5, 0x0b, 0xb3, 0x9f, 0x00, 0xdd,
-	0x9d, 0xdd, 0x84, 0x56, 0x45, 0x62, 0x62, 0xbb, 0xfb, 0xff, 0x3e, 0xee, 0xff, 0x65, 0x83, 0x47,
-	0x03, 0xc2, 0x22, 0xca, 0xac, 0x24, 0xa5, 0xc3, 0x20, 0x24, 0xcc, 0x3a, 0x58, 0x77, 0x09, 0xc7,
-	0xeb, 0x56, 0x44, 0x07, 0x24, 0x64, 0x7d, 0x6f, 0x0f, 0x07, 0x71, 0x3f, 0x0c, 0xe2, 0x7d, 0x66,
-	0x26, 0x29, 0xe5, 0x54, 0xbf, 0xad, 0x14, 0x66, 0xa5, 0x30, 0x4b, 0xc5, 0x52, 0xc7, 0xa7, 0x3e,
-	0x95, 0x1c, 0x4b, 0x9c, 0x14, 0x7d, 0xe9, 0x8e, 0x4f, 0xa9, 0x1f, 0x12, 0x4b, 0xde, 0xdc, 0x6c,
-	0x68, 0xe1, 0x78, 0x5c, 0x42, 0xf0, 0x22, 0xc4, 0x83, 0x88, 0x30, 0x8e, 0xa3, 0xa4, 0xd2, 0x7a,
-	0x54, 0x3c, 0xd5, 0x57, 0xa6, 0xea, 0xa2, 0x20, 0xf4, 0x69, 0x06, 0x34, 0xb6, 0x45, 0x6e, 0xaf,
-	0x82, 0x78, 0x5f, 0xbf, 0x07, 0x66, 0x33, 0x46, 0xd2, 0xae, 0xb6, 0xa2, 0xad, 0x36, 0xec, 0x1b,
-	0x45, 0x0e, 0x9b, 0x63, 0x1c, 0x85, 0x9b, 0x48, 0x44, 0x91, 0x23, 0x41, 0xfd, 0x0d, 0x98, 0xc7,
-	0x83, 0x41, 0x4a, 0x18, 0xeb, 0x5e, 0x5b, 0xd1, 0x56, 0x9b, 0xbd, 0x8e, 0xa9, 0x12, 0x30, 0xab,
-	0x04, 0xcc, 0xad, 0x78, 0x6c, 0xdf, 0x2d, 0x72, 0x78, 0x5d, 0xa9, 0x4b, 0x3a, 0xfa, 0xfa, 0x79,
-	0xad, 0xb9, 0xa5, 0xce, 0xcf, 0x31, 0xc7, 0x4e, 0xe5, 0xa3, 0xbf, 0x04, 0xf5, 0x24, 0xa5, 0x74,
-	0xd8, 0x9d, 0x91, 0x86, 0x86, 0x79, 0x45, 0x6f, 0xcc, 0xd7, 0x82, 0x65, 0x77, 0x8e, 0x73, 0x58,
-	0x2b, 0x72, 0xd8, 0x52, 0xf6, 0x52, 0x8a, 0x1c, 0x65, 0xa1, 0x0f, 0x40, 0x4b, 0x35, 0xdb, 0xa3,
-	0xf1, 0x30, 0xf0, 0xbb, 0xb3, 0xd2, 0xf2, 0xfe, 0x95, 0x96, 0xb2, 0xfa, 0x6d, 0xc9, 0xb5, 0x97,
-	0x4b, 0xe3, 0x9b, 0xca, 0x78, 0xda, 0x07, 0x39, 0x4d, 0x6f, 0xc2, 0xd4, 0x31, 0x68, 0x7b, 0x29,
-	0xc1, 0x3c, 0xa0, 0x71, 0x5f, 0xb4, 0xbb, 0x5b, 0x97, 0xcf, 0x2c, 0x5d, 0x6a, 0xc5, 0x6e, 0x35,
-	0x0b, 0x7b, 0xa5, 0x34, 0xef, 0x94, 0xe6, 0xd3, 0x72, 0x74, 0xf8, 0x03, 0x6a, 0x4e, 0xab, 0x8a,
-	0x09, 0xd1, 0x66, 0xeb, 0xc3, 0x11, 0xac, 0x7d, 0x3c, 0x82, 0xda, 0xaf, 0x23, 0xa8, 0xa1, 0x67,
-	0xa0, 0x39, 0x95, 0xa9, 0x98, 0x54, 0x8c, 0x23, 0x72, 0x79, 0x52, 0x22, 0x8a, 0x1c, 0x09, 0x5e,
-	0x70, 0xf8, 0xa2, 0x81, 0xba, 0xec, 0x9f, 0xbe, 0x05, 0xe6, 0x93, 0xcc, 0xed, 0xef, 0x93, 0xb1,
-	0xd4, 0x5f, 0x35, 0x41, 0x7d, 0x32, 0xc1, 0x92, 0x8e, 0x9c, 0xb9, 0x24, 0x73, 0x77, 0xc8, 0x58,
-	0xef, 0x81, 0x06, 0x0b, 0xfc, 0x18, 0xf3, 0x2c, 0x25, 0x72, 0x0d, 0x1a, 0x76, 0xa7, 0xc8, 0xe1,
-	0x82, 0xa2, 0x9f, 0x43, 0xc8, 0x99, 0xd0, 0xf4, 0xc7, 0x00, 0x24, 0xa1, 0xe8, 0x28, 0x27, 0x23,
-	0x2e, 0x47, 0xdd, 0xb0, 0x6f, 0x15, 0x39, 0x5c, 0x2c, 0xdf, 0x38, 0xc7, 0x90, 0xd3, 0x90, 0x97,
-	0x5d, 0x32, 0xe2, 0x17, 0x8a, 0x78, 0x0f, 0xda, 0x36, 0xf1, 0xf6, 0x36, 0x7a, 0xe5, 0x1e, 0xe9,
-	0x0f, 0x40, 0xfd, 0x00, 0x87, 0x59, 0xd5, 0x89, 0x85, 0xc9, 0x5a, 0xc8, 0x30, 0x72, 0x14, 0xac,
-	0x3f, 0x04, 0x73, 0x49, 0x4a, 0x86, 0xc1, 0xa8, 0xcc, 0x76, 0xb1, 0xc8, 0x61, 0xbb, 0xda, 0x1f,
-	0x11, 0x17, 0xb5, 0xc9, 0xc3, 0xe6, 0xf2, 0xf4, 0x8b, 0xdf, 0xfe, 0xdc, 0x59, 0xb4, 0x0b, 0xda,
-	0x36, 0x66, 0xe4, 0xc9, 0xd3, 0x7f, 0x4c, 0xe0, 0xef, 0xae, 0xef, 0x00, 0x78, 0x41, 0x46, 0xff,
-	0xa9, 0x26, 0x7b, 0xe7, 0xf8, 0xd4, 0xd0, 0x4e, 0x4e, 0x0d, 0xed, 0xe7, 0xa9, 0xa1, 0x1d, 0x9e,
-	0x19, 0xb5, 0x93, 0x33, 0xa3, 0xf6, 0xfd, 0xcc, 0xa8, 0xbd, 0x5d, 0xf7, 0x03, 0xbe, 0x97, 0xb9,
-	0xa6, 0x47, 0x23, 0x4b, 0x7d, 0x40, 0x6b, 0x21, 0x76, 0x59, 0x79, 0xb6, 0x0e, 0x7a, 0xd6, 0x68,
-	0xf2, 0xcb, 0xe3, 0xe3, 0x84, 0x30, 0x77, 0x4e, 0xee, 0xd0, 0xc6, 0xef, 0x00, 0x00, 0x00, 0xff,
-	0xff, 0xc6, 0xc7, 0xe8, 0x24, 0x12, 0x05, 0x00, 0x00,
+	// 793 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x55, 0xcf, 0x4f, 0xdb, 0x48,
+	0x14, 0x8e, 0x17, 0x02, 0x9b, 0x49, 0xc2, 0x82, 0xc9, 0x6a, 0x03, 0xac, 0x62, 0xd6, 0xac, 0x76,
+	0xd9, 0x03, 0xf6, 0x12, 0x76, 0xb5, 0xab, 0x9c, 0x1a, 0xd3, 0x03, 0x2a, 0x20, 0xb5, 0x86, 0x53,
+	0x2f, 0xd1, 0xd8, 0x99, 0x98, 0x11, 0xb6, 0xc7, 0xb2, 0xc7, 0x28, 0x91, 0x2a, 0xf5, 0xd8, 0x1e,
+	0x39, 0xf6, 0xc8, 0xa9, 0xff, 0x40, 0xfb, 0x47, 0xa0, 0x9e, 0x50, 0x4f, 0x9c, 0xd2, 0x0a, 0x2e,
+	0x3d, 0xe7, 0x2f, 0xa8, 0xe6, 0x87, 0x93, 0x10, 0x14, 0xa4, 0x9e, 0x7a, 0x9b, 0x99, 0xf7, 0x7d,
+	0xdf, 0xbc, 0x37, 0xef, 0xf3, 0x33, 0xf8, 0xbb, 0x8d, 0x92, 0x80, 0x24, 0x66, 0x14, 0x93, 0x0e,
+	0xf6, 0x51, 0x62, 0x9e, 0x6d, 0x3b, 0x88, 0xc2, 0x6d, 0x33, 0x20, 0x6d, 0xe4, 0x27, 0x2d, 0xf7,
+	0x04, 0xe2, 0xb0, 0xe5, 0xe3, 0xf0, 0x34, 0x31, 0xa2, 0x98, 0x50, 0xa2, 0xfe, 0x22, 0x18, 0x46,
+	0xc6, 0x30, 0x24, 0x63, 0xb5, 0xe2, 0x11, 0x8f, 0x70, 0x8c, 0xc9, 0x56, 0x02, 0xbe, 0xba, 0xe2,
+	0x11, 0xe2, 0xf9, 0xc8, 0xe4, 0x3b, 0x27, 0xed, 0x98, 0x30, 0xec, 0xc9, 0x90, 0x36, 0x19, 0xa2,
+	0x38, 0x40, 0x09, 0x85, 0x41, 0x94, 0x71, 0x5d, 0xc2, 0xae, 0x6a, 0x09, 0x51, 0xb1, 0x91, 0xa1,
+	0x3f, 0xc5, 0xce, 0xa4, 0x5d, 0x33, 0xc1, 0x5e, 0x88, 0x43, 0x6f, 0x98, 0xb9, 0xdc, 0x4b, 0xe0,
+	0x96, 0x04, 0xba, 0x71, 0x2f, 0xa2, 0xc4, 0x0c, 0x52, 0x9f, 0xe2, 0x04, 0x8f, 0xd0, 0xd9, 0x81,
+	0x80, 0xeb, 0x6f, 0x67, 0x40, 0x61, 0x97, 0xd5, 0x7c, 0x80, 0xc3, 0x53, 0x75, 0x03, 0xcc, 0xa6,
+	0x09, 0x8a, 0xab, 0xca, 0xba, 0xb2, 0x59, 0xb0, 0x7e, 0x1a, 0xf4, 0xb5, 0x62, 0x0f, 0x06, 0x7e,
+	0x43, 0x67, 0xa7, 0xba, 0xcd, 0x83, 0xea, 0x33, 0x30, 0x0f, 0xdb, 0xed, 0x18, 0x25, 0x49, 0xf5,
+	0x87, 0x75, 0x65, 0xb3, 0x58, 0xaf, 0x18, 0xa2, 0x30, 0x23, 0x2b, 0xcc, 0x68, 0x86, 0x3d, 0xeb,
+	0xb7, 0x41, 0x5f, 0x5b, 0x10, 0x6c, 0x09, 0xd7, 0x3f, 0xbc, 0xdf, 0x2a, 0x36, 0xc5, 0xfa, 0x31,
+	0xa4, 0xd0, 0xce, 0x74, 0xd4, 0x27, 0x20, 0x1f, 0xc5, 0x84, 0x74, 0xaa, 0x33, 0x5c, 0xb0, 0x66,
+	0x4c, 0x79, 0x73, 0xe3, 0x29, 0x43, 0x59, 0x95, 0xcb, 0xbe, 0x96, 0x1b, 0xf4, 0xb5, 0x92, 0x90,
+	0xe7, 0x54, 0xdd, 0x16, 0x12, 0x6a, 0x1b, 0x94, 0x44, 0x13, 0x5d, 0x12, 0x76, 0xb0, 0x57, 0x9d,
+	0xe5, 0x92, 0xbf, 0x4f, 0x95, 0xe4, 0xd5, 0xef, 0x72, 0xac, 0xb5, 0x26, 0x85, 0x97, 0x85, 0xf0,
+	0xb8, 0x8e, 0x6e, 0x17, 0xdd, 0x11, 0x52, 0x85, 0xa0, 0xec, 0xc6, 0x08, 0x52, 0x4c, 0xc2, 0x16,
+	0x6b, 0x63, 0x35, 0xcf, 0xaf, 0x59, 0xbd, 0xf7, 0x14, 0xc7, 0x59, 0x8f, 0xad, 0x75, 0x29, 0x5e,
+	0x91, 0xe2, 0xe3, 0x74, 0xfd, 0xfc, 0x93, 0xa6, 0xd8, 0xa5, 0xec, 0x8c, 0x91, 0x1a, 0xa5, 0xd7,
+	0x17, 0x5a, 0xee, 0xcd, 0x85, 0xa6, 0x7c, 0xb9, 0xd0, 0x14, 0xfd, 0x11, 0x28, 0x8e, 0x65, 0xca,
+	0x3a, 0x15, 0xc2, 0x00, 0xdd, 0xef, 0x14, 0x3b, 0xd5, 0x6d, 0x1e, 0x9c, 0x50, 0xb8, 0x56, 0x40,
+	0x9e, 0xbf, 0x9f, 0xda, 0x04, 0xf3, 0x51, 0xea, 0xb4, 0x4e, 0x51, 0x8f, 0xf3, 0xa7, 0x75, 0x50,
+	0x1d, 0x75, 0x50, 0xc2, 0x75, 0x7b, 0x2e, 0x4a, 0x9d, 0x7d, 0xd4, 0x53, 0xf7, 0x40, 0x81, 0xf9,
+	0x0e, 0xd2, 0x34, 0x46, 0x0f, 0xda, 0xa0, 0x32, 0xe8, 0x6b, 0x8b, 0x42, 0x64, 0x48, 0xd0, 0xed,
+	0x11, 0x59, 0xfd, 0x07, 0x80, 0xc8, 0x67, 0xef, 0x4c, 0x51, 0x97, 0x72, 0x03, 0x14, 0xac, 0x9f,
+	0x07, 0x7d, 0x6d, 0x49, 0xde, 0x3c, 0x8c, 0xe9, 0x76, 0x81, 0x6f, 0x8e, 0x51, 0x97, 0x4e, 0x94,
+	0xf6, 0x12, 0x94, 0x2d, 0xe4, 0x9e, 0xec, 0xd4, 0xa5, 0xbb, 0xd4, 0x3f, 0x40, 0xfe, 0x0c, 0xfa,
+	0x69, 0xf6, 0x3e, 0x8b, 0x23, 0xb3, 0xf0, 0x63, 0xdd, 0x16, 0x61, 0xf5, 0x2f, 0x30, 0x17, 0xc5,
+	0xa8, 0x83, 0xbb, 0xbc, 0x86, 0x82, 0xb5, 0x34, 0xe8, 0x6b, 0xe5, 0xcc, 0x55, 0xec, 0x9c, 0x55,
+	0xcc, 0x17, 0x8d, 0xb5, 0xf1, 0x1b, 0x3f, 0xde, 0x75, 0xb2, 0x7e, 0x0c, 0xca, 0x16, 0x4c, 0xd0,
+	0xbf, 0xff, 0x7f, 0x63, 0x02, 0x0f, 0xab, 0xbe, 0x00, 0x60, 0x0f, 0x75, 0xbf, 0x57, 0x4d, 0xaf,
+	0x14, 0xb0, 0x7c, 0x84, 0x43, 0xcf, 0x47, 0x47, 0x59, 0xb3, 0xd8, 0xb9, 0xfa, 0x1f, 0x98, 0x65,
+	0xc3, 0x92, 0xa7, 0xb1, 0x50, 0xdf, 0x30, 0xe4, 0x9c, 0xa2, 0x5d, 0x23, 0x9b, 0x44, 0xd9, 0xa7,
+	0xc5, 0x78, 0x87, 0xa4, 0x8d, 0x6c, 0x4e, 0x50, 0x7f, 0x9d, 0xf4, 0x4c, 0x69, 0xcc, 0x07, 0x8d,
+	0x15, 0x96, 0x8b, 0xcc, 0xa3, 0x7c, 0xe7, 0x46, 0xfd, 0x9d, 0x02, 0xd4, 0x43, 0x36, 0xb7, 0xee,
+	0x26, 0x72, 0x00, 0x0a, 0x0e, 0xa6, 0x2d, 0x18, 0xc7, 0x30, 0x33, 0xb2, 0x99, 0x65, 0x23, 0xc6,
+	0x9f, 0x31, 0x9c, 0x76, 0xc3, 0xaf, 0x9d, 0x04, 0x11, 0x74, 0xa9, 0x85, 0x69, 0x93, 0xd1, 0xec,
+	0x1f, 0x1d, 0xb9, 0x62, 0x3e, 0x1c, 0x26, 0xc3, 0x26, 0xdb, 0xcc, 0x34, 0x4b, 0xdb, 0x63, 0xb8,
+	0x07, 0xb2, 0xb6, 0xf6, 0x2f, 0x6f, 0x6a, 0xca, 0xd5, 0x4d, 0x4d, 0xf9, 0x7c, 0x53, 0x53, 0xce,
+	0x6f, 0x6b, 0xb9, 0xab, 0xdb, 0x5a, 0xee, 0xfa, 0xb6, 0x96, 0x7b, 0xbe, 0xed, 0x61, 0x7a, 0x92,
+	0x3a, 0x86, 0x4b, 0x02, 0x53, 0x8c, 0xa5, 0x2d, 0x1f, 0x3a, 0x89, 0x5c, 0x9b, 0x67, 0x75, 0xb3,
+	0x3b, 0xfa, 0x41, 0xd1, 0x5e, 0x84, 0x12, 0x67, 0x8e, 0x67, 0xb0, 0xf3, 0x35, 0x00, 0x00, 0xff,
+	0xff, 0x75, 0xed, 0x8c, 0x26, 0xc0, 0x06, 0x00, 0x00,
 }
 
 func (this *ChainLink) Equal(that interface{}) bool {
@@ -422,7 +521,7 @@ func (this *Proof) Equal(that interface{}) bool {
 	if !this.PubKey.Equal(that1.PubKey) {
 		return false
 	}
-	if this.Signature != that1.Signature {
+	if !this.Signature.Equal(that1.Signature) {
 		return false
 	}
 	if this.PlainText != that1.PlainText {
@@ -508,6 +607,65 @@ func (this *HexAddress) Equal(that interface{}) bool {
 	}
 	return true
 }
+func (this *SingleSignatureData) Equal(that interface{}) bool {
+	if that == nil {
+		return this == nil
+	}
+
+	that1, ok := that.(*SingleSignatureData)
+	if !ok {
+		that2, ok := that.(SingleSignatureData)
+		if ok {
+			that1 = &that2
+		} else {
+			return false
+		}
+	}
+	if that1 == nil {
+		return this == nil
+	} else if this == nil {
+		return false
+	}
+	if this.Mode != that1.Mode {
+		return false
+	}
+	if !bytes.Equal(this.Signature, that1.Signature) {
+		return false
+	}
+	return true
+}
+func (this *MultiSignatureData) Equal(that interface{}) bool {
+	if that == nil {
+		return this == nil
+	}
+
+	that1, ok := that.(*MultiSignatureData)
+	if !ok {
+		that2, ok := that.(MultiSignatureData)
+		if ok {
+			that1 = &that2
+		} else {
+			return false
+		}
+	}
+	if that1 == nil {
+		return this == nil
+	} else if this == nil {
+		return false
+	}
+	if !this.BitArray.Equal(that1.BitArray) {
+		return false
+	}
+	if len(this.Signatures) != len(that1.Signatures) {
+		return false
+	}
+	for i := range this.Signatures {
+		if !this.Signatures[i].Equal(that1.Signatures[i]) {
+			return false
+		}
+	}
+	return true
+}
 func (m *ChainLink) Marshal() (dAtA []byte, err error) {
 	size := m.Size()
 	dAtA = make([]byte, size)
@@ -635,10 +793,15 @@ func (m *Proof) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 		i--
 		dAtA[i] = 0x1a
 	}
-	if len(m.Signature) > 0 {
-		i -= len(m.Signature)
-		copy(dAtA[i:], m.Signature)
-		i = encodeVarintModelsChainLinks(dAtA, i, uint64(len(m.Signature)))
+	if m.Signature != nil {
+		{
+			size, err := m.Signature.MarshalToSizedBuffer(dAtA[:i])
+			if err != nil {
+				return 0, err
+			}
+			i -= size
+			i = encodeVarintModelsChainLinks(dAtA, i, uint64(size))
+		}
 		i--
 		dAtA[i] = 0x12
 	}
@@ -761,6 +924,90 @@ func (m *HexAddress) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	return len(dAtA) - i, nil
 }
 
+func (m *SingleSignatureData) 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 *SingleSignatureData) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *SingleSignatureData) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.Signature) > 0 {
+		i -= len(m.Signature)
+		copy(dAtA[i:], m.Signature)
+		i = encodeVarintModelsChainLinks(dAtA, i, uint64(len(m.Signature)))
+		i--
+		dAtA[i] = 0x12
+	}
+	if m.Mode != 0 {
+		i = encodeVarintModelsChainLinks(dAtA, i, uint64(m.Mode))
+		i--
+		dAtA[i] = 0x8
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *MultiSignatureData) 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 *MultiSignatureData) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *MultiSignatureData) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.Signatures) > 0 {
+		for iNdEx := len(m.Signatures) - 1; iNdEx >= 0; iNdEx-- {
+			{
+				size, err := m.Signatures[iNdEx].MarshalToSizedBuffer(dAtA[:i])
+				if err != nil {
+					return 0, err
+				}
+				i -= size
+				i = encodeVarintModelsChainLinks(dAtA, i, uint64(size))
+			}
+			i--
+			dAtA[i] = 0x12
+		}
+	}
+	if m.BitArray != nil {
+		{
+			size, err := m.BitArray.MarshalToSizedBuffer(dAtA[:i])
+			if err != nil {
+				return 0, err
+			}
+			i -= size
+			i = encodeVarintModelsChainLinks(dAtA, i, uint64(size))
+		}
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
 func encodeVarintModelsChainLinks(dAtA []byte, offset int, v uint64) int {
 	offset -= sovModelsChainLinks(v)
 	base := offset
@@ -818,8 +1065,8 @@ func (m *Proof) Size() (n int) {
 		l = m.PubKey.Size()
 		n += 1 + l + sovModelsChainLinks(uint64(l))
 	}
-	l = len(m.Signature)
-	if l > 0 {
+	if m.Signature != nil {
+		l = m.Signature.Size()
 		n += 1 + l + sovModelsChainLinks(uint64(l))
 	}
 	l = len(m.PlainText)
@@ -876,6 +1123,41 @@ func (m *HexAddress) Size() (n int) {
 	return n
 }
 
+func (m *SingleSignatureData) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.Mode != 0 {
+		n += 1 + sovModelsChainLinks(uint64(m.Mode))
+	}
+	l = len(m.Signature)
+	if l > 0 {
+		n += 1 + l + sovModelsChainLinks(uint64(l))
+	}
+	return n
+}
+
+func (m *MultiSignatureData) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.BitArray != nil {
+		l = m.BitArray.Size()
+		n += 1 + l + sovModelsChainLinks(uint64(l))
+	}
+	if len(m.Signatures) > 0 {
+		for _, e := range m.Signatures {
+			l = e.Size()
+			n += 1 + l + sovModelsChainLinks(uint64(l))
+		}
+	}
+	return n
+}
+
 func sovModelsChainLinks(x uint64) (n int) {
 	return (math_bits.Len64(x|1) + 6) / 7
 }
@@ -1250,7 +1532,7 @@ func (m *Proof) Unmarshal(dAtA []byte) error {
 			if wireType != 2 {
 				return fmt.Errorf("proto: wrong wireType = %d for field Signature", wireType)
 			}
-			var stringLen uint64
+			var msglen int
 			for shift := uint(0); ; shift += 7 {
 				if shift >= 64 {
 					return ErrIntOverflowModelsChainLinks
@@ -1260,23 +1542,27 @@ func (m *Proof) Unmarshal(dAtA []byte) error {
 				}
 				b := dAtA[iNdEx]
 				iNdEx++
-				stringLen |= uint64(b&0x7F) << shift
+				msglen |= int(b&0x7F) << shift
 				if b < 0x80 {
 					break
 				}
 			}
-			intStringLen := int(stringLen)
-			if intStringLen < 0 {
+			if msglen < 0 {
 				return ErrInvalidLengthModelsChainLinks
 			}
-			postIndex := iNdEx + intStringLen
+			postIndex := iNdEx + msglen
 			if postIndex < 0 {
 				return ErrInvalidLengthModelsChainLinks
 			}
 			if postIndex > l {
 				return io.ErrUnexpectedEOF
 			}
-			m.Signature = string(dAtA[iNdEx:postIndex])
+			if m.Signature == nil {
+				m.Signature = &types.Any{}
+			}
+			if err := m.Signature.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
 			iNdEx = postIndex
 		case 3:
 			if wireType != 2 {
@@ -1641,6 +1927,229 @@ func (m *HexAddress) Unmarshal(dAtA []byte) error {
 	}
 	return nil
 }
+func (m *SingleSignatureData) 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 ErrIntOverflowModelsChainLinks
+			}
+			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: SingleSignatureData: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: SingleSignatureData: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Mode", wireType)
+			}
+			m.Mode = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowModelsChainLinks
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.Mode |= signing.SignMode(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Signature", wireType)
+			}
+			var byteLen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowModelsChainLinks
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				byteLen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if byteLen < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			postIndex := iNdEx + byteLen
+			if postIndex < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Signature = append(m.Signature[:0], dAtA[iNdEx:postIndex]...)
+			if m.Signature == nil {
+				m.Signature = []byte{}
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipModelsChainLinks(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *MultiSignatureData) 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 ErrIntOverflowModelsChainLinks
+			}
+			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: MultiSignatureData: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: MultiSignatureData: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field BitArray", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowModelsChainLinks
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.BitArray == nil {
+				m.BitArray = &types1.CompactBitArray{}
+			}
+			if err := m.BitArray.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Signatures", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowModelsChainLinks
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Signatures = append(m.Signatures, &types.Any{})
+			if err := m.Signatures[len(m.Signatures)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipModelsChainLinks(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthModelsChainLinks
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
 func skipModelsChainLinks(dAtA []byte) (n int, err error) {
 	l := len(dAtA)
 	iNdEx := 0


Test patch:
diff --git a/app/desmos/cmd/chainlink/create_json_suite_test.go b/app/desmos/cmd/chainlink/create_json_suite_test.go
new file mode 100644
index 00000000..90bbeca8
--- /dev/null
+++ b/app/desmos/cmd/chainlink/create_json_suite_test.go
@@ -0,0 +1,104 @@
+package chainlink_test
+
+import (
+	"io/ioutil"
+	"os"
+	"testing"
+
+	"github.com/desmos-labs/desmos/v2/app/desmos/cmd/chainlink/types"
+
+	"github.com/cosmos/cosmos-sdk/client"
+	"github.com/cosmos/cosmos-sdk/codec"
+	cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
+	"github.com/stretchr/testify/suite"
+
+	"github.com/desmos-labs/desmos/v2/app"
+)
+
+func TestCreateJSONChainLinkSuite(t *testing.T) {
+	suite.Run(t, new(CreateJSONChainLinkTestSuite))
+}
+
+type CreateJSONChainLinkTestSuite struct {
+	suite.Suite
+
+	Codec     codec.Codec
+	ClientCtx client.Context
+}
+
+func (suite *CreateJSONChainLinkTestSuite) SetupSuite() {
+	cfg := sdk.GetConfig()
+	app.SetupConfig(cfg)
+
+	encodingConfig := app.MakeTestEncodingConfig()
+	suite.Codec = encodingConfig.Marshaler
+	suite.ClientCtx = client.Context{}.WithOutput(os.Stdout).WithTxConfig(encodingConfig.TxConfig)
+}
+
+func (suite *CreateJSONChainLinkTestSuite) TempFile() string {
+	info, err := ioutil.TempFile(suite.T().TempDir(), "Test_")
+	suite.Require().NoError(err)
+	return info.Name()
+}
+
+func (suite *CreateJSONChainLinkTestSuite) GetPubKeyFromTxFile(txFile string) cryptotypes.PubKey {
+	parsedTx, err := authclient.ReadTxFromFile(suite.ClientCtx, txFile)
+
+	txBuilder, err := suite.ClientCtx.TxConfig.WrapTxBuilder(parsedTx)
+	suite.Require().NoError(err)
+
+	sigs, err := txBuilder.GetTx().GetSignaturesV2()
+	suite.Require().NoError(err)
+	suite.Require().NotEmpty(sigs)
+
+	return sigs[0].PubKey
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+// MockGetter represents a mock implementation of ChainLinkReferenceGetter
+type MockGetter struct {
+	FileName string
+	IsSingle bool
+	TxFile   string
+}
+
+func NewMockGetter(fileName string, isSingle bool, txFile string) MockGetter {
+	return MockGetter{
+		FileName: fileName,
+		IsSingle: isSingle,
+		TxFile:   txFile,
+	}
+}
+
+// GetIsSingleSignature implements ChainLinkReferenceGetter
+func (mock MockGetter) IsSingleSignatureAccount() (bool, error) {
+	return mock.IsSingle, nil
+}
+
+// GetChain implements ChainLinkReferenceGetter
+func (mock MockGetter) GetChain() (types.Chain, error) {
+	return types.NewChain("Cosmos", "cosmos", "cosmos", "m/44'/118'/0'/0/0"), nil
+}
+
+// GetFilename implements ChainLinkReferenceGetter
+func (mock MockGetter) GetFilename() (string, error) {
+	return mock.FileName, nil
+}
+
+// GetMnemonic implements SingleSignatureAccountReferenceGetter
+func (mock MockGetter) GetMnemonic() (string, error) {
+	return "clip toilet stairs jaguar baby over mosquito capital speed mule adjust eye print voyage verify smart open crack imitate auto gauge museum planet rebel", nil
+}
+
+// GetMultiSignedTxFilePath implements MultiSignatureAccountReferenceGetter
+func (mock MockGetter) GetMultiSignedTxFilePath() (string, error) {
+	return mock.TxFile, nil
+}
+
+// GetSignedChainID implements MultiSignatureAccountReferenceGetter
+func (mock MockGetter) GetSignedChainID() (string, error) {
+	return "cosmos", nil
+}
diff --git a/app/desmos/cmd/chainlink/create_json_test.go b/app/desmos/cmd/chainlink/create_json_test.go
index 1e4d9766..5d17177e 100644
--- a/app/desmos/cmd/chainlink/create_json_test.go
+++ b/app/desmos/cmd/chainlink/create_json_test.go
@@ -2,94 +2,173 @@ package chainlink_test
 
 import (
 	"io/ioutil"
-	"os"
-	"path"
-	"testing"
+
+	"github.com/desmos-labs/desmos/v2/app/desmos/cmd/chainlink/builder"
 
 	cmd "github.com/desmos-labs/desmos/v2/app/desmos/cmd/chainlink"
-	"github.com/desmos-labs/desmos/v2/app/desmos/cmd/chainlink/types"
+	"github.com/desmos-labs/desmos/v2/testutil"
 
-	"github.com/cosmos/cosmos-sdk/client"
 	"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/v2/app"
+	multibuilder "github.com/desmos-labs/desmos/v2/app/desmos/cmd/chainlink/builder/multi"
+	singlebuilder "github.com/desmos-labs/desmos/v2/app/desmos/cmd/chainlink/builder/single"
 	profilescliutils "github.com/desmos-labs/desmos/v2/x/profiles/client/utils"
 	profilestypes "github.com/desmos-labs/desmos/v2/x/profiles/types"
 )
 
-// MockGetter represents a mock implementation of ChainLinkReferenceGetter
-type MockGetter struct {
-	FileName string
-}
-
-func NewMockGetter(fileName string) MockGetter {
-	return MockGetter{
-		FileName: fileName,
+func BuildMockChainLinkJSONBuilderProvider(getter MockGetter) builder.ChainLinkJSONBuilderProvider {
+	return func(isSingleAccount bool) builder.ChainLinkJSONBuilder {
+		if isSingleAccount {
+			return singlebuilder.NewAccountChainLinkJSONBuilder(getter)
+		}
+		return multibuilder.NewAccountChainLinkJSONBuilder(getter)
 	}
 }
 
-// GetMnemonic implements ChainLinkReferenceGetter
-func (mock MockGetter) GetMnemonic() (string, error) {
-	return "clip toilet stairs jaguar baby over mosquito capital speed mule adjust eye print voyage verify smart open crack imitate auto gauge museum planet rebel", nil
-}
-
-// GetChain implements ChainLinkReferenceGetter
-func (mock MockGetter) GetChain() (types.Chain, error) {
-	return types.NewChain("Cosmos", "cosmos", "cosmos", "m/44'/118'/0'/0/0"), nil
-}
-
-// GetFilename implements ChainLinkReferenceGetter
-func (mock MockGetter) GetFilename() (string, error) {
-	return mock.FileName, nil
-}
-
-func TestGetCreateChainLinkJSON(t *testing.T) {
-	cfg := sdk.GetConfig()
-	app.SetupConfig(cfg)
-
-	// Define where the data will be saved
-	fileName := path.Join(t.TempDir(), "out.json")
+// --------------------------------------------------------------------------------------------------------------------
 
-	clientCtx := client.Context{}.WithOutput(os.Stdout)
-	_, err := clitestutil.ExecTestCLICmd(clientCtx, cmd.GetCreateChainLinkJSON(NewMockGetter(fileName)), []string{})
-	require.NoError(t, err)
+func (suite *CreateJSONChainLinkTestSuite) TestSingleSignatureAccount() {
+	fileName := suite.TempFile()
+	getter := NewMockGetter(fileName, true, "")
+	_, err := clitestutil.ExecTestCLICmd(
+		suite.ClientCtx,
+		cmd.GetCreateChainLinkJSON(getter, BuildMockChainLinkJSONBuilderProvider(getter)),
+		[]string{},
+	)
+	suite.Require().NoError(err)
 
 	out, err := ioutil.ReadFile(fileName)
-	require.NoError(t, err)
+	suite.Require().NoError(err)
 
-	cdc, _ := app.MakeCodecs()
 	var data profilescliutils.ChainLinkJSON
-	err = cdc.UnmarshalJSON(out, &data)
-	require.NoError(t, err)
+	err = suite.Codec.UnmarshalJSON(out, &data)
+	suite.Require().NoError(err)
 
 	// Create an account inside the inmemory keybase
 	keyBase := keyring.NewInMemory()
-	keyName := "chainlink"
-	_, err = keyBase.NewAccount(
-		"chainlink",
-		"clip toilet stairs jaguar baby over mosquito capital speed mule adjust eye print voyage verify smart open crack imitate auto gauge museum planet rebel",
-		"",
-		"m/44'/118'/0'/0/0",
-		hd.Secp256k1,
-	)
-	require.NoError(t, err)
+	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(singlebuilder.KeyName, mnemonic, "", "m/44'/118'/0'/0/0", hd.Secp256k1)
+	suite.Require().NoError(err)
 
 	// Get the key from the keybase
-	key, err := keyBase.Key(keyName)
-	require.NoError(t, err)
+	key, err := keyBase.Key(singlebuilder.KeyName)
+	suite.Require().NoError(err)
 
 	expected := profilescliutils.NewChainLinkJSON(
 		profilestypes.NewBech32Address("cosmos13j7p6faa9jr8ty6lvqv0prldprr6m5xenmafnt", "cosmos"),
 		profilestypes.NewProof(
 			key.GetPubKey(),
-			"c3bd014b2178d63d94b9c28e628bfcf56736de28f352841b0bb27d6fff2968d62c13a10aeddd1ebfe3b13f3f8e61f79a2c63ae6ff5cb78cb0d64e6b0a70fae57",
+			testutil.SingleSignatureProtoFromHex("c3bd014b2178d63d94b9c28e628bfcf56736de28f352841b0bb27d6fff2968d62c13a10aeddd1ebfe3b13f3f8e61f79a2c63ae6ff5cb78cb0d64e6b0a70fae57"),
 			"636f736d6f7331336a377036666161396a72387479366c7671763070726c64707272366d3578656e6d61666e74"),
 		profilestypes.NewChainConfig("cosmos"),
 	)
 
-	require.Equal(t, expected, data)
+	suite.Require().Equal(expected, data)
+}
+
+func (suite *CreateJSONChainLinkTestSuite) TestMultiSignatureAccount() {
+	fileName := suite.TempFile()
+	txFileData := `{
+		"body": {
+			"messages": [
+				{
+					"@type": "/cosmos.gov.v1beta1.MsgVote",
+					"proposal_id": "1",
+					"voter": "cosmos1exdjkfxud8yzqtvua3hdd93xu0gmek5l47r8ra",
+					"option": "VOTE_OPTION_YES"
+				}
+			],
+			"memo": "",
+			"timeout_height": "0",
+			"extension_options": [],
+			"non_critical_extension_options": []
+		},
+		"auth_info": {
+			"signer_infos": [
+				{
+					"public_key": {
+						"@type": "/cosmos.crypto.multisig.LegacyAminoPubKey",
+						"threshold": 2,
+						"public_keys": [
+							{
+								"@type": "/cosmos.crypto.secp256k1.PubKey",
+								"key": "A4k1o4weHTkMVXqzT0zKRkRmWTwQEh3JGiPkJvCQ4VO7"
+							},
+							{
+								"@type": "/cosmos.crypto.secp256k1.PubKey",
+								"key": "ApfZ2jzyWcRxzgCnKEKr+oIMyrGIJMp+1FjouYPovluE"
+							},
+							{
+								"@type": "/cosmos.crypto.secp256k1.PubKey",
+								"key": "AziL8Ly6QrMOr+V7Vf6XCBjDjLPTq0Dtxv7PPzDRnFQe"
+							}
+						]
+					},
+					"mode_info": {
+						"multi": {
+							"bitarray": {
+								"extra_bits_stored": 3,
+								"elems": "wA=="
+							},
+							"mode_infos": [
+								{
+									"single": {
+										"mode": "SIGN_MODE_LEGACY_AMINO_JSON"
+									}
+								},
+								{
+									"single": {
+										"mode": "SIGN_MODE_LEGACY_AMINO_JSON"
+									}
+								}
+							]
+						}
+					},
+					"sequence": "0"
+				}
+			],
+			"fee": {
+				"amount": [],
+				"gas_limit": "200000",
+				"payer": "",
+				"granter": ""
+			}
+		},
+		"signatures": [
+			"CkAv4+a/BrQeFNM2ETyv8w5NTRigi4N6qF+Ry5Vx9/C4RWBd4EesFQhm/KBKuzFWq6QFNolXd/SH0ZjyQDd/darECkAcEtkxg/x/0ZqZdud7eI3yvTMn1TKSiu+KawEHBgUsSFvyh8ViIAmu1nLUVEXUOuD+PBmAI0BG0LL9Lnwfwjmg"
+		]
+	}`
+
+	// Define where the data will be saved
+	txFile := suite.TempFile()
+	err := ioutil.WriteFile(txFile, []byte(txFileData), 0600)
+	suite.Require().NoError(err)
+
+	getter := NewMockGetter(fileName, false, txFile)
+	_, err = clitestutil.ExecTestCLICmd(
+		suite.ClientCtx,
+		cmd.GetCreateChainLinkJSON(getter, BuildMockChainLinkJSONBuilderProvider(getter)),
+		[]string{},
+	)
+	suite.Require().NoError(err)
+
+	out, err := ioutil.ReadFile(fileName)
+	suite.Require().NoError(err)
+
+	var data profilescliutils.ChainLinkJSON
+	err = suite.Codec.UnmarshalJSON(out, &data)
+	suite.Require().NoError(err)
+
+	expected := profilescliutils.NewChainLinkJSON(
+		profilestypes.NewBech32Address("cosmos1exdjkfxud8yzqtvua3hdd93xu0gmek5l47r8ra", "cosmos"),
+		profilestypes.NewProof(
+			suite.GetPubKeyFromTxFile(txFile),
+			testutil.MultiSignatureProtoFromAnyHex(suite.Codec, "0a2b2f6465736d6f732e70726f66696c65732e763162657461312e4d756c74695369676e61747572654461746112f3010a0508031201c012740a2c2f6465736d6f732e70726f66696c65732e763162657461312e53696e676c655369676e6174757265446174611244087f12402fe3e6bf06b41e14d336113caff30e4d4d18a08b837aa85f91cb9571f7f0b845605de047ac150866fca04abb3156aba40536895777f487d198f240377f75aac412740a2c2f6465736d6f732e70726f66696c65732e763162657461312e53696e676c655369676e6174757265446174611244087f12401c12d93183fc7fd19a9976e77b788df2bd3327d532928aef8a6b010706052c485bf287c5622009aed672d45445d43ae0fe3c1980234046d0b2fd2e7c1fc239a0"),
+			"7b226163636f756e745f6e756d626572223a2230222c22636861696e5f6964223a22636f736d6f73222c22666565223a7b22616d6f756e74223a5b5d2c22676173223a22323030303030227d2c226d656d6f223a22222c226d736773223a5b7b2274797065223a22636f736d6f732d73646b2f4d7367566f7465222c2276616c7565223a7b226f7074696f6e223a312c2270726f706f73616c5f6964223a2231222c22766f746572223a22636f736d6f73316578646a6b6678756438797a7174767561336864643933787530676d656b356c343772387261227d7d5d2c2273657175656e6365223a2230227d"),
+		profilestypes.NewChainConfig("cosmos"),
+	)
+
+	suite.Require().Equal(expected, data)
 }
diff --git a/testutil/profiles.go b/testutil/profiles.go
index 5fa2567f..4915fa2d 100644
--- a/testutil/profiles.go
+++ b/testutil/profiles.go
@@ -1,6 +1,7 @@
 package testutil
 
 import (
+	"encoding/hex"
 	"fmt"
 	"time"
 
@@ -8,7 +9,9 @@ import (
 	cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	"github.com/cosmos/cosmos-sdk/types/bech32/legacybech32"
+	"github.com/cosmos/cosmos-sdk/types/tx/signing"
 	authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
+
 	"github.com/gogo/protobuf/proto"
 
 	"github.com/desmos-labs/desmos/v2/x/profiles/types"
@@ -60,3 +63,33 @@ func ProfileFromAddr(address string) *types.Profile {
 
 	return profile
 }
+
+// SingleSignatureProtoFromHex convert the hex-encoded string of the single signature to SignatureData
+func SingleSignatureProtoFromHex(s string) types.SignatureData {
+	sig, err := hex.DecodeString(s)
+	if err != nil {
+		panic(err)
+	}
+	return &types.SingleSignatureData{
+		Mode:      signing.SignMode_SIGN_MODE_DIRECT,
+		Signature: sig,
+	}
+}
+
+// MultiSignatureProtoFromAnyHex convert the hex-encoded string of the MultiSignature Any value to SignatureData
+func MultiSignatureProtoFromAnyHex(unpacker codectypes.AnyUnpacker, s string) types.SignatureData {
+	sig, err := hex.DecodeString(s)
+	if err != nil {
+		panic(err)
+	}
+	var multisigAny codectypes.Any
+	err = multisigAny.Unmarshal(sig)
+	if err != nil {
+		panic(err)
+	}
+	var sigData types.SignatureData
+	if err = unpacker.UnpackAny(&multisigAny, &sigData); err != nil {
+		panic(err)
+	}
+	return sigData
+}
diff --git a/x/profiles/client/cli/cli_chain_links_test.go b/x/profiles/client/cli/cli_chain_links_test.go
index b5269ac4..96dc29ff 100644
--- a/x/profiles/client/cli/cli_chain_links_test.go
+++ b/x/profiles/client/cli/cli_chain_links_test.go
@@ -43,7 +43,7 @@ func (s *IntegrationTestSuite) TestCmdQueryChainLinks() {
 						types.NewBech32Address("cosmos1cjf97gpzwmaf30pzvaargfgr884mpp5ak8f7ns", "cosmos"),
 						types.NewProof(
 							pubKey,
-							"909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b",
+							testutil.SingleSignatureProtoFromHex("909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b"),
 							"74657874",
 						),
 						types.NewChainConfig("cosmos"),
@@ -54,7 +54,7 @@ func (s *IntegrationTestSuite) TestCmdQueryChainLinks() {
 						types.NewBech32Address("cosmos1xmquc944hzu6n6qtljcexkuhhz76mucxtgm5x0", "cosmos"),
 						types.NewProof(
 							pubKey,
-							"909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b",
+							testutil.SingleSignatureProtoFromHex("909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b"),
 							"74657874",
 						),
 						types.NewChainConfig("cosmos"),
@@ -96,7 +96,7 @@ func (s *IntegrationTestSuite) TestCmdQueryChainLinks() {
 						types.NewBech32Address("cosmos1cjf97gpzwmaf30pzvaargfgr884mpp5ak8f7ns", "cosmos"),
 						types.NewProof(
 							pubKey,
-							"909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b",
+							testutil.SingleSignatureProtoFromHex("909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b"),
 							"74657874",
 						),
 						types.NewChainConfig("cosmos"),
@@ -107,7 +107,7 @@ func (s *IntegrationTestSuite) TestCmdQueryChainLinks() {
 						types.NewBech32Address("cosmos1xmquc944hzu6n6qtljcexkuhhz76mucxtgm5x0", "cosmos"),
 						types.NewProof(
 							pubKey,
-							"909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b",
+							testutil.SingleSignatureProtoFromHex("909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b"),
 							"74657874",
 						),
 						types.NewChainConfig("cosmos"),
diff --git a/x/profiles/client/cli/cli_test.go b/x/profiles/client/cli/cli_test.go
index 39491eb7..32ea23a9 100644
--- a/x/profiles/client/cli/cli_test.go
+++ b/x/profiles/client/cli/cli_test.go
@@ -163,7 +163,7 @@ func (s *IntegrationTestSuite) SetupSuite() {
 			types.NewBech32Address(stringAddr, "cosmos"),
 			types.NewProof(
 				pubKey,
-				"909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b",
+				testutil.SingleSignatureProtoFromHex("909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b"),
 				"74657874",
 			),
 			types.NewChainConfig("cosmos"),
@@ -178,7 +178,6 @@ func (s *IntegrationTestSuite) SetupSuite() {
 
 	s.cfg = cfg
 	s.network = network.New(s.T(), cfg)
-
 	_, err = s.network.WaitForHeight(1)
 	s.Require().NoError(err)
 }
@@ -226,7 +225,7 @@ func (s *IntegrationTestSuite) writeChainLinkJSONFile(filePath string) {
 
 	jsonData := utils.NewChainLinkJSON(
 		types.NewBech32Address(addStr, "cosmos"),
-		types.NewProof(srcKey.PubKey(), hex.EncodeToString(sigBz), hex.EncodeToString([]byte(plainText))),
+		types.NewProof(srcKey.PubKey(), testutil.SingleSignatureProtoFromHex(hex.EncodeToString(sigBz)), hex.EncodeToString([]byte(plainText))),
 		types.NewChainConfig("cosmos"),
 	)
 
diff --git a/x/profiles/client/utils/utils_test.go b/x/profiles/client/utils/utils_test.go
index aa32bfcf..7534be5f 100644
--- a/x/profiles/client/utils/utils_test.go
+++ b/x/profiles/client/utils/utils_test.go
@@ -12,6 +12,7 @@ import (
 	"github.com/stretchr/testify/require"
 
 	"github.com/desmos-labs/desmos/v2/app"
+	"github.com/desmos-labs/desmos/v2/testutil"
 	"github.com/desmos-labs/desmos/v2/x/profiles/client/utils"
 	"github.com/desmos-labs/desmos/v2/x/profiles/types"
 )
@@ -33,7 +34,7 @@ func TestParseLinkAccountJSON(t *testing.T) {
 
 	jsonData := utils.NewChainLinkJSON(
 		types.NewBech32Address(addStr, "cosmos"),
-		types.NewProof(pubKey, hex.EncodeToString(sigBz), plainText),
+		types.NewProof(pubKey, testutil.SingleSignatureProtoFromHex(hex.EncodeToString(sigBz)), plainText),
 		types.NewChainConfig("cosmos"),
 	)
 
diff --git a/x/profiles/keeper/alias_functions_test.go b/x/profiles/keeper/alias_functions_test.go
index ed2f8c80..f1a00e74 100644
--- a/x/profiles/keeper/alias_functions_test.go
+++ b/x/profiles/keeper/alias_functions_test.go
@@ -291,7 +291,7 @@ func (suite *KeeperTestSuite) TestKeeper_GetChainLinks() {
 						types.NewChainLink(
 							"cosmos1y54exmx84cqtasvjnskf9f63djuuj68p7hqf47",
 							types.NewBech32Address("cosmos10clxpupsmddtj7wu7g0wdysajqwp890mva046f", "cosmos"),
-							types.NewProof(pub1, "signature", "plain_text"),
+							types.NewProof(pub1, testutil.SingleSignatureProtoFromHex("1234"), "plain_text"),
 							types.NewChainConfig("cosmos"),
 							time.Date(2020, 1, 2, 00, 00, 00, 000, time.UTC),
 						),
@@ -304,7 +304,7 @@ func (suite *KeeperTestSuite) TestKeeper_GetChainLinks() {
 						types.NewChainLink(
 							"cosmos1y54exmx84cqtasvjnskf9f63djuuj68p7hqf47",
 							types.NewBech32Address("cosmos1ftkjv8njvkekk00ehwdfl5sst8zgdpenjfm4hs", "cosmos"),
-							types.NewProof(pub2, "signature", "plain_text"),
+							types.NewProof(pub2, testutil.SingleSignatureProtoFromHex("1234"), "plain_text"),
 							types.NewChainConfig("cosmos"),
 							time.Date(2020, 1, 2, 00, 00, 00, 000, time.UTC),
 						),
@@ -315,14 +315,14 @@ func (suite *KeeperTestSuite) TestKeeper_GetChainLinks() {
 				types.NewChainLink(
 					"cosmos1y54exmx84cqtasvjnskf9f63djuuj68p7hqf47",
 					types.NewBech32Address("cosmos10clxpupsmddtj7wu7g0wdysajqwp890mva046f", "cosmos"),
-					types.NewProof(pub1, "signature", "plain_text"),
+					types.NewProof(pub1, testutil.SingleSignatureProtoFromHex("1234"), "plain_text"),
 					types.NewChainConfig("cosmos"),
 					time.Date(2020, 1, 2, 00, 00, 00, 000, time.UTC),
 				),
 				types.NewChainLink(
 					"cosmos1y54exmx84cqtasvjnskf9f63djuuj68p7hqf47",
 					types.NewBech32Address("cosmos1ftkjv8njvkekk00ehwdfl5sst8zgdpenjfm4hs", "cosmos"),
-					types.NewProof(pub2, "signature", "plain_text"),
+					types.NewProof(pub2, testutil.SingleSignatureProtoFromHex("1234"), "plain_text"),
 					types.NewChainConfig("cosmos"),
 					time.Date(2020, 1, 2, 00, 00, 00, 000, time.UTC),
 				),
diff --git a/x/profiles/keeper/genesis_test.go b/x/profiles/keeper/genesis_test.go
index d571d8d2..77f65297 100644
--- a/x/profiles/keeper/genesis_test.go
+++ b/x/profiles/keeper/genesis_test.go
@@ -110,7 +110,7 @@ func (suite *KeeperTestSuite) Test_ExportGenesis() {
 						types.NewBech32Address("cosmos1nc54z3kzyal57w6wcf5khmwrxx5rafnwvu0m5z", "cosmos"),
 						types.NewProof(
 							testutil.PubKeyFromBech32("cosmospub1addwnpepqvryxhhqhw52c4ny5twtfzf3fsrjqhx0x5cuya0fylw0wu0eqptykeqhr4d"),
-							"909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b",
+							testutil.SingleSignatureProtoFromHex("909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b"),
 							"74657874",
 						),
 						types.NewChainConfig("cosmos"),
@@ -193,7 +193,7 @@ func (suite *KeeperTestSuite) Test_ExportGenesis() {
 						types.NewBech32Address("cosmos1nc54z3kzyal57w6wcf5khmwrxx5rafnwvu0m5z", "cosmos"),
 						types.NewProof(
 							testutil.PubKeyFromBech32("cosmospub1addwnpepqvryxhhqhw52c4ny5twtfzf3fsrjqhx0x5cuya0fylw0wu0eqptykeqhr4d"),
-							"909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b",
+							testutil.SingleSignatureProtoFromHex("909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b"),
 							"74657874",
 						),
 						types.NewChainConfig("cosmos"),
@@ -307,14 +307,14 @@ func (suite *KeeperTestSuite) Test_InitGenesis() {
 					types.NewChainLink(
 						"cosmos1y54exmx84cqtasvjnskf9f63djuuj68p7hqf47",
 						types.NewBech32Address(ext.GetAddress().String(), "cosmos"),
-						types.NewProof(ext.GetPubKey(), hex.EncodeToString(ext.Sign(ext.GetAddress())), hex.EncodeToString([]byte(ext.GetAddress().String()))),
+						types.NewProof(ext.GetPubKey(), testutil.SingleSignatureProtoFromHex(hex.EncodeToString(ext.Sign(ext.GetAddress()))), hex.EncodeToString([]byte(ext.GetAddress().String()))),
 						types.NewChainConfig("cosmos"),
 						time.Date(2020, 1, 2, 00, 00, 00, 000, time.UTC),
 					),
 					types.NewChainLink(
 						"cosmos1y54exmx84cqtasvjnskf9f63djuuj68p7hqf47",
 						types.NewBech32Address(ext.GetAddress().String(), "cosmos"),
-						types.NewProof(ext.GetPubKey(), hex.EncodeToString(ext.Sign(ext.GetAddress())), hex.EncodeToString([]byte(ext.GetAddress().String()))),
+						types.NewProof(ext.GetPubKey(), testutil.SingleSignatureProtoFromHex(hex.EncodeToString(ext.Sign(ext.GetAddress()))), hex.EncodeToString([]byte(ext.GetAddress().String()))),
 						types.NewChainConfig("cosmos"),
 						time.Date(2020, 1, 2, 00, 00, 00, 000, time.UTC),
 					),
@@ -393,7 +393,7 @@ func (suite *KeeperTestSuite) Test_InitGenesis() {
 						types.NewBech32Address(ext.GetAddress().String(), "cosmos"),
 						types.NewProof(
 							ext.GetPubKey(),
-							hex.EncodeToString(ext.Sign([]byte(ext.GetAddress().String()))),
+							testutil.SingleSignatureProtoFromHex(hex.EncodeToString(ext.Sign([]byte(ext.GetAddress().String())))),
 							hex.EncodeToString([]byte(ext.GetAddress().String())),
 						),
 						types.NewChainConfig("cosmos"),
@@ -477,7 +477,7 @@ func (suite *KeeperTestSuite) Test_InitGenesis() {
 						types.NewBech32Address(ext.GetAddress().String(), "cosmos"),
 						types.NewProof(
 							ext.GetPubKey(),
-							hex.EncodeToString(ext.Sign([]byte(ext.GetAddress().String()))),
+							testutil.SingleSignatureProtoFromHex(hex.EncodeToString(ext.Sign([]byte(ext.GetAddress().String())))),
 							hex.EncodeToString([]byte(ext.GetAddress().String())),
 						),
 						types.NewChainConfig("cosmos"),
diff --git a/x/profiles/keeper/grpc_query_test.go b/x/profiles/keeper/grpc_query_test.go
index ec868b80..7803589e 100644
--- a/x/profiles/keeper/grpc_query_test.go
+++ b/x/profiles/keeper/grpc_query_test.go
@@ -214,7 +214,7 @@ func (suite *KeeperTestSuite) TestQueryServer_ChainLinks() {
 					types.NewBech32Address("cosmos1cjf97gpzwmaf30pzvaargfgr884mpp5ak8f7ns", "cosmos"),
 					types.NewProof(
 						testutil.PubKeyFromBech32("cosmospub1addwnpepqvryxhhqhw52c4ny5twtfzf3fsrjqhx0x5cuya0fylw0wu0eqptykeqhr4d"),
-						"909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b",
+						testutil.SingleSignatureProtoFromHex("909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b"),
 						"74657874",
 					),
 					types.NewChainConfig("cosmos"),
@@ -230,7 +230,7 @@ func (suite *KeeperTestSuite) TestQueryServer_ChainLinks() {
 					types.NewBech32Address("cosmos19s242dxhxgzlsdmfjjg38jgfwhxca7569g84sw", "cosmos"),
 					types.NewProof(
 						testutil.PubKeyFromBech32("cosmospub1addwnpepqvryxhhqhw52c4ny5twtfzf3fsrjqhx0x5cuya0fylw0wu0eqptykeqhr4d"),
-						"909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b",
+						testutil.SingleSignatureProtoFromHex("909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b"),
 						"74657874",
 					),
 					types.NewChainConfig("cosmos"),
@@ -251,7 +251,7 @@ func (suite *KeeperTestSuite) TestQueryServer_ChainLinks() {
 					types.NewBech32Address("cosmos19s242dxhxgzlsdmfjjg38jgfwhxca7569g84sw", "cosmos"),
 					types.NewProof(
 						testutil.PubKeyFromBech32("cosmospub1addwnpepqvryxhhqhw52c4ny5twtfzf3fsrjqhx0x5cuya0fylw0wu0eqptykeqhr4d"),
-						"909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b",
+						testutil.SingleSignatureProtoFromHex("909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b"),
 						"74657874",
 					),
 					types.NewChainConfig("cosmos"),
@@ -262,7 +262,7 @@ func (suite *KeeperTestSuite) TestQueryServer_ChainLinks() {
 					types.NewBech32Address("cosmos1cjf97gpzwmaf30pzvaargfgr884mpp5ak8f7ns", "cosmos"),
 					types.NewProof(
 						testutil.PubKeyFromBech32("cosmospub1addwnpepqvryxhhqhw52c4ny5twtfzf3fsrjqhx0x5cuya0fylw0wu0eqptykeqhr4d"),
-						"909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b",
+						testutil.SingleSignatureProtoFromHex("909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b"),
 						"74657874",
 					),
 					types.NewChainConfig("cosmos"),
@@ -280,7 +280,7 @@ func (suite *KeeperTestSuite) TestQueryServer_ChainLinks() {
 					types.NewBech32Address("cosmos1cjf97gpzwmaf30pzvaargfgr884mpp5ak8f7ns", "cosmos"),
 					types.NewProof(
 						testutil.PubKeyFromBech32("cosmospub1addwnpepqvryxhhqhw52c4ny5twtfzf3fsrjqhx0x5cuya0fylw0wu0eqptykeqhr4d"),
-						"909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b",
+						testutil.SingleSignatureProtoFromHex("909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b"),
 						"74657874",
 					),
 					types.NewChainConfig("cosmos"),
@@ -296,7 +296,7 @@ func (suite *KeeperTestSuite) TestQueryServer_ChainLinks() {
 					types.NewBech32Address("cosmos19s242dxhxgzlsdmfjjg38jgfwhxca7569g84sw", "cosmos"),
 					types.NewProof(
 						testutil.PubKeyFromBech32("cosmospub1addwnpepqvryxhhqhw52c4ny5twtfzf3fsrjqhx0x5cuya0fylw0wu0eqptykeqhr4d"),
-						"909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b",
+						testutil.SingleSignatureProtoFromHex("909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b"),
 						"74657874",
 					),
 					types.NewChainConfig("cosmos"),
@@ -318,7 +318,7 @@ func (suite *KeeperTestSuite) TestQueryServer_ChainLinks() {
 					types.NewBech32Address("cosmos19s242dxhxgzlsdmfjjg38jgfwhxca7569g84sw", "cosmos"),
 					types.NewProof(
 						testutil.PubKeyFromBech32("cosmospub1addwnpepqvryxhhqhw52c4ny5twtfzf3fsrjqhx0x5cuya0fylw0wu0eqptykeqhr4d"),
-						"909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b",
+						testutil.SingleSignatureProtoFromHex("909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b"),
 						"74657874",
 					),
 					types.NewChainConfig("cosmos"),
@@ -377,7 +377,7 @@ func (suite *KeeperTestSuite) TestQueryServer_UserChainLink() {
 					types.NewBech32Address("cosmos1nc54z3kzyal57w6wcf5khmwrxx5rafnwvu0m5z", "cosmos"),
 					types.NewProof(
 						testutil.PubKeyFromBech32("cosmospub1addwnpepqvryxhhqhw52c4ny5twtfzf3fsrjqhx0x5cuya0fylw0wu0eqptykeqhr4d"),
-						"909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b",
+						testutil.SingleSignatureProtoFromHex("909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b"),
 						"74657874",
 					),
 					types.NewChainConfig("cosmos"),
@@ -397,7 +397,7 @@ func (suite *KeeperTestSuite) TestQueryServer_UserChainLink() {
 					types.NewBech32Address("cosmos1nc54z3kzyal57w6wcf5khmwrxx5rafnwvu0m5z", "cosmos"),
 					types.NewProof(
 						testutil.PubKeyFromBech32("cosmospub1addwnpepqvryxhhqhw52c4ny5twtfzf3fsrjqhx0x5cuya0fylw0wu0eqptykeqhr4d"),
-						"909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b",
+						testutil.SingleSignatureProtoFromHex("909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b"),
 						"74657874",
 					),
 					types.NewChainConfig("cosmos"),
diff --git a/x/profiles/keeper/invariants_test.go b/x/profiles/keeper/invariants_test.go
index 8cafc5c2..800c8238 100644
--- a/x/profiles/keeper/invariants_test.go
+++ b/x/profiles/keeper/invariants_test.go
@@ -122,7 +122,7 @@ func (suite *KeeperTestSuite) TestInvariants() {
 				link := types.NewChainLink(
 					"user",
 					types.NewBech32Address("value", "prefix"),
-					types.NewProof(key, "signature", "value"),
+					types.NewProof(key, testutil.SingleSignatureProtoFromHex("1234"), "value"),
 					types.NewChainConfig("chain_name"),
 					time.Now(),
 				)
diff --git a/x/profiles/keeper/keeper_chain_links_test.go b/x/profiles/keeper/keeper_chain_links_test.go
index de09e376..2415c896 100644
--- a/x/profiles/keeper/keeper_chain_links_test.go
+++ b/x/profiles/keeper/keeper_chain_links_test.go
@@ -32,7 +32,7 @@ func (suite *KeeperTestSuite) TestKeeper_StoreChainLink() {
 			name: "invalid chain address packed value returns error",
 			link: types.ChainLink{
 				Address:      testutil.NewAny(ext.privKey),
-				Proof:        types.NewProof(ext.GetPubKey(), sig, plainText),
+				Proof:        types.NewProof(ext.GetPubKey(), testutil.SingleSignatureProtoFromHex(sig), plainText),
 				ChainConfig:  types.NewChainConfig("cosmos"),
 				CreationTime: time.Date(2021, 1, 1, 00, 00, 00, 000, time.UTC),
 			},
@@ -43,7 +43,7 @@ func (suite *KeeperTestSuite) TestKeeper_StoreChainLink() {
 			link: types.NewChainLink(
 				"cosmos19xz3mrvzvp9ymgmudhpukucg6668l5haakh04x",
 				types.NewBech32Address("", "cosmos"),
-				types.NewProof(ext.GetPubKey(), sig, plainText),
+				types.NewProof(ext.GetPubKey(), testutil.SingleSignatureProtoFromHex(sig), plainText),
 				types.NewChainConfig("cosmos"),
 				time.Date(2021, 1, 1, 00, 00, 00, 000, time.UTC),
 			),
@@ -54,7 +54,7 @@ func (suite *KeeperTestSuite) TestKeeper_StoreChainLink() {
 			link: types.NewChainLink(
 				"cosmos19xz3mrvzvp9ymgmudhpukucg6668l5haakh04x",
 				types.NewBech32Address(ext.GetAddress().String(), "cosmos"),
-				types.NewProof(ext.GetPubKey(), sig, "wrong"),
+				types.NewProof(ext.GetPubKey(), testutil.SingleSignatureProtoFromHex(sig), "wrong"),
 				types.NewChainConfig("cosmos"),
 				time.Date(2021, 1, 1, 00, 00, 00, 000, time.UTC),
 			),
@@ -70,7 +70,7 @@ func (suite *KeeperTestSuite) TestKeeper_StoreChainLink() {
 				link := types.NewChainLink(
 					address,
 					types.NewBech32Address(ext.GetAddress().String(), "cosmos"),
-					types.NewProof(ext.GetPubKey(), sig, plainText),
+					types.NewProof(ext.GetPubKey(), testutil.SingleSignatureProtoFromHex(sig), plainText),
 					types.NewChainConfig("cosmos"),
 					time.Date(2020, 1, 2, 00, 00, 00, 000, time.UTC),
 				)
@@ -79,7 +79,7 @@ func (suite *KeeperTestSuite) TestKeeper_StoreChainLink() {
 			link: types.NewChainLink(
 				"cosmos19xz3mrvzvp9ymgmudhpukucg6668l5haakh04x",
 				types.NewBech32Address(ext.GetAddress().String(), "cosmos"),
-				types.NewProof(ext.GetPubKey(), sig, plainText),
+				types.NewProof(ext.GetPubKey(), testutil.SingleSignatureProtoFromHex(sig), plainText),
 				types.NewChainConfig("cosmos"),
 				time.Date(2020, 1, 2, 00, 00, 00, 000, time.UTC),
 			),
@@ -90,7 +90,7 @@ func (suite *KeeperTestSuite) TestKeeper_StoreChainLink() {
 				suite.Require().Contains(links, types.NewChainLink(
 					"cosmos19xz3mrvzvp9ymgmudhpukucg6668l5haakh04x",
 					types.NewBech32Address(ext.GetAddress().String(), "cosmos"),
-					types.NewProof(ext.GetPubKey(), sig, plainText),
+					types.NewProof(ext.GetPubKey(), testutil.SingleSignatureProtoFromHex(sig), plainText),
 					types.NewChainConfig("cosmos"),
 					time.Date(2020, 1, 2, 00, 00, 00, 000, time.UTC),
 				))
@@ -101,7 +101,7 @@ func (suite *KeeperTestSuite) TestKeeper_StoreChainLink() {
 			link: types.NewChainLink(
 				"",
 				types.NewBech32Address(ext.GetAddress().String(), "cosmos"),
-				types.NewProof(ext.GetPubKey(), sig, plainText),
+				types.NewProof(ext.GetPubKey(), testutil.SingleSignatureProtoFromHex(sig), plainText),
 				types.NewChainConfig("cosmos"),
 				time.Date(2020, 1, 2, 00, 00, 00, 000, time.UTC),
 			),
@@ -112,7 +112,7 @@ func (suite *KeeperTestSuite) TestKeeper_StoreChainLink() {
 			link: types.NewChainLink(
 				"cosmos19xz3mrvzvp9ymgmudhpukucg6668l5haakh04x",
 				types.NewBech32Address(ext.GetAddress().String(), "cosmos"),
-				types.NewProof(ext.GetPubKey(), sig, plainText),
+				types.NewProof(ext.GetPubKey(), testutil.SingleSignatureProtoFromHex(sig), plainText),
 				types.NewChainConfig("cosmos"),
 				time.Date(2020, 1, 2, 00, 00, 00, 000, time.UTC),
 			),
@@ -128,7 +128,7 @@ func (suite *KeeperTestSuite) TestKeeper_StoreChainLink() {
 			link: types.NewChainLink(
 				"cosmos19xz3mrvzvp9ymgmudhpukucg6668l5haakh04x",
 				types.NewBech32Address(ext.GetAddress().String(), "cosmos"),
-				types.NewProof(ext.GetPubKey(), sig, plainText),
+				types.NewProof(ext.GetPubKey(), testutil.SingleSignatureProtoFromHex(sig), plainText),
 				types.NewChainConfig("cosmos"),
 				time.Date(2020, 1, 2, 00, 00, 00, 000, time.UTC),
 			),
@@ -139,7 +139,7 @@ func (suite *KeeperTestSuite) TestKeeper_StoreChainLink() {
 				suite.Require().Contains(links, types.NewChainLink(
 					"cosmos19xz3mrvzvp9ymgmudhpukucg6668l5haakh04x",
 					types.NewBech32Address(ext.GetAddress().String(), "cosmos"),
-					types.NewProof(ext.GetPubKey(), sig, plainText),
+					types.NewProof(ext.GetPubKey(), testutil.SingleSignatureProtoFromHex(sig), plainText),
 					types.NewChainConfig("cosmos"),
 					time.Date(2020, 1, 2, 00, 00, 00, 000, time.UTC),
 				))
@@ -192,7 +192,7 @@ func (suite *KeeperTestSuite) TestKeeper_GetChainLink() {
 				link := types.NewChainLink(
 					"cosmos1y54exmx84cqtasvjnskf9f63djuuj68p7hqf47",
 					types.NewBech32Address("cosmos1cjf97gpzwmaf30pzvaargfgr884mpp5ak8f7ns", "cosmos"),
-					types.NewProof(secp256k1.GenPrivKey().PubKey(), "signature", "706c61696e5f74657874"),
+					types.NewProof(secp256k1.GenPrivKey().PubKey(), testutil.SingleSignatureProtoFromHex("1234"), "706c61696e5f74657874"),
 					types.NewChainConfig("cosmos"),
 					time.Date(2020, 1, 2, 00, 00, 00, 000, time.UTC),
 				)
@@ -336,7 +336,7 @@ func (suite *KeeperTestSuite) TestKeeper_DeleteAllUserChainLinks() {
 				link := types.NewChainLink(
 					user,
 					types.NewBech32Address("cosmos10nsdxxdvy9qka3zv0lzw8z9cnu6kanld8jh773", "cosmos"),
-					types.NewProof(key, "signature", "706c61696e74657874"),
+					types.NewProof(key, testutil.SingleSignatureProtoFromHex("1234"), "706c61696e74657874"),
 					types.NewChainConfig("cosmos"),
 					time.Date(2021, 1, 1, 00, 00, 00, 000, time.UTC),
 				)
@@ -348,7 +348,7 @@ func (suite *KeeperTestSuite) TestKeeper_DeleteAllUserChainLinks() {
 				link = types.NewChainLink(
 					user,
 					types.NewBech32Address("cosmos1xcy3els9ua75kdm783c3qu0rfa2eplesldfevn", "cosmos"),
-					types.NewProof(key, "signature", "706c61696e74657874"),
+					types.NewProof(key, testutil.SingleSignatureProtoFromHex("1234"), "706c61696e74657874"),
 					types.NewChainConfig("cosmos"),
 					time.Date(2021, 1, 1, 00, 00, 00, 000, time.UTC),
 				)
diff --git a/x/profiles/keeper/msg_server_chain_link_test.go b/x/profiles/keeper/msg_server_chain_link_test.go
index 3b8aaede..763228a6 100644
--- a/x/profiles/keeper/msg_server_chain_link_test.go
+++ b/x/profiles/keeper/msg_server_chain_link_test.go
@@ -51,7 +51,7 @@ func (suite *KeeperTestSuite) TestMsgServer_LinkChainAccount() {
 			name: "invalid chain link returns error",
 			msg: types.NewMsgLinkChainAccount(
 				types.NewBech32Address(srcAddr, "cosmos"),
-				types.NewProof(srcPubKey, srcSigHex, hex.EncodeToString([]byte(srcAddr))),
+				types.NewProof(srcPubKey, testutil.SingleSignatureProtoFromHex(srcSigHex), hex.EncodeToString([]byte(srcAddr))),
 				types.NewChainConfig("cosmos"),
 				destAddr,
 			),
@@ -62,7 +62,7 @@ func (suite *KeeperTestSuite) TestMsgServer_LinkChainAccount() {
 			name: "invalid chain address packed value returns error",
 			msg: &types.MsgLinkChainAccount{
 				ChainAddress: testutil.NewAny(srcPriv),
-				Proof:        types.NewProof(srcPubKey, srcSigHex, hex.EncodeToString([]byte(srcAddr))),
+				Proof:        types.NewProof(srcPubKey, testutil.SingleSignatureProtoFromHex(srcSigHex), hex.EncodeToString([]byte(srcAddr))),
 				ChainConfig:  types.NewChainConfig("cosmos"),
 				Signer:       destAddr,
 			},
@@ -101,7 +101,7 @@ func (suite *KeeperTestSuite) TestMsgServer_LinkChainAccount() {
 			},
 			msg: types.NewMsgLinkChainAccount(
 				types.NewBech32Address(srcAddr, "cosmos"),
-				types.NewProof(srcPubKey, srcSigHex, hex.EncodeToString([]byte(srcAddr))),
+				types.NewProof(srcPubKey, testutil.SingleSignatureProtoFromHex(srcSigHex), hex.EncodeToString([]byte(srcAddr))),
 				types.NewChainConfig("cosmos"),
 				destAddr,
 			),
@@ -180,7 +180,7 @@ func (suite *KeeperTestSuite) TestMsgServer_UnlinkChainAccount() {
 					types.NewBech32Address("cosmos1cjf97gpzwmaf30pzvaargfgr884mpp5ak8f7ns", "cosmos"),
 					types.NewProof(
 						testutil.PubKeyFromBech32("cosmospub1addwnpepqvryxhhqhw52c4ny5twtfzf3fsrjqhx0x5cuya0fylw0wu0eqptykeqhr4d"),
-						"909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b",
+						testutil.SingleSignatureProtoFromHex("909e38994b1583d3f14384c2e9a03c90064e8fd8e19b780bb0ba303dfe671a27287da04d0ce096ce9a140bd070ee36818f5519eb2070a16971efd8143855524b"),
 						"74657874",
 					),
 					types.NewChainConfig("cosmos"),
diff --git a/x/profiles/keeper/relay_chain_links_test.go b/x/profiles/keeper/relay_chain_links_test.go
index acf9f42f..6d284fd4 100644
--- a/x/profiles/keeper/relay_chain_links_test.go
+++ b/x/profiles/keeper/relay_chain_links_test.go
@@ -8,6 +8,7 @@ import (
 	"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
 	authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
 
+	"github.com/desmos-labs/desmos/v2/testutil"
 	"github.com/desmos-labs/desmos/v2/x/profiles/types"
 )
 
@@ -31,14 +32,14 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() {
 					SourceAddress: nil,
 					SourceProof: types.NewProof(
 						suite.chainA.Account.GetPubKey(),
-						srcSigHex,
+						testutil.SingleSignatureProtoFromHex(srcSigHex),
 						hex.EncodeToString([]byte(srcAddr)),
 					),
 					SourceChainConfig:  types.NewChainConfig("cosmos"),
 					DestinationAddress: destAddr,
 					DestinationProof: types.NewProof(
 						suite.chainB.Account.GetPubKey(),
-						destSigHex,
+						testutil.SingleSignatureProtoFromHex(destSigHex),
 						hex.EncodeToString([]byte(destAddr)),
 					),
 				}
@@ -54,14 +55,14 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() {
 					SourceAddress: invalidAny,
 					SourceProof: types.NewProof(
 						suite.chainA.Account.GetPubKey(),
-						srcSigHex,
+						testutil.SingleSignatureProtoFromHex(srcSigHex),
 						hex.EncodeToString([]byte(srcAddr)),
 					),
 					SourceChainConfig:  types.NewChainConfig("cosmos"),
 					DestinationAddress: destAddr,
 					DestinationProof: types.NewProof(
 						suite.chainB.Account.GetPubKey(),
-						destSigHex,
+						testutil.SingleSignatureProtoFromHex(destSigHex),
 						hex.EncodeToString([]byte(destAddr)),
 					),
 				}
@@ -75,14 +76,14 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() {
 					types.NewBech32Address(srcAddr, "cosmos"),
 					types.NewProof(
 						suite.chainA.Account.GetPubKey(),
-						srcSigHex,
+						testutil.SingleSignatureProtoFromHex(srcSigHex),
 						hex.EncodeToString([]byte(srcAddr)),
 					),
 					types.NewChainConfig("cosmos"),
 					"cosmos1asdjlansdjhasd",
 					types.NewProof(
 						suite.chainB.Account.GetPubKey(),
-						destSigHex,
+						testutil.SingleSignatureProtoFromHex(destSigHex),
 						hex.EncodeToString([]byte(destAddr)),
 					),
 				)
@@ -96,14 +97,14 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() {
 					types.NewBech32Address(srcAddr, "cosmos"),
 					types.NewProof(
 						suite.chainA.Account.GetPubKey(),
-						srcSigHex,
+						testutil.SingleSignatureProtoFromHex(srcSigHex),
 						hex.EncodeToString([]byte(srcAddr)),
 					),
 					types.NewChainConfig("cosmos"),
 					destAddr,
 					types.NewProof(
 						suite.chainB.Account.GetPubKey(),
-						destSigHex,
+						testutil.SingleSignatureProtoFromHex(destSigHex),
 						hex.EncodeToString([]byte(destAddr)),
 					),
 				)
@@ -117,14 +118,14 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() {
 					types.NewBech32Address(srcAddr, "cosmos"),
 					types.NewProof(
 						suite.chainA.Account.GetPubKey(),
-						srcSigHex,
+						testutil.SingleSignatureProtoFromHex(srcSigHex),
 						hex.EncodeToString([]byte(srcAddr)),
 					),
 					types.NewChainConfig("cosmos"),
 					destAddr,
 					types.NewProof(
 						suite.chainB.Account.GetPubKey(),
-						destSigHex,
+						testutil.SingleSignatureProtoFromHex(destSigHex),
 						hex.EncodeToString([]byte(destAddr)),
 					),
 				)
@@ -158,7 +159,7 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() {
 					types.NewBech32Address(srcAddr, "cosmos"),
 					types.NewProof(
 						suite.chainA.Account.GetPubKey(),
-						srcSigHex,
+						testutil.SingleSignatureProtoFromHex(srcSigHex),
 						hex.EncodeToString([]byte(srcAddr)),
 					),
 					types.NewChainConfig(
@@ -167,7 +168,7 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() {
 					destAddr,
 					types.NewProof(
 						suite.chainB.Account.GetPubKey(),
-						destSigHex,
+						testutil.SingleSignatureProtoFromHex(destSigHex),
 						"696e76616c6964",
 					),
 				)
@@ -201,7 +202,7 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() {
 					types.NewBech32Address(srcAddr, "cosmos"),
 					types.NewProof(
 						suite.chainA.Account.GetPubKey(),
-						srcSigHex,
+						testutil.SingleSignatureProtoFromHex(srcSigHex),
 						hex.EncodeToString([]byte(srcAddr)),
 					),
 					types.NewChainConfig(
@@ -210,7 +211,7 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() {
 					destAddr,
 					types.NewProof(
 						suite.chainB.Account.GetPubKey(),
-						destSigHex,
+						testutil.SingleSignatureProtoFromHex(destSigHex),
 						hex.EncodeToString([]byte(destAddr)),
 					),
 				)
@@ -241,7 +242,7 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() {
 				link := types.NewChainLink(
 					addr.String(),
 					types.NewBech32Address(srcAddr, "cosmos"),
-					types.NewProof(suite.chainA.Account.GetPubKey(), "signature", hex.EncodeToString([]byte(srcAddr))),
+					types.NewProof(suite.chainA.Account.GetPubKey(), testutil.SingleSignatureProtoFromHex("1234"), hex.EncodeToString([]byte(srcAddr))),
 					types.NewChainConfig("cosmos"),
 					time.Date(2021, 1, 1, 00, 00, 00, 000, time.UTC),
 				)
@@ -256,7 +257,7 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() {
 					types.NewBech32Address(srcAddr, "cosmos"),
 					types.NewProof(
 						suite.chainA.Account.GetPubKey(),
-						srcSigHex,
+						testutil.SingleSignatureProtoFromHex(srcSigHex),
 						hex.EncodeToString([]byte(srcAddr)),
 					),
 					types.NewChainConfig(
@@ -265,7 +266,7 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() {
 					destAddr,
 					types.NewProof(
 						suite.chainB.Account.GetPubKey(),
-						destSigHex,
+						testutil.SingleSignatureProtoFromHex(destSigHex),
 						hex.EncodeToString([]byte(destAddr)),
 					),
 				)
diff --git a/x/profiles/legacy/v210/store_test.go b/x/profiles/legacy/v210/store_test.go
index b7846e16..6a2634e4 100644
--- a/x/profiles/legacy/v210/store_test.go
+++ b/x/profiles/legacy/v210/store_test.go
@@ -11,6 +11,7 @@ import (
 
 	"github.com/desmos-labs/desmos/v2/app"
 	v210 "github.com/desmos-labs/desmos/v2/x/profiles/legacy/v210"
+	v230 "github.com/desmos-labs/desmos/v2/x/profiles/legacy/v230"
 	"github.com/desmos-labs/desmos/v2/x/profiles/types"
 )
 
@@ -67,18 +68,18 @@ func TestStoreMigration(t *testing.T) {
 				"desmos",
 				"desmos13yp2fq3tslq6mmtq4628q38xzj75ethzela9uu",
 			),
-			oldValue: types.MustMarshalChainLink(cdc, types.NewChainLink(
+			oldValue: v230.MustMarshalChainLink(cdc, v230.NewChainLink(
 				"cosmos19xz3mrvzvp9ymgmudhpukucg6668l5haakh04x",
-				types.NewBech32Address("desmos13yp2fq3tslq6mmtq4628q38xzj75ethzela9uu", "desmos"),
-				types.NewProof(&secp256k1.PubKey{Key: []byte{1}}, "signature", "wrong"),
-				types.NewChainConfig("cosmos"),
+				v230.NewBech32Address("desmos13yp2fq3tslq6mmtq4628q38xzj75ethzela9uu", "desmos"),
+				v230.NewProof(&secp256k1.PubKey{Key: []byte{1}}, "signature", "wrong"),
+				v230.NewChainConfig("cosmos"),
 				time.Date(2021, 1, 1, 00, 00, 00, 000, time.UTC),
 			)),
-			newValue: types.MustMarshalChainLink(cdc, types.NewChainLink(
+			newValue: v230.MustMarshalChainLink(cdc, v230.NewChainLink(
 				"cosmos19xz3mrvzvp9ymgmudhpukucg6668l5haakh04x",
-				types.NewBech32Address("desmos13yp2fq3tslq6mmtq4628q38xzj75ethzela9uu", "desmos"),
-				types.NewProof(&secp256k1.PubKey{Key: []byte{1}}, "signature", "77726f6e67"), // Plain text is now in HEX
-				types.NewChainConfig("cosmos"),
+				v230.NewBech32Address("desmos13yp2fq3tslq6mmtq4628q38xzj75ethzela9uu", "desmos"),
+				v230.NewProof(&secp256k1.PubKey{Key: []byte{1}}, "signature", "77726f6e67"), // Plain text is now in HEX
+				v230.NewChainConfig("cosmos"),
 				time.Date(2021, 1, 1, 00, 00, 00, 000, time.UTC),
 			)),
 		},
diff --git a/x/profiles/types/genesis_test.go b/x/profiles/types/genesis_test.go
index 7db64493..09b99bf3 100644
--- a/x/profiles/types/genesis_test.go
+++ b/x/profiles/types/genesis_test.go
@@ -160,7 +160,7 @@ func TestValidateGenesis(t *testing.T) {
 						types.NewBech32Address("cosmos1xmquc944hzu6n6qtljcexkuhhz76mucxtgm5x0", "cosmos"),
 						types.NewProof(
 							testutil.PubKeyFromBech32("cosmospub1addwnpepq0j8zw4t6tg3v8gh7d2d799gjhue7ewwmpg2hwr77f9kuuyzgqtrw5r6wec"),
-							"sig_hex",
+							&types.SingleSignatureData{},
 							"addr",
 						),
 						types.NewChainConfig(""),
@@ -258,7 +258,7 @@ func TestValidateGenesis(t *testing.T) {
 						types.NewBech32Address("cosmos1xmquc944hzu6n6qtljcexkuhhz76mucxtgm5x0", "cosmos"),
 						types.NewProof(
 							testutil.PubKeyFromBech32("cosmospub1addwnpepq0j8zw4t6tg3v8gh7d2d799gjhue7ewwmpg2hwr77f9kuuyzgqtrw5r6wec"),
-							"4e188d9c17150037d5199bbdb91ae1eb2a78a15aca04cb35530cccb81494b36e",
+							testutil.SingleSignatureProtoFromHex("4e188d9c17150037d5199bbdb91ae1eb2a78a15aca04cb35530cccb81494b36e"),
 							"636f736d6f7331786d717563393434687a75366e3671746c6a6365786b7568687a37366d75637874676d357830",
 						),
 						types.NewChainConfig("cosmos"),
@@ -269,7 +269,7 @@ func TestValidateGenesis(t *testing.T) {
 						types.NewBech32Address("cosmos1xmquc944hzu6n6qtljcexkuhhz76mucxtgm5x0", "cosmos"),
 						types.NewProof(
 							testutil.PubKeyFromBech32("cosmospub1addwnpepq0j8zw4t6tg3v8gh7d2d799gjhue7ewwmpg2hwr77f9kuuyzgqtrw5r6wec"),
-							"4e188d9c17150037d5199bbdb91ae1eb2a78a15aca04cb35530cccb81494b36e",
+							testutil.SingleSignatureProtoFromHex("4e188d9c17150037d5199bbdb91ae1eb2a78a15aca04cb35530cccb81494b36e"),
 							"636f736d6f7331786d717563393434687a75366e3671746c6a6365786b7568687a37366d75637874676d357830",
 						),
 						types.NewChainConfig("cosmos"),
diff --git a/x/profiles/types/models_chain_links_test.go b/x/profiles/types/models_chain_links_test.go
index e6971896..db6d7c21 100644
--- a/x/profiles/types/models_chain_links_test.go
+++ b/x/profiles/types/models_chain_links_test.go
@@ -12,9 +12,13 @@ import (
 
 	codectypes "github.com/cosmos/cosmos-sdk/codec/types"
 	"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
+	kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig"
 	"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
+	cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
+	"github.com/cosmos/cosmos-sdk/crypto/types/multisig"
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	"github.com/cosmos/cosmos-sdk/types/bech32"
+	"github.com/cosmos/cosmos-sdk/types/tx/signing"
 
 	"github.com/desmos-labs/desmos/v2/app"
 
@@ -68,6 +72,9 @@ func TestChainConfig_Validate(t *testing.T) {
 // --------------------------------------------------------------------------------------------------------------------
 
 func TestProof_Validate(t *testing.T) {
+	pubKeyAny, err := codectypes.NewAnyWithValue(secp256k1.GenPrivKey().PubKey())
+	require.NoError(t, err)
+
 	testCases := []struct {
 		name      string
 		proof     types.Proof
@@ -75,27 +82,31 @@ func TestProof_Validate(t *testing.T) {
 	}{
 		{
 			name:      "null public key returns error",
-			proof:     types.Proof{Signature: "74657874", PlainText: "74657874"},
+			proof:     types.Proof{Signature: &codectypes.Any{}, PlainText: "74657874"},
 			shouldErr: true,
 		},
 		{
-			name:      "invalid signature format returns error",
-			proof:     types.NewProof(secp256k1.GenPrivKey().PubKey(), "=", "74657874"),
+			name:      "null signature returns error",
+			proof:     types.Proof{PubKey: pubKeyAny, PlainText: "74657874"},
 			shouldErr: true,
 		},
 		{
 			name:      "empty plain text returns error",
-			proof:     types.NewProof(secp256k1.GenPrivKey().PubKey(), "74657874", ""),
+			proof:     types.NewProof(secp256k1.GenPrivKey().PubKey(), testutil.SingleSignatureProtoFromHex("74657874"), ""),
 			shouldErr: true,
 		},
 		{
 			name:      "invalid plain text format returns error",
-			proof:     types.NewProof(secp256k1.GenPrivKey().PubKey(), "74657874", "="),
+			proof:     types.NewProof(secp256k1.GenPrivKey().PubKey(), testutil.SingleSignatureProtoFromHex("74657874"), "="),
 			shouldErr: true,
 		},
 		{
-			name:      "valid proof returns no error",
-			proof:     types.NewProof(secp256k1.GenPrivKey().PubKey(), "74657874", "74657874"),
+			name: "valid proof returns no error",
+			proof: types.NewProof(
+				secp256k1.GenPrivKey().PubKey(),
+				testutil.SingleSignatureProtoFromHex("74657874"),
+				"74657874",
+			),
 			shouldErr: false,
 		},
 	}
@@ -114,6 +125,33 @@ func TestProof_Validate(t *testing.T) {
 	}
 }
 
+// generatePubKeyAndMultiSignatureData generates a new multi sig public key made of the given amount of
+// individual public keys. Then, it uses the generated public key to sign the given message and returns the
+// obtained MultiSignatureData instance.
+func generatePubKeyAndMultiSignatureData(t *testing.T, n int, msg []byte) (cryptotypes.PubKey, types.SignatureData) {
+	pubKeys := make([]cryptotypes.PubKey, n)
+	cosmosMultisig := multisig.NewMultisig(n)
+	for i := 0; i < n; i++ {
+		// Generate the private key
+		privkey := secp256k1.GenPrivKey()
+		pubKeys[i] = privkey.PubKey()
+
+		// Sign the message using the generated private key
+		sig, err := privkey.Sign(msg)
+		require.NoError(t, err)
+
+		// Build the signature data for the single signature and add it to the multi signature data
+		sigData := &signing.SingleSignatureData{Signature: sig}
+		err = multisig.AddSignatureFromPubKey(cosmosMultisig, sigData, pubKeys[i], pubKeys)
+		require.NoError(t, err)
+	}
+
+	sigData, err := types.SignatureDataFromCosmosSignatureData(cosmosMultisig)
+	require.NoError(t, err)
+
+	return kmultisig.NewLegacyAminoPubKey(n, pubKeys), sigData
+}
+
 func TestProof_Verify(t *testing.T) {
 	plainText := "tc"
 
@@ -125,7 +163,12 @@ func TestProof_Verify(t *testing.T) {
 
 	bech32Sig, err := bech32PrivKey.Sign([]byte(plainText))
 	require.NoError(t, err)
-	bech32SigHex := hex.EncodeToString(bech32Sig)
+	bech32SigData := &types.SingleSignatureData{
+		Mode:      signing.SignMode_SIGN_MODE_DIRECT,
+		Signature: bech32Sig,
+	}
+	anySigData, err := codectypes.NewAnyWithValue(bech32SigData)
+	require.NoError(t, err)
 
 	// Base58
 	base58PrivKeyBz, err := hex.DecodeString("bb98111da675930d32f79451fa8d05f188289699558c17148a5d9c82cdb31d1fe04fb0a0d9e689b436b59eff9676d7f2d788244cc4ccfc5768fe117efbd0f9d3")
@@ -136,7 +179,10 @@ func TestProof_Verify(t *testing.T) {
 
 	base58Sig, err := base58PrivKey.Sign([]byte(plainText))
 	require.NoError(t, err)
-	base58SigHex := hex.EncodeToString(base58Sig)
+	base58SigData := &types.SingleSignatureData{
+		Mode:      signing.SignMode_SIGN_MODE_DIRECT,
+		Signature: base58Sig,
+	}
 
 	// Hex
 	hexPrivKeyBz, err := hex.DecodeString("2842d8f3701d16711b9ee320f32efe38e6b0891e243eaf6515250e7b006de53e")
@@ -147,8 +193,20 @@ func TestProof_Verify(t *testing.T) {
 	hexAddr := "0x941991947B6eC9F5537bcaC30C1295E8154Df4cC"
 	hexSig, err := hexPrivKey.Sign([]byte(plainText))
 	require.NoError(t, err)
-	hexSigHex := hex.EncodeToString(hexSig)
+	hexSigData := &types.SingleSignatureData{
+		Mode:      signing.SignMode_SIGN_MODE_DIRECT,
+		Signature: hexSig,
+	}
 
+	// Multisig
+	multisigPubKey, multisigData := generatePubKeyAndMultiSignatureData(t, 3, []byte(plainText))
+	multisigAddr, err := sdk.Bech32ifyAddressBytes("cosmos", multisigPubKey.Address())
+	require.NoError(t, err)
+	validMultisigDataAny, err := codectypes.NewAnyWithValue(multisigData)
+	require.NoError(t, err)
+
+	validaPubKeyAny, err := codectypes.NewAnyWithValue(bech32PubKey)
+	require.NoError(t, err)
 	invalidAny, err := codectypes.NewAnyWithValue(bech32PrivKey)
 	require.NoError(t, err)
 
@@ -159,59 +217,89 @@ func TestProof_Verify(t *testing.T) {
 		shouldErr   bool
 	}{
 		{
-			name:        "Invalid public key value returns error",
-			proof:       types.Proof{PubKey: invalidAny, Signature: bech32SigHex, PlainText: hex.EncodeToString([]byte(plainText))},
+			name:        "invalid public key value returns error",
+			proof:       types.Proof{PubKey: invalidAny, Signature: anySigData, PlainText: hex.EncodeToString([]byte(plainText))},
+			addressData: types.NewBech32Address(bech32Addr, "cosmos"),
+			shouldErr:   true,
+		},
+		{
+			name:        "invalid signature value returns error",
+			proof:       types.Proof{PubKey: validaPubKeyAny, Signature: invalidAny, PlainText: hex.EncodeToString([]byte(plainText))},
 			addressData: types.NewBech32Address(bech32Addr, "cosmos"),
 			shouldErr:   true,
 		},
 		{
 			name:        "wrong plain text returns error",
-			proof:       types.NewProof(bech32PubKey, bech32SigHex, "wrong"),
+			proof:       types.NewProof(bech32PubKey, bech32SigData, "wrong"),
 			addressData: types.NewBech32Address(bech32Addr, "cosmos"),
 			shouldErr:   true,
 		},
 		{
 			name:        "wrong signature returns error",
-			proof:       types.NewProof(bech32PubKey, "74657874", hex.EncodeToString([]byte(plainText))),
+			proof:       types.NewProof(bech32PubKey, testutil.SingleSignatureProtoFromHex("74657874"), hex.EncodeToString([]byte(plainText))),
 			addressData: types.NewBech32Address(bech32Addr, "cosmos"),
 			shouldErr:   true,
 		},
 		{
 			name:        "wrong Bech32 address returns error",
-			proof:       types.NewProof(bech32PubKey, bech32SigHex, hex.EncodeToString([]byte(plainText))),
+			proof:       types.NewProof(bech32PubKey, bech32SigData, hex.EncodeToString([]byte(plainText))),
 			addressData: types.NewBech32Address("cosmos1xcy3els9ua75kdm783c3qu0rfa2eplesldfevn", "cosmos"),
 			shouldErr:   true,
 		},
 		{
 			name:        "wrong Base58 address returns error",
-			proof:       types.NewProof(base58PubKey, base58SigHex, hex.EncodeToString([]byte(plainText))),
+			proof:       types.NewProof(base58PubKey, base58SigData, hex.EncodeToString([]byte(plainText))),
 			addressData: types.NewBase58Address("HWQ14mk82aRMAad2TdxFHbeqLeUGo5SiBxTXyZyTesJT"),
 			shouldErr:   true,
 		},
 		{
 			name:        "wrong Hex address returns error",
-			proof:       types.NewProof(hexPubKey, hexSigHex, hex.EncodeToString([]byte(plainText))),
+			proof:       types.NewProof(hexPubKey, hexSigData, hex.EncodeToString([]byte(plainText))),
 			addressData: types.NewHexAddress("0xcdAFfbFd8c131464fEE561e3d9b585141e403719", "0x"),
 			shouldErr:   true,
 		},
+		{
+			name:        "invalid Multisig pubkey returns error",
+			proof:       types.Proof{PubKey: invalidAny, Signature: validMultisigDataAny, PlainText: hex.EncodeToString([]byte(plainText))},
+			addressData: types.NewBech32Address("cosmos1xcy3els9ua75kdm783c3qu0rfa2eplesldfevn", "cosmos"),
+			shouldErr:   true,
+		},
+		{
+			name:        "wrong Multisig address returns error",
+			proof:       types.NewProof(multisigPubKey, multisigData, hex.EncodeToString([]byte(plainText))),
+			addressData: types.NewBech32Address("cosmos1xcy3els9ua75kdm783c3qu0rfa2eplesldfevn", "cosmos"),
+			shouldErr:   true,
+		},
+		{
+			name:        "wrong Multisig pubkey returns error",
+			proof:       types.NewProof(bech32PubKey, multisigData, hex.EncodeToString([]byte(plainText))),
+			addressData: types.NewBech32Address("cosmos1xcy3els9ua75kdm783c3qu0rfa2eplesldfevn", "cosmos"),
+			shouldErr:   true,
+		},
 		{
 			name:        "correct proof with Base58 address returns no error",
-			proof:       types.NewProof(base58PubKey, base58SigHex, hex.EncodeToString([]byte(plainText))),
+			proof:       types.NewProof(base58PubKey, base58SigData, hex.EncodeToString([]byte(plainText))),
 			addressData: types.NewBase58Address(base58Addr),
 			shouldErr:   false,
 		},
 		{
 			name:        "correct proof with Bech32 address returns no error",
-			proof:       types.NewProof(bech32PubKey, bech32SigHex, hex.EncodeToString([]byte(plainText))),
+			proof:       types.NewProof(bech32PubKey, bech32SigData, hex.EncodeToString([]byte(plainText))),
 			addressData: types.NewBech32Address(bech32Addr, "cosmos"),
 			shouldErr:   false,
 		},
 		{
 			name:        "correct proof with Hex address returns no error",
-			proof:       types.NewProof(hexPubKey, hexSigHex, hex.EncodeToString([]byte(plainText))),
+			proof:       types.NewProof(hexPubKey, hexSigData, hex.EncodeToString([]byte(plainText))),
 			addressData: types.NewHexAddress(hexAddr, "0x"),
 			shouldErr:   false,
 		},
+		{
+			name:        "correct proof with multisig address returns no error",
+			proof:       types.NewProof(multisigPubKey, multisigData, hex.EncodeToString([]byte(plainText))),
+			addressData: types.NewBech32Address(multisigAddr, "cosmos"),
+			shouldErr:   false,
+		},
 	}
 
 	for _, tc := range testCases {
@@ -439,7 +527,7 @@ func TestChainLink_Validate(t *testing.T) {
 			name: "empty address returns error",
 			chainLink: types.ChainLink{
 				User:         "cosmos10clxpupsmddtj7wu7g0wdysajqwp890mva046f",
-				Proof:        types.NewProof(secp256k1.GenPrivKey().PubKey(), "=", "74657874"),
+				Proof:        types.NewProof(secp256k1.GenPrivKey().PubKey(), testutil.SingleSignatureProtoFromHex("74657874"), "74657874"),
 				ChainConfig:  types.NewChainConfig("cosmos"),
 				CreationTime: time.Now(),
 			},
@@ -450,7 +538,7 @@ func TestChainLink_Validate(t *testing.T) {
 			chainLink: types.NewChainLink(
 				"",
 				types.NewBech32Address("cosmos1cjf97gpzwmaf30pzvaargfgr884mpp5ak8f7ns", "cosmos"),
-				types.NewProof(secp256k1.GenPrivKey().PubKey(), "=", "74657874"),
+				types.NewProof(secp256k1.GenPrivKey().PubKey(), testutil.SingleSignatureProtoFromHex("74657874"), "74657874"),
 				types.NewChainConfig("cosmos"),
 				time.Now(),
 			),
@@ -461,7 +549,7 @@ func TestChainLink_Validate(t *testing.T) {
 			chainLink: types.NewChainLink(
 				"cosmos10clxpupsmddtj7wu7g0wdysajqwp890mva046f",
 				types.NewBech32Address("cosmos1cjf97gpzwmaf30pzvaargfgr884mpp5ak8f7ns", "cosmos"),
-				types.NewProof(secp256k1.GenPrivKey().PubKey(), "=", "74657874"),
+				types.NewProof(secp256k1.GenPrivKey().PubKey(), &types.SingleSignatureData{}, "="),
 				types.NewChainConfig("cosmos"),
 				time.Now(),
 			),
@@ -472,7 +560,7 @@ func TestChainLink_Validate(t *testing.T) {
 			chainLink: types.NewChainLink(
 				"cosmos10clxpupsmddtj7wu7g0wdysajqwp890mva046f",
 				types.NewBech32Address("cosmos1cjf97gpzwmaf30pzvaargfgr884mpp5ak8f7ns", "cosmos"),
-				types.NewProof(secp256k1.GenPrivKey().PubKey(), "74657874", "74657874"),
+				types.NewProof(secp256k1.GenPrivKey().PubKey(), testutil.SingleSignatureProtoFromHex("74657874"), "74657874"),
 				types.NewChainConfig(""),
 				time.Now(),
 			),
@@ -483,7 +571,7 @@ func TestChainLink_Validate(t *testing.T) {
 			chainLink: types.NewChainLink(
 				"cosmos10clxpupsmddtj7wu7g0wdysajqwp890mva046f",
 				types.NewBech32Address("cosmos1cjf97gpzwmaf30pzvaargfgr884mpp5ak8f7ns", "cosmos"),
-				types.NewProof(secp256k1.GenPrivKey().PubKey(), "74657874", "74657874"),
+				types.NewProof(secp256k1.GenPrivKey().PubKey(), testutil.SingleSignatureProtoFromHex("74657874"), "74657874"),
 				types.NewChainConfig("cosmos"),
 				time.Time{},
 			),
@@ -494,7 +582,7 @@ func TestChainLink_Validate(t *testing.T) {
 			chainLink: types.NewChainLink(
 				"cosmos10clxpupsmddtj7wu7g0wdysajqwp890mva046f",
 				types.NewBech32Address("cosmos1cjf97gpzwmaf30pzvaargfgr884mpp5ak8f7ns", "cosmos"),
-				types.NewProof(secp256k1.GenPrivKey().PubKey(), "74657874", "74657874"),
+				types.NewProof(secp256k1.GenPrivKey().PubKey(), testutil.SingleSignatureProtoFromHex("74657874"), "74657874"),
 				types.NewChainConfig("cosmos"),
 				time.Now(),
 			),
@@ -526,7 +614,7 @@ func TestChainLinkMarshaling(t *testing.T) {
 	chainLink := types.NewChainLink(
 		"cosmos10clxpupsmddtj7wu7g0wdysajqwp890mva046f",
 		types.NewBech32Address(addr, "cosmos"),
-		types.NewProof(pubKey, "sig-hex", "plain-text"),
+		types.NewProof(pubKey, testutil.SingleSignatureProtoFromHex("74657874"), "plain-text"),
 		types.NewChainConfig("cosmos"),
 		time.Date(2020, 1, 1, 00, 00, 00, 000, time.UTC),
 	)
diff --git a/x/profiles/types/models_packets_test.go b/x/profiles/types/models_packets_test.go
index 629fb43e..183e96a1 100644
--- a/x/profiles/types/models_packets_test.go
+++ b/x/profiles/types/models_packets_test.go
@@ -6,6 +6,7 @@ import (
 	"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
 	"github.com/stretchr/testify/require"
 
+	"github.com/desmos-labs/desmos/v2/testutil"
 	"github.com/desmos-labs/desmos/v2/x/profiles/types"
 )
 
@@ -20,14 +21,14 @@ func TestLinkChainAccountPacketData_Validate(t *testing.T) {
 			packet: types.LinkChainAccountPacketData{
 				SourceProof: types.NewProof(
 					secp256k1.GenPrivKey().PubKey(),
-					"032086ede8d4bce29fe364a94744ca71dbeaf370221ba20f9716a165c54b079561",
+					testutil.SingleSignatureProtoFromHex("032086ede8d4bce29fe364a94744ca71dbeaf370221ba20f9716a165c54b079561"),
 					"706c61696e5f74657874",
 				),
 				SourceChainConfig:  types.NewChainConfig("cosmos"),
 				DestinationAddress: "cosmos1yt7rqhj0hjw92ed0948r2pqwtp9smukurqcs70",
 				DestinationProof: types.NewProof(
 					secp256k1.GenPrivKey().PubKey(),
-					"032086ede8d4bce29fe364a94744ca71dbeaf370221ba20f9716a165c54b079561",
+					testutil.SingleSignatureProtoFromHex("032086ede8d4bce29fe364a94744ca71dbeaf370221ba20f9716a165c54b079561"),
 					"706c61696e5f74657874",
 				),
 			},
@@ -37,12 +38,12 @@ func TestLinkChainAccountPacketData_Validate(t *testing.T) {
 			name: "invalid source proof returns error",
 			packet: types.NewLinkChainAccountPacketData(
 				types.NewBech32Address("cosmos1yt7rqhj0hjw92ed0948r2pqwtp9smukurqcs70", "cosmos"),
-				types.NewProof(secp256k1.GenPrivKey().PubKey(), "=", "wrong"),
+				types.NewProof(secp256k1.GenPrivKey().PubKey(), &types.SingleSignatureData{}, "wrong"),
 				types.NewChainConfig("cosmos"),
 				"cosmos1yt7rqhj0hjw92ed0948r2pqwtp9smukurqcs70",
 				types.NewProof(
 					secp256k1.GenPrivKey().PubKey(),
-					"032086ede8d4bce29fe364a94744ca71dbeaf370221ba20f9716a165c54b079561",
+					testutil.SingleSignatureProtoFromHex("032086ede8d4bce29fe364a94744ca71dbeaf370221ba20f9716a165c54b079561"),
 					"706c61696e5f74657874",
 				),
 			),
@@ -54,14 +55,14 @@ func TestLinkChainAccountPacketData_Validate(t *testing.T) {
 				types.NewBech32Address("cosmos1yt7rqhj0hjw92ed0948r2pqwtp9smukurqcs70", "cosmos"),
 				types.NewProof(
 					secp256k1.GenPrivKey().PubKey(),
-					"032086ede8d4bce29fe364a94744ca71dbeaf370221ba20f9716a165c54b079561",
+					testutil.SingleSignatureProtoFromHex("032086ede8d4bce29fe364a94744ca71dbeaf370221ba20f9716a165c54b079561"),
 					"706c61696e5f74657874",
 				),
 				types.NewChainConfig(""),
 				"cosmos1yt7rqhj0hjw92ed0948r2pqwtp9smukurqcs70",
 				types.NewProof(
 					secp256k1.GenPrivKey().PubKey(),
-					"032086ede8d4bce29fe364a94744ca71dbeaf370221ba20f9716a165c54b079561",
+					testutil.SingleSignatureProtoFromHex("032086ede8d4bce29fe364a94744ca71dbeaf370221ba20f9716a165c54b079561"),
 					"706c61696e5f74657874",
 				),
 			),
@@ -73,14 +74,14 @@ func TestLinkChainAccountPacketData_Validate(t *testing.T) {
 				types.NewBech32Address("cosmos1yt7rqhj0hjw92ed0948r2pqwtp9smukurqcs70", "cosmos"),
 				types.NewProof(
 					secp256k1.GenPrivKey().PubKey(),
-					"032086ede8d4bce29fe364a94744ca71dbeaf370221ba20f9716a165c54b079561",
+					testutil.SingleSignatureProtoFromHex("032086ede8d4bce29fe364a94744ca71dbeaf370221ba20f9716a165c54b079561"),
 					"706c61696e5f74657874",
 				),
 				types.NewChainConfig("cosmos"),
 				"",
 				types.NewProof(
 					secp256k1.GenPrivKey().PubKey(),
-					"032086ede8d4bce29fe364a94744ca71dbeaf370221ba20f9716a165c54b079561",
+					testutil.SingleSignatureProtoFromHex("032086ede8d4bce29fe364a94744ca71dbeaf370221ba20f9716a165c54b079561"),
 					"706c61696e5f74657874",
 				),
 			),
@@ -92,12 +93,12 @@ func TestLinkChainAccountPacketData_Validate(t *testing.T) {
 				types.NewBech32Address("cosmos1yt7rqhj0hjw92ed0948r2pqwtp9smukurqcs70", "cosmos"),
 				types.NewProof(
 					secp256k1.GenPrivKey().PubKey(),
-					"032086ede8d4bce29fe364a94744ca71dbeaf370221ba20f9716a165c54b079561",
+					testutil.SingleSignatureProtoFromHex("032086ede8d4bce29fe364a94744ca71dbeaf370221ba20f9716a165c54b079561"),
 					"706c61696e5f74657874",
 				),
 				types.NewChainConfig("cosmos"),
 				"cosmos1yt7rqhj0hjw92ed0948r2pqwtp9smukurqcs70",
-				types.NewProof(secp256k1.GenPrivKey().PubKey(), "=", "wrong"),
+				types.NewProof(secp256k1.GenPrivKey().PubKey(), &types.SingleSignatureData{}, "wrong"),
 			),
 			shouldErr: true,
 		},
@@ -107,14 +108,14 @@ func TestLinkChainAccountPacketData_Validate(t *testing.T) {
 				types.NewBech32Address("cosmos1yt7rqhj0hjw92ed0948r2pqwtp9smukurqcs70", "cosmos"),
 				types.NewProof(
 					secp256k1.GenPrivKey().PubKey(),
-					"032086ede8d4bce29fe364a94744ca71dbeaf370221ba20f9716a165c54b079561",
+					testutil.SingleSignatureProtoFromHex("032086ede8d4bce29fe364a94744ca71dbeaf370221ba20f9716a165c54b079561"),
 					"706c61696e5f74657874",
 				),
 				types.NewChainConfig("cosmos"),
 				"cosmos1yt7rqhj0hjw92ed0948r2pqwtp9smukurqcs70",
 				types.NewProof(
 					secp256k1.GenPrivKey().PubKey(),
-					"032086ede8d4bce29fe364a94744ca71dbeaf370221ba20f9716a165c54b079561",
+					testutil.SingleSignatureProtoFromHex("032086ede8d4bce29fe364a94744ca71dbeaf370221ba20f9716a165c54b079561"),
 					"706c61696e5f74657874",
 				),
 			),
@@ -139,10 +140,10 @@ func TestLinkChainAccountPacketData_Validate(t *testing.T) {
 func TestLinkChainAccountPacketData_GetBytes(t *testing.T) {
 	packetData := types.NewLinkChainAccountPacketData(
 		types.NewBech32Address("cosmos1yt7rqhj0hjw92ed0948r2pqwtp9smukurqcs70", "cosmos"),
-		types.NewProof(secp256k1.GenPrivKey().PubKey(), "032086ede8d4bce29fe364a94744ca71dbeaf370221ba20f9716a165c54b079561", "plain_text"),
+		types.NewProof(secp256k1.GenPrivKey().PubKey(), testutil.SingleSignatureProtoFromHex("032086ede8d4bce29fe364a94744ca71dbeaf370221ba20f9716a165c54b079561"), "plain_text"),
 		types.NewChainConfig("cosmos"),
 		"cosmos1yt7rqhj0hjw92ed0948r2pqwtp9smukurqcs70",
-		types.NewProof(secp256k1.GenPrivKey().PubKey(), "032086ede8d4bce29fe364a94744ca71dbeaf370221ba20f9716a165c54b079561", "plain_text"),
+		types.NewProof(secp256k1.GenPrivKey().PubKey(), testutil.SingleSignatureProtoFromHex("032086ede8d4bce29fe364a94744ca71dbeaf370221ba20f9716a165c54b079561"), "plain_text"),
 	)
 	_, err := packetData.GetBytes()
 	require.NoError(t, err)
diff --git a/x/profiles/types/msgs_chain_links_test.go b/x/profiles/types/msgs_chain_links_test.go
index 8becae5c..cb3e965c 100644
--- a/x/profiles/types/msgs_chain_links_test.go
+++ b/x/profiles/types/msgs_chain_links_test.go
@@ -17,7 +17,7 @@ var msgChainLinkAccount = types.NewMsgLinkChainAccount(
 	types.NewBech32Address("cosmos1xmquc944hzu6n6qtljcexkuhhz76mucxtgm5x0", "cosmos"),
 	types.NewProof(
 		testutil.PubKeyFromBech32("cosmospub1addwnpepq0j8zw4t6tg3v8gh7d2d799gjhue7ewwmpg2hwr77f9kuuyzgqtrw5r6wec"),
-		"ad112abb30e5240c7b9d21b4cc5421d76cfadfcd5977cca262523b5f5bc759457d4aa6d5c1eb6223db104b47aa1f222468be8eb5bb2762b971622ac5b96351b5",
+		testutil.SingleSignatureProtoFromHex("ad112abb30e5240c7b9d21b4cc5421d76cfadfcd5977cca262523b5f5bc759457d4aa6d5c1eb6223db104b47aa1f222468be8eb5bb2762b971622ac5b96351b5"),
 		"74657874",
 	),
 	types.NewChainConfig("cosmos"),
@@ -51,7 +51,7 @@ func TestMsgLinkChainAccount_ValidateBasic(t *testing.T) {
 			name: "invalid proof returns error",
 			msg: types.NewMsgLinkChainAccount(
 				types.NewBech32Address("cosmos1xmquc944hzu6n6qtljcexkuhhz76mucxtgm5x0", "cosmos"),
-				types.NewProof(secp256k1.GenPrivKey().PubKey(), "=", "wrong"),
+				types.NewProof(secp256k1.GenPrivKey().PubKey(), &types.SingleSignatureData{}, "wrong"),
 				msgChainLinkAccount.ChainConfig,
 				msgChainLinkAccount.Signer,
 			),
@@ -99,7 +99,7 @@ func TestMsgLinkChainAccount_ValidateBasic(t *testing.T) {
 }
 
 func TestMsgLinkChainAccount_GetSignBytes(t *testing.T) {
-	expected := `{"type":"desmos/MsgLinkChainAccount","value":{"chain_address":{"type":"desmos/Bech32Address","value":{"prefix":"cosmos","value":"cosmos1xmquc944hzu6n6qtljcexkuhhz76mucxtgm5x0"}},"chain_config":{"name":"cosmos"},"proof":{"plain_text":"74657874","pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A+RxOqvS0RYdF/NU3xSolfmfZc7YUKu4fvJLbnCCQBY3"},"signature":"ad112abb30e5240c7b9d21b4cc5421d76cfadfcd5977cca262523b5f5bc759457d4aa6d5c1eb6223db104b47aa1f222468be8eb5bb2762b971622ac5b96351b5"},"signer":"cosmos1u9hgsqfpe3snftr7p7fsyja3wtlmj2sgf2w9yl"}}`
+	expected := `{"type":"desmos/MsgLinkChainAccount","value":{"chain_address":{"type":"desmos/Bech32Address","value":{"prefix":"cosmos","value":"cosmos1xmquc944hzu6n6qtljcexkuhhz76mucxtgm5x0"}},"chain_config":{"name":"cosmos"},"proof":{"plain_text":"74657874","pub_key":{"type":"tendermint/PubKeySecp256k1","value":"A+RxOqvS0RYdF/NU3xSolfmfZc7YUKu4fvJLbnCCQBY3"},"signature":{"type":"desmos/SingleSignatureData","value":{"mode":1,"signature":"rREquzDlJAx7nSG0zFQh12z6381Zd8yiYlI7X1vHWUV9SqbVwetiI9sQS0eqHyIkaL6OtbsnYrlxYirFuWNRtQ=="}}},"signer":"cosmos1u9hgsqfpe3snftr7p7fsyja3wtlmj2sgf2w9yl"}}`
 	require.Equal(t, expected, string(msgChainLinkAccount.GetSignBytes()))
 }