diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 1e7104dae2..75d01a6f24 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -252,6 +252,32 @@ func TestCoinSend(t *testing.T) { assert.Equal(t, int64(1), mycoins.Amount) } +func TestIBCTransfer(t *testing.T) { + + // create TX + resultTx := doIBCTransfer(t, port, seed) + + time.Sleep(time.Second * 2) // T + + // check if tx was commited + assert.Equal(t, uint32(0), resultTx.CheckTx.Code) + assert.Equal(t, uint32(0), resultTx.DeliverTx.Code) + + // query sender + res, body := request(t, port, "GET", "/accounts/"+sendAddr, nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var m auth.BaseAccount + err := json.Unmarshal([]byte(body), &m) + require.Nil(t, err) + coins := m.Coins + mycoins := coins[0] + assert.Equal(t, coinDenom, mycoins.Denom) + assert.Equal(t, coinAmount-2, mycoins.Amount) + + // TODO: query ibc egress packet state +} + func TestTxs(t *testing.T) { // TODO: re-enable once we can get txs by tag @@ -444,3 +470,32 @@ func doSend(t *testing.T, port, seed string) (receiveAddr string, resultTx ctype return receiveAddr, resultTx } + +func doIBCTransfer(t *testing.T, port, seed string) (resultTx ctypes.ResultBroadcastTxCommit) { + + // create receive address + kb := client.MockKeyBase() + receiveInfo, _, err := kb.Create("receive_address", "1234567890", cryptoKeys.CryptoAlgo("ed25519")) + require.Nil(t, err) + receiveAddr := receiveInfo.PubKey.Address().String() + + // get the account to get the sequence + res, body := request(t, port, "GET", "/accounts/"+sendAddr, nil) + // require.Equal(t, http.StatusOK, res.StatusCode, body) + acc := auth.BaseAccount{} + err = json.Unmarshal([]byte(body), &acc) + require.Nil(t, err) + fmt.Println("BODY", body) + fmt.Println("ACC", acc) + sequence := acc.Sequence + + // send + jsonStr := []byte(fmt.Sprintf(`{ "name":"%s", "password":"%s", "sequence":%d, "amount":[{ "denom": "%s", "amount": 1 }] }`, name, password, sequence, coinDenom)) + res, body = request(t, port, "POST", "/ibc/testchain/"+receiveAddr+"/send", jsonStr) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + err = json.Unmarshal([]byte(body), &resultTx) + require.Nil(t, err) + + return resultTx +} diff --git a/client/lcd/root.go b/client/lcd/root.go index 7f18af59dc..9464081e0d 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -20,6 +20,7 @@ import ( "github.com/cosmos/cosmos-sdk/wire" auth "github.com/cosmos/cosmos-sdk/x/auth/rest" bank "github.com/cosmos/cosmos-sdk/x/bank/rest" + ibc "github.com/cosmos/cosmos-sdk/x/ibc/rest" ) const ( @@ -78,5 +79,6 @@ func createHandler(cdc *wire.Codec) http.Handler { tx.RegisterRoutes(r, cdc) auth.RegisterRoutes(r, cdc, "main") bank.RegisterRoutes(r, cdc, kb) + ibc.RegisterRoutes(r, cdc, kb) return r } diff --git a/x/ibc/rest/root.go b/x/ibc/rest/root.go new file mode 100644 index 0000000000..81e74d0312 --- /dev/null +++ b/x/ibc/rest/root.go @@ -0,0 +1,14 @@ +package rest + +import ( + "github.com/gorilla/mux" + + keys "github.com/tendermint/go-crypto/keys" + + "github.com/cosmos/cosmos-sdk/wire" +) + +// RegisterRoutes - Central function to define routes that get registered by the main application +func RegisterRoutes(r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { + r.HandleFunc("/ibc/{destchain}/{address}/send", TransferRequestHandler(cdc, kb)).Methods("POST") +} diff --git a/x/ibc/rest/transfer.go b/x/ibc/rest/transfer.go new file mode 100644 index 0000000000..f47159160e --- /dev/null +++ b/x/ibc/rest/transfer.go @@ -0,0 +1,100 @@ +package rest + +import ( + "encoding/hex" + "encoding/json" + "io/ioutil" + "net/http" + + "github.com/gorilla/mux" + "github.com/spf13/viper" + "github.com/tendermint/go-crypto/keys" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/builder" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/bank/commands" + "github.com/cosmos/cosmos-sdk/x/ibc" +) + +type transferBody struct { + // Fees sdk.Coin `json="fees"` + Amount sdk.Coins `json:"amount"` + LocalAccountName string `json:"name"` + Password string `json:"password"` + SrcChainID string `json:"src_chain_id"` + Sequence int64 `json:"sequence"` +} + +// TransferRequestHandler - http request handler to transfer coins to a address +// on a different chain via IBC +func TransferRequestHandler(cdc *wire.Codec, kb keys.Keybase) func(http.ResponseWriter, *http.Request) { + c := commands.Commander{cdc} + return func(w http.ResponseWriter, r *http.Request) { + // collect data + vars := mux.Vars(r) + destChainID := vars["destchain"] + address := vars["address"] + + var m transferBody + body, err := ioutil.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + err = json.Unmarshal(body, &m) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + info, err := kb.Get(m.LocalAccountName) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(err.Error())) + return + } + + bz, err := hex.DecodeString(address) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + to := sdk.Address(bz) + + // build message + packet := ibc.NewIBCPacket(info.PubKey.Address(), to, m.Amount, m.SrcChainID, destChainID) + msg := ibc.IBCTransferMsg{packet} + + // sign + // XXX: OMG + viper.Set(client.FlagSequence, m.Sequence) + txBytes, err := builder.SignAndBuild(m.LocalAccountName, m.Password, msg, c.Cdc) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(err.Error())) + return + } + + // send + res, err := builder.BroadcastTx(txBytes) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + output, err := json.MarshalIndent(res, "", " ") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + w.Write(output) + } +}