diff --git a/pkg/rpc/client/rpc_test.go b/pkg/rpc/client/rpc_test.go
index 6f2ee6483..043e7a83f 100644
--- a/pkg/rpc/client/rpc_test.go
+++ b/pkg/rpc/client/rpc_test.go
@@ -27,8 +27,8 @@ import (
 	"github.com/nspcc-dev/neo-go/pkg/encoding/address"
 	"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
 	"github.com/nspcc-dev/neo-go/pkg/io"
-	"github.com/nspcc-dev/neo-go/pkg/rpc/request"
 	"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
+	"github.com/nspcc-dev/neo-go/pkg/rpc/server/params"
 	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
 	"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
 	"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
@@ -1816,7 +1816,7 @@ func initTestServer(t *testing.T, resp string) *httptest.Server {
 				if err != nil {
 					break
 				}
-				r := request.NewIn()
+				r := params.NewIn()
 				err = json.Unmarshal(p, r)
 				if err != nil {
 					t.Fatalf("Cannot decode request body: %s", req.Body)
@@ -1832,7 +1832,7 @@ func initTestServer(t *testing.T, resp string) *httptest.Server {
 			ws.Close()
 			return
 		}
-		r := request.NewRequest()
+		r := params.NewRequest()
 		err := r.DecodeData(req.Body)
 		if err != nil {
 			t.Fatalf("Cannot decode request body: %s", req.Body)
@@ -1845,7 +1845,7 @@ func initTestServer(t *testing.T, resp string) *httptest.Server {
 	return srv
 }
 
-func requestHandler(t *testing.T, r *request.In, w http.ResponseWriter, resp string) {
+func requestHandler(t *testing.T, r *params.In, w http.ResponseWriter, resp string) {
 	w.Header().Set("Content-Type", "application/json; charset=utf-8")
 	response := wrapInitResponse(r, resp)
 	_, err := w.Write([]byte(response))
@@ -1854,7 +1854,7 @@ func requestHandler(t *testing.T, r *request.In, w http.ResponseWriter, resp str
 	}
 }
 
-func wrapInitResponse(r *request.In, resp string) string {
+func wrapInitResponse(r *params.In, resp string) string {
 	var response string
 	switch r.Method {
 	case "getversion":
@@ -1862,7 +1862,7 @@ func wrapInitResponse(r *request.In, resp string) string {
 	case "getnativecontracts":
 		response = `{"jsonrpc":"2.0","id":1,"result":[{"id":-1,"hash":"0xfffdc93764dbaddd97c48f252a53ea4643faa3fd","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1110259869},"manifest":{"name":"ContractManagement","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"deploy","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"}],"returntype":"Array","offset":0,"safe":false},{"name":"deploy","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Array","offset":7,"safe":false},{"name":"destroy","parameters":[],"returntype":"Void","offset":14,"safe":false},{"name":"getContract","parameters":[{"name":"hash","type":"Hash160"}],"returntype":"Array","offset":21,"safe":true},{"name":"getMinimumDeploymentFee","parameters":[],"returntype":"Integer","offset":28,"safe":true},{"name":"setMinimumDeploymentFee","parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","offset":35,"safe":false},{"name":"update","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"}],"returntype":"Void","offset":42,"safe":false},{"name":"update","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","offset":49,"safe":false}],"events":[{"name":"Deploy","parameters":[{"name":"Hash","type":"Hash160"}]},{"name":"Update","parameters":[{"name":"Hash","type":"Hash160"}]},{"name":"Destroy","parameters":[{"name":"Hash","type":"Hash160"}]}]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null},"updatehistory":[0]},{"id":-2,"hash":"0xacce6fd80d44e1796aa0c2c625e9e4e0ce39efc0","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1325686241},"manifest":{"name":"StdLib","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"atoi","parameters":[{"name":"value","type":"String"}],"returntype":"Integer","offset":0,"safe":true},{"name":"atoi","parameters":[{"name":"value","type":"String"},{"name":"base","type":"Integer"}],"returntype":"Integer","offset":7,"safe":true},{"name":"base58CheckDecode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":14,"safe":true},{"name":"base58CheckEncode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":21,"safe":true},{"name":"base58Decode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":28,"safe":true},{"name":"base58Encode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":35,"safe":true},{"name":"base64Decode","parameters":[{"name":"s","type":"String"}],"returntype":"ByteArray","offset":42,"safe":true},{"name":"base64Encode","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"String","offset":49,"safe":true},{"name":"deserialize","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"Any","offset":56,"safe":true},{"name":"itoa","parameters":[{"name":"value","type":"Integer"}],"returntype":"String","offset":63,"safe":true},{"name":"itoa","parameters":[{"name":"value","type":"Integer"},{"name":"base","type":"Integer"}],"returntype":"String","offset":70,"safe":true},{"name":"jsonDeserialize","parameters":[{"name":"json","type":"ByteArray"}],"returntype":"Any","offset":77,"safe":true},{"name":"jsonSerialize","parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","offset":84,"safe":true},{"name":"memoryCompare","parameters":[{"name":"str1","type":"ByteArray"},{"name":"str2","type":"ByteArray"}],"returntype":"Integer","offset":91,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"}],"returntype":"Integer","offset":98,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"}],"returntype":"Integer","offset":105,"safe":true},{"name":"memorySearch","parameters":[{"name":"mem","type":"ByteArray"},{"name":"value","type":"ByteArray"},{"name":"start","type":"Integer"},{"name":"backward","type":"Boolean"}],"returntype":"Integer","offset":112,"safe":true},{"name":"serialize","parameters":[{"name":"item","type":"Any"}],"returntype":"ByteArray","offset":119,"safe":true},{"name":"stringSplit","parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"}],"returntype":"Array","offset":126,"safe":true},{"name":"stringSplit","parameters":[{"name":"str","type":"String"},{"name":"separator","type":"String"},{"name":"removeEmptyEntries","type":"Boolean"}],"returntype":"Array","offset":133,"safe":true}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null},"updatehistory":[0]},{"id":-3,"hash":"0x726cb6e0cd8628a1350a611384688911ab75f51b","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dA","checksum":1592866325},"manifest":{"name":"CryptoLib","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"ripemd160","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","offset":0,"safe":true},{"name":"sha256","parameters":[{"name":"data","type":"ByteArray"}],"returntype":"ByteArray","offset":7,"safe":true},{"name":"verifyWithECDsa","parameters":[{"name":"message","type":"ByteArray"},{"name":"pubkey","type":"ByteArray"},{"name":"signature","type":"ByteArray"},{"name":"curve","type":"Integer"}],"returntype":"Boolean","offset":14,"safe":true}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null},"updatehistory":[0]},{"id":-4,"hash":"0xda65b600f7124ce6c79950c1772a36403104f2be","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dA","checksum":529571427},"manifest":{"name":"LedgerContract","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"currentHash","parameters":[],"returntype":"Hash256","offset":0,"safe":true},{"name":"currentIndex","parameters":[],"returntype":"Integer","offset":7,"safe":true},{"name":"getBlock","parameters":[{"name":"indexOrHash","type":"ByteArray"}],"returntype":"Array","offset":14,"safe":true},{"name":"getTransaction","parameters":[{"name":"hash","type":"Hash256"}],"returntype":"Array","offset":21,"safe":true},{"name":"getTransactionFromBlock","parameters":[{"name":"blockIndexOrHash","type":"ByteArray"},{"name":"txIndex","type":"Integer"}],"returntype":"Array","offset":28,"safe":true},{"name":"getTransactionHeight","parameters":[{"name":"hash","type":"Hash256"}],"returntype":"Integer","offset":35,"safe":true}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null},"updatehistory":[0]},{"id":-5,"hash":"0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":588003825},"manifest":{"name":"NeoToken","groups":[],"features":{},"supportedstandards":["NEP-17"],"abi":{"methods":[{"name":"balanceOf","parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer","offset":0,"safe":true},{"name":"decimals","parameters":[],"returntype":"Integer","offset":7,"safe":true},{"name":"getAccountState","parameters":[{"name":"account","type":"Hash160"}],"returntype":"Array","offset":14,"safe":true},{"name":"getCandidates","parameters":[],"returntype":"Array","offset":21,"safe":true},{"name":"getCommittee","parameters":[],"returntype":"Array","offset":28,"safe":true},{"name":"getGasPerBlock","parameters":[],"returntype":"Integer","offset":35,"safe":true},{"name":"getNextBlockValidators","parameters":[],"returntype":"Array","offset":42,"safe":true},{"name":"getRegisterPrice","parameters":[],"returntype":"Integer","offset":49,"safe":true},{"name":"registerCandidate","parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean","offset":56,"safe":false},{"name":"setGasPerBlock","parameters":[{"name":"gasPerBlock","type":"Integer"}],"returntype":"Void","offset":63,"safe":false},{"name":"setRegisterPrice","parameters":[{"name":"registerPrice","type":"Integer"}],"returntype":"Void","offset":70,"safe":false},{"name":"symbol","parameters":[],"returntype":"String","offset":77,"safe":true},{"name":"totalSupply","parameters":[],"returntype":"Integer","offset":84,"safe":true},{"name":"transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Boolean","offset":91,"safe":false},{"name":"unclaimedGas","parameters":[{"name":"account","type":"Hash160"},{"name":"end","type":"Integer"}],"returntype":"Integer","offset":98,"safe":true},{"name":"unregisterCandidate","parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean","offset":105,"safe":false},{"name":"vote","parameters":[{"name":"account","type":"Hash160"},{"name":"voteTo","type":"PublicKey"}],"returntype":"Boolean","offset":112,"safe":false}],"events":[{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}]}]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null},"updatehistory":[0]},{"id":-6,"hash":"0xd2a4cff31913016155e38e474a2c06d08be276cf","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":2663858513},"manifest":{"name":"GasToken","groups":[],"features":{},"supportedstandards":["NEP-17"],"abi":{"methods":[{"name":"balanceOf","parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer","offset":0,"safe":true},{"name":"decimals","parameters":[],"returntype":"Integer","offset":7,"safe":true},{"name":"symbol","parameters":[],"returntype":"String","offset":14,"safe":true},{"name":"totalSupply","parameters":[],"returntype":"Integer","offset":21,"safe":true},{"name":"transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Boolean","offset":28,"safe":false}],"events":[{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}]}]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null},"updatehistory":[0]},{"id":-7,"hash":"0xcc5e4edd9f5f8dba8bb65734541df7a1c081c67b","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dA","checksum":3443651689},"manifest":{"name":"PolicyContract","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"blockAccount","parameters":[{"name":"account","type":"Hash160"}],"returntype":"Boolean","offset":0,"safe":false},{"name":"getExecFeeFactor","parameters":[],"returntype":"Integer","offset":7,"safe":true},{"name":"getFeePerByte","parameters":[],"returntype":"Integer","offset":14,"safe":true},{"name":"getStoragePrice","parameters":[],"returntype":"Integer","offset":21,"safe":true},{"name":"isBlocked","parameters":[{"name":"account","type":"Hash160"}],"returntype":"Boolean","offset":28,"safe":true},{"name":"setExecFeeFactor","parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","offset":35,"safe":false},{"name":"setFeePerByte","parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","offset":42,"safe":false},{"name":"setStoragePrice","parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","offset":49,"safe":false},{"name":"unblockAccount","parameters":[{"name":"account","type":"Hash160"}],"returntype":"Boolean","offset":56,"safe":false}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null},"updatehistory":[0]},{"id":-8,"hash":"0x49cf4e5378ffcd4dec034fd98a174c5491e395e2","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0A=","checksum":983638438},"manifest":{"name":"RoleManagement","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"designateAsRole","parameters":[{"name":"role","type":"Integer"},{"name":"nodes","type":"Array"}],"returntype":"Void","offset":0,"safe":false},{"name":"getDesignatedByRole","parameters":[{"name":"role","type":"Integer"},{"name":"index","type":"Integer"}],"returntype":"Array","offset":7,"safe":true}],"events":[{"name":"Designation","parameters":[{"name":"Role","type":"Integer"},{"name":"BlockIndex","type":"Integer"}]}]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null},"updatehistory":[0]},{"id":-9,"hash":"0xfe924b7cfe89ddd271abaf7210a80a7e11178758","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":2663858513},"manifest":{"name":"OracleContract","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"finish","parameters":[],"returntype":"Void","offset":0,"safe":false},{"name":"getPrice","parameters":[],"returntype":"Integer","offset":7,"safe":true},{"name":"request","parameters":[{"name":"url","type":"String"},{"name":"filter","type":"String"},{"name":"callback","type":"String"},{"name":"userData","type":"Any"},{"name":"gasForResponse","type":"Integer"}],"returntype":"Void","offset":14,"safe":false},{"name":"setPrice","parameters":[{"name":"price","type":"Integer"}],"returntype":"Void","offset":21,"safe":false},{"name":"verify","parameters":[],"returntype":"Boolean","offset":28,"safe":true}],"events":[{"name":"OracleRequest","parameters":[{"name":"Id","type":"Integer"},{"name":"RequestContract","type":"Hash160"},{"name":"Url","type":"String"},{"name":"Filter","type":"String"}]},{"name":"OracleResponse","parameters":[{"name":"Id","type":"Integer"},{"name":"OriginalTx","type":"Hash256"}]}]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null},"updatehistory":[0]}]}`
 	case "getcontractstate":
-		p := request.Params(r.RawParams)
+		p := params.Params(r.RawParams)
 		name, _ := p.Value(0).GetString()
 		switch name {
 		case "NeoToken":
@@ -1886,7 +1886,7 @@ func TestCalculateValidUntilBlock(t *testing.T) {
 		getValidatorsCalled int
 	)
 	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
-		r := request.NewRequest()
+		r := params.NewRequest()
 		err := r.DecodeData(req.Body)
 		if err != nil {
 			t.Fatalf("Cannot decode request body: %s", req.Body)
@@ -1929,7 +1929,7 @@ func TestCalculateValidUntilBlock(t *testing.T) {
 
 func TestGetNetwork(t *testing.T) {
 	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
-		r := request.NewRequest()
+		r := params.NewRequest()
 		err := r.DecodeData(req.Body)
 		if err != nil {
 			t.Fatalf("Cannot decode request body: %s", req.Body)
@@ -1968,7 +1968,7 @@ func TestGetNetwork(t *testing.T) {
 
 func TestUninitedClient(t *testing.T) {
 	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
-		r := request.NewRequest()
+		r := params.NewRequest()
 		err := r.DecodeData(req.Body)
 		require.NoErrorf(t, err, "Cannot decode request body: %s", req.Body)
 		// request handler already have `getversion` response wrapper
diff --git a/pkg/rpc/client/wsclient.go b/pkg/rpc/client/wsclient.go
index 14cd25f8e..4a36585f8 100644
--- a/pkg/rpc/client/wsclient.go
+++ b/pkg/rpc/client/wsclient.go
@@ -63,9 +63,9 @@ type Notification struct {
 // requestResponse is a combined type for request and response since we can get
 // any of them here.
 type requestResponse struct {
-	request.In
-	Error  *response.Error `json:"error,omitempty"`
-	Result json.RawMessage `json:"result,omitempty"`
+	response.Raw
+	Method    string            `json:"method"`
+	RawParams []json.RawMessage `json:"params,omitempty"`
 }
 
 const (
@@ -158,7 +158,7 @@ readloop:
 			connCloseErr = fmt.Errorf("failed to read JSON response (timeout/connection loss/malformed response): %w", err)
 			break readloop
 		}
-		if rr.RawID == nil && rr.Method != "" {
+		if rr.ID == nil && rr.Method != "" {
 			event, err := response.GetEventIDFromString(rr.Method)
 			if err != nil {
 				// Bad event received.
@@ -196,7 +196,7 @@ readloop:
 				break readloop
 			}
 			if event != response.MissedEventID {
-				err = json.Unmarshal(rr.RawParams[0].RawMessage, val)
+				err = json.Unmarshal(rr.RawParams[0], val)
 				if err != nil {
 					// Bad event received.
 					connCloseErr = fmt.Errorf("failed to unmarshal event of type %s from JSON: %w", event, err)
@@ -204,15 +204,10 @@ readloop:
 				}
 			}
 			c.Notifications <- Notification{event, val}
-		} else if rr.RawID != nil && (rr.Error != nil || rr.Result != nil) {
-			resp := new(response.Raw)
-			resp.ID = rr.RawID
-			resp.JSONRPC = rr.JSONRPC
-			resp.Error = rr.Error
-			resp.Result = rr.Result
-			id, err := strconv.Atoi(string(resp.ID))
+		} else if rr.ID != nil && (rr.Error != nil || rr.Result != nil) {
+			id, err := strconv.Atoi(string(rr.ID))
 			if err != nil {
-				connCloseErr = fmt.Errorf("failed to retrieve response ID from string %s: %w", string(resp.ID), err)
+				connCloseErr = fmt.Errorf("failed to retrieve response ID from string %s: %w", string(rr.ID), err)
 				break readloop // Malformed response (invalid response ID).
 			}
 			ch := c.getResponseChannel(uint64(id))
@@ -220,7 +215,7 @@ readloop:
 				connCloseErr = fmt.Errorf("unknown response channel for response %d", id)
 				break readloop // Unknown response (unexpected response ID).
 			}
-			ch <- resp
+			ch <- &rr.Raw
 		} else {
 			// Malformed response, neither valid request, nor valid response.
 			connCloseErr = fmt.Errorf("malformed response")
diff --git a/pkg/rpc/client/wsclient_test.go b/pkg/rpc/client/wsclient_test.go
index 73ea93ff6..b4d93fec7 100644
--- a/pkg/rpc/client/wsclient_test.go
+++ b/pkg/rpc/client/wsclient_test.go
@@ -18,6 +18,7 @@ import (
 	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
 	"github.com/nspcc-dev/neo-go/pkg/network/payload"
 	"github.com/nspcc-dev/neo-go/pkg/rpc/request"
+	"github.com/nspcc-dev/neo-go/pkg/rpc/server/params"
 	"github.com/nspcc-dev/neo-go/pkg/util"
 	"github.com/stretchr/testify/require"
 	"go.uber.org/atomic"
@@ -189,7 +190,7 @@ func TestWSFilteredSubscriptions(t *testing.T) {
 	var cases = []struct {
 		name       string
 		clientCode func(*testing.T, *WSClient)
-		serverCode func(*testing.T, *request.Params)
+		serverCode func(*testing.T, *params.Params)
 	}{
 		{"blocks",
 			func(t *testing.T, wsc *WSClient) {
@@ -197,7 +198,7 @@ func TestWSFilteredSubscriptions(t *testing.T) {
 				_, err := wsc.SubscribeForNewBlocks(&primary)
 				require.NoError(t, err)
 			},
-			func(t *testing.T, p *request.Params) {
+			func(t *testing.T, p *params.Params) {
 				param := p.Value(1)
 				filt := new(request.BlockFilter)
 				require.NoError(t, json.Unmarshal(param.RawMessage, filt))
@@ -210,7 +211,7 @@ func TestWSFilteredSubscriptions(t *testing.T) {
 				_, err := wsc.SubscribeForNewTransactions(&sender, nil)
 				require.NoError(t, err)
 			},
-			func(t *testing.T, p *request.Params) {
+			func(t *testing.T, p *params.Params) {
 				param := p.Value(1)
 				filt := new(request.TxFilter)
 				require.NoError(t, json.Unmarshal(param.RawMessage, filt))
@@ -224,7 +225,7 @@ func TestWSFilteredSubscriptions(t *testing.T) {
 				_, err := wsc.SubscribeForNewTransactions(nil, &signer)
 				require.NoError(t, err)
 			},
-			func(t *testing.T, p *request.Params) {
+			func(t *testing.T, p *params.Params) {
 				param := p.Value(1)
 				filt := new(request.TxFilter)
 				require.NoError(t, json.Unmarshal(param.RawMessage, filt))
@@ -239,7 +240,7 @@ func TestWSFilteredSubscriptions(t *testing.T) {
 				_, err := wsc.SubscribeForNewTransactions(&sender, &signer)
 				require.NoError(t, err)
 			},
-			func(t *testing.T, p *request.Params) {
+			func(t *testing.T, p *params.Params) {
 				param := p.Value(1)
 				filt := new(request.TxFilter)
 				require.NoError(t, json.Unmarshal(param.RawMessage, filt))
@@ -253,7 +254,7 @@ func TestWSFilteredSubscriptions(t *testing.T) {
 				_, err := wsc.SubscribeForExecutionNotifications(&contract, nil)
 				require.NoError(t, err)
 			},
-			func(t *testing.T, p *request.Params) {
+			func(t *testing.T, p *params.Params) {
 				param := p.Value(1)
 				filt := new(request.NotificationFilter)
 				require.NoError(t, json.Unmarshal(param.RawMessage, filt))
@@ -267,7 +268,7 @@ func TestWSFilteredSubscriptions(t *testing.T) {
 				_, err := wsc.SubscribeForExecutionNotifications(nil, &name)
 				require.NoError(t, err)
 			},
-			func(t *testing.T, p *request.Params) {
+			func(t *testing.T, p *params.Params) {
 				param := p.Value(1)
 				filt := new(request.NotificationFilter)
 				require.NoError(t, json.Unmarshal(param.RawMessage, filt))
@@ -282,7 +283,7 @@ func TestWSFilteredSubscriptions(t *testing.T) {
 				_, err := wsc.SubscribeForExecutionNotifications(&contract, &name)
 				require.NoError(t, err)
 			},
-			func(t *testing.T, p *request.Params) {
+			func(t *testing.T, p *params.Params) {
 				param := p.Value(1)
 				filt := new(request.NotificationFilter)
 				require.NoError(t, json.Unmarshal(param.RawMessage, filt))
@@ -296,7 +297,7 @@ func TestWSFilteredSubscriptions(t *testing.T) {
 				_, err := wsc.SubscribeForTransactionExecutions(&state)
 				require.NoError(t, err)
 			},
-			func(t *testing.T, p *request.Params) {
+			func(t *testing.T, p *params.Params) {
 				param := p.Value(1)
 				filt := new(request.ExecutionFilter)
 				require.NoError(t, json.Unmarshal(param.RawMessage, filt))
@@ -313,10 +314,10 @@ func TestWSFilteredSubscriptions(t *testing.T) {
 					require.NoError(t, err)
 					err = ws.SetReadDeadline(time.Now().Add(2 * time.Second))
 					require.NoError(t, err)
-					req := request.In{}
+					req := params.In{}
 					err = ws.ReadJSON(&req)
 					require.NoError(t, err)
-					params := request.Params(req.RawParams)
+					params := params.Params(req.RawParams)
 					c.serverCode(t, &params)
 					err = ws.SetWriteDeadline(time.Now().Add(2 * time.Second))
 					require.NoError(t, err)
@@ -370,7 +371,7 @@ func TestWSConcurrentAccess(t *testing.T) {
 				if err != nil {
 					break
 				}
-				r := request.NewIn()
+				r := params.NewIn()
 				err = json.Unmarshal(p, r)
 				if err != nil {
 					t.Fatalf("Cannot decode request body: %s", req.Body)
diff --git a/pkg/rpc/request/types.go b/pkg/rpc/request/types.go
index 1862649d1..7ef644862 100644
--- a/pkg/rpc/request/types.go
+++ b/pkg/rpc/request/types.go
@@ -1,19 +1,19 @@
 package request
 
 import (
-	"bytes"
 	"encoding/json"
-	"errors"
 	"fmt"
-	"io"
+	"strings"
+
+	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
+	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
+	"github.com/nspcc-dev/neo-go/pkg/encoding/address"
+	"github.com/nspcc-dev/neo-go/pkg/util"
 )
 
 const (
 	// JSONRPCVersion is the only JSON-RPC protocol version supported.
 	JSONRPCVersion = "2.0"
-
-	// maxBatchSize is the maximum number of requests per batch.
-	maxBatchSize = 100
 )
 
 // RawParams is just a slice of abstract values, used to represent parameters
@@ -40,97 +40,88 @@ type Raw struct {
 	ID        uint64        `json:"id"`
 }
 
-// Request contains standard JSON-RPC 2.0 request and batch of
-// requests: http://www.jsonrpc.org/specification.
-// It's used in server to represent incoming queries.
-type Request struct {
-	In    *In
-	Batch Batch
-}
+type (
+	// BlockFilter is a wrapper structure for the block event filter. The only
+	// allowed filter is primary index.
+	BlockFilter struct {
+		Primary int `json:"primary"`
+	}
+	// TxFilter is a wrapper structure for the transaction event filter. It
+	// allows to filter transactions by senders and signers.
+	TxFilter struct {
+		Sender *util.Uint160 `json:"sender,omitempty"`
+		Signer *util.Uint160 `json:"signer,omitempty"`
+	}
+	// NotificationFilter is a wrapper structure representing a filter used for
+	// notifications generated during transaction execution. Notifications can
+	// be filtered by contract hash and by name.
+	NotificationFilter struct {
+		Contract *util.Uint160 `json:"contract,omitempty"`
+		Name     *string       `json:"name,omitempty"`
+	}
+	// ExecutionFilter is a wrapper structure used for transaction execution
+	// events. It allows to choose failing or successful transactions based
+	// on their VM state.
+	ExecutionFilter struct {
+		State string `json:"state"`
+	}
+	// SignerWithWitness represents transaction's signer with the corresponding witness.
+	SignerWithWitness struct {
+		transaction.Signer
+		transaction.Witness
+	}
+)
 
-// In represents a standard JSON-RPC 2.0
-// request: http://www.jsonrpc.org/specification#request_object.
-type In struct {
-	JSONRPC   string          `json:"jsonrpc"`
-	Method    string          `json:"method"`
-	RawParams []Param         `json:"params,omitempty"`
-	RawID     json.RawMessage `json:"id,omitempty"`
+// signerWithWitnessAux is an auxiliary struct for JSON marshalling. We need it because of
+// DisallowUnknownFields JSON marshaller setting.
+type signerWithWitnessAux struct {
+	Account            string                    `json:"account"`
+	Scopes             transaction.WitnessScope  `json:"scopes"`
+	AllowedContracts   []util.Uint160            `json:"allowedcontracts,omitempty"`
+	AllowedGroups      []*keys.PublicKey         `json:"allowedgroups,omitempty"`
+	Rules              []transaction.WitnessRule `json:"rules,omitempty"`
+	InvocationScript   []byte                    `json:"invocation,omitempty"`
+	VerificationScript []byte                    `json:"verification,omitempty"`
 }
 
-// Batch represents a standard JSON-RPC 2.0
-// batch: https://www.jsonrpc.org/specification#batch.
-type Batch []In
-
 // MarshalJSON implements the json.Marshaler interface.
-func (r Request) MarshalJSON() ([]byte, error) {
-	if r.In != nil {
-		return json.Marshal(r.In)
+func (s *SignerWithWitness) MarshalJSON() ([]byte, error) {
+	signer := &signerWithWitnessAux{
+		Account:            s.Account.StringLE(),
+		Scopes:             s.Scopes,
+		AllowedContracts:   s.AllowedContracts,
+		AllowedGroups:      s.AllowedGroups,
+		Rules:              s.Rules,
+		InvocationScript:   s.InvocationScript,
+		VerificationScript: s.VerificationScript,
 	}
-	return json.Marshal(r.Batch)
+	return json.Marshal(signer)
 }
 
 // UnmarshalJSON implements the json.Unmarshaler interface.
-func (r *Request) UnmarshalJSON(data []byte) error {
-	var (
-		in    *In
-		batch Batch
-	)
-	in = &In{}
-	err := json.Unmarshal(data, in)
-	if err == nil {
-		r.In = in
-		return nil
-	}
-	decoder := json.NewDecoder(bytes.NewReader(data))
-	t, err := decoder.Token() // read `[`
+func (s *SignerWithWitness) UnmarshalJSON(data []byte) error {
+	aux := new(signerWithWitnessAux)
+	err := json.Unmarshal(data, aux)
 	if err != nil {
-		return err
+		return fmt.Errorf("not a signer: %w", err)
 	}
-	if t != json.Delim('[') {
-		return fmt.Errorf("`[` expected, got %s", t)
+	acc, err := util.Uint160DecodeStringLE(strings.TrimPrefix(aux.Account, "0x"))
+	if err != nil {
+		acc, err = address.StringToUint160(aux.Account)
 	}
-	count := 0
-	for decoder.More() {
-		if count > maxBatchSize {
-			return fmt.Errorf("the number of requests in batch shouldn't exceed %d", maxBatchSize)
-		}
-		in = &In{}
-		decodeErr := decoder.Decode(in)
-		if decodeErr != nil {
-			return decodeErr
-		}
-		batch = append(batch, *in)
-		count++
+	if err != nil {
+		return fmt.Errorf("not a signer: %w", err)
 	}
-	if len(batch) == 0 {
-		return errors.New("empty request")
+	s.Signer = transaction.Signer{
+		Account:          acc,
+		Scopes:           aux.Scopes,
+		AllowedContracts: aux.AllowedContracts,
+		AllowedGroups:    aux.AllowedGroups,
+		Rules:            aux.Rules,
+	}
+	s.Witness = transaction.Witness{
+		InvocationScript:   aux.InvocationScript,
+		VerificationScript: aux.VerificationScript,
 	}
-	r.Batch = batch
 	return nil
 }
-
-// DecodeData decodes the given reader into the the request
-// struct.
-func (r *Request) DecodeData(data io.ReadCloser) error {
-	defer data.Close()
-
-	rawData := json.RawMessage{}
-	err := json.NewDecoder(data).Decode(&rawData)
-	if err != nil {
-		return fmt.Errorf("error parsing JSON payload: %w", err)
-	}
-
-	return r.UnmarshalJSON(rawData)
-}
-
-// NewRequest creates a new Request struct.
-func NewRequest() *Request {
-	return &Request{}
-}
-
-// NewIn creates a new In struct.
-func NewIn() *In {
-	return &In{
-		JSONRPC: JSONRPCVersion,
-	}
-}
diff --git a/pkg/rpc/request/param.go b/pkg/rpc/server/params/param.go
similarity index 74%
rename from pkg/rpc/request/param.go
rename to pkg/rpc/server/params/param.go
index 429955ce8..b4dfcd4da 100644
--- a/pkg/rpc/request/param.go
+++ b/pkg/rpc/server/params/param.go
@@ -1,4 +1,4 @@
-package request
+package params
 
 import (
 	"bytes"
@@ -13,8 +13,8 @@ import (
 
 	"github.com/google/uuid"
 	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
-	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
 	"github.com/nspcc-dev/neo-go/pkg/encoding/address"
+	"github.com/nspcc-dev/neo-go/pkg/rpc/request"
 	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
 	"github.com/nspcc-dev/neo-go/pkg/util"
 )
@@ -34,35 +34,6 @@ type (
 		Type  smartcontract.ParamType `json:"type"`
 		Value Param                   `json:"value"`
 	}
-	// BlockFilter is a wrapper structure for the block event filter. The only
-	// allowed filter is primary index.
-	BlockFilter struct {
-		Primary int `json:"primary"`
-	}
-	// TxFilter is a wrapper structure for the transaction event filter. It
-	// allows to filter transactions by senders and signers.
-	TxFilter struct {
-		Sender *util.Uint160 `json:"sender,omitempty"`
-		Signer *util.Uint160 `json:"signer,omitempty"`
-	}
-	// NotificationFilter is a wrapper structure representing a filter used for
-	// notifications generated during transaction execution. Notifications can
-	// be filtered by contract hash and by name.
-	NotificationFilter struct {
-		Contract *util.Uint160 `json:"contract,omitempty"`
-		Name     *string       `json:"name,omitempty"`
-	}
-	// ExecutionFilter is a wrapper structure used for transaction execution
-	// events. It allows to choose failing or successful transactions based
-	// on their VM state.
-	ExecutionFilter struct {
-		State string `json:"state"`
-	}
-	// SignerWithWitness represents transaction's signer with the corresponding witness.
-	SignerWithWitness struct {
-		transaction.Signer
-		transaction.Witness
-	}
 )
 
 var (
@@ -409,33 +380,13 @@ func (p *Param) GetBytesBase64() ([]byte, error) {
 	return base64.StdEncoding.DecodeString(s)
 }
 
-// GetSignerWithWitness returns a SignerWithWitness value of the parameter.
-func (p *Param) GetSignerWithWitness() (SignerWithWitness, error) {
+// GetSignerWithWitness returns a request.SignerWithWitness value of the parameter.
+func (p *Param) GetSignerWithWitness() (request.SignerWithWitness, error) {
 	// This one doesn't need to be cached, it's used only once.
-	aux := new(signerWithWitnessAux)
-	err := json.Unmarshal(p.RawMessage, aux)
+	c := request.SignerWithWitness{}
+	err := json.Unmarshal(p.RawMessage, &c)
 	if err != nil {
-		return SignerWithWitness{}, fmt.Errorf("not a signer: %w", err)
-	}
-	acc, err := util.Uint160DecodeStringLE(strings.TrimPrefix(aux.Account, "0x"))
-	if err != nil {
-		acc, err = address.StringToUint160(aux.Account)
-	}
-	if err != nil {
-		return SignerWithWitness{}, fmt.Errorf("not a signer: %w", err)
-	}
-	c := SignerWithWitness{
-		Signer: transaction.Signer{
-			Account:          acc,
-			Scopes:           aux.Scopes,
-			AllowedContracts: aux.AllowedContracts,
-			AllowedGroups:    aux.AllowedGroups,
-			Rules:            aux.Rules,
-		},
-		Witness: transaction.Witness{
-			InvocationScript:   aux.InvocationScript,
-			VerificationScript: aux.VerificationScript,
-		},
+		return request.SignerWithWitness{}, fmt.Errorf("not a signer: %w", err)
 	}
 	return c, nil
 }
@@ -480,32 +431,6 @@ func (p *Param) IsNull() bool {
 	return bytes.Equal(p.RawMessage, jsonNullBytes)
 }
 
-// signerWithWitnessAux is an auxiliary struct for JSON marshalling. We need it because of
-// DisallowUnknownFields JSON marshaller setting.
-type signerWithWitnessAux struct {
-	Account            string                    `json:"account"`
-	Scopes             transaction.WitnessScope  `json:"scopes"`
-	AllowedContracts   []util.Uint160            `json:"allowedcontracts,omitempty"`
-	AllowedGroups      []*keys.PublicKey         `json:"allowedgroups,omitempty"`
-	Rules              []transaction.WitnessRule `json:"rules,omitempty"`
-	InvocationScript   []byte                    `json:"invocation,omitempty"`
-	VerificationScript []byte                    `json:"verification,omitempty"`
-}
-
-// MarshalJSON implements the json.Marshaler interface.
-func (s *SignerWithWitness) MarshalJSON() ([]byte, error) {
-	signer := &signerWithWitnessAux{
-		Account:            s.Account.StringLE(),
-		Scopes:             s.Scopes,
-		AllowedContracts:   s.AllowedContracts,
-		AllowedGroups:      s.AllowedGroups,
-		Rules:              s.Rules,
-		InvocationScript:   s.InvocationScript,
-		VerificationScript: s.VerificationScript,
-	}
-	return json.Marshal(signer)
-}
-
 // GetUUID returns UUID from parameter.
 func (p *Param) GetUUID() (uuid.UUID, error) {
 	s, err := p.GetString()
diff --git a/pkg/rpc/request/param_test.go b/pkg/rpc/server/params/param_test.go
similarity index 96%
rename from pkg/rpc/request/param_test.go
rename to pkg/rpc/server/params/param_test.go
index 59e0a2bcb..44227ab59 100644
--- a/pkg/rpc/request/param_test.go
+++ b/pkg/rpc/server/params/param_test.go
@@ -1,4 +1,4 @@
-package request
+package params
 
 import (
 	"encoding/base64"
@@ -12,6 +12,7 @@ import (
 
 	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
 	"github.com/nspcc-dev/neo-go/pkg/encoding/address"
+	"github.com/nspcc-dev/neo-go/pkg/rpc/request"
 	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
 	"github.com/nspcc-dev/neo-go/pkg/util"
 	"github.com/stretchr/testify/assert"
@@ -154,7 +155,7 @@ func TestParam_UnmarshalJSON(t *testing.T) {
 				require.NoError(t, err)
 				expectedAcc, err := util.Uint160DecodeStringLE("cadb3dc2faa3ef14a13b619c9a43124755aa2569")
 				require.NoError(t, err)
-				require.Equal(t, SignerWithWitness{Signer: transaction.Signer{Account: expectedAcc}}, actual)
+				require.Equal(t, request.SignerWithWitness{Signer: transaction.Signer{Account: expectedAcc}}, actual)
 			},
 			expectedRawMessage: []byte(`{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569"}`),
 		},
@@ -164,7 +165,7 @@ func TestParam_UnmarshalJSON(t *testing.T) {
 				require.NoError(t, err)
 				expectedAcc, err := address.StringToUint160("NYxb4fSZVKAz8YsgaPK2WkT3KcAE9b3Vag")
 				require.NoError(t, err)
-				require.Equal(t, SignerWithWitness{Signer: transaction.Signer{Account: expectedAcc, Scopes: transaction.Global}}, actual)
+				require.Equal(t, request.SignerWithWitness{Signer: transaction.Signer{Account: expectedAcc, Scopes: transaction.Global}}, actual)
 			},
 			expectedRawMessage: []byte(`{"account": "NYxb4fSZVKAz8YsgaPK2WkT3KcAE9b3Vag", "scopes": "Global"}`),
 		},
@@ -243,21 +244,21 @@ func TestGetWitness(t *testing.T) {
 
 	testCases := []struct {
 		raw      string
-		expected SignerWithWitness
+		expected request.SignerWithWitness
 	}{
-		{`{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569"}`, SignerWithWitness{
+		{`{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569"}`, request.SignerWithWitness{
 			Signer: transaction.Signer{
 				Account: accountHash,
 				Scopes:  transaction.None,
 			}},
 		},
-		{`{"account": "NYxb4fSZVKAz8YsgaPK2WkT3KcAE9b3Vag", "scopes": "Global"}`, SignerWithWitness{
+		{`{"account": "NYxb4fSZVKAz8YsgaPK2WkT3KcAE9b3Vag", "scopes": "Global"}`, request.SignerWithWitness{
 			Signer: transaction.Signer{
 				Account: addrHash,
 				Scopes:  transaction.Global,
 			}},
 		},
-		{`{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": "Global"}`, SignerWithWitness{
+		{`{"account": "0xcadb3dc2faa3ef14a13b619c9a43124755aa2569", "scopes": "Global"}`, request.SignerWithWitness{
 			Signer: transaction.Signer{
 				Account: accountHash,
 				Scopes:  transaction.Global,
@@ -404,7 +405,7 @@ func TestParamGetBytesBase64(t *testing.T) {
 }
 
 func TestParamGetSigner(t *testing.T) {
-	c := SignerWithWitness{
+	c := request.SignerWithWitness{
 		Signer: transaction.Signer{
 			Account: util.Uint160{1, 2, 3, 4},
 			Scopes:  transaction.Global,
diff --git a/pkg/rpc/request/params.go b/pkg/rpc/server/params/params.go
similarity index 95%
rename from pkg/rpc/request/params.go
rename to pkg/rpc/server/params/params.go
index 9ba8fb159..92a197f8d 100644
--- a/pkg/rpc/request/params.go
+++ b/pkg/rpc/server/params/params.go
@@ -1,4 +1,4 @@
-package request
+package params
 
 import "fmt"
 
diff --git a/pkg/rpc/request/txBuilder.go b/pkg/rpc/server/params/txBuilder.go
similarity index 99%
rename from pkg/rpc/request/txBuilder.go
rename to pkg/rpc/server/params/txBuilder.go
index 84215314d..2c8272739 100644
--- a/pkg/rpc/request/txBuilder.go
+++ b/pkg/rpc/server/params/txBuilder.go
@@ -1,4 +1,4 @@
-package request
+package params
 
 import (
 	"errors"
diff --git a/pkg/rpc/request/tx_builder_test.go b/pkg/rpc/server/params/tx_builder_test.go
similarity index 99%
rename from pkg/rpc/request/tx_builder_test.go
rename to pkg/rpc/server/params/tx_builder_test.go
index 1a5fd4f1a..983e31abb 100644
--- a/pkg/rpc/request/tx_builder_test.go
+++ b/pkg/rpc/server/params/tx_builder_test.go
@@ -1,4 +1,4 @@
-package request
+package params
 
 import (
 	"encoding/base64"
diff --git a/pkg/rpc/server/params/types.go b/pkg/rpc/server/params/types.go
new file mode 100644
index 000000000..48e3403eb
--- /dev/null
+++ b/pkg/rpc/server/params/types.go
@@ -0,0 +1,111 @@
+package params
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+
+	"github.com/nspcc-dev/neo-go/pkg/rpc/request"
+)
+
+const (
+	// maxBatchSize is the maximum number of requests per batch.
+	maxBatchSize = 100
+)
+
+// Request contains standard JSON-RPC 2.0 request and batch of
+// requests: http://www.jsonrpc.org/specification.
+// It's used in server to represent incoming queries.
+type Request struct {
+	In    *In
+	Batch Batch
+}
+
+// In represents a standard JSON-RPC 2.0
+// request: http://www.jsonrpc.org/specification#request_object.
+type In struct {
+	JSONRPC   string          `json:"jsonrpc"`
+	Method    string          `json:"method"`
+	RawParams []Param         `json:"params,omitempty"`
+	RawID     json.RawMessage `json:"id,omitempty"`
+}
+
+// Batch represents a standard JSON-RPC 2.0
+// batch: https://www.jsonrpc.org/specification#batch.
+type Batch []In
+
+// MarshalJSON implements the json.Marshaler interface.
+func (r Request) MarshalJSON() ([]byte, error) {
+	if r.In != nil {
+		return json.Marshal(r.In)
+	}
+	return json.Marshal(r.Batch)
+}
+
+// UnmarshalJSON implements the json.Unmarshaler interface.
+func (r *Request) UnmarshalJSON(data []byte) error {
+	var (
+		in    *In
+		batch Batch
+	)
+	in = &In{}
+	err := json.Unmarshal(data, in)
+	if err == nil {
+		r.In = in
+		return nil
+	}
+	decoder := json.NewDecoder(bytes.NewReader(data))
+	t, err := decoder.Token() // read `[`
+	if err != nil {
+		return err
+	}
+	if t != json.Delim('[') {
+		return fmt.Errorf("`[` expected, got %s", t)
+	}
+	count := 0
+	for decoder.More() {
+		if count > maxBatchSize {
+			return fmt.Errorf("the number of requests in batch shouldn't exceed %d", maxBatchSize)
+		}
+		in = &In{}
+		decodeErr := decoder.Decode(in)
+		if decodeErr != nil {
+			return decodeErr
+		}
+		batch = append(batch, *in)
+		count++
+	}
+	if len(batch) == 0 {
+		return errors.New("empty request")
+	}
+	r.Batch = batch
+	return nil
+}
+
+// DecodeData decodes the given reader into the the request
+// struct.
+func (r *Request) DecodeData(data io.ReadCloser) error {
+	defer data.Close()
+
+	rawData := json.RawMessage{}
+	err := json.NewDecoder(data).Decode(&rawData)
+	if err != nil {
+		return fmt.Errorf("error parsing JSON payload: %w", err)
+	}
+
+	return r.UnmarshalJSON(rawData)
+}
+
+// NewRequest creates a new Request struct.
+func NewRequest() *Request {
+	return &Request{}
+}
+
+// NewIn creates a new In struct.
+func NewIn() *In {
+	return &In{
+		JSONRPC: request.JSONRPCVersion,
+	}
+}
diff --git a/pkg/rpc/request/types_test.go b/pkg/rpc/server/params/types_test.go
similarity index 98%
rename from pkg/rpc/request/types_test.go
rename to pkg/rpc/server/params/types_test.go
index aabbb27fe..d4bab190b 100644
--- a/pkg/rpc/request/types_test.go
+++ b/pkg/rpc/server/params/types_test.go
@@ -1,4 +1,4 @@
-package request
+package params
 
 import (
 	"bytes"
diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go
index 39ed8a909..cd8460e48 100644
--- a/pkg/rpc/server/server.go
+++ b/pkg/rpc/server/server.go
@@ -44,6 +44,7 @@ import (
 	"github.com/nspcc-dev/neo-go/pkg/rpc/response"
 	"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
 	"github.com/nspcc-dev/neo-go/pkg/rpc/response/result/subscriptions"
+	"github.com/nspcc-dev/neo-go/pkg/rpc/server/params"
 	"github.com/nspcc-dev/neo-go/pkg/services/oracle"
 	"github.com/nspcc-dev/neo-go/pkg/services/oracle/broadcaster"
 	"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
@@ -148,7 +149,7 @@ const (
 	defaultSessionPoolSize = 20
 )
 
-var rpcHandlers = map[string]func(*Server, request.Params) (interface{}, *response.Error){
+var rpcHandlers = map[string]func(*Server, params.Params) (interface{}, *response.Error){
 	"calculatenetworkfee":          (*Server).calculateNetworkFee,
 	"findstates":                   (*Server).findStates,
 	"getapplicationlog":            (*Server).getApplicationLog,
@@ -197,7 +198,7 @@ var rpcHandlers = map[string]func(*Server, request.Params) (interface{}, *respon
 	"verifyproof":                  (*Server).verifyProof,
 }
 
-var rpcWsHandlers = map[string]func(*Server, request.Params, *subscriber) (interface{}, *response.Error){
+var rpcWsHandlers = map[string]func(*Server, params.Params, *subscriber) (interface{}, *response.Error){
 	"subscribe":   (*Server).subscribe,
 	"unsubscribe": (*Server).unsubscribe,
 }
@@ -366,7 +367,7 @@ func (s *Server) Shutdown() {
 }
 
 func (s *Server) handleHTTPRequest(w http.ResponseWriter, httpRequest *http.Request) {
-	req := request.NewRequest()
+	req := params.NewRequest()
 
 	if httpRequest.URL.Path == "/ws" && httpRequest.Method == "GET" {
 		// Technically there is a race between this check and
@@ -378,7 +379,7 @@ func (s *Server) handleHTTPRequest(w http.ResponseWriter, httpRequest *http.Requ
 		s.subsLock.RUnlock()
 		if numOfSubs >= maxSubscribers {
 			s.writeHTTPErrorResponse(
-				request.NewIn(),
+				params.NewIn(),
 				w,
 				response.NewInternalServerError("websocket users limit reached"),
 			)
@@ -402,7 +403,7 @@ func (s *Server) handleHTTPRequest(w http.ResponseWriter, httpRequest *http.Requ
 
 	if httpRequest.Method != "POST" {
 		s.writeHTTPErrorResponse(
-			request.NewIn(),
+			params.NewIn(),
 			w,
 			response.NewInvalidParamsError(fmt.Sprintf("invalid method '%s', please retry with 'POST'", httpRequest.Method)),
 		)
@@ -411,7 +412,7 @@ func (s *Server) handleHTTPRequest(w http.ResponseWriter, httpRequest *http.Requ
 
 	err := req.DecodeData(httpRequest.Body)
 	if err != nil {
-		s.writeHTTPErrorResponse(request.NewIn(), w, response.NewParseError(err.Error()))
+		s.writeHTTPErrorResponse(params.NewIn(), w, response.NewParseError(err.Error()))
 		return
 	}
 
@@ -419,7 +420,7 @@ func (s *Server) handleHTTPRequest(w http.ResponseWriter, httpRequest *http.Requ
 	s.writeHTTPServerResponse(req, w, resp)
 }
 
-func (s *Server) handleRequest(req *request.Request, sub *subscriber) abstractResult {
+func (s *Server) handleRequest(req *params.Request, sub *subscriber) abstractResult {
 	if req.In != nil {
 		req.In.Method = escapeForLog(req.In.Method) // No valid method name will be changed by it.
 		return s.handleIn(req.In, sub)
@@ -432,14 +433,14 @@ func (s *Server) handleRequest(req *request.Request, sub *subscriber) abstractRe
 	return resp
 }
 
-func (s *Server) handleIn(req *request.In, sub *subscriber) abstract {
+func (s *Server) handleIn(req *params.In, sub *subscriber) abstract {
 	var res interface{}
 	var resErr *response.Error
 	if req.JSONRPC != request.JSONRPCVersion {
 		return s.packResponse(req, nil, response.NewInvalidParamsError(fmt.Sprintf("problem parsing JSON: invalid version, expected 2.0 got '%s'", req.JSONRPC)))
 	}
 
-	reqParams := request.Params(req.RawParams)
+	reqParams := params.Params(req.RawParams)
 
 	s.log.Debug("processing rpc request",
 		zap.String("method", req.Method),
@@ -519,7 +520,7 @@ func (s *Server) handleWsReads(ws *websocket.Conn, resChan chan<- abstractResult
 	ws.SetPongHandler(func(string) error { return ws.SetReadDeadline(time.Now().Add(wsPongLimit)) })
 requestloop:
 	for err == nil {
-		req := request.NewRequest()
+		req := params.NewRequest()
 		err := ws.ReadJSON(req)
 		if err != nil {
 			break
@@ -546,23 +547,23 @@ requestloop:
 	ws.Close()
 }
 
-func (s *Server) getBestBlockHash(_ request.Params) (interface{}, *response.Error) {
+func (s *Server) getBestBlockHash(_ params.Params) (interface{}, *response.Error) {
 	return "0x" + s.chain.CurrentBlockHash().StringLE(), nil
 }
 
-func (s *Server) getBlockCount(_ request.Params) (interface{}, *response.Error) {
+func (s *Server) getBlockCount(_ params.Params) (interface{}, *response.Error) {
 	return s.chain.BlockHeight() + 1, nil
 }
 
-func (s *Server) getBlockHeaderCount(_ request.Params) (interface{}, *response.Error) {
+func (s *Server) getBlockHeaderCount(_ params.Params) (interface{}, *response.Error) {
 	return s.chain.HeaderHeight() + 1, nil
 }
 
-func (s *Server) getConnectionCount(_ request.Params) (interface{}, *response.Error) {
+func (s *Server) getConnectionCount(_ params.Params) (interface{}, *response.Error) {
 	return s.coreServer.PeerCount(), nil
 }
 
-func (s *Server) blockHashFromParam(param *request.Param) (util.Uint256, *response.Error) {
+func (s *Server) blockHashFromParam(param *params.Param) (util.Uint256, *response.Error) {
 	var (
 		hash util.Uint256
 		err  error
@@ -581,7 +582,7 @@ func (s *Server) blockHashFromParam(param *request.Param) (util.Uint256, *respon
 	return hash, nil
 }
 
-func (s *Server) getBlock(reqParams request.Params) (interface{}, *response.Error) {
+func (s *Server) getBlock(reqParams params.Params) (interface{}, *response.Error) {
 	param := reqParams.Value(0)
 	hash, respErr := s.blockHashFromParam(param)
 	if respErr != nil {
@@ -601,7 +602,7 @@ func (s *Server) getBlock(reqParams request.Params) (interface{}, *response.Erro
 	return writer.Bytes(), nil
 }
 
-func (s *Server) getBlockHash(reqParams request.Params) (interface{}, *response.Error) {
+func (s *Server) getBlockHash(reqParams params.Params) (interface{}, *response.Error) {
 	num, err := s.blockHeightFromParam(reqParams.Value(0))
 	if err != nil {
 		return nil, response.ErrInvalidParams
@@ -610,7 +611,7 @@ func (s *Server) getBlockHash(reqParams request.Params) (interface{}, *response.
 	return s.chain.GetHeaderHash(num), nil
 }
 
-func (s *Server) getVersion(_ request.Params) (interface{}, *response.Error) {
+func (s *Server) getVersion(_ params.Params) (interface{}, *response.Error) {
 	port, err := s.coreServer.Port()
 	if err != nil {
 		return nil, response.NewInternalServerError(fmt.Sprintf("cannot fetch tcp port: %s", err))
@@ -638,7 +639,7 @@ func (s *Server) getVersion(_ request.Params) (interface{}, *response.Error) {
 	}, nil
 }
 
-func (s *Server) getPeers(_ request.Params) (interface{}, *response.Error) {
+func (s *Server) getPeers(_ params.Params) (interface{}, *response.Error) {
 	peers := result.NewGetPeers()
 	peers.AddUnconnected(s.coreServer.UnconnectedPeers())
 	peers.AddConnected(s.coreServer.ConnectedPeers())
@@ -646,7 +647,7 @@ func (s *Server) getPeers(_ request.Params) (interface{}, *response.Error) {
 	return peers, nil
 }
 
-func (s *Server) getRawMempool(reqParams request.Params) (interface{}, *response.Error) {
+func (s *Server) getRawMempool(reqParams params.Params) (interface{}, *response.Error) {
 	verbose, _ := reqParams.Value(0).GetBoolean()
 	mp := s.chain.GetMemPool()
 	hashList := make([]util.Uint256, 0)
@@ -663,7 +664,7 @@ func (s *Server) getRawMempool(reqParams request.Params) (interface{}, *response
 	}, nil
 }
 
-func (s *Server) validateAddress(reqParams request.Params) (interface{}, *response.Error) {
+func (s *Server) validateAddress(reqParams params.Params) (interface{}, *response.Error) {
 	param, err := reqParams.Value(0).GetString()
 	if err != nil {
 		return nil, response.ErrInvalidParams
@@ -676,7 +677,7 @@ func (s *Server) validateAddress(reqParams request.Params) (interface{}, *respon
 }
 
 // calculateNetworkFee calculates network fee for the transaction.
-func (s *Server) calculateNetworkFee(reqParams request.Params) (interface{}, *response.Error) {
+func (s *Server) calculateNetworkFee(reqParams params.Params) (interface{}, *response.Error) {
 	if len(reqParams) < 1 {
 		return 0, response.ErrInvalidParams
 	}
@@ -730,7 +731,7 @@ func (s *Server) calculateNetworkFee(reqParams request.Params) (interface{}, *re
 }
 
 // getApplicationLog returns the contract log based on the specified txid or blockid.
-func (s *Server) getApplicationLog(reqParams request.Params) (interface{}, *response.Error) {
+func (s *Server) getApplicationLog(reqParams params.Params) (interface{}, *response.Error) {
 	hash, err := reqParams.Value(0).GetUint256()
 	if err != nil {
 		return nil, response.ErrInvalidParams
@@ -779,7 +780,7 @@ func (s *Server) getNEP11Tokens(h util.Uint160, acc util.Uint160, bw *io.BufBinW
 	return vals, sym, int(dec.Int64()), nil
 }
 
-func (s *Server) getNEP11Balances(ps request.Params) (interface{}, *response.Error) {
+func (s *Server) getNEP11Balances(ps params.Params) (interface{}, *response.Error) {
 	u, err := ps.Value(0).GetUint160FromAddressOrHex()
 	if err != nil {
 		return nil, response.ErrInvalidParams
@@ -868,7 +869,7 @@ func (s *Server) invokeNEP11Properties(h util.Uint160, id []byte, bw *io.BufBinW
 	return item.Value().([]stackitem.MapElement), nil
 }
 
-func (s *Server) getNEP11Properties(ps request.Params) (interface{}, *response.Error) {
+func (s *Server) getNEP11Properties(ps params.Params) (interface{}, *response.Error) {
 	asset, err := ps.Value(0).GetUint160FromAddressOrHex()
 	if err != nil {
 		return nil, response.ErrInvalidParams
@@ -904,7 +905,7 @@ func (s *Server) getNEP11Properties(ps request.Params) (interface{}, *response.E
 	return res, nil
 }
 
-func (s *Server) getNEP17Balances(ps request.Params) (interface{}, *response.Error) {
+func (s *Server) getNEP17Balances(ps params.Params) (interface{}, *response.Error) {
 	u, err := ps.Value(0).GetUint160FromAddressOrHex()
 	if err != nil {
 		return nil, response.ErrInvalidParams
@@ -1034,7 +1035,7 @@ func (s *Server) getNEP11DTokenBalance(h util.Uint160, acc util.Uint160, id []by
 	return res, nil
 }
 
-func getTimestampsAndLimit(ps request.Params, index int) (uint64, uint64, int, int, error) {
+func getTimestampsAndLimit(ps params.Params, index int) (uint64, uint64, int, int, error) {
 	var start, end uint64
 	var limit, page int
 
@@ -1084,15 +1085,15 @@ func getTimestampsAndLimit(ps request.Params, index int) (uint64, uint64, int, i
 	return start, end, limit, page, nil
 }
 
-func (s *Server) getNEP11Transfers(ps request.Params) (interface{}, *response.Error) {
+func (s *Server) getNEP11Transfers(ps params.Params) (interface{}, *response.Error) {
 	return s.getTokenTransfers(ps, true)
 }
 
-func (s *Server) getNEP17Transfers(ps request.Params) (interface{}, *response.Error) {
+func (s *Server) getNEP17Transfers(ps params.Params) (interface{}, *response.Error) {
 	return s.getTokenTransfers(ps, false)
 }
 
-func (s *Server) getTokenTransfers(ps request.Params, isNEP11 bool) (interface{}, *response.Error) {
+func (s *Server) getTokenTransfers(ps params.Params, isNEP11 bool) (interface{}, *response.Error) {
 	u, err := ps.Value(0).GetUint160FromAddressOrHex()
 	if err != nil {
 		return nil, response.ErrInvalidParams
@@ -1209,7 +1210,7 @@ func (s *Server) getHash(contractID int32, cache map[int32]util.Uint160) (util.U
 	return h, nil
 }
 
-func (s *Server) contractIDFromParam(param *request.Param) (int32, *response.Error) {
+func (s *Server) contractIDFromParam(param *params.Param) (int32, *response.Error) {
 	var result int32
 	if param == nil {
 		return 0, response.ErrInvalidParams
@@ -1234,7 +1235,7 @@ func (s *Server) contractIDFromParam(param *request.Param) (int32, *response.Err
 }
 
 // getContractScriptHashFromParam returns the contract script hash by hex contract hash, address, id or native contract name.
-func (s *Server) contractScriptHashFromParam(param *request.Param) (util.Uint160, *response.Error) {
+func (s *Server) contractScriptHashFromParam(param *params.Param) (util.Uint160, *response.Error) {
 	var result util.Uint160
 	if param == nil {
 		return result, response.ErrInvalidParams
@@ -1274,7 +1275,7 @@ func makeStorageKey(id int32, key []byte) []byte {
 
 var errKeepOnlyLatestState = errors.New("'KeepOnlyLatestState' setting is enabled")
 
-func (s *Server) getProof(ps request.Params) (interface{}, *response.Error) {
+func (s *Server) getProof(ps params.Params) (interface{}, *response.Error) {
 	if s.chain.GetConfig().KeepOnlyLatestState {
 		return nil, response.NewInvalidRequestError(fmt.Sprintf("'getproof' is not supported: %s", errKeepOnlyLatestState))
 	}
@@ -1305,7 +1306,7 @@ func (s *Server) getProof(ps request.Params) (interface{}, *response.Error) {
 	}, nil
 }
 
-func (s *Server) verifyProof(ps request.Params) (interface{}, *response.Error) {
+func (s *Server) verifyProof(ps params.Params) (interface{}, *response.Error) {
 	if s.chain.GetConfig().KeepOnlyLatestState {
 		return nil, response.NewInvalidRequestError(fmt.Sprintf("'verifyproof' is not supported: %s", errKeepOnlyLatestState))
 	}
@@ -1329,7 +1330,7 @@ func (s *Server) verifyProof(ps request.Params) (interface{}, *response.Error) {
 	return vp, nil
 }
 
-func (s *Server) getState(ps request.Params) (interface{}, *response.Error) {
+func (s *Server) getState(ps params.Params) (interface{}, *response.Error) {
 	root, err := ps.Value(0).GetUint256()
 	if err != nil {
 		return nil, response.WrapErrorWithData(response.ErrInvalidParams, "invalid stateroot")
@@ -1363,7 +1364,7 @@ func (s *Server) getState(ps request.Params) (interface{}, *response.Error) {
 	return res, nil
 }
 
-func (s *Server) findStates(ps request.Params) (interface{}, *response.Error) {
+func (s *Server) findStates(ps params.Params) (interface{}, *response.Error) {
 	root, err := ps.Value(0).GetUint256()
 	if err != nil {
 		return nil, response.WrapErrorWithData(response.ErrInvalidParams, "invalid stateroot")
@@ -1471,7 +1472,7 @@ func (s *Server) getHistoricalContractState(root util.Uint256, csHash util.Uint1
 	return contract, nil
 }
 
-func (s *Server) getStateHeight(_ request.Params) (interface{}, *response.Error) {
+func (s *Server) getStateHeight(_ params.Params) (interface{}, *response.Error) {
 	var height = s.chain.BlockHeight()
 	var stateHeight = s.chain.GetStateModule().CurrentValidatedHeight()
 	if s.chain.GetConfig().StateRootInHeader {
@@ -1483,7 +1484,7 @@ func (s *Server) getStateHeight(_ request.Params) (interface{}, *response.Error)
 	}, nil
 }
 
-func (s *Server) getStateRoot(ps request.Params) (interface{}, *response.Error) {
+func (s *Server) getStateRoot(ps params.Params) (interface{}, *response.Error) {
 	p := ps.Value(0)
 	if p == nil {
 		return nil, response.NewInvalidParamsError("missing stateroot identifier")
@@ -1509,7 +1510,7 @@ func (s *Server) getStateRoot(ps request.Params) (interface{}, *response.Error)
 	return rt, nil
 }
 
-func (s *Server) getStorage(ps request.Params) (interface{}, *response.Error) {
+func (s *Server) getStorage(ps params.Params) (interface{}, *response.Error) {
 	id, rErr := s.contractIDFromParam(ps.Value(0))
 	if rErr == response.ErrUnknown {
 		return nil, nil
@@ -1531,7 +1532,7 @@ func (s *Server) getStorage(ps request.Params) (interface{}, *response.Error) {
 	return []byte(item), nil
 }
 
-func (s *Server) getrawtransaction(reqParams request.Params) (interface{}, *response.Error) {
+func (s *Server) getrawtransaction(reqParams params.Params) (interface{}, *response.Error) {
 	txHash, err := reqParams.Value(0).GetUint256()
 	if err != nil {
 		return nil, response.ErrInvalidParams
@@ -1561,7 +1562,7 @@ func (s *Server) getrawtransaction(reqParams request.Params) (interface{}, *resp
 	return tx.Bytes(), nil
 }
 
-func (s *Server) getTransactionHeight(ps request.Params) (interface{}, *response.Error) {
+func (s *Server) getTransactionHeight(ps params.Params) (interface{}, *response.Error) {
 	h, err := ps.Value(0).GetUint256()
 	if err != nil {
 		return nil, response.ErrInvalidParams
@@ -1577,7 +1578,7 @@ func (s *Server) getTransactionHeight(ps request.Params) (interface{}, *response
 
 // getContractState returns contract state (contract information, according to the contract script hash,
 // contract id or native contract name).
-func (s *Server) getContractState(reqParams request.Params) (interface{}, *response.Error) {
+func (s *Server) getContractState(reqParams params.Params) (interface{}, *response.Error) {
 	scriptHash, err := s.contractScriptHashFromParam(reqParams.Value(0))
 	if err != nil {
 		return nil, err
@@ -1589,12 +1590,12 @@ func (s *Server) getContractState(reqParams request.Params) (interface{}, *respo
 	return cs, nil
 }
 
-func (s *Server) getNativeContracts(_ request.Params) (interface{}, *response.Error) {
+func (s *Server) getNativeContracts(_ params.Params) (interface{}, *response.Error) {
 	return s.chain.GetNatives(), nil
 }
 
 // getBlockSysFee returns the system fees of the block, based on the specified index.
-func (s *Server) getBlockSysFee(reqParams request.Params) (interface{}, *response.Error) {
+func (s *Server) getBlockSysFee(reqParams params.Params) (interface{}, *response.Error) {
 	num, err := s.blockHeightFromParam(reqParams.Value(0))
 	if err != nil {
 		return 0, response.NewRPCError("Invalid height", "invalid block identifier")
@@ -1615,7 +1616,7 @@ func (s *Server) getBlockSysFee(reqParams request.Params) (interface{}, *respons
 }
 
 // getBlockHeader returns the corresponding block header information according to the specified script hash.
-func (s *Server) getBlockHeader(reqParams request.Params) (interface{}, *response.Error) {
+func (s *Server) getBlockHeader(reqParams params.Params) (interface{}, *response.Error) {
 	param := reqParams.Value(0)
 	hash, respErr := s.blockHashFromParam(param)
 	if respErr != nil {
@@ -1641,7 +1642,7 @@ func (s *Server) getBlockHeader(reqParams request.Params) (interface{}, *respons
 }
 
 // getUnclaimedGas returns unclaimed GAS amount of the specified address.
-func (s *Server) getUnclaimedGas(ps request.Params) (interface{}, *response.Error) {
+func (s *Server) getUnclaimedGas(ps params.Params) (interface{}, *response.Error) {
 	u, err := ps.Value(0).GetUint160FromAddressOrHex()
 	if err != nil {
 		return nil, response.ErrInvalidParams
@@ -1664,7 +1665,7 @@ func (s *Server) getUnclaimedGas(ps request.Params) (interface{}, *response.Erro
 }
 
 // getCandidates returns the current list of candidates with their active/inactive voting status.
-func (s *Server) getCandidates(_ request.Params) (interface{}, *response.Error) {
+func (s *Server) getCandidates(_ params.Params) (interface{}, *response.Error) {
 	var validators keys.PublicKeys
 
 	validators, err := s.chain.GetNextBlockValidators()
@@ -1687,7 +1688,7 @@ func (s *Server) getCandidates(_ request.Params) (interface{}, *response.Error)
 }
 
 // getNextBlockValidators returns validators for the next block with voting status.
-func (s *Server) getNextBlockValidators(_ request.Params) (interface{}, *response.Error) {
+func (s *Server) getNextBlockValidators(_ params.Params) (interface{}, *response.Error) {
 	var validators keys.PublicKeys
 
 	validators, err := s.chain.GetNextBlockValidators()
@@ -1712,7 +1713,7 @@ func (s *Server) getNextBlockValidators(_ request.Params) (interface{}, *respons
 }
 
 // getCommittee returns the current list of NEO committee members.
-func (s *Server) getCommittee(_ request.Params) (interface{}, *response.Error) {
+func (s *Server) getCommittee(_ params.Params) (interface{}, *response.Error) {
 	keys, err := s.chain.GetCommittee()
 	if err != nil {
 		return nil, response.NewInternalServerError(fmt.Sprintf("can't get committee members: %s", err))
@@ -1721,7 +1722,7 @@ func (s *Server) getCommittee(_ request.Params) (interface{}, *response.Error) {
 }
 
 // invokeFunction implements the `invokeFunction` RPC call.
-func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *response.Error) {
+func (s *Server) invokeFunction(reqParams params.Params) (interface{}, *response.Error) {
 	tx, verbose, respErr := s.getInvokeFunctionParams(reqParams)
 	if respErr != nil {
 		return nil, respErr
@@ -1730,7 +1731,7 @@ func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *respons
 }
 
 // invokeFunctionHistoric implements the `invokeFunctionHistoric` RPC call.
-func (s *Server) invokeFunctionHistoric(reqParams request.Params) (interface{}, *response.Error) {
+func (s *Server) invokeFunctionHistoric(reqParams params.Params) (interface{}, *response.Error) {
 	b, respErr := s.getHistoricParams(reqParams)
 	if respErr != nil {
 		return nil, respErr
@@ -1745,7 +1746,7 @@ func (s *Server) invokeFunctionHistoric(reqParams request.Params) (interface{},
 	return s.runScriptInVM(trigger.Application, tx.Script, util.Uint160{}, tx, b, verbose)
 }
 
-func (s *Server) getInvokeFunctionParams(reqParams request.Params) (*transaction.Transaction, bool, *response.Error) {
+func (s *Server) getInvokeFunctionParams(reqParams params.Params) (*transaction.Transaction, bool, *response.Error) {
 	if len(reqParams) < 2 {
 		return nil, false, response.ErrInvalidParams
 	}
@@ -1757,9 +1758,9 @@ func (s *Server) getInvokeFunctionParams(reqParams request.Params) (*transaction
 	if err != nil {
 		return nil, false, response.ErrInvalidParams
 	}
-	var params *request.Param
+	var invparams *params.Param
 	if len(reqParams) > 2 {
-		params = &reqParams[2]
+		invparams = &reqParams[2]
 	}
 	tx := &transaction.Transaction{}
 	if len(reqParams) > 3 {
@@ -1779,7 +1780,7 @@ func (s *Server) getInvokeFunctionParams(reqParams request.Params) (*transaction
 	if len(tx.Signers) == 0 {
 		tx.Signers = []transaction.Signer{{Account: util.Uint160{}, Scopes: transaction.None}}
 	}
-	script, err := request.CreateFunctionInvocationScript(scriptHash, method, params)
+	script, err := params.CreateFunctionInvocationScript(scriptHash, method, invparams)
 	if err != nil {
 		return nil, false, response.NewInternalServerError(fmt.Sprintf("can't create invocation script: %s", err))
 	}
@@ -1788,7 +1789,7 @@ func (s *Server) getInvokeFunctionParams(reqParams request.Params) (*transaction
 }
 
 // invokescript implements the `invokescript` RPC call.
-func (s *Server) invokescript(reqParams request.Params) (interface{}, *response.Error) {
+func (s *Server) invokescript(reqParams params.Params) (interface{}, *response.Error) {
 	tx, verbose, respErr := s.getInvokeScriptParams(reqParams)
 	if respErr != nil {
 		return nil, respErr
@@ -1797,7 +1798,7 @@ func (s *Server) invokescript(reqParams request.Params) (interface{}, *response.
 }
 
 // invokescripthistoric implements the `invokescripthistoric` RPC call.
-func (s *Server) invokescripthistoric(reqParams request.Params) (interface{}, *response.Error) {
+func (s *Server) invokescripthistoric(reqParams params.Params) (interface{}, *response.Error) {
 	b, respErr := s.getHistoricParams(reqParams)
 	if respErr != nil {
 		return nil, respErr
@@ -1812,7 +1813,7 @@ func (s *Server) invokescripthistoric(reqParams request.Params) (interface{}, *r
 	return s.runScriptInVM(trigger.Application, tx.Script, util.Uint160{}, tx, b, verbose)
 }
 
-func (s *Server) getInvokeScriptParams(reqParams request.Params) (*transaction.Transaction, bool, *response.Error) {
+func (s *Server) getInvokeScriptParams(reqParams params.Params) (*transaction.Transaction, bool, *response.Error) {
 	script, err := reqParams.Value(0).GetBytesBase64()
 	if err != nil {
 		return nil, false, response.ErrInvalidParams
@@ -1842,7 +1843,7 @@ func (s *Server) getInvokeScriptParams(reqParams request.Params) (*transaction.T
 }
 
 // invokeContractVerify implements the `invokecontractverify` RPC call.
-func (s *Server) invokeContractVerify(reqParams request.Params) (interface{}, *response.Error) {
+func (s *Server) invokeContractVerify(reqParams params.Params) (interface{}, *response.Error) {
 	scriptHash, tx, invocationScript, respErr := s.getInvokeContractVerifyParams(reqParams)
 	if respErr != nil {
 		return nil, respErr
@@ -1851,7 +1852,7 @@ func (s *Server) invokeContractVerify(reqParams request.Params) (interface{}, *r
 }
 
 // invokeContractVerifyHistoric implements the `invokecontractverifyhistoric` RPC call.
-func (s *Server) invokeContractVerifyHistoric(reqParams request.Params) (interface{}, *response.Error) {
+func (s *Server) invokeContractVerifyHistoric(reqParams params.Params) (interface{}, *response.Error) {
 	b, respErr := s.getHistoricParams(reqParams)
 	if respErr != nil {
 		return nil, respErr
@@ -1866,7 +1867,7 @@ func (s *Server) invokeContractVerifyHistoric(reqParams request.Params) (interfa
 	return s.runScriptInVM(trigger.Verification, invocationScript, scriptHash, tx, b, false)
 }
 
-func (s *Server) getInvokeContractVerifyParams(reqParams request.Params) (util.Uint160, *transaction.Transaction, []byte, *response.Error) {
+func (s *Server) getInvokeContractVerifyParams(reqParams params.Params) (util.Uint160, *transaction.Transaction, []byte, *response.Error) {
 	scriptHash, responseErr := s.contractScriptHashFromParam(reqParams.Value(0))
 	if responseErr != nil {
 		return util.Uint160{}, nil, nil, responseErr
@@ -1879,7 +1880,7 @@ func (s *Server) getInvokeContractVerifyParams(reqParams request.Params) (util.U
 			return util.Uint160{}, nil, nil, response.WrapErrorWithData(response.ErrInvalidParams, err.Error())
 		}
 		if len(args) > 0 {
-			err := request.ExpandArrayIntoScript(bw.BinWriter, args)
+			err := params.ExpandArrayIntoScript(bw.BinWriter, args)
 			if err != nil {
 				return util.Uint160{}, nil, nil, response.NewInternalServerError(fmt.Sprintf("can't create witness invocation script: %s", err))
 			}
@@ -1906,7 +1907,7 @@ func (s *Server) getInvokeContractVerifyParams(reqParams request.Params) (util.U
 // with the specified index to perform the historic call. It also checks that
 // specified stateroot is stored at the specified height for further request
 // handling consistency.
-func (s *Server) getHistoricParams(reqParams request.Params) (*block.Block, *response.Error) {
+func (s *Server) getHistoricParams(reqParams params.Params) (*block.Block, *response.Error) {
 	if s.chain.GetConfig().KeepOnlyLatestState {
 		return nil, response.NewInvalidRequestError(fmt.Sprintf("only latest state is supported: %s", errKeepOnlyLatestState))
 	}
@@ -2065,7 +2066,7 @@ func (s *Server) runScriptInVM(t trigger.Type, script []byte, contractScriptHash
 	return result.NewInvoke(ic, script, faultException, registerIterator, s.config.MaxIteratorResultItems), nil
 }
 
-func (s *Server) traverseIterator(reqParams request.Params) (interface{}, *response.Error) {
+func (s *Server) traverseIterator(reqParams params.Params) (interface{}, *response.Error) {
 	if !s.config.SessionEnabled {
 		return nil, response.NewInvalidRequestError("sessions are disabled")
 	}
@@ -2154,7 +2155,7 @@ func (s *Server) traverseIterator(reqParams request.Params) (interface{}, *respo
 	return result, nil
 }
 
-func (s *Server) terminateSession(reqParams request.Params) (interface{}, *response.Error) {
+func (s *Server) terminateSession(reqParams params.Params) (interface{}, *response.Error) {
 	if !s.config.SessionEnabled {
 		return nil, response.NewInvalidRequestError("sessions are disabled")
 	}
@@ -2183,7 +2184,7 @@ func (s *Server) terminateSession(reqParams request.Params) (interface{}, *respo
 }
 
 // submitBlock broadcasts a raw block over the NEO network.
-func (s *Server) submitBlock(reqParams request.Params) (interface{}, *response.Error) {
+func (s *Server) submitBlock(reqParams params.Params) (interface{}, *response.Error) {
 	blockBytes, err := reqParams.Value(0).GetBytesBase64()
 	if err != nil {
 		return nil, response.NewInvalidParamsError(fmt.Sprintf("missing parameter or not a base64: %s", err))
@@ -2209,7 +2210,7 @@ func (s *Server) submitBlock(reqParams request.Params) (interface{}, *response.E
 }
 
 // submitNotaryRequest broadcasts P2PNotaryRequest over the NEO network.
-func (s *Server) submitNotaryRequest(ps request.Params) (interface{}, *response.Error) {
+func (s *Server) submitNotaryRequest(ps params.Params) (interface{}, *response.Error) {
 	if !s.chain.P2PSigExtensionsEnabled() {
 		return nil, response.NewRPCError("P2PSignatureExtensions are disabled", "")
 	}
@@ -2243,7 +2244,7 @@ func getRelayResult(err error, hash util.Uint256) (interface{}, *response.Error)
 	}
 }
 
-func (s *Server) submitOracleResponse(ps request.Params) (interface{}, *response.Error) {
+func (s *Server) submitOracleResponse(ps params.Params) (interface{}, *response.Error) {
 	if s.oracle == nil {
 		return nil, response.NewRPCError("Oracle is not enabled", "")
 	}
@@ -2275,7 +2276,7 @@ func (s *Server) submitOracleResponse(ps request.Params) (interface{}, *response
 	return json.RawMessage([]byte("{}")), nil
 }
 
-func (s *Server) sendrawtransaction(reqParams request.Params) (interface{}, *response.Error) {
+func (s *Server) sendrawtransaction(reqParams params.Params) (interface{}, *response.Error) {
 	if len(reqParams) < 1 {
 		return nil, response.NewInvalidParamsError("not enough parameters")
 	}
@@ -2291,7 +2292,7 @@ func (s *Server) sendrawtransaction(reqParams request.Params) (interface{}, *res
 }
 
 // subscribe handles subscription requests from websocket clients.
-func (s *Server) subscribe(reqParams request.Params, sub *subscriber) (interface{}, *response.Error) {
+func (s *Server) subscribe(reqParams params.Params, sub *subscriber) (interface{}, *response.Error) {
 	streamName, err := reqParams.Value(0).GetString()
 	if err != nil {
 		return nil, response.ErrInvalidParams
@@ -2392,7 +2393,7 @@ func (s *Server) subscribeToChannel(event response.EventID) {
 }
 
 // unsubscribe handles unsubscription requests from websocket clients.
-func (s *Server) unsubscribe(reqParams request.Params, sub *subscriber) (interface{}, *response.Error) {
+func (s *Server) unsubscribe(reqParams params.Params, sub *subscriber) (interface{}, *response.Error) {
 	id, err := reqParams.Value(0).GetInt()
 	if err != nil || id < 0 {
 		return nil, response.ErrInvalidParams
@@ -2562,7 +2563,7 @@ drainloop:
 	close(s.notaryRequestCh)
 }
 
-func (s *Server) blockHeightFromParam(param *request.Param) (int, *response.Error) {
+func (s *Server) blockHeightFromParam(param *params.Param) (int, *response.Error) {
 	num, err := param.GetInt()
 	if err != nil {
 		return 0, response.ErrInvalidParams
@@ -2574,7 +2575,7 @@ func (s *Server) blockHeightFromParam(param *request.Param) (int, *response.Erro
 	return num, nil
 }
 
-func (s *Server) packResponse(r *request.In, result interface{}, respErr *response.Error) abstract {
+func (s *Server) packResponse(r *params.In, result interface{}, respErr *response.Error) abstract {
 	resp := abstract{
 		Header: response.Header{
 			JSONRPC: r.JSONRPC,
@@ -2590,7 +2591,7 @@ func (s *Server) packResponse(r *request.In, result interface{}, respErr *respon
 }
 
 // logRequestError is a request error logger.
-func (s *Server) logRequestError(r *request.Request, jsonErr *response.Error) {
+func (s *Server) logRequestError(r *params.Request, jsonErr *response.Error) {
 	logFields := []zap.Field{
 		zap.Int64("code", jsonErr.Code),
 	}
@@ -2600,7 +2601,7 @@ func (s *Server) logRequestError(r *request.Request, jsonErr *response.Error) {
 
 	if r.In != nil {
 		logFields = append(logFields, zap.String("method", r.In.Method))
-		params := request.Params(r.In.RawParams)
+		params := params.Params(r.In.RawParams)
 		logFields = append(logFields, zap.Any("params", params))
 	}
 
@@ -2614,12 +2615,12 @@ func (s *Server) logRequestError(r *request.Request, jsonErr *response.Error) {
 }
 
 // writeHTTPErrorResponse writes an error response to the ResponseWriter.
-func (s *Server) writeHTTPErrorResponse(r *request.In, w http.ResponseWriter, jsonErr *response.Error) {
+func (s *Server) writeHTTPErrorResponse(r *params.In, w http.ResponseWriter, jsonErr *response.Error) {
 	resp := s.packResponse(r, nil, jsonErr)
-	s.writeHTTPServerResponse(&request.Request{In: r}, w, resp)
+	s.writeHTTPServerResponse(&params.Request{In: r}, w, resp)
 }
 
-func (s *Server) writeHTTPServerResponse(r *request.Request, w http.ResponseWriter, resp abstractResult) {
+func (s *Server) writeHTTPServerResponse(r *params.Request, w http.ResponseWriter, resp abstractResult) {
 	// Errors can happen in many places and we can only catch ALL of them here.
 	resp.RunForErrors(func(jsonErr *response.Error) {
 		s.logRequestError(r, jsonErr)
diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go
index f69af3308..6e80c8eee 100644
--- a/pkg/rpc/server/server_test.go
+++ b/pkg/rpc/server/server_test.go
@@ -35,9 +35,9 @@ import (
 	"github.com/nspcc-dev/neo-go/pkg/io"
 	"github.com/nspcc-dev/neo-go/pkg/network"
 	"github.com/nspcc-dev/neo-go/pkg/network/payload"
-	"github.com/nspcc-dev/neo-go/pkg/rpc/request"
 	"github.com/nspcc-dev/neo-go/pkg/rpc/response"
 	"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
+	"github.com/nspcc-dev/neo-go/pkg/rpc/server/params"
 	rpc2 "github.com/nspcc-dev/neo-go/pkg/services/oracle/broadcaster"
 	"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
 	"github.com/nspcc-dev/neo-go/pkg/util"
@@ -3138,7 +3138,7 @@ func BenchmarkHandleIn(b *testing.B) {
 		b.ResetTimer()
 		for i := 0; i < b.N; i++ {
 			b.StopTimer()
-			in := new(request.In)
+			in := new(params.In)
 			b.StartTimer()
 			err := json.Unmarshal(req, in)
 			if err != nil {