From 352df59cf3061f8d9ece05cd7b8cddc201f4a81e Mon Sep 17 00:00:00 2001
From: Volodymyr Manilo <wmanilo@gmail.com>
Date: Thu, 26 May 2022 10:32:51 +0200
Subject: [PATCH 1/7] WIP

---
 .gitignore                             |    1 +
 api/gen/powergate/user/v1/user.pb.go   | 1738 +++++++++++++++---------
 api/server/server.go                   |    5 +-
 api/server/user/storageconfig.go       |   14 +-
 api/server/user/util.go                |  203 ++-
 deals/module/module.go                 |    5 +-
 deals/module/store/store.go            |    7 +-
 ffs/integrationtest/manager/manager.go |    4 +-
 ffs/types.go                           |   37 +-
 notifications/notifier.go              |   31 +
 proto/powergate/user/v1/user.proto     |   31 +
 11 files changed, 1423 insertions(+), 653 deletions(-)
 create mode 100644 notifications/notifier.go

diff --git a/.gitignore b/.gitignore
index d486affc7..dffb3a5db 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,6 +17,7 @@ cover.out
 
 # vscode config folder
 .vscode/
+.idea
 
 # File names that can be used for testing.
 myfile
diff --git a/api/gen/powergate/user/v1/user.pb.go b/api/gen/powergate/user/v1/user.pb.go
index 991e10cb8..6e262662e 100644
--- a/api/gen/powergate/user/v1/user.pb.go
+++ b/api/gen/powergate/user/v1/user.pb.go
@@ -3530,20 +3530,351 @@ func (x *ColdConfig) GetFilecoin() *FilConfig {
 	return nil
 }
 
+type WebhookAuthData struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"`
+	Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
+}
+
+func (x *WebhookAuthData) Reset() {
+	*x = WebhookAuthData{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_powergate_user_v1_user_proto_msgTypes[65]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *WebhookAuthData) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*WebhookAuthData) ProtoMessage() {}
+
+func (x *WebhookAuthData) ProtoReflect() protoreflect.Message {
+	mi := &file_powergate_user_v1_user_proto_msgTypes[65]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use WebhookAuthData.ProtoReflect.Descriptor instead.
+func (*WebhookAuthData) Descriptor() ([]byte, []int) {
+	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{65}
+}
+
+func (x *WebhookAuthData) GetUsername() string {
+	if x != nil {
+		return x.Username
+	}
+	return ""
+}
+
+func (x *WebhookAuthData) GetPassword() string {
+	if x != nil {
+		return x.Password
+	}
+	return ""
+}
+
+type WebhookAuthentication struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Type string           `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
+	Data *WebhookAuthData `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
+}
+
+func (x *WebhookAuthentication) Reset() {
+	*x = WebhookAuthentication{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_powergate_user_v1_user_proto_msgTypes[66]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *WebhookAuthentication) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*WebhookAuthentication) ProtoMessage() {}
+
+func (x *WebhookAuthentication) ProtoReflect() protoreflect.Message {
+	mi := &file_powergate_user_v1_user_proto_msgTypes[66]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use WebhookAuthentication.ProtoReflect.Descriptor instead.
+func (*WebhookAuthentication) Descriptor() ([]byte, []int) {
+	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{66}
+}
+
+func (x *WebhookAuthentication) GetType() string {
+	if x != nil {
+		return x.Type
+	}
+	return ""
+}
+
+func (x *WebhookAuthentication) GetData() *WebhookAuthData {
+	if x != nil {
+		return x.Data
+	}
+	return nil
+}
+
+type Webhook struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Endpoint       string                 `protobuf:"bytes,1,opt,name=endpoint,proto3" json:"endpoint,omitempty"`
+	Authentication *WebhookAuthentication `protobuf:"bytes,2,opt,name=authentication,proto3" json:"authentication,omitempty"`
+}
+
+func (x *Webhook) Reset() {
+	*x = Webhook{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_powergate_user_v1_user_proto_msgTypes[67]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Webhook) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Webhook) ProtoMessage() {}
+
+func (x *Webhook) ProtoReflect() protoreflect.Message {
+	mi := &file_powergate_user_v1_user_proto_msgTypes[67]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Webhook.ProtoReflect.Descriptor instead.
+func (*Webhook) Descriptor() ([]byte, []int) {
+	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{67}
+}
+
+func (x *Webhook) GetEndpoint() string {
+	if x != nil {
+		return x.Endpoint
+	}
+	return ""
+}
+
+func (x *Webhook) GetAuthentication() *WebhookAuthentication {
+	if x != nil {
+		return x.Authentication
+	}
+	return nil
+}
+
+type WebhookAlert struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Type      string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
+	Threshold string `protobuf:"bytes,2,opt,name=threshold,proto3" json:"threshold,omitempty"`
+}
+
+func (x *WebhookAlert) Reset() {
+	*x = WebhookAlert{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_powergate_user_v1_user_proto_msgTypes[68]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *WebhookAlert) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*WebhookAlert) ProtoMessage() {}
+
+func (x *WebhookAlert) ProtoReflect() protoreflect.Message {
+	mi := &file_powergate_user_v1_user_proto_msgTypes[68]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use WebhookAlert.ProtoReflect.Descriptor instead.
+func (*WebhookAlert) Descriptor() ([]byte, []int) {
+	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{68}
+}
+
+func (x *WebhookAlert) GetType() string {
+	if x != nil {
+		return x.Type
+	}
+	return ""
+}
+
+func (x *WebhookAlert) GetThreshold() string {
+	if x != nil {
+		return x.Threshold
+	}
+	return ""
+}
+
+type WebhookConfiguration struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Events []string        `protobuf:"bytes,1,rep,name=events,proto3" json:"events,omitempty"`
+	Alerts []*WebhookAlert `protobuf:"bytes,2,rep,name=alerts,proto3" json:"alerts,omitempty"`
+}
+
+func (x *WebhookConfiguration) Reset() {
+	*x = WebhookConfiguration{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_powergate_user_v1_user_proto_msgTypes[69]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *WebhookConfiguration) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*WebhookConfiguration) ProtoMessage() {}
+
+func (x *WebhookConfiguration) ProtoReflect() protoreflect.Message {
+	mi := &file_powergate_user_v1_user_proto_msgTypes[69]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use WebhookConfiguration.ProtoReflect.Descriptor instead.
+func (*WebhookConfiguration) Descriptor() ([]byte, []int) {
+	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{69}
+}
+
+func (x *WebhookConfiguration) GetEvents() []string {
+	if x != nil {
+		return x.Events
+	}
+	return nil
+}
+
+func (x *WebhookConfiguration) GetAlerts() []*WebhookAlert {
+	if x != nil {
+		return x.Alerts
+	}
+	return nil
+}
+
+type NotificationConfig struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Webhook       *Webhook              `protobuf:"bytes,1,opt,name=webhook,proto3" json:"webhook,omitempty"`
+	Configuration *WebhookConfiguration `protobuf:"bytes,2,opt,name=configuration,proto3" json:"configuration,omitempty"`
+}
+
+func (x *NotificationConfig) Reset() {
+	*x = NotificationConfig{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_powergate_user_v1_user_proto_msgTypes[70]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *NotificationConfig) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*NotificationConfig) ProtoMessage() {}
+
+func (x *NotificationConfig) ProtoReflect() protoreflect.Message {
+	mi := &file_powergate_user_v1_user_proto_msgTypes[70]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use NotificationConfig.ProtoReflect.Descriptor instead.
+func (*NotificationConfig) Descriptor() ([]byte, []int) {
+	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{70}
+}
+
+func (x *NotificationConfig) GetWebhook() *Webhook {
+	if x != nil {
+		return x.Webhook
+	}
+	return nil
+}
+
+func (x *NotificationConfig) GetConfiguration() *WebhookConfiguration {
+	if x != nil {
+		return x.Configuration
+	}
+	return nil
+}
+
 type StorageConfig struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Hot        *HotConfig  `protobuf:"bytes,1,opt,name=hot,proto3" json:"hot,omitempty"`
-	Cold       *ColdConfig `protobuf:"bytes,2,opt,name=cold,proto3" json:"cold,omitempty"`
-	Repairable bool        `protobuf:"varint,3,opt,name=repairable,proto3" json:"repairable,omitempty"`
+	Hot           *HotConfig            `protobuf:"bytes,1,opt,name=hot,proto3" json:"hot,omitempty"`
+	Cold          *ColdConfig           `protobuf:"bytes,2,opt,name=cold,proto3" json:"cold,omitempty"`
+	Repairable    bool                  `protobuf:"varint,3,opt,name=repairable,proto3" json:"repairable,omitempty"`
+	Notifications []*NotificationConfig `protobuf:"bytes,4,rep,name=notifications,proto3" json:"notifications,omitempty"`
 }
 
 func (x *StorageConfig) Reset() {
 	*x = StorageConfig{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_powergate_user_v1_user_proto_msgTypes[65]
+		mi := &file_powergate_user_v1_user_proto_msgTypes[71]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -3556,7 +3887,7 @@ func (x *StorageConfig) String() string {
 func (*StorageConfig) ProtoMessage() {}
 
 func (x *StorageConfig) ProtoReflect() protoreflect.Message {
-	mi := &file_powergate_user_v1_user_proto_msgTypes[65]
+	mi := &file_powergate_user_v1_user_proto_msgTypes[71]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -3569,7 +3900,7 @@ func (x *StorageConfig) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use StorageConfig.ProtoReflect.Descriptor instead.
 func (*StorageConfig) Descriptor() ([]byte, []int) {
-	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{65}
+	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{71}
 }
 
 func (x *StorageConfig) GetHot() *HotConfig {
@@ -3593,6 +3924,13 @@ func (x *StorageConfig) GetRepairable() bool {
 	return false
 }
 
+func (x *StorageConfig) GetNotifications() []*NotificationConfig {
+	if x != nil {
+		return x.Notifications
+	}
+	return nil
+}
+
 type IpfsHotInfo struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -3604,7 +3942,7 @@ type IpfsHotInfo struct {
 func (x *IpfsHotInfo) Reset() {
 	*x = IpfsHotInfo{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_powergate_user_v1_user_proto_msgTypes[66]
+		mi := &file_powergate_user_v1_user_proto_msgTypes[72]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -3617,7 +3955,7 @@ func (x *IpfsHotInfo) String() string {
 func (*IpfsHotInfo) ProtoMessage() {}
 
 func (x *IpfsHotInfo) ProtoReflect() protoreflect.Message {
-	mi := &file_powergate_user_v1_user_proto_msgTypes[66]
+	mi := &file_powergate_user_v1_user_proto_msgTypes[72]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -3630,7 +3968,7 @@ func (x *IpfsHotInfo) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use IpfsHotInfo.ProtoReflect.Descriptor instead.
 func (*IpfsHotInfo) Descriptor() ([]byte, []int) {
-	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{66}
+	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{72}
 }
 
 func (x *IpfsHotInfo) GetCreated() int64 {
@@ -3653,7 +3991,7 @@ type HotInfo struct {
 func (x *HotInfo) Reset() {
 	*x = HotInfo{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_powergate_user_v1_user_proto_msgTypes[67]
+		mi := &file_powergate_user_v1_user_proto_msgTypes[73]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -3666,7 +4004,7 @@ func (x *HotInfo) String() string {
 func (*HotInfo) ProtoMessage() {}
 
 func (x *HotInfo) ProtoReflect() protoreflect.Message {
-	mi := &file_powergate_user_v1_user_proto_msgTypes[67]
+	mi := &file_powergate_user_v1_user_proto_msgTypes[73]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -3679,7 +4017,7 @@ func (x *HotInfo) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use HotInfo.ProtoReflect.Descriptor instead.
 func (*HotInfo) Descriptor() ([]byte, []int) {
-	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{67}
+	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{73}
 }
 
 func (x *HotInfo) GetEnabled() bool {
@@ -3720,7 +4058,7 @@ type FilStorage struct {
 func (x *FilStorage) Reset() {
 	*x = FilStorage{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_powergate_user_v1_user_proto_msgTypes[68]
+		mi := &file_powergate_user_v1_user_proto_msgTypes[74]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -3733,7 +4071,7 @@ func (x *FilStorage) String() string {
 func (*FilStorage) ProtoMessage() {}
 
 func (x *FilStorage) ProtoReflect() protoreflect.Message {
-	mi := &file_powergate_user_v1_user_proto_msgTypes[68]
+	mi := &file_powergate_user_v1_user_proto_msgTypes[74]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -3746,7 +4084,7 @@ func (x *FilStorage) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use FilStorage.ProtoReflect.Descriptor instead.
 func (*FilStorage) Descriptor() ([]byte, []int) {
-	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{68}
+	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{74}
 }
 
 func (x *FilStorage) GetDealId() int64 {
@@ -3811,7 +4149,7 @@ type FilInfo struct {
 func (x *FilInfo) Reset() {
 	*x = FilInfo{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_powergate_user_v1_user_proto_msgTypes[69]
+		mi := &file_powergate_user_v1_user_proto_msgTypes[75]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -3824,7 +4162,7 @@ func (x *FilInfo) String() string {
 func (*FilInfo) ProtoMessage() {}
 
 func (x *FilInfo) ProtoReflect() protoreflect.Message {
-	mi := &file_powergate_user_v1_user_proto_msgTypes[69]
+	mi := &file_powergate_user_v1_user_proto_msgTypes[75]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -3837,7 +4175,7 @@ func (x *FilInfo) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use FilInfo.ProtoReflect.Descriptor instead.
 func (*FilInfo) Descriptor() ([]byte, []int) {
-	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{69}
+	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{75}
 }
 
 func (x *FilInfo) GetDataCid() string {
@@ -3873,7 +4211,7 @@ type ColdInfo struct {
 func (x *ColdInfo) Reset() {
 	*x = ColdInfo{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_powergate_user_v1_user_proto_msgTypes[70]
+		mi := &file_powergate_user_v1_user_proto_msgTypes[76]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -3886,7 +4224,7 @@ func (x *ColdInfo) String() string {
 func (*ColdInfo) ProtoMessage() {}
 
 func (x *ColdInfo) ProtoReflect() protoreflect.Message {
-	mi := &file_powergate_user_v1_user_proto_msgTypes[70]
+	mi := &file_powergate_user_v1_user_proto_msgTypes[76]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -3899,7 +4237,7 @@ func (x *ColdInfo) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use ColdInfo.ProtoReflect.Descriptor instead.
 func (*ColdInfo) Descriptor() ([]byte, []int) {
-	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{70}
+	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{76}
 }
 
 func (x *ColdInfo) GetEnabled() bool {
@@ -3931,7 +4269,7 @@ type StorageInfo struct {
 func (x *StorageInfo) Reset() {
 	*x = StorageInfo{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_powergate_user_v1_user_proto_msgTypes[71]
+		mi := &file_powergate_user_v1_user_proto_msgTypes[77]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -3944,7 +4282,7 @@ func (x *StorageInfo) String() string {
 func (*StorageInfo) ProtoMessage() {}
 
 func (x *StorageInfo) ProtoReflect() protoreflect.Message {
-	mi := &file_powergate_user_v1_user_proto_msgTypes[71]
+	mi := &file_powergate_user_v1_user_proto_msgTypes[77]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -3957,7 +4295,7 @@ func (x *StorageInfo) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use StorageInfo.ProtoReflect.Descriptor instead.
 func (*StorageInfo) Descriptor() ([]byte, []int) {
-	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{71}
+	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{77}
 }
 
 func (x *StorageInfo) GetJobId() string {
@@ -4010,7 +4348,7 @@ type CidInfo struct {
 func (x *CidInfo) Reset() {
 	*x = CidInfo{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_powergate_user_v1_user_proto_msgTypes[72]
+		mi := &file_powergate_user_v1_user_proto_msgTypes[78]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -4023,7 +4361,7 @@ func (x *CidInfo) String() string {
 func (*CidInfo) ProtoMessage() {}
 
 func (x *CidInfo) ProtoReflect() protoreflect.Message {
-	mi := &file_powergate_user_v1_user_proto_msgTypes[72]
+	mi := &file_powergate_user_v1_user_proto_msgTypes[78]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -4036,7 +4374,7 @@ func (x *CidInfo) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use CidInfo.ProtoReflect.Descriptor instead.
 func (*CidInfo) Descriptor() ([]byte, []int) {
-	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{72}
+	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{78}
 }
 
 func (x *CidInfo) GetCid() string {
@@ -4096,7 +4434,7 @@ type DealInfo struct {
 func (x *DealInfo) Reset() {
 	*x = DealInfo{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_powergate_user_v1_user_proto_msgTypes[73]
+		mi := &file_powergate_user_v1_user_proto_msgTypes[79]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -4109,7 +4447,7 @@ func (x *DealInfo) String() string {
 func (*DealInfo) ProtoMessage() {}
 
 func (x *DealInfo) ProtoReflect() protoreflect.Message {
-	mi := &file_powergate_user_v1_user_proto_msgTypes[73]
+	mi := &file_powergate_user_v1_user_proto_msgTypes[79]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -4122,7 +4460,7 @@ func (x *DealInfo) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use DealInfo.ProtoReflect.Descriptor instead.
 func (*DealInfo) Descriptor() ([]byte, []int) {
-	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{73}
+	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{79}
 }
 
 func (x *DealInfo) GetProposalCid() string {
@@ -4227,7 +4565,7 @@ type StorageJob struct {
 func (x *StorageJob) Reset() {
 	*x = StorageJob{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_powergate_user_v1_user_proto_msgTypes[74]
+		mi := &file_powergate_user_v1_user_proto_msgTypes[80]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -4240,7 +4578,7 @@ func (x *StorageJob) String() string {
 func (*StorageJob) ProtoMessage() {}
 
 func (x *StorageJob) ProtoReflect() protoreflect.Message {
-	mi := &file_powergate_user_v1_user_proto_msgTypes[74]
+	mi := &file_powergate_user_v1_user_proto_msgTypes[80]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -4253,7 +4591,7 @@ func (x *StorageJob) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use StorageJob.ProtoReflect.Descriptor instead.
 func (*StorageJob) Descriptor() ([]byte, []int) {
-	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{74}
+	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{80}
 }
 
 func (x *StorageJob) GetId() string {
@@ -4325,7 +4663,7 @@ type DealError struct {
 func (x *DealError) Reset() {
 	*x = DealError{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_powergate_user_v1_user_proto_msgTypes[75]
+		mi := &file_powergate_user_v1_user_proto_msgTypes[81]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -4338,7 +4676,7 @@ func (x *DealError) String() string {
 func (*DealError) ProtoMessage() {}
 
 func (x *DealError) ProtoReflect() protoreflect.Message {
-	mi := &file_powergate_user_v1_user_proto_msgTypes[75]
+	mi := &file_powergate_user_v1_user_proto_msgTypes[81]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -4351,7 +4689,7 @@ func (x *DealError) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use DealError.ProtoReflect.Descriptor instead.
 func (*DealError) Descriptor() ([]byte, []int) {
-	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{75}
+	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{81}
 }
 
 func (x *DealError) GetProposalCid() string {
@@ -4389,7 +4727,7 @@ type LogEntry struct {
 func (x *LogEntry) Reset() {
 	*x = LogEntry{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_powergate_user_v1_user_proto_msgTypes[76]
+		mi := &file_powergate_user_v1_user_proto_msgTypes[82]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -4402,7 +4740,7 @@ func (x *LogEntry) String() string {
 func (*LogEntry) ProtoMessage() {}
 
 func (x *LogEntry) ProtoReflect() protoreflect.Message {
-	mi := &file_powergate_user_v1_user_proto_msgTypes[76]
+	mi := &file_powergate_user_v1_user_proto_msgTypes[82]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -4415,7 +4753,7 @@ func (x *LogEntry) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use LogEntry.ProtoReflect.Descriptor instead.
 func (*LogEntry) Descriptor() ([]byte, []int) {
-	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{76}
+	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{82}
 }
 
 func (x *LogEntry) GetCid() string {
@@ -4462,7 +4800,7 @@ type DealRecordsConfig struct {
 func (x *DealRecordsConfig) Reset() {
 	*x = DealRecordsConfig{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_powergate_user_v1_user_proto_msgTypes[77]
+		mi := &file_powergate_user_v1_user_proto_msgTypes[83]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -4475,7 +4813,7 @@ func (x *DealRecordsConfig) String() string {
 func (*DealRecordsConfig) ProtoMessage() {}
 
 func (x *DealRecordsConfig) ProtoReflect() protoreflect.Message {
-	mi := &file_powergate_user_v1_user_proto_msgTypes[77]
+	mi := &file_powergate_user_v1_user_proto_msgTypes[83]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -4488,7 +4826,7 @@ func (x *DealRecordsConfig) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use DealRecordsConfig.ProtoReflect.Descriptor instead.
 func (*DealRecordsConfig) Descriptor() ([]byte, []int) {
-	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{77}
+	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{83}
 }
 
 func (x *DealRecordsConfig) GetFromAddrs() []string {
@@ -4555,7 +4893,7 @@ type StorageDealInfo struct {
 func (x *StorageDealInfo) Reset() {
 	*x = StorageDealInfo{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_powergate_user_v1_user_proto_msgTypes[78]
+		mi := &file_powergate_user_v1_user_proto_msgTypes[84]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -4568,7 +4906,7 @@ func (x *StorageDealInfo) String() string {
 func (*StorageDealInfo) ProtoMessage() {}
 
 func (x *StorageDealInfo) ProtoReflect() protoreflect.Message {
-	mi := &file_powergate_user_v1_user_proto_msgTypes[78]
+	mi := &file_powergate_user_v1_user_proto_msgTypes[84]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -4581,7 +4919,7 @@ func (x *StorageDealInfo) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use StorageDealInfo.ProtoReflect.Descriptor instead.
 func (*StorageDealInfo) Descriptor() ([]byte, []int) {
-	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{78}
+	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{84}
 }
 
 func (x *StorageDealInfo) GetProposalCid() string {
@@ -4690,7 +5028,7 @@ type StorageDealRecord struct {
 func (x *StorageDealRecord) Reset() {
 	*x = StorageDealRecord{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_powergate_user_v1_user_proto_msgTypes[79]
+		mi := &file_powergate_user_v1_user_proto_msgTypes[85]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -4703,7 +5041,7 @@ func (x *StorageDealRecord) String() string {
 func (*StorageDealRecord) ProtoMessage() {}
 
 func (x *StorageDealRecord) ProtoReflect() protoreflect.Message {
-	mi := &file_powergate_user_v1_user_proto_msgTypes[79]
+	mi := &file_powergate_user_v1_user_proto_msgTypes[85]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -4716,7 +5054,7 @@ func (x *StorageDealRecord) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use StorageDealRecord.ProtoReflect.Descriptor instead.
 func (*StorageDealRecord) Descriptor() ([]byte, []int) {
-	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{79}
+	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{85}
 }
 
 func (x *StorageDealRecord) GetRootCid() string {
@@ -4820,7 +5158,7 @@ type RetrievalDealInfo struct {
 func (x *RetrievalDealInfo) Reset() {
 	*x = RetrievalDealInfo{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_powergate_user_v1_user_proto_msgTypes[80]
+		mi := &file_powergate_user_v1_user_proto_msgTypes[86]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -4833,7 +5171,7 @@ func (x *RetrievalDealInfo) String() string {
 func (*RetrievalDealInfo) ProtoMessage() {}
 
 func (x *RetrievalDealInfo) ProtoReflect() protoreflect.Message {
-	mi := &file_powergate_user_v1_user_proto_msgTypes[80]
+	mi := &file_powergate_user_v1_user_proto_msgTypes[86]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -4846,7 +5184,7 @@ func (x *RetrievalDealInfo) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use RetrievalDealInfo.ProtoReflect.Descriptor instead.
 func (*RetrievalDealInfo) Descriptor() ([]byte, []int) {
-	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{80}
+	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{86}
 }
 
 func (x *RetrievalDealInfo) GetRootCid() string {
@@ -4917,7 +5255,7 @@ type RetrievalDealRecord struct {
 func (x *RetrievalDealRecord) Reset() {
 	*x = RetrievalDealRecord{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_powergate_user_v1_user_proto_msgTypes[81]
+		mi := &file_powergate_user_v1_user_proto_msgTypes[87]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -4930,7 +5268,7 @@ func (x *RetrievalDealRecord) String() string {
 func (*RetrievalDealRecord) ProtoMessage() {}
 
 func (x *RetrievalDealRecord) ProtoReflect() protoreflect.Message {
-	mi := &file_powergate_user_v1_user_proto_msgTypes[81]
+	mi := &file_powergate_user_v1_user_proto_msgTypes[87]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -4943,7 +5281,7 @@ func (x *RetrievalDealRecord) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use RetrievalDealRecord.ProtoReflect.Descriptor instead.
 func (*RetrievalDealRecord) Descriptor() ([]byte, []int) {
-	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{81}
+	return file_powergate_user_v1_user_proto_rawDescGZIP(), []int{87}
 }
 
 func (x *RetrievalDealRecord) GetAddress() string {
@@ -5020,7 +5358,7 @@ type AddrInfo_VerifiedClientInfo struct {
 func (x *AddrInfo_VerifiedClientInfo) Reset() {
 	*x = AddrInfo_VerifiedClientInfo{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_powergate_user_v1_user_proto_msgTypes[82]
+		mi := &file_powergate_user_v1_user_proto_msgTypes[88]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -5033,7 +5371,7 @@ func (x *AddrInfo_VerifiedClientInfo) String() string {
 func (*AddrInfo_VerifiedClientInfo) ProtoMessage() {}
 
 func (x *AddrInfo_VerifiedClientInfo) ProtoReflect() protoreflect.Message {
-	mi := &file_powergate_user_v1_user_proto_msgTypes[82]
+	mi := &file_powergate_user_v1_user_proto_msgTypes[88]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -5402,7 +5740,46 @@ var file_powergate_user_v1_user_proto_rawDesc = []byte{
 	0x65, 0x63, 0x6f, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x6f,
 	0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
 	0x46, 0x69, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x63,
-	0x6f, 0x69, 0x6e, 0x22, 0x92, 0x01, 0x0a, 0x0d, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43,
+	0x6f, 0x69, 0x6e, 0x22, 0x49, 0x0a, 0x0f, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x41, 0x75,
+	0x74, 0x68, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61,
+	0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61,
+	0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x63,
+	0x0a, 0x15, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74,
+	0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x36, 0x0a, 0x04, 0x64,
+	0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x6f, 0x77, 0x65,
+	0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x65,
+	0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x41, 0x75, 0x74, 0x68, 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x64,
+	0x61, 0x74, 0x61, 0x22, 0x77, 0x0a, 0x07, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x12, 0x1a,
+	0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x50, 0x0a, 0x0e, 0x61, 0x75,
+	0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x28, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75,
+	0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x41, 0x75,
+	0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0e, 0x61, 0x75,
+	0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x40, 0x0a, 0x0c,
+	0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x12, 0x12, 0x0a, 0x04,
+	0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65,
+	0x12, 0x1c, 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x02, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x22, 0x67,
+	0x0a, 0x14, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75,
+	0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73,
+	0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x37,
+	0x0a, 0x06, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f,
+	0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
+	0x76, 0x31, 0x2e, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x41, 0x6c, 0x65, 0x72, 0x74, 0x52,
+	0x06, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x73, 0x22, 0x99, 0x01, 0x0a, 0x12, 0x4e, 0x6f, 0x74, 0x69,
+	0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x34,
+	0x0a, 0x07, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x1a, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72,
+	0x2e, 0x76, 0x31, 0x2e, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x52, 0x07, 0x77, 0x65, 0x62,
+	0x68, 0x6f, 0x6f, 0x6b, 0x12, 0x4d, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72,
+	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x70, 0x6f,
+	0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
+	0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61,
+	0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x22, 0xdf, 0x01, 0x0a, 0x0d, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43,
 	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x0a, 0x03, 0x68, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01,
 	0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75,
 	0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
@@ -5411,459 +5788,464 @@ var file_powergate_user_v1_user_proto_rawDesc = []byte{
 	0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x64, 0x43, 0x6f, 0x6e, 0x66,
 	0x69, 0x67, 0x52, 0x04, 0x63, 0x6f, 0x6c, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x72, 0x65, 0x70, 0x61,
 	0x69, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x72, 0x65,
-	0x70, 0x61, 0x69, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x27, 0x0a, 0x0b, 0x49, 0x70, 0x66, 0x73,
-	0x48, 0x6f, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74,
-	0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65,
-	0x64, 0x22, 0x6b, 0x0a, 0x07, 0x48, 0x6f, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07,
-	0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65,
-	0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02,
-	0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x32, 0x0a, 0x04, 0x69, 0x70,
-	0x66, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72,
-	0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x70, 0x66,
-	0x73, 0x48, 0x6f, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x70, 0x66, 0x73, 0x22, 0xd0,
-	0x01, 0x0a, 0x0a, 0x46, 0x69, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x12, 0x17, 0x0a,
-	0x07, 0x64, 0x65, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06,
-	0x64, 0x65, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x65,
-	0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x65, 0x64,
-	0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01,
-	0x28, 0x03, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b,
-	0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28,
-	0x04, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x14, 0x0a,
-	0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x69,
-	0x6e, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x70, 0x72, 0x69,
-	0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x50,
-	0x72, 0x69, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x69, 0x65, 0x63, 0x65, 0x5f, 0x63, 0x69,
-	0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x69, 0x65, 0x63, 0x65, 0x43, 0x69,
-	0x64, 0x22, 0x75, 0x0a, 0x07, 0x46, 0x69, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x19, 0x0a, 0x08,
-	0x64, 0x61, 0x74, 0x61, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
-	0x64, 0x61, 0x74, 0x61, 0x43, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x3b, 0x0a, 0x09, 0x70,
-	0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d,
-	0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
-	0x76, 0x31, 0x2e, 0x46, 0x69, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x09, 0x70,
-	0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x73, 0x22, 0x5c, 0x0a, 0x08, 0x43, 0x6f, 0x6c, 0x64,
-	0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x36,
-	0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x63, 0x6f, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
-	0x32, 0x1a, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65,
-	0x72, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x66, 0x69,
-	0x6c, 0x65, 0x63, 0x6f, 0x69, 0x6e, 0x22, 0xaf, 0x01, 0x0a, 0x0b, 0x53, 0x74, 0x6f, 0x72, 0x61,
-	0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x10, 0x0a,
-	0x03, 0x63, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x63, 0x69, 0x64, 0x12,
-	0x18, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03,
-	0x52, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x12, 0x2c, 0x0a, 0x03, 0x68, 0x6f, 0x74,
-	0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61,
-	0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x74, 0x49, 0x6e,
-	0x66, 0x6f, 0x52, 0x03, 0x68, 0x6f, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x63, 0x6f, 0x6c, 0x64, 0x18,
-	0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74,
-	0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x64, 0x49, 0x6e,
-	0x66, 0x6f, 0x52, 0x04, 0x63, 0x6f, 0x6c, 0x64, 0x22, 0xf2, 0x02, 0x0a, 0x07, 0x43, 0x69, 0x64,
-	0x49, 0x6e, 0x66, 0x6f, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x03, 0x63, 0x69, 0x64, 0x12, 0x61, 0x0a, 0x1c, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74,
-	0x5f, 0x70, 0x75, 0x73, 0x68, 0x65, 0x64, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f,
-	0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70,
-	0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
-	0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x19,
-	0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x50, 0x75, 0x73, 0x68, 0x65, 0x64, 0x53, 0x74, 0x6f, 0x72,
-	0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x50, 0x0a, 0x14, 0x63, 0x75, 0x72,
-	0x72, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x69, 0x6e, 0x66,
-	0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67,
-	0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72,
-	0x61, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x12, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74,
-	0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x4d, 0x0a, 0x13, 0x71,
-	0x75, 0x65, 0x75, 0x65, 0x64, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x6a, 0x6f,
-	0x62, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72,
-	0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f,
-	0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x11, 0x71, 0x75, 0x65, 0x75, 0x65, 0x64, 0x53,
-	0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x73, 0x12, 0x51, 0x0a, 0x15, 0x65, 0x78,
-	0x65, 0x63, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f,
-	0x6a, 0x6f, 0x62, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x6f, 0x77, 0x65,
+	0x70, 0x61, 0x69, 0x72, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x4b, 0x0a, 0x0d, 0x6e, 0x6f, 0x74, 0x69,
+	0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32,
+	0x25, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72,
+	0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
+	0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x27, 0x0a, 0x0b, 0x49, 0x70, 0x66, 0x73, 0x48, 0x6f, 0x74,
+	0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x22, 0x6b,
+	0x0a, 0x07, 0x48, 0x6f, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61,
+	0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62,
+	0x6c, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x32, 0x0a, 0x04, 0x69, 0x70, 0x66, 0x73, 0x18,
+	0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74,
+	0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x70, 0x66, 0x73, 0x48, 0x6f,
+	0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x70, 0x66, 0x73, 0x22, 0xd0, 0x01, 0x0a, 0x0a,
+	0x46, 0x69, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x65,
+	0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x64, 0x65, 0x61,
+	0x6c, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x65, 0x64, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x65, 0x64, 0x12, 0x1a, 0x0a,
+	0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52,
+	0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61,
+	0x72, 0x74, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a,
+	0x73, 0x74, 0x61, 0x72, 0x74, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x69,
+	0x6e, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72,
+	0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18,
+	0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x50, 0x72, 0x69, 0x63,
+	0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x69, 0x65, 0x63, 0x65, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x07,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x69, 0x65, 0x63, 0x65, 0x43, 0x69, 0x64, 0x22, 0x75,
+	0x0a, 0x07, 0x46, 0x69, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x19, 0x0a, 0x08, 0x64, 0x61, 0x74,
+	0x61, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x61, 0x74,
+	0x61, 0x43, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01,
+	0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x3b, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x70,
+	0x6f, 0x73, 0x61, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x6f,
+	0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
+	0x46, 0x69, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x70,
+	0x6f, 0x73, 0x61, 0x6c, 0x73, 0x22, 0x5c, 0x0a, 0x08, 0x43, 0x6f, 0x6c, 0x64, 0x49, 0x6e, 0x66,
+	0x6f, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x36, 0x0a, 0x08, 0x66,
+	0x69, 0x6c, 0x65, 0x63, 0x6f, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
+	0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76,
+	0x31, 0x2e, 0x46, 0x69, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x63,
+	0x6f, 0x69, 0x6e, 0x22, 0xaf, 0x01, 0x0a, 0x0b, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49,
+	0x6e, 0x66, 0x6f, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x69,
+	0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x63, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07,
+	0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x63,
+	0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x12, 0x2c, 0x0a, 0x03, 0x68, 0x6f, 0x74, 0x18, 0x04, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e,
+	0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52,
+	0x03, 0x68, 0x6f, 0x74, 0x12, 0x2f, 0x0a, 0x04, 0x63, 0x6f, 0x6c, 0x64, 0x18, 0x05, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75,
+	0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52,
+	0x04, 0x63, 0x6f, 0x6c, 0x64, 0x22, 0xf2, 0x02, 0x0a, 0x07, 0x43, 0x69, 0x64, 0x49, 0x6e, 0x66,
+	0x6f, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
+	0x63, 0x69, 0x64, 0x12, 0x61, 0x0a, 0x1c, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x70, 0x75,
+	0x73, 0x68, 0x65, 0x64, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x70, 0x6f, 0x77, 0x65,
 	0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74,
-	0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x13, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74,
-	0x69, 0x6e, 0x67, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x22, 0xf1, 0x02,
-	0x0a, 0x08, 0x44, 0x65, 0x61, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72,
-	0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x43, 0x69, 0x64, 0x12, 0x19, 0x0a,
-	0x08, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52,
-	0x07, 0x73, 0x74, 0x61, 0x74, 0x65, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74,
-	0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74,
-	0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72,
-	0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x1b, 0x0a,
-	0x09, 0x70, 0x69, 0x65, 0x63, 0x65, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x08, 0x70, 0x69, 0x65, 0x63, 0x65, 0x43, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69,
-	0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x26,
-	0x0a, 0x0f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x65, 0x70, 0x6f, 0x63,
-	0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x70, 0x72, 0x69, 0x63, 0x65, 0x50, 0x65,
-	0x72, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f,
-	0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x73, 0x74, 0x61,
-	0x72, 0x74, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74,
-	0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74,
-	0x69, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x65, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x0a,
-	0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x64, 0x65, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x10,
-	0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68,
-	0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69,
-	0x6f, 0x6e, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61,
-	0x67, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
-	0x65, 0x22, 0xb4, 0x02, 0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62,
-	0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64,
-	0x12, 0x15, 0x0a, 0x06, 0x61, 0x70, 0x69, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x05, 0x61, 0x70, 0x69, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x69, 0x64, 0x18, 0x03,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x63, 0x69, 0x64, 0x12, 0x34, 0x0a, 0x06, 0x73, 0x74, 0x61,
-	0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x70, 0x6f, 0x77, 0x65,
-	0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4a, 0x6f,
-	0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12,
-	0x1f, 0x0a, 0x0b, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63, 0x61, 0x75, 0x73, 0x65, 0x18, 0x05,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x61, 0x75, 0x73, 0x65,
-	0x12, 0x38, 0x0a, 0x09, 0x64, 0x65, 0x61, 0x6c, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x06, 0x20,
-	0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e,
-	0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x61, 0x6c, 0x49, 0x6e, 0x66, 0x6f,
-	0x52, 0x08, 0x64, 0x65, 0x61, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x3d, 0x0a, 0x0b, 0x64, 0x65,
-	0x61, 0x6c, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32,
-	0x1c, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72,
-	0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x61, 0x6c, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x0a, 0x64,
-	0x65, 0x61, 0x6c, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65,
-	0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63,
-	0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x5e, 0x0a, 0x09, 0x44, 0x65, 0x61, 0x6c,
-	0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61,
-	0x6c, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f,
-	0x70, 0x6f, 0x73, 0x61, 0x6c, 0x43, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x69, 0x6e, 0x65,
-	0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x18,
-	0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x61, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x45,
-	0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x03, 0x63, 0x69, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64,
-	0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x12, 0x0a,
-	0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x74, 0x69, 0x6d,
-	0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xe2, 0x01, 0x0a, 0x11,
-	0x44, 0x65, 0x61, 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69,
-	0x67, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18,
-	0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x66, 0x72, 0x6f, 0x6d, 0x41, 0x64, 0x64, 0x72, 0x73,
-	0x12, 0x1b, 0x0a, 0x09, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x63, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20,
-	0x03, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x43, 0x69, 0x64, 0x73, 0x12, 0x27, 0x0a,
-	0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67,
-	0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x50,
-	0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64,
-	0x65, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69,
-	0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x61,
-	0x73, 0x63, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09,
-	0x61, 0x73, 0x63, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6e, 0x63,
-	0x6c, 0x75, 0x64, 0x65, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28,
-	0x08, 0x52, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64,
-	0x22, 0xf8, 0x02, 0x0a, 0x0f, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x44, 0x65, 0x61, 0x6c,
-	0x49, 0x6e, 0x66, 0x6f, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c,
-	0x5f, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x70,
-	0x6f, 0x73, 0x61, 0x6c, 0x43, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x74, 0x61, 0x74, 0x65,
-	0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x73, 0x74, 0x61, 0x74, 0x65,
-	0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
-	0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d,
-	0x65, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x69, 0x65, 0x63, 0x65,
-	0x5f, 0x63, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x69, 0x65, 0x63,
-	0x65, 0x43, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01,
-	0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x72, 0x69, 0x63,
-	0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28,
-	0x04, 0x52, 0x0d, 0x70, 0x72, 0x69, 0x63, 0x65, 0x50, 0x65, 0x72, 0x45, 0x70, 0x6f, 0x63, 0x68,
-	0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18,
-	0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x45, 0x70, 0x6f, 0x63,
-	0x68, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20,
-	0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x0a,
-	0x07, 0x64, 0x65, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06,
-	0x64, 0x65, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61,
-	0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03,
-	0x52, 0x0f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x70, 0x6f, 0x63,
-	0x68, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x0c, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xc2, 0x04, 0x0a, 0x11,
-	0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x44, 0x65, 0x61, 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72,
-	0x64, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x6f, 0x6f, 0x74, 0x43, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07,
-	0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61,
-	0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03,
-	0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x65,
-	0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x65, 0x6e,
-	0x64, 0x69, 0x6e, 0x67, 0x12, 0x3f, 0x0a, 0x09, 0x64, 0x65, 0x61, 0x6c, 0x5f, 0x69, 0x6e, 0x66,
-	0x6f, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67,
-	0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72,
-	0x61, 0x67, 0x65, 0x44, 0x65, 0x61, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x64, 0x65, 0x61,
-	0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65,
-	0x72, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x72,
-	0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x4a, 0x0a, 0x13, 0x64, 0x61,
-	0x74, 0x61, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x72,
-	0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
-	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
-	0x61, 0x6d, 0x70, 0x52, 0x11, 0x64, 0x61, 0x74, 0x61, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65,
-	0x72, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x46, 0x0a, 0x11, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x74,
-	0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28,
-	0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0f, 0x64,
-	0x61, 0x74, 0x61, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x12, 0x3f,
-	0x0a, 0x0d, 0x73, 0x65, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18,
-	0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
-	0x70, 0x52, 0x0c, 0x73, 0x65, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12,
-	0x3b, 0x0a, 0x0b, 0x73, 0x65, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x0a,
+	0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x19, 0x6c, 0x61, 0x74,
+	0x65, 0x73, 0x74, 0x50, 0x75, 0x73, 0x68, 0x65, 0x64, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
+	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x50, 0x0a, 0x14, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e,
+	0x74, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65,
+	0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
+	0x49, 0x6e, 0x66, 0x6f, 0x52, 0x12, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x6f,
+	0x72, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x4d, 0x0a, 0x13, 0x71, 0x75, 0x65, 0x75,
+	0x65, 0x64, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x6a, 0x6f, 0x62, 0x73, 0x18,
+	0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74,
+	0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67,
+	0x65, 0x4a, 0x6f, 0x62, 0x52, 0x11, 0x71, 0x75, 0x65, 0x75, 0x65, 0x64, 0x53, 0x74, 0x6f, 0x72,
+	0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x73, 0x12, 0x51, 0x0a, 0x15, 0x65, 0x78, 0x65, 0x63, 0x75,
+	0x74, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x6a, 0x6f, 0x62,
+	0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61,
+	0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61,
+	0x67, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x13, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6e, 0x67,
+	0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x22, 0xf1, 0x02, 0x0a, 0x08, 0x44,
+	0x65, 0x61, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x70, 0x6f,
+	0x73, 0x61, 0x6c, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70,
+	0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x43, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x74,
+	0x61, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x73, 0x74,
+	0x61, 0x74, 0x65, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x6e,
+	0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x74, 0x65,
+	0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x69,
+	0x65, 0x63, 0x65, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70,
+	0x69, 0x65, 0x63, 0x65, 0x43, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18,
+	0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x70,
+	0x72, 0x69, 0x63, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x07,
+	0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x70, 0x72, 0x69, 0x63, 0x65, 0x50, 0x65, 0x72, 0x45, 0x70,
+	0x6f, 0x63, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x65, 0x70, 0x6f,
+	0x63, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x45,
+	0x70, 0x6f, 0x63, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+	0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+	0x12, 0x17, 0x0a, 0x07, 0x64, 0x65, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28,
+	0x04, 0x52, 0x06, 0x64, 0x65, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x61, 0x63, 0x74,
+	0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x0b, 0x20,
+	0x01, 0x28, 0x03, 0x52, 0x0f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45,
+	0x70, 0x6f, 0x63, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18,
+	0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xb4,
+	0x02, 0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x0e, 0x0a,
+	0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x15, 0x0a,
+	0x06, 0x61, 0x70, 0x69, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x61,
+	0x70, 0x69, 0x49, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x03, 0x63, 0x69, 0x64, 0x12, 0x34, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
+	0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61,
+	0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4a, 0x6f, 0x62, 0x53, 0x74,
+	0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1f, 0x0a, 0x0b,
+	0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63, 0x61, 0x75, 0x73, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x61, 0x75, 0x73, 0x65, 0x12, 0x38, 0x0a,
+	0x09, 0x64, 0x65, 0x61, 0x6c, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b,
+	0x32, 0x1b, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65,
+	0x72, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x61, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x64,
+	0x65, 0x61, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x3d, 0x0a, 0x0b, 0x64, 0x65, 0x61, 0x6c, 0x5f,
+	0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70,
+	0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
+	0x2e, 0x44, 0x65, 0x61, 0x6c, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x0a, 0x64, 0x65, 0x61, 0x6c,
+	0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65,
+	0x64, 0x5f, 0x61, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61,
+	0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x5e, 0x0a, 0x09, 0x44, 0x65, 0x61, 0x6c, 0x45, 0x72, 0x72,
+	0x6f, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x63,
+	0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73,
+	0x61, 0x6c, 0x43, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x6d,
+	0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65,
+	0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x61, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72,
+	0x79, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
+	0x63, 0x69, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x6a, 0x6f, 0x62, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x05, 0x6a, 0x6f, 0x62, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69,
+	0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x18,
+	0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xe2, 0x01, 0x0a, 0x11, 0x44, 0x65, 0x61,
+	0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1d,
+	0x0a, 0x0a, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03,
+	0x28, 0x09, 0x52, 0x09, 0x66, 0x72, 0x6f, 0x6d, 0x41, 0x64, 0x64, 0x72, 0x73, 0x12, 0x1b, 0x0a,
+	0x09, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x63, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09,
+	0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x43, 0x69, 0x64, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x69, 0x6e,
+	0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20,
+	0x01, 0x28, 0x08, 0x52, 0x0e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x50, 0x65, 0x6e, 0x64,
+	0x69, 0x6e, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x66,
+	0x69, 0x6e, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x6e, 0x63, 0x6c,
+	0x75, 0x64, 0x65, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x73, 0x63, 0x65,
+	0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x61, 0x73, 0x63,
+	0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64,
+	0x65, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d,
+	0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x22, 0xf8, 0x02,
+	0x0a, 0x0f, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x44, 0x65, 0x61, 0x6c, 0x49, 0x6e, 0x66,
+	0x6f, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61, 0x6c, 0x5f, 0x63, 0x69,
+	0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x61,
+	0x6c, 0x43, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x64,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x73, 0x74, 0x61, 0x74, 0x65, 0x49, 0x64, 0x12,
+	0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14,
+	0x0a, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d,
+	0x69, 0x6e, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x69, 0x65, 0x63, 0x65, 0x5f, 0x63, 0x69,
+	0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x69, 0x65, 0x63, 0x65, 0x43, 0x69,
+	0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52,
+	0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x70,
+	0x65, 0x72, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d,
+	0x70, 0x72, 0x69, 0x63, 0x65, 0x50, 0x65, 0x72, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x1f, 0x0a,
+	0x0b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x08, 0x20, 0x01,
+	0x28, 0x04, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x1a,
+	0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04,
+	0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x17, 0x0a, 0x07, 0x64, 0x65,
+	0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x64, 0x65, 0x61,
+	0x6c, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f,
+	0x6e, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x61,
+	0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x18,
+	0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xc2, 0x04, 0x0a, 0x11, 0x53, 0x74, 0x6f,
+	0x72, 0x61, 0x67, 0x65, 0x44, 0x65, 0x61, 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x19,
+	0x0a, 0x08, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x07, 0x72, 0x6f, 0x6f, 0x74, 0x43, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64,
+	0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72,
+	0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x03, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x65, 0x6e, 0x64, 0x69,
+	0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e,
+	0x67, 0x12, 0x3f, 0x0a, 0x09, 0x64, 0x65, 0x61, 0x6c, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x05,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65,
+	0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
+	0x44, 0x65, 0x61, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x64, 0x65, 0x61, 0x6c, 0x49, 0x6e,
+	0x66, 0x6f, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x5f, 0x73,
+	0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x74, 0x72, 0x61, 0x6e, 0x73,
+	0x66, 0x65, 0x72, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x4a, 0x0a, 0x13, 0x64, 0x61, 0x74, 0x61, 0x5f,
+	0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x07,
 	0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
 	0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
-	0x52, 0x0a, 0x73, 0x65, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x64, 0x12, 0x17, 0x0a, 0x07,
-	0x65, 0x72, 0x72, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65,
-	0x72, 0x72, 0x4d, 0x73, 0x67, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64,
-	0x5f, 0x61, 0x74, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
-	0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65,
-	0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74,
-	0x22, 0x80, 0x02, 0x0a, 0x11, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x61, 0x6c, 0x44, 0x65,
-	0x61, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x63,
-	0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x6f, 0x6f, 0x74, 0x43, 0x69,
-	0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52,
-	0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x70, 0x72, 0x69,
-	0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x50, 0x72, 0x69,
-	0x63, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e,
-	0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x70, 0x61,
-	0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x3a, 0x0a,
-	0x19, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61,
-	0x6c, 0x5f, 0x69, 0x6e, 0x63, 0x72, 0x65, 0x61, 0x73, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04,
-	0x52, 0x17, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61,
-	0x6c, 0x49, 0x6e, 0x63, 0x72, 0x65, 0x61, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x69, 0x6e,
-	0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x12,
-	0x22, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x64,
-	0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x50, 0x65, 0x65,
-	0x72, 0x49, 0x64, 0x22, 0xa5, 0x03, 0x0a, 0x13, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x61,
-	0x6c, 0x44, 0x65, 0x61, 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x61,
-	0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64,
-	0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20,
-	0x01, 0x28, 0x03, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x41, 0x0a, 0x09, 0x64, 0x65, 0x61,
-	0x6c, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x70,
-	0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
-	0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x61, 0x6c, 0x44, 0x65, 0x61, 0x6c, 0x49, 0x6e,
-	0x66, 0x6f, 0x52, 0x08, 0x64, 0x65, 0x61, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x4a, 0x0a, 0x13,
-	0x64, 0x61, 0x74, 0x61, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x5f, 0x73, 0x74,
-	0x61, 0x72, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
-	0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65,
-	0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x11, 0x64, 0x61, 0x74, 0x61, 0x54, 0x72, 0x61, 0x6e, 0x73,
-	0x66, 0x65, 0x72, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x46, 0x0a, 0x11, 0x64, 0x61, 0x74, 0x61,
-	0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x05, 0x20,
-	0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
-	0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52,
-	0x0f, 0x64, 0x61, 0x74, 0x61, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x45, 0x6e, 0x64,
-	0x12, 0x17, 0x0a, 0x07, 0x65, 0x72, 0x72, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x06, 0x65, 0x72, 0x72, 0x4d, 0x73, 0x67, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64,
-	0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
-	0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
-	0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74,
-	0x65, 0x64, 0x41, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x02, 0x69, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x72, 0x65,
-	0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x62, 0x79,
-	0x74, 0x65, 0x73, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x2a, 0xa0, 0x01, 0x0a, 0x09,
-	0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x0a, 0x16, 0x4a, 0x4f, 0x42,
-	0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46,
-	0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x4a, 0x4f, 0x42, 0x5f, 0x53, 0x54, 0x41,
-	0x54, 0x55, 0x53, 0x5f, 0x51, 0x55, 0x45, 0x55, 0x45, 0x44, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14,
-	0x4a, 0x4f, 0x42, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55,
-	0x54, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x4a, 0x4f, 0x42, 0x5f, 0x53, 0x54,
-	0x41, 0x54, 0x55, 0x53, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x12, 0x17, 0x0a,
-	0x13, 0x4a, 0x4f, 0x42, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x41, 0x4e, 0x43,
-	0x45, 0x4c, 0x45, 0x44, 0x10, 0x04, 0x12, 0x16, 0x0a, 0x12, 0x4a, 0x4f, 0x42, 0x5f, 0x53, 0x54,
-	0x41, 0x54, 0x55, 0x53, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x05, 0x2a, 0xc3,
-	0x01, 0x0a, 0x13, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x73, 0x53, 0x65,
-	0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x25, 0x0a, 0x21, 0x53, 0x54, 0x4f, 0x52, 0x41, 0x47,
-	0x45, 0x5f, 0x4a, 0x4f, 0x42, 0x53, 0x5f, 0x53, 0x45, 0x4c, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x5f,
-	0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1d, 0x0a,
-	0x19, 0x53, 0x54, 0x4f, 0x52, 0x41, 0x47, 0x45, 0x5f, 0x4a, 0x4f, 0x42, 0x53, 0x5f, 0x53, 0x45,
-	0x4c, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x5f, 0x41, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x20, 0x0a, 0x1c,
-	0x53, 0x54, 0x4f, 0x52, 0x41, 0x47, 0x45, 0x5f, 0x4a, 0x4f, 0x42, 0x53, 0x5f, 0x53, 0x45, 0x4c,
-	0x45, 0x43, 0x54, 0x4f, 0x52, 0x5f, 0x51, 0x55, 0x45, 0x55, 0x45, 0x44, 0x10, 0x02, 0x12, 0x23,
-	0x0a, 0x1f, 0x53, 0x54, 0x4f, 0x52, 0x41, 0x47, 0x45, 0x5f, 0x4a, 0x4f, 0x42, 0x53, 0x5f, 0x53,
-	0x45, 0x4c, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x49, 0x4e,
-	0x47, 0x10, 0x03, 0x12, 0x1f, 0x0a, 0x1b, 0x53, 0x54, 0x4f, 0x52, 0x41, 0x47, 0x45, 0x5f, 0x4a,
-	0x4f, 0x42, 0x53, 0x5f, 0x53, 0x45, 0x4c, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x5f, 0x46, 0x49, 0x4e,
-	0x41, 0x4c, 0x10, 0x04, 0x32, 0xeb, 0x16, 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x72,
-	0x76, 0x69, 0x63, 0x65, 0x12, 0x58, 0x0a, 0x09, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66,
-	0x6f, 0x12, 0x23, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73,
-	0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52,
-	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61,
-	0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64,
-	0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x67,
-	0x0a, 0x0e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72,
-	0x12, 0x28, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65,
-	0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66,
-	0x69, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x70, 0x6f, 0x77,
-	0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55,
-	0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x65, 0x73,
-	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x79, 0x0a, 0x14, 0x44, 0x65, 0x66, 0x61, 0x75,
-	0x6c, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
-	0x2e, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72,
-	0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61,
-	0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
-	0x2f, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72,
-	0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61,
-	0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
-	0x22, 0x00, 0x12, 0x82, 0x01, 0x0a, 0x17, 0x53, 0x65, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c,
-	0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x31,
+	0x52, 0x11, 0x64, 0x61, 0x74, 0x61, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x53, 0x74,
+	0x61, 0x72, 0x74, 0x12, 0x46, 0x0a, 0x11, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x74, 0x72, 0x61, 0x6e,
+	0x73, 0x66, 0x65, 0x72, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,
+	0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66,
+	0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0f, 0x64, 0x61, 0x74, 0x61,
+	0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x12, 0x3f, 0x0a, 0x0d, 0x73,
+	0x65, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x09, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c,
+	0x73, 0x65, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x3b, 0x0a, 0x0b,
+	0x73, 0x65, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x73,
+	0x65, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x45, 0x6e, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x65, 0x72, 0x72,
+	0x5f, 0x6d, 0x73, 0x67, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x65, 0x72, 0x72, 0x4d,
+	0x73, 0x67, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74,
+	0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
+	0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x80, 0x02,
+	0x0a, 0x11, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x61, 0x6c, 0x44, 0x65, 0x61, 0x6c, 0x49,
+	0x6e, 0x66, 0x6f, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x63, 0x69, 0x64, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x6f, 0x6f, 0x74, 0x43, 0x69, 0x64, 0x12, 0x12,
+	0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69,
+	0x7a, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18,
+	0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12,
+	0x29, 0x0a, 0x10, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72,
+	0x76, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x70, 0x61, 0x79, 0x6d, 0x65,
+	0x6e, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x3a, 0x0a, 0x19, 0x70, 0x61,
+	0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x69,
+	0x6e, 0x63, 0x72, 0x65, 0x61, 0x73, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x17, 0x70,
+	0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x49, 0x6e,
+	0x63, 0x72, 0x65, 0x61, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x18,
+	0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x22, 0x0a, 0x0d,
+	0x6d, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x50, 0x65, 0x65, 0x72, 0x49, 0x64,
+	0x22, 0xa5, 0x03, 0x0a, 0x13, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x61, 0x6c, 0x44, 0x65,
+	0x61, 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72,
+	0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65,
+	0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03,
+	0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x41, 0x0a, 0x09, 0x64, 0x65, 0x61, 0x6c, 0x5f, 0x69,
+	0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x70, 0x6f, 0x77, 0x65,
+	0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65,
+	0x74, 0x72, 0x69, 0x65, 0x76, 0x61, 0x6c, 0x44, 0x65, 0x61, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x52,
+	0x08, 0x64, 0x65, 0x61, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x4a, 0x0a, 0x13, 0x64, 0x61, 0x74,
+	0x61, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74,
+	0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
+	0x6d, 0x70, 0x52, 0x11, 0x64, 0x61, 0x74, 0x61, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72,
+	0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x46, 0x0a, 0x11, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x74, 0x72,
+	0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x5f, 0x65, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
+	0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0f, 0x64, 0x61,
+	0x74, 0x61, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x12, 0x17, 0x0a,
+	0x07, 0x65, 0x72, 0x72, 0x5f, 0x6d, 0x73, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
+	0x65, 0x72, 0x72, 0x4d, 0x73, 0x67, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65,
+	0x64, 0x5f, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
+	0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d,
+	0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41,
+	0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69,
+	0x64, 0x12, 0x25, 0x0a, 0x0e, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69,
+	0x76, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x62, 0x79, 0x74, 0x65, 0x73,
+	0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x2a, 0xa0, 0x01, 0x0a, 0x09, 0x4a, 0x6f, 0x62,
+	0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x0a, 0x16, 0x4a, 0x4f, 0x42, 0x5f, 0x53, 0x54,
+	0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44,
+	0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x4a, 0x4f, 0x42, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53,
+	0x5f, 0x51, 0x55, 0x45, 0x55, 0x45, 0x44, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x4a, 0x4f, 0x42,
+	0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x49, 0x4e,
+	0x47, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x4a, 0x4f, 0x42, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55,
+	0x53, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x12, 0x17, 0x0a, 0x13, 0x4a, 0x4f,
+	0x42, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x45,
+	0x44, 0x10, 0x04, 0x12, 0x16, 0x0a, 0x12, 0x4a, 0x4f, 0x42, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x55,
+	0x53, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x05, 0x2a, 0xc3, 0x01, 0x0a, 0x13,
+	0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x73, 0x53, 0x65, 0x6c, 0x65, 0x63,
+	0x74, 0x6f, 0x72, 0x12, 0x25, 0x0a, 0x21, 0x53, 0x54, 0x4f, 0x52, 0x41, 0x47, 0x45, 0x5f, 0x4a,
+	0x4f, 0x42, 0x53, 0x5f, 0x53, 0x45, 0x4c, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x5f, 0x55, 0x4e, 0x53,
+	0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1d, 0x0a, 0x19, 0x53, 0x54,
+	0x4f, 0x52, 0x41, 0x47, 0x45, 0x5f, 0x4a, 0x4f, 0x42, 0x53, 0x5f, 0x53, 0x45, 0x4c, 0x45, 0x43,
+	0x54, 0x4f, 0x52, 0x5f, 0x41, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x20, 0x0a, 0x1c, 0x53, 0x54, 0x4f,
+	0x52, 0x41, 0x47, 0x45, 0x5f, 0x4a, 0x4f, 0x42, 0x53, 0x5f, 0x53, 0x45, 0x4c, 0x45, 0x43, 0x54,
+	0x4f, 0x52, 0x5f, 0x51, 0x55, 0x45, 0x55, 0x45, 0x44, 0x10, 0x02, 0x12, 0x23, 0x0a, 0x1f, 0x53,
+	0x54, 0x4f, 0x52, 0x41, 0x47, 0x45, 0x5f, 0x4a, 0x4f, 0x42, 0x53, 0x5f, 0x53, 0x45, 0x4c, 0x45,
+	0x43, 0x54, 0x4f, 0x52, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x03,
+	0x12, 0x1f, 0x0a, 0x1b, 0x53, 0x54, 0x4f, 0x52, 0x41, 0x47, 0x45, 0x5f, 0x4a, 0x4f, 0x42, 0x53,
+	0x5f, 0x53, 0x45, 0x4c, 0x45, 0x43, 0x54, 0x4f, 0x52, 0x5f, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x10,
+	0x04, 0x32, 0xeb, 0x16, 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
+	0x65, 0x12, 0x58, 0x0a, 0x09, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x23,
 	0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
-	0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x74, 0x6f,
-	0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
-	0x74, 0x1a, 0x32, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73,
-	0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74,
-	0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73,
-	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x73, 0x0a, 0x12, 0x41, 0x70, 0x70, 0x6c, 0x79,
-	0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2c, 0x2e,
+	0x76, 0x31, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e,
+	0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x66,
+	0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x67, 0x0a, 0x0e, 0x55,
+	0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x28, 0x2e,
 	0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76,
-	0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f,
-	0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x70, 0x6f,
+	0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72,
+	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67,
+	0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72,
+	0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
+	0x73, 0x65, 0x22, 0x00, 0x12, 0x79, 0x0a, 0x14, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53,
+	0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x2e, 0x70,
+	0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
+	0x2e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43,
+	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x70,
+	0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
+	0x2e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43,
+	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12,
+	0x82, 0x01, 0x0a, 0x17, 0x53, 0x65, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x74,
+	0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x31, 0x2e, 0x70, 0x6f,
 	0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
-	0x41, 0x70, 0x70, 0x6c, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66,
-	0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4f, 0x0a, 0x06,
-	0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x12, 0x20, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61,
-	0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76,
-	0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72,
-	0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d,
-	0x6f, 0x76, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4e, 0x0a,
-	0x05, 0x53, 0x74, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61,
+	0x53, 0x65, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67,
+	0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32,
+	0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
+	0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x53, 0x74, 0x6f,
+	0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
+	0x73, 0x65, 0x22, 0x00, 0x12, 0x73, 0x0a, 0x12, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x53, 0x74, 0x6f,
+	0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2c, 0x2e, 0x70, 0x6f, 0x77,
+	0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41,
+	0x70, 0x70, 0x6c, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+	0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72,
+	0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x70, 0x70,
+	0x6c, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52,
+	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4f, 0x0a, 0x06, 0x52, 0x65, 0x6d,
+	0x6f, 0x76, 0x65, 0x12, 0x20, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e,
+	0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65,
+	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74,
+	0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65,
+	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4e, 0x0a, 0x05, 0x53, 0x74,
+	0x61, 0x67, 0x65, 0x12, 0x1f, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e,
+	0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65,
+	0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x65,
+	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x12, 0x55, 0x0a, 0x08, 0x53, 0x74,
+	0x61, 0x67, 0x65, 0x43, 0x69, 0x64, 0x12, 0x22, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61,
 	0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x67, 0x65,
-	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67,
-	0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x61, 0x67,
-	0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x12, 0x55, 0x0a,
-	0x08, 0x53, 0x74, 0x61, 0x67, 0x65, 0x43, 0x69, 0x64, 0x12, 0x22, 0x2e, 0x70, 0x6f, 0x77, 0x65,
-	0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74,
-	0x61, 0x67, 0x65, 0x43, 0x69, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e,
+	0x43, 0x69, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x70, 0x6f, 0x77,
+	0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53,
+	0x74, 0x61, 0x67, 0x65, 0x43, 0x69, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
+	0x00, 0x12, 0x5e, 0x0a, 0x0b, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x44, 0x61, 0x74, 0x61,
+	0x12, 0x25, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65,
+	0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x44, 0x61, 0x74, 0x61,
+	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67,
+	0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6c,
+	0x61, 0x63, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
+	0x00, 0x12, 0x48, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x1d, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72,
+	0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74,
+	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67,
+	0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52,
+	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x5a, 0x0a, 0x09, 0x57,
+	0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x23, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72,
+	0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x61, 0x74,
+	0x63, 0x68, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e,
 	0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76,
-	0x31, 0x2e, 0x53, 0x74, 0x61, 0x67, 0x65, 0x43, 0x69, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
-	0x73, 0x65, 0x22, 0x00, 0x12, 0x5e, 0x0a, 0x0b, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x44,
-	0x61, 0x74, 0x61, 0x12, 0x25, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e,
-	0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x44,
-	0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x70, 0x6f, 0x77,
-	0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52,
-	0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
-	0x73, 0x65, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x1d, 0x2e, 0x70, 0x6f,
+	0x31, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
+	0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x5b, 0x0a, 0x0a, 0x43, 0x69, 0x64, 0x53, 0x75,
+	0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x24, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74,
+	0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x69, 0x64, 0x53, 0x75, 0x6d,
+	0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x70, 0x6f,
 	0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
-	0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x70, 0x6f, 0x77,
-	0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x47,
-	0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x5a,
-	0x0a, 0x09, 0x57, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x23, 0x2e, 0x70, 0x6f,
-	0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
-	0x57, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
-	0x1a, 0x24, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65,
-	0x72, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65,
-	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x5b, 0x0a, 0x0a, 0x43, 0x69,
-	0x64, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x24, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72,
-	0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x69, 0x64,
-	0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25,
-	0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
-	0x76, 0x31, 0x2e, 0x43, 0x69, 0x64, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73,
-	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x52, 0x0a, 0x07, 0x43, 0x69, 0x64, 0x49, 0x6e,
-	0x66, 0x6f, 0x12, 0x21, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75,
+	0x43, 0x69, 0x64, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
+	0x73, 0x65, 0x22, 0x00, 0x12, 0x52, 0x0a, 0x07, 0x43, 0x69, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x12,
+	0x21, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72,
+	0x2e, 0x76, 0x31, 0x2e, 0x43, 0x69, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x1a, 0x22, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75,
 	0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x69, 0x64, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65,
-	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74,
-	0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x69, 0x64, 0x49, 0x6e, 0x66,
-	0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x52, 0x0a, 0x07, 0x42,
-	0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x21, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61,
+	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x52, 0x0a, 0x07, 0x42, 0x61, 0x6c, 0x61,
+	0x6e, 0x63, 0x65, 0x12, 0x21, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e,
+	0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52,
+	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61,
 	0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e,
-	0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x70, 0x6f, 0x77, 0x65,
-	0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61,
-	0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12,
-	0x5b, 0x0a, 0x0a, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x24, 0x2e,
-	0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76,
-	0x31, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75,
-	0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e,
-	0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65,
-	0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x09,
-	0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x23, 0x2e, 0x70, 0x6f, 0x77, 0x65,
-	0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64,
-	0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24,
+	0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5b, 0x0a, 0x0a,
+	0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x24, 0x2e, 0x70, 0x6f, 0x77,
+	0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4e,
+	0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x1a, 0x25, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65,
+	0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52,
+	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x58, 0x0a, 0x09, 0x41, 0x64, 0x64,
+	0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x23, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61,
+	0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65,
+	0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x70, 0x6f,
+	0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
+	0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+	0x65, 0x22, 0x00, 0x12, 0x52, 0x0a, 0x07, 0x53, 0x65, 0x6e, 0x64, 0x46, 0x69, 0x6c, 0x12, 0x21,
 	0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
-	0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70,
-	0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x52, 0x0a, 0x07, 0x53, 0x65, 0x6e, 0x64, 0x46, 0x69,
-	0x6c, 0x12, 0x21, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73,
-	0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x46, 0x69, 0x6c, 0x52, 0x65, 0x71,
-	0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65,
-	0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x46, 0x69, 0x6c,
-	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5e, 0x0a, 0x0b, 0x53, 0x69,
-	0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x25, 0x2e, 0x70, 0x6f, 0x77, 0x65,
-	0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69,
-	0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
-	0x1a, 0x26, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65,
-	0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
-	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x64, 0x0a, 0x0d, 0x56, 0x65,
-	0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x27, 0x2e, 0x70, 0x6f,
+	0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x46, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+	0x74, 0x1a, 0x22, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73,
+	0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x46, 0x69, 0x6c, 0x52, 0x65, 0x73,
+	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5e, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x4d,
+	0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x25, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61,
+	0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d,
+	0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e,
+	0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76,
+	0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73,
+	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x64, 0x0a, 0x0d, 0x56, 0x65, 0x72, 0x69, 0x66,
+	0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x27, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72,
+	0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72,
+	0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+	0x74, 0x1a, 0x28, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73,
+	0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73,
+	0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5e, 0x0a,
+	0x0b, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x25, 0x2e, 0x70,
+	0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
+	0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e,
+	0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49,
+	0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6a, 0x0a,
+	0x0f, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f,
+	0x12, 0x29, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65,
+	0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
+	0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x70, 0x6f,
 	0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e,
-	0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71,
-	0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65,
-	0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x4d,
-	0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
-	0x12, 0x5e, 0x0a, 0x0b, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12,
-	0x25, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72,
-	0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52,
-	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61,
-	0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61,
-	0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
-	0x12, 0x6a, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49,
-	0x6e, 0x66, 0x6f, 0x12, 0x29, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e,
-	0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72,
-	0x61, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a,
+	0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52,
+	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5b, 0x0a, 0x0a, 0x53, 0x74, 0x6f,
+	0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x24, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67,
+	0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72,
+	0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e,
+	0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76,
+	0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70,
+	0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x76, 0x0a, 0x13, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67,
+	0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x6f, 0x72, 0x4a, 0x6f, 0x62, 0x12, 0x2d, 0x2e,
+	0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76,
+	0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46,
+	0x6f, 0x72, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x70,
+	0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
+	0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x6f,
+	0x72, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x6a,
+	0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62,
+	0x73, 0x12, 0x29, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73,
+	0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67,
+	0x65, 0x4a, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x70,
+	0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
+	0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x73,
+	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x73, 0x0a, 0x12, 0x53, 0x74,
+	0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x73, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79,
+	0x12, 0x2c, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65,
+	0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x73,
+	0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d,
 	0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e,
-	0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, 0x6e,
-	0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5b, 0x0a, 0x0a,
-	0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x24, 0x2e, 0x70, 0x6f, 0x77,
-	0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53,
-	0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
-	0x1a, 0x25, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65,
-	0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x52,
-	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x76, 0x0a, 0x13, 0x53, 0x74, 0x6f,
-	0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x6f, 0x72, 0x4a, 0x6f, 0x62,
-	0x12, 0x2d, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65,
-	0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66,
-	0x69, 0x67, 0x46, 0x6f, 0x72, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
-	0x2e, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72,
-	0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69,
-	0x67, 0x46, 0x6f, 0x72, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
-	0x00, 0x12, 0x6a, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
-	0x4a, 0x6f, 0x62, 0x73, 0x12, 0x29, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65,
-	0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f,
+	0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x73, 0x53, 0x75,
+	0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12,
+	0x6f, 0x0a, 0x10, 0x57, 0x61, 0x74, 0x63, 0x68, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a,
+	0x6f, 0x62, 0x73, 0x12, 0x2a, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e,
+	0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x53, 0x74, 0x6f,
 	0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
-	0x2a, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72,
-	0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a,
-	0x6f, 0x62, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x73, 0x0a,
-	0x12, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x73, 0x53, 0x75, 0x6d, 0x6d,
-	0x61, 0x72, 0x79, 0x12, 0x2c, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e,
-	0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a,
-	0x6f, 0x62, 0x73, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
-	0x74, 0x1a, 0x2d, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73,
-	0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62,
-	0x73, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
-	0x22, 0x00, 0x12, 0x6f, 0x0a, 0x10, 0x57, 0x61, 0x74, 0x63, 0x68, 0x53, 0x74, 0x6f, 0x72, 0x61,
-	0x67, 0x65, 0x4a, 0x6f, 0x62, 0x73, 0x12, 0x2a, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61,
-	0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68,
-	0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
-	0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75,
-	0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x53, 0x74, 0x6f, 0x72,
-	0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
-	0x00, 0x30, 0x01, 0x12, 0x6d, 0x0a, 0x10, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x53, 0x74, 0x6f,
-	0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x12, 0x2a, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67,
-	0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6e, 0x63,
-	0x65, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75,
-	0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e,
-	0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x53, 0x74,
-	0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
-	0x22, 0x00, 0x12, 0x73, 0x0a, 0x12, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x44, 0x65, 0x61,
-	0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x2c, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72,
-	0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f,
-	0x72, 0x61, 0x67, 0x65, 0x44, 0x65, 0x61, 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52,
-	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61,
-	0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61,
-	0x67, 0x65, 0x44, 0x65, 0x61, 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73,
-	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x79, 0x0a, 0x14, 0x52, 0x65, 0x74, 0x72, 0x69,
-	0x65, 0x76, 0x61, 0x6c, 0x44, 0x65, 0x61, 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12,
-	0x2e, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72,
-	0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x61, 0x6c, 0x44, 0x65, 0x61,
-	0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
-	0x2f, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72,
-	0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x61, 0x6c, 0x44, 0x65, 0x61,
-	0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
-	0x22, 0x00, 0x42, 0x44, 0x5a, 0x42, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
-	0x2f, 0x74, 0x65, 0x78, 0x74, 0x69, 0x6c, 0x65, 0x69, 0x6f, 0x2f, 0x70, 0x6f, 0x77, 0x65, 0x72,
-	0x67, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x32, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f,
-	0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x76,
-	0x31, 0x3b, 0x75, 0x73, 0x65, 0x72, 0x50, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x2b, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72,
+	0x2e, 0x76, 0x31, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
+	0x4a, 0x6f, 0x62, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01,
+	0x12, 0x6d, 0x0a, 0x10, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67,
+	0x65, 0x4a, 0x6f, 0x62, 0x12, 0x2a, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65,
+	0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x53,
+	0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x1a, 0x2b, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65,
+	0x72, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x61,
+	0x67, 0x65, 0x4a, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12,
+	0x73, 0x0a, 0x12, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x44, 0x65, 0x61, 0x6c, 0x52, 0x65,
+	0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x2c, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74,
+	0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67,
+	0x65, 0x44, 0x65, 0x61, 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e,
+	0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x44,
+	0x65, 0x61, 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
+	0x73, 0x65, 0x22, 0x00, 0x12, 0x79, 0x0a, 0x14, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x61,
+	0x6c, 0x44, 0x65, 0x61, 0x6c, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x2e, 0x2e, 0x70,
+	0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
+	0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x61, 0x6c, 0x44, 0x65, 0x61, 0x6c, 0x52, 0x65,
+	0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x70,
+	0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31,
+	0x2e, 0x52, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x61, 0x6c, 0x44, 0x65, 0x61, 0x6c, 0x52, 0x65,
+	0x63, 0x6f, 0x72, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42,
+	0x44, 0x5a, 0x42, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x65,
+	0x78, 0x74, 0x69, 0x6c, 0x65, 0x69, 0x6f, 0x2f, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x67, 0x61, 0x74,
+	0x65, 0x2f, 0x76, 0x32, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x6f, 0x77,
+	0x65, 0x72, 0x67, 0x61, 0x74, 0x65, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x3b, 0x75,
+	0x73, 0x65, 0x72, 0x50, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -5879,7 +6261,7 @@ func file_powergate_user_v1_user_proto_rawDescGZIP() []byte {
 }
 
 var file_powergate_user_v1_user_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
-var file_powergate_user_v1_user_proto_msgTypes = make([]protoimpl.MessageInfo, 83)
+var file_powergate_user_v1_user_proto_msgTypes = make([]protoimpl.MessageInfo, 89)
 var file_powergate_user_v1_user_proto_goTypes = []interface{}{
 	(JobStatus)(0),                          // 0: powergate.user.v1.JobStatus
 	(StorageJobsSelector)(0),                // 1: powergate.user.v1.StorageJobsSelector
@@ -5948,136 +6330,148 @@ var file_powergate_user_v1_user_proto_goTypes = []interface{}{
 	(*FilRenew)(nil),                        // 64: powergate.user.v1.FilRenew
 	(*FilConfig)(nil),                       // 65: powergate.user.v1.FilConfig
 	(*ColdConfig)(nil),                      // 66: powergate.user.v1.ColdConfig
-	(*StorageConfig)(nil),                   // 67: powergate.user.v1.StorageConfig
-	(*IpfsHotInfo)(nil),                     // 68: powergate.user.v1.IpfsHotInfo
-	(*HotInfo)(nil),                         // 69: powergate.user.v1.HotInfo
-	(*FilStorage)(nil),                      // 70: powergate.user.v1.FilStorage
-	(*FilInfo)(nil),                         // 71: powergate.user.v1.FilInfo
-	(*ColdInfo)(nil),                        // 72: powergate.user.v1.ColdInfo
-	(*StorageInfo)(nil),                     // 73: powergate.user.v1.StorageInfo
-	(*CidInfo)(nil),                         // 74: powergate.user.v1.CidInfo
-	(*DealInfo)(nil),                        // 75: powergate.user.v1.DealInfo
-	(*StorageJob)(nil),                      // 76: powergate.user.v1.StorageJob
-	(*DealError)(nil),                       // 77: powergate.user.v1.DealError
-	(*LogEntry)(nil),                        // 78: powergate.user.v1.LogEntry
-	(*DealRecordsConfig)(nil),               // 79: powergate.user.v1.DealRecordsConfig
-	(*StorageDealInfo)(nil),                 // 80: powergate.user.v1.StorageDealInfo
-	(*StorageDealRecord)(nil),               // 81: powergate.user.v1.StorageDealRecord
-	(*RetrievalDealInfo)(nil),               // 82: powergate.user.v1.RetrievalDealInfo
-	(*RetrievalDealRecord)(nil),             // 83: powergate.user.v1.RetrievalDealRecord
-	(*AddrInfo_VerifiedClientInfo)(nil),     // 84: powergate.user.v1.AddrInfo.VerifiedClientInfo
-	(*timestamppb.Timestamp)(nil),           // 85: google.protobuf.Timestamp
+	(*WebhookAuthData)(nil),                 // 67: powergate.user.v1.WebhookAuthData
+	(*WebhookAuthentication)(nil),           // 68: powergate.user.v1.WebhookAuthentication
+	(*Webhook)(nil),                         // 69: powergate.user.v1.Webhook
+	(*WebhookAlert)(nil),                    // 70: powergate.user.v1.WebhookAlert
+	(*WebhookConfiguration)(nil),            // 71: powergate.user.v1.WebhookConfiguration
+	(*NotificationConfig)(nil),              // 72: powergate.user.v1.NotificationConfig
+	(*StorageConfig)(nil),                   // 73: powergate.user.v1.StorageConfig
+	(*IpfsHotInfo)(nil),                     // 74: powergate.user.v1.IpfsHotInfo
+	(*HotInfo)(nil),                         // 75: powergate.user.v1.HotInfo
+	(*FilStorage)(nil),                      // 76: powergate.user.v1.FilStorage
+	(*FilInfo)(nil),                         // 77: powergate.user.v1.FilInfo
+	(*ColdInfo)(nil),                        // 78: powergate.user.v1.ColdInfo
+	(*StorageInfo)(nil),                     // 79: powergate.user.v1.StorageInfo
+	(*CidInfo)(nil),                         // 80: powergate.user.v1.CidInfo
+	(*DealInfo)(nil),                        // 81: powergate.user.v1.DealInfo
+	(*StorageJob)(nil),                      // 82: powergate.user.v1.StorageJob
+	(*DealError)(nil),                       // 83: powergate.user.v1.DealError
+	(*LogEntry)(nil),                        // 84: powergate.user.v1.LogEntry
+	(*DealRecordsConfig)(nil),               // 85: powergate.user.v1.DealRecordsConfig
+	(*StorageDealInfo)(nil),                 // 86: powergate.user.v1.StorageDealInfo
+	(*StorageDealRecord)(nil),               // 87: powergate.user.v1.StorageDealRecord
+	(*RetrievalDealInfo)(nil),               // 88: powergate.user.v1.RetrievalDealInfo
+	(*RetrievalDealRecord)(nil),             // 89: powergate.user.v1.RetrievalDealRecord
+	(*AddrInfo_VerifiedClientInfo)(nil),     // 90: powergate.user.v1.AddrInfo.VerifiedClientInfo
+	(*timestamppb.Timestamp)(nil),           // 91: google.protobuf.Timestamp
 }
 var file_powergate_user_v1_user_proto_depIdxs = []int32{
-	67, // 0: powergate.user.v1.DefaultStorageConfigResponse.default_storage_config:type_name -> powergate.user.v1.StorageConfig
-	67, // 1: powergate.user.v1.SetDefaultStorageConfigRequest.config:type_name -> powergate.user.v1.StorageConfig
-	67, // 2: powergate.user.v1.ApplyStorageConfigRequest.config:type_name -> powergate.user.v1.StorageConfig
-	78, // 3: powergate.user.v1.WatchLogsResponse.log_entry:type_name -> powergate.user.v1.LogEntry
+	73, // 0: powergate.user.v1.DefaultStorageConfigResponse.default_storage_config:type_name -> powergate.user.v1.StorageConfig
+	73, // 1: powergate.user.v1.SetDefaultStorageConfigRequest.config:type_name -> powergate.user.v1.StorageConfig
+	73, // 2: powergate.user.v1.ApplyStorageConfigRequest.config:type_name -> powergate.user.v1.StorageConfig
+	84, // 3: powergate.user.v1.WatchLogsResponse.log_entry:type_name -> powergate.user.v1.LogEntry
 	61, // 4: powergate.user.v1.CidSummaryResponse.cid_summary:type_name -> powergate.user.v1.CidSummary
-	74, // 5: powergate.user.v1.CidInfoResponse.cid_info:type_name -> powergate.user.v1.CidInfo
+	80, // 5: powergate.user.v1.CidInfoResponse.cid_info:type_name -> powergate.user.v1.CidInfo
 	60, // 6: powergate.user.v1.AddressesResponse.addresses:type_name -> powergate.user.v1.AddrInfo
-	73, // 7: powergate.user.v1.StorageInfoResponse.storage_info:type_name -> powergate.user.v1.StorageInfo
-	73, // 8: powergate.user.v1.ListStorageInfoResponse.storage_info:type_name -> powergate.user.v1.StorageInfo
-	76, // 9: powergate.user.v1.StorageJobResponse.storage_job:type_name -> powergate.user.v1.StorageJob
-	67, // 10: powergate.user.v1.StorageConfigForJobResponse.storage_config:type_name -> powergate.user.v1.StorageConfig
+	79, // 7: powergate.user.v1.StorageInfoResponse.storage_info:type_name -> powergate.user.v1.StorageInfo
+	79, // 8: powergate.user.v1.ListStorageInfoResponse.storage_info:type_name -> powergate.user.v1.StorageInfo
+	82, // 9: powergate.user.v1.StorageJobResponse.storage_job:type_name -> powergate.user.v1.StorageJob
+	73, // 10: powergate.user.v1.StorageConfigForJobResponse.storage_config:type_name -> powergate.user.v1.StorageConfig
 	1,  // 11: powergate.user.v1.ListStorageJobsRequest.selector:type_name -> powergate.user.v1.StorageJobsSelector
-	76, // 12: powergate.user.v1.ListStorageJobsResponse.storage_jobs:type_name -> powergate.user.v1.StorageJob
-	76, // 13: powergate.user.v1.WatchStorageJobsResponse.storage_job:type_name -> powergate.user.v1.StorageJob
-	79, // 14: powergate.user.v1.StorageDealRecordsRequest.config:type_name -> powergate.user.v1.DealRecordsConfig
-	81, // 15: powergate.user.v1.StorageDealRecordsResponse.records:type_name -> powergate.user.v1.StorageDealRecord
-	79, // 16: powergate.user.v1.RetrievalDealRecordsRequest.config:type_name -> powergate.user.v1.DealRecordsConfig
-	83, // 17: powergate.user.v1.RetrievalDealRecordsResponse.records:type_name -> powergate.user.v1.RetrievalDealRecord
-	84, // 18: powergate.user.v1.AddrInfo.verified_client_info:type_name -> powergate.user.v1.AddrInfo.VerifiedClientInfo
+	82, // 12: powergate.user.v1.ListStorageJobsResponse.storage_jobs:type_name -> powergate.user.v1.StorageJob
+	82, // 13: powergate.user.v1.WatchStorageJobsResponse.storage_job:type_name -> powergate.user.v1.StorageJob
+	85, // 14: powergate.user.v1.StorageDealRecordsRequest.config:type_name -> powergate.user.v1.DealRecordsConfig
+	87, // 15: powergate.user.v1.StorageDealRecordsResponse.records:type_name -> powergate.user.v1.StorageDealRecord
+	85, // 16: powergate.user.v1.RetrievalDealRecordsRequest.config:type_name -> powergate.user.v1.DealRecordsConfig
+	89, // 17: powergate.user.v1.RetrievalDealRecordsResponse.records:type_name -> powergate.user.v1.RetrievalDealRecord
+	90, // 18: powergate.user.v1.AddrInfo.verified_client_info:type_name -> powergate.user.v1.AddrInfo.VerifiedClientInfo
 	62, // 19: powergate.user.v1.HotConfig.ipfs:type_name -> powergate.user.v1.IpfsConfig
 	64, // 20: powergate.user.v1.FilConfig.renew:type_name -> powergate.user.v1.FilRenew
 	65, // 21: powergate.user.v1.ColdConfig.filecoin:type_name -> powergate.user.v1.FilConfig
-	63, // 22: powergate.user.v1.StorageConfig.hot:type_name -> powergate.user.v1.HotConfig
-	66, // 23: powergate.user.v1.StorageConfig.cold:type_name -> powergate.user.v1.ColdConfig
-	68, // 24: powergate.user.v1.HotInfo.ipfs:type_name -> powergate.user.v1.IpfsHotInfo
-	70, // 25: powergate.user.v1.FilInfo.proposals:type_name -> powergate.user.v1.FilStorage
-	71, // 26: powergate.user.v1.ColdInfo.filecoin:type_name -> powergate.user.v1.FilInfo
-	69, // 27: powergate.user.v1.StorageInfo.hot:type_name -> powergate.user.v1.HotInfo
-	72, // 28: powergate.user.v1.StorageInfo.cold:type_name -> powergate.user.v1.ColdInfo
-	67, // 29: powergate.user.v1.CidInfo.latest_pushed_storage_config:type_name -> powergate.user.v1.StorageConfig
-	73, // 30: powergate.user.v1.CidInfo.current_storage_info:type_name -> powergate.user.v1.StorageInfo
-	76, // 31: powergate.user.v1.CidInfo.queued_storage_jobs:type_name -> powergate.user.v1.StorageJob
-	76, // 32: powergate.user.v1.CidInfo.executing_storage_job:type_name -> powergate.user.v1.StorageJob
-	0,  // 33: powergate.user.v1.StorageJob.status:type_name -> powergate.user.v1.JobStatus
-	75, // 34: powergate.user.v1.StorageJob.deal_info:type_name -> powergate.user.v1.DealInfo
-	77, // 35: powergate.user.v1.StorageJob.deal_errors:type_name -> powergate.user.v1.DealError
-	80, // 36: powergate.user.v1.StorageDealRecord.deal_info:type_name -> powergate.user.v1.StorageDealInfo
-	85, // 37: powergate.user.v1.StorageDealRecord.data_transfer_start:type_name -> google.protobuf.Timestamp
-	85, // 38: powergate.user.v1.StorageDealRecord.data_transfer_end:type_name -> google.protobuf.Timestamp
-	85, // 39: powergate.user.v1.StorageDealRecord.sealing_start:type_name -> google.protobuf.Timestamp
-	85, // 40: powergate.user.v1.StorageDealRecord.sealing_end:type_name -> google.protobuf.Timestamp
-	85, // 41: powergate.user.v1.StorageDealRecord.updated_at:type_name -> google.protobuf.Timestamp
-	82, // 42: powergate.user.v1.RetrievalDealRecord.deal_info:type_name -> powergate.user.v1.RetrievalDealInfo
-	85, // 43: powergate.user.v1.RetrievalDealRecord.data_transfer_start:type_name -> google.protobuf.Timestamp
-	85, // 44: powergate.user.v1.RetrievalDealRecord.data_transfer_end:type_name -> google.protobuf.Timestamp
-	85, // 45: powergate.user.v1.RetrievalDealRecord.updated_at:type_name -> google.protobuf.Timestamp
-	2,  // 46: powergate.user.v1.UserService.BuildInfo:input_type -> powergate.user.v1.BuildInfoRequest
-	4,  // 47: powergate.user.v1.UserService.UserIdentifier:input_type -> powergate.user.v1.UserIdentifierRequest
-	6,  // 48: powergate.user.v1.UserService.DefaultStorageConfig:input_type -> powergate.user.v1.DefaultStorageConfigRequest
-	8,  // 49: powergate.user.v1.UserService.SetDefaultStorageConfig:input_type -> powergate.user.v1.SetDefaultStorageConfigRequest
-	14, // 50: powergate.user.v1.UserService.ApplyStorageConfig:input_type -> powergate.user.v1.ApplyStorageConfigRequest
-	20, // 51: powergate.user.v1.UserService.Remove:input_type -> powergate.user.v1.RemoveRequest
-	10, // 52: powergate.user.v1.UserService.Stage:input_type -> powergate.user.v1.StageRequest
-	12, // 53: powergate.user.v1.UserService.StageCid:input_type -> powergate.user.v1.StageCidRequest
-	16, // 54: powergate.user.v1.UserService.ReplaceData:input_type -> powergate.user.v1.ReplaceDataRequest
-	18, // 55: powergate.user.v1.UserService.Get:input_type -> powergate.user.v1.GetRequest
-	22, // 56: powergate.user.v1.UserService.WatchLogs:input_type -> powergate.user.v1.WatchLogsRequest
-	24, // 57: powergate.user.v1.UserService.CidSummary:input_type -> powergate.user.v1.CidSummaryRequest
-	26, // 58: powergate.user.v1.UserService.CidInfo:input_type -> powergate.user.v1.CidInfoRequest
-	28, // 59: powergate.user.v1.UserService.Balance:input_type -> powergate.user.v1.BalanceRequest
-	30, // 60: powergate.user.v1.UserService.NewAddress:input_type -> powergate.user.v1.NewAddressRequest
-	32, // 61: powergate.user.v1.UserService.Addresses:input_type -> powergate.user.v1.AddressesRequest
-	34, // 62: powergate.user.v1.UserService.SendFil:input_type -> powergate.user.v1.SendFilRequest
-	36, // 63: powergate.user.v1.UserService.SignMessage:input_type -> powergate.user.v1.SignMessageRequest
-	38, // 64: powergate.user.v1.UserService.VerifyMessage:input_type -> powergate.user.v1.VerifyMessageRequest
-	40, // 65: powergate.user.v1.UserService.StorageInfo:input_type -> powergate.user.v1.StorageInfoRequest
-	42, // 66: powergate.user.v1.UserService.ListStorageInfo:input_type -> powergate.user.v1.ListStorageInfoRequest
-	46, // 67: powergate.user.v1.UserService.StorageJob:input_type -> powergate.user.v1.StorageJobRequest
-	48, // 68: powergate.user.v1.UserService.StorageConfigForJob:input_type -> powergate.user.v1.StorageConfigForJobRequest
-	50, // 69: powergate.user.v1.UserService.ListStorageJobs:input_type -> powergate.user.v1.ListStorageJobsRequest
-	52, // 70: powergate.user.v1.UserService.StorageJobsSummary:input_type -> powergate.user.v1.StorageJobsSummaryRequest
-	54, // 71: powergate.user.v1.UserService.WatchStorageJobs:input_type -> powergate.user.v1.WatchStorageJobsRequest
-	44, // 72: powergate.user.v1.UserService.CancelStorageJob:input_type -> powergate.user.v1.CancelStorageJobRequest
-	56, // 73: powergate.user.v1.UserService.StorageDealRecords:input_type -> powergate.user.v1.StorageDealRecordsRequest
-	58, // 74: powergate.user.v1.UserService.RetrievalDealRecords:input_type -> powergate.user.v1.RetrievalDealRecordsRequest
-	3,  // 75: powergate.user.v1.UserService.BuildInfo:output_type -> powergate.user.v1.BuildInfoResponse
-	5,  // 76: powergate.user.v1.UserService.UserIdentifier:output_type -> powergate.user.v1.UserIdentifierResponse
-	7,  // 77: powergate.user.v1.UserService.DefaultStorageConfig:output_type -> powergate.user.v1.DefaultStorageConfigResponse
-	9,  // 78: powergate.user.v1.UserService.SetDefaultStorageConfig:output_type -> powergate.user.v1.SetDefaultStorageConfigResponse
-	15, // 79: powergate.user.v1.UserService.ApplyStorageConfig:output_type -> powergate.user.v1.ApplyStorageConfigResponse
-	21, // 80: powergate.user.v1.UserService.Remove:output_type -> powergate.user.v1.RemoveResponse
-	11, // 81: powergate.user.v1.UserService.Stage:output_type -> powergate.user.v1.StageResponse
-	13, // 82: powergate.user.v1.UserService.StageCid:output_type -> powergate.user.v1.StageCidResponse
-	17, // 83: powergate.user.v1.UserService.ReplaceData:output_type -> powergate.user.v1.ReplaceDataResponse
-	19, // 84: powergate.user.v1.UserService.Get:output_type -> powergate.user.v1.GetResponse
-	23, // 85: powergate.user.v1.UserService.WatchLogs:output_type -> powergate.user.v1.WatchLogsResponse
-	25, // 86: powergate.user.v1.UserService.CidSummary:output_type -> powergate.user.v1.CidSummaryResponse
-	27, // 87: powergate.user.v1.UserService.CidInfo:output_type -> powergate.user.v1.CidInfoResponse
-	29, // 88: powergate.user.v1.UserService.Balance:output_type -> powergate.user.v1.BalanceResponse
-	31, // 89: powergate.user.v1.UserService.NewAddress:output_type -> powergate.user.v1.NewAddressResponse
-	33, // 90: powergate.user.v1.UserService.Addresses:output_type -> powergate.user.v1.AddressesResponse
-	35, // 91: powergate.user.v1.UserService.SendFil:output_type -> powergate.user.v1.SendFilResponse
-	37, // 92: powergate.user.v1.UserService.SignMessage:output_type -> powergate.user.v1.SignMessageResponse
-	39, // 93: powergate.user.v1.UserService.VerifyMessage:output_type -> powergate.user.v1.VerifyMessageResponse
-	41, // 94: powergate.user.v1.UserService.StorageInfo:output_type -> powergate.user.v1.StorageInfoResponse
-	43, // 95: powergate.user.v1.UserService.ListStorageInfo:output_type -> powergate.user.v1.ListStorageInfoResponse
-	47, // 96: powergate.user.v1.UserService.StorageJob:output_type -> powergate.user.v1.StorageJobResponse
-	49, // 97: powergate.user.v1.UserService.StorageConfigForJob:output_type -> powergate.user.v1.StorageConfigForJobResponse
-	51, // 98: powergate.user.v1.UserService.ListStorageJobs:output_type -> powergate.user.v1.ListStorageJobsResponse
-	53, // 99: powergate.user.v1.UserService.StorageJobsSummary:output_type -> powergate.user.v1.StorageJobsSummaryResponse
-	55, // 100: powergate.user.v1.UserService.WatchStorageJobs:output_type -> powergate.user.v1.WatchStorageJobsResponse
-	45, // 101: powergate.user.v1.UserService.CancelStorageJob:output_type -> powergate.user.v1.CancelStorageJobResponse
-	57, // 102: powergate.user.v1.UserService.StorageDealRecords:output_type -> powergate.user.v1.StorageDealRecordsResponse
-	59, // 103: powergate.user.v1.UserService.RetrievalDealRecords:output_type -> powergate.user.v1.RetrievalDealRecordsResponse
-	75, // [75:104] is the sub-list for method output_type
-	46, // [46:75] is the sub-list for method input_type
-	46, // [46:46] is the sub-list for extension type_name
-	46, // [46:46] is the sub-list for extension extendee
-	0,  // [0:46] is the sub-list for field type_name
+	67, // 22: powergate.user.v1.WebhookAuthentication.data:type_name -> powergate.user.v1.WebhookAuthData
+	68, // 23: powergate.user.v1.Webhook.authentication:type_name -> powergate.user.v1.WebhookAuthentication
+	70, // 24: powergate.user.v1.WebhookConfiguration.alerts:type_name -> powergate.user.v1.WebhookAlert
+	69, // 25: powergate.user.v1.NotificationConfig.webhook:type_name -> powergate.user.v1.Webhook
+	71, // 26: powergate.user.v1.NotificationConfig.configuration:type_name -> powergate.user.v1.WebhookConfiguration
+	63, // 27: powergate.user.v1.StorageConfig.hot:type_name -> powergate.user.v1.HotConfig
+	66, // 28: powergate.user.v1.StorageConfig.cold:type_name -> powergate.user.v1.ColdConfig
+	72, // 29: powergate.user.v1.StorageConfig.notifications:type_name -> powergate.user.v1.NotificationConfig
+	74, // 30: powergate.user.v1.HotInfo.ipfs:type_name -> powergate.user.v1.IpfsHotInfo
+	76, // 31: powergate.user.v1.FilInfo.proposals:type_name -> powergate.user.v1.FilStorage
+	77, // 32: powergate.user.v1.ColdInfo.filecoin:type_name -> powergate.user.v1.FilInfo
+	75, // 33: powergate.user.v1.StorageInfo.hot:type_name -> powergate.user.v1.HotInfo
+	78, // 34: powergate.user.v1.StorageInfo.cold:type_name -> powergate.user.v1.ColdInfo
+	73, // 35: powergate.user.v1.CidInfo.latest_pushed_storage_config:type_name -> powergate.user.v1.StorageConfig
+	79, // 36: powergate.user.v1.CidInfo.current_storage_info:type_name -> powergate.user.v1.StorageInfo
+	82, // 37: powergate.user.v1.CidInfo.queued_storage_jobs:type_name -> powergate.user.v1.StorageJob
+	82, // 38: powergate.user.v1.CidInfo.executing_storage_job:type_name -> powergate.user.v1.StorageJob
+	0,  // 39: powergate.user.v1.StorageJob.status:type_name -> powergate.user.v1.JobStatus
+	81, // 40: powergate.user.v1.StorageJob.deal_info:type_name -> powergate.user.v1.DealInfo
+	83, // 41: powergate.user.v1.StorageJob.deal_errors:type_name -> powergate.user.v1.DealError
+	86, // 42: powergate.user.v1.StorageDealRecord.deal_info:type_name -> powergate.user.v1.StorageDealInfo
+	91, // 43: powergate.user.v1.StorageDealRecord.data_transfer_start:type_name -> google.protobuf.Timestamp
+	91, // 44: powergate.user.v1.StorageDealRecord.data_transfer_end:type_name -> google.protobuf.Timestamp
+	91, // 45: powergate.user.v1.StorageDealRecord.sealing_start:type_name -> google.protobuf.Timestamp
+	91, // 46: powergate.user.v1.StorageDealRecord.sealing_end:type_name -> google.protobuf.Timestamp
+	91, // 47: powergate.user.v1.StorageDealRecord.updated_at:type_name -> google.protobuf.Timestamp
+	88, // 48: powergate.user.v1.RetrievalDealRecord.deal_info:type_name -> powergate.user.v1.RetrievalDealInfo
+	91, // 49: powergate.user.v1.RetrievalDealRecord.data_transfer_start:type_name -> google.protobuf.Timestamp
+	91, // 50: powergate.user.v1.RetrievalDealRecord.data_transfer_end:type_name -> google.protobuf.Timestamp
+	91, // 51: powergate.user.v1.RetrievalDealRecord.updated_at:type_name -> google.protobuf.Timestamp
+	2,  // 52: powergate.user.v1.UserService.BuildInfo:input_type -> powergate.user.v1.BuildInfoRequest
+	4,  // 53: powergate.user.v1.UserService.UserIdentifier:input_type -> powergate.user.v1.UserIdentifierRequest
+	6,  // 54: powergate.user.v1.UserService.DefaultStorageConfig:input_type -> powergate.user.v1.DefaultStorageConfigRequest
+	8,  // 55: powergate.user.v1.UserService.SetDefaultStorageConfig:input_type -> powergate.user.v1.SetDefaultStorageConfigRequest
+	14, // 56: powergate.user.v1.UserService.ApplyStorageConfig:input_type -> powergate.user.v1.ApplyStorageConfigRequest
+	20, // 57: powergate.user.v1.UserService.Remove:input_type -> powergate.user.v1.RemoveRequest
+	10, // 58: powergate.user.v1.UserService.Stage:input_type -> powergate.user.v1.StageRequest
+	12, // 59: powergate.user.v1.UserService.StageCid:input_type -> powergate.user.v1.StageCidRequest
+	16, // 60: powergate.user.v1.UserService.ReplaceData:input_type -> powergate.user.v1.ReplaceDataRequest
+	18, // 61: powergate.user.v1.UserService.Get:input_type -> powergate.user.v1.GetRequest
+	22, // 62: powergate.user.v1.UserService.WatchLogs:input_type -> powergate.user.v1.WatchLogsRequest
+	24, // 63: powergate.user.v1.UserService.CidSummary:input_type -> powergate.user.v1.CidSummaryRequest
+	26, // 64: powergate.user.v1.UserService.CidInfo:input_type -> powergate.user.v1.CidInfoRequest
+	28, // 65: powergate.user.v1.UserService.Balance:input_type -> powergate.user.v1.BalanceRequest
+	30, // 66: powergate.user.v1.UserService.NewAddress:input_type -> powergate.user.v1.NewAddressRequest
+	32, // 67: powergate.user.v1.UserService.Addresses:input_type -> powergate.user.v1.AddressesRequest
+	34, // 68: powergate.user.v1.UserService.SendFil:input_type -> powergate.user.v1.SendFilRequest
+	36, // 69: powergate.user.v1.UserService.SignMessage:input_type -> powergate.user.v1.SignMessageRequest
+	38, // 70: powergate.user.v1.UserService.VerifyMessage:input_type -> powergate.user.v1.VerifyMessageRequest
+	40, // 71: powergate.user.v1.UserService.StorageInfo:input_type -> powergate.user.v1.StorageInfoRequest
+	42, // 72: powergate.user.v1.UserService.ListStorageInfo:input_type -> powergate.user.v1.ListStorageInfoRequest
+	46, // 73: powergate.user.v1.UserService.StorageJob:input_type -> powergate.user.v1.StorageJobRequest
+	48, // 74: powergate.user.v1.UserService.StorageConfigForJob:input_type -> powergate.user.v1.StorageConfigForJobRequest
+	50, // 75: powergate.user.v1.UserService.ListStorageJobs:input_type -> powergate.user.v1.ListStorageJobsRequest
+	52, // 76: powergate.user.v1.UserService.StorageJobsSummary:input_type -> powergate.user.v1.StorageJobsSummaryRequest
+	54, // 77: powergate.user.v1.UserService.WatchStorageJobs:input_type -> powergate.user.v1.WatchStorageJobsRequest
+	44, // 78: powergate.user.v1.UserService.CancelStorageJob:input_type -> powergate.user.v1.CancelStorageJobRequest
+	56, // 79: powergate.user.v1.UserService.StorageDealRecords:input_type -> powergate.user.v1.StorageDealRecordsRequest
+	58, // 80: powergate.user.v1.UserService.RetrievalDealRecords:input_type -> powergate.user.v1.RetrievalDealRecordsRequest
+	3,  // 81: powergate.user.v1.UserService.BuildInfo:output_type -> powergate.user.v1.BuildInfoResponse
+	5,  // 82: powergate.user.v1.UserService.UserIdentifier:output_type -> powergate.user.v1.UserIdentifierResponse
+	7,  // 83: powergate.user.v1.UserService.DefaultStorageConfig:output_type -> powergate.user.v1.DefaultStorageConfigResponse
+	9,  // 84: powergate.user.v1.UserService.SetDefaultStorageConfig:output_type -> powergate.user.v1.SetDefaultStorageConfigResponse
+	15, // 85: powergate.user.v1.UserService.ApplyStorageConfig:output_type -> powergate.user.v1.ApplyStorageConfigResponse
+	21, // 86: powergate.user.v1.UserService.Remove:output_type -> powergate.user.v1.RemoveResponse
+	11, // 87: powergate.user.v1.UserService.Stage:output_type -> powergate.user.v1.StageResponse
+	13, // 88: powergate.user.v1.UserService.StageCid:output_type -> powergate.user.v1.StageCidResponse
+	17, // 89: powergate.user.v1.UserService.ReplaceData:output_type -> powergate.user.v1.ReplaceDataResponse
+	19, // 90: powergate.user.v1.UserService.Get:output_type -> powergate.user.v1.GetResponse
+	23, // 91: powergate.user.v1.UserService.WatchLogs:output_type -> powergate.user.v1.WatchLogsResponse
+	25, // 92: powergate.user.v1.UserService.CidSummary:output_type -> powergate.user.v1.CidSummaryResponse
+	27, // 93: powergate.user.v1.UserService.CidInfo:output_type -> powergate.user.v1.CidInfoResponse
+	29, // 94: powergate.user.v1.UserService.Balance:output_type -> powergate.user.v1.BalanceResponse
+	31, // 95: powergate.user.v1.UserService.NewAddress:output_type -> powergate.user.v1.NewAddressResponse
+	33, // 96: powergate.user.v1.UserService.Addresses:output_type -> powergate.user.v1.AddressesResponse
+	35, // 97: powergate.user.v1.UserService.SendFil:output_type -> powergate.user.v1.SendFilResponse
+	37, // 98: powergate.user.v1.UserService.SignMessage:output_type -> powergate.user.v1.SignMessageResponse
+	39, // 99: powergate.user.v1.UserService.VerifyMessage:output_type -> powergate.user.v1.VerifyMessageResponse
+	41, // 100: powergate.user.v1.UserService.StorageInfo:output_type -> powergate.user.v1.StorageInfoResponse
+	43, // 101: powergate.user.v1.UserService.ListStorageInfo:output_type -> powergate.user.v1.ListStorageInfoResponse
+	47, // 102: powergate.user.v1.UserService.StorageJob:output_type -> powergate.user.v1.StorageJobResponse
+	49, // 103: powergate.user.v1.UserService.StorageConfigForJob:output_type -> powergate.user.v1.StorageConfigForJobResponse
+	51, // 104: powergate.user.v1.UserService.ListStorageJobs:output_type -> powergate.user.v1.ListStorageJobsResponse
+	53, // 105: powergate.user.v1.UserService.StorageJobsSummary:output_type -> powergate.user.v1.StorageJobsSummaryResponse
+	55, // 106: powergate.user.v1.UserService.WatchStorageJobs:output_type -> powergate.user.v1.WatchStorageJobsResponse
+	45, // 107: powergate.user.v1.UserService.CancelStorageJob:output_type -> powergate.user.v1.CancelStorageJobResponse
+	57, // 108: powergate.user.v1.UserService.StorageDealRecords:output_type -> powergate.user.v1.StorageDealRecordsResponse
+	59, // 109: powergate.user.v1.UserService.RetrievalDealRecords:output_type -> powergate.user.v1.RetrievalDealRecordsResponse
+	81, // [81:110] is the sub-list for method output_type
+	52, // [52:81] is the sub-list for method input_type
+	52, // [52:52] is the sub-list for extension type_name
+	52, // [52:52] is the sub-list for extension extendee
+	0,  // [0:52] is the sub-list for field type_name
 }
 
 func init() { file_powergate_user_v1_user_proto_init() }
@@ -6867,7 +7261,7 @@ func file_powergate_user_v1_user_proto_init() {
 			}
 		}
 		file_powergate_user_v1_user_proto_msgTypes[65].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*StorageConfig); i {
+			switch v := v.(*WebhookAuthData); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -6879,7 +7273,7 @@ func file_powergate_user_v1_user_proto_init() {
 			}
 		}
 		file_powergate_user_v1_user_proto_msgTypes[66].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*IpfsHotInfo); i {
+			switch v := v.(*WebhookAuthentication); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -6891,7 +7285,7 @@ func file_powergate_user_v1_user_proto_init() {
 			}
 		}
 		file_powergate_user_v1_user_proto_msgTypes[67].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*HotInfo); i {
+			switch v := v.(*Webhook); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -6903,7 +7297,7 @@ func file_powergate_user_v1_user_proto_init() {
 			}
 		}
 		file_powergate_user_v1_user_proto_msgTypes[68].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*FilStorage); i {
+			switch v := v.(*WebhookAlert); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -6915,7 +7309,7 @@ func file_powergate_user_v1_user_proto_init() {
 			}
 		}
 		file_powergate_user_v1_user_proto_msgTypes[69].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*FilInfo); i {
+			switch v := v.(*WebhookConfiguration); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -6927,7 +7321,7 @@ func file_powergate_user_v1_user_proto_init() {
 			}
 		}
 		file_powergate_user_v1_user_proto_msgTypes[70].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*ColdInfo); i {
+			switch v := v.(*NotificationConfig); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -6939,7 +7333,7 @@ func file_powergate_user_v1_user_proto_init() {
 			}
 		}
 		file_powergate_user_v1_user_proto_msgTypes[71].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*StorageInfo); i {
+			switch v := v.(*StorageConfig); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -6951,7 +7345,7 @@ func file_powergate_user_v1_user_proto_init() {
 			}
 		}
 		file_powergate_user_v1_user_proto_msgTypes[72].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*CidInfo); i {
+			switch v := v.(*IpfsHotInfo); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -6963,7 +7357,7 @@ func file_powergate_user_v1_user_proto_init() {
 			}
 		}
 		file_powergate_user_v1_user_proto_msgTypes[73].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*DealInfo); i {
+			switch v := v.(*HotInfo); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -6975,7 +7369,7 @@ func file_powergate_user_v1_user_proto_init() {
 			}
 		}
 		file_powergate_user_v1_user_proto_msgTypes[74].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*StorageJob); i {
+			switch v := v.(*FilStorage); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -6987,7 +7381,7 @@ func file_powergate_user_v1_user_proto_init() {
 			}
 		}
 		file_powergate_user_v1_user_proto_msgTypes[75].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*DealError); i {
+			switch v := v.(*FilInfo); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -6999,7 +7393,7 @@ func file_powergate_user_v1_user_proto_init() {
 			}
 		}
 		file_powergate_user_v1_user_proto_msgTypes[76].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*LogEntry); i {
+			switch v := v.(*ColdInfo); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -7011,7 +7405,7 @@ func file_powergate_user_v1_user_proto_init() {
 			}
 		}
 		file_powergate_user_v1_user_proto_msgTypes[77].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*DealRecordsConfig); i {
+			switch v := v.(*StorageInfo); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -7023,7 +7417,7 @@ func file_powergate_user_v1_user_proto_init() {
 			}
 		}
 		file_powergate_user_v1_user_proto_msgTypes[78].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*StorageDealInfo); i {
+			switch v := v.(*CidInfo); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -7035,7 +7429,7 @@ func file_powergate_user_v1_user_proto_init() {
 			}
 		}
 		file_powergate_user_v1_user_proto_msgTypes[79].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*StorageDealRecord); i {
+			switch v := v.(*DealInfo); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -7047,7 +7441,7 @@ func file_powergate_user_v1_user_proto_init() {
 			}
 		}
 		file_powergate_user_v1_user_proto_msgTypes[80].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*RetrievalDealInfo); i {
+			switch v := v.(*StorageJob); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -7059,7 +7453,7 @@ func file_powergate_user_v1_user_proto_init() {
 			}
 		}
 		file_powergate_user_v1_user_proto_msgTypes[81].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*RetrievalDealRecord); i {
+			switch v := v.(*DealError); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -7071,6 +7465,78 @@ func file_powergate_user_v1_user_proto_init() {
 			}
 		}
 		file_powergate_user_v1_user_proto_msgTypes[82].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*LogEntry); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_powergate_user_v1_user_proto_msgTypes[83].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*DealRecordsConfig); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_powergate_user_v1_user_proto_msgTypes[84].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*StorageDealInfo); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_powergate_user_v1_user_proto_msgTypes[85].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*StorageDealRecord); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_powergate_user_v1_user_proto_msgTypes[86].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*RetrievalDealInfo); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_powergate_user_v1_user_proto_msgTypes[87].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*RetrievalDealRecord); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_powergate_user_v1_user_proto_msgTypes[88].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*AddrInfo_VerifiedClientInfo); i {
 			case 0:
 				return &v.state
@@ -7089,7 +7555,7 @@ func file_powergate_user_v1_user_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_powergate_user_v1_user_proto_rawDesc,
 			NumEnums:      2,
-			NumMessages:   83,
+			NumMessages:   89,
 			NumExtensions: 0,
 			NumServices:   1,
 		},
diff --git a/api/server/server.go b/api/server/server.go
index 7c0dc1884..6334d272b 100644
--- a/api/server/server.go
+++ b/api/server/server.go
@@ -50,6 +50,7 @@ import (
 	"github.com/textileio/powergate/v2/iplocation/maxmind"
 	"github.com/textileio/powergate/v2/lotus"
 	"github.com/textileio/powergate/v2/migration"
+	"github.com/textileio/powergate/v2/notifications"
 	"github.com/textileio/powergate/v2/reputation"
 	txndstr "github.com/textileio/powergate/v2/txndstransform"
 	"github.com/textileio/powergate/v2/util"
@@ -253,8 +254,10 @@ func NewServer(conf Config) (*Server, error) {
 		conf.DealWatchPollDuration = time.Second
 	}
 
+	nt := notifications.New()
+
 	log.Info("Starting deals module...")
-	dm, err := dealsModule.New(txndstr.Wrap(ds, "deals"), clientBuilder, conf.DealWatchPollDuration, conf.FFSDealFinalityTimeout, deals.WithImportPath(filepath.Join(conf.RepoPath, "imports")))
+	dm, err := dealsModule.New(txndstr.Wrap(ds, "deals"), nt, clientBuilder, conf.DealWatchPollDuration, conf.FFSDealFinalityTimeout, deals.WithImportPath(filepath.Join(conf.RepoPath, "imports")))
 	if err != nil {
 		return nil, fmt.Errorf("creating deal module: %s", err)
 	}
diff --git a/api/server/user/storageconfig.go b/api/server/user/storageconfig.go
index f8c9b4c49..44425d7f1 100644
--- a/api/server/user/storageconfig.go
+++ b/api/server/user/storageconfig.go
@@ -28,9 +28,10 @@ func (s *Service) SetDefaultStorageConfig(ctx context.Context, req *userPb.SetDe
 		return nil, err
 	}
 	defaultConfig := ffs.StorageConfig{
-		Repairable: req.Config.Repairable,
-		Hot:        fromRPCHotConfig(req.Config.Hot),
-		Cold:       fromRPCColdConfig(req.Config.Cold),
+		Repairable:    req.Config.Repairable,
+		Hot:           fromRPCHotConfig(req.Config.Hot),
+		Cold:          fromRPCColdConfig(req.Config.Cold),
+		Notifications: fromRPCNotificationConfigs(req.Config.Notifications),
 	}
 	if err := i.SetDefaultStorageConfig(defaultConfig); err != nil {
 		return nil, err
@@ -54,9 +55,10 @@ func (s *Service) ApplyStorageConfig(ctx context.Context, req *userPb.ApplyStora
 
 	if req.HasConfig {
 		config := ffs.StorageConfig{
-			Repairable: req.Config.Repairable,
-			Hot:        fromRPCHotConfig(req.Config.Hot),
-			Cold:       fromRPCColdConfig(req.Config.Cold),
+			Repairable:    req.Config.Repairable,
+			Hot:           fromRPCHotConfig(req.Config.Hot),
+			Cold:          fromRPCColdConfig(req.Config.Cold),
+			Notifications: fromRPCNotificationConfigs(req.Config.Notifications),
 		}
 		options = append(options, api.WithStorageConfig(config))
 	}
diff --git a/api/server/user/util.go b/api/server/user/util.go
index 9defe3e2f..7cbfaab15 100644
--- a/api/server/user/util.go
+++ b/api/server/user/util.go
@@ -8,9 +8,10 @@ import (
 
 func toRPCStorageConfig(config ffs.StorageConfig) *userPb.StorageConfig {
 	return &userPb.StorageConfig{
-		Repairable: config.Repairable,
-		Hot:        toRPCHotConfig(config.Hot),
-		Cold:       toRPCColdConfig(config.Cold),
+		Repairable:    config.Repairable,
+		Hot:           toRPCHotConfig(config.Hot),
+		Cold:          toRPCColdConfig(config.Cold),
+		Notifications: toRPCNotificationConfigs(config.Notifications),
 	}
 }
 
@@ -47,6 +48,103 @@ func toRPCColdConfig(config ffs.ColdConfig) *userPb.ColdConfig {
 	}
 }
 
+func toRPCNotificationConfigs(notifications []*ffs.NotificationConfig) []*userPb.NotificationConfig {
+	if notifications == nil {
+		return nil
+	}
+
+	var out []*userPb.NotificationConfig
+
+	for _, cfg := range notifications {
+		if res := toRPCNotificationConfig(cfg); res != nil {
+			out = append(out, res)
+		}
+	}
+
+	return out
+}
+
+func toRPCNotificationConfig(cfg *ffs.NotificationConfig) *userPb.NotificationConfig {
+	if cfg == nil {
+		return nil
+	}
+
+	return &userPb.NotificationConfig{
+		Webhook:       toRPCWebhook(cfg.Webhook),
+		Configuration: toRPCWebhookConfiguration(cfg.Configuration),
+	}
+}
+
+func toRPCWebhook(webhook *ffs.Webhook) *userPb.Webhook {
+	if webhook == nil {
+		return nil
+	}
+
+	return &userPb.Webhook{
+		Endpoint:       webhook.Endpoint,
+		Authentication: toRPCWebhookAuth(webhook.Authentication),
+	}
+}
+
+func toRPCWebhookAuth(authentication *ffs.WebhookAuthentication) *userPb.WebhookAuthentication {
+	if authentication == nil {
+		return nil
+	}
+
+	return &userPb.WebhookAuthentication{
+		Type: authentication.Type,
+		Data: toRPCWebhookAuthData(authentication.Data),
+	}
+}
+
+func toRPCWebhookAuthData(data *ffs.WebhookAuthData) *userPb.WebhookAuthData {
+	if data == nil {
+		return nil
+	}
+
+	return &userPb.WebhookAuthData{
+		Username: data.Username,
+		Password: data.Password,
+	}
+}
+
+func toRPCWebhookConfiguration(configuration *ffs.WebhookConfiguration) *userPb.WebhookConfiguration {
+	if configuration == nil {
+		return nil
+	}
+
+	return &userPb.WebhookConfiguration{
+		Events: configuration.Events,
+		Alerts: toRPCWebhookAlerts(configuration.Alerts),
+	}
+}
+
+func toRPCWebhookAlerts(alerts []*ffs.WebhookAlert) []*userPb.WebhookAlert {
+	if alerts == nil {
+		return nil
+	}
+
+	var out []*userPb.WebhookAlert
+	for _, alert := range alerts {
+		if res := toRPCWebhookAlert(alert); res != nil {
+			out = append(out, res)
+		}
+	}
+
+	return out
+}
+
+func toRPCWebhookAlert(alert *ffs.WebhookAlert) *userPb.WebhookAlert {
+	if alert == nil {
+		return nil
+	}
+
+	return &userPb.WebhookAlert{
+		Type:      alert.Type,
+		Threshold: alert.Threshold,
+	}
+}
+
 func fromRPCHotConfig(config *userPb.HotConfig) ffs.HotConfig {
 	res := ffs.HotConfig{}
 	if config != nil {
@@ -93,6 +191,105 @@ func fromRPCColdConfig(config *userPb.ColdConfig) ffs.ColdConfig {
 	return res
 }
 
+func fromRPCNotificationConfigs(configs []*userPb.NotificationConfig) []*ffs.NotificationConfig {
+	if configs == nil {
+		return nil
+	}
+
+	var out []*ffs.NotificationConfig
+	for _, cfg := range configs {
+		res := fromRPCNotificationConfig(cfg)
+
+		if res != nil {
+			out = append(out, res)
+		}
+	}
+
+	return out
+}
+
+func fromRPCNotificationConfig(config *userPb.NotificationConfig) *ffs.NotificationConfig {
+	if config == nil {
+		return nil
+	}
+
+	return &ffs.NotificationConfig{
+		Webhook:       fromRPCWebhook(config.Webhook),
+		Configuration: fromRPCWebhookConfiguration(config.Configuration),
+	}
+}
+
+func fromRPCWebhookConfiguration(configuration *userPb.WebhookConfiguration) *ffs.WebhookConfiguration {
+	if configuration == nil {
+		return nil
+	}
+
+	return &ffs.WebhookConfiguration{
+		Events: configuration.Events,
+		Alerts: fromRPCWebhookAlerts(configuration.Alerts),
+	}
+}
+
+func fromRPCWebhookAlerts(alerts []*userPb.WebhookAlert) []*ffs.WebhookAlert {
+	if alerts == nil {
+		return nil
+	}
+
+	var out []*ffs.WebhookAlert
+	for _, alert := range alerts {
+		res := fromRPCWebhookAlert(alert)
+		if res != nil {
+			out = append(out, res)
+		}
+	}
+
+	return out
+}
+
+func fromRPCWebhookAlert(alert *userPb.WebhookAlert) *ffs.WebhookAlert {
+	if alert == nil {
+		return nil
+	}
+
+	return &ffs.WebhookAlert{
+		Type:      alert.Type,
+		Threshold: alert.Threshold,
+	}
+}
+
+func fromRPCWebhook(webhook *userPb.Webhook) *ffs.Webhook {
+	if webhook == nil {
+		return nil
+	}
+
+	return &ffs.Webhook{
+		Endpoint:       webhook.Endpoint,
+		Authentication: fromRPCWebhookAuthentication(webhook.Authentication),
+	}
+}
+
+func fromRPCWebhookAuthentication(authentication *userPb.WebhookAuthentication) *ffs.WebhookAuthentication {
+	if authentication == nil {
+		return nil
+	}
+
+	return &ffs.WebhookAuthentication{
+		Type: authentication.Type,
+		Data: fromRPCWebhookAuthenticationData(authentication.Data),
+	}
+}
+
+func fromRPCWebhookAuthenticationData(data *userPb.WebhookAuthData) *ffs.WebhookAuthData {
+	if data == nil {
+		return nil
+	}
+
+	return &ffs.WebhookAuthData{
+		Username: data.Username,
+		Password: data.Password,
+	}
+}
+
 func buildListDealRecordsOptions(conf *userPb.DealRecordsConfig) []deals.DealRecordsOption {
 	var opts []deals.DealRecordsOption
 	if conf != nil {
diff --git a/deals/module/module.go b/deals/module/module.go
index a45cc2228..bf1d78cd9 100644
--- a/deals/module/module.go
+++ b/deals/module/module.go
@@ -19,6 +19,7 @@ import (
 	"github.com/textileio/powergate/v2/deals/module/dealwatcher"
 	"github.com/textileio/powergate/v2/deals/module/store"
 	"github.com/textileio/powergate/v2/lotus"
+	"github.com/textileio/powergate/v2/notifications"
 	"go.opentelemetry.io/otel/metric"
 )
 
@@ -40,7 +41,7 @@ type Module struct {
 }
 
 // New creates a new Module.
-func New(ds datastore.TxnDatastore, clientBuilder lotus.ClientBuilder, pollDuration time.Duration, dealFinalityTimeout time.Duration, opts ...deals.Option) (*Module, error) {
+func New(ds datastore.TxnDatastore, nt notifications.Notifier, clientBuilder lotus.ClientBuilder, pollDuration time.Duration, dealFinalityTimeout time.Duration, opts ...deals.Option) (*Module, error) {
 	var cfg deals.Config
 	for _, o := range opts {
 		if err := o(&cfg); err != nil {
@@ -56,7 +57,7 @@ func New(ds datastore.TxnDatastore, clientBuilder lotus.ClientBuilder, pollDurat
 	m := &Module{
 		clientBuilder:       clientBuilder,
 		cfg:                 &cfg,
-		store:               store.New(ds),
+		store:               store.New(ds, nt),
 		pollDuration:        pollDuration,
 		dealFinalityTimeout: dealFinalityTimeout,
 		dealWatcher:         dw,
diff --git a/deals/module/store/store.go b/deals/module/store/store.go
index b0782039e..99f4932dd 100644
--- a/deals/module/store/store.go
+++ b/deals/module/store/store.go
@@ -17,6 +17,7 @@ import (
 	"github.com/ipfs/go-datastore/query"
 	logging "github.com/ipfs/go-log/v2"
 	"github.com/textileio/powergate/v2/deals"
+	"github.com/textileio/powergate/v2/notifications"
 	"github.com/textileio/powergate/v2/util"
 	"go.opentelemetry.io/otel/metric"
 )
@@ -39,6 +40,7 @@ var (
 // Store stores deal and retrieval records.
 type Store struct {
 	ds   datastore.TxnDatastore
+	nt   notifications.Notifier
 	lock sync.Mutex
 
 	metricFinalTotal  metric.Int64Counter
@@ -46,9 +48,10 @@ type Store struct {
 }
 
 // New returns a new *Store.
-func New(ds datastore.TxnDatastore) *Store {
+func New(ds datastore.TxnDatastore, nt notifications.Notifier) *Store {
 	s := &Store{
 		ds: ds,
+		nt: nt,
 	}
 	s.initMetrics()
 
@@ -112,6 +115,8 @@ func (s *Store) PutStorageDeal(dr deals.StorageDealRecord) error {
 		return fmt.Errorf("committing transaction: %s", err)
 	}
 
+	s.nt.NotifyDeal(dr)
+
 	return nil
 }
 
diff --git a/ffs/integrationtest/manager/manager.go b/ffs/integrationtest/manager/manager.go
index 906dfb8ae..a94c40a16 100644
--- a/ffs/integrationtest/manager/manager.go
+++ b/ffs/integrationtest/manager/manager.go
@@ -20,6 +20,7 @@ import (
 	"github.com/textileio/powergate/v2/ffs/scheduler"
 	"github.com/textileio/powergate/v2/filchain"
 	"github.com/textileio/powergate/v2/lotus"
+	"github.com/textileio/powergate/v2/notifications"
 	"github.com/textileio/powergate/v2/tests"
 	"github.com/textileio/powergate/v2/util"
 
@@ -79,7 +80,8 @@ func NewFFSManager(t require.TestingT, ds datastore.TxnDatastore, clientBuilder
 
 // NewCustomFFSManager returns a new customized FFS manager.
 func NewCustomFFSManager(t require.TestingT, ds datastore.TxnDatastore, cb lotus.ClientBuilder, masterAddr address.Address, ms ffs.MinerSelector, ipfsClient *httpapi.HttpApi, minimumPieceSize uint64) (*manager.Manager, *coreipfs.CoreIpfs, func()) {
-	dm, err := dealsModule.New(txndstr.Wrap(ds, "deals"), cb, util.AvgBlockTime, time.Minute*10)
+	nt := notifications.New()
+	dm, err := dealsModule.New(txndstr.Wrap(ds, "deals"), nt, cb, util.AvgBlockTime, time.Minute*10)
 	require.NoError(t, err)
 
 	fchain := filchain.New(cb)
diff --git a/ffs/types.go b/ffs/types.go
index 50bb8836b..8341eb29c 100644
--- a/ffs/types.go
+++ b/ffs/types.go
@@ -132,9 +132,10 @@ type RetrievalJob struct {
 
 // StorageConfig contains a default storage configuration for an Api instance.
 type StorageConfig struct {
-	Hot        HotConfig
-	Cold       ColdConfig
-	Repairable bool
+	Hot           HotConfig
+	Cold          ColdConfig
+	Repairable    bool
+	Notifications []*NotificationConfig
 }
 
 // WithRepairable allows to enable/disable auto-repair.
@@ -342,6 +343,36 @@ func (cc ColdConfig) Validate() error {
 	return nil
 }
 
+type NotificationConfig struct {
+	Webhook       *Webhook
+	Configuration *WebhookConfiguration
+}
+
+type Webhook struct {
+	Endpoint       string
+	Authentication *WebhookAuthentication
+}
+
+type WebhookAuthentication struct {
+	Type string
+	Data *WebhookAuthData
+}
+
+type WebhookAuthData struct {
+	Username string
+	Password string
+}
+
+type WebhookConfiguration struct {
+	Events []string
+	Alerts []*WebhookAlert
+}
+
+type WebhookAlert struct {
+	Type      string
+	Threshold string
+}
+
 // FilConfig is the desired state of a Cid in the Filecoin network.
 type FilConfig struct {
 	// RepFactor indicates the desired amount of active deals
diff --git a/notifications/notifier.go b/notifications/notifier.go
new file mode 100644
index 000000000..a772f8597
--- /dev/null
+++ b/notifications/notifier.go
@@ -0,0 +1,31 @@
+package notifications
+
+import (
+	"bytes"
+	"encoding/json"
+	"net/http"
+
+	"github.com/textileio/powergate/v2/deals"
+)
+
+type Notifier interface {
+	NotifyDeal(dr deals.StorageDealRecord)
+}
+
+type notifier struct {
+	client *http.Client
+}
+
+func New() *notifier {
+	return &notifier{
+		client: http.DefaultClient,
+	}
+}
+
+func (n *notifier) NotifyDeal(dr deals.StorageDealRecord) {
+	endpoint := "https://vmanilo.free.beeceptor.com/webhook"
+	contentType := "application/json"
+	payload, _ := json.Marshal(dr)
+
+	n.client.Post(endpoint, contentType, bytes.NewBuffer(payload))
+}
\ No newline at end of file
diff --git a/proto/powergate/user/v1/user.proto b/proto/powergate/user/v1/user.proto
index f5ce4eaae..1d47881c0 100644
--- a/proto/powergate/user/v1/user.proto
+++ b/proto/powergate/user/v1/user.proto
@@ -371,10 +371,41 @@ message ColdConfig {
   FilConfig filecoin = 2;
 }
 
+message WebhookAuthData {
+  string username = 1;
+  string password = 2;
+}
+
+message WebhookAuthentication {
+  string type = 1;
+  WebhookAuthData data = 2;
+}
+
+message Webhook {
+  string endpoint = 1;
+  WebhookAuthentication authentication = 2;
+}
+
+message WebhookAlert {
+  string type = 1;
+  string threshold = 2;
+}
+
+message WebhookConfiguration {
+  repeated string events = 1;
+  repeated WebhookAlert alerts = 2;
+}
+
+message NotificationConfig {
+  Webhook webhook = 1;
+  WebhookConfiguration configuration = 2;
+}
+
 message StorageConfig {
   HotConfig hot = 1;
   ColdConfig cold = 2;
   bool repairable = 3;
+  repeated NotificationConfig notifications = 4;
 }
 
 message IpfsHotInfo {

From b24f0c20013493f9a4baf0ce0bb818e01b1b9a0b Mon Sep 17 00:00:00 2001
From: Volodymyr Manilo <wmanilo@gmail.com>
Date: Wed, 8 Jun 2022 18:53:46 +0200
Subject: [PATCH 2/7] added notification for storage jobs

---
 api/server/server.go                          |   6 +-
 deals/module/module.go                        |   5 +-
 deals/module/store/store.go                   |   7 +-
 docker/docker-compose-localnet.yaml           |   3 +-
 docker/docker-compose.yaml                    |   3 +-
 ffs/integrationtest/manager/manager.go        |   6 +-
 ffs/manager/manager.go                        |  12 ++
 ffs/scheduler/internal/sjstore/sjstore.go     |  11 +-
 .../internal/sjstore/sjstore_test.go          |   2 +-
 ffs/scheduler/scheduler.go                    |  29 ++--
 ffs/scheduler/scheduler_storage.go            |   3 +
 ffs/types.go                                  |  33 +++++
 notifications/notifier.go                     | 140 ++++++++++++++++--
 tests/txmapds.go                              |  13 ++
 14 files changed, 233 insertions(+), 40 deletions(-)

diff --git a/api/server/server.go b/api/server/server.go
index 6334d272b..183135c5f 100644
--- a/api/server/server.go
+++ b/api/server/server.go
@@ -254,10 +254,10 @@ func NewServer(conf Config) (*Server, error) {
 		conf.DealWatchPollDuration = time.Second
 	}
 
-	nt := notifications.New()
+	notifier := notifications.New()
 
 	log.Info("Starting deals module...")
-	dm, err := dealsModule.New(txndstr.Wrap(ds, "deals"), nt, clientBuilder, conf.DealWatchPollDuration, conf.FFSDealFinalityTimeout, deals.WithImportPath(filepath.Join(conf.RepoPath, "imports")))
+	dm, err := dealsModule.New(txndstr.Wrap(ds, "deals"), clientBuilder, conf.DealWatchPollDuration, conf.FFSDealFinalityTimeout, deals.WithImportPath(filepath.Join(conf.RepoPath, "imports")))
 	if err != nil {
 		return nil, fmt.Errorf("creating deal module: %s", err)
 	}
@@ -297,7 +297,7 @@ func NewServer(conf Config) (*Server, error) {
 		sr2rf = ms.GetReplicationFactor
 	}
 	gcConfig := scheduler.GCConfig{StageGracePeriod: conf.FFSGCStageGracePeriod, AutoGCInterval: conf.FFSGCAutomaticGCInterval}
-	sched, err := scheduler.New(txndstr.Wrap(ds, "ffs/scheduler"), l, hs, cs, conf.SchedMaxParallel, conf.FFSDealFinalityTimeout, sr2rf, gcConfig)
+	sched, err := scheduler.New(txndstr.Wrap(ds, "ffs/scheduler"), l, hs, cs, conf.SchedMaxParallel, conf.FFSDealFinalityTimeout, sr2rf, gcConfig, notifier)
 	if err != nil {
 		return nil, fmt.Errorf("creating scheduler: %s", err)
 	}
diff --git a/deals/module/module.go b/deals/module/module.go
index bf1d78cd9..a45cc2228 100644
--- a/deals/module/module.go
+++ b/deals/module/module.go
@@ -19,7 +19,6 @@ import (
 	"github.com/textileio/powergate/v2/deals/module/dealwatcher"
 	"github.com/textileio/powergate/v2/deals/module/store"
 	"github.com/textileio/powergate/v2/lotus"
-	"github.com/textileio/powergate/v2/notifications"
 	"go.opentelemetry.io/otel/metric"
 )
 
@@ -41,7 +40,7 @@ type Module struct {
 }
 
 // New creates a new Module.
-func New(ds datastore.TxnDatastore, nt notifications.Notifier, clientBuilder lotus.ClientBuilder, pollDuration time.Duration, dealFinalityTimeout time.Duration, opts ...deals.Option) (*Module, error) {
+func New(ds datastore.TxnDatastore, clientBuilder lotus.ClientBuilder, pollDuration time.Duration, dealFinalityTimeout time.Duration, opts ...deals.Option) (*Module, error) {
 	var cfg deals.Config
 	for _, o := range opts {
 		if err := o(&cfg); err != nil {
@@ -57,7 +56,7 @@ func New(ds datastore.TxnDatastore, nt notifications.Notifier, clientBuilder lot
 	m := &Module{
 		clientBuilder:       clientBuilder,
 		cfg:                 &cfg,
-		store:               store.New(ds, nt),
+		store:               store.New(ds),
 		pollDuration:        pollDuration,
 		dealFinalityTimeout: dealFinalityTimeout,
 		dealWatcher:         dw,
diff --git a/deals/module/store/store.go b/deals/module/store/store.go
index 99f4932dd..b0782039e 100644
--- a/deals/module/store/store.go
+++ b/deals/module/store/store.go
@@ -17,7 +17,6 @@ import (
 	"github.com/ipfs/go-datastore/query"
 	logging "github.com/ipfs/go-log/v2"
 	"github.com/textileio/powergate/v2/deals"
-	"github.com/textileio/powergate/v2/notifications"
 	"github.com/textileio/powergate/v2/util"
 	"go.opentelemetry.io/otel/metric"
 )
@@ -40,7 +39,6 @@ var (
 // Store stores deal and retrieval records.
 type Store struct {
 	ds   datastore.TxnDatastore
-	nt   notifications.Notifier
 	lock sync.Mutex
 
 	metricFinalTotal  metric.Int64Counter
@@ -48,10 +46,9 @@ type Store struct {
 }
 
 // New returns a new *Store.
-func New(ds datastore.TxnDatastore, nt notifications.Notifier) *Store {
+func New(ds datastore.TxnDatastore) *Store {
 	s := &Store{
 		ds: ds,
-		nt: nt,
 	}
 	s.initMetrics()
 
@@ -115,8 +112,6 @@ func (s *Store) PutStorageDeal(dr deals.StorageDealRecord) error {
 		return fmt.Errorf("committing transaction: %s", err)
 	}
 
-	s.nt.NotifyDeal(dr)
-
 	return nil
 }
 
diff --git a/docker/docker-compose-localnet.yaml b/docker/docker-compose-localnet.yaml
index 8c466f5d0..efcea8c00 100644
--- a/docker/docker-compose-localnet.yaml
+++ b/docker/docker-compose-localnet.yaml
@@ -8,7 +8,7 @@ services:
       - 6060:6060
       - 5002:5002
       - 6002:6002
-      - 7000:7000
+      - 7001:7001
     depends_on:
       - ipfs
       - lotus
@@ -16,6 +16,7 @@ services:
       - POWD_DEVNET=true
       - POWD_LOTUSHOST=/dns4/lotus/tcp/7777
       - POWD_IPFSAPIADDR=/dns4/ipfs/tcp/5001
+      - POW_GATEWAYHOSTADDR=0.0.0.0:7001
     restart: unless-stopped
 
   ipfs:
diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml
index 25a3d985f..02d16ff7a 100644
--- a/docker/docker-compose.yaml
+++ b/docker/docker-compose.yaml
@@ -14,7 +14,7 @@ services:
       - 6060:6060
       - 5002:5002
       - 6002:6002
-      - 7000:7000
+      - 7001:7001
     depends_on:
       - ipfs
       - lotus
@@ -23,6 +23,7 @@ services:
       - POWD_IPFSAPIADDR=/dns4/ipfs/tcp/5001
       - POWD_LOTUSTOKENFILE=/root/lotus/.lotus/token
       - POWD_REPOPATH=/root/powergate/.powergate
+      - POW_GATEWAYHOSTADDR=0.0.0.0:7001
     restart: unless-stopped
     volumes:
       - powergate-powd:/root/powergate
diff --git a/ffs/integrationtest/manager/manager.go b/ffs/integrationtest/manager/manager.go
index a94c40a16..7871b5d66 100644
--- a/ffs/integrationtest/manager/manager.go
+++ b/ffs/integrationtest/manager/manager.go
@@ -80,8 +80,8 @@ func NewFFSManager(t require.TestingT, ds datastore.TxnDatastore, clientBuilder
 
 // NewCustomFFSManager returns a new customized FFS manager.
 func NewCustomFFSManager(t require.TestingT, ds datastore.TxnDatastore, cb lotus.ClientBuilder, masterAddr address.Address, ms ffs.MinerSelector, ipfsClient *httpapi.HttpApi, minimumPieceSize uint64) (*manager.Manager, *coreipfs.CoreIpfs, func()) {
-	nt := notifications.New()
-	dm, err := dealsModule.New(txndstr.Wrap(ds, "deals"), nt, cb, util.AvgBlockTime, time.Minute*10)
+	notifier := notifications.New()
+	dm, err := dealsModule.New(txndstr.Wrap(ds, "deals"), cb, util.AvgBlockTime, time.Minute*10)
 	require.NoError(t, err)
 
 	fchain := filchain.New(cb)
@@ -91,7 +91,7 @@ func NewCustomFFSManager(t require.TestingT, ds datastore.TxnDatastore, cb lotus
 	cl := filcold.New(ms, dm, nil, ipfsClient, fchain, l, lsm, minimumPieceSize, 1, time.Hour)
 	hl, err := coreipfs.New(ds, ipfsClient, l)
 	require.NoError(t, err)
-	sched, err := scheduler.New(txndstr.Wrap(ds, "ffs/scheduler"), l, hl, cl, 10, time.Minute*10, nil, scheduler.GCConfig{AutoGCInterval: 0})
+	sched, err := scheduler.New(txndstr.Wrap(ds, "ffs/scheduler"), l, hl, cl, 10, time.Minute*10, nil, scheduler.GCConfig{AutoGCInterval: 0}, notifier)
 	require.NoError(t, err)
 
 	wm, err := lotusWallet.New(cb, masterAddr, *big.NewInt(iWalletBal), false, "")
diff --git a/ffs/manager/manager.go b/ffs/manager/manager.go
index f17ee36cf..0a2712b7c 100644
--- a/ffs/manager/manager.go
+++ b/ffs/manager/manager.go
@@ -63,6 +63,18 @@ var (
 				DealMinDuration: util.MinDealDuration,
 			},
 		},
+
+		// TODO: remove after review, just for testing
+		Notifications: []*ffs.NotificationConfig{
+			{
+				Webhook: &ffs.Webhook{
+					Endpoint: "https://vmanilo.free.beeceptor.com/webhook/job",
+				},
+				Configuration: &ffs.WebhookConfiguration{
+					Events: []string{"storage-deal-created"},
+				},
+			},
+		},
 	}
 	dsDefaultStorageConfigKey = datastore.NewKey("defaultstorageconfig")
 )
diff --git a/ffs/scheduler/internal/sjstore/sjstore.go b/ffs/scheduler/internal/sjstore/sjstore.go
index fd97cdb61..3616f58b9 100644
--- a/ffs/scheduler/internal/sjstore/sjstore.go
+++ b/ffs/scheduler/internal/sjstore/sjstore.go
@@ -16,6 +16,7 @@ import (
 	logging "github.com/ipfs/go-log/v2"
 	"github.com/textileio/powergate/v2/deals"
 	"github.com/textileio/powergate/v2/ffs"
+	"github.com/textileio/powergate/v2/notifications"
 	"github.com/textileio/powergate/v2/util"
 	"go.opentelemetry.io/otel/metric"
 )
@@ -64,6 +65,8 @@ type Store struct {
 
 	// Metrics
 	metricJobCounter metric.Int64UpDownCounter
+
+	notifier notifications.Notifier
 }
 
 // Stats return metrics about current job queues.
@@ -78,7 +81,7 @@ type watcher struct {
 }
 
 // New returns a new JobStore backed by the Datastore.
-func New(ds datastore.TxnDatastore) (*Store, error) {
+func New(ds datastore.TxnDatastore, notifier notifications.Notifier) (*Store, error) {
 	s := &Store{
 		ds:                 ds,
 		jobStatusCache:     make(map[ffs.APIID]map[cid.Cid]map[cid.Cid]deals.StorageDealInfo),
@@ -88,6 +91,7 @@ func New(ds datastore.TxnDatastore) (*Store, error) {
 		lastSuccessfulJobs: make(map[ffs.APIID]map[cid.Cid]*ffs.StorageJob),
 		queuedIDs:          make(map[ffs.JobID]struct{}),
 		executingIDs:       make(map[ffs.JobID]struct{}),
+		notifier:           notifier,
 	}
 	s.initMetrics()
 	if err := s.loadCaches(); err != nil {
@@ -116,8 +120,13 @@ func (s *Store) MonitorJob(j ffs.StorageJob) chan deals.StorageDealInfo {
 			if err != nil {
 				log.Errorf("getting job: %v", err)
 				s.lock.Unlock()
+				// do we need to continue here?
+				// probably we will fail to get job from the store on the next iteration as well
 				continue
 			}
+
+			s.notifier.NotifyStorageJob(job, update)
+
 			values := make([]deals.StorageDealInfo, 0, len(s.jobStatusCache[j.APIID][j.Cid]))
 			for _, v := range s.jobStatusCache[j.APIID][j.Cid] {
 				values = append(values, v)
diff --git a/ffs/scheduler/internal/sjstore/sjstore_test.go b/ffs/scheduler/internal/sjstore/sjstore_test.go
index caea44c28..c8222ede1 100644
--- a/ffs/scheduler/internal/sjstore/sjstore_test.go
+++ b/ffs/scheduler/internal/sjstore/sjstore_test.go
@@ -512,7 +512,7 @@ func createJob(_ *testing.T, apiid string, c cid.Cid) ffs.StorageJob {
 
 func create(t *testing.T) *Store {
 	ds := tests.NewTxMapDatastore()
-	store, err := New(ds)
+	store, err := New(ds, tests.NewMockNotifier())
 	require.NoError(t, err)
 	return store
 }
diff --git a/ffs/scheduler/scheduler.go b/ffs/scheduler/scheduler.go
index bc53c5a9f..d70f1e425 100644
--- a/ffs/scheduler/scheduler.go
+++ b/ffs/scheduler/scheduler.go
@@ -18,6 +18,7 @@ import (
 	"github.com/textileio/powergate/v2/ffs/scheduler/internal/rjstore"
 	"github.com/textileio/powergate/v2/ffs/scheduler/internal/sjstore"
 	"github.com/textileio/powergate/v2/ffs/scheduler/internal/trackstore"
+	"github.com/textileio/powergate/v2/notifications"
 	txndstr "github.com/textileio/powergate/v2/txndstransform"
 )
 
@@ -41,15 +42,16 @@ var (
 // This Jobs are executed by delegating the work to the hot and cold storage configured for
 // the scheduler.
 type Scheduler struct {
-	cs  ffs.ColdStorage
-	hs  ffs.HotStorage
-	sjs *sjstore.Store
-	rjs *rjstore.Store
-	as  *astore.Store
-	ts  *trackstore.Store
-	cis *cistore.Store
-	ris *ristore.Store
-	l   ffs.JobLogger
+	cs       ffs.ColdStorage
+	hs       ffs.HotStorage
+	sjs      *sjstore.Store
+	rjs      *rjstore.Store
+	as       *astore.Store
+	ts       *trackstore.Store
+	cis      *cistore.Store
+	ris      *ristore.Store
+	l        ffs.JobLogger
+	notifier notifications.Notifier
 
 	sr2RepFactor        func() (int, error)
 	dealFinalityTimeout time.Duration
@@ -89,8 +91,8 @@ type GCConfig struct {
 
 // New returns a new instance of Scheduler which uses JobStore as its backing repository for state,
 // HotStorage for the hot layer, and ColdStorage for the cold layer.
-func New(ds datastore.TxnDatastore, l ffs.JobLogger, hs ffs.HotStorage, cs ffs.ColdStorage, maxParallel int, dealFinalityTimeout time.Duration, sr2rf func() (int, error), gcConfig GCConfig) (*Scheduler, error) {
-	sjs, err := sjstore.New(txndstr.Wrap(ds, "sjstore"))
+func New(ds datastore.TxnDatastore, l ffs.JobLogger, hs ffs.HotStorage, cs ffs.ColdStorage, maxParallel int, dealFinalityTimeout time.Duration, sr2rf func() (int, error), gcConfig GCConfig, notifier notifications.Notifier) (*Scheduler, error) {
+	sjs, err := sjstore.New(txndstr.Wrap(ds, "sjstore"), notifier)
 	if err != nil {
 		return nil, fmt.Errorf("loading stroage jobstore: %s", err)
 	}
@@ -124,6 +126,8 @@ func New(ds datastore.TxnDatastore, l ffs.JobLogger, hs ffs.HotStorage, cs ffs.C
 		l:  l,
 		gc: gcConfig,
 
+		notifier: notifier,
+
 		jobsCancel: make(map[ffs.JobID]chan struct{}),
 		sd: storageDaemon{
 			rateLim:       make(chan struct{}, maxParallel),
@@ -520,9 +524,12 @@ func (s *Scheduler) executeQueuedStorage(j ffs.StorageJob) {
 		return
 	}
 
+	s.notifier.RegisterStorageJob(j, a.Cfg.Notifications)
+
 	// Execute
 	s.l.Log(ctx, "Executing job %s...", j.ID)
 	dealUpdates := s.sjs.MonitorJob(j)
+	// TODO: vova: job executes start here
 	info, dealErrors, err := s.executeStorage(ctx, a, j, dealUpdates)
 	close(dealUpdates)
 	// Something bad-enough happened to make Job
diff --git a/ffs/scheduler/scheduler_storage.go b/ffs/scheduler/scheduler_storage.go
index c00fdf317..f5b626cf5 100644
--- a/ffs/scheduler/scheduler_storage.go
+++ b/ffs/scheduler/scheduler_storage.go
@@ -81,6 +81,9 @@ func (s *Scheduler) push(iid ffs.APIID, c cid.Cid, cfg ffs.StorageConfig, oldCid
 	}
 
 	s.l.Log(ctx, "Configuration saved successfully")
+
+	s.notifier.RegisterStorageJob(j, cfg.Notifications)
+
 	return jid, nil
 }
 
diff --git a/ffs/types.go b/ffs/types.go
index 8341eb29c..f5e91fe3f 100644
--- a/ffs/types.go
+++ b/ffs/types.go
@@ -2,7 +2,10 @@ package ffs
 
 import (
 	"context"
+	"encoding/base64"
 	"fmt"
+	"io"
+	"net/http"
 	"time"
 
 	"github.com/google/uuid"
@@ -353,16 +356,46 @@ type Webhook struct {
 	Authentication *WebhookAuthentication
 }
 
+func (w *Webhook) Publish(client *http.Client, payload io.Reader) error {
+	req, err := http.NewRequest("POST", w.Endpoint, payload)
+	if err != nil {
+		return err
+	}
+	req.Header.Set("Content-Type", "application/json")
+
+	if w.Authentication != nil {
+		w.Authentication.setHeaders(req)
+	}
+
+	_, err = client.Do(req)
+	return err
+}
+
 type WebhookAuthentication struct {
 	Type string
 	Data *WebhookAuthData
 }
 
+func (a *WebhookAuthentication) setHeaders(req *http.Request) {
+	switch a.Type {
+	case "basic_auth":
+		if a.Data != nil {
+			req.Header.Set("Authorization", fmt.Sprintf("Basic %s", a.Data.encode()))
+		}
+	}
+}
+
 type WebhookAuthData struct {
 	Username string
 	Password string
 }
 
+func (d WebhookAuthData) encode() string {
+	return base64.StdEncoding.EncodeToString(
+		[]byte(fmt.Sprintf("%s:%s", d.Username, d.Password)),
+	)
+}
+
 type WebhookConfiguration struct {
 	Events []string
 	Alerts []*WebhookAlert
diff --git a/notifications/notifier.go b/notifications/notifier.go
index a772f8597..5c767b215 100644
--- a/notifications/notifier.go
+++ b/notifications/notifier.go
@@ -4,28 +4,148 @@ import (
 	"bytes"
 	"encoding/json"
 	"net/http"
+	"sync"
 
 	"github.com/textileio/powergate/v2/deals"
+	"github.com/textileio/powergate/v2/ffs"
 )
 
 type Notifier interface {
-	NotifyDeal(dr deals.StorageDealRecord)
+	RegisterStorageJob(job ffs.StorageJob, notificationConfig []*ffs.NotificationConfig)
+	NotifyStorageJob(job ffs.StorageJob, dealInfo deals.StorageDealInfo)
+}
+
+type storageJobUpdate struct {
+	job      ffs.StorageJob
+	dealInfo deals.StorageDealInfo
 }
 
 type notifier struct {
-	client *http.Client
+	client                  *http.Client
+	store                   *configStore
+	storageJobNotifications chan *storageJobUpdate
 }
 
 func New() *notifier {
-	return &notifier{
-		client: http.DefaultClient,
+	nt := &notifier{
+		client:                  http.DefaultClient,
+		store:                   newConfigStore(),
+		storageJobNotifications: make(chan *storageJobUpdate, 1000),
+	}
+
+	go nt.run()
+
+	return nt
+}
+
+func (n *notifier) RegisterStorageJob(job ffs.StorageJob, notificationConfig []*ffs.NotificationConfig) {
+	if notificationConfig == nil {
+		return
+	}
+
+	n.store.put(job, notificationConfig)
+}
+
+func (n *notifier) NotifyStorageJob(job ffs.StorageJob, dealInfo deals.StorageDealInfo) {
+	n.storageJobNotifications <- &storageJobUpdate{
+		job:      job,
+		dealInfo: dealInfo,
+	}
+}
+
+func (n *notifier) run() {
+	for updates := range n.storageJobNotifications {
+		if updates == nil {
+			continue
+		}
+
+		config := n.store.get(updates.job.ID)
+		if config == nil {
+			continue
+		}
+
+		n.notifyAll(config.notifications, updates)
+	}
+}
+
+func (n *notifier) notifyAll(configs []*ffs.NotificationConfig, updates *storageJobUpdate) {
+	for _, cfg := range configs {
+		n.notify(cfg, updates)
+	}
+}
+
+func (n *notifier) notify(config *ffs.NotificationConfig, updates *storageJobUpdate) {
+	if matchNotificationConfig(config.Configuration, updates.dealInfo) {
+		n.publishNotification(config.Webhook, updates)
+	}
+}
+
+func matchNotificationConfig(config *ffs.WebhookConfiguration, updates deals.StorageDealInfo) bool {
+	if config == nil {
+		return false
 	}
+
+	return matchNotificationEvents(config.Events, updates) || matchNotificationAlerts(config.Alerts, updates)
+}
+
+func matchNotificationEvents(events []string, updates deals.StorageDealInfo) bool {
+	// TODO
+	return true
 }
 
-func (n *notifier) NotifyDeal(dr deals.StorageDealRecord) {
-	endpoint := "https://vmanilo.free.beeceptor.com/webhook"
-	contentType := "application/json"
-	payload, _ := json.Marshal(dr)
+func matchNotificationAlerts(alerts []*ffs.WebhookAlert, updates deals.StorageDealInfo) bool {
+	// TODO
+	return false
+}
 
-	n.client.Post(endpoint, contentType, bytes.NewBuffer(payload))
-}
\ No newline at end of file
+func (n *notifier) publishNotification(webhook *ffs.Webhook, updates *storageJobUpdate) {
+	if webhook == nil {
+		return
+	}
+
+	data, err := json.Marshal(updates.dealInfo)
+	if err != nil {
+		// TODO: log error
+		return
+	}
+
+	err = webhook.Publish(n.client, bytes.NewBuffer(data))
+	if err != nil {
+		// TODO: log error
+		return
+	}
+}
+
+type configStore struct {
+	sync.RWMutex
+
+	configs map[ffs.JobID]*jobConfig
+}
+
+func newConfigStore() *configStore {
+	return &configStore{
+		configs: make(map[ffs.JobID]*jobConfig),
+	}
+}
+
+type jobConfig struct {
+	job           ffs.StorageJob
+	notifications []*ffs.NotificationConfig
+}
+
+func (s *configStore) put(job ffs.StorageJob, notifications []*ffs.NotificationConfig) {
+	s.Lock()
+	defer s.Unlock()
+
+	s.configs[job.ID] = &jobConfig{
+		job:           job,
+		notifications: notifications,
+	}
+}
+
+func (s *configStore) get(jobID ffs.JobID) *jobConfig {
+	s.RLock()
+	defer s.RUnlock()
+
+	return s.configs[jobID]
+}
diff --git a/tests/txmapds.go b/tests/txmapds.go
index 736700477..95bd7b29d 100644
--- a/tests/txmapds.go
+++ b/tests/txmapds.go
@@ -6,6 +6,8 @@ import (
 
 	"github.com/ipfs/go-datastore"
 	"github.com/ipfs/go-datastore/query"
+	"github.com/textileio/powergate/v2/deals"
+	"github.com/textileio/powergate/v2/ffs"
 )
 
 // TxMapDatastore is a in-memory datastore that satisfies TxnDatastore.
@@ -172,3 +174,14 @@ func (bt *SimpleTx) Commit() error {
 
 	return err
 }
+
+type mockNotifier struct{}
+
+func NewMockNotifier() *mockNotifier {
+	return &mockNotifier{}
+}
+
+func (n *mockNotifier) NotifyDealRecord(dr deals.StorageDealRecord) {}
+func (n *mockNotifier) RegisterStorageJob(job ffs.StorageJob, notificationConfig []*ffs.NotificationConfig) {
+}
+func (n *mockNotifier) NotifyStorageJob(jobID ffs.JobID, dealInfo deals.StorageDealInfo) {}

From b45698bfa01cd1efd979ea4bf15ffe5fcec773fe Mon Sep 17 00:00:00 2001
From: Volodymyr Manilo <wmanilo@gmail.com>
Date: Tue, 14 Jun 2022 22:29:30 +0300
Subject: [PATCH 3/7] added notification payload

---
 ffs/scheduler/scheduler.go |   1 -
 notifications/notifier.go  | 120 ++++++++++++++++++++++++++++++++++++-
 2 files changed, 117 insertions(+), 4 deletions(-)

diff --git a/ffs/scheduler/scheduler.go b/ffs/scheduler/scheduler.go
index d70f1e425..594c2395d 100644
--- a/ffs/scheduler/scheduler.go
+++ b/ffs/scheduler/scheduler.go
@@ -529,7 +529,6 @@ func (s *Scheduler) executeQueuedStorage(j ffs.StorageJob) {
 	// Execute
 	s.l.Log(ctx, "Executing job %s...", j.ID)
 	dealUpdates := s.sjs.MonitorJob(j)
-	// TODO: vova: job executes start here
 	info, dealErrors, err := s.executeStorage(ctx, a, j, dealUpdates)
 	close(dealUpdates)
 	// Something bad-enough happened to make Job
diff --git a/notifications/notifier.go b/notifications/notifier.go
index 5c767b215..a5413a96a 100644
--- a/notifications/notifier.go
+++ b/notifications/notifier.go
@@ -6,6 +6,7 @@ import (
 	"net/http"
 	"sync"
 
+	"github.com/filecoin-project/go-fil-markets/storagemarket"
 	"github.com/textileio/powergate/v2/deals"
 	"github.com/textileio/powergate/v2/ffs"
 )
@@ -75,6 +76,10 @@ func (n *notifier) notifyAll(configs []*ffs.NotificationConfig, updates *storage
 }
 
 func (n *notifier) notify(config *ffs.NotificationConfig, updates *storageJobUpdate) {
+	if config == nil {
+		return
+	}
+
 	if matchNotificationConfig(config.Configuration, updates.dealInfo) {
 		n.publishNotification(config.Webhook, updates)
 	}
@@ -88,9 +93,92 @@ func matchNotificationConfig(config *ffs.WebhookConfiguration, updates deals.Sto
 	return matchNotificationEvents(config.Events, updates) || matchNotificationAlerts(config.Alerts, updates)
 }
 
+const (
+	all            = "*"
+	created        = "created"
+	completed      = "completed"
+	retried        = "retried"
+	failed         = "failed"
+	expired        = "expired"
+	slashed        = "slashed"
+	separator      = "-"
+	storageDeal    = "storage-deal"
+	storageAuction = "storage-auction"
+	dataRetrieval  = "data-retrieval"
+
+	AllEvents          = all
+	AllCreatedEvents   = all + separator + created
+	AllCompletedEvents = all + separator + completed
+	AllRetriedEvents   = all + separator + retried
+	AllFailedEvents    = all + separator + failed
+
+	AllStorageDealEvents      = storageDeal + separator + all
+	StorageDealCreatedEvent   = storageDeal + separator + created
+	StorageDealCompletedEvent = storageDeal + separator + completed
+	StorageDealRetriedEvent   = storageDeal + separator + retried
+	StorageDealFailedEvent    = storageDeal + separator + failed
+	StorageDealExpiredEvent   = storageDeal + separator + expired
+	StorageDealSlashedEvent   = storageDeal + separator + slashed
+
+	AllStorageAuctionEvents      = storageAuction + separator + all
+	StorageAuctionCreatedEvent   = storageAuction + separator + created
+	StorageAuctionCompletedEvent = storageAuction + separator + completed
+	StorageAuctionFailedEvent    = storageAuction + separator + failed
+
+	AllDataRetrievalEvents      = dataRetrieval + separator + all
+	DataRetrievalCompletedEvent = dataRetrieval + separator + completed
+	DataRetrievalRetriedEvent   = dataRetrieval + separator + retried
+	DataRetrievalFailedEvent    = dataRetrieval + separator + failed
+)
+
 func matchNotificationEvents(events []string, updates deals.StorageDealInfo) bool {
-	// TODO
-	return true
+	for _, event := range events {
+		if matchNotificationEvent(event, updates) {
+			return true
+		}
+	}
+
+	return false
+}
+
+func matchNotificationEvent(event string, updates deals.StorageDealInfo) bool {
+	switch event {
+	case AllEvents:
+		return true
+	case AllCreatedEvents:
+		// TODO: add created events
+		return updates.DealID != 0 // ||
+	case AllCompletedEvents:
+		// TODO: add other completed events
+		return updates.StateID == storagemarket.StorageDealActive // ||
+
+	// TODO:
+	// case AllRetriedEvents:
+
+	case AllStorageDealEvents:
+		return true
+
+	case StorageDealCreatedEvent:
+		return updates.DealID != 0
+
+	case StorageDealCompletedEvent:
+		return updates.StateID == storagemarket.StorageDealActive
+
+	// TODO:
+	// case StorageDealRetriedEvent:
+
+	case StorageDealFailedEvent:
+		return updates.StateID == storagemarket.StorageDealFailing || updates.StateID == storagemarket.StorageDealError || updates.Message != ""
+
+	case StorageDealExpiredEvent:
+		return updates.StateID == storagemarket.StorageDealExpired
+
+	case StorageDealSlashedEvent:
+		return updates.StateID == storagemarket.StorageDealSlashed
+
+	default:
+		return false
+	}
 }
 
 func matchNotificationAlerts(alerts []*ffs.WebhookAlert, updates deals.StorageDealInfo) bool {
@@ -98,12 +186,38 @@ func matchNotificationAlerts(alerts []*ffs.WebhookAlert, updates deals.StorageDe
 	return false
 }
 
+type notification struct {
+	Cid         string    `json:"cid"`
+	JobID       ffs.JobID `json:"jobId"`
+	JobStatus   string    `json:"jobStatus"`
+	Miner       string    `json:"miner"`
+	Price       uint64    `json:"price"`
+	ProposalCid string    `json:"proposalCid"`
+	DealID      uint64    `json:"dealId,omitempty"`
+	DealStatus  string    `json:"dealStatus"`
+	ErrCause    string    `json:"error,omitempty"`
+	Message     string    `json:"message,omitempty"`
+}
+
 func (n *notifier) publishNotification(webhook *ffs.Webhook, updates *storageJobUpdate) {
 	if webhook == nil {
 		return
 	}
 
-	data, err := json.Marshal(updates.dealInfo)
+	obj := &notification{
+		Cid:         updates.job.Cid.String(),
+		JobID:       updates.job.ID,
+		JobStatus:   ffs.JobStatusStr[updates.job.Status],
+		Miner:       updates.dealInfo.Miner,
+		Price:       updates.dealInfo.PricePerEpoch,
+		ProposalCid: updates.dealInfo.ProposalCid.String(),
+		DealID:      updates.dealInfo.DealID,
+		DealStatus:  updates.dealInfo.StateName,
+		ErrCause:    updates.job.ErrCause,
+		Message:     updates.dealInfo.Message,
+	}
+
+	data, err := json.Marshal(obj)
 	if err != nil {
 		// TODO: log error
 		return

From 6e2a5471635df43bea789f3e7ff0459459c2a478 Mon Sep 17 00:00:00 2001
From: Volodymyr Manilo <wmanilo@gmail.com>
Date: Tue, 21 Jun 2022 16:23:48 +0300
Subject: [PATCH 4/7] refactor notifier

---
 api/server/server.go                          |   5 +-
 ffs/api/api_retrieval.go                      |   2 +-
 ffs/integrationtest/manager/manager.go        |   4 +-
 ffs/manager/manager.go                        |   2 +-
 ffs/scheduler/internal/astore/astore.go       |   1 +
 ffs/scheduler/internal/rjstore/rjstore.go     |  13 +-
 .../internal/rjstore/rjstore_test.go          |   2 +-
 ffs/scheduler/internal/sjstore/sjstore.go     |  12 +-
 ffs/scheduler/scheduler.go                    |  21 +-
 ffs/scheduler/scheduler_retrieval.go          |   3 +-
 ffs/scheduler/scheduler_storage.go            |   2 +-
 notifications/events.go                       |  49 +++
 notifications/final_job_status.go             |  90 ++++++
 notifications/job_updates.go                  |  16 +
 notifications/notifier.go                     | 283 ++++++------------
 notifications/retrieval_job.go                |  85 ++++++
 notifications/storage_job.go                  |  96 ++++++
 notifications/store.go                        |  42 +++
 tests/txmapds.go                              |   8 +-
 19 files changed, 523 insertions(+), 213 deletions(-)
 create mode 100644 notifications/events.go
 create mode 100644 notifications/final_job_status.go
 create mode 100644 notifications/job_updates.go
 create mode 100644 notifications/retrieval_job.go
 create mode 100644 notifications/storage_job.go
 create mode 100644 notifications/store.go

diff --git a/api/server/server.go b/api/server/server.go
index 183135c5f..7c0dc1884 100644
--- a/api/server/server.go
+++ b/api/server/server.go
@@ -50,7 +50,6 @@ import (
 	"github.com/textileio/powergate/v2/iplocation/maxmind"
 	"github.com/textileio/powergate/v2/lotus"
 	"github.com/textileio/powergate/v2/migration"
-	"github.com/textileio/powergate/v2/notifications"
 	"github.com/textileio/powergate/v2/reputation"
 	txndstr "github.com/textileio/powergate/v2/txndstransform"
 	"github.com/textileio/powergate/v2/util"
@@ -254,8 +253,6 @@ func NewServer(conf Config) (*Server, error) {
 		conf.DealWatchPollDuration = time.Second
 	}
 
-	notifier := notifications.New()
-
 	log.Info("Starting deals module...")
 	dm, err := dealsModule.New(txndstr.Wrap(ds, "deals"), clientBuilder, conf.DealWatchPollDuration, conf.FFSDealFinalityTimeout, deals.WithImportPath(filepath.Join(conf.RepoPath, "imports")))
 	if err != nil {
@@ -297,7 +294,7 @@ func NewServer(conf Config) (*Server, error) {
 		sr2rf = ms.GetReplicationFactor
 	}
 	gcConfig := scheduler.GCConfig{StageGracePeriod: conf.FFSGCStageGracePeriod, AutoGCInterval: conf.FFSGCAutomaticGCInterval}
-	sched, err := scheduler.New(txndstr.Wrap(ds, "ffs/scheduler"), l, hs, cs, conf.SchedMaxParallel, conf.FFSDealFinalityTimeout, sr2rf, gcConfig, notifier)
+	sched, err := scheduler.New(txndstr.Wrap(ds, "ffs/scheduler"), l, hs, cs, conf.SchedMaxParallel, conf.FFSDealFinalityTimeout, sr2rf, gcConfig)
 	if err != nil {
 		return nil, fmt.Errorf("creating scheduler: %s", err)
 	}
diff --git a/ffs/api/api_retrieval.go b/ffs/api/api_retrieval.go
index d9191359e..a3d05eca0 100644
--- a/ffs/api/api_retrieval.go
+++ b/ffs/api/api_retrieval.go
@@ -56,7 +56,7 @@ func (i *API) StartRetrieval(payloadCid, pieceCid cid.Cid, selector string, mine
 	}
 
 	rID := ffs.NewRetrievalID()
-	jid, err := i.sched.StartRetrieval(i.cfg.ID, rID, payloadCid, pieceCid, selector, miners, rc.walletAddress, rc.maxPrice)
+	jid, err := i.sched.StartRetrieval(i.cfg.ID, rID, payloadCid, pieceCid, selector, miners, rc.walletAddress, rc.maxPrice, i.cfg.DefaultStorageConfig.Notifications)
 	if err != nil {
 		return Retrieval{}, fmt.Errorf("starting retrieval in scheduler: %s", err)
 	}
diff --git a/ffs/integrationtest/manager/manager.go b/ffs/integrationtest/manager/manager.go
index 7871b5d66..906dfb8ae 100644
--- a/ffs/integrationtest/manager/manager.go
+++ b/ffs/integrationtest/manager/manager.go
@@ -20,7 +20,6 @@ import (
 	"github.com/textileio/powergate/v2/ffs/scheduler"
 	"github.com/textileio/powergate/v2/filchain"
 	"github.com/textileio/powergate/v2/lotus"
-	"github.com/textileio/powergate/v2/notifications"
 	"github.com/textileio/powergate/v2/tests"
 	"github.com/textileio/powergate/v2/util"
 
@@ -80,7 +79,6 @@ func NewFFSManager(t require.TestingT, ds datastore.TxnDatastore, clientBuilder
 
 // NewCustomFFSManager returns a new customized FFS manager.
 func NewCustomFFSManager(t require.TestingT, ds datastore.TxnDatastore, cb lotus.ClientBuilder, masterAddr address.Address, ms ffs.MinerSelector, ipfsClient *httpapi.HttpApi, minimumPieceSize uint64) (*manager.Manager, *coreipfs.CoreIpfs, func()) {
-	notifier := notifications.New()
 	dm, err := dealsModule.New(txndstr.Wrap(ds, "deals"), cb, util.AvgBlockTime, time.Minute*10)
 	require.NoError(t, err)
 
@@ -91,7 +89,7 @@ func NewCustomFFSManager(t require.TestingT, ds datastore.TxnDatastore, cb lotus
 	cl := filcold.New(ms, dm, nil, ipfsClient, fchain, l, lsm, minimumPieceSize, 1, time.Hour)
 	hl, err := coreipfs.New(ds, ipfsClient, l)
 	require.NoError(t, err)
-	sched, err := scheduler.New(txndstr.Wrap(ds, "ffs/scheduler"), l, hl, cl, 10, time.Minute*10, nil, scheduler.GCConfig{AutoGCInterval: 0}, notifier)
+	sched, err := scheduler.New(txndstr.Wrap(ds, "ffs/scheduler"), l, hl, cl, 10, time.Minute*10, nil, scheduler.GCConfig{AutoGCInterval: 0})
 	require.NoError(t, err)
 
 	wm, err := lotusWallet.New(cb, masterAddr, *big.NewInt(iWalletBal), false, "")
diff --git a/ffs/manager/manager.go b/ffs/manager/manager.go
index 0a2712b7c..6e65423ee 100644
--- a/ffs/manager/manager.go
+++ b/ffs/manager/manager.go
@@ -71,7 +71,7 @@ var (
 					Endpoint: "https://vmanilo.free.beeceptor.com/webhook/job",
 				},
 				Configuration: &ffs.WebhookConfiguration{
-					Events: []string{"storage-deal-created"},
+					Events: []string{"*-created", "*-completed"},
 				},
 			},
 		},
diff --git a/ffs/scheduler/internal/astore/astore.go b/ffs/scheduler/internal/astore/astore.go
index 33087d998..cd4627481 100644
--- a/ffs/scheduler/internal/astore/astore.go
+++ b/ffs/scheduler/internal/astore/astore.go
@@ -38,6 +38,7 @@ type RetrievalAction struct {
 	Miners        []string
 	WalletAddress string
 	MaxPrice      uint64
+	Notifications []*ffs.NotificationConfig
 }
 
 // Store persists Actions.
diff --git a/ffs/scheduler/internal/rjstore/rjstore.go b/ffs/scheduler/internal/rjstore/rjstore.go
index a08b31102..b316c163b 100644
--- a/ffs/scheduler/internal/rjstore/rjstore.go
+++ b/ffs/scheduler/internal/rjstore/rjstore.go
@@ -11,6 +11,7 @@ import (
 	"github.com/ipfs/go-datastore/query"
 	logging "github.com/ipfs/go-log/v2"
 	"github.com/textileio/powergate/v2/ffs"
+	"github.com/textileio/powergate/v2/notifications"
 )
 
 var (
@@ -26,6 +27,7 @@ type Store struct {
 	lock     sync.Mutex
 	ds       datastore.Datastore
 	watchers []watcher
+	notifier notifications.Notifier
 }
 
 // watcher represents an API instance who is watching for
@@ -36,8 +38,8 @@ type watcher struct {
 }
 
 // New returns a new retrieval job store.
-func New(ds datastore.Datastore) (*Store, error) {
-	s := &Store{ds: ds}
+func New(ds datastore.Datastore, notifier notifications.Notifier) (*Store, error) {
+	s := &Store{ds: ds, notifier: notifier}
 	return s, nil
 }
 
@@ -62,6 +64,13 @@ func (s *Store) Finalize(jid ffs.JobID, st ffs.JobStatus, jobError error) error
 	if jobError != nil {
 		j.ErrCause = jobError.Error()
 	}
+
+	s.notifier.NotifyJobUpdates(&notifications.FinalJobStatus{
+		JobId:     jid,
+		JobStatus: st,
+		JobError:  jobError,
+	})
+
 	if err := s.put(j); err != nil {
 		return fmt.Errorf("saving in datastore: %s", err)
 	}
diff --git a/ffs/scheduler/internal/rjstore/rjstore_test.go b/ffs/scheduler/internal/rjstore/rjstore_test.go
index a149f6fc5..a902557b5 100644
--- a/ffs/scheduler/internal/rjstore/rjstore_test.go
+++ b/ffs/scheduler/internal/rjstore/rjstore_test.go
@@ -53,7 +53,7 @@ func createJob() ffs.RetrievalJob {
 
 func create(t *testing.T) *Store {
 	ds := tests.NewTxMapDatastore()
-	store, err := New(ds)
+	store, err := New(ds, tests.NewMockNotifier())
 	require.NoError(t, err)
 	return store
 }
diff --git a/ffs/scheduler/internal/sjstore/sjstore.go b/ffs/scheduler/internal/sjstore/sjstore.go
index 3616f58b9..9be8867af 100644
--- a/ffs/scheduler/internal/sjstore/sjstore.go
+++ b/ffs/scheduler/internal/sjstore/sjstore.go
@@ -125,7 +125,10 @@ func (s *Store) MonitorJob(j ffs.StorageJob) chan deals.StorageDealInfo {
 				continue
 			}
 
-			s.notifier.NotifyStorageJob(job, update)
+			s.notifier.NotifyJobUpdates(&notifications.StorageJobUpdates{
+				Job:  job,
+				Info: update,
+			})
 
 			values := make([]deals.StorageDealInfo, 0, len(s.jobStatusCache[j.APIID][j.Cid]))
 			for _, v := range s.jobStatusCache[j.APIID][j.Cid] {
@@ -157,6 +160,13 @@ func (s *Store) Finalize(jid ffs.JobID, st ffs.JobStatus, jobError error, dealEr
 		return err
 	}
 
+	s.notifier.NotifyJobUpdates(&notifications.FinalJobStatus{
+		JobId:      jid,
+		JobStatus:  st,
+		JobError:   jobError,
+		DealErrors: dealErrors,
+	})
+
 	ctx := context.Background()
 	s.metricJobCounter.Add(ctx, -1, attrStatusExecuting)
 	switch st {
diff --git a/ffs/scheduler/scheduler.go b/ffs/scheduler/scheduler.go
index 594c2395d..aabac0fc6 100644
--- a/ffs/scheduler/scheduler.go
+++ b/ffs/scheduler/scheduler.go
@@ -91,25 +91,30 @@ type GCConfig struct {
 
 // New returns a new instance of Scheduler which uses JobStore as its backing repository for state,
 // HotStorage for the hot layer, and ColdStorage for the cold layer.
-func New(ds datastore.TxnDatastore, l ffs.JobLogger, hs ffs.HotStorage, cs ffs.ColdStorage, maxParallel int, dealFinalityTimeout time.Duration, sr2rf func() (int, error), gcConfig GCConfig, notifier notifications.Notifier) (*Scheduler, error) {
+func New(ds datastore.TxnDatastore, l ffs.JobLogger, hs ffs.HotStorage, cs ffs.ColdStorage, maxParallel int, dealFinalityTimeout time.Duration, sr2rf func() (int, error), gcConfig GCConfig) (*Scheduler, error) {
+	ctx, cancel := context.WithCancel(context.Background())
+	notifier := notifications.New(ctx)
+
 	sjs, err := sjstore.New(txndstr.Wrap(ds, "sjstore"), notifier)
 	if err != nil {
+		cancel()
 		return nil, fmt.Errorf("loading stroage jobstore: %s", err)
 	}
-	rjs, err := rjstore.New(txndstr.Wrap(ds, "rjstore"))
+	rjs, err := rjstore.New(txndstr.Wrap(ds, "rjstore"), notifier)
 	if err != nil {
+		cancel()
 		return nil, fmt.Errorf("loading retrieval jobstore: %s", err)
 	}
 	as := astore.New(txndstr.Wrap(ds, "astore"))
 	ts, err := trackstore.New(txndstr.Wrap(ds, "tstore"))
 	if err != nil {
+		cancel()
 		return nil, fmt.Errorf("loading scheduler trackstore: %s", err)
 	}
 
 	cis := cistore.New(txndstr.Wrap(ds, "cistore_v2"))
 	ris := ristore.New(txndstr.Wrap(ds, "ristore"))
 
-	ctx, cancel := context.WithCancel(context.Background())
 	sch := &Scheduler{
 		cs: cs,
 		hs: hs,
@@ -524,7 +529,7 @@ func (s *Scheduler) executeQueuedStorage(j ffs.StorageJob) {
 		return
 	}
 
-	s.notifier.RegisterStorageJob(j, a.Cfg.Notifications)
+	s.notifier.RegisterJob(j.ID, a.Cfg.Notifications)
 
 	// Execute
 	s.l.Log(ctx, "Executing job %s...", j.ID)
@@ -640,6 +645,8 @@ func (s *Scheduler) executeQueuedRetrievals(j ffs.RetrievalJob) {
 		return
 	}
 
+	s.notifier.RegisterJob(j.ID, a.Notifications)
+
 	// Execute
 	s.l.Log(ctx, "Executing job %s...", j.ID)
 	info, err := s.executeRetrieval(ctx, a, j)
@@ -654,6 +661,12 @@ func (s *Scheduler) executeQueuedRetrievals(j ffs.RetrievalJob) {
 		s.l.Log(ctx, "Job %s execution failed: %s", j.ID, err)
 		return
 	}
+
+	s.notifier.NotifyJobUpdates(&notifications.RetrievalJobUpdates{
+		Job:  j,
+		Info: info,
+	})
+
 	// Save whatever stored information was completely/partially
 	// done in execution.
 	if err := s.ris.Put(info); err != nil {
diff --git a/ffs/scheduler/scheduler_retrieval.go b/ffs/scheduler/scheduler_retrieval.go
index e93965f69..34b1fe8fa 100644
--- a/ffs/scheduler/scheduler_retrieval.go
+++ b/ffs/scheduler/scheduler_retrieval.go
@@ -11,7 +11,7 @@ import (
 )
 
 // StartRetrieval schedules a new RetrievalJob to execute a Filecoin retrieval.
-func (s *Scheduler) StartRetrieval(iid ffs.APIID, rid ffs.RetrievalID, pyCid, piCid cid.Cid, sel string, miners []string, walletAddr string, maxPrice uint64) (ffs.JobID, error) {
+func (s *Scheduler) StartRetrieval(iid ffs.APIID, rid ffs.RetrievalID, pyCid, piCid cid.Cid, sel string, miners []string, walletAddr string, maxPrice uint64, notifications []*ffs.NotificationConfig) (ffs.JobID, error) {
 	if iid == ffs.EmptyInstanceID {
 		return ffs.EmptyJobID, fmt.Errorf("empty API ID")
 	}
@@ -52,6 +52,7 @@ func (s *Scheduler) StartRetrieval(iid ffs.APIID, rid ffs.RetrievalID, pyCid, pi
 		Miners:        miners,
 		WalletAddress: walletAddr,
 		MaxPrice:      maxPrice,
+		Notifications: notifications,
 	}
 	if err := s.as.PutRetrievalAction(j.ID, ra); err != nil {
 		return ffs.EmptyJobID, fmt.Errorf("saving retrieval action for job: %s", err)
diff --git a/ffs/scheduler/scheduler_storage.go b/ffs/scheduler/scheduler_storage.go
index f5b626cf5..265b14a66 100644
--- a/ffs/scheduler/scheduler_storage.go
+++ b/ffs/scheduler/scheduler_storage.go
@@ -82,7 +82,7 @@ func (s *Scheduler) push(iid ffs.APIID, c cid.Cid, cfg ffs.StorageConfig, oldCid
 
 	s.l.Log(ctx, "Configuration saved successfully")
 
-	s.notifier.RegisterStorageJob(j, cfg.Notifications)
+	s.notifier.RegisterJob(j.ID, cfg.Notifications)
 
 	return jid, nil
 }
diff --git a/notifications/events.go b/notifications/events.go
new file mode 100644
index 000000000..41a370823
--- /dev/null
+++ b/notifications/events.go
@@ -0,0 +1,49 @@
+package notifications
+
+const (
+	all            = "*"
+	created        = "created"
+	completed      = "completed"
+	retried        = "retried"
+	failed         = "failed"
+	canceled       = "canceled"
+	expired        = "expired"
+	slashed        = "slashed"
+	separator      = "-"
+	storageDeal    = "storage-deal"
+	storageAuction = "storage-auction"
+	dataRetrieval  = "data-retrieval"
+
+	// All events
+	AllEvents          = all
+	AllCreatedEvents   = all + separator + created
+	AllCompletedEvents = all + separator + completed
+	AllRetriedEvents   = all + separator + retried
+	AllFailedEvents    = all + separator + failed
+	AllCanceledEvents  = all + separator + canceled
+
+	// Storage deal events
+	AllStorageDealEvents      = storageDeal + separator + all
+	StorageDealCreatedEvent   = storageDeal + separator + created
+	StorageDealCompletedEvent = storageDeal + separator + completed
+	StorageDealRetriedEvent   = storageDeal + separator + retried
+	StorageDealFailedEvent    = storageDeal + separator + failed
+	StorageDealCanceledEvent  = storageDeal + separator + canceled
+	StorageDealExpiredEvent   = storageDeal + separator + expired
+	StorageDealSlashedEvent   = storageDeal + separator + slashed
+
+	// Storage auction events
+	AllStorageAuctionEvents      = storageAuction + separator + all
+	StorageAuctionCreatedEvent   = storageAuction + separator + created
+	StorageAuctionCompletedEvent = storageAuction + separator + completed
+	StorageAuctionFailedEvent    = storageAuction + separator + failed
+	StorageAuctionCanceledEvent  = storageAuction + separator + canceled
+
+	// Data retrieval events
+	AllDataRetrievalEvents      = dataRetrieval + separator + all
+	DataRetrievalCreatedEvent   = dataRetrieval + separator + created
+	DataRetrievalCompletedEvent = dataRetrieval + separator + completed
+	DataRetrievalRetriedEvent   = dataRetrieval + separator + retried
+	DataRetrievalFailedEvent    = dataRetrieval + separator + failed
+	DataRetrievalCanceledEvent  = dataRetrieval + separator + canceled
+)
diff --git a/notifications/final_job_status.go b/notifications/final_job_status.go
new file mode 100644
index 000000000..b3f441cf7
--- /dev/null
+++ b/notifications/final_job_status.go
@@ -0,0 +1,90 @@
+package notifications
+
+import (
+	"bytes"
+	"encoding/json"
+	"io"
+
+	"github.com/textileio/powergate/v2/ffs"
+)
+
+type FinalJobStatus struct {
+	JobId      ffs.JobID
+	JobStatus  ffs.JobStatus
+	JobError   error
+	DealErrors []ffs.DealError
+}
+
+func (f FinalJobStatus) JobID() ffs.JobID {
+	return f.JobId
+}
+
+func (f FinalJobStatus) FinalUpdates() bool {
+	return true
+}
+
+func (f FinalJobStatus) MatchNotificationAlert(alert *ffs.WebhookAlert) bool {
+	return false
+}
+
+func (f FinalJobStatus) MatchNotificationEvent(event string) bool {
+	switch event {
+	case AllEvents, AllStorageDealEvents:
+		return true
+
+	case AllCompletedEvents, StorageDealCompletedEvent:
+		return f.JobStatus == ffs.Success
+
+	case AllFailedEvents, StorageDealFailedEvent:
+		return f.JobStatus == ffs.Failed
+
+	case AllCanceledEvents, StorageDealCanceledEvent:
+		return f.JobStatus == ffs.Canceled
+
+	default:
+		return false
+	}
+}
+
+type finalStorageJobNotification struct {
+	JobId      ffs.JobID   `json:"jobId"`
+	JobStatus  string      `json:"jobStatus"`
+	JobError   string      `json:"jobError,omitempty"`
+	DealErrors []dealError `json:"dealErrors,omitempty"`
+}
+
+type dealError struct {
+	ProposalCid string `json:"proposalCid"`
+	Miner       string `json:"miner"`
+	Message     string `json:"error"`
+}
+
+func (f FinalJobStatus) Payload() (io.Reader, error) {
+	var errMessage string
+	if f.JobError != nil {
+		errMessage = f.JobError.Error()
+	}
+
+	var dealErrors []dealError
+	for _, deal := range f.DealErrors {
+		dealErrors = append(dealErrors, dealError{
+			ProposalCid: deal.ProposalCid.String(),
+			Miner:       deal.Miner,
+			Message:     deal.Message,
+		})
+	}
+
+	obj := &finalStorageJobNotification{
+		JobId:      f.JobId,
+		JobStatus:  ffs.JobStatusStr[f.JobStatus],
+		JobError:   errMessage,
+		DealErrors: dealErrors,
+	}
+
+	data, err := json.Marshal(obj)
+	if err != nil {
+		return nil, err
+	}
+
+	return bytes.NewBuffer(data), nil
+}
diff --git a/notifications/job_updates.go b/notifications/job_updates.go
new file mode 100644
index 000000000..37dd1b450
--- /dev/null
+++ b/notifications/job_updates.go
@@ -0,0 +1,16 @@
+package notifications
+
+import (
+	"io"
+
+	"github.com/textileio/powergate/v2/ffs"
+)
+
+type JobUpdates interface {
+	JobID() ffs.JobID
+	FinalUpdates() bool
+
+	Payload() (io.Reader, error)
+	MatchNotificationEvent(event string) bool
+	MatchNotificationAlert(alert *ffs.WebhookAlert) bool
+}
diff --git a/notifications/notifier.go b/notifications/notifier.go
index a5413a96a..a0673462e 100644
--- a/notifications/notifier.go
+++ b/notifications/notifier.go
@@ -1,91 +1,110 @@
 package notifications
 
 import (
-	"bytes"
-	"encoding/json"
+	"context"
+	"io"
 	"net/http"
-	"sync"
 
-	"github.com/filecoin-project/go-fil-markets/storagemarket"
-	"github.com/textileio/powergate/v2/deals"
+	logging "github.com/ipfs/go-log/v2"
 	"github.com/textileio/powergate/v2/ffs"
 )
 
-type Notifier interface {
-	RegisterStorageJob(job ffs.StorageJob, notificationConfig []*ffs.NotificationConfig)
-	NotifyStorageJob(job ffs.StorageJob, dealInfo deals.StorageDealInfo)
-}
+var (
+	log = logging.Logger("notifier")
+)
 
-type storageJobUpdate struct {
-	job      ffs.StorageJob
-	dealInfo deals.StorageDealInfo
+type Notifier interface {
+	RegisterJob(jobId ffs.JobID, configs []*ffs.NotificationConfig)
+	NotifyJobUpdates(job JobUpdates)
 }
 
 type notifier struct {
-	client                  *http.Client
-	store                   *configStore
-	storageJobNotifications chan *storageJobUpdate
+	ctx           context.Context
+	configs       *configStore
+	updates       chan JobUpdates
+	toDelete      chan ffs.JobID
+	notifications chan *notification
 }
 
-func New() *notifier {
+func New(ctx context.Context) *notifier {
 	nt := &notifier{
-		client:                  http.DefaultClient,
-		store:                   newConfigStore(),
-		storageJobNotifications: make(chan *storageJobUpdate, 1000),
+		ctx:           ctx,
+		configs:       newConfigStore(),
+		updates:       make(chan JobUpdates, 1000),
+		toDelete:      make(chan ffs.JobID, 1000),
+		notifications: make(chan *notification, 1000),
 	}
 
 	go nt.run()
 
+	const workers = 10
+	for i := 0; i < workers; i++ {
+		go nt.worker()
+	}
+
 	return nt
 }
 
-func (n *notifier) RegisterStorageJob(job ffs.StorageJob, notificationConfig []*ffs.NotificationConfig) {
-	if notificationConfig == nil {
+func (n *notifier) RegisterJob(jobId ffs.JobID, configs []*ffs.NotificationConfig) {
+	if configs == nil {
 		return
 	}
 
-	n.store.put(job, notificationConfig)
+	n.configs.put(jobId, configs)
 }
 
-func (n *notifier) NotifyStorageJob(job ffs.StorageJob, dealInfo deals.StorageDealInfo) {
-	n.storageJobNotifications <- &storageJobUpdate{
-		job:      job,
-		dealInfo: dealInfo,
-	}
+func (n *notifier) NotifyJobUpdates(jobUpdates JobUpdates) {
+	n.updates <- jobUpdates
 }
 
 func (n *notifier) run() {
-	for updates := range n.storageJobNotifications {
-		if updates == nil {
-			continue
-		}
-
-		config := n.store.get(updates.job.ID)
-		if config == nil {
-			continue
+	for {
+		select {
+		case <-n.ctx.Done():
+			return
+
+		case updates, ok := <-n.updates:
+			if !ok {
+				return
+			}
+
+			config := n.configs.get(updates.JobID())
+			if config == nil {
+				continue
+			}
+
+			n.notifyAll(config, updates)
+
+			if updates.FinalUpdates() {
+				n.configs.delete(updates.JobID())
+			}
 		}
-
-		n.notifyAll(config.notifications, updates)
 	}
 }
 
-func (n *notifier) notifyAll(configs []*ffs.NotificationConfig, updates *storageJobUpdate) {
+func (n *notifier) notifyAll(configs []*ffs.NotificationConfig, updates JobUpdates) {
 	for _, cfg := range configs {
+		if cfg == nil {
+			continue
+		}
+
 		n.notify(cfg, updates)
 	}
 }
 
-func (n *notifier) notify(config *ffs.NotificationConfig, updates *storageJobUpdate) {
-	if config == nil {
-		return
-	}
+func (n *notifier) notify(config *ffs.NotificationConfig, updates JobUpdates) {
+	if matchNotificationConfig(config.Configuration, updates) {
+		payload, err := updates.Payload()
+		if err != nil {
+			log.Errorf("failed to make notification payload: %s", err)
+			return
+		}
 
-	if matchNotificationConfig(config.Configuration, updates.dealInfo) {
-		n.publishNotification(config.Webhook, updates)
+		n.publishNotification(config.Webhook, payload)
 	}
 }
 
-func matchNotificationConfig(config *ffs.WebhookConfiguration, updates deals.StorageDealInfo) bool {
+func matchNotificationConfig(config *ffs.WebhookConfiguration, updates JobUpdates) bool {
 	if config == nil {
 		return false
 	}
@@ -93,47 +112,9 @@ func matchNotificationConfig(config *ffs.WebhookConfiguration, updates deals.Sto
 	return matchNotificationEvents(config.Events, updates) || matchNotificationAlerts(config.Alerts, updates)
 }
 
-const (
-	all            = "*"
-	created        = "created"
-	completed      = "completed"
-	retried        = "retried"
-	failed         = "failed"
-	expired        = "expired"
-	slashed        = "slashed"
-	separator      = "-"
-	storageDeal    = "storage-deal"
-	storageAuction = "storage-auction"
-	dataRetrieval  = "data-retrieval"
-
-	AllEvents          = all
-	AllCreatedEvents   = all + separator + created
-	AllCompletedEvents = all + separator + completed
-	AllRetriedEvents   = all + separator + retried
-	AllFailedEvents    = all + separator + failed
-
-	AllStorageDealEvents      = storageDeal + separator + all
-	StorageDealCreatedEvent   = storageDeal + separator + created
-	StorageDealCompletedEvent = storageDeal + separator + completed
-	StorageDealRetriedEvent   = storageDeal + separator + retried
-	StorageDealFailedEvent    = storageDeal + separator + failed
-	StorageDealExpiredEvent   = storageDeal + separator + expired
-	StorageDealSlashedEvent   = storageDeal + separator + slashed
-
-	AllStorageAuctionEvents      = storageAuction + separator + all
-	StorageAuctionCreatedEvent   = storageAuction + separator + created
-	StorageAuctionCompletedEvent = storageAuction + separator + completed
-	StorageAuctionFailedEvent    = storageAuction + separator + failed
-
-	AllDataRetrievalEvents      = dataRetrieval + separator + all
-	DataRetrievalCompletedEvent = dataRetrieval + separator + completed
-	DataRetrievalRetriedEvent   = dataRetrieval + separator + retried
-	DataRetrievalFailedEvent    = dataRetrieval + separator + failed
-)
-
-func matchNotificationEvents(events []string, updates deals.StorageDealInfo) bool {
+func matchNotificationEvents(events []string, updates JobUpdates) bool {
 	for _, event := range events {
-		if matchNotificationEvent(event, updates) {
+		if updates.MatchNotificationEvent(event) {
 			return true
 		}
 	}
@@ -141,125 +122,49 @@ func matchNotificationEvents(events []string, updates deals.StorageDealInfo) boo
 	return false
 }
 
-func matchNotificationEvent(event string, updates deals.StorageDealInfo) bool {
-	switch event {
-	case AllEvents:
-		return true
-	case AllCreatedEvents:
-		// TODO: add created events
-		return updates.DealID != 0 // ||
-	case AllCompletedEvents:
-		// TODO: add other completed events
-		return updates.StateID == storagemarket.StorageDealActive // ||
-
-	// TODO:
-	// case AllRetriedEvents:
-
-	case AllStorageDealEvents:
-		return true
-
-	case StorageDealCreatedEvent:
-		return updates.DealID != 0
-
-	case StorageDealCompletedEvent:
-		return updates.StateID == storagemarket.StorageDealActive
-
-	// TODO:
-	// case StorageDealRetriedEvent:
-
-	case StorageDealFailedEvent:
-		return updates.StateID == storagemarket.StorageDealFailing || updates.StateID == storagemarket.StorageDealError || updates.Message != ""
-
-	case StorageDealExpiredEvent:
-		return updates.StateID == storagemarket.StorageDealExpired
-
-	case StorageDealSlashedEvent:
-		return updates.StateID == storagemarket.StorageDealSlashed
-
-	default:
-		return false
+func matchNotificationAlerts(alerts []*ffs.WebhookAlert, updates JobUpdates) bool {
+	for _, alert := range alerts {
+		if updates.MatchNotificationAlert(alert) {
+			return true
+		}
 	}
-}
 
-func matchNotificationAlerts(alerts []*ffs.WebhookAlert, updates deals.StorageDealInfo) bool {
-	// TODO
 	return false
 }
 
-type notification struct {
-	Cid         string    `json:"cid"`
-	JobID       ffs.JobID `json:"jobId"`
-	JobStatus   string    `json:"jobStatus"`
-	Miner       string    `json:"miner"`
-	Price       uint64    `json:"price"`
-	ProposalCid string    `json:"proposalCid"`
-	DealID      uint64    `json:"dealId,omitempty"`
-	DealStatus  string    `json:"dealStatus"`
-	ErrCause    string    `json:"error,omitempty"`
-	Message     string    `json:"message,omitempty"`
-}
-
-func (n *notifier) publishNotification(webhook *ffs.Webhook, updates *storageJobUpdate) {
-	if webhook == nil {
-		return
-	}
-
-	obj := &notification{
-		Cid:         updates.job.Cid.String(),
-		JobID:       updates.job.ID,
-		JobStatus:   ffs.JobStatusStr[updates.job.Status],
-		Miner:       updates.dealInfo.Miner,
-		Price:       updates.dealInfo.PricePerEpoch,
-		ProposalCid: updates.dealInfo.ProposalCid.String(),
-		DealID:      updates.dealInfo.DealID,
-		DealStatus:  updates.dealInfo.StateName,
-		ErrCause:    updates.job.ErrCause,
-		Message:     updates.dealInfo.Message,
-	}
-
-	data, err := json.Marshal(obj)
-	if err != nil {
-		// TODO: log error
+func (n *notifier) publishNotification(webhook *ffs.Webhook, payload io.Reader) {
+	if webhook == nil || payload == nil {
 		return
 	}
 
-	err = webhook.Publish(n.client, bytes.NewBuffer(data))
-	if err != nil {
-		// TODO: log error
-		return
+	n.notifications <- &notification{
+		webhook: webhook,
+		payload: payload,
 	}
 }
 
-type configStore struct {
-	sync.RWMutex
-
-	configs map[ffs.JobID]*jobConfig
+type notification struct {
+	webhook *ffs.Webhook
+	payload io.Reader
 }
 
-func newConfigStore() *configStore {
-	return &configStore{
-		configs: make(map[ffs.JobID]*jobConfig),
-	}
-}
+func (n *notifier) worker() {
+	client := http.DefaultClient
 
-type jobConfig struct {
-	job           ffs.StorageJob
-	notifications []*ffs.NotificationConfig
-}
+	for {
+		select {
+		case <-n.ctx.Done():
+			return
 
-func (s *configStore) put(job ffs.StorageJob, notifications []*ffs.NotificationConfig) {
-	s.Lock()
-	defer s.Unlock()
+		case notification, ok := <-n.notifications:
+			if !ok {
+				return
+			}
 
-	s.configs[job.ID] = &jobConfig{
-		job:           job,
-		notifications: notifications,
+			err := notification.webhook.Publish(client, notification.payload)
+			if err != nil {
+				log.Errorf("failed to publish notification: %s", err)
+			}
+		}
 	}
 }
-
-func (s *configStore) get(jobID ffs.JobID) *jobConfig {
-	s.RLock()
-	defer s.RUnlock()
-
-	return s.configs[jobID]
-}
diff --git a/notifications/retrieval_job.go b/notifications/retrieval_job.go
new file mode 100644
index 000000000..a0848373c
--- /dev/null
+++ b/notifications/retrieval_job.go
@@ -0,0 +1,85 @@
+package notifications
+
+import (
+	"bytes"
+	"encoding/json"
+	"io"
+	"time"
+
+	"github.com/textileio/powergate/v2/ffs"
+)
+
+type RetrievalJobUpdates struct {
+	Job  ffs.RetrievalJob
+	Info ffs.RetrievalInfo
+}
+
+func (r RetrievalJobUpdates) JobID() ffs.JobID {
+	return r.Job.ID
+}
+
+func (r RetrievalJobUpdates) FinalUpdates() bool {
+	return false
+}
+
+type retrievalJobNotification struct {
+	JobID       ffs.JobID       `json:"jobId"`
+	JobStatus   string          `json:"jobStatus"`
+	RetrievalID ffs.RetrievalID `json:"retrievalId"`
+	DataCid     string          `json:"dataCid"`
+	TotalPaid   uint64          `json:"totalPaid"`
+	Miner       string          `json:"miner"`
+	Size        int64           `json:"size"`
+	CreatedAt   string          `json:"createdAt"`
+	ErrCause    string          `json:"error,omitempty"`
+}
+
+func (r RetrievalJobUpdates) Payload() (io.Reader, error) {
+	obj := &retrievalJobNotification{
+		JobID:       r.Job.ID,
+		JobStatus:   ffs.JobStatusStr[r.Job.Status],
+		RetrievalID: r.Job.RetrievalID,
+		DataCid:     r.Info.DataCid.String(),
+		TotalPaid:   r.Info.TotalPaid,
+		Miner:       r.Info.MinerAddr,
+		Size:        r.Info.Size,
+		CreatedAt:   r.Info.CreatedAt.Format(time.RFC3339),
+		ErrCause:    r.Job.ErrCause,
+	}
+
+	data, err := json.Marshal(obj)
+	if err != nil {
+		return nil, err
+	}
+
+	return bytes.NewBuffer(data), nil
+}
+
+func (r RetrievalJobUpdates) MatchNotificationEvent(event string) bool {
+	switch event {
+	case AllEvents, AllDataRetrievalEvents:
+		return true
+	case AllCreatedEvents, DataRetrievalCreatedEvent:
+		return r.Job.Status == ffs.Executing
+	case AllCompletedEvents, DataRetrievalCompletedEvent:
+		return r.Job.Status == ffs.Success
+
+	case AllRetriedEvents, DataRetrievalRetriedEvent:
+		// TODO:
+		return false
+
+	case AllFailedEvents, DataRetrievalFailedEvent:
+		return r.Job.Status == ffs.Failed
+
+	case AllCanceledEvents, DataRetrievalCanceledEvent:
+		return r.Job.Status == ffs.Canceled
+
+	default:
+		return false
+	}
+}
+
+func (r RetrievalJobUpdates) MatchNotificationAlert(alert *ffs.WebhookAlert) bool {
+	// TODO
+	return false
+}
diff --git a/notifications/storage_job.go b/notifications/storage_job.go
new file mode 100644
index 000000000..0fb33ceb4
--- /dev/null
+++ b/notifications/storage_job.go
@@ -0,0 +1,96 @@
+package notifications
+
+import (
+	"bytes"
+	"encoding/json"
+	"io"
+
+	"github.com/filecoin-project/go-fil-markets/storagemarket"
+	"github.com/textileio/powergate/v2/deals"
+	"github.com/textileio/powergate/v2/ffs"
+)
+
+type StorageJobUpdates struct {
+	Job  ffs.StorageJob
+	Info deals.StorageDealInfo
+}
+
+func (s StorageJobUpdates) JobID() ffs.JobID {
+	return s.Job.ID
+}
+
+func (s StorageJobUpdates) FinalUpdates() bool {
+	return false
+}
+
+func (s StorageJobUpdates) MatchNotificationAlert(alert *ffs.WebhookAlert) bool {
+	// TODO:
+	return false
+}
+
+func (s StorageJobUpdates) MatchNotificationEvent(event string) bool {
+	switch event {
+	case AllEvents, AllStorageDealEvents:
+		return true
+	case AllCreatedEvents, StorageDealCreatedEvent:
+		return s.Info.DealID != 0
+	case AllCompletedEvents, StorageDealCompletedEvent:
+		return s.Info.StateID == storagemarket.StorageDealActive
+
+	case AllRetriedEvents, StorageDealRetriedEvent:
+		// TODO:
+		return false
+
+	case AllFailedEvents, StorageDealFailedEvent:
+		return s.Job.Status == ffs.Failed ||
+			s.Info.StateID == storagemarket.StorageDealFailing ||
+			s.Info.StateID == storagemarket.StorageDealError
+
+	case AllCanceledEvents, StorageDealCanceledEvent:
+		return s.Job.Status == ffs.Canceled
+
+	case StorageDealExpiredEvent:
+		return s.Info.StateID == storagemarket.StorageDealExpired
+
+	case StorageDealSlashedEvent:
+		return s.Info.StateID == storagemarket.StorageDealSlashed
+
+	default:
+		return false
+	}
+}
+
+type storageJobNotification struct {
+	Cid         string    `json:"cid"`
+	JobID       ffs.JobID `json:"jobId"`
+	JobStatus   string    `json:"jobStatus"`
+	Miner       string    `json:"miner"`
+	Price       uint64    `json:"price"`
+	ProposalCid string    `json:"proposalCid"`
+	DealID      uint64    `json:"dealId,omitempty"`
+	DealStatus  string    `json:"dealStatus"`
+	ErrCause    string    `json:"error,omitempty"`
+	Message     string    `json:"message,omitempty"`
+}
+
+func (s StorageJobUpdates) Payload() (io.Reader, error) {
+	obj := &storageJobNotification{
+		Cid:         s.Job.Cid.String(),
+		JobID:       s.Job.ID,
+		JobStatus:   ffs.JobStatusStr[s.Job.Status],
+		Miner:       s.Info.Miner,
+		Price:       s.Info.PricePerEpoch,
+		ProposalCid: s.Info.ProposalCid.String(),
+		DealID:      s.Info.DealID,
+		DealStatus:  s.Info.StateName,
+		ErrCause:    s.Job.ErrCause,
+		Message:     s.Info.Message,
+	}
+
+	data, err := json.Marshal(obj)
+	if err != nil {
+		return nil, err
+	}
+
+	return bytes.NewBuffer(data), nil
+}
diff --git a/notifications/store.go b/notifications/store.go
new file mode 100644
index 000000000..78e30d322
--- /dev/null
+++ b/notifications/store.go
@@ -0,0 +1,42 @@
+package notifications
+
+import (
+	"sync"
+
+	"github.com/textileio/powergate/v2/ffs"
+)
+
+type configStore struct {
+	sync.RWMutex
+
+	configs map[ffs.JobID][]*ffs.NotificationConfig
+}
+
+func newConfigStore() *configStore {
+	return &configStore{
+		configs: make(map[ffs.JobID][]*ffs.NotificationConfig),
+	}
+}
+
+func (s *configStore) put(jobId ffs.JobID, configs []*ffs.NotificationConfig) {
+	s.Lock()
+	defer s.Unlock()
+
+	s.configs[jobId] = configs
+}
+
+func (s *configStore) get(jobId ffs.JobID) []*ffs.NotificationConfig {
+	s.RLock()
+	defer s.RUnlock()
+
+	return s.configs[jobId]
+}
+
+func (s *configStore) delete(jobId ffs.JobID) {
+	s.Lock()
+	defer s.Unlock()
+
+	if _, ok := s.configs[jobId]; ok {
+		delete(s.configs, jobId)
+	}
+}
diff --git a/tests/txmapds.go b/tests/txmapds.go
index 95bd7b29d..ed82d3916 100644
--- a/tests/txmapds.go
+++ b/tests/txmapds.go
@@ -6,8 +6,8 @@ import (
 
 	"github.com/ipfs/go-datastore"
 	"github.com/ipfs/go-datastore/query"
-	"github.com/textileio/powergate/v2/deals"
 	"github.com/textileio/powergate/v2/ffs"
+	"github.com/textileio/powergate/v2/notifications"
 )
 
 // TxMapDatastore is a in-memory datastore that satisfies TxnDatastore.
@@ -181,7 +181,5 @@ func NewMockNotifier() *mockNotifier {
 	return &mockNotifier{}
 }
 
-func (n *mockNotifier) NotifyDealRecord(dr deals.StorageDealRecord) {}
-func (n *mockNotifier) RegisterStorageJob(job ffs.StorageJob, notificationConfig []*ffs.NotificationConfig) {
-}
-func (n *mockNotifier) NotifyStorageJob(jobID ffs.JobID, dealInfo deals.StorageDealInfo) {}
+func (n *mockNotifier) RegisterJob(jobId ffs.JobID, configs []*ffs.NotificationConfig) {}
+func (n *mockNotifier) NotifyJobUpdates(job notifications.JobUpdates)                  {}

From 1fe306fb2451d9285c68cb0258777740b10881f7 Mon Sep 17 00:00:00 2001
From: Volodymyr Manilo <wmanilo@gmail.com>
Date: Mon, 27 Jun 2022 23:19:29 +0300
Subject: [PATCH 5/7] added datacap alert

---
 ffs/manager/manager.go             |   6 ++
 ffs/scheduler/scheduler.go         |   2 +
 ffs/scheduler/scheduler_storage.go |   2 +
 notifications/alerts.go            | 105 +++++++++++++++++++++++++++++
 notifications/events.go            |  30 +++------
 notifications/final_job_status.go  |   4 +-
 notifications/job_updates.go       |   8 ++-
 notifications/notifier.go          |  48 ++++++++++---
 notifications/retrieval_job.go     |   5 +-
 notifications/storage_job.go       |   5 +-
 10 files changed, 175 insertions(+), 40 deletions(-)
 create mode 100644 notifications/alerts.go

diff --git a/ffs/manager/manager.go b/ffs/manager/manager.go
index 6e65423ee..e31b182e9 100644
--- a/ffs/manager/manager.go
+++ b/ffs/manager/manager.go
@@ -72,6 +72,12 @@ var (
 				},
 				Configuration: &ffs.WebhookConfiguration{
 					Events: []string{"*-created", "*-completed"},
+					Alerts: []*ffs.WebhookAlert{
+						{
+							Type:      "datacap",
+							Threshold: "500 GB",
+						},
+					},
 				},
 			},
 		},
diff --git a/ffs/scheduler/scheduler.go b/ffs/scheduler/scheduler.go
index aabac0fc6..c765f9658 100644
--- a/ffs/scheduler/scheduler.go
+++ b/ffs/scheduler/scheduler.go
@@ -530,6 +530,7 @@ func (s *Scheduler) executeQueuedStorage(j ffs.StorageJob) {
 	}
 
 	s.notifier.RegisterJob(j.ID, a.Cfg.Notifications)
+	s.notifier.Alert(notifications.DiskSpaceAlert{JobID: j.ID}, a.Cfg.Notifications)
 
 	// Execute
 	s.l.Log(ctx, "Executing job %s...", j.ID)
@@ -646,6 +647,7 @@ func (s *Scheduler) executeQueuedRetrievals(j ffs.RetrievalJob) {
 	}
 
 	s.notifier.RegisterJob(j.ID, a.Notifications)
+	s.notifier.Alert(notifications.DiskSpaceAlert{JobID: j.ID}, a.Notifications)
 
 	// Execute
 	s.l.Log(ctx, "Executing job %s...", j.ID)
diff --git a/ffs/scheduler/scheduler_storage.go b/ffs/scheduler/scheduler_storage.go
index 265b14a66..8a13380db 100644
--- a/ffs/scheduler/scheduler_storage.go
+++ b/ffs/scheduler/scheduler_storage.go
@@ -13,6 +13,7 @@ import (
 	"github.com/textileio/powergate/v2/ffs/scheduler/internal/astore"
 	"github.com/textileio/powergate/v2/ffs/scheduler/internal/cistore"
 	"github.com/textileio/powergate/v2/ffs/scheduler/internal/sjstore"
+	"github.com/textileio/powergate/v2/notifications"
 )
 
 // PushConfig queues the specified StorageConfig to be executed as a new Job. It returns
@@ -83,6 +84,7 @@ func (s *Scheduler) push(iid ffs.APIID, c cid.Cid, cfg ffs.StorageConfig, oldCid
 	s.l.Log(ctx, "Configuration saved successfully")
 
 	s.notifier.RegisterJob(j.ID, cfg.Notifications)
+	s.notifier.Alert(notifications.DiskSpaceAlert{JobID: jid}, cfg.Notifications)
 
 	return jid, nil
 }
diff --git a/notifications/alerts.go b/notifications/alerts.go
new file mode 100644
index 000000000..e69310b77
--- /dev/null
+++ b/notifications/alerts.go
@@ -0,0 +1,105 @@
+package notifications
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"os"
+
+	"github.com/docker/go-units"
+	"github.com/textileio/powergate/v2/ffs"
+	"golang.org/x/sys/unix"
+)
+
+const (
+	DiskSpaceCheck = "datacap"
+)
+
+type DiskSpaceAlert struct {
+	JobID ffs.JobID
+}
+
+type diskSpaceAlertNotification struct {
+	JobID              ffs.JobID `json:"jobId"`
+	AlertType          string    `json:"alertType"`
+	AvailableDiskSpace string    `json:"availableDiskSpace"`
+	Error              string    `json:"error"`
+}
+
+func (d DiskSpaceAlert) Payload() (io.Reader, error) {
+	availableDiskSpace, err := getAvailableDiskSpace()
+	if err != nil {
+		err = fmt.Errorf("failed to get available disk space: %w", err)
+		log.Error(err)
+		return nil, err
+	}
+
+	obj := &diskSpaceAlertNotification{
+		JobID:              d.JobID,
+		AlertType:          DiskSpaceCheck,
+		AvailableDiskSpace: units.BytesSize(float64(availableDiskSpace)),
+		Error:              "available disk space below threshold",
+	}
+
+	data, err := json.Marshal(obj)
+	if err != nil {
+		return nil, err
+	}
+
+	return bytes.NewBuffer(data), nil
+}
+
+func (d DiskSpaceAlert) MatchEvent(event string) bool {
+	return false
+}
+
+func (d DiskSpaceAlert) MatchAlert(alert *ffs.WebhookAlert) bool {
+	if alert == nil {
+		return false
+	}
+
+	if alert.Type != DiskSpaceCheck {
+		return false
+	}
+
+	threshold, err := parseDiskSpaceThresholdToBytes(alert.Threshold)
+	if err != nil {
+		log.Errorf("failed to parse alert disk space threshold: %s", err)
+		return false
+	}
+
+	availableDiskSpace, err := getAvailableDiskSpace()
+	if err != nil {
+		log.Errorf("failed to get available disk space: %s", err)
+		return false
+	}
+
+	return availableDiskSpace < threshold
+}
+
+func parseDiskSpaceThresholdToBytes(threshold string) (uint64, error) {
+	size, err := units.RAMInBytes(threshold)
+	if err != nil {
+		return 0, err
+	}
+
+	return uint64(size), nil
+}
+
+// getAvailableDiskSpace - provides available disk space in bytes
+func getAvailableDiskSpace() (uint64, error) {
+	var stat unix.Statfs_t
+
+	wd, err := os.Getwd()
+	if err != nil {
+		return 0, err
+	}
+
+	if err := unix.Statfs(wd, &stat); err != nil {
+		return 0, err
+	}
+
+	// available blocks * size per block
+	return stat.Bavail * uint64(stat.Bsize), nil
+}
diff --git a/notifications/events.go b/notifications/events.go
index 41a370823..7a718dfbc 100644
--- a/notifications/events.go
+++ b/notifications/events.go
@@ -1,18 +1,17 @@
 package notifications
 
 const (
-	all            = "*"
-	created        = "created"
-	completed      = "completed"
-	retried        = "retried"
-	failed         = "failed"
-	canceled       = "canceled"
-	expired        = "expired"
-	slashed        = "slashed"
-	separator      = "-"
-	storageDeal    = "storage-deal"
-	storageAuction = "storage-auction"
-	dataRetrieval  = "data-retrieval"
+	all           = "*"
+	created       = "created"
+	completed     = "completed"
+	retried       = "retried"
+	failed        = "failed"
+	canceled      = "canceled"
+	expired       = "expired"
+	slashed       = "slashed"
+	separator     = "-"
+	storageDeal   = "storage-deal"
+	dataRetrieval = "data-retrieval"
 
 	// All events
 	AllEvents          = all
@@ -32,13 +31,6 @@ const (
 	StorageDealExpiredEvent   = storageDeal + separator + expired
 	StorageDealSlashedEvent   = storageDeal + separator + slashed
 
-	// Storage auction events
-	AllStorageAuctionEvents      = storageAuction + separator + all
-	StorageAuctionCreatedEvent   = storageAuction + separator + created
-	StorageAuctionCompletedEvent = storageAuction + separator + completed
-	StorageAuctionFailedEvent    = storageAuction + separator + failed
-	StorageAuctionCanceledEvent  = storageAuction + separator + canceled
-
 	// Data retrieval events
 	AllDataRetrievalEvents      = dataRetrieval + separator + all
 	DataRetrievalCreatedEvent   = dataRetrieval + separator + created
diff --git a/notifications/final_job_status.go b/notifications/final_job_status.go
index b3f441cf7..4af70c284 100644
--- a/notifications/final_job_status.go
+++ b/notifications/final_job_status.go
@@ -23,11 +23,11 @@ func (f FinalJobStatus) FinalUpdates() bool {
 	return true
 }
 
-func (f FinalJobStatus) MatchNotificationAlert(alert *ffs.WebhookAlert) bool {
+func (f FinalJobStatus) MatchAlert(alert *ffs.WebhookAlert) bool {
 	return false
 }
 
-func (f FinalJobStatus) MatchNotificationEvent(event string) bool {
+func (f FinalJobStatus) MatchEvent(event string) bool {
 	switch event {
 	case AllEvents, AllStorageDealEvents:
 		return true
diff --git a/notifications/job_updates.go b/notifications/job_updates.go
index 37dd1b450..cadebb757 100644
--- a/notifications/job_updates.go
+++ b/notifications/job_updates.go
@@ -10,7 +10,11 @@ type JobUpdates interface {
 	JobID() ffs.JobID
 	FinalUpdates() bool
 
+	Notification
+}
+
+type Notification interface {
 	Payload() (io.Reader, error)
-	MatchNotificationEvent(event string) bool
-	MatchNotificationAlert(alert *ffs.WebhookAlert) bool
+	MatchEvent(event string) bool
+	MatchAlert(alert *ffs.WebhookAlert) bool
 }
diff --git a/notifications/notifier.go b/notifications/notifier.go
index a0673462e..ddd775e59 100644
--- a/notifications/notifier.go
+++ b/notifications/notifier.go
@@ -16,6 +16,7 @@ var (
 type Notifier interface {
 	RegisterJob(jobId ffs.JobID, configs []*ffs.NotificationConfig)
 	NotifyJobUpdates(job JobUpdates)
+	Alert(alert Notification, configs []*ffs.NotificationConfig)
 }
 
 type notifier struct {
@@ -24,6 +25,7 @@ type notifier struct {
 	updates       chan JobUpdates
 	toDelete      chan ffs.JobID
 	notifications chan *notification
+	alerts        chan *alert
 }
 
 func New(ctx context.Context) *notifier {
@@ -33,6 +35,7 @@ func New(ctx context.Context) *notifier {
 		updates:       make(chan JobUpdates, 1000),
 		toDelete:      make(chan ffs.JobID, 1000),
 		notifications: make(chan *notification, 1000),
+		alerts:        make(chan *alert, 1000),
 	}
 
 	go nt.run()
@@ -45,6 +48,22 @@ func New(ctx context.Context) *notifier {
 	return nt
 }
 
+type alert struct {
+	notification Notification
+	configs      []*ffs.NotificationConfig
+}
+
+func (n *notifier) Alert(notification Notification, configs []*ffs.NotificationConfig) {
+	if configs == nil {
+		return
+	}
+
+	n.alerts <- &alert{
+		notification: notification,
+		configs:      configs,
+	}
+}
+
 func (n *notifier) RegisterJob(jobId ffs.JobID, configs []*ffs.NotificationConfig) {
 	if configs == nil {
 		return
@@ -78,23 +97,30 @@ func (n *notifier) run() {
 			if updates.FinalUpdates() {
 				n.configs.delete(updates.JobID())
 			}
+
+		case alert, ok := <-n.alerts:
+			if !ok {
+				return
+			}
+
+			n.notifyAll(alert.configs, alert.notification)
 		}
 	}
 }
 
-func (n *notifier) notifyAll(configs []*ffs.NotificationConfig, updates JobUpdates) {
+func (n *notifier) notifyAll(configs []*ffs.NotificationConfig, notification Notification) {
 	for _, cfg := range configs {
 		if cfg == nil {
 			continue
 		}
 
-		n.notify(cfg, updates)
+		n.notify(cfg, notification)
 	}
 }
 
-func (n *notifier) notify(config *ffs.NotificationConfig, updates JobUpdates) {
-	if matchNotificationConfig(config.Configuration, updates) {
-		payload, err := updates.Payload()
+func (n *notifier) notify(config *ffs.NotificationConfig, notification Notification) {
+	if matchEventsOrAlerts(config.Configuration, notification) {
+		payload, err := notification.Payload()
 		if err != nil {
 			log.Errorf("failed to make notification payload: %s", err)
 			return
@@ -104,17 +130,17 @@ func (n *notifier) notify(config *ffs.NotificationConfig, updates JobUpdates) {
 	}
 }
 
-func matchNotificationConfig(config *ffs.WebhookConfiguration, updates JobUpdates) bool {
+func matchEventsOrAlerts(config *ffs.WebhookConfiguration, notification Notification) bool {
 	if config == nil {
 		return false
 	}
 
-	return matchNotificationEvents(config.Events, updates) || matchNotificationAlerts(config.Alerts, updates)
+	return matchEvents(config.Events, notification) || matchAlerts(config.Alerts, notification)
 }
 
-func matchNotificationEvents(events []string, updates JobUpdates) bool {
+func matchEvents(events []string, notification Notification) bool {
 	for _, event := range events {
-		if updates.MatchNotificationEvent(event) {
+		if notification.MatchEvent(event) {
 			return true
 		}
 	}
@@ -122,9 +148,9 @@ func matchNotificationEvents(events []string, updates JobUpdates) bool {
 	return false
 }
 
-func matchNotificationAlerts(alerts []*ffs.WebhookAlert, updates JobUpdates) bool {
+func matchAlerts(alerts []*ffs.WebhookAlert, notification Notification) bool {
 	for _, alert := range alerts {
-		if updates.MatchNotificationAlert(alert) {
+		if notification.MatchAlert(alert) {
 			return true
 		}
 	}
diff --git a/notifications/retrieval_job.go b/notifications/retrieval_job.go
index a0848373c..0f9dda600 100644
--- a/notifications/retrieval_job.go
+++ b/notifications/retrieval_job.go
@@ -55,7 +55,7 @@ func (r RetrievalJobUpdates) Payload() (io.Reader, error) {
 	return bytes.NewBuffer(data), nil
 }
 
-func (r RetrievalJobUpdates) MatchNotificationEvent(event string) bool {
+func (r RetrievalJobUpdates) MatchEvent(event string) bool {
 	switch event {
 	case AllEvents, AllDataRetrievalEvents:
 		return true
@@ -79,7 +79,6 @@ func (r RetrievalJobUpdates) MatchNotificationEvent(event string) bool {
 	}
 }
 
-func (r RetrievalJobUpdates) MatchNotificationAlert(alert *ffs.WebhookAlert) bool {
-	// TODO
+func (r RetrievalJobUpdates) MatchAlert(alert *ffs.WebhookAlert) bool {
 	return false
 }
diff --git a/notifications/storage_job.go b/notifications/storage_job.go
index 0fb33ceb4..3982d5ad2 100644
--- a/notifications/storage_job.go
+++ b/notifications/storage_job.go
@@ -23,12 +23,11 @@ func (s StorageJobUpdates) FinalUpdates() bool {
 	return false
 }
 
-func (s StorageJobUpdates) MatchNotificationAlert(alert *ffs.WebhookAlert) bool {
-	// TODO:
+func (s StorageJobUpdates) MatchAlert(alert *ffs.WebhookAlert) bool {
 	return false
 }
 
-func (s StorageJobUpdates) MatchNotificationEvent(event string) bool {
+func (s StorageJobUpdates) MatchEvent(event string) bool {
 	switch event {
 	case AllEvents, AllStorageDealEvents:
 		return true

From 570b79db869efa905894cfabd995e3183b00d369 Mon Sep 17 00:00:00 2001
From: Volodymyr Manilo <wmanilo@gmail.com>
Date: Tue, 28 Jun 2022 00:10:25 +0300
Subject: [PATCH 6/7] added comments to notifier

---
 notifications/alerts.go           | 6 ++++++
 notifications/events.go           | 2 ++
 notifications/final_job_status.go | 6 ++++++
 notifications/job_updates.go      | 7 +++++++
 notifications/notifier.go         | 4 ++++
 notifications/retrieval_job.go    | 1 +
 notifications/storage_job.go      | 1 +
 notifications/store.go            | 2 ++
 8 files changed, 29 insertions(+)

diff --git a/notifications/alerts.go b/notifications/alerts.go
index e69310b77..6d2e2f61e 100644
--- a/notifications/alerts.go
+++ b/notifications/alerts.go
@@ -12,10 +12,13 @@ import (
 	"golang.org/x/sys/unix"
 )
 
+// Alert types
+
 const (
 	DiskSpaceCheck = "datacap"
 )
 
+// DiskSpaceAlert - uses for alert notification when available disk space goes below threshold
 type DiskSpaceAlert struct {
 	JobID ffs.JobID
 }
@@ -27,6 +30,7 @@ type diskSpaceAlertNotification struct {
 	Error              string    `json:"error"`
 }
 
+// Payload - forms notification payload
 func (d DiskSpaceAlert) Payload() (io.Reader, error) {
 	availableDiskSpace, err := getAvailableDiskSpace()
 	if err != nil {
@@ -50,10 +54,12 @@ func (d DiskSpaceAlert) Payload() (io.Reader, error) {
 	return bytes.NewBuffer(data), nil
 }
 
+// MatchEvent - checks for matching with events
 func (d DiskSpaceAlert) MatchEvent(event string) bool {
 	return false
 }
 
+// MatchAlert - checks for matching with alerts
 func (d DiskSpaceAlert) MatchAlert(alert *ffs.WebhookAlert) bool {
 	if alert == nil {
 		return false
diff --git a/notifications/events.go b/notifications/events.go
index 7a718dfbc..4e8ec07e7 100644
--- a/notifications/events.go
+++ b/notifications/events.go
@@ -1,5 +1,7 @@
 package notifications
 
+// Event types
+
 const (
 	all           = "*"
 	created       = "created"
diff --git a/notifications/final_job_status.go b/notifications/final_job_status.go
index 4af70c284..cec702b04 100644
--- a/notifications/final_job_status.go
+++ b/notifications/final_job_status.go
@@ -8,6 +8,7 @@ import (
 	"github.com/textileio/powergate/v2/ffs"
 )
 
+// FinalJobStatus - uses for notification about final job status
 type FinalJobStatus struct {
 	JobId      ffs.JobID
 	JobStatus  ffs.JobStatus
@@ -15,18 +16,22 @@ type FinalJobStatus struct {
 	DealErrors []ffs.DealError
 }
 
+// JobID - expose job id
 func (f FinalJobStatus) JobID() ffs.JobID {
 	return f.JobId
 }
 
+// FinalUpdates - checks if it's final job update
 func (f FinalJobStatus) FinalUpdates() bool {
 	return true
 }
 
+// MatchAlert - matches for alerts
 func (f FinalJobStatus) MatchAlert(alert *ffs.WebhookAlert) bool {
 	return false
 }
 
+// MatchEvent - matches for events
 func (f FinalJobStatus) MatchEvent(event string) bool {
 	switch event {
 	case AllEvents, AllStorageDealEvents:
@@ -59,6 +64,7 @@ type dealError struct {
 	Message     string `json:"error"`
 }
 
+// Payload - forms notification payload for final job update
 func (f FinalJobStatus) Payload() (io.Reader, error) {
 	var errMessage string
 	if f.JobError != nil {
diff --git a/notifications/job_updates.go b/notifications/job_updates.go
index cadebb757..f8b8180bb 100644
--- a/notifications/job_updates.go
+++ b/notifications/job_updates.go
@@ -6,15 +6,22 @@ import (
 	"github.com/textileio/powergate/v2/ffs"
 )
 
+// JobUpdates - provides interface for job notifications
 type JobUpdates interface {
+	// JobID - expose job id
 	JobID() ffs.JobID
+	// FinalUpdates - indicates if it's a final job update
 	FinalUpdates() bool
 
 	Notification
 }
 
+// Notification - common interface for notifications (events and alerts)
 type Notification interface {
+	// Payload - forms notification payload
 	Payload() (io.Reader, error)
+	// MatchEvent - matches notification by events
 	MatchEvent(event string) bool
+	// MatchAlert - matches notification by alerts
 	MatchAlert(alert *ffs.WebhookAlert) bool
 }
diff --git a/notifications/notifier.go b/notifications/notifier.go
index ddd775e59..debd94b35 100644
--- a/notifications/notifier.go
+++ b/notifications/notifier.go
@@ -13,9 +13,13 @@ var (
 	log = logging.Logger("notifier")
 )
 
+// Notifier - notifies external systems about job updates by subscribing to events and alerts
 type Notifier interface {
+	// RegisterJob - stores notification configuration for each job
 	RegisterJob(jobId ffs.JobID, configs []*ffs.NotificationConfig)
+	// NotifyJobUpdates - notifies about job updates according to notification configurations
 	NotifyJobUpdates(job JobUpdates)
+	// Alert - triggers check and notifies in case of alert
 	Alert(alert Notification, configs []*ffs.NotificationConfig)
 }
 
diff --git a/notifications/retrieval_job.go b/notifications/retrieval_job.go
index 0f9dda600..bb418b5ba 100644
--- a/notifications/retrieval_job.go
+++ b/notifications/retrieval_job.go
@@ -9,6 +9,7 @@ import (
 	"github.com/textileio/powergate/v2/ffs"
 )
 
+// RetrievalJobUpdates - uses for retrieval job events
 type RetrievalJobUpdates struct {
 	Job  ffs.RetrievalJob
 	Info ffs.RetrievalInfo
diff --git a/notifications/storage_job.go b/notifications/storage_job.go
index 3982d5ad2..a62c97dcd 100644
--- a/notifications/storage_job.go
+++ b/notifications/storage_job.go
@@ -10,6 +10,7 @@ import (
 	"github.com/textileio/powergate/v2/ffs"
 )
 
+// StorageJobUpdates - uses for retrieval job events
 type StorageJobUpdates struct {
 	Job  ffs.StorageJob
 	Info deals.StorageDealInfo
diff --git a/notifications/store.go b/notifications/store.go
index 78e30d322..8ad2ab928 100644
--- a/notifications/store.go
+++ b/notifications/store.go
@@ -6,6 +6,8 @@ import (
 	"github.com/textileio/powergate/v2/ffs"
 )
 
+// in-memory store for job's notification configs
+
 type configStore struct {
 	sync.RWMutex
 

From e1e5fd45c435d256a0c260779d196fba92951579 Mon Sep 17 00:00:00 2001
From: Volodymyr Manilo <wmanilo@gmail.com>
Date: Wed, 20 Jul 2022 22:39:01 +0200
Subject: [PATCH 7/7] added alert for deals pending expiration

---
 ffs/filcold/filcold.go                 |  10 +++
 ffs/interfaces.go                      |   3 +
 ffs/manager/manager.go                 |   4 +
 ffs/scheduler/scheduler_storage.go     |  40 +++++++--
 notifications/alerts.go                | 107 +------------------------
 notifications/deal_expiration_alert.go |  92 +++++++++++++++++++++
 notifications/disk_space_alert.go      | 105 ++++++++++++++++++++++++
 notifications/retrieval_job.go         |   5 +-
 tests/txmapds.go                       |   5 +-
 9 files changed, 254 insertions(+), 117 deletions(-)
 create mode 100644 notifications/deal_expiration_alert.go
 create mode 100644 notifications/disk_space_alert.go

diff --git a/ffs/filcold/filcold.go b/ffs/filcold/filcold.go
index 512749bbe..ade56fd0b 100644
--- a/ffs/filcold/filcold.go
+++ b/ffs/filcold/filcold.go
@@ -223,6 +223,16 @@ func (fc *FilCold) GetDealInfo(ctx context.Context, dealID uint64) (api.MarketDe
 	return di, nil
 }
 
+// GetCurrentEpoch returns on-chain current epoch
+func (fc *FilCold) GetCurrentEpoch(ctx context.Context) (uint64, error) {
+	currentEpoch, err := fc.chain.GetHeight(ctx)
+	if err != nil {
+		return 0, fmt.Errorf("get current filecoin epoch: %s", err)
+	}
+
+	return currentEpoch, nil
+}
+
 // EnsureRenewals analyzes a FilInfo state for a Cid and executes renewals considering the FilConfig desired configuration.
 // Deal status updates are sent on the provided dealUpdates channel.
 // The caller should close the channel once all calls to EnsureRenewals have returned.
diff --git a/ffs/interfaces.go b/ffs/interfaces.go
index 8dbeb5928..986300bd7 100644
--- a/ffs/interfaces.go
+++ b/ffs/interfaces.go
@@ -122,6 +122,9 @@ type ColdStorage interface {
 	// If a deal ID doesn't exist, the deal isn't active anymore, or was
 	// slashed, then ErrOnChainDealNotFound is returned.
 	GetDealInfo(context.Context, uint64) (api.MarketDeal, error)
+
+	// GetCurrentEpoch returns information about current epoch on-chain.
+	GetCurrentEpoch(context.Context) (uint64, error)
 }
 
 // MinerSelector returns miner addresses and ask storage information using a
diff --git a/ffs/manager/manager.go b/ffs/manager/manager.go
index e31b182e9..39b133daa 100644
--- a/ffs/manager/manager.go
+++ b/ffs/manager/manager.go
@@ -77,6 +77,10 @@ var (
 							Type:      "datacap",
 							Threshold: "500 GB",
 						},
+						{
+							Type:      "storage-deal-pending-expiration",
+							Threshold: "1000h",
+						},
 					},
 				},
 			},
diff --git a/ffs/scheduler/scheduler_storage.go b/ffs/scheduler/scheduler_storage.go
index 8a13380db..9a821b636 100644
--- a/ffs/scheduler/scheduler_storage.go
+++ b/ffs/scheduler/scheduler_storage.go
@@ -246,7 +246,7 @@ func (s *Scheduler) executeStorage(ctx context.Context, a astore.StorageAction,
 	}
 
 	s.l.Log(ctx, "Executing Cold-Storage configuration...")
-	cold, errors, err := s.executeColdStorage(ctx, ci, a.Cfg.Cold, dealUpdates)
+	cold, errors, err := s.executeColdStorage(ctx, ci, a.Cfg, dealUpdates)
 	if err != nil {
 		s.l.Log(ctx, "Cold-Storage execution failed.")
 		return ffs.StorageInfo{}, errors, fmt.Errorf("executing cold-storage config: %s", err)
@@ -397,8 +397,8 @@ func (s *Scheduler) getRefreshedColdInfo(ctx context.Context, curr ffs.ColdInfo)
 	return curr, nil
 }
 
-func (s *Scheduler) executeColdStorage(ctx context.Context, curr ffs.StorageInfo, cfg ffs.ColdConfig, dealUpdates chan deals.StorageDealInfo) (ffs.ColdInfo, []ffs.DealError, error) {
-	if !cfg.Enabled {
+func (s *Scheduler) executeColdStorage(ctx context.Context, curr ffs.StorageInfo, cfg ffs.StorageConfig, dealUpdates chan deals.StorageDealInfo) (ffs.ColdInfo, []ffs.DealError, error) {
+	if !cfg.Cold.Enabled {
 		s.l.Log(ctx, "Cold-Storage was disabled, Filecoin deals will eventually expire.")
 		return curr.Cold, nil, nil
 	}
@@ -426,12 +426,36 @@ func (s *Scheduler) executeColdStorage(ctx context.Context, curr ffs.StorageInfo
 		}
 	}
 
+	currentEpoch, err := s.cs.GetCurrentEpoch(ctx)
+	if err != nil {
+		log.Error(err)
+	} else {
+		for _, deal := range curr.Cold.Filecoin.Proposals {
+			// no need to alert for renewed deals
+			if deal.Renewed {
+				continue
+			}
+
+			s.notifier.Alert(
+				notifications.DealExpirationAlert{
+					JobID:        curr.JobID,
+					DealID:       deal.DealID,
+					PieceCid:     deal.PieceCid,
+					Miner:        deal.Miner,
+					ExpiryEpoch:  deal.StartEpoch + uint64(deal.Duration),
+					CurrentEpoch: currentEpoch,
+				},
+				cfg.Notifications,
+			)
+		}
+	}
+
 	// 2. If this Storage Config is renewable, then let's check if any of the existing deals
 	// should be renewed, and do it.
-	if cfg.Filecoin.Renew.Enabled {
+	if cfg.Cold.Filecoin.Renew.Enabled {
 		if curr.Hot.Enabled {
 			s.l.Log(ctx, "Checking deal renewals...")
-			newFilInfo, errors, err := s.cs.EnsureRenewals(ctx, curr.Cid, curr.Cold.Filecoin, cfg.Filecoin, s.dealFinalityTimeout, dealUpdates)
+			newFilInfo, errors, err := s.cs.EnsureRenewals(ctx, curr.Cid, curr.Cold.Filecoin, cfg.Cold.Filecoin, s.dealFinalityTimeout, dealUpdates)
 			if err != nil {
 				s.l.Log(ctx, "Deal renewal process couldn't be executed: %s", err)
 			} else {
@@ -476,18 +500,18 @@ func (s *Scheduler) executeColdStorage(ctx context.Context, curr ffs.StorageInfo
 
 	// Do we need to do some work?
 	if s.sr2RepFactor != nil {
-		cfg.Filecoin.RepFactor, err = s.sr2RepFactor()
+		cfg.Cold.Filecoin.RepFactor, err = s.sr2RepFactor()
 		if err != nil {
 			return ffs.ColdInfo{}, nil, fmt.Errorf("getting SR2 replication factor: %s", err)
 		}
 	}
-	if cfg.Filecoin.RepFactor-len(curr.Cold.Filecoin.Proposals) <= 0 {
+	if cfg.Cold.Filecoin.RepFactor-len(curr.Cold.Filecoin.Proposals) <= 0 {
 		s.l.Log(ctx, "The current replication factor is equal or higher than desired, avoiding making new deals.")
 		return curr.Cold, nil, nil
 	}
 
 	// The answer is yes, calculate how many extra deals we need and create them.
-	deltaFilConfig := createDeltaFilConfig(cfg, curr.Cold.Filecoin)
+	deltaFilConfig := createDeltaFilConfig(cfg.Cold, curr.Cold.Filecoin)
 	s.l.Log(ctx, "Current replication factor is lower than desired, making %d new deals...", deltaFilConfig.RepFactor)
 	startedProposals, rejectedProposals, size, err := s.cs.Store(ctx, curr.Cid, deltaFilConfig)
 	if err != nil {
diff --git a/notifications/alerts.go b/notifications/alerts.go
index 6d2e2f61e..652921038 100644
--- a/notifications/alerts.go
+++ b/notifications/alerts.go
@@ -1,111 +1,8 @@
 package notifications
 
-import (
-	"bytes"
-	"encoding/json"
-	"fmt"
-	"io"
-	"os"
-
-	"github.com/docker/go-units"
-	"github.com/textileio/powergate/v2/ffs"
-	"golang.org/x/sys/unix"
-)
-
 // Alert types
 
 const (
-	DiskSpaceCheck = "datacap"
+	DiskSpaceCheck      = "datacap"
+	DealExpirationCheck = "storage-deal-pending-expiration"
 )
-
-// DiskSpaceAlert - uses for alert notification when available disk space goes below threshold
-type DiskSpaceAlert struct {
-	JobID ffs.JobID
-}
-
-type diskSpaceAlertNotification struct {
-	JobID              ffs.JobID `json:"jobId"`
-	AlertType          string    `json:"alertType"`
-	AvailableDiskSpace string    `json:"availableDiskSpace"`
-	Error              string    `json:"error"`
-}
-
-// Payload - forms notification payload
-func (d DiskSpaceAlert) Payload() (io.Reader, error) {
-	availableDiskSpace, err := getAvailableDiskSpace()
-	if err != nil {
-		err = fmt.Errorf("failed to get available disk space: %w", err)
-		log.Error(err)
-		return nil, err
-	}
-
-	obj := &diskSpaceAlertNotification{
-		JobID:              d.JobID,
-		AlertType:          DiskSpaceCheck,
-		AvailableDiskSpace: units.BytesSize(float64(availableDiskSpace)),
-		Error:              "available disk space below threshold",
-	}
-
-	data, err := json.Marshal(obj)
-	if err != nil {
-		return nil, err
-	}
-
-	return bytes.NewBuffer(data), nil
-}
-
-// MatchEvent - checks for matching with events
-func (d DiskSpaceAlert) MatchEvent(event string) bool {
-	return false
-}
-
-// MatchAlert - checks for matching with alerts
-func (d DiskSpaceAlert) MatchAlert(alert *ffs.WebhookAlert) bool {
-	if alert == nil {
-		return false
-	}
-
-	if alert.Type != DiskSpaceCheck {
-		return false
-	}
-
-	threshold, err := parseDiskSpaceThresholdToBytes(alert.Threshold)
-	if err != nil {
-		log.Errorf("failed to parse alert disk space threshold: %s", err)
-		return false
-	}
-
-	availableDiskSpace, err := getAvailableDiskSpace()
-	if err != nil {
-		log.Errorf("failed to get available disk space: %s", err)
-		return false
-	}
-
-	return availableDiskSpace < threshold
-}
-
-func parseDiskSpaceThresholdToBytes(threshold string) (uint64, error) {
-	size, err := units.RAMInBytes(threshold)
-	if err != nil {
-		return 0, err
-	}
-
-	return uint64(size), nil
-}
-
-// getAvailableDiskSpace - provides available disk space in bytes
-func getAvailableDiskSpace() (uint64, error) {
-	var stat unix.Statfs_t
-
-	wd, err := os.Getwd()
-	if err != nil {
-		return 0, err
-	}
-
-	if err := unix.Statfs(wd, &stat); err != nil {
-		return 0, err
-	}
-
-	// available blocks * size per block
-	return stat.Bavail * uint64(stat.Bsize), nil
-}
diff --git a/notifications/deal_expiration_alert.go b/notifications/deal_expiration_alert.go
new file mode 100644
index 000000000..484daa6d0
--- /dev/null
+++ b/notifications/deal_expiration_alert.go
@@ -0,0 +1,92 @@
+package notifications
+
+import (
+	"bytes"
+	"encoding/json"
+	"io"
+	"time"
+
+	"github.com/ipfs/go-cid"
+	"github.com/textileio/powergate/v2/ffs"
+	"github.com/textileio/powergate/v2/util"
+)
+
+// DealExpirationAlert - uses for alert notification when deal close to expiration and goes below threshold
+type DealExpirationAlert struct {
+	JobID        ffs.JobID
+	DealID       uint64
+	PieceCid     cid.Cid
+	Miner        string
+	ExpiryEpoch  uint64
+	CurrentEpoch uint64
+}
+
+type dealExpirationAlertNotification struct {
+	AlertType           string    `json:"alertType"`
+	JobID               ffs.JobID `json:"jobId"`
+	DealID              uint64    `json:"dealId"`
+	PieceCid            string    `json:"pieceCid"`
+	Miner               string    `json:"miner"`
+	EpochTillExpiration uint64    `json:"epochTillExpiration"`
+	Error               string    `json:"error"`
+}
+
+// Payload - forms notification payload
+func (d DealExpirationAlert) Payload() (io.Reader, error) {
+	var epochTillExpiration uint64
+	msg := "deal already expired"
+
+	if d.ExpiryEpoch > d.CurrentEpoch {
+		msg = "deal close to expiration"
+		epochTillExpiration = d.ExpiryEpoch - d.CurrentEpoch
+	}
+
+	obj := &dealExpirationAlertNotification{
+		AlertType:           DealExpirationCheck,
+		JobID:               d.JobID,
+		DealID:              d.DealID,
+		PieceCid:            d.PieceCid.String(),
+		Miner:               d.Miner,
+		EpochTillExpiration: epochTillExpiration,
+		Error:               msg,
+	}
+
+	data, err := json.Marshal(obj)
+	if err != nil {
+		return nil, err
+	}
+
+	return bytes.NewBuffer(data), nil
+}
+
+// MatchEvent - checks for matching with events
+func (d DealExpirationAlert) MatchEvent(event string) bool {
+	return false
+}
+
+// MatchAlert - checks for matching with alerts
+func (d DealExpirationAlert) MatchAlert(alert *ffs.WebhookAlert) bool {
+	if alert == nil {
+		return false
+	}
+
+	if alert.Type != DealExpirationCheck {
+		return false
+	}
+
+	threshold, err := time.ParseDuration(alert.Threshold)
+	if err != nil {
+		log.Errorf("failed to parse deal expiration check threshold: %s", err)
+		return false
+	}
+
+	if d.ExpiryEpoch <= d.CurrentEpoch {
+		// already expired
+		return true
+	}
+
+	epochTillExpiration := d.ExpiryEpoch - d.CurrentEpoch
+	epochThreshold := uint64(threshold.Seconds() / util.EpochDurationSeconds)
+
+	return epochTillExpiration < epochThreshold
+}
diff --git a/notifications/disk_space_alert.go b/notifications/disk_space_alert.go
new file mode 100644
index 000000000..3592c0ac6
--- /dev/null
+++ b/notifications/disk_space_alert.go
@@ -0,0 +1,105 @@
+package notifications
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"os"
+
+	"github.com/docker/go-units"
+	"github.com/textileio/powergate/v2/ffs"
+	"golang.org/x/sys/unix"
+)
+
+// DiskSpaceAlert - uses for alert notification when available disk space goes below threshold
+type DiskSpaceAlert struct {
+	JobID ffs.JobID
+}
+
+type diskSpaceAlertNotification struct {
+	JobID              ffs.JobID `json:"jobId"`
+	AlertType          string    `json:"alertType"`
+	AvailableDiskSpace string    `json:"availableDiskSpace"`
+	Error              string    `json:"error"`
+}
+
+// Payload - forms notification payload
+func (d DiskSpaceAlert) Payload() (io.Reader, error) {
+	availableDiskSpace, err := getAvailableDiskSpace()
+	if err != nil {
+		err = fmt.Errorf("failed to get available disk space: %w", err)
+		log.Error(err)
+		return nil, err
+	}
+
+	obj := &diskSpaceAlertNotification{
+		JobID:              d.JobID,
+		AlertType:          DiskSpaceCheck,
+		AvailableDiskSpace: units.BytesSize(float64(availableDiskSpace)),
+		Error:              "available disk space below threshold",
+	}
+
+	data, err := json.Marshal(obj)
+	if err != nil {
+		return nil, err
+	}
+
+	return bytes.NewBuffer(data), nil
+}
+
+// MatchEvent - checks for matching with events
+func (d DiskSpaceAlert) MatchEvent(event string) bool {
+	return false
+}
+
+// MatchAlert - checks for matching with alerts
+func (d DiskSpaceAlert) MatchAlert(alert *ffs.WebhookAlert) bool {
+	if alert == nil {
+		return false
+	}
+
+	if alert.Type != DiskSpaceCheck {
+		return false
+	}
+
+	threshold, err := parseDiskSpaceThresholdToBytes(alert.Threshold)
+	if err != nil {
+		log.Errorf("failed to parse alert disk space threshold: %s", err)
+		return false
+	}
+
+	availableDiskSpace, err := getAvailableDiskSpace()
+	if err != nil {
+		log.Errorf("failed to get available disk space: %s", err)
+		return false
+	}
+
+	return availableDiskSpace < threshold
+}
+
+func parseDiskSpaceThresholdToBytes(threshold string) (uint64, error) {
+	size, err := units.RAMInBytes(threshold)
+	if err != nil {
+		return 0, err
+	}
+
+	return uint64(size), nil
+}
+
+// getAvailableDiskSpace - provides available disk space in bytes
+func getAvailableDiskSpace() (uint64, error) {
+	var stat unix.Statfs_t
+
+	wd, err := os.Getwd()
+	if err != nil {
+		return 0, err
+	}
+
+	if err := unix.Statfs(wd, &stat); err != nil {
+		return 0, err
+	}
+
+	// available blocks * size per block
+	return stat.Bavail * uint64(stat.Bsize), nil
+}
diff --git a/notifications/retrieval_job.go b/notifications/retrieval_job.go
index bb418b5ba..2a031cb59 100644
--- a/notifications/retrieval_job.go
+++ b/notifications/retrieval_job.go
@@ -31,8 +31,9 @@ type retrievalJobNotification struct {
 	TotalPaid   uint64          `json:"totalPaid"`
 	Miner       string          `json:"miner"`
 	Size        int64           `json:"size"`
-	CreatedAt   string          `json:"createdAt"`
-	ErrCause    string          `json:"error,omitempty"`
+	// CreatedAt timestamp in RFC3339 format
+	CreatedAt string `json:"createdAt"`
+	ErrCause  string `json:"error,omitempty"`
 }
 
 func (r RetrievalJobUpdates) Payload() (io.Reader, error) {
diff --git a/tests/txmapds.go b/tests/txmapds.go
index ed82d3916..150cfdb23 100644
--- a/tests/txmapds.go
+++ b/tests/txmapds.go
@@ -181,5 +181,6 @@ func NewMockNotifier() *mockNotifier {
 	return &mockNotifier{}
 }
 
-func (n *mockNotifier) RegisterJob(jobId ffs.JobID, configs []*ffs.NotificationConfig) {}
-func (n *mockNotifier) NotifyJobUpdates(job notifications.JobUpdates)                  {}
+func (n *mockNotifier) RegisterJob(jobId ffs.JobID, configs []*ffs.NotificationConfig)            {}
+func (n *mockNotifier) NotifyJobUpdates(job notifications.JobUpdates)                             {}
+func (n *mockNotifier) Alert(alert notifications.Notification, configs []*ffs.NotificationConfig) {}