Repository: cosmos/relayer
Pull request #922 (2022-08-17 09:22:27)

Keystore implementation for substrate chain
This PR implements a keystore for the substrate chain and the keystore provider. It also stubs out the provider methods which would be implemented in subsequent PRs. Currently, they all implement panic statements. 


Patch:
diff --git a/go.mod b/go.mod
index e250f30..f3639e7 100644
--- a/go.mod
+++ b/go.mod
@@ -3,14 +3,18 @@ module github.com/cosmos/relayer/v2
 go 1.18
 
 require (
+	github.com/99designs/keyring v1.2.1
+	github.com/ComposableFi/go-substrate-rpc-client/v4 v4.0.1-0.20220820010439-71c9d526f2f5
 	github.com/avast/retry-go/v4 v4.1.0
 	github.com/cosmos/cosmos-sdk v0.46.0
+	github.com/cosmos/go-bip39 v1.0.0
 	github.com/cosmos/ibc-go/v5 v5.0.0-beta1
 	github.com/gogo/protobuf v1.3.3
 	github.com/google/go-cmp v0.5.8
 	github.com/google/go-github/v43 v43.0.0
 	github.com/jsternberg/zap-logfmt v1.2.0
 	github.com/ory/dockertest/v3 v3.9.1
+	github.com/pkg/errors v0.9.1
 	github.com/prometheus/client_golang v1.12.2
 	github.com/spf13/cobra v1.5.0
 	github.com/spf13/viper v1.12.0
@@ -19,6 +23,7 @@ require (
 	github.com/tendermint/tendermint v0.34.20
 	go.uber.org/multierr v1.8.0
 	go.uber.org/zap v1.21.0
+	golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8
 	golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
 	golang.org/x/term v0.0.0-20220526004731-065cf7ba2467
 	gopkg.in/yaml.v3 v3.0.1
@@ -33,9 +38,9 @@ require (
 	cosmossdk.io/math v1.0.0-beta.3 // indirect
 	filippo.io/edwards25519 v1.0.0-rc.1 // indirect
 	github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
-	github.com/99designs/keyring v1.2.1 // indirect
 	github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
-	github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect
+	github.com/ChainSafe/go-schnorrkel v1.0.0 // indirect
+	github.com/ComposableFi/go-subkey/v2 v2.0.0-tm03420 // indirect
 	github.com/Microsoft/go-winio v0.5.2 // indirect
 	github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
 	github.com/Workiva/go-datastructures v1.0.53 // indirect
@@ -57,13 +62,14 @@ require (
 	github.com/containerd/continuity v0.3.0 // indirect
 	github.com/cosmos/btcutil v1.0.4 // indirect
 	github.com/cosmos/cosmos-proto v1.0.0-alpha7 // indirect
-	github.com/cosmos/go-bip39 v1.0.0 // indirect
 	github.com/cosmos/gorocksdb v1.2.0 // indirect
 	github.com/cosmos/iavl v0.19.0 // indirect
 	github.com/cosmos/ledger-cosmos-go v0.11.1 // indirect
 	github.com/cosmos/ledger-go v0.9.2 // indirect
 	github.com/danieljoos/wincred v1.1.2 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/decred/base58 v1.0.4 // indirect
+	github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
 	github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
 	github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
 	github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
@@ -75,7 +81,7 @@ require (
 	github.com/docker/go-units v0.4.0 // indirect
 	github.com/dustin/go-humanize v1.0.0 // indirect
 	github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
-	github.com/ethereum/go-ethereum v1.10.19 // indirect
+	github.com/ethereum/go-ethereum v1.10.20 // indirect
 	github.com/evmos/ethermint v0.6.1-0.20220810122651-42abb259cbed // indirect
 	github.com/felixge/httpsnoop v1.0.1 // indirect
 	github.com/fsnotify/fsnotify v1.5.4 // indirect
@@ -121,7 +127,7 @@ require (
 	github.com/mattn/go-colorable v0.1.12 // indirect
 	github.com/mattn/go-isatty v0.0.14 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
-	github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 // indirect
+	github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b // indirect
 	github.com/minio/highwayhash v1.0.2 // indirect
 	github.com/mitchellh/go-homedir v1.1.0 // indirect
 	github.com/mitchellh/go-testing-interface v1.0.0 // indirect
@@ -134,7 +140,6 @@ require (
 	github.com/pelletier/go-toml v1.9.5 // indirect
 	github.com/pelletier/go-toml/v2 v2.0.2 // indirect
 	github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
-	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/prometheus/client_model v0.2.0 // indirect
 	github.com/prometheus/common v0.34.0 // indirect
@@ -151,7 +156,7 @@ require (
 	github.com/spf13/jwalterweatherman v1.1.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/subosito/gotenv v1.4.0 // indirect
-	github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
+	github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect
 	github.com/tendermint/btcd v0.1.1 // indirect
 	github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 // indirect
 	github.com/tendermint/go-amino v0.16.0 // indirect
@@ -165,10 +170,9 @@ require (
 	go.etcd.io/bbolt v1.3.6 // indirect
 	go.opencensus.io v0.23.0 // indirect
 	go.uber.org/atomic v1.9.0 // indirect
-	golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
 	golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 // indirect
 	golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
-	golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect
+	golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 // indirect
 	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect
 	google.golang.org/api v0.81.0 // indirect
@@ -183,3 +187,5 @@ require (
 )
 
 replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1
+
+replace github.com/ChainSafe/go-schnorrkel v1.0.0 => github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d
diff --git a/go.sum b/go.sum
index 2117f7a..a069c32 100644
--- a/go.sum
+++ b/go.sum
@@ -99,6 +99,10 @@ github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg=
 github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4=
+github.com/ComposableFi/go-subkey/v2 v2.0.0-tm03420 h1:oknQF/iIhf5lVjbwjsVDzDByupRhga8nhA3NAmwyHDA=
+github.com/ComposableFi/go-subkey/v2 v2.0.0-tm03420/go.mod h1:KYkiMX5AbOlXXYfxkrYPrRPV6EbVUALTQh5ptUOJzu8=
+github.com/ComposableFi/go-substrate-rpc-client/v4 v4.0.1-0.20220820010439-71c9d526f2f5 h1:OXSENjIo4rEYYn8GapFPEc3dpP8SMayxyHSOwOiXHg4=
+github.com/ComposableFi/go-substrate-rpc-client/v4 v4.0.1-0.20220820010439-71c9d526f2f5/go.mod h1:swRgltPLuxQuM1AnrlRRXkbtkcHN0BjlMvGC0iEkNjk=
 github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
 github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
 github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
@@ -340,6 +344,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo=
+github.com/decred/base58 v1.0.4 h1:QJC6B0E0rXOPA8U/kw2rP+qiRJsUaE2Er+pYb3siUeA=
+github.com/decred/base58 v1.0.4/go.mod h1:jJswKPEdvpFpvf7dsDvFZyLT22xZ9lWqEByX38oGd9E=
 github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
 github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
@@ -406,8 +412,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
 github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
 github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0=
 github.com/ethereum/go-ethereum v1.10.17/go.mod h1:Lt5WzjM07XlXc95YzrhosmR4J9Ahd6X2wyEV2SvGhk0=
-github.com/ethereum/go-ethereum v1.10.19 h1:EOR5JbL4MD5yeOqv8W2iC1s4NximrTjqFccUz8lyBRA=
-github.com/ethereum/go-ethereum v1.10.19/go.mod h1:IJBNMtzKcNHPtllYihy6BL2IgK1u+32JriaTbdt4v+w=
+github.com/ethereum/go-ethereum v1.10.20 h1:75IW830ClSS40yrQC1ZCMZCt5I+zU16oqId2SiQwdQ4=
+github.com/ethereum/go-ethereum v1.10.20/go.mod h1:LWUN82TCHGpxB3En5HVmLLzPD7YSrEUFmFfN1nKkVN0=
 github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY=
 github.com/evmos/ethermint v0.6.1-0.20220810122651-42abb259cbed h1:XgebosrOkNn6UnsR8C7CECu+X/FT3fZ5H5TVghjTSro=
 github.com/evmos/ethermint v0.6.1-0.20220810122651-42abb259cbed/go.mod h1:3UVQJyCBB9tW3zvtmqGtVTfSRgNBFP29izugIxyizQE=
@@ -492,8 +498,8 @@ github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
 github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
-github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
 github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ=
@@ -968,8 +974,9 @@ github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0
 github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
 github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
 github.com/miguelmota/go-ethereum-hdwallet v0.1.1 h1:zdXGlHao7idpCBjEGTXThVAtMKs+IxAgivZ75xqkWK0=
-github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 h1:hLDRPB66XQT/8+wG9WsDpiCvZf1yKO7sz7scAjSlBa0=
 github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM=
+github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b h1:QrHweqAtyJ9EwCaGHBu1fghwxIPiopAHV06JlXrMHjk=
+github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b/go.mod h1:xxLb2ip6sSUts3g1irPVHyk/DGslwQsNOo9I7smJfNU=
 github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
 github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
 github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
@@ -1060,8 +1067,9 @@ github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+
 github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
 github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
 github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
-github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
 github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
+github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
+github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
 github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
 github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
 github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
@@ -1071,6 +1079,7 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
 github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
 github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
 github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
+github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
 github.com/onsi/gomega v1.20.0 h1:8W0cWlwFkflGPLltQvLRB7ZVD5HuP6ng320w2IS245Q=
 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
@@ -1130,6 +1139,7 @@ github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG
 github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
 github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
+github.com/pierrec/xxHash v0.1.5 h1:n/jBpwTHiER4xYvK3/CdPVnLDPchj8eTJFFLUb4QHBo=
 github.com/pierrre/gotestcover v0.0.0-20160517101806-924dca7d15f0/go.mod h1:4xpMLz7RBWyB+ElzHu8Llua96TRCB3YwX+l5EP1wmHk=
 github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
 github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
@@ -1339,8 +1349,9 @@ github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7
 github.com/sylvia7788/contextcheck v1.0.4/go.mod h1:vuPKJMQ7MQ91ZTqfdyreNKwZjyUg6KO+IebVyQDedZQ=
 github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
 github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM=
-github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
+github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI=
+github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
 github.com/tdakkota/asciicheck v0.1.1/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM=
 github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8=
 github.com/tendermint/btcd v0.1.1 h1:0VcxPfflS2zZ3RiOAHkBiFUcPvbtRj5O7zHmcJWHV7s=
@@ -1530,8 +1541,8 @@ golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5y
 golang.org/x/crypto v0.0.0-20220313003712-b769efc7c000/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
-golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 h1:GIAS/yBem/gq2MUqgNIzUHW7cJMmx3TGZOrnyYaNQ6c=
+golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1646,6 +1657,7 @@ golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su
 golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 h1:Yqz/iviulwKwAREEeUd3nbBFn0XuyJqkoft2IlrvOhc=
 golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -1810,8 +1822,9 @@ golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU=
 golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 h1:Sx/u41w+OwrInGdEckYmEuU5gHoGSL4QbDz3S9s6j4U=
+golang.org/x/sys v0.0.0-20220818161305-2296e01440c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
diff --git a/relayer/chains/substrate/key.go b/relayer/chains/substrate/key.go
new file mode 100644
index 0000000..b9db231
--- /dev/null
+++ b/relayer/chains/substrate/key.go
@@ -0,0 +1,125 @@
+package substrate
+
+import (
+	"os"
+
+	"github.com/cosmos/go-bip39"
+	"github.com/cosmos/relayer/v2/relayer/chains/substrate/keystore"
+	"github.com/cosmos/relayer/v2/relayer/provider"
+)
+
+func (sp *SubstrateProvider) CreateKeystore(path string) error {
+	keybase, err := keystore.New(sp.PCfg.ChainID, sp.PCfg.KeyringBackend, sp.PCfg.KeyDirectory, sp.Input)
+	if err != nil {
+		return err
+	}
+	sp.Keybase = keybase
+	return nil
+}
+
+func (sp *SubstrateProvider) KeystoreCreated(path string) bool {
+	if _, err := os.Stat(path); err != nil || sp.Keybase == nil {
+		return false
+	}
+	return true
+}
+
+func (sp *SubstrateProvider) AddKey(name string, coinType uint32) (output *provider.KeyOutput, err error) {
+	ko, err := sp.KeyAddOrRestore(name, coinType)
+	if err != nil {
+		return nil, err
+	}
+
+	return ko, nil
+}
+
+func (sp *SubstrateProvider) RestoreKey(name, mnemonic string, coinType uint32) (address string, err error) {
+	ko, err := sp.KeyAddOrRestore(name, coinType, mnemonic)
+	if err != nil {
+		return "", err
+	}
+	return ko.Address, nil
+}
+
+func (sp *SubstrateProvider) ShowAddress(name string) (address string, err error) {
+
+	info, err := sp.Keybase.Key(name)
+	if err != nil {
+		return "", err
+	}
+	return info.GetAddress(), nil
+}
+
+func (sp *SubstrateProvider) ListAddresses() (map[string]string, error) {
+
+	out := map[string]string{}
+	info, err := sp.Keybase.List()
+	if err != nil {
+		return nil, err
+	}
+	for _, k := range info {
+		addr := k.GetAddress()
+		out[k.GetName()] = addr
+	}
+	return out, nil
+}
+
+func (sp *SubstrateProvider) DeleteKey(name string) error {
+
+	if err := sp.Keybase.Delete(name); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (sp *SubstrateProvider) KeyExists(name string) bool {
+	if sp.Keybase == nil {
+		return false
+	}
+
+	k, err := sp.Keybase.Key(name)
+	if err != nil {
+		return false
+	}
+	return k.GetName() == name
+}
+
+func (sp *SubstrateProvider) ExportPrivKeyArmor(keyName string) (armor string, err error) {
+	return "", nil
+}
+
+func (sp *SubstrateProvider) KeyAddOrRestore(keyName string, coinType uint32, mnemonic ...string) (*provider.KeyOutput, error) {
+
+	var mnemonicStr string
+	var err error
+
+	if len(mnemonic) > 0 {
+		mnemonicStr = mnemonic[0]
+	} else {
+		mnemonicStr, err = CreateMnemonic()
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	info, err := sp.Keybase.NewAccount(keyName, mnemonicStr, sp.PCfg.Network)
+	if err != nil {
+		return nil, err
+	}
+
+	return &provider.KeyOutput{Mnemonic: mnemonicStr, Address: info.GetAddress()}, nil
+}
+
+// CreateMnemonic creates a new mnemonic
+func CreateMnemonic() (string, error) {
+	entropySeed, err := bip39.NewEntropy(256)
+	if err != nil {
+		return "", err
+	}
+	mnemonic, err := bip39.NewMnemonic(entropySeed)
+	if err != nil {
+		return "", err
+	}
+	return mnemonic, nil
+}
diff --git a/relayer/chains/substrate/keystore/errors.go b/relayer/chains/substrate/keystore/errors.go
new file mode 100644
index 0000000..b4f267b
--- /dev/null
+++ b/relayer/chains/substrate/keystore/errors.go
@@ -0,0 +1,14 @@
+package keystore
+
+const (
+	ErrTextKeyNotFound             = "key not found"
+	ErrTextKeyWithAddressNotFound  = "key with address not found"
+	ErrTextAddressExists           = "account with address already exists in keyring"
+	ErrTextPubkeyExists            = "public key already exists in keystore"
+	ErrTextUnknownKeyringBackend   = "unknown keyring backend"
+	ErrTextFailedToRead            = "failed to read"
+	ErrTextFailedToOpen            = "failed to open"
+	ErrTextTooManyWrongPassphrases = "too many failed passphrase attempts"
+	ErrTextIncorrectPassphrase     = "incorrect passphrase"
+	ErrTextPassphraseDoNotMatch    = "passphrase do not match"
+)
diff --git a/relayer/chains/substrate/keystore/info.go b/relayer/chains/substrate/keystore/info.go
new file mode 100644
index 0000000..753be30
--- /dev/null
+++ b/relayer/chains/substrate/keystore/info.go
@@ -0,0 +1,70 @@
+package keystore
+
+import (
+	"encoding/json"
+	"fmt"
+
+	"github.com/ComposableFi/go-substrate-rpc-client/v4/signature"
+)
+
+// Info is the publicly exposed information about a keypair
+type Info interface {
+	// GetName returns the name of the underlying key
+	GetName() string
+	// GetAddress returns the address of the underlying key as a string
+	GetAddress() string
+	// GetPublicKey returns the public key as bytes
+	GetPublicKey() []byte
+	// GetKeyringPair returns the keyring pair
+	GetKeyringPair() signature.KeyringPair
+}
+
+// localInfo is the public information about a locally stored key
+type localInfo struct {
+	Name    string                `json:"name"`
+	Keypair signature.KeyringPair `json:"keypair"`
+}
+
+func newLocalInfo(name string, kp signature.KeyringPair) Info {
+	return &localInfo{
+		Name:    name,
+		Keypair: kp,
+	}
+}
+
+func (i localInfo) GetAddress() string {
+	return i.Keypair.Address
+}
+
+func (i localInfo) GetName() string {
+	return i.Name
+}
+
+func (i localInfo) GetPublicKey() []byte {
+	return i.Keypair.PublicKey
+}
+
+func (i localInfo) GetKeyringPair() signature.KeyringPair {
+	return i.Keypair
+}
+
+func marshalInfo(i Info) ([]byte, error) {
+	marshalled, err := json.Marshal(i)
+	if err != nil {
+		return nil, err
+	}
+	return marshalled, nil
+}
+
+func unmarshalInfo(bz []byte) (Info, error) {
+	localInfo := localInfo{}
+	if err := json.Unmarshal(bz, &localInfo); err != nil {
+		return nil, err
+	}
+
+	return localInfo, nil
+}
+
+func infoKey(name string) string { return fmt.Sprintf("%s.%s", name, infoSuffix) }
+
+func infoKeyBz(name string) []byte { return []byte(infoKey(name)) }
diff --git a/relayer/chains/substrate/keystore/keyring.go b/relayer/chains/substrate/keystore/keyring.go
new file mode 100644
index 0000000..0a19c6a
--- /dev/null
+++ b/relayer/chains/substrate/keystore/keyring.go
@@ -0,0 +1,356 @@
+package keystore
+
+import (
+	"bufio"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+
+	"github.com/ComposableFi/go-substrate-rpc-client/v4/signature"
+
+	"github.com/99designs/keyring"
+	"github.com/cosmos/cosmos-sdk/client/input"
+	"github.com/pkg/errors"
+	"golang.org/x/crypto/bcrypt"
+)
+
+// Backend options for Keyring
+const (
+	BackendFile   = "file"
+	BackendTest   = "test"
+	BackendMemory = "memory"
+
+	infoSuffix = "info"
+)
+
+const (
+	keyringFileDirName = "keyring-file"
+	keyringTestDirName = "keyring-test"
+)
+
+const (
+	maxPassphraseEntryAttempts = 3
+)
+
+type keystore struct {
+	db keyring.Keyring
+}
+
+// Keyring exposes operations over a backend supported by github.com/99designs/keyring.
+type Keyring interface {
+	// List returns a list of all stored keys.
+	List() ([]Info, error)
+
+	// Key fetches and returns a key using its uid.
+	Key(uid string) (Info, error)
+
+	// Delete remove keys from the keyring.
+	Delete(uid string) error
+
+	// NewAccount converts a mnemonic to a private key and BIP-39 HD Path and persists it.
+	// It fails if there is an existing key Info with the same address.
+	NewAccount(name, mnemonic string, network uint16) (Info, error)
+}
+
+func New(
+	appName, backend string, rootDir string, userInput io.Reader,
+) (Keyring, error) {
+	var (
+		db  keyring.Keyring
+		err error
+	)
+
+	switch backend {
+	case BackendMemory:
+		return NewInMemory(), err
+	case BackendTest:
+		db, err = keyring.Open(newTestBackendKeyringConfig(appName, rootDir))
+	case BackendFile:
+		db, err = keyring.Open(newFileBackendKeyringConfig(appName, rootDir, userInput))
+	default:
+		return nil, fmt.Errorf("%s: %s", ErrTextUnknownKeyringBackend, backend)
+	}
+
+	if err != nil {
+		return nil, err
+	}
+
+	return newKeystore(db), nil
+}
+
+// NewInMemory creates a transient keyring useful for testing
+// purposes and on-the-fly key generation.
+// Keybase options can be applied when generating this new Keybase.
+func NewInMemory() Keyring {
+	return newKeystore(keyring.NewArrayKeyring(nil))
+}
+
+func newTestBackendKeyringConfig(appName, dir string) keyring.Config {
+	return keyring.Config{
+		AllowedBackends: []keyring.BackendType{keyring.FileBackend},
+		ServiceName:     appName,
+		FileDir:         filepath.Join(dir, keyringTestDirName),
+		FilePasswordFunc: func(_ string) (string, error) {
+			return "test", nil
+		},
+	}
+}
+
+func newFileBackendKeyringConfig(name, dir string, buf io.Reader) keyring.Config {
+	fileDir := filepath.Join(dir, keyringFileDirName)
+
+	return keyring.Config{
+		AllowedBackends:  []keyring.BackendType{keyring.FileBackend},
+		ServiceName:      name,
+		FileDir:          fileDir,
+		FilePasswordFunc: newRealPrompt(fileDir, buf),
+	}
+}
+
+func newRealPrompt(dir string, buf io.Reader) func(string) (string, error) {
+	return func(prompt string) (string, error) {
+		keyhashStored := false
+		keyhashFilePath := filepath.Join(dir, "keyhash")
+
+		var keyhash []byte
+
+		_, err := os.Stat(keyhashFilePath)
+
+		switch {
+		case err == nil:
+			keyhash, err = ioutil.ReadFile(keyhashFilePath)
+			if err != nil {
+				return "", fmt.Errorf("%s: %s: %s", ErrTextFailedToRead, keyhashFilePath, err)
+			}
+
+			keyhashStored = true
+
+		case os.IsNotExist(err):
+			keyhashStored = false
+
+		default:
+			return "", fmt.Errorf("%s: %s: %s", ErrTextFailedToOpen, keyhashFilePath, err)
+		}
+
+		failureCounter := 0
+
+		for {
+			failureCounter++
+			if failureCounter > maxPassphraseEntryAttempts {
+				return "", fmt.Errorf(ErrTextTooManyWrongPassphrases)
+			}
+
+			buf := bufio.NewReader(buf)
+			pass, err := input.GetPassword("Enter keyring passphrase:", buf)
+			if err != nil {
+				// NOTE: LGTM.io reports a false positive alert that states we are printing the password,
+				// but we only log the error.
+				//
+				// lgtm [go/clear-text-logging]
+				fmt.Fprintln(os.Stderr, err)
+				continue
+			}
+
+			if keyhashStored {
+				if err := bcrypt.CompareHashAndPassword(keyhash, []byte(pass)); err != nil {
+					fmt.Fprintln(os.Stderr, ErrTextIncorrectPassphrase)
+					continue
+				}
+
+				return pass, nil
+			}
+
+			reEnteredPass, err := input.GetPassword("Re-enter keyring passphrase:", buf)
+			if err != nil {
+				// NOTE: LGTM.io reports a false positive alert that states we are printing the password,
+				// but we only log the error.
+				//
+				// lgtm [go/clear-text-logging]
+				fmt.Fprintln(os.Stderr, err)
+				continue
+			}
+
+			if pass != reEnteredPass {
+				fmt.Fprintln(os.Stderr, ErrTextPassphraseDoNotMatch)
+				continue
+			}
+
+			passwordHash, err := bcrypt.GenerateFromPassword([]byte(pass), 2)
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err)
+				continue
+			}
+
+			if err := ioutil.WriteFile(keyhashFilePath, passwordHash, 0555); err != nil {
+				return "", err
+			}
+
+			return pass, nil
+		}
+	}
+}
+
+func newKeystore(kr keyring.Keyring) keystore {
+	return keystore{kr}
+}
+
+func (ks keystore) key(infoKey string) (Info, error) {
+	bs, err := ks.db.Get(infoKey)
+	if err != nil {
+		return nil, errors.Errorf("%s: %s", ErrTextKeyNotFound, infoKey)
+	}
+	if len(bs.Data) == 0 {
+		return nil, errors.Errorf("%s: %s", ErrTextKeyNotFound, infoKey)
+	}
+	return unmarshalInfo(bs.Data)
+}
+
+func (ks keystore) Delete(name string) error {
+	info, err := ks.Key(name)
+	if err != nil {
+		return err
+	}
+
+	err = ks.db.Remove(info.GetAddress())
+	if err != nil {
+		return err
+	}
+
+	err = ks.db.Remove(infoKey(name))
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (ks keystore) List() ([]Info, error) {
+	var res []Info
+
+	keys, err := ks.db.Keys()
+	if err != nil {
+		return nil, err
+	}
+
+	sort.Strings(keys)
+
+	for _, key := range keys {
+		if !strings.HasSuffix(key, infoSuffix) {
+			continue
+		}
+		rawInfo, err := ks.db.Get(key)
+		if err != nil {
+			return nil, err
+		}
+
+		if len(rawInfo.Data) == 0 {
+			return nil, errors.Errorf("%s: %s", ErrTextKeyNotFound, key)
+		}
+
+		info, err := unmarshalInfo(rawInfo.Data)
+		if err != nil {
+			return nil, err
+		}
+
+		res = append(res, info)
+	}
+
+	return res, nil
+}
+
+func (ks keystore) Key(uid string) (Info, error) {
+	infoKey := infoKey(uid)
+	return ks.key(infoKey)
+}
+
+func (ks keystore) NewAccount(name string, mnemonic string, network uint16) (Info, error) {
+	kp, err := signature.KeyringPairFromSecret(mnemonic, network)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create keypair: %w", err)
+	}
+
+	if _, err := ks.KeyByAddress(kp.Address); err == nil {
+		return nil, fmt.Errorf("%s: %s", ErrTextAddressExists, kp.Address)
+	}
+
+	return ks.writeLocalKey(name, kp)
+}
+
+func (ks keystore) writeLocalKey(name string, keypair signature.KeyringPair) (Info, error) {
+	info := newLocalInfo(name, keypair)
+	if err := ks.writeInfo(info); err != nil {
+		return nil, err
+	}
+
+	return info, nil
+}
+
+func (ks keystore) writeInfo(info Info) error {
+	key := infoKeyBz(info.GetName())
+	serializedInfo, err := marshalInfo(info)
+	if err != nil {
+		return err
+	}
+
+	exists, err := ks.existsInDb(info)
+	if err != nil {
+		return err
+	}
+	if exists {
+		return errors.New(ErrTextPubkeyExists)
+	}
+
+	err = ks.db.Set(keyring.Item{
+		Key:  string(key),
+		Data: serializedInfo,
+	})
+	if err != nil {
+		return err
+	}
+
+	err = ks.db.Set(keyring.Item{
+		Key:  info.GetAddress(),
+		Data: key,
+	})
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// existsInDb returns true if key is in DB. Error is returned only when we have error
+// different thant ErrKeyNotFound
+func (ks keystore) existsInDb(info Info) (bool, error) {
+
+	if _, err := ks.db.Get(info.GetAddress()); err == nil {
+		return true, nil // address lookup succeeds - info exists
+	} else if err != keyring.ErrKeyNotFound {
+		return false, err // received unexpected error - returns error
+	}
+
+	if _, err := ks.db.Get(infoKey(info.GetName())); err == nil {
+		return true, nil // uid lookup succeeds - info exists
+	} else if err != keyring.ErrKeyNotFound {
+		return false, err // received unexpected error - returns
+	}
+
+	// both lookups failed, info does not exist
+	return false, nil
+}
+
+func (ks keystore) KeyByAddress(address string) (Info, error) {
+	ik, err := ks.db.Get(address)
+	if err != nil {
+		return nil, errors.Errorf("%s: %s", ErrTextKeyWithAddressNotFound, address)
+	}
+
+	if len(ik.Data) == 0 {
+		return nil, errors.Errorf("%s: %s", ErrTextKeyWithAddressNotFound, address)
+	}
+	return ks.key(string(ik.Data))
+}
diff --git a/relayer/chains/substrate/provider.go b/relayer/chains/substrate/provider.go
new file mode 100644
index 0000000..b8ddfa1
--- /dev/null
+++ b/relayer/chains/substrate/provider.go
@@ -0,0 +1,476 @@
+package substrate
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"path"
+	"time"
+
+	sdk "github.com/cosmos/cosmos-sdk/types"
+	transfertypes "github.com/cosmos/ibc-go/v5/modules/apps/transfer/types"
+	clienttypes "github.com/cosmos/ibc-go/v5/modules/core/02-client/types"
+	conntypes "github.com/cosmos/ibc-go/v5/modules/core/03-connection/types"
+	chantypes "github.com/cosmos/ibc-go/v5/modules/core/04-channel/types"
+	ibcexported "github.com/cosmos/ibc-go/v5/modules/core/exported"
+	"github.com/cosmos/relayer/v2/relayer/chains/substrate/keystore"
+	"github.com/cosmos/relayer/v2/relayer/provider"
+	"github.com/gogo/protobuf/proto"
+	"go.uber.org/zap"
+)
+
+var (
+	_ provider.ChainProvider  = &SubstrateProvider{}
+	_ provider.KeyProvider    = &SubstrateProvider{}
+	_ provider.ProviderConfig = &SubstrateProviderConfig{}
+)
+
+type SubstrateProviderConfig struct {
+	Key            string `json:"key" yaml:"key"`
+	ChainName      string `json:"-" yaml:"-"`
+	ChainID        string `json:"chain-id" yaml:"chain-id"`
+	RPCAddr        string `json:"rpc-addr" yaml:"rpc-addr"`
+	RelayRPCAddr   string `json:"relay-rpc-addr" yaml:"relay-rpc-addr"`
+	AccountPrefix  string `json:"account-prefix" yaml:"account-prefix"`
+	KeyringBackend string `json:"keyring-backend" yaml:"keyring-backend"`
+	KeyDirectory   string `json:"key-directory" yaml:"key-directory"`
+	Debug          bool   `json:"debug" yaml:"debug"`
+	Timeout        string `json:"timeout" yaml:"timeout"`
+	Network        uint16 `json:"network" yaml:"network"`
+}
+
+func (spc SubstrateProviderConfig) Validate() error {
+	if _, err := time.ParseDuration(spc.Timeout); err != nil {
+		return fmt.Errorf("invalid Timeout: %w", err)
+	}
+	return nil
+}
+
+func keysDir(home, chainID string) string {
+	return path.Join(home, "keys", chainID)
+}
+
+// NewProvider validates the SubstrateProviderConfig, instantiates a ChainClient and then instantiates a SubstrateProvider
+func (spc SubstrateProviderConfig) NewProvider(log *zap.Logger, homepath string, debug bool, chainName string) (provider.ChainProvider, error) {
+	if err := spc.Validate(); err != nil {
+		return nil, err
+	}
+
+	if len(spc.KeyDirectory) == 0 {
+		spc.KeyDirectory = keysDir(homepath, spc.ChainID)
+	}
+
+	sp := &SubstrateProvider{
+		log:  log,
+		PCfg: spc,
+	}
+
+	err := sp.Init()
+	if err != nil {
+		return nil, err
+	}
+
+	return sp, nil
+}
+
+func (sp *SubstrateProvider) Init() error {
+	keybase, err := keystore.New(sp.PCfg.ChainID, sp.PCfg.KeyringBackend, sp.PCfg.KeyDirectory, sp.Input)
+	if err != nil {
+		return err
+	}
+
+	sp.Keybase = keybase
+	return nil
+}
+
+type SubstrateProvider struct {
+	log     *zap.Logger
+	Keybase keystore.Keyring
+	PCfg    SubstrateProviderConfig
+	Input   io.Reader
+}
+
+func (sp *SubstrateProvider) BlockTime(ctx context.Context, height int64) (time.Time, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryTx(ctx context.Context, hashHex string) (*provider.RelayerTxResponse, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryTxs(ctx context.Context, page, limit int, events []string) ([]*provider.RelayerTxResponse, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryLatestHeight(ctx context.Context) (int64, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryIBCHeader(ctx context.Context, h int64) (provider.IBCHeader, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QuerySendPacket(ctx context.Context, srcChanID, srcPortID string, sequence uint64) (provider.PacketInfo, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryRecvPacket(ctx context.Context, dstChanID, dstPortID string, sequence uint64) (provider.PacketInfo, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryBalance(ctx context.Context, keyName string) (sdk.Coins, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryBalanceWithAddress(ctx context.Context, addr string) (sdk.Coins, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryUnbondingPeriod(ctx context.Context) (time.Duration, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryClientState(ctx context.Context, height int64, clientid string) (ibcexported.ClientState, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryClientStateResponse(ctx context.Context, height int64, srcClientId string) (*clienttypes.QueryClientStateResponse, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryClientConsensusState(ctx context.Context, chainHeight int64, clientid string, clientHeight ibcexported.Height) (*clienttypes.QueryConsensusStateResponse, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryUpgradedClient(ctx context.Context, height int64) (*clienttypes.QueryClientStateResponse, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryUpgradedConsState(ctx context.Context, height int64) (*clienttypes.QueryConsensusStateResponse, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryConsensusState(ctx context.Context, height int64) (ibcexported.ConsensusState, int64, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryClients(ctx context.Context) (clienttypes.IdentifiedClientStates, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryConnection(ctx context.Context, height int64, connectionid string) (*conntypes.QueryConnectionResponse, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryConnections(ctx context.Context) (conns []*conntypes.IdentifiedConnection, err error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryConnectionsUsingClient(ctx context.Context, height int64, clientid string) (*conntypes.QueryConnectionsResponse, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) GenerateConnHandshakeProof(ctx context.Context, height int64, clientId, connId string) (clientState ibcexported.ClientState, clientStateProof []byte, consensusProof []byte, connectionProof []byte, connectionProofHeight ibcexported.Height, err error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryChannel(ctx context.Context, height int64, channelid, portid string) (chanRes *chantypes.QueryChannelResponse, err error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryChannelClient(ctx context.Context, height int64, channelid, portid string) (*clienttypes.IdentifiedClientState, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryConnectionChannels(ctx context.Context, height int64, connectionid string) ([]*chantypes.IdentifiedChannel, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryChannels(ctx context.Context) ([]*chantypes.IdentifiedChannel, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryPacketCommitments(ctx context.Context, height uint64, channelid, portid string) (commitments *chantypes.QueryPacketCommitmentsResponse, err error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryPacketAcknowledgements(ctx context.Context, height uint64, channelid, portid string) (acknowledgements []*chantypes.PacketState, err error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryUnreceivedPackets(ctx context.Context, height uint64, channelid, portid string, seqs []uint64) ([]uint64, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryUnreceivedAcknowledgements(ctx context.Context, height uint64, channelid, portid string, seqs []uint64) ([]uint64, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryNextSeqRecv(ctx context.Context, height int64, channelid, portid string) (recvRes *chantypes.QueryNextSequenceReceiveResponse, err error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryPacketCommitment(ctx context.Context, height int64, channelid, portid string, seq uint64) (comRes *chantypes.QueryPacketCommitmentResponse, err error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryPacketAcknowledgement(ctx context.Context, height int64, channelid, portid string, seq uint64) (ackRes *chantypes.QueryPacketAcknowledgementResponse, err error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryPacketReceipt(ctx context.Context, height int64, channelid, portid string, seq uint64) (recRes *chantypes.QueryPacketReceiptResponse, err error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryDenomTrace(ctx context.Context, denom string) (*transfertypes.DenomTrace, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) QueryDenomTraces(ctx context.Context, offset, limit uint64, height int64) ([]transfertypes.DenomTrace, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) NewClientState(dstChainID string, dstIBCHeader provider.IBCHeader, dstTrustingPeriod, dstUbdPeriod time.Duration, allowUpdateAfterExpiry, allowUpdateAfterMisbehaviour bool) (ibcexported.ClientState, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) MsgCreateClient(clientState ibcexported.ClientState, consensusState ibcexported.ConsensusState) (provider.RelayerMessage, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) MsgUpgradeClient(srcClientId string, consRes *clienttypes.QueryConsensusStateResponse, clientRes *clienttypes.QueryClientStateResponse) (provider.RelayerMessage, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) ValidatePacket(msgTransfer provider.PacketInfo, latestBlock provider.LatestBlock) error {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) PacketCommitment(ctx context.Context, msgTransfer provider.PacketInfo, height uint64) (provider.PacketProof, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) PacketAcknowledgement(ctx context.Context, msgRecvPacket provider.PacketInfo, height uint64) (provider.PacketProof, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) PacketReceipt(ctx context.Context, msgTransfer provider.PacketInfo, height uint64) (provider.PacketProof, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) NextSeqRecv(ctx context.Context, msgTransfer provider.PacketInfo, height uint64) (provider.PacketProof, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) MsgTransfer(dstAddr string, amount sdk.Coin, info provider.PacketInfo) (provider.RelayerMessage, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) MsgRecvPacket(msgTransfer provider.PacketInfo, proof provider.PacketProof) (provider.RelayerMessage, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) MsgAcknowledgement(msgRecvPacket provider.PacketInfo, proofAcked provider.PacketProof) (provider.RelayerMessage, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) MsgTimeout(msgTransfer provider.PacketInfo, proofUnreceived provider.PacketProof) (provider.RelayerMessage, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) MsgTimeoutOnClose(msgTransfer provider.PacketInfo, proofUnreceived provider.PacketProof) (provider.RelayerMessage, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) ConnectionHandshakeProof(ctx context.Context, msgOpenInit provider.ConnectionInfo, height uint64) (provider.ConnectionProof, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) ConnectionProof(ctx context.Context, msgOpenAck provider.ConnectionInfo, height uint64) (provider.ConnectionProof, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) MsgConnectionOpenInit(info provider.ConnectionInfo, proof provider.ConnectionProof) (provider.RelayerMessage, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) MsgConnectionOpenTry(msgOpenInit provider.ConnectionInfo, proof provider.ConnectionProof) (provider.RelayerMessage, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) MsgConnectionOpenAck(msgOpenTry provider.ConnectionInfo, proof provider.ConnectionProof) (provider.RelayerMessage, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) MsgConnectionOpenConfirm(msgOpenAck provider.ConnectionInfo, proof provider.ConnectionProof) (provider.RelayerMessage, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) ChannelProof(ctx context.Context, msg provider.ChannelInfo, height uint64) (provider.ChannelProof, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) MsgChannelOpenInit(info provider.ChannelInfo, proof provider.ChannelProof) (provider.RelayerMessage, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) MsgChannelOpenTry(msgOpenInit provider.ChannelInfo, proof provider.ChannelProof) (provider.RelayerMessage, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) MsgChannelOpenAck(msgOpenTry provider.ChannelInfo, proof provider.ChannelProof) (provider.RelayerMessage, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) MsgChannelOpenConfirm(msgOpenAck provider.ChannelInfo, proof provider.ChannelProof) (provider.RelayerMessage, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) MsgChannelCloseInit(info provider.ChannelInfo, proof provider.ChannelProof) (provider.RelayerMessage, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) MsgChannelCloseConfirm(msgCloseInit provider.ChannelInfo, proof provider.ChannelProof) (provider.RelayerMessage, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) MsgUpdateClientHeader(latestHeader provider.IBCHeader, trustedHeight clienttypes.Height, trustedHeader provider.IBCHeader) (ibcexported.Header, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) MsgUpdateClient(clientId string, counterpartyHeader ibcexported.Header) (provider.RelayerMessage, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) RelayPacketFromSequence(ctx context.Context, src provider.ChainProvider, srch, dsth, seq uint64, srcChanID, srcPortID string, order chantypes.Order) (provider.RelayerMessage, provider.RelayerMessage, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) AcknowledgementFromSequence(ctx context.Context, dst provider.ChainProvider, dsth, seq uint64, dstChanID, dstPortID, srcChanID, srcPortID string) (provider.RelayerMessage, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) SendMessage(ctx context.Context, msg provider.RelayerMessage, memo string) (*provider.RelayerTxResponse, bool, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) SendMessages(ctx context.Context, msgs []provider.RelayerMessage, memo string) (*provider.RelayerTxResponse, bool, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) ChainName() string {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) ChainId() string {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) Type() string {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) ProviderConfig() provider.ProviderConfig {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) Key() string {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) Address() (string, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) Timeout() string {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) TrustingPeriod(ctx context.Context) (time.Duration, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) WaitForNBlocks(ctx context.Context, n int64) error {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (sp *SubstrateProvider) Sprint(toPrint proto.Message) (string, error) {
+	//TODO implement me
+	panic("implement me")
+}


Test patch:
diff --git a/ibctest/go.mod b/ibctest/go.mod
index 150d5ef..7decb8b 100644
--- a/ibctest/go.mod
+++ b/ibctest/go.mod
@@ -61,7 +61,7 @@ require (
 	github.com/docker/go-units v0.4.0 // indirect
 	github.com/dustin/go-humanize v1.0.0 // indirect
 	github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
-	github.com/ethereum/go-ethereum v1.10.19 // indirect
+	github.com/ethereum/go-ethereum v1.10.20 // indirect
 	github.com/evmos/ethermint v0.6.1-0.20220810122651-42abb259cbed // indirect
 	github.com/felixge/httpsnoop v1.0.1 // indirect
 	github.com/fsnotify/fsnotify v1.5.4 // indirect
@@ -106,7 +106,7 @@ require (
 	github.com/magiconair/properties v1.8.6 // indirect
 	github.com/mattn/go-isatty v0.0.14 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
-	github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect
+	github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b // indirect
 	github.com/mitchellh/go-homedir v1.1.0 // indirect
 	github.com/mitchellh/go-testing-interface v1.0.0 // indirect
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
@@ -152,12 +152,12 @@ require (
 	go.opencensus.io v0.23.0 // indirect
 	go.uber.org/atomic v1.9.0 // indirect
 	go.uber.org/multierr v1.8.0 // indirect
-	golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
+	golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 // indirect
 	golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
 	golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 // indirect
 	golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
 	golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 // indirect
-	golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect
+	golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 // indirect
 	golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect
 	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/tools v0.1.11 // indirect
diff --git a/ibctest/go.sum b/ibctest/go.sum
index 043674f..0177ab8 100644
--- a/ibctest/go.sum
+++ b/ibctest/go.sum
@@ -515,8 +515,8 @@ github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
 github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0=
-github.com/ethereum/go-ethereum v1.10.19 h1:EOR5JbL4MD5yeOqv8W2iC1s4NximrTjqFccUz8lyBRA=
-github.com/ethereum/go-ethereum v1.10.19/go.mod h1:IJBNMtzKcNHPtllYihy6BL2IgK1u+32JriaTbdt4v+w=
+github.com/ethereum/go-ethereum v1.10.20 h1:75IW830ClSS40yrQC1ZCMZCt5I+zU16oqId2SiQwdQ4=
+github.com/ethereum/go-ethereum v1.10.20/go.mod h1:LWUN82TCHGpxB3En5HVmLLzPD7YSrEUFmFfN1nKkVN0=
 github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY=
 github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/evmos/ethermint v0.6.1-0.20220810122651-42abb259cbed h1:XgebosrOkNn6UnsR8C7CECu+X/FT3fZ5H5TVghjTSro=
@@ -1037,8 +1037,8 @@ github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WT
 github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
 github.com/miguelmota/go-ethereum-hdwallet v0.1.1 h1:zdXGlHao7idpCBjEGTXThVAtMKs+IxAgivZ75xqkWK0=
 github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM=
-github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 h1:QRUSJEgZn2Snx0EmT/QLXibWjSUDjKWvXIT19NBVp94=
-github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM=
+github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b h1:QrHweqAtyJ9EwCaGHBu1fghwxIPiopAHV06JlXrMHjk=
+github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b/go.mod h1:xxLb2ip6sSUts3g1irPVHyk/DGslwQsNOo9I7smJfNU=
 github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
 github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
 github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
@@ -1626,8 +1626,8 @@ golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5y
 golang.org/x/crypto v0.0.0-20220313003712-b769efc7c000/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
-golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 h1:GIAS/yBem/gq2MUqgNIzUHW7cJMmx3TGZOrnyYaNQ6c=
+golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1924,8 +1924,9 @@ golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU=
 golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 h1:Sx/u41w+OwrInGdEckYmEuU5gHoGSL4QbDz3S9s6j4U=
+golang.org/x/sys v0.0.0-20220818161305-2296e01440c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
diff --git a/relayer/chains/substrate/key_test.go b/relayer/chains/substrate/key_test.go
new file mode 100644
index 0000000..fd303b0
--- /dev/null
+++ b/relayer/chains/substrate/key_test.go
@@ -0,0 +1,36 @@
+package substrate_test
+
+import (
+	"testing"
+
+	"github.com/cosmos/relayer/v2/relayer/chains/substrate/keystore"
+
+	"github.com/stretchr/testify/require"
+)
+
+// TestKeyRestore restores a test mnemonic
+func TestKeyRestoreAndRetrieve(t *testing.T) {
+	keyName := "test_key"
+	mnemonic := "blind master acoustic speak victory lend kiss grab glad help demand hood roast zone lend sponsor level cheap truck kingdom apology token hover reunion"
+	expectedAddress := "5Hn67YZ75F3XrHiJAtiscJMGQ4zFNw9e45CNfLuxL6vEVYz8"
+
+	provider, err := getTestProvider()
+	require.Nil(t, err)
+
+	config := getSubstrateConfig(homePath, 42)
+	provider.Keybase, err = keystore.New(config.ChainName, config.KeyringBackend, config.KeyDirectory, nil)
+	require.Nil(t, err)
+
+	if provider.KeyExists(keyName) {
+		err = provider.DeleteKey(keyName) // Delete if test is being run again
+		require.Nil(t, err)
+	}
+
+	address, err := provider.RestoreKey(keyName, mnemonic, 0)
+	require.Nil(t, err)
+	require.Equal(t, expectedAddress, address, "Restored address: %s does not match expected: %s", address, expectedAddress)
+
+	retrievedAddress, err := provider.ShowAddress(keyName)
+	require.Nil(t, err)
+	require.Equal(t, expectedAddress, address, "Restored address: %s does not match expected: %s", retrievedAddress, expectedAddress)
+}
diff --git a/relayer/chains/substrate/provider_test.go b/relayer/chains/substrate/provider_test.go
new file mode 100644
index 0000000..ded7ce7
--- /dev/null
+++ b/relayer/chains/substrate/provider_test.go
@@ -0,0 +1,28 @@
+package substrate_test
+
+import (
+	"github.com/cosmos/relayer/v2/relayer/chains/substrate"
+	"github.com/cosmos/relayer/v2/relayer/chains/substrate/keystore"
+)
+
+const homePath = "/tmp"
+
+func getSubstrateConfig(keyDir string, network uint16) *substrate.SubstrateProviderConfig {
+	return &substrate.SubstrateProviderConfig{
+		Key:            "default",
+		ChainID:        "substrate-test",
+		KeyringBackend: keystore.BackendTest,
+		KeyDirectory:   keyDir,
+		Timeout:        "20s",
+		Network:        network,
+	}
+}
+
+func getTestProvider() (*substrate.SubstrateProvider, error) {
+	config := getSubstrateConfig(homePath, 42)
+	provider, err := config.NewProvider(nil, homePath, true, "substrate")
+	if err != nil {
+		return nil, err
+	}
+	return provider.(*substrate.SubstrateProvider), nil
+}