diff --git a/pkg/rpcclient/unwrap/unwrap.go b/pkg/rpcclient/unwrap/unwrap.go
index 1e2d7286f..69315101b 100644
--- a/pkg/rpcclient/unwrap/unwrap.go
+++ b/pkg/rpcclient/unwrap/unwrap.go
@@ -25,12 +25,24 @@ import (
 	"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
 )
 
+// Exception is a type used for VM fault messages (aka exceptions). If any of
+// unwrapper functions encounters a FAULT VM state it creates an instance of
+// this type as an error using exception string. It can be used with [errors.As]
+// to get the exact message from VM and compare with known contract-specific
+// errors.
+type Exception string
+
 // ErrNoSessionID is returned from the SessionIterator when the server does not
 // have sessions enabled and does not perform automatic iterator expansion. It
 // means you have no way to get the data from returned iterators using this
 // server, other than expanding it in the VM script.
 var ErrNoSessionID = errors.New("server returned iterator ID, but no session ID")
 
+// Error implements the error interface.
+func (e Exception) Error() string {
+	return string(e)
+}
+
 // BigInt expects correct execution (HALT state) with a single stack item
 // returned. A big.Int is extracted from this item and returned.
 func BigInt(r *result.Invoke, err error) (*big.Int, error) {
@@ -399,10 +411,10 @@ func checkResOK(r *result.Invoke, err error) error {
 		return err
 	}
 	if r.State != vmstate.Halt.String() {
-		return fmt.Errorf("invocation failed: %s", r.FaultException)
+		return fmt.Errorf("invocation failed: %w", Exception(r.FaultException))
 	}
 	if r.FaultException != "" {
-		return fmt.Errorf("inconsistent result, HALTed with exception: %s", r.FaultException)
+		return fmt.Errorf("inconsistent result, HALTed with exception: %w", Exception(r.FaultException))
 	}
 	return nil
 }
diff --git a/pkg/rpcclient/unwrap/unwrap_test.go b/pkg/rpcclient/unwrap/unwrap_test.go
index 51d1f451a..9dc137316 100644
--- a/pkg/rpcclient/unwrap/unwrap_test.go
+++ b/pkg/rpcclient/unwrap/unwrap_test.go
@@ -95,6 +95,20 @@ func TestStdErrors(t *testing.T) {
 		for _, f := range funcs {
 			_, err := f(&result.Invoke{State: "FAULT", Stack: []stackitem.Item{stackitem.Make(42)}}, nil)
 			require.Error(t, err)
+
+			var fault Exception
+			require.True(t, errors.As(err, &fault))
+			require.Equal(t, "", string(fault))
+		}
+	})
+	t.Run("FAULT state with exception", func(t *testing.T) {
+		for _, f := range funcs {
+			_, err := f(&result.Invoke{State: "FAULT", FaultException: "something bad", Stack: []stackitem.Item{stackitem.Make(42)}}, nil)
+			require.Error(t, err)
+
+			var fault Exception
+			require.True(t, errors.As(err, &fault))
+			require.Equal(t, "something bad", string(fault))
 		}
 	})
 	t.Run("nothing returned", func(t *testing.T) {
@@ -207,10 +221,12 @@ func TestItemJSONError(t *testing.T) {
 
 	var received result.Invoke
 	require.NoError(t, json.Unmarshal(data, &received))
+	require.True(t, len(received.FaultException) != 0)
 
 	_, err = Item(&received, nil)
-	require.True(t, len(received.FaultException) != 0)
-	require.Contains(t, err.Error(), received.FaultException)
+	var fault Exception
+	require.True(t, errors.As(err, &fault))
+	require.Equal(t, received.FaultException, string(fault))
 }
 
 func TestUTF8String(t *testing.T) {