diff --git a/api-spec/openapi/swagger/ark/v1/service.swagger.json b/api-spec/openapi/swagger/ark/v1/service.swagger.json index 775531e..1e3bc6c 100644 --- a/api-spec/openapi/swagger/ark/v1/service.swagger.json +++ b/api-spec/openapi/swagger/ark/v1/service.swagger.json @@ -16,6 +16,38 @@ "application/json" ], "paths": { + "/v1/boarding": { + "post": { + "operationId": "ArkService_GetBoardingAddress", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1GetBoardingAddressResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1GetBoardingAddressRequest" + } + } + ], + "tags": [ + "ArkService" + ] + } + }, "/v1/events": { "get": { "operationId": "ArkService_GetEventStream", @@ -69,38 +101,6 @@ ] } }, - "/v1/onboard": { - "post": { - "operationId": "ArkService_Onboard", - "responses": { - "200": { - "description": "A successful response.", - "schema": { - "$ref": "#/definitions/v1OnboardResponse" - } - }, - "default": { - "description": "An unexpected error response.", - "schema": { - "$ref": "#/definitions/rpcStatus" - } - } - }, - "parameters": [ - { - "name": "body", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/v1OnboardRequest" - } - } - ], - "tags": [ - "ArkService" - ] - } - }, "/v1/payment": { "post": { "operationId": "ArkService_CreatePayment", @@ -475,6 +475,21 @@ } } }, + "v1BoardingInput": { + "type": "object", + "properties": { + "txid": { + "type": "string" + }, + "vout": { + "type": "integer", + "format": "int64" + }, + "descriptor": { + "type": "string" + } + } + }, "v1ClaimPaymentRequest": { "type": "object", "properties": { @@ -555,12 +570,35 @@ "type": "string" }, "description": "Forfeit txs signed by the user." + }, + "signedRoundTx": { + "type": "string", + "description": "If payment has boarding input, the user must sign the associated inputs." } } }, "v1FinalizePaymentResponse": { "type": "object" }, + "v1GetBoardingAddressRequest": { + "type": "object", + "properties": { + "pubkey": { + "type": "string" + } + } + }, + "v1GetBoardingAddressResponse": { + "type": "object", + "properties": { + "address": { + "type": "string" + }, + "descriptor": { + "type": "string" + } + } + }, "v1GetEventStreamResponse": { "type": "object", "properties": { @@ -605,6 +643,9 @@ "minRelayFee": { "type": "string", "format": "int64" + }, + "boardingDescriptorTemplate": { + "type": "string" } } }, @@ -627,12 +668,11 @@ "v1Input": { "type": "object", "properties": { - "txid": { - "type": "string" + "vtxoInput": { + "$ref": "#/definitions/v1VtxoInput" }, - "vout": { - "type": "integer", - "format": "int64" + "boardingInput": { + "$ref": "#/definitions/v1BoardingInput" } } }, @@ -669,23 +709,6 @@ } } }, - "v1OnboardRequest": { - "type": "object", - "properties": { - "boardingTx": { - "type": "string" - }, - "congestionTree": { - "$ref": "#/definitions/v1Tree" - }, - "userPubkey": { - "type": "string" - } - } - }, - "v1OnboardResponse": { - "type": "object" - }, "v1Output": { "type": "object", "properties": { @@ -972,6 +995,18 @@ "$ref": "#/definitions/v1PendingPayment" } } + }, + "v1VtxoInput": { + "type": "object", + "properties": { + "txid": { + "type": "string" + }, + "vout": { + "type": "integer", + "format": "int64" + } + } } } } diff --git a/api-spec/protobuf/ark/v1/service.proto b/api-spec/protobuf/ark/v1/service.proto index 65a926a..4a1a631 100755 --- a/api-spec/protobuf/ark/v1/service.proto +++ b/api-spec/protobuf/ark/v1/service.proto @@ -65,12 +65,12 @@ service ArkService { get: "/v1/info" }; } - rpc Onboard(OnboardRequest) returns (OnboardResponse) { + rpc GetBoardingAddress(GetBoardingAddressRequest) returns (GetBoardingAddressResponse) { option (google.api.http) = { - post: "/v1/onboard" + post: "/v1/boarding" body: "*" }; - } + }; rpc CreatePayment(CreatePaymentRequest) returns (CreatePaymentResponse) { option (google.api.http) = { post: "/v1/payment" @@ -100,6 +100,15 @@ message CompletePaymentRequest { } message CompletePaymentResponse {} +message GetBoardingAddressRequest { + string pubkey = 1; +} + +message GetBoardingAddressResponse { + string address = 1; + string descriptor = 2; +} + message RegisterPaymentRequest { repeated Input inputs = 1; optional string ephemeral_pubkey = 2; @@ -120,6 +129,8 @@ message ClaimPaymentResponse {} message FinalizePaymentRequest { // Forfeit txs signed by the user. repeated string signed_forfeit_txs = 1; + // If payment has boarding input, the user must sign the associated inputs. + optional string signed_round_tx = 2; } message FinalizePaymentResponse {} @@ -177,14 +188,7 @@ message GetInfoResponse { int64 round_interval = 4; string network = 5; int64 min_relay_fee = 6; -} - -message OnboardRequest { - string boarding_tx = 1; - Tree congestion_tree = 2; - string user_pubkey = 3; -} -message OnboardResponse { + string boarding_descriptor_template = 7; } // EVENT TYPES @@ -239,11 +243,24 @@ message Round { RoundStage stage = 8; } -message Input { +message VtxoInput { string txid = 1; uint32 vout = 2; } +message BoardingInput { + string txid = 1; + uint32 vout = 2; + string descriptor = 3; +} + +message Input { + oneof input { + VtxoInput vtxo_input = 1; + BoardingInput boarding_input = 2; + } +} + message Output { // Either the offchain or onchain address. string address = 1; diff --git a/api-spec/protobuf/gen/ark/v1/service.pb.go b/api-spec/protobuf/gen/ark/v1/service.pb.go index de8e03d..da94220 100644 --- a/api-spec/protobuf/gen/ark/v1/service.pb.go +++ b/api-spec/protobuf/gen/ark/v1/service.pb.go @@ -279,6 +279,108 @@ func (*CompletePaymentResponse) Descriptor() ([]byte, []int) { return file_ark_v1_service_proto_rawDescGZIP(), []int{3} } +type GetBoardingAddressRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Pubkey string `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` +} + +func (x *GetBoardingAddressRequest) Reset() { + *x = GetBoardingAddressRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_ark_v1_service_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetBoardingAddressRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBoardingAddressRequest) ProtoMessage() {} + +func (x *GetBoardingAddressRequest) ProtoReflect() protoreflect.Message { + mi := &file_ark_v1_service_proto_msgTypes[4] + 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 GetBoardingAddressRequest.ProtoReflect.Descriptor instead. +func (*GetBoardingAddressRequest) Descriptor() ([]byte, []int) { + return file_ark_v1_service_proto_rawDescGZIP(), []int{4} +} + +func (x *GetBoardingAddressRequest) GetPubkey() string { + if x != nil { + return x.Pubkey + } + return "" +} + +type GetBoardingAddressResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Descriptor_ string `protobuf:"bytes,2,opt,name=descriptor,proto3" json:"descriptor,omitempty"` +} + +func (x *GetBoardingAddressResponse) Reset() { + *x = GetBoardingAddressResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_ark_v1_service_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetBoardingAddressResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBoardingAddressResponse) ProtoMessage() {} + +func (x *GetBoardingAddressResponse) ProtoReflect() protoreflect.Message { + mi := &file_ark_v1_service_proto_msgTypes[5] + 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 GetBoardingAddressResponse.ProtoReflect.Descriptor instead. +func (*GetBoardingAddressResponse) Descriptor() ([]byte, []int) { + return file_ark_v1_service_proto_rawDescGZIP(), []int{5} +} + +func (x *GetBoardingAddressResponse) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *GetBoardingAddressResponse) GetDescriptor_() string { + if x != nil { + return x.Descriptor_ + } + return "" +} + type RegisterPaymentRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -291,7 +393,7 @@ type RegisterPaymentRequest struct { func (x *RegisterPaymentRequest) Reset() { *x = RegisterPaymentRequest{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[4] + mi := &file_ark_v1_service_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -304,7 +406,7 @@ func (x *RegisterPaymentRequest) String() string { func (*RegisterPaymentRequest) ProtoMessage() {} func (x *RegisterPaymentRequest) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[4] + mi := &file_ark_v1_service_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -317,7 +419,7 @@ func (x *RegisterPaymentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RegisterPaymentRequest.ProtoReflect.Descriptor instead. func (*RegisterPaymentRequest) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{4} + return file_ark_v1_service_proto_rawDescGZIP(), []int{6} } func (x *RegisterPaymentRequest) GetInputs() []*Input { @@ -346,7 +448,7 @@ type RegisterPaymentResponse struct { func (x *RegisterPaymentResponse) Reset() { *x = RegisterPaymentResponse{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[5] + mi := &file_ark_v1_service_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -359,7 +461,7 @@ func (x *RegisterPaymentResponse) String() string { func (*RegisterPaymentResponse) ProtoMessage() {} func (x *RegisterPaymentResponse) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[5] + mi := &file_ark_v1_service_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -372,7 +474,7 @@ func (x *RegisterPaymentResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RegisterPaymentResponse.ProtoReflect.Descriptor instead. func (*RegisterPaymentResponse) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{5} + return file_ark_v1_service_proto_rawDescGZIP(), []int{7} } func (x *RegisterPaymentResponse) GetId() string { @@ -396,7 +498,7 @@ type ClaimPaymentRequest struct { func (x *ClaimPaymentRequest) Reset() { *x = ClaimPaymentRequest{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[6] + mi := &file_ark_v1_service_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -409,7 +511,7 @@ func (x *ClaimPaymentRequest) String() string { func (*ClaimPaymentRequest) ProtoMessage() {} func (x *ClaimPaymentRequest) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[6] + mi := &file_ark_v1_service_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -422,7 +524,7 @@ func (x *ClaimPaymentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ClaimPaymentRequest.ProtoReflect.Descriptor instead. func (*ClaimPaymentRequest) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{6} + return file_ark_v1_service_proto_rawDescGZIP(), []int{8} } func (x *ClaimPaymentRequest) GetId() string { @@ -448,7 +550,7 @@ type ClaimPaymentResponse struct { func (x *ClaimPaymentResponse) Reset() { *x = ClaimPaymentResponse{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[7] + mi := &file_ark_v1_service_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -461,7 +563,7 @@ func (x *ClaimPaymentResponse) String() string { func (*ClaimPaymentResponse) ProtoMessage() {} func (x *ClaimPaymentResponse) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[7] + mi := &file_ark_v1_service_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -474,7 +576,7 @@ func (x *ClaimPaymentResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ClaimPaymentResponse.ProtoReflect.Descriptor instead. func (*ClaimPaymentResponse) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{7} + return file_ark_v1_service_proto_rawDescGZIP(), []int{9} } type FinalizePaymentRequest struct { @@ -484,12 +586,14 @@ type FinalizePaymentRequest struct { // Forfeit txs signed by the user. SignedForfeitTxs []string `protobuf:"bytes,1,rep,name=signed_forfeit_txs,json=signedForfeitTxs,proto3" json:"signed_forfeit_txs,omitempty"` + // If payment has boarding input, the user must sign the associated inputs. + SignedRoundTx *string `protobuf:"bytes,2,opt,name=signed_round_tx,json=signedRoundTx,proto3,oneof" json:"signed_round_tx,omitempty"` } func (x *FinalizePaymentRequest) Reset() { *x = FinalizePaymentRequest{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[8] + mi := &file_ark_v1_service_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -502,7 +606,7 @@ func (x *FinalizePaymentRequest) String() string { func (*FinalizePaymentRequest) ProtoMessage() {} func (x *FinalizePaymentRequest) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[8] + mi := &file_ark_v1_service_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -515,7 +619,7 @@ func (x *FinalizePaymentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FinalizePaymentRequest.ProtoReflect.Descriptor instead. func (*FinalizePaymentRequest) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{8} + return file_ark_v1_service_proto_rawDescGZIP(), []int{10} } func (x *FinalizePaymentRequest) GetSignedForfeitTxs() []string { @@ -525,6 +629,13 @@ func (x *FinalizePaymentRequest) GetSignedForfeitTxs() []string { return nil } +func (x *FinalizePaymentRequest) GetSignedRoundTx() string { + if x != nil && x.SignedRoundTx != nil { + return *x.SignedRoundTx + } + return "" +} + type FinalizePaymentResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -534,7 +645,7 @@ type FinalizePaymentResponse struct { func (x *FinalizePaymentResponse) Reset() { *x = FinalizePaymentResponse{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[9] + mi := &file_ark_v1_service_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -547,7 +658,7 @@ func (x *FinalizePaymentResponse) String() string { func (*FinalizePaymentResponse) ProtoMessage() {} func (x *FinalizePaymentResponse) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[9] + mi := &file_ark_v1_service_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -560,7 +671,7 @@ func (x *FinalizePaymentResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FinalizePaymentResponse.ProtoReflect.Descriptor instead. func (*FinalizePaymentResponse) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{9} + return file_ark_v1_service_proto_rawDescGZIP(), []int{11} } type GetRoundRequest struct { @@ -574,7 +685,7 @@ type GetRoundRequest struct { func (x *GetRoundRequest) Reset() { *x = GetRoundRequest{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[10] + mi := &file_ark_v1_service_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -587,7 +698,7 @@ func (x *GetRoundRequest) String() string { func (*GetRoundRequest) ProtoMessage() {} func (x *GetRoundRequest) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[10] + mi := &file_ark_v1_service_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -600,7 +711,7 @@ func (x *GetRoundRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRoundRequest.ProtoReflect.Descriptor instead. func (*GetRoundRequest) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{10} + return file_ark_v1_service_proto_rawDescGZIP(), []int{12} } func (x *GetRoundRequest) GetTxid() string { @@ -621,7 +732,7 @@ type GetRoundResponse struct { func (x *GetRoundResponse) Reset() { *x = GetRoundResponse{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[11] + mi := &file_ark_v1_service_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -634,7 +745,7 @@ func (x *GetRoundResponse) String() string { func (*GetRoundResponse) ProtoMessage() {} func (x *GetRoundResponse) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[11] + mi := &file_ark_v1_service_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -647,7 +758,7 @@ func (x *GetRoundResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRoundResponse.ProtoReflect.Descriptor instead. func (*GetRoundResponse) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{11} + return file_ark_v1_service_proto_rawDescGZIP(), []int{13} } func (x *GetRoundResponse) GetRound() *Round { @@ -668,7 +779,7 @@ type GetRoundByIdRequest struct { func (x *GetRoundByIdRequest) Reset() { *x = GetRoundByIdRequest{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[12] + mi := &file_ark_v1_service_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -681,7 +792,7 @@ func (x *GetRoundByIdRequest) String() string { func (*GetRoundByIdRequest) ProtoMessage() {} func (x *GetRoundByIdRequest) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[12] + mi := &file_ark_v1_service_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -694,7 +805,7 @@ func (x *GetRoundByIdRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRoundByIdRequest.ProtoReflect.Descriptor instead. func (*GetRoundByIdRequest) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{12} + return file_ark_v1_service_proto_rawDescGZIP(), []int{14} } func (x *GetRoundByIdRequest) GetId() string { @@ -715,7 +826,7 @@ type GetRoundByIdResponse struct { func (x *GetRoundByIdResponse) Reset() { *x = GetRoundByIdResponse{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[13] + mi := &file_ark_v1_service_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -728,7 +839,7 @@ func (x *GetRoundByIdResponse) String() string { func (*GetRoundByIdResponse) ProtoMessage() {} func (x *GetRoundByIdResponse) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[13] + mi := &file_ark_v1_service_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -741,7 +852,7 @@ func (x *GetRoundByIdResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRoundByIdResponse.ProtoReflect.Descriptor instead. func (*GetRoundByIdResponse) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{13} + return file_ark_v1_service_proto_rawDescGZIP(), []int{15} } func (x *GetRoundByIdResponse) GetRound() *Round { @@ -760,7 +871,7 @@ type GetEventStreamRequest struct { func (x *GetEventStreamRequest) Reset() { *x = GetEventStreamRequest{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[14] + mi := &file_ark_v1_service_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -773,7 +884,7 @@ func (x *GetEventStreamRequest) String() string { func (*GetEventStreamRequest) ProtoMessage() {} func (x *GetEventStreamRequest) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[14] + mi := &file_ark_v1_service_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -786,7 +897,7 @@ func (x *GetEventStreamRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetEventStreamRequest.ProtoReflect.Descriptor instead. func (*GetEventStreamRequest) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{14} + return file_ark_v1_service_proto_rawDescGZIP(), []int{16} } type GetEventStreamResponse struct { @@ -807,7 +918,7 @@ type GetEventStreamResponse struct { func (x *GetEventStreamResponse) Reset() { *x = GetEventStreamResponse{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[15] + mi := &file_ark_v1_service_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -820,7 +931,7 @@ func (x *GetEventStreamResponse) String() string { func (*GetEventStreamResponse) ProtoMessage() {} func (x *GetEventStreamResponse) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[15] + mi := &file_ark_v1_service_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -833,7 +944,7 @@ func (x *GetEventStreamResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetEventStreamResponse.ProtoReflect.Descriptor instead. func (*GetEventStreamResponse) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{15} + return file_ark_v1_service_proto_rawDescGZIP(), []int{17} } func (m *GetEventStreamResponse) GetEvent() isGetEventStreamResponse_Event { @@ -923,7 +1034,7 @@ type PingRequest struct { func (x *PingRequest) Reset() { *x = PingRequest{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[16] + mi := &file_ark_v1_service_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -936,7 +1047,7 @@ func (x *PingRequest) String() string { func (*PingRequest) ProtoMessage() {} func (x *PingRequest) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[16] + mi := &file_ark_v1_service_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -949,7 +1060,7 @@ func (x *PingRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PingRequest.ProtoReflect.Descriptor instead. func (*PingRequest) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{16} + return file_ark_v1_service_proto_rawDescGZIP(), []int{18} } func (x *PingRequest) GetPaymentId() string { @@ -977,7 +1088,7 @@ type PingResponse struct { func (x *PingResponse) Reset() { *x = PingResponse{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[17] + mi := &file_ark_v1_service_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -990,7 +1101,7 @@ func (x *PingResponse) String() string { func (*PingResponse) ProtoMessage() {} func (x *PingResponse) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[17] + mi := &file_ark_v1_service_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1003,7 +1114,7 @@ func (x *PingResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PingResponse.ProtoReflect.Descriptor instead. func (*PingResponse) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{17} + return file_ark_v1_service_proto_rawDescGZIP(), []int{19} } func (m *PingResponse) GetEvent() isPingResponse_Event { @@ -1093,7 +1204,7 @@ type ListVtxosRequest struct { func (x *ListVtxosRequest) Reset() { *x = ListVtxosRequest{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[18] + mi := &file_ark_v1_service_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1106,7 +1217,7 @@ func (x *ListVtxosRequest) String() string { func (*ListVtxosRequest) ProtoMessage() {} func (x *ListVtxosRequest) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[18] + mi := &file_ark_v1_service_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1119,7 +1230,7 @@ func (x *ListVtxosRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListVtxosRequest.ProtoReflect.Descriptor instead. func (*ListVtxosRequest) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{18} + return file_ark_v1_service_proto_rawDescGZIP(), []int{20} } func (x *ListVtxosRequest) GetAddress() string { @@ -1141,7 +1252,7 @@ type ListVtxosResponse struct { func (x *ListVtxosResponse) Reset() { *x = ListVtxosResponse{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[19] + mi := &file_ark_v1_service_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1154,7 +1265,7 @@ func (x *ListVtxosResponse) String() string { func (*ListVtxosResponse) ProtoMessage() {} func (x *ListVtxosResponse) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[19] + mi := &file_ark_v1_service_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1167,7 +1278,7 @@ func (x *ListVtxosResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListVtxosResponse.ProtoReflect.Descriptor instead. func (*ListVtxosResponse) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{19} + return file_ark_v1_service_proto_rawDescGZIP(), []int{21} } func (x *ListVtxosResponse) GetSpendableVtxos() []*Vtxo { @@ -1193,7 +1304,7 @@ type GetInfoRequest struct { func (x *GetInfoRequest) Reset() { *x = GetInfoRequest{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[20] + mi := &file_ark_v1_service_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1206,7 +1317,7 @@ func (x *GetInfoRequest) String() string { func (*GetInfoRequest) ProtoMessage() {} func (x *GetInfoRequest) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[20] + mi := &file_ark_v1_service_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1219,7 +1330,7 @@ func (x *GetInfoRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetInfoRequest.ProtoReflect.Descriptor instead. func (*GetInfoRequest) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{20} + return file_ark_v1_service_proto_rawDescGZIP(), []int{22} } type GetInfoResponse struct { @@ -1227,18 +1338,19 @@ type GetInfoResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Pubkey string `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` - RoundLifetime int64 `protobuf:"varint,2,opt,name=round_lifetime,json=roundLifetime,proto3" json:"round_lifetime,omitempty"` - UnilateralExitDelay int64 `protobuf:"varint,3,opt,name=unilateral_exit_delay,json=unilateralExitDelay,proto3" json:"unilateral_exit_delay,omitempty"` - RoundInterval int64 `protobuf:"varint,4,opt,name=round_interval,json=roundInterval,proto3" json:"round_interval,omitempty"` - Network string `protobuf:"bytes,5,opt,name=network,proto3" json:"network,omitempty"` - MinRelayFee int64 `protobuf:"varint,6,opt,name=min_relay_fee,json=minRelayFee,proto3" json:"min_relay_fee,omitempty"` + Pubkey string `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` + RoundLifetime int64 `protobuf:"varint,2,opt,name=round_lifetime,json=roundLifetime,proto3" json:"round_lifetime,omitempty"` + UnilateralExitDelay int64 `protobuf:"varint,3,opt,name=unilateral_exit_delay,json=unilateralExitDelay,proto3" json:"unilateral_exit_delay,omitempty"` + RoundInterval int64 `protobuf:"varint,4,opt,name=round_interval,json=roundInterval,proto3" json:"round_interval,omitempty"` + Network string `protobuf:"bytes,5,opt,name=network,proto3" json:"network,omitempty"` + MinRelayFee int64 `protobuf:"varint,6,opt,name=min_relay_fee,json=minRelayFee,proto3" json:"min_relay_fee,omitempty"` + BoardingDescriptorTemplate string `protobuf:"bytes,7,opt,name=boarding_descriptor_template,json=boardingDescriptorTemplate,proto3" json:"boarding_descriptor_template,omitempty"` } func (x *GetInfoResponse) Reset() { *x = GetInfoResponse{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[21] + mi := &file_ark_v1_service_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1251,7 +1363,7 @@ func (x *GetInfoResponse) String() string { func (*GetInfoResponse) ProtoMessage() {} func (x *GetInfoResponse) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[21] + mi := &file_ark_v1_service_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1264,7 +1376,7 @@ func (x *GetInfoResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetInfoResponse.ProtoReflect.Descriptor instead. func (*GetInfoResponse) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{21} + return file_ark_v1_service_proto_rawDescGZIP(), []int{23} } func (x *GetInfoResponse) GetPubkey() string { @@ -1309,107 +1421,13 @@ func (x *GetInfoResponse) GetMinRelayFee() int64 { return 0 } -type OnboardRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - BoardingTx string `protobuf:"bytes,1,opt,name=boarding_tx,json=boardingTx,proto3" json:"boarding_tx,omitempty"` - CongestionTree *Tree `protobuf:"bytes,2,opt,name=congestion_tree,json=congestionTree,proto3" json:"congestion_tree,omitempty"` - UserPubkey string `protobuf:"bytes,3,opt,name=user_pubkey,json=userPubkey,proto3" json:"user_pubkey,omitempty"` -} - -func (x *OnboardRequest) Reset() { - *x = OnboardRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[22] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *OnboardRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*OnboardRequest) ProtoMessage() {} - -func (x *OnboardRequest) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[22] - 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 OnboardRequest.ProtoReflect.Descriptor instead. -func (*OnboardRequest) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{22} -} - -func (x *OnboardRequest) GetBoardingTx() string { +func (x *GetInfoResponse) GetBoardingDescriptorTemplate() string { if x != nil { - return x.BoardingTx + return x.BoardingDescriptorTemplate } return "" } -func (x *OnboardRequest) GetCongestionTree() *Tree { - if x != nil { - return x.CongestionTree - } - return nil -} - -func (x *OnboardRequest) GetUserPubkey() string { - if x != nil { - return x.UserPubkey - } - return "" -} - -type OnboardResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *OnboardResponse) Reset() { - *x = OnboardResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[23] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *OnboardResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*OnboardResponse) ProtoMessage() {} - -func (x *OnboardResponse) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[23] - 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 OnboardResponse.ProtoReflect.Descriptor instead. -func (*OnboardResponse) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{23} -} - type RoundFinalizationEvent struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1820,7 +1838,7 @@ func (x *Round) GetStage() RoundStage { return RoundStage_ROUND_STAGE_UNSPECIFIED } -type Input struct { +type VtxoInput struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields @@ -1829,10 +1847,131 @@ type Input struct { Vout uint32 `protobuf:"varint,2,opt,name=vout,proto3" json:"vout,omitempty"` } +func (x *VtxoInput) Reset() { + *x = VtxoInput{} + if protoimpl.UnsafeEnabled { + mi := &file_ark_v1_service_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *VtxoInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VtxoInput) ProtoMessage() {} + +func (x *VtxoInput) ProtoReflect() protoreflect.Message { + mi := &file_ark_v1_service_proto_msgTypes[30] + 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 VtxoInput.ProtoReflect.Descriptor instead. +func (*VtxoInput) Descriptor() ([]byte, []int) { + return file_ark_v1_service_proto_rawDescGZIP(), []int{30} +} + +func (x *VtxoInput) GetTxid() string { + if x != nil { + return x.Txid + } + return "" +} + +func (x *VtxoInput) GetVout() uint32 { + if x != nil { + return x.Vout + } + return 0 +} + +type BoardingInput struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Txid string `protobuf:"bytes,1,opt,name=txid,proto3" json:"txid,omitempty"` + Vout uint32 `protobuf:"varint,2,opt,name=vout,proto3" json:"vout,omitempty"` + Descriptor_ string `protobuf:"bytes,3,opt,name=descriptor,proto3" json:"descriptor,omitempty"` +} + +func (x *BoardingInput) Reset() { + *x = BoardingInput{} + if protoimpl.UnsafeEnabled { + mi := &file_ark_v1_service_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BoardingInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BoardingInput) ProtoMessage() {} + +func (x *BoardingInput) ProtoReflect() protoreflect.Message { + mi := &file_ark_v1_service_proto_msgTypes[31] + 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 BoardingInput.ProtoReflect.Descriptor instead. +func (*BoardingInput) Descriptor() ([]byte, []int) { + return file_ark_v1_service_proto_rawDescGZIP(), []int{31} +} + +func (x *BoardingInput) GetTxid() string { + if x != nil { + return x.Txid + } + return "" +} + +func (x *BoardingInput) GetVout() uint32 { + if x != nil { + return x.Vout + } + return 0 +} + +func (x *BoardingInput) GetDescriptor_() string { + if x != nil { + return x.Descriptor_ + } + return "" +} + +type Input struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Input: + // + // *Input_VtxoInput + // *Input_BoardingInput + Input isInput_Input `protobuf_oneof:"input"` +} + func (x *Input) Reset() { *x = Input{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[30] + mi := &file_ark_v1_service_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1845,7 +1984,7 @@ func (x *Input) String() string { func (*Input) ProtoMessage() {} func (x *Input) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[30] + mi := &file_ark_v1_service_proto_msgTypes[32] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1858,23 +1997,46 @@ func (x *Input) ProtoReflect() protoreflect.Message { // Deprecated: Use Input.ProtoReflect.Descriptor instead. func (*Input) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{30} + return file_ark_v1_service_proto_rawDescGZIP(), []int{32} } -func (x *Input) GetTxid() string { - if x != nil { - return x.Txid +func (m *Input) GetInput() isInput_Input { + if m != nil { + return m.Input } - return "" + return nil } -func (x *Input) GetVout() uint32 { - if x != nil { - return x.Vout +func (x *Input) GetVtxoInput() *VtxoInput { + if x, ok := x.GetInput().(*Input_VtxoInput); ok { + return x.VtxoInput } - return 0 + return nil } +func (x *Input) GetBoardingInput() *BoardingInput { + if x, ok := x.GetInput().(*Input_BoardingInput); ok { + return x.BoardingInput + } + return nil +} + +type isInput_Input interface { + isInput_Input() +} + +type Input_VtxoInput struct { + VtxoInput *VtxoInput `protobuf:"bytes,1,opt,name=vtxo_input,json=vtxoInput,proto3,oneof"` +} + +type Input_BoardingInput struct { + BoardingInput *BoardingInput `protobuf:"bytes,2,opt,name=boarding_input,json=boardingInput,proto3,oneof"` +} + +func (*Input_VtxoInput) isInput_Input() {} + +func (*Input_BoardingInput) isInput_Input() {} + type Output struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1889,7 +2051,7 @@ type Output struct { func (x *Output) Reset() { *x = Output{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[31] + mi := &file_ark_v1_service_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1902,7 +2064,7 @@ func (x *Output) String() string { func (*Output) ProtoMessage() {} func (x *Output) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[31] + mi := &file_ark_v1_service_proto_msgTypes[33] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1915,7 +2077,7 @@ func (x *Output) ProtoReflect() protoreflect.Message { // Deprecated: Use Output.ProtoReflect.Descriptor instead. func (*Output) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{31} + return file_ark_v1_service_proto_rawDescGZIP(), []int{33} } func (x *Output) GetAddress() string { @@ -1943,7 +2105,7 @@ type Tree struct { func (x *Tree) Reset() { *x = Tree{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[32] + mi := &file_ark_v1_service_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1956,7 +2118,7 @@ func (x *Tree) String() string { func (*Tree) ProtoMessage() {} func (x *Tree) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[32] + mi := &file_ark_v1_service_proto_msgTypes[34] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1969,7 +2131,7 @@ func (x *Tree) ProtoReflect() protoreflect.Message { // Deprecated: Use Tree.ProtoReflect.Descriptor instead. func (*Tree) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{32} + return file_ark_v1_service_proto_rawDescGZIP(), []int{34} } func (x *Tree) GetLevels() []*TreeLevel { @@ -1990,7 +2152,7 @@ type TreeLevel struct { func (x *TreeLevel) Reset() { *x = TreeLevel{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[33] + mi := &file_ark_v1_service_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2003,7 +2165,7 @@ func (x *TreeLevel) String() string { func (*TreeLevel) ProtoMessage() {} func (x *TreeLevel) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[33] + mi := &file_ark_v1_service_proto_msgTypes[35] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2016,7 +2178,7 @@ func (x *TreeLevel) ProtoReflect() protoreflect.Message { // Deprecated: Use TreeLevel.ProtoReflect.Descriptor instead. func (*TreeLevel) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{33} + return file_ark_v1_service_proto_rawDescGZIP(), []int{35} } func (x *TreeLevel) GetNodes() []*Node { @@ -2039,7 +2201,7 @@ type Node struct { func (x *Node) Reset() { *x = Node{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[34] + mi := &file_ark_v1_service_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2052,7 +2214,7 @@ func (x *Node) String() string { func (*Node) ProtoMessage() {} func (x *Node) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[34] + mi := &file_ark_v1_service_proto_msgTypes[36] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2065,7 +2227,7 @@ func (x *Node) ProtoReflect() protoreflect.Message { // Deprecated: Use Node.ProtoReflect.Descriptor instead. func (*Node) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{34} + return file_ark_v1_service_proto_rawDescGZIP(), []int{36} } func (x *Node) GetTxid() string { @@ -2108,7 +2270,7 @@ type Vtxo struct { func (x *Vtxo) Reset() { *x = Vtxo{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[35] + mi := &file_ark_v1_service_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2121,7 +2283,7 @@ func (x *Vtxo) String() string { func (*Vtxo) ProtoMessage() {} func (x *Vtxo) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[35] + mi := &file_ark_v1_service_proto_msgTypes[37] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2134,7 +2296,7 @@ func (x *Vtxo) ProtoReflect() protoreflect.Message { // Deprecated: Use Vtxo.ProtoReflect.Descriptor instead. func (*Vtxo) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{35} + return file_ark_v1_service_proto_rawDescGZIP(), []int{37} } func (x *Vtxo) GetOutpoint() *Input { @@ -2212,7 +2374,7 @@ type PendingPayment struct { func (x *PendingPayment) Reset() { *x = PendingPayment{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[36] + mi := &file_ark_v1_service_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2225,7 +2387,7 @@ func (x *PendingPayment) String() string { func (*PendingPayment) ProtoMessage() {} func (x *PendingPayment) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[36] + mi := &file_ark_v1_service_proto_msgTypes[38] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2238,7 +2400,7 @@ func (x *PendingPayment) ProtoReflect() protoreflect.Message { // Deprecated: Use PendingPayment.ProtoReflect.Descriptor instead. func (*PendingPayment) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{36} + return file_ark_v1_service_proto_rawDescGZIP(), []int{38} } func (x *PendingPayment) GetRedeemTx() string { @@ -2268,7 +2430,7 @@ type SendTreeNoncesRequest struct { func (x *SendTreeNoncesRequest) Reset() { *x = SendTreeNoncesRequest{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[37] + mi := &file_ark_v1_service_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2281,7 +2443,7 @@ func (x *SendTreeNoncesRequest) String() string { func (*SendTreeNoncesRequest) ProtoMessage() {} func (x *SendTreeNoncesRequest) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[37] + mi := &file_ark_v1_service_proto_msgTypes[39] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2294,7 +2456,7 @@ func (x *SendTreeNoncesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SendTreeNoncesRequest.ProtoReflect.Descriptor instead. func (*SendTreeNoncesRequest) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{37} + return file_ark_v1_service_proto_rawDescGZIP(), []int{39} } func (x *SendTreeNoncesRequest) GetRoundId() string { @@ -2327,7 +2489,7 @@ type SendTreeNoncesResponse struct { func (x *SendTreeNoncesResponse) Reset() { *x = SendTreeNoncesResponse{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[38] + mi := &file_ark_v1_service_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2340,7 +2502,7 @@ func (x *SendTreeNoncesResponse) String() string { func (*SendTreeNoncesResponse) ProtoMessage() {} func (x *SendTreeNoncesResponse) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[38] + mi := &file_ark_v1_service_proto_msgTypes[40] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2353,7 +2515,7 @@ func (x *SendTreeNoncesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SendTreeNoncesResponse.ProtoReflect.Descriptor instead. func (*SendTreeNoncesResponse) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{38} + return file_ark_v1_service_proto_rawDescGZIP(), []int{40} } type SendTreeSignaturesRequest struct { @@ -2369,7 +2531,7 @@ type SendTreeSignaturesRequest struct { func (x *SendTreeSignaturesRequest) Reset() { *x = SendTreeSignaturesRequest{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[39] + mi := &file_ark_v1_service_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2382,7 +2544,7 @@ func (x *SendTreeSignaturesRequest) String() string { func (*SendTreeSignaturesRequest) ProtoMessage() {} func (x *SendTreeSignaturesRequest) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[39] + mi := &file_ark_v1_service_proto_msgTypes[41] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2395,7 +2557,7 @@ func (x *SendTreeSignaturesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SendTreeSignaturesRequest.ProtoReflect.Descriptor instead. func (*SendTreeSignaturesRequest) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{39} + return file_ark_v1_service_proto_rawDescGZIP(), []int{41} } func (x *SendTreeSignaturesRequest) GetRoundId() string { @@ -2428,7 +2590,7 @@ type SendTreeSignaturesResponse struct { func (x *SendTreeSignaturesResponse) Reset() { *x = SendTreeSignaturesResponse{} if protoimpl.UnsafeEnabled { - mi := &file_ark_v1_service_proto_msgTypes[40] + mi := &file_ark_v1_service_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2441,7 +2603,7 @@ func (x *SendTreeSignaturesResponse) String() string { func (*SendTreeSignaturesResponse) ProtoMessage() {} func (x *SendTreeSignaturesResponse) ProtoReflect() protoreflect.Message { - mi := &file_ark_v1_service_proto_msgTypes[40] + mi := &file_ark_v1_service_proto_msgTypes[42] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2454,7 +2616,7 @@ func (x *SendTreeSignaturesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SendTreeSignaturesResponse.ProtoReflect.Descriptor instead. func (*SendTreeSignaturesResponse) Descriptor() ([]byte, []int) { - return file_ark_v1_service_proto_rawDescGZIP(), []int{40} + return file_ark_v1_service_proto_rawDescGZIP(), []int{42} } var File_ark_v1_service_proto protoreflect.FileDescriptor @@ -2489,359 +2651,383 @@ var file_ark_v1_service_proto_rawDesc = []byte{ 0x03, 0x28, 0x09, 0x52, 0x1d, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x46, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x22, 0x19, 0x0a, 0x17, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x84, 0x01, - 0x0a, 0x16, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x06, 0x69, 0x6e, 0x70, 0x75, - 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, - 0x31, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, - 0x2e, 0x0a, 0x10, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x62, - 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0f, 0x65, 0x70, 0x68, - 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x88, 0x01, 0x01, 0x42, - 0x13, 0x0a, 0x11, 0x5f, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x70, 0x75, - 0x62, 0x6b, 0x65, 0x79, 0x22, 0x29, 0x0a, 0x17, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, - 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, - 0x4f, 0x0a, 0x13, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x28, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, - 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, - 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, - 0x22, 0x16, 0x0a, 0x14, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x46, 0x0a, 0x16, 0x46, 0x69, 0x6e, 0x61, - 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x66, 0x6f, 0x72, - 0x66, 0x65, 0x69, 0x74, 0x5f, 0x74, 0x78, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, - 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, - 0x22, 0x19, 0x0a, 0x17, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x61, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x25, 0x0a, 0x0f, 0x47, - 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, - 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, - 0x69, 0x64, 0x22, 0x37, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x25, 0x0a, 0x13, 0x47, - 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, - 0x69, 0x64, 0x22, 0x3b, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x42, 0x79, - 0x49, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x05, 0x72, 0x6f, - 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x22, - 0x17, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa7, 0x03, 0x0a, 0x16, 0x47, 0x65, 0x74, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x12, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x66, 0x69, 0x6e, - 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1e, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x69, - 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, - 0x00, 0x52, 0x11, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x0f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x66, 0x69, - 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, - 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x69, 0x6e, 0x61, - 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x72, 0x6f, - 0x75, 0x6e, 0x64, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x38, 0x0a, 0x0c, - 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, - 0x64, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0b, 0x72, 0x6f, 0x75, 0x6e, 0x64, - 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x40, 0x0a, 0x0d, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, - 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, - 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, 0x67, 0x6e, - 0x69, 0x6e, 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x72, 0x6f, 0x75, 0x6e, - 0x64, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x6f, 0x0a, 0x1e, 0x72, 0x6f, 0x75, 0x6e, - 0x64, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, - 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x28, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, - 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x47, 0x65, 0x6e, 0x65, - 0x72, 0x61, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x1b, 0x72, 0x6f, - 0x75, 0x6e, 0x64, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, - 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, - 0x6e, 0x74, 0x22, 0x2c, 0x0a, 0x0b, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, - 0x22, 0x9d, 0x03, 0x0a, 0x0c, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x4f, 0x0a, 0x12, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, - 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, - 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x69, 0x6e, 0x61, - 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, - 0x11, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x0f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x66, 0x69, 0x6e, 0x61, - 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x72, - 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, - 0x7a, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x72, 0x6f, 0x75, 0x6e, - 0x64, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x38, 0x0a, 0x0c, 0x72, 0x6f, - 0x75, 0x6e, 0x64, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x13, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x46, - 0x61, 0x69, 0x6c, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0b, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x61, - 0x69, 0x6c, 0x65, 0x64, 0x12, 0x40, 0x0a, 0x0d, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x73, 0x69, - 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x72, - 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, - 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x53, - 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x6f, 0x0a, 0x1e, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, - 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x5f, 0x67, - 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, - 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, 0x67, - 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, - 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x1b, 0x72, 0x6f, 0x75, 0x6e, - 0x64, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x47, 0x65, - 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, - 0x22, 0x2c, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 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, 0x22, 0x79, - 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x0f, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, - 0x5f, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, - 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x74, 0x78, 0x6f, 0x52, 0x0e, 0x73, 0x70, 0x65, 0x6e, - 0x64, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x12, 0x2d, 0x0a, 0x0b, 0x73, 0x70, - 0x65, 0x6e, 0x74, 0x5f, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x0c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x74, 0x78, 0x6f, 0x52, 0x0a, 0x73, - 0x70, 0x65, 0x6e, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x22, 0x10, 0x0a, 0x0e, 0x47, 0x65, 0x74, - 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xe9, 0x01, 0x0a, 0x0f, - 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x6f, 0x75, 0x6e, 0x64, - 0x5f, 0x6c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0d, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x4c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x32, - 0x0a, 0x15, 0x75, 0x6e, 0x69, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x65, 0x78, 0x69, - 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x75, - 0x6e, 0x69, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x45, 0x78, 0x69, 0x74, 0x44, 0x65, 0x6c, - 0x61, 0x79, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x76, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x72, 0x6f, 0x75, 0x6e, - 0x64, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, - 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, - 0x6f, 0x72, 0x6b, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x79, - 0x5f, 0x66, 0x65, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x52, - 0x65, 0x6c, 0x61, 0x79, 0x46, 0x65, 0x65, 0x22, 0x89, 0x01, 0x0a, 0x0e, 0x4f, 0x6e, 0x62, 0x6f, - 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x6f, - 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0a, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x54, 0x78, 0x12, 0x35, 0x0a, 0x0f, 0x63, - 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, - 0x65, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x72, - 0x65, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, - 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x75, 0x73, 0x65, 0x72, 0x50, 0x75, 0x62, - 0x6b, 0x65, 0x79, 0x22, 0x11, 0x0a, 0x0f, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xb9, 0x01, 0x0a, 0x16, 0x52, 0x6f, 0x75, 0x6e, 0x64, - 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, - 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x74, 0x78, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x54, 0x78, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x6f, - 0x72, 0x66, 0x65, 0x69, 0x74, 0x5f, 0x74, 0x78, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x0a, 0x66, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x12, 0x35, 0x0a, 0x0f, 0x63, - 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, - 0x65, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x72, - 0x65, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, - 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, - 0x72, 0x73, 0x22, 0x42, 0x0a, 0x13, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x69, 0x6e, 0x61, 0x6c, - 0x69, 0x7a, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6f, 0x6f, - 0x6c, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, - 0x6f, 0x6c, 0x54, 0x78, 0x69, 0x64, 0x22, 0x35, 0x0a, 0x0b, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x46, - 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x83, 0x01, - 0x0a, 0x11, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x02, 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x6f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x73, - 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, - 0x63, 0x6f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x73, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, - 0x12, 0x31, 0x0a, 0x0d, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x74, 0x72, 0x65, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, - 0x2e, 0x54, 0x72, 0x65, 0x65, 0x52, 0x0c, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, - 0x72, 0x65, 0x65, 0x22, 0x53, 0x0a, 0x20, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, 0x67, 0x6e, - 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, - 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x72, 0x65, 0x65, 0x5f, - 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x72, - 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x22, 0xfa, 0x01, 0x0a, 0x05, 0x52, 0x6f, 0x75, - 0x6e, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, - 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, - 0x6f, 0x6c, 0x5f, 0x74, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, - 0x6c, 0x54, 0x78, 0x12, 0x35, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, - 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x65, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x67, - 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x72, 0x65, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x6f, - 0x72, 0x66, 0x65, 0x69, 0x74, 0x5f, 0x74, 0x78, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x0a, 0x66, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x63, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x28, 0x0a, 0x05, 0x73, - 0x74, 0x61, 0x67, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x61, 0x72, 0x6b, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x67, 0x65, 0x52, 0x05, - 0x73, 0x74, 0x61, 0x67, 0x65, 0x22, 0x2f, 0x0a, 0x05, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x12, - 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, - 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x76, 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x04, 0x76, 0x6f, 0x75, 0x74, 0x22, 0x3a, 0x0a, 0x06, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x33, 0x0a, + 0x19, 0x47, 0x65, 0x74, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, + 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, + 0x65, 0x79, 0x22, 0x56, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, + 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 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, 0x16, 0x0a, 0x06, 0x61, 0x6d, - 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x22, 0x31, 0x0a, 0x04, 0x54, 0x72, 0x65, 0x65, 0x12, 0x29, 0x0a, 0x06, 0x6c, 0x65, - 0x76, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x72, 0x6b, - 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x65, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x06, 0x6c, - 0x65, 0x76, 0x65, 0x6c, 0x73, 0x22, 0x2f, 0x0a, 0x09, 0x54, 0x72, 0x65, 0x65, 0x4c, 0x65, 0x76, - 0x65, 0x6c, 0x12, 0x22, 0x0a, 0x05, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, - 0x05, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x22, 0x4b, 0x0a, 0x04, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x12, - 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, - 0x69, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, - 0x74, 0x78, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x78, 0x69, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x54, - 0x78, 0x69, 0x64, 0x22, 0xb3, 0x02, 0x0a, 0x04, 0x56, 0x74, 0x78, 0x6f, 0x12, 0x29, 0x0a, 0x08, - 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, - 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x08, 0x6f, - 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x08, 0x72, 0x65, 0x63, 0x65, 0x69, - 0x76, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x72, 0x6b, 0x2e, - 0x76, 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x08, 0x72, 0x65, 0x63, 0x65, 0x69, - 0x76, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x05, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6f, 0x6f, - 0x6c, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, - 0x6f, 0x6c, 0x54, 0x78, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x5f, - 0x62, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x42, - 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x5f, 0x61, 0x74, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x74, 0x12, 0x14, - 0x0a, 0x05, 0x73, 0x77, 0x65, 0x70, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x73, - 0x77, 0x65, 0x70, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x39, - 0x0a, 0x0c, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x0b, 0x70, 0x65, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x44, 0x61, 0x74, 0x61, 0x22, 0x69, 0x0a, 0x0e, 0x50, 0x65, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x72, - 0x65, 0x64, 0x65, 0x65, 0x6d, 0x5f, 0x74, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x72, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x54, 0x78, 0x12, 0x3a, 0x0a, 0x19, 0x75, 0x6e, 0x63, 0x6f, - 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x6f, 0x72, 0x66, 0x65, 0x69, - 0x74, 0x5f, 0x74, 0x78, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x17, 0x75, 0x6e, 0x63, - 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x46, 0x6f, 0x72, 0x66, 0x65, 0x69, - 0x74, 0x54, 0x78, 0x73, 0x22, 0x72, 0x0a, 0x15, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, - 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, - 0x08, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, - 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x75, - 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x72, 0x65, 0x65, 0x5f, - 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x72, - 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x22, 0x18, 0x0a, 0x16, 0x53, 0x65, 0x6e, 0x64, - 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x7e, 0x0a, 0x19, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, - 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, - 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x72, 0x65, - 0x65, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0e, 0x74, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x73, 0x22, 0x1c, 0x0a, 0x1a, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, - 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x2a, 0x98, 0x01, 0x0a, 0x0a, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x67, 0x65, 0x12, - 0x1b, 0x0a, 0x17, 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x5f, 0x55, - 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, - 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x5f, 0x52, 0x45, 0x47, 0x49, - 0x53, 0x54, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x4f, - 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x5f, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x49, - 0x5a, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, 0x52, 0x4f, 0x55, 0x4e, - 0x44, 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x5f, 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x49, 0x5a, 0x45, - 0x44, 0x10, 0x03, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x54, 0x41, - 0x47, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x04, 0x32, 0xae, 0x0b, 0x0a, 0x0a, - 0x41, 0x72, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x73, 0x0a, 0x0f, 0x52, 0x65, - 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x2e, - 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, - 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x70, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, - 0x67, 0x0a, 0x0c, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, - 0x1b, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x50, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x61, - 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x50, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x16, 0x3a, 0x01, 0x2a, 0x22, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x2f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x73, 0x0a, 0x0e, 0x53, 0x65, 0x6e, 0x64, - 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x1d, 0x2e, 0x61, 0x72, 0x6b, - 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, - 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x61, 0x72, 0x6b, 0x2e, - 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x1c, 0x3a, 0x01, 0x2a, 0x22, 0x17, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, - 0x74, 0x2f, 0x74, 0x72, 0x65, 0x65, 0x2f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x83, 0x01, - 0x0a, 0x12, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x73, 0x12, 0x21, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, - 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, - 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x26, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x20, 0x3a, 0x01, 0x2a, 0x22, 0x1b, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x2f, 0x74, 0x72, 0x65, 0x65, 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x73, 0x12, 0x73, 0x0a, 0x0f, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, - 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, - 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, - 0x01, 0x2a, 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2f, - 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x12, 0x57, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x52, - 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x17, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, - 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, - 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x12, - 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x7b, 0x74, 0x78, 0x69, 0x64, - 0x7d, 0x12, 0x64, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x42, 0x79, 0x49, - 0x64, 0x12, 0x1b, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, - 0x75, 0x6e, 0x64, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, - 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, - 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, - 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, - 0x69, 0x64, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x65, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x1d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, - 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x12, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0c, - 0x12, 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x30, 0x01, 0x12, 0x50, - 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x13, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, - 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x61, 0x72, - 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x70, - 0x69, 0x6e, 0x67, 0x2f, 0x7b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, - 0x12, 0x5d, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x12, 0x18, 0x2e, + 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x22, 0x84, 0x01, 0x0a, 0x16, 0x52, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x49, + 0x6e, 0x70, 0x75, 0x74, 0x52, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x2e, 0x0a, 0x10, + 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0f, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, + 0x72, 0x61, 0x6c, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x88, 0x01, 0x01, 0x42, 0x13, 0x0a, 0x11, + 0x5f, 0x65, 0x70, 0x68, 0x65, 0x6d, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, + 0x79, 0x22, 0x29, 0x0a, 0x17, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x4f, 0x0a, 0x13, + 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x69, 0x64, 0x12, 0x28, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x22, 0x16, 0x0a, + 0x14, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x87, 0x01, 0x0a, 0x16, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, + 0x7a, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x66, 0x6f, 0x72, 0x66, 0x65, + 0x69, 0x74, 0x5f, 0x74, 0x78, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x73, 0x69, + 0x67, 0x6e, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x12, 0x2b, + 0x0a, 0x0f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, + 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0d, 0x73, 0x69, 0x67, 0x6e, 0x65, + 0x64, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x78, 0x88, 0x01, 0x01, 0x42, 0x12, 0x0a, 0x10, 0x5f, + 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x78, 0x22, + 0x19, 0x0a, 0x17, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x25, 0x0a, 0x0f, 0x47, 0x65, + 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, + 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, + 0x64, 0x22, 0x37, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, + 0x75, 0x6e, 0x64, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x25, 0x0a, 0x13, 0x47, 0x65, + 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, + 0x64, 0x22, 0x3b, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x42, 0x79, 0x49, + 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x23, 0x0a, 0x05, 0x72, 0x6f, 0x75, + 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x17, + 0x0a, 0x15, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa7, 0x03, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x12, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x66, 0x69, 0x6e, 0x61, + 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, + 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x69, 0x6e, + 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, + 0x52, 0x11, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x0f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x66, 0x69, 0x6e, + 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, + 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x69, 0x6e, 0x61, 0x6c, + 0x69, 0x7a, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x72, 0x6f, 0x75, + 0x6e, 0x64, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x38, 0x0a, 0x0c, 0x72, + 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x13, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, + 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0b, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x46, + 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x40, 0x0a, 0x0d, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x73, + 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, + 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, 0x67, 0x6e, 0x69, + 0x6e, 0x67, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x72, 0x6f, 0x75, 0x6e, 0x64, + 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x6f, 0x0a, 0x1e, 0x72, 0x6f, 0x75, 0x6e, 0x64, + 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x5f, + 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x28, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, + 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x47, 0x65, 0x6e, 0x65, 0x72, + 0x61, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x1b, 0x72, 0x6f, 0x75, + 0x6e, 0x64, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x47, + 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x22, 0x2c, 0x0a, 0x0b, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, + 0x9d, 0x03, 0x0a, 0x0c, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x4f, 0x0a, 0x12, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x61, + 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x69, 0x6e, 0x61, 0x6c, + 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x11, + 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x46, 0x0a, 0x0f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x66, 0x69, 0x6e, 0x61, 0x6c, + 0x69, 0x7a, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x61, 0x72, 0x6b, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, + 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x72, 0x6f, 0x75, 0x6e, 0x64, + 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x38, 0x0a, 0x0c, 0x72, 0x6f, 0x75, + 0x6e, 0x64, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x13, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x61, + 0x69, 0x6c, 0x65, 0x64, 0x48, 0x00, 0x52, 0x0b, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x61, 0x69, + 0x6c, 0x65, 0x64, 0x12, 0x40, 0x0a, 0x0d, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x73, 0x69, 0x67, + 0x6e, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x72, 0x6b, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, + 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x12, 0x6f, 0x0a, 0x1e, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x73, + 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x5f, 0x67, 0x65, + 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, + 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, 0x67, 0x6e, + 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, + 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x1b, 0x72, 0x6f, 0x75, 0x6e, 0x64, + 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x47, 0x65, 0x6e, + 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, + 0x2c, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 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, 0x22, 0x79, 0x0a, + 0x11, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x35, 0x0a, 0x0f, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x5f, + 0x76, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, + 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x74, 0x78, 0x6f, 0x52, 0x0e, 0x73, 0x70, 0x65, 0x6e, 0x64, + 0x61, 0x62, 0x6c, 0x65, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x12, 0x2d, 0x0a, 0x0b, 0x73, 0x70, 0x65, + 0x6e, 0x74, 0x5f, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, + 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x74, 0x78, 0x6f, 0x52, 0x0a, 0x73, 0x70, + 0x65, 0x6e, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x22, 0x10, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xab, 0x02, 0x0a, 0x0f, 0x47, + 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, + 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, + 0x6c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, + 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x4c, 0x69, 0x66, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x32, 0x0a, + 0x15, 0x75, 0x6e, 0x69, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x5f, 0x65, 0x78, 0x69, 0x74, + 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x75, 0x6e, + 0x69, 0x6c, 0x61, 0x74, 0x65, 0x72, 0x61, 0x6c, 0x45, 0x78, 0x69, 0x74, 0x44, 0x65, 0x6c, 0x61, + 0x79, 0x12, 0x25, 0x0a, 0x0e, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x76, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x72, 0x6f, 0x75, 0x6e, 0x64, + 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, 0x77, + 0x6f, 0x72, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, 0x6f, + 0x72, 0x6b, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f, + 0x66, 0x65, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x6d, 0x69, 0x6e, 0x52, 0x65, + 0x6c, 0x61, 0x79, 0x46, 0x65, 0x65, 0x12, 0x40, 0x0a, 0x1c, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, + 0x6e, 0x67, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x5f, 0x74, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1a, 0x62, 0x6f, + 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, + 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x22, 0xb9, 0x01, 0x0a, 0x16, 0x52, 0x6f, 0x75, + 0x6e, 0x64, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x74, 0x78, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6f, 0x6c, 0x54, 0x78, 0x12, 0x1f, 0x0a, 0x0b, + 0x66, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x5f, 0x74, 0x78, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x0a, 0x66, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x12, 0x35, 0x0a, + 0x0f, 0x63, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x72, 0x65, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, + 0x54, 0x72, 0x65, 0x65, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, + 0x54, 0x72, 0x65, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x73, 0x22, 0x42, 0x0a, 0x13, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x46, 0x69, 0x6e, + 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x70, + 0x6f, 0x6f, 0x6c, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x70, 0x6f, 0x6f, 0x6c, 0x54, 0x78, 0x69, 0x64, 0x22, 0x35, 0x0a, 0x0b, 0x52, 0x6f, 0x75, 0x6e, + 0x64, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, + 0x83, 0x01, 0x0a, 0x11, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x6f, 0x73, 0x69, 0x67, 0x6e, 0x65, + 0x72, 0x73, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x10, 0x63, 0x6f, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x73, 0x50, 0x75, 0x62, 0x6b, 0x65, + 0x79, 0x73, 0x12, 0x31, 0x0a, 0x0d, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, 0x74, + 0x72, 0x65, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, + 0x76, 0x31, 0x2e, 0x54, 0x72, 0x65, 0x65, 0x52, 0x0c, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, + 0x64, 0x54, 0x72, 0x65, 0x65, 0x22, 0x53, 0x0a, 0x20, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x69, + 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x47, 0x65, 0x6e, 0x65, 0x72, + 0x61, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x72, 0x65, + 0x65, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x74, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x22, 0xfa, 0x01, 0x0a, 0x05, 0x52, + 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x17, 0x0a, 0x07, + 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x74, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, + 0x6f, 0x6f, 0x6c, 0x54, 0x78, 0x12, 0x35, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, + 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x65, 0x65, 0x52, 0x0e, 0x63, 0x6f, + 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x72, 0x65, 0x65, 0x12, 0x1f, 0x0a, 0x0b, + 0x66, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x5f, 0x74, 0x78, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x0a, 0x66, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x12, 0x1e, 0x0a, + 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x28, 0x0a, + 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x61, + 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x74, 0x61, 0x67, 0x65, + 0x52, 0x05, 0x73, 0x74, 0x61, 0x67, 0x65, 0x22, 0x33, 0x0a, 0x09, 0x56, 0x74, 0x78, 0x6f, 0x49, + 0x6e, 0x70, 0x75, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x76, 0x6f, 0x75, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x76, 0x6f, 0x75, 0x74, 0x22, 0x57, 0x0a, 0x0d, + 0x42, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x12, 0x0a, + 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, + 0x64, 0x12, 0x12, 0x0a, 0x04, 0x76, 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x04, 0x76, 0x6f, 0x75, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x6f, 0x72, 0x22, 0x84, 0x01, 0x0a, 0x05, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, + 0x32, 0x0a, 0x0a, 0x76, 0x74, 0x78, 0x6f, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x56, 0x74, 0x78, + 0x6f, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x48, 0x00, 0x52, 0x09, 0x76, 0x74, 0x78, 0x6f, 0x49, 0x6e, + 0x70, 0x75, 0x74, 0x12, 0x3e, 0x0a, 0x0e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, + 0x69, 0x6e, 0x70, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x61, 0x72, + 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x70, + 0x75, 0x74, 0x48, 0x00, 0x52, 0x0d, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x49, 0x6e, + 0x70, 0x75, 0x74, 0x42, 0x07, 0x0a, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x22, 0x3a, 0x0a, 0x06, + 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 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, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x31, 0x0a, 0x04, 0x54, 0x72, 0x65, 0x65, + 0x12, 0x29, 0x0a, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x11, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x72, 0x65, 0x65, 0x4c, 0x65, + 0x76, 0x65, 0x6c, 0x52, 0x06, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x22, 0x2f, 0x0a, 0x09, 0x54, + 0x72, 0x65, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x22, 0x0a, 0x05, 0x6e, 0x6f, 0x64, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, + 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x05, 0x6e, 0x6f, 0x64, 0x65, 0x73, 0x22, 0x4b, 0x0a, 0x04, + 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x78, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x74, 0x78, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x65, + 0x6e, 0x74, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, + 0x61, 0x72, 0x65, 0x6e, 0x74, 0x54, 0x78, 0x69, 0x64, 0x22, 0xb3, 0x02, 0x0a, 0x04, 0x56, 0x74, + 0x78, 0x6f, 0x12, 0x29, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, + 0x70, 0x75, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x2a, 0x0a, + 0x08, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x0e, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, + 0x08, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x70, 0x65, + 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x12, + 0x1b, 0x0a, 0x09, 0x70, 0x6f, 0x6f, 0x6c, 0x5f, 0x74, 0x78, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x70, 0x6f, 0x6f, 0x6c, 0x54, 0x78, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, + 0x73, 0x70, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x73, 0x70, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, + 0x65, 0x5f, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x65, 0x78, 0x70, 0x69, + 0x72, 0x65, 0x41, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x77, 0x65, 0x70, 0x74, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x05, 0x73, 0x77, 0x65, 0x70, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x65, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x70, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x12, 0x39, 0x0a, 0x0c, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, + 0x64, 0x61, 0x74, 0x61, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x61, 0x72, 0x6b, + 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x52, 0x0b, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x44, 0x61, 0x74, 0x61, 0x22, + 0x69, 0x0a, 0x0e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x5f, 0x74, 0x78, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x54, 0x78, 0x12, 0x3a, + 0x0a, 0x19, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, + 0x66, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x5f, 0x74, 0x78, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x17, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, + 0x46, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x22, 0x72, 0x0a, 0x15, 0x53, 0x65, + 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x64, 0x12, 0x1d, + 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x1f, 0x0a, + 0x0b, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x74, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x22, 0x18, + 0x0a, 0x16, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x7e, 0x0a, 0x19, 0x53, 0x65, 0x6e, 0x64, + 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x49, 0x64, + 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, + 0x27, 0x0a, 0x0f, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, + 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x74, 0x72, 0x65, 0x65, 0x53, 0x69, + 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x22, 0x1c, 0x0a, 0x1a, 0x53, 0x65, 0x6e, 0x64, + 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x98, 0x01, 0x0a, 0x0a, 0x52, 0x6f, 0x75, 0x6e, 0x64, + 0x53, 0x74, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, + 0x54, 0x41, 0x47, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, + 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x54, 0x41, 0x47, + 0x45, 0x5f, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x01, + 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x5f, + 0x46, 0x49, 0x4e, 0x41, 0x4c, 0x49, 0x5a, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x02, 0x12, 0x19, + 0x0a, 0x15, 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x5f, 0x46, 0x49, + 0x4e, 0x41, 0x4c, 0x49, 0x5a, 0x45, 0x44, 0x10, 0x03, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x4f, 0x55, + 0x4e, 0x44, 0x5f, 0x53, 0x54, 0x41, 0x47, 0x45, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, + 0x04, 0x32, 0xd0, 0x0b, 0x0a, 0x0a, 0x41, 0x72, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x12, 0x73, 0x0a, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, + 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x72, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x67, 0x0a, 0x0c, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1b, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x6c, 0x61, 0x69, 0x6d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x61, 0x69, + 0x6d, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x3a, 0x01, 0x2a, 0x22, 0x11, 0x2f, 0x76, 0x31, + 0x2f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x12, 0x73, + 0x0a, 0x0e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, + 0x12, 0x1d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, + 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1e, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, 0x65, + 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x22, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x3a, 0x01, 0x2a, 0x22, 0x17, 0x2f, 0x76, 0x31, 0x2f, + 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x74, 0x72, 0x65, 0x65, 0x2f, 0x6e, 0x6f, 0x6e, + 0x63, 0x65, 0x73, 0x12, 0x83, 0x01, 0x0a, 0x12, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, + 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x21, 0x2e, 0x61, 0x72, 0x6b, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, + 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x54, 0x72, 0x65, 0x65, 0x53, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x26, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x20, 0x3a, 0x01, 0x2a, 0x22, 0x1b, 0x2f, 0x76, + 0x31, 0x2f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x74, 0x72, 0x65, 0x65, 0x2f, 0x73, + 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x73, 0x0a, 0x0f, 0x46, 0x69, 0x6e, + 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x61, + 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61, + 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x12, 0x57, + 0x0a, 0x08, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x17, 0x2e, 0x61, 0x72, 0x6b, + 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x12, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, + 0x2f, 0x7b, 0x74, 0x78, 0x69, 0x64, 0x7d, 0x12, 0x64, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x52, 0x6f, + 0x75, 0x6e, 0x64, 0x42, 0x79, 0x49, 0x64, 0x12, 0x1b, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, + 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x19, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x13, 0x12, 0x11, 0x2f, 0x76, 0x31, 0x2f, + 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x69, 0x64, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0x65, 0x0a, + 0x0e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, + 0x1d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, + 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x12, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0c, 0x12, 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x73, 0x30, 0x01, 0x12, 0x50, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x13, 0x2e, 0x61, + 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x14, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, + 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x69, 0x6e, 0x67, 0x2f, 0x7b, 0x70, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, 0x12, 0x5d, 0x0a, 0x09, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x74, + 0x78, 0x6f, 0x73, 0x12, 0x18, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, - 0x76, 0x74, 0x78, 0x6f, 0x73, 0x2f, 0x7b, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x7d, 0x12, - 0x4c, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x2e, 0x61, 0x72, 0x6b, - 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x49, - 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x10, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x0a, 0x12, 0x08, 0x2f, 0x76, 0x31, 0x2f, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x52, 0x0a, - 0x07, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x12, 0x16, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, - 0x31, 0x2e, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x17, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, - 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x10, 0x3a, 0x01, 0x2a, 0x22, 0x0b, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, - 0x64, 0x12, 0x64, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x12, 0x1c, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x3a, 0x01, 0x2a, 0x22, 0x0b, 0x2f, 0x76, 0x31, 0x2f, - 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x73, 0x0a, 0x0f, 0x43, 0x6f, 0x6d, 0x70, 0x6c, - 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x61, 0x72, 0x6b, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61, 0x72, 0x6b, - 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, - 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, 0x79, 0x6d, - 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x92, 0x01, 0x0a, - 0x0a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x42, 0x0c, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3d, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x72, 0x6b, 0x2d, 0x6e, 0x65, 0x74, 0x77, - 0x6f, 0x72, 0x6b, 0x2f, 0x61, 0x72, 0x6b, 0x2f, 0x61, 0x70, 0x69, 0x2d, 0x73, 0x70, 0x65, 0x63, - 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x61, 0x72, - 0x6b, 0x2f, 0x76, 0x31, 0x3b, 0x61, 0x72, 0x6b, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x41, 0x58, 0x58, - 0xaa, 0x02, 0x06, 0x41, 0x72, 0x6b, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x06, 0x41, 0x72, 0x6b, 0x5c, - 0x56, 0x31, 0xe2, 0x02, 0x12, 0x41, 0x72, 0x6b, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x07, 0x41, 0x72, 0x6b, 0x3a, 0x3a, 0x56, - 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x1b, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, + 0x12, 0x13, 0x2f, 0x76, 0x31, 0x2f, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x2f, 0x7b, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x7d, 0x12, 0x4c, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, + 0x12, 0x16, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, + 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x10, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0a, 0x12, 0x08, 0x2f, 0x76, 0x31, 0x2f, 0x69, + 0x6e, 0x66, 0x6f, 0x12, 0x74, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x69, + 0x6e, 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x21, 0x2e, 0x61, 0x72, 0x6b, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x61, + 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, + 0x67, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x3a, 0x01, 0x2a, 0x22, 0x0c, 0x2f, 0x76, 0x31, + 0x2f, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x64, 0x0a, 0x0d, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1c, 0x2e, 0x61, 0x72, 0x6b, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, + 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x16, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x10, 0x3a, + 0x01, 0x2a, 0x22, 0x0b, 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, + 0x73, 0x0a, 0x0f, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, + 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x70, + 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x70, + 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x3a, 0x01, 0x2a, 0x22, 0x14, + 0x2f, 0x76, 0x31, 0x2f, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x2f, 0x63, 0x6f, 0x6d, 0x70, + 0x6c, 0x65, 0x74, 0x65, 0x42, 0x92, 0x01, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x72, 0x6b, + 0x2e, 0x76, 0x31, 0x42, 0x0c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x50, 0x01, 0x5a, 0x3d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x61, 0x72, 0x6b, 0x2d, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x61, 0x72, 0x6b, 0x2f, + 0x61, 0x70, 0x69, 0x2d, 0x73, 0x70, 0x65, 0x63, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x61, 0x72, 0x6b, 0x2f, 0x76, 0x31, 0x3b, 0x61, 0x72, 0x6b, + 0x76, 0x31, 0xa2, 0x02, 0x03, 0x41, 0x58, 0x58, 0xaa, 0x02, 0x06, 0x41, 0x72, 0x6b, 0x2e, 0x56, + 0x31, 0xca, 0x02, 0x06, 0x41, 0x72, 0x6b, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x12, 0x41, 0x72, 0x6b, + 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, + 0x02, 0x07, 0x41, 0x72, 0x6b, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( @@ -2857,56 +3043,58 @@ func file_ark_v1_service_proto_rawDescGZIP() []byte { } var file_ark_v1_service_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_ark_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 41) +var file_ark_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 43) var file_ark_v1_service_proto_goTypes = []interface{}{ (RoundStage)(0), // 0: ark.v1.RoundStage (*CreatePaymentRequest)(nil), // 1: ark.v1.CreatePaymentRequest (*CreatePaymentResponse)(nil), // 2: ark.v1.CreatePaymentResponse (*CompletePaymentRequest)(nil), // 3: ark.v1.CompletePaymentRequest (*CompletePaymentResponse)(nil), // 4: ark.v1.CompletePaymentResponse - (*RegisterPaymentRequest)(nil), // 5: ark.v1.RegisterPaymentRequest - (*RegisterPaymentResponse)(nil), // 6: ark.v1.RegisterPaymentResponse - (*ClaimPaymentRequest)(nil), // 7: ark.v1.ClaimPaymentRequest - (*ClaimPaymentResponse)(nil), // 8: ark.v1.ClaimPaymentResponse - (*FinalizePaymentRequest)(nil), // 9: ark.v1.FinalizePaymentRequest - (*FinalizePaymentResponse)(nil), // 10: ark.v1.FinalizePaymentResponse - (*GetRoundRequest)(nil), // 11: ark.v1.GetRoundRequest - (*GetRoundResponse)(nil), // 12: ark.v1.GetRoundResponse - (*GetRoundByIdRequest)(nil), // 13: ark.v1.GetRoundByIdRequest - (*GetRoundByIdResponse)(nil), // 14: ark.v1.GetRoundByIdResponse - (*GetEventStreamRequest)(nil), // 15: ark.v1.GetEventStreamRequest - (*GetEventStreamResponse)(nil), // 16: ark.v1.GetEventStreamResponse - (*PingRequest)(nil), // 17: ark.v1.PingRequest - (*PingResponse)(nil), // 18: ark.v1.PingResponse - (*ListVtxosRequest)(nil), // 19: ark.v1.ListVtxosRequest - (*ListVtxosResponse)(nil), // 20: ark.v1.ListVtxosResponse - (*GetInfoRequest)(nil), // 21: ark.v1.GetInfoRequest - (*GetInfoResponse)(nil), // 22: ark.v1.GetInfoResponse - (*OnboardRequest)(nil), // 23: ark.v1.OnboardRequest - (*OnboardResponse)(nil), // 24: ark.v1.OnboardResponse + (*GetBoardingAddressRequest)(nil), // 5: ark.v1.GetBoardingAddressRequest + (*GetBoardingAddressResponse)(nil), // 6: ark.v1.GetBoardingAddressResponse + (*RegisterPaymentRequest)(nil), // 7: ark.v1.RegisterPaymentRequest + (*RegisterPaymentResponse)(nil), // 8: ark.v1.RegisterPaymentResponse + (*ClaimPaymentRequest)(nil), // 9: ark.v1.ClaimPaymentRequest + (*ClaimPaymentResponse)(nil), // 10: ark.v1.ClaimPaymentResponse + (*FinalizePaymentRequest)(nil), // 11: ark.v1.FinalizePaymentRequest + (*FinalizePaymentResponse)(nil), // 12: ark.v1.FinalizePaymentResponse + (*GetRoundRequest)(nil), // 13: ark.v1.GetRoundRequest + (*GetRoundResponse)(nil), // 14: ark.v1.GetRoundResponse + (*GetRoundByIdRequest)(nil), // 15: ark.v1.GetRoundByIdRequest + (*GetRoundByIdResponse)(nil), // 16: ark.v1.GetRoundByIdResponse + (*GetEventStreamRequest)(nil), // 17: ark.v1.GetEventStreamRequest + (*GetEventStreamResponse)(nil), // 18: ark.v1.GetEventStreamResponse + (*PingRequest)(nil), // 19: ark.v1.PingRequest + (*PingResponse)(nil), // 20: ark.v1.PingResponse + (*ListVtxosRequest)(nil), // 21: ark.v1.ListVtxosRequest + (*ListVtxosResponse)(nil), // 22: ark.v1.ListVtxosResponse + (*GetInfoRequest)(nil), // 23: ark.v1.GetInfoRequest + (*GetInfoResponse)(nil), // 24: ark.v1.GetInfoResponse (*RoundFinalizationEvent)(nil), // 25: ark.v1.RoundFinalizationEvent (*RoundFinalizedEvent)(nil), // 26: ark.v1.RoundFinalizedEvent (*RoundFailed)(nil), // 27: ark.v1.RoundFailed (*RoundSigningEvent)(nil), // 28: ark.v1.RoundSigningEvent (*RoundSigningNoncesGeneratedEvent)(nil), // 29: ark.v1.RoundSigningNoncesGeneratedEvent (*Round)(nil), // 30: ark.v1.Round - (*Input)(nil), // 31: ark.v1.Input - (*Output)(nil), // 32: ark.v1.Output - (*Tree)(nil), // 33: ark.v1.Tree - (*TreeLevel)(nil), // 34: ark.v1.TreeLevel - (*Node)(nil), // 35: ark.v1.Node - (*Vtxo)(nil), // 36: ark.v1.Vtxo - (*PendingPayment)(nil), // 37: ark.v1.PendingPayment - (*SendTreeNoncesRequest)(nil), // 38: ark.v1.SendTreeNoncesRequest - (*SendTreeNoncesResponse)(nil), // 39: ark.v1.SendTreeNoncesResponse - (*SendTreeSignaturesRequest)(nil), // 40: ark.v1.SendTreeSignaturesRequest - (*SendTreeSignaturesResponse)(nil), // 41: ark.v1.SendTreeSignaturesResponse + (*VtxoInput)(nil), // 31: ark.v1.VtxoInput + (*BoardingInput)(nil), // 32: ark.v1.BoardingInput + (*Input)(nil), // 33: ark.v1.Input + (*Output)(nil), // 34: ark.v1.Output + (*Tree)(nil), // 35: ark.v1.Tree + (*TreeLevel)(nil), // 36: ark.v1.TreeLevel + (*Node)(nil), // 37: ark.v1.Node + (*Vtxo)(nil), // 38: ark.v1.Vtxo + (*PendingPayment)(nil), // 39: ark.v1.PendingPayment + (*SendTreeNoncesRequest)(nil), // 40: ark.v1.SendTreeNoncesRequest + (*SendTreeNoncesResponse)(nil), // 41: ark.v1.SendTreeNoncesResponse + (*SendTreeSignaturesRequest)(nil), // 42: ark.v1.SendTreeSignaturesRequest + (*SendTreeSignaturesResponse)(nil), // 43: ark.v1.SendTreeSignaturesResponse } var file_ark_v1_service_proto_depIdxs = []int32{ - 31, // 0: ark.v1.CreatePaymentRequest.inputs:type_name -> ark.v1.Input - 32, // 1: ark.v1.CreatePaymentRequest.outputs:type_name -> ark.v1.Output - 31, // 2: ark.v1.RegisterPaymentRequest.inputs:type_name -> ark.v1.Input - 32, // 3: ark.v1.ClaimPaymentRequest.outputs:type_name -> ark.v1.Output + 33, // 0: ark.v1.CreatePaymentRequest.inputs:type_name -> ark.v1.Input + 34, // 1: ark.v1.CreatePaymentRequest.outputs:type_name -> ark.v1.Output + 33, // 2: ark.v1.RegisterPaymentRequest.inputs:type_name -> ark.v1.Input + 34, // 3: ark.v1.ClaimPaymentRequest.outputs:type_name -> ark.v1.Output 30, // 4: ark.v1.GetRoundResponse.round:type_name -> ark.v1.Round 30, // 5: ark.v1.GetRoundByIdResponse.round:type_name -> ark.v1.Round 25, // 6: ark.v1.GetEventStreamResponse.round_finalization:type_name -> ark.v1.RoundFinalizationEvent @@ -2919,51 +3107,52 @@ var file_ark_v1_service_proto_depIdxs = []int32{ 27, // 13: ark.v1.PingResponse.round_failed:type_name -> ark.v1.RoundFailed 28, // 14: ark.v1.PingResponse.round_signing:type_name -> ark.v1.RoundSigningEvent 29, // 15: ark.v1.PingResponse.round_signing_nonces_generated:type_name -> ark.v1.RoundSigningNoncesGeneratedEvent - 36, // 16: ark.v1.ListVtxosResponse.spendable_vtxos:type_name -> ark.v1.Vtxo - 36, // 17: ark.v1.ListVtxosResponse.spent_vtxos:type_name -> ark.v1.Vtxo - 33, // 18: ark.v1.OnboardRequest.congestion_tree:type_name -> ark.v1.Tree - 33, // 19: ark.v1.RoundFinalizationEvent.congestion_tree:type_name -> ark.v1.Tree - 33, // 20: ark.v1.RoundSigningEvent.unsigned_tree:type_name -> ark.v1.Tree - 33, // 21: ark.v1.Round.congestion_tree:type_name -> ark.v1.Tree - 0, // 22: ark.v1.Round.stage:type_name -> ark.v1.RoundStage - 34, // 23: ark.v1.Tree.levels:type_name -> ark.v1.TreeLevel - 35, // 24: ark.v1.TreeLevel.nodes:type_name -> ark.v1.Node - 31, // 25: ark.v1.Vtxo.outpoint:type_name -> ark.v1.Input - 32, // 26: ark.v1.Vtxo.receiver:type_name -> ark.v1.Output - 37, // 27: ark.v1.Vtxo.pending_data:type_name -> ark.v1.PendingPayment - 5, // 28: ark.v1.ArkService.RegisterPayment:input_type -> ark.v1.RegisterPaymentRequest - 7, // 29: ark.v1.ArkService.ClaimPayment:input_type -> ark.v1.ClaimPaymentRequest - 38, // 30: ark.v1.ArkService.SendTreeNonces:input_type -> ark.v1.SendTreeNoncesRequest - 40, // 31: ark.v1.ArkService.SendTreeSignatures:input_type -> ark.v1.SendTreeSignaturesRequest - 9, // 32: ark.v1.ArkService.FinalizePayment:input_type -> ark.v1.FinalizePaymentRequest - 11, // 33: ark.v1.ArkService.GetRound:input_type -> ark.v1.GetRoundRequest - 13, // 34: ark.v1.ArkService.GetRoundById:input_type -> ark.v1.GetRoundByIdRequest - 15, // 35: ark.v1.ArkService.GetEventStream:input_type -> ark.v1.GetEventStreamRequest - 17, // 36: ark.v1.ArkService.Ping:input_type -> ark.v1.PingRequest - 19, // 37: ark.v1.ArkService.ListVtxos:input_type -> ark.v1.ListVtxosRequest - 21, // 38: ark.v1.ArkService.GetInfo:input_type -> ark.v1.GetInfoRequest - 23, // 39: ark.v1.ArkService.Onboard:input_type -> ark.v1.OnboardRequest - 1, // 40: ark.v1.ArkService.CreatePayment:input_type -> ark.v1.CreatePaymentRequest - 3, // 41: ark.v1.ArkService.CompletePayment:input_type -> ark.v1.CompletePaymentRequest - 6, // 42: ark.v1.ArkService.RegisterPayment:output_type -> ark.v1.RegisterPaymentResponse - 8, // 43: ark.v1.ArkService.ClaimPayment:output_type -> ark.v1.ClaimPaymentResponse - 39, // 44: ark.v1.ArkService.SendTreeNonces:output_type -> ark.v1.SendTreeNoncesResponse - 41, // 45: ark.v1.ArkService.SendTreeSignatures:output_type -> ark.v1.SendTreeSignaturesResponse - 10, // 46: ark.v1.ArkService.FinalizePayment:output_type -> ark.v1.FinalizePaymentResponse - 12, // 47: ark.v1.ArkService.GetRound:output_type -> ark.v1.GetRoundResponse - 14, // 48: ark.v1.ArkService.GetRoundById:output_type -> ark.v1.GetRoundByIdResponse - 16, // 49: ark.v1.ArkService.GetEventStream:output_type -> ark.v1.GetEventStreamResponse - 18, // 50: ark.v1.ArkService.Ping:output_type -> ark.v1.PingResponse - 20, // 51: ark.v1.ArkService.ListVtxos:output_type -> ark.v1.ListVtxosResponse - 22, // 52: ark.v1.ArkService.GetInfo:output_type -> ark.v1.GetInfoResponse - 24, // 53: ark.v1.ArkService.Onboard:output_type -> ark.v1.OnboardResponse - 2, // 54: ark.v1.ArkService.CreatePayment:output_type -> ark.v1.CreatePaymentResponse - 4, // 55: ark.v1.ArkService.CompletePayment:output_type -> ark.v1.CompletePaymentResponse - 42, // [42:56] is the sub-list for method output_type - 28, // [28:42] is the sub-list for method input_type - 28, // [28:28] is the sub-list for extension type_name - 28, // [28:28] is the sub-list for extension extendee - 0, // [0:28] is the sub-list for field type_name + 38, // 16: ark.v1.ListVtxosResponse.spendable_vtxos:type_name -> ark.v1.Vtxo + 38, // 17: ark.v1.ListVtxosResponse.spent_vtxos:type_name -> ark.v1.Vtxo + 35, // 18: ark.v1.RoundFinalizationEvent.congestion_tree:type_name -> ark.v1.Tree + 35, // 19: ark.v1.RoundSigningEvent.unsigned_tree:type_name -> ark.v1.Tree + 35, // 20: ark.v1.Round.congestion_tree:type_name -> ark.v1.Tree + 0, // 21: ark.v1.Round.stage:type_name -> ark.v1.RoundStage + 31, // 22: ark.v1.Input.vtxo_input:type_name -> ark.v1.VtxoInput + 32, // 23: ark.v1.Input.boarding_input:type_name -> ark.v1.BoardingInput + 36, // 24: ark.v1.Tree.levels:type_name -> ark.v1.TreeLevel + 37, // 25: ark.v1.TreeLevel.nodes:type_name -> ark.v1.Node + 33, // 26: ark.v1.Vtxo.outpoint:type_name -> ark.v1.Input + 34, // 27: ark.v1.Vtxo.receiver:type_name -> ark.v1.Output + 39, // 28: ark.v1.Vtxo.pending_data:type_name -> ark.v1.PendingPayment + 7, // 29: ark.v1.ArkService.RegisterPayment:input_type -> ark.v1.RegisterPaymentRequest + 9, // 30: ark.v1.ArkService.ClaimPayment:input_type -> ark.v1.ClaimPaymentRequest + 40, // 31: ark.v1.ArkService.SendTreeNonces:input_type -> ark.v1.SendTreeNoncesRequest + 42, // 32: ark.v1.ArkService.SendTreeSignatures:input_type -> ark.v1.SendTreeSignaturesRequest + 11, // 33: ark.v1.ArkService.FinalizePayment:input_type -> ark.v1.FinalizePaymentRequest + 13, // 34: ark.v1.ArkService.GetRound:input_type -> ark.v1.GetRoundRequest + 15, // 35: ark.v1.ArkService.GetRoundById:input_type -> ark.v1.GetRoundByIdRequest + 17, // 36: ark.v1.ArkService.GetEventStream:input_type -> ark.v1.GetEventStreamRequest + 19, // 37: ark.v1.ArkService.Ping:input_type -> ark.v1.PingRequest + 21, // 38: ark.v1.ArkService.ListVtxos:input_type -> ark.v1.ListVtxosRequest + 23, // 39: ark.v1.ArkService.GetInfo:input_type -> ark.v1.GetInfoRequest + 5, // 40: ark.v1.ArkService.GetBoardingAddress:input_type -> ark.v1.GetBoardingAddressRequest + 1, // 41: ark.v1.ArkService.CreatePayment:input_type -> ark.v1.CreatePaymentRequest + 3, // 42: ark.v1.ArkService.CompletePayment:input_type -> ark.v1.CompletePaymentRequest + 8, // 43: ark.v1.ArkService.RegisterPayment:output_type -> ark.v1.RegisterPaymentResponse + 10, // 44: ark.v1.ArkService.ClaimPayment:output_type -> ark.v1.ClaimPaymentResponse + 41, // 45: ark.v1.ArkService.SendTreeNonces:output_type -> ark.v1.SendTreeNoncesResponse + 43, // 46: ark.v1.ArkService.SendTreeSignatures:output_type -> ark.v1.SendTreeSignaturesResponse + 12, // 47: ark.v1.ArkService.FinalizePayment:output_type -> ark.v1.FinalizePaymentResponse + 14, // 48: ark.v1.ArkService.GetRound:output_type -> ark.v1.GetRoundResponse + 16, // 49: ark.v1.ArkService.GetRoundById:output_type -> ark.v1.GetRoundByIdResponse + 18, // 50: ark.v1.ArkService.GetEventStream:output_type -> ark.v1.GetEventStreamResponse + 20, // 51: ark.v1.ArkService.Ping:output_type -> ark.v1.PingResponse + 22, // 52: ark.v1.ArkService.ListVtxos:output_type -> ark.v1.ListVtxosResponse + 24, // 53: ark.v1.ArkService.GetInfo:output_type -> ark.v1.GetInfoResponse + 6, // 54: ark.v1.ArkService.GetBoardingAddress:output_type -> ark.v1.GetBoardingAddressResponse + 2, // 55: ark.v1.ArkService.CreatePayment:output_type -> ark.v1.CreatePaymentResponse + 4, // 56: ark.v1.ArkService.CompletePayment:output_type -> ark.v1.CompletePaymentResponse + 43, // [43:57] is the sub-list for method output_type + 29, // [29:43] is the sub-list for method input_type + 29, // [29:29] is the sub-list for extension type_name + 29, // [29:29] is the sub-list for extension extendee + 0, // [0:29] is the sub-list for field type_name } func init() { file_ark_v1_service_proto_init() } @@ -3021,7 +3210,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RegisterPaymentRequest); i { + switch v := v.(*GetBoardingAddressRequest); i { case 0: return &v.state case 1: @@ -3033,7 +3222,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RegisterPaymentResponse); i { + switch v := v.(*GetBoardingAddressResponse); i { case 0: return &v.state case 1: @@ -3045,7 +3234,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ClaimPaymentRequest); i { + switch v := v.(*RegisterPaymentRequest); i { case 0: return &v.state case 1: @@ -3057,7 +3246,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ClaimPaymentResponse); i { + switch v := v.(*RegisterPaymentResponse); i { case 0: return &v.state case 1: @@ -3069,7 +3258,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FinalizePaymentRequest); i { + switch v := v.(*ClaimPaymentRequest); i { case 0: return &v.state case 1: @@ -3081,7 +3270,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*FinalizePaymentResponse); i { + switch v := v.(*ClaimPaymentResponse); i { case 0: return &v.state case 1: @@ -3093,7 +3282,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetRoundRequest); i { + switch v := v.(*FinalizePaymentRequest); i { case 0: return &v.state case 1: @@ -3105,7 +3294,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetRoundResponse); i { + switch v := v.(*FinalizePaymentResponse); i { case 0: return &v.state case 1: @@ -3117,7 +3306,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetRoundByIdRequest); i { + switch v := v.(*GetRoundRequest); i { case 0: return &v.state case 1: @@ -3129,7 +3318,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetRoundByIdResponse); i { + switch v := v.(*GetRoundResponse); i { case 0: return &v.state case 1: @@ -3141,7 +3330,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetEventStreamRequest); i { + switch v := v.(*GetRoundByIdRequest); i { case 0: return &v.state case 1: @@ -3153,7 +3342,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetEventStreamResponse); i { + switch v := v.(*GetRoundByIdResponse); i { case 0: return &v.state case 1: @@ -3165,7 +3354,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PingRequest); i { + switch v := v.(*GetEventStreamRequest); i { case 0: return &v.state case 1: @@ -3177,7 +3366,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PingResponse); i { + switch v := v.(*GetEventStreamResponse); i { case 0: return &v.state case 1: @@ -3189,7 +3378,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListVtxosRequest); i { + switch v := v.(*PingRequest); i { case 0: return &v.state case 1: @@ -3201,7 +3390,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ListVtxosResponse); i { + switch v := v.(*PingResponse); i { case 0: return &v.state case 1: @@ -3213,7 +3402,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetInfoRequest); i { + switch v := v.(*ListVtxosRequest); i { case 0: return &v.state case 1: @@ -3225,7 +3414,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GetInfoResponse); i { + switch v := v.(*ListVtxosResponse); i { case 0: return &v.state case 1: @@ -3237,7 +3426,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OnboardRequest); i { + switch v := v.(*GetInfoRequest); i { case 0: return &v.state case 1: @@ -3249,7 +3438,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OnboardResponse); i { + switch v := v.(*GetInfoResponse); i { case 0: return &v.state case 1: @@ -3333,7 +3522,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Input); i { + switch v := v.(*VtxoInput); i { case 0: return &v.state case 1: @@ -3345,7 +3534,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Output); i { + switch v := v.(*BoardingInput); i { case 0: return &v.state case 1: @@ -3357,7 +3546,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Tree); i { + switch v := v.(*Input); i { case 0: return &v.state case 1: @@ -3369,7 +3558,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TreeLevel); i { + switch v := v.(*Output); i { case 0: return &v.state case 1: @@ -3381,7 +3570,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Node); i { + switch v := v.(*Tree); i { case 0: return &v.state case 1: @@ -3393,7 +3582,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Vtxo); i { + switch v := v.(*TreeLevel); i { case 0: return &v.state case 1: @@ -3405,7 +3594,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PendingPayment); i { + switch v := v.(*Node); i { case 0: return &v.state case 1: @@ -3417,7 +3606,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SendTreeNoncesRequest); i { + switch v := v.(*Vtxo); i { case 0: return &v.state case 1: @@ -3429,7 +3618,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SendTreeNoncesResponse); i { + switch v := v.(*PendingPayment); i { case 0: return &v.state case 1: @@ -3441,7 +3630,7 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SendTreeSignaturesRequest); i { + switch v := v.(*SendTreeNoncesRequest); i { case 0: return &v.state case 1: @@ -3453,6 +3642,30 @@ func file_ark_v1_service_proto_init() { } } file_ark_v1_service_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendTreeNoncesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ark_v1_service_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SendTreeSignaturesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_ark_v1_service_proto_msgTypes[42].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SendTreeSignaturesResponse); i { case 0: return &v.state @@ -3465,28 +3678,33 @@ func file_ark_v1_service_proto_init() { } } } - file_ark_v1_service_proto_msgTypes[4].OneofWrappers = []interface{}{} - file_ark_v1_service_proto_msgTypes[15].OneofWrappers = []interface{}{ + file_ark_v1_service_proto_msgTypes[6].OneofWrappers = []interface{}{} + file_ark_v1_service_proto_msgTypes[10].OneofWrappers = []interface{}{} + file_ark_v1_service_proto_msgTypes[17].OneofWrappers = []interface{}{ (*GetEventStreamResponse_RoundFinalization)(nil), (*GetEventStreamResponse_RoundFinalized)(nil), (*GetEventStreamResponse_RoundFailed)(nil), (*GetEventStreamResponse_RoundSigning)(nil), (*GetEventStreamResponse_RoundSigningNoncesGenerated)(nil), } - file_ark_v1_service_proto_msgTypes[17].OneofWrappers = []interface{}{ + file_ark_v1_service_proto_msgTypes[19].OneofWrappers = []interface{}{ (*PingResponse_RoundFinalization)(nil), (*PingResponse_RoundFinalized)(nil), (*PingResponse_RoundFailed)(nil), (*PingResponse_RoundSigning)(nil), (*PingResponse_RoundSigningNoncesGenerated)(nil), } + file_ark_v1_service_proto_msgTypes[32].OneofWrappers = []interface{}{ + (*Input_VtxoInput)(nil), + (*Input_BoardingInput)(nil), + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_ark_v1_service_proto_rawDesc, NumEnums: 1, - NumMessages: 41, + NumMessages: 43, NumExtensions: 0, NumServices: 1, }, diff --git a/api-spec/protobuf/gen/ark/v1/service.pb.gw.go b/api-spec/protobuf/gen/ark/v1/service.pb.gw.go index f0d8300..ff89525 100644 --- a/api-spec/protobuf/gen/ark/v1/service.pb.gw.go +++ b/api-spec/protobuf/gen/ark/v1/service.pb.gw.go @@ -404,28 +404,28 @@ func local_request_ArkService_GetInfo_0(ctx context.Context, marshaler runtime.M } -func request_ArkService_Onboard_0(ctx context.Context, marshaler runtime.Marshaler, client ArkServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq OnboardRequest +func request_ArkService_GetBoardingAddress_0(ctx context.Context, marshaler runtime.Marshaler, client ArkServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetBoardingAddressRequest var metadata runtime.ServerMetadata if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := client.Onboard(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + msg, err := client.GetBoardingAddress(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err } -func local_request_ArkService_Onboard_0(ctx context.Context, marshaler runtime.Marshaler, server ArkServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq OnboardRequest +func local_request_ArkService_GetBoardingAddress_0(ctx context.Context, marshaler runtime.Marshaler, server ArkServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetBoardingAddressRequest var metadata runtime.ServerMetadata if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } - msg, err := server.Onboard(ctx, &protoReq) + msg, err := server.GetBoardingAddress(ctx, &protoReq) return msg, metadata, err } @@ -745,7 +745,7 @@ func RegisterArkServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, }) - mux.Handle("POST", pattern_ArkService_Onboard_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("POST", pattern_ArkService_GetBoardingAddress_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -753,12 +753,12 @@ func RegisterArkServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ark.v1.ArkService/Onboard", runtime.WithHTTPPathPattern("/v1/onboard")) + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ark.v1.ArkService/GetBoardingAddress", runtime.WithHTTPPathPattern("/v1/boarding")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_ArkService_Onboard_0(annotatedContext, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_ArkService_GetBoardingAddress_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { @@ -766,7 +766,7 @@ func RegisterArkServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, return } - forward_ArkService_Onboard_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_ArkService_GetBoardingAddress_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -1103,25 +1103,25 @@ func RegisterArkServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, }) - mux.Handle("POST", pattern_ArkService_Onboard_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("POST", pattern_ArkService_GetBoardingAddress_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) var err error var annotatedContext context.Context - annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ark.v1.ArkService/Onboard", runtime.WithHTTPPathPattern("/v1/onboard")) + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ark.v1.ArkService/GetBoardingAddress", runtime.WithHTTPPathPattern("/v1/boarding")) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := request_ArkService_Onboard_0(annotatedContext, inboundMarshaler, client, req, pathParams) + resp, md, err := request_ArkService_GetBoardingAddress_0(annotatedContext, inboundMarshaler, client, req, pathParams) annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_ArkService_Onboard_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_ArkService_GetBoardingAddress_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -1195,7 +1195,7 @@ var ( pattern_ArkService_GetInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "info"}, "")) - pattern_ArkService_Onboard_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "onboard"}, "")) + pattern_ArkService_GetBoardingAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "boarding"}, "")) pattern_ArkService_CreatePayment_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "payment"}, "")) @@ -1225,7 +1225,7 @@ var ( forward_ArkService_GetInfo_0 = runtime.ForwardResponseMessage - forward_ArkService_Onboard_0 = runtime.ForwardResponseMessage + forward_ArkService_GetBoardingAddress_0 = runtime.ForwardResponseMessage forward_ArkService_CreatePayment_0 = runtime.ForwardResponseMessage diff --git a/api-spec/protobuf/gen/ark/v1/service_grpc.pb.go b/api-spec/protobuf/gen/ark/v1/service_grpc.pb.go index 7fddef3..05314c0 100644 --- a/api-spec/protobuf/gen/ark/v1/service_grpc.pb.go +++ b/api-spec/protobuf/gen/ark/v1/service_grpc.pb.go @@ -29,7 +29,7 @@ type ArkServiceClient interface { Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error) ListVtxos(ctx context.Context, in *ListVtxosRequest, opts ...grpc.CallOption) (*ListVtxosResponse, error) GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error) - Onboard(ctx context.Context, in *OnboardRequest, opts ...grpc.CallOption) (*OnboardResponse, error) + GetBoardingAddress(ctx context.Context, in *GetBoardingAddressRequest, opts ...grpc.CallOption) (*GetBoardingAddressResponse, error) CreatePayment(ctx context.Context, in *CreatePaymentRequest, opts ...grpc.CallOption) (*CreatePaymentResponse, error) CompletePayment(ctx context.Context, in *CompletePaymentRequest, opts ...grpc.CallOption) (*CompletePaymentResponse, error) } @@ -164,9 +164,9 @@ func (c *arkServiceClient) GetInfo(ctx context.Context, in *GetInfoRequest, opts return out, nil } -func (c *arkServiceClient) Onboard(ctx context.Context, in *OnboardRequest, opts ...grpc.CallOption) (*OnboardResponse, error) { - out := new(OnboardResponse) - err := c.cc.Invoke(ctx, "/ark.v1.ArkService/Onboard", in, out, opts...) +func (c *arkServiceClient) GetBoardingAddress(ctx context.Context, in *GetBoardingAddressRequest, opts ...grpc.CallOption) (*GetBoardingAddressResponse, error) { + out := new(GetBoardingAddressResponse) + err := c.cc.Invoke(ctx, "/ark.v1.ArkService/GetBoardingAddress", in, out, opts...) if err != nil { return nil, err } @@ -206,7 +206,7 @@ type ArkServiceServer interface { Ping(context.Context, *PingRequest) (*PingResponse, error) ListVtxos(context.Context, *ListVtxosRequest) (*ListVtxosResponse, error) GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error) - Onboard(context.Context, *OnboardRequest) (*OnboardResponse, error) + GetBoardingAddress(context.Context, *GetBoardingAddressRequest) (*GetBoardingAddressResponse, error) CreatePayment(context.Context, *CreatePaymentRequest) (*CreatePaymentResponse, error) CompletePayment(context.Context, *CompletePaymentRequest) (*CompletePaymentResponse, error) } @@ -248,8 +248,8 @@ func (UnimplementedArkServiceServer) ListVtxos(context.Context, *ListVtxosReques func (UnimplementedArkServiceServer) GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetInfo not implemented") } -func (UnimplementedArkServiceServer) Onboard(context.Context, *OnboardRequest) (*OnboardResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method Onboard not implemented") +func (UnimplementedArkServiceServer) GetBoardingAddress(context.Context, *GetBoardingAddressRequest) (*GetBoardingAddressResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetBoardingAddress not implemented") } func (UnimplementedArkServiceServer) CreatePayment(context.Context, *CreatePaymentRequest) (*CreatePaymentResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method CreatePayment not implemented") @@ -470,20 +470,20 @@ func _ArkService_GetInfo_Handler(srv interface{}, ctx context.Context, dec func( return interceptor(ctx, in, info, handler) } -func _ArkService_Onboard_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(OnboardRequest) +func _ArkService_GetBoardingAddress_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetBoardingAddressRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(ArkServiceServer).Onboard(ctx, in) + return srv.(ArkServiceServer).GetBoardingAddress(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/ark.v1.ArkService/Onboard", + FullMethod: "/ark.v1.ArkService/GetBoardingAddress", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ArkServiceServer).Onboard(ctx, req.(*OnboardRequest)) + return srv.(ArkServiceServer).GetBoardingAddress(ctx, req.(*GetBoardingAddressRequest)) } return interceptor(ctx, in, info, handler) } @@ -572,8 +572,8 @@ var ArkService_ServiceDesc = grpc.ServiceDesc{ Handler: _ArkService_GetInfo_Handler, }, { - MethodName: "Onboard", - Handler: _ArkService_Onboard_Handler, + MethodName: "GetBoardingAddress", + Handler: _ArkService_GetBoardingAddress_Handler, }, { MethodName: "CreatePayment", diff --git a/client/covenant/balance.go b/client/covenant/balance.go index 01ddcdc..73e81c7 100644 --- a/client/covenant/balance.go +++ b/client/covenant/balance.go @@ -9,6 +9,7 @@ import ( arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1" "github.com/ark-network/ark/client/flags" "github.com/ark-network/ark/client/utils" + "github.com/ark-network/ark/common/descriptor" "github.com/urfave/cli/v2" ) @@ -21,18 +22,30 @@ func (*covenantLiquidCLI) Balance(ctx *cli.Context) error { } defer cancel() - offchainAddr, onchainAddr, redemptionAddr, err := getAddress(ctx) - if err != nil { - return err - } - network, err := utils.GetNetwork(ctx) + offchainAddr, boardingAddr, redemptionAddr, err := getAddress(ctx) if err != nil { return err } + // No need to check for error here becuase this function is called also by getAddress(). // nolint:all unilateralExitDelay, _ := utils.GetUnilateralExitDelay(ctx) + boardingDescriptor, err := utils.GetBoardingDescriptor(ctx) + if err != nil { + return err + } + + desc, err := descriptor.ParseTaprootDescriptor(boardingDescriptor) + if err != nil { + return err + } + + _, timeoutBoarding, err := descriptor.ParseBoardingDescriptor(*desc) + if err != nil { + return err + } + wg := &sync.WaitGroup{} wg.Add(3) @@ -54,19 +67,19 @@ func (*covenantLiquidCLI) Balance(ctx *cli.Context) error { go func() { defer wg.Done() explorer := utils.NewExplorer(ctx) - balance, err := explorer.GetBalance(onchainAddr, toElementsNetwork(network).AssetID) + spendableBalance, lockedBalance, err := explorer.GetDelayedBalance(boardingAddr, int64(timeoutBoarding)) if err != nil { chRes <- balanceRes{0, 0, nil, nil, err} return } - chRes <- balanceRes{0, balance, nil, nil, nil} + chRes <- balanceRes{0, spendableBalance, lockedBalance, nil, nil} }() go func() { defer wg.Done() explorer := utils.NewExplorer(ctx) - spendableBalance, lockedBalance, err := explorer.GetRedeemedVtxosBalance( + spendableBalance, lockedBalance, err := explorer.GetDelayedBalance( redemptionAddr, unilateralExitDelay, ) if err != nil { diff --git a/client/covenant/claim.go b/client/covenant/claim.go new file mode 100644 index 0000000..36d82f2 --- /dev/null +++ b/client/covenant/claim.go @@ -0,0 +1,148 @@ +package covenant + +import ( + "fmt" + "time" + + arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1" + "github.com/ark-network/ark/client/utils" + "github.com/ark-network/ark/common/descriptor" + "github.com/urfave/cli/v2" +) + +func (c *covenantLiquidCLI) Claim(ctx *cli.Context) error { + client, cancel, err := getClientFromState(ctx) + if err != nil { + return err + } + defer cancel() + + offchainAddr, boardingAddr, _, err := getAddress(ctx) + if err != nil { + return err + } + + boardingDescriptor, err := utils.GetBoardingDescriptor(ctx) + if err != nil { + return err + } + + desc, err := descriptor.ParseTaprootDescriptor(boardingDescriptor) + if err != nil { + return err + } + + _, timeoutBoarding, err := descriptor.ParseBoardingDescriptor(*desc) + if err != nil { + return err + } + + explorer := utils.NewExplorer(ctx) + + boardingUtxosFromExplorer, err := explorer.GetUtxos(boardingAddr) + if err != nil { + return err + } + + now := time.Now() + boardingUtxos := make([]utils.Utxo, 0, len(boardingUtxosFromExplorer)) + for _, utxo := range boardingUtxosFromExplorer { + u := utils.NewUtxo(utxo, uint(timeoutBoarding)) + if u.SpendableAt.Before(now) { + continue // cannot claim if onchain spendable + } + + boardingUtxos = append(boardingUtxos, u) + } + + var pendingBalance uint64 + + for _, utxo := range boardingUtxos { + pendingBalance += utxo.Amount + } + + if pendingBalance == 0 { + return fmt.Errorf("no boarding utxos to claim") + } + + receiver := receiver{ + To: offchainAddr, + Amount: pendingBalance, + } + + if len(ctx.String("password")) == 0 { + if ok := askForConfirmation( + fmt.Sprintf( + "claim %d satoshis from %d boarding utxos", + pendingBalance, len(boardingUtxos), + ), + ); !ok { + return nil + } + } + + return selfTransferAllPendingPayments( + ctx, client, boardingUtxos, receiver, boardingDescriptor, + ) +} + +func selfTransferAllPendingPayments( + ctx *cli.Context, + client arkv1.ArkServiceClient, + boardingUtxos []utils.Utxo, + myself receiver, + desc string, +) error { + inputs := make([]*arkv1.Input, 0, len(boardingUtxos)) + + for _, outpoint := range boardingUtxos { + inputs = append(inputs, &arkv1.Input{ + Input: &arkv1.Input_BoardingInput{ + BoardingInput: &arkv1.BoardingInput{ + Txid: outpoint.Txid, + Vout: outpoint.Vout, + Descriptor_: desc, + }, + }, + }) + } + + receiversOutput := []*arkv1.Output{ + { + Address: myself.To, + Amount: myself.Amount, + }, + } + + secKey, err := utils.PrivateKeyFromPassword(ctx) + if err != nil { + return err + } + + registerResponse, err := client.RegisterPayment( + ctx.Context, &arkv1.RegisterPaymentRequest{Inputs: inputs}, + ) + if err != nil { + return err + } + + _, err = client.ClaimPayment(ctx.Context, &arkv1.ClaimPaymentRequest{ + Id: registerResponse.GetId(), + Outputs: []*arkv1.Output{{Address: myself.To, Amount: myself.Amount}}, + }) + if err != nil { + return err + } + + poolTxID, err := handleRoundStream( + ctx, client, registerResponse.GetId(), make([]vtxo, 0), + len(boardingUtxos) > 0, secKey, receiversOutput, + ) + if err != nil { + return err + } + + return utils.PrintJSON(map[string]interface{}{ + "pool_txid": poolTxID, + }) +} diff --git a/client/covenant/cli.go b/client/covenant/cli.go index 79d780a..9c6c77b 100644 --- a/client/covenant/cli.go +++ b/client/covenant/cli.go @@ -9,16 +9,13 @@ import ( "github.com/ark-network/ark/client/interfaces" "github.com/ark-network/ark/client/utils" "github.com/ark-network/ark/common" + "github.com/ark-network/ark/common/descriptor" "github.com/ark-network/ark/common/tree" "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/urfave/cli/v2" "github.com/vulpemventures/go-elements/address" - "github.com/vulpemventures/go-elements/elementsutil" - "github.com/vulpemventures/go-elements/network" "github.com/vulpemventures/go-elements/payment" "github.com/vulpemventures/go-elements/psetv2" - "github.com/vulpemventures/go-elements/taproot" - "github.com/vulpemventures/go-elements/transaction" ) const dust = 450 @@ -29,19 +26,15 @@ func (c *covenantLiquidCLI) SendAsync(ctx *cli.Context) error { return fmt.Errorf("not implemented") } -func (c *covenantLiquidCLI) ClaimAsync(ctx *cli.Context) error { - return fmt.Errorf("not implemented") -} - func (c *covenantLiquidCLI) Receive(ctx *cli.Context) error { - offchainAddr, onchainAddr, _, err := getAddress(ctx) + offchainAddr, boardingAddr, _, err := getAddress(ctx) if err != nil { return err } return utils.PrintJSON(map[string]interface{}{ "offchain_address": offchainAddr, - "onchain_address": onchainAddr, + "boarding_address": boardingAddr, }) } @@ -131,14 +124,14 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) { explorer := utils.NewExplorer(ctx) - utxos, delayedUtxos, change, err := coinSelectOnchain( + utxos, change, err := coinSelectOnchain( ctx, explorer, targetAmount, nil, ) if err != nil { return "", err } - if err := addInputs(ctx, updater, utxos, delayedUtxos, &liquidNet); err != nil { + if err := addInputs(ctx, updater, utxos); err != nil { return "", err } @@ -181,14 +174,14 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) { updater.Pset.Outputs = updater.Pset.Outputs[:len(updater.Pset.Outputs)-1] } // reselect the difference - selected, delayedSelected, newChange, err := coinSelectOnchain( - ctx, explorer, feeAmount-change, append(utxos, delayedUtxos...), + selected, newChange, err := coinSelectOnchain( + ctx, explorer, feeAmount-change, utxos, ) if err != nil { return "", err } - if err := addInputs(ctx, updater, selected, delayedSelected, &liquidNet); err != nil { + if err := addInputs(ctx, updater, selected); err != nil { return "", err } @@ -243,20 +236,37 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) { func coinSelectOnchain( ctx *cli.Context, explorer utils.Explorer, targetAmount uint64, exclude []utils.Utxo, -) ([]utils.Utxo, []utils.Utxo, uint64, error) { - _, onchainAddr, _, err := getAddress(ctx) +) ([]utils.Utxo, uint64, error) { + _, boardingAddr, redemptionAddr, err := getAddress(ctx) if err != nil { - return nil, nil, 0, err + return nil, 0, err } - fromExplorer, err := explorer.GetUtxos(onchainAddr) + boardingUtxosFromExplorer, err := explorer.GetUtxos(boardingAddr) if err != nil { - return nil, nil, 0, err + return nil, 0, err } utxos := make([]utils.Utxo, 0) selectedAmount := uint64(0) - for _, utxo := range fromExplorer { + now := time.Now() + + boardingDescriptor, err := utils.GetBoardingDescriptor(ctx) + if err != nil { + return nil, 0, err + } + + desc, err := descriptor.ParseTaprootDescriptor(boardingDescriptor) + if err != nil { + return nil, 0, err + } + + _, timeoutBoarding, err := descriptor.ParseBoardingDescriptor(*desc) + if err != nil { + return nil, 0, err + } + + for _, utxo := range boardingUtxosFromExplorer { if selectedAmount >= targetAmount { break } @@ -267,207 +277,99 @@ func coinSelectOnchain( } } - utxos = append(utxos, utxo) - selectedAmount += utxo.Amount + utxo := utils.NewUtxo(utxo, uint(timeoutBoarding)) + + if utxo.SpendableAt.Before(now) { + utxos = append(utxos, utxo) + selectedAmount += utxo.Amount + } } if selectedAmount >= targetAmount { - return utxos, nil, selectedAmount - targetAmount, nil + return utxos, selectedAmount - targetAmount, nil } - userPubkey, err := utils.GetWalletPublicKey(ctx) + redemptionUtxosFromExplorer, err := explorer.GetUtxos(redemptionAddr) if err != nil { - return nil, nil, 0, err + return nil, 0, err } - aspPubkey, err := utils.GetAspPublicKey(ctx) + vtxoExitDelay, err := utils.GetUnilateralExitDelay(ctx) if err != nil { - return nil, nil, 0, err + return nil, 0, err } - unilateralExitDelay, err := utils.GetUnilateralExitDelay(ctx) - if err != nil { - return nil, nil, 0, err - } - - vtxoTapKey, _, err := computeVtxoTaprootScript( - userPubkey, aspPubkey, uint(unilateralExitDelay), - ) - if err != nil { - return nil, nil, 0, err - } - - net, err := utils.GetNetwork(ctx) - if err != nil { - return nil, nil, 0, err - } - - liquidNet := toElementsNetwork(net) - - pay, err := payment.FromTweakedKey(vtxoTapKey, &liquidNet, nil) - if err != nil { - return nil, nil, 0, err - } - - addr, err := pay.TaprootAddress() - if err != nil { - return nil, nil, 0, err - } - - fromExplorer, err = explorer.GetUtxos(addr) - if err != nil { - return nil, nil, 0, err - } - - delayedUtxos := make([]utils.Utxo, 0) - for _, utxo := range fromExplorer { + for _, utxo := range redemptionUtxosFromExplorer { if selectedAmount >= targetAmount { break } - availableAt := time.Unix(utxo.Status.Blocktime, 0).Add( - time.Duration(unilateralExitDelay) * time.Second, - ) - if availableAt.After(time.Now()) { - continue - } - for _, excluded := range exclude { if utxo.Txid == excluded.Txid && utxo.Vout == excluded.Vout { continue } } - delayedUtxos = append(delayedUtxos, utxo) - selectedAmount += utxo.Amount + utxo := utils.NewUtxo(utxo, uint(vtxoExitDelay)) + + if utxo.SpendableAt.Before(now) { + utxos = append(utxos, utxo) + selectedAmount += utxo.Amount + } } if selectedAmount < targetAmount { - return nil, nil, 0, fmt.Errorf( + return nil, 0, fmt.Errorf( "not enough funds to cover amount %d", targetAmount, ) } - return utxos, delayedUtxos, selectedAmount - targetAmount, nil + return utxos, selectedAmount - targetAmount, nil } func addInputs( ctx *cli.Context, - updater *psetv2.Updater, utxos, delayedUtxos []utils.Utxo, net *network.Network, + updater *psetv2.Updater, + utxos []utils.Utxo, ) error { - _, onchainAddr, _, err := getAddress(ctx) + userPubkey, err := utils.GetWalletPublicKey(ctx) if err != nil { return err } - changeScript, err := address.ToOutputScript(onchainAddr) + aspPubkey, err := utils.GetAspPublicKey(ctx) if err != nil { return err } for _, utxo := range utxos { + sequence, err := utxo.Sequence() + if err != nil { + return err + } + if err := updater.AddInputs([]psetv2.InputArgs{ { - Txid: utxo.Txid, - TxIndex: utxo.Vout, + Txid: utxo.Txid, + TxIndex: utxo.Vout, + Sequence: sequence, }, }); err != nil { return err } - assetID, err := elementsutil.AssetHashToBytes(utxo.Asset) - if err != nil { - return err - } - - value, err := elementsutil.ValueToBytes(utxo.Amount) - if err != nil { - return err - } - - witnessUtxo := transaction.TxOutput{ - Asset: assetID, - Value: value, - Script: changeScript, - Nonce: []byte{0x00}, - } - - if err := updater.AddInWitnessUtxo( - len(updater.Pset.Inputs)-1, &witnessUtxo, - ); err != nil { - return err - } - } - - if len(delayedUtxos) > 0 { - userPubkey, err := utils.GetWalletPublicKey(ctx) - if err != nil { - return err - } - - aspPubkey, err := utils.GetAspPublicKey(ctx) - if err != nil { - return err - } - - unilateralExitDelay, err := utils.GetUnilateralExitDelay(ctx) - if err != nil { - return err - } - - vtxoTapKey, leafProof, err := computeVtxoTaprootScript( - userPubkey, aspPubkey, uint(unilateralExitDelay), + _, leafProof, err := computeVtxoTaprootScript( + userPubkey, aspPubkey, utxo.Delay, ) if err != nil { return err } - pay, err := payment.FromTweakedKey(vtxoTapKey, net, nil) - if err != nil { + inputIndex := len(updater.Pset.Inputs) - 1 + + if err := updater.AddInTapLeafScript(inputIndex, psetv2.NewTapLeafScript(*leafProof, tree.UnspendableKey())); err != nil { return err } - - addr, err := pay.TaprootAddress() - if err != nil { - return err - } - - script, err := address.ToOutputScript(addr) - if err != nil { - return err - } - - for _, utxo := range delayedUtxos { - if err := addVtxoInput( - updater, - psetv2.InputArgs{ - Txid: utxo.Txid, - TxIndex: utxo.Vout, - }, - uint(unilateralExitDelay), - leafProof, - ); err != nil { - return err - } - - assetID, err := elementsutil.AssetHashToBytes(utxo.Asset) - if err != nil { - return err - } - - value, err := elementsutil.ValueToBytes(utxo.Amount) - if err != nil { - return err - } - - witnessUtxo := transaction.NewTxOutput(assetID, value, script) - - if err := updater.AddInWitnessUtxo( - len(updater.Pset.Inputs)-1, witnessUtxo, - ); err != nil { - return err - } - } } return nil @@ -503,32 +405,7 @@ func decodeReceiverAddress(addr string) ( return true, outputScript, nil, nil } -func addVtxoInput( - updater *psetv2.Updater, inputArgs psetv2.InputArgs, exitDelay uint, - tapLeafProof *taproot.TapscriptElementsProof, -) error { - sequence, err := common.BIP68EncodeAsNumber(exitDelay) - if err != nil { - return nil - } - - nextInputIndex := len(updater.Pset.Inputs) - if err := updater.AddInputs([]psetv2.InputArgs{inputArgs}); err != nil { - return err - } - - updater.Pset.Inputs[nextInputIndex].Sequence = sequence - - return updater.AddInTapLeafScript( - nextInputIndex, - psetv2.NewTapLeafScript( - *tapLeafProof, - tree.UnspendableKey(), - ), - ) -} - -func getAddress(ctx *cli.Context) (offchainAddr, onchainAddr, redemptionAddr string, err error) { +func getAddress(ctx *cli.Context) (offchainAddr, boardingAddr, redemptionAddr string, err error) { userPubkey, err := utils.GetWalletPublicKey(ctx) if err != nil { return @@ -544,6 +421,21 @@ func getAddress(ctx *cli.Context) (offchainAddr, onchainAddr, redemptionAddr str return } + boardingDescriptor, err := utils.GetBoardingDescriptor(ctx) + if err != nil { + return + } + + desc, err := descriptor.ParseTaprootDescriptor(boardingDescriptor) + if err != nil { + return + } + + _, timeoutBoarding, err := descriptor.ParseBoardingDescriptor(*desc) + if err != nil { + return + } + arkNet, err := utils.GetNetwork(ctx) if err != nil { return @@ -556,12 +448,6 @@ func getAddress(ctx *cli.Context) (offchainAddr, onchainAddr, redemptionAddr str liquidNet := toElementsNetwork(arkNet) - p2wpkh := payment.FromPublicKey(userPubkey, &liquidNet, nil) - liquidAddr, err := p2wpkh.WitnessPubKeyHash() - if err != nil { - return - } - vtxoTapKey, _, err := computeVtxoTaprootScript( userPubkey, aspPubkey, uint(unilateralExitDelay), ) @@ -569,18 +455,34 @@ func getAddress(ctx *cli.Context) (offchainAddr, onchainAddr, redemptionAddr str return } - payment, err := payment.FromTweakedKey(vtxoTapKey, &liquidNet, nil) + redemptionPay, err := payment.FromTweakedKey(vtxoTapKey, &liquidNet, nil) if err != nil { return } - redemptionAddr, err = payment.TaprootAddress() + redemptionAddr, err = redemptionPay.TaprootAddress() + if err != nil { + return + } + + boardingTapKey, _, err := computeVtxoTaprootScript( + userPubkey, aspPubkey, uint(timeoutBoarding), + ) + if err != nil { + return + } + + boardingPay, err := payment.FromTweakedKey(boardingTapKey, &liquidNet, nil) + if err != nil { + return + } + + boardingAddr, err = boardingPay.TaprootAddress() if err != nil { return } offchainAddr = arkAddr - onchainAddr = liquidAddr return } diff --git a/client/covenant/client.go b/client/covenant/client.go index 923043c..529f797 100644 --- a/client/covenant/client.go +++ b/client/covenant/client.go @@ -51,13 +51,16 @@ func getVtxos( if v.Swept { continue } - vtxos = append(vtxos, vtxo{ - amount: v.Receiver.Amount, - txid: v.Outpoint.Txid, - vout: v.Outpoint.Vout, - poolTxid: v.PoolTxid, - expireAt: expireAt, - }) + + if v.Outpoint.GetVtxoInput() != nil { + vtxos = append(vtxos, vtxo{ + amount: v.Receiver.Amount, + txid: v.Outpoint.GetVtxoInput().GetTxid(), + vout: v.Outpoint.GetVtxoInput().GetVout(), + poolTxid: v.PoolTxid, + expireAt: expireAt, + }) + } } if !computeExpiration { @@ -193,32 +196,10 @@ func toCongestionTree(treeFromProto *arkv1.Tree) (tree.CongestionTree, error) { return levels, nil } -// castCongestionTree converts a tree.CongestionTree to a repeated arkv1.TreeLevel -func castCongestionTree(congestionTree tree.CongestionTree) *arkv1.Tree { - levels := make([]*arkv1.TreeLevel, 0, len(congestionTree)) - for _, level := range congestionTree { - levelProto := &arkv1.TreeLevel{ - Nodes: make([]*arkv1.Node, 0, len(level)), - } - - for _, node := range level { - levelProto.Nodes = append(levelProto.Nodes, &arkv1.Node{ - Txid: node.Txid, - Tx: node.Tx, - ParentTxid: node.ParentTxid, - }) - } - - levels = append(levels, levelProto) - } - return &arkv1.Tree{ - Levels: levels, - } -} - func handleRoundStream( ctx *cli.Context, client arkv1.ArkServiceClient, paymentID string, - vtxosToSign []vtxo, secKey *secp256k1.PrivateKey, receivers []*arkv1.Output, + vtxosToSign []vtxo, mustSignRoundTx bool, + secKey *secp256k1.PrivateKey, receivers []*arkv1.Output, ) (poolTxID string, err error) { stream, err := client.GetEventStream(ctx.Context, &arkv1.GetEventStreamRequest{}) if err != nil { @@ -254,8 +235,8 @@ func handleRoundStream( pingStop() fmt.Println("round finalization started") - poolTx := e.GetPoolTx() - ptx, err := psetv2.NewPsetFromBase64(poolTx) + roundTx := e.GetPoolTx() + ptx, err := psetv2.NewPsetFromBase64(roundTx) if err != nil { return "", err } @@ -280,13 +261,13 @@ func handleRoundStream( if !isOnchainOnly(receivers) { // validate the congestion tree if err := tree.ValidateCongestionTree( - congestionTree, poolTx, aspPubkey, int64(roundLifetime), + congestionTree, roundTx, aspPubkey, int64(roundLifetime), ); err != nil { return "", err } } - if err := common.ValidateConnectors(poolTx, connectors); err != nil { + if err := common.ValidateConnectors(roundTx, connectors); err != nil { return "", err } @@ -379,78 +360,100 @@ func handleRoundStream( fmt.Println("congestion tree validated") - forfeits := e.GetForfeitTxs() - signedForfeits := make([]string, 0) - - fmt.Print("signing forfeit txs... ") - explorer := utils.NewExplorer(ctx) + finalizePaymentRequest := &arkv1.FinalizePaymentRequest{} - connectorsTxids := make([]string, 0, len(connectors)) - for _, connector := range connectors { - p, _ := psetv2.NewPsetFromBase64(connector) - utx, _ := p.UnsignedTx() - txid := utx.TxHash().String() + if len(vtxosToSign) > 0 { + forfeits := e.GetForfeitTxs() + signedForfeits := make([]string, 0) - connectorsTxids = append(connectorsTxids, txid) + fmt.Print("signing forfeit txs... ") + + connectorsTxids := make([]string, 0, len(connectors)) + for _, connector := range connectors { + p, _ := psetv2.NewPsetFromBase64(connector) + utx, _ := p.UnsignedTx() + txid := utx.TxHash().String() + + connectorsTxids = append(connectorsTxids, txid) + } + + for _, forfeit := range forfeits { + pset, err := psetv2.NewPsetFromBase64(forfeit) + if err != nil { + return "", err + } + + for _, input := range pset.Inputs { + inputTxid := chainhash.Hash(input.PreviousTxid).String() + + for _, coin := range vtxosToSign { + // check if it contains one of the input to sign + if inputTxid == coin.txid { + // verify that the connector is in the connectors list + connectorTxid := chainhash.Hash(pset.Inputs[0].PreviousTxid).String() + connectorFound := false + for _, txid := range connectorsTxids { + if txid == connectorTxid { + connectorFound = true + break + } + } + + if !connectorFound { + return "", fmt.Errorf("connector txid %s not found in the connectors list", connectorTxid) + } + + if err := signPset(ctx, pset, explorer, secKey); err != nil { + return "", err + } + + signedPset, err := pset.ToBase64() + if err != nil { + return "", err + } + + signedForfeits = append(signedForfeits, signedPset) + } + } + } + } + + // if no forfeit txs have been signed, start pinging again and wait for the next round + if len(signedForfeits) == 0 { + fmt.Printf("\nno forfeit txs to sign, waiting for the next round...\n") + pingStop = nil + for pingStop == nil { + pingStop = ping(ctx.Context, client, pingReq) + } + continue + } + + fmt.Printf("%d signed\n", len(signedForfeits)) + finalizePaymentRequest.SignedForfeitTxs = signedForfeits } - for _, forfeit := range forfeits { - pset, err := psetv2.NewPsetFromBase64(forfeit) + if mustSignRoundTx { + ptx, err := psetv2.NewPsetFromBase64(roundTx) if err != nil { return "", err } - for _, input := range pset.Inputs { - inputTxid := chainhash.Hash(input.PreviousTxid).String() - - for _, coin := range vtxosToSign { - // check if it contains one of the input to sign - if inputTxid == coin.txid { - // verify that the connector is in the connectors list - connectorTxid := chainhash.Hash(pset.Inputs[0].PreviousTxid).String() - connectorFound := false - for _, txid := range connectorsTxids { - if txid == connectorTxid { - connectorFound = true - break - } - } - - if !connectorFound { - return "", fmt.Errorf("connector txid %s not found in the connectors list", connectorTxid) - } - - if err := signPset(ctx, pset, explorer, secKey); err != nil { - return "", err - } - - signedPset, err := pset.ToBase64() - if err != nil { - return "", err - } - - signedForfeits = append(signedForfeits, signedPset) - } - } + if err := signPset(ctx, ptx, explorer, secKey); err != nil { + return "", err } + + signedRoundTx, err := ptx.ToBase64() + if err != nil { + return "", err + } + + fmt.Println("round tx signed") + finalizePaymentRequest.SignedRoundTx = &signedRoundTx } - // if no forfeit txs have been signed, start pinging again and wait for the next round - if len(signedForfeits) == 0 { - fmt.Printf("\nno forfeit txs to sign, waiting for the next round...\n") - pingStop = nil - for pingStop == nil { - pingStop = ping(ctx.Context, client, pingReq) - } - continue - } - - fmt.Printf("%d signed\n", len(signedForfeits)) fmt.Print("finalizing payment... ") - _, err = client.FinalizePayment(ctx.Context, &arkv1.FinalizePaymentRequest{ - SignedForfeitTxs: signedForfeits, - }) + _, err = client.FinalizePayment(ctx.Context, finalizePaymentRequest) if err != nil { return "", err } diff --git a/client/covenant/init.go b/client/covenant/init.go index d1d6aac..e63c44f 100644 --- a/client/covenant/init.go +++ b/client/covenant/init.go @@ -104,6 +104,7 @@ func connectToAsp(ctx *cli.Context, net, url, explorer string) error { utils.ROUND_LIFETIME: strconv.Itoa(int(resp.GetRoundLifetime())), utils.UNILATERAL_EXIT_DELAY: strconv.Itoa(int(resp.GetUnilateralExitDelay())), utils.EXPLORER: explorer, + utils.BOARDING_TEMPLATE: resp.GetBoardingDescriptorTemplate(), }) } diff --git a/client/covenant/onboard.go b/client/covenant/onboard.go deleted file mode 100644 index c247140..0000000 --- a/client/covenant/onboard.go +++ /dev/null @@ -1,115 +0,0 @@ -package covenant - -import ( - "encoding/hex" - "fmt" - - arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1" - "github.com/ark-network/ark/client/utils" - "github.com/ark-network/ark/common/tree" - "github.com/urfave/cli/v2" - "github.com/vulpemventures/go-elements/payment" - "github.com/vulpemventures/go-elements/psetv2" -) - -const minRelayFee = 30 - -func (c *covenantLiquidCLI) Onboard(ctx *cli.Context) error { - amount := ctx.Uint64("amount") - - if amount <= 0 { - return fmt.Errorf("missing amount flag (--amount)") - } - - net, err := utils.GetNetwork(ctx) - if err != nil { - return err - } - - userPubKey, err := utils.GetWalletPublicKey(ctx) - if err != nil { - return err - } - - client, cancel, err := getClientFromState(ctx) - if err != nil { - return err - } - defer cancel() - - aspPubkey, err := utils.GetAspPublicKey(ctx) - if err != nil { - return err - } - - roundLifetime, err := utils.GetRoundLifetime(ctx) - if err != nil { - return err - } - - unilateralExitDelay, err := utils.GetUnilateralExitDelay(ctx) - if err != nil { - return err - } - - congestionTreeLeaf := tree.Receiver{ - Pubkey: hex.EncodeToString(userPubKey.SerializeCompressed()), - Amount: amount, - } - - liquidNet := toElementsNetwork(net) - - treeFactoryFn, sharedOutputScript, sharedOutputAmount, err := tree.CraftCongestionTree( - liquidNet.AssetID, aspPubkey, []tree.Receiver{congestionTreeLeaf}, - minRelayFee, roundLifetime, unilateralExitDelay, - ) - if err != nil { - return err - } - - pay, err := payment.FromScript(sharedOutputScript, &liquidNet, nil) - if err != nil { - return err - } - - address, err := pay.TaprootAddress() - if err != nil { - return err - } - - onchainReceiver := receiver{ - To: address, - Amount: sharedOutputAmount, - } - - pset, err := sendOnchain(ctx, []receiver{onchainReceiver}) - if err != nil { - return err - } - - ptx, _ := psetv2.NewPsetFromBase64(pset) - utx, _ := ptx.UnsignedTx() - txid := utx.TxHash().String() - - congestionTree, err := treeFactoryFn(psetv2.InputArgs{ - Txid: txid, - TxIndex: 0, - }) - - if err != nil { - return err - } - - _, err = client.Onboard(ctx.Context, &arkv1.OnboardRequest{ - BoardingTx: pset, - CongestionTree: castCongestionTree(congestionTree), - UserPubkey: hex.EncodeToString(userPubKey.SerializeCompressed()), - }) - if err != nil { - return err - } - - fmt.Println("onboard_txid:", txid) - - return nil -} diff --git a/client/covenant/redeem.go b/client/covenant/redeem.go index 3a3ebb8..f0bdfbe 100644 --- a/client/covenant/redeem.go +++ b/client/covenant/redeem.go @@ -78,8 +78,12 @@ func collaborativeRedeem( for _, coin := range selectedCoins { inputs = append(inputs, &arkv1.Input{ - Txid: coin.txid, - Vout: coin.vout, + Input: &arkv1.Input_VtxoInput{ + VtxoInput: &arkv1.VtxoInput{ + Txid: coin.txid, + Vout: coin.vout, + }, + }, }) } @@ -108,6 +112,7 @@ func collaborativeRedeem( client, registerResponse.GetId(), selectedCoins, + false, secKey, receivers, ) diff --git a/client/covenant/send.go b/client/covenant/send.go index 5900175..9111a18 100644 --- a/client/covenant/send.go +++ b/client/covenant/send.go @@ -143,8 +143,12 @@ func sendOffchain(ctx *cli.Context, receivers []receiver) error { for _, coin := range selectedCoins { inputs = append(inputs, &arkv1.Input{ - Txid: coin.txid, - Vout: coin.vout, + Input: &arkv1.Input_VtxoInput{ + VtxoInput: &arkv1.VtxoInput{ + Txid: coin.txid, + Vout: coin.vout, + }, + }, }) } @@ -170,7 +174,7 @@ func sendOffchain(ctx *cli.Context, receivers []receiver) error { poolTxID, err := handleRoundStream( ctx, client, registerResponse.GetId(), - selectedCoins, secKey, receiversOutput, + selectedCoins, false, secKey, receiversOutput, ) if err != nil { return err diff --git a/client/covenant/signer.go b/client/covenant/signer.go index 8820dea..33fdc79 100644 --- a/client/covenant/signer.go +++ b/client/covenant/signer.go @@ -6,14 +6,11 @@ import ( "github.com/ark-network/ark/client/utils" "github.com/ark-network/ark/common/tree" - "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/urfave/cli/v2" - "github.com/vulpemventures/go-elements/address" - "github.com/vulpemventures/go-elements/payment" "github.com/vulpemventures/go-elements/psetv2" "github.com/vulpemventures/go-elements/transaction" ) @@ -50,13 +47,7 @@ func signPset( return err } - sighashType := txscript.SigHashAll - - if utxo.Script[0] == txscript.OP_1 { - sighashType = txscript.SigHashDefault - } - - if err := updater.AddInSighashType(i, sighashType); err != nil { + if err := updater.AddInSighashType(i, txscript.SigHashDefault); err != nil { return err } } @@ -66,16 +57,6 @@ func signPset( return err } - _, onchainAddr, _, err := getAddress(ctx) - if err != nil { - return err - } - - onchainWalletScript, err := address.ToOutputScript(onchainAddr) - if err != nil { - return err - } - utx, err := pset.UnsignedTx() if err != nil { return err @@ -99,34 +80,6 @@ func signPset( liquidNet := toElementsNetwork(net) for i, input := range pset.Inputs { - if bytes.Equal(input.WitnessUtxo.Script, onchainWalletScript) { - p, err := payment.FromScript(input.WitnessUtxo.Script, &liquidNet, nil) - if err != nil { - return err - } - - preimage := utx.HashForWitnessV0( - i, - p.Script, - input.WitnessUtxo.Value, - txscript.SigHashAll, - ) - - sig := ecdsa.Sign( - prvKey, - preimage[:], - ) - - signatureWithSighashType := append(sig.Serialize(), byte(txscript.SigHashAll)) - - err = signer.SignInput(i, signatureWithSighashType, prvKey.PubKey().SerializeCompressed(), nil, nil) - if err != nil { - fmt.Println("error signing input: ", err) - return err - } - continue - } - if len(input.TapLeafScript) > 0 { genesis, err := chainhash.NewHashFromStr(liquidNet.GenesisBlockHash) if err != nil { diff --git a/client/covenantless/balance.go b/client/covenantless/balance.go index 4a4a651..e16f6a2 100644 --- a/client/covenantless/balance.go +++ b/client/covenantless/balance.go @@ -9,6 +9,7 @@ import ( arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1" "github.com/ark-network/ark/client/flags" "github.com/ark-network/ark/client/utils" + "github.com/ark-network/ark/common/descriptor" "github.com/urfave/cli/v2" ) @@ -21,7 +22,7 @@ func (*clArkBitcoinCLI) Balance(ctx *cli.Context) error { } defer cancel() - offchainAddr, onchainAddr, redemptionAddr, err := getAddress(ctx) + offchainAddr, boardingAddr, redemptionAddr, err := getAddress(ctx) if err != nil { return err } @@ -29,6 +30,21 @@ func (*clArkBitcoinCLI) Balance(ctx *cli.Context) error { // nolint:all unilateralExitDelay, _ := utils.GetUnilateralExitDelay(ctx) + boardingDescriptor, err := utils.GetBoardingDescriptor(ctx) + if err != nil { + return err + } + + desc, err := descriptor.ParseTaprootDescriptor(boardingDescriptor) + if err != nil { + return err + } + + _, timeoutBoarding, err := descriptor.ParseBoardingDescriptor(*desc) + if err != nil { + return err + } + wg := &sync.WaitGroup{} wg.Add(3) @@ -50,19 +66,19 @@ func (*clArkBitcoinCLI) Balance(ctx *cli.Context) error { go func() { defer wg.Done() explorer := utils.NewExplorer(ctx) - balance, err := explorer.GetBalance(onchainAddr.EncodeAddress(), "") + balance, lockedBalance, err := explorer.GetDelayedBalance(boardingAddr.EncodeAddress(), int64(timeoutBoarding)) if err != nil { chRes <- balanceRes{0, 0, nil, nil, err} return } - chRes <- balanceRes{0, balance, nil, nil, nil} + chRes <- balanceRes{0, balance, lockedBalance, nil, nil} }() go func() { defer wg.Done() explorer := utils.NewExplorer(ctx) - spendableBalance, lockedBalance, err := explorer.GetRedeemedVtxosBalance( + spendableBalance, lockedBalance, err := explorer.GetDelayedBalance( redemptionAddr.EncodeAddress(), unilateralExitDelay, ) if err != nil { @@ -126,6 +142,7 @@ func (*clArkBitcoinCLI) Balance(ctx *cli.Context) error { } response := make(map[string]interface{}) + response["onchain_balance"] = map[string]interface{}{ "spendable_amount": onchainBalance, } diff --git a/client/covenantless/claim.go b/client/covenantless/claim.go index a6a5a0a..e49e6df 100644 --- a/client/covenantless/claim.go +++ b/client/covenantless/claim.go @@ -2,26 +2,62 @@ package covenantless import ( "encoding/hex" + "fmt" + "time" arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1" "github.com/ark-network/ark/client/utils" + "github.com/ark-network/ark/common/descriptor" "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/urfave/cli/v2" ) -func (c *clArkBitcoinCLI) ClaimAsync(ctx *cli.Context) error { +func (c *clArkBitcoinCLI) Claim(ctx *cli.Context) error { client, cancel, err := getClientFromState(ctx) if err != nil { return err } defer cancel() - myselfOffchain, _, _, err := getAddress(ctx) + offchainAddr, boardingAddr, _, err := getAddress(ctx) if err != nil { return err } - vtxos, err := getVtxos(ctx, nil, client, myselfOffchain, false) + boardingDescriptor, err := utils.GetBoardingDescriptor(ctx) + if err != nil { + return err + } + + desc, err := descriptor.ParseTaprootDescriptor(boardingDescriptor) + if err != nil { + return err + } + + _, timeoutBoarding, err := descriptor.ParseBoardingDescriptor(*desc) + if err != nil { + return err + } + + explorer := utils.NewExplorer(ctx) + + boardingUtxosFromExplorer, err := explorer.GetUtxos(boardingAddr.EncodeAddress()) + if err != nil { + return err + } + + now := time.Now() + boardingUtxos := make([]utils.Utxo, 0, len(boardingUtxosFromExplorer)) + for _, utxo := range boardingUtxosFromExplorer { + u := utils.NewUtxo(utxo, uint(timeoutBoarding)) + if u.SpendableAt.Before(now) { + continue // cannot claim if onchain spendable + } + + boardingUtxos = append(boardingUtxos, u) + } + + vtxos, err := getVtxos(ctx, nil, client, offchainAddr, false) if err != nil { return err } @@ -34,32 +70,71 @@ func (c *clArkBitcoinCLI) ClaimAsync(ctx *cli.Context) error { pendingVtxos = append(pendingVtxos, vtxo) } } + + for _, utxo := range boardingUtxos { + pendingBalance += utxo.Amount + } + if pendingBalance == 0 { return nil } receiver := receiver{ - To: myselfOffchain, + To: offchainAddr, Amount: pendingBalance, } + + if len(ctx.String("password")) == 0 { + if ok := askForConfirmation( + fmt.Sprintf( + "claim %d satoshis from %d pending payments and %d boarding utxos", + pendingBalance, len(pendingVtxos), len(boardingUtxos), + ), + ); !ok { + return nil + } + } + return selfTransferAllPendingPayments( - ctx, client, pendingVtxos, receiver, + ctx, client, pendingVtxos, boardingUtxos, receiver, boardingDescriptor, ) } func selfTransferAllPendingPayments( - ctx *cli.Context, client arkv1.ArkServiceClient, - pendingVtxos []vtxo, myself receiver, + ctx *cli.Context, + client arkv1.ArkServiceClient, + pendingVtxos []vtxo, + boardingUtxos []utils.Utxo, + myself receiver, + desc string, ) error { - inputs := make([]*arkv1.Input, 0, len(pendingVtxos)) + inputs := make([]*arkv1.Input, 0, len(pendingVtxos)+len(boardingUtxos)) for _, coin := range pendingVtxos { inputs = append(inputs, &arkv1.Input{ - Txid: coin.txid, - Vout: coin.vout, + Input: &arkv1.Input_VtxoInput{ + VtxoInput: &arkv1.VtxoInput{ + Txid: coin.txid, + Vout: coin.vout, + }, + }, }) } + if len(boardingUtxos) > 0 { + for _, outpoint := range boardingUtxos { + inputs = append(inputs, &arkv1.Input{ + Input: &arkv1.Input_BoardingInput{ + BoardingInput: &arkv1.BoardingInput{ + Txid: outpoint.Txid, + Vout: outpoint.Vout, + Descriptor_: desc, + }, + }, + }) + } + } + receiversOutput := []*arkv1.Output{ { Address: myself.To, @@ -99,8 +174,8 @@ func selfTransferAllPendingPayments( } poolTxID, err := handleRoundStream( - ctx, client, registerResponse.GetId(), - pendingVtxos, secKey, receiversOutput, ephemeralKey, + ctx, client, registerResponse.GetId(), pendingVtxos, + len(boardingUtxos) > 0, secKey, receiversOutput, ephemeralKey, ) if err != nil { return err diff --git a/client/covenantless/cli.go b/client/covenantless/cli.go index fcbfd50..1744e4f 100644 --- a/client/covenantless/cli.go +++ b/client/covenantless/cli.go @@ -9,10 +9,10 @@ import ( "github.com/ark-network/ark/client/utils" "github.com/ark-network/ark/common" "github.com/ark-network/ark/common/bitcointree" + "github.com/ark-network/ark/common/descriptor" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil/psbt" - "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" @@ -25,14 +25,14 @@ const dust = 450 type clArkBitcoinCLI struct{} func (c *clArkBitcoinCLI) Receive(ctx *cli.Context) error { - offchainAddr, onchainAddr, _, err := getAddress(ctx) + offchainAddr, boardingAddr, _, err := getAddress(ctx) if err != nil { return err } return utils.PrintJSON(map[string]interface{}{ "offchain_address": offchainAddr, - "onchain_address": onchainAddr.EncodeAddress(), + "boarding_address": boardingAddr.EncodeAddress(), }) } @@ -79,11 +79,6 @@ type receiver struct { Amount uint64 `json:"amount"` } -// func (r *receiver) isOnchain() bool { -// _, err := btcutil.DecodeAddress(r.To, nil) -// return err == nil -// } - func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) { ptx, err := psbt.New(nil, nil, 2, 0, nil) if err != nil { @@ -128,14 +123,14 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) { explorer := utils.NewExplorer(ctx) - utxos, delayedUtxos, change, err := coinSelectOnchain( + utxos, change, err := coinSelectOnchain( ctx, explorer, targetAmount, nil, ) if err != nil { return "", err } - if err := addInputs(ctx, updater, utxos, delayedUtxos, &netParams); err != nil { + if err := addInputs(ctx, updater, utxos); err != nil { return "", err } @@ -174,14 +169,14 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) { updater.Upsbt.UnsignedTx.TxOut = updater.Upsbt.UnsignedTx.TxOut[:len(updater.Upsbt.UnsignedTx.TxOut)-1] } // reselect the difference - selected, delayedSelected, newChange, err := coinSelectOnchain( - ctx, explorer, feeAmount-change, append(utxos, delayedUtxos...), + selected, newChange, err := coinSelectOnchain( + ctx, explorer, feeAmount-change, utxos, ) if err != nil { return "", err } - if err := addInputs(ctx, updater, selected, delayedSelected, &netParams); err != nil { + if err := addInputs(ctx, updater, selected); err != nil { return "", err } @@ -225,20 +220,37 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) { func coinSelectOnchain( ctx *cli.Context, explorer utils.Explorer, targetAmount uint64, exclude []utils.Utxo, -) ([]utils.Utxo, []utils.Utxo, uint64, error) { - _, onchainAddr, _, err := getAddress(ctx) +) ([]utils.Utxo, uint64, error) { + _, boardingAddr, redemptionAddr, err := getAddress(ctx) if err != nil { - return nil, nil, 0, err + return nil, 0, err } - fromExplorer, err := explorer.GetUtxos(onchainAddr.EncodeAddress()) + boardingUtxosFromExplorer, err := explorer.GetUtxos(boardingAddr.EncodeAddress()) if err != nil { - return nil, nil, 0, err + return nil, 0, err } utxos := make([]utils.Utxo, 0) selectedAmount := uint64(0) - for _, utxo := range fromExplorer { + now := time.Now() + + boardingDescriptor, err := utils.GetBoardingDescriptor(ctx) + if err != nil { + return nil, 0, err + } + + desc, err := descriptor.ParseTaprootDescriptor(boardingDescriptor) + if err != nil { + return nil, 0, err + } + + _, timeoutBoarding, err := descriptor.ParseBoardingDescriptor(*desc) + if err != nil { + return nil, 0, err + } + + for _, utxo := range boardingUtxosFromExplorer { if selectedAmount >= targetAmount { break } @@ -249,102 +261,67 @@ func coinSelectOnchain( } } - utxos = append(utxos, utxo) - selectedAmount += utxo.Amount + utxo := utils.NewUtxo(utxo, uint(timeoutBoarding)) + + if utxo.SpendableAt.After(now) { + utxos = append(utxos, utxo) + selectedAmount += utxo.Amount + } } if selectedAmount >= targetAmount { - return utxos, nil, selectedAmount - targetAmount, nil + return utxos, selectedAmount - targetAmount, nil } - userPubkey, err := utils.GetWalletPublicKey(ctx) + redemptionUtxosFromExplorer, err := explorer.GetUtxos(redemptionAddr.EncodeAddress()) if err != nil { - return nil, nil, 0, err + return nil, 0, err } - aspPubkey, err := utils.GetAspPublicKey(ctx) + vtxoExitDelay, err := utils.GetUnilateralExitDelay(ctx) if err != nil { - return nil, nil, 0, err + return nil, 0, err } - unilateralExitDelay, err := utils.GetUnilateralExitDelay(ctx) - if err != nil { - return nil, nil, 0, err - } - - vtxoTapKey, _, err := computeVtxoTaprootScript( - userPubkey, aspPubkey, uint(unilateralExitDelay), - ) - if err != nil { - return nil, nil, 0, err - } - - net, err := utils.GetNetwork(ctx) - if err != nil { - return nil, nil, 0, err - } - - liquidNet := toChainParams(net) - - p2tr, err := btcutil.NewAddressTaproot( - schnorr.SerializePubKey(vtxoTapKey), - &liquidNet, - ) - if err != nil { - return nil, nil, 0, err - } - - addr := p2tr.EncodeAddress() - - fromExplorer, err = explorer.GetUtxos(addr) - if err != nil { - return nil, nil, 0, err - } - - delayedUtxos := make([]utils.Utxo, 0) - for _, utxo := range fromExplorer { + for _, utxo := range redemptionUtxosFromExplorer { if selectedAmount >= targetAmount { break } - availableAt := time.Unix(utxo.Status.Blocktime, 0).Add( - time.Duration(unilateralExitDelay) * time.Second, - ) - if availableAt.After(time.Now()) { - continue - } - for _, excluded := range exclude { if utxo.Txid == excluded.Txid && utxo.Vout == excluded.Vout { continue } } - delayedUtxos = append(delayedUtxos, utxo) - selectedAmount += utxo.Amount + utxo := utils.NewUtxo(utxo, uint(vtxoExitDelay)) + + if utxo.SpendableAt.After(now) { + utxos = append(utxos, utxo) + selectedAmount += utxo.Amount + } } if selectedAmount < targetAmount { - return nil, nil, 0, fmt.Errorf( + return nil, 0, fmt.Errorf( "not enough funds to cover amount %d", targetAmount, ) } - return utxos, delayedUtxos, selectedAmount - targetAmount, nil + return utxos, selectedAmount - targetAmount, nil } func addInputs( ctx *cli.Context, updater *psbt.Updater, - utxos, delayedUtxos []utils.Utxo, - net *chaincfg.Params, + utxos []utils.Utxo, ) error { - _, onchainAddr, _, err := getAddress(ctx) + userPubkey, err := utils.GetWalletPublicKey(ctx) if err != nil { return err } - changeScript, err := txscript.PayToAddrScript(onchainAddr) + aspPubkey, err := utils.GetAspPublicKey(ctx) if err != nil { return err } @@ -355,87 +332,41 @@ func addInputs( return err } + sequence, err := utxo.Sequence() + if err != nil { + return err + } + updater.Upsbt.UnsignedTx.AddTxIn(&wire.TxIn{ PreviousOutPoint: wire.OutPoint{ Hash: *previousHash, Index: utxo.Vout, }, + Sequence: sequence, }) - updater.Upsbt.Inputs = append(updater.Upsbt.Inputs, psbt.PInput{}) - - if err := updater.AddInWitnessUtxo( - &wire.TxOut{ - Value: int64(utxo.Amount), - PkScript: changeScript, - }, - len(updater.Upsbt.UnsignedTx.TxIn)-1, - ); err != nil { - return err - } - } - - if len(delayedUtxos) > 0 { - userPubkey, err := utils.GetWalletPublicKey(ctx) - if err != nil { - return err - } - - aspPubkey, err := utils.GetAspPublicKey(ctx) - if err != nil { - return err - } - - unilateralExitDelay, err := utils.GetUnilateralExitDelay(ctx) - if err != nil { - return err - } - - vtxoTapKey, leafProof, err := computeVtxoTaprootScript( - userPubkey, aspPubkey, uint(unilateralExitDelay), + _, leafProof, err := computeVtxoTaprootScript( + userPubkey, aspPubkey, utxo.Delay, ) if err != nil { return err } - p2tr, err := btcutil.NewAddressTaproot(schnorr.SerializePubKey(vtxoTapKey), net) + controlBlock := leafProof.ToControlBlock(bitcointree.UnspendableKey()) + controlBlockBytes, err := controlBlock.ToBytes() if err != nil { return err } - script, err := txscript.PayToAddrScript(p2tr) - if err != nil { - return err - } - - for _, utxo := range delayedUtxos { - previousHash, err := chainhash.NewHashFromStr(utxo.Txid) - if err != nil { - return err - } - - if err := addVtxoInput( - updater, - &wire.OutPoint{ - Hash: *previousHash, - Index: utxo.Vout, + updater.Upsbt.Inputs = append(updater.Upsbt.Inputs, psbt.PInput{ + TaprootLeafScript: []*psbt.TaprootTapLeafScript{ + { + ControlBlock: controlBlockBytes, + Script: leafProof.Script, + LeafVersion: leafProof.LeafVersion, }, - uint(unilateralExitDelay), - leafProof, - ); err != nil { - return err - } - - if err := updater.AddInWitnessUtxo( - &wire.TxOut{ - Value: int64(utxo.Amount), - PkScript: script, - }, - len(updater.Upsbt.Inputs)-1, - ); err != nil { - return err - } - } + }, + }) } return nil @@ -461,40 +392,7 @@ func decodeReceiverAddress(addr string) ( return true, pkscript, nil, nil } -func addVtxoInput( - updater *psbt.Updater, inputArgs *wire.OutPoint, exitDelay uint, - tapLeafProof *txscript.TapscriptProof, -) error { - sequence, err := common.BIP68EncodeAsNumber(exitDelay) - if err != nil { - return nil - } - - nextInputIndex := len(updater.Upsbt.Inputs) - updater.Upsbt.UnsignedTx.AddTxIn(&wire.TxIn{ - PreviousOutPoint: *inputArgs, - Sequence: sequence, - }) - updater.Upsbt.Inputs = append(updater.Upsbt.Inputs, psbt.PInput{}) - - controlBlock := tapLeafProof.ToControlBlock(bitcointree.UnspendableKey()) - controlBlockBytes, err := controlBlock.ToBytes() - if err != nil { - return err - } - - updater.Upsbt.Inputs[nextInputIndex].TaprootLeafScript = []*psbt.TaprootTapLeafScript{ - { - ControlBlock: controlBlockBytes, - Script: tapLeafProof.Script, - LeafVersion: tapLeafProof.LeafVersion, - }, - } - - return nil -} - -func getAddress(ctx *cli.Context) (offchainAddr string, onchainAddr, redemptionAddr btcutil.Address, err error) { +func getAddress(ctx *cli.Context) (offchainAddr string, boardingAddr, redemptionAddr btcutil.Address, err error) { userPubkey, err := utils.GetWalletPublicKey(ctx) if err != nil { return @@ -510,6 +408,21 @@ func getAddress(ctx *cli.Context) (offchainAddr string, onchainAddr, redemptionA return } + boardingDescriptor, err := utils.GetBoardingDescriptor(ctx) + if err != nil { + return + } + + desc, err := descriptor.ParseTaprootDescriptor(boardingDescriptor) + if err != nil { + return + } + + _, timeoutBoarding, err := descriptor.ParseBoardingDescriptor(*desc) + if err != nil { + return + } + arkNet, err := utils.GetNetwork(ctx) if err != nil { return @@ -522,11 +435,6 @@ func getAddress(ctx *cli.Context) (offchainAddr string, onchainAddr, redemptionA netParams := toChainParams(arkNet) - p2wpkh, err := btcutil.NewAddressWitnessPubKeyHash(btcutil.Hash160(userPubkey.SerializeCompressed()), &netParams) - if err != nil { - return - } - vtxoTapKey, _, err := computeVtxoTaprootScript( userPubkey, aspPubkey, uint(unilateralExitDelay), ) @@ -534,7 +442,7 @@ func getAddress(ctx *cli.Context) (offchainAddr string, onchainAddr, redemptionA return } - p2tr, err := btcutil.NewAddressTaproot( + redemptionP2TR, err := btcutil.NewAddressTaproot( schnorr.SerializePubKey(vtxoTapKey), &netParams, ) @@ -542,8 +450,20 @@ func getAddress(ctx *cli.Context) (offchainAddr string, onchainAddr, redemptionA return } - redemptionAddr = p2tr - onchainAddr = p2wpkh + boardingTapKey, _, err := computeVtxoTaprootScript( + userPubkey, aspPubkey, uint(timeoutBoarding), + ) + if err != nil { + return + } + + boardingP2TR, err := btcutil.NewAddressTaproot( + schnorr.SerializePubKey(boardingTapKey), + &netParams, + ) + + redemptionAddr = redemptionP2TR + boardingAddr = boardingP2TR offchainAddr = arkAddr return diff --git a/client/covenantless/client.go b/client/covenantless/client.go index 1a8dc5f..b4e2bcf 100644 --- a/client/covenantless/client.go +++ b/client/covenantless/client.go @@ -53,14 +53,16 @@ func getVtxos( if v.GetSwept() { continue } - vtxos = append(vtxos, vtxo{ - amount: v.GetReceiver().GetAmount(), - txid: v.GetOutpoint().GetTxid(), - vout: v.GetOutpoint().GetVout(), - poolTxid: v.GetPoolTxid(), - expireAt: expireAt, - pending: v.GetPending(), - }) + if v.Outpoint.GetVtxoInput() != nil { + vtxos = append(vtxos, vtxo{ + amount: v.Receiver.Amount, + txid: v.Outpoint.GetVtxoInput().GetTxid(), + vout: v.Outpoint.GetVtxoInput().GetVout(), + poolTxid: v.PoolTxid, + expireAt: expireAt, + pending: v.GetPending(), + }) + } } if !computeExpiration { @@ -196,32 +198,10 @@ func toCongestionTree(treeFromProto *arkv1.Tree) (tree.CongestionTree, error) { return levels, nil } -// castCongestionTree converts a tree.CongestionTree to a repeated arkv1.TreeLevel -func castCongestionTree(congestionTree tree.CongestionTree) *arkv1.Tree { - levels := make([]*arkv1.TreeLevel, 0, len(congestionTree)) - for _, level := range congestionTree { - levelProto := &arkv1.TreeLevel{ - Nodes: make([]*arkv1.Node, 0, len(level)), - } - - for _, node := range level { - levelProto.Nodes = append(levelProto.Nodes, &arkv1.Node{ - Txid: node.Txid, - Tx: node.Tx, - ParentTxid: node.ParentTxid, - }) - } - - levels = append(levels, levelProto) - } - return &arkv1.Tree{ - Levels: levels, - } -} - func handleRoundStream( ctx *cli.Context, client arkv1.ArkServiceClient, paymentID string, - vtxosToSign []vtxo, secKey *secp256k1.PrivateKey, receivers []*arkv1.Output, + vtxosToSign []vtxo, mustSignRoundTx bool, + secKey *secp256k1.PrivateKey, receivers []*arkv1.Output, ephemeralKey *secp256k1.PrivateKey, ) (poolTxID string, err error) { stream, err := client.GetEventStream(ctx.Context, &arkv1.GetEventStreamRequest{}) @@ -398,8 +378,8 @@ func handleRoundStream( // stop pinging as soon as we receive some forfeit txs pingStop() - poolTx := e.GetPoolTx() - ptx, err := psbt.NewFromRawBytes(strings.NewReader(poolTx), true) + roundTx := e.GetPoolTx() + ptx, err := psbt.NewFromRawBytes(strings.NewReader(roundTx), true) if err != nil { return "", err } @@ -428,7 +408,7 @@ func handleRoundStream( if !isOnchainOnly(receivers) { if err := bitcointree.ValidateCongestionTree( - congestionTree, poolTx, aspPubkey, int64(roundLifetime), int64(minRelayFee), + congestionTree, roundTx, aspPubkey, int64(roundLifetime), int64(minRelayFee), ); err != nil { return "", err } @@ -529,80 +509,103 @@ func handleRoundStream( fmt.Println("congestion tree validated") - forfeits := e.GetForfeitTxs() - signedForfeits := make([]string, 0) - - fmt.Print("signing forfeit txs... ") - explorer := utils.NewExplorer(ctx) - connectorsTxids := make([]string, 0, len(connectors)) - for _, connector := range connectors { - p, err := psbt.NewFromRawBytes(strings.NewReader(connector), true) - if err != nil { - return "", err - } - txid := p.UnsignedTx.TxHash().String() + finalizePaymentRequest := &arkv1.FinalizePaymentRequest{} - connectorsTxids = append(connectorsTxids, txid) - } + if len(vtxosToSign) > 0 { + forfeits := e.GetForfeitTxs() + signedForfeits := make([]string, 0) - for _, forfeit := range forfeits { - ptx, err := psbt.NewFromRawBytes(strings.NewReader(forfeit), true) - if err != nil { - return "", err + fmt.Print("signing forfeit txs... ") + + connectorsTxids := make([]string, 0, len(connectors)) + for _, connector := range connectors { + p, err := psbt.NewFromRawBytes(strings.NewReader(connector), true) + if err != nil { + return "", err + } + txid := p.UnsignedTx.TxHash().String() + + connectorsTxids = append(connectorsTxids, txid) } - for _, input := range ptx.UnsignedTx.TxIn { - inputTxid := input.PreviousOutPoint.Hash.String() + for _, forfeit := range forfeits { + ptx, err := psbt.NewFromRawBytes(strings.NewReader(forfeit), true) + if err != nil { + return "", err + } - for _, coin := range vtxosToSign { - // check if it contains one of the input to sign - if inputTxid == coin.txid { - // verify that the connector is in the connectors list - connectorTxid := ptx.UnsignedTx.TxIn[0].PreviousOutPoint.Hash.String() - connectorFound := false - for _, txid := range connectorsTxids { - if txid == connectorTxid { - connectorFound = true - break + for _, input := range ptx.UnsignedTx.TxIn { + inputTxid := input.PreviousOutPoint.Hash.String() + + for _, coin := range vtxosToSign { + // check if it contains one of the input to sign + if inputTxid == coin.txid { + // verify that the connector is in the connectors list + connectorTxid := ptx.UnsignedTx.TxIn[0].PreviousOutPoint.Hash.String() + connectorFound := false + for _, txid := range connectorsTxids { + if txid == connectorTxid { + connectorFound = true + break + } } - } - if !connectorFound { - return "", fmt.Errorf("connector txid %s not found in the connectors list", connectorTxid) - } + if !connectorFound { + return "", fmt.Errorf("connector txid %s not found in the connectors list", connectorTxid) + } - if err := signPsbt(ctx, ptx, explorer, secKey); err != nil { - return "", err - } + if err := signPsbt(ctx, ptx, explorer, secKey); err != nil { + return "", err + } - signedPset, err := ptx.B64Encode() - if err != nil { - return "", err - } + signedPset, err := ptx.B64Encode() + if err != nil { + return "", err + } - signedForfeits = append(signedForfeits, signedPset) + signedForfeits = append(signedForfeits, signedPset) + } } } } - } - // if no forfeit txs have been signed, start pinging again and wait for the next round - if len(signedForfeits) == 0 { - fmt.Printf("\nno forfeit txs to sign, waiting for the next round...\n") - pingStop = nil - for pingStop == nil { - pingStop = ping(ctx.Context, client, pingReq) + // if no forfeit txs have been signed, start pinging again and wait for the next round + if len(vtxosToSign) > 0 && len(signedForfeits) == 0 { + fmt.Printf("\nno forfeit txs to sign, waiting for the next round...\n") + pingStop = nil + for pingStop == nil { + pingStop = ping(ctx.Context, client, pingReq) + } + continue } - continue + + fmt.Printf("%d signed\n", len(signedForfeits)) + finalizePaymentRequest.SignedForfeitTxs = signedForfeits + } + + if mustSignRoundTx { + ptx, err := psbt.NewFromRawBytes(strings.NewReader(roundTx), true) + if err != nil { + return "", err + } + + if err := signPsbt(ctx, ptx, explorer, secKey); err != nil { + return "", err + } + + signedRoundTx, err := ptx.B64Encode() + if err != nil { + return "", err + } + + fmt.Println("round tx signed") + finalizePaymentRequest.SignedRoundTx = &signedRoundTx } - fmt.Printf("%d signed\n", len(signedForfeits)) fmt.Print("finalizing payment... ") - _, err = client.FinalizePayment(ctx.Context, &arkv1.FinalizePaymentRequest{ - SignedForfeitTxs: signedForfeits, - }) + _, err = client.FinalizePayment(ctx.Context, finalizePaymentRequest) if err != nil { return "", err } diff --git a/client/covenantless/init.go b/client/covenantless/init.go index e4913e8..927813e 100644 --- a/client/covenantless/init.go +++ b/client/covenantless/init.go @@ -82,6 +82,7 @@ func connectToAsp(ctx *cli.Context, net, url, explorer string) error { utils.UNILATERAL_EXIT_DELAY: strconv.Itoa(int(resp.GetUnilateralExitDelay())), utils.MIN_RELAY_FEE: strconv.Itoa(int(resp.MinRelayFee)), utils.EXPLORER: explorer, + utils.BOARDING_TEMPLATE: resp.GetBoardingDescriptorTemplate(), }) } diff --git a/client/covenantless/onboard.go b/client/covenantless/onboard.go deleted file mode 100644 index 3bd35ed..0000000 --- a/client/covenantless/onboard.go +++ /dev/null @@ -1,205 +0,0 @@ -package covenantless - -import ( - "encoding/hex" - "fmt" - "strings" - - arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1" - "github.com/ark-network/ark/client/utils" - "github.com/ark-network/ark/common/bitcointree" - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/btcutil/psbt" - "github.com/btcsuite/btcd/txscript" - "github.com/btcsuite/btcd/wire" - "github.com/decred/dcrd/dcrec/secp256k1/v4" - "github.com/urfave/cli/v2" -) - -func (c *clArkBitcoinCLI) Onboard(ctx *cli.Context) error { - amount := ctx.Uint64("amount") - - if amount <= 0 { - return fmt.Errorf("missing amount flag (--amount)") - } - - net, err := utils.GetNetwork(ctx) - if err != nil { - return err - } - - userPubKey, err := utils.GetWalletPublicKey(ctx) - if err != nil { - return err - } - - client, cancel, err := getClientFromState(ctx) - if err != nil { - return err - } - defer cancel() - - aspPubkey, err := utils.GetAspPublicKey(ctx) - if err != nil { - return err - } - - roundLifetime, err := utils.GetRoundLifetime(ctx) - if err != nil { - return err - } - - unilateralExitDelay, err := utils.GetUnilateralExitDelay(ctx) - if err != nil { - return err - } - - minRelayFee, err := utils.GetMinRelayFee(ctx) - if err != nil { - return err - } - - congestionTreeLeaf := bitcointree.Receiver{ - Pubkey: hex.EncodeToString(userPubKey.SerializeCompressed()), - Amount: uint64(amount), // Convert amount to uint64 - } - - leaves := []bitcointree.Receiver{congestionTreeLeaf} - - ephemeralKey, err := secp256k1.GeneratePrivateKey() - if err != nil { - return err - } - - cosigners := []*secp256k1.PublicKey{ephemeralKey.PubKey()} // TODO asp as cosigner - - sharedOutputScript, sharedOutputAmount, err := bitcointree.CraftSharedOutput( - cosigners, - aspPubkey, - leaves, - uint64(minRelayFee), - roundLifetime, - unilateralExitDelay, - ) - if err != nil { - return err - } - - netParams := toChainParams(net) - - address, err := btcutil.NewAddressTaproot(sharedOutputScript[2:], &netParams) - if err != nil { - return err - } - - onchainReceiver := receiver{ - To: address.EncodeAddress(), - Amount: uint64(sharedOutputAmount), - } - - partialTx, err := sendOnchain(ctx, []receiver{onchainReceiver}) - if err != nil { - return err - } - - ptx, err := psbt.NewFromRawBytes(strings.NewReader(partialTx), true) - if err != nil { - return err - } - txid := ptx.UnsignedTx.TxHash().String() - - congestionTree, err := bitcointree.CraftCongestionTree( - &wire.OutPoint{ - Hash: ptx.UnsignedTx.TxHash(), - Index: 0, - }, - cosigners, - aspPubkey, - leaves, - uint64(minRelayFee), - roundLifetime, - unilateralExitDelay, - ) - if err != nil { - return err - } - - sweepClosure := bitcointree.CSVSigClosure{ - Pubkey: aspPubkey, - Seconds: uint(roundLifetime), - } - - sweepTapLeaf, err := sweepClosure.Leaf() - if err != nil { - return err - } - - sweepTapTree := txscript.AssembleTaprootScriptTree(*sweepTapLeaf) - root := sweepTapTree.RootNode.TapHash() - - signer := bitcointree.NewTreeSignerSession( - ephemeralKey, - congestionTree, - minRelayFee, - root.CloneBytes(), - ) - - nonces, err := signer.GetNonces() // TODO send nonces to ASP - if err != nil { - return err - } - - coordinator, err := bitcointree.NewTreeCoordinatorSession( - congestionTree, - minRelayFee, - root.CloneBytes(), - cosigners, - ) - if err != nil { - return err - } - - if err := coordinator.AddNonce(ephemeralKey.PubKey(), nonces); err != nil { - return err - } - - aggregatedNonces, err := coordinator.AggregateNonces() - if err != nil { - return err - } - - if err := signer.SetKeys(cosigners); err != nil { - return err - } - - if err := signer.SetAggregatedNonces(aggregatedNonces); err != nil { - return err - } - - sigs, err := signer.Sign() - if err != nil { - return err - } - - if err := coordinator.AddSig(ephemeralKey.PubKey(), sigs); err != nil { - return err - } - - signedTree, err := coordinator.SignTree() - if err != nil { - return err - } - - _, err = client.Onboard(ctx.Context, &arkv1.OnboardRequest{ - BoardingTx: partialTx, - CongestionTree: castCongestionTree(signedTree), - UserPubkey: hex.EncodeToString(userPubKey.SerializeCompressed()), - }) - if err != nil { - return err - } - - fmt.Println("onboard_txid:", txid) - - return nil -} diff --git a/client/covenantless/redeem.go b/client/covenantless/redeem.go index 13063c6..bb39542 100644 --- a/client/covenantless/redeem.go +++ b/client/covenantless/redeem.go @@ -72,8 +72,12 @@ func collaborativeRedeem( for _, coin := range selectedCoins { inputs = append(inputs, &arkv1.Input{ - Txid: coin.txid, - Vout: coin.vout, + Input: &arkv1.Input_VtxoInput{ + VtxoInput: &arkv1.VtxoInput{ + Txid: coin.txid, + Vout: coin.vout, + }, + }, }) } @@ -110,6 +114,7 @@ func collaborativeRedeem( client, registerResponse.GetId(), selectedCoins, + false, secKey, receivers, ephemeralKey, diff --git a/client/covenantless/send.go b/client/covenantless/send.go index 80ea100..dbc902d 100644 --- a/client/covenantless/send.go +++ b/client/covenantless/send.go @@ -14,7 +14,7 @@ import ( ) func (c *clArkBitcoinCLI) SendAsync(ctx *cli.Context) error { - receiver := ctx.String("to") + receiverAddr := ctx.String("to") amount := ctx.Uint64("amount") withExpiryCoinselect := ctx.Bool("enable-expiry-coinselect") @@ -22,15 +22,22 @@ func (c *clArkBitcoinCLI) SendAsync(ctx *cli.Context) error { return fmt.Errorf("invalid amount (%d), must be greater than dust %d", amount, dust) } - if receiver == "" { + if receiverAddr == "" { return fmt.Errorf("receiver address is required") } - isOnchain, _, _, err := decodeReceiverAddress(receiver) + isOnchain, _, _, err := decodeReceiverAddress(receiverAddr) if err != nil { return err } if isOnchain { - return fmt.Errorf("receiver address is onchain") + txid, err := sendOnchain(ctx, []receiver{{receiverAddr, amount}}) + if err != nil { + return err + } + + return utils.PrintJSON(map[string]interface{}{ + "txid": txid, + }) } offchainAddr, _, _, err := getAddress(ctx) @@ -41,20 +48,20 @@ func (c *clArkBitcoinCLI) SendAsync(ctx *cli.Context) error { if err != nil { return err } - _, _, aspKey, err := common.DecodeAddress(receiver) + _, _, aspKey, err := common.DecodeAddress(receiverAddr) if err != nil { return fmt.Errorf("invalid receiver address: %s", err) } if !bytes.Equal( aspPubKey.SerializeCompressed(), aspKey.SerializeCompressed(), ) { - return fmt.Errorf("invalid receiver address '%s': must be associated with the connected service provider", receiver) + return fmt.Errorf("invalid receiver address '%s': must be associated with the connected service provider", receiverAddr) } receiversOutput := make([]*arkv1.Output, 0) sumOfReceivers := uint64(0) receiversOutput = append(receiversOutput, &arkv1.Output{ - Address: receiver, + Address: receiverAddr, Amount: amount, }) sumOfReceivers += amount @@ -88,8 +95,12 @@ func (c *clArkBitcoinCLI) SendAsync(ctx *cli.Context) error { for _, coin := range selectedCoins { inputs = append(inputs, &arkv1.Input{ - Txid: coin.txid, - Vout: coin.vout, + Input: &arkv1.Input_VtxoInput{ + VtxoInput: &arkv1.VtxoInput{ + Txid: coin.txid, + Vout: coin.vout, + }, + }, }) } diff --git a/client/covenantless/signer.go b/client/covenantless/signer.go index be57c20..b2a5497 100644 --- a/client/covenantless/signer.go +++ b/client/covenantless/signer.go @@ -8,7 +8,6 @@ import ( "github.com/ark-network/ark/client/utils" "github.com/ark-network/ark/common/bitcointree" - "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/txscript" @@ -18,7 +17,7 @@ import ( ) func signPsbt( - ctx *cli.Context, ptx *psbt.Packet, explorer utils.Explorer, prvKey *secp256k1.PrivateKey, + _ *cli.Context, ptx *psbt.Packet, explorer utils.Explorer, prvKey *secp256k1.PrivateKey, ) error { updater, err := psbt.NewUpdater(ptx) if err != nil { @@ -50,27 +49,11 @@ func signPsbt( return err } - sighashType := txscript.SigHashAll - - if utxo.PkScript[0] == txscript.OP_1 { - sighashType = txscript.SigHashDefault - } - - if err := updater.AddInSighashType(sighashType, i); err != nil { + if err := updater.AddInSighashType(txscript.SigHashDefault, i); err != nil { return err } } - _, onchainAddr, _, err := getAddress(ctx) - if err != nil { - return err - } - - onchainWalletScript, err := txscript.PayToAddrScript(onchainAddr) - if err != nil { - return err - } - prevouts := make(map[wire.OutPoint]*wire.TxOut) for i, input := range updater.Upsbt.Inputs { @@ -85,40 +68,6 @@ func signPsbt( txsighashes := txscript.NewTxSigHashes(updater.Upsbt.UnsignedTx, prevoutFetcher) for i, input := range ptx.Inputs { - if bytes.Equal(input.WitnessUtxo.PkScript, onchainWalletScript) { - if err := updater.AddInSighashType(txscript.SigHashAll, i); err != nil { - return err - } - - preimage, err := txscript.CalcWitnessSigHash( - input.WitnessUtxo.PkScript, - txsighashes, - txscript.SigHashAll, - updater.Upsbt.UnsignedTx, - i, - int64(input.WitnessUtxo.Value), - ) - if err != nil { - return err - } - - sig := ecdsa.Sign( - prvKey, - preimage, - ) - - signatureWithSighashType := append(sig.Serialize(), byte(txscript.SigHashAll)) - - updater.Upsbt.Inputs[i].PartialSigs = []*psbt.PartialSig{ - { - PubKey: prvKey.PubKey().SerializeCompressed(), - Signature: signatureWithSighashType, - }, - } - - continue - } - if len(input.TaprootLeafScript) > 0 { pubkey := prvKey.PubKey() for _, leaf := range input.TaprootLeafScript { @@ -178,7 +127,6 @@ func signPsbt( } } } - } return nil diff --git a/client/flags/flags.go b/client/flags/flags.go index 1f0e736..48aa059 100644 --- a/client/flags/flags.go +++ b/client/flags/flags.go @@ -21,10 +21,6 @@ var ( Required: false, Hidden: true, } - AmountOnboardFlag = cli.Uint64Flag{ - Name: "amount", - Usage: "amount to onboard in sats", - } ExpiryDetailsFlag = cli.BoolFlag{ Name: "compute-expiry-details", Usage: "compute client-side the VTXOs expiry time", diff --git a/client/go.mod b/client/go.mod index b4b3803..848bcac 100644 --- a/client/go.mod +++ b/client/go.mod @@ -2,6 +2,8 @@ module github.com/ark-network/ark/client go 1.22.6 +replace github.com/ark-network/ark/common => ../common + replace github.com/btcsuite/btcd/btcec/v2 => github.com/btcsuite/btcd/btcec/v2 v2.3.3 require ( @@ -19,9 +21,7 @@ require ( require ( github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/vulpemventures/fastsha256 v0.0.0-20160815193821-637e65642941 // indirect ) diff --git a/client/go.sum b/client/go.sum index 5fd8564..d9dc201 100644 --- a/client/go.sum +++ b/client/go.sum @@ -1,8 +1,6 @@ github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/ark-network/ark/api-spec v0.0.0-20240812233307-18e343b31899 h1:PJL9Pam042F790x3mMovaIIkgeKIVaWm1aFOyH0k4PY= github.com/ark-network/ark/api-spec v0.0.0-20240812233307-18e343b31899/go.mod h1:0B5seq/gzuGL8OZGUaO12yj73ZJKAde8L+nmLQAZ7IA= -github.com/ark-network/ark/common v0.0.0-20240812233307-18e343b31899 h1:PxcHv+KaBdfrZCHoNYSUiCdI2wNIZ3Oxx8ZUewcEesg= -github.com/ark-network/ark/common v0.0.0-20240812233307-18e343b31899/go.mod h1:8DYeb06Dl8onmrV09xfsdDMGv5HoVtWoKhLBLXOYHew= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= diff --git a/client/interfaces/cli.go b/client/interfaces/cli.go index b1283cb..98cd4ad 100644 --- a/client/interfaces/cli.go +++ b/client/interfaces/cli.go @@ -8,7 +8,6 @@ type CLI interface { Receive(ctx *cli.Context) error Redeem(ctx *cli.Context) error Send(ctx *cli.Context) error - ClaimAsync(ctx *cli.Context) error + Claim(ctx *cli.Context) error SendAsync(ctx *cli.Context) error - Onboard(ctx *cli.Context) error } diff --git a/client/main.go b/client/main.go index 2fcd56f..f5dd8c4 100644 --- a/client/main.go +++ b/client/main.go @@ -74,19 +74,6 @@ var ( Flags: []cli.Flag{&flags.PasswordFlag, &flags.PrivateKeyFlag, &flags.NetworkFlag, &flags.UrlFlag, &flags.ExplorerFlag}, } - onboardCommand = cli.Command{ - Name: "onboard", - Usage: "Onboard the Ark by lifting your funds", - Action: func(ctx *cli.Context) error { - cli, err := getCLIFromState(ctx) - if err != nil { - return err - } - return cli.Onboard(ctx) - }, - Flags: []cli.Flag{&flags.AmountOnboardFlag, &flags.PasswordFlag}, - } - sendCommand = cli.Command{ Name: "send", Usage: "Send your onchain or offchain funds to one or many receivers", @@ -117,7 +104,7 @@ var ( if err != nil { return err } - return cli.ClaimAsync(ctx) + return cli.Claim(ctx) }, Flags: []cli.Flag{&flags.PasswordFlag}, } @@ -167,7 +154,6 @@ func main() { &redeemCommand, &sendCommand, &claimCommand, - &onboardCommand, ) app.Flags = []cli.Flag{ flags.DatadirFlag, diff --git a/client/utils/explorer.go b/client/utils/explorer.go index 55967f2..dfd4090 100644 --- a/client/utils/explorer.go +++ b/client/utils/explorer.go @@ -18,7 +18,7 @@ import ( "github.com/vulpemventures/go-elements/transaction" ) -type Utxo struct { +type ExplorerUtxo struct { Txid string `json:"txid"` Vout uint32 `json:"vout"` Amount uint64 `json:"value"` @@ -32,9 +32,9 @@ type Utxo struct { type Explorer interface { GetTxHex(txid string) (string, error) Broadcast(txHex string) (string, error) - GetUtxos(addr string) ([]Utxo, error) + GetUtxos(addr string) ([]ExplorerUtxo, error) GetBalance(addr, asset string) (uint64, error) - GetRedeemedVtxosBalance( + GetDelayedBalance( addr string, unilateralExitDelay int64, ) (uint64, map[int64]uint64, error) GetTxBlocktime(txid string) (confirmed bool, blocktime int64, err error) @@ -130,7 +130,7 @@ func (e *explorer) Broadcast(txStr string) (string, error) { return txid, nil } -func (e *explorer) GetUtxos(addr string) ([]Utxo, error) { +func (e *explorer) GetUtxos(addr string) ([]ExplorerUtxo, error) { endpoint, err := url.JoinPath(e.baseUrl, "address", addr, "utxo") if err != nil { return nil, err @@ -149,7 +149,7 @@ func (e *explorer) GetUtxos(addr string) ([]Utxo, error) { if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf(string(body)) } - payload := []Utxo{} + payload := []ExplorerUtxo{} if err := json.Unmarshal(body, &payload); err != nil { return nil, err } @@ -175,7 +175,7 @@ func (e *explorer) GetBalance(addr, asset string) (uint64, error) { return balance, nil } -func (e *explorer) GetRedeemedVtxosBalance( +func (e *explorer) GetDelayedBalance( addr string, unilateralExitDelay int64, ) (spendableBalance uint64, lockedBalance map[int64]uint64, err error) { utxos, err := e.GetUtxos(addr) diff --git a/client/utils/state.go b/client/utils/state.go index 74e6687..f21af2b 100644 --- a/client/utils/state.go +++ b/client/utils/state.go @@ -7,8 +7,10 @@ import ( "os" "path/filepath" "strconv" + "strings" "github.com/ark-network/ark/common" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/urfave/cli/v2" ) @@ -18,6 +20,7 @@ const ( ASP_PUBKEY = "asp_public_key" ROUND_LIFETIME = "round_lifetime" UNILATERAL_EXIT_DELAY = "unilateral_exit_delay" + BOARDING_TEMPLATE = "boarding_template" ENCRYPTED_PRVKEY = "encrypted_private_key" PASSWORD_HASH = "password_hash" PUBKEY = "public_key" @@ -109,6 +112,27 @@ func GetUnilateralExitDelay(ctx *cli.Context) (int64, error) { return int64(redeemDelay), nil } +func GetBoardingDescriptor(ctx *cli.Context) (string, error) { + state, err := GetState(ctx) + if err != nil { + return "", err + } + + pubkey, err := GetWalletPublicKey(ctx) + if err != nil { + return "", err + } + + template := state[BOARDING_TEMPLATE] + if len(template) <= 0 { + return "", fmt.Errorf("missing boarding descriptor template") + } + + pubkeyhex := hex.EncodeToString(schnorr.SerializePubKey(pubkey)) + + return strings.ReplaceAll(template, "USER", pubkeyhex), nil +} + func GetWalletPublicKey(ctx *cli.Context) (*secp256k1.PublicKey, error) { state, err := GetState(ctx) if err != nil { diff --git a/client/utils/types.go b/client/utils/types.go new file mode 100644 index 0000000..6b34fe4 --- /dev/null +++ b/client/utils/types.go @@ -0,0 +1,36 @@ +package utils + +import ( + "time" + + "github.com/ark-network/ark/common" +) + +type Utxo struct { + Txid string + Vout uint32 + Amount uint64 + Asset string // optional + Delay uint + SpendableAt time.Time +} + +func (u *Utxo) Sequence() (uint32, error) { + return common.BIP68EncodeAsNumber(u.Delay) +} + +func NewUtxo(explorerUtxo ExplorerUtxo, delay uint) Utxo { + utxoTime := explorerUtxo.Status.Blocktime + if utxoTime == 0 { + utxoTime = time.Now().Unix() + } + + return Utxo{ + Txid: explorerUtxo.Txid, + Vout: explorerUtxo.Vout, + Amount: explorerUtxo.Amount, + Asset: explorerUtxo.Asset, + Delay: delay, + SpendableAt: time.Unix(utxoTime, 0).Add(time.Duration(delay) * time.Second), + } +} diff --git a/common/bitcointree/descriptor.go b/common/bitcointree/descriptor.go new file mode 100644 index 0000000..6640288 --- /dev/null +++ b/common/bitcointree/descriptor.go @@ -0,0 +1,48 @@ +package bitcointree + +import ( + "encoding/hex" + + "github.com/ark-network/ark/common/descriptor" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/txscript" +) + +func ComputeOutputScript(desc descriptor.TaprootDescriptor) ([]byte, error) { + leaves := make([]txscript.TapLeaf, 0) + for _, leaf := range desc.ScriptTree { + scriptHex, err := leaf.Script(false) + if err != nil { + return nil, err + } + + script, err := hex.DecodeString(scriptHex) + if err != nil { + return nil, err + } + + leaves = append(leaves, txscript.NewBaseTapLeaf(script)) + } + + taprootTree := txscript.AssembleTaprootScriptTree(leaves...) + + root := taprootTree.RootNode.TapHash() + internalKey, err := hex.DecodeString(desc.InternalKey.Hex) + if err != nil { + return nil, err + } + + internalKeyParsed, err := schnorr.ParsePubKey(internalKey) + if err != nil { + return nil, err + } + + taprootKey := txscript.ComputeTaprootOutputKey(internalKeyParsed, root[:]) + + outputScript, err := taprootOutputScript(taprootKey) + if err != nil { + return nil, err + } + + return outputScript, nil +} diff --git a/common/descriptor/ark.go b/common/descriptor/ark.go new file mode 100644 index 0000000..ab93963 --- /dev/null +++ b/common/descriptor/ark.go @@ -0,0 +1,45 @@ +package descriptor + +import ( + "encoding/hex" + "errors" + + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/decred/dcrd/dcrec/secp256k1/v4" +) + +const BoardingDescriptorTemplate = "tr(%s,{ and(pk(%s), pk(%s)), and(older(%d), pk(%s)) })" + +func ParseBoardingDescriptor( + desc TaprootDescriptor, +) (user *secp256k1.PublicKey, timeout uint, err error) { + for _, leaf := range desc.ScriptTree { + if andLeaf, ok := leaf.(*And); ok { + if first, ok := andLeaf.First.(*Older); ok { + timeout = first.Timeout + } + + if second, ok := andLeaf.Second.(*PK); ok { + keyBytes, err := hex.DecodeString(second.Key.Hex) + if err != nil { + return nil, 0, err + } + + user, err = schnorr.ParsePubKey(keyBytes) + if err != nil { + return nil, 0, err + } + } + } + } + + if user == nil { + return nil, 0, errors.New("boarding descriptor is invalid") + } + + if timeout == 0 { + return nil, 0, errors.New("boarding descriptor is invalid") + } + + return +} diff --git a/common/descriptor/expression.go b/common/descriptor/expression.go new file mode 100644 index 0000000..667c481 --- /dev/null +++ b/common/descriptor/expression.go @@ -0,0 +1,224 @@ +package descriptor + +import ( + "encoding/hex" + "errors" + "fmt" + "strconv" + "strings" + + "github.com/ark-network/ark/common" + "github.com/btcsuite/btcd/txscript" +) + +var ( + ErrInvalidXOnlyKey = errors.New("invalid x only public key") + ErrInvalidPkPolicy = errors.New("invalid public key policy") + ErrInvalidOlderPolicy = errors.New("invalid older policy") + ErrInvalidAndPolicy = errors.New("invalid and() policy") + ErrNotExpectedPolicy = errors.New("not the expected policy") +) + +type Expression interface { + Parse(policy string) error + Script(verify bool) (string, error) + String() string +} + +type XOnlyKey struct { + Key +} + +func (e *XOnlyKey) Parse(policy string) error { + if len(policy) != 64 { + fmt.Println(policy) + return ErrInvalidXOnlyKey + } + + e.Hex = policy + return nil +} + +func (e *XOnlyKey) Script() string { + return e.Hex +} + +// pk(xonlypubkey) +type PK struct { + Key XOnlyKey +} + +func (e *PK) String() string { + return fmt.Sprintf("pk(%s)", e.Key.Hex) +} + +func (e *PK) Parse(policy string) error { + if !strings.HasPrefix(policy, "pk(") { + return ErrNotExpectedPolicy + } + if len(policy) != 3+64+1 { + return ErrInvalidPkPolicy + } + + var key XOnlyKey + if err := key.Parse(policy[3 : 64+3]); err != nil { + return err + } + + e.Key = key + return nil +} + +func (e *PK) Script(verify bool) (string, error) { + pubkeyBytes, err := hex.DecodeString(e.Key.Hex) + if err != nil { + return "", err + } + + checksig := txscript.OP_CHECKSIG + if verify { + checksig = txscript.OP_CHECKSIGVERIFY + } + + script, err := txscript.NewScriptBuilder().AddData( + pubkeyBytes, + ).AddOp( + byte(checksig), + ).Script() + if err != nil { + return "", err + } + + return hex.EncodeToString(script), nil +} + +type Older struct { + Timeout uint +} + +func (e *Older) String() string { + return fmt.Sprintf("older(%d)", e.Timeout) +} + +func (e *Older) Parse(policy string) error { + if !strings.HasPrefix(policy, "older(") { + return ErrNotExpectedPolicy + } + + index := strings.IndexRune(policy, ')') + if index == -1 { + return ErrInvalidOlderPolicy + } + + number := policy[6:index] + if len(number) == 0 { + return ErrInvalidOlderPolicy + } + + timeout, err := strconv.Atoi(number) + if err != nil { + return ErrInvalidOlderPolicy + } + + e.Timeout = uint(timeout) + + return nil +} + +func (e *Older) Script(bool) (string, error) { + sequence, err := common.BIP68Encode(e.Timeout) + if err != nil { + return "", err + } + + script, err := txscript.NewScriptBuilder().AddData(sequence).AddOps([]byte{ + txscript.OP_CHECKSEQUENCEVERIFY, + txscript.OP_DROP, + }).Script() + if err != nil { + return "", err + } + + return hex.EncodeToString(script), nil +} + +type And struct { + First Expression + Second Expression +} + +func (e *And) String() string { + return fmt.Sprintf("and(%s,%s)", e.First.String(), e.Second.String()) +} + +func (e *And) Parse(policy string) error { + if !strings.HasPrefix(policy, "and(") { + return ErrNotExpectedPolicy + } + + index := strings.LastIndexByte(policy, ')') + if index == -1 { + return ErrInvalidAndPolicy + } + + childrenPolicy := policy[4:index] + if len(childrenPolicy) == 0 { + return ErrInvalidAndPolicy + } + + children := strings.Split(childrenPolicy, ",") + if len(children) != 2 { + fmt.Println(children) + return ErrInvalidAndPolicy + } + + first, err := parseExpression(children[0]) + if err != nil { + return err + } + + second, err := parseExpression(children[1]) + if err != nil { + return err + } + + e.First = first + e.Second = second + + return nil +} + +func (e *And) Script(verify bool) (string, error) { + firstScript, err := e.First.Script(true) + if err != nil { + return "", err + } + + secondScript, err := e.Second.Script(verify) + if err != nil { + return "", err + } + + return firstScript + secondScript, nil +} + +func parseExpression(policy string) (Expression, error) { + policy = strings.TrimSpace(policy) + expressions := make([]Expression, 0) + expressions = append(expressions, &PK{}) + expressions = append(expressions, &Older{}) + expressions = append(expressions, &And{}) + + for _, e := range expressions { + if err := e.Parse(policy); err != nil { + if err != ErrNotExpectedPolicy { + return nil, err + } + continue + } + + return e, nil + } + + return nil, fmt.Errorf("unable to parse expression '%s'", policy) +} diff --git a/common/descriptor/parser.go b/common/descriptor/parser.go new file mode 100644 index 0000000..3bd9caf --- /dev/null +++ b/common/descriptor/parser.go @@ -0,0 +1,125 @@ +package descriptor + +import ( + "encoding/hex" + "fmt" + "strings" +) + +// UnspendableKey is the x-only pubkey of the secp256k1 base point G +const UnspendableKey = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798" + +func ParseTaprootDescriptor(desc string) (*TaprootDescriptor, error) { + desc = strings.ReplaceAll(desc, " ", "") + + if !strings.HasPrefix(desc, "tr(") || !strings.HasSuffix(desc, ")") { + return nil, fmt.Errorf("invalid descriptor format") + } + + content := desc[3 : len(desc)-1] + parts := strings.SplitN(content, ",", 2) + + if len(parts) != 2 { + return nil, fmt.Errorf("invalid descriptor format: missing script tree") + } + + internalKey, err := parseKey(parts[0]) + if err != nil { + return nil, err + } + + scriptTreeStr := parts[1] + if !strings.HasPrefix(scriptTreeStr, "{") || !strings.HasSuffix(scriptTreeStr, "}") { + return nil, fmt.Errorf("invalid script tree format") + } + scriptTreeStr = scriptTreeStr[1 : len(scriptTreeStr)-1] + + scriptTree := []Expression{} + if scriptTreeStr != "" { + scriptParts, err := splitScriptTree(scriptTreeStr) + if err != nil { + return nil, err + } + for _, scriptStr := range scriptParts { + leaf, err := parseExpression(scriptStr) + if err != nil { + return nil, err + } + scriptTree = append(scriptTree, leaf) + } + } + + return &TaprootDescriptor{ + InternalKey: internalKey, + ScriptTree: scriptTree, + }, nil +} + +// CompileDescriptor compiles a TaprootDescriptor struct back into a descriptor string +func CompileDescriptor(desc TaprootDescriptor) string { + scriptParts := make([]string, len(desc.ScriptTree)) + for i, leaf := range desc.ScriptTree { + scriptParts[i] = leaf.String() + } + scriptTree := strings.Join(scriptParts, ",") + return fmt.Sprintf("tr(%s,{%s})", desc.InternalKey.Hex, scriptTree) +} + +func parseKey(keyStr string) (Key, error) { + decoded, err := hex.DecodeString(keyStr) + if err != nil { + return Key{}, fmt.Errorf("invalid key: not a valid hex string: %v", err) + } + + switch len(decoded) { + case 32: + // x-only public key, this is correct for Taproot + return Key{Hex: keyStr}, nil + case 33: + // compressed public key, we need to remove the prefix byte + return Key{Hex: keyStr[2:]}, nil + default: + return Key{}, fmt.Errorf("invalid key length: expected 32 or 33 bytes, got %d", len(decoded)) + } +} +func splitScriptTree(scriptTreeStr string) ([]string, error) { + var result []string + var current strings.Builder + depth := 0 + + for _, char := range scriptTreeStr { + switch char { + case '(': + depth++ + current.WriteRune(char) + case ')': + depth-- + current.WriteRune(char) + if depth == 0 { + result = append(result, current.String()) + current.Reset() + } + case ',': + if depth == 0 { + if current.Len() > 0 { + result = append(result, current.String()) + current.Reset() + } + } else { + current.WriteRune(char) + } + default: + current.WriteRune(char) + } + } + + if current.Len() > 0 { + result = append(result, current.String()) + } + + if depth != 0 { + return nil, fmt.Errorf("mismatched parentheses in script tree") + } + + return result, nil +} diff --git a/common/descriptor/parser_test.go b/common/descriptor/parser_test.go new file mode 100644 index 0000000..4684dcd --- /dev/null +++ b/common/descriptor/parser_test.go @@ -0,0 +1,350 @@ +package descriptor_test + +import ( + "testing" + + "github.com/ark-network/ark/common/descriptor" + "github.com/stretchr/testify/require" +) + +func TestParseTaprootDescriptor(t *testing.T) { + tests := []struct { + name string + desc string + expected descriptor.TaprootDescriptor + wantErr bool + }{ + { + name: "Basic Taproot", + desc: "tr(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,{pk(81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c)})", + expected: descriptor.TaprootDescriptor{ + InternalKey: descriptor.Key{Hex: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"}, + ScriptTree: []descriptor.Expression{ + &descriptor.PK{ + Key: descriptor.XOnlyKey{ + descriptor.Key{ + Hex: "81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c", + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "VTXO", + desc: "tr(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,{pk(81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c),and(pk(59bffef74a89f39715b9f6b8a83e53a60a458d45542f20e2e2f4f7dbffafc5f8),older(144))})", + expected: descriptor.TaprootDescriptor{ + InternalKey: descriptor.Key{Hex: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"}, + ScriptTree: []descriptor.Expression{ + &descriptor.PK{ + Key: descriptor.XOnlyKey{ + descriptor.Key{ + Hex: "81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c", + }, + }, + }, + &descriptor.And{ + First: &descriptor.PK{ + Key: descriptor.XOnlyKey{ + descriptor.Key{ + Hex: "59bffef74a89f39715b9f6b8a83e53a60a458d45542f20e2e2f4f7dbffafc5f8", + }, + }, + }, + Second: &descriptor.Older{ + Timeout: 144, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "Boarding", + desc: "tr(0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0,{ and(pk(873079a0091c9b16abd1f8c508320b07f0d50144d09ccd792ce9c915dac60465), pk(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)), and(older(604672), pk(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)) })", + expected: descriptor.TaprootDescriptor{ + InternalKey: descriptor.Key{Hex: "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"}, + ScriptTree: []descriptor.Expression{ + &descriptor.And{ + First: &descriptor.PK{ + Key: descriptor.XOnlyKey{ + descriptor.Key{ + Hex: "873079a0091c9b16abd1f8c508320b07f0d50144d09ccd792ce9c915dac60465", + }, + }, + }, + Second: &descriptor.PK{ + Key: descriptor.XOnlyKey{ + descriptor.Key{ + Hex: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + }, + }, + }, + }, + &descriptor.And{ + Second: &descriptor.PK{ + Key: descriptor.XOnlyKey{ + descriptor.Key{ + Hex: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + }, + }, + }, + First: &descriptor.Older{ + Timeout: 604672, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "Invalid Key", + desc: "tr(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798G,{pk(81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c)})", + expected: descriptor.TaprootDescriptor{}, + wantErr: true, + }, + { + name: "Invalid Descriptor Format", + desc: "tr(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)", + expected: descriptor.TaprootDescriptor{}, + wantErr: true, + }, + { + name: "Invalid Descriptor Format - Missing Script Tree", + desc: "tr(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)", + expected: descriptor.TaprootDescriptor{}, + wantErr: true, + }, + { + name: "Valid Empty Script Tree", + desc: "tr(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,{})", + expected: descriptor.TaprootDescriptor{ + InternalKey: descriptor.Key{Hex: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"}, + ScriptTree: []descriptor.Expression{}, + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := descriptor.ParseTaprootDescriptor(tt.desc) + if (err != nil) != tt.wantErr { + require.Equal(t, tt.wantErr, err != nil, err) + return + } + require.Equal(t, tt.expected, got) + }) + } +} + +func TestCompileDescriptor(t *testing.T) { + tests := []struct { + name string + desc descriptor.TaprootDescriptor + expected string + }{ + { + name: "Basic Taproot", + desc: descriptor.TaprootDescriptor{ + InternalKey: descriptor.Key{Hex: descriptor.UnspendableKey}, + ScriptTree: []descriptor.Expression{ + &descriptor.PK{ + Key: descriptor.XOnlyKey{ + descriptor.Key{ + Hex: "81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c", + }, + }, + }, + }, + }, + expected: "tr(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,{pk(81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c)})", + }, + { + name: "VTXO", + desc: descriptor.TaprootDescriptor{ + InternalKey: descriptor.Key{Hex: descriptor.UnspendableKey}, + ScriptTree: []descriptor.Expression{ + &descriptor.PK{ + Key: descriptor.XOnlyKey{ + descriptor.Key{ + Hex: "81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c", + }, + }, + }, + &descriptor.And{ + First: &descriptor.PK{ + Key: descriptor.XOnlyKey{ + descriptor.Key{ + Hex: "59bffef74a89f39715b9f6b8a83e53a60a458d45542f20e2e2f4f7dbffafc5f8", + }, + }, + }, + Second: &descriptor.Older{ + Timeout: 1024, + }, + }, + }, + }, + expected: "tr(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,{pk(81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c),and(pk(59bffef74a89f39715b9f6b8a83e53a60a458d45542f20e2e2f4f7dbffafc5f8),older(1024))})", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := descriptor.CompileDescriptor(tt.desc) + require.Equal(t, tt.expected, got) + }) + } +} + +func TestParsePk(t *testing.T) { + tests := []struct { + policy string + expectedScript string + expected descriptor.PK + verify bool + }{ + { + policy: "pk(81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c)", + expectedScript: "2081e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952cac", + verify: false, + expected: descriptor.PK{ + Key: descriptor.XOnlyKey{ + descriptor.Key{ + Hex: "81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c", + }, + }, + }, + }, + { + policy: "pk(81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c)", + expectedScript: "2081e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952cad", + verify: true, + expected: descriptor.PK{ + Key: descriptor.XOnlyKey{ + descriptor.Key{ + Hex: "81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c", + }, + }, + }, + }, + } + + for _, test := range tests { + var parsed descriptor.PK + err := parsed.Parse(test.policy) + require.NoError(t, err) + require.Equal(t, test.expected, parsed) + + script, err := parsed.Script(test.verify) + require.NoError(t, err) + require.Equal(t, test.expectedScript, script) + } +} + +func TestParseOlder(t *testing.T) { + tests := []struct { + policy string + expectedScript string + expected descriptor.Older + }{ + { + policy: "older(512)", + expectedScript: "03010040b275", + expected: descriptor.Older{ + Timeout: uint(512), + }, + }, + { + policy: "older(1024)", + expectedScript: "03020040b275", + expected: descriptor.Older{ + Timeout: uint(1024), + }, + }, + } + + for _, test := range tests { + var parsed descriptor.Older + err := parsed.Parse(test.policy) + require.NoError(t, err) + require.Equal(t, test.expected, parsed) + + script, err := parsed.Script(false) + require.NoError(t, err) + require.Equal(t, test.expectedScript, script) + } +} + +func TestParseAnd(t *testing.T) { + tests := []struct { + policy string + expectedScript string + expected descriptor.And + }{ + { + policy: "and(pk(81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c), older(512))", + expectedScript: "2081e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952cad03010040b275", + expected: descriptor.And{ + First: &descriptor.PK{ + Key: descriptor.XOnlyKey{ + descriptor.Key{ + Hex: "81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c", + }, + }, + }, + Second: &descriptor.Older{ + Timeout: 512, + }, + }, + }, + { + policy: "and(older(512), pk(81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c))", + expectedScript: "03010040b2752081e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952cac", + expected: descriptor.And{ + Second: &descriptor.PK{ + Key: descriptor.XOnlyKey{ + descriptor.Key{ + Hex: "81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c", + }, + }, + }, + First: &descriptor.Older{ + Timeout: 512, + }, + }, + }, + { + policy: "and(pk(81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c), pk(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798))", + expectedScript: "2081e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952cad2079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ac", + expected: descriptor.And{ + First: &descriptor.PK{ + Key: descriptor.XOnlyKey{ + descriptor.Key{ + Hex: "81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c", + }, + }, + }, + Second: &descriptor.PK{ + Key: descriptor.XOnlyKey{ + descriptor.Key{ + Hex: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + }, + }, + }, + }, + }, + } + + for _, test := range tests { + var parsed descriptor.And + err := parsed.Parse(test.policy) + require.NoError(t, err) + require.Equal(t, test.expected, parsed) + + script, err := parsed.Script(false) + require.NoError(t, err) + require.Equal(t, test.expectedScript, script) + } +} diff --git a/common/descriptor/types.go b/common/descriptor/types.go new file mode 100644 index 0000000..12ae89d --- /dev/null +++ b/common/descriptor/types.go @@ -0,0 +1,10 @@ +package descriptor + +type Key struct { + Hex string +} + +type TaprootDescriptor struct { + InternalKey Key + ScriptTree []Expression +} diff --git a/common/encoding.go b/common/encoding.go index b272668..caa6139 100644 --- a/common/encoding.go +++ b/common/encoding.go @@ -35,7 +35,7 @@ func EncodeAddress( func DecodeAddress( addr string, -) (hrp string, userKey *secp256k1.PublicKey, aspKey *secp256k1.PublicKey, err error) { +) (hrp string, userKey, aspKey *secp256k1.PublicKey, err error) { prefix, buf, err := bech32.DecodeNoLimit(addr) if err != nil { return diff --git a/common/tree/descriptor.go b/common/tree/descriptor.go new file mode 100644 index 0000000..a9d35ca --- /dev/null +++ b/common/tree/descriptor.go @@ -0,0 +1,52 @@ +package tree + +import ( + "encoding/hex" + + "github.com/ark-network/ark/common/descriptor" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/vulpemventures/go-elements/taproot" +) + +func ComputeOutputScript(desc descriptor.TaprootDescriptor) ([]byte, error) { + leaves := make([]taproot.TapElementsLeaf, 0) + + for _, l := range desc.ScriptTree { + script, err := l.Script(false) + if err != nil { + return nil, err + } + + scriptBytes, err := hex.DecodeString(script) + if err != nil { + return nil, err + } + + leaves = append(leaves, taproot.NewBaseTapElementsLeaf(scriptBytes)) + } + + taprootTree := taproot.AssembleTaprootScriptTree( + leaves..., + ) + + root := taprootTree.RootNode.TapHash() + + internalKey, err := hex.DecodeString(desc.InternalKey.Hex) + if err != nil { + return nil, err + } + + internalKeyParsed, err := schnorr.ParsePubKey(internalKey) + if err != nil { + return nil, err + } + + taprootKey := taproot.ComputeTaprootOutputKey(internalKeyParsed, root[:]) + + outputScript, err := taprootOutputScript(taprootKey) + if err != nil { + return nil, err + } + + return outputScript, nil +} diff --git a/common/tree/script.go b/common/tree/script.go index 687103c..b426b94 100644 --- a/common/tree/script.go +++ b/common/tree/script.go @@ -3,6 +3,7 @@ package tree import ( "bytes" "encoding/binary" + "encoding/hex" "fmt" "github.com/ark-network/ark/common" @@ -62,7 +63,7 @@ func DecodeClosure(script []byte) (Closure, error) { return closure, nil } - return nil, fmt.Errorf("invalid closure script") + return nil, fmt.Errorf("invalid closure script %s", hex.EncodeToString(script)) } func (f *ForfeitClosure) Leaf() (*taproot.TapElementsLeaf, error) { diff --git a/docker-compose.clark.regtest.yml b/docker-compose.clark.regtest.yml index cb703a1..f7a153f 100644 --- a/docker-compose.clark.regtest.yml +++ b/docker-compose.clark.regtest.yml @@ -12,21 +12,18 @@ services: - ARK_ROUND_LIFETIME=512 - ARK_TX_BUILDER_TYPE=covenantless - ARK_MIN_RELAY_FEE=200 - - ARK_NEUTRINO_PEER=bitcoin:18444 - ARK_ESPLORA_URL=http://chopsticks:3000 + - ARK_BITCOIND_RPC_USER=admin1 + - ARK_BITCOIND_RPC_PASS=123 + - ARK_BITCOIND_RPC_HOST=bitcoin:18443 - ARK_NO_TLS=true - ARK_NO_MACAROONS=true + - ARK_DATADIR=/app/data ports: - "7070:7070" volumes: - - clarkd:/app/data - - clark:/app/wallet-data - -volumes: - clarkd: - external: false - clark: - external: false + - type: tmpfs + target: /app/data networks: default: diff --git a/docker-compose.regtest.yml b/docker-compose.regtest.yml index 2da9e6d..5fa2072 100644 --- a/docker-compose.regtest.yml +++ b/docker-compose.regtest.yml @@ -12,11 +12,12 @@ services: - OCEAN_NETWORK=regtest - OCEAN_UTXO_EXPIRY_DURATION_IN_SECONDS=60 - OCEAN_DB_TYPE=badger + - OCEAN_DATADIR=/app/data ports: - "18000:18000" volumes: - - oceand:/app/data/oceand - - ocean:/app/data/ocean + - type: tmpfs + target: /app/data arkd: container_name: arkd build: @@ -36,21 +37,12 @@ services: - ARK_PORT=6060 - ARK_NO_TLS=true - ARK_NO_MACAROONS=true + - ARK_DATADIR=/app/data ports: - "6060:6060" volumes: - - arkd:/app/data - - ark:/app/wallet-data - -volumes: - oceand: - external: false - ocean: - external: false - arkd: - external: false - ark: - external: false + - type: tmpfs + target: /app/data networks: default: diff --git a/pkg/client-sdk/ark_sdk.go b/pkg/client-sdk/ark_sdk.go index 0853580..57540a5 100644 --- a/pkg/client-sdk/ark_sdk.go +++ b/pkg/client-sdk/ark_sdk.go @@ -15,7 +15,6 @@ type ArkClient interface { Unlock(ctx context.Context, password string) error Lock(ctx context.Context, password string) error Balance(ctx context.Context, computeExpiryDetails bool) (*Balance, error) - Onboard(ctx context.Context, amount uint64) (string, error) Receive(ctx context.Context) (string, string, error) SendOnChain(ctx context.Context, receivers []Receiver) (string, error) SendOffChain( @@ -26,7 +25,7 @@ type ArkClient interface { ctx context.Context, addr string, amount uint64, withExpiryCoinselect bool, ) (string, error) SendAsync(ctx context.Context, withExpiryCoinselect bool, receivers []Receiver) (string, error) - ClaimAsync(ctx context.Context) (string, error) + Claim(ctx context.Context) (string, error) ListVtxos(ctx context.Context) ([]client.Vtxo, []client.Vtxo, error) } diff --git a/pkg/client-sdk/client.go b/pkg/client-sdk/client.go index ab0b026..991b2a0 100644 --- a/pkg/client-sdk/client.go +++ b/pkg/client-sdk/client.go @@ -92,14 +92,15 @@ func (a *arkClient) InitWithWallet( } storeData := store.StoreData{ - AspUrl: args.AspUrl, - AspPubkey: aspPubkey, - WalletType: args.Wallet.GetType(), - ClientType: args.ClientType, - Network: network, - RoundLifetime: info.RoundLifetime, - UnilateralExitDelay: info.UnilateralExitDelay, - MinRelayFee: uint64(info.MinRelayFee), + AspUrl: args.AspUrl, + AspPubkey: aspPubkey, + WalletType: args.Wallet.GetType(), + ClientType: args.ClientType, + Network: network, + RoundLifetime: info.RoundLifetime, + UnilateralExitDelay: info.UnilateralExitDelay, + MinRelayFee: uint64(info.MinRelayFee), + BoardingDescriptorTemplate: info.BoardingDescriptorTemplate, } if err := a.store.AddData(ctx, storeData); err != nil { return err @@ -155,14 +156,15 @@ func (a *arkClient) Init( } storeData := store.StoreData{ - AspUrl: args.AspUrl, - AspPubkey: aspPubkey, - WalletType: args.WalletType, - ClientType: args.ClientType, - Network: network, - RoundLifetime: info.RoundLifetime, - UnilateralExitDelay: info.UnilateralExitDelay, - MinRelayFee: uint64(info.MinRelayFee), + AspUrl: args.AspUrl, + AspPubkey: aspPubkey, + WalletType: args.WalletType, + ClientType: args.ClientType, + Network: network, + RoundLifetime: info.RoundLifetime, + UnilateralExitDelay: info.UnilateralExitDelay, + MinRelayFee: uint64(info.MinRelayFee), + BoardingDescriptorTemplate: info.BoardingDescriptorTemplate, } walletSvc, err := getWallet(a.store, &storeData, supportedWallets) if err != nil { @@ -201,12 +203,12 @@ func (a *arkClient) IsLocked(ctx context.Context) bool { } func (a *arkClient) Receive(ctx context.Context) (string, string, error) { - offchainAddr, onchainAddr, err := a.wallet.NewAddress(ctx, false) + offchainAddr, boardingAddr, err := a.wallet.NewAddress(ctx, false) if err != nil { return "", "", err } - return offchainAddr, onchainAddr, nil + return offchainAddr, boardingAddr, nil } func (a *arkClient) ListVtxos( @@ -232,14 +234,11 @@ func (a *arkClient) ListVtxos( func (a *arkClient) ping( ctx context.Context, paymentID string, ) func() { - _, err := a.client.Ping(ctx, paymentID) - if err != nil { - return nil - } - ticker := time.NewTicker(5 * time.Second) go func(t *time.Ticker) { + // nolint + a.client.Ping(ctx, paymentID) for range t.C { // nolint a.client.Ping(ctx, paymentID) diff --git a/pkg/client-sdk/client/client.go b/pkg/client-sdk/client/client.go index 2280da6..5d1c819 100644 --- a/pkg/client-sdk/client/client.go +++ b/pkg/client-sdk/client/client.go @@ -23,11 +23,8 @@ type ASPClient interface { ListVtxos(ctx context.Context, addr string) ([]Vtxo, []Vtxo, error) GetRound(ctx context.Context, txID string) (*Round, error) GetRoundByID(ctx context.Context, roundID string) (*Round, error) - Onboard( - ctx context.Context, tx, userPubkey string, congestionTree tree.CongestionTree, - ) error RegisterPayment( - ctx context.Context, inputs []VtxoKey, ephemeralPublicKey string, + ctx context.Context, inputs []Input, ephemeralKey string, ) (string, error) ClaimPayment( ctx context.Context, paymentID string, outputs []Output, @@ -37,7 +34,7 @@ type ASPClient interface { ) (<-chan RoundEventChannel, error) Ping(ctx context.Context, paymentID string) (RoundEvent, error) FinalizePayment( - ctx context.Context, signedForfeitTxs []string, + ctx context.Context, signedForfeitTxs []string, signedRoundTx string, ) error CreatePayment( ctx context.Context, inputs []VtxoKey, outputs []Output, @@ -45,6 +42,7 @@ type ASPClient interface { CompletePayment( ctx context.Context, signedRedeemTx string, signedUnconditionalForfeitTxs []string, ) error + GetBoardingAddress(ctx context.Context, userPubkey string) (string, error) SendTreeNonces( ctx context.Context, roundID, cosignerPubkey string, nonces bitcointree.TreeNonces, ) error @@ -55,12 +53,13 @@ type ASPClient interface { } type Info struct { - Pubkey string - RoundLifetime int64 - UnilateralExitDelay int64 - RoundInterval int64 - Network string - MinRelayFee int64 + Pubkey string + RoundLifetime int64 + UnilateralExitDelay int64 + RoundInterval int64 + Network string + MinRelayFee int64 + BoardingDescriptorTemplate string } type RoundEventChannel struct { @@ -68,11 +67,38 @@ type RoundEventChannel struct { Err error } +type Input interface { + GetTxID() string + GetVOut() uint32 + GetDescriptor() string +} + type VtxoKey struct { Txid string VOut uint32 } +func (k VtxoKey) GetTxID() string { + return k.Txid +} + +func (k VtxoKey) GetVOut() uint32 { + return k.VOut +} + +func (k VtxoKey) GetDescriptor() string { + return "" +} + +type BoardingInput struct { + VtxoKey + Descriptor string +} + +func (k BoardingInput) GetDescriptor() string { + return k.Descriptor +} + type Vtxo struct { VtxoKey Amount uint64 diff --git a/pkg/client-sdk/client/grpc/client.go b/pkg/client-sdk/client/grpc/client.go index 11bb0f2..8702495 100644 --- a/pkg/client-sdk/client/grpc/client.go +++ b/pkg/client-sdk/client/grpc/client.go @@ -97,12 +97,13 @@ func (a *grpcClient) GetInfo(ctx context.Context) (*client.Info, error) { return nil, err } return &client.Info{ - Pubkey: resp.GetPubkey(), - RoundLifetime: resp.GetRoundLifetime(), - UnilateralExitDelay: resp.GetUnilateralExitDelay(), - RoundInterval: resp.GetRoundInterval(), - Network: resp.GetNetwork(), - MinRelayFee: resp.GetMinRelayFee(), + Pubkey: resp.GetPubkey(), + RoundLifetime: resp.GetRoundLifetime(), + UnilateralExitDelay: resp.GetUnilateralExitDelay(), + RoundInterval: resp.GetRoundInterval(), + Network: resp.GetNetwork(), + MinRelayFee: resp.GetMinRelayFee(), + BoardingDescriptorTemplate: resp.GetBoardingDescriptorTemplate(), }, nil } @@ -143,20 +144,8 @@ func (a *grpcClient) GetRound( }, nil } -func (a *grpcClient) Onboard( - ctx context.Context, tx, userPubkey string, congestionTree tree.CongestionTree, -) error { - req := &arkv1.OnboardRequest{ - BoardingTx: tx, - UserPubkey: userPubkey, - CongestionTree: treeToProto(congestionTree).parse(), - } - _, err := a.svc.Onboard(ctx, req) - return err -} - func (a *grpcClient) RegisterPayment( - ctx context.Context, inputs []client.VtxoKey, ephemeralPublicKey string, + ctx context.Context, inputs []client.Input, ephemeralPublicKey string, ) (string, error) { req := &arkv1.RegisterPaymentRequest{ Inputs: ins(inputs).toProto(), @@ -198,11 +187,16 @@ func (a *grpcClient) Ping( } func (a *grpcClient) FinalizePayment( - ctx context.Context, signedForfeitTxs []string, + ctx context.Context, signedForfeitTxs []string, signedRoundTx string, ) error { req := &arkv1.FinalizePaymentRequest{ SignedForfeitTxs: signedForfeitTxs, } + + if len(signedRoundTx) > 0 { + req.SignedRoundTx = &signedRoundTx + } + _, err := a.svc.FinalizePayment(ctx, req) return err } @@ -210,8 +204,13 @@ func (a *grpcClient) FinalizePayment( func (a *grpcClient) CreatePayment( ctx context.Context, inputs []client.VtxoKey, outputs []client.Output, ) (string, []string, error) { + insCast := make([]client.Input, 0, len(inputs)) + for _, in := range inputs { + insCast = append(insCast, in) + } + req := &arkv1.CreatePaymentRequest{ - Inputs: ins(inputs).toProto(), + Inputs: ins(insCast).toProto(), Outputs: outs(outputs).toProto(), } resp, err := a.svc.CreatePayment(ctx, req) @@ -260,6 +259,19 @@ func (a *grpcClient) GetRoundByID( }, nil } +func (a *grpcClient) GetBoardingAddress( + ctx context.Context, userPubkey string, +) (string, error) { + req := &arkv1.GetBoardingAddressRequest{ + Pubkey: userPubkey, + } + resp, err := a.svc.GetBoardingAddress(ctx, req) + if err != nil { + return "", err + } + return resp.GetAddress(), nil +} + func (a *grpcClient) SendTreeNonces( ctx context.Context, roundID, cosignerPubkey string, nonces bitcointree.TreeNonces, ) error { @@ -418,8 +430,8 @@ func (v vtxo) toVtxo() client.Vtxo { } return client.Vtxo{ VtxoKey: client.VtxoKey{ - Txid: v.GetOutpoint().GetTxid(), - VOut: v.GetOutpoint().GetVout(), + Txid: v.GetOutpoint().GetVtxoInput().GetTxid(), + VOut: v.GetOutpoint().GetVtxoInput().GetVout(), }, Amount: v.GetReceiver().GetAmount(), RoundTxid: v.GetPoolTxid(), @@ -441,21 +453,35 @@ func (v vtxos) toVtxos() []client.Vtxo { return list } -type input client.VtxoKey +func toProtoInput(i client.Input) *arkv1.Input { + if len(i.GetDescriptor()) > 0 { + return &arkv1.Input{ + Input: &arkv1.Input_BoardingInput{ + BoardingInput: &arkv1.BoardingInput{ + Txid: i.GetTxID(), + Vout: i.GetVOut(), + Descriptor_: i.GetDescriptor(), + }, + }, + } + } -func (i input) toProto() *arkv1.Input { return &arkv1.Input{ - Txid: i.Txid, - Vout: i.VOut, + Input: &arkv1.Input_VtxoInput{ + VtxoInput: &arkv1.VtxoInput{ + Txid: i.GetTxID(), + Vout: i.GetVOut(), + }, + }, } } -type ins []client.VtxoKey +type ins []client.Input func (i ins) toProto() []*arkv1.Input { list := make([]*arkv1.Input, 0, len(i)) for _, ii := range i { - list = append(list, input(ii).toProto()) + list = append(list, toProtoInput(ii)) } return list } @@ -496,27 +522,3 @@ func (t treeFromProto) parse() tree.CongestionTree { return levels } - -type treeToProto tree.CongestionTree - -func (t treeToProto) parse() *arkv1.Tree { - levels := make([]*arkv1.TreeLevel, 0, len(t)) - for _, level := range t { - levelProto := &arkv1.TreeLevel{ - Nodes: make([]*arkv1.Node, 0, len(level)), - } - - for _, node := range level { - levelProto.Nodes = append(levelProto.Nodes, &arkv1.Node{ - Txid: node.Txid, - Tx: node.Tx, - ParentTxid: node.ParentTxid, - }) - } - - levels = append(levels, levelProto) - } - return &arkv1.Tree{ - Levels: levels, - } -} diff --git a/pkg/client-sdk/client/rest/client.go b/pkg/client-sdk/client/rest/client.go index 718b8c6..aae1aff 100644 --- a/pkg/client-sdk/client/rest/client.go +++ b/pkg/client-sdk/client/rest/client.go @@ -114,12 +114,13 @@ func (a *restClient) GetInfo( } return &client.Info{ - Pubkey: resp.Payload.Pubkey, - RoundLifetime: int64(roundLifetime), - UnilateralExitDelay: int64(unilateralExitDelay), - RoundInterval: int64(roundInterval), - Network: resp.Payload.Network, - MinRelayFee: int64(minRelayFee), + Pubkey: resp.Payload.Pubkey, + RoundLifetime: int64(roundLifetime), + UnilateralExitDelay: int64(unilateralExitDelay), + RoundInterval: int64(roundInterval), + Network: resp.Payload.Network, + MinRelayFee: int64(minRelayFee), + BoardingDescriptorTemplate: resp.Payload.BoardingDescriptorTemplate, }, nil } @@ -159,8 +160,8 @@ func (a *restClient) ListVtxos( spendableVtxos = append(spendableVtxos, client.Vtxo{ VtxoKey: client.VtxoKey{ - Txid: v.Outpoint.Txid, - VOut: uint32(v.Outpoint.Vout), + Txid: v.Outpoint.VtxoInput.Txid, + VOut: uint32(v.Outpoint.VtxoInput.Vout), }, Amount: uint64(amount), RoundTxid: v.PoolTxid, @@ -191,8 +192,8 @@ func (a *restClient) ListVtxos( spentVtxos = append(spentVtxos, client.Vtxo{ VtxoKey: client.VtxoKey{ - Txid: v.Outpoint.Txid, - VOut: uint32(v.Outpoint.Vout), + Txid: v.Outpoint.VtxoInput.Txid, + VOut: uint32(v.Outpoint.VtxoInput.Vout), }, Amount: uint64(amount), RoundTxid: v.PoolTxid, @@ -243,29 +244,31 @@ func (a *restClient) GetRound( }, nil } -func (a *restClient) Onboard( - ctx context.Context, tx, userPubkey string, congestionTree tree.CongestionTree, -) error { - body := models.V1OnboardRequest{ - BoardingTx: tx, - CongestionTree: treeToProto(congestionTree).parse(), - UserPubkey: userPubkey, - } - _, err := a.svc.ArkServiceOnboard( - ark_service.NewArkServiceOnboardParams().WithBody(&body), - ) - return err -} - func (a *restClient) RegisterPayment( - ctx context.Context, inputs []client.VtxoKey, ephemeralPublicKey string, + ctx context.Context, inputs []client.Input, ephemeralPublicKey string, ) (string, error) { ins := make([]*models.V1Input, 0, len(inputs)) for _, i := range inputs { - ins = append(ins, &models.V1Input{ - Txid: i.Txid, - Vout: int64(i.VOut), - }) + var input *models.V1Input + + if len(i.GetDescriptor()) > 0 { + input = &models.V1Input{ + BoardingInput: &models.V1BoardingInput{ + Txid: i.GetTxID(), + Vout: int64(i.GetVOut()), + Descriptor: i.GetDescriptor(), + }, + } + } else { + input = &models.V1Input{ + VtxoInput: &models.V1VtxoInput{ + Txid: i.GetTxID(), + Vout: int64(i.GetVOut()), + }, + } + } + + ins = append(ins, input) } body := &models.V1RegisterPaymentRequest{ Inputs: ins, @@ -377,13 +380,11 @@ func (a *restClient) Ping( } func (a *restClient) FinalizePayment( - ctx context.Context, signedForfeitTxs []string, + ctx context.Context, signedForfeitTxs []string, signedRoundTx string, ) error { - req := &arkv1.FinalizePaymentRequest{ - SignedForfeitTxs: signedForfeitTxs, - } body := models.V1FinalizePaymentRequest{ - SignedForfeitTxs: req.GetSignedForfeitTxs(), + SignedForfeitTxs: signedForfeitTxs, + SignedRoundTx: signedRoundTx, } _, err := a.svc.ArkServiceFinalizePayment( ark_service.NewArkServiceFinalizePaymentParams().WithBody(&body), @@ -396,9 +397,15 @@ func (a *restClient) CreatePayment( ) (string, []string, error) { ins := make([]*models.V1Input, 0, len(inputs)) for _, i := range inputs { + if len(i.GetDescriptor()) > 0 { + return "", nil, fmt.Errorf("boarding inputs are not allowed in create payment") + } + ins = append(ins, &models.V1Input{ - Txid: i.Txid, - Vout: int64(i.VOut), + VtxoInput: &models.V1VtxoInput{ + Txid: i.Txid, + Vout: int64(i.VOut), + }, }) } outs := make([]*models.V1Output, 0, len(outputs)) @@ -477,6 +484,23 @@ func (a *restClient) GetRoundByID( }, nil } +func (a *restClient) GetBoardingAddress( + ctx context.Context, pubkey string, +) (string, error) { + body := models.V1GetBoardingAddressRequest{ + Pubkey: pubkey, + } + + resp, err := a.svc.ArkServiceGetBoardingAddress( + ark_service.NewArkServiceGetBoardingAddressParams().WithBody(&body), + ) + if err != nil { + return "", + err + } + return resp.Payload.Address, nil +} + func (a *restClient) SendTreeNonces( ctx context.Context, roundID, cosignerPubkey string, nonces bitcointree.TreeNonces, ) error { @@ -604,25 +628,3 @@ func (t treeFromProto) parse() tree.CongestionTree { return congestionTree } - -type treeToProto tree.CongestionTree - -func (t treeToProto) parse() *models.V1Tree { - levels := make([]*models.V1TreeLevel, 0, len(t)) - for _, level := range t { - nodes := make([]*models.V1Node, 0, len(level)) - for _, n := range level { - nodes = append(nodes, &models.V1Node{ - Txid: n.Txid, - Tx: n.Tx, - ParentTxid: n.ParentTxid, - }) - } - levels = append(levels, &models.V1TreeLevel{ - Nodes: nodes, - }) - } - return &models.V1Tree{ - Levels: levels, - } -} diff --git a/pkg/client-sdk/client/rest/service/arkservice/ark_service/ark_service_client.go b/pkg/client-sdk/client/rest/service/arkservice/ark_service/ark_service_client.go index 94689fe..dc1745f 100644 --- a/pkg/client-sdk/client/rest/service/arkservice/ark_service/ark_service_client.go +++ b/pkg/client-sdk/client/rest/service/arkservice/ark_service/ark_service_client.go @@ -62,6 +62,8 @@ type ClientService interface { ArkServiceFinalizePayment(params *ArkServiceFinalizePaymentParams, opts ...ClientOption) (*ArkServiceFinalizePaymentOK, error) + ArkServiceGetBoardingAddress(params *ArkServiceGetBoardingAddressParams, opts ...ClientOption) (*ArkServiceGetBoardingAddressOK, error) + ArkServiceGetEventStream(params *ArkServiceGetEventStreamParams, opts ...ClientOption) (*ArkServiceGetEventStreamOK, error) ArkServiceGetInfo(params *ArkServiceGetInfoParams, opts ...ClientOption) (*ArkServiceGetInfoOK, error) @@ -72,8 +74,6 @@ type ClientService interface { ArkServiceListVtxos(params *ArkServiceListVtxosParams, opts ...ClientOption) (*ArkServiceListVtxosOK, error) - ArkServiceOnboard(params *ArkServiceOnboardParams, opts ...ClientOption) (*ArkServiceOnboardOK, error) - ArkServicePing(params *ArkServicePingParams, opts ...ClientOption) (*ArkServicePingOK, error) ArkServiceRegisterPayment(params *ArkServiceRegisterPaymentParams, opts ...ClientOption) (*ArkServiceRegisterPaymentOK, error) @@ -233,6 +233,43 @@ func (a *Client) ArkServiceFinalizePayment(params *ArkServiceFinalizePaymentPara return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) } +/* +ArkServiceGetBoardingAddress ark service get boarding address API +*/ +func (a *Client) ArkServiceGetBoardingAddress(params *ArkServiceGetBoardingAddressParams, opts ...ClientOption) (*ArkServiceGetBoardingAddressOK, error) { + // TODO: Validate the params before sending + if params == nil { + params = NewArkServiceGetBoardingAddressParams() + } + op := &runtime.ClientOperation{ + ID: "ArkService_GetBoardingAddress", + Method: "POST", + PathPattern: "/v1/boarding", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &ArkServiceGetBoardingAddressReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + success, ok := result.(*ArkServiceGetBoardingAddressOK) + if ok { + return success, nil + } + // unexpected success response + unexpectedSuccess := result.(*ArkServiceGetBoardingAddressDefault) + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + /* ArkServiceGetEventStream ark service get event stream API */ @@ -418,43 +455,6 @@ func (a *Client) ArkServiceListVtxos(params *ArkServiceListVtxosParams, opts ... return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) } -/* -ArkServiceOnboard ark service onboard API -*/ -func (a *Client) ArkServiceOnboard(params *ArkServiceOnboardParams, opts ...ClientOption) (*ArkServiceOnboardOK, error) { - // TODO: Validate the params before sending - if params == nil { - params = NewArkServiceOnboardParams() - } - op := &runtime.ClientOperation{ - ID: "ArkService_Onboard", - Method: "POST", - PathPattern: "/v1/onboard", - ProducesMediaTypes: []string{"application/json"}, - ConsumesMediaTypes: []string{"application/json"}, - Schemes: []string{"http"}, - Params: params, - Reader: &ArkServiceOnboardReader{formats: a.formats}, - Context: params.Context, - Client: params.HTTPClient, - } - for _, opt := range opts { - opt(op) - } - - result, err := a.transport.Submit(op) - if err != nil { - return nil, err - } - success, ok := result.(*ArkServiceOnboardOK) - if ok { - return success, nil - } - // unexpected success response - unexpectedSuccess := result.(*ArkServiceOnboardDefault) - return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) -} - /* ArkServicePing ark service ping API */ diff --git a/pkg/client-sdk/client/rest/service/arkservice/ark_service/ark_service_get_boarding_address_parameters.go b/pkg/client-sdk/client/rest/service/arkservice/ark_service/ark_service_get_boarding_address_parameters.go new file mode 100644 index 0000000..180ad0e --- /dev/null +++ b/pkg/client-sdk/client/rest/service/arkservice/ark_service/ark_service_get_boarding_address_parameters.go @@ -0,0 +1,150 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package ark_service + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" + + "github.com/ark-network/ark/pkg/client-sdk/client/rest/service/models" +) + +// NewArkServiceGetBoardingAddressParams creates a new ArkServiceGetBoardingAddressParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewArkServiceGetBoardingAddressParams() *ArkServiceGetBoardingAddressParams { + return &ArkServiceGetBoardingAddressParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewArkServiceGetBoardingAddressParamsWithTimeout creates a new ArkServiceGetBoardingAddressParams object +// with the ability to set a timeout on a request. +func NewArkServiceGetBoardingAddressParamsWithTimeout(timeout time.Duration) *ArkServiceGetBoardingAddressParams { + return &ArkServiceGetBoardingAddressParams{ + timeout: timeout, + } +} + +// NewArkServiceGetBoardingAddressParamsWithContext creates a new ArkServiceGetBoardingAddressParams object +// with the ability to set a context for a request. +func NewArkServiceGetBoardingAddressParamsWithContext(ctx context.Context) *ArkServiceGetBoardingAddressParams { + return &ArkServiceGetBoardingAddressParams{ + Context: ctx, + } +} + +// NewArkServiceGetBoardingAddressParamsWithHTTPClient creates a new ArkServiceGetBoardingAddressParams object +// with the ability to set a custom HTTPClient for a request. +func NewArkServiceGetBoardingAddressParamsWithHTTPClient(client *http.Client) *ArkServiceGetBoardingAddressParams { + return &ArkServiceGetBoardingAddressParams{ + HTTPClient: client, + } +} + +/* +ArkServiceGetBoardingAddressParams contains all the parameters to send to the API endpoint + + for the ark service get boarding address operation. + + Typically these are written to a http.Request. +*/ +type ArkServiceGetBoardingAddressParams struct { + + // Body. + Body *models.V1GetBoardingAddressRequest + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the ark service get boarding address params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *ArkServiceGetBoardingAddressParams) WithDefaults() *ArkServiceGetBoardingAddressParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the ark service get boarding address params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *ArkServiceGetBoardingAddressParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the ark service get boarding address params +func (o *ArkServiceGetBoardingAddressParams) WithTimeout(timeout time.Duration) *ArkServiceGetBoardingAddressParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the ark service get boarding address params +func (o *ArkServiceGetBoardingAddressParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the ark service get boarding address params +func (o *ArkServiceGetBoardingAddressParams) WithContext(ctx context.Context) *ArkServiceGetBoardingAddressParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the ark service get boarding address params +func (o *ArkServiceGetBoardingAddressParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the ark service get boarding address params +func (o *ArkServiceGetBoardingAddressParams) WithHTTPClient(client *http.Client) *ArkServiceGetBoardingAddressParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the ark service get boarding address params +func (o *ArkServiceGetBoardingAddressParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithBody adds the body to the ark service get boarding address params +func (o *ArkServiceGetBoardingAddressParams) WithBody(body *models.V1GetBoardingAddressRequest) *ArkServiceGetBoardingAddressParams { + o.SetBody(body) + return o +} + +// SetBody adds the body to the ark service get boarding address params +func (o *ArkServiceGetBoardingAddressParams) SetBody(body *models.V1GetBoardingAddressRequest) { + o.Body = body +} + +// WriteToRequest writes these params to a swagger request +func (o *ArkServiceGetBoardingAddressParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + if o.Body != nil { + if err := r.SetBodyParam(o.Body); err != nil { + return err + } + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/pkg/client-sdk/client/rest/service/arkservice/ark_service/ark_service_get_boarding_address_responses.go b/pkg/client-sdk/client/rest/service/arkservice/ark_service/ark_service_get_boarding_address_responses.go new file mode 100644 index 0000000..0fff40c --- /dev/null +++ b/pkg/client-sdk/client/rest/service/arkservice/ark_service/ark_service_get_boarding_address_responses.go @@ -0,0 +1,187 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package ark_service + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "encoding/json" + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + "github.com/ark-network/ark/pkg/client-sdk/client/rest/service/models" +) + +// ArkServiceGetBoardingAddressReader is a Reader for the ArkServiceGetBoardingAddress structure. +type ArkServiceGetBoardingAddressReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *ArkServiceGetBoardingAddressReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) { + switch response.Code() { + case 200: + result := NewArkServiceGetBoardingAddressOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewArkServiceGetBoardingAddressDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewArkServiceGetBoardingAddressOK creates a ArkServiceGetBoardingAddressOK with default headers values +func NewArkServiceGetBoardingAddressOK() *ArkServiceGetBoardingAddressOK { + return &ArkServiceGetBoardingAddressOK{} +} + +/* +ArkServiceGetBoardingAddressOK describes a response with status code 200, with default header values. + +A successful response. +*/ +type ArkServiceGetBoardingAddressOK struct { + Payload *models.V1GetBoardingAddressResponse +} + +// IsSuccess returns true when this ark service get boarding address o k response has a 2xx status code +func (o *ArkServiceGetBoardingAddressOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this ark service get boarding address o k response has a 3xx status code +func (o *ArkServiceGetBoardingAddressOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this ark service get boarding address o k response has a 4xx status code +func (o *ArkServiceGetBoardingAddressOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this ark service get boarding address o k response has a 5xx status code +func (o *ArkServiceGetBoardingAddressOK) IsServerError() bool { + return false +} + +// IsCode returns true when this ark service get boarding address o k response a status code equal to that given +func (o *ArkServiceGetBoardingAddressOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the ark service get boarding address o k response +func (o *ArkServiceGetBoardingAddressOK) Code() int { + return 200 +} + +func (o *ArkServiceGetBoardingAddressOK) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /v1/boarding][%d] arkServiceGetBoardingAddressOK %s", 200, payload) +} + +func (o *ArkServiceGetBoardingAddressOK) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /v1/boarding][%d] arkServiceGetBoardingAddressOK %s", 200, payload) +} + +func (o *ArkServiceGetBoardingAddressOK) GetPayload() *models.V1GetBoardingAddressResponse { + return o.Payload +} + +func (o *ArkServiceGetBoardingAddressOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.V1GetBoardingAddressResponse) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} + +// NewArkServiceGetBoardingAddressDefault creates a ArkServiceGetBoardingAddressDefault with default headers values +func NewArkServiceGetBoardingAddressDefault(code int) *ArkServiceGetBoardingAddressDefault { + return &ArkServiceGetBoardingAddressDefault{ + _statusCode: code, + } +} + +/* +ArkServiceGetBoardingAddressDefault describes a response with status code -1, with default header values. + +An unexpected error response. +*/ +type ArkServiceGetBoardingAddressDefault struct { + _statusCode int + + Payload *models.RPCStatus +} + +// IsSuccess returns true when this ark service get boarding address default response has a 2xx status code +func (o *ArkServiceGetBoardingAddressDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this ark service get boarding address default response has a 3xx status code +func (o *ArkServiceGetBoardingAddressDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this ark service get boarding address default response has a 4xx status code +func (o *ArkServiceGetBoardingAddressDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this ark service get boarding address default response has a 5xx status code +func (o *ArkServiceGetBoardingAddressDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this ark service get boarding address default response a status code equal to that given +func (o *ArkServiceGetBoardingAddressDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the ark service get boarding address default response +func (o *ArkServiceGetBoardingAddressDefault) Code() int { + return o._statusCode +} + +func (o *ArkServiceGetBoardingAddressDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /v1/boarding][%d] ArkService_GetBoardingAddress default %s", o._statusCode, payload) +} + +func (o *ArkServiceGetBoardingAddressDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /v1/boarding][%d] ArkService_GetBoardingAddress default %s", o._statusCode, payload) +} + +func (o *ArkServiceGetBoardingAddressDefault) GetPayload() *models.RPCStatus { + return o.Payload +} + +func (o *ArkServiceGetBoardingAddressDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.RPCStatus) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} diff --git a/pkg/client-sdk/client/rest/service/arkservice/ark_service/ark_service_onboard_parameters.go b/pkg/client-sdk/client/rest/service/arkservice/ark_service/ark_service_onboard_parameters.go deleted file mode 100644 index 1c76487..0000000 --- a/pkg/client-sdk/client/rest/service/arkservice/ark_service/ark_service_onboard_parameters.go +++ /dev/null @@ -1,150 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package ark_service - -// This file was generated by the swagger tool. -// Editing this file might prove futile when you re-run the swagger generate command - -import ( - "context" - "net/http" - "time" - - "github.com/go-openapi/errors" - "github.com/go-openapi/runtime" - cr "github.com/go-openapi/runtime/client" - "github.com/go-openapi/strfmt" - - "github.com/ark-network/ark/pkg/client-sdk/client/rest/service/models" -) - -// NewArkServiceOnboardParams creates a new ArkServiceOnboardParams object, -// with the default timeout for this client. -// -// Default values are not hydrated, since defaults are normally applied by the API server side. -// -// To enforce default values in parameter, use SetDefaults or WithDefaults. -func NewArkServiceOnboardParams() *ArkServiceOnboardParams { - return &ArkServiceOnboardParams{ - timeout: cr.DefaultTimeout, - } -} - -// NewArkServiceOnboardParamsWithTimeout creates a new ArkServiceOnboardParams object -// with the ability to set a timeout on a request. -func NewArkServiceOnboardParamsWithTimeout(timeout time.Duration) *ArkServiceOnboardParams { - return &ArkServiceOnboardParams{ - timeout: timeout, - } -} - -// NewArkServiceOnboardParamsWithContext creates a new ArkServiceOnboardParams object -// with the ability to set a context for a request. -func NewArkServiceOnboardParamsWithContext(ctx context.Context) *ArkServiceOnboardParams { - return &ArkServiceOnboardParams{ - Context: ctx, - } -} - -// NewArkServiceOnboardParamsWithHTTPClient creates a new ArkServiceOnboardParams object -// with the ability to set a custom HTTPClient for a request. -func NewArkServiceOnboardParamsWithHTTPClient(client *http.Client) *ArkServiceOnboardParams { - return &ArkServiceOnboardParams{ - HTTPClient: client, - } -} - -/* -ArkServiceOnboardParams contains all the parameters to send to the API endpoint - - for the ark service onboard operation. - - Typically these are written to a http.Request. -*/ -type ArkServiceOnboardParams struct { - - // Body. - Body *models.V1OnboardRequest - - timeout time.Duration - Context context.Context - HTTPClient *http.Client -} - -// WithDefaults hydrates default values in the ark service onboard params (not the query body). -// -// All values with no default are reset to their zero value. -func (o *ArkServiceOnboardParams) WithDefaults() *ArkServiceOnboardParams { - o.SetDefaults() - return o -} - -// SetDefaults hydrates default values in the ark service onboard params (not the query body). -// -// All values with no default are reset to their zero value. -func (o *ArkServiceOnboardParams) SetDefaults() { - // no default values defined for this parameter -} - -// WithTimeout adds the timeout to the ark service onboard params -func (o *ArkServiceOnboardParams) WithTimeout(timeout time.Duration) *ArkServiceOnboardParams { - o.SetTimeout(timeout) - return o -} - -// SetTimeout adds the timeout to the ark service onboard params -func (o *ArkServiceOnboardParams) SetTimeout(timeout time.Duration) { - o.timeout = timeout -} - -// WithContext adds the context to the ark service onboard params -func (o *ArkServiceOnboardParams) WithContext(ctx context.Context) *ArkServiceOnboardParams { - o.SetContext(ctx) - return o -} - -// SetContext adds the context to the ark service onboard params -func (o *ArkServiceOnboardParams) SetContext(ctx context.Context) { - o.Context = ctx -} - -// WithHTTPClient adds the HTTPClient to the ark service onboard params -func (o *ArkServiceOnboardParams) WithHTTPClient(client *http.Client) *ArkServiceOnboardParams { - o.SetHTTPClient(client) - return o -} - -// SetHTTPClient adds the HTTPClient to the ark service onboard params -func (o *ArkServiceOnboardParams) SetHTTPClient(client *http.Client) { - o.HTTPClient = client -} - -// WithBody adds the body to the ark service onboard params -func (o *ArkServiceOnboardParams) WithBody(body *models.V1OnboardRequest) *ArkServiceOnboardParams { - o.SetBody(body) - return o -} - -// SetBody adds the body to the ark service onboard params -func (o *ArkServiceOnboardParams) SetBody(body *models.V1OnboardRequest) { - o.Body = body -} - -// WriteToRequest writes these params to a swagger request -func (o *ArkServiceOnboardParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { - - if err := r.SetTimeout(o.timeout); err != nil { - return err - } - var res []error - if o.Body != nil { - if err := r.SetBodyParam(o.Body); err != nil { - return err - } - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} diff --git a/pkg/client-sdk/client/rest/service/arkservice/ark_service/ark_service_onboard_responses.go b/pkg/client-sdk/client/rest/service/arkservice/ark_service/ark_service_onboard_responses.go deleted file mode 100644 index 7ac4c2f..0000000 --- a/pkg/client-sdk/client/rest/service/arkservice/ark_service/ark_service_onboard_responses.go +++ /dev/null @@ -1,185 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package ark_service - -// This file was generated by the swagger tool. -// Editing this file might prove futile when you re-run the swagger generate command - -import ( - "encoding/json" - "fmt" - "io" - - "github.com/go-openapi/runtime" - "github.com/go-openapi/strfmt" - - "github.com/ark-network/ark/pkg/client-sdk/client/rest/service/models" -) - -// ArkServiceOnboardReader is a Reader for the ArkServiceOnboard structure. -type ArkServiceOnboardReader struct { - formats strfmt.Registry -} - -// ReadResponse reads a server response into the received o. -func (o *ArkServiceOnboardReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) { - switch response.Code() { - case 200: - result := NewArkServiceOnboardOK() - if err := result.readResponse(response, consumer, o.formats); err != nil { - return nil, err - } - return result, nil - default: - result := NewArkServiceOnboardDefault(response.Code()) - if err := result.readResponse(response, consumer, o.formats); err != nil { - return nil, err - } - if response.Code()/100 == 2 { - return result, nil - } - return nil, result - } -} - -// NewArkServiceOnboardOK creates a ArkServiceOnboardOK with default headers values -func NewArkServiceOnboardOK() *ArkServiceOnboardOK { - return &ArkServiceOnboardOK{} -} - -/* -ArkServiceOnboardOK describes a response with status code 200, with default header values. - -A successful response. -*/ -type ArkServiceOnboardOK struct { - Payload models.V1OnboardResponse -} - -// IsSuccess returns true when this ark service onboard o k response has a 2xx status code -func (o *ArkServiceOnboardOK) IsSuccess() bool { - return true -} - -// IsRedirect returns true when this ark service onboard o k response has a 3xx status code -func (o *ArkServiceOnboardOK) IsRedirect() bool { - return false -} - -// IsClientError returns true when this ark service onboard o k response has a 4xx status code -func (o *ArkServiceOnboardOK) IsClientError() bool { - return false -} - -// IsServerError returns true when this ark service onboard o k response has a 5xx status code -func (o *ArkServiceOnboardOK) IsServerError() bool { - return false -} - -// IsCode returns true when this ark service onboard o k response a status code equal to that given -func (o *ArkServiceOnboardOK) IsCode(code int) bool { - return code == 200 -} - -// Code gets the status code for the ark service onboard o k response -func (o *ArkServiceOnboardOK) Code() int { - return 200 -} - -func (o *ArkServiceOnboardOK) Error() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[POST /v1/onboard][%d] arkServiceOnboardOK %s", 200, payload) -} - -func (o *ArkServiceOnboardOK) String() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[POST /v1/onboard][%d] arkServiceOnboardOK %s", 200, payload) -} - -func (o *ArkServiceOnboardOK) GetPayload() models.V1OnboardResponse { - return o.Payload -} - -func (o *ArkServiceOnboardOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { - - // response payload - if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF { - return err - } - - return nil -} - -// NewArkServiceOnboardDefault creates a ArkServiceOnboardDefault with default headers values -func NewArkServiceOnboardDefault(code int) *ArkServiceOnboardDefault { - return &ArkServiceOnboardDefault{ - _statusCode: code, - } -} - -/* -ArkServiceOnboardDefault describes a response with status code -1, with default header values. - -An unexpected error response. -*/ -type ArkServiceOnboardDefault struct { - _statusCode int - - Payload *models.RPCStatus -} - -// IsSuccess returns true when this ark service onboard default response has a 2xx status code -func (o *ArkServiceOnboardDefault) IsSuccess() bool { - return o._statusCode/100 == 2 -} - -// IsRedirect returns true when this ark service onboard default response has a 3xx status code -func (o *ArkServiceOnboardDefault) IsRedirect() bool { - return o._statusCode/100 == 3 -} - -// IsClientError returns true when this ark service onboard default response has a 4xx status code -func (o *ArkServiceOnboardDefault) IsClientError() bool { - return o._statusCode/100 == 4 -} - -// IsServerError returns true when this ark service onboard default response has a 5xx status code -func (o *ArkServiceOnboardDefault) IsServerError() bool { - return o._statusCode/100 == 5 -} - -// IsCode returns true when this ark service onboard default response a status code equal to that given -func (o *ArkServiceOnboardDefault) IsCode(code int) bool { - return o._statusCode == code -} - -// Code gets the status code for the ark service onboard default response -func (o *ArkServiceOnboardDefault) Code() int { - return o._statusCode -} - -func (o *ArkServiceOnboardDefault) Error() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[POST /v1/onboard][%d] ArkService_Onboard default %s", o._statusCode, payload) -} - -func (o *ArkServiceOnboardDefault) String() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[POST /v1/onboard][%d] ArkService_Onboard default %s", o._statusCode, payload) -} - -func (o *ArkServiceOnboardDefault) GetPayload() *models.RPCStatus { - return o.Payload -} - -func (o *ArkServiceOnboardDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { - - o.Payload = new(models.RPCStatus) - - // response payload - if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { - return err - } - - return nil -} diff --git a/pkg/client-sdk/client/rest/service/arkservice/ark_service/ark_service_trusted_onboarding_parameters.go b/pkg/client-sdk/client/rest/service/arkservice/ark_service/ark_service_trusted_onboarding_parameters.go deleted file mode 100644 index 0f2069e..0000000 --- a/pkg/client-sdk/client/rest/service/arkservice/ark_service/ark_service_trusted_onboarding_parameters.go +++ /dev/null @@ -1,150 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package ark_service - -// This file was generated by the swagger tool. -// Editing this file might prove futile when you re-run the swagger generate command - -import ( - "context" - "net/http" - "time" - - "github.com/go-openapi/errors" - "github.com/go-openapi/runtime" - cr "github.com/go-openapi/runtime/client" - "github.com/go-openapi/strfmt" - - "github.com/ark-network/ark/pkg/client-sdk/client/rest/service/models" -) - -// NewArkServiceTrustedOnboardingParams creates a new ArkServiceTrustedOnboardingParams object, -// with the default timeout for this client. -// -// Default values are not hydrated, since defaults are normally applied by the API server side. -// -// To enforce default values in parameter, use SetDefaults or WithDefaults. -func NewArkServiceTrustedOnboardingParams() *ArkServiceTrustedOnboardingParams { - return &ArkServiceTrustedOnboardingParams{ - timeout: cr.DefaultTimeout, - } -} - -// NewArkServiceTrustedOnboardingParamsWithTimeout creates a new ArkServiceTrustedOnboardingParams object -// with the ability to set a timeout on a request. -func NewArkServiceTrustedOnboardingParamsWithTimeout(timeout time.Duration) *ArkServiceTrustedOnboardingParams { - return &ArkServiceTrustedOnboardingParams{ - timeout: timeout, - } -} - -// NewArkServiceTrustedOnboardingParamsWithContext creates a new ArkServiceTrustedOnboardingParams object -// with the ability to set a context for a request. -func NewArkServiceTrustedOnboardingParamsWithContext(ctx context.Context) *ArkServiceTrustedOnboardingParams { - return &ArkServiceTrustedOnboardingParams{ - Context: ctx, - } -} - -// NewArkServiceTrustedOnboardingParamsWithHTTPClient creates a new ArkServiceTrustedOnboardingParams object -// with the ability to set a custom HTTPClient for a request. -func NewArkServiceTrustedOnboardingParamsWithHTTPClient(client *http.Client) *ArkServiceTrustedOnboardingParams { - return &ArkServiceTrustedOnboardingParams{ - HTTPClient: client, - } -} - -/* -ArkServiceTrustedOnboardingParams contains all the parameters to send to the API endpoint - - for the ark service trusted onboarding operation. - - Typically these are written to a http.Request. -*/ -type ArkServiceTrustedOnboardingParams struct { - - // Body. - Body *models.V1TrustedOnboardingRequest - - timeout time.Duration - Context context.Context - HTTPClient *http.Client -} - -// WithDefaults hydrates default values in the ark service trusted onboarding params (not the query body). -// -// All values with no default are reset to their zero value. -func (o *ArkServiceTrustedOnboardingParams) WithDefaults() *ArkServiceTrustedOnboardingParams { - o.SetDefaults() - return o -} - -// SetDefaults hydrates default values in the ark service trusted onboarding params (not the query body). -// -// All values with no default are reset to their zero value. -func (o *ArkServiceTrustedOnboardingParams) SetDefaults() { - // no default values defined for this parameter -} - -// WithTimeout adds the timeout to the ark service trusted onboarding params -func (o *ArkServiceTrustedOnboardingParams) WithTimeout(timeout time.Duration) *ArkServiceTrustedOnboardingParams { - o.SetTimeout(timeout) - return o -} - -// SetTimeout adds the timeout to the ark service trusted onboarding params -func (o *ArkServiceTrustedOnboardingParams) SetTimeout(timeout time.Duration) { - o.timeout = timeout -} - -// WithContext adds the context to the ark service trusted onboarding params -func (o *ArkServiceTrustedOnboardingParams) WithContext(ctx context.Context) *ArkServiceTrustedOnboardingParams { - o.SetContext(ctx) - return o -} - -// SetContext adds the context to the ark service trusted onboarding params -func (o *ArkServiceTrustedOnboardingParams) SetContext(ctx context.Context) { - o.Context = ctx -} - -// WithHTTPClient adds the HTTPClient to the ark service trusted onboarding params -func (o *ArkServiceTrustedOnboardingParams) WithHTTPClient(client *http.Client) *ArkServiceTrustedOnboardingParams { - o.SetHTTPClient(client) - return o -} - -// SetHTTPClient adds the HTTPClient to the ark service trusted onboarding params -func (o *ArkServiceTrustedOnboardingParams) SetHTTPClient(client *http.Client) { - o.HTTPClient = client -} - -// WithBody adds the body to the ark service trusted onboarding params -func (o *ArkServiceTrustedOnboardingParams) WithBody(body *models.V1TrustedOnboardingRequest) *ArkServiceTrustedOnboardingParams { - o.SetBody(body) - return o -} - -// SetBody adds the body to the ark service trusted onboarding params -func (o *ArkServiceTrustedOnboardingParams) SetBody(body *models.V1TrustedOnboardingRequest) { - o.Body = body -} - -// WriteToRequest writes these params to a swagger request -func (o *ArkServiceTrustedOnboardingParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { - - if err := r.SetTimeout(o.timeout); err != nil { - return err - } - var res []error - if o.Body != nil { - if err := r.SetBodyParam(o.Body); err != nil { - return err - } - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} diff --git a/pkg/client-sdk/client/rest/service/arkservice/ark_service/ark_service_trusted_onboarding_responses.go b/pkg/client-sdk/client/rest/service/arkservice/ark_service/ark_service_trusted_onboarding_responses.go deleted file mode 100644 index 12f9185..0000000 --- a/pkg/client-sdk/client/rest/service/arkservice/ark_service/ark_service_trusted_onboarding_responses.go +++ /dev/null @@ -1,187 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package ark_service - -// This file was generated by the swagger tool. -// Editing this file might prove futile when you re-run the swagger generate command - -import ( - "encoding/json" - "fmt" - "io" - - "github.com/go-openapi/runtime" - "github.com/go-openapi/strfmt" - - "github.com/ark-network/ark/pkg/client-sdk/client/rest/service/models" -) - -// ArkServiceTrustedOnboardingReader is a Reader for the ArkServiceTrustedOnboarding structure. -type ArkServiceTrustedOnboardingReader struct { - formats strfmt.Registry -} - -// ReadResponse reads a server response into the received o. -func (o *ArkServiceTrustedOnboardingReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) { - switch response.Code() { - case 200: - result := NewArkServiceTrustedOnboardingOK() - if err := result.readResponse(response, consumer, o.formats); err != nil { - return nil, err - } - return result, nil - default: - result := NewArkServiceTrustedOnboardingDefault(response.Code()) - if err := result.readResponse(response, consumer, o.formats); err != nil { - return nil, err - } - if response.Code()/100 == 2 { - return result, nil - } - return nil, result - } -} - -// NewArkServiceTrustedOnboardingOK creates a ArkServiceTrustedOnboardingOK with default headers values -func NewArkServiceTrustedOnboardingOK() *ArkServiceTrustedOnboardingOK { - return &ArkServiceTrustedOnboardingOK{} -} - -/* -ArkServiceTrustedOnboardingOK describes a response with status code 200, with default header values. - -A successful response. -*/ -type ArkServiceTrustedOnboardingOK struct { - Payload *models.V1TrustedOnboardingResponse -} - -// IsSuccess returns true when this ark service trusted onboarding o k response has a 2xx status code -func (o *ArkServiceTrustedOnboardingOK) IsSuccess() bool { - return true -} - -// IsRedirect returns true when this ark service trusted onboarding o k response has a 3xx status code -func (o *ArkServiceTrustedOnboardingOK) IsRedirect() bool { - return false -} - -// IsClientError returns true when this ark service trusted onboarding o k response has a 4xx status code -func (o *ArkServiceTrustedOnboardingOK) IsClientError() bool { - return false -} - -// IsServerError returns true when this ark service trusted onboarding o k response has a 5xx status code -func (o *ArkServiceTrustedOnboardingOK) IsServerError() bool { - return false -} - -// IsCode returns true when this ark service trusted onboarding o k response a status code equal to that given -func (o *ArkServiceTrustedOnboardingOK) IsCode(code int) bool { - return code == 200 -} - -// Code gets the status code for the ark service trusted onboarding o k response -func (o *ArkServiceTrustedOnboardingOK) Code() int { - return 200 -} - -func (o *ArkServiceTrustedOnboardingOK) Error() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[POST /v1/onboard/address][%d] arkServiceTrustedOnboardingOK %s", 200, payload) -} - -func (o *ArkServiceTrustedOnboardingOK) String() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[POST /v1/onboard/address][%d] arkServiceTrustedOnboardingOK %s", 200, payload) -} - -func (o *ArkServiceTrustedOnboardingOK) GetPayload() *models.V1TrustedOnboardingResponse { - return o.Payload -} - -func (o *ArkServiceTrustedOnboardingOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { - - o.Payload = new(models.V1TrustedOnboardingResponse) - - // response payload - if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { - return err - } - - return nil -} - -// NewArkServiceTrustedOnboardingDefault creates a ArkServiceTrustedOnboardingDefault with default headers values -func NewArkServiceTrustedOnboardingDefault(code int) *ArkServiceTrustedOnboardingDefault { - return &ArkServiceTrustedOnboardingDefault{ - _statusCode: code, - } -} - -/* -ArkServiceTrustedOnboardingDefault describes a response with status code -1, with default header values. - -An unexpected error response. -*/ -type ArkServiceTrustedOnboardingDefault struct { - _statusCode int - - Payload *models.RPCStatus -} - -// IsSuccess returns true when this ark service trusted onboarding default response has a 2xx status code -func (o *ArkServiceTrustedOnboardingDefault) IsSuccess() bool { - return o._statusCode/100 == 2 -} - -// IsRedirect returns true when this ark service trusted onboarding default response has a 3xx status code -func (o *ArkServiceTrustedOnboardingDefault) IsRedirect() bool { - return o._statusCode/100 == 3 -} - -// IsClientError returns true when this ark service trusted onboarding default response has a 4xx status code -func (o *ArkServiceTrustedOnboardingDefault) IsClientError() bool { - return o._statusCode/100 == 4 -} - -// IsServerError returns true when this ark service trusted onboarding default response has a 5xx status code -func (o *ArkServiceTrustedOnboardingDefault) IsServerError() bool { - return o._statusCode/100 == 5 -} - -// IsCode returns true when this ark service trusted onboarding default response a status code equal to that given -func (o *ArkServiceTrustedOnboardingDefault) IsCode(code int) bool { - return o._statusCode == code -} - -// Code gets the status code for the ark service trusted onboarding default response -func (o *ArkServiceTrustedOnboardingDefault) Code() int { - return o._statusCode -} - -func (o *ArkServiceTrustedOnboardingDefault) Error() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[POST /v1/onboard/address][%d] ArkService_TrustedOnboarding default %s", o._statusCode, payload) -} - -func (o *ArkServiceTrustedOnboardingDefault) String() string { - payload, _ := json.Marshal(o.Payload) - return fmt.Sprintf("[POST /v1/onboard/address][%d] ArkService_TrustedOnboarding default %s", o._statusCode, payload) -} - -func (o *ArkServiceTrustedOnboardingDefault) GetPayload() *models.RPCStatus { - return o.Payload -} - -func (o *ArkServiceTrustedOnboardingDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { - - o.Payload = new(models.RPCStatus) - - // response payload - if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { - return err - } - - return nil -} diff --git a/pkg/client-sdk/client/rest/service/models/v1_boarding_input.go b/pkg/client-sdk/client/rest/service/models/v1_boarding_input.go new file mode 100644 index 0000000..14c4f7e --- /dev/null +++ b/pkg/client-sdk/client/rest/service/models/v1_boarding_input.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// V1BoardingInput v1 boarding input +// +// swagger:model v1BoardingInput +type V1BoardingInput struct { + + // descriptor + Descriptor string `json:"descriptor,omitempty"` + + // txid + Txid string `json:"txid,omitempty"` + + // vout + Vout int64 `json:"vout,omitempty"` +} + +// Validate validates this v1 boarding input +func (m *V1BoardingInput) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this v1 boarding input based on context it is used +func (m *V1BoardingInput) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *V1BoardingInput) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *V1BoardingInput) UnmarshalBinary(b []byte) error { + var res V1BoardingInput + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/client-sdk/client/rest/service/models/v1_finalize_payment_request.go b/pkg/client-sdk/client/rest/service/models/v1_finalize_payment_request.go index f7bbb17..17b31aa 100644 --- a/pkg/client-sdk/client/rest/service/models/v1_finalize_payment_request.go +++ b/pkg/client-sdk/client/rest/service/models/v1_finalize_payment_request.go @@ -19,6 +19,9 @@ type V1FinalizePaymentRequest struct { // Forfeit txs signed by the user. SignedForfeitTxs []string `json:"signedForfeitTxs"` + + // If payment has reverse boarding, the user must sign the associated inputs. + SignedRoundTx string `json:"signedRoundTx,omitempty"` } // Validate validates this v1 finalize payment request diff --git a/pkg/client-sdk/client/rest/service/models/v1_get_boarding_address_request.go b/pkg/client-sdk/client/rest/service/models/v1_get_boarding_address_request.go new file mode 100644 index 0000000..898393b --- /dev/null +++ b/pkg/client-sdk/client/rest/service/models/v1_get_boarding_address_request.go @@ -0,0 +1,50 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// V1GetBoardingAddressRequest v1 get boarding address request +// +// swagger:model v1GetBoardingAddressRequest +type V1GetBoardingAddressRequest struct { + + // pubkey + Pubkey string `json:"pubkey,omitempty"` +} + +// Validate validates this v1 get boarding address request +func (m *V1GetBoardingAddressRequest) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this v1 get boarding address request based on context it is used +func (m *V1GetBoardingAddressRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *V1GetBoardingAddressRequest) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *V1GetBoardingAddressRequest) UnmarshalBinary(b []byte) error { + var res V1GetBoardingAddressRequest + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/client-sdk/client/rest/service/models/v1_get_boarding_address_response.go b/pkg/client-sdk/client/rest/service/models/v1_get_boarding_address_response.go new file mode 100644 index 0000000..85152f8 --- /dev/null +++ b/pkg/client-sdk/client/rest/service/models/v1_get_boarding_address_response.go @@ -0,0 +1,53 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// V1GetBoardingAddressResponse v1 get boarding address response +// +// swagger:model v1GetBoardingAddressResponse +type V1GetBoardingAddressResponse struct { + + // address + Address string `json:"address,omitempty"` + + // descriptor + Descriptor string `json:"descriptor,omitempty"` +} + +// Validate validates this v1 get boarding address response +func (m *V1GetBoardingAddressResponse) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this v1 get boarding address response based on context it is used +func (m *V1GetBoardingAddressResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *V1GetBoardingAddressResponse) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *V1GetBoardingAddressResponse) UnmarshalBinary(b []byte) error { + var res V1GetBoardingAddressResponse + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/client-sdk/client/rest/service/models/v1_get_info_response.go b/pkg/client-sdk/client/rest/service/models/v1_get_info_response.go index 5395dff..a120e0b 100644 --- a/pkg/client-sdk/client/rest/service/models/v1_get_info_response.go +++ b/pkg/client-sdk/client/rest/service/models/v1_get_info_response.go @@ -17,6 +17,9 @@ import ( // swagger:model v1GetInfoResponse type V1GetInfoResponse struct { + // boarding descriptor template + BoardingDescriptorTemplate string `json:"boardingDescriptorTemplate,omitempty"` + // min relay fee MinRelayFee string `json:"minRelayFee,omitempty"` diff --git a/pkg/client-sdk/client/rest/service/models/v1_input.go b/pkg/client-sdk/client/rest/service/models/v1_input.go index 3fba81a..887451b 100644 --- a/pkg/client-sdk/client/rest/service/models/v1_input.go +++ b/pkg/client-sdk/client/rest/service/models/v1_input.go @@ -8,6 +8,7 @@ package models import ( "context" + "github.com/go-openapi/errors" "github.com/go-openapi/strfmt" "github.com/go-openapi/swag" ) @@ -17,20 +18,126 @@ import ( // swagger:model v1Input type V1Input struct { - // txid - Txid string `json:"txid,omitempty"` + // boarding input + BoardingInput *V1BoardingInput `json:"boardingInput,omitempty"` - // vout - Vout int64 `json:"vout,omitempty"` + // vtxo input + VtxoInput *V1VtxoInput `json:"vtxoInput,omitempty"` } // Validate validates this v1 input func (m *V1Input) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateBoardingInput(formats); err != nil { + res = append(res, err) + } + + if err := m.validateVtxoInput(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } return nil } -// ContextValidate validates this v1 input based on context it is used +func (m *V1Input) validateBoardingInput(formats strfmt.Registry) error { + if swag.IsZero(m.BoardingInput) { // not required + return nil + } + + if m.BoardingInput != nil { + if err := m.BoardingInput.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("boardingInput") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("boardingInput") + } + return err + } + } + + return nil +} + +func (m *V1Input) validateVtxoInput(formats strfmt.Registry) error { + if swag.IsZero(m.VtxoInput) { // not required + return nil + } + + if m.VtxoInput != nil { + if err := m.VtxoInput.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("vtxoInput") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("vtxoInput") + } + return err + } + } + + return nil +} + +// ContextValidate validate this v1 input based on the context it is used func (m *V1Input) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateBoardingInput(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateVtxoInput(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *V1Input) contextValidateBoardingInput(ctx context.Context, formats strfmt.Registry) error { + + if m.BoardingInput != nil { + + if swag.IsZero(m.BoardingInput) { // not required + return nil + } + + if err := m.BoardingInput.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("boardingInput") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("boardingInput") + } + return err + } + } + + return nil +} + +func (m *V1Input) contextValidateVtxoInput(ctx context.Context, formats strfmt.Registry) error { + + if m.VtxoInput != nil { + + if swag.IsZero(m.VtxoInput) { // not required + return nil + } + + if err := m.VtxoInput.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("vtxoInput") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("vtxoInput") + } + return err + } + } + return nil } diff --git a/pkg/client-sdk/client/rest/service/models/v1_onboard_request.go b/pkg/client-sdk/client/rest/service/models/v1_onboard_request.go deleted file mode 100644 index a242776..0000000 --- a/pkg/client-sdk/client/rest/service/models/v1_onboard_request.go +++ /dev/null @@ -1,115 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package models - -// This file was generated by the swagger tool. -// Editing this file might prove futile when you re-run the swagger generate command - -import ( - "context" - - "github.com/go-openapi/errors" - "github.com/go-openapi/strfmt" - "github.com/go-openapi/swag" -) - -// V1OnboardRequest v1 onboard request -// -// swagger:model v1OnboardRequest -type V1OnboardRequest struct { - - // boarding tx - BoardingTx string `json:"boardingTx,omitempty"` - - // congestion tree - CongestionTree *V1Tree `json:"congestionTree,omitempty"` - - // user pubkey - UserPubkey string `json:"userPubkey,omitempty"` -} - -// Validate validates this v1 onboard request -func (m *V1OnboardRequest) Validate(formats strfmt.Registry) error { - var res []error - - if err := m.validateCongestionTree(formats); err != nil { - res = append(res, err) - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -func (m *V1OnboardRequest) validateCongestionTree(formats strfmt.Registry) error { - if swag.IsZero(m.CongestionTree) { // not required - return nil - } - - if m.CongestionTree != nil { - if err := m.CongestionTree.Validate(formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { - return ve.ValidateName("congestionTree") - } else if ce, ok := err.(*errors.CompositeError); ok { - return ce.ValidateName("congestionTree") - } - return err - } - } - - return nil -} - -// ContextValidate validate this v1 onboard request based on the context it is used -func (m *V1OnboardRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error { - var res []error - - if err := m.contextValidateCongestionTree(ctx, formats); err != nil { - res = append(res, err) - } - - if len(res) > 0 { - return errors.CompositeValidationError(res...) - } - return nil -} - -func (m *V1OnboardRequest) contextValidateCongestionTree(ctx context.Context, formats strfmt.Registry) error { - - if m.CongestionTree != nil { - - if swag.IsZero(m.CongestionTree) { // not required - return nil - } - - if err := m.CongestionTree.ContextValidate(ctx, formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { - return ve.ValidateName("congestionTree") - } else if ce, ok := err.(*errors.CompositeError); ok { - return ce.ValidateName("congestionTree") - } - return err - } - } - - return nil -} - -// MarshalBinary interface implementation -func (m *V1OnboardRequest) MarshalBinary() ([]byte, error) { - if m == nil { - return nil, nil - } - return swag.WriteJSON(m) -} - -// UnmarshalBinary interface implementation -func (m *V1OnboardRequest) UnmarshalBinary(b []byte) error { - var res V1OnboardRequest - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *m = res - return nil -} diff --git a/pkg/client-sdk/client/rest/service/models/v1_onboard_response.go b/pkg/client-sdk/client/rest/service/models/v1_onboard_response.go deleted file mode 100644 index 470dedb..0000000 --- a/pkg/client-sdk/client/rest/service/models/v1_onboard_response.go +++ /dev/null @@ -1,11 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package models - -// This file was generated by the swagger tool. -// Editing this file might prove futile when you re-run the swagger generate command - -// V1OnboardResponse v1 onboard response -// -// swagger:model v1OnboardResponse -type V1OnboardResponse interface{} diff --git a/pkg/client-sdk/client/rest/service/models/v1_trusted_onboarding_request.go b/pkg/client-sdk/client/rest/service/models/v1_trusted_onboarding_request.go deleted file mode 100644 index 6a6c673..0000000 --- a/pkg/client-sdk/client/rest/service/models/v1_trusted_onboarding_request.go +++ /dev/null @@ -1,50 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package models - -// This file was generated by the swagger tool. -// Editing this file might prove futile when you re-run the swagger generate command - -import ( - "context" - - "github.com/go-openapi/strfmt" - "github.com/go-openapi/swag" -) - -// V1TrustedOnboardingRequest v1 trusted onboarding request -// -// swagger:model v1TrustedOnboardingRequest -type V1TrustedOnboardingRequest struct { - - // user pubkey - UserPubkey string `json:"userPubkey,omitempty"` -} - -// Validate validates this v1 trusted onboarding request -func (m *V1TrustedOnboardingRequest) Validate(formats strfmt.Registry) error { - return nil -} - -// ContextValidate validates this v1 trusted onboarding request based on context it is used -func (m *V1TrustedOnboardingRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error { - return nil -} - -// MarshalBinary interface implementation -func (m *V1TrustedOnboardingRequest) MarshalBinary() ([]byte, error) { - if m == nil { - return nil, nil - } - return swag.WriteJSON(m) -} - -// UnmarshalBinary interface implementation -func (m *V1TrustedOnboardingRequest) UnmarshalBinary(b []byte) error { - var res V1TrustedOnboardingRequest - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *m = res - return nil -} diff --git a/pkg/client-sdk/client/rest/service/models/v1_trusted_onboarding_response.go b/pkg/client-sdk/client/rest/service/models/v1_trusted_onboarding_response.go deleted file mode 100644 index f3cb790..0000000 --- a/pkg/client-sdk/client/rest/service/models/v1_trusted_onboarding_response.go +++ /dev/null @@ -1,50 +0,0 @@ -// Code generated by go-swagger; DO NOT EDIT. - -package models - -// This file was generated by the swagger tool. -// Editing this file might prove futile when you re-run the swagger generate command - -import ( - "context" - - "github.com/go-openapi/strfmt" - "github.com/go-openapi/swag" -) - -// V1TrustedOnboardingResponse v1 trusted onboarding response -// -// swagger:model v1TrustedOnboardingResponse -type V1TrustedOnboardingResponse struct { - - // address - Address string `json:"address,omitempty"` -} - -// Validate validates this v1 trusted onboarding response -func (m *V1TrustedOnboardingResponse) Validate(formats strfmt.Registry) error { - return nil -} - -// ContextValidate validates this v1 trusted onboarding response based on context it is used -func (m *V1TrustedOnboardingResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error { - return nil -} - -// MarshalBinary interface implementation -func (m *V1TrustedOnboardingResponse) MarshalBinary() ([]byte, error) { - if m == nil { - return nil, nil - } - return swag.WriteJSON(m) -} - -// UnmarshalBinary interface implementation -func (m *V1TrustedOnboardingResponse) UnmarshalBinary(b []byte) error { - var res V1TrustedOnboardingResponse - if err := swag.ReadJSON(b, &res); err != nil { - return err - } - *m = res - return nil -} diff --git a/pkg/client-sdk/client/rest/service/models/v1_vtxo_input.go b/pkg/client-sdk/client/rest/service/models/v1_vtxo_input.go new file mode 100644 index 0000000..d579cf7 --- /dev/null +++ b/pkg/client-sdk/client/rest/service/models/v1_vtxo_input.go @@ -0,0 +1,53 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// V1VtxoInput v1 vtxo input +// +// swagger:model v1VtxoInput +type V1VtxoInput struct { + + // txid + Txid string `json:"txid,omitempty"` + + // vout + Vout int64 `json:"vout,omitempty"` +} + +// Validate validates this v1 vtxo input +func (m *V1VtxoInput) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this v1 vtxo input based on context it is used +func (m *V1VtxoInput) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *V1VtxoInput) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *V1VtxoInput) UnmarshalBinary(b []byte) error { + var res V1VtxoInput + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/client-sdk/covenant_client.go b/pkg/client-sdk/covenant_client.go index 49ce456..590c570 100644 --- a/pkg/client-sdk/covenant_client.go +++ b/pkg/client-sdk/covenant_client.go @@ -11,6 +11,7 @@ import ( "time" "github.com/ark-network/ark/common" + "github.com/ark-network/ark/common/descriptor" "github.com/ark-network/ark/common/tree" "github.com/ark-network/ark/pkg/client-sdk/client" "github.com/ark-network/ark/pkg/client-sdk/explorer" @@ -23,12 +24,7 @@ import ( "github.com/decred/dcrd/dcrec/secp256k1/v4" log "github.com/sirupsen/logrus" "github.com/vulpemventures/go-elements/address" - "github.com/vulpemventures/go-elements/elementsutil" - "github.com/vulpemventures/go-elements/network" - "github.com/vulpemventures/go-elements/payment" "github.com/vulpemventures/go-elements/psetv2" - "github.com/vulpemventures/go-elements/taproot" - "github.com/vulpemventures/go-elements/transaction" ) type liquidReceiver struct { @@ -139,92 +135,24 @@ func LoadCovenantClientWithWallet( }, nil } -func (a *covenantArkClient) Onboard( - ctx context.Context, amount uint64, -) (string, error) { - if amount <= 0 { - return "", fmt.Errorf("invalid amount to onboard %d", amount) - } - - offchainAddr, _, err := a.wallet.NewAddress(ctx, false) - if err != nil { - return "", err - } - - net := utils.ToElementsNetwork(a.Network) - _, userPubkey, aspPubkey, _ := common.DecodeAddress(offchainAddr) - userPubkeyStr := hex.EncodeToString(userPubkey.SerializeCompressed()) - congestionTreeLeaf := tree.Receiver{ - Pubkey: userPubkeyStr, - Amount: amount, - } - - treeFactoryFn, sharedOutputScript, sharedOutputAmount, err := tree.CraftCongestionTree( - net.AssetID, - aspPubkey, - []tree.Receiver{congestionTreeLeaf}, - a.MinRelayFee, - a.RoundLifetime, - a.UnilateralExitDelay, - ) - if err != nil { - return "", err - } - - pay, err := payment.FromScript(sharedOutputScript, &net, nil) - if err != nil { - return "", err - } - - addr, err := pay.TaprootAddress() - if err != nil { - return "", err - } - - onchainReceiver := NewLiquidReceiver(addr, sharedOutputAmount) - - pset, err := a.sendOnchain(ctx, []Receiver{onchainReceiver}) - if err != nil { - return "", err - } - - ptx, _ := psetv2.NewPsetFromBase64(pset) - utx, _ := ptx.UnsignedTx() - txid := utx.TxHash().String() - - congestionTree, err := treeFactoryFn(psetv2.InputArgs{ - Txid: txid, - TxIndex: 0, - }) - if err != nil { - return "", err - } - - if err := a.client.Onboard( - ctx, pset, userPubkeyStr, congestionTree, - ); err != nil { - return "", err - } - - return txid, nil -} - func (a *covenantArkClient) Balance( ctx context.Context, computeVtxoExpiration bool, ) (*Balance, error) { - offchainAddrs, onchainAddrs, redeemAddrs, err := a.wallet.GetAddresses(ctx) + offchainAddrs, boardingAddrs, redeemAddrs, err := a.wallet.GetAddresses(ctx) if err != nil { return nil, err } + const nbWorkers = 3 wg := &sync.WaitGroup{} - wg.Add(3 * len(offchainAddrs)) + wg.Add(nbWorkers * len(offchainAddrs)) - chRes := make(chan balanceRes, 3) + chRes := make(chan balanceRes, nbWorkers*len(offchainAddrs)) for i := range offchainAddrs { offchainAddr := offchainAddrs[i] - onchainAddr := onchainAddrs[i] + boardingAddr := boardingAddrs[i] redeemAddr := redeemAddrs[i] + go func(addr string) { defer wg.Done() balance, amountByExpiration, err := a.getOffchainBalance( @@ -241,17 +169,7 @@ func (a *covenantArkClient) Balance( } }(offchainAddr) - go func(addr string) { - defer wg.Done() - balance, err := a.explorer.GetBalance(addr) - if err != nil { - chRes <- balanceRes{err: err} - return - } - chRes <- balanceRes{onchainSpendableBalance: balance} - }(onchainAddr) - - go func(addr string) { + getDelayedBalance := func(addr string) { defer wg.Done() spendableBalance, lockedBalance, err := a.explorer.GetRedeemedVtxosBalance( @@ -267,7 +185,10 @@ func (a *covenantArkClient) Balance( onchainLockedBalance: lockedBalance, err: err, } - }(redeemAddr) + } + + go getDelayedBalance(boardingAddr) + go getDelayedBalance(redeemAddr) } wg.Wait() @@ -317,7 +238,7 @@ func (a *covenantArkClient) Balance( } count++ - if count == 3 { + if count == nbWorkers { break } } @@ -519,7 +440,7 @@ func (a *covenantArkClient) CollaborativeRedeem( }) } - inputs := make([]client.VtxoKey, 0, len(selectedCoins)) + inputs := make([]client.Input, 0, len(selectedCoins)) for _, coin := range selectedCoins { inputs = append(inputs, client.VtxoKey{ @@ -538,7 +459,7 @@ func (a *covenantArkClient) CollaborativeRedeem( } poolTxID, err := a.handleRoundStream( - ctx, paymentID, selectedCoins, receivers, + ctx, paymentID, selectedCoins, false, receivers, ) if err != nil { return "", err @@ -554,8 +475,85 @@ func (a *covenantArkClient) SendAsync( return "", fmt.Errorf("not implemented") } -func (a *covenantArkClient) ClaimAsync(ctx context.Context) (string, error) { - return "", fmt.Errorf("not implemented") +func (a *covenantArkClient) Claim(ctx context.Context) (string, error) { + myselfOffchain, _, err := a.wallet.NewAddress(ctx, false) + if err != nil { + return "", err + } + + _, mypubkey, _, err := common.DecodeAddress(myselfOffchain) + if err != nil { + return "", err + } + + boardingUtxos, err := a.getClaimableBoardingUtxos(ctx) + if err != nil { + return "", err + } + + var pendingBalance uint64 + for _, vtxo := range boardingUtxos { + pendingBalance += vtxo.Amount + } + if pendingBalance == 0 { + return "", fmt.Errorf("no funds to claim") + } + + receiver := client.Output{ + Address: myselfOffchain, + Amount: pendingBalance, + } + + desc := strings.ReplaceAll(a.BoardingDescriptorTemplate, "USER", hex.EncodeToString(schnorr.SerializePubKey(mypubkey))) + + return a.selfTransferAllPendingPayments(ctx, boardingUtxos, receiver, desc) +} + +func (a *covenantArkClient) getClaimableBoardingUtxos(ctx context.Context) ([]explorer.Utxo, error) { + offchainAddrs, boardingAddrs, _, err := a.wallet.GetAddresses(ctx) + if err != nil { + return nil, err + } + + claimable := make([]explorer.Utxo, 0) + now := time.Now() + + _, myPubkey, _, err := common.DecodeAddress(offchainAddrs[0]) + if err != nil { + return nil, err + } + + myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(myPubkey)) + descriptorStr := strings.ReplaceAll( + a.BoardingDescriptorTemplate, "USER", myPubkeyStr, + ) + desc, err := descriptor.ParseTaprootDescriptor(descriptorStr) + if err != nil { + return nil, err + } + + _, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc) + if err != nil { + return nil, err + } + + for _, addr := range boardingAddrs { + boardingUtxos, err := a.explorer.GetUtxos(addr) + if err != nil { + return nil, err + } + + for _, utxo := range boardingUtxos { + u := utxo.ToUtxo(boardingTimeout) + + if u.SpendableAt.Before(now) { + continue + } + claimable = append(claimable, u) + } + } + + return claimable, nil } func (a *covenantArkClient) sendOnchain( @@ -599,14 +597,14 @@ func (a *covenantArkClient) sendOnchain( } } - utxos, delayedUtxos, change, err := a.coinSelectOnchain( + utxos, change, err := a.coinSelectOnchain( ctx, targetAmount, nil, ) if err != nil { return "", err } - if err := a.addInputs(ctx, updater, utxos, delayedUtxos, net); err != nil { + if err := a.addInputs(ctx, updater, utxos); err != nil { return "", err } @@ -649,14 +647,14 @@ func (a *covenantArkClient) sendOnchain( updater.Pset.Outputs = updater.Pset.Outputs[:len(updater.Pset.Outputs)-1] } // reselect the difference - selected, delayedSelected, newChange, err := a.coinSelectOnchain( - ctx, feeAmount-change, append(utxos, delayedUtxos...), + selected, newChange, err := a.coinSelectOnchain( + ctx, feeAmount-change, utxos, ) if err != nil { return "", err } - if err := a.addInputs(ctx, updater, selected, delayedSelected, net); err != nil { + if err := a.addInputs(ctx, updater, selected); err != nil { return "", err } @@ -787,7 +785,7 @@ func (a *covenantArkClient) sendOffchain( receiversOutput = append(receiversOutput, changeReceiver) } - inputs := make([]client.VtxoKey, 0, len(selectedCoins)) + inputs := make([]client.Input, 0, len(selectedCoins)) for _, coin := range selectedCoins { inputs = append(inputs, client.VtxoKey{ Txid: coin.Txid, @@ -811,7 +809,7 @@ func (a *covenantArkClient) sendOffchain( log.Infof("payment registered with id: %s", paymentID) poolTxID, err := a.handleRoundStream( - ctx, paymentID, selectedCoins, receiversOutput, + ctx, paymentID, selectedCoins, false, receiversOutput, ) if err != nil { return "", err @@ -821,126 +819,60 @@ func (a *covenantArkClient) sendOffchain( } func (a *covenantArkClient) addInputs( - ctx context.Context, updater *psetv2.Updater, utxos, delayedUtxos []explorer.Utxo, net network.Network, + ctx context.Context, + updater *psetv2.Updater, + utxos []explorer.Utxo, ) error { - offchainAddr, onchainAddr, err := a.wallet.NewAddress(ctx, false) + // TODO works only with single-key wallet + offchain, _, err := a.wallet.NewAddress(ctx, false) if err != nil { return err } - _, userPubkey, aspPubkey, _ := common.DecodeAddress(offchainAddr) - - changeScript, err := address.ToOutputScript(onchainAddr) + _, userPubkey, aspPubkey, err := common.DecodeAddress(offchain) if err != nil { return err } for _, utxo := range utxos { + sequence, err := utxo.Sequence() + if err != nil { + return err + } + if err := updater.AddInputs([]psetv2.InputArgs{ { - Txid: utxo.Txid, - TxIndex: utxo.Vout, + Txid: utxo.Txid, + TxIndex: utxo.Vout, + Sequence: sequence, }, }); err != nil { return err } - assetID, err := elementsutil.AssetHashToBytes(utxo.Asset) - if err != nil { - return err - } - - value, err := elementsutil.ValueToBytes(utxo.Amount) - if err != nil { - return err - } - - witnessUtxo := transaction.TxOutput{ - Asset: assetID, - Value: value, - Script: changeScript, - Nonce: []byte{0x00}, - } - - if err := updater.AddInWitnessUtxo( - len(updater.Pset.Inputs)-1, &witnessUtxo, - ); err != nil { - return err - } - } - - if len(delayedUtxos) > 0 { - _, leafProof, script, _, err := tree.ComputeVtxoTaprootScript( - userPubkey, aspPubkey, uint(a.UnilateralExitDelay), net, + _, leafProof, _, _, err := tree.ComputeVtxoTaprootScript( + userPubkey, aspPubkey, utxo.Delay, utils.ToElementsNetwork(a.Network), ) if err != nil { return err } - for _, utxo := range delayedUtxos { - if err := a.addVtxoInput( - updater, - psetv2.InputArgs{ - Txid: utxo.Txid, - TxIndex: utxo.Vout, - }, - uint(a.UnilateralExitDelay), - leafProof, - ); err != nil { - return err - } + inputIndex := len(updater.Pset.Inputs) - 1 - assetID, err := elementsutil.AssetHashToBytes(utxo.Asset) - if err != nil { - return err - } - - value, err := elementsutil.ValueToBytes(utxo.Amount) - if err != nil { - return err - } - - witnessUtxo := transaction.NewTxOutput(assetID, value, script) - - if err := updater.AddInWitnessUtxo( - len(updater.Pset.Inputs)-1, witnessUtxo, - ); err != nil { - return err - } + if err := updater.AddInTapLeafScript(inputIndex, psetv2.NewTapLeafScript(*leafProof, tree.UnspendableKey())); err != nil { + return err } } return nil } -func (a *covenantArkClient) addVtxoInput( - updater *psetv2.Updater, inputArgs psetv2.InputArgs, exitDelay uint, - tapLeafProof *taproot.TapscriptElementsProof, -) error { - sequence, err := common.BIP68EncodeAsNumber(exitDelay) - if err != nil { - return nil - } - - nextInputIndex := len(updater.Pset.Inputs) - if err := updater.AddInputs([]psetv2.InputArgs{inputArgs}); err != nil { - return err - } - - updater.Pset.Inputs[nextInputIndex].Sequence = sequence - - return updater.AddInTapLeafScript( - nextInputIndex, - psetv2.NewTapLeafScript( - *tapLeafProof, - tree.UnspendableKey(), - ), - ) -} - func (a *covenantArkClient) handleRoundStream( ctx context.Context, - paymentID string, vtxosToSign []client.Vtxo, receivers []client.Output, + paymentID string, + vtxosToSign []client.Vtxo, + mustSignRoundTx bool, + receivers []client.Output, ) (string, error) { eventsCh, err := a.client.GetEventStream(ctx, paymentID) if err != nil { @@ -972,20 +904,20 @@ func (a *covenantArkClient) handleRoundStream( pingStop() log.Info("a round finalization started") - signedForfeitTxs, err := a.handleRoundFinalization( - ctx, event.(client.RoundFinalizationEvent), vtxosToSign, receivers, + signedForfeitTxs, signedRoundTx, err := a.handleRoundFinalization( + ctx, event.(client.RoundFinalizationEvent), vtxosToSign, mustSignRoundTx, receivers, ) if err != nil { return "", err } - if len(signedForfeitTxs) <= 0 { + if len(signedForfeitTxs) <= 0 && len(vtxosToSign) > 0 { log.Info("no forfeit txs to sign, waiting for the next round") continue } log.Info("finalizing payment... ") - if err := a.client.FinalizePayment(ctx, signedForfeitTxs); err != nil { + if err := a.client.FinalizePayment(ctx, signedForfeitTxs, signedRoundTx); err != nil { return "", err } @@ -998,15 +930,29 @@ func (a *covenantArkClient) handleRoundStream( func (a *covenantArkClient) handleRoundFinalization( ctx context.Context, event client.RoundFinalizationEvent, - vtxos []client.Vtxo, receivers []client.Output, -) ([]string, error) { - if err := a.validateCongestionTree(event, receivers); err != nil { - return nil, fmt.Errorf("failed to verify congestion tree: %s", err) + vtxos []client.Vtxo, mustSignRoundTx bool, receivers []client.Output, +) (signedForfeits []string, signedRoundTx string, err error) { + if err = a.validateCongestionTree(event, receivers); err != nil { + return } - return a.loopAndSign( - ctx, event.ForfeitTxs, vtxos, event.Connectors, - ) + if len(vtxos) > 0 { + signedForfeits, err = a.loopAndSign( + ctx, event.ForfeitTxs, vtxos, event.Connectors, + ) + if err != nil { + return + } + } + + if mustSignRoundTx { + signedRoundTx, err = a.wallet.SignTransaction(ctx, a.explorer, event.Tx) + if err != nil { + return + } + } + + return } func (a *covenantArkClient) validateCongestionTree( @@ -1197,23 +1143,49 @@ func (a *covenantArkClient) signForfeitTx( func (a *covenantArkClient) coinSelectOnchain( ctx context.Context, targetAmount uint64, exclude []explorer.Utxo, -) ([]explorer.Utxo, []explorer.Utxo, uint64, error) { - offchainAddrs, onchainAddrs, _, err := a.wallet.GetAddresses(ctx) +) ([]explorer.Utxo, uint64, error) { + offchainAddrs, boardingAddrs, redemptionAddrs, err := a.wallet.GetAddresses(ctx) if err != nil { - return nil, nil, 0, err + return nil, 0, err } - net := utils.ToElementsNetwork(a.Network) + + _, myPubkey, _, err := common.DecodeAddress(offchainAddrs[0]) + if err != nil { + return nil, 0, err + } + + myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(myPubkey)) + descriptorStr := strings.ReplaceAll( + a.BoardingDescriptorTemplate, "USER", myPubkeyStr, + ) + desc, err := descriptor.ParseTaprootDescriptor(descriptorStr) + if err != nil { + return nil, 0, err + } + + _, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc) + if err != nil { + return nil, 0, err + } + + now := time.Now() fetchedUtxos := make([]explorer.Utxo, 0) - for _, onchainAddr := range onchainAddrs { - utxos, err := a.explorer.GetUtxos(onchainAddr) + for _, addr := range boardingAddrs { + utxos, err := a.explorer.GetUtxos(addr) if err != nil { - return nil, nil, 0, err + return nil, 0, err + } + + for _, utxo := range utxos { + u := utxo.ToUtxo(boardingTimeout) + if u.SpendableAt.Before(now) { + fetchedUtxos = append(fetchedUtxos, u) + } } - fetchedUtxos = append(fetchedUtxos, utxos...) } - utxos := make([]explorer.Utxo, 0) + selected := make([]explorer.Utxo, 0) selectedAmount := uint64(0) for _, utxo := range fetchedUtxos { if selectedAmount >= targetAmount { @@ -1226,61 +1198,51 @@ func (a *covenantArkClient) coinSelectOnchain( } } - utxos = append(utxos, utxo) + selected = append(selected, utxo) selectedAmount += utxo.Amount } if selectedAmount >= targetAmount { - return utxos, nil, selectedAmount - targetAmount, nil + return selected, selectedAmount - targetAmount, nil } fetchedUtxos = make([]explorer.Utxo, 0) - for _, offchainAddr := range offchainAddrs { - _, userPubkey, aspPubkey, _ := common.DecodeAddress(offchainAddr) - _, _, _, addr, err := tree.ComputeVtxoTaprootScript( - userPubkey, aspPubkey, uint(a.UnilateralExitDelay), net, - ) + for _, addr := range redemptionAddrs { + utxos, err := a.explorer.GetUtxos(addr) if err != nil { - return nil, nil, 0, err + return nil, 0, err } - utxos, err = a.explorer.GetUtxos(addr) - if err != nil { - return nil, nil, 0, err + for _, utxo := range utxos { + u := utxo.ToUtxo(uint(a.UnilateralExitDelay)) + if u.SpendableAt.Before(now) { + fetchedUtxos = append(fetchedUtxos, u) + } } - fetchedUtxos = append(fetchedUtxos, utxos...) } - delayedUtxos := make([]explorer.Utxo, 0) for _, utxo := range fetchedUtxos { if selectedAmount >= targetAmount { break } - availableAt := time.Unix(utxo.Status.Blocktime, 0).Add( - time.Duration(a.UnilateralExitDelay) * time.Second, - ) - if availableAt.After(time.Now()) { - continue - } - for _, excluded := range exclude { if utxo.Txid == excluded.Txid && utxo.Vout == excluded.Vout { continue } } - delayedUtxos = append(delayedUtxos, utxo) + selected = append(selected, utxo) selectedAmount += utxo.Amount } if selectedAmount < targetAmount { - return nil, nil, 0, fmt.Errorf( + return nil, 0, fmt.Errorf( "not enough funds to cover amount %d", targetAmount, ) } - return utxos, delayedUtxos, selectedAmount - targetAmount, nil + return selected, selectedAmount - targetAmount, nil } func (a *covenantArkClient) getRedeemBranches( @@ -1373,3 +1335,39 @@ func (a *covenantArkClient) getVtxos( return vtxos, nil } + +func (a *covenantArkClient) selfTransferAllPendingPayments( + ctx context.Context, boardingUtxo []explorer.Utxo, myself client.Output, boardingDescriptor string, +) (string, error) { + inputs := make([]client.Input, 0, len(boardingUtxo)) + + for _, utxo := range boardingUtxo { + inputs = append(inputs, client.BoardingInput{ + VtxoKey: client.VtxoKey{ + Txid: utxo.Txid, + VOut: utxo.Vout, + }, + Descriptor: boardingDescriptor, + }) + } + + outputs := []client.Output{myself} + + paymentID, err := a.client.RegisterPayment(ctx, inputs, "") // ephemeralPublicKey is not required for covenant + if err != nil { + return "", err + } + + if err := a.client.ClaimPayment(ctx, paymentID, outputs); err != nil { + return "", err + } + + roundTxid, err := a.handleRoundStream( + ctx, paymentID, make([]client.Vtxo, 0), len(boardingUtxo) > 0, outputs, + ) + if err != nil { + return "", err + } + + return roundTxid, nil +} diff --git a/pkg/client-sdk/covenantless_client.go b/pkg/client-sdk/covenantless_client.go index 087b942..a4bd8ca 100644 --- a/pkg/client-sdk/covenantless_client.go +++ b/pkg/client-sdk/covenantless_client.go @@ -12,6 +12,7 @@ import ( "github.com/ark-network/ark/common" "github.com/ark-network/ark/common/bitcointree" + "github.com/ark-network/ark/common/descriptor" "github.com/ark-network/ark/common/tree" "github.com/ark-network/ark/pkg/client-sdk/client" "github.com/ark-network/ark/pkg/client-sdk/explorer" @@ -22,7 +23,6 @@ import ( "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil/psbt" - "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" @@ -139,176 +139,24 @@ func LoadCovenantlessClientWithWallet( }, nil } -func (a *covenantlessArkClient) Onboard( - ctx context.Context, amount uint64, -) (string, error) { - if amount <= 0 { - return "", fmt.Errorf("invalid amount to onboard %d", amount) - } - - offchainAddr, _, err := a.wallet.NewAddress(ctx, false) - if err != nil { - return "", err - } - - _, userPubkey, aspPubkey, _ := common.DecodeAddress(offchainAddr) - userPubkeyStr := hex.EncodeToString(userPubkey.SerializeCompressed()) - - congestionTreeLeaf := bitcointree.Receiver{ - Pubkey: userPubkeyStr, - Amount: amount, - } - - leaves := []bitcointree.Receiver{congestionTreeLeaf} - - ephemeralKey, err := secp256k1.GeneratePrivateKey() - if err != nil { - return "", err - } - - cosigners := []*secp256k1.PublicKey{ephemeralKey.PubKey()} // TODO asp as cosigner - - sharedOutputScript, sharedOutputAmount, err := bitcointree.CraftSharedOutput( - cosigners, - aspPubkey, - leaves, - a.MinRelayFee, - a.RoundLifetime, - a.UnilateralExitDelay, - ) - if err != nil { - return "", err - } - - netParams := utils.ToBitcoinNetwork(a.Network) - - address, err := btcutil.NewAddressTaproot(sharedOutputScript[2:], &netParams) - if err != nil { - return "", err - } - - onchainReceiver := NewBitcoinReceiver( - address.EncodeAddress(), uint64(sharedOutputAmount), - ) - - partialTx, err := a.sendOnchain(ctx, []Receiver{onchainReceiver}) - if err != nil { - return "", err - } - - ptx, err := psbt.NewFromRawBytes(strings.NewReader(partialTx), true) - if err != nil { - return "", err - } - txid := ptx.UnsignedTx.TxHash().String() - - congestionTree, err := bitcointree.CraftCongestionTree( - &wire.OutPoint{ - Hash: ptx.UnsignedTx.TxHash(), - Index: 0, - }, - cosigners, - aspPubkey, - leaves, - a.MinRelayFee, - a.RoundLifetime, - a.UnilateralExitDelay, - ) - if err != nil { - return "", err - } - - sweepClosure := bitcointree.CSVSigClosure{ - Pubkey: aspPubkey, - Seconds: uint(a.RoundLifetime), - } - - sweepTapLeaf, err := sweepClosure.Leaf() - if err != nil { - return "", err - } - - sweepTapTree := txscript.AssembleTaprootScriptTree(*sweepTapLeaf) - root := sweepTapTree.RootNode.TapHash() - - signer := bitcointree.NewTreeSignerSession( - ephemeralKey, - congestionTree, - int64(a.MinRelayFee), - root.CloneBytes(), - ) - - nonces, err := signer.GetNonces() // TODO send nonces to ASP - if err != nil { - return "", err - } - - coordinator, err := bitcointree.NewTreeCoordinatorSession( - congestionTree, - int64(a.MinRelayFee), - root.CloneBytes(), - cosigners, - ) - if err != nil { - return "", err - } - - if err := coordinator.AddNonce(ephemeralKey.PubKey(), nonces); err != nil { - return "", err - } - - aggregatedNonces, err := coordinator.AggregateNonces() - if err != nil { - return "", err - } - - if err := signer.SetKeys(cosigners); err != nil { - return "", err - } - - if err := signer.SetAggregatedNonces(aggregatedNonces); err != nil { - return "", err - } - - sigs, err := signer.Sign() - if err != nil { - return "", err - } - - if err := coordinator.AddSig(ephemeralKey.PubKey(), sigs); err != nil { - return "", err - } - - signedTree, err := coordinator.SignTree() - if err != nil { - return "", err - } - - if err := a.client.Onboard( - ctx, partialTx, userPubkeyStr, signedTree, - ); err != nil { - return "", err - } - - return txid, nil -} - func (a *covenantlessArkClient) Balance( ctx context.Context, computeVtxoExpiration bool, ) (*Balance, error) { - offchainAddrs, onchainAddrs, redeemAddrs, err := a.wallet.GetAddresses(ctx) + offchainAddrs, boardingAddrs, redeemAddrs, err := a.wallet.GetAddresses(ctx) if err != nil { return nil, err } + const nbWorkers = 3 wg := &sync.WaitGroup{} - wg.Add(3 * len(offchainAddrs)) + wg.Add(nbWorkers * len(offchainAddrs)) - chRes := make(chan balanceRes, 3) + chRes := make(chan balanceRes, nbWorkers*len(offchainAddrs)) for i := range offchainAddrs { offchainAddr := offchainAddrs[i] - onchainAddr := onchainAddrs[i] + boardingAddr := boardingAddrs[i] redeemAddr := redeemAddrs[i] + go func(addr string) { defer wg.Done() balance, amountByExpiration, err := a.getOffchainBalance( @@ -325,17 +173,7 @@ func (a *covenantlessArkClient) Balance( } }(offchainAddr) - go func(addr string) { - defer wg.Done() - balance, err := a.explorer.GetBalance(addr) - if err != nil { - chRes <- balanceRes{err: err} - return - } - chRes <- balanceRes{onchainSpendableBalance: balance} - }(onchainAddr) - - go func(addr string) { + getDelayedBalance := func(addr string) { defer wg.Done() spendableBalance, lockedBalance, err := a.explorer.GetRedeemedVtxosBalance( @@ -351,7 +189,10 @@ func (a *covenantlessArkClient) Balance( onchainLockedBalance: lockedBalance, err: err, } - }(redeemAddr) + } + + go getDelayedBalance(boardingAddr) + go getDelayedBalance(redeemAddr) } wg.Wait() @@ -401,7 +242,7 @@ func (a *covenantlessArkClient) Balance( } count++ - if count == 3 { + if count == nbWorkers { break } } @@ -603,7 +444,7 @@ func (a *covenantlessArkClient) CollaborativeRedeem( }) } - inputs := make([]client.VtxoKey, 0, len(selectedCoins)) + inputs := make([]client.Input, 0, len(selectedCoins)) for _, coin := range selectedCoins { inputs = append(inputs, client.VtxoKey{ @@ -631,7 +472,7 @@ func (a *covenantlessArkClient) CollaborativeRedeem( } poolTxID, err := a.handleRoundStream( - ctx, paymentID, selectedCoins, receivers, roundEphemeralKey, + ctx, paymentID, selectedCoins, false, receivers, roundEphemeralKey, ) if err != nil { return "", err @@ -751,9 +592,7 @@ func (a *covenantlessArkClient) SendAsync( return signedRedeemTx, nil } -func (a *covenantlessArkClient) ClaimAsync( - ctx context.Context, -) (string, error) { +func (a *covenantlessArkClient) Claim(ctx context.Context) (string, error) { myselfOffchain, _, err := a.wallet.NewAddress(ctx, false) if err != nil { return "", err @@ -764,10 +603,23 @@ func (a *covenantlessArkClient) ClaimAsync( return "", err } + _, mypubkey, _, err := common.DecodeAddress(myselfOffchain) + if err != nil { + return "", err + } + + boardingUtxos, err := a.getClaimableBoardingUtxos(ctx) + if err != nil { + return "", err + } + var pendingBalance uint64 for _, vtxo := range pendingVtxos { pendingBalance += vtxo.Amount } + for _, vtxo := range boardingUtxos { + pendingBalance += vtxo.Amount + } if pendingBalance == 0 { return "", nil } @@ -776,7 +628,9 @@ func (a *covenantlessArkClient) ClaimAsync( Address: myselfOffchain, Amount: pendingBalance, } - return a.selfTransferAllPendingPayments(ctx, pendingVtxos, receiver) + + desc := strings.ReplaceAll(a.BoardingDescriptorTemplate, "USER", hex.EncodeToString(schnorr.SerializePubKey(mypubkey))) + return a.selfTransferAllPendingPayments(ctx, pendingVtxos, boardingUtxos, receiver, desc) } func (a *covenantlessArkClient) sendOnchain( @@ -822,14 +676,14 @@ func (a *covenantlessArkClient) sendOnchain( updater.Upsbt.Outputs = append(updater.Upsbt.Outputs, psbt.POutput{}) } - utxos, delayedUtxos, change, err := a.coinSelectOnchain( + utxos, change, err := a.coinSelectOnchain( ctx, targetAmount, nil, ) if err != nil { return "", err } - if err := a.addInputs(ctx, updater, utxos, delayedUtxos, &netParams); err != nil { + if err := a.addInputs(ctx, updater, utxos); err != nil { return "", err } @@ -869,14 +723,14 @@ func (a *covenantlessArkClient) sendOnchain( updater.Upsbt.UnsignedTx.TxOut = updater.Upsbt.UnsignedTx.TxOut[:len(updater.Upsbt.UnsignedTx.TxOut)-1] } // reselect the difference - selected, delayedSelected, newChange, err := a.coinSelectOnchain( - ctx, feeAmount-change, append(utxos, delayedUtxos...), + selected, newChange, err := a.coinSelectOnchain( + ctx, feeAmount-change, utxos, ) if err != nil { return "", err } - if err := a.addInputs(ctx, updater, selected, delayedSelected, &netParams); err != nil { + if err := a.addInputs(ctx, updater, selected); err != nil { return "", err } @@ -995,7 +849,7 @@ func (a *covenantlessArkClient) sendOffchain( receiversOutput = append(receiversOutput, changeReceiver) } - inputs := make([]client.VtxoKey, 0, len(selectedCoins)) + inputs := make([]client.Input, 0, len(selectedCoins)) for _, coin := range selectedCoins { inputs = append(inputs, client.VtxoKey{ Txid: coin.Txid, @@ -1024,7 +878,7 @@ func (a *covenantlessArkClient) sendOffchain( log.Infof("payment registered with id: %s", paymentID) poolTxID, err := a.handleRoundStream( - ctx, paymentID, selectedCoins, receiversOutput, roundEphemeralKey, + ctx, paymentID, selectedCoins, false, receiversOutput, roundEphemeralKey, ) if err != nil { return "", err @@ -1034,16 +888,17 @@ func (a *covenantlessArkClient) sendOffchain( } func (a *covenantlessArkClient) addInputs( - ctx context.Context, updater *psbt.Updater, - utxos, delayedUtxos []explorer.Utxo, net *chaincfg.Params, + ctx context.Context, + updater *psbt.Updater, + utxos []explorer.Utxo, ) error { - offchainAddr, onchainAddr, err := a.wallet.NewAddress(ctx, false) + // TODO works only with single-key wallet + offchain, _, err := a.wallet.NewAddress(ctx, false) if err != nil { return err } - addr, _ := btcutil.DecodeAddress(onchainAddr, net) - changeScript, err := txscript.PayToAddrScript(addr) + _, userPubkey, aspPubkey, err := common.DecodeAddress(offchain) if err != nil { return err } @@ -1054,107 +909,41 @@ func (a *covenantlessArkClient) addInputs( return err } + sequence, err := utxo.Sequence() + if err != nil { + return err + } + updater.Upsbt.UnsignedTx.AddTxIn(&wire.TxIn{ PreviousOutPoint: wire.OutPoint{ Hash: *previousHash, Index: utxo.Vout, }, + Sequence: sequence, }) - updater.Upsbt.Inputs = append(updater.Upsbt.Inputs, psbt.PInput{}) - - if err := updater.AddInWitnessUtxo( - &wire.TxOut{ - Value: int64(utxo.Amount), - PkScript: changeScript, - }, - len(updater.Upsbt.UnsignedTx.TxIn)-1, - ); err != nil { - return err - } - } - - if len(delayedUtxos) > 0 { - _, userPubkey, aspPubkey, _ := common.DecodeAddress(offchainAddr) - - vtxoTapKey, leafProof, err := bitcointree.ComputeVtxoTaprootScript( - userPubkey, aspPubkey, uint(a.UnilateralExitDelay), + _, leafProof, err := bitcointree.ComputeVtxoTaprootScript( + userPubkey, aspPubkey, utxo.Delay, ) if err != nil { return err } - p2tr, err := btcutil.NewAddressTaproot(schnorr.SerializePubKey(vtxoTapKey), net) + controlBlock := leafProof.ToControlBlock(bitcointree.UnspendableKey()) + controlBlockBytes, err := controlBlock.ToBytes() if err != nil { return err } - script, err := txscript.PayToAddrScript(p2tr) - if err != nil { - return err - } - - for _, utxo := range delayedUtxos { - previousHash, err := chainhash.NewHashFromStr(utxo.Txid) - if err != nil { - return err - } - - if err := a.addVtxoInput( - updater, - &wire.OutPoint{ - Hash: *previousHash, - Index: utxo.Vout, + updater.Upsbt.Inputs = append(updater.Upsbt.Inputs, psbt.PInput{ + TaprootLeafScript: []*psbt.TaprootTapLeafScript{ + { + ControlBlock: controlBlockBytes, + Script: leafProof.Script, + LeafVersion: leafProof.LeafVersion, }, - uint(a.UnilateralExitDelay), - leafProof, - ); err != nil { - return err - } - - if err := updater.AddInWitnessUtxo( - &wire.TxOut{ - Value: int64(utxo.Amount), - PkScript: script, - }, - len(updater.Upsbt.Inputs)-1, - ); err != nil { - return err - } - } - } - - return nil -} - -func (a *covenantlessArkClient) addVtxoInput( - updater *psbt.Updater, inputArgs *wire.OutPoint, exitDelay uint, - tapLeafProof *txscript.TapscriptProof, -) error { - sequence, err := common.BIP68EncodeAsNumber(exitDelay) - if err != nil { - return nil - } - - nextInputIndex := len(updater.Upsbt.Inputs) - updater.Upsbt.UnsignedTx.AddTxIn(&wire.TxIn{ - PreviousOutPoint: *inputArgs, - Sequence: sequence, - }) - updater.Upsbt.Inputs = append(updater.Upsbt.Inputs, psbt.PInput{}) - - controlBlock := tapLeafProof.ToControlBlock(bitcointree.UnspendableKey()) - controlBlockBytes, err := controlBlock.ToBytes() - if err != nil { - return err - } - - updater.Upsbt.Inputs[nextInputIndex].TaprootLeafScript = []*psbt.TaprootTapLeafScript{ - { - ControlBlock: controlBlockBytes, - Script: tapLeafProof.Script, - LeafVersion: tapLeafProof.LeafVersion, - }, + }, + }) } return nil @@ -1164,6 +953,7 @@ func (a *covenantlessArkClient) handleRoundStream( ctx context.Context, paymentID string, vtxosToSign []client.Vtxo, + mustSignRoundTx bool, receivers []client.Output, roundEphemeralKey *secp256k1.PrivateKey, ) (string, error) { @@ -1218,20 +1008,20 @@ func (a *covenantlessArkClient) handleRoundStream( pingStop() log.Info("a round finalization started") - signedForfeitTxs, err := a.handleRoundFinalization( - ctx, event.(client.RoundFinalizationEvent), vtxosToSign, receivers, + signedForfeitTxs, signedRoundTx, err := a.handleRoundFinalization( + ctx, event.(client.RoundFinalizationEvent), vtxosToSign, mustSignRoundTx, receivers, ) if err != nil { return "", err } - if len(signedForfeitTxs) <= 0 { + if len(signedForfeitTxs) <= 0 && len(vtxosToSign) > 0 { log.Info("no forfeit txs to sign, waiting for the next round") continue } log.Info("finalizing payment... ") - if err := a.client.FinalizePayment(ctx, signedForfeitTxs); err != nil { + if err := a.client.FinalizePayment(ctx, signedForfeitTxs, signedRoundTx); err != nil { return "", err } @@ -1311,15 +1101,29 @@ func (a *covenantlessArkClient) handleRoundSigningNoncesGenerated( func (a *covenantlessArkClient) handleRoundFinalization( ctx context.Context, event client.RoundFinalizationEvent, - vtxos []client.Vtxo, receivers []client.Output, -) ([]string, error) { + vtxos []client.Vtxo, mustSignRoundTx bool, receivers []client.Output, +) (signedForfeits []string, signedRoundTx string, err error) { if err := a.validateCongestionTree(event, receivers); err != nil { - return nil, fmt.Errorf("failed to verify congestion tree: %s", err) + return nil, "", fmt.Errorf("failed to verify congestion tree: %s", err) } - return a.loopAndSign( - ctx, event.ForfeitTxs, vtxos, event.Connectors, - ) + if len(vtxos) > 0 { + signedForfeits, err = a.loopAndSign( + ctx, event.ForfeitTxs, vtxos, event.Connectors, + ) + if err != nil { + return + } + } + + if mustSignRoundTx { + signedRoundTx, err = a.wallet.SignTransaction(ctx, a.explorer, event.Tx) + if err != nil { + return + } + } + + return } func (a *covenantlessArkClient) validateCongestionTree( @@ -1516,24 +1320,49 @@ func (a *covenantlessArkClient) loopAndSign( func (a *covenantlessArkClient) coinSelectOnchain( ctx context.Context, targetAmount uint64, exclude []explorer.Utxo, -) ([]explorer.Utxo, []explorer.Utxo, uint64, error) { - offchainAddrs, onchainAddrs, _, err := a.wallet.GetAddresses(ctx) +) ([]explorer.Utxo, uint64, error) { + offchainAddrs, boardingAddrs, redemptionAddrs, err := a.wallet.GetAddresses(ctx) if err != nil { - return nil, nil, 0, err + return nil, 0, err } - net := utils.ToBitcoinNetwork(a.Network) + _, myPubkey, _, err := common.DecodeAddress(offchainAddrs[0]) + if err != nil { + return nil, 0, err + } + + myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(myPubkey)) + descriptorStr := strings.ReplaceAll( + a.BoardingDescriptorTemplate, "USER", myPubkeyStr, + ) + desc, err := descriptor.ParseTaprootDescriptor(descriptorStr) + if err != nil { + return nil, 0, err + } + + _, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc) + if err != nil { + return nil, 0, err + } + + now := time.Now() fetchedUtxos := make([]explorer.Utxo, 0) - for _, onchainAddr := range onchainAddrs { - utxos, err := a.explorer.GetUtxos(onchainAddr) + for _, addr := range boardingAddrs { + utxos, err := a.explorer.GetUtxos(addr) if err != nil { - return nil, nil, 0, err + return nil, 0, err + } + + for _, utxo := range utxos { + u := utxo.ToUtxo(boardingTimeout) + if u.SpendableAt.Before(now) { + fetchedUtxos = append(fetchedUtxos, u) + } } - fetchedUtxos = append(fetchedUtxos, utxos...) } - utxos := make([]explorer.Utxo, 0) + selected := make([]explorer.Utxo, 0) selectedAmount := uint64(0) for _, utxo := range fetchedUtxos { if selectedAmount >= targetAmount { @@ -1546,70 +1375,51 @@ func (a *covenantlessArkClient) coinSelectOnchain( } } - utxos = append(utxos, utxo) + selected = append(selected, utxo) selectedAmount += utxo.Amount } if selectedAmount >= targetAmount { - return utxos, nil, selectedAmount - targetAmount, nil + return selected, selectedAmount - targetAmount, nil } fetchedUtxos = make([]explorer.Utxo, 0) - for _, offchainAddr := range offchainAddrs { - _, userPubkey, aspPubkey, _ := common.DecodeAddress(offchainAddr) - - vtxoTapKey, _, err := bitcointree.ComputeVtxoTaprootScript( - userPubkey, aspPubkey, uint(a.UnilateralExitDelay), - ) + for _, addr := range redemptionAddrs { + utxos, err := a.explorer.GetUtxos(addr) if err != nil { - return nil, nil, 0, err - } - p2tr, err := btcutil.NewAddressTaproot( - schnorr.SerializePubKey(vtxoTapKey), &net, - ) - if err != nil { - return nil, nil, 0, err + return nil, 0, err } - addr := p2tr.EncodeAddress() - - utxos, err = a.explorer.GetUtxos(addr) - if err != nil { - return nil, nil, 0, err + for _, utxo := range utxos { + u := utxo.ToUtxo(uint(a.UnilateralExitDelay)) + if u.SpendableAt.Before(now) { + fetchedUtxos = append(fetchedUtxos, u) + } } - fetchedUtxos = append(fetchedUtxos, utxos...) } - delayedUtxos := make([]explorer.Utxo, 0) for _, utxo := range fetchedUtxos { if selectedAmount >= targetAmount { break } - availableAt := time.Unix(utxo.Status.Blocktime, 0).Add( - time.Duration(a.UnilateralExitDelay) * time.Second, - ) - if availableAt.After(time.Now()) { - continue - } - for _, excluded := range exclude { if utxo.Txid == excluded.Txid && utxo.Vout == excluded.Vout { continue } } - delayedUtxos = append(delayedUtxos, utxo) + selected = append(selected, utxo) selectedAmount += utxo.Amount } if selectedAmount < targetAmount { - return nil, nil, 0, fmt.Errorf( + return nil, 0, fmt.Errorf( "not enough funds to cover amount %d", targetAmount, ) } - return utxos, delayedUtxos, selectedAmount - targetAmount, nil + return selected, selectedAmount - targetAmount, nil } func (a *covenantlessArkClient) getRedeemBranches( @@ -1673,6 +1483,53 @@ func (a *covenantlessArkClient) getOffchainBalance( return balance, amountByExpiration, nil } +func (a *covenantlessArkClient) getClaimableBoardingUtxos(ctx context.Context) ([]explorer.Utxo, error) { + offchainAddrs, boardingAddrs, _, err := a.wallet.GetAddresses(ctx) + if err != nil { + return nil, err + } + + _, myPubkey, _, err := common.DecodeAddress(offchainAddrs[0]) + if err != nil { + return nil, err + } + + myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(myPubkey)) + descriptorStr := strings.ReplaceAll( + a.BoardingDescriptorTemplate, "USER", myPubkeyStr, + ) + desc, err := descriptor.ParseTaprootDescriptor(descriptorStr) + if err != nil { + return nil, err + } + + _, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc) + if err != nil { + return nil, err + } + + claimable := make([]explorer.Utxo, 0) + now := time.Now() + + for _, addr := range boardingAddrs { + boardingUtxos, err := a.explorer.GetUtxos(addr) + if err != nil { + return nil, err + } + + for _, utxo := range boardingUtxos { + u := utxo.ToUtxo(boardingTimeout) + if u.SpendableAt.Before(now) { + continue + } + claimable = append(claimable, u) + } + + } + + return claimable, nil +} + func (a *covenantlessArkClient) getVtxos( ctx context.Context, addr string, computeVtxoExpiration bool, ) ([]client.Vtxo, []client.Vtxo, error) { @@ -1718,14 +1575,25 @@ func (a *covenantlessArkClient) getVtxos( } func (a *covenantlessArkClient) selfTransferAllPendingPayments( - ctx context.Context, pendingVtxos []client.Vtxo, myself client.Output, + ctx context.Context, pendingVtxos []client.Vtxo, boardingUtxo []explorer.Utxo, myself client.Output, boardingDescriptor string, ) (string, error) { - inputs := make([]client.VtxoKey, 0, len(pendingVtxos)) + inputs := make([]client.Input, 0, len(pendingVtxos)+len(boardingUtxo)) for _, coin := range pendingVtxos { inputs = append(inputs, coin.VtxoKey) } + for _, utxo := range boardingUtxo { + fmt.Println(utxo) + fmt.Println(boardingDescriptor) + inputs = append(inputs, client.BoardingInput{ + VtxoKey: client.VtxoKey{ + Txid: utxo.Txid, + VOut: utxo.Vout, + }, + Descriptor: boardingDescriptor, + }) + } outputs := []client.Output{myself} roundEphemeralKey, err := secp256k1.GeneratePrivateKey() @@ -1747,7 +1615,7 @@ func (a *covenantlessArkClient) selfTransferAllPendingPayments( } roundTxid, err := a.handleRoundStream( - ctx, paymentID, pendingVtxos, outputs, roundEphemeralKey, + ctx, paymentID, pendingVtxos, len(boardingUtxo) > 0, outputs, roundEphemeralKey, ) if err != nil { return "", err diff --git a/pkg/client-sdk/example/covenant/alice_to_bob.go b/pkg/client-sdk/example/covenant/alice_to_bob.go index 1c782e2..d28ee6b 100644 --- a/pkg/client-sdk/example/covenant/alice_to_bob.go +++ b/pkg/client-sdk/example/covenant/alice_to_bob.go @@ -38,31 +38,27 @@ func main() { defer aliceArkClient.Lock(ctx, password) log.Info("alice is acquiring onchain funds...") - _, aliceOnchainAddr, err := aliceArkClient.Receive(ctx) + _, aliceBoardingAddr, err := aliceArkClient.Receive(ctx) if err != nil { log.Fatal(err) } - if _, err := runCommand("nigiri", "faucet", "--liquid", aliceOnchainAddr); err != nil { + if _, err := runCommand("nigiri", "faucet", "--liquid", aliceBoardingAddr); err != nil { log.Fatal(err) } time.Sleep(5 * time.Second) - onboardAmount := uint64(20000) + onboardAmount := uint64(1_0000_0000) // 1 BTC log.Infof("alice is onboarding with %d sats offchain...", onboardAmount) - txid, err := aliceArkClient.Onboard(ctx, onboardAmount) + + log.Infof("alice claiming onboarding funds...") + txid, err := aliceArkClient.Claim(ctx) if err != nil { log.Fatal(err) } - if err := generateBlock(); err != nil { - log.Fatal(err) - } - - time.Sleep(5 * time.Second) - - log.Infof("alice onboarded with tx: %s", txid) + log.Infof("onboarding completed in round tx: %s", txid) aliceBalance, err := aliceArkClient.Balance(ctx, false) if err != nil { diff --git a/pkg/client-sdk/example/covenantless/alice_to_bob.go b/pkg/client-sdk/example/covenantless/alice_to_bob.go index 08c5790..ebf9056 100644 --- a/pkg/client-sdk/example/covenantless/alice_to_bob.go +++ b/pkg/client-sdk/example/covenantless/alice_to_bob.go @@ -38,31 +38,19 @@ func main() { defer aliceArkClient.Lock(ctx, password) log.Info("alice is acquiring onchain funds...") - _, aliceOnchainAddr, err := aliceArkClient.Receive(ctx) + _, boardingAddress, err := aliceArkClient.Receive(ctx) if err != nil { log.Fatal(err) } - if _, err := runCommand("nigiri", "faucet", aliceOnchainAddr); err != nil { + if _, err := runCommand("nigiri", "faucet", boardingAddress); err != nil { log.Fatal(err) } time.Sleep(5 * time.Second) - onboardAmount := uint64(20000) + onboardAmount := uint64(1_0000_0000) // 1 BTC log.Infof("alice is onboarding with %d sats offchain...", onboardAmount) - txid, err := aliceArkClient.Onboard(ctx, onboardAmount) - if err != nil { - log.Fatal(err) - } - - if err := generateBlock(); err != nil { - log.Fatal(err) - } - - time.Sleep(5 * time.Second) - - log.Infof("alice onboarded with tx: %s", txid) aliceBalance, err := aliceArkClient.Balance(ctx, false) if err != nil { @@ -72,6 +60,14 @@ func main() { log.Infof("alice onchain balance: %d", aliceBalance.OnchainBalance.SpendableAmount) log.Infof("alice offchain balance: %d", aliceBalance.OffchainBalance.Total) + log.Infof("alice claiming onboarding funds...") + txid, err := aliceArkClient.Claim(ctx) + if err != nil { + log.Fatal(err) + } + + log.Infof("alice claimed onboarding funds in round %s", txid) + fmt.Println("") log.Info("bob is setting up his ark wallet...") bobArkClient, err := setupArkClient() @@ -137,7 +133,7 @@ func main() { fmt.Println("") log.Info("bob is claiming the incoming payment...") - roundTxid, err := bobArkClient.ClaimAsync(ctx) + roundTxid, err := bobArkClient.Claim(ctx) if err != nil { log.Fatal(err) } diff --git a/pkg/client-sdk/explorer/explorer.go b/pkg/client-sdk/explorer/explorer.go index fba9b71..d435534 100644 --- a/pkg/client-sdk/explorer/explorer.go +++ b/pkg/client-sdk/explorer/explorer.go @@ -25,6 +25,35 @@ const ( ) type Utxo struct { + Txid string + Vout uint32 + Amount uint64 + Asset string // liquid only + Delay uint + SpendableAt time.Time +} + +func (u *Utxo) Sequence() (uint32, error) { + return common.BIP68EncodeAsNumber(u.Delay) +} + +func newUtxo(explorerUtxo ExplorerUtxo, delay uint) Utxo { + utxoTime := explorerUtxo.Status.Blocktime + if utxoTime == 0 { + utxoTime = time.Now().Unix() + } + + return Utxo{ + Txid: explorerUtxo.Txid, + Vout: explorerUtxo.Vout, + Amount: explorerUtxo.Amount, + Asset: explorerUtxo.Asset, + Delay: delay, + SpendableAt: time.Unix(utxoTime, 0).Add(time.Duration(delay) * time.Second), + } +} + +type ExplorerUtxo struct { Txid string `json:"txid"` Vout uint32 `json:"vout"` Amount uint64 `json:"value"` @@ -35,10 +64,14 @@ type Utxo struct { } `json:"status"` } +func (e ExplorerUtxo) ToUtxo(delay uint) Utxo { + return newUtxo(e, delay) +} + type Explorer interface { GetTxHex(txid string) (string, error) Broadcast(txHex string) (string, error) - GetUtxos(addr string) ([]Utxo, error) + GetUtxos(addr string) ([]ExplorerUtxo, error) GetBalance(addr string) (uint64, error) GetRedeemedVtxosBalance( addr string, unilateralExitDelay int64, @@ -143,7 +176,7 @@ func (e *explorerSvc) Broadcast(txStr string) (string, error) { return txid, nil } -func (e *explorerSvc) GetUtxos(addr string) ([]Utxo, error) { +func (e *explorerSvc) GetUtxos(addr string) ([]ExplorerUtxo, error) { resp, err := http.Get(fmt.Sprintf("%s/address/%s/utxo", e.baseUrl, addr)) if err != nil { return nil, err @@ -157,7 +190,7 @@ func (e *explorerSvc) GetUtxos(addr string) ([]Utxo, error) { if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf(string(body)) } - payload := []Utxo{} + payload := []ExplorerUtxo{} if err := json.Unmarshal(body, &payload); err != nil { return nil, err } diff --git a/pkg/client-sdk/go.mod b/pkg/client-sdk/go.mod index eb00fa0..473bd75 100644 --- a/pkg/client-sdk/go.mod +++ b/pkg/client-sdk/go.mod @@ -2,6 +2,8 @@ module github.com/ark-network/ark/pkg/client-sdk go 1.22.6 +replace github.com/ark-network/ark/common => ../../common + require ( github.com/ark-network/ark/api-spec v0.0.0-20240815203029-edc4534dfc87 github.com/ark-network/ark/common v0.0.0-20240815203029-edc4534dfc87 diff --git a/pkg/client-sdk/go.sum b/pkg/client-sdk/go.sum index 511d2d4..e4b13fd 100644 --- a/pkg/client-sdk/go.sum +++ b/pkg/client-sdk/go.sum @@ -1,8 +1,6 @@ github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/ark-network/ark/api-spec v0.0.0-20240815203029-edc4534dfc87 h1:VBY4KqHqxE4q6NnmvEZTLvLZoNA0Q6NhMhjBs1hzy9Y= github.com/ark-network/ark/api-spec v0.0.0-20240815203029-edc4534dfc87/go.mod h1:m5H86Dx+k8cQjLXeYL1MV+h3x/XnhJCXJP/PL3KgZqY= -github.com/ark-network/ark/common v0.0.0-20240815203029-edc4534dfc87 h1:TIv00zlpxLKmY2LjFAIMF8RxNtn9rFqQsv73Lwoj2ds= -github.com/ark-network/ark/common v0.0.0-20240815203029-edc4534dfc87/go.mod h1:aYAGDfoeBLofnZt9n85wusFyCkrS7hvwdo5TynBlkuY= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= diff --git a/pkg/client-sdk/store/file/store.go b/pkg/client-sdk/store/file/store.go index 03e9780..b82e402 100644 --- a/pkg/client-sdk/store/file/store.go +++ b/pkg/client-sdk/store/file/store.go @@ -21,14 +21,15 @@ const ( ) type storeData struct { - AspUrl string `json:"asp_url"` - AspPubkey string `json:"asp_pubkey"` - WalletType string `json:"wallet_type"` - ClientType string `json:"client_type"` - Network string `json:"network"` - RoundLifetime string `json:"round_lifetime"` - UnilateralExitDelay string `json:"unilateral_exit_delay"` - MinRelayFee string `json:"min_relay_fee"` + AspUrl string `json:"asp_url"` + AspPubkey string `json:"asp_pubkey"` + WalletType string `json:"wallet_type"` + ClientType string `json:"client_type"` + Network string `json:"network"` + RoundLifetime string `json:"round_lifetime"` + UnilateralExitDelay string `json:"unilateral_exit_delay"` + MinRelayFee string `json:"min_relay_fee"` + BoardingDescriptorTemplate string `json:"boarding_descriptor_template"` } func (d storeData) isEmpty() bool { @@ -43,27 +44,29 @@ func (d storeData) decode() store.StoreData { buf, _ := hex.DecodeString(d.AspPubkey) aspPubkey, _ := secp256k1.ParsePubKey(buf) return store.StoreData{ - AspUrl: d.AspUrl, - AspPubkey: aspPubkey, - WalletType: d.WalletType, - ClientType: d.ClientType, - Network: network, - RoundLifetime: int64(roundLifetime), - UnilateralExitDelay: int64(unilateralExitDelay), - MinRelayFee: uint64(minRelayFee), + AspUrl: d.AspUrl, + AspPubkey: aspPubkey, + WalletType: d.WalletType, + ClientType: d.ClientType, + Network: network, + RoundLifetime: int64(roundLifetime), + UnilateralExitDelay: int64(unilateralExitDelay), + MinRelayFee: uint64(minRelayFee), + BoardingDescriptorTemplate: d.BoardingDescriptorTemplate, } } func (d storeData) asMap() map[string]string { return map[string]string{ - "asp_url": d.AspUrl, - "asp_pubkey": d.AspPubkey, - "wallet_type": d.WalletType, - "client_type": d.ClientType, - "network": d.Network, - "round_lifetime": d.RoundLifetime, - "unilateral_exit_delay": d.UnilateralExitDelay, - "min_relay_fee": d.MinRelayFee, + "asp_url": d.AspUrl, + "asp_pubkey": d.AspPubkey, + "wallet_type": d.WalletType, + "client_type": d.ClientType, + "network": d.Network, + "round_lifetime": d.RoundLifetime, + "unilateral_exit_delay": d.UnilateralExitDelay, + "min_relay_fee": d.MinRelayFee, + "boarding_descriptor_template": d.BoardingDescriptorTemplate, } } @@ -100,14 +103,15 @@ func (s *Store) GetDatadir() string { func (s *Store) AddData(ctx context.Context, data store.StoreData) error { sd := &storeData{ - AspUrl: data.AspUrl, - AspPubkey: hex.EncodeToString(data.AspPubkey.SerializeCompressed()), - WalletType: data.WalletType, - ClientType: data.ClientType, - Network: data.Network.Name, - RoundLifetime: fmt.Sprintf("%d", data.RoundLifetime), - UnilateralExitDelay: fmt.Sprintf("%d", data.UnilateralExitDelay), - MinRelayFee: fmt.Sprintf("%d", data.MinRelayFee), + AspUrl: data.AspUrl, + AspPubkey: hex.EncodeToString(data.AspPubkey.SerializeCompressed()), + WalletType: data.WalletType, + ClientType: data.ClientType, + Network: data.Network.Name, + RoundLifetime: fmt.Sprintf("%d", data.RoundLifetime), + UnilateralExitDelay: fmt.Sprintf("%d", data.UnilateralExitDelay), + MinRelayFee: fmt.Sprintf("%d", data.MinRelayFee), + BoardingDescriptorTemplate: data.BoardingDescriptorTemplate, } if err := s.write(sd); err != nil { diff --git a/pkg/client-sdk/store/store.go b/pkg/client-sdk/store/store.go index efaa4ad..80c2c2d 100644 --- a/pkg/client-sdk/store/store.go +++ b/pkg/client-sdk/store/store.go @@ -13,14 +13,15 @@ const ( ) type StoreData struct { - AspUrl string - AspPubkey *secp256k1.PublicKey - WalletType string - ClientType string - Network common.Network - RoundLifetime int64 - UnilateralExitDelay int64 - MinRelayFee uint64 + AspUrl string + AspPubkey *secp256k1.PublicKey + WalletType string + ClientType string + Network common.Network + RoundLifetime int64 + UnilateralExitDelay int64 + MinRelayFee uint64 + BoardingDescriptorTemplate string } type ConfigStore interface { diff --git a/pkg/client-sdk/store/store_test.go b/pkg/client-sdk/store/store_test.go index 0fa3d2d..775bec5 100644 --- a/pkg/client-sdk/store/store_test.go +++ b/pkg/client-sdk/store/store_test.go @@ -18,14 +18,15 @@ func TestStore(t *testing.T) { key, _ := btcec.NewPrivateKey() ctx := context.Background() testStoreData := store.StoreData{ - AspUrl: "localhost:7070", - AspPubkey: key.PubKey(), - WalletType: wallet.SingleKeyWallet, - ClientType: client.GrpcClient, - Network: common.LiquidRegTest, - RoundLifetime: 512, - UnilateralExitDelay: 512, - MinRelayFee: 300, + AspUrl: "localhost:7070", + AspPubkey: key.PubKey(), + WalletType: wallet.SingleKeyWallet, + ClientType: client.GrpcClient, + Network: common.LiquidRegTest, + RoundLifetime: 512, + UnilateralExitDelay: 512, + MinRelayFee: 300, + BoardingDescriptorTemplate: "tr(0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0,{ and(pk(873079a0091c9b16abd1f8c508320b07f0d50144d09ccd792ce9c915dac60465), pk(USER)), and(older(604672), pk(USER)) })", } tests := []struct { diff --git a/pkg/client-sdk/wallet/singlekey/bitcoin_wallet.go b/pkg/client-sdk/wallet/singlekey/bitcoin_wallet.go index 698fb7f..0e09490 100644 --- a/pkg/client-sdk/wallet/singlekey/bitcoin_wallet.go +++ b/pkg/client-sdk/wallet/singlekey/bitcoin_wallet.go @@ -9,12 +9,12 @@ import ( "github.com/ark-network/ark/common" "github.com/ark-network/ark/common/bitcointree" + "github.com/ark-network/ark/common/descriptor" "github.com/ark-network/ark/pkg/client-sdk/explorer" "github.com/ark-network/ark/pkg/client-sdk/internal/utils" "github.com/ark-network/ark/pkg/client-sdk/store" "github.com/ark-network/ark/pkg/client-sdk/wallet" walletstore "github.com/ark-network/ark/pkg/client-sdk/wallet/singlekey/store" - "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil/psbt" @@ -41,42 +41,42 @@ func NewBitcoinWallet( func (w *bitcoinWallet) GetAddresses( ctx context.Context, ) ([]string, []string, []string, error) { - offchainAddr, onchainAddr, redemptionAddr, err := w.getAddress(ctx) + offchainAddr, boardingAddr, redemptionAddr, err := w.getAddress(ctx) if err != nil { return nil, nil, nil, err } offchainAddrs := []string{offchainAddr} - onchainAddrs := []string{onchainAddr} + boardingAddrs := []string{boardingAddr} redemptionAddrs := []string{redemptionAddr} - return offchainAddrs, onchainAddrs, redemptionAddrs, nil + return offchainAddrs, boardingAddrs, redemptionAddrs, nil } func (w *bitcoinWallet) NewAddress( ctx context.Context, _ bool, ) (string, string, error) { - offchainAddr, onchainAddr, _, err := w.getAddress(ctx) + offchainAddr, boardingAddr, _, err := w.getAddress(ctx) if err != nil { return "", "", err } - return offchainAddr, onchainAddr, nil + return offchainAddr, boardingAddr, nil } func (w *bitcoinWallet) NewAddresses( ctx context.Context, _ bool, num int, ) ([]string, []string, error) { - offchainAddr, onchainAddr, _, err := w.getAddress(ctx) + offchainAddr, boardingAddr, _, err := w.getAddress(ctx) if err != nil { return nil, nil, err } offchainAddrs := make([]string, 0, num) - onchainAddrs := make([]string, 0, num) + boardingAddrs := make([]string, 0, num) for i := 0; i < num; i++ { offchainAddrs = append(offchainAddrs, offchainAddr) - onchainAddrs = append(onchainAddrs, onchainAddr) + boardingAddrs = append(boardingAddrs, boardingAddr) } - return offchainAddrs, onchainAddrs, nil + return offchainAddrs, boardingAddrs, nil } func (s *bitcoinWallet) SignTransaction( @@ -92,11 +92,6 @@ func (s *bitcoinWallet) SignTransaction( return "", err } - data, err := s.configStore.GetData(ctx) - if err != nil { - return "", err - } - for i, input := range updater.Upsbt.UnsignedTx.TxIn { if updater.Upsbt.Inputs[i].WitnessUtxo != nil { continue @@ -122,28 +117,11 @@ func (s *bitcoinWallet) SignTransaction( return "", err } - sighashType := txscript.SigHashAll - - if utxo.PkScript[0] == txscript.OP_1 { - sighashType = txscript.SigHashDefault - } - - if err := updater.AddInSighashType(sighashType, i); err != nil { + if err := updater.AddInSighashType(txscript.SigHashDefault, i); err != nil { return "", err } } - _, onchainAddr, _, err := s.getAddress(ctx) - if err != nil { - return "", err - } - net := utils.ToBitcoinNetwork(data.Network) - addr, _ := btcutil.DecodeAddress(onchainAddr, &net) - onchainWalletScript, err := txscript.PayToAddrScript(addr) - if err != nil { - return "", err - } - prevouts := make(map[wire.OutPoint]*wire.TxOut) for i, input := range updater.Upsbt.Inputs { @@ -158,36 +136,6 @@ func (s *bitcoinWallet) SignTransaction( txsighashes := txscript.NewTxSigHashes(updater.Upsbt.UnsignedTx, prevoutFetcher) for i, input := range ptx.Inputs { - if bytes.Equal(input.WitnessUtxo.PkScript, onchainWalletScript) { - if err := updater.AddInSighashType(txscript.SigHashAll, i); err != nil { - return "", err - } - - preimage, err := txscript.CalcWitnessSigHash( - input.WitnessUtxo.PkScript, - txsighashes, - txscript.SigHashAll, - updater.Upsbt.UnsignedTx, - i, - int64(input.WitnessUtxo.Value), - ) - if err != nil { - return "", err - } - - sig := ecdsa.Sign(s.privateKey, preimage) - signatureWithSighashType := append(sig.Serialize(), byte(txscript.SigHashAll)) - - updater.Upsbt.Inputs[i].PartialSigs = []*psbt.PartialSig{ - { - PubKey: s.walletData.Pubkey.SerializeCompressed(), - Signature: signatureWithSighashType, - }, - } - - continue - } - if len(input.TaprootLeafScript) > 0 { pubkey := s.walletData.Pubkey for _, leaf := range input.TaprootLeafScript { @@ -269,11 +217,6 @@ func (w *bitcoinWallet) getAddress( netParams := utils.ToBitcoinNetwork(data.Network) - onchainAddr, err := btcutil.NewAddressWitnessPubKeyHash(btcutil.Hash160(w.walletData.Pubkey.SerializeCompressed()), &netParams) - if err != nil { - return "", "", "", err - } - vtxoTapKey, _, err := bitcointree.ComputeVtxoTaprootScript( w.walletData.Pubkey, data.AspPubkey, uint(data.UnilateralExitDelay), ) @@ -289,5 +232,35 @@ func (w *bitcoinWallet) getAddress( return "", "", "", err } - return offchainAddr, onchainAddr.EncodeAddress(), redemptionAddr.EncodeAddress(), nil + myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(w.walletData.Pubkey)) + descriptorStr := strings.ReplaceAll( + data.BoardingDescriptorTemplate, "USER", myPubkeyStr, + ) + + desc, err := descriptor.ParseTaprootDescriptor(descriptorStr) + if err != nil { + return "", "", "", err + } + + _, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc) + if err != nil { + return "", "", "", err + } + + boardingTapKey, _, err := bitcointree.ComputeVtxoTaprootScript( + w.walletData.Pubkey, data.AspPubkey, boardingTimeout, + ) + if err != nil { + return "", "", "", err + } + + boardingAddr, err := btcutil.NewAddressTaproot( + schnorr.SerializePubKey(boardingTapKey), + &netParams, + ) + if err != nil { + return "", "", "", err + } + + return offchainAddr, boardingAddr.EncodeAddress(), redemptionAddr.EncodeAddress(), nil } diff --git a/pkg/client-sdk/wallet/singlekey/liquid_wallet.go b/pkg/client-sdk/wallet/singlekey/liquid_wallet.go index 296cfc6..425f4f5 100644 --- a/pkg/client-sdk/wallet/singlekey/liquid_wallet.go +++ b/pkg/client-sdk/wallet/singlekey/liquid_wallet.go @@ -3,20 +3,21 @@ package singlekeywallet import ( "bytes" "context" + "encoding/hex" "fmt" + "strings" "github.com/ark-network/ark/common" + "github.com/ark-network/ark/common/descriptor" "github.com/ark-network/ark/common/tree" "github.com/ark-network/ark/pkg/client-sdk/explorer" "github.com/ark-network/ark/pkg/client-sdk/internal/utils" "github.com/ark-network/ark/pkg/client-sdk/store" "github.com/ark-network/ark/pkg/client-sdk/wallet" walletstore "github.com/ark-network/ark/pkg/client-sdk/wallet/singlekey/store" - "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" - "github.com/vulpemventures/go-elements/payment" "github.com/vulpemventures/go-elements/psetv2" "github.com/vulpemventures/go-elements/transaction" ) @@ -40,42 +41,42 @@ func NewLiquidWallet( func (w *liquidWallet) GetAddresses( ctx context.Context, ) ([]string, []string, []string, error) { - offchainAddr, onchainAddr, redemptionAddr, err := w.getAddress(ctx) + offchainAddr, boardingAddr, redemptionAddr, err := w.getAddress(ctx) if err != nil { return nil, nil, nil, err } offchainAddrs := []string{offchainAddr} - onchainAddrs := []string{onchainAddr} + boardingAddrs := []string{boardingAddr} redemptionAddrs := []string{redemptionAddr} - return offchainAddrs, onchainAddrs, redemptionAddrs, nil + return offchainAddrs, boardingAddrs, redemptionAddrs, nil } func (w *liquidWallet) NewAddress( ctx context.Context, _ bool, ) (string, string, error) { - offchainAddr, onchainAddr, _, err := w.getAddress(ctx) + offchainAddr, boardingAddr, _, err := w.getAddress(ctx) if err != nil { return "", "", err } - return offchainAddr, onchainAddr, nil + return offchainAddr, boardingAddr, nil } func (w *liquidWallet) NewAddresses( ctx context.Context, _ bool, num int, ) ([]string, []string, error) { - offchainAddr, onchainAddr, _, err := w.getAddress(ctx) + offchainAddr, boardingAddr, _, err := w.getAddress(ctx) if err != nil { return nil, nil, err } offchainAddrs := make([]string, 0, num) - onchainAddrs := make([]string, 0, num) + boardingAddrs := make([]string, 0, num) for i := 0; i < num; i++ { offchainAddrs = append(offchainAddrs, offchainAddr) - onchainAddrs = append(onchainAddrs, onchainAddr) + boardingAddrs = append(boardingAddrs, boardingAddr) } - return offchainAddrs, onchainAddrs, nil + return offchainAddrs, boardingAddrs, nil } func (s *liquidWallet) SignTransaction( @@ -114,13 +115,7 @@ func (s *liquidWallet) SignTransaction( return "", err } - sighashType := txscript.SigHashAll - - if utxo.Script[0] == txscript.OP_1 { - sighashType = txscript.SigHashDefault - } - - if err := updater.AddInSighashType(i, sighashType); err != nil { + if err := updater.AddInSighashType(i, txscript.SigHashDefault); err != nil { return "", err } } @@ -135,8 +130,6 @@ func (s *liquidWallet) SignTransaction( return "", err } liquidNet := utils.ToElementsNetwork(storeData.Network) - p2wpkh := payment.FromPublicKey(s.walletData.Pubkey, &liquidNet, nil) - onchainWalletScript := p2wpkh.WitnessScript utx, err := pset.UnsignedTx() if err != nil { @@ -156,33 +149,6 @@ func (s *liquidWallet) SignTransaction( serializedPubKey := s.walletData.Pubkey.SerializeCompressed() for i, input := range pset.Inputs { - prevout := input.GetUtxo() - - if bytes.Equal(prevout.Script, onchainWalletScript) { - p, err := payment.FromScript(prevout.Script, &liquidNet, nil) - if err != nil { - return "", err - } - - preimage := utx.HashForWitnessV0( - i, p.Script, prevout.Value, txscript.SigHashAll, - ) - - sig := ecdsa.Sign(s.privateKey, preimage[:]) - - signatureWithSighashType := append( - sig.Serialize(), byte(txscript.SigHashAll), - ) - - err = signer.SignInput( - i, signatureWithSighashType, serializedPubKey, nil, nil, - ) - if err != nil { - return "", err - } - continue - } - if len(input.TapLeafScript) > 0 { genesis, err := chainhash.NewHashFromStr(liquidNet.GenesisBlockHash) if err != nil { @@ -276,12 +242,6 @@ func (w *liquidWallet) getAddress( liquidNet := utils.ToElementsNetwork(data.Network) - p2wpkh := payment.FromPublicKey(w.walletData.Pubkey, &liquidNet, nil) - onchainAddr, err := p2wpkh.WitnessPubKeyHash() - if err != nil { - return "", "", "", err - } - _, _, _, redemptionAddr, err := tree.ComputeVtxoTaprootScript( w.walletData.Pubkey, data.AspPubkey, uint(data.UnilateralExitDelay), liquidNet, ) @@ -289,5 +249,27 @@ func (w *liquidWallet) getAddress( return "", "", "", err } - return offchainAddr, onchainAddr, redemptionAddr, nil + myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(w.walletData.Pubkey)) + descriptorStr := strings.ReplaceAll( + data.BoardingDescriptorTemplate, "USER", myPubkeyStr, + ) + + desc, err := descriptor.ParseTaprootDescriptor(descriptorStr) + if err != nil { + return "", "", "", err + } + + _, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc) + if err != nil { + return "", "", "", err + } + + _, _, _, boardingAddr, err := tree.ComputeVtxoTaprootScript( + w.walletData.Pubkey, data.AspPubkey, boardingTimeout, liquidNet, + ) + if err != nil { + return "", "", "", err + } + + return offchainAddr, boardingAddr, redemptionAddr, nil } diff --git a/pkg/client-sdk/wallet/wallet.go b/pkg/client-sdk/wallet/wallet.go index cc60e5c..5bd777a 100644 --- a/pkg/client-sdk/wallet/wallet.go +++ b/pkg/client-sdk/wallet/wallet.go @@ -20,7 +20,7 @@ type WalletService interface { IsLocked() bool GetAddresses( ctx context.Context, - ) (offchainAddresses, onchainAddresses, redemptionAddresses []string, err error) + ) (offchainAddresses, boardingAddresses, redemptionAddresses []string, err error) NewAddress( ctx context.Context, change bool, ) (offchainAddr, onchainAddr string, err error) @@ -29,5 +29,5 @@ type WalletService interface { ) (offchainAddresses, onchainAddresses []string, err error) SignTransaction( ctx context.Context, explorerSvc explorer.Explorer, tx string, - ) (singedTx string, err error) + ) (signedTx string, err error) } diff --git a/pkg/client-sdk/wallet/wallet_test.go b/pkg/client-sdk/wallet/wallet_test.go index 0f7b675..5e5c9c4 100644 --- a/pkg/client-sdk/wallet/wallet_test.go +++ b/pkg/client-sdk/wallet/wallet_test.go @@ -21,14 +21,15 @@ func TestWallet(t *testing.T) { key, _ := btcec.NewPrivateKey() password := "password" testStoreData := store.StoreData{ - AspUrl: "localhost:7070", - AspPubkey: key.PubKey(), - WalletType: wallet.SingleKeyWallet, - ClientType: client.GrpcClient, - Network: common.LiquidRegTest, - RoundLifetime: 512, - UnilateralExitDelay: 512, - MinRelayFee: 300, + AspUrl: "localhost:7070", + AspPubkey: key.PubKey(), + WalletType: wallet.SingleKeyWallet, + ClientType: client.GrpcClient, + Network: common.LiquidRegTest, + RoundLifetime: 512, + UnilateralExitDelay: 512, + MinRelayFee: 300, + BoardingDescriptorTemplate: "tr(0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0,{ and(pk(873079a0091c9b16abd1f8c508320b07f0d50144d09ccd792ce9c915dac60465), pk(USER)), and(older(604672), pk(USER)) })", } tests := []struct { name string diff --git a/pkg/client-sdk/wasm/browser/exports.go b/pkg/client-sdk/wasm/browser/exports.go index a3ecdb0..57ea105 100644 --- a/pkg/client-sdk/wasm/browser/exports.go +++ b/pkg/client-sdk/wasm/browser/exports.go @@ -27,7 +27,6 @@ func init() { js.Global().Set("lock", LockWrapper()) js.Global().Set("locked", IsLockedWrapper()) js.Global().Set("balance", BalanceWrapper()) - js.Global().Set("onboard", OnboardWrapper()) js.Global().Set("receive", ReceiveWrapper()) js.Global().Set("sendOnChain", SendOnChainWrapper()) js.Global().Set("sendOffChain", SendOffChainWrapper()) diff --git a/pkg/client-sdk/wasm/browser/wrappers.go b/pkg/client-sdk/wasm/browser/wrappers.go index c50693e..a1ac12c 100644 --- a/pkg/client-sdk/wasm/browser/wrappers.go +++ b/pkg/client-sdk/wasm/browser/wrappers.go @@ -140,33 +140,18 @@ func BalanceWrapper() js.Func { }) } -func OnboardWrapper() js.Func { - return JSPromise(func(args []js.Value) (interface{}, error) { - if len(args) != 1 { - return nil, errors.New("invalid number of args") - } - amount := uint64(args[0].Int()) - - txID, err := arkSdkClient.Onboard(context.Background(), amount) - if err != nil { - return nil, err - } - return js.ValueOf(txID), nil - }) -} - func ReceiveWrapper() js.Func { return JSPromise(func(args []js.Value) (interface{}, error) { if arkSdkClient == nil { return nil, errors.New("ARK SDK client is not initialized") } - offchainAddr, onchainAddr, err := arkSdkClient.Receive(context.Background()) + offchainAddr, boardingAddr, err := arkSdkClient.Receive(context.Background()) if err != nil { return nil, err } result := map[string]interface{}{ "offchainAddr": offchainAddr, - "onchainAddr": onchainAddr, + "boardingAddr": boardingAddr, } return js.ValueOf(result), nil }) diff --git a/server/cmd/arkd/main.go b/server/cmd/arkd/main.go index 2c65a9a..ddff131 100755 --- a/server/cmd/arkd/main.go +++ b/server/cmd/arkd/main.go @@ -79,6 +79,7 @@ func mainAction(_ *cli.Context) error { BitcoindRpcUser: cfg.BitcoindRpcUser, BitcoindRpcPass: cfg.BitcoindRpcPass, BitcoindRpcHost: cfg.BitcoindRpcHost, + BoardingExitDelay: cfg.BoardingExitDelay, } svc, err := grpcservice.NewService(svcConfig, appConfig) if err != nil { diff --git a/server/go.mod b/server/go.mod index f07fc8f..7b8d5d1 100644 --- a/server/go.mod +++ b/server/go.mod @@ -2,6 +2,8 @@ module github.com/ark-network/ark/server go 1.22.6 +replace github.com/ark-network/ark/common => ../common + replace github.com/btcsuite/btcd/btcec/v2 => github.com/btcsuite/btcd/btcec/v2 v2.3.3 require ( diff --git a/server/go.sum b/server/go.sum index 9533521..a8a945f 100644 --- a/server/go.sum +++ b/server/go.sum @@ -24,8 +24,6 @@ github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBA github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/ark-network/ark/api-spec v0.0.0-20240812233307-18e343b31899 h1:PJL9Pam042F790x3mMovaIIkgeKIVaWm1aFOyH0k4PY= github.com/ark-network/ark/api-spec v0.0.0-20240812233307-18e343b31899/go.mod h1:0B5seq/gzuGL8OZGUaO12yj73ZJKAde8L+nmLQAZ7IA= -github.com/ark-network/ark/common v0.0.0-20240812233307-18e343b31899 h1:PxcHv+KaBdfrZCHoNYSUiCdI2wNIZ3Oxx8ZUewcEesg= -github.com/ark-network/ark/common v0.0.0-20240812233307-18e343b31899/go.mod h1:8DYeb06Dl8onmrV09xfsdDMGv5HoVtWoKhLBLXOYHew= github.com/ark-network/ark/server/pkg/kvdb v0.0.0-20240812233307-18e343b31899 h1:pUtYz5kx/hvm8EiF4aj1BNdLFKMGnV3g4s1lQST9jFg= github.com/ark-network/ark/server/pkg/kvdb v0.0.0-20240812233307-18e343b31899/go.mod h1:8DXpdLoJXeIPh3JPd6AoANVTf7Rw63QLL8l+OJkNBlU= github.com/ark-network/ark/server/pkg/macaroons v0.0.0-20240812233307-18e343b31899 h1:Vlc9pbGToiqeBbAx3q4Wovg/DK6DJXAE2k5YAQiqza4= diff --git a/server/internal/app-config/config.go b/server/internal/app-config/config.go index d639cbe..d307082 100644 --- a/server/internal/app-config/config.go +++ b/server/internal/app-config/config.go @@ -63,6 +63,7 @@ type Config struct { MinRelayFee uint64 RoundLifetime int64 UnilateralExitDelay int64 + BoardingExitDelay int64 EsploraURL string NeutrinoPeer string @@ -126,6 +127,12 @@ func (c *Config) Validate() error { ) } + if c.BoardingExitDelay < minAllowedSequence { + return fmt.Errorf( + "invalid boarding exit delay, must at least %d", minAllowedSequence, + ) + } + if c.RoundLifetime%minAllowedSequence != 0 { c.RoundLifetime -= c.RoundLifetime % minAllowedSequence log.Infof( @@ -142,6 +149,14 @@ func (c *Config) Validate() error { ) } + if c.BoardingExitDelay%minAllowedSequence != 0 { + c.BoardingExitDelay -= c.BoardingExitDelay % minAllowedSequence + log.Infof( + "boarding exit delay must be a multiple of %d, rounded to %d", + minAllowedSequence, c.BoardingExitDelay, + ) + } + if err := c.repoManager(); err != nil { return err } @@ -275,11 +290,11 @@ func (c *Config) txBuilderService() error { switch c.TxBuilderType { case "covenant": svc = txbuilder.NewTxBuilder( - c.wallet, c.Network, c.RoundLifetime, c.UnilateralExitDelay, + c.wallet, c.Network, c.RoundLifetime, c.UnilateralExitDelay, c.BoardingExitDelay, ) case "covenantless": svc = cltxbuilder.NewTxBuilder( - c.wallet, c.Network, c.RoundLifetime, c.UnilateralExitDelay, + c.wallet, c.Network, c.RoundLifetime, c.UnilateralExitDelay, c.BoardingExitDelay, ) default: err = fmt.Errorf("unknown tx builder type") @@ -323,7 +338,7 @@ func (c *Config) schedulerService() error { func (c *Config) appService() error { if common.IsLiquid(c.Network) { svc, err := application.NewCovenantService( - c.Network, c.RoundInterval, c.RoundLifetime, c.UnilateralExitDelay, + c.Network, c.RoundInterval, c.RoundLifetime, c.UnilateralExitDelay, c.BoardingExitDelay, c.MinRelayFee, c.wallet, c.repo, c.txBuilder, c.scanner, c.scheduler, ) if err != nil { @@ -335,7 +350,7 @@ func (c *Config) appService() error { } svc, err := application.NewCovenantlessService( - c.Network, c.RoundInterval, c.RoundLifetime, c.UnilateralExitDelay, + c.Network, c.RoundInterval, c.RoundLifetime, c.UnilateralExitDelay, c.BoardingExitDelay, c.MinRelayFee, c.wallet, c.repo, c.txBuilder, c.scanner, c.scheduler, ) if err != nil { diff --git a/server/internal/config/config.go b/server/internal/config/config.go index 2824dcb..cef1971 100644 --- a/server/internal/config/config.go +++ b/server/internal/config/config.go @@ -29,6 +29,7 @@ type Config struct { MinRelayFee uint64 RoundLifetime int64 UnilateralExitDelay int64 + BoardingExitDelay int64 EsploraURL string NeutrinoPeer string BitcoindRpcUser string @@ -54,6 +55,7 @@ var ( MinRelayFee = "MIN_RELAY_FEE" RoundLifetime = "ROUND_LIFETIME" UnilateralExitDelay = "UNILATERAL_EXIT_DELAY" + BoardingExitDelay = "BOARDING_EXIT_DELAY" EsploraURL = "ESPLORA_URL" NeutrinoPeer = "NEUTRINO_PEER" BitcoindRpcUser = "BITCOIND_RPC_USER" @@ -79,6 +81,7 @@ var ( defaultMinRelayFee = 30 // 0.1 sat/vbyte on Liquid defaultRoundLifetime = 604672 defaultUnilateralExitDelay = 1024 + defaultBoardingExitDelay = 604672 defaultNoMacaroons = false defaultNoTLS = false ) @@ -104,6 +107,7 @@ func LoadConfig() (*Config, error) { viper.SetDefault(UnilateralExitDelay, defaultUnilateralExitDelay) viper.SetDefault(BlockchainScannerType, defaultBlockchainScannerType) viper.SetDefault(NoMacaroons, defaultNoMacaroons) + viper.SetDefault(BoardingExitDelay, defaultBoardingExitDelay) net, err := getNetwork() if err != nil { @@ -132,6 +136,7 @@ func LoadConfig() (*Config, error) { MinRelayFee: viper.GetUint64(MinRelayFee), RoundLifetime: viper.GetInt64(RoundLifetime), UnilateralExitDelay: viper.GetInt64(UnilateralExitDelay), + BoardingExitDelay: viper.GetInt64(BoardingExitDelay), EsploraURL: viper.GetString(EsploraURL), NeutrinoPeer: viper.GetString(NeutrinoPeer), BitcoindRpcUser: viper.GetString(BitcoindRpcUser), diff --git a/server/internal/core/application/covenant.go b/server/internal/core/application/covenant.go index f97ad68..64cc351 100644 --- a/server/internal/core/application/covenant.go +++ b/server/internal/core/application/covenant.go @@ -10,14 +10,18 @@ import ( "time" "github.com/ark-network/ark/common" + "github.com/ark-network/ark/common/descriptor" "github.com/ark-network/ark/common/tree" "github.com/ark-network/ark/server/internal/core/domain" "github.com/ark-network/ark/server/internal/core/ports" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/decred/dcrd/dcrec/secp256k1/v4" log "github.com/sirupsen/logrus" + "github.com/vulpemventures/go-elements/elementsutil" "github.com/vulpemventures/go-elements/psetv2" + "github.com/vulpemventures/go-elements/transaction" ) var ( @@ -30,6 +34,7 @@ type covenantService struct { roundLifetime int64 roundInterval int64 unilateralExitDelay int64 + boardingExitDelay int64 minRelayFee uint64 wallet ports.WalletService @@ -41,23 +46,22 @@ type covenantService struct { paymentRequests *paymentsMap forfeitTxs *forfeitTxsMap - eventsCh chan domain.RoundEvent - onboardingCh chan onboarding + eventsCh chan domain.RoundEvent - lastEvent domain.RoundEvent - currentRound *domain.Round + currentRoundLock sync.Mutex + currentRound *domain.Round + lastEvent domain.RoundEvent } func NewCovenantService( network common.Network, - roundInterval, roundLifetime, unilateralExitDelay int64, minRelayFee uint64, + roundInterval, roundLifetime, unilateralExitDelay, boardingExitDelay int64, minRelayFee uint64, walletSvc ports.WalletService, repoManager ports.RepoManager, builder ports.TxBuilder, scanner ports.BlockchainScanner, scheduler ports.SchedulerService, ) (Service, error) { eventsCh := make(chan domain.RoundEvent) - onboardingCh := make(chan onboarding) - paymentRequests := newPaymentsMap(nil) + paymentRequests := newPaymentsMap() forfeitTxs := newForfeitTxsMap(builder) pubkey, err := walletSvc.GetPubkey(context.Background()) @@ -69,9 +73,9 @@ func NewCovenantService( svc := &covenantService{ network, pubkey, - roundLifetime, roundInterval, unilateralExitDelay, minRelayFee, + roundLifetime, roundInterval, unilateralExitDelay, boardingExitDelay, minRelayFee, walletSvc, repoManager, builder, scanner, sweeper, - paymentRequests, forfeitTxs, eventsCh, onboardingCh, nil, nil, + paymentRequests, forfeitTxs, eventsCh, sync.Mutex{}, nil, nil, } repoManager.RegisterEventsHandler( func(round *domain.Round) { @@ -88,7 +92,6 @@ func NewCovenantService( return nil, fmt.Errorf("failed to restore watching vtxos: %s", err) } go svc.listenToScannerNotifications() - go svc.listenToOnboarding() return svc, nil } @@ -116,30 +119,164 @@ func (s *covenantService) Stop() { s.repoManager.Close() log.Debug("closed connection to db") close(s.eventsCh) - close(s.onboardingCh) } -func (s *covenantService) SpendVtxos(ctx context.Context, inputs []domain.VtxoKey) (string, error) { - vtxos, err := s.repoManager.Vtxos().GetVtxos(ctx, inputs) +func (s *covenantService) GetBoardingAddress(ctx context.Context, userPubkey *secp256k1.PublicKey) (string, error) { + addr, _, err := s.builder.GetBoardingScript(userPubkey, s.pubkey) if err != nil { return "", err } - for _, v := range vtxos { - if v.Spent { - return "", fmt.Errorf("input %s:%d already spent", v.Txid, v.VOut) + return addr, nil +} + +func (s *covenantService) SpendVtxos(ctx context.Context, inputs []Input) (string, error) { + vtxosInputs := make([]domain.VtxoKey, 0) + boardingInputs := make([]Input, 0) + + for _, in := range inputs { + if in.IsVtxo() { + vtxosInputs = append(vtxosInputs, in.VtxoKey()) + continue } + boardingInputs = append(boardingInputs, in) + } + + vtxos := make([]domain.Vtxo, 0) + + if len(vtxosInputs) > 0 { + var err error + vtxos, err = s.repoManager.Vtxos().GetVtxos(ctx, vtxosInputs) + if err != nil { + return "", err + } + for _, v := range vtxos { + if v.Spent { + return "", fmt.Errorf("input %s:%d already spent", v.Txid, v.VOut) + } + + if v.Redeemed { + return "", fmt.Errorf("input %s:%d already redeemed", v.Txid, v.VOut) + } + + if v.Spent { + return "", fmt.Errorf("input %s:%d already spent", v.Txid, v.VOut) + } + } + } + + boardingTxs := make(map[string]string, 0) // txid -> txhex + now := time.Now().Unix() + + for _, in := range boardingInputs { + if _, ok := boardingTxs[in.Txid]; !ok { + txhex, err := s.wallet.GetTransaction(ctx, in.Txid) + if err != nil { + return "", fmt.Errorf("failed to get tx %s: %s", in.Txid, err) + } + + confirmed, blocktime, err := s.wallet.IsTransactionConfirmed(ctx, in.Txid) + if err != nil { + return "", fmt.Errorf("failed to check tx %s: %s", in.Txid, err) + } + + if !confirmed { + return "", fmt.Errorf("tx %s not confirmed", in.Txid) + } + + if blocktime+int64(s.boardingExitDelay) < now { + return "", fmt.Errorf("tx %s expired", in.Txid) + } + + boardingTxs[in.Txid] = txhex + } + } + + utxos := make([]ports.BoardingInput, 0, len(boardingInputs)) + + for _, in := range boardingInputs { + desc, err := in.GetDescriptor() + if err != nil { + log.WithError(err).Debugf("failed to parse boarding input descriptor") + return "", fmt.Errorf("failed to parse descriptor %s for input %s:%d", in.Descriptor, in.Txid, in.Index) + } + input, err := s.newBoardingInput(boardingTxs[in.Txid], in.Index, *desc) + if err != nil { + log.WithError(err).Debugf("failed to create boarding input") + return "", fmt.Errorf("input %s:%d is not a valid boarding input", in.Txid, in.Index) + } + + utxos = append(utxos, input) } payment, err := domain.NewPayment(vtxos) if err != nil { return "", err } - if err := s.paymentRequests.push(*payment); err != nil { + if err := s.paymentRequests.push(*payment, utxos); err != nil { return "", err } return payment.Id, nil } +func (s *covenantService) newBoardingInput( + txhex string, vout uint32, desc descriptor.TaprootDescriptor, +) (ports.BoardingInput, error) { + tx, err := transaction.NewTxFromHex(txhex) + if err != nil { + return nil, fmt.Errorf("failed to parse tx: %s", err) + } + + if len(tx.Outputs) <= int(vout) { + return nil, fmt.Errorf("output not found") + } + + out := tx.Outputs[vout] + script := out.Script + + if len(out.RangeProof) > 0 || len(out.SurjectionProof) > 0 { + return nil, fmt.Errorf("output is confidential") + } + + scriptFromDescriptor, err := tree.ComputeOutputScript(desc) + if err != nil { + return nil, fmt.Errorf("failed to compute output script: %s", err) + } + + if !bytes.Equal(script, scriptFromDescriptor) { + return nil, fmt.Errorf("descriptor does not match script in transaction output") + } + + pubkey, timeout, err := descriptor.ParseBoardingDescriptor(desc) + if err != nil { + return nil, fmt.Errorf("failed to parse boarding descriptor: %s", err) + } + + if timeout != uint(s.boardingExitDelay) { + return nil, fmt.Errorf("invalid boarding descriptor, timeout mismatch") + } + + _, expectedScript, err := s.builder.GetBoardingScript(pubkey, s.pubkey) + if err != nil { + return nil, fmt.Errorf("failed to compute boarding script: %s", err) + } + + if !bytes.Equal(script, expectedScript) { + return nil, fmt.Errorf("output script mismatch expected script") + } + + value, err := elementsutil.ValueFromBytes(out.Value) + if err != nil { + return nil, fmt.Errorf("failed to parse value: %s", err) + } + + return &boardingInput{ + txId: tx.TxHash(), + vout: vout, + boardingPubKey: pubkey, + amount: value, + }, nil +} + func (s *covenantService) ClaimVtxos(ctx context.Context, creds string, receivers []domain.Receiver) error { // Check credentials payment, ok := s.paymentRequests.view(creds) @@ -178,6 +315,19 @@ func (s *covenantService) SignVtxos(ctx context.Context, forfeitTxs []string) er return s.forfeitTxs.sign(forfeitTxs) } +func (s *covenantService) SignRoundTx(ctx context.Context, signedRoundTx string) error { + s.currentRoundLock.Lock() + defer s.currentRoundLock.Unlock() + + combined, err := s.builder.VerifyAndCombinePartialTx(s.currentRound.UnsignedTx, signedRoundTx) + if err != nil { + return err + } + + s.currentRound.UnsignedTx = combined + return nil +} + func (s *covenantService) ListVtxos(ctx context.Context, pubkey *secp256k1.PublicKey) ([]domain.Vtxo, []domain.Vtxo, error) { pk := hex.EncodeToString(pubkey.SerializeCompressed()) return s.repoManager.Vtxos().GetAllVtxos(ctx, pk) @@ -209,50 +359,17 @@ func (s *covenantService) GetInfo(ctx context.Context) (*ServiceInfo, error) { RoundInterval: s.roundInterval, Network: s.network.Name, MinRelayFee: int64(s.minRelayFee), + BoardingDescriptorTemplate: fmt.Sprintf( + descriptor.BoardingDescriptorTemplate, + hex.EncodeToString(tree.UnspendableKey().SerializeCompressed()), + hex.EncodeToString(schnorr.SerializePubKey(s.pubkey)), + "USER", + s.boardingExitDelay, + "USER", + ), }, nil } -func (s *covenantService) Onboard( - ctx context.Context, boardingTx string, - congestionTree tree.CongestionTree, userPubkey *secp256k1.PublicKey, -) error { - ptx, err := psetv2.NewPsetFromBase64(boardingTx) - if err != nil { - return fmt.Errorf("failed to parse boarding tx: %s", err) - } - - if err := tree.ValidateCongestionTree( - congestionTree, boardingTx, s.pubkey, s.roundLifetime, - ); err != nil { - return err - } - - extracted, err := psetv2.Extract(ptx) - if err != nil { - return fmt.Errorf("failed to extract boarding tx: %s", err) - } - - boardingTxHex, err := extracted.ToHex() - if err != nil { - return fmt.Errorf("failed to convert boarding tx to hex: %s", err) - } - - txid, err := s.wallet.BroadcastTransaction(ctx, boardingTxHex) - if err != nil { - return fmt.Errorf("failed to broadcast boarding tx: %s", err) - } - - log.Debugf("broadcasted boarding tx %s", txid) - - s.onboardingCh <- onboarding{ - tx: boardingTx, - congestionTree: congestionTree, - userPubkey: userPubkey, - } - - return nil -} - func (s *covenantService) RegisterCosignerPubkey(ctx context.Context, paymentId string, _ string) error { // if the user sends an ephemeral pubkey, something is going wrong client-side // we should delete the associated payment @@ -329,7 +446,7 @@ func (s *covenantService) startFinalization() { if num > paymentsThreshold { num = paymentsThreshold } - payments, _ := s.paymentRequests.pop(num) + payments, boardingInputs, _ := s.paymentRequests.pop(num) if _, err := round.RegisterPayments(payments); err != nil { round.Fail(fmt.Errorf("failed to register payments: %s", err)) log.WithError(err).Warn("failed to register payments") @@ -343,7 +460,7 @@ func (s *covenantService) startFinalization() { return } - unsignedPoolTx, tree, connectorAddress, err := s.builder.BuildPoolTx(s.pubkey, payments, s.minRelayFee, sweptRounds) + unsignedPoolTx, tree, connectorAddress, err := s.builder.BuildPoolTx(s.pubkey, payments, boardingInputs, s.minRelayFee, sweptRounds) if err != nil { round.Fail(fmt.Errorf("failed to create pool tx: %s", err)) log.WithError(err).Warn("failed to create pool tx") @@ -351,16 +468,26 @@ func (s *covenantService) startFinalization() { } log.Debugf("pool tx created for round %s", round.Id) - // TODO BTC make the senders sign the tree - - connectors, forfeitTxs, err := s.builder.BuildForfeitTxs(s.pubkey, unsignedPoolTx, payments, s.minRelayFee) - if err != nil { - round.Fail(fmt.Errorf("failed to create connectors and forfeit txs: %s", err)) - log.WithError(err).Warn("failed to create connectors and forfeit txs") - return + needForfeits := false + for _, pay := range payments { + if len(pay.Inputs) > 0 { + needForfeits = true + break + } } - log.Debugf("forfeit transactions created for round %s", round.Id) + var forfeitTxs, connectors []string + + if needForfeits { + connectors, forfeitTxs, err = s.builder.BuildForfeitTxs(s.pubkey, unsignedPoolTx, payments, s.minRelayFee) + if err != nil { + round.Fail(fmt.Errorf("failed to create connectors and forfeit txs: %s", err)) + log.WithError(err).Warn("failed to create connectors and forfeit txs") + return + } + + log.Debugf("forfeit transactions created for round %s", round.Id) + } if _, err := round.StartFinalization( connectorAddress, connectors, tree, unsignedPoolTx, @@ -401,71 +528,63 @@ func (s *covenantService) finalizeRound() { } log.Debugf("signing round transaction %s\n", round.Id) - signedPoolTx, err := s.wallet.SignTransaction(ctx, round.UnsignedTx, true) + + boardingInputs := make([]int, 0) + roundTx, err := psetv2.NewPsetFromBase64(round.UnsignedTx) + if err != nil { + log.Debugf("failed to parse round tx: %s", round.UnsignedTx) + changes = round.Fail(fmt.Errorf("failed to parse round tx: %s", err)) + log.WithError(err).Warn("failed to parse round tx") + return + } + + for i, in := range roundTx.Inputs { + if len(in.TapLeafScript) > 0 { + if len(in.TapScriptSig) == 0 { + err = fmt.Errorf("missing tapscript spend sig for input %d", i) + changes = round.Fail(err) + log.WithError(err).Warn("missing boarding sig") + return + } + + boardingInputs = append(boardingInputs, i) + } + } + + signedRoundTx := round.UnsignedTx + + if len(boardingInputs) > 0 { + signedRoundTx, err = s.wallet.SignTransactionTapscript(ctx, signedRoundTx, boardingInputs) + if err != nil { + changes = round.Fail(fmt.Errorf("failed to sign round tx: %s", err)) + log.WithError(err).Warn("failed to sign round tx") + return + } + } + + signedRoundTx, err = s.wallet.SignTransaction(ctx, signedRoundTx, true) if err != nil { changes = round.Fail(fmt.Errorf("failed to sign round tx: %s", err)) log.WithError(err).Warn("failed to sign round tx") return } - txid, err := s.wallet.BroadcastTransaction(ctx, signedPoolTx) + txid, err := s.wallet.BroadcastTransaction(ctx, signedRoundTx) if err != nil { + log.Debugf("failed to broadcast round tx: %s", signedRoundTx) changes = round.Fail(fmt.Errorf("failed to broadcast pool tx: %s", err)) log.WithError(err).Warn("failed to broadcast pool tx") return } - changes, _ = round.EndFinalization(forfeitTxs, txid) - - log.Debugf("finalized round %s with pool tx %s", round.Id, round.Txid) -} - -func (s *covenantService) listenToOnboarding() { - for onboarding := range s.onboardingCh { - go s.handleOnboarding(onboarding) - } -} - -func (s *covenantService) handleOnboarding(onboarding onboarding) { - ctx := context.Background() - - ptx, _ := psetv2.NewPsetFromBase64(onboarding.tx) - utx, _ := psetv2.Extract(ptx) - txid := utx.TxHash().String() - - // wait for the tx to be confirmed with a timeout - timeout := time.NewTimer(5 * time.Minute) - defer timeout.Stop() - - isConfirmed := false - - for !isConfirmed { - select { - case <-timeout.C: - log.WithError(fmt.Errorf("operation timed out")).Warnf("failed to get confirmation for boarding tx %s", txid) - return - default: - var err error - isConfirmed, _, err = s.wallet.IsTransactionConfirmed(ctx, txid) - if err != nil { - log.WithError(err).Warn("failed to check tx confirmation") - } - - if err != nil || !isConfirmed { - time.Sleep(5 * time.Second) - } - } - } - - pubkey := hex.EncodeToString(onboarding.userPubkey.SerializeCompressed()) - payments := getPaymentsFromOnboardingLiquid(onboarding.congestionTree, pubkey) - round := domain.NewFinalizedRound( - dustAmount, pubkey, txid, onboarding.tx, onboarding.congestionTree, payments, - ) - if err := s.saveEvents(ctx, round.Id, round.Events()); err != nil { - log.WithError(err).Warn("failed to store new round events") + changes, err = round.EndFinalization(forfeitTxs, txid) + if err != nil { + changes = round.Fail(fmt.Errorf("failed to finalize round: %s", err)) + log.WithError(err).Warn("failed to finalize round") return } + + log.Debugf("finalized round %s with pool tx %s", round.Id, round.Txid) } func (s *covenantService) listenToScannerNotifications() { @@ -867,23 +986,6 @@ func (s *covenantService) saveEvents( return s.repoManager.Rounds().AddOrUpdateRound(ctx, *round) } -func getPaymentsFromOnboardingLiquid( - congestionTree tree.CongestionTree, userKey string, -) []domain.Payment { - leaves := congestionTree.Leaves() - receivers := make([]domain.Receiver, 0, len(leaves)) - for _, node := range leaves { - ptx, _ := psetv2.NewPsetFromBase64(node.Tx) - receiver := domain.Receiver{ - Pubkey: userKey, - Amount: ptx.Outputs[0].Value, - } - receivers = append(receivers, receiver) - } - payment := domain.NewPaymentUnsafe(nil, receivers) - return []domain.Payment{*payment} -} - func findForfeitTxLiquid( forfeits []string, connectorTxid string, connectorVout uint32, vtxoTxid string, ) (string, error) { diff --git a/server/internal/core/application/covenantless.go b/server/internal/core/application/covenantless.go index d4a077f..15e8495 100644 --- a/server/internal/core/application/covenantless.go +++ b/server/internal/core/application/covenantless.go @@ -11,12 +11,15 @@ import ( "github.com/ark-network/ark/common" "github.com/ark-network/ark/common/bitcointree" + "github.com/ark-network/ark/common/descriptor" "github.com/ark-network/ark/common/tree" "github.com/ark-network/ark/server/internal/core/domain" "github.com/ark-network/ark/server/internal/core/ports" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/decred/dcrd/dcrec/secp256k1/v4" log "github.com/sirupsen/logrus" ) @@ -27,6 +30,7 @@ type covenantlessService struct { roundLifetime int64 roundInterval int64 unilateralExitDelay int64 + boardingExitDelay int64 minRelayFee uint64 wallet ports.WalletService @@ -38,11 +42,11 @@ type covenantlessService struct { paymentRequests *paymentsMap forfeitTxs *forfeitTxsMap - eventsCh chan domain.RoundEvent - onboardingCh chan onboarding + eventsCh chan domain.RoundEvent // cached data for the current round lastEvent domain.RoundEvent + currentRoundLock sync.Mutex currentRound *domain.Round treeSigningSessions map[string]*musigSigningSession asyncPaymentsCache map[domain.VtxoKey]struct { @@ -53,14 +57,13 @@ type covenantlessService struct { func NewCovenantlessService( network common.Network, - roundInterval, roundLifetime, unilateralExitDelay int64, minRelayFee uint64, + roundInterval, roundLifetime, unilateralExitDelay, boardingExitDelay int64, minRelayFee uint64, walletSvc ports.WalletService, repoManager ports.RepoManager, builder ports.TxBuilder, scanner ports.BlockchainScanner, scheduler ports.SchedulerService, ) (Service, error) { eventsCh := make(chan domain.RoundEvent) - onboardingCh := make(chan onboarding) - paymentRequests := newPaymentsMap(nil) + paymentRequests := newPaymentsMap() forfeitTxs := newForfeitTxsMap(builder) pubkey, err := walletSvc.GetPubkey(context.Background()) @@ -89,9 +92,10 @@ func NewCovenantlessService( paymentRequests: paymentRequests, forfeitTxs: forfeitTxs, eventsCh: eventsCh, - onboardingCh: onboardingCh, + currentRoundLock: sync.Mutex{}, asyncPaymentsCache: asyncPaymentsCache, treeSigningSessions: make(map[string]*musigSigningSession), + boardingExitDelay: boardingExitDelay, } repoManager.RegisterEventsHandler( @@ -109,7 +113,6 @@ func NewCovenantlessService( return nil, fmt.Errorf("failed to restore watching vtxos: %s", err) } go svc.listenToScannerNotifications() - go svc.listenToOnboarding() return svc, nil } @@ -137,7 +140,6 @@ func (s *covenantlessService) Stop() { s.repoManager.Close() log.Debug("closed connection to db") close(s.eventsCh) - close(s.onboardingCh) } func (s *covenantlessService) CompleteAsyncPayment( @@ -242,27 +244,147 @@ func (s *covenantlessService) CreateAsyncPayment( return res.RedeemTx, res.UnconditionalForfeitTxs, nil } -func (s *covenantlessService) SpendVtxos(ctx context.Context, inputs []domain.VtxoKey) (string, error) { - vtxos, err := s.repoManager.Vtxos().GetVtxos(ctx, inputs) - if err != nil { - return "", err - } - for _, v := range vtxos { - if v.Spent { - return "", fmt.Errorf("input %s:%d already spent", v.Txid, v.VOut) +func (s *covenantlessService) SpendVtxos(ctx context.Context, inputs []Input) (string, error) { + vtxosInputs := make([]domain.VtxoKey, 0) + boardingInputs := make([]Input, 0) + + for _, input := range inputs { + if input.IsVtxo() { + vtxosInputs = append(vtxosInputs, input.VtxoKey()) + continue } + + boardingInputs = append(boardingInputs, input) + } + + vtxos := make([]domain.Vtxo, 0) + if len(vtxosInputs) > 0 { + var err error + vtxos, err = s.repoManager.Vtxos().GetVtxos(ctx, vtxosInputs) + if err != nil { + return "", err + } + for _, v := range vtxos { + if v.Spent { + return "", fmt.Errorf("input %s:%d already spent", v.Txid, v.VOut) + } + + if v.Redeemed { + return "", fmt.Errorf("input %s:%d already redeemed", v.Txid, v.VOut) + } + + if v.Spent { + return "", fmt.Errorf("input %s:%d already spent", v.Txid, v.VOut) + } + } + } + + boardingTxs := make(map[string]string, 0) // txid -> txhex + now := time.Now().Unix() + + for _, in := range boardingInputs { + if _, ok := boardingTxs[in.Txid]; !ok { + // check if the tx exists and is confirmed + txhex, err := s.wallet.GetTransaction(ctx, in.Txid) + if err != nil { + return "", fmt.Errorf("failed to get tx %s: %s", in.Txid, err) + } + + confirmed, blocktime, err := s.wallet.IsTransactionConfirmed(ctx, in.Txid) + if err != nil { + return "", fmt.Errorf("failed to check tx %s: %s", in.Txid, err) + } + + if !confirmed { + return "", fmt.Errorf("tx %s not confirmed", in.Txid) + } + + // if the exit path is available, forbid registering the boarding utxo + if blocktime+int64(s.boardingExitDelay) < now { + return "", fmt.Errorf("tx %s expired", in.Txid) + } + + boardingTxs[in.Txid] = txhex + } + } + + utxos := make([]ports.BoardingInput, 0, len(boardingInputs)) + + for _, in := range boardingInputs { + desc, err := in.GetDescriptor() + if err != nil { + log.WithError(err).Debugf("failed to parse boarding input descriptor") + return "", fmt.Errorf("failed to parse descriptor %s for input %s:%d", in.Descriptor, in.Txid, in.Index) + } + + input, err := s.newBoardingInput(boardingTxs[in.Txid], in.Index, *desc) + if err != nil { + log.WithError(err).Debugf("failed to create boarding input") + return "", fmt.Errorf("input %s:%d is not a valid boarding input", in.Txid, in.Index) + } + + utxos = append(utxos, input) } payment, err := domain.NewPayment(vtxos) if err != nil { return "", err } - if err := s.paymentRequests.push(*payment); err != nil { + if err := s.paymentRequests.push(*payment, utxos); err != nil { return "", err } return payment.Id, nil } +func (s *covenantlessService) newBoardingInput(txhex string, vout uint32, desc descriptor.TaprootDescriptor) (ports.BoardingInput, error) { + var tx wire.MsgTx + + if err := tx.Deserialize(hex.NewDecoder(strings.NewReader(txhex))); err != nil { + return nil, fmt.Errorf("failed to deserialize tx: %s", err) + } + + if len(tx.TxOut) <= int(vout) { + return nil, fmt.Errorf("output not found") + } + + out := tx.TxOut[vout] + script := out.PkScript + + scriptFromDescriptor, err := bitcointree.ComputeOutputScript(desc) + if err != nil { + return nil, fmt.Errorf("failed to compute output script: %s", err) + } + + if !bytes.Equal(script, scriptFromDescriptor) { + return nil, fmt.Errorf("descriptor does not match script in transaction output") + } + + pubkey, timeout, err := descriptor.ParseBoardingDescriptor(desc) + if err != nil { + return nil, fmt.Errorf("failed to parse boarding descriptor: %s", err) + } + + if timeout != uint(s.boardingExitDelay) { + return nil, fmt.Errorf("invalid boarding descriptor, timeout mismatch") + } + + _, expectedScript, err := s.builder.GetBoardingScript(pubkey, s.pubkey) + if err != nil { + return nil, fmt.Errorf("failed to get boarding script: %s", err) + } + + if !bytes.Equal(script, expectedScript) { + return nil, fmt.Errorf("invalid boarding input output script") + } + + return &boardingInput{ + txId: tx.TxHash(), + vout: vout, + boardingPubKey: pubkey, + amount: uint64(out.Value), + }, nil +} + func (s *covenantlessService) ClaimVtxos(ctx context.Context, creds string, receivers []domain.Receiver) error { // Check credentials payment, ok := s.paymentRequests.view(creds) @@ -293,6 +415,19 @@ func (s *covenantlessService) SignVtxos(ctx context.Context, forfeitTxs []string return s.forfeitTxs.sign(forfeitTxs) } +func (s *covenantlessService) SignRoundTx(ctx context.Context, signedRoundTx string) error { + s.currentRoundLock.Lock() + defer s.currentRoundLock.Unlock() + + combined, err := s.builder.VerifyAndCombinePartialTx(s.currentRound.UnsignedTx, signedRoundTx) + if err != nil { + return fmt.Errorf("failed to verify and combine partial tx: %s", err) + } + + s.currentRound.UnsignedTx = combined + return nil +} + func (s *covenantlessService) ListVtxos(ctx context.Context, pubkey *secp256k1.PublicKey) ([]domain.Vtxo, []domain.Vtxo, error) { pk := hex.EncodeToString(pubkey.SerializeCompressed()) return s.repoManager.Vtxos().GetAllVtxos(ctx, pk) @@ -324,50 +459,26 @@ func (s *covenantlessService) GetInfo(ctx context.Context) (*ServiceInfo, error) RoundInterval: s.roundInterval, Network: s.network.Name, MinRelayFee: int64(s.minRelayFee), + BoardingDescriptorTemplate: fmt.Sprintf( + descriptor.BoardingDescriptorTemplate, + hex.EncodeToString(bitcointree.UnspendableKey().SerializeCompressed()), + hex.EncodeToString(schnorr.SerializePubKey(s.pubkey)), + "USER", + s.boardingExitDelay, + "USER", + ), }, nil } -// TODO clArk changes the onboard flow (2 rounds ?) -func (s *covenantlessService) Onboard( - ctx context.Context, boardingTx string, - congestionTree tree.CongestionTree, userPubkey *secp256k1.PublicKey, -) error { - ptx, err := psbt.NewFromRawBytes(strings.NewReader(boardingTx), true) +func (s *covenantlessService) GetBoardingAddress( + ctx context.Context, userPubkey *secp256k1.PublicKey, +) (string, error) { + addr, _, err := s.builder.GetBoardingScript(userPubkey, s.pubkey) if err != nil { - return fmt.Errorf("failed to parse boarding tx: %s", err) + return "", fmt.Errorf("failed to compute boarding script: %s", err) } - if err := bitcointree.ValidateCongestionTree( - congestionTree, boardingTx, s.pubkey, s.roundLifetime, int64(s.minRelayFee), - ); err != nil { - return err - } - - extracted, err := psbt.Extract(ptx) - if err != nil { - return fmt.Errorf("failed to extract boarding tx: %s", err) - } - - var serialized bytes.Buffer - - if err := extracted.Serialize(&serialized); err != nil { - return fmt.Errorf("failed to serialize boarding tx: %s", err) - } - - txid, err := s.wallet.BroadcastTransaction(ctx, hex.EncodeToString(serialized.Bytes())) - if err != nil { - return fmt.Errorf("failed to broadcast boarding tx: %s", err) - } - - log.Debugf("broadcasted boarding tx %s", txid) - - s.onboardingCh <- onboarding{ - tx: boardingTx, - congestionTree: congestionTree, - userPubkey: userPubkey, - } - - return nil + return addr, nil } func (s *covenantlessService) RegisterCosignerPubkey(ctx context.Context, paymentId string, pubkey string) error { @@ -504,7 +615,7 @@ func (s *covenantlessService) startFinalization() { if num > paymentsThreshold { num = paymentsThreshold } - payments, cosigners := s.paymentRequests.pop(num) + payments, boardingInputs, cosigners := s.paymentRequests.pop(num) if len(payments) > len(cosigners) { err := fmt.Errorf("missing ephemeral key for payments") round.Fail(fmt.Errorf("round aborted: %s", err)) @@ -534,7 +645,7 @@ func (s *covenantlessService) startFinalization() { cosigners = append(cosigners, ephemeralKey.PubKey()) - unsignedPoolTx, tree, connectorAddress, err := s.builder.BuildPoolTx(s.pubkey, payments, s.minRelayFee, sweptRounds, cosigners...) + unsignedPoolTx, tree, connectorAddress, err := s.builder.BuildPoolTx(s.pubkey, payments, boardingInputs, s.minRelayFee, sweptRounds, cosigners...) if err != nil { round.Fail(fmt.Errorf("failed to create pool tx: %s", err)) log.WithError(err).Warn("failed to create pool tx") @@ -683,14 +794,25 @@ func (s *covenantlessService) startFinalization() { tree = signedTree } - connectors, forfeitTxs, err := s.builder.BuildForfeitTxs(s.pubkey, unsignedPoolTx, payments, s.minRelayFee) - if err != nil { - round.Fail(fmt.Errorf("failed to create connectors and forfeit txs: %s", err)) - log.WithError(err).Warn("failed to create connectors and forfeit txs") - return + needForfeits := false + for _, pay := range payments { + if len(pay.Inputs) > 0 { + needForfeits = true + break + } } - log.Debugf("forfeit transactions created for round %s", round.Id) + var forfeitTxs, connectors []string + + if needForfeits { + connectors, forfeitTxs, err = s.builder.BuildForfeitTxs(s.pubkey, unsignedPoolTx, payments, s.minRelayFee) + if err != nil { + round.Fail(fmt.Errorf("failed to create connectors and forfeit txs: %s", err)) + log.WithError(err).Warn("failed to create connectors and forfeit txs") + return + } + log.Debugf("forfeit transactions created for round %s", round.Id) + } if _, err := round.StartFinalization( connectorAddress, connectors, tree, unsignedPoolTx, @@ -763,73 +885,61 @@ func (s *covenantlessService) finalizeRound() { } log.Debugf("signing round transaction %s\n", round.Id) - signedPoolTx, err := s.wallet.SignTransaction(ctx, round.UnsignedTx, true) + + boardingInputs := make([]int, 0) + roundTx, err := psbt.NewFromRawBytes(strings.NewReader(round.UnsignedTx), true) + if err != nil { + changes = round.Fail(fmt.Errorf("failed to parse round tx: %s", err)) + log.WithError(err).Warn("failed to parse round tx") + return + } + + for i, in := range roundTx.Inputs { + if len(in.TaprootLeafScript) > 0 { + if len(in.TaprootScriptSpendSig) == 0 { + err = fmt.Errorf("missing tapscript spend sig for input %d", i) + changes = round.Fail(err) + log.WithError(err).Warn("missing boarding sig") + return + } + + boardingInputs = append(boardingInputs, i) + } + } + + signedRoundTx := round.UnsignedTx + + if len(boardingInputs) > 0 { + signedRoundTx, err = s.wallet.SignTransactionTapscript(ctx, signedRoundTx, boardingInputs) + if err != nil { + changes = round.Fail(fmt.Errorf("failed to sign round tx: %s", err)) + log.WithError(err).Warn("failed to sign round tx") + return + } + } + + signedRoundTx, err = s.wallet.SignTransaction(ctx, signedRoundTx, true) if err != nil { changes = round.Fail(fmt.Errorf("failed to sign round tx: %s", err)) log.WithError(err).Warn("failed to sign round tx") return } - txid, err := s.wallet.BroadcastTransaction(ctx, signedPoolTx) + txid, err := s.wallet.BroadcastTransaction(ctx, signedRoundTx) if err != nil { changes = round.Fail(fmt.Errorf("failed to broadcast pool tx: %s", err)) log.WithError(err).Warn("failed to broadcast pool tx") return } - changes, _ = round.EndFinalization(forfeitTxs, txid) - - log.Debugf("finalized round %s with pool tx %s", round.Id, round.Txid) -} - -func (s *covenantlessService) listenToOnboarding() { - for onboarding := range s.onboardingCh { - go s.handleOnboarding(onboarding) - } -} - -func (s *covenantlessService) handleOnboarding(onboarding onboarding) { - ctx := context.Background() - - ptx, _ := psbt.NewFromRawBytes(strings.NewReader(onboarding.tx), true) - txid := ptx.UnsignedTx.TxHash().String() - - // wait for the tx to be confirmed with a timeout - timeout := time.NewTimer(15 * time.Minute) - defer timeout.Stop() - - isConfirmed := false - - for !isConfirmed { - select { - case <-timeout.C: - log.WithError(fmt.Errorf("operation timed out")).Warnf("failed to get confirmation for boarding tx %s", txid) - return - default: - var err error - isConfirmed, _, err = s.wallet.IsTransactionConfirmed(ctx, txid) - if err != nil { - log.WithError(err).Warn("failed to check tx confirmation") - } - - if err != nil || !isConfirmed { - log.Debugf("waiting for boarding tx %s to be confirmed", txid) - time.Sleep(5 * time.Second) - } - } - } - - log.Debugf("boarding tx %s confirmed", txid) - - pubkey := hex.EncodeToString(onboarding.userPubkey.SerializeCompressed()) - payments := getPaymentsFromOnboardingBitcoin(onboarding.congestionTree, pubkey) - round := domain.NewFinalizedRound( - dustAmount, pubkey, txid, onboarding.tx, onboarding.congestionTree, payments, - ) - if err := s.saveEvents(ctx, round.Id, round.Events()); err != nil { - log.WithError(err).Warn("failed to store new round events") + changes, err = round.EndFinalization(forfeitTxs, txid) + if err != nil { + changes = round.Fail(fmt.Errorf("failed to finalize round: %s", err)) + log.WithError(err).Warn("failed to finalize round") return } + + log.Debugf("finalized round %s with pool tx %s", round.Id, round.Txid) } func (s *covenantlessService) listenToScannerNotifications() { @@ -1232,24 +1342,6 @@ func (s *covenantlessService) saveEvents( return s.repoManager.Rounds().AddOrUpdateRound(ctx, *round) } -func getPaymentsFromOnboardingBitcoin( - congestionTree tree.CongestionTree, userKey string, -) []domain.Payment { - leaves := congestionTree.Leaves() - receivers := make([]domain.Receiver, 0, len(leaves)) - for _, node := range leaves { - ptx, _ := psbt.NewFromRawBytes(strings.NewReader(node.Tx), true) - - receiver := domain.Receiver{ - Pubkey: userKey, - Amount: uint64(ptx.UnsignedTx.TxOut[0].Value), - } - receivers = append(receivers, receiver) - } - payment := domain.NewPaymentUnsafe(nil, receivers) - return []domain.Payment{*payment} -} - func findForfeitTxBitcoin( forfeits []string, connectorTxid string, connectorVout uint32, vtxoTxid string, ) (string, error) { diff --git a/server/internal/core/application/types.go b/server/internal/core/application/types.go index 45bb569..6006478 100644 --- a/server/internal/core/application/types.go +++ b/server/internal/core/application/types.go @@ -2,8 +2,9 @@ package application import ( "context" + "fmt" - "github.com/ark-network/ark/common/tree" + "github.com/ark-network/ark/common/descriptor" "github.com/ark-network/ark/server/internal/core/domain" "github.com/decred/dcrd/dcrec/secp256k1/v4" ) @@ -16,9 +17,10 @@ var ( type Service interface { Start() error Stop() - SpendVtxos(ctx context.Context, inputs []domain.VtxoKey) (string, error) + SpendVtxos(ctx context.Context, inputs []Input) (string, error) ClaimVtxos(ctx context.Context, creds string, receivers []domain.Receiver) error SignVtxos(ctx context.Context, forfeitTxs []string) error + SignRoundTx(ctx context.Context, roundTx string) error GetRoundByTxid(ctx context.Context, poolTxid string) (*domain.Round, error) GetRoundById(ctx context.Context, id string) (*domain.Round, error) GetCurrentRound(ctx context.Context) (*domain.Round, error) @@ -30,10 +32,6 @@ type Service interface { ctx context.Context, pubkey *secp256k1.PublicKey, ) (spendableVtxos, spentVtxos []domain.Vtxo, err error) GetInfo(ctx context.Context) (*ServiceInfo, error) - Onboard( - ctx context.Context, boardingTx string, - congestionTree tree.CongestionTree, userPubkey *secp256k1.PublicKey, - ) error // Async payments CreateAsyncPayment( ctx context.Context, inputs []domain.VtxoKey, receivers []domain.Receiver, @@ -41,6 +39,7 @@ type Service interface { CompleteAsyncPayment( ctx context.Context, redeemTx string, unconditionalForfeitTxs []string, ) error + GetBoardingAddress(ctx context.Context, userPubkey *secp256k1.PublicKey) (string, error) // Tree signing methods RegisterCosignerPubkey(ctx context.Context, paymentId string, ephemeralPublicKey string) error RegisterCosignerNonces( @@ -54,12 +53,13 @@ type Service interface { } type ServiceInfo struct { - PubKey string - RoundLifetime int64 - UnilateralExitDelay int64 - RoundInterval int64 - Network string - MinRelayFee int64 + PubKey string + RoundLifetime int64 + UnilateralExitDelay int64 + RoundInterval int64 + Network string + MinRelayFee int64 + BoardingDescriptorTemplate string } type WalletStatus struct { @@ -68,10 +68,28 @@ type WalletStatus struct { IsSynced bool } -type onboarding struct { - tx string - congestionTree tree.CongestionTree - userPubkey *secp256k1.PublicKey +type Input struct { + Txid string + Index uint32 + Descriptor string +} + +func (i Input) IsVtxo() bool { + return len(i.Descriptor) <= 0 +} + +func (i Input) VtxoKey() domain.VtxoKey { + return domain.VtxoKey{ + Txid: i.Txid, + VOut: i.Index, + } +} + +func (i Input) GetDescriptor() (*descriptor.TaprootDescriptor, error) { + if i.IsVtxo() { + return nil, fmt.Errorf("input is not a boarding input") + } + return descriptor.ParseTaprootDescriptor(i.Descriptor) } type txOutpoint struct { diff --git a/server/internal/core/application/utils.go b/server/internal/core/application/utils.go index 6cf1922..b003932 100644 --- a/server/internal/core/application/utils.go +++ b/server/internal/core/application/utils.go @@ -10,14 +10,16 @@ import ( "github.com/ark-network/ark/common/tree" "github.com/ark-network/ark/server/internal/core/domain" "github.com/ark-network/ark/server/internal/core/ports" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/sirupsen/logrus" ) type timedPayment struct { domain.Payment - timestamp time.Time - pingTimestamp time.Time + boardingInputs []ports.BoardingInput + timestamp time.Time + pingTimestamp time.Time } type paymentsMap struct { @@ -26,11 +28,8 @@ type paymentsMap struct { ephemeralKeys map[string]*secp256k1.PublicKey } -func newPaymentsMap(payments []domain.Payment) *paymentsMap { +func newPaymentsMap() *paymentsMap { paymentsById := make(map[string]*timedPayment) - for _, p := range payments { - paymentsById[p.Id] = &timedPayment{p, time.Now(), time.Time{}} - } lock := &sync.RWMutex{} return &paymentsMap{lock, paymentsById, make(map[string]*secp256k1.PublicKey)} } @@ -60,7 +59,7 @@ func (m *paymentsMap) delete(id string) error { return nil } -func (m *paymentsMap) push(payment domain.Payment) error { +func (m *paymentsMap) push(payment domain.Payment, boardingInputs []ports.BoardingInput) error { m.lock.Lock() defer m.lock.Unlock() @@ -68,7 +67,7 @@ func (m *paymentsMap) push(payment domain.Payment) error { return fmt.Errorf("duplicated inputs") } - m.payments[payment.Id] = &timedPayment{payment, time.Now(), time.Time{}} + m.payments[payment.Id] = &timedPayment{payment, boardingInputs, time.Now(), time.Time{}} return nil } @@ -84,7 +83,7 @@ func (m *paymentsMap) pushEphemeralKey(paymentId string, pubkey *secp256k1.Publi return nil } -func (m *paymentsMap) pop(num int64) ([]domain.Payment, []*secp256k1.PublicKey) { +func (m *paymentsMap) pop(num int64) ([]domain.Payment, []ports.BoardingInput, []*secp256k1.PublicKey) { m.lock.Lock() defer m.lock.Unlock() @@ -109,8 +108,10 @@ func (m *paymentsMap) pop(num int64) ([]domain.Payment, []*secp256k1.PublicKey) } payments := make([]domain.Payment, 0, num) + boardingInputs := make([]ports.BoardingInput, 0) cosigners := make([]*secp256k1.PublicKey, 0, num) for _, p := range paymentsByTime[:num] { + boardingInputs = append(boardingInputs, p.boardingInputs...) payments = append(payments, p.Payment) if pubkey, ok := m.ephemeralKeys[p.Payment.Id]; ok { cosigners = append(cosigners, pubkey) @@ -118,7 +119,7 @@ func (m *paymentsMap) pop(num int64) ([]domain.Payment, []*secp256k1.PublicKey) } delete(m.payments, p.Id) } - return payments, cosigners + return payments, boardingInputs, cosigners } func (m *paymentsMap) update(payment domain.Payment) error { @@ -312,3 +313,26 @@ func getSpentVtxos(payments map[string]domain.Payment) []domain.VtxoKey { } return vtxos } + +type boardingInput struct { + txId chainhash.Hash + vout uint32 + boardingPubKey *secp256k1.PublicKey + amount uint64 +} + +func (b boardingInput) GetHash() chainhash.Hash { + return b.txId +} + +func (b boardingInput) GetIndex() uint32 { + return b.vout +} + +func (b boardingInput) GetAmount() uint64 { + return b.amount +} + +func (b boardingInput) GetBoardingPubkey() *secp256k1.PublicKey { + return b.boardingPubKey +} diff --git a/server/internal/core/domain/payment.go b/server/internal/core/domain/payment.go index 230da99..f283e22 100644 --- a/server/internal/core/domain/payment.go +++ b/server/internal/core/domain/payment.go @@ -28,14 +28,6 @@ func NewPayment(inputs []Vtxo) (*Payment, error) { return p, nil } -func NewPaymentUnsafe(inputs []Vtxo, receivers []Receiver) *Payment { - return &Payment{ - Id: uuid.New().String(), - Inputs: inputs, - Receivers: receivers, - } -} - func (p *Payment) AddReceivers(receivers []Receiver) (err error) { if p.Receivers == nil { p.Receivers = make([]Receiver, 0) @@ -70,18 +62,13 @@ func (p Payment) validate(ignoreOuts bool) error { if len(p.Id) <= 0 { return fmt.Errorf("missing id") } - if len(p.Inputs) <= 0 { - return fmt.Errorf("missing inputs") - } if ignoreOuts { return nil } + if len(p.Receivers) <= 0 { return fmt.Errorf("missing outputs") } - // Check that input and output and output amounts match. - inAmount := p.TotalInputAmount() - outAmount := uint64(0) for _, r := range p.Receivers { if len(r.OnchainAddress) <= 0 && len(r.Pubkey) <= 0 { return fmt.Errorf("missing receiver destination") @@ -89,10 +76,6 @@ func (p Payment) validate(ignoreOuts bool) error { if r.Amount < dustAmount { return fmt.Errorf("receiver amount must be greater than dust") } - outAmount += r.Amount - } - if inAmount != outAmount { - return fmt.Errorf("input and output amounts mismatch") } return nil } diff --git a/server/internal/core/domain/payment_test.go b/server/internal/core/domain/payment_test.go index 9c025e2..de6e0c1 100644 --- a/server/internal/core/domain/payment_test.go +++ b/server/internal/core/domain/payment_test.go @@ -22,7 +22,7 @@ var inputs = []domain.Vtxo{ func TestPayment(t *testing.T) { t.Run("new_payment", func(t *testing.T) { - t.Run("vaild", func(t *testing.T) { + t.Run("valid", func(t *testing.T) { payment, err := domain.NewPayment(inputs) require.NoError(t, err) require.NotNil(t, payment) @@ -30,24 +30,6 @@ func TestPayment(t *testing.T) { require.Exactly(t, inputs, payment.Inputs) require.Empty(t, payment.Receivers) }) - - t.Run("invaild", func(t *testing.T) { - fixtures := []struct { - inputs []domain.Vtxo - expectedErr string - }{ - { - inputs: nil, - expectedErr: "missing inputs", - }, - } - - for _, f := range fixtures { - payment, err := domain.NewPayment(f.inputs) - require.EqualError(t, err, f.expectedErr) - require.Nil(t, payment) - } - }) }) t.Run("add_receivers", func(t *testing.T) { @@ -87,15 +69,6 @@ func TestPayment(t *testing.T) { }, expectedErr: "receiver amount must be greater than dust", }, - { - receivers: []domain.Receiver{ - { - Pubkey: "030000000000000000000000000000000000000000000000000000000000000001", - Amount: 600, - }, - }, - expectedErr: "input and output amounts mismatch", - }, } payment, err := domain.NewPayment(inputs) diff --git a/server/internal/core/domain/round.go b/server/internal/core/domain/round.go index 9a96f76..dbe59ac 100644 --- a/server/internal/core/domain/round.go +++ b/server/internal/core/domain/round.go @@ -60,39 +60,6 @@ func NewRound(dustAmount uint64) *Round { } } -func NewFinalizedRound( - dustAmount uint64, userKey, poolTxid, poolTx string, - congestionTree tree.CongestionTree, payments []Payment, -) *Round { - r := NewRound(dustAmount) - events := []RoundEvent{ - RoundStarted{ - Id: r.Id, - Timestamp: time.Now().Unix(), - }, - PaymentsRegistered{ - Id: r.Id, - Payments: payments, - }, - RoundFinalizationStarted{ - Id: r.Id, - CongestionTree: congestionTree, - PoolTx: poolTx, - }, - RoundFinalized{ - Id: r.Id, - Txid: poolTxid, - Timestamp: time.Now().Unix(), - }, - } - - for _, event := range events { - r.raise(event) - } - - return r -} - func NewRoundFromEvents(events []RoundEvent) *Round { r := &Round{} @@ -205,7 +172,11 @@ func (r *Round) StartFinalization(connectorAddress string, connectors []string, func (r *Round) EndFinalization(forfeitTxs []string, txid string) ([]RoundEvent, error) { if len(forfeitTxs) <= 0 { - return nil, fmt.Errorf("missing list of signed forfeit txs") + for _, p := range r.Payments { + if len(p.Inputs) > 0 { + return nil, fmt.Errorf("missing list of signed forfeit txs") + } + } } if len(txid) <= 0 { return nil, fmt.Errorf("missing pool txid") @@ -216,6 +187,10 @@ func (r *Round) EndFinalization(forfeitTxs []string, txid string) ([]RoundEvent, if r.Stage.Ended { return nil, fmt.Errorf("round already finalized") } + if forfeitTxs == nil { + forfeitTxs = make([]string, 0) + } + event := RoundFinalized{ Id: r.Id, Txid: txid, diff --git a/server/internal/core/domain/round_test.go b/server/internal/core/domain/round_test.go index df0f19c..6108730 100644 --- a/server/internal/core/domain/round_test.go +++ b/server/internal/core/domain/round_test.go @@ -449,6 +449,7 @@ func testEndFinalization(t *testing.T) { Stage: domain.Stage{ Code: domain.FinalizationStage, }, + Payments: paymentsById, }, forfeitTxs: nil, txid: txid, diff --git a/server/internal/core/ports/tx_builder.go b/server/internal/core/ports/tx_builder.go index 20f9f67..ea4c6a4 100644 --- a/server/internal/core/ports/tx_builder.go +++ b/server/internal/core/ports/tx_builder.go @@ -16,9 +16,16 @@ type SweepInput interface { GetInternalKey() *secp256k1.PublicKey } +type BoardingInput interface { + GetAmount() uint64 + GetIndex() uint32 + GetHash() chainhash.Hash + GetBoardingPubkey() *secp256k1.PublicKey +} + type TxBuilder interface { BuildPoolTx( - aspPubkey *secp256k1.PublicKey, payments []domain.Payment, minRelayFee uint64, sweptRounds []domain.Round, + aspPubkey *secp256k1.PublicKey, payments []domain.Payment, boardingInputs []BoardingInput, minRelayFee uint64, sweptRounds []domain.Round, cosigners ...*secp256k1.PublicKey, ) (poolTx string, congestionTree tree.CongestionTree, connectorAddress string, err error) BuildForfeitTxs(aspPubkey *secp256k1.PublicKey, poolTx string, payments []domain.Payment, minRelayFee uint64) (connectors []string, forfeitTxs []string, err error) @@ -33,4 +40,6 @@ type TxBuilder interface { vtxosToSpend []domain.Vtxo, aspPubKey *secp256k1.PublicKey, receivers []domain.Receiver, minRelayFee uint64, ) (*domain.AsyncPaymentTxs, error) + GetBoardingScript(userPubkey, aspPubkey *secp256k1.PublicKey) (addr string, script []byte, err error) + VerifyAndCombinePartialTx(dest string, src string) (string, error) } diff --git a/server/internal/core/ports/wallet.go b/server/internal/core/ports/wallet.go index 48ef036..4b6a0cb 100644 --- a/server/internal/core/ports/wallet.go +++ b/server/internal/core/ports/wallet.go @@ -33,6 +33,7 @@ type WalletService interface { MainAccountBalance(ctx context.Context) (uint64, uint64, error) ConnectorsAccountBalance(ctx context.Context) (uint64, uint64, error) LockConnectorUtxos(ctx context.Context, utxos []TxOutpoint) error + GetTransaction(ctx context.Context, txid string) (string, error) Close() } diff --git a/server/internal/infrastructure/db/badger/utils.go b/server/internal/infrastructure/db/badger/utils.go index 439323c..af0c05b 100644 --- a/server/internal/infrastructure/db/badger/utils.go +++ b/server/internal/infrastructure/db/badger/utils.go @@ -95,7 +95,7 @@ func deserializeEvent(buf []byte) (domain.RoundEvent, error) { } { var event = domain.RoundFinalizationStarted{} - if err := json.Unmarshal(buf, &event); err == nil && len(event.Connectors) > 0 { + if err := json.Unmarshal(buf, &event); err == nil && len(event.PoolTx) > 0 { return event, nil } } diff --git a/server/internal/infrastructure/tx-builder/covenant/builder.go b/server/internal/infrastructure/tx-builder/covenant/builder.go index c5c9dbd..9b8eb44 100644 --- a/server/internal/infrastructure/tx-builder/covenant/builder.go +++ b/server/internal/infrastructure/tx-builder/covenant/builder.go @@ -11,12 +11,15 @@ import ( "github.com/ark-network/ark/server/internal/core/ports" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/vulpemventures/go-elements/address" + "github.com/vulpemventures/go-elements/elementsutil" "github.com/vulpemventures/go-elements/network" "github.com/vulpemventures/go-elements/payment" "github.com/vulpemventures/go-elements/psetv2" "github.com/vulpemventures/go-elements/taproot" + "github.com/vulpemventures/go-elements/transaction" ) const ( @@ -25,10 +28,11 @@ const ( ) type txBuilder struct { - wallet ports.WalletService - net common.Network - roundLifetime int64 // in seconds - exitDelay int64 // in seconds + wallet ports.WalletService + net common.Network + roundLifetime int64 // in seconds + exitDelay int64 // in seconds + boardingExitDelay int64 // in seconds } func NewTxBuilder( @@ -36,8 +40,18 @@ func NewTxBuilder( net common.Network, roundLifetime int64, exitDelay int64, + boardingExitDelay int64, ) ports.TxBuilder { - return &txBuilder{wallet, net, roundLifetime, exitDelay} + return &txBuilder{wallet, net, roundLifetime, exitDelay, boardingExitDelay} +} + +func (b *txBuilder) GetBoardingScript(owner, asp *secp256k1.PublicKey) (string, []byte, error) { + addr, script, _, err := b.getBoardingTaproot(owner, asp) + if err != nil { + return "", nil, err + } + + return addr, script, nil } func (b *txBuilder) GetVtxoScript(userPubkey, aspPubkey *secp256k1.PublicKey) ([]byte, error) { @@ -112,7 +126,11 @@ func (b *txBuilder) BuildForfeitTxs( } func (b *txBuilder) BuildPoolTx( - aspPubkey *secp256k1.PublicKey, payments []domain.Payment, minRelayFee uint64, sweptRounds []domain.Round, + aspPubkey *secp256k1.PublicKey, + payments []domain.Payment, + boardingInputs []ports.BoardingInput, + minRelayFee uint64, + sweptRounds []domain.Round, _ ...*secp256k1.PublicKey, // cosigners are not used in the covenant ) (poolTx string, congestionTree tree.CongestionTree, connectorAddress string, err error) { // The creation of the tree and the pool tx are tightly coupled: @@ -146,7 +164,7 @@ func (b *txBuilder) BuildPoolTx( } ptx, err := b.createPoolTx( - sharedOutputAmount, sharedOutputScript, payments, aspPubkey, connectorAddress, minRelayFee, sweptRounds, + sharedOutputAmount, sharedOutputScript, payments, boardingInputs, aspPubkey, connectorAddress, minRelayFee, sweptRounds, ) if err != nil { return @@ -197,9 +215,19 @@ func (b *txBuilder) GetSweepInput(parentblocktime int64, node tree.Node) (expira expirationTime := parentblocktime + lifetime - amount := uint64(0) - for _, out := range pset.Outputs { - amount += out.Value + txhex, err := b.wallet.GetTransaction(context.Background(), txid) + if err != nil { + return -1, nil, err + } + + tx, err := transaction.NewTxFromHex(txhex) + if err != nil { + return -1, nil, err + } + + inputValue, err := elementsutil.ValueFromBytes(tx.Outputs[index].Value) + if err != nil { + return -1, nil, err } sweepInput = &sweepLiquidInput{ @@ -208,7 +236,7 @@ func (b *txBuilder) GetSweepInput(parentblocktime int64, node tree.Node) (expira TxIndex: index, }, sweepLeaf: sweepLeaf, - amount: amount, + amount: inputValue, } return expirationTime, sweepInput, nil @@ -362,8 +390,11 @@ func (b *txBuilder) getLeafScriptAndTree( } func (b *txBuilder) createPoolTx( - sharedOutputAmount uint64, sharedOutputScript []byte, - payments []domain.Payment, aspPubKey *secp256k1.PublicKey, connectorAddress string, minRelayFee uint64, + sharedOutputAmount uint64, + sharedOutputScript []byte, + payments []domain.Payment, + boardingInputs []ports.BoardingInput, + aspPubKey *secp256k1.PublicKey, connectorAddress string, minRelayFee uint64, sweptRounds []domain.Round, ) (*psetv2.Pset, error) { aspScript, err := p2wpkhScript(aspPubKey, b.onchainNetwork()) @@ -396,11 +427,13 @@ func (b *txBuilder) createPoolTx( }) } - outputs = append(outputs, psetv2.OutputArgs{ - Asset: b.onchainNetwork().AssetID, - Amount: connectorsAmount, - Script: connectorScript, - }) + if connectorsAmount > 0 { + outputs = append(outputs, psetv2.OutputArgs{ + Asset: b.onchainNetwork().AssetID, + Amount: connectorsAmount, + Script: connectorScript, + }) + } for _, receiver := range receivers { targetAmount += receiver.Amount @@ -417,6 +450,9 @@ func (b *txBuilder) createPoolTx( }) } + for _, in := range boardingInputs { + targetAmount -= in.GetAmount() + } ctx := context.Background() utxos, change, err := b.selectUtxos(ctx, sweptRounds, targetAmount) if err != nil { @@ -447,6 +483,48 @@ func (b *txBuilder) createPoolTx( return nil, err } + for _, in := range boardingInputs { + if err := updater.AddInputs( + []psetv2.InputArgs{ + { + Txid: in.GetHash().String(), + TxIndex: in.GetIndex(), + }, + }, + ); err != nil { + return nil, err + } + + index := len(ptx.Inputs) - 1 + + assetBytes, err := elementsutil.AssetHashToBytes(b.onchainNetwork().AssetID) + if err != nil { + return nil, fmt.Errorf("failed to convert asset to bytes: %s", err) + } + + valueBytes, err := elementsutil.ValueToBytes(in.GetAmount()) + if err != nil { + return nil, fmt.Errorf("failed to convert value to bytes: %s", err) + } + + _, script, tapLeafProof, err := b.getBoardingTaproot(in.GetBoardingPubkey(), aspPubKey) + if err != nil { + return nil, err + } + + if err := updater.AddInWitnessUtxo(index, transaction.NewTxOutput(assetBytes, valueBytes, script)); err != nil { + return nil, err + } + + if err := updater.AddInTapLeafScript(index, psetv2.NewTapLeafScript(*tapLeafProof, tree.UnspendableKey())); err != nil { + return nil, err + } + + if err := updater.AddInSighashType(index, txscript.SigHashDefault); err != nil { + return nil, err + } + } + if err := addInputs(updater, utxos); err != nil { return nil, err } @@ -471,14 +549,23 @@ func (b *txBuilder) createPoolTx( if feeAmount == change { // fees = change, remove change output ptx.Outputs = ptx.Outputs[:len(ptx.Outputs)-1] + ptx.Global.OutputCount-- + feeAmount += change } else if feeAmount < change { // change covers the fees, reduce change amount - ptx.Outputs[len(ptx.Outputs)-1].Value = change - feeAmount + if change-feeAmount < dustLimit { + ptx.Outputs = ptx.Outputs[:len(ptx.Outputs)-1] + ptx.Global.OutputCount-- + feeAmount += change + } else { + ptx.Outputs[len(ptx.Outputs)-1].Value = change - feeAmount + } } else { // change is not enough to cover fees, re-select utxos if change > 0 { // remove change output if present ptx.Outputs = ptx.Outputs[:len(ptx.Outputs)-1] + ptx.Global.OutputCount-- } newUtxos, change, err := b.selectUtxos(ctx, sweptRounds, feeAmount-change) if err != nil { @@ -486,14 +573,18 @@ func (b *txBuilder) createPoolTx( } if change > 0 { - if err := updater.AddOutputs([]psetv2.OutputArgs{ - { - Asset: b.onchainNetwork().AssetID, - Amount: change, - Script: aspScript, - }, - }); err != nil { - return nil, err + if change < dustLimit { + feeAmount += change + } else { + if err := updater.AddOutputs([]psetv2.OutputArgs{ + { + Asset: b.onchainNetwork().AssetID, + Amount: change, + Script: aspScript, + }, + }); err != nil { + return nil, err + } } } @@ -541,6 +632,77 @@ func (b *txBuilder) createPoolTx( return ptx, nil } +// This method aims to verify and add partial signature from boarding input +func (b *txBuilder) VerifyAndCombinePartialTx(dest string, src string) (string, error) { + roundPset, err := psetv2.NewPsetFromBase64(dest) + if err != nil { + return "", err + } + + sourcePset, err := psetv2.NewPsetFromBase64(src) + if err != nil { + return "", err + } + + roundUtx, err := roundPset.UnsignedTx() + if err != nil { + return "", err + } + + sourceUtx, err := sourcePset.UnsignedTx() + if err != nil { + return "", err + } + + if roundUtx.TxHash().String() != sourceUtx.TxHash().String() { + return "", fmt.Errorf("txid mismatch") + } + + roundSigner, err := psetv2.NewSigner(roundPset) + if err != nil { + return "", err + } + + for i, input := range sourcePset.Inputs { + if len(input.TapScriptSig) == 0 || len(input.TapLeafScript) == 0 { + continue + } + + partialSig := input.TapScriptSig[0] + + leafHash, err := chainhash.NewHash(partialSig.LeafHash) + if err != nil { + return "", err + } + + preimage, err := b.getTaprootPreimage(src, i, leafHash) + if err != nil { + return "", err + } + + sig, err := schnorr.ParseSignature(partialSig.Signature) + if err != nil { + return "", err + } + + pubkey, err := schnorr.ParsePubKey(partialSig.PubKey) + if err != nil { + return "", err + } + + if !sig.Verify(preimage, pubkey) { + return "", fmt.Errorf("invalid signature") + } + + if err := roundSigner.SignTaprootInputTapscriptSig(i, partialSig); err != nil { + return "", err + } + + } + + return roundSigner.Pset.ToBase64() +} + func (b *txBuilder) createConnectors( poolTx string, payments []domain.Payment, connectorAddress string, minRelayFee uint64, ) ([]*psetv2.Pset, error) { @@ -733,6 +895,52 @@ func (b *txBuilder) onchainNetwork() *network.Network { } } +func (b *txBuilder) getBoardingTaproot(owner, asp *secp256k1.PublicKey) (string, []byte, *taproot.TapscriptElementsProof, error) { + multisigClosure := tree.ForfeitClosure{ + Pubkey: owner, + AspPubkey: asp, + } + + csvClosure := tree.CSVSigClosure{ + Pubkey: owner, + Seconds: uint(b.boardingExitDelay), + } + + multisigLeaf, err := multisigClosure.Leaf() + if err != nil { + return "", nil, nil, err + } + + csvLeaf, err := csvClosure.Leaf() + if err != nil { + return "", nil, nil, err + } + + tapTree := taproot.AssembleTaprootScriptTree(*multisigLeaf, *csvLeaf) + root := tapTree.RootNode.TapHash() + tapKey := taproot.ComputeTaprootOutputKey(tree.UnspendableKey(), root[:]) + + p2tr, err := payment.FromTweakedKey(tapKey, b.onchainNetwork(), nil) + if err != nil { + return "", nil, nil, err + } + + addr, err := p2tr.TaprootAddress() + if err != nil { + return "", nil, nil, err + } + + tapLeaf, err := multisigClosure.Leaf() + if err != nil { + return "", nil, nil, err + } + + leafProofIndex := tapTree.LeafProofIndex[tapLeaf.TapHash()] + leafProof := tapTree.LeafMerkleProofs[leafProofIndex] + + return addr, p2tr.Script, &leafProof, nil +} + func extractSweepLeaf(input psetv2.Input) (sweepLeaf *psetv2.TapLeafScript, lifetime int64, err error) { for _, leaf := range input.TapLeafScript { closure := &tree.CSVSigClosure{} diff --git a/server/internal/infrastructure/tx-builder/covenant/builder_test.go b/server/internal/infrastructure/tx-builder/covenant/builder_test.go index 910c475..3aba6b1 100644 --- a/server/internal/infrastructure/tx-builder/covenant/builder_test.go +++ b/server/internal/infrastructure/tx-builder/covenant/builder_test.go @@ -25,6 +25,7 @@ const ( minRelayFee = uint64(30) roundLifetime = int64(1209344) unilateralExitDelay = int64(512) + boardingExitDelay = int64(512) ) var ( @@ -49,7 +50,7 @@ func TestMain(m *testing.M) { func TestBuildPoolTx(t *testing.T) { builder := txbuilder.NewTxBuilder( - wallet, common.Liquid, roundLifetime, unilateralExitDelay, + wallet, common.Liquid, roundLifetime, unilateralExitDelay, boardingExitDelay, ) fixtures, err := parsePoolTxFixtures() @@ -60,7 +61,7 @@ func TestBuildPoolTx(t *testing.T) { t.Run("valid", func(t *testing.T) { for _, f := range fixtures.Valid { poolTx, congestionTree, connAddr, err := builder.BuildPoolTx( - pubkey, f.Payments, minRelayFee, []domain.Round{}, + pubkey, f.Payments, []ports.BoardingInput{}, minRelayFee, []domain.Round{}, ) require.NoError(t, err) require.NotEmpty(t, poolTx) @@ -81,7 +82,7 @@ func TestBuildPoolTx(t *testing.T) { t.Run("invalid", func(t *testing.T) { for _, f := range fixtures.Invalid { poolTx, congestionTree, connAddr, err := builder.BuildPoolTx( - pubkey, f.Payments, minRelayFee, []domain.Round{}, + pubkey, f.Payments, []ports.BoardingInput{}, minRelayFee, []domain.Round{}, ) require.EqualError(t, err, f.ExpectedErr) require.Empty(t, poolTx) @@ -94,7 +95,7 @@ func TestBuildPoolTx(t *testing.T) { func TestBuildForfeitTxs(t *testing.T) { builder := txbuilder.NewTxBuilder( - wallet, common.Liquid, 1209344, unilateralExitDelay, + wallet, common.Liquid, 1209344, unilateralExitDelay, boardingExitDelay, ) fixtures, err := parseForfeitTxsFixtures() diff --git a/server/internal/infrastructure/tx-builder/covenant/mocks_test.go b/server/internal/infrastructure/tx-builder/covenant/mocks_test.go index af510cf..808f9ff 100644 --- a/server/internal/infrastructure/tx-builder/covenant/mocks_test.go +++ b/server/internal/infrastructure/tx-builder/covenant/mocks_test.go @@ -229,6 +229,16 @@ func (m *mockedWallet) MainAccountBalance(ctx context.Context) (uint64, uint64, return res, res2, args.Error(2) } +func (m *mockedWallet) GetTransaction(ctx context.Context, txid string) (string, error) { + args := m.Called(ctx, txid) + + var res string + if a := args.Get(0); a != nil { + res = a.(string) + } + return res, args.Error(1) +} + type mockedInput struct { mock.Mock } diff --git a/server/internal/infrastructure/tx-builder/covenantless/builder.go b/server/internal/infrastructure/tx-builder/covenantless/builder.go index f952b53..8b37138 100644 --- a/server/internal/infrastructure/tx-builder/covenantless/builder.go +++ b/server/internal/infrastructure/tx-builder/covenantless/builder.go @@ -28,16 +28,17 @@ const ( ) type txBuilder struct { - wallet ports.WalletService - net common.Network - roundLifetime int64 // in seconds - exitDelay int64 // in seconds + wallet ports.WalletService + net common.Network + roundLifetime int64 // in seconds + exitDelay int64 // in seconds + boardingExitDelay int64 // in seconds } func NewTxBuilder( - wallet ports.WalletService, net common.Network, roundLifetime int64, exitDelay int64, + wallet ports.WalletService, net common.Network, roundLifetime, exitDelay, boardingExitDelay int64, ) ports.TxBuilder { - return &txBuilder{wallet, net, roundLifetime, exitDelay} + return &txBuilder{wallet, net, roundLifetime, exitDelay, boardingExitDelay} } func (b *txBuilder) VerifyForfeitTx(tx string) (bool, string, error) { @@ -45,6 +46,7 @@ func (b *txBuilder) VerifyForfeitTx(tx string) (bool, string, error) { txid := ptx.UnsignedTx.TxHash().String() for index, input := range ptx.Inputs { + // TODO (@louisinger): verify control block for _, tapScriptSig := range input.TaprootScriptSpendSig { preimage, err := b.getTaprootPreimage( tx, @@ -178,7 +180,12 @@ func (b *txBuilder) BuildForfeitTxs( } func (b *txBuilder) BuildPoolTx( - aspPubkey *secp256k1.PublicKey, payments []domain.Payment, minRelayFee uint64, sweptRounds []domain.Round, cosigners ...*secp256k1.PublicKey, + aspPubkey *secp256k1.PublicKey, + payments []domain.Payment, + boardingInputs []ports.BoardingInput, + minRelayFee uint64, + sweptRounds []domain.Round, + cosigners ...*secp256k1.PublicKey, ) (poolTx string, congestionTree tree.CongestionTree, connectorAddress string, err error) { var sharedOutputScript []byte var sharedOutputAmount int64 @@ -204,7 +211,7 @@ func (b *txBuilder) BuildPoolTx( } ptx, err := b.createPoolTx( - sharedOutputAmount, sharedOutputScript, payments, connectorAddress, minRelayFee, sweptRounds, + aspPubkey, sharedOutputAmount, sharedOutputScript, payments, boardingInputs, connectorAddress, minRelayFee, sweptRounds, ) if err != nil { return @@ -253,9 +260,14 @@ func (b *txBuilder) GetSweepInput(parentblocktime int64, node tree.Node) (expira expirationTime := parentblocktime + lifetime - amount := int64(0) - for _, out := range partialTx.UnsignedTx.TxOut { - amount += out.Value + txhex, err := b.wallet.GetTransaction(context.Background(), txid.String()) + if err != nil { + return -1, nil, err + } + + var tx wire.MsgTx + if err := tx.Deserialize(hex.NewDecoder(strings.NewReader(txhex))); err != nil { + return -1, nil, err } sweepInput = &sweepBitcoinInput{ @@ -265,7 +277,7 @@ func (b *txBuilder) GetSweepInput(parentblocktime int64, node tree.Node) (expira }, internalPubkey: internalKey, sweepLeaf: sweepLeaf, - amount: amount, + amount: tx.TxOut[index].Value, } return expirationTime, sweepInput, nil @@ -468,6 +480,15 @@ func (b *txBuilder) BuildAsyncPaymentTransactions( }, nil } +func (b *txBuilder) GetBoardingScript(userPubkey, aspPubkey *secp256k1.PublicKey) (string, []byte, error) { + addr, script, _, err := b.craftBoardingTaproot(userPubkey, aspPubkey) + if err != nil { + return "", nil, err + } + + return addr, script, nil +} + func (b *txBuilder) getLeafScriptAndTree( userPubkey, aspPubkey *secp256k1.PublicKey, ) ([]byte, *txscript.IndexedTapScriptTree, error) { @@ -508,8 +529,9 @@ func (b *txBuilder) getLeafScriptAndTree( } func (b *txBuilder) createPoolTx( + aspPubKey *secp256k1.PublicKey, sharedOutputAmount int64, sharedOutputScript []byte, - payments []domain.Payment, connectorAddress string, minRelayFee uint64, + payments []domain.Payment, boardingInputs []ports.BoardingInput, connectorAddress string, minRelayFee uint64, sweptRounds []domain.Round, ) (*psbt.Packet, error) { connectorAddr, err := btcutil.DecodeAddress(connectorAddress, b.onchainNetwork()) @@ -541,10 +563,12 @@ func (b *txBuilder) createPoolTx( }) } - outputs = append(outputs, &wire.TxOut{ - Value: int64(connectorAmount), - PkScript: connectorScript, - }) + if connectorsAmount > 0 { + outputs = append(outputs, &wire.TxOut{ + Value: int64(connectorsAmount), + PkScript: connectorScript, + }) + } for _, receiver := range receivers { targetAmount += receiver.Amount @@ -565,6 +589,10 @@ func (b *txBuilder) createPoolTx( }) } + for _, input := range boardingInputs { + targetAmount -= input.GetAmount() + } + ctx := context.Background() utxos, change, err := b.selectUtxos(ctx, sweptRounds, targetAmount) if err != nil { @@ -601,6 +629,9 @@ func (b *txBuilder) createPoolTx( ins := make([]*wire.OutPoint, 0) nSequences := make([]uint32, 0) + witnessUtxos := make(map[int]*wire.TxOut) + boardingTapLeaves := make(map[int]*psbt.TaprootTapLeafScript) + nextIndex := 0 for _, utxo := range utxos { txhash, err := chainhash.NewHashFromStr(utxo.GetTxid()) @@ -613,6 +644,37 @@ func (b *txBuilder) createPoolTx( Index: utxo.GetIndex(), }) nSequences = append(nSequences, wire.MaxTxInSequenceNum) + + script, err := hex.DecodeString(utxo.GetScript()) + if err != nil { + return nil, err + } + + witnessUtxos[nextIndex] = &wire.TxOut{ + Value: int64(utxo.GetValue()), + PkScript: script, + } + nextIndex++ + } + + for _, input := range boardingInputs { + ins = append(ins, &wire.OutPoint{ + Hash: input.GetHash(), + Index: input.GetIndex(), + }) + nSequences = append(nSequences, wire.MaxTxInSequenceNum) + + _, script, tapLeaf, err := b.craftBoardingTaproot(input.GetBoardingPubkey(), aspPubKey) + if err != nil { + return nil, err + } + + boardingTapLeaves[nextIndex] = tapLeaf + witnessUtxos[nextIndex] = &wire.TxOut{ + Value: int64(input.GetAmount()), + PkScript: script, + } + nextIndex++ } ptx, err := psbt.New(ins, outputs, 2, 0, nSequences) @@ -624,20 +686,20 @@ func (b *txBuilder) createPoolTx( if err != nil { return nil, err } - for inIndex, utxo := range utxos { - script, err := hex.DecodeString(utxo.GetScript()) - if err != nil { - return nil, err - } - if err := updater.AddInWitnessUtxo(&wire.TxOut{ - Value: int64(utxo.GetValue()), - PkScript: script, - }, inIndex); err != nil { + for inIndex, utxo := range witnessUtxos { + if err := updater.AddInWitnessUtxo(utxo, inIndex); err != nil { return nil, err } } + unspendableInternalKey := schnorr.SerializePubKey(bitcointree.UnspendableKey()) + + for inIndex, tapLeaf := range boardingTapLeaves { + updater.Upsbt.Inputs[inIndex].TaprootLeafScript = []*psbt.TaprootTapLeafScript{tapLeaf} + updater.Upsbt.Inputs[inIndex].TaprootInternalKey = unspendableInternalKey + } + b64, err := ptx.B64Encode() if err != nil { return nil, err @@ -793,6 +855,62 @@ func (b *txBuilder) createPoolTx( return ptx, nil } +func (b *txBuilder) VerifyAndCombinePartialTx(dest string, src string) (string, error) { + roundTx, err := psbt.NewFromRawBytes(strings.NewReader(dest), true) + if err != nil { + return "", err + } + + sourceTx, err := psbt.NewFromRawBytes(strings.NewReader(src), true) + if err != nil { + return "", err + } + + if sourceTx.UnsignedTx.TxHash().String() != roundTx.UnsignedTx.TxHash().String() { + return "", fmt.Errorf("txids do not match") + } + + for i, in := range sourceTx.Inputs { + isMultisigTaproot := len(in.TaprootLeafScript) > 0 + if isMultisigTaproot { + // check if the source tx signs the leaf + sourceInput := sourceTx.Inputs[i] + + if len(sourceInput.TaprootScriptSpendSig) == 0 { + continue + } + + partialSig := sourceInput.TaprootScriptSpendSig[0] + preimage, err := b.getTaprootPreimage(src, i, sourceInput.TaprootLeafScript[0].Script) + if err != nil { + return "", err + } + + sig, err := schnorr.ParseSignature(partialSig.Signature) + if err != nil { + return "", err + } + + pubkey, err := schnorr.ParsePubKey(partialSig.XOnlyPubKey) + if err != nil { + return "", err + } + + if !sig.Verify(preimage, pubkey) { + return "", fmt.Errorf( + "invalid signature for input %s:%d", + sourceTx.UnsignedTx.TxIn[i].PreviousOutPoint.Hash.String(), + sourceTx.UnsignedTx.TxIn[i].PreviousOutPoint.Index, + ) + } + + roundTx.Inputs[i].TaprootScriptSpendSig = sourceInput.TaprootScriptSpendSig + } + } + + return roundTx.B64Encode() +} + func (b *txBuilder) createConnectors( poolTx string, payments []domain.Payment, connectorScript []byte, minRelayFee uint64, ) ([]*psbt.Packet, error) { @@ -858,9 +976,7 @@ func (b *txBuilder) createConnectors( func (b *txBuilder) createForfeitTxs( aspPubkey *secp256k1.PublicKey, payments []domain.Payment, connectors []*psbt.Packet, minRelayFee uint64, ) ([]string, error) { - // TODO (@louisinger): are we sure about this change? aspScript, err := p2trScript(aspPubkey, b.onchainNetwork()) - // aspScript, err := p2wpkhScript(aspPubkey, b.onchainNetwork()) if err != nil { return nil, err } @@ -1026,6 +1142,61 @@ func (b *txBuilder) onchainNetwork() *chaincfg.Params { } } +// craftBoardingTaproot returns the addr, script and the leaf belonging to the ASP +func (b *txBuilder) craftBoardingTaproot(userPubkey, aspPubkey *secp256k1.PublicKey) (string, []byte, *psbt.TaprootTapLeafScript, error) { + multisigClosure := bitcointree.MultisigClosure{ + Pubkey: userPubkey, + AspPubkey: aspPubkey, + } + + csvClosure := bitcointree.CSVSigClosure{ + Pubkey: userPubkey, + Seconds: uint(b.boardingExitDelay), + } + + multisigLeaf, err := multisigClosure.Leaf() + if err != nil { + return "", nil, nil, err + } + + csvLeaf, err := csvClosure.Leaf() + if err != nil { + return "", nil, nil, err + } + + tree := txscript.AssembleTaprootScriptTree(*multisigLeaf, *csvLeaf) + + root := tree.RootNode.TapHash() + + taprootKey := txscript.ComputeTaprootOutputKey(bitcointree.UnspendableKey(), root[:]) + script, err := txscript.PayToTaprootScript(taprootKey) + if err != nil { + return "", nil, nil, err + } + + addr, err := btcutil.NewAddressTaproot(schnorr.SerializePubKey(taprootKey), b.onchainNetwork()) + if err != nil { + return "", nil, nil, err + } + + proofIndex := tree.LeafProofIndex[multisigLeaf.TapHash()] + proof := tree.LeafMerkleProofs[proofIndex] + + ctrlBlock := proof.ToControlBlock(bitcointree.UnspendableKey()) + ctrlBlockBytes, err := ctrlBlock.ToBytes() + if err != nil { + return "", nil, nil, err + } + + tapLeaf := &psbt.TaprootTapLeafScript{ + ControlBlock: ctrlBlockBytes, + Script: multisigLeaf.Script, + LeafVersion: txscript.BaseLeafVersion, + } + + return addr.String(), script, tapLeaf, nil +} + func castToOutpoints(inputs []ports.TxInput) []ports.TxOutpoint { outpoints := make([]ports.TxOutpoint, 0, len(inputs)) for _, input := range inputs { diff --git a/server/internal/infrastructure/tx-builder/covenantless/builder_test.go b/server/internal/infrastructure/tx-builder/covenantless/builder_test.go index 6ac4409..2fdc937 100644 --- a/server/internal/infrastructure/tx-builder/covenantless/builder_test.go +++ b/server/internal/infrastructure/tx-builder/covenantless/builder_test.go @@ -25,6 +25,7 @@ const ( minRelayFee = uint64(30) roundLifetime = int64(1209344) unilateralExitDelay = int64(512) + boardingExitDelay = int64(512) ) var ( @@ -49,7 +50,7 @@ func TestMain(m *testing.M) { func TestBuildPoolTx(t *testing.T) { builder := txbuilder.NewTxBuilder( - wallet, common.Bitcoin, roundLifetime, unilateralExitDelay, + wallet, common.Bitcoin, roundLifetime, unilateralExitDelay, boardingExitDelay, ) fixtures, err := parsePoolTxFixtures() @@ -72,7 +73,7 @@ func TestBuildPoolTx(t *testing.T) { } poolTx, congestionTree, connAddr, err := builder.BuildPoolTx( - pubkey, f.Payments, minRelayFee, []domain.Round{}, cosigners..., + pubkey, f.Payments, []ports.BoardingInput{}, minRelayFee, []domain.Round{}, cosigners..., ) require.NoError(t, err) require.NotEmpty(t, poolTx) @@ -93,7 +94,7 @@ func TestBuildPoolTx(t *testing.T) { t.Run("invalid", func(t *testing.T) { for _, f := range fixtures.Invalid { poolTx, congestionTree, connAddr, err := builder.BuildPoolTx( - pubkey, f.Payments, minRelayFee, []domain.Round{}, + pubkey, f.Payments, []ports.BoardingInput{}, minRelayFee, []domain.Round{}, ) require.EqualError(t, err, f.ExpectedErr) require.Empty(t, poolTx) @@ -106,7 +107,7 @@ func TestBuildPoolTx(t *testing.T) { func TestBuildForfeitTxs(t *testing.T) { builder := txbuilder.NewTxBuilder( - wallet, common.Bitcoin, 1209344, unilateralExitDelay, + wallet, common.Bitcoin, 1209344, unilateralExitDelay, boardingExitDelay, ) fixtures, err := parseForfeitTxsFixtures() diff --git a/server/internal/infrastructure/tx-builder/covenantless/mocks_test.go b/server/internal/infrastructure/tx-builder/covenantless/mocks_test.go index 4db2f74..d52679f 100644 --- a/server/internal/infrastructure/tx-builder/covenantless/mocks_test.go +++ b/server/internal/infrastructure/tx-builder/covenantless/mocks_test.go @@ -201,6 +201,16 @@ func (m *mockedWallet) WaitForSync(ctx context.Context, txid string) error { return args.Error(0) } +func (m *mockedWallet) GetTransaction(ctx context.Context, txid string) (string, error) { + args := m.Called(ctx, txid) + + var res string + if a := args.Get(0); a != nil { + res = a.(string) + } + return res, args.Error(1) +} + func (m *mockedWallet) ConnectorsAccountBalance(ctx context.Context) (uint64, uint64, error) { panic("not implemented") } diff --git a/server/internal/infrastructure/wallet/btc-embedded/esplora.go b/server/internal/infrastructure/wallet/btc-embedded/esplora.go index 69f232e..361df66 100644 --- a/server/internal/infrastructure/wallet/btc-embedded/esplora.go +++ b/server/internal/infrastructure/wallet/btc-embedded/esplora.go @@ -11,6 +11,7 @@ import ( "github.com/ark-network/ark/server/internal/core/ports" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" log "github.com/sirupsen/logrus" ) @@ -54,6 +55,31 @@ func (f *esploraClient) broadcast(txhex string) error { return nil } +func (f *esploraClient) getTx(txid string) (*wire.MsgTx, error) { + endpoint, err := url.JoinPath(f.url, "tx", txid, "raw") + if err != nil { + return nil, err + } + + resp, err := http.DefaultClient.Get(endpoint) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, errors.New("tx endpoint HTTP error: " + resp.Status) + } + + var tx wire.MsgTx + + if err := tx.Deserialize(resp.Body); err != nil { + return nil, err + } + + return &tx, nil +} + func (f *esploraClient) getTxStatus(txid string) (isConfirmed bool, blocktime int64, err error) { endpoint, err := url.JoinPath(f.url, "tx", txid) if err != nil { diff --git a/server/internal/infrastructure/wallet/btc-embedded/psbt.go b/server/internal/infrastructure/wallet/btc-embedded/psbt.go index d99e065..3b2cc75 100644 --- a/server/internal/infrastructure/wallet/btc-embedded/psbt.go +++ b/server/internal/infrastructure/wallet/btc-embedded/psbt.go @@ -11,7 +11,7 @@ import ( log "github.com/sirupsen/logrus" ) -func (s *service) signPsbt(packet *psbt.Packet) ([]uint32, error) { +func (s *service) signPsbt(packet *psbt.Packet, inputsToSign []int) ([]uint32, error) { // iterates over the inputs and set the default sighash flags updater, err := psbt.NewUpdater(packet) if err != nil { @@ -54,6 +54,19 @@ func (s *service) signPsbt(packet *psbt.Packet) ([]uint32, error) { continue } + if len(inputsToSign) > 0 { + found := false + for _, i := range inputsToSign { + if i == idx { + found = true + break + } + } + if !found { + continue + } + } + var managedAddress waddrmgr.ManagedPubKeyAddress var isTaproot bool diff --git a/server/internal/infrastructure/wallet/btc-embedded/wallet.go b/server/internal/infrastructure/wallet/btc-embedded/wallet.go index 5cf9712..cc1c026 100644 --- a/server/internal/infrastructure/wallet/btc-embedded/wallet.go +++ b/server/internal/infrastructure/wallet/btc-embedded/wallet.go @@ -10,8 +10,10 @@ import ( "time" "github.com/ark-network/ark/common" + "github.com/ark-network/ark/common/bitcointree" "github.com/ark-network/ark/server/internal/core/domain" "github.com/ark-network/ark/server/internal/core/ports" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/chaincfg" @@ -501,9 +503,11 @@ func (s *service) SelectUtxos(ctx context.Context, _ string, amount uint64) ([]p selectedUtxos = append(selectedUtxos, coinTxInput{coin}) } - change := selectedAmount - amount + if selectedAmount < amount { + return nil, 0, fmt.Errorf("insufficient funds to select %d, only %d available", amount, selectedAmount) + } - return selectedUtxos, change, nil + return selectedUtxos, selectedAmount - amount, nil } func (s *service) SignTransaction(ctx context.Context, partialTx string, extractRawTx bool) (string, error) { @@ -515,7 +519,7 @@ func (s *service) SignTransaction(ctx context.Context, partialTx string, extract return "", err } - signedInputs, err := s.signPsbt(ptx) + signedInputs, err := s.signPsbt(ptx, nil) if err != nil { return "", err } @@ -525,8 +529,55 @@ func (s *service) SignTransaction(ctx context.Context, partialTx string, extract return "", fmt.Errorf("not all inputs are signed, unable to finalize the psbt") } - if err := psbt.MaybeFinalizeAll(ptx); err != nil { - return "", err + for i, in := range ptx.Inputs { + isTaproot := txscript.IsPayToTaproot(in.WitnessUtxo.PkScript) + if isTaproot && len(in.TaprootLeafScript) > 0 { + closure, err := bitcointree.DecodeClosure(in.TaprootLeafScript[0].Script) + if err != nil { + return "", err + } + + witness := make(wire.TxWitness, 4) + + castClosure, isTaprootMultisig := closure.(*bitcointree.MultisigClosure) + if isTaprootMultisig { + ownerPubkey := schnorr.SerializePubKey(castClosure.Pubkey) + aspKey := schnorr.SerializePubKey(castClosure.AspPubkey) + + for _, sig := range in.TaprootScriptSpendSig { + if bytes.Equal(sig.XOnlyPubKey, ownerPubkey) { + witness[0] = sig.Signature + } + + if bytes.Equal(sig.XOnlyPubKey, aspKey) { + witness[1] = sig.Signature + } + } + + witness[2] = in.TaprootLeafScript[0].Script + witness[3] = in.TaprootLeafScript[0].ControlBlock + + for idw, w := range witness { + if w == nil { + return "", fmt.Errorf("missing witness element %d, cannot finalize taproot mutlisig input %d", idw, i) + } + } + + var witnessBuf bytes.Buffer + + if err := psbt.WriteTxWitness(&witnessBuf, witness); err != nil { + return "", err + } + + ptx.Inputs[i].FinalScriptWitness = witnessBuf.Bytes() + continue + } + + } + + if err := psbt.Finalize(ptx, i); err != nil { + return "", fmt.Errorf("failed to finalize input %d: %w", i, err) + } } extracted, err := psbt.Extract(ptx) @@ -561,7 +612,7 @@ func (s *service) SignTransactionTapscript(ctx context.Context, partialTx string } } - signedInputs, err := s.signPsbt(partial) + signedInputs, err := s.signPsbt(partial, inputIndexes) if err != nil { return "", err } @@ -719,6 +770,24 @@ func (s *service) IsTransactionConfirmed( return s.esploraClient.getTxStatus(txid) } +func (s *service) GetTransaction(ctx context.Context, txid string) (string, error) { + tx, err := s.esploraClient.getTx(txid) + if err != nil { + return "", err + } + + if tx == nil { + return "", fmt.Errorf("transaction not found") + } + + var buf bytes.Buffer + if err := tx.Serialize(&buf); err != nil { + return "", err + } + + return hex.EncodeToString(buf.Bytes()), nil +} + func (s *service) castNotification(tx *wtxmgr.TxRecord) map[string]ports.VtxoWithValue { vtxos := make(map[string]ports.VtxoWithValue) diff --git a/server/internal/infrastructure/wallet/liquid-standalone/transaction.go b/server/internal/infrastructure/wallet/liquid-standalone/transaction.go index 82ab96b..298d2f3 100644 --- a/server/internal/infrastructure/wallet/liquid-standalone/transaction.go +++ b/server/internal/infrastructure/wallet/liquid-standalone/transaction.go @@ -1,6 +1,7 @@ package oceanwallet import ( + "bytes" "context" "encoding/binary" "encoding/hex" @@ -11,6 +12,8 @@ import ( pb "github.com/ark-network/ark/api-spec/protobuf/gen/ocean/v1" "github.com/ark-network/ark/common/tree" "github.com/ark-network/ark/server/internal/core/ports" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/vulpemventures/go-elements/elementsutil" @@ -22,7 +25,7 @@ const ( ) func (s *service) SignTransaction( - ctx context.Context, pset string, extractRawTx bool, + ctx context.Context, pset string, finalizeAndExtractRawTx bool, ) (string, error) { res, err := s.txClient.SignPset(ctx, &pb.SignPsetRequest{ Pset: pset, @@ -32,7 +35,7 @@ func (s *service) SignTransaction( } signedPset := res.GetPset() - if !extractRawTx { + if !finalizeAndExtractRawTx { return signedPset, nil } @@ -41,8 +44,61 @@ func (s *service) SignTransaction( return "", err } - if err := psetv2.MaybeFinalizeAll(ptx); err != nil { - return "", fmt.Errorf("failed to finalize signed pset: %s", err) + for i, in := range ptx.Inputs { + if in.WitnessUtxo == nil { + return "", fmt.Errorf("missing witness utxo, cannot finalize tx") + } + + if len(in.TapLeafScript) > 0 { + tapLeaf := in.TapLeafScript[0] + + closure, err := tree.DecodeClosure(tapLeaf.Script) + if err != nil { + return "", err + } + + switch c := closure.(type) { + case *tree.ForfeitClosure: + asp := schnorr.SerializePubKey(c.AspPubkey) + owner := schnorr.SerializePubKey(c.Pubkey) + + witness := make([][]byte, 4) + for _, sig := range in.TapScriptSig { + if bytes.Equal(sig.PubKey, owner) { + witness[0] = sig.Signature + continue + } + + if bytes.Equal(sig.PubKey, asp) { + witness[1] = sig.Signature + } + } + + witness[2] = tapLeaf.Script + + controlBlock, err := tapLeaf.ControlBlock.ToBytes() + if err != nil { + return "", err + } + + witness[3] = controlBlock + + var witnessBuf bytes.Buffer + + if err := psbt.WriteTxWitness(&witnessBuf, witness); err != nil { + return "", err + } + + ptx.Inputs[i].FinalScriptWitness = witnessBuf.Bytes() + continue + default: + return "", fmt.Errorf("unexpected closure type %T", c) + } + } + + if err := psetv2.Finalize(ptx, i); err != nil { + return "", fmt.Errorf("failed to finalize signed pset: %s", err) + } } extractedTx, err := psetv2.Extract(ptx) @@ -281,6 +337,15 @@ func (s *service) EstimateFees( return fee.GetFeeAmount() + 5, nil } +func (s *service) GetTransaction(ctx context.Context, txid string) (string, error) { + txHex, _, _, err := s.getTransaction(ctx, txid) + if err != nil { + return "", err + } + + return txHex, nil +} + func (s *service) getTransaction( ctx context.Context, txid string, ) (string, bool, int64, error) { diff --git a/server/internal/interface/grpc/handlers/arkservice.go b/server/internal/interface/grpc/handlers/arkservice.go index 434ab78..ee21432 100644 --- a/server/internal/interface/grpc/handlers/arkservice.go +++ b/server/internal/interface/grpc/handlers/arkservice.go @@ -60,11 +60,20 @@ func (h *handler) CompletePayment(ctx context.Context, req *arkv1.CompletePaymen } func (h *handler) CreatePayment(ctx context.Context, req *arkv1.CreatePaymentRequest) (*arkv1.CreatePaymentResponse, error) { - vtxosKeys, err := parseInputs(req.GetInputs()) + inputs, err := parseInputs(req.GetInputs()) if err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) } + vtxosKeys := make([]domain.VtxoKey, 0, len(inputs)) + for _, input := range inputs { + if !input.IsVtxo() { + return nil, status.Error(codes.InvalidArgument, "only vtxos input allowed") + } + + vtxosKeys = append(vtxosKeys, input.VtxoKey()) + } + receivers, err := parseReceivers(req.GetOutputs()) if err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) @@ -83,37 +92,6 @@ func (h *handler) CreatePayment(ctx context.Context, req *arkv1.CreatePaymentReq }, nil } -func (h *handler) Onboard(ctx context.Context, req *arkv1.OnboardRequest) (*arkv1.OnboardResponse, error) { - if req.GetUserPubkey() == "" { - return nil, status.Error(codes.InvalidArgument, "missing user pubkey") - } - - pubKey, err := hex.DecodeString(req.GetUserPubkey()) - if err != nil { - return nil, status.Error(codes.InvalidArgument, "invalid user pubkey") - } - - decodedPubKey, err := secp256k1.ParsePubKey(pubKey) - if err != nil { - return nil, status.Error(codes.InvalidArgument, "invalid user pubkey") - } - - if req.GetBoardingTx() == "" { - return nil, status.Error(codes.InvalidArgument, "missing boarding tx id") - } - - tree, err := toCongestionTree(req.GetCongestionTree()) - if err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) - } - - if err := h.svc.Onboard(ctx, req.GetBoardingTx(), tree, decodedPubKey); err != nil { - return nil, err - } - - return &arkv1.OnboardResponse{}, nil -} - func (h *handler) Ping(ctx context.Context, req *arkv1.PingRequest) (*arkv1.PingResponse, error) { if req.GetPaymentId() == "" { return nil, status.Error(codes.InvalidArgument, "missing payment id") @@ -193,12 +171,11 @@ func (h *handler) Ping(ctx context.Context, req *arkv1.PingRequest) (*arkv1.Ping } func (h *handler) RegisterPayment(ctx context.Context, req *arkv1.RegisterPaymentRequest) (*arkv1.RegisterPaymentResponse, error) { - vtxosKeys, err := parseInputs(req.GetInputs()) + inputs, err := parseInputs(req.GetInputs()) if err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) } - - id, err := h.svc.SpendVtxos(ctx, vtxosKeys) + id, err := h.svc.SpendVtxos(ctx, inputs) if err != nil { return nil, err } @@ -229,12 +206,23 @@ func (h *handler) ClaimPayment(ctx context.Context, req *arkv1.ClaimPaymentReque } func (h *handler) FinalizePayment(ctx context.Context, req *arkv1.FinalizePaymentRequest) (*arkv1.FinalizePaymentResponse, error) { - forfeitTxs, err := parseTxs(req.GetSignedForfeitTxs()) - if err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) + forfeitTxs := req.GetSignedForfeitTxs() + roundTx := req.GetSignedRoundTx() + + if len(forfeitTxs) <= 0 && roundTx == "" { + return nil, status.Error(codes.InvalidArgument, "missing forfeit txs or round tx") } - if err := h.svc.SignVtxos(ctx, forfeitTxs); err != nil { - return nil, err + + if len(forfeitTxs) > 0 { + if err := h.svc.SignVtxos(ctx, forfeitTxs); err != nil { + return nil, err + } + } + + if roundTx != "" { + if err := h.svc.SignRoundTx(ctx, roundTx); err != nil { + return nil, err + } } return &arkv1.FinalizePaymentResponse{}, nil @@ -355,12 +343,39 @@ func (h *handler) GetInfo(ctx context.Context, req *arkv1.GetInfoRequest) (*arkv } return &arkv1.GetInfoResponse{ - Pubkey: info.PubKey, - RoundLifetime: info.RoundLifetime, - UnilateralExitDelay: info.UnilateralExitDelay, - RoundInterval: info.RoundInterval, - Network: info.Network, - MinRelayFee: info.MinRelayFee, + Pubkey: info.PubKey, + RoundLifetime: info.RoundLifetime, + UnilateralExitDelay: info.UnilateralExitDelay, + RoundInterval: info.RoundInterval, + Network: info.Network, + MinRelayFee: info.MinRelayFee, + BoardingDescriptorTemplate: info.BoardingDescriptorTemplate, + }, nil +} + +func (h *handler) GetBoardingAddress(ctx context.Context, req *arkv1.GetBoardingAddressRequest) (*arkv1.GetBoardingAddressResponse, error) { + pubkey := req.GetPubkey() + if pubkey == "" { + return nil, status.Error(codes.InvalidArgument, "missing pubkey") + } + + pubkeyBytes, err := hex.DecodeString(pubkey) + if err != nil { + return nil, status.Error(codes.InvalidArgument, "invalid pubkey (invalid hex)") + } + + userPubkey, err := secp256k1.ParsePubKey(pubkeyBytes) + if err != nil { + return nil, status.Error(codes.InvalidArgument, "invalid pubkey (parse error)") + } + + addr, err := h.svc.GetBoardingAddress(ctx, userPubkey) + if err != nil { + return nil, err + } + + return &arkv1.GetBoardingAddressResponse{ + Address: addr, }, nil } @@ -548,8 +563,12 @@ func (v vtxoList) toProto(hrp string, aspKey *secp256k1.PublicKey) []*arkv1.Vtxo } list = append(list, &arkv1.Vtxo{ Outpoint: &arkv1.Input{ - Txid: vv.Txid, - Vout: vv.VOut, + Input: &arkv1.Input_VtxoInput{ + VtxoInput: &arkv1.VtxoInput{ + Txid: vv.Txid, + Vout: vv.VOut, + }, + }, }, Receiver: &arkv1.Output{ Address: addr, @@ -590,36 +609,3 @@ func castCongestionTree(congestionTree tree.CongestionTree) *arkv1.Tree { Levels: levels, } } - -func toCongestionTree(treeFromProto *arkv1.Tree) (tree.CongestionTree, error) { - if treeFromProto == nil { - return nil, nil - } - - levels := make(tree.CongestionTree, 0, len(treeFromProto.Levels)) - - for _, level := range treeFromProto.Levels { - nodes := make([]tree.Node, 0, len(level.Nodes)) - - for _, node := range level.Nodes { - nodes = append(nodes, tree.Node{ - Txid: node.Txid, - Tx: node.Tx, - ParentTxid: node.ParentTxid, - Leaf: false, - }) - } - - levels = append(levels, nodes) - } - - for j, treeLvl := range levels { - for i, node := range treeLvl { - if len(levels.Children(node.Txid)) == 0 { - levels[j][i].Leaf = true - } - } - } - - return levels, nil -} diff --git a/server/internal/interface/grpc/handlers/utils.go b/server/internal/interface/grpc/handlers/utils.go index 6d5bac8..2a2c7a4 100644 --- a/server/internal/interface/grpc/handlers/utils.go +++ b/server/internal/interface/grpc/handlers/utils.go @@ -6,23 +6,11 @@ import ( arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1" "github.com/ark-network/ark/common" + "github.com/ark-network/ark/server/internal/core/application" "github.com/ark-network/ark/server/internal/core/domain" "github.com/decred/dcrd/dcrec/secp256k1/v4" ) -func parseTxs(txs []string) ([]string, error) { - if len(txs) <= 0 { - return nil, fmt.Errorf("missing list of forfeit txs") - } - // TODO abstract this ? - // for _, tx := range txs { - // if _, err := psetv2.NewPsetFromBase64(tx); err != nil { - // return nil, fmt.Errorf("invalid tx format") - // } - // } - return txs, nil -} - func parseAddress(addr string) (string, *secp256k1.PublicKey, *secp256k1.PublicKey, error) { if len(addr) <= 0 { return "", nil, nil, fmt.Errorf("missing address") @@ -30,19 +18,31 @@ func parseAddress(addr string) (string, *secp256k1.PublicKey, *secp256k1.PublicK return common.DecodeAddress(addr) } -func parseInputs(ins []*arkv1.Input) ([]domain.VtxoKey, error) { +func parseInputs(ins []*arkv1.Input) ([]application.Input, error) { if len(ins) <= 0 { return nil, fmt.Errorf("missing inputs") } - vtxos := make([]domain.VtxoKey, 0, len(ins)) + inputs := make([]application.Input, 0, len(ins)) for _, input := range ins { - vtxos = append(vtxos, domain.VtxoKey{ - Txid: input.GetTxid(), - VOut: input.GetVout(), + if input.GetBoardingInput() != nil { + desc := input.GetBoardingInput().GetDescriptor_() + inputs = append(inputs, application.Input{ + Txid: input.GetBoardingInput().GetTxid(), + Index: input.GetBoardingInput().GetVout(), + Descriptor: desc, + }) + + continue + } + + inputs = append(inputs, application.Input{ + Txid: input.GetVtxoInput().GetTxid(), + Index: input.GetVtxoInput().GetVout(), }) } - return vtxos, nil + + return inputs, nil } func parseReceivers(outs []*arkv1.Output) ([]domain.Receiver, error) { diff --git a/server/internal/interface/grpc/permissions/permissions.go b/server/internal/interface/grpc/permissions/permissions.go index dfa4336..88aa6dc 100644 --- a/server/internal/interface/grpc/permissions/permissions.go +++ b/server/internal/interface/grpc/permissions/permissions.go @@ -145,10 +145,6 @@ func Whitelist() map[string][]bakery.Op { Entity: EntityArk, Action: "read", }}, - fmt.Sprintf("/%s/Onboard", arkv1.ArkService_ServiceDesc.ServiceName): {{ - Entity: EntityArk, - Action: "write", - }}, fmt.Sprintf("/%s/CreatePayment", arkv1.ArkService_ServiceDesc.ServiceName): {{ Entity: EntityArk, Action: "write", @@ -161,6 +157,10 @@ func Whitelist() map[string][]bakery.Op { Entity: EntityHealth, Action: "read", }}, + fmt.Sprintf("/%s/GetBoardingAddress", arkv1.ArkService_ServiceDesc.ServiceName): {{ + Entity: EntityArk, + Action: "read", + }}, fmt.Sprintf("/%s/SendTreeNonces", arkv1.ArkService_ServiceDesc.ServiceName): {{ Entity: EntityArk, Action: "write", diff --git a/server/test/e2e/covenant/e2e_test.go b/server/test/e2e/covenant/e2e_test.go index 051da3a..651cb4c 100644 --- a/server/test/e2e/covenant/e2e_test.go +++ b/server/test/e2e/covenant/e2e_test.go @@ -17,6 +17,7 @@ import ( const ( composePath = "../../../../docker-compose.regtest.yml" ONE_BTC = 1_0000_0000 + redeemAddr = "ert1p7hffs7y50jy8l34g334yke9cntzahml40xm2hx90g34jq8mqu7zsezhwcc" ) func TestMain(m *testing.M) { @@ -26,6 +27,8 @@ func TestMain(m *testing.M) { os.Exit(1) } + fmt.Println("waiting for docker containers to start...") + time.Sleep(10 * time.Second) if err := setupAspWallet(); err != nil { @@ -41,26 +44,6 @@ func TestMain(m *testing.M) { os.Exit(1) } - var receive utils.ArkReceive - receiveStr, err := runArkCommand("receive") - if err != nil { - fmt.Printf("error getting ark receive addresses: %s", err) - os.Exit(1) - } - - if err := json.Unmarshal([]byte(receiveStr), &receive); err != nil { - fmt.Printf("error unmarshalling ark receive addresses: %s", err) - os.Exit(1) - } - - _, err = utils.RunCommand("nigiri", "faucet", "--liquid", receive.Onchain) - if err != nil { - fmt.Printf("error funding ark account: %s", err) - os.Exit(1) - } - - time.Sleep(5 * time.Second) - code := m.Run() _, err = utils.RunCommand("docker", "compose", "-f", composePath, "down") @@ -71,37 +54,23 @@ func TestMain(m *testing.M) { os.Exit(code) } -func TestOnboard(t *testing.T) { - var balance utils.ArkBalance - balanceStr, err := runArkCommand("balance") - require.NoError(t, err) - - require.NoError(t, json.Unmarshal([]byte(balanceStr), &balance)) - balanceBefore := balance.Offchain.Total - - _, err = runArkCommand("onboard", "--amount", "1000", "--password", utils.Password) - require.NoError(t, err) - err = utils.GenerateBlock() - require.NoError(t, err) - - balanceStr, err = runArkCommand("balance") - require.NoError(t, err) - - require.NoError(t, json.Unmarshal([]byte(balanceStr), &balance)) - require.Equal(t, balanceBefore+1000, balance.Offchain.Total) -} - func TestSendOffchain(t *testing.T) { - _, err := runArkCommand("onboard", "--amount", "1000", "--password", utils.Password) - require.NoError(t, err) - err = utils.GenerateBlock() - require.NoError(t, err) - var receive utils.ArkReceive receiveStr, err := runArkCommand("receive") require.NoError(t, err) + require.NoError(t, json.Unmarshal([]byte(receiveStr), &receive)) + _, err = utils.RunCommand("nigiri", "faucet", "--liquid", receive.Boarding) + require.NoError(t, err) + + time.Sleep(5 * time.Second) + + _, err = runArkCommand("claim", "--password", utils.Password) + require.NoError(t, err) + + time.Sleep(3 * time.Second) + _, err = runArkCommand("send", "--amount", "1000", "--to", receive.Offchain, "--password", utils.Password) require.NoError(t, err) @@ -113,17 +82,27 @@ func TestSendOffchain(t *testing.T) { } func TestUnilateralExit(t *testing.T) { - _, err := runArkCommand("onboard", "--amount", "1000", "--password", utils.Password) + var receive utils.ArkReceive + receiveStr, err := runArkCommand("receive") require.NoError(t, err) - err = utils.GenerateBlock() + + require.NoError(t, json.Unmarshal([]byte(receiveStr), &receive)) + + _, err = utils.RunCommand("nigiri", "faucet", "--liquid", receive.Boarding) require.NoError(t, err) + time.Sleep(5 * time.Second) + + _, err = runArkCommand("claim", "--password", utils.Password) + require.NoError(t, err) + + time.Sleep(3 * time.Second) + var balance utils.ArkBalance balanceStr, err := runArkCommand("balance") require.NoError(t, err) require.NoError(t, json.Unmarshal([]byte(balanceStr), &balance)) require.NotZero(t, balance.Offchain.Total) - require.Len(t, balance.Onchain.Locked, 0) _, err = runArkCommand("redeem", "--force", "--password", utils.Password) require.NoError(t, err) @@ -144,35 +123,24 @@ func TestUnilateralExit(t *testing.T) { } func TestCollaborativeExit(t *testing.T) { - _, err := runArkCommand("onboard", "--amount", "1000", "--password", utils.Password) - require.NoError(t, err) - err = utils.GenerateBlock() - require.NoError(t, err) - var receive utils.ArkReceive receiveStr, err := runArkCommand("receive") require.NoError(t, err) + require.NoError(t, json.Unmarshal([]byte(receiveStr), &receive)) - var balance utils.ArkBalance - balanceStr, err := runArkCommand("balance") - require.NoError(t, err) - require.NoError(t, json.Unmarshal([]byte(balanceStr), &balance)) - - balanceBefore := balance.Offchain.Total - balanceOnchainBefore := balance.Onchain.Spendable - - _, err = runArkCommand("redeem", "--amount", "1000", "--address", receive.Onchain, "--password", utils.Password) + _, err = utils.RunCommand("nigiri", "faucet", "--liquid", receive.Boarding) require.NoError(t, err) time.Sleep(5 * time.Second) - balanceStr, err = runArkCommand("balance") + _, err = runArkCommand("claim", "--password", utils.Password) require.NoError(t, err) - require.NoError(t, json.Unmarshal([]byte(balanceStr), &balance)) - require.Equal(t, balanceBefore-1000, balance.Offchain.Total) - require.Equal(t, balanceOnchainBefore+1000, balance.Onchain.Spendable) + time.Sleep(3 * time.Second) + + _, err = runArkCommand("redeem", "--amount", "10000", "--address", redeemAddr, "--password", utils.Password) + require.NoError(t, err) } func runArkCommand(arg ...string) (string, error) { @@ -258,6 +226,14 @@ func setupAspWallet() error { if _, err := utils.RunCommand("nigiri", "faucet", "--liquid", addr.Address); err != nil { return fmt.Errorf("failed to fund wallet: %s", err) } + if _, err := utils.RunCommand("nigiri", "faucet", "--liquid", addr.Address); err != nil { + return fmt.Errorf("failed to fund wallet: %s", err) + } + if _, err := utils.RunCommand("nigiri", "faucet", "--liquid", addr.Address); err != nil { + return fmt.Errorf("failed to fund wallet: %s", err) + } + + time.Sleep(5 * time.Second) return nil } diff --git a/server/test/e2e/covenantless/e2e_test.go b/server/test/e2e/covenantless/e2e_test.go index aad30a2..e1c0776 100644 --- a/server/test/e2e/covenantless/e2e_test.go +++ b/server/test/e2e/covenantless/e2e_test.go @@ -14,8 +14,8 @@ import ( ) const ( - composePath = "../../../../docker-compose.clark.regtest.yml" - ONE_BTC = 1_0000_0000 + composePath = "../../../../docker-compose.clark.regtest.yml" + redeemAddress = "bcrt1q2wrgf2hrkfegt0t97cnv4g5yvfjua9k6vua54d" ) func TestMain(m *testing.M) { @@ -27,6 +27,11 @@ func TestMain(m *testing.M) { time.Sleep(10 * time.Second) + if err := utils.GenerateBlock(); err != nil { + fmt.Printf("error generating block: %s", err) + os.Exit(1) + } + if err := setupAspWallet(); err != nil { fmt.Println(err) os.Exit(1) @@ -40,26 +45,6 @@ func TestMain(m *testing.M) { os.Exit(1) } - var receive utils.ArkReceive - receiveStr, err := runClarkCommand("receive") - if err != nil { - fmt.Printf("error getting ark receive addresses: %s", err) - os.Exit(1) - } - - if err := json.Unmarshal([]byte(receiveStr), &receive); err != nil { - fmt.Printf("error unmarshalling ark receive addresses: %s", err) - os.Exit(1) - } - - _, err = utils.RunCommand("nigiri", "faucet", receive.Onchain) - if err != nil { - fmt.Printf("error funding ark account: %s", err) - os.Exit(1) - } - - time.Sleep(5 * time.Second) - code := m.Run() _, err = utils.RunCommand("docker", "compose", "-f", composePath, "down") @@ -70,38 +55,25 @@ func TestMain(m *testing.M) { os.Exit(code) } -func TestOnboard(t *testing.T) { - var balance utils.ArkBalance - balanceStr, err := runClarkCommand("balance") - require.NoError(t, err) - - require.NoError(t, json.Unmarshal([]byte(balanceStr), &balance)) - balanceBefore := balance.Offchain.Total - - _, err = runClarkCommand("onboard", "--amount", "1000", "--password", utils.Password) - require.NoError(t, err) - err = utils.GenerateBlock() - require.NoError(t, err) - - balanceStr, err = runClarkCommand("balance") - require.NoError(t, err) - - require.NoError(t, json.Unmarshal([]byte(balanceStr), &balance)) - require.Equal(t, balanceBefore+1000, balance.Offchain.Total) -} - func TestSendOffchain(t *testing.T) { - _, err := runClarkCommand("onboard", "--amount", "1000", "--password", utils.Password) - require.NoError(t, err) - err = utils.GenerateBlock() - require.NoError(t, err) - var receive utils.ArkReceive receiveStr, err := runClarkCommand("receive") require.NoError(t, err) - require.NoError(t, json.Unmarshal([]byte(receiveStr), &receive)) - _, err = runClarkCommand("send", "--amount", "1000", "--to", receive.Offchain, "--password", utils.Password) + err = json.Unmarshal([]byte(receiveStr), &receive) + require.NoError(t, err) + + _, err = utils.RunCommand("nigiri", "faucet", receive.Boarding) + require.NoError(t, err) + + time.Sleep(5 * time.Second) + + _, err = runClarkCommand("claim", "--password", utils.Password) + require.NoError(t, err) + + time.Sleep(3 * time.Second) + + _, err = runClarkCommand("send", "--amount", "10000", "--to", receive.Offchain, "--password", utils.Password) require.NoError(t, err) var balance utils.ArkBalance @@ -120,17 +92,28 @@ func TestSendOffchain(t *testing.T) { } func TestUnilateralExit(t *testing.T) { - _, err := runClarkCommand("onboard", "--amount", "1000", "--password", utils.Password) + var receive utils.ArkReceive + receiveStr, err := runClarkCommand("receive") require.NoError(t, err) - err = utils.GenerateBlock() + + err = json.Unmarshal([]byte(receiveStr), &receive) require.NoError(t, err) + _, err = utils.RunCommand("nigiri", "faucet", receive.Boarding) + require.NoError(t, err) + + time.Sleep(5 * time.Second) + + _, err = runClarkCommand("claim", "--password", utils.Password) + require.NoError(t, err) + + time.Sleep(3 * time.Second) + var balance utils.ArkBalance balanceStr, err := runClarkCommand("balance") require.NoError(t, err) require.NoError(t, json.Unmarshal([]byte(balanceStr), &balance)) require.NotZero(t, balance.Offchain.Total) - require.Len(t, balance.Onchain.Locked, 0) _, err = runClarkCommand("redeem", "--force", "--password", utils.Password) require.NoError(t, err) @@ -149,35 +132,25 @@ func TestUnilateralExit(t *testing.T) { } func TestCollaborativeExit(t *testing.T) { - _, err := runClarkCommand("onboard", "--amount", "1000", "--password", utils.Password) - require.NoError(t, err) - err = utils.GenerateBlock() - require.NoError(t, err) - var receive utils.ArkReceive receiveStr, err := runClarkCommand("receive") require.NoError(t, err) - require.NoError(t, json.Unmarshal([]byte(receiveStr), &receive)) - var balance utils.ArkBalance - balanceStr, err := runClarkCommand("balance") + err = json.Unmarshal([]byte(receiveStr), &receive) require.NoError(t, err) - require.NoError(t, json.Unmarshal([]byte(balanceStr), &balance)) - balanceBefore := balance.Offchain.Total - balanceOnchainBefore := balance.Onchain.Spendable - - _, err = runClarkCommand("redeem", "--amount", "1000", "--address", receive.Onchain, "--password", utils.Password) + _, err = utils.RunCommand("nigiri", "faucet", receive.Boarding) require.NoError(t, err) time.Sleep(5 * time.Second) - balanceStr, err = runClarkCommand("balance") + _, err = runClarkCommand("claim", "--password", utils.Password) require.NoError(t, err) - require.NoError(t, json.Unmarshal([]byte(balanceStr), &balance)) - require.Equal(t, balanceBefore-1000, balance.Offchain.Total) - require.Equal(t, balanceOnchainBefore+1000, balance.Onchain.Spendable) + time.Sleep(3 * time.Second) + + _, err = runClarkCommand("redeem", "--amount", "1000", "--address", redeemAddress, "--password", utils.Password) + require.NoError(t, err) } func runClarkCommand(arg ...string) (string, error) { @@ -259,5 +232,27 @@ func setupAspWallet() error { return fmt.Errorf("failed to fund wallet: %s", err) } + _, err = utils.RunCommand("nigiri", "faucet", addr.Address) + if err != nil { + return fmt.Errorf("failed to fund wallet: %s", err) + } + + _, err = utils.RunCommand("nigiri", "faucet", addr.Address) + if err != nil { + return fmt.Errorf("failed to fund wallet: %s", err) + } + + _, err = utils.RunCommand("nigiri", "faucet", addr.Address) + if err != nil { + return fmt.Errorf("failed to fund wallet: %s", err) + } + + _, err = utils.RunCommand("nigiri", "faucet", addr.Address) + if err != nil { + return fmt.Errorf("failed to fund wallet: %s", err) + } + + time.Sleep(5 * time.Second) + return nil } diff --git a/server/test/e2e/test_utils.go b/server/test/e2e/test_utils.go index 7e94d37..284223f 100644 --- a/server/test/e2e/test_utils.go +++ b/server/test/e2e/test_utils.go @@ -28,11 +28,7 @@ type ArkBalance struct { type ArkReceive struct { Offchain string `json:"offchain_address"` - Onchain string `json:"onchain_address"` -} - -type ArkTrustedOnboard struct { - OnboardAddress string `json:"onboard_address"` + Boarding string `json:"boarding_address"` } func GenerateBlock() error {