diff --git a/api-spec/openapi/swagger/ark/v1/service.swagger.json b/api-spec/openapi/swagger/ark/v1/service.swagger.json index 9966c68..5937a58 100644 --- a/api-spec/openapi/swagger/ark/v1/service.swagger.json +++ b/api-spec/openapi/swagger/ark/v1/service.swagger.json @@ -1070,7 +1070,7 @@ "swept": { "type": "boolean" }, - "pending": { + "isOor": { "type": "boolean" }, "redeemTx": { diff --git a/api-spec/protobuf/ark/v1/service.proto b/api-spec/protobuf/ark/v1/service.proto index b924eb6..cb44c0d 100755 --- a/api-spec/protobuf/ark/v1/service.proto +++ b/api-spec/protobuf/ark/v1/service.proto @@ -318,7 +318,7 @@ message Vtxo { string spent_by = 4; int64 expire_at = 5; bool swept = 6; - bool pending = 7; + bool is_oor = 7; string redeem_tx = 8; uint64 amount = 9; string pubkey = 10; diff --git a/api-spec/protobuf/gen/ark/v1/service.pb.go b/api-spec/protobuf/gen/ark/v1/service.pb.go index 10e6dcc..2625f4a 100644 --- a/api-spec/protobuf/gen/ark/v1/service.pb.go +++ b/api-spec/protobuf/gen/ark/v1/service.pb.go @@ -2436,7 +2436,7 @@ type Vtxo struct { SpentBy string `protobuf:"bytes,4,opt,name=spent_by,json=spentBy,proto3" json:"spent_by,omitempty"` ExpireAt int64 `protobuf:"varint,5,opt,name=expire_at,json=expireAt,proto3" json:"expire_at,omitempty"` Swept bool `protobuf:"varint,6,opt,name=swept,proto3" json:"swept,omitempty"` - Pending bool `protobuf:"varint,7,opt,name=pending,proto3" json:"pending,omitempty"` + IsOor bool `protobuf:"varint,7,opt,name=is_oor,json=isOor,proto3" json:"is_oor,omitempty"` RedeemTx string `protobuf:"bytes,8,opt,name=redeem_tx,json=redeemTx,proto3" json:"redeem_tx,omitempty"` Amount uint64 `protobuf:"varint,9,opt,name=amount,proto3" json:"amount,omitempty"` Pubkey string `protobuf:"bytes,10,opt,name=pubkey,proto3" json:"pubkey,omitempty"` @@ -2516,9 +2516,9 @@ func (x *Vtxo) GetSwept() bool { return false } -func (x *Vtxo) GetPending() bool { +func (x *Vtxo) GetIsOor() bool { if x != nil { - return x.Pending + return x.IsOor } return false } @@ -3067,7 +3067,7 @@ var file_ark_v1_service_proto_rawDesc = []byte{ 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, 0x9e, 0x02, 0x0a, 0x04, 0x56, 0x74, 0x78, 0x6f, + 0x65, 0x6e, 0x74, 0x54, 0x78, 0x69, 0x64, 0x22, 0x9b, 0x02, 0x0a, 0x04, 0x56, 0x74, 0x78, 0x6f, 0x12, 0x2c, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x14, @@ -3079,177 +3079,177 @@ var file_ark_v1_service_proto_rawDesc = []byte{ 0x0a, 0x09, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x41, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x77, 0x65, 0x70, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x05, 0x73, 0x77, 0x65, 0x70, - 0x74, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x07, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x72, - 0x65, 0x64, 0x65, 0x65, 0x6d, 0x5f, 0x74, 0x78, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x72, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x54, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, - 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, - 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x22, 0x1e, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x54, - 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x8c, 0x01, 0x0a, 0x1d, 0x47, 0x65, 0x74, - 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x05, 0x72, 0x6f, - 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x72, 0x6b, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x33, 0x0a, 0x06, - 0x72, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, - 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x54, 0x72, 0x61, 0x6e, - 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x06, 0x72, 0x65, 0x64, 0x65, 0x65, - 0x6d, 0x42, 0x04, 0x0a, 0x02, 0x74, 0x78, 0x22, 0xd8, 0x01, 0x0a, 0x10, 0x52, 0x6f, 0x75, 0x6e, - 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, - 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, - 0x12, 0x31, 0x0a, 0x0b, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x5f, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4f, - 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x56, 0x74, - 0x78, 0x6f, 0x73, 0x12, 0x35, 0x0a, 0x0f, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, - 0x5f, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x03, 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, 0x46, 0x0a, 0x16, 0x63, 0x6c, - 0x61, 0x69, 0x6d, 0x65, 0x64, 0x5f, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x75, - 0x74, 0x78, 0x6f, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x72, 0x6b, - 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x14, 0x63, 0x6c, - 0x61, 0x69, 0x6d, 0x65, 0x64, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x55, 0x74, 0x78, - 0x6f, 0x73, 0x22, 0x91, 0x01, 0x0a, 0x11, 0x52, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x54, 0x72, 0x61, - 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x31, 0x0a, 0x0b, - 0x73, 0x70, 0x65, 0x6e, 0x74, 0x5f, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, - 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x12, - 0x35, 0x0a, 0x0f, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x74, 0x78, - 0x6f, 0x73, 0x18, 0x03, 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, 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, 0xeb, 0x0d, 0x0a, 0x0a, 0x41, 0x72, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 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, 0x12, 0x15, 0x0a, 0x06, 0x69, 0x73, 0x5f, 0x6f, 0x6f, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x05, 0x69, 0x73, 0x4f, 0x6f, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x64, 0x65, + 0x65, 0x6d, 0x5f, 0x74, 0x78, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x64, + 0x65, 0x65, 0x6d, 0x54, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x16, 0x0a, + 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, + 0x75, 0x62, 0x6b, 0x65, 0x79, 0x22, 0x1e, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x8c, 0x01, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x48, 0x00, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x33, 0x0a, 0x06, 0x72, 0x65, 0x64, + 0x65, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x61, 0x72, 0x6b, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x06, 0x72, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x42, 0x04, + 0x0a, 0x02, 0x74, 0x78, 0x22, 0xd8, 0x01, 0x0a, 0x10, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x31, 0x0a, + 0x0b, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x5f, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x0a, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, + 0x12, 0x35, 0x0a, 0x0f, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x74, + 0x78, 0x6f, 0x73, 0x18, 0x03, 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, 0x46, 0x0a, 0x16, 0x63, 0x6c, 0x61, 0x69, 0x6d, + 0x65, 0x64, 0x5f, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x75, 0x74, 0x78, 0x6f, + 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, + 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x14, 0x63, 0x6c, 0x61, 0x69, 0x6d, + 0x65, 0x64, 0x42, 0x6f, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x22, + 0x91, 0x01, 0x0a, 0x11, 0x52, 0x65, 0x64, 0x65, 0x65, 0x6d, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x69, 0x64, 0x12, 0x31, 0x0a, 0x0b, 0x73, 0x70, 0x65, + 0x6e, 0x74, 0x5f, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, + 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x52, 0x0a, 0x73, 0x70, 0x65, 0x6e, 0x74, 0x56, 0x74, 0x78, 0x6f, 0x73, 0x12, 0x35, 0x0a, 0x0f, + 0x73, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x74, 0x78, 0x6f, 0x73, 0x18, + 0x03, 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, 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, 0xeb, + 0x0d, 0x0a, 0x0a, 0x41, 0x72, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 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, 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, 0x98, 0x01, 0x0a, 0x1a, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, - 0x72, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x46, 0x6f, 0x72, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x6f, - 0x75, 0x6e, 0x64, 0x12, 0x29, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x46, 0x6f, 0x72, 0x4e, 0x65, - 0x78, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, - 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, - 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x46, 0x6f, 0x72, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x6f, 0x75, - 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x23, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x1d, 0x3a, 0x01, 0x2a, 0x22, 0x18, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, - 0x2f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, - 0x9c, 0x01, 0x0a, 0x1b, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x70, - 0x75, 0x74, 0x73, 0x46, 0x6f, 0x72, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x12, - 0x2a, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, - 0x72, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x46, 0x6f, 0x72, 0x4e, 0x65, 0x78, 0x74, 0x52, - 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x61, 0x72, - 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, + 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, 0x98, 0x01, 0x0a, 0x1a, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x46, 0x6f, 0x72, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, - 0x3a, 0x01, 0x2a, 0x22, 0x19, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x72, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x7d, - 0x0a, 0x10, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, - 0x65, 0x73, 0x12, 0x1f, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, - 0x69, 0x74, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, - 0x6d, 0x69, 0x74, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 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, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x74, 0x72, 0x65, 0x65, - 0x2f, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x8d, 0x01, - 0x0a, 0x14, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, - 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x23, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, - 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, - 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x61, 0x72, - 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x54, 0x72, 0x65, 0x65, 0x53, - 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x2a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x24, 0x3a, 0x01, 0x2a, 0x22, 0x1f, 0x2f, 0x76, - 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x74, 0x72, 0x65, 0x65, 0x2f, 0x73, 0x75, 0x62, - 0x6d, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x8e, 0x01, - 0x0a, 0x16, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x46, 0x6f, - 0x72, 0x66, 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x12, 0x25, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, - 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x46, 0x6f, - 0x72, 0x66, 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x26, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x53, - 0x69, 0x67, 0x6e, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x3a, - 0x01, 0x2a, 0x22, 0x1a, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x73, 0x75, - 0x62, 0x6d, 0x69, 0x74, 0x46, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x12, 0x6b, - 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, - 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x12, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, - 0x6e, 0x64, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x30, 0x01, 0x12, 0x56, 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, 0x23, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x12, 0x1b, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, - 0x64, 0x2f, 0x70, 0x69, 0x6e, 0x67, 0x2f, 0x7b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, - 0x69, 0x64, 0x7d, 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, 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, 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, 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, 0x80, 0x01, 0x0a, - 0x15, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x24, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, - 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, - 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x61, - 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x12, 0x10, 0x2f, 0x76, 0x31, - 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x30, 0x01, 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, + 0x12, 0x29, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, + 0x65, 0x72, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x46, 0x6f, 0x72, 0x4e, 0x65, 0x78, 0x74, 0x52, + 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x61, 0x72, + 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x70, + 0x75, 0x74, 0x73, 0x46, 0x6f, 0x72, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x23, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x3a, + 0x01, 0x2a, 0x22, 0x18, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x72, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x9c, 0x01, 0x0a, + 0x1b, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, + 0x46, 0x6f, 0x72, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x2a, 0x2e, 0x61, + 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x73, 0x46, 0x6f, 0x72, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x6f, 0x75, 0x6e, + 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, + 0x73, 0x46, 0x6f, 0x72, 0x4e, 0x65, 0x78, 0x74, 0x52, 0x6f, 0x75, 0x6e, 0x64, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x24, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x3a, 0x01, 0x2a, + 0x22, 0x19, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x72, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x65, 0x72, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x7d, 0x0a, 0x10, 0x53, + 0x75, 0x62, 0x6d, 0x69, 0x74, 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x12, + 0x1f, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x54, + 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x20, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, + 0x54, 0x72, 0x65, 0x65, 0x4e, 0x6f, 0x6e, 0x63, 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, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x74, 0x72, 0x65, 0x65, 0x2f, 0x73, 0x75, + 0x62, 0x6d, 0x69, 0x74, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x8d, 0x01, 0x0a, 0x14, 0x53, + 0x75, 0x62, 0x6d, 0x69, 0x74, 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x73, 0x12, 0x23, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, + 0x6d, 0x69, 0x74, 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, + 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x54, 0x72, 0x65, 0x65, 0x53, 0x69, 0x67, 0x6e, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2a, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x24, 0x3a, 0x01, 0x2a, 0x22, 0x1f, 0x2f, 0x76, 0x31, 0x2f, 0x72, + 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x74, 0x72, 0x65, 0x65, 0x2f, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, + 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x12, 0x8e, 0x01, 0x0a, 0x16, 0x53, + 0x75, 0x62, 0x6d, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x66, 0x65, + 0x69, 0x74, 0x54, 0x78, 0x73, 0x12, 0x25, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, + 0x75, 0x62, 0x6d, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x66, 0x65, + 0x69, 0x74, 0x54, 0x78, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x26, 0x2e, 0x61, + 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x53, 0x69, 0x67, 0x6e, + 0x65, 0x64, 0x46, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x25, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1f, 0x3a, 0x01, 0x2a, 0x22, + 0x1a, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x73, 0x75, 0x62, 0x6d, 0x69, + 0x74, 0x46, 0x6f, 0x72, 0x66, 0x65, 0x69, 0x74, 0x54, 0x78, 0x73, 0x12, 0x6b, 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, 0x18, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x12, 0x12, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x30, 0x01, 0x12, 0x56, 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, 0x23, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x1d, 0x12, 0x1b, 0x2f, 0x76, 0x31, 0x2f, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x70, + 0x69, 0x6e, 0x67, 0x2f, 0x7b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x7d, + 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, 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, 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, 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, 0x80, 0x01, 0x0a, 0x15, 0x47, 0x65, + 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x12, 0x24, 0x2e, 0x61, 0x72, 0x6b, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x61, 0x72, 0x6b, 0x2e, + 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x12, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x30, 0x01, 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 ( diff --git a/client/main.go b/client/main.go index 41c55de..fdc2eb1 100644 --- a/client/main.go +++ b/client/main.go @@ -39,7 +39,7 @@ func main() { &configCommand, &dumpCommand, &receiveCommand, - &claimCmd, + &settleCmd, &sendCommand, &balanceCommand, &redeemCommand, @@ -160,11 +160,11 @@ var ( return receive(ctx) }, } - claimCmd = cli.Command{ - Name: "claim", - Usage: "Claim onboarding funds or pending payments", + settleCmd = cli.Command{ + Name: "settle", + Usage: "Settle onboarding funds or oor payments", Action: func(ctx *cli.Context) error { - return claim(ctx) + return settle(ctx) }, Flags: []cli.Flag{passwordFlag}, } @@ -266,7 +266,7 @@ func receive(ctx *cli.Context) error { }) } -func claim(ctx *cli.Context) error { +func settle(ctx *cli.Context) error { password, err := readPassword(ctx) if err != nil { return err @@ -275,7 +275,7 @@ func claim(ctx *cli.Context) error { return err } - txID, err := arkSdkClient.Claim(ctx.Context) + txID, err := arkSdkClient.Settle(ctx.Context) if err != nil { return err } diff --git a/common/bitcointree/vtxo.go b/common/bitcointree/vtxo.go index f57d1ad..a8bf770 100644 --- a/common/bitcointree/vtxo.go +++ b/common/bitcointree/vtxo.go @@ -15,20 +15,17 @@ import ( type VtxoScript common.VtxoScript[bitcoinTapTree] func ParseVtxoScript(desc string) (VtxoScript, error) { - v := &DefaultVtxoScript{} - // TODO add other type - err := v.FromDescriptor(desc) - if err != nil { - v := &ReversibleVtxoScript{} - err = v.FromDescriptor(desc) - if err != nil { - return nil, fmt.Errorf("invalid vtxo descriptor: %s", desc) - } - - return v, nil + types := []VtxoScript{ + &DefaultVtxoScript{}, } - return v, nil + for _, v := range types { + if err := v.FromDescriptor(desc); err == nil { + return v, nil + } + } + + return nil, fmt.Errorf("invalid vtxo descriptor: %s", desc) } /* @@ -101,94 +98,6 @@ func (v *DefaultVtxoScript) TapTree() (*secp256k1.PublicKey, bitcoinTapTree, err return taprootKey, bitcoinTapTree{tapTree}, nil } -/* -* ReversibleVtxoScript allows sender of the VTXO to revert the transaction -* unilateral exit is in favor of the sender -* - Owner and ASP (forfeit owner) -* - Sender and ASP (forfeit sender) -* - Sender after t (unilateral exit) - */ -type ReversibleVtxoScript struct { - Asp *secp256k1.PublicKey - Sender *secp256k1.PublicKey - Owner *secp256k1.PublicKey - ExitDelay uint -} - -func (v *ReversibleVtxoScript) ToDescriptor() string { - owner := hex.EncodeToString(schnorr.SerializePubKey(v.Owner)) - sender := hex.EncodeToString(schnorr.SerializePubKey(v.Sender)) - asp := hex.EncodeToString(schnorr.SerializePubKey(v.Asp)) - - return fmt.Sprintf( - descriptor.ReversibleVtxoScriptTemplate, - hex.EncodeToString(UnspendableKey().SerializeCompressed()), - sender, - asp, - v.ExitDelay, - sender, - owner, - asp, - ) -} - -func (v *ReversibleVtxoScript) FromDescriptor(desc string) error { - owner, sender, asp, exitDelay, err := descriptor.ParseReversibleVtxoDescriptor(desc) - if err != nil { - return err - } - - v.Owner = owner - v.Sender = sender - v.Asp = asp - v.ExitDelay = exitDelay - return nil -} - -func (v *ReversibleVtxoScript) TapTree() (*secp256k1.PublicKey, bitcoinTapTree, error) { - redeemClosure := &CSVSigClosure{ - Pubkey: v.Sender, - Seconds: v.ExitDelay, - } - - redeemLeaf, err := redeemClosure.Leaf() - if err != nil { - return nil, bitcoinTapTree{}, err - } - - forfeitClosure := &MultisigClosure{ - Pubkey: v.Owner, - AspPubkey: v.Asp, - } - - forfeitLeaf, err := forfeitClosure.Leaf() - if err != nil { - return nil, bitcoinTapTree{}, err - } - - reverseForfeitClosure := &MultisigClosure{ - Pubkey: v.Sender, - AspPubkey: v.Asp, - } - - reverseForfeitLeaf, err := reverseForfeitClosure.Leaf() - if err != nil { - return nil, bitcoinTapTree{}, err - } - - tapTree := txscript.AssembleTaprootScriptTree( - *redeemLeaf, *forfeitLeaf, *reverseForfeitLeaf, - ) - - root := tapTree.RootNode.TapHash() - taprootKey := txscript.ComputeTaprootOutputKey( - UnspendableKey(), - root[:], - ) - - return taprootKey, bitcoinTapTree{tapTree}, nil -} - // bitcoinTapTree is a wrapper around txscript.IndexedTapScriptTree to implement the common.TaprootTree interface type bitcoinTapTree struct { *txscript.IndexedTapScriptTree diff --git a/common/bitcointree/vtxo_test.go b/common/bitcointree/vtxo_test.go index 927824e..271b903 100644 --- a/common/bitcointree/vtxo_test.go +++ b/common/bitcointree/vtxo_test.go @@ -19,12 +19,8 @@ func TestParseDescriptor(t *testing.T) { aliceKey, err := secp256k1.GeneratePrivateKey() require.NoError(t, err) - bobKey, err := secp256k1.GeneratePrivateKey() - require.NoError(t, err) - aspPubKey := hex.EncodeToString(schnorr.SerializePubKey(aspKey.PubKey())) alicePubKey := hex.EncodeToString(schnorr.SerializePubKey(aliceKey.PubKey())) - bobPubKey := hex.EncodeToString(schnorr.SerializePubKey(bobKey.PubKey())) unspendableKey := hex.EncodeToString(bitcointree.UnspendableKey().SerializeCompressed()) @@ -44,24 +40,4 @@ func TestParseDescriptor(t *testing.T) { require.Equal(t, defaultScriptDescriptor, vtxo.ToDescriptor()) require.Equal(t, alicePubKey, hex.EncodeToString(schnorr.SerializePubKey(vtxo.(*bitcointree.DefaultVtxoScript).Owner))) require.Equal(t, aspPubKey, hex.EncodeToString(schnorr.SerializePubKey(vtxo.(*bitcointree.DefaultVtxoScript).Asp))) - - reversibleScriptDescriptor := fmt.Sprintf( - descriptor.ReversibleVtxoScriptTemplate, - unspendableKey, - alicePubKey, - aspPubKey, - 512, - alicePubKey, - bobPubKey, - aspPubKey, - ) - - vtxo, err = bitcointree.ParseVtxoScript(reversibleScriptDescriptor) - require.NoError(t, err) - - require.IsType(t, &bitcointree.ReversibleVtxoScript{}, vtxo) - require.Equal(t, reversibleScriptDescriptor, vtxo.ToDescriptor()) - require.Equal(t, alicePubKey, hex.EncodeToString(schnorr.SerializePubKey(vtxo.(*bitcointree.ReversibleVtxoScript).Sender))) - require.Equal(t, bobPubKey, hex.EncodeToString(schnorr.SerializePubKey(vtxo.(*bitcointree.ReversibleVtxoScript).Owner))) - require.Equal(t, aspPubKey, hex.EncodeToString(schnorr.SerializePubKey(vtxo.(*bitcointree.ReversibleVtxoScript).Asp))) } diff --git a/common/descriptor/ark.go b/common/descriptor/ark.go index 2b1da0d..f2805cd 100644 --- a/common/descriptor/ark.go +++ b/common/descriptor/ark.go @@ -11,91 +11,6 @@ import ( // tr(unspendable, { and(pk(user), pk(asp)), and(older(timeout), pk(user)) }) const DefaultVtxoDescriptorTemplate = "tr(%s,{ and(pk(%s), pk(%s)), and(older(%d), pk(%s)) })" -// tr(unspendable, { { and(pk(sender), pk(asp)), and(older(timeout), pk(sender)) }, and(pk(receiver), pk(asp)) }) -const ReversibleVtxoScriptTemplate = "tr(%s,{ { and(pk(%s), pk(%s)), and(older(%d), pk(%s)) }, and(pk(%s), pk(%s)) })" - -func ParseReversibleVtxoDescriptor( - descriptor string, -) (user, sender, asp *secp256k1.PublicKey, timeout uint, err error) { - desc, err := ParseTaprootDescriptor(descriptor) - if err != nil { - return nil, nil, nil, 0, err - } - - if len(desc.ScriptTree) != 3 { - return nil, nil, nil, 0, errors.New("not a reversible vtxo script descriptor") - } - - for _, leaf := range desc.ScriptTree { - if andLeaf, ok := leaf.(*And); ok { - if first, ok := andLeaf.First.(*PK); ok { - if second, ok := andLeaf.Second.(*PK); ok { - keyBytes, err := hex.DecodeString(first.Key.Hex) - if err != nil { - return nil, nil, nil, 0, err - } - if sender == nil { - sender, err = schnorr.ParsePubKey(keyBytes) - if err != nil { - return nil, nil, nil, 0, err - } - } else { - user, err = schnorr.ParsePubKey(keyBytes) - if err != nil { - return nil, nil, nil, 0, err - } - } - - if asp == nil { - keyBytes, err = hex.DecodeString(second.Key.Hex) - if err != nil { - return nil, nil, nil, 0, err - } - - asp, err = schnorr.ParsePubKey(keyBytes) - if err != nil { - return nil, nil, nil, 0, err - } - } - } - } - - if first, ok := andLeaf.First.(*Older); ok { - if second, ok := andLeaf.Second.(*PK); ok { - timeout = first.Timeout - keyBytes, err := hex.DecodeString(second.Key.Hex) - if err != nil { - return nil, nil, nil, 0, err - } - - sender, err = schnorr.ParsePubKey(keyBytes) - if err != nil { - return nil, nil, nil, 0, err - } - } - } - } - } - - if user == nil { - return nil, nil, nil, 0, errors.New("descriptor is invalid") - } - - if asp == nil { - return nil, nil, nil, 0, errors.New("descriptor is invalid") - } - - if timeout == 0 { - return nil, nil, nil, 0, errors.New("descriptor is invalid") - } - - if sender == nil { - return nil, nil, nil, 0, errors.New("descriptor is invalid") - } - - return -} - func ParseDefaultVtxoDescriptor( descriptor string, ) (user, asp *secp256k1.PublicKey, timeout uint, err error) { diff --git a/pkg/client-sdk/ark_sdk.go b/pkg/client-sdk/ark_sdk.go index 87eca1b..5a0c415 100644 --- a/pkg/client-sdk/ark_sdk.go +++ b/pkg/client-sdk/ark_sdk.go @@ -25,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) - Claim(ctx context.Context) (string, error) + Settle(ctx context.Context) (string, error) ListVtxos(ctx context.Context) (spendable, spent []client.Vtxo, err error) Dump(ctx context.Context) (seed string, err error) GetTransactionHistory(ctx context.Context) ([]types.Transaction, error) diff --git a/pkg/client-sdk/client.go b/pkg/client-sdk/client.go index 92ce9b5..d03b89b 100644 --- a/pkg/client-sdk/client.go +++ b/pkg/client-sdk/client.go @@ -270,6 +270,27 @@ func (a *arkClient) ping( return ticker.Stop } +func (a *arkClient) ListVtxos( + ctx context.Context, +) (spendableVtxos, spentVtxos []client.Vtxo, err error) { + offchainAddrs, _, _, err := a.wallet.GetAddresses(ctx) + if err != nil { + return + } + + for _, addr := range offchainAddrs { + spendable, spent, err := a.client.ListVtxos(ctx, addr.Address) + if err != nil { + return nil, nil, err + } + + spendableVtxos = append(spendableVtxos, spendable...) + spentVtxos = append(spentVtxos, spent...) + } + + return +} + func getClient( supportedClients utils.SupportedType[utils.ClientFactory], clientType, aspUrl string, ) (client.ASPClient, error) { @@ -329,3 +350,15 @@ func getWalletStore(storeType, datadir string) (walletstore.WalletStore, error) func getCreatedAtFromExpiry(roundLifetime int64, expiry time.Time) time.Time { return expiry.Add(-time.Duration(roundLifetime) * time.Second) } + +func filterByOutpoints(vtxos []client.Vtxo, outpoints []client.Outpoint) []client.Vtxo { + filtered := make([]client.Vtxo, 0, len(vtxos)) + for _, vtxo := range vtxos { + for _, outpoint := range outpoints { + if vtxo.Outpoint.Equals(outpoint) { + filtered = append(filtered, vtxo) + } + } + } + return filtered +} diff --git a/pkg/client-sdk/client/client.go b/pkg/client-sdk/client/client.go index d832ef7..e29175a 100644 --- a/pkg/client-sdk/client/client.go +++ b/pkg/client-sdk/client/client.go @@ -78,6 +78,10 @@ type Outpoint struct { VOut uint32 } +func (o Outpoint) Equals(other Outpoint) bool { + return o.Txid == other.Txid && o.VOut == other.VOut +} + type Input struct { Outpoint Descriptor string @@ -95,7 +99,7 @@ type Vtxo struct { RoundTxid string ExpiresAt *time.Time RedeemTx string - Pending bool + IsOOR bool SpentBy string } diff --git a/pkg/client-sdk/client/grpc/client.go b/pkg/client-sdk/client/grpc/client.go index 30e352f..9250702 100644 --- a/pkg/client-sdk/client/grpc/client.go +++ b/pkg/client-sdk/client/grpc/client.go @@ -359,7 +359,7 @@ func (c *grpcClient) GetTransactionsStream( Round: &client.RoundTransaction{ Txid: tx.Round.Txid, SpentVtxos: outpointsFromProto(tx.Round.SpentVtxos), - SpendableVtxos: vtxosFromProto(tx.Round.SpendableVtxos), + SpendableVtxos: vtxos(tx.Round.SpendableVtxos).toVtxos(), ClaimedBoardingUtxos: outpointsFromProto(tx.Round.ClaimedBoardingUtxos), }, } @@ -368,7 +368,7 @@ func (c *grpcClient) GetTransactionsStream( Redeem: &client.RedeemTransaction{ Txid: tx.Redeem.Txid, SpentVtxos: outpointsFromProto(tx.Redeem.SpentVtxos), - SpendableVtxos: vtxosFromProto(tx.Redeem.SpendableVtxos), + SpendableVtxos: vtxos(tx.Redeem.SpendableVtxos).toVtxos(), }, } } @@ -394,24 +394,3 @@ func outpointsFromProto(protoOutpoints []*arkv1.Outpoint) []client.Outpoint { } return outpoints } - -func vtxosFromProto(protoVtxos []*arkv1.Vtxo) []client.Vtxo { - vtxos := make([]client.Vtxo, len(protoVtxos)) - for i, v := range protoVtxos { - expiresAt := time.Unix(v.ExpireAt, 0) - vtxos[i] = client.Vtxo{ - Outpoint: client.Outpoint{ - Txid: v.Outpoint.Txid, - VOut: v.Outpoint.Vout, - }, - Pubkey: v.Pubkey, - Amount: v.Amount, - RoundTxid: v.RoundTxid, - ExpiresAt: &expiresAt, - RedeemTx: v.RedeemTx, - Pending: v.Pending, - SpentBy: v.SpentBy, - } - } - return vtxos -} diff --git a/pkg/client-sdk/client/grpc/types.go b/pkg/client-sdk/client/grpc/types.go index 4b13dae..6070af7 100644 --- a/pkg/client-sdk/client/grpc/types.go +++ b/pkg/client-sdk/client/grpc/types.go @@ -125,7 +125,7 @@ func (v vtxo) toVtxo() client.Vtxo { Amount: v.GetAmount(), RoundTxid: v.GetRoundTxid(), ExpiresAt: expiresAt, - Pending: v.GetPending(), + IsOOR: v.GetIsOor(), RedeemTx: v.GetRedeemTx(), SpentBy: v.GetSpentBy(), Pubkey: v.GetPubkey(), diff --git a/pkg/client-sdk/client/rest/client.go b/pkg/client-sdk/client/rest/client.go index 73458c4..ec8a6f8 100644 --- a/pkg/client-sdk/client/rest/client.go +++ b/pkg/client-sdk/client/rest/client.go @@ -499,7 +499,7 @@ func (a *restClient) ListVtxos( Amount: uint64(amount), RoundTxid: v.RoundTxid, ExpiresAt: expiresAt, - Pending: v.Pending, + IsOOR: v.IsOor, RedeemTx: v.RedeemTx, SpentBy: v.SpentBy, Pubkey: v.Pubkey, @@ -701,7 +701,7 @@ func vtxosFromRest(restVtxos []*models.V1Vtxo) []client.Vtxo { RoundTxid: v.RoundTxid, ExpiresAt: expiresAt, RedeemTx: v.RedeemTx, - Pending: v.Pending, + IsOOR: v.IsOor, SpentBy: v.SpentBy, } } diff --git a/pkg/client-sdk/client/rest/service/models/v1_vtxo.go b/pkg/client-sdk/client/rest/service/models/v1_vtxo.go index 5bca48d..5ae090e 100644 --- a/pkg/client-sdk/client/rest/service/models/v1_vtxo.go +++ b/pkg/client-sdk/client/rest/service/models/v1_vtxo.go @@ -24,12 +24,12 @@ type V1Vtxo struct { // expire at ExpireAt string `json:"expireAt,omitempty"` + // is oor + IsOor bool `json:"isOor,omitempty"` + // outpoint Outpoint *V1Outpoint `json:"outpoint,omitempty"` - // pending - Pending bool `json:"pending,omitempty"` - // pubkey Pubkey string `json:"pubkey,omitempty"` diff --git a/pkg/client-sdk/client_test.go b/pkg/client-sdk/client_test.go index b615bb5..aa889fe 100644 --- a/pkg/client-sdk/client_test.go +++ b/pkg/client-sdk/client_test.go @@ -33,7 +33,6 @@ func TestVtxosToTxs(t *testing.T) { }, Amount: 1000, Type: sdktypes.TxSent, - IsPending: false, CreatedAt: time.Unix(1726054898, 0), }, }, @@ -48,7 +47,6 @@ func TestVtxosToTxs(t *testing.T) { }, Amount: 1000, Type: sdktypes.TxReceived, - IsPending: true, CreatedAt: time.Unix(1726054898, 0), }, { @@ -57,7 +55,6 @@ func TestVtxosToTxs(t *testing.T) { }, Amount: 2000, Type: sdktypes.TxReceived, - IsPending: true, CreatedAt: time.Unix(1726486359, 0), }, }, @@ -72,7 +69,6 @@ func TestVtxosToTxs(t *testing.T) { }, Amount: 1000, Type: sdktypes.TxReceived, - IsPending: false, CreatedAt: time.Unix(1726054898, 0), }, { @@ -81,7 +77,6 @@ func TestVtxosToTxs(t *testing.T) { }, Amount: 2000, Type: sdktypes.TxReceived, - IsPending: false, CreatedAt: time.Unix(1726486359, 0), }, }, @@ -96,7 +91,6 @@ func TestVtxosToTxs(t *testing.T) { }, Amount: 1000, Type: sdktypes.TxReceived, - IsPending: false, CreatedAt: time.Unix(1726054898, 0), }, { @@ -105,7 +99,6 @@ func TestVtxosToTxs(t *testing.T) { }, Amount: 2000, Type: sdktypes.TxReceived, - IsPending: false, CreatedAt: time.Unix(1726486359, 0), }, { @@ -114,7 +107,6 @@ func TestVtxosToTxs(t *testing.T) { }, Amount: 2100, Type: sdktypes.TxSent, - IsPending: false, CreatedAt: time.Unix(1726503865, 0), }, }, @@ -138,7 +130,6 @@ func TestVtxosToTxs(t *testing.T) { require.Equal(t, wantTx.RedeemTxid, gotTx.RedeemTxid) require.Equal(t, int(wantTx.Amount), int(gotTx.Amount)) require.Equal(t, wantTx.Type, gotTx.Type) - require.Equal(t, wantTx.IsPending, gotTx.IsPending) } }) } @@ -161,16 +152,12 @@ func loadFixtures(jsonStr string) (vtxos, map[string]struct{}, error) { Address string `json:"address"` Amount string `json:"amount"` } `json:"receiver"` - Spent bool `json:"spent"` - PoolTxid string `json:"poolTxid"` - SpentBy string `json:"spentBy"` - ExpireAt string `json:"expireAt"` - Swept bool `json:"swept"` - Pending bool `json:"pending"` - PendingData struct { - RedeemTx string `json:"redeemTx"` - UnconditionalForfeitTxs []string `json:"unconditionalForfeitTxs"` - } `json:"pendingData"` + Spent bool `json:"spent"` + PoolTxid string `json:"poolTxid"` + SpentBy string `json:"spentBy"` + ExpireAt string `json:"expireAt"` + Swept bool `json:"swept"` + RedeemTx string `json:"redeemTx"` } `json:"spendableVtxos"` SpentVtxos []struct { Outpoint struct { @@ -181,16 +168,12 @@ func loadFixtures(jsonStr string) (vtxos, map[string]struct{}, error) { Address string `json:"address"` Amount string `json:"amount"` } `json:"receiver"` - Spent bool `json:"spent"` - PoolTxid string `json:"poolTxid"` - SpentBy string `json:"spentBy"` - ExpireAt string `json:"expireAt"` - Swept bool `json:"swept"` - Pending bool `json:"pending"` - PendingData struct { - RedeemTx string `json:"redeemTx"` - UnconditionalForfeitTxs []string `json:"unconditionalForfeitTxs"` - } `json:"pendingData"` + Spent bool `json:"spent"` + PoolTxid string `json:"poolTxid"` + SpentBy string `json:"spentBy"` + ExpireAt string `json:"expireAt"` + Swept bool `json:"swept"` + RedeemTx string `json:"redeemTx"` } `json:"spentVtxos"` } @@ -216,8 +199,7 @@ func loadFixtures(jsonStr string) (vtxos, map[string]struct{}, error) { Amount: amount, RoundTxid: vtxo.PoolTxid, ExpiresAt: &expireAt, - RedeemTx: vtxo.PendingData.RedeemTx, - Pending: vtxo.Pending, + RedeemTx: vtxo.RedeemTx, SpentBy: vtxo.SpentBy, } } @@ -240,8 +222,7 @@ func loadFixtures(jsonStr string) (vtxos, map[string]struct{}, error) { Amount: amount, RoundTxid: vtxo.PoolTxid, ExpiresAt: &expireAt, - RedeemTx: vtxo.PendingData.RedeemTx, - Pending: vtxo.Pending, + RedeemTx: vtxo.RedeemTx, SpentBy: vtxo.SpentBy, } } @@ -301,9 +282,7 @@ var ( "poolTxid": "377fa2fbd27c82bdbc095478384c88b6c75432c0ef464189e49c965194446cdf", "spentBy": "", "expireAt": "1726054928", - "swept": false, - "pending": false, - "pendingData": null + "swept": false } ], "spentVtxos": [] @@ -329,13 +308,7 @@ var ( "spentBy": "", "expireAt": "1726054928", "swept": false, - "pending": false, - "pendingData": { - "redeemTx": "cHNidP8BAIkCAAAAAWwxXUjG5tidFA0LmUljX//jwW6xWaS6HKyRCw5StsxpAAAAAAD/////AugDAAAAAAAAIlEgt2eR8LtqTP7yUcQtSydeGrRiHnVmHHnZwYjdC23G7MZwSQAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFAAAAAAABASsgTgAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFIgYDp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShcYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSvBIBey3T0IV353FkuGLMmMJFpqHTrliIsJwJsfJkzq7J0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHQAFqkBLiRmP3AZ8MS77s1QIWZswMV3L72D9gN0f0MbD6XHkmzZeC1clF3uzxr+13wsF0vcFe29Zl3e2gAhMNGYVCFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wKRtST8P7teUpSF4DAEbfJj5OIXITx5QGbZns/AtxqGyRSCn2zP0K2jsWEX4L3b1j+MnDXORFbGro1RF32RfTmZKF60grwSAXst09CFd+dxZLhizJjCRaah065YiLCcCbHyZM6uswCEWp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShc5AbJ0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA=", - "unconditionalForfeitTxs": [ - "cHNidP8BAFICAAAAAWwxXUjG5tidFA0LmUljX//jwW6xWaS6HKyRCw5StsxpAAAAAAD/////AVhNAAAAAAAAFgAUSU38/3Mzx5BdILG4oUO+JoHcoT8AAAAAAAEBKyBOAAAAAAAAIlEgp9TN/+j2H6vS/3LiebI632o7wSQO6ZA9BkZNsS/x9IVBFK8EgF7LdPQhXfncWS4YsyYwkWmodOuWIiwnAmx8mTOrsnQHxtDSP3zjaHmVR85ZxuPZN6gXHo4KmCgcipYgGEdAjH8Mg1Z3GdjGzp78Mg2xq1fop9KDfeji+xoyMgYS7q0Nl0AGOAaNzkDRW4cNcefll5jZC2i3nfygKdXsUsR+LEIVwVCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrApG1JPw/u15SlIXgMARt8mPk4hchPHlAZtmez8C3GobJFIKfbM/QraOxYRfgvdvWP4ycNc5EVsaujVEXfZF9OZkoXrSCvBIBey3T0IV353FkuGLMmMJFpqHTrliIsJwJsfJkzq6zAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAA==" - ] - } + "redeemTx": "cHNidP8BAIkCAAAAAWwxXUjG5tidFA0LmUljX//jwW6xWaS6HKyRCw5StsxpAAAAAAD/////AugDAAAAAAAAIlEgt2eR8LtqTP7yUcQtSydeGrRiHnVmHHnZwYjdC23G7MZwSQAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFAAAAAAABASsgTgAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFIgYDp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShcYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSvBIBey3T0IV353FkuGLMmMJFpqHTrliIsJwJsfJkzq7J0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHQAFqkBLiRmP3AZ8MS77s1QIWZswMV3L72D9gN0f0MbD6XHkmzZeC1clF3uzxr+13wsF0vcFe29Zl3e2gAhMNGYVCFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wKRtST8P7teUpSF4DAEbfJj5OIXITx5QGbZns/AtxqGyRSCn2zP0K2jsWEX4L3b1j+MnDXORFbGro1RF32RfTmZKF60grwSAXst09CFd+dxZLhizJjCRaah065YiLCcCbHyZM6uswCEWp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShc5AbJ0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA=" } ], "spentVtxos": [ @@ -353,8 +326,7 @@ var ( "spentBy": "94fa598302f17f00c8881e742ec0ce2f8c8d16f3d54fe6ba0fb7d13a493d84ad", "expireAt": "1726054928", "swept": false, - "pending": false, - "pendingData": null + "redeemTx": "" } ] }` @@ -376,13 +348,7 @@ var ( "spentBy": "", "expireAt": "1726054928", "swept": false, - "pending": true, - "pendingData": { - "redeemTx": "cHNidP8BAIkCAAAAAWwxXUjG5tidFA0LmUljX//jwW6xWaS6HKyRCw5StsxpAAAAAAD/////AugDAAAAAAAAIlEgt2eR8LtqTP7yUcQtSydeGrRiHnVmHHnZwYjdC23G7MZwSQAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFAAAAAAABASsgTgAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFIgYDp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShcYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSvBIBey3T0IV353FkuGLMmMJFpqHTrliIsJwJsfJkzq7J0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHQAFqkBLiRmP3AZ8MS77s1QIWZswMV3L72D9gN0f0MbD6XHkmzZeC1clF3uzxr+13wsF0vcFe29Zl3e2gAhMNGYVCFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wKRtST8P7teUpSF4DAEbfJj5OIXITx5QGbZns/AtxqGyRSCn2zP0K2jsWEX4L3b1j+MnDXORFbGro1RF32RfTmZKF60grwSAXst09CFd+dxZLhizJjCRaah065YiLCcCbHyZM6uswCEWp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShc5AbJ0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA=", - "unconditionalForfeitTxs": [ - "cHNidP8BAFICAAAAAWwxXUjG5tidFA0LmUljX//jwW6xWaS6HKyRCw5StsxpAAAAAAD/////AVhNAAAAAAAAFgAUSU38/3Mzx5BdILG4oUO+JoHcoT8AAAAAAAEBKyBOAAAAAAAAIlEgp9TN/+j2H6vS/3LiebI632o7wSQO6ZA9BkZNsS/x9IVBFK8EgF7LdPQhXfncWS4YsyYwkWmodOuWIiwnAmx8mTOrsnQHxtDSP3zjaHmVR85ZxuPZN6gXHo4KmCgcipYgGEdAjH8Mg1Z3GdjGzp78Mg2xq1fop9KDfeji+xoyMgYS7q0Nl0AGOAaNzkDRW4cNcefll5jZC2i3nfygKdXsUsR+LEIVwVCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrApG1JPw/u15SlIXgMARt8mPk4hchPHlAZtmez8C3GobJFIKfbM/QraOxYRfgvdvWP4ycNc5EVsaujVEXfZF9OZkoXrSCvBIBey3T0IV353FkuGLMmMJFpqHTrliIsJwJsfJkzq6zAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAA==" - ] - } + "redeemTx": "cHNidP8BAIkCAAAAAWwxXUjG5tidFA0LmUljX//jwW6xWaS6HKyRCw5StsxpAAAAAAD/////AugDAAAAAAAAIlEgt2eR8LtqTP7yUcQtSydeGrRiHnVmHHnZwYjdC23G7MZwSQAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFAAAAAAABASsgTgAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFIgYDp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShcYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSvBIBey3T0IV353FkuGLMmMJFpqHTrliIsJwJsfJkzq7J0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHQAFqkBLiRmP3AZ8MS77s1QIWZswMV3L72D9gN0f0MbD6XHkmzZeC1clF3uzxr+13wsF0vcFe29Zl3e2gAhMNGYVCFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wKRtST8P7teUpSF4DAEbfJj5OIXITx5QGbZns/AtxqGyRSCn2zP0K2jsWEX4L3b1j+MnDXORFbGro1RF32RfTmZKF60grwSAXst09CFd+dxZLhizJjCRaah065YiLCcCbHyZM6uswCEWp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShc5AbJ0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA=" }, { "outpoint": { @@ -398,13 +364,7 @@ var ( "spentBy": "", "expireAt": "1726486389", "swept": false, - "pending": true, - "pendingData": { - "redeemTx": "cHNidP8BAIkCAAAAARH6LJRGP/pFIkD/o5bBp8fXAhjl8yjfN7MhJsxdt5lrAQAAAAD/////AtAHAAAAAAAAIlEguuBh3KQUVZp+NHV2sixQ/mrsngCuLCGXzsgJPC1FzY7ANQ8AAAAAACJRIP7uXXtl4jLUcVVQU+sX7WFXmx2H6iCMzn7gye0v1Y8JAAAAAAABAStYPg8AAAAAACJRIP7uXXtl4jLUcVVQU+sX7WFXmx2H6iCMzn7gye0v1Y8JIgYCHVQFdXiyvwIlLdnji8FvbUgb+ECuTRNAB67owJnjcaMYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSc1yjQ/vHRHev23fKGANLvbOhkNYmGtmRWt8fSszlOJUzRFbnfxd1fq9gIEpaI0vrZww8tlZ94iEL75QoaIbVqQJsPdLYf7fAoXO82VoqwYHu1WevE4g6LxUGBPzfd96q5EEZkoW5qqg+v5dWJUEY467Q6qZLFHwziUaB3KEY8yEpCFcBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wO5D2Mh3x0XNGxFCS67GNughkENFodpFeVpZjn76chI8RSAdVAV1eLK/AiUt2eOLwW9tSBv4QK5NE0AHrujAmeNxo60gnNco0P7x0R3r9t3yhgDS72zoZDWJhrZkVrfH0rM5TiWswCEWHVQFdXiyvwIlLdnji8FvbUgb+ECuTRNAB67owJnjcaM5AUzRFbnfxd1fq9gIEpaI0vrZww8tlZ94iEL75QoaIbVqAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA=", - "unconditionalForfeitTxs": [ - "cHNidP8BAFICAAAAARH6LJRGP/pFIkD/o5bBp8fXAhjl8yjfN7MhJsxdt5lrAQAAAAD/////AZA9DwAAAAAAFgAU+9NJhjFhe8jX1hrXh3NvDyHZ1cYAAAAAAAEBK1g+DwAAAAAAIlEg/u5de2XiMtRxVVBT6xftYVebHYfqIIzOfuDJ7S/VjwlBFJzXKND+8dEd6/bd8oYA0u9s6GQ1iYa2ZFa3x9KzOU4lTNEVud/F3V+r2AgSlojS+tnDDy2Vn3iIQvvlChohtWpARJzBjlEkN/kTpyFEtpvP2Ui7ypevuxb9J/NUAwhYf8Pmnnj1l3WuKCSi4Fcp1O+lQjIiZlNpwY6J73q/V8Fe2kIVwFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrA7kPYyHfHRc0bEUJLrsY26CGQQ0Wh2kV5WlmOfvpyEjxFIB1UBXV4sr8CJS3Z44vBb21IG/hArk0TQAeu6MCZ43GjrSCc1yjQ/vHRHev23fKGANLvbOhkNYmGtmRWt8fSszlOJazAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAA==" - ] - } + "redeemTx": "cHNidP8BAIkCAAAAARH6LJRGP/pFIkD/o5bBp8fXAhjl8yjfN7MhJsxdt5lrAQAAAAD/////AtAHAAAAAAAAIlEguuBh3KQUVZp+NHV2sixQ/mrsngCuLCGXzsgJPC1FzY7ANQ8AAAAAACJRIP7uXXtl4jLUcVVQU+sX7WFXmx2H6iCMzn7gye0v1Y8JAAAAAAABAStYPg8AAAAAACJRIP7uXXtl4jLUcVVQU+sX7WFXmx2H6iCMzn7gye0v1Y8JIgYCHVQFdXiyvwIlLdnji8FvbUgb+ECuTRNAB67owJnjcaMYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSc1yjQ/vHRHev23fKGANLvbOhkNYmGtmRWt8fSszlOJUzRFbnfxd1fq9gIEpaI0vrZww8tlZ94iEL75QoaIbVqQJsPdLYf7fAoXO82VoqwYHu1WevE4g6LxUGBPzfd96q5EEZkoW5qqg+v5dWJUEY467Q6qZLFHwziUaB3KEY8yEpCFcBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wO5D2Mh3x0XNGxFCS67GNughkENFodpFeVpZjn76chI8RSAdVAV1eLK/AiUt2eOLwW9tSBv4QK5NE0AHrujAmeNxo60gnNco0P7x0R3r9t3yhgDS72zoZDWJhrZkVrfH0rM5TiWswCEWHVQFdXiyvwIlLdnji8FvbUgb+ECuTRNAB67owJnjcaM5AUzRFbnfxd1fq9gIEpaI0vrZww8tlZ94iEL75QoaIbVqAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA=" } ], "spentVtxos": [] @@ -426,8 +386,7 @@ var ( "spentBy": "", "expireAt": "1726503895", "swept": false, - "pending": false, - "pendingData": null + "redeemTx": "" } ], "spentVtxos": [ @@ -445,13 +404,7 @@ var ( "spentBy": "d6684a5b9e6939dccdf07d1f0eaf7fdd7b31de4d123e63e400d23de739800d4e", "expireAt": "1726054928", "swept": false, - "pending": true, - "pendingData": { - "redeemTx": "cHNidP8BAIkCAAAAAWwxXUjG5tidFA0LmUljX//jwW6xWaS6HKyRCw5StsxpAAAAAAD/////AugDAAAAAAAAIlEgt2eR8LtqTP7yUcQtSydeGrRiHnVmHHnZwYjdC23G7MZwSQAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFAAAAAAABASsgTgAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFIgYDp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShcYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSvBIBey3T0IV353FkuGLMmMJFpqHTrliIsJwJsfJkzq7J0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHQAFqkBLiRmP3AZ8MS77s1QIWZswMV3L72D9gN0f0MbD6XHkmzZeC1clF3uzxr+13wsF0vcFe29Zl3e2gAhMNGYVCFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wKRtST8P7teUpSF4DAEbfJj5OIXITx5QGbZns/AtxqGyRSCn2zP0K2jsWEX4L3b1j+MnDXORFbGro1RF32RfTmZKF60grwSAXst09CFd+dxZLhizJjCRaah065YiLCcCbHyZM6uswCEWp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShc5AbJ0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA=", - "unconditionalForfeitTxs": [ - "cHNidP8BAFICAAAAAWwxXUjG5tidFA0LmUljX//jwW6xWaS6HKyRCw5StsxpAAAAAAD/////AVhNAAAAAAAAFgAUSU38/3Mzx5BdILG4oUO+JoHcoT8AAAAAAAEBKyBOAAAAAAAAIlEgp9TN/+j2H6vS/3LiebI632o7wSQO6ZA9BkZNsS/x9IVBFK8EgF7LdPQhXfncWS4YsyYwkWmodOuWIiwnAmx8mTOrsnQHxtDSP3zjaHmVR85ZxuPZN6gXHo4KmCgcipYgGEdAjH8Mg1Z3GdjGzp78Mg2xq1fop9KDfeji+xoyMgYS7q0Nl0AGOAaNzkDRW4cNcefll5jZC2i3nfygKdXsUsR+LEIVwVCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrApG1JPw/u15SlIXgMARt8mPk4hchPHlAZtmez8C3GobJFIKfbM/QraOxYRfgvdvWP4ycNc5EVsaujVEXfZF9OZkoXrSCvBIBey3T0IV353FkuGLMmMJFpqHTrliIsJwJsfJkzq6zAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAA==" - ] - } + "redeemTx": "cHNidP8BAIkCAAAAAWwxXUjG5tidFA0LmUljX//jwW6xWaS6HKyRCw5StsxpAAAAAAD/////AugDAAAAAAAAIlEgt2eR8LtqTP7yUcQtSydeGrRiHnVmHHnZwYjdC23G7MZwSQAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFAAAAAAABASsgTgAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFIgYDp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShcYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSvBIBey3T0IV353FkuGLMmMJFpqHTrliIsJwJsfJkzq7J0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHQAFqkBLiRmP3AZ8MS77s1QIWZswMV3L72D9gN0f0MbD6XHkmzZeC1clF3uzxr+13wsF0vcFe29Zl3e2gAhMNGYVCFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wKRtST8P7teUpSF4DAEbfJj5OIXITx5QGbZns/AtxqGyRSCn2zP0K2jsWEX4L3b1j+MnDXORFbGro1RF32RfTmZKF60grwSAXst09CFd+dxZLhizJjCRaah065YiLCcCbHyZM6uswCEWp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShc5AbJ0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA=" }, { "outpoint": { @@ -467,13 +420,7 @@ var ( "spentBy": "d6684a5b9e6939dccdf07d1f0eaf7fdd7b31de4d123e63e400d23de739800d4e", "expireAt": "1726486389", "swept": false, - "pending": true, - "pendingData": { - "redeemTx": "cHNidP8BAIkCAAAAARH6LJRGP/pFIkD/o5bBp8fXAhjl8yjfN7MhJsxdt5lrAQAAAAD/////AtAHAAAAAAAAIlEguuBh3KQUVZp+NHV2sixQ/mrsngCuLCGXzsgJPC1FzY7ANQ8AAAAAACJRIP7uXXtl4jLUcVVQU+sX7WFXmx2H6iCMzn7gye0v1Y8JAAAAAAABAStYPg8AAAAAACJRIP7uXXtl4jLUcVVQU+sX7WFXmx2H6iCMzn7gye0v1Y8JIgYCHVQFdXiyvwIlLdnji8FvbUgb+ECuTRNAB67owJnjcaMYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSc1yjQ/vHRHev23fKGANLvbOhkNYmGtmRWt8fSszlOJUzRFbnfxd1fq9gIEpaI0vrZww8tlZ94iEL75QoaIbVqQJsPdLYf7fAoXO82VoqwYHu1WevE4g6LxUGBPzfd96q5EEZkoW5qqg+v5dWJUEY467Q6qZLFHwziUaB3KEY8yEpCFcBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wO5D2Mh3x0XNGxFCS67GNughkENFodpFeVpZjn76chI8RSAdVAV1eLK/AiUt2eOLwW9tSBv4QK5NE0AHrujAmeNxo60gnNco0P7x0R3r9t3yhgDS72zoZDWJhrZkVrfH0rM5TiWswCEWHVQFdXiyvwIlLdnji8FvbUgb+ECuTRNAB67owJnjcaM5AUzRFbnfxd1fq9gIEpaI0vrZww8tlZ94iEL75QoaIbVqAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA=", - "unconditionalForfeitTxs": [ - "cHNidP8BAFICAAAAARH6LJRGP/pFIkD/o5bBp8fXAhjl8yjfN7MhJsxdt5lrAQAAAAD/////AZA9DwAAAAAAFgAU+9NJhjFhe8jX1hrXh3NvDyHZ1cYAAAAAAAEBK1g+DwAAAAAAIlEg/u5de2XiMtRxVVBT6xftYVebHYfqIIzOfuDJ7S/VjwlBFJzXKND+8dEd6/bd8oYA0u9s6GQ1iYa2ZFa3x9KzOU4lTNEVud/F3V+r2AgSlojS+tnDDy2Vn3iIQvvlChohtWpARJzBjlEkN/kTpyFEtpvP2Ui7ypevuxb9J/NUAwhYf8Pmnnj1l3WuKCSi4Fcp1O+lQjIiZlNpwY6J73q/V8Fe2kIVwFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrA7kPYyHfHRc0bEUJLrsY26CGQQ0Wh2kV5WlmOfvpyEjxFIB1UBXV4sr8CJS3Z44vBb21IG/hArk0TQAeu6MCZ43GjrSCc1yjQ/vHRHev23fKGANLvbOhkNYmGtmRWt8fSszlOJazAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAA==" - ] - } + "redeemTx": "cHNidP8BAIkCAAAAARH6LJRGP/pFIkD/o5bBp8fXAhjl8yjfN7MhJsxdt5lrAQAAAAD/////AtAHAAAAAAAAIlEguuBh3KQUVZp+NHV2sixQ/mrsngCuLCGXzsgJPC1FzY7ANQ8AAAAAACJRIP7uXXtl4jLUcVVQU+sX7WFXmx2H6iCMzn7gye0v1Y8JAAAAAAABAStYPg8AAAAAACJRIP7uXXtl4jLUcVVQU+sX7WFXmx2H6iCMzn7gye0v1Y8JIgYCHVQFdXiyvwIlLdnji8FvbUgb+ECuTRNAB67owJnjcaMYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSc1yjQ/vHRHev23fKGANLvbOhkNYmGtmRWt8fSszlOJUzRFbnfxd1fq9gIEpaI0vrZww8tlZ94iEL75QoaIbVqQJsPdLYf7fAoXO82VoqwYHu1WevE4g6LxUGBPzfd96q5EEZkoW5qqg+v5dWJUEY467Q6qZLFHwziUaB3KEY8yEpCFcBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wO5D2Mh3x0XNGxFCS67GNughkENFodpFeVpZjn76chI8RSAdVAV1eLK/AiUt2eOLwW9tSBv4QK5NE0AHrujAmeNxo60gnNco0P7x0R3r9t3yhgDS72zoZDWJhrZkVrfH0rM5TiWswCEWHVQFdXiyvwIlLdnji8FvbUgb+ECuTRNAB67owJnjcaM5AUzRFbnfxd1fq9gIEpaI0vrZww8tlZ94iEL75QoaIbVqAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA=" } ] }` @@ -494,13 +441,7 @@ var ( "spentBy": "", "expireAt": "1726503895", "swept": false, - "pending": false, - "pendingData": { - "redeemTx": "cHNidP8BAIkCAAAAAdOK9YzYw1ceJznqJxtRXGe0KeHj6CLcLtqLVwcbMCivAAAAAAD/////ArgLAAAAAAAAIlEgC39Vxhw3dIa4heHgFS6X4XwDl1mBggsKLVTBwF1h3qEgegEAAAAAACJRIMkktfIFxFNTtAmy3K0p+7JqVn2kcA0P6y2vJ1QX2zysAAAAAAABASughgEAAAAAACJRIMkktfIFxFNTtAmy3K0p+7JqVn2kcA0P6y2vJ1QX2zysIgYDjGeMfnNwCrU45iB3iRqiFdWTADaiJ968+w3ruFuq1F0YAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRTYEOuHJ0hyLBGzY8nSHpD2F1nby5/XQ5Sh2Je+cQ5Wsx0ZucLmB/LLspxMRN9JcJn3Q2KJRMhhg7415cCg1d0gQNSvgaBk/1WLYqQxCKxCfv8ViVJ7vjBxvNO5tc2FEDy27V9cIrfL1jPJoVrhgPZT0GwY7dkVZS7saIKI03CbipBCFcBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wPKiQ0JM6aw2kcUByijEbOydM3gTIVCGN/69q+dmyxcqRSCMZ4x+c3AKtTjmIHeJGqIV1ZMANqIn3rz7Deu4W6rUXa0g2BDrhydIciwRs2PJ0h6Q9hdZ28uf10OUodiXvnEOVrOswCEWjGeMfnNwCrU45iB3iRqiFdWTADaiJ968+w3ruFuq1F05AR0ZucLmB/LLspxMRN9JcJn3Q2KJRMhhg7415cCg1d0gAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA=", - "unconditionalForfeitTxs": [ - "cHNidP8BAFICAAAAAdOK9YzYw1ceJznqJxtRXGe0KeHj6CLcLtqLVwcbMCivAAAAAAD/////AdiFAQAAAAAAFgAUlsBYsQa9BEiB8ZumuN4J50lbQIoAAAAAAAEBK6CGAQAAAAAAIlEgySS18gXEU1O0CbLcrSn7smpWfaRwDQ/rLa8nVBfbPKxBFNgQ64cnSHIsEbNjydIekPYXWdvLn9dDlKHYl75xDlazHRm5wuYH8suynExE30lwmfdDYolEyGGDvjXlwKDV3SBAZadgbU8gCDvq3XN0EeLIwGKGSAYHZRkGbAnr9ZjCHGKAQlfFNYS0af1Lz4j7Th2osVY8JJv7O736sC5NNQome0IVwFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrA8qJDQkzprDaRxQHKKMRs7J0zeBMhUIY3/r2r52bLFypFIIxnjH5zcAq1OOYgd4kaohXVkwA2oifevPsN67hbqtRdrSDYEOuHJ0hyLBGzY8nSHpD2F1nby5/XQ5Sh2Je+cQ5Ws6zAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAA==" - ] - } + "redeemTx": "cHNidP8BAIkCAAAAAdOK9YzYw1ceJznqJxtRXGe0KeHj6CLcLtqLVwcbMCivAAAAAAD/////ArgLAAAAAAAAIlEgC39Vxhw3dIa4heHgFS6X4XwDl1mBggsKLVTBwF1h3qEgegEAAAAAACJRIMkktfIFxFNTtAmy3K0p+7JqVn2kcA0P6y2vJ1QX2zysAAAAAAABASughgEAAAAAACJRIMkktfIFxFNTtAmy3K0p+7JqVn2kcA0P6y2vJ1QX2zysIgYDjGeMfnNwCrU45iB3iRqiFdWTADaiJ968+w3ruFuq1F0YAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRTYEOuHJ0hyLBGzY8nSHpD2F1nby5/XQ5Sh2Je+cQ5Wsx0ZucLmB/LLspxMRN9JcJn3Q2KJRMhhg7415cCg1d0gQNSvgaBk/1WLYqQxCKxCfv8ViVJ7vjBxvNO5tc2FEDy27V9cIrfL1jPJoVrhgPZT0GwY7dkVZS7saIKI03CbipBCFcBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wPKiQ0JM6aw2kcUByijEbOydM3gTIVCGN/69q+dmyxcqRSCMZ4x+c3AKtTjmIHeJGqIV1ZMANqIn3rz7Deu4W6rUXa0g2BDrhydIciwRs2PJ0h6Q9hdZ28uf10OUodiXvnEOVrOswCEWjGeMfnNwCrU45iB3iRqiFdWTADaiJ968+w3ruFuq1F05AR0ZucLmB/LLspxMRN9JcJn3Q2KJRMhhg7415cCg1d0gAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA=" } ], "spentVtxos": [ @@ -518,13 +459,7 @@ var ( "spentBy": "d6684a5b9e6939dccdf07d1f0eaf7fdd7b31de4d123e63e400d23de739800d4e", "expireAt": "1726054928", "swept": false, - "pending": true, - "pendingData": { - "redeemTx": "cHNidP8BAIkCAAAAAWwxXUjG5tidFA0LmUljX//jwW6xWaS6HKyRCw5StsxpAAAAAAD/////AugDAAAAAAAAIlEgt2eR8LtqTP7yUcQtSydeGrRiHnVmHHnZwYjdC23G7MZwSQAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFAAAAAAABASsgTgAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFIgYDp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShcYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSvBIBey3T0IV353FkuGLMmMJFpqHTrliIsJwJsfJkzq7J0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHQAFqkBLiRmP3AZ8MS77s1QIWZswMV3L72D9gN0f0MbD6XHkmzZeC1clF3uzxr+13wsF0vcFe29Zl3e2gAhMNGYVCFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wKRtST8P7teUpSF4DAEbfJj5OIXITx5QGbZns/AtxqGyRSCn2zP0K2jsWEX4L3b1j+MnDXORFbGro1RF32RfTmZKF60grwSAXst09CFd+dxZLhizJjCRaah065YiLCcCbHyZM6uswCEWp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShc5AbJ0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA=", - "unconditionalForfeitTxs": [ - "cHNidP8BAFICAAAAAWwxXUjG5tidFA0LmUljX//jwW6xWaS6HKyRCw5StsxpAAAAAAD/////AVhNAAAAAAAAFgAUSU38/3Mzx5BdILG4oUO+JoHcoT8AAAAAAAEBKyBOAAAAAAAAIlEgp9TN/+j2H6vS/3LiebI632o7wSQO6ZA9BkZNsS/x9IVBFK8EgF7LdPQhXfncWS4YsyYwkWmodOuWIiwnAmx8mTOrsnQHxtDSP3zjaHmVR85ZxuPZN6gXHo4KmCgcipYgGEdAjH8Mg1Z3GdjGzp78Mg2xq1fop9KDfeji+xoyMgYS7q0Nl0AGOAaNzkDRW4cNcefll5jZC2i3nfygKdXsUsR+LEIVwVCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrApG1JPw/u15SlIXgMARt8mPk4hchPHlAZtmez8C3GobJFIKfbM/QraOxYRfgvdvWP4ycNc5EVsaujVEXfZF9OZkoXrSCvBIBey3T0IV353FkuGLMmMJFpqHTrliIsJwJsfJkzq6zAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAA==" - ] - } + "redeemTx": "cHNidP8BAIkCAAAAAWwxXUjG5tidFA0LmUljX//jwW6xWaS6HKyRCw5StsxpAAAAAAD/////AugDAAAAAAAAIlEgt2eR8LtqTP7yUcQtSydeGrRiHnVmHHnZwYjdC23G7MZwSQAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFAAAAAAABASsgTgAAAAAAACJRIKfUzf/o9h+r0v9y4nmyOt9qO8EkDumQPQZGTbEv8fSFIgYDp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShcYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSvBIBey3T0IV353FkuGLMmMJFpqHTrliIsJwJsfJkzq7J0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHQAFqkBLiRmP3AZ8MS77s1QIWZswMV3L72D9gN0f0MbD6XHkmzZeC1clF3uzxr+13wsF0vcFe29Zl3e2gAhMNGYVCFcFQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wKRtST8P7teUpSF4DAEbfJj5OIXITx5QGbZns/AtxqGyRSCn2zP0K2jsWEX4L3b1j+MnDXORFbGro1RF32RfTmZKF60grwSAXst09CFd+dxZLhizJjCRaah065YiLCcCbHyZM6uswCEWp9sz9Cto7FhF+C929Y/jJw1zkRWxq6NURd9kX05mShc5AbJ0B8bQ0j9842h5lUfOWcbj2TeoFx6OCpgoHIqWIBhHAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA=" }, { "outpoint": { @@ -541,12 +476,7 @@ var ( "expireAt": "1726486389", "swept": false, "pending": true, - "pendingData": { - "redeemTx": "cHNidP8BAIkCAAAAARH6LJRGP/pFIkD/o5bBp8fXAhjl8yjfN7MhJsxdt5lrAQAAAAD/////AtAHAAAAAAAAIlEguuBh3KQUVZp+NHV2sixQ/mrsngCuLCGXzsgJPC1FzY7ANQ8AAAAAACJRIP7uXXtl4jLUcVVQU+sX7WFXmx2H6iCMzn7gye0v1Y8JAAAAAAABAStYPg8AAAAAACJRIP7uXXtl4jLUcVVQU+sX7WFXmx2H6iCMzn7gye0v1Y8JIgYCHVQFdXiyvwIlLdnji8FvbUgb+ECuTRNAB67owJnjcaMYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSc1yjQ/vHRHev23fKGANLvbOhkNYmGtmRWt8fSszlOJUzRFbnfxd1fq9gIEpaI0vrZww8tlZ94iEL75QoaIbVqQJsPdLYf7fAoXO82VoqwYHu1WevE4g6LxUGBPzfd96q5EEZkoW5qqg+v5dWJUEY467Q6qZLFHwziUaB3KEY8yEpCFcBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wO5D2Mh3x0XNGxFCS67GNughkENFodpFeVpZjn76chI8RSAdVAV1eLK/AiUt2eOLwW9tSBv4QK5NE0AHrujAmeNxo60gnNco0P7x0R3r9t3yhgDS72zoZDWJhrZkVrfH0rM5TiWswCEWHVQFdXiyvwIlLdnji8FvbUgb+ECuTRNAB67owJnjcaM5AUzRFbnfxd1fq9gIEpaI0vrZww8tlZ94iEL75QoaIbVqAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA=", - "unconditionalForfeitTxs": [ - "cHNidP8BAFICAAAAARH6LJRGP/pFIkD/o5bBp8fXAhjl8yjfN7MhJsxdt5lrAQAAAAD/////AZA9DwAAAAAAFgAU+9NJhjFhe8jX1hrXh3NvDyHZ1cYAAAAAAAEBK1g+DwAAAAAAIlEg/u5de2XiMtRxVVBT6xftYVebHYfqIIzOfuDJ7S/VjwlBFJzXKND+8dEd6/bd8oYA0u9s6GQ1iYa2ZFa3x9KzOU4lTNEVud/F3V+r2AgSlojS+tnDDy2Vn3iIQvvlChohtWpARJzBjlEkN/kTpyFEtpvP2Ui7ypevuxb9J/NUAwhYf8Pmnnj1l3WuKCSi4Fcp1O+lQjIiZlNpwY6J73q/V8Fe2kIVwFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrA7kPYyHfHRc0bEUJLrsY26CGQQ0Wh2kV5WlmOfvpyEjxFIB1UBXV4sr8CJS3Z44vBb21IG/hArk0TQAeu6MCZ43GjrSCc1yjQ/vHRHev23fKGANLvbOhkNYmGtmRWt8fSszlOJazAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAA==" - ] - } + "redeemTx": "cHNidP8BAIkCAAAAARH6LJRGP/pFIkD/o5bBp8fXAhjl8yjfN7MhJsxdt5lrAQAAAAD/////AtAHAAAAAAAAIlEguuBh3KQUVZp+NHV2sixQ/mrsngCuLCGXzsgJPC1FzY7ANQ8AAAAAACJRIP7uXXtl4jLUcVVQU+sX7WFXmx2H6iCMzn7gye0v1Y8JAAAAAAABAStYPg8AAAAAACJRIP7uXXtl4jLUcVVQU+sX7WFXmx2H6iCMzn7gye0v1Y8JIgYCHVQFdXiyvwIlLdnji8FvbUgb+ECuTRNAB67owJnjcaMYAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAQRSc1yjQ/vHRHev23fKGANLvbOhkNYmGtmRWt8fSszlOJUzRFbnfxd1fq9gIEpaI0vrZww8tlZ94iEL75QoaIbVqQJsPdLYf7fAoXO82VoqwYHu1WevE4g6LxUGBPzfd96q5EEZkoW5qqg+v5dWJUEY467Q6qZLFHwziUaB3KEY8yEpCFcBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6wO5D2Mh3x0XNGxFCS67GNughkENFodpFeVpZjn76chI8RSAdVAV1eLK/AiUt2eOLwW9tSBv4QK5NE0AHrujAmeNxo60gnNco0P7x0R3r9t3yhgDS72zoZDWJhrZkVrfH0rM5TiWswCEWHVQFdXiyvwIlLdnji8FvbUgb+ECuTRNAB67owJnjcaM5AUzRFbnfxd1fq9gIEpaI0vrZww8tlZ94iEL75QoaIbVqAAAAAFYAAIAAAACAAQAAgAAAAAAAAAAAARcgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsAAAAA=" }, { "outpoint": { @@ -562,8 +492,7 @@ var ( "spentBy": "23c3a885f0ea05f7bdf83f3bf7f8ac9dc3f791ad292f4e63a6f53fa5e4935ab0", "expireAt": "1726503895", "swept": false, - "pending": false, - "pendingData": null + "redeemTx": "" } ] }` diff --git a/pkg/client-sdk/covenant_client.go b/pkg/client-sdk/covenant_client.go index 5a0fd3c..2af5c0d 100644 --- a/pkg/client-sdk/covenant_client.go +++ b/pkg/client-sdk/covenant_client.go @@ -14,7 +14,6 @@ import ( "github.com/ark-network/ark/common" "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" "github.com/ark-network/ark/pkg/client-sdk/internal/utils" "github.com/ark-network/ark/pkg/client-sdk/redemption" "github.com/ark-network/ark/pkg/client-sdk/types" @@ -251,26 +250,6 @@ func (a *covenantArkClient) listenForBoardingUtxos( //are multiple boarding inputs + spent vtxo with change in spendable + received in the same round } -func (a *covenantArkClient) ListVtxos( - ctx context.Context, -) (spendableVtxos, spentVtxos []client.Vtxo, err error) { - offchainAddrs, _, _, err := a.wallet.GetAddresses(ctx) - if err != nil { - return - } - - for _, addr := range offchainAddrs { - spendable, spent, err := a.client.ListVtxos(ctx, addr.Address) - if err != nil { - return nil, nil, err - } - spendableVtxos = append(spendableVtxos, spendable...) - spentVtxos = append(spentVtxos, spent...) - } - - return -} - func (a *covenantArkClient) Balance( ctx context.Context, computeVtxoExpiration bool, ) (*Balance, error) { @@ -285,14 +264,13 @@ func (a *covenantArkClient) Balance( chRes := make(chan balanceRes, nbWorkers*len(offchainAddrs)) for i := range offchainAddrs { - offchainAddr := offchainAddrs[i] boardingAddr := boardingAddrs[i] redeemAddr := redeemAddrs[i] - go func(addr string) { + go func() { defer wg.Done() balance, amountByExpiration, err := a.getOffchainBalance( - ctx, addr, computeVtxoExpiration, + ctx, computeVtxoExpiration, ) if err != nil { chRes <- balanceRes{err: err} @@ -303,7 +281,7 @@ func (a *covenantArkClient) Balance( offchainBalance: balance, offchainBalanceByExpiration: amountByExpiration, } - }(offchainAddr.Address) + }() getDelayedBalance := func(addr string) { defer wg.Done() @@ -448,20 +426,11 @@ func (a *covenantArkClient) UnilateralRedeem(ctx context.Context) error { return fmt.Errorf("wallet is locked") } - offchainAddrs, _, _, err := a.wallet.GetAddresses(ctx) + vtxos, err := a.getVtxos(ctx, false, nil) if err != nil { return err } - vtxos := make([]client.Vtxo, 0) - for _, offchainAddr := range offchainAddrs { - fetchedVtxos, _, err := a.client.ListVtxos(ctx, offchainAddr.Address) - if err != nil { - return err - } - vtxos = append(vtxos, fetchedVtxos...) - } - totalVtxosAmount := uint64(0) for _, vtxo := range vtxos { totalVtxosAmount += vtxo.Amount @@ -519,22 +488,13 @@ func (a *covenantArkClient) CollaborativeRedeem( return "", fmt.Errorf("wallet is locked") } + // validate liquid address if _, err := address.ToOutputScript(addr); err != nil { return "", fmt.Errorf("invalid onchain address") } - addrNet, err := address.NetworkForAddress(addr) - if err != nil { - return "", fmt.Errorf("invalid onchain address: unknown network") - } - net := utils.ToElementsNetwork(a.Network) - if net.Name != addrNet.Name { - return "", fmt.Errorf("invalid onchain address: must be for %s network", net.Name) - } - - if isConf, _ := address.IsConfidential(addr); isConf { - info, _ := address.FromConfidential(addr) - addr = info.Address + if isConf, err := address.IsConfidential(addr); err != nil || isConf { + return "", fmt.Errorf("confidential onchain address not supported") } offchainAddrs, _, _, err := a.wallet.GetAddresses(ctx) @@ -550,22 +510,34 @@ func (a *covenantArkClient) CollaborativeRedeem( } vtxos := make([]client.DescriptorVtxo, 0) - for _, offchainAddr := range offchainAddrs { - spendableVtxos, err := a.getVtxos(ctx, offchainAddr.Address, withExpiryCoinselect) - if err != nil { - return "", err - } + spendableVtxos, err := a.getVtxos(ctx, false, nil) + if err != nil { + return "", err + } - for _, vtxo := range spendableVtxos { - vtxos = append(vtxos, client.DescriptorVtxo{ - Vtxo: vtxo, - Descriptor: offchainAddr.Descriptor, - }) + for _, offchainAddr := range offchainAddrs { + for _, v := range spendableVtxos { + vtxoAddr, err := v.Address(a.AspPubkey, a.Network) + if err != nil { + return "", err + } + + if vtxoAddr == offchainAddr.Address { + vtxos = append(vtxos, client.DescriptorVtxo{ + Vtxo: v, + Descriptor: offchainAddr.Descriptor, + }) + } } } - selectedCoins, changeAmount, err := utils.CoinSelect( - vtxos, amount, a.Dust, withExpiryCoinselect, + boardingUtxos, err := a.getClaimableBoardingUtxos(ctx, nil) + if err != nil { + return "", err + } + + selectedBoardingUtxos, selectedCoins, changeAmount, err := utils.CoinSelect( + boardingUtxos, vtxos, amount, a.Dust, withExpiryCoinselect, ) if err != nil { return "", err @@ -583,7 +555,7 @@ func (a *covenantArkClient) CollaborativeRedeem( }) } - inputs := make([]client.Input, 0, len(selectedCoins)) + inputs := make([]client.Input, 0, len(selectedCoins)+len(selectedBoardingUtxos)) for _, coin := range selectedCoins { inputs = append(inputs, client.Input{ @@ -594,8 +566,17 @@ func (a *covenantArkClient) CollaborativeRedeem( Descriptor: coin.Descriptor, }) } + for _, coin := range selectedBoardingUtxos { + inputs = append(inputs, client.Input{ + Outpoint: client.Outpoint{ + Txid: coin.Txid, + VOut: coin.VOut, + }, + Descriptor: coin.Descriptor, + }) + } - paymentID, err := a.client.RegisterInputsForNextRound(ctx, inputs, "") // ephemeralPublicKey is not required for covenant + paymentID, err := a.client.RegisterInputsForNextRound(ctx, inputs, "") if err != nil { return "", err } @@ -604,9 +585,7 @@ func (a *covenantArkClient) CollaborativeRedeem( return "", err } - poolTxID, err := a.handleRoundStream( - ctx, paymentID, selectedCoins, nil, "", receivers, - ) + poolTxID, err := a.handleRoundStream(ctx, paymentID, selectedCoins, selectedBoardingUtxos, receivers) if err != nil { return "", err } @@ -621,36 +600,10 @@ func (a *covenantArkClient) SendAsync( return "", fmt.Errorf("not implemented") } -func (a *covenantArkClient) Claim(ctx context.Context) (string, error) { - myselfOffchain, boardingAddr, err := a.wallet.NewAddress(ctx, false) - 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.Address, - Amount: pendingBalance, - } - - return a.selfTransferAllPendingPayments( - ctx, - boardingUtxos, - receiver, - boardingAddr.Descriptor, - ) +func (a *covenantArkClient) Settle( + ctx context.Context, +) (string, error) { + return a.sendOffchain(ctx, false, nil) } func (a *covenantArkClient) GetTransactionHistory( @@ -675,13 +628,13 @@ func (a *covenantArkClient) GetTransactionHistory( return vtxosToTxsCovenant(config.RoundLifetime, spendableVtxos, spentVtxos, boardingTxs) } -func (a *covenantArkClient) getAllBoardingUtxos(ctx context.Context) ([]explorer.Utxo, error) { +func (a *covenantArkClient) getAllBoardingUtxos(ctx context.Context) ([]types.Utxo, error) { _, boardingAddrs, _, err := a.wallet.GetAddresses(ctx) if err != nil { return nil, err } - utxos := []explorer.Utxo{} + utxos := make([]types.Utxo, 0) for _, addr := range boardingAddrs { txs, err := a.explorer.GetTxs(addr.Address) if err != nil { @@ -694,11 +647,12 @@ func (a *covenantArkClient) getAllBoardingUtxos(ctx context.Context) ([]explorer if tx.Status.Confirmed { createdAt = time.Unix(tx.Status.Blocktime, 0) } - utxos = append(utxos, explorer.Utxo{ - Txid: tx.Txid, - Vout: uint32(i), - Amount: vout.Amount, - CreatedAt: createdAt, + utxos = append(utxos, types.Utxo{ + Txid: tx.Txid, + VOut: uint32(i), + Amount: vout.Amount, + CreatedAt: createdAt, + Descriptor: addr.Descriptor, }) } } @@ -708,13 +662,13 @@ func (a *covenantArkClient) getAllBoardingUtxos(ctx context.Context) ([]explorer return utxos, nil } -func (a *covenantArkClient) getClaimableBoardingUtxos(ctx context.Context) ([]explorer.Utxo, error) { +func (a *covenantArkClient) getClaimableBoardingUtxos(ctx context.Context, opts *CoinSelectOptions) ([]types.Utxo, error) { _, boardingAddrs, _, err := a.wallet.GetAddresses(ctx) if err != nil { return nil, err } - claimable := make([]explorer.Utxo, 0) + claimable := make([]types.Utxo, 0) for _, addr := range boardingAddrs { boardingScript, err := tree.ParseVtxoScript(addr.Descriptor) @@ -738,8 +692,25 @@ func (a *covenantArkClient) getClaimableBoardingUtxos(ctx context.Context) ([]ex now := time.Now() for _, utxo := range boardingUtxos { - u := utxo.ToUtxo(boardingTimeout) + if opts != nil && len(opts.OutpointsFilter) > 0 { + utxoOutpoint := client.Outpoint{ + Txid: utxo.Txid, + VOut: utxo.Vout, + } + found := false + for _, outpoint := range opts.OutpointsFilter { + if outpoint.Equals(utxoOutpoint) { + found = true + break + } + } + if !found { + continue + } + } + + u := utxo.ToUtxo(boardingTimeout, addr.Descriptor) if u.SpendableAt.Before(now) { continue } @@ -906,84 +877,123 @@ func (a *covenantArkClient) sendOnchain( } func (a *covenantArkClient) sendOffchain( - ctx context.Context, withExpiryCoinselect bool, receivers []Receiver, + ctx context.Context, + withExpiryCoinselect bool, receivers []Receiver, ) (string, error) { if a.wallet.IsLocked() { return "", fmt.Errorf("wallet is locked") } + expectedAspPubKey := schnorr.SerializePubKey(a.AspPubkey) + outputs := make([]client.Output, 0) + sumOfReceivers := uint64(0) + + // validate receivers and create outputs + for _, receiver := range receivers { + rcvAddr, err := common.DecodeAddress(receiver.To()) + if err != nil { + return "", fmt.Errorf("invalid receiver address: %s", err) + } + + rcvAspPubKey := schnorr.SerializePubKey(rcvAddr.Asp) + + if !bytes.Equal(expectedAspPubKey, rcvAspPubKey) { + return "", fmt.Errorf("invalid receiver address '%s': expected ASP %s, got %s", receiver.To(), hex.EncodeToString(expectedAspPubKey), hex.EncodeToString(rcvAspPubKey)) + } + + if receiver.Amount() < a.Dust { + return "", fmt.Errorf("invalid amount (%d), must be greater than dust %d", receiver.Amount(), a.Dust) + } + + outputs = append(outputs, client.Output{ + Address: receiver.To(), + Amount: receiver.Amount(), + }) + sumOfReceivers += receiver.Amount() + } + offchainAddrs, _, _, err := a.wallet.GetAddresses(ctx) if err != nil { return "", err } if len(offchainAddrs) <= 0 { - return "", fmt.Errorf("no funds detected") - } - - expectedAspPubkey := schnorr.SerializePubKey(a.AspPubkey) - - receiversOutput := make([]client.Output, 0) - sumOfReceivers := uint64(0) - - for _, receiver := range receivers { - rcvAddr, err := common.DecodeAddress(receiver.To()) - if err != nil { - return "", fmt.Errorf("invalid receiver address: %s", err) - } - - rcvAspPubkey := schnorr.SerializePubKey(rcvAddr.Asp) - - if !bytes.Equal(rcvAspPubkey, expectedAspPubkey) { - return "", fmt.Errorf("invalid receiver address '%s': expected ASP %s, got %s", receiver.To(), hex.EncodeToString(expectedAspPubkey), hex.EncodeToString(rcvAspPubkey)) - } - - if receiver.Amount() < a.Dust { - return "", fmt.Errorf("invalid amount (%d), must be greater than dust %d", receiver.Amount(), a.Dust) - } - - receiversOutput = append(receiversOutput, client.Output{ - Address: receiver.To(), - Amount: receiver.Amount(), - }) - sumOfReceivers += receiver.Amount() + return "", fmt.Errorf("no offchain addresses found") } vtxos := make([]client.DescriptorVtxo, 0) - for _, offchainAddr := range offchainAddrs { - spendableVtxos, err := a.getVtxos(ctx, offchainAddr.Address, withExpiryCoinselect) - if err != nil { - return "", err - } - for _, vtxo := range spendableVtxos { - vtxos = append(vtxos, client.DescriptorVtxo{ - Vtxo: vtxo, - Descriptor: offchainAddr.Descriptor, - }) + spendableVtxos, err := a.getVtxos(ctx, withExpiryCoinselect, nil) + if err != nil { + return "", err + } + + for _, offchainAddr := range offchainAddrs { + for _, v := range spendableVtxos { + vtxoAddr, err := v.Address(a.AspPubkey, a.Network) + if err != nil { + return "", err + } + + if vtxoAddr == offchainAddr.Address { + vtxos = append(vtxos, client.DescriptorVtxo{ + Vtxo: v, + Descriptor: offchainAddr.Descriptor, + }) + } } } - selectedCoins, changeAmount, err := utils.CoinSelect( - vtxos, sumOfReceivers, a.Dust, withExpiryCoinselect, - ) + boardingUtxos, err := a.getClaimableBoardingUtxos(ctx, nil) if err != nil { return "", err } + var selectedBoardingCoins []types.Utxo + var selectedCoins []client.DescriptorVtxo + var changeAmount uint64 + + // if no receivers, self send all selected coins + if len(outputs) <= 0 { + selectedBoardingCoins = boardingUtxos + selectedCoins = vtxos + + amount := uint64(0) + for _, utxo := range boardingUtxos { + amount += utxo.Amount + } + for _, utxo := range vtxos { + amount += utxo.Amount + } + + outputs = append(outputs, client.Output{ + Address: offchainAddrs[0].Address, + Amount: amount, + }) + + changeAmount = 0 + } else { + selectedBoardingCoins, selectedCoins, changeAmount, err = utils.CoinSelect( + boardingUtxos, vtxos, sumOfReceivers, a.Dust, withExpiryCoinselect, + ) + if err != nil { + return "", err + } + } + if changeAmount > 0 { offchainAddr, _, err := a.wallet.NewAddress(ctx, true) if err != nil { return "", err } - changeReceiver := client.Output{ + outputs = append(outputs, client.Output{ Address: offchainAddr.Address, Amount: changeAmount, - } - receiversOutput = append(receiversOutput, changeReceiver) + }) } - inputs := make([]client.Input, 0, len(selectedCoins)) + inputs := make([]client.Input, 0, len(selectedCoins)+len(selectedBoardingCoins)) + for _, coin := range selectedCoins { inputs = append(inputs, client.Input{ Outpoint: client.Outpoint{ @@ -993,16 +1003,23 @@ func (a *covenantArkClient) sendOffchain( Descriptor: coin.Descriptor, }) } + for _, coin := range selectedBoardingCoins { + inputs = append(inputs, client.Input{ + Outpoint: client.Outpoint{ + Txid: coin.Txid, + VOut: coin.VOut, + }, + Descriptor: coin.Descriptor, + }) + } - paymentID, err := a.client.RegisterInputsForNextRound( - ctx, inputs, "", // ephemeralPublicKey is not required for covenant - ) + paymentID, err := a.client.RegisterInputsForNextRound(ctx, inputs, "") if err != nil { return "", err } if err := a.client.RegisterOutputsForNextRound( - ctx, paymentID, receiversOutput, + ctx, paymentID, outputs, ); err != nil { return "", err } @@ -1010,7 +1027,7 @@ func (a *covenantArkClient) sendOffchain( log.Infof("payment registered with id: %s", paymentID) poolTxID, err := a.handleRoundStream( - ctx, paymentID, selectedCoins, nil, "", receiversOutput, + ctx, paymentID, selectedCoins, boardingUtxos, outputs, ) if err != nil { return "", err @@ -1023,7 +1040,7 @@ func (a *covenantArkClient) sendOffchain( func (a *covenantArkClient) addInputs( ctx context.Context, updater *psetv2.Updater, - utxos []explorer.Utxo, + utxos []types.Utxo, ) error { // TODO works only with single-key wallet offchain, _, err := a.wallet.NewAddress(ctx, false) @@ -1055,7 +1072,7 @@ func (a *covenantArkClient) addInputs( if err := updater.AddInputs([]psetv2.InputArgs{ { Txid: utxo.Txid, - TxIndex: utxo.Vout, + TxIndex: utxo.VOut, Sequence: sequence, }, }); err != nil { @@ -1113,8 +1130,7 @@ func (a *covenantArkClient) handleRoundStream( ctx context.Context, paymentID string, vtxosToSign []client.DescriptorVtxo, - boardingUtxos []explorer.Utxo, - boardingDescriptor string, + boardingUtxos []types.Utxo, receivers []client.Output, ) (string, error) { eventsCh, close, err := a.client.GetEventStream(ctx, paymentID) @@ -1151,7 +1167,7 @@ func (a *covenantArkClient) handleRoundStream( log.Info("a round finalization started") signedForfeitTxs, signedRoundTx, err := a.handleRoundFinalization( - ctx, event.(client.RoundFinalizationEvent), vtxosToSign, boardingUtxos, boardingDescriptor, receivers, + ctx, event.(client.RoundFinalizationEvent), vtxosToSign, boardingUtxos, receivers, ) if err != nil { return "", err @@ -1178,8 +1194,7 @@ func (a *covenantArkClient) handleRoundFinalization( ctx context.Context, event client.RoundFinalizationEvent, vtxos []client.DescriptorVtxo, - boardingUtxos []explorer.Utxo, - boardingDescriptor string, + boardingUtxos []types.Utxo, receivers []client.Output, ) (signedForfeits []string, signedRoundTx string, err error) { if err = a.validateCongestionTree(event, receivers); err != nil { @@ -1193,13 +1208,23 @@ func (a *covenantArkClient) handleRoundFinalization( } } - if len(boardingUtxos) > 0 { - boardingVtxoScript, err := tree.ParseVtxoScript(boardingDescriptor) - if err != nil { - return nil, "", err - } + // if no boarding utxos inputs, we don't need to sign the round transaction + if len(boardingUtxos) <= 0 { + return + } - roundPtx, err := psetv2.NewPsetFromBase64(event.Tx) + roundPtx, err := psetv2.NewPsetFromBase64(event.Tx) + if err != nil { + return nil, "", err + } + + updater, err := psetv2.NewUpdater(roundPtx) + if err != nil { + return nil, "", err + } + + for _, boardingUtxo := range boardingUtxos { + boardingVtxoScript, err := tree.ParseVtxoScript(boardingUtxo.Descriptor) if err != nil { return nil, "", err } @@ -1213,7 +1238,7 @@ func (a *covenantArkClient) handleRoundFinalization( AspPubkey: a.AspPubkey, } default: - return nil, "", fmt.Errorf("unsupported boarding descriptor: %s", boardingDescriptor) + return nil, "", fmt.Errorf("unsupported boarding descriptor: %s", boardingUtxo.Descriptor) } forfeitLeaf, err := forfeitClosure.Leaf() @@ -1241,31 +1266,25 @@ func (a *covenantArkClient) handleRoundFinalization( ControlBlock: *ctrlBlock, } - updater, err := psetv2.NewUpdater(roundPtx) - if err != nil { - return nil, "", err - } - for i, input := range updater.Pset.Inputs { - for _, boardingUtxo := range boardingUtxos { - if chainhash.Hash(input.PreviousTxid).String() == boardingUtxo.Txid && boardingUtxo.Vout == input.PreviousTxIndex { - if err := updater.AddInTapLeafScript(i, tapscript); err != nil { - return nil, "", err - } - break + if chainhash.Hash(input.PreviousTxid).String() == boardingUtxo.Txid && boardingUtxo.VOut == input.PreviousTxIndex { + if err := updater.AddInTapLeafScript(i, tapscript); err != nil { + return nil, "", err } + break } } - b64, err := updater.Pset.ToBase64() - if err != nil { - return nil, "", err - } + } - signedRoundTx, err = a.wallet.SignTransaction(ctx, a.explorer, b64) - if err != nil { - return nil, "", err - } + b64, err := updater.Pset.ToBase64() + if err != nil { + return nil, "", err + } + + signedRoundTx, err = a.wallet.SignTransaction(ctx, a.explorer, b64) + if err != nil { + return nil, "", err } return signedForfeits, signedRoundTx, nil @@ -1518,8 +1537,8 @@ func (a *covenantArkClient) createAndSignForfeits( } func (a *covenantArkClient) coinSelectOnchain( - ctx context.Context, targetAmount uint64, exclude []explorer.Utxo, -) ([]explorer.Utxo, uint64, error) { + ctx context.Context, targetAmount uint64, exclude []types.Utxo, +) ([]types.Utxo, uint64, error) { _, boardingAddrs, redemptionAddrs, err := a.wallet.GetAddresses(ctx) if err != nil { return nil, 0, err @@ -1527,7 +1546,7 @@ func (a *covenantArkClient) coinSelectOnchain( now := time.Now() - fetchedUtxos := make([]explorer.Utxo, 0) + fetchedUtxos := make([]types.Utxo, 0) for _, addr := range boardingAddrs { boardingDescriptor := addr.Descriptor @@ -1550,14 +1569,14 @@ func (a *covenantArkClient) coinSelectOnchain( } for _, utxo := range utxos { - u := utxo.ToUtxo(boardingTimeout) + u := utxo.ToUtxo(boardingTimeout, addr.Descriptor) if u.SpendableAt.Before(now) { fetchedUtxos = append(fetchedUtxos, u) } } } - selected := make([]explorer.Utxo, 0) + selected := make([]types.Utxo, 0) selectedAmount := uint64(0) for _, utxo := range fetchedUtxos { if selectedAmount >= targetAmount { @@ -1565,7 +1584,7 @@ func (a *covenantArkClient) coinSelectOnchain( } for _, excluded := range exclude { - if utxo.Txid == excluded.Txid && utxo.Vout == excluded.Vout { + if utxo.Txid == excluded.Txid && utxo.VOut == excluded.VOut { continue } } @@ -1578,7 +1597,7 @@ func (a *covenantArkClient) coinSelectOnchain( return selected, selectedAmount - targetAmount, nil } - fetchedUtxos = make([]explorer.Utxo, 0) + fetchedUtxos = make([]types.Utxo, 0) for _, addr := range redemptionAddrs { utxos, err := a.explorer.GetUtxos(addr.Address) if err != nil { @@ -1586,7 +1605,7 @@ func (a *covenantArkClient) coinSelectOnchain( } for _, utxo := range utxos { - u := utxo.ToUtxo(uint(a.UnilateralExitDelay)) + u := utxo.ToUtxo(uint(a.UnilateralExitDelay), addr.Descriptor) if u.SpendableAt.Before(now) { fetchedUtxos = append(fetchedUtxos, u) } @@ -1599,7 +1618,7 @@ func (a *covenantArkClient) coinSelectOnchain( } for _, excluded := range exclude { - if utxo.Txid == excluded.Txid && utxo.Vout == excluded.Vout { + if utxo.Txid == excluded.Txid && utxo.VOut == excluded.VOut { continue } } @@ -1648,11 +1667,11 @@ func (a *covenantArkClient) getRedeemBranches( } func (a *covenantArkClient) getOffchainBalance( - ctx context.Context, addr string, computeVtxoExpiration bool, + ctx context.Context, computeVtxoExpiration bool, ) (uint64, map[int64]uint64, error) { amountByExpiration := make(map[int64]uint64, 0) - vtxos, err := a.getVtxos(ctx, addr, computeVtxoExpiration) + vtxos, err := a.getVtxos(ctx, computeVtxoExpiration, nil) if err != nil { return 0, nil, err } @@ -1675,18 +1694,24 @@ func (a *covenantArkClient) getOffchainBalance( } func (a *covenantArkClient) getVtxos( - ctx context.Context, addr string, computeVtxoExpiration bool, + ctx context.Context, + withExpiryCoinselect bool, opts *CoinSelectOptions, ) ([]client.Vtxo, error) { - vtxos, _, err := a.client.ListVtxos(ctx, addr) + spendableVtxos, _, err := a.ListVtxos(ctx) if err != nil { return nil, err } - if !computeVtxoExpiration { - return vtxos, nil + if opts != nil && len(opts.OutpointsFilter) > 0 { + spendableVtxos = filterByOutpoints(spendableVtxos, opts.OutpointsFilter) } - redeemBranches, err := a.getRedeemBranches(ctx, vtxos) + if opts == nil || !opts.WithExpirySorting { + return spendableVtxos, nil + } + + // if sorting by expiry is required, we need to get the expiration date of each vtxo + redeemBranches, err := a.getRedeemBranches(ctx, spendableVtxos) if err != nil { return nil, err } @@ -1697,75 +1722,24 @@ func (a *covenantArkClient) getVtxos( return nil, err } - for i, vtxo := range vtxos { + for i, vtxo := range spendableVtxos { if vtxo.Txid == vtxoTxid { - vtxos[i].ExpiresAt = expiration + spendableVtxos[i].ExpiresAt = expiration break } } } - return vtxos, nil -} - -func (a *covenantArkClient) selfTransferAllPendingPayments( - ctx context.Context, boardingUtxos []explorer.Utxo, myself client.Output, boardingDescriptor string, -) (string, error) { - inputs := make([]client.Input, 0, len(boardingUtxos)) - - for _, utxo := range boardingUtxos { - inputs = append(inputs, client.Input{ - Outpoint: client.Outpoint{ - Txid: utxo.Txid, - VOut: utxo.Vout, - }, - Descriptor: boardingDescriptor, - }) - } - - outputs := []client.Output{myself} - - paymentID, err := a.client.RegisterInputsForNextRound(ctx, inputs, "") // ephemeralPublicKey is not required for covenant - if err != nil { - return "", err - } - - if err := a.client.RegisterOutputsForNextRound(ctx, paymentID, outputs); err != nil { - return "", err - } - - roundTxid, err := a.handleRoundStream( - ctx, paymentID, make([]client.DescriptorVtxo, 0), boardingUtxos, boardingDescriptor, outputs, - ) - if err != nil { - return "", err - } - - return roundTxid, nil + return spendableVtxos, nil } func (a *covenantArkClient) getBoardingTxs(ctx context.Context) (transactions []types.Transaction) { - utxos, err := a.getClaimableBoardingUtxos(ctx) - if err != nil { - return nil - } - - isPending := make(map[string]bool) - for _, u := range utxos { - isPending[u.Txid] = true - } - allUtxos, err := a.getAllBoardingUtxos(ctx) if err != nil { return nil } for _, u := range allUtxos { - pending := false - if isPending[u.Txid] { - pending = true - } - transactions = append(transactions, types.Transaction{ TransactionKey: types.TransactionKey{ BoardingTxid: u.Txid, @@ -1773,7 +1747,6 @@ func (a *covenantArkClient) getBoardingTxs(ctx context.Context) (transactions [] Amount: u.Amount, Type: types.TxReceived, CreatedAt: u.CreatedAt, - IsPending: pending, }) } return @@ -1796,9 +1769,7 @@ func vtxosToTxsCovenant( for _, v := range append(spendable, spent...) { // get vtxo amount amount := int(v.Amount) - if !v.Pending { - continue - } + // find other spent vtxos that spent this one relatedVtxos := findVtxosBySpentBy(spent, v.Txid) for _, r := range relatedVtxos { diff --git a/pkg/client-sdk/covenantless_client.go b/pkg/client-sdk/covenantless_client.go index a8d06e7..447f9c0 100644 --- a/pkg/client-sdk/covenantless_client.go +++ b/pkg/client-sdk/covenantless_client.go @@ -15,7 +15,6 @@ import ( "github.com/ark-network/ark/common/bitcointree" "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" "github.com/ark-network/ark/pkg/client-sdk/internal/utils" "github.com/ark-network/ark/pkg/client-sdk/redemption" "github.com/ark-network/ark/pkg/client-sdk/types" @@ -243,7 +242,7 @@ func (a *covenantlessArkClient) listenForTransactions(ctx context.Context) { continue } - pendingBoardingTxsMap, newPendingBoardingTxs, err := a.getBoardingPendingTransactions(ctx) + newPendingBoardingTxs, err := a.getBoardingPendingTransactions(ctx) if err != nil { log.WithError(err).Error("Failed to get pending transactions") continue @@ -255,7 +254,7 @@ func (a *covenantlessArkClient) listenForTransactions(ctx context.Context) { continue } - a.processTransactionEvent(addrPubkey, event, pendingBoardingTxsMap) + a.processTransactionEvent(addrPubkey, event) case <-ctx.Done(): return } @@ -269,7 +268,7 @@ func (a *covenantlessArkClient) listenForBoardingUtxos(ctx context.Context) { for { select { case <-ticker.C: - _, newPendingBoardingTxs, err := a.getBoardingPendingTransactions(ctx) + newPendingBoardingTxs, err := a.getBoardingPendingTransactions(ctx) if err != nil { log.WithError(err).Error("Failed to get pending transactions") continue @@ -288,58 +287,76 @@ func (a *covenantlessArkClient) listenForBoardingUtxos(ctx context.Context) { func (a *covenantlessArkClient) getBoardingPendingTransactions( ctx context.Context, -) (map[string]types.Transaction, []types.Transaction, error) { +) ([]types.Transaction, error) { oldTxs, err := a.store.TransactionStore().GetAllTransactions(ctx) if err != nil { - return nil, nil, err + return nil, err } - pendingBoardingTxsMap := make(map[string]types.Transaction) - for _, tx := range oldTxs { - if tx.IsBoarding() { - if tx.IsPending { - pendingBoardingTxsMap[tx.BoardingTxid] = tx - } - } - } - - boardingTxs, _, err := a.getBoardingTxs(ctx) + boardingUtxos, err := a.getClaimableBoardingUtxos(ctx, nil) if err != nil { - return nil, nil, err - } - newPendingBoardingTxs := make([]types.Transaction, 0) - for _, tx := range boardingTxs { - if tx.IsBoarding() && tx.IsPending { - if _, ok := pendingBoardingTxsMap[tx.BoardingTxid]; !ok { - newPendingBoardingTxs = append(newPendingBoardingTxs, tx) - pendingBoardingTxsMap[tx.BoardingTxid] = tx - } - } + return nil, err } - return pendingBoardingTxsMap, newPendingBoardingTxs, nil + newPendingBoardingTxs := make([]types.Transaction, 0) + for _, u := range boardingUtxos { + found := false + for _, tx := range oldTxs { + if tx.BoardingTxid == u.Txid { + found = true + break + } + } + + if found { + continue + } + + newPendingBoardingTxs = append(newPendingBoardingTxs, types.Transaction{ + TransactionKey: types.TransactionKey{ + BoardingTxid: u.Txid, + }, + Amount: u.Amount, + Type: types.TxReceived, + CreatedAt: u.CreatedAt, + }) + } + + return newPendingBoardingTxs, nil } func (a *covenantlessArkClient) processTransactionEvent( pubkey string, event client.TransactionEvent, - pendingBoardingTxsMap map[string]types.Transaction, ) { if event.Round != nil { - boardingAmount := 0 - boardingTxsToUpdate := make([]types.Transaction, 0) - for _, v := range event.Round.ClaimedBoardingUtxos { - boardingKey := fmt.Sprintf("%v-%v", v.Txid, v.VOut) - if tx, ok := pendingBoardingTxsMap[boardingKey]; ok { - boardingAmount += int(tx.Amount) - tx.IsPending = false - boardingTxsToUpdate = append(boardingTxsToUpdate, tx) + allTxs, err := a.store.TransactionStore().GetAllTransactions(context.Background()) + if err != nil { + log.WithError(err).Error("Failed to get all transactions") + return + } + pendingBoardingTxs := make(map[string]types.Transaction) + for _, tx := range allTxs { + if tx.BoardingTxid != "" && !tx.Settled { + pendingBoardingTxs[tx.BoardingTxid] = tx + } + } + var ignoreNewTxs bool + settledBoardingTxs := make([]types.Transaction, 0, len(event.Round.ClaimedBoardingUtxos)) + for _, u := range event.Round.ClaimedBoardingUtxos { + if tx, ok := pendingBoardingTxs[u.Txid]; ok { + ignoreNewTxs = true + tx.Settled = true + settledBoardingTxs = append(settledBoardingTxs, tx) } } - if err := a.store.TransactionStore(). - UpdateTransactions(context.Background(), boardingTxsToUpdate); err != nil { - log.WithError(err).Error("Failed to update boarding transactions") + if len(settledBoardingTxs) > 0 { + if err := a.store.TransactionStore(). + UpdateTransactions(context.Background(), settledBoardingTxs); err != nil { + log.WithError(err).Error("Failed to settle boarding transactions") + return + } } spentKeys := make([]types.VtxoKey, 0, len(event.Round.SpentVtxos)) @@ -388,33 +405,34 @@ func (a *covenantlessArkClient) processTransactionEvent( Spent: false, }) - if boardingAmount == int(v.Amount) { - continue + if !ignoreNewTxs { + txsToInsert = append(txsToInsert, types.Transaction{ + TransactionKey: types.TransactionKey{ + RoundTxid: event.Round.Txid, + }, + Amount: v.Amount, + Type: types.TxReceived, + CreatedAt: time.Now(), //TODO is this ok? + }) } - - txsToInsert = append(txsToInsert, types.Transaction{ - TransactionKey: types.TransactionKey{ - RoundTxid: event.Round.Txid, - }, - Amount: v.Amount, - Type: types.TxReceived, - CreatedAt: time.Now(), //TODO is this ok? - }) } } - if err := a.store.VtxoStore(). - AddVtxos(context.Background(), vtxosToInsert); err != nil { - log.WithError(err).Error("Failed to insert new vtxos") - return + if len(vtxosToInsert) > 0 { + if err := a.store.VtxoStore(). + AddVtxos(context.Background(), vtxosToInsert); err != nil { + log.WithError(err).Error("Failed to insert new vtxos") + return + } } - if err := a.store.TransactionStore(). - AddTransactions(context.Background(), txsToInsert); err != nil { - log.WithError(err).Error("Failed to insert received transaction") - return + if len(txsToInsert) > 0 { + if err := a.store.TransactionStore(). + AddTransactions(context.Background(), txsToInsert); err != nil { + log.WithError(err).Error("Failed to insert received transaction") + return + } } - } if event.Redeem != nil { @@ -474,7 +492,6 @@ func (a *covenantlessArkClient) processTransactionEvent( }, Amount: inputAmount - outputAmount, Type: types.TxSent, - IsPending: true, CreatedAt: time.Now(), //TODO is this ok? } @@ -504,7 +521,6 @@ func (a *covenantlessArkClient) processTransactionEvent( }, Amount: v.Amount, Type: types.TxReceived, - IsPending: true, CreatedAt: time.Now(), //TODO is this ok? } if err := a.store.TransactionStore(). @@ -523,80 +539,6 @@ func (a *covenantlessArkClient) processTransactionEvent( } } -func (a *covenantlessArkClient) ListVtxos( - ctx context.Context, -) (spendableVtxos, spentVtxos []client.Vtxo, err error) { - offchainAddrs, boardingAddrs, _, err := a.wallet.GetAddresses(ctx) - if err != nil { - return - } - - boardingAddrScript, err := bitcointree.ParseVtxoScript(boardingAddrs[0].Descriptor) - if err != nil { - return - } - - var myPubkey []byte - - if boardingScript, ok := boardingAddrScript.(*bitcointree.DefaultVtxoScript); ok { - myPubkey = schnorr.SerializePubKey(boardingScript.Owner) - } - - if myPubkey == nil { - return nil, nil, fmt.Errorf("invalid boarding address descriptor") - } - - // The ASP returns the vtxos sent to others via async payments as spendable - // because they are actually revertable. Since we do not provide any revert - // feature, we want to ignore them. - // To understand if the output of a redeem tx is sent or received we look at - // the inputs and check if they are owned by us. - // The auxiliary variables below are used to make these checks in an - // efficient way. - for _, addr := range offchainAddrs { - spendable, spent, err := a.client.ListVtxos(ctx, addr.Address) - if err != nil { - return nil, nil, err - } - - script, err := bitcointree.ParseVtxoScript(addr.Descriptor) - if err != nil { - return nil, nil, err - } - - reversibleVtxo, isReversible := script.(*bitcointree.ReversibleVtxoScript) - - for _, v := range spendable { - if !v.Pending { - spendableVtxos = append(spendableVtxos, v) - continue - } - - if !isReversible { - spendableVtxos = append(spendableVtxos, v) - continue - } - - if !bytes.Equal(schnorr.SerializePubKey(reversibleVtxo.Sender), myPubkey) { - spendableVtxos = append(spendableVtxos, v) - } - } - - for _, v := range spent { - if !isReversible { - spentVtxos = append(spentVtxos, v) - continue - } - - if !bytes.Equal(schnorr.SerializePubKey(reversibleVtxo.Sender), myPubkey) { - spentVtxos = append(spentVtxos, v) - } - } - } - - return -} - func (a *covenantlessArkClient) Balance( ctx context.Context, computeVtxoExpiration bool, ) (*Balance, error) { @@ -773,7 +715,7 @@ func (a *covenantlessArkClient) UnilateralRedeem(ctx context.Context) error { return fmt.Errorf("wallet is locked") } - vtxos, _, err := a.getVtxos(ctx, false) + vtxos, err := a.getVtxos(ctx, nil) if err != nil { return err } @@ -853,7 +795,7 @@ func (a *covenantlessArkClient) CollaborativeRedeem( } vtxos := make([]client.DescriptorVtxo, 0) - spendableVtxos, _, err := a.getVtxos(ctx, withExpiryCoinselect) + spendableVtxos, err := a.getVtxos(ctx, nil) if err != nil { return "", err } @@ -874,8 +816,13 @@ func (a *covenantlessArkClient) CollaborativeRedeem( } } - selectedCoins, changeAmount, err := utils.CoinSelect( - vtxos, amount, a.Dust, withExpiryCoinselect, + boardingUtxos, err := a.getClaimableBoardingUtxos(ctx, nil) + if err != nil { + return "", err + } + + selectedBoardingCoins, selectedCoins, changeAmount, err := utils.CoinSelect( + boardingUtxos, vtxos, amount, a.Dust, withExpiryCoinselect, ) if err != nil { return "", err @@ -893,7 +840,7 @@ func (a *covenantlessArkClient) CollaborativeRedeem( }) } - inputs := make([]client.Input, 0, len(selectedCoins)) + inputs := make([]client.Input, 0, len(selectedCoins)+len(selectedBoardingCoins)) for _, coin := range selectedCoins { inputs = append(inputs, client.Input{ @@ -904,6 +851,15 @@ func (a *covenantlessArkClient) CollaborativeRedeem( Descriptor: coin.Descriptor, }) } + for _, coin := range selectedBoardingCoins { + inputs = append(inputs, client.Input{ + Outpoint: client.Outpoint{ + Txid: coin.Txid, + VOut: coin.VOut, + }, + Descriptor: coin.Descriptor, + }) + } roundEphemeralKey, err := secp256k1.GeneratePrivateKey() if err != nil { @@ -924,7 +880,7 @@ func (a *covenantlessArkClient) CollaborativeRedeem( } poolTxID, err := a.handleRoundStream( - ctx, paymentID, selectedCoins, nil, "", receivers, roundEphemeralKey, + ctx, paymentID, selectedCoins, selectedBoardingCoins, receivers, roundEphemeralKey, ) if err != nil { return "", err @@ -986,8 +942,10 @@ func (a *covenantlessArkClient) SendAsync( } vtxos := make([]client.DescriptorVtxo, 0) - - spendableVtxos, _, err := a.getVtxos(ctx, withExpiryCoinselect) + opts := &CoinSelectOptions{ + WithExpirySorting: withExpiryCoinselect, + } + spendableVtxos, err := a.getVtxos(ctx, opts) if err != nil { return "", err } @@ -1008,8 +966,9 @@ func (a *covenantlessArkClient) SendAsync( } } - selectedCoins, changeAmount, err := utils.CoinSelect( - vtxos, sumOfReceivers, a.Dust, withExpiryCoinselect, + // do not include boarding utxos + _, selectedCoins, changeAmount, err := utils.CoinSelect( + nil, vtxos, sumOfReceivers, a.Dust, withExpiryCoinselect, ) if err != nil { return "", err @@ -1039,11 +998,6 @@ func (a *covenantlessArkClient) SendAsync( Pubkey: s.Owner, AspPubkey: s.Asp, } - case *bitcointree.ReversibleVtxoScript: - forfeitClosure = &bitcointree.MultisigClosure{ - Pubkey: s.Owner, - AspPubkey: s.Asp, - } default: return "", fmt.Errorf("unsupported vtxo script: %T", s) } @@ -1086,53 +1040,8 @@ func (a *covenantlessArkClient) SendAsync( return signedRedeemTx, nil } -func (a *covenantlessArkClient) Claim(ctx context.Context) (string, error) { - myselfOffchain, boardingAddr, err := a.wallet.NewAddress(ctx, false) - if err != nil { - return "", err - } - - _, pendingVtxos, err := a.getVtxos(ctx, false) - if err != nil { - return "", err - } - - pendingVtxosWithDescriptor := make([]client.DescriptorVtxo, 0) - for _, vtxo := range pendingVtxos { - pendingVtxosWithDescriptor = append(pendingVtxosWithDescriptor, client.DescriptorVtxo{ - Vtxo: vtxo, - Descriptor: myselfOffchain.Descriptor, - }) - } - - 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 - } - - receiver := client.Output{ - Address: myselfOffchain.Address, - Amount: pendingBalance, - } - - return a.selfTransferAllPendingPayments( - ctx, - pendingVtxosWithDescriptor, - boardingUtxos, - receiver, - boardingAddr.Descriptor, - ) +func (a *covenantlessArkClient) Settle(ctx context.Context) (string, error) { + return a.sendOffchain(ctx, false, nil) } func (a *covenantlessArkClient) GetTransactionHistory( @@ -1326,19 +1235,11 @@ func (a *covenantlessArkClient) sendOffchain( return "", fmt.Errorf("wallet is locked") } - offchainAddrs, _, _, err := a.wallet.GetAddresses(ctx) - if err != nil { - return "", err - } - if len(offchainAddrs) <= 0 { - return "", fmt.Errorf("no funds detected") - } - expectedAspPubKey := schnorr.SerializePubKey(a.AspPubkey) - - receiversOutput := make([]client.Output, 0) + outputs := make([]client.Output, 0) sumOfReceivers := uint64(0) + // validate receivers and create outputs for _, receiver := range receivers { rcvAddr, err := common.DecodeAddress(receiver.To()) if err != nil { @@ -1355,16 +1256,25 @@ func (a *covenantlessArkClient) sendOffchain( return "", fmt.Errorf("invalid amount (%d), must be greater than dust %d", receiver.Amount(), a.Dust) } - receiversOutput = append(receiversOutput, client.Output{ + outputs = append(outputs, client.Output{ Address: receiver.To(), Amount: receiver.Amount(), }) sumOfReceivers += receiver.Amount() } - vtxos := make([]client.DescriptorVtxo, 0) + offchainAddrs, _, _, err := a.wallet.GetAddresses(ctx) + if err != nil { + return "", err + } + if len(offchainAddrs) <= 0 { + return "", fmt.Errorf("no offchain addresses found") + } - spendableVtxos, _, err := a.getVtxos(ctx, withExpiryCoinselect) + vtxos := make([]client.DescriptorVtxo, 0) + opts := &CoinSelectOptions{ + WithExpirySorting: withExpiryCoinselect} + spendableVtxos, err := a.getVtxos(ctx, opts) if err != nil { return "", err } @@ -1385,26 +1295,55 @@ func (a *covenantlessArkClient) sendOffchain( } } - selectedCoins, changeAmount, err := utils.CoinSelect( - vtxos, sumOfReceivers, a.Dust, withExpiryCoinselect, - ) + boardingUtxos, err := a.getClaimableBoardingUtxos(ctx, nil) if err != nil { return "", err } + var selectedBoardingCoins []types.Utxo + var selectedCoins []client.DescriptorVtxo + var changeAmount uint64 + + // if no receivers, self send all selected coins + if len(outputs) <= 0 { + selectedBoardingCoins = boardingUtxos + selectedCoins = vtxos + + amount := uint64(0) + for _, utxo := range boardingUtxos { + amount += utxo.Amount + } + for _, utxo := range vtxos { + amount += utxo.Amount + } + + outputs = append(outputs, client.Output{ + Address: offchainAddrs[0].Address, + Amount: amount, + }) + + changeAmount = 0 + } else { + selectedBoardingCoins, selectedCoins, changeAmount, err = utils.CoinSelect( + boardingUtxos, vtxos, sumOfReceivers, a.Dust, withExpiryCoinselect, + ) + if err != nil { + return "", err + } + } + if changeAmount > 0 { offchainAddr, _, err := a.wallet.NewAddress(ctx, true) if err != nil { return "", err } - changeReceiver := client.Output{ + outputs = append(outputs, client.Output{ Address: offchainAddr.Address, Amount: changeAmount, - } - receiversOutput = append(receiversOutput, changeReceiver) + }) } - inputs := make([]client.Input, 0, len(selectedCoins)) + inputs := make([]client.Input, 0, len(selectedCoins)+len(selectedBoardingCoins)) for _, coin := range selectedCoins { inputs = append(inputs, client.Input{ Outpoint: client.Outpoint{ @@ -1414,6 +1353,15 @@ func (a *covenantlessArkClient) sendOffchain( Descriptor: coin.Descriptor, }) } + for _, boardingUtxo := range selectedBoardingCoins { + inputs = append(inputs, client.Input{ + Outpoint: client.Outpoint{ + Txid: boardingUtxo.Txid, + VOut: boardingUtxo.VOut, + }, + Descriptor: boardingUtxo.Descriptor, + }) + } roundEphemeralKey, err := secp256k1.GeneratePrivateKey() if err != nil { @@ -1428,7 +1376,7 @@ func (a *covenantlessArkClient) sendOffchain( } if err := a.client.RegisterOutputsForNextRound( - ctx, paymentID, receiversOutput, + ctx, paymentID, outputs, ); err != nil { return "", err } @@ -1436,7 +1384,7 @@ func (a *covenantlessArkClient) sendOffchain( log.Infof("payment registered with id: %s", paymentID) poolTxID, err := a.handleRoundStream( - ctx, paymentID, selectedCoins, nil, "", receiversOutput, roundEphemeralKey, + ctx, paymentID, selectedCoins, selectedBoardingCoins, outputs, roundEphemeralKey, ) if err != nil { return "", err @@ -1448,7 +1396,7 @@ func (a *covenantlessArkClient) sendOffchain( func (a *covenantlessArkClient) addInputs( ctx context.Context, updater *psbt.Updater, - utxos []explorer.Utxo, + utxos []types.Utxo, ) error { // TODO works only with single-key wallet offchain, _, err := a.wallet.NewAddress(ctx, false) @@ -1485,7 +1433,7 @@ func (a *covenantlessArkClient) addInputs( updater.Upsbt.UnsignedTx.AddTxIn(&wire.TxIn{ PreviousOutPoint: wire.OutPoint{ Hash: *previousHash, - Index: utxo.Vout, + Index: utxo.VOut, }, Sequence: sequence, }) @@ -1534,8 +1482,7 @@ func (a *covenantlessArkClient) handleRoundStream( ctx context.Context, paymentID string, vtxosToSign []client.DescriptorVtxo, - boardingUtxos []explorer.Utxo, - boardingDescriptor string, + boardingUtxos []types.Utxo, receivers []client.Output, roundEphemeralKey *secp256k1.PrivateKey, ) (string, error) { @@ -1616,7 +1563,7 @@ func (a *covenantlessArkClient) handleRoundStream( log.Info("a round finalization started") signedForfeitTxs, signedRoundTx, err := a.handleRoundFinalization( - ctx, event.(client.RoundFinalizationEvent), vtxosToSign, boardingUtxos, boardingDescriptor, receivers, + ctx, event.(client.RoundFinalizationEvent), vtxosToSign, boardingUtxos, receivers, ) if err != nil { return "", err @@ -1720,8 +1667,7 @@ func (a *covenantlessArkClient) handleRoundFinalization( ctx context.Context, event client.RoundFinalizationEvent, vtxos []client.DescriptorVtxo, - boardingUtxos []explorer.Utxo, - boardingDescriptor string, + boardingUtxos []types.Utxo, receivers []client.Output, ) ([]string, string, error) { if err := a.validateCongestionTree(event, receivers); err != nil { @@ -1741,13 +1687,18 @@ func (a *covenantlessArkClient) handleRoundFinalization( forfeits = signedForfeits } - if len(boardingUtxos) > 0 { - boardingVtxoScript, err := bitcointree.ParseVtxoScript(boardingDescriptor) - if err != nil { - return nil, "", err - } + // if no boarding utxos inputs, we don't need to sign the round transaction + if len(boardingUtxos) <= 0 { + return forfeits, "", nil + } - roundPtx, err := psbt.NewFromRawBytes(strings.NewReader(event.Tx), true) + roundPtx, err := psbt.NewFromRawBytes(strings.NewReader(event.Tx), true) + if err != nil { + return nil, "", err + } + + for _, boardingUtxo := range boardingUtxos { + boardingVtxoScript, err := bitcointree.ParseVtxoScript(boardingUtxo.Descriptor) if err != nil { return nil, "", err } @@ -1758,7 +1709,7 @@ func (a *covenantlessArkClient) handleRoundFinalization( case *bitcointree.DefaultVtxoScript: myPubkey = v.Owner default: - return nil, "", fmt.Errorf("unsupported boarding descriptor: %s", boardingDescriptor) + return nil, "", fmt.Errorf("unsupported boarding descriptor: %s", boardingUtxo.Descriptor) } // add tapscript leaf @@ -1791,28 +1742,24 @@ func (a *covenantlessArkClient) handleRoundFinalization( for i := range roundPtx.Inputs { previousOutpoint := roundPtx.UnsignedTx.TxIn[i].PreviousOutPoint - for _, boardingUtxo := range boardingUtxos { - if boardingUtxo.Txid == previousOutpoint.Hash.String() && boardingUtxo.Vout == previousOutpoint.Index { - roundPtx.Inputs[i].TaprootLeafScript = []*psbt.TaprootTapLeafScript{tapscript} - break - } + if boardingUtxo.Txid == previousOutpoint.Hash.String() && boardingUtxo.VOut == previousOutpoint.Index { + roundPtx.Inputs[i].TaprootLeafScript = []*psbt.TaprootTapLeafScript{tapscript} + break } } - - b64, err := roundPtx.B64Encode() - if err != nil { - return nil, "", err - } - - signedRoundTx, err := a.wallet.SignTransaction(ctx, a.explorer, b64) - if err != nil { - return nil, "", err - } - - return forfeits, signedRoundTx, nil } - return forfeits, "", nil + b64, err := roundPtx.B64Encode() + if err != nil { + return nil, "", err + } + + signedRoundTx, err := a.wallet.SignTransaction(ctx, a.explorer, b64) + if err != nil { + return nil, "", err + } + + return forfeits, signedRoundTx, nil } func (a *covenantlessArkClient) validateCongestionTree( @@ -2021,11 +1968,6 @@ func (a *covenantlessArkClient) createAndSignForfeits( Pubkey: v.Owner, AspPubkey: a.AspPubkey, } - case *bitcointree.ReversibleVtxoScript: - forfeitClosure = &bitcointree.MultisigClosure{ - Pubkey: v.Owner, - AspPubkey: a.AspPubkey, - } default: return nil, fmt.Errorf("unsupported vtxo script: %T", vtxoScript) } @@ -2081,8 +2023,8 @@ func (a *covenantlessArkClient) createAndSignForfeits( } func (a *covenantlessArkClient) coinSelectOnchain( - ctx context.Context, targetAmount uint64, exclude []explorer.Utxo, -) ([]explorer.Utxo, uint64, error) { + ctx context.Context, targetAmount uint64, exclude []types.Utxo, +) ([]types.Utxo, uint64, error) { _, boardingAddrs, redemptionAddrs, err := a.wallet.GetAddresses(ctx) if err != nil { return nil, 0, err @@ -2090,7 +2032,7 @@ func (a *covenantlessArkClient) coinSelectOnchain( now := time.Now() - fetchedUtxos := make([]explorer.Utxo, 0) + fetchedUtxos := make([]types.Utxo, 0) for _, addr := range boardingAddrs { boardingScript, err := bitcointree.ParseVtxoScript(addr.Descriptor) if err != nil { @@ -2110,14 +2052,14 @@ func (a *covenantlessArkClient) coinSelectOnchain( } for _, utxo := range utxos { - u := utxo.ToUtxo(boardingTimeout) + u := utxo.ToUtxo(boardingTimeout, addr.Descriptor) if u.SpendableAt.Before(now) { fetchedUtxos = append(fetchedUtxos, u) } } } - selected := make([]explorer.Utxo, 0) + selected := make([]types.Utxo, 0) selectedAmount := uint64(0) for _, utxo := range fetchedUtxos { if selectedAmount >= targetAmount { @@ -2125,7 +2067,7 @@ func (a *covenantlessArkClient) coinSelectOnchain( } for _, excluded := range exclude { - if utxo.Txid == excluded.Txid && utxo.Vout == excluded.Vout { + if utxo.Txid == excluded.Txid && utxo.VOut == excluded.VOut { continue } } @@ -2138,7 +2080,7 @@ func (a *covenantlessArkClient) coinSelectOnchain( return selected, selectedAmount - targetAmount, nil } - fetchedUtxos = make([]explorer.Utxo, 0) + fetchedUtxos = make([]types.Utxo, 0) for _, addr := range redemptionAddrs { utxos, err := a.explorer.GetUtxos(addr.Address) if err != nil { @@ -2146,7 +2088,7 @@ func (a *covenantlessArkClient) coinSelectOnchain( } for _, utxo := range utxos { - u := utxo.ToUtxo(uint(a.UnilateralExitDelay)) + u := utxo.ToUtxo(uint(a.UnilateralExitDelay), addr.Descriptor) if u.SpendableAt.Before(now) { fetchedUtxos = append(fetchedUtxos, u) } @@ -2159,7 +2101,7 @@ func (a *covenantlessArkClient) coinSelectOnchain( } for _, excluded := range exclude { - if utxo.Txid == excluded.Txid && utxo.Vout == excluded.Vout { + if utxo.Txid == excluded.Txid && utxo.VOut == excluded.VOut { continue } } @@ -2213,15 +2155,14 @@ func (a *covenantlessArkClient) getRedeemBranches( return redeemBranches, nil } -// TODO (@louisinger): return pending balance in dedicated map. -// Currently, the returned balance is calculated from both spendable and -// pending vtxos. func (a *covenantlessArkClient) getOffchainBalance( ctx context.Context, computeVtxoExpiration bool, ) (uint64, map[int64]uint64, error) { amountByExpiration := make(map[int64]uint64, 0) - - vtxos, _, err := a.getVtxos(ctx, computeVtxoExpiration) + opts := &CoinSelectOptions{ + WithExpirySorting: computeVtxoExpiration, + } + vtxos, err := a.getVtxos(ctx, opts) if err != nil { return 0, nil, err } @@ -2245,13 +2186,13 @@ func (a *covenantlessArkClient) getOffchainBalance( func (a *covenantlessArkClient) getAllBoardingUtxos( ctx context.Context, -) ([]explorer.Utxo, map[string]struct{}, error) { +) ([]types.Utxo, map[string]struct{}, error) { _, boardingAddrs, _, err := a.wallet.GetAddresses(ctx) if err != nil { return nil, nil, err } - utxos := []explorer.Utxo{} + utxos := []types.Utxo{} ignoreVtxos := make(map[string]struct{}, 0) for _, addr := range boardingAddrs { txs, err := a.explorer.GetTxs(addr.Address) @@ -2260,6 +2201,7 @@ func (a *covenantlessArkClient) getAllBoardingUtxos( } for _, tx := range txs { for i, vout := range tx.Vout { + var spent bool if vout.Address == addr.Address { spentStatuses, err := a.explorer.GetTxOutspends(tx.Txid) if err != nil { @@ -2267,16 +2209,19 @@ func (a *covenantlessArkClient) getAllBoardingUtxos( } if s := spentStatuses[i]; s.Spent { ignoreVtxos[s.SpentBy] = struct{}{} + spent = true } createdAt := time.Time{} if tx.Status.Confirmed { createdAt = time.Unix(tx.Status.Blocktime, 0) } - utxos = append(utxos, explorer.Utxo{ - Txid: tx.Txid, - Vout: uint32(i), - Amount: vout.Amount, - CreatedAt: createdAt, + utxos = append(utxos, types.Utxo{ + Txid: tx.Txid, + VOut: uint32(i), + Amount: vout.Amount, + CreatedAt: createdAt, + Descriptor: addr.Descriptor, + Spent: spent, }) } } @@ -2286,14 +2231,13 @@ func (a *covenantlessArkClient) getAllBoardingUtxos( return utxos, ignoreVtxos, nil } -func (a *covenantlessArkClient) getClaimableBoardingUtxos(ctx context.Context) ([]explorer.Utxo, error) { +func (a *covenantlessArkClient) getClaimableBoardingUtxos(ctx context.Context, opts *CoinSelectOptions) ([]types.Utxo, error) { _, boardingAddrs, _, err := a.wallet.GetAddresses(ctx) if err != nil { return nil, err } - claimable := make([]explorer.Utxo, 0) - now := time.Now() + claimable := make([]types.Utxo, 0) for _, addr := range boardingAddrs { boardingScript, err := bitcointree.ParseVtxoScript(addr.Descriptor) @@ -2314,47 +2258,65 @@ func (a *covenantlessArkClient) getClaimableBoardingUtxos(ctx context.Context) ( return nil, err } + now := time.Now() + for _, utxo := range boardingUtxos { - u := utxo.ToUtxo(boardingTimeout) + if opts != nil && len(opts.OutpointsFilter) > 0 { + utxoOutpoint := client.Outpoint{ + Txid: utxo.Txid, + VOut: utxo.Vout, + } + found := false + for _, outpoint := range opts.OutpointsFilter { + if outpoint.Equals(utxoOutpoint) { + found = true + break + } + } + + if !found { + continue + } + } + + u := utxo.ToUtxo(boardingTimeout, addr.Descriptor) if u.SpendableAt.Before(now) { continue } + claimable = append(claimable, u) } - } return claimable, nil } func (a *covenantlessArkClient) getVtxos( - ctx context.Context, computeVtxoExpiration bool, -) ([]client.Vtxo, []client.Vtxo, error) { + ctx context.Context, opts *CoinSelectOptions, +) ([]client.Vtxo, error) { spendableVtxos, _, err := a.ListVtxos(ctx) if err != nil { - return nil, nil, err + return nil, err } - pendingVtxos := make([]client.Vtxo, 0) - for _, vtxo := range spendableVtxos { - if vtxo.RedeemTx != "" { - pendingVtxos = append(pendingVtxos, vtxo) - } + if opts != nil && len(opts.OutpointsFilter) > 0 { + spendableVtxos = filterByOutpoints(spendableVtxos, opts.OutpointsFilter) } - if !computeVtxoExpiration { - return spendableVtxos, pendingVtxos, nil + if opts == nil || !opts.WithExpirySorting { + return spendableVtxos, nil } + // if sorting by expiry is required, we need to get the expiration date of each vtxo redeemBranches, err := a.getRedeemBranches(ctx, spendableVtxos) if err != nil { - return nil, nil, err + return nil, err } for vtxoTxid, branch := range redeemBranches { expiration, err := branch.ExpiresAt() if err != nil { - return nil, nil, err + return nil, err } for i, vtxo := range spendableVtxos { @@ -2365,83 +2327,12 @@ func (a *covenantlessArkClient) getVtxos( } } - return spendableVtxos, pendingVtxos, nil + return spendableVtxos, nil } -func (a *covenantlessArkClient) selfTransferAllPendingPayments( - ctx context.Context, pendingVtxos []client.DescriptorVtxo, boardingUtxos []explorer.Utxo, myself client.Output, boardingDescriptor string, -) (string, error) { - inputs := make([]client.Input, 0, len(pendingVtxos)+len(boardingUtxos)) - - for _, coin := range pendingVtxos { - inputs = append(inputs, client.Input{ - Outpoint: client.Outpoint{ - Txid: coin.Txid, - VOut: coin.VOut, - }, - Descriptor: coin.Descriptor, - }) - } - - for _, utxo := range boardingUtxos { - inputs = append(inputs, client.Input{ - Outpoint: client.Outpoint{ - Txid: utxo.Txid, - VOut: utxo.Vout, - }, - Descriptor: boardingDescriptor, - }) - } - outputs := []client.Output{myself} - - roundEphemeralKey, err := secp256k1.GeneratePrivateKey() - if err != nil { - return "", err - } - - paymentID, err := a.client.RegisterInputsForNextRound( - ctx, - inputs, - hex.EncodeToString(roundEphemeralKey.PubKey().SerializeCompressed()), - ) - if err != nil { - return "", err - } - - if err := a.client.RegisterOutputsForNextRound(ctx, paymentID, outputs); err != nil { - return "", err - } - - roundTxid, err := a.handleRoundStream( - ctx, paymentID, pendingVtxos, boardingUtxos, boardingDescriptor, outputs, roundEphemeralKey, - ) - if err != nil { - return "", err - } - - return roundTxid, nil -} - -// getBoardingTxs builds the boarding tx history from onchain utxos: -// - unspent utxo => pending boarding tx -// - spent utxo => claimed boarding tx -// -// The tx spending an onchain utxo is an ark round, therefore an indexed list -// of round txids is returned to specify the vtxos to be ignored to build the -// offchain tx history and prevent duplicates. func (a *covenantlessArkClient) getBoardingTxs( ctx context.Context, ) ([]types.Transaction, map[string]struct{}, error) { - utxos, err := a.getClaimableBoardingUtxos(ctx) - if err != nil { - return nil, nil, err - } - - isPending := make(map[string]bool) - for _, u := range utxos { - isPending[u.Txid] = true - } - allUtxos, ignoreVtxos, err := a.getAllBoardingUtxos(ctx) if err != nil { return nil, nil, err @@ -2450,19 +2341,14 @@ func (a *covenantlessArkClient) getBoardingTxs( unconfirmedTxs := make([]types.Transaction, 0) confirmedTxs := make([]types.Transaction, 0) for _, u := range allUtxos { - pending := false - if isPending[u.Txid] { - pending = true - } - tx := types.Transaction{ TransactionKey: types.TransactionKey{ BoardingTxid: u.Txid, }, Amount: u.Amount, Type: types.TxReceived, - IsPending: pending, CreatedAt: u.CreatedAt, + Settled: u.Spent, } emptyTime := time.Time{} @@ -2490,23 +2376,37 @@ func vtxosToTxsCovenantless( roundLifetime int64, spendable, spent []client.Vtxo, ignoreVtxos map[string]struct{}, ) ([]types.Transaction, error) { transactions := make([]types.Transaction, 0) - indexedTxs := make(map[string]types.Transaction) + settledVtxos := make(map[string]struct{}) + + // First, loop over all vtxos to find those that have been settled + // (they have round txid instead of redeem tx) + for _, v := range append(spendable, spent...) { + _, ok1 := ignoreVtxos[v.Txid] + _, ok2 := ignoreVtxos[v.RoundTxid] + if ok1 || ok2 { + continue + } + if len(v.RoundTxid) > 0 { + settledVtxos[v.RoundTxid] = struct{}{} + } + } + for _, v := range spent { - // If the vtxo was pending and is spent => it's been claimed. - if v.Pending { + // If the vtxo is settled, add the record to the tx history. + if _, ok := settledVtxos[v.SpentBy]; ok { transactions = append(transactions, types.Transaction{ TransactionKey: types.TransactionKey{ RedeemTxid: v.Txid, }, Amount: v.Amount, Type: types.TxReceived, - IsPending: false, CreatedAt: getCreatedAtFromExpiry(roundLifetime, *v.ExpiresAt), + Settled: true, }) // Delete any duplicate in the indexed list. delete(indexedTxs, v.SpentBy) - // Ignore the spendable vtxo created by the claim. + // Ignore the spendable vtxo created by the settlement. ignoreVtxos[v.SpentBy] = struct{}{} continue } @@ -2522,7 +2422,8 @@ func vtxosToTxsCovenantless( indexedTxs[v.Txid] = tx } - // Add a transaction to the indexed list if not existing, it will be deleted if it's a duplicate. + // Add a transaction to the indexed list if not existing. + // This is an intermediate tx state that is updated in the next iterations. tx, ok := indexedTxs[v.SpentBy] if !ok { indexedTxs[v.SpentBy] = types.Transaction{ @@ -2532,7 +2433,6 @@ func vtxosToTxsCovenantless( }, Amount: v.Amount, Type: types.TxSent, - IsPending: false, CreatedAt: getCreatedAtFromExpiry(roundLifetime, *v.ExpiresAt), } continue @@ -2544,6 +2444,7 @@ func vtxosToTxsCovenantless( } for _, v := range spendable { + // Ignore the vtxo eventually. _, ok1 := ignoreVtxos[v.Txid] _, ok2 := ignoreVtxos[v.RoundTxid] if ok1 || ok2 { @@ -2555,6 +2456,7 @@ func vtxosToTxsCovenantless( } tx, ok := indexedTxs[txid] + // If there is no track of records, add a received tx record in the history. if !ok { redeemTxid := "" if v.RoundTxid == "" { @@ -2567,12 +2469,12 @@ func vtxosToTxsCovenantless( }, Amount: v.Amount, Type: types.TxReceived, - IsPending: v.Pending, CreatedAt: getCreatedAtFromExpiry(roundLifetime, *v.ExpiresAt), }) continue } + // Otherwise subtract the amount to find the actual spent amount. tx.Amount -= v.Amount if v.RedeemTx == "" { tx.RedeemTxid = "" diff --git a/pkg/client-sdk/example/covenant/alice_to_bob.go b/pkg/client-sdk/example/covenant/alice_to_bob.go index 99e628f..b11e6b4 100644 --- a/pkg/client-sdk/example/covenant/alice_to_bob.go +++ b/pkg/client-sdk/example/covenant/alice_to_bob.go @@ -55,8 +55,8 @@ func main() { onboardAmount := uint64(1_0000_0000) // 1 BTC log.Infof("alice is onboarding with %d sats offchain...", onboardAmount) - log.Infof("alice claiming onboarding funds...") - txid, err := aliceArkClient.Claim(ctx) + log.Infof("alice settled the onboard funds...") + txid, err := aliceArkClient.Settle(ctx) if err != nil { log.Fatal(err) } diff --git a/pkg/client-sdk/example/covenantless/alice_to_bob.go b/pkg/client-sdk/example/covenantless/alice_to_bob.go index 2563c11..04d2c65 100644 --- a/pkg/client-sdk/example/covenantless/alice_to_bob.go +++ b/pkg/client-sdk/example/covenantless/alice_to_bob.go @@ -84,13 +84,13 @@ 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) + log.Infof("alice is settling the onboard funds...") + txid, err := aliceArkClient.Settle(ctx) if err != nil { log.Fatal(err) } - log.Infof("alice claimed onboarding funds in round %s", txid) + log.Infof("alice settled the onboard funds in round %s", txid) fmt.Println("") log.Info("bob is setting up his ark wallet...") @@ -128,7 +128,7 @@ func main() { fmt.Println("") log.Infof("alice is sending %d sats to bob offchain...", amount) - if _, err = aliceArkClient.SendAsync(ctx, false, receivers); err != nil { + if _, err = aliceArkClient.SendOffChain(ctx, false, receivers); err != nil { log.Fatal(err) } @@ -158,13 +158,13 @@ func main() { log.Infof("bob offchain balance: %d", bobBalance.OffchainBalance.Total) fmt.Println("") - log.Info("bob is claiming the incoming payment...") - roundTxid, err := bobArkClient.Claim(ctx) + log.Info("bob is settling the received funds...") + roundTxid, err := bobArkClient.Settle(ctx) if err != nil { log.Fatal(err) } - log.Infof("bob claimed the incoming payment in round %s", roundTxid) + log.Infof("bob settled the received funds in round %s", roundTxid) time.Sleep(500 * time.Second) } diff --git a/pkg/client-sdk/example/covenantless/wasm/index.html b/pkg/client-sdk/example/covenantless/wasm/index.html index 65f71be..cd40119 100644 --- a/pkg/client-sdk/example/covenantless/wasm/index.html +++ b/pkg/client-sdk/example/covenantless/wasm/index.html @@ -90,19 +90,19 @@ } } - async function claimVtxos() { + async function settleVtxos() { const password = document.getElementById("c_password").value; if (!password) { - logMessage("Claim error: password is required"); + logMessage("Settle error: password is required"); return; } try { await unlock(password); - const txID = await claim(); - logMessage("Claimed money with tx ID: " + txID); + const txID = await settle(); + logMessage("Settled money with tx ID: " + txID); } catch (err) { - logMessage("Claim error: " + err.message); + logMessage("Settle error: " + err.message); } finally { await lock(password); } @@ -166,7 +166,7 @@