diff --git a/server/rosetta/cosmos/client/client.go b/server/rosetta/cosmos/client/client.go index c89f70289942..b5d4fcdd2e0b 100644 --- a/server/rosetta/cosmos/client/client.go +++ b/server/rosetta/cosmos/client/client.go @@ -64,7 +64,7 @@ type Client struct { ir codectypes.InterfaceRegistry - client client.Context + clientCtx client.Context } // NewSingle instantiates a single network client @@ -86,15 +86,16 @@ func NewSingle(grpcEndpoint, tendermintEndpoint string, optsFunc ...OptionFunc) authClient := auth.NewQueryClient(grpcConn) bankClient := bank.NewQueryClient(grpcConn) + // NodeURI and Client are set from here otherwise // WitNodeURI will require to create a new client // it's done here because WithNodeURI panics if // connection to tendermint node fails - clientContext := client.Context{ + clientCtx := client.Context{ Client: tmRPC, NodeURI: tendermintEndpoint, } - clientContext = clientContext. + clientCtx = clientCtx. WithJSONMarshaler(opts.cdc). WithInterfaceRegistry(opts.interfaceRegistry). WithTxConfig(tx.NewTxConfig(opts.cdc, tx.DefaultSignModes)). @@ -102,13 +103,36 @@ func NewSingle(grpcEndpoint, tendermintEndpoint string, optsFunc ...OptionFunc) WithBroadcastMode(flags.BroadcastBlock) return &Client{ - auth: authClient, - bank: bankClient, - client: clientContext, - ir: opts.interfaceRegistry, + auth: authClient, + bank: bankClient, + clientCtx: clientCtx, + ir: opts.interfaceRegistry, }, nil } +func (c *Client) AccountInfo(ctx context.Context, addr string, height *int64) (auth.AccountI, error) { + // if height is set, send height instruction to account + if height != nil { + strHeight := strconv.FormatInt(*height, 10) + ctx = metadata.AppendToOutgoingContext(ctx, grpctypes.GRPCBlockHeightHeader, strHeight) + } + // retrieve account info + accountInfo, err := c.auth.Account(ctx, &auth.QueryAccountRequest{ + Address: addr, + }) + if err != nil { + return nil, rosetta.FromGRPCToRosettaError(err) + } + // success + var account auth.AccountI + err = c.ir.UnpackAny(accountInfo.Account, &account) + if err != nil { + return nil, rosetta.WrapError(rosetta.ErrCodec, err.Error()) + } + + return account, nil +} + func (c *Client) Balances(ctx context.Context, addr string, height *int64) ([]sdk.Coin, error) { if height != nil { strHeight := strconv.FormatInt(*height, 10) @@ -130,7 +154,7 @@ func (c *Client) BlockByHash(ctx context.Context, hash string) (*tmtypes.ResultB return nil, nil, rosetta.WrapError(rosetta.ErrBadArgument, fmt.Sprintf("invalid block hash: %s", err)) } - block, err := c.client.Client.BlockByHash(ctx, bHash) + block, err := c.clientCtx.Client.BlockByHash(ctx, bHash) if err != nil { return nil, nil, rosetta.WrapError(rosetta.ErrUnknown, err.Error()) // can be either a connection error or bad argument? } @@ -144,7 +168,7 @@ func (c *Client) BlockByHash(ctx context.Context, hash string) (*tmtypes.ResultB // BlockByHeight returns the block and the transactions contained inside it given its height func (c *Client) BlockByHeight(ctx context.Context, height *int64) (*tmtypes.ResultBlock, []*rosetta.SdkTxWithHash, error) { - block, err := c.client.Client.Block(ctx, height) + block, err := c.clientCtx.Client.Block(ctx, height) if err != nil { return nil, nil, rosetta.WrapError(rosetta.ErrUnknown, err.Error()) } @@ -167,12 +191,12 @@ func (c *Client) Coins(ctx context.Context) (sdk.Coins, error) { // ListTransactionsInBlock returns the list of the transactions in a block given its height func (c *Client) ListTransactionsInBlock(ctx context.Context, height int64) ([]*rosetta.SdkTxWithHash, error) { txQuery := fmt.Sprintf(`tx.height=%d`, height) - txList, err := c.client.Client.TxSearch(ctx, txQuery, true, nil, nil, "") + txList, err := c.clientCtx.Client.TxSearch(ctx, txQuery, true, nil, nil, "") if err != nil { return nil, rosetta.WrapError(rosetta.ErrUnknown, err.Error()) } - sdkTxs, err := conversion.TmResultTxsToSdkTxs(c.client.TxConfig.TxDecoder(), txList.Txs) + sdkTxs, err := conversion.TmResultTxsToSdkTxs(c.clientCtx.TxConfig.TxDecoder(), txList.Txs) if err != nil { return nil, err } @@ -181,7 +205,7 @@ func (c *Client) ListTransactionsInBlock(ctx context.Context, height int64) ([]* // GetTx returns a transaction given its hash func (c *Client) GetTx(_ context.Context, hash string) (sdk.Tx, error) { - txResp, err := authclient.QueryTx(c.client, hash) + txResp, err := authclient.QueryTx(c.clientCtx, hash) if err != nil { return nil, rosetta.WrapError(rosetta.ErrUnknown, err.Error()) } @@ -201,7 +225,7 @@ func (c *Client) GetUnconfirmedTx(_ context.Context, hash string) (sdk.Tx, error // Mempool returns the unconfirmed transactions in the mempool func (c *Client) Mempool(ctx context.Context) (*tmtypes.ResultUnconfirmedTxs, error) { - txs, err := c.client.Client.UnconfirmedTxs(ctx, nil) + txs, err := c.clientCtx.Client.UnconfirmedTxs(ctx, nil) if err != nil { return nil, rosetta.WrapError(rosetta.ErrUnknown, err.Error()) } @@ -210,7 +234,7 @@ func (c *Client) Mempool(ctx context.Context) (*tmtypes.ResultUnconfirmedTxs, er // Peers gets the number of peers func (c *Client) Peers(ctx context.Context) ([]tmtypes.Peer, error) { - netInfo, err := c.client.Client.NetInfo(ctx) + netInfo, err := c.clientCtx.Client.NetInfo(ctx) if err != nil { return nil, rosetta.WrapError(rosetta.ErrUnknown, err.Error()) } @@ -218,9 +242,17 @@ func (c *Client) Peers(ctx context.Context) ([]tmtypes.Peer, error) { } func (c *Client) Status(ctx context.Context) (*tmtypes.ResultStatus, error) { - status, err := c.client.Client.Status(ctx) + status, err := c.clientCtx.Client.Status(ctx) if err != nil { return nil, rosetta.WrapError(rosetta.ErrUnknown, err.Error()) } return status, err } + +func (c *Client) GetTxConfig() client.TxConfig { + return c.clientCtx.TxConfig +} + +func (c *Client) PostTx(txBytes []byte) (res *sdk.TxResponse, err error) { + return c.clientCtx.BroadcastTx(txBytes) +} diff --git a/server/rosetta/cosmos/conversion/data.go b/server/rosetta/cosmos/conversion/data.go index d8d53f4412da..16e46eceffe6 100644 --- a/server/rosetta/cosmos/conversion/data.go +++ b/server/rosetta/cosmos/conversion/data.go @@ -2,6 +2,8 @@ package conversion import ( "fmt" + "strconv" + "strings" "time" "github.com/coinbase/rosetta-sdk-go/types" @@ -65,7 +67,7 @@ func ResultTxSearchToTransaction(txs []*rosetta.SdkTxWithHash) []*types.Transact // SdkTxResponseToOperations converts a tx response to operations func SdkTxToOperations(tx sdk.Tx, hasError, withoutStatus bool) []*types.Operation { - return toOperations(tx.GetMsgs(), hasError, withoutStatus) + return ToOperations(tx.GetMsgs(), hasError, withoutStatus) } // TendermintTxsToTxIdentifiers converts a tendermint raw transaction into a rosetta tx identifier @@ -85,7 +87,7 @@ func TendermintBlockToBlockIdentifier(block *tmcoretypes.ResultBlock) *types.Blo } } -func toOperations(msgs []sdk.Msg, hasError bool, withoutStatus bool) []*types.Operation { +func ToOperations(msgs []sdk.Msg, hasError bool, withoutStatus bool) []*types.Operation { var operations []*types.Operation for i, msg := range msgs { switch msg.Type() { // nolint @@ -132,6 +134,41 @@ func toOperations(msgs []sdk.Msg, hasError bool, withoutStatus bool) []*types.Op return operations } +// GetTransferTxDataFromOperations extracts the from and to addresses from a list of operations. +// We assume that it comes formated in the correct way. And that the balance of the sender is the same +// as the receiver operations. +func GetTransferTxDataFromOperations(ops []*types.Operation) (*banktypes.MsgSend, error) { + var ( + from, to sdk.AccAddress + sendAmt sdk.Coin + err error + ) + + for _, op := range ops { + if strings.HasPrefix(op.Amount.Value, "-") { + from, err = sdk.AccAddressFromBech32(op.Account.Address) + if err != nil { + return nil, err + } + } else { + to, err = sdk.AccAddressFromBech32(op.Account.Address) + if err != nil { + return nil, err + } + + amount, err := strconv.ParseInt(op.Amount.Value, 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid amount") + } + + sendAmt = sdk.NewCoin(op.Amount.Currency.Symbol, sdk.NewInt(amount)) + } + } + + msg := banktypes.NewMsgSend(from, to, sdk.NewCoins(sendAmt)) + return msg, nil +} + // TmPeersToRosettaPeers converts tendermint peers to rosetta ones func TmPeersToRosettaPeers(peers []tmcoretypes.Peer) []*types.Peer { converted := make([]*types.Peer, len(peers)) diff --git a/server/rosetta/errors.go b/server/rosetta/errors.go index eba2d7f73712..fcba7443e37d 100644 --- a/server/rosetta/errors.go +++ b/server/rosetta/errors.go @@ -128,7 +128,13 @@ var ( // ErrUnsupportedCurve is returned when the curve specified is not supported ErrUnsupportedCurve = NewError(15, "unsupported curve, expected secp256k1", false) // ErrInvalidPubkey is returned when the public key is invalid - ErrInvalidPubkey = NewError(8, "invalid pubkey", false) + ErrInvalidPubkey = NewError(8, "invalid pubkey", false) + ErrInterpreting = NewError(1, "error interpreting data from node", false) + ErrInvalidAddress = NewError(7, "invalid address", false) + ErrInvalidMemo = NewError(11, "invalid memo", false) + ErrInvalidOperation = NewError(4, "invalid operation", false) + ErrInvalidRequest = NewError(6, "invalid request", false) + ErrInvalidTransaction = NewError(5, "invalid transaction", false) ) // AllowedErrors lists all the rosetta allowed errors diff --git a/server/rosetta/services/construction.go b/server/rosetta/services/construction.go index c4219c25700e..25aaca8c23d4 100644 --- a/server/rosetta/services/construction.go +++ b/server/rosetta/services/construction.go @@ -2,21 +2,95 @@ package services import ( "context" + "crypto/sha256" + "encoding/hex" + "strings" + + "github.com/cosmos/cosmos-sdk/client" "github.com/btcsuite/btcd/btcec" "github.com/coinbase/rosetta-sdk-go/types" crg "github.com/tendermint/cosmos-rosetta-gateway/rosetta" + "github.com/tendermint/tendermint/crypto" - secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" "github.com/cosmos/cosmos-sdk/server/rosetta" + "github.com/cosmos/cosmos-sdk/server/rosetta/cosmos/conversion" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" ) // interface implementation assertion var _ crg.ConstructionAPI = SingleNetwork{} func (sn SingleNetwork) ConstructionCombine(ctx context.Context, request *types.ConstructionCombineRequest) (*types.ConstructionCombineResponse, *types.Error) { - return nil, rosetta.ErrNotImplemented.RosettaError() + txBldr, err := sn.getTxBuilderFromBytesTx(request.UnsignedTransaction) + if err != nil { + return nil, rosetta.ToRosettaError(err) + } + + var sigs = make([]signing.SignatureV2, len(request.Signatures)) + for i, signature := range request.Signatures { + if signature.PublicKey.CurveType != "secp256k1" { + return nil, rosetta.ErrUnsupportedCurve.RosettaError() + } + + cmp, err := btcec.ParsePubKey(signature.PublicKey.Bytes, btcec.S256()) + if err != nil { + return nil, rosetta.ToRosettaError(err) + } + + compressedPublicKey := make([]byte, secp256k1.PubKeySize) + copy(compressedPublicKey, cmp.SerializeCompressed()) + pubKey := &secp256k1.PubKey{Key: compressedPublicKey} + + accountInfo, err := sn.client.AccountInfo(ctx, sdk.AccAddress(pubKey.Address()).String(), nil) + if err != nil { + return nil, rosetta.ToRosettaError(err) + } + + sig := signing.SignatureV2{ + PubKey: pubKey, + Data: &signing.SingleSignatureData{ + SignMode: signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, + Signature: signature.Bytes, + }, + Sequence: accountInfo.GetSequence(), + } + sigs[i] = sig + } + + err = txBldr.SetSignatures(sigs...) + if err != nil { + return nil, rosetta.ToRosettaError(err) + } + + txBytes, err := sn.client.GetTxConfig().TxEncoder()(txBldr.GetTx()) + if err != nil { + return nil, rosetta.ToRosettaError(err) + } + + return &types.ConstructionCombineResponse{ + SignedTransaction: hex.EncodeToString(txBytes), + }, nil +} + +func (sn SingleNetwork) getTxBuilderFromBytesTx(tx string) (client.TxBuilder, error) { + txBytes, err := hex.DecodeString(tx) + if err != nil { + return nil, err + } + + TxConfig := sn.client.GetTxConfig() + rawTx, err := TxConfig.TxDecoder()(txBytes) + if err != nil { + return nil, err + } + + txBldr, _ := TxConfig.WrapTxBuilder(rawTx) + return txBldr, nil } func (sn SingleNetwork) ConstructionDerive(ctx context.Context, request *types.ConstructionDeriveRequest) (*types.ConstructionDeriveResponse, *types.Error) { @@ -32,33 +106,211 @@ func (sn SingleNetwork) ConstructionDerive(ctx context.Context, request *types.C compressedPublicKey := make([]byte, secp256k1.PubKeySize) copy(compressedPublicKey, cmp.SerializeCompressed()) + pk := secp256k1.PubKey{Key: compressedPublicKey} return &types.ConstructionDeriveResponse{ AccountIdentifier: &types.AccountIdentifier{ - Address: sdk.AccAddress(compressedPublicKey).String(), + Address: sdk.AccAddress(pk.Address()).String(), }, }, nil } func (sn SingleNetwork) ConstructionHash(ctx context.Context, request *types.ConstructionHashRequest) (*types.TransactionIdentifierResponse, *types.Error) { - return nil, rosetta.ErrNotImplemented.RosettaError() + bz, err := hex.DecodeString(request.SignedTransaction) + if err != nil { + return nil, rosetta.WrapError(rosetta.ErrInvalidTransaction, "error decoding tx").RosettaError() + } + + hash := sha256.Sum256(bz) + bzHash := hash[:] + + hashString := hex.EncodeToString(bzHash) + + return &types.TransactionIdentifierResponse{ + TransactionIdentifier: &types.TransactionIdentifier{ + Hash: strings.ToUpper(hashString), + }, + }, nil } func (sn SingleNetwork) ConstructionMetadata(ctx context.Context, request *types.ConstructionMetadataRequest) (*types.ConstructionMetadataResponse, *types.Error) { - return nil, rosetta.ErrNotImplemented.RosettaError() + if len(request.Options) == 0 { + return nil, rosetta.ErrInterpreting.RosettaError() + } + + addr, ok := request.Options[rosetta.OptionAddress] + if !ok { + return nil, rosetta.ErrInvalidAddress.RosettaError() + } + addrString := addr.(string) + accountInfo, err := sn.client.AccountInfo(ctx, addrString, nil) + if err != nil { + return nil, rosetta.ToRosettaError(err) + } + + gas, ok := request.Options[rosetta.OptionGas] + if !ok { + return nil, rosetta.WrapError(rosetta.ErrInvalidAddress, "gas not set").RosettaError() + } + + memo, ok := request.Options[rosetta.OptionMemo] + if !ok { + return nil, rosetta.WrapError(rosetta.ErrInvalidMemo, "memo not set").RosettaError() + } + + status, err := sn.client.Status(ctx) + if err != nil { + return nil, rosetta.ToRosettaError(err) + } + + res := &types.ConstructionMetadataResponse{ + Metadata: map[string]interface{}{ + rosetta.AccountNumber: accountInfo.GetAccountNumber(), + rosetta.Sequence: accountInfo.GetSequence(), + rosetta.ChainID: status.NodeInfo.Network, + rosetta.OptionGas: gas, + rosetta.OptionMemo: memo, + }, + } + + return res, nil } func (sn SingleNetwork) ConstructionParse(ctx context.Context, request *types.ConstructionParseRequest) (*types.ConstructionParseResponse, *types.Error) { - return nil, rosetta.ErrNotImplemented.RosettaError() + txBldr, err := sn.getTxBuilderFromBytesTx(request.Transaction) + if err != nil { + return nil, rosetta.ToRosettaError(err) + } + + var accountIdentifierSigners []*types.AccountIdentifier + if request.Signed { + addrs := txBldr.GetTx().GetSigners() + for _, addr := range addrs { + signer := &types.AccountIdentifier{ + Address: addr.String(), + } + accountIdentifierSigners = append(accountIdentifierSigners, signer) + } + } + + return &types.ConstructionParseResponse{ + Operations: conversion.ToOperations(txBldr.GetTx().GetMsgs(), false, true), + AccountIdentifierSigners: accountIdentifierSigners, + }, nil } func (sn SingleNetwork) ConstructionPayloads(ctx context.Context, request *types.ConstructionPayloadsRequest) (*types.ConstructionPayloadsResponse, *types.Error) { - return nil, rosetta.ErrNotImplemented.RosettaError() + if len(request.Operations) != 2 { + return nil, rosetta.ErrInvalidOperation.RosettaError() + } + + if request.Operations[0].Type != rosetta.OperationSend || request.Operations[1].Type != rosetta.OperationSend { + return nil, rosetta.WrapError(rosetta.ErrInvalidOperation, "the operations are not Transfer").RosettaError() + } + + sendMsg, err := conversion.GetTransferTxDataFromOperations(request.Operations) + if err != nil { + return nil, rosetta.WrapError(rosetta.ErrInvalidOperation, err.Error()).RosettaError() + } + + metadata, err := GetMetadataFromPayloadReq(request) + if err != nil { + return nil, rosetta.WrapError(rosetta.ErrInvalidRequest, err.Error()).RosettaError() + } + + txFactory := tx.Factory{}.WithAccountNumber(metadata.AccountNumber).WithChainID(metadata.ChainID). + WithGas(metadata.Gas).WithSequence(metadata.Sequence).WithMemo(metadata.Memo) + + TxConfig := sn.client.GetTxConfig() + txFactory = txFactory.WithTxConfig(TxConfig) + txBldr, err := tx.BuildUnsignedTx(txFactory, sendMsg) + if err != nil { + return nil, rosetta.ToRosettaError(err) + } + + if txFactory.SignMode() == signing.SignMode_SIGN_MODE_UNSPECIFIED { + txFactory = txFactory.WithSignMode(signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON) + } + + signerData := authsigning.SignerData{ + ChainID: txFactory.ChainID(), + AccountNumber: txFactory.AccountNumber(), + Sequence: txFactory.Sequence(), + } + + signBytes, err := TxConfig.SignModeHandler().GetSignBytes(txFactory.SignMode(), signerData, txBldr.GetTx()) + if err != nil { + return nil, rosetta.ToRosettaError(err) + } + + txBytes, err := TxConfig.TxEncoder()(txBldr.GetTx()) + if err != nil { + return nil, rosetta.ToRosettaError(err) + } + + return &types.ConstructionPayloadsResponse{ + UnsignedTransaction: hex.EncodeToString(txBytes), + Payloads: []*types.SigningPayload{ + { + AccountIdentifier: &types.AccountIdentifier{ + Address: sendMsg.FromAddress, + }, + Bytes: crypto.Sha256(signBytes), + SignatureType: "ecdsa", + }, + }, + }, nil } func (sn SingleNetwork) ConstructionPreprocess(ctx context.Context, request *types.ConstructionPreprocessRequest) (*types.ConstructionPreprocessResponse, *types.Error) { - return nil, rosetta.ErrNotImplemented.RosettaError() + operations := request.Operations + if len(operations) != 2 { + return nil, rosetta.ErrInterpreting.RosettaError() + } + + txData, err := conversion.GetTransferTxDataFromOperations(operations) + if err != nil { + return nil, rosetta.WrapError(rosetta.ErrInvalidAddress, err.Error()).RosettaError() + } + if txData.FromAddress == "" { + return nil, rosetta.WrapError(rosetta.ErrInvalidAddress, err.Error()).RosettaError() + } + + memo, ok := request.Metadata["memo"] + if !ok { + memo = "" + } + + defaultGas := float64(200000) + gas := request.SuggestedFeeMultiplier + if gas == nil { + gas = &defaultGas + } + var res = &types.ConstructionPreprocessResponse{ + Options: map[string]interface{}{ + rosetta.OptionAddress: txData.FromAddress, + rosetta.OptionMemo: memo, + rosetta.OptionGas: gas, + }, + } + return res, nil } func (sn SingleNetwork) ConstructionSubmit(ctx context.Context, request *types.ConstructionSubmitRequest) (*types.TransactionIdentifierResponse, *types.Error) { - return nil, rosetta.ErrNotImplemented.RosettaError() + txBytes, err := hex.DecodeString(request.SignedTransaction) + if err != nil { + return nil, rosetta.ToRosettaError(err) + } + + res, err := sn.client.PostTx(txBytes) + if err != nil { + return nil, rosetta.ToRosettaError(err) + } + return &types.TransactionIdentifierResponse{ + TransactionIdentifier: &types.TransactionIdentifier{ + Hash: res.TxHash, + }, + Metadata: map[string]interface{}{ + "log": res.RawLog, + }, + }, nil } diff --git a/server/rosetta/services/utils.go b/server/rosetta/services/utils.go new file mode 100644 index 000000000000..c2ab9de1e29f --- /dev/null +++ b/server/rosetta/services/utils.go @@ -0,0 +1,70 @@ +package services + +import ( + "fmt" + + "github.com/coinbase/rosetta-sdk-go/types" + + "github.com/cosmos/cosmos-sdk/server/rosetta" +) + +type PayloadReqMetadata struct { + ChainID string + Sequence uint64 + AccountNumber uint64 + Gas uint64 + Memo string +} + +// GetMetadataFromPayloadReq obtains the metadata from the request to /construction/payloads endpoint. +func GetMetadataFromPayloadReq(req *types.ConstructionPayloadsRequest) (*PayloadReqMetadata, error) { + chainID, ok := req.Metadata[rosetta.ChainID].(string) + if !ok { + return nil, fmt.Errorf("chain_id metadata was not provided") + } + + sequence, ok := req.Metadata[rosetta.Sequence] + if !ok { + return nil, fmt.Errorf("sequence metadata was not provided") + } + seqNum, ok := sequence.(float64) + if !ok { + return nil, fmt.Errorf("invalid sequence value") + } + + accountNum, ok := req.Metadata[rosetta.AccountNumber] + if !ok { + return nil, fmt.Errorf("account_number metadata was not provided") + } + accNum, ok := accountNum.(float64) + if !ok { + fmt.Printf("this is type %T", accountNum) + return nil, fmt.Errorf("invalid account_number value") + } + + gasNum, ok := req.Metadata[rosetta.OptionGas] + if !ok { + return nil, fmt.Errorf("gas metadata was not provided") + } + gasF64, ok := gasNum.(float64) + if !ok { + return nil, fmt.Errorf("invalid gas value") + } + + memo, ok := req.Metadata[rosetta.OptionMemo] + if !ok { + memo = "" + } + memoStr, ok := memo.(string) + if !ok { + return nil, fmt.Errorf("invalid memo") + } + + return &PayloadReqMetadata{ + ChainID: chainID, + Sequence: uint64(seqNum), + AccountNumber: uint64(accNum), + Gas: uint64(gasF64), + Memo: memoStr, + }, nil +} diff --git a/server/rosetta/types.go b/server/rosetta/types.go index 1c117fe06391..839aca1e39e6 100644 --- a/server/rosetta/types.go +++ b/server/rosetta/types.go @@ -8,13 +8,21 @@ import ( "github.com/tendermint/cosmos-rosetta-gateway/service" tmtypes "github.com/tendermint/tendermint/rpc/core/types" + "github.com/cosmos/cosmos-sdk/client" sdk "github.com/cosmos/cosmos-sdk/types" + auth "github.com/cosmos/cosmos-sdk/x/auth/types" ) // list of supported operations const ( StatusReverted = "Reverted" StatusSuccess = "Success" + OptionAddress = "address" + OptionGas = "gas" + OptionMemo = "memo" + Sequence = "sequence" + AccountNumber = "account_number" + ChainID = "chain_id" OperationSend = "send" ) @@ -30,6 +38,7 @@ func NewNetwork(networkIdentifier *types.NetworkIdentifier, adapter crg.Adapter) Properties: crg.NetworkProperties{ Blockchain: networkIdentifier.Blockchain, Network: networkIdentifier.Network, + AddrPrefix: sdk.GetConfig().GetBech32AccountAddrPrefix(), // since we're inside cosmos sdk the config is supposed to be sealed SupportedOperations: []string{OperationSend}, }, Adapter: adapter, @@ -46,6 +55,7 @@ type SdkTxWithHash struct { // a client has to implement in order to // interact with cosmos-sdk chains type DataAPIClient interface { + AccountInfo(ctx context.Context, addr string, height *int64) (auth.AccountI, error) // Balances fetches the balance of the given address // if height is not nil, then the balance will be displayed // at the provided height, otherwise last block balance will be returned @@ -67,6 +77,8 @@ type DataAPIClient interface { Peers(ctx context.Context) ([]tmtypes.Peer, error) // Status returns the node status, such as sync data, version etc Status(ctx context.Context) (*tmtypes.ResultStatus, error) + GetTxConfig() client.TxConfig + PostTx(txBytes []byte) (res *sdk.TxResponse, err error) } // Version returns the version for rosetta