mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-17 12:14:21 +01:00
New boarding protocol (#279)
* [domain] add reverse boarding inputs in Payment struct * [tx-builder] support reverse boarding script * [wallet] add GetTransaction * [api-spec][application] add reverse boarding support in covenantless * [config] add reverse boarding config * [api-spec] add ReverseBoardingAddress RPC * [domain][application] support empty forfeits txs in EndFinalization events * [tx-builder] optional connector output in round tx * [btc-embedded] fix getTx and taproot finalizer * whitelist ReverseBoardingAddress RPC * [test] add reverse boarding integration test * [client] support reverse boarding * [sdk] support reverse boarding * [e2e] add sleep time after faucet * [test] run using bitcoin-core RPC * [tx-builder] fix GetSweepInput * [application][tx-builder] support reverse onboarding in covenant * [cli] support reverse onboarding in covenant CLI * [test] rework integration tests * [sdk] remove onchain wallet, replace by onboarding address * remove old onboarding protocols * [sdk] Fix RegisterPayment * [e2e] add more funds to covenant ASP * [e2e] add sleeping time * several fixes * descriptor boarding * remove boarding delay from info * [sdk] implement descriptor boarding * go mod tidy * fixes and revert error msgs * move descriptor pkg to common * add replace in go.mod * [sdk] fix unit tests * rename DescriptorInput --> BoardingInput * genrest in SDK * remove boarding input from domain * remove all "reverse boarding" * rename "onboarding" ==> "boarding" * remove outdate payment unit test * use tmpfs docker volument for compose testing files * several fixes
This commit is contained in:
@@ -16,6 +16,38 @@
|
|||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
|
"/v1/boarding": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "ArkService_GetBoardingAddress",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1GetBoardingAddressResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"description": "An unexpected error response.",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/rpcStatus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/v1GetBoardingAddressRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"ArkService"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/v1/events": {
|
"/v1/events": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "ArkService_GetEventStream",
|
"operationId": "ArkService_GetEventStream",
|
||||||
@@ -69,38 +101,6 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/v1/onboard": {
|
|
||||||
"post": {
|
|
||||||
"operationId": "ArkService_Onboard",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "A successful response.",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/v1OnboardResponse"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"default": {
|
|
||||||
"description": "An unexpected error response.",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/rpcStatus"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "body",
|
|
||||||
"in": "body",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/v1OnboardRequest"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"ArkService"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/v1/payment": {
|
"/v1/payment": {
|
||||||
"post": {
|
"post": {
|
||||||
"operationId": "ArkService_CreatePayment",
|
"operationId": "ArkService_CreatePayment",
|
||||||
@@ -475,6 +475,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"v1BoardingInput": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"txid": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"vout": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"descriptor": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"v1ClaimPaymentRequest": {
|
"v1ClaimPaymentRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -555,12 +570,35 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"description": "Forfeit txs signed by the user."
|
"description": "Forfeit txs signed by the user."
|
||||||
|
},
|
||||||
|
"signedRoundTx": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "If payment has boarding input, the user must sign the associated inputs."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"v1FinalizePaymentResponse": {
|
"v1FinalizePaymentResponse": {
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"v1GetBoardingAddressRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"pubkey": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"v1GetBoardingAddressResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"address": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"descriptor": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"v1GetEventStreamResponse": {
|
"v1GetEventStreamResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -605,6 +643,9 @@
|
|||||||
"minRelayFee": {
|
"minRelayFee": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "int64"
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"boardingDescriptorTemplate": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -627,12 +668,11 @@
|
|||||||
"v1Input": {
|
"v1Input": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"txid": {
|
"vtxoInput": {
|
||||||
"type": "string"
|
"$ref": "#/definitions/v1VtxoInput"
|
||||||
},
|
},
|
||||||
"vout": {
|
"boardingInput": {
|
||||||
"type": "integer",
|
"$ref": "#/definitions/v1BoardingInput"
|
||||||
"format": "int64"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -669,23 +709,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"v1OnboardRequest": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"boardingTx": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"congestionTree": {
|
|
||||||
"$ref": "#/definitions/v1Tree"
|
|
||||||
},
|
|
||||||
"userPubkey": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"v1OnboardResponse": {
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"v1Output": {
|
"v1Output": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -972,6 +995,18 @@
|
|||||||
"$ref": "#/definitions/v1PendingPayment"
|
"$ref": "#/definitions/v1PendingPayment"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"v1VtxoInput": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"txid": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"vout": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,12 +65,12 @@ service ArkService {
|
|||||||
get: "/v1/info"
|
get: "/v1/info"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
rpc Onboard(OnboardRequest) returns (OnboardResponse) {
|
rpc GetBoardingAddress(GetBoardingAddressRequest) returns (GetBoardingAddressResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
post: "/v1/onboard"
|
post: "/v1/boarding"
|
||||||
body: "*"
|
body: "*"
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
rpc CreatePayment(CreatePaymentRequest) returns (CreatePaymentResponse) {
|
rpc CreatePayment(CreatePaymentRequest) returns (CreatePaymentResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
post: "/v1/payment"
|
post: "/v1/payment"
|
||||||
@@ -100,6 +100,15 @@ message CompletePaymentRequest {
|
|||||||
}
|
}
|
||||||
message CompletePaymentResponse {}
|
message CompletePaymentResponse {}
|
||||||
|
|
||||||
|
message GetBoardingAddressRequest {
|
||||||
|
string pubkey = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetBoardingAddressResponse {
|
||||||
|
string address = 1;
|
||||||
|
string descriptor = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message RegisterPaymentRequest {
|
message RegisterPaymentRequest {
|
||||||
repeated Input inputs = 1;
|
repeated Input inputs = 1;
|
||||||
optional string ephemeral_pubkey = 2;
|
optional string ephemeral_pubkey = 2;
|
||||||
@@ -120,6 +129,8 @@ message ClaimPaymentResponse {}
|
|||||||
message FinalizePaymentRequest {
|
message FinalizePaymentRequest {
|
||||||
// Forfeit txs signed by the user.
|
// Forfeit txs signed by the user.
|
||||||
repeated string signed_forfeit_txs = 1;
|
repeated string signed_forfeit_txs = 1;
|
||||||
|
// If payment has boarding input, the user must sign the associated inputs.
|
||||||
|
optional string signed_round_tx = 2;
|
||||||
}
|
}
|
||||||
message FinalizePaymentResponse {}
|
message FinalizePaymentResponse {}
|
||||||
|
|
||||||
@@ -177,14 +188,7 @@ message GetInfoResponse {
|
|||||||
int64 round_interval = 4;
|
int64 round_interval = 4;
|
||||||
string network = 5;
|
string network = 5;
|
||||||
int64 min_relay_fee = 6;
|
int64 min_relay_fee = 6;
|
||||||
}
|
string boarding_descriptor_template = 7;
|
||||||
|
|
||||||
message OnboardRequest {
|
|
||||||
string boarding_tx = 1;
|
|
||||||
Tree congestion_tree = 2;
|
|
||||||
string user_pubkey = 3;
|
|
||||||
}
|
|
||||||
message OnboardResponse {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EVENT TYPES
|
// EVENT TYPES
|
||||||
@@ -239,11 +243,24 @@ message Round {
|
|||||||
RoundStage stage = 8;
|
RoundStage stage = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Input {
|
message VtxoInput {
|
||||||
string txid = 1;
|
string txid = 1;
|
||||||
uint32 vout = 2;
|
uint32 vout = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message BoardingInput {
|
||||||
|
string txid = 1;
|
||||||
|
uint32 vout = 2;
|
||||||
|
string descriptor = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Input {
|
||||||
|
oneof input {
|
||||||
|
VtxoInput vtxo_input = 1;
|
||||||
|
BoardingInput boarding_input = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
message Output {
|
message Output {
|
||||||
// Either the offchain or onchain address.
|
// Either the offchain or onchain address.
|
||||||
string address = 1;
|
string address = 1;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -404,28 +404,28 @@ func local_request_ArkService_GetInfo_0(ctx context.Context, marshaler runtime.M
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func request_ArkService_Onboard_0(ctx context.Context, marshaler runtime.Marshaler, client ArkServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
func request_ArkService_GetBoardingAddress_0(ctx context.Context, marshaler runtime.Marshaler, client ArkServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
var protoReq OnboardRequest
|
var protoReq GetBoardingAddressRequest
|
||||||
var metadata runtime.ServerMetadata
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
|
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
|
||||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
msg, err := client.Onboard(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
msg, err := client.GetBoardingAddress(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||||
return msg, metadata, err
|
return msg, metadata, err
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func local_request_ArkService_Onboard_0(ctx context.Context, marshaler runtime.Marshaler, server ArkServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
func local_request_ArkService_GetBoardingAddress_0(ctx context.Context, marshaler runtime.Marshaler, server ArkServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
var protoReq OnboardRequest
|
var protoReq GetBoardingAddressRequest
|
||||||
var metadata runtime.ServerMetadata
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
|
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
|
||||||
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
msg, err := server.Onboard(ctx, &protoReq)
|
msg, err := server.GetBoardingAddress(ctx, &protoReq)
|
||||||
return msg, metadata, err
|
return msg, metadata, err
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -745,7 +745,7 @@ func RegisterArkServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux,
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.Handle("POST", pattern_ArkService_Onboard_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
mux.Handle("POST", pattern_ArkService_GetBoardingAddress_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
ctx, cancel := context.WithCancel(req.Context())
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
var stream runtime.ServerTransportStream
|
var stream runtime.ServerTransportStream
|
||||||
@@ -753,12 +753,12 @@ func RegisterArkServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux,
|
|||||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
var err error
|
var err error
|
||||||
var annotatedContext context.Context
|
var annotatedContext context.Context
|
||||||
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ark.v1.ArkService/Onboard", runtime.WithHTTPPathPattern("/v1/onboard"))
|
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ark.v1.ArkService/GetBoardingAddress", runtime.WithHTTPPathPattern("/v1/boarding"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
resp, md, err := local_request_ArkService_Onboard_0(annotatedContext, inboundMarshaler, server, req, pathParams)
|
resp, md, err := local_request_ArkService_GetBoardingAddress_0(annotatedContext, inboundMarshaler, server, req, pathParams)
|
||||||
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
|
||||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -766,7 +766,7 @@ func RegisterArkServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
forward_ArkService_Onboard_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
forward_ArkService_GetBoardingAddress_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1103,25 +1103,25 @@ func RegisterArkServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux,
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
mux.Handle("POST", pattern_ArkService_Onboard_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
mux.Handle("POST", pattern_ArkService_GetBoardingAddress_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
ctx, cancel := context.WithCancel(req.Context())
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||||
var err error
|
var err error
|
||||||
var annotatedContext context.Context
|
var annotatedContext context.Context
|
||||||
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ark.v1.ArkService/Onboard", runtime.WithHTTPPathPattern("/v1/onboard"))
|
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ark.v1.ArkService/GetBoardingAddress", runtime.WithHTTPPathPattern("/v1/boarding"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
resp, md, err := request_ArkService_Onboard_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
resp, md, err := request_ArkService_GetBoardingAddress_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
||||||
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
forward_ArkService_Onboard_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
forward_ArkService_GetBoardingAddress_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1195,7 +1195,7 @@ var (
|
|||||||
|
|
||||||
pattern_ArkService_GetInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "info"}, ""))
|
pattern_ArkService_GetInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "info"}, ""))
|
||||||
|
|
||||||
pattern_ArkService_Onboard_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "onboard"}, ""))
|
pattern_ArkService_GetBoardingAddress_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "boarding"}, ""))
|
||||||
|
|
||||||
pattern_ArkService_CreatePayment_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "payment"}, ""))
|
pattern_ArkService_CreatePayment_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "payment"}, ""))
|
||||||
|
|
||||||
@@ -1225,7 +1225,7 @@ var (
|
|||||||
|
|
||||||
forward_ArkService_GetInfo_0 = runtime.ForwardResponseMessage
|
forward_ArkService_GetInfo_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
forward_ArkService_Onboard_0 = runtime.ForwardResponseMessage
|
forward_ArkService_GetBoardingAddress_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
forward_ArkService_CreatePayment_0 = runtime.ForwardResponseMessage
|
forward_ArkService_CreatePayment_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ type ArkServiceClient interface {
|
|||||||
Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error)
|
Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error)
|
||||||
ListVtxos(ctx context.Context, in *ListVtxosRequest, opts ...grpc.CallOption) (*ListVtxosResponse, error)
|
ListVtxos(ctx context.Context, in *ListVtxosRequest, opts ...grpc.CallOption) (*ListVtxosResponse, error)
|
||||||
GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error)
|
GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error)
|
||||||
Onboard(ctx context.Context, in *OnboardRequest, opts ...grpc.CallOption) (*OnboardResponse, error)
|
GetBoardingAddress(ctx context.Context, in *GetBoardingAddressRequest, opts ...grpc.CallOption) (*GetBoardingAddressResponse, error)
|
||||||
CreatePayment(ctx context.Context, in *CreatePaymentRequest, opts ...grpc.CallOption) (*CreatePaymentResponse, error)
|
CreatePayment(ctx context.Context, in *CreatePaymentRequest, opts ...grpc.CallOption) (*CreatePaymentResponse, error)
|
||||||
CompletePayment(ctx context.Context, in *CompletePaymentRequest, opts ...grpc.CallOption) (*CompletePaymentResponse, error)
|
CompletePayment(ctx context.Context, in *CompletePaymentRequest, opts ...grpc.CallOption) (*CompletePaymentResponse, error)
|
||||||
}
|
}
|
||||||
@@ -164,9 +164,9 @@ func (c *arkServiceClient) GetInfo(ctx context.Context, in *GetInfoRequest, opts
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *arkServiceClient) Onboard(ctx context.Context, in *OnboardRequest, opts ...grpc.CallOption) (*OnboardResponse, error) {
|
func (c *arkServiceClient) GetBoardingAddress(ctx context.Context, in *GetBoardingAddressRequest, opts ...grpc.CallOption) (*GetBoardingAddressResponse, error) {
|
||||||
out := new(OnboardResponse)
|
out := new(GetBoardingAddressResponse)
|
||||||
err := c.cc.Invoke(ctx, "/ark.v1.ArkService/Onboard", in, out, opts...)
|
err := c.cc.Invoke(ctx, "/ark.v1.ArkService/GetBoardingAddress", in, out, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -206,7 +206,7 @@ type ArkServiceServer interface {
|
|||||||
Ping(context.Context, *PingRequest) (*PingResponse, error)
|
Ping(context.Context, *PingRequest) (*PingResponse, error)
|
||||||
ListVtxos(context.Context, *ListVtxosRequest) (*ListVtxosResponse, error)
|
ListVtxos(context.Context, *ListVtxosRequest) (*ListVtxosResponse, error)
|
||||||
GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error)
|
GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error)
|
||||||
Onboard(context.Context, *OnboardRequest) (*OnboardResponse, error)
|
GetBoardingAddress(context.Context, *GetBoardingAddressRequest) (*GetBoardingAddressResponse, error)
|
||||||
CreatePayment(context.Context, *CreatePaymentRequest) (*CreatePaymentResponse, error)
|
CreatePayment(context.Context, *CreatePaymentRequest) (*CreatePaymentResponse, error)
|
||||||
CompletePayment(context.Context, *CompletePaymentRequest) (*CompletePaymentResponse, error)
|
CompletePayment(context.Context, *CompletePaymentRequest) (*CompletePaymentResponse, error)
|
||||||
}
|
}
|
||||||
@@ -248,8 +248,8 @@ func (UnimplementedArkServiceServer) ListVtxos(context.Context, *ListVtxosReques
|
|||||||
func (UnimplementedArkServiceServer) GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error) {
|
func (UnimplementedArkServiceServer) GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetInfo not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetInfo not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedArkServiceServer) Onboard(context.Context, *OnboardRequest) (*OnboardResponse, error) {
|
func (UnimplementedArkServiceServer) GetBoardingAddress(context.Context, *GetBoardingAddressRequest) (*GetBoardingAddressResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method Onboard not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetBoardingAddress not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedArkServiceServer) CreatePayment(context.Context, *CreatePaymentRequest) (*CreatePaymentResponse, error) {
|
func (UnimplementedArkServiceServer) CreatePayment(context.Context, *CreatePaymentRequest) (*CreatePaymentResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method CreatePayment not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method CreatePayment not implemented")
|
||||||
@@ -470,20 +470,20 @@ func _ArkService_GetInfo_Handler(srv interface{}, ctx context.Context, dec func(
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func _ArkService_Onboard_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
func _ArkService_GetBoardingAddress_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(OnboardRequest)
|
in := new(GetBoardingAddressRequest)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if interceptor == nil {
|
if interceptor == nil {
|
||||||
return srv.(ArkServiceServer).Onboard(ctx, in)
|
return srv.(ArkServiceServer).GetBoardingAddress(ctx, in)
|
||||||
}
|
}
|
||||||
info := &grpc.UnaryServerInfo{
|
info := &grpc.UnaryServerInfo{
|
||||||
Server: srv,
|
Server: srv,
|
||||||
FullMethod: "/ark.v1.ArkService/Onboard",
|
FullMethod: "/ark.v1.ArkService/GetBoardingAddress",
|
||||||
}
|
}
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
return srv.(ArkServiceServer).Onboard(ctx, req.(*OnboardRequest))
|
return srv.(ArkServiceServer).GetBoardingAddress(ctx, req.(*GetBoardingAddressRequest))
|
||||||
}
|
}
|
||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
@@ -572,8 +572,8 @@ var ArkService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
Handler: _ArkService_GetInfo_Handler,
|
Handler: _ArkService_GetInfo_Handler,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
MethodName: "Onboard",
|
MethodName: "GetBoardingAddress",
|
||||||
Handler: _ArkService_Onboard_Handler,
|
Handler: _ArkService_GetBoardingAddress_Handler,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
MethodName: "CreatePayment",
|
MethodName: "CreatePayment",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
|
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
|
||||||
"github.com/ark-network/ark/client/flags"
|
"github.com/ark-network/ark/client/flags"
|
||||||
"github.com/ark-network/ark/client/utils"
|
"github.com/ark-network/ark/client/utils"
|
||||||
|
"github.com/ark-network/ark/common/descriptor"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,18 +22,30 @@ func (*covenantLiquidCLI) Balance(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
offchainAddr, onchainAddr, redemptionAddr, err := getAddress(ctx)
|
offchainAddr, boardingAddr, redemptionAddr, err := getAddress(ctx)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
network, err := utils.GetNetwork(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// No need to check for error here becuase this function is called also by getAddress().
|
// No need to check for error here becuase this function is called also by getAddress().
|
||||||
// nolint:all
|
// nolint:all
|
||||||
unilateralExitDelay, _ := utils.GetUnilateralExitDelay(ctx)
|
unilateralExitDelay, _ := utils.GetUnilateralExitDelay(ctx)
|
||||||
|
|
||||||
|
boardingDescriptor, err := utils.GetBoardingDescriptor(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
desc, err := descriptor.ParseTaprootDescriptor(boardingDescriptor)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, timeoutBoarding, err := descriptor.ParseBoardingDescriptor(*desc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
wg := &sync.WaitGroup{}
|
wg := &sync.WaitGroup{}
|
||||||
wg.Add(3)
|
wg.Add(3)
|
||||||
|
|
||||||
@@ -54,19 +67,19 @@ func (*covenantLiquidCLI) Balance(ctx *cli.Context) error {
|
|||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
explorer := utils.NewExplorer(ctx)
|
explorer := utils.NewExplorer(ctx)
|
||||||
balance, err := explorer.GetBalance(onchainAddr, toElementsNetwork(network).AssetID)
|
spendableBalance, lockedBalance, err := explorer.GetDelayedBalance(boardingAddr, int64(timeoutBoarding))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
chRes <- balanceRes{0, 0, nil, nil, err}
|
chRes <- balanceRes{0, 0, nil, nil, err}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
chRes <- balanceRes{0, balance, nil, nil, nil}
|
chRes <- balanceRes{0, spendableBalance, lockedBalance, nil, nil}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
explorer := utils.NewExplorer(ctx)
|
explorer := utils.NewExplorer(ctx)
|
||||||
|
|
||||||
spendableBalance, lockedBalance, err := explorer.GetRedeemedVtxosBalance(
|
spendableBalance, lockedBalance, err := explorer.GetDelayedBalance(
|
||||||
redemptionAddr, unilateralExitDelay,
|
redemptionAddr, unilateralExitDelay,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
148
client/covenant/claim.go
Normal file
148
client/covenant/claim.go
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
package covenant
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
|
||||||
|
"github.com/ark-network/ark/client/utils"
|
||||||
|
"github.com/ark-network/ark/common/descriptor"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *covenantLiquidCLI) Claim(ctx *cli.Context) error {
|
||||||
|
client, cancel, err := getClientFromState(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
offchainAddr, boardingAddr, _, err := getAddress(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
boardingDescriptor, err := utils.GetBoardingDescriptor(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
desc, err := descriptor.ParseTaprootDescriptor(boardingDescriptor)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, timeoutBoarding, err := descriptor.ParseBoardingDescriptor(*desc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
explorer := utils.NewExplorer(ctx)
|
||||||
|
|
||||||
|
boardingUtxosFromExplorer, err := explorer.GetUtxos(boardingAddr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
boardingUtxos := make([]utils.Utxo, 0, len(boardingUtxosFromExplorer))
|
||||||
|
for _, utxo := range boardingUtxosFromExplorer {
|
||||||
|
u := utils.NewUtxo(utxo, uint(timeoutBoarding))
|
||||||
|
if u.SpendableAt.Before(now) {
|
||||||
|
continue // cannot claim if onchain spendable
|
||||||
|
}
|
||||||
|
|
||||||
|
boardingUtxos = append(boardingUtxos, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pendingBalance uint64
|
||||||
|
|
||||||
|
for _, utxo := range boardingUtxos {
|
||||||
|
pendingBalance += utxo.Amount
|
||||||
|
}
|
||||||
|
|
||||||
|
if pendingBalance == 0 {
|
||||||
|
return fmt.Errorf("no boarding utxos to claim")
|
||||||
|
}
|
||||||
|
|
||||||
|
receiver := receiver{
|
||||||
|
To: offchainAddr,
|
||||||
|
Amount: pendingBalance,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ctx.String("password")) == 0 {
|
||||||
|
if ok := askForConfirmation(
|
||||||
|
fmt.Sprintf(
|
||||||
|
"claim %d satoshis from %d boarding utxos",
|
||||||
|
pendingBalance, len(boardingUtxos),
|
||||||
|
),
|
||||||
|
); !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return selfTransferAllPendingPayments(
|
||||||
|
ctx, client, boardingUtxos, receiver, boardingDescriptor,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func selfTransferAllPendingPayments(
|
||||||
|
ctx *cli.Context,
|
||||||
|
client arkv1.ArkServiceClient,
|
||||||
|
boardingUtxos []utils.Utxo,
|
||||||
|
myself receiver,
|
||||||
|
desc string,
|
||||||
|
) error {
|
||||||
|
inputs := make([]*arkv1.Input, 0, len(boardingUtxos))
|
||||||
|
|
||||||
|
for _, outpoint := range boardingUtxos {
|
||||||
|
inputs = append(inputs, &arkv1.Input{
|
||||||
|
Input: &arkv1.Input_BoardingInput{
|
||||||
|
BoardingInput: &arkv1.BoardingInput{
|
||||||
|
Txid: outpoint.Txid,
|
||||||
|
Vout: outpoint.Vout,
|
||||||
|
Descriptor_: desc,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
receiversOutput := []*arkv1.Output{
|
||||||
|
{
|
||||||
|
Address: myself.To,
|
||||||
|
Amount: myself.Amount,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
secKey, err := utils.PrivateKeyFromPassword(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
registerResponse, err := client.RegisterPayment(
|
||||||
|
ctx.Context, &arkv1.RegisterPaymentRequest{Inputs: inputs},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.ClaimPayment(ctx.Context, &arkv1.ClaimPaymentRequest{
|
||||||
|
Id: registerResponse.GetId(),
|
||||||
|
Outputs: []*arkv1.Output{{Address: myself.To, Amount: myself.Amount}},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
poolTxID, err := handleRoundStream(
|
||||||
|
ctx, client, registerResponse.GetId(), make([]vtxo, 0),
|
||||||
|
len(boardingUtxos) > 0, secKey, receiversOutput,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.PrintJSON(map[string]interface{}{
|
||||||
|
"pool_txid": poolTxID,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -9,16 +9,13 @@ import (
|
|||||||
"github.com/ark-network/ark/client/interfaces"
|
"github.com/ark-network/ark/client/interfaces"
|
||||||
"github.com/ark-network/ark/client/utils"
|
"github.com/ark-network/ark/client/utils"
|
||||||
"github.com/ark-network/ark/common"
|
"github.com/ark-network/ark/common"
|
||||||
|
"github.com/ark-network/ark/common/descriptor"
|
||||||
"github.com/ark-network/ark/common/tree"
|
"github.com/ark-network/ark/common/tree"
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"github.com/vulpemventures/go-elements/address"
|
"github.com/vulpemventures/go-elements/address"
|
||||||
"github.com/vulpemventures/go-elements/elementsutil"
|
|
||||||
"github.com/vulpemventures/go-elements/network"
|
|
||||||
"github.com/vulpemventures/go-elements/payment"
|
"github.com/vulpemventures/go-elements/payment"
|
||||||
"github.com/vulpemventures/go-elements/psetv2"
|
"github.com/vulpemventures/go-elements/psetv2"
|
||||||
"github.com/vulpemventures/go-elements/taproot"
|
|
||||||
"github.com/vulpemventures/go-elements/transaction"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const dust = 450
|
const dust = 450
|
||||||
@@ -29,19 +26,15 @@ func (c *covenantLiquidCLI) SendAsync(ctx *cli.Context) error {
|
|||||||
return fmt.Errorf("not implemented")
|
return fmt.Errorf("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *covenantLiquidCLI) ClaimAsync(ctx *cli.Context) error {
|
|
||||||
return fmt.Errorf("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *covenantLiquidCLI) Receive(ctx *cli.Context) error {
|
func (c *covenantLiquidCLI) Receive(ctx *cli.Context) error {
|
||||||
offchainAddr, onchainAddr, _, err := getAddress(ctx)
|
offchainAddr, boardingAddr, _, err := getAddress(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return utils.PrintJSON(map[string]interface{}{
|
return utils.PrintJSON(map[string]interface{}{
|
||||||
"offchain_address": offchainAddr,
|
"offchain_address": offchainAddr,
|
||||||
"onchain_address": onchainAddr,
|
"boarding_address": boardingAddr,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,14 +124,14 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) {
|
|||||||
|
|
||||||
explorer := utils.NewExplorer(ctx)
|
explorer := utils.NewExplorer(ctx)
|
||||||
|
|
||||||
utxos, delayedUtxos, change, err := coinSelectOnchain(
|
utxos, change, err := coinSelectOnchain(
|
||||||
ctx, explorer, targetAmount, nil,
|
ctx, explorer, targetAmount, nil,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := addInputs(ctx, updater, utxos, delayedUtxos, &liquidNet); err != nil {
|
if err := addInputs(ctx, updater, utxos); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,14 +174,14 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) {
|
|||||||
updater.Pset.Outputs = updater.Pset.Outputs[:len(updater.Pset.Outputs)-1]
|
updater.Pset.Outputs = updater.Pset.Outputs[:len(updater.Pset.Outputs)-1]
|
||||||
}
|
}
|
||||||
// reselect the difference
|
// reselect the difference
|
||||||
selected, delayedSelected, newChange, err := coinSelectOnchain(
|
selected, newChange, err := coinSelectOnchain(
|
||||||
ctx, explorer, feeAmount-change, append(utxos, delayedUtxos...),
|
ctx, explorer, feeAmount-change, utxos,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := addInputs(ctx, updater, selected, delayedSelected, &liquidNet); err != nil {
|
if err := addInputs(ctx, updater, selected); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,20 +236,37 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) {
|
|||||||
func coinSelectOnchain(
|
func coinSelectOnchain(
|
||||||
ctx *cli.Context,
|
ctx *cli.Context,
|
||||||
explorer utils.Explorer, targetAmount uint64, exclude []utils.Utxo,
|
explorer utils.Explorer, targetAmount uint64, exclude []utils.Utxo,
|
||||||
) ([]utils.Utxo, []utils.Utxo, uint64, error) {
|
) ([]utils.Utxo, uint64, error) {
|
||||||
_, onchainAddr, _, err := getAddress(ctx)
|
_, boardingAddr, redemptionAddr, err := getAddress(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fromExplorer, err := explorer.GetUtxos(onchainAddr)
|
boardingUtxosFromExplorer, err := explorer.GetUtxos(boardingAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
utxos := make([]utils.Utxo, 0)
|
utxos := make([]utils.Utxo, 0)
|
||||||
selectedAmount := uint64(0)
|
selectedAmount := uint64(0)
|
||||||
for _, utxo := range fromExplorer {
|
now := time.Now()
|
||||||
|
|
||||||
|
boardingDescriptor, err := utils.GetBoardingDescriptor(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
desc, err := descriptor.ParseTaprootDescriptor(boardingDescriptor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, timeoutBoarding, err := descriptor.ParseBoardingDescriptor(*desc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, utxo := range boardingUtxosFromExplorer {
|
||||||
if selectedAmount >= targetAmount {
|
if selectedAmount >= targetAmount {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -267,207 +277,99 @@ func coinSelectOnchain(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
utxos = append(utxos, utxo)
|
utxo := utils.NewUtxo(utxo, uint(timeoutBoarding))
|
||||||
selectedAmount += utxo.Amount
|
|
||||||
|
if utxo.SpendableAt.Before(now) {
|
||||||
|
utxos = append(utxos, utxo)
|
||||||
|
selectedAmount += utxo.Amount
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if selectedAmount >= targetAmount {
|
if selectedAmount >= targetAmount {
|
||||||
return utxos, nil, selectedAmount - targetAmount, nil
|
return utxos, selectedAmount - targetAmount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
userPubkey, err := utils.GetWalletPublicKey(ctx)
|
redemptionUtxosFromExplorer, err := explorer.GetUtxos(redemptionAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
aspPubkey, err := utils.GetAspPublicKey(ctx)
|
vtxoExitDelay, err := utils.GetUnilateralExitDelay(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
unilateralExitDelay, err := utils.GetUnilateralExitDelay(ctx)
|
for _, utxo := range redemptionUtxosFromExplorer {
|
||||||
if err != nil {
|
|
||||||
return nil, nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
vtxoTapKey, _, err := computeVtxoTaprootScript(
|
|
||||||
userPubkey, aspPubkey, uint(unilateralExitDelay),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
net, err := utils.GetNetwork(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
liquidNet := toElementsNetwork(net)
|
|
||||||
|
|
||||||
pay, err := payment.FromTweakedKey(vtxoTapKey, &liquidNet, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
addr, err := pay.TaprootAddress()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fromExplorer, err = explorer.GetUtxos(addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
delayedUtxos := make([]utils.Utxo, 0)
|
|
||||||
for _, utxo := range fromExplorer {
|
|
||||||
if selectedAmount >= targetAmount {
|
if selectedAmount >= targetAmount {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
availableAt := time.Unix(utxo.Status.Blocktime, 0).Add(
|
|
||||||
time.Duration(unilateralExitDelay) * time.Second,
|
|
||||||
)
|
|
||||||
if availableAt.After(time.Now()) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, excluded := range exclude {
|
for _, excluded := range exclude {
|
||||||
if utxo.Txid == excluded.Txid && utxo.Vout == excluded.Vout {
|
if utxo.Txid == excluded.Txid && utxo.Vout == excluded.Vout {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delayedUtxos = append(delayedUtxos, utxo)
|
utxo := utils.NewUtxo(utxo, uint(vtxoExitDelay))
|
||||||
selectedAmount += utxo.Amount
|
|
||||||
|
if utxo.SpendableAt.Before(now) {
|
||||||
|
utxos = append(utxos, utxo)
|
||||||
|
selectedAmount += utxo.Amount
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if selectedAmount < targetAmount {
|
if selectedAmount < targetAmount {
|
||||||
return nil, nil, 0, fmt.Errorf(
|
return nil, 0, fmt.Errorf(
|
||||||
"not enough funds to cover amount %d", targetAmount,
|
"not enough funds to cover amount %d", targetAmount,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return utxos, delayedUtxos, selectedAmount - targetAmount, nil
|
return utxos, selectedAmount - targetAmount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addInputs(
|
func addInputs(
|
||||||
ctx *cli.Context,
|
ctx *cli.Context,
|
||||||
updater *psetv2.Updater, utxos, delayedUtxos []utils.Utxo, net *network.Network,
|
updater *psetv2.Updater,
|
||||||
|
utxos []utils.Utxo,
|
||||||
) error {
|
) error {
|
||||||
_, onchainAddr, _, err := getAddress(ctx)
|
userPubkey, err := utils.GetWalletPublicKey(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
changeScript, err := address.ToOutputScript(onchainAddr)
|
aspPubkey, err := utils.GetAspPublicKey(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, utxo := range utxos {
|
for _, utxo := range utxos {
|
||||||
|
sequence, err := utxo.Sequence()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err := updater.AddInputs([]psetv2.InputArgs{
|
if err := updater.AddInputs([]psetv2.InputArgs{
|
||||||
{
|
{
|
||||||
Txid: utxo.Txid,
|
Txid: utxo.Txid,
|
||||||
TxIndex: utxo.Vout,
|
TxIndex: utxo.Vout,
|
||||||
|
Sequence: sequence,
|
||||||
},
|
},
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
assetID, err := elementsutil.AssetHashToBytes(utxo.Asset)
|
_, leafProof, err := computeVtxoTaprootScript(
|
||||||
if err != nil {
|
userPubkey, aspPubkey, utxo.Delay,
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
value, err := elementsutil.ValueToBytes(utxo.Amount)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
witnessUtxo := transaction.TxOutput{
|
|
||||||
Asset: assetID,
|
|
||||||
Value: value,
|
|
||||||
Script: changeScript,
|
|
||||||
Nonce: []byte{0x00},
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := updater.AddInWitnessUtxo(
|
|
||||||
len(updater.Pset.Inputs)-1, &witnessUtxo,
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(delayedUtxos) > 0 {
|
|
||||||
userPubkey, err := utils.GetWalletPublicKey(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
aspPubkey, err := utils.GetAspPublicKey(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
unilateralExitDelay, err := utils.GetUnilateralExitDelay(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
vtxoTapKey, leafProof, err := computeVtxoTaprootScript(
|
|
||||||
userPubkey, aspPubkey, uint(unilateralExitDelay),
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
pay, err := payment.FromTweakedKey(vtxoTapKey, net, nil)
|
inputIndex := len(updater.Pset.Inputs) - 1
|
||||||
if err != nil {
|
|
||||||
|
if err := updater.AddInTapLeafScript(inputIndex, psetv2.NewTapLeafScript(*leafProof, tree.UnspendableKey())); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
addr, err := pay.TaprootAddress()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
script, err := address.ToOutputScript(addr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, utxo := range delayedUtxos {
|
|
||||||
if err := addVtxoInput(
|
|
||||||
updater,
|
|
||||||
psetv2.InputArgs{
|
|
||||||
Txid: utxo.Txid,
|
|
||||||
TxIndex: utxo.Vout,
|
|
||||||
},
|
|
||||||
uint(unilateralExitDelay),
|
|
||||||
leafProof,
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
assetID, err := elementsutil.AssetHashToBytes(utxo.Asset)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
value, err := elementsutil.ValueToBytes(utxo.Amount)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
witnessUtxo := transaction.NewTxOutput(assetID, value, script)
|
|
||||||
|
|
||||||
if err := updater.AddInWitnessUtxo(
|
|
||||||
len(updater.Pset.Inputs)-1, witnessUtxo,
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -503,32 +405,7 @@ func decodeReceiverAddress(addr string) (
|
|||||||
return true, outputScript, nil, nil
|
return true, outputScript, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addVtxoInput(
|
func getAddress(ctx *cli.Context) (offchainAddr, boardingAddr, redemptionAddr string, err error) {
|
||||||
updater *psetv2.Updater, inputArgs psetv2.InputArgs, exitDelay uint,
|
|
||||||
tapLeafProof *taproot.TapscriptElementsProof,
|
|
||||||
) error {
|
|
||||||
sequence, err := common.BIP68EncodeAsNumber(exitDelay)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
nextInputIndex := len(updater.Pset.Inputs)
|
|
||||||
if err := updater.AddInputs([]psetv2.InputArgs{inputArgs}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
updater.Pset.Inputs[nextInputIndex].Sequence = sequence
|
|
||||||
|
|
||||||
return updater.AddInTapLeafScript(
|
|
||||||
nextInputIndex,
|
|
||||||
psetv2.NewTapLeafScript(
|
|
||||||
*tapLeafProof,
|
|
||||||
tree.UnspendableKey(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAddress(ctx *cli.Context) (offchainAddr, onchainAddr, redemptionAddr string, err error) {
|
|
||||||
userPubkey, err := utils.GetWalletPublicKey(ctx)
|
userPubkey, err := utils.GetWalletPublicKey(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -544,6 +421,21 @@ func getAddress(ctx *cli.Context) (offchainAddr, onchainAddr, redemptionAddr str
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boardingDescriptor, err := utils.GetBoardingDescriptor(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
desc, err := descriptor.ParseTaprootDescriptor(boardingDescriptor)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, timeoutBoarding, err := descriptor.ParseBoardingDescriptor(*desc)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
arkNet, err := utils.GetNetwork(ctx)
|
arkNet, err := utils.GetNetwork(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -556,12 +448,6 @@ func getAddress(ctx *cli.Context) (offchainAddr, onchainAddr, redemptionAddr str
|
|||||||
|
|
||||||
liquidNet := toElementsNetwork(arkNet)
|
liquidNet := toElementsNetwork(arkNet)
|
||||||
|
|
||||||
p2wpkh := payment.FromPublicKey(userPubkey, &liquidNet, nil)
|
|
||||||
liquidAddr, err := p2wpkh.WitnessPubKeyHash()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
vtxoTapKey, _, err := computeVtxoTaprootScript(
|
vtxoTapKey, _, err := computeVtxoTaprootScript(
|
||||||
userPubkey, aspPubkey, uint(unilateralExitDelay),
|
userPubkey, aspPubkey, uint(unilateralExitDelay),
|
||||||
)
|
)
|
||||||
@@ -569,18 +455,34 @@ func getAddress(ctx *cli.Context) (offchainAddr, onchainAddr, redemptionAddr str
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
payment, err := payment.FromTweakedKey(vtxoTapKey, &liquidNet, nil)
|
redemptionPay, err := payment.FromTweakedKey(vtxoTapKey, &liquidNet, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
redemptionAddr, err = payment.TaprootAddress()
|
redemptionAddr, err = redemptionPay.TaprootAddress()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
boardingTapKey, _, err := computeVtxoTaprootScript(
|
||||||
|
userPubkey, aspPubkey, uint(timeoutBoarding),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
boardingPay, err := payment.FromTweakedKey(boardingTapKey, &liquidNet, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
boardingAddr, err = boardingPay.TaprootAddress()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
offchainAddr = arkAddr
|
offchainAddr = arkAddr
|
||||||
onchainAddr = liquidAddr
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,13 +51,16 @@ func getVtxos(
|
|||||||
if v.Swept {
|
if v.Swept {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
vtxos = append(vtxos, vtxo{
|
|
||||||
amount: v.Receiver.Amount,
|
if v.Outpoint.GetVtxoInput() != nil {
|
||||||
txid: v.Outpoint.Txid,
|
vtxos = append(vtxos, vtxo{
|
||||||
vout: v.Outpoint.Vout,
|
amount: v.Receiver.Amount,
|
||||||
poolTxid: v.PoolTxid,
|
txid: v.Outpoint.GetVtxoInput().GetTxid(),
|
||||||
expireAt: expireAt,
|
vout: v.Outpoint.GetVtxoInput().GetVout(),
|
||||||
})
|
poolTxid: v.PoolTxid,
|
||||||
|
expireAt: expireAt,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !computeExpiration {
|
if !computeExpiration {
|
||||||
@@ -193,32 +196,10 @@ func toCongestionTree(treeFromProto *arkv1.Tree) (tree.CongestionTree, error) {
|
|||||||
return levels, nil
|
return levels, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// castCongestionTree converts a tree.CongestionTree to a repeated arkv1.TreeLevel
|
|
||||||
func castCongestionTree(congestionTree tree.CongestionTree) *arkv1.Tree {
|
|
||||||
levels := make([]*arkv1.TreeLevel, 0, len(congestionTree))
|
|
||||||
for _, level := range congestionTree {
|
|
||||||
levelProto := &arkv1.TreeLevel{
|
|
||||||
Nodes: make([]*arkv1.Node, 0, len(level)),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, node := range level {
|
|
||||||
levelProto.Nodes = append(levelProto.Nodes, &arkv1.Node{
|
|
||||||
Txid: node.Txid,
|
|
||||||
Tx: node.Tx,
|
|
||||||
ParentTxid: node.ParentTxid,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
levels = append(levels, levelProto)
|
|
||||||
}
|
|
||||||
return &arkv1.Tree{
|
|
||||||
Levels: levels,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleRoundStream(
|
func handleRoundStream(
|
||||||
ctx *cli.Context, client arkv1.ArkServiceClient, paymentID string,
|
ctx *cli.Context, client arkv1.ArkServiceClient, paymentID string,
|
||||||
vtxosToSign []vtxo, secKey *secp256k1.PrivateKey, receivers []*arkv1.Output,
|
vtxosToSign []vtxo, mustSignRoundTx bool,
|
||||||
|
secKey *secp256k1.PrivateKey, receivers []*arkv1.Output,
|
||||||
) (poolTxID string, err error) {
|
) (poolTxID string, err error) {
|
||||||
stream, err := client.GetEventStream(ctx.Context, &arkv1.GetEventStreamRequest{})
|
stream, err := client.GetEventStream(ctx.Context, &arkv1.GetEventStreamRequest{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -254,8 +235,8 @@ func handleRoundStream(
|
|||||||
pingStop()
|
pingStop()
|
||||||
fmt.Println("round finalization started")
|
fmt.Println("round finalization started")
|
||||||
|
|
||||||
poolTx := e.GetPoolTx()
|
roundTx := e.GetPoolTx()
|
||||||
ptx, err := psetv2.NewPsetFromBase64(poolTx)
|
ptx, err := psetv2.NewPsetFromBase64(roundTx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -280,13 +261,13 @@ func handleRoundStream(
|
|||||||
if !isOnchainOnly(receivers) {
|
if !isOnchainOnly(receivers) {
|
||||||
// validate the congestion tree
|
// validate the congestion tree
|
||||||
if err := tree.ValidateCongestionTree(
|
if err := tree.ValidateCongestionTree(
|
||||||
congestionTree, poolTx, aspPubkey, int64(roundLifetime),
|
congestionTree, roundTx, aspPubkey, int64(roundLifetime),
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := common.ValidateConnectors(poolTx, connectors); err != nil {
|
if err := common.ValidateConnectors(roundTx, connectors); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,78 +360,100 @@ func handleRoundStream(
|
|||||||
|
|
||||||
fmt.Println("congestion tree validated")
|
fmt.Println("congestion tree validated")
|
||||||
|
|
||||||
forfeits := e.GetForfeitTxs()
|
|
||||||
signedForfeits := make([]string, 0)
|
|
||||||
|
|
||||||
fmt.Print("signing forfeit txs... ")
|
|
||||||
|
|
||||||
explorer := utils.NewExplorer(ctx)
|
explorer := utils.NewExplorer(ctx)
|
||||||
|
finalizePaymentRequest := &arkv1.FinalizePaymentRequest{}
|
||||||
|
|
||||||
connectorsTxids := make([]string, 0, len(connectors))
|
if len(vtxosToSign) > 0 {
|
||||||
for _, connector := range connectors {
|
forfeits := e.GetForfeitTxs()
|
||||||
p, _ := psetv2.NewPsetFromBase64(connector)
|
signedForfeits := make([]string, 0)
|
||||||
utx, _ := p.UnsignedTx()
|
|
||||||
txid := utx.TxHash().String()
|
|
||||||
|
|
||||||
connectorsTxids = append(connectorsTxids, txid)
|
fmt.Print("signing forfeit txs... ")
|
||||||
|
|
||||||
|
connectorsTxids := make([]string, 0, len(connectors))
|
||||||
|
for _, connector := range connectors {
|
||||||
|
p, _ := psetv2.NewPsetFromBase64(connector)
|
||||||
|
utx, _ := p.UnsignedTx()
|
||||||
|
txid := utx.TxHash().String()
|
||||||
|
|
||||||
|
connectorsTxids = append(connectorsTxids, txid)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, forfeit := range forfeits {
|
||||||
|
pset, err := psetv2.NewPsetFromBase64(forfeit)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, input := range pset.Inputs {
|
||||||
|
inputTxid := chainhash.Hash(input.PreviousTxid).String()
|
||||||
|
|
||||||
|
for _, coin := range vtxosToSign {
|
||||||
|
// check if it contains one of the input to sign
|
||||||
|
if inputTxid == coin.txid {
|
||||||
|
// verify that the connector is in the connectors list
|
||||||
|
connectorTxid := chainhash.Hash(pset.Inputs[0].PreviousTxid).String()
|
||||||
|
connectorFound := false
|
||||||
|
for _, txid := range connectorsTxids {
|
||||||
|
if txid == connectorTxid {
|
||||||
|
connectorFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !connectorFound {
|
||||||
|
return "", fmt.Errorf("connector txid %s not found in the connectors list", connectorTxid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := signPset(ctx, pset, explorer, secKey); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
signedPset, err := pset.ToBase64()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
signedForfeits = append(signedForfeits, signedPset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no forfeit txs have been signed, start pinging again and wait for the next round
|
||||||
|
if len(signedForfeits) == 0 {
|
||||||
|
fmt.Printf("\nno forfeit txs to sign, waiting for the next round...\n")
|
||||||
|
pingStop = nil
|
||||||
|
for pingStop == nil {
|
||||||
|
pingStop = ping(ctx.Context, client, pingReq)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%d signed\n", len(signedForfeits))
|
||||||
|
finalizePaymentRequest.SignedForfeitTxs = signedForfeits
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, forfeit := range forfeits {
|
if mustSignRoundTx {
|
||||||
pset, err := psetv2.NewPsetFromBase64(forfeit)
|
ptx, err := psetv2.NewPsetFromBase64(roundTx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, input := range pset.Inputs {
|
if err := signPset(ctx, ptx, explorer, secKey); err != nil {
|
||||||
inputTxid := chainhash.Hash(input.PreviousTxid).String()
|
return "", err
|
||||||
|
|
||||||
for _, coin := range vtxosToSign {
|
|
||||||
// check if it contains one of the input to sign
|
|
||||||
if inputTxid == coin.txid {
|
|
||||||
// verify that the connector is in the connectors list
|
|
||||||
connectorTxid := chainhash.Hash(pset.Inputs[0].PreviousTxid).String()
|
|
||||||
connectorFound := false
|
|
||||||
for _, txid := range connectorsTxids {
|
|
||||||
if txid == connectorTxid {
|
|
||||||
connectorFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !connectorFound {
|
|
||||||
return "", fmt.Errorf("connector txid %s not found in the connectors list", connectorTxid)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := signPset(ctx, pset, explorer, secKey); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
signedPset, err := pset.ToBase64()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
signedForfeits = append(signedForfeits, signedPset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signedRoundTx, err := ptx.ToBase64()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("round tx signed")
|
||||||
|
finalizePaymentRequest.SignedRoundTx = &signedRoundTx
|
||||||
}
|
}
|
||||||
|
|
||||||
// if no forfeit txs have been signed, start pinging again and wait for the next round
|
|
||||||
if len(signedForfeits) == 0 {
|
|
||||||
fmt.Printf("\nno forfeit txs to sign, waiting for the next round...\n")
|
|
||||||
pingStop = nil
|
|
||||||
for pingStop == nil {
|
|
||||||
pingStop = ping(ctx.Context, client, pingReq)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("%d signed\n", len(signedForfeits))
|
|
||||||
fmt.Print("finalizing payment... ")
|
fmt.Print("finalizing payment... ")
|
||||||
_, err = client.FinalizePayment(ctx.Context, &arkv1.FinalizePaymentRequest{
|
_, err = client.FinalizePayment(ctx.Context, finalizePaymentRequest)
|
||||||
SignedForfeitTxs: signedForfeits,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ func connectToAsp(ctx *cli.Context, net, url, explorer string) error {
|
|||||||
utils.ROUND_LIFETIME: strconv.Itoa(int(resp.GetRoundLifetime())),
|
utils.ROUND_LIFETIME: strconv.Itoa(int(resp.GetRoundLifetime())),
|
||||||
utils.UNILATERAL_EXIT_DELAY: strconv.Itoa(int(resp.GetUnilateralExitDelay())),
|
utils.UNILATERAL_EXIT_DELAY: strconv.Itoa(int(resp.GetUnilateralExitDelay())),
|
||||||
utils.EXPLORER: explorer,
|
utils.EXPLORER: explorer,
|
||||||
|
utils.BOARDING_TEMPLATE: resp.GetBoardingDescriptorTemplate(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,115 +0,0 @@
|
|||||||
package covenant
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
|
|
||||||
"github.com/ark-network/ark/client/utils"
|
|
||||||
"github.com/ark-network/ark/common/tree"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"github.com/vulpemventures/go-elements/payment"
|
|
||||||
"github.com/vulpemventures/go-elements/psetv2"
|
|
||||||
)
|
|
||||||
|
|
||||||
const minRelayFee = 30
|
|
||||||
|
|
||||||
func (c *covenantLiquidCLI) Onboard(ctx *cli.Context) error {
|
|
||||||
amount := ctx.Uint64("amount")
|
|
||||||
|
|
||||||
if amount <= 0 {
|
|
||||||
return fmt.Errorf("missing amount flag (--amount)")
|
|
||||||
}
|
|
||||||
|
|
||||||
net, err := utils.GetNetwork(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
userPubKey, err := utils.GetWalletPublicKey(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
client, cancel, err := getClientFromState(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
aspPubkey, err := utils.GetAspPublicKey(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
roundLifetime, err := utils.GetRoundLifetime(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
unilateralExitDelay, err := utils.GetUnilateralExitDelay(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
congestionTreeLeaf := tree.Receiver{
|
|
||||||
Pubkey: hex.EncodeToString(userPubKey.SerializeCompressed()),
|
|
||||||
Amount: amount,
|
|
||||||
}
|
|
||||||
|
|
||||||
liquidNet := toElementsNetwork(net)
|
|
||||||
|
|
||||||
treeFactoryFn, sharedOutputScript, sharedOutputAmount, err := tree.CraftCongestionTree(
|
|
||||||
liquidNet.AssetID, aspPubkey, []tree.Receiver{congestionTreeLeaf},
|
|
||||||
minRelayFee, roundLifetime, unilateralExitDelay,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pay, err := payment.FromScript(sharedOutputScript, &liquidNet, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
address, err := pay.TaprootAddress()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
onchainReceiver := receiver{
|
|
||||||
To: address,
|
|
||||||
Amount: sharedOutputAmount,
|
|
||||||
}
|
|
||||||
|
|
||||||
pset, err := sendOnchain(ctx, []receiver{onchainReceiver})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ptx, _ := psetv2.NewPsetFromBase64(pset)
|
|
||||||
utx, _ := ptx.UnsignedTx()
|
|
||||||
txid := utx.TxHash().String()
|
|
||||||
|
|
||||||
congestionTree, err := treeFactoryFn(psetv2.InputArgs{
|
|
||||||
Txid: txid,
|
|
||||||
TxIndex: 0,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = client.Onboard(ctx.Context, &arkv1.OnboardRequest{
|
|
||||||
BoardingTx: pset,
|
|
||||||
CongestionTree: castCongestionTree(congestionTree),
|
|
||||||
UserPubkey: hex.EncodeToString(userPubKey.SerializeCompressed()),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("onboard_txid:", txid)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -78,8 +78,12 @@ func collaborativeRedeem(
|
|||||||
|
|
||||||
for _, coin := range selectedCoins {
|
for _, coin := range selectedCoins {
|
||||||
inputs = append(inputs, &arkv1.Input{
|
inputs = append(inputs, &arkv1.Input{
|
||||||
Txid: coin.txid,
|
Input: &arkv1.Input_VtxoInput{
|
||||||
Vout: coin.vout,
|
VtxoInput: &arkv1.VtxoInput{
|
||||||
|
Txid: coin.txid,
|
||||||
|
Vout: coin.vout,
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,6 +112,7 @@ func collaborativeRedeem(
|
|||||||
client,
|
client,
|
||||||
registerResponse.GetId(),
|
registerResponse.GetId(),
|
||||||
selectedCoins,
|
selectedCoins,
|
||||||
|
false,
|
||||||
secKey,
|
secKey,
|
||||||
receivers,
|
receivers,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -143,8 +143,12 @@ func sendOffchain(ctx *cli.Context, receivers []receiver) error {
|
|||||||
|
|
||||||
for _, coin := range selectedCoins {
|
for _, coin := range selectedCoins {
|
||||||
inputs = append(inputs, &arkv1.Input{
|
inputs = append(inputs, &arkv1.Input{
|
||||||
Txid: coin.txid,
|
Input: &arkv1.Input_VtxoInput{
|
||||||
Vout: coin.vout,
|
VtxoInput: &arkv1.VtxoInput{
|
||||||
|
Txid: coin.txid,
|
||||||
|
Vout: coin.vout,
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,7 +174,7 @@ func sendOffchain(ctx *cli.Context, receivers []receiver) error {
|
|||||||
|
|
||||||
poolTxID, err := handleRoundStream(
|
poolTxID, err := handleRoundStream(
|
||||||
ctx, client, registerResponse.GetId(),
|
ctx, client, registerResponse.GetId(),
|
||||||
selectedCoins, secKey, receiversOutput,
|
selectedCoins, false, secKey, receiversOutput,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -6,14 +6,11 @@ import (
|
|||||||
|
|
||||||
"github.com/ark-network/ark/client/utils"
|
"github.com/ark-network/ark/client/utils"
|
||||||
"github.com/ark-network/ark/common/tree"
|
"github.com/ark-network/ark/common/tree"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
|
||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"github.com/vulpemventures/go-elements/address"
|
|
||||||
"github.com/vulpemventures/go-elements/payment"
|
|
||||||
"github.com/vulpemventures/go-elements/psetv2"
|
"github.com/vulpemventures/go-elements/psetv2"
|
||||||
"github.com/vulpemventures/go-elements/transaction"
|
"github.com/vulpemventures/go-elements/transaction"
|
||||||
)
|
)
|
||||||
@@ -50,13 +47,7 @@ func signPset(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
sighashType := txscript.SigHashAll
|
if err := updater.AddInSighashType(i, txscript.SigHashDefault); err != nil {
|
||||||
|
|
||||||
if utxo.Script[0] == txscript.OP_1 {
|
|
||||||
sighashType = txscript.SigHashDefault
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := updater.AddInSighashType(i, sighashType); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,16 +57,6 @@ func signPset(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, onchainAddr, _, err := getAddress(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
onchainWalletScript, err := address.ToOutputScript(onchainAddr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
utx, err := pset.UnsignedTx()
|
utx, err := pset.UnsignedTx()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -99,34 +80,6 @@ func signPset(
|
|||||||
liquidNet := toElementsNetwork(net)
|
liquidNet := toElementsNetwork(net)
|
||||||
|
|
||||||
for i, input := range pset.Inputs {
|
for i, input := range pset.Inputs {
|
||||||
if bytes.Equal(input.WitnessUtxo.Script, onchainWalletScript) {
|
|
||||||
p, err := payment.FromScript(input.WitnessUtxo.Script, &liquidNet, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
preimage := utx.HashForWitnessV0(
|
|
||||||
i,
|
|
||||||
p.Script,
|
|
||||||
input.WitnessUtxo.Value,
|
|
||||||
txscript.SigHashAll,
|
|
||||||
)
|
|
||||||
|
|
||||||
sig := ecdsa.Sign(
|
|
||||||
prvKey,
|
|
||||||
preimage[:],
|
|
||||||
)
|
|
||||||
|
|
||||||
signatureWithSighashType := append(sig.Serialize(), byte(txscript.SigHashAll))
|
|
||||||
|
|
||||||
err = signer.SignInput(i, signatureWithSighashType, prvKey.PubKey().SerializeCompressed(), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("error signing input: ", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(input.TapLeafScript) > 0 {
|
if len(input.TapLeafScript) > 0 {
|
||||||
genesis, err := chainhash.NewHashFromStr(liquidNet.GenesisBlockHash)
|
genesis, err := chainhash.NewHashFromStr(liquidNet.GenesisBlockHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
|
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
|
||||||
"github.com/ark-network/ark/client/flags"
|
"github.com/ark-network/ark/client/flags"
|
||||||
"github.com/ark-network/ark/client/utils"
|
"github.com/ark-network/ark/client/utils"
|
||||||
|
"github.com/ark-network/ark/common/descriptor"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ func (*clArkBitcoinCLI) Balance(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
offchainAddr, onchainAddr, redemptionAddr, err := getAddress(ctx)
|
offchainAddr, boardingAddr, redemptionAddr, err := getAddress(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -29,6 +30,21 @@ func (*clArkBitcoinCLI) Balance(ctx *cli.Context) error {
|
|||||||
// nolint:all
|
// nolint:all
|
||||||
unilateralExitDelay, _ := utils.GetUnilateralExitDelay(ctx)
|
unilateralExitDelay, _ := utils.GetUnilateralExitDelay(ctx)
|
||||||
|
|
||||||
|
boardingDescriptor, err := utils.GetBoardingDescriptor(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
desc, err := descriptor.ParseTaprootDescriptor(boardingDescriptor)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, timeoutBoarding, err := descriptor.ParseBoardingDescriptor(*desc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
wg := &sync.WaitGroup{}
|
wg := &sync.WaitGroup{}
|
||||||
wg.Add(3)
|
wg.Add(3)
|
||||||
|
|
||||||
@@ -50,19 +66,19 @@ func (*clArkBitcoinCLI) Balance(ctx *cli.Context) error {
|
|||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
explorer := utils.NewExplorer(ctx)
|
explorer := utils.NewExplorer(ctx)
|
||||||
balance, err := explorer.GetBalance(onchainAddr.EncodeAddress(), "")
|
balance, lockedBalance, err := explorer.GetDelayedBalance(boardingAddr.EncodeAddress(), int64(timeoutBoarding))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
chRes <- balanceRes{0, 0, nil, nil, err}
|
chRes <- balanceRes{0, 0, nil, nil, err}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
chRes <- balanceRes{0, balance, nil, nil, nil}
|
chRes <- balanceRes{0, balance, lockedBalance, nil, nil}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
explorer := utils.NewExplorer(ctx)
|
explorer := utils.NewExplorer(ctx)
|
||||||
|
|
||||||
spendableBalance, lockedBalance, err := explorer.GetRedeemedVtxosBalance(
|
spendableBalance, lockedBalance, err := explorer.GetDelayedBalance(
|
||||||
redemptionAddr.EncodeAddress(), unilateralExitDelay,
|
redemptionAddr.EncodeAddress(), unilateralExitDelay,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -126,6 +142,7 @@ func (*clArkBitcoinCLI) Balance(ctx *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
response := make(map[string]interface{})
|
response := make(map[string]interface{})
|
||||||
|
|
||||||
response["onchain_balance"] = map[string]interface{}{
|
response["onchain_balance"] = map[string]interface{}{
|
||||||
"spendable_amount": onchainBalance,
|
"spendable_amount": onchainBalance,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,26 +2,62 @@ package covenantless
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
|
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
|
||||||
"github.com/ark-network/ark/client/utils"
|
"github.com/ark-network/ark/client/utils"
|
||||||
|
"github.com/ark-network/ark/common/descriptor"
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *clArkBitcoinCLI) ClaimAsync(ctx *cli.Context) error {
|
func (c *clArkBitcoinCLI) Claim(ctx *cli.Context) error {
|
||||||
client, cancel, err := getClientFromState(ctx)
|
client, cancel, err := getClientFromState(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
myselfOffchain, _, _, err := getAddress(ctx)
|
offchainAddr, boardingAddr, _, err := getAddress(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
vtxos, err := getVtxos(ctx, nil, client, myselfOffchain, false)
|
boardingDescriptor, err := utils.GetBoardingDescriptor(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
desc, err := descriptor.ParseTaprootDescriptor(boardingDescriptor)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, timeoutBoarding, err := descriptor.ParseBoardingDescriptor(*desc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
explorer := utils.NewExplorer(ctx)
|
||||||
|
|
||||||
|
boardingUtxosFromExplorer, err := explorer.GetUtxos(boardingAddr.EncodeAddress())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
boardingUtxos := make([]utils.Utxo, 0, len(boardingUtxosFromExplorer))
|
||||||
|
for _, utxo := range boardingUtxosFromExplorer {
|
||||||
|
u := utils.NewUtxo(utxo, uint(timeoutBoarding))
|
||||||
|
if u.SpendableAt.Before(now) {
|
||||||
|
continue // cannot claim if onchain spendable
|
||||||
|
}
|
||||||
|
|
||||||
|
boardingUtxos = append(boardingUtxos, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
vtxos, err := getVtxos(ctx, nil, client, offchainAddr, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -34,32 +70,71 @@ func (c *clArkBitcoinCLI) ClaimAsync(ctx *cli.Context) error {
|
|||||||
pendingVtxos = append(pendingVtxos, vtxo)
|
pendingVtxos = append(pendingVtxos, vtxo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, utxo := range boardingUtxos {
|
||||||
|
pendingBalance += utxo.Amount
|
||||||
|
}
|
||||||
|
|
||||||
if pendingBalance == 0 {
|
if pendingBalance == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
receiver := receiver{
|
receiver := receiver{
|
||||||
To: myselfOffchain,
|
To: offchainAddr,
|
||||||
Amount: pendingBalance,
|
Amount: pendingBalance,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(ctx.String("password")) == 0 {
|
||||||
|
if ok := askForConfirmation(
|
||||||
|
fmt.Sprintf(
|
||||||
|
"claim %d satoshis from %d pending payments and %d boarding utxos",
|
||||||
|
pendingBalance, len(pendingVtxos), len(boardingUtxos),
|
||||||
|
),
|
||||||
|
); !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return selfTransferAllPendingPayments(
|
return selfTransferAllPendingPayments(
|
||||||
ctx, client, pendingVtxos, receiver,
|
ctx, client, pendingVtxos, boardingUtxos, receiver, boardingDescriptor,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func selfTransferAllPendingPayments(
|
func selfTransferAllPendingPayments(
|
||||||
ctx *cli.Context, client arkv1.ArkServiceClient,
|
ctx *cli.Context,
|
||||||
pendingVtxos []vtxo, myself receiver,
|
client arkv1.ArkServiceClient,
|
||||||
|
pendingVtxos []vtxo,
|
||||||
|
boardingUtxos []utils.Utxo,
|
||||||
|
myself receiver,
|
||||||
|
desc string,
|
||||||
) error {
|
) error {
|
||||||
inputs := make([]*arkv1.Input, 0, len(pendingVtxos))
|
inputs := make([]*arkv1.Input, 0, len(pendingVtxos)+len(boardingUtxos))
|
||||||
|
|
||||||
for _, coin := range pendingVtxos {
|
for _, coin := range pendingVtxos {
|
||||||
inputs = append(inputs, &arkv1.Input{
|
inputs = append(inputs, &arkv1.Input{
|
||||||
Txid: coin.txid,
|
Input: &arkv1.Input_VtxoInput{
|
||||||
Vout: coin.vout,
|
VtxoInput: &arkv1.VtxoInput{
|
||||||
|
Txid: coin.txid,
|
||||||
|
Vout: coin.vout,
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(boardingUtxos) > 0 {
|
||||||
|
for _, outpoint := range boardingUtxos {
|
||||||
|
inputs = append(inputs, &arkv1.Input{
|
||||||
|
Input: &arkv1.Input_BoardingInput{
|
||||||
|
BoardingInput: &arkv1.BoardingInput{
|
||||||
|
Txid: outpoint.Txid,
|
||||||
|
Vout: outpoint.Vout,
|
||||||
|
Descriptor_: desc,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
receiversOutput := []*arkv1.Output{
|
receiversOutput := []*arkv1.Output{
|
||||||
{
|
{
|
||||||
Address: myself.To,
|
Address: myself.To,
|
||||||
@@ -99,8 +174,8 @@ func selfTransferAllPendingPayments(
|
|||||||
}
|
}
|
||||||
|
|
||||||
poolTxID, err := handleRoundStream(
|
poolTxID, err := handleRoundStream(
|
||||||
ctx, client, registerResponse.GetId(),
|
ctx, client, registerResponse.GetId(), pendingVtxos,
|
||||||
pendingVtxos, secKey, receiversOutput, ephemeralKey,
|
len(boardingUtxos) > 0, secKey, receiversOutput, ephemeralKey,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import (
|
|||||||
"github.com/ark-network/ark/client/utils"
|
"github.com/ark-network/ark/client/utils"
|
||||||
"github.com/ark-network/ark/common"
|
"github.com/ark-network/ark/common"
|
||||||
"github.com/ark-network/ark/common/bitcointree"
|
"github.com/ark-network/ark/common/bitcointree"
|
||||||
|
"github.com/ark-network/ark/common/descriptor"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"github.com/btcsuite/btcd/btcutil"
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
"github.com/btcsuite/btcd/btcutil/psbt"
|
"github.com/btcsuite/btcd/btcutil/psbt"
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
@@ -25,14 +25,14 @@ const dust = 450
|
|||||||
type clArkBitcoinCLI struct{}
|
type clArkBitcoinCLI struct{}
|
||||||
|
|
||||||
func (c *clArkBitcoinCLI) Receive(ctx *cli.Context) error {
|
func (c *clArkBitcoinCLI) Receive(ctx *cli.Context) error {
|
||||||
offchainAddr, onchainAddr, _, err := getAddress(ctx)
|
offchainAddr, boardingAddr, _, err := getAddress(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return utils.PrintJSON(map[string]interface{}{
|
return utils.PrintJSON(map[string]interface{}{
|
||||||
"offchain_address": offchainAddr,
|
"offchain_address": offchainAddr,
|
||||||
"onchain_address": onchainAddr.EncodeAddress(),
|
"boarding_address": boardingAddr.EncodeAddress(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,11 +79,6 @@ type receiver struct {
|
|||||||
Amount uint64 `json:"amount"`
|
Amount uint64 `json:"amount"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// func (r *receiver) isOnchain() bool {
|
|
||||||
// _, err := btcutil.DecodeAddress(r.To, nil)
|
|
||||||
// return err == nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) {
|
func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) {
|
||||||
ptx, err := psbt.New(nil, nil, 2, 0, nil)
|
ptx, err := psbt.New(nil, nil, 2, 0, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -128,14 +123,14 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) {
|
|||||||
|
|
||||||
explorer := utils.NewExplorer(ctx)
|
explorer := utils.NewExplorer(ctx)
|
||||||
|
|
||||||
utxos, delayedUtxos, change, err := coinSelectOnchain(
|
utxos, change, err := coinSelectOnchain(
|
||||||
ctx, explorer, targetAmount, nil,
|
ctx, explorer, targetAmount, nil,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := addInputs(ctx, updater, utxos, delayedUtxos, &netParams); err != nil {
|
if err := addInputs(ctx, updater, utxos); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,14 +169,14 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) {
|
|||||||
updater.Upsbt.UnsignedTx.TxOut = updater.Upsbt.UnsignedTx.TxOut[:len(updater.Upsbt.UnsignedTx.TxOut)-1]
|
updater.Upsbt.UnsignedTx.TxOut = updater.Upsbt.UnsignedTx.TxOut[:len(updater.Upsbt.UnsignedTx.TxOut)-1]
|
||||||
}
|
}
|
||||||
// reselect the difference
|
// reselect the difference
|
||||||
selected, delayedSelected, newChange, err := coinSelectOnchain(
|
selected, newChange, err := coinSelectOnchain(
|
||||||
ctx, explorer, feeAmount-change, append(utxos, delayedUtxos...),
|
ctx, explorer, feeAmount-change, utxos,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := addInputs(ctx, updater, selected, delayedSelected, &netParams); err != nil {
|
if err := addInputs(ctx, updater, selected); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,20 +220,37 @@ func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) {
|
|||||||
func coinSelectOnchain(
|
func coinSelectOnchain(
|
||||||
ctx *cli.Context,
|
ctx *cli.Context,
|
||||||
explorer utils.Explorer, targetAmount uint64, exclude []utils.Utxo,
|
explorer utils.Explorer, targetAmount uint64, exclude []utils.Utxo,
|
||||||
) ([]utils.Utxo, []utils.Utxo, uint64, error) {
|
) ([]utils.Utxo, uint64, error) {
|
||||||
_, onchainAddr, _, err := getAddress(ctx)
|
_, boardingAddr, redemptionAddr, err := getAddress(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fromExplorer, err := explorer.GetUtxos(onchainAddr.EncodeAddress())
|
boardingUtxosFromExplorer, err := explorer.GetUtxos(boardingAddr.EncodeAddress())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
utxos := make([]utils.Utxo, 0)
|
utxos := make([]utils.Utxo, 0)
|
||||||
selectedAmount := uint64(0)
|
selectedAmount := uint64(0)
|
||||||
for _, utxo := range fromExplorer {
|
now := time.Now()
|
||||||
|
|
||||||
|
boardingDescriptor, err := utils.GetBoardingDescriptor(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
desc, err := descriptor.ParseTaprootDescriptor(boardingDescriptor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, timeoutBoarding, err := descriptor.ParseBoardingDescriptor(*desc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, utxo := range boardingUtxosFromExplorer {
|
||||||
if selectedAmount >= targetAmount {
|
if selectedAmount >= targetAmount {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -249,102 +261,67 @@ func coinSelectOnchain(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
utxos = append(utxos, utxo)
|
utxo := utils.NewUtxo(utxo, uint(timeoutBoarding))
|
||||||
selectedAmount += utxo.Amount
|
|
||||||
|
if utxo.SpendableAt.After(now) {
|
||||||
|
utxos = append(utxos, utxo)
|
||||||
|
selectedAmount += utxo.Amount
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if selectedAmount >= targetAmount {
|
if selectedAmount >= targetAmount {
|
||||||
return utxos, nil, selectedAmount - targetAmount, nil
|
return utxos, selectedAmount - targetAmount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
userPubkey, err := utils.GetWalletPublicKey(ctx)
|
redemptionUtxosFromExplorer, err := explorer.GetUtxos(redemptionAddr.EncodeAddress())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
aspPubkey, err := utils.GetAspPublicKey(ctx)
|
vtxoExitDelay, err := utils.GetUnilateralExitDelay(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
unilateralExitDelay, err := utils.GetUnilateralExitDelay(ctx)
|
for _, utxo := range redemptionUtxosFromExplorer {
|
||||||
if err != nil {
|
|
||||||
return nil, nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
vtxoTapKey, _, err := computeVtxoTaprootScript(
|
|
||||||
userPubkey, aspPubkey, uint(unilateralExitDelay),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
net, err := utils.GetNetwork(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
liquidNet := toChainParams(net)
|
|
||||||
|
|
||||||
p2tr, err := btcutil.NewAddressTaproot(
|
|
||||||
schnorr.SerializePubKey(vtxoTapKey),
|
|
||||||
&liquidNet,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
addr := p2tr.EncodeAddress()
|
|
||||||
|
|
||||||
fromExplorer, err = explorer.GetUtxos(addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
delayedUtxos := make([]utils.Utxo, 0)
|
|
||||||
for _, utxo := range fromExplorer {
|
|
||||||
if selectedAmount >= targetAmount {
|
if selectedAmount >= targetAmount {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
availableAt := time.Unix(utxo.Status.Blocktime, 0).Add(
|
|
||||||
time.Duration(unilateralExitDelay) * time.Second,
|
|
||||||
)
|
|
||||||
if availableAt.After(time.Now()) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, excluded := range exclude {
|
for _, excluded := range exclude {
|
||||||
if utxo.Txid == excluded.Txid && utxo.Vout == excluded.Vout {
|
if utxo.Txid == excluded.Txid && utxo.Vout == excluded.Vout {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delayedUtxos = append(delayedUtxos, utxo)
|
utxo := utils.NewUtxo(utxo, uint(vtxoExitDelay))
|
||||||
selectedAmount += utxo.Amount
|
|
||||||
|
if utxo.SpendableAt.After(now) {
|
||||||
|
utxos = append(utxos, utxo)
|
||||||
|
selectedAmount += utxo.Amount
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if selectedAmount < targetAmount {
|
if selectedAmount < targetAmount {
|
||||||
return nil, nil, 0, fmt.Errorf(
|
return nil, 0, fmt.Errorf(
|
||||||
"not enough funds to cover amount %d", targetAmount,
|
"not enough funds to cover amount %d", targetAmount,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return utxos, delayedUtxos, selectedAmount - targetAmount, nil
|
return utxos, selectedAmount - targetAmount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addInputs(
|
func addInputs(
|
||||||
ctx *cli.Context,
|
ctx *cli.Context,
|
||||||
updater *psbt.Updater,
|
updater *psbt.Updater,
|
||||||
utxos, delayedUtxos []utils.Utxo,
|
utxos []utils.Utxo,
|
||||||
net *chaincfg.Params,
|
|
||||||
) error {
|
) error {
|
||||||
_, onchainAddr, _, err := getAddress(ctx)
|
userPubkey, err := utils.GetWalletPublicKey(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
changeScript, err := txscript.PayToAddrScript(onchainAddr)
|
aspPubkey, err := utils.GetAspPublicKey(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -355,87 +332,41 @@ func addInputs(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sequence, err := utxo.Sequence()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
updater.Upsbt.UnsignedTx.AddTxIn(&wire.TxIn{
|
updater.Upsbt.UnsignedTx.AddTxIn(&wire.TxIn{
|
||||||
PreviousOutPoint: wire.OutPoint{
|
PreviousOutPoint: wire.OutPoint{
|
||||||
Hash: *previousHash,
|
Hash: *previousHash,
|
||||||
Index: utxo.Vout,
|
Index: utxo.Vout,
|
||||||
},
|
},
|
||||||
|
Sequence: sequence,
|
||||||
})
|
})
|
||||||
|
|
||||||
updater.Upsbt.Inputs = append(updater.Upsbt.Inputs, psbt.PInput{})
|
_, leafProof, err := computeVtxoTaprootScript(
|
||||||
|
userPubkey, aspPubkey, utxo.Delay,
|
||||||
if err := updater.AddInWitnessUtxo(
|
|
||||||
&wire.TxOut{
|
|
||||||
Value: int64(utxo.Amount),
|
|
||||||
PkScript: changeScript,
|
|
||||||
},
|
|
||||||
len(updater.Upsbt.UnsignedTx.TxIn)-1,
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(delayedUtxos) > 0 {
|
|
||||||
userPubkey, err := utils.GetWalletPublicKey(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
aspPubkey, err := utils.GetAspPublicKey(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
unilateralExitDelay, err := utils.GetUnilateralExitDelay(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
vtxoTapKey, leafProof, err := computeVtxoTaprootScript(
|
|
||||||
userPubkey, aspPubkey, uint(unilateralExitDelay),
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
p2tr, err := btcutil.NewAddressTaproot(schnorr.SerializePubKey(vtxoTapKey), net)
|
controlBlock := leafProof.ToControlBlock(bitcointree.UnspendableKey())
|
||||||
|
controlBlockBytes, err := controlBlock.ToBytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
script, err := txscript.PayToAddrScript(p2tr)
|
updater.Upsbt.Inputs = append(updater.Upsbt.Inputs, psbt.PInput{
|
||||||
if err != nil {
|
TaprootLeafScript: []*psbt.TaprootTapLeafScript{
|
||||||
return err
|
{
|
||||||
}
|
ControlBlock: controlBlockBytes,
|
||||||
|
Script: leafProof.Script,
|
||||||
for _, utxo := range delayedUtxos {
|
LeafVersion: leafProof.LeafVersion,
|
||||||
previousHash, err := chainhash.NewHashFromStr(utxo.Txid)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := addVtxoInput(
|
|
||||||
updater,
|
|
||||||
&wire.OutPoint{
|
|
||||||
Hash: *previousHash,
|
|
||||||
Index: utxo.Vout,
|
|
||||||
},
|
},
|
||||||
uint(unilateralExitDelay),
|
},
|
||||||
leafProof,
|
})
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := updater.AddInWitnessUtxo(
|
|
||||||
&wire.TxOut{
|
|
||||||
Value: int64(utxo.Amount),
|
|
||||||
PkScript: script,
|
|
||||||
},
|
|
||||||
len(updater.Upsbt.Inputs)-1,
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -461,40 +392,7 @@ func decodeReceiverAddress(addr string) (
|
|||||||
return true, pkscript, nil, nil
|
return true, pkscript, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addVtxoInput(
|
func getAddress(ctx *cli.Context) (offchainAddr string, boardingAddr, redemptionAddr btcutil.Address, err error) {
|
||||||
updater *psbt.Updater, inputArgs *wire.OutPoint, exitDelay uint,
|
|
||||||
tapLeafProof *txscript.TapscriptProof,
|
|
||||||
) error {
|
|
||||||
sequence, err := common.BIP68EncodeAsNumber(exitDelay)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
nextInputIndex := len(updater.Upsbt.Inputs)
|
|
||||||
updater.Upsbt.UnsignedTx.AddTxIn(&wire.TxIn{
|
|
||||||
PreviousOutPoint: *inputArgs,
|
|
||||||
Sequence: sequence,
|
|
||||||
})
|
|
||||||
updater.Upsbt.Inputs = append(updater.Upsbt.Inputs, psbt.PInput{})
|
|
||||||
|
|
||||||
controlBlock := tapLeafProof.ToControlBlock(bitcointree.UnspendableKey())
|
|
||||||
controlBlockBytes, err := controlBlock.ToBytes()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
updater.Upsbt.Inputs[nextInputIndex].TaprootLeafScript = []*psbt.TaprootTapLeafScript{
|
|
||||||
{
|
|
||||||
ControlBlock: controlBlockBytes,
|
|
||||||
Script: tapLeafProof.Script,
|
|
||||||
LeafVersion: tapLeafProof.LeafVersion,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAddress(ctx *cli.Context) (offchainAddr string, onchainAddr, redemptionAddr btcutil.Address, err error) {
|
|
||||||
userPubkey, err := utils.GetWalletPublicKey(ctx)
|
userPubkey, err := utils.GetWalletPublicKey(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -510,6 +408,21 @@ func getAddress(ctx *cli.Context) (offchainAddr string, onchainAddr, redemptionA
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boardingDescriptor, err := utils.GetBoardingDescriptor(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
desc, err := descriptor.ParseTaprootDescriptor(boardingDescriptor)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, timeoutBoarding, err := descriptor.ParseBoardingDescriptor(*desc)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
arkNet, err := utils.GetNetwork(ctx)
|
arkNet, err := utils.GetNetwork(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -522,11 +435,6 @@ func getAddress(ctx *cli.Context) (offchainAddr string, onchainAddr, redemptionA
|
|||||||
|
|
||||||
netParams := toChainParams(arkNet)
|
netParams := toChainParams(arkNet)
|
||||||
|
|
||||||
p2wpkh, err := btcutil.NewAddressWitnessPubKeyHash(btcutil.Hash160(userPubkey.SerializeCompressed()), &netParams)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
vtxoTapKey, _, err := computeVtxoTaprootScript(
|
vtxoTapKey, _, err := computeVtxoTaprootScript(
|
||||||
userPubkey, aspPubkey, uint(unilateralExitDelay),
|
userPubkey, aspPubkey, uint(unilateralExitDelay),
|
||||||
)
|
)
|
||||||
@@ -534,7 +442,7 @@ func getAddress(ctx *cli.Context) (offchainAddr string, onchainAddr, redemptionA
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
p2tr, err := btcutil.NewAddressTaproot(
|
redemptionP2TR, err := btcutil.NewAddressTaproot(
|
||||||
schnorr.SerializePubKey(vtxoTapKey),
|
schnorr.SerializePubKey(vtxoTapKey),
|
||||||
&netParams,
|
&netParams,
|
||||||
)
|
)
|
||||||
@@ -542,8 +450,20 @@ func getAddress(ctx *cli.Context) (offchainAddr string, onchainAddr, redemptionA
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
redemptionAddr = p2tr
|
boardingTapKey, _, err := computeVtxoTaprootScript(
|
||||||
onchainAddr = p2wpkh
|
userPubkey, aspPubkey, uint(timeoutBoarding),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
boardingP2TR, err := btcutil.NewAddressTaproot(
|
||||||
|
schnorr.SerializePubKey(boardingTapKey),
|
||||||
|
&netParams,
|
||||||
|
)
|
||||||
|
|
||||||
|
redemptionAddr = redemptionP2TR
|
||||||
|
boardingAddr = boardingP2TR
|
||||||
offchainAddr = arkAddr
|
offchainAddr = arkAddr
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -53,14 +53,16 @@ func getVtxos(
|
|||||||
if v.GetSwept() {
|
if v.GetSwept() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
vtxos = append(vtxos, vtxo{
|
if v.Outpoint.GetVtxoInput() != nil {
|
||||||
amount: v.GetReceiver().GetAmount(),
|
vtxos = append(vtxos, vtxo{
|
||||||
txid: v.GetOutpoint().GetTxid(),
|
amount: v.Receiver.Amount,
|
||||||
vout: v.GetOutpoint().GetVout(),
|
txid: v.Outpoint.GetVtxoInput().GetTxid(),
|
||||||
poolTxid: v.GetPoolTxid(),
|
vout: v.Outpoint.GetVtxoInput().GetVout(),
|
||||||
expireAt: expireAt,
|
poolTxid: v.PoolTxid,
|
||||||
pending: v.GetPending(),
|
expireAt: expireAt,
|
||||||
})
|
pending: v.GetPending(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !computeExpiration {
|
if !computeExpiration {
|
||||||
@@ -196,32 +198,10 @@ func toCongestionTree(treeFromProto *arkv1.Tree) (tree.CongestionTree, error) {
|
|||||||
return levels, nil
|
return levels, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// castCongestionTree converts a tree.CongestionTree to a repeated arkv1.TreeLevel
|
|
||||||
func castCongestionTree(congestionTree tree.CongestionTree) *arkv1.Tree {
|
|
||||||
levels := make([]*arkv1.TreeLevel, 0, len(congestionTree))
|
|
||||||
for _, level := range congestionTree {
|
|
||||||
levelProto := &arkv1.TreeLevel{
|
|
||||||
Nodes: make([]*arkv1.Node, 0, len(level)),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, node := range level {
|
|
||||||
levelProto.Nodes = append(levelProto.Nodes, &arkv1.Node{
|
|
||||||
Txid: node.Txid,
|
|
||||||
Tx: node.Tx,
|
|
||||||
ParentTxid: node.ParentTxid,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
levels = append(levels, levelProto)
|
|
||||||
}
|
|
||||||
return &arkv1.Tree{
|
|
||||||
Levels: levels,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleRoundStream(
|
func handleRoundStream(
|
||||||
ctx *cli.Context, client arkv1.ArkServiceClient, paymentID string,
|
ctx *cli.Context, client arkv1.ArkServiceClient, paymentID string,
|
||||||
vtxosToSign []vtxo, secKey *secp256k1.PrivateKey, receivers []*arkv1.Output,
|
vtxosToSign []vtxo, mustSignRoundTx bool,
|
||||||
|
secKey *secp256k1.PrivateKey, receivers []*arkv1.Output,
|
||||||
ephemeralKey *secp256k1.PrivateKey,
|
ephemeralKey *secp256k1.PrivateKey,
|
||||||
) (poolTxID string, err error) {
|
) (poolTxID string, err error) {
|
||||||
stream, err := client.GetEventStream(ctx.Context, &arkv1.GetEventStreamRequest{})
|
stream, err := client.GetEventStream(ctx.Context, &arkv1.GetEventStreamRequest{})
|
||||||
@@ -398,8 +378,8 @@ func handleRoundStream(
|
|||||||
// stop pinging as soon as we receive some forfeit txs
|
// stop pinging as soon as we receive some forfeit txs
|
||||||
pingStop()
|
pingStop()
|
||||||
|
|
||||||
poolTx := e.GetPoolTx()
|
roundTx := e.GetPoolTx()
|
||||||
ptx, err := psbt.NewFromRawBytes(strings.NewReader(poolTx), true)
|
ptx, err := psbt.NewFromRawBytes(strings.NewReader(roundTx), true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -428,7 +408,7 @@ func handleRoundStream(
|
|||||||
|
|
||||||
if !isOnchainOnly(receivers) {
|
if !isOnchainOnly(receivers) {
|
||||||
if err := bitcointree.ValidateCongestionTree(
|
if err := bitcointree.ValidateCongestionTree(
|
||||||
congestionTree, poolTx, aspPubkey, int64(roundLifetime), int64(minRelayFee),
|
congestionTree, roundTx, aspPubkey, int64(roundLifetime), int64(minRelayFee),
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -529,80 +509,103 @@ func handleRoundStream(
|
|||||||
|
|
||||||
fmt.Println("congestion tree validated")
|
fmt.Println("congestion tree validated")
|
||||||
|
|
||||||
forfeits := e.GetForfeitTxs()
|
|
||||||
signedForfeits := make([]string, 0)
|
|
||||||
|
|
||||||
fmt.Print("signing forfeit txs... ")
|
|
||||||
|
|
||||||
explorer := utils.NewExplorer(ctx)
|
explorer := utils.NewExplorer(ctx)
|
||||||
|
|
||||||
connectorsTxids := make([]string, 0, len(connectors))
|
finalizePaymentRequest := &arkv1.FinalizePaymentRequest{}
|
||||||
for _, connector := range connectors {
|
|
||||||
p, err := psbt.NewFromRawBytes(strings.NewReader(connector), true)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
txid := p.UnsignedTx.TxHash().String()
|
|
||||||
|
|
||||||
connectorsTxids = append(connectorsTxids, txid)
|
if len(vtxosToSign) > 0 {
|
||||||
}
|
forfeits := e.GetForfeitTxs()
|
||||||
|
signedForfeits := make([]string, 0)
|
||||||
|
|
||||||
for _, forfeit := range forfeits {
|
fmt.Print("signing forfeit txs... ")
|
||||||
ptx, err := psbt.NewFromRawBytes(strings.NewReader(forfeit), true)
|
|
||||||
if err != nil {
|
connectorsTxids := make([]string, 0, len(connectors))
|
||||||
return "", err
|
for _, connector := range connectors {
|
||||||
|
p, err := psbt.NewFromRawBytes(strings.NewReader(connector), true)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
txid := p.UnsignedTx.TxHash().String()
|
||||||
|
|
||||||
|
connectorsTxids = append(connectorsTxids, txid)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, input := range ptx.UnsignedTx.TxIn {
|
for _, forfeit := range forfeits {
|
||||||
inputTxid := input.PreviousOutPoint.Hash.String()
|
ptx, err := psbt.NewFromRawBytes(strings.NewReader(forfeit), true)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
for _, coin := range vtxosToSign {
|
for _, input := range ptx.UnsignedTx.TxIn {
|
||||||
// check if it contains one of the input to sign
|
inputTxid := input.PreviousOutPoint.Hash.String()
|
||||||
if inputTxid == coin.txid {
|
|
||||||
// verify that the connector is in the connectors list
|
for _, coin := range vtxosToSign {
|
||||||
connectorTxid := ptx.UnsignedTx.TxIn[0].PreviousOutPoint.Hash.String()
|
// check if it contains one of the input to sign
|
||||||
connectorFound := false
|
if inputTxid == coin.txid {
|
||||||
for _, txid := range connectorsTxids {
|
// verify that the connector is in the connectors list
|
||||||
if txid == connectorTxid {
|
connectorTxid := ptx.UnsignedTx.TxIn[0].PreviousOutPoint.Hash.String()
|
||||||
connectorFound = true
|
connectorFound := false
|
||||||
break
|
for _, txid := range connectorsTxids {
|
||||||
|
if txid == connectorTxid {
|
||||||
|
connectorFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !connectorFound {
|
if !connectorFound {
|
||||||
return "", fmt.Errorf("connector txid %s not found in the connectors list", connectorTxid)
|
return "", fmt.Errorf("connector txid %s not found in the connectors list", connectorTxid)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := signPsbt(ctx, ptx, explorer, secKey); err != nil {
|
if err := signPsbt(ctx, ptx, explorer, secKey); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
signedPset, err := ptx.B64Encode()
|
signedPset, err := ptx.B64Encode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
signedForfeits = append(signedForfeits, signedPset)
|
signedForfeits = append(signedForfeits, signedPset)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// if no forfeit txs have been signed, start pinging again and wait for the next round
|
// if no forfeit txs have been signed, start pinging again and wait for the next round
|
||||||
if len(signedForfeits) == 0 {
|
if len(vtxosToSign) > 0 && len(signedForfeits) == 0 {
|
||||||
fmt.Printf("\nno forfeit txs to sign, waiting for the next round...\n")
|
fmt.Printf("\nno forfeit txs to sign, waiting for the next round...\n")
|
||||||
pingStop = nil
|
pingStop = nil
|
||||||
for pingStop == nil {
|
for pingStop == nil {
|
||||||
pingStop = ping(ctx.Context, client, pingReq)
|
pingStop = ping(ctx.Context, client, pingReq)
|
||||||
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
continue
|
|
||||||
|
fmt.Printf("%d signed\n", len(signedForfeits))
|
||||||
|
finalizePaymentRequest.SignedForfeitTxs = signedForfeits
|
||||||
|
}
|
||||||
|
|
||||||
|
if mustSignRoundTx {
|
||||||
|
ptx, err := psbt.NewFromRawBytes(strings.NewReader(roundTx), true)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := signPsbt(ctx, ptx, explorer, secKey); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
signedRoundTx, err := ptx.B64Encode()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("round tx signed")
|
||||||
|
finalizePaymentRequest.SignedRoundTx = &signedRoundTx
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%d signed\n", len(signedForfeits))
|
|
||||||
fmt.Print("finalizing payment... ")
|
fmt.Print("finalizing payment... ")
|
||||||
_, err = client.FinalizePayment(ctx.Context, &arkv1.FinalizePaymentRequest{
|
_, err = client.FinalizePayment(ctx.Context, finalizePaymentRequest)
|
||||||
SignedForfeitTxs: signedForfeits,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ func connectToAsp(ctx *cli.Context, net, url, explorer string) error {
|
|||||||
utils.UNILATERAL_EXIT_DELAY: strconv.Itoa(int(resp.GetUnilateralExitDelay())),
|
utils.UNILATERAL_EXIT_DELAY: strconv.Itoa(int(resp.GetUnilateralExitDelay())),
|
||||||
utils.MIN_RELAY_FEE: strconv.Itoa(int(resp.MinRelayFee)),
|
utils.MIN_RELAY_FEE: strconv.Itoa(int(resp.MinRelayFee)),
|
||||||
utils.EXPLORER: explorer,
|
utils.EXPLORER: explorer,
|
||||||
|
utils.BOARDING_TEMPLATE: resp.GetBoardingDescriptorTemplate(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,205 +0,0 @@
|
|||||||
package covenantless
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
|
|
||||||
"github.com/ark-network/ark/client/utils"
|
|
||||||
"github.com/ark-network/ark/common/bitcointree"
|
|
||||||
"github.com/btcsuite/btcd/btcutil"
|
|
||||||
"github.com/btcsuite/btcd/btcutil/psbt"
|
|
||||||
"github.com/btcsuite/btcd/txscript"
|
|
||||||
"github.com/btcsuite/btcd/wire"
|
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *clArkBitcoinCLI) Onboard(ctx *cli.Context) error {
|
|
||||||
amount := ctx.Uint64("amount")
|
|
||||||
|
|
||||||
if amount <= 0 {
|
|
||||||
return fmt.Errorf("missing amount flag (--amount)")
|
|
||||||
}
|
|
||||||
|
|
||||||
net, err := utils.GetNetwork(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
userPubKey, err := utils.GetWalletPublicKey(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
client, cancel, err := getClientFromState(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
aspPubkey, err := utils.GetAspPublicKey(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
roundLifetime, err := utils.GetRoundLifetime(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
unilateralExitDelay, err := utils.GetUnilateralExitDelay(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
minRelayFee, err := utils.GetMinRelayFee(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
congestionTreeLeaf := bitcointree.Receiver{
|
|
||||||
Pubkey: hex.EncodeToString(userPubKey.SerializeCompressed()),
|
|
||||||
Amount: uint64(amount), // Convert amount to uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
leaves := []bitcointree.Receiver{congestionTreeLeaf}
|
|
||||||
|
|
||||||
ephemeralKey, err := secp256k1.GeneratePrivateKey()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cosigners := []*secp256k1.PublicKey{ephemeralKey.PubKey()} // TODO asp as cosigner
|
|
||||||
|
|
||||||
sharedOutputScript, sharedOutputAmount, err := bitcointree.CraftSharedOutput(
|
|
||||||
cosigners,
|
|
||||||
aspPubkey,
|
|
||||||
leaves,
|
|
||||||
uint64(minRelayFee),
|
|
||||||
roundLifetime,
|
|
||||||
unilateralExitDelay,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
netParams := toChainParams(net)
|
|
||||||
|
|
||||||
address, err := btcutil.NewAddressTaproot(sharedOutputScript[2:], &netParams)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
onchainReceiver := receiver{
|
|
||||||
To: address.EncodeAddress(),
|
|
||||||
Amount: uint64(sharedOutputAmount),
|
|
||||||
}
|
|
||||||
|
|
||||||
partialTx, err := sendOnchain(ctx, []receiver{onchainReceiver})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ptx, err := psbt.NewFromRawBytes(strings.NewReader(partialTx), true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
txid := ptx.UnsignedTx.TxHash().String()
|
|
||||||
|
|
||||||
congestionTree, err := bitcointree.CraftCongestionTree(
|
|
||||||
&wire.OutPoint{
|
|
||||||
Hash: ptx.UnsignedTx.TxHash(),
|
|
||||||
Index: 0,
|
|
||||||
},
|
|
||||||
cosigners,
|
|
||||||
aspPubkey,
|
|
||||||
leaves,
|
|
||||||
uint64(minRelayFee),
|
|
||||||
roundLifetime,
|
|
||||||
unilateralExitDelay,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sweepClosure := bitcointree.CSVSigClosure{
|
|
||||||
Pubkey: aspPubkey,
|
|
||||||
Seconds: uint(roundLifetime),
|
|
||||||
}
|
|
||||||
|
|
||||||
sweepTapLeaf, err := sweepClosure.Leaf()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sweepTapTree := txscript.AssembleTaprootScriptTree(*sweepTapLeaf)
|
|
||||||
root := sweepTapTree.RootNode.TapHash()
|
|
||||||
|
|
||||||
signer := bitcointree.NewTreeSignerSession(
|
|
||||||
ephemeralKey,
|
|
||||||
congestionTree,
|
|
||||||
minRelayFee,
|
|
||||||
root.CloneBytes(),
|
|
||||||
)
|
|
||||||
|
|
||||||
nonces, err := signer.GetNonces() // TODO send nonces to ASP
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
coordinator, err := bitcointree.NewTreeCoordinatorSession(
|
|
||||||
congestionTree,
|
|
||||||
minRelayFee,
|
|
||||||
root.CloneBytes(),
|
|
||||||
cosigners,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := coordinator.AddNonce(ephemeralKey.PubKey(), nonces); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
aggregatedNonces, err := coordinator.AggregateNonces()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := signer.SetKeys(cosigners); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := signer.SetAggregatedNonces(aggregatedNonces); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sigs, err := signer.Sign()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := coordinator.AddSig(ephemeralKey.PubKey(), sigs); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
signedTree, err := coordinator.SignTree()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = client.Onboard(ctx.Context, &arkv1.OnboardRequest{
|
|
||||||
BoardingTx: partialTx,
|
|
||||||
CongestionTree: castCongestionTree(signedTree),
|
|
||||||
UserPubkey: hex.EncodeToString(userPubKey.SerializeCompressed()),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("onboard_txid:", txid)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -72,8 +72,12 @@ func collaborativeRedeem(
|
|||||||
|
|
||||||
for _, coin := range selectedCoins {
|
for _, coin := range selectedCoins {
|
||||||
inputs = append(inputs, &arkv1.Input{
|
inputs = append(inputs, &arkv1.Input{
|
||||||
Txid: coin.txid,
|
Input: &arkv1.Input_VtxoInput{
|
||||||
Vout: coin.vout,
|
VtxoInput: &arkv1.VtxoInput{
|
||||||
|
Txid: coin.txid,
|
||||||
|
Vout: coin.vout,
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,6 +114,7 @@ func collaborativeRedeem(
|
|||||||
client,
|
client,
|
||||||
registerResponse.GetId(),
|
registerResponse.GetId(),
|
||||||
selectedCoins,
|
selectedCoins,
|
||||||
|
false,
|
||||||
secKey,
|
secKey,
|
||||||
receivers,
|
receivers,
|
||||||
ephemeralKey,
|
ephemeralKey,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (c *clArkBitcoinCLI) SendAsync(ctx *cli.Context) error {
|
func (c *clArkBitcoinCLI) SendAsync(ctx *cli.Context) error {
|
||||||
receiver := ctx.String("to")
|
receiverAddr := ctx.String("to")
|
||||||
amount := ctx.Uint64("amount")
|
amount := ctx.Uint64("amount")
|
||||||
withExpiryCoinselect := ctx.Bool("enable-expiry-coinselect")
|
withExpiryCoinselect := ctx.Bool("enable-expiry-coinselect")
|
||||||
|
|
||||||
@@ -22,15 +22,22 @@ func (c *clArkBitcoinCLI) SendAsync(ctx *cli.Context) error {
|
|||||||
return fmt.Errorf("invalid amount (%d), must be greater than dust %d", amount, dust)
|
return fmt.Errorf("invalid amount (%d), must be greater than dust %d", amount, dust)
|
||||||
}
|
}
|
||||||
|
|
||||||
if receiver == "" {
|
if receiverAddr == "" {
|
||||||
return fmt.Errorf("receiver address is required")
|
return fmt.Errorf("receiver address is required")
|
||||||
}
|
}
|
||||||
isOnchain, _, _, err := decodeReceiverAddress(receiver)
|
isOnchain, _, _, err := decodeReceiverAddress(receiverAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if isOnchain {
|
if isOnchain {
|
||||||
return fmt.Errorf("receiver address is onchain")
|
txid, err := sendOnchain(ctx, []receiver{{receiverAddr, amount}})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.PrintJSON(map[string]interface{}{
|
||||||
|
"txid": txid,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
offchainAddr, _, _, err := getAddress(ctx)
|
offchainAddr, _, _, err := getAddress(ctx)
|
||||||
@@ -41,20 +48,20 @@ func (c *clArkBitcoinCLI) SendAsync(ctx *cli.Context) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, _, aspKey, err := common.DecodeAddress(receiver)
|
_, _, aspKey, err := common.DecodeAddress(receiverAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid receiver address: %s", err)
|
return fmt.Errorf("invalid receiver address: %s", err)
|
||||||
}
|
}
|
||||||
if !bytes.Equal(
|
if !bytes.Equal(
|
||||||
aspPubKey.SerializeCompressed(), aspKey.SerializeCompressed(),
|
aspPubKey.SerializeCompressed(), aspKey.SerializeCompressed(),
|
||||||
) {
|
) {
|
||||||
return fmt.Errorf("invalid receiver address '%s': must be associated with the connected service provider", receiver)
|
return fmt.Errorf("invalid receiver address '%s': must be associated with the connected service provider", receiverAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
receiversOutput := make([]*arkv1.Output, 0)
|
receiversOutput := make([]*arkv1.Output, 0)
|
||||||
sumOfReceivers := uint64(0)
|
sumOfReceivers := uint64(0)
|
||||||
receiversOutput = append(receiversOutput, &arkv1.Output{
|
receiversOutput = append(receiversOutput, &arkv1.Output{
|
||||||
Address: receiver,
|
Address: receiverAddr,
|
||||||
Amount: amount,
|
Amount: amount,
|
||||||
})
|
})
|
||||||
sumOfReceivers += amount
|
sumOfReceivers += amount
|
||||||
@@ -88,8 +95,12 @@ func (c *clArkBitcoinCLI) SendAsync(ctx *cli.Context) error {
|
|||||||
|
|
||||||
for _, coin := range selectedCoins {
|
for _, coin := range selectedCoins {
|
||||||
inputs = append(inputs, &arkv1.Input{
|
inputs = append(inputs, &arkv1.Input{
|
||||||
Txid: coin.txid,
|
Input: &arkv1.Input_VtxoInput{
|
||||||
Vout: coin.vout,
|
VtxoInput: &arkv1.VtxoInput{
|
||||||
|
Txid: coin.txid,
|
||||||
|
Vout: coin.vout,
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
|
|
||||||
"github.com/ark-network/ark/client/utils"
|
"github.com/ark-network/ark/client/utils"
|
||||||
"github.com/ark-network/ark/common/bitcointree"
|
"github.com/ark-network/ark/common/bitcointree"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
|
||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"github.com/btcsuite/btcd/btcutil/psbt"
|
"github.com/btcsuite/btcd/btcutil/psbt"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
@@ -18,7 +17,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func signPsbt(
|
func signPsbt(
|
||||||
ctx *cli.Context, ptx *psbt.Packet, explorer utils.Explorer, prvKey *secp256k1.PrivateKey,
|
_ *cli.Context, ptx *psbt.Packet, explorer utils.Explorer, prvKey *secp256k1.PrivateKey,
|
||||||
) error {
|
) error {
|
||||||
updater, err := psbt.NewUpdater(ptx)
|
updater, err := psbt.NewUpdater(ptx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -50,27 +49,11 @@ func signPsbt(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
sighashType := txscript.SigHashAll
|
if err := updater.AddInSighashType(txscript.SigHashDefault, i); err != nil {
|
||||||
|
|
||||||
if utxo.PkScript[0] == txscript.OP_1 {
|
|
||||||
sighashType = txscript.SigHashDefault
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := updater.AddInSighashType(sighashType, i); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, onchainAddr, _, err := getAddress(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
onchainWalletScript, err := txscript.PayToAddrScript(onchainAddr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
prevouts := make(map[wire.OutPoint]*wire.TxOut)
|
prevouts := make(map[wire.OutPoint]*wire.TxOut)
|
||||||
|
|
||||||
for i, input := range updater.Upsbt.Inputs {
|
for i, input := range updater.Upsbt.Inputs {
|
||||||
@@ -85,40 +68,6 @@ func signPsbt(
|
|||||||
txsighashes := txscript.NewTxSigHashes(updater.Upsbt.UnsignedTx, prevoutFetcher)
|
txsighashes := txscript.NewTxSigHashes(updater.Upsbt.UnsignedTx, prevoutFetcher)
|
||||||
|
|
||||||
for i, input := range ptx.Inputs {
|
for i, input := range ptx.Inputs {
|
||||||
if bytes.Equal(input.WitnessUtxo.PkScript, onchainWalletScript) {
|
|
||||||
if err := updater.AddInSighashType(txscript.SigHashAll, i); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
preimage, err := txscript.CalcWitnessSigHash(
|
|
||||||
input.WitnessUtxo.PkScript,
|
|
||||||
txsighashes,
|
|
||||||
txscript.SigHashAll,
|
|
||||||
updater.Upsbt.UnsignedTx,
|
|
||||||
i,
|
|
||||||
int64(input.WitnessUtxo.Value),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sig := ecdsa.Sign(
|
|
||||||
prvKey,
|
|
||||||
preimage,
|
|
||||||
)
|
|
||||||
|
|
||||||
signatureWithSighashType := append(sig.Serialize(), byte(txscript.SigHashAll))
|
|
||||||
|
|
||||||
updater.Upsbt.Inputs[i].PartialSigs = []*psbt.PartialSig{
|
|
||||||
{
|
|
||||||
PubKey: prvKey.PubKey().SerializeCompressed(),
|
|
||||||
Signature: signatureWithSighashType,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(input.TaprootLeafScript) > 0 {
|
if len(input.TaprootLeafScript) > 0 {
|
||||||
pubkey := prvKey.PubKey()
|
pubkey := prvKey.PubKey()
|
||||||
for _, leaf := range input.TaprootLeafScript {
|
for _, leaf := range input.TaprootLeafScript {
|
||||||
@@ -178,7 +127,6 @@ func signPsbt(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -21,10 +21,6 @@ var (
|
|||||||
Required: false,
|
Required: false,
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
}
|
}
|
||||||
AmountOnboardFlag = cli.Uint64Flag{
|
|
||||||
Name: "amount",
|
|
||||||
Usage: "amount to onboard in sats",
|
|
||||||
}
|
|
||||||
ExpiryDetailsFlag = cli.BoolFlag{
|
ExpiryDetailsFlag = cli.BoolFlag{
|
||||||
Name: "compute-expiry-details",
|
Name: "compute-expiry-details",
|
||||||
Usage: "compute client-side the VTXOs expiry time",
|
Usage: "compute client-side the VTXOs expiry time",
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ module github.com/ark-network/ark/client
|
|||||||
|
|
||||||
go 1.22.6
|
go 1.22.6
|
||||||
|
|
||||||
|
replace github.com/ark-network/ark/common => ../common
|
||||||
|
|
||||||
replace github.com/btcsuite/btcd/btcec/v2 => github.com/btcsuite/btcd/btcec/v2 v2.3.3
|
replace github.com/btcsuite/btcd/btcec/v2 => github.com/btcsuite/btcd/btcec/v2 v2.3.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -19,9 +21,7 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
|
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
|
||||||
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
|
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
|
||||||
github.com/vulpemventures/fastsha256 v0.0.0-20160815193821-637e65642941 // indirect
|
github.com/vulpemventures/fastsha256 v0.0.0-20160815193821-637e65642941 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||||
github.com/ark-network/ark/api-spec v0.0.0-20240812233307-18e343b31899 h1:PJL9Pam042F790x3mMovaIIkgeKIVaWm1aFOyH0k4PY=
|
github.com/ark-network/ark/api-spec v0.0.0-20240812233307-18e343b31899 h1:PJL9Pam042F790x3mMovaIIkgeKIVaWm1aFOyH0k4PY=
|
||||||
github.com/ark-network/ark/api-spec v0.0.0-20240812233307-18e343b31899/go.mod h1:0B5seq/gzuGL8OZGUaO12yj73ZJKAde8L+nmLQAZ7IA=
|
github.com/ark-network/ark/api-spec v0.0.0-20240812233307-18e343b31899/go.mod h1:0B5seq/gzuGL8OZGUaO12yj73ZJKAde8L+nmLQAZ7IA=
|
||||||
github.com/ark-network/ark/common v0.0.0-20240812233307-18e343b31899 h1:PxcHv+KaBdfrZCHoNYSUiCdI2wNIZ3Oxx8ZUewcEesg=
|
|
||||||
github.com/ark-network/ark/common v0.0.0-20240812233307-18e343b31899/go.mod h1:8DYeb06Dl8onmrV09xfsdDMGv5HoVtWoKhLBLXOYHew=
|
|
||||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||||
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
|
github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
|
||||||
github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A=
|
github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A=
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ type CLI interface {
|
|||||||
Receive(ctx *cli.Context) error
|
Receive(ctx *cli.Context) error
|
||||||
Redeem(ctx *cli.Context) error
|
Redeem(ctx *cli.Context) error
|
||||||
Send(ctx *cli.Context) error
|
Send(ctx *cli.Context) error
|
||||||
ClaimAsync(ctx *cli.Context) error
|
Claim(ctx *cli.Context) error
|
||||||
SendAsync(ctx *cli.Context) error
|
SendAsync(ctx *cli.Context) error
|
||||||
Onboard(ctx *cli.Context) error
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,19 +74,6 @@ var (
|
|||||||
Flags: []cli.Flag{&flags.PasswordFlag, &flags.PrivateKeyFlag, &flags.NetworkFlag, &flags.UrlFlag, &flags.ExplorerFlag},
|
Flags: []cli.Flag{&flags.PasswordFlag, &flags.PrivateKeyFlag, &flags.NetworkFlag, &flags.UrlFlag, &flags.ExplorerFlag},
|
||||||
}
|
}
|
||||||
|
|
||||||
onboardCommand = cli.Command{
|
|
||||||
Name: "onboard",
|
|
||||||
Usage: "Onboard the Ark by lifting your funds",
|
|
||||||
Action: func(ctx *cli.Context) error {
|
|
||||||
cli, err := getCLIFromState(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return cli.Onboard(ctx)
|
|
||||||
},
|
|
||||||
Flags: []cli.Flag{&flags.AmountOnboardFlag, &flags.PasswordFlag},
|
|
||||||
}
|
|
||||||
|
|
||||||
sendCommand = cli.Command{
|
sendCommand = cli.Command{
|
||||||
Name: "send",
|
Name: "send",
|
||||||
Usage: "Send your onchain or offchain funds to one or many receivers",
|
Usage: "Send your onchain or offchain funds to one or many receivers",
|
||||||
@@ -117,7 +104,7 @@ var (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return cli.ClaimAsync(ctx)
|
return cli.Claim(ctx)
|
||||||
},
|
},
|
||||||
Flags: []cli.Flag{&flags.PasswordFlag},
|
Flags: []cli.Flag{&flags.PasswordFlag},
|
||||||
}
|
}
|
||||||
@@ -167,7 +154,6 @@ func main() {
|
|||||||
&redeemCommand,
|
&redeemCommand,
|
||||||
&sendCommand,
|
&sendCommand,
|
||||||
&claimCommand,
|
&claimCommand,
|
||||||
&onboardCommand,
|
|
||||||
)
|
)
|
||||||
app.Flags = []cli.Flag{
|
app.Flags = []cli.Flag{
|
||||||
flags.DatadirFlag,
|
flags.DatadirFlag,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
"github.com/vulpemventures/go-elements/transaction"
|
"github.com/vulpemventures/go-elements/transaction"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Utxo struct {
|
type ExplorerUtxo struct {
|
||||||
Txid string `json:"txid"`
|
Txid string `json:"txid"`
|
||||||
Vout uint32 `json:"vout"`
|
Vout uint32 `json:"vout"`
|
||||||
Amount uint64 `json:"value"`
|
Amount uint64 `json:"value"`
|
||||||
@@ -32,9 +32,9 @@ type Utxo struct {
|
|||||||
type Explorer interface {
|
type Explorer interface {
|
||||||
GetTxHex(txid string) (string, error)
|
GetTxHex(txid string) (string, error)
|
||||||
Broadcast(txHex string) (string, error)
|
Broadcast(txHex string) (string, error)
|
||||||
GetUtxos(addr string) ([]Utxo, error)
|
GetUtxos(addr string) ([]ExplorerUtxo, error)
|
||||||
GetBalance(addr, asset string) (uint64, error)
|
GetBalance(addr, asset string) (uint64, error)
|
||||||
GetRedeemedVtxosBalance(
|
GetDelayedBalance(
|
||||||
addr string, unilateralExitDelay int64,
|
addr string, unilateralExitDelay int64,
|
||||||
) (uint64, map[int64]uint64, error)
|
) (uint64, map[int64]uint64, error)
|
||||||
GetTxBlocktime(txid string) (confirmed bool, blocktime int64, err error)
|
GetTxBlocktime(txid string) (confirmed bool, blocktime int64, err error)
|
||||||
@@ -130,7 +130,7 @@ func (e *explorer) Broadcast(txStr string) (string, error) {
|
|||||||
return txid, nil
|
return txid, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *explorer) GetUtxos(addr string) ([]Utxo, error) {
|
func (e *explorer) GetUtxos(addr string) ([]ExplorerUtxo, error) {
|
||||||
endpoint, err := url.JoinPath(e.baseUrl, "address", addr, "utxo")
|
endpoint, err := url.JoinPath(e.baseUrl, "address", addr, "utxo")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -149,7 +149,7 @@ func (e *explorer) GetUtxos(addr string) ([]Utxo, error) {
|
|||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return nil, fmt.Errorf(string(body))
|
return nil, fmt.Errorf(string(body))
|
||||||
}
|
}
|
||||||
payload := []Utxo{}
|
payload := []ExplorerUtxo{}
|
||||||
if err := json.Unmarshal(body, &payload); err != nil {
|
if err := json.Unmarshal(body, &payload); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -175,7 +175,7 @@ func (e *explorer) GetBalance(addr, asset string) (uint64, error) {
|
|||||||
return balance, nil
|
return balance, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *explorer) GetRedeemedVtxosBalance(
|
func (e *explorer) GetDelayedBalance(
|
||||||
addr string, unilateralExitDelay int64,
|
addr string, unilateralExitDelay int64,
|
||||||
) (spendableBalance uint64, lockedBalance map[int64]uint64, err error) {
|
) (spendableBalance uint64, lockedBalance map[int64]uint64, err error) {
|
||||||
utxos, err := e.GetUtxos(addr)
|
utxos, err := e.GetUtxos(addr)
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/ark-network/ark/common"
|
"github.com/ark-network/ark/common"
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
@@ -18,6 +20,7 @@ const (
|
|||||||
ASP_PUBKEY = "asp_public_key"
|
ASP_PUBKEY = "asp_public_key"
|
||||||
ROUND_LIFETIME = "round_lifetime"
|
ROUND_LIFETIME = "round_lifetime"
|
||||||
UNILATERAL_EXIT_DELAY = "unilateral_exit_delay"
|
UNILATERAL_EXIT_DELAY = "unilateral_exit_delay"
|
||||||
|
BOARDING_TEMPLATE = "boarding_template"
|
||||||
ENCRYPTED_PRVKEY = "encrypted_private_key"
|
ENCRYPTED_PRVKEY = "encrypted_private_key"
|
||||||
PASSWORD_HASH = "password_hash"
|
PASSWORD_HASH = "password_hash"
|
||||||
PUBKEY = "public_key"
|
PUBKEY = "public_key"
|
||||||
@@ -109,6 +112,27 @@ func GetUnilateralExitDelay(ctx *cli.Context) (int64, error) {
|
|||||||
return int64(redeemDelay), nil
|
return int64(redeemDelay), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetBoardingDescriptor(ctx *cli.Context) (string, error) {
|
||||||
|
state, err := GetState(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
pubkey, err := GetWalletPublicKey(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
template := state[BOARDING_TEMPLATE]
|
||||||
|
if len(template) <= 0 {
|
||||||
|
return "", fmt.Errorf("missing boarding descriptor template")
|
||||||
|
}
|
||||||
|
|
||||||
|
pubkeyhex := hex.EncodeToString(schnorr.SerializePubKey(pubkey))
|
||||||
|
|
||||||
|
return strings.ReplaceAll(template, "USER", pubkeyhex), nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetWalletPublicKey(ctx *cli.Context) (*secp256k1.PublicKey, error) {
|
func GetWalletPublicKey(ctx *cli.Context) (*secp256k1.PublicKey, error) {
|
||||||
state, err := GetState(ctx)
|
state, err := GetState(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
36
client/utils/types.go
Normal file
36
client/utils/types.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ark-network/ark/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Utxo struct {
|
||||||
|
Txid string
|
||||||
|
Vout uint32
|
||||||
|
Amount uint64
|
||||||
|
Asset string // optional
|
||||||
|
Delay uint
|
||||||
|
SpendableAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Utxo) Sequence() (uint32, error) {
|
||||||
|
return common.BIP68EncodeAsNumber(u.Delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUtxo(explorerUtxo ExplorerUtxo, delay uint) Utxo {
|
||||||
|
utxoTime := explorerUtxo.Status.Blocktime
|
||||||
|
if utxoTime == 0 {
|
||||||
|
utxoTime = time.Now().Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
return Utxo{
|
||||||
|
Txid: explorerUtxo.Txid,
|
||||||
|
Vout: explorerUtxo.Vout,
|
||||||
|
Amount: explorerUtxo.Amount,
|
||||||
|
Asset: explorerUtxo.Asset,
|
||||||
|
Delay: delay,
|
||||||
|
SpendableAt: time.Unix(utxoTime, 0).Add(time.Duration(delay) * time.Second),
|
||||||
|
}
|
||||||
|
}
|
||||||
48
common/bitcointree/descriptor.go
Normal file
48
common/bitcointree/descriptor.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package bitcointree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
|
||||||
|
"github.com/ark-network/ark/common/descriptor"
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
|
"github.com/btcsuite/btcd/txscript"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ComputeOutputScript(desc descriptor.TaprootDescriptor) ([]byte, error) {
|
||||||
|
leaves := make([]txscript.TapLeaf, 0)
|
||||||
|
for _, leaf := range desc.ScriptTree {
|
||||||
|
scriptHex, err := leaf.Script(false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
script, err := hex.DecodeString(scriptHex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
leaves = append(leaves, txscript.NewBaseTapLeaf(script))
|
||||||
|
}
|
||||||
|
|
||||||
|
taprootTree := txscript.AssembleTaprootScriptTree(leaves...)
|
||||||
|
|
||||||
|
root := taprootTree.RootNode.TapHash()
|
||||||
|
internalKey, err := hex.DecodeString(desc.InternalKey.Hex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
internalKeyParsed, err := schnorr.ParsePubKey(internalKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
taprootKey := txscript.ComputeTaprootOutputKey(internalKeyParsed, root[:])
|
||||||
|
|
||||||
|
outputScript, err := taprootOutputScript(taprootKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputScript, nil
|
||||||
|
}
|
||||||
45
common/descriptor/ark.go
Normal file
45
common/descriptor/ark.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package descriptor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
const BoardingDescriptorTemplate = "tr(%s,{ and(pk(%s), pk(%s)), and(older(%d), pk(%s)) })"
|
||||||
|
|
||||||
|
func ParseBoardingDescriptor(
|
||||||
|
desc TaprootDescriptor,
|
||||||
|
) (user *secp256k1.PublicKey, timeout uint, err error) {
|
||||||
|
for _, leaf := range desc.ScriptTree {
|
||||||
|
if andLeaf, ok := leaf.(*And); ok {
|
||||||
|
if first, ok := andLeaf.First.(*Older); ok {
|
||||||
|
timeout = first.Timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
if second, ok := andLeaf.Second.(*PK); ok {
|
||||||
|
keyBytes, err := hex.DecodeString(second.Key.Hex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err = schnorr.ParsePubKey(keyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if user == nil {
|
||||||
|
return nil, 0, errors.New("boarding descriptor is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
if timeout == 0 {
|
||||||
|
return nil, 0, errors.New("boarding descriptor is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
224
common/descriptor/expression.go
Normal file
224
common/descriptor/expression.go
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
package descriptor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ark-network/ark/common"
|
||||||
|
"github.com/btcsuite/btcd/txscript"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidXOnlyKey = errors.New("invalid x only public key")
|
||||||
|
ErrInvalidPkPolicy = errors.New("invalid public key policy")
|
||||||
|
ErrInvalidOlderPolicy = errors.New("invalid older policy")
|
||||||
|
ErrInvalidAndPolicy = errors.New("invalid and() policy")
|
||||||
|
ErrNotExpectedPolicy = errors.New("not the expected policy")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Expression interface {
|
||||||
|
Parse(policy string) error
|
||||||
|
Script(verify bool) (string, error)
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type XOnlyKey struct {
|
||||||
|
Key
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *XOnlyKey) Parse(policy string) error {
|
||||||
|
if len(policy) != 64 {
|
||||||
|
fmt.Println(policy)
|
||||||
|
return ErrInvalidXOnlyKey
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Hex = policy
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *XOnlyKey) Script() string {
|
||||||
|
return e.Hex
|
||||||
|
}
|
||||||
|
|
||||||
|
// pk(xonlypubkey)
|
||||||
|
type PK struct {
|
||||||
|
Key XOnlyKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *PK) String() string {
|
||||||
|
return fmt.Sprintf("pk(%s)", e.Key.Hex)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *PK) Parse(policy string) error {
|
||||||
|
if !strings.HasPrefix(policy, "pk(") {
|
||||||
|
return ErrNotExpectedPolicy
|
||||||
|
}
|
||||||
|
if len(policy) != 3+64+1 {
|
||||||
|
return ErrInvalidPkPolicy
|
||||||
|
}
|
||||||
|
|
||||||
|
var key XOnlyKey
|
||||||
|
if err := key.Parse(policy[3 : 64+3]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Key = key
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *PK) Script(verify bool) (string, error) {
|
||||||
|
pubkeyBytes, err := hex.DecodeString(e.Key.Hex)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
checksig := txscript.OP_CHECKSIG
|
||||||
|
if verify {
|
||||||
|
checksig = txscript.OP_CHECKSIGVERIFY
|
||||||
|
}
|
||||||
|
|
||||||
|
script, err := txscript.NewScriptBuilder().AddData(
|
||||||
|
pubkeyBytes,
|
||||||
|
).AddOp(
|
||||||
|
byte(checksig),
|
||||||
|
).Script()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return hex.EncodeToString(script), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Older struct {
|
||||||
|
Timeout uint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Older) String() string {
|
||||||
|
return fmt.Sprintf("older(%d)", e.Timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Older) Parse(policy string) error {
|
||||||
|
if !strings.HasPrefix(policy, "older(") {
|
||||||
|
return ErrNotExpectedPolicy
|
||||||
|
}
|
||||||
|
|
||||||
|
index := strings.IndexRune(policy, ')')
|
||||||
|
if index == -1 {
|
||||||
|
return ErrInvalidOlderPolicy
|
||||||
|
}
|
||||||
|
|
||||||
|
number := policy[6:index]
|
||||||
|
if len(number) == 0 {
|
||||||
|
return ErrInvalidOlderPolicy
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout, err := strconv.Atoi(number)
|
||||||
|
if err != nil {
|
||||||
|
return ErrInvalidOlderPolicy
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Timeout = uint(timeout)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Older) Script(bool) (string, error) {
|
||||||
|
sequence, err := common.BIP68Encode(e.Timeout)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
script, err := txscript.NewScriptBuilder().AddData(sequence).AddOps([]byte{
|
||||||
|
txscript.OP_CHECKSEQUENCEVERIFY,
|
||||||
|
txscript.OP_DROP,
|
||||||
|
}).Script()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return hex.EncodeToString(script), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type And struct {
|
||||||
|
First Expression
|
||||||
|
Second Expression
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *And) String() string {
|
||||||
|
return fmt.Sprintf("and(%s,%s)", e.First.String(), e.Second.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *And) Parse(policy string) error {
|
||||||
|
if !strings.HasPrefix(policy, "and(") {
|
||||||
|
return ErrNotExpectedPolicy
|
||||||
|
}
|
||||||
|
|
||||||
|
index := strings.LastIndexByte(policy, ')')
|
||||||
|
if index == -1 {
|
||||||
|
return ErrInvalidAndPolicy
|
||||||
|
}
|
||||||
|
|
||||||
|
childrenPolicy := policy[4:index]
|
||||||
|
if len(childrenPolicy) == 0 {
|
||||||
|
return ErrInvalidAndPolicy
|
||||||
|
}
|
||||||
|
|
||||||
|
children := strings.Split(childrenPolicy, ",")
|
||||||
|
if len(children) != 2 {
|
||||||
|
fmt.Println(children)
|
||||||
|
return ErrInvalidAndPolicy
|
||||||
|
}
|
||||||
|
|
||||||
|
first, err := parseExpression(children[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
second, err := parseExpression(children[1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
e.First = first
|
||||||
|
e.Second = second
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *And) Script(verify bool) (string, error) {
|
||||||
|
firstScript, err := e.First.Script(true)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
secondScript, err := e.Second.Script(verify)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return firstScript + secondScript, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseExpression(policy string) (Expression, error) {
|
||||||
|
policy = strings.TrimSpace(policy)
|
||||||
|
expressions := make([]Expression, 0)
|
||||||
|
expressions = append(expressions, &PK{})
|
||||||
|
expressions = append(expressions, &Older{})
|
||||||
|
expressions = append(expressions, &And{})
|
||||||
|
|
||||||
|
for _, e := range expressions {
|
||||||
|
if err := e.Parse(policy); err != nil {
|
||||||
|
if err != ErrNotExpectedPolicy {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("unable to parse expression '%s'", policy)
|
||||||
|
}
|
||||||
125
common/descriptor/parser.go
Normal file
125
common/descriptor/parser.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package descriptor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnspendableKey is the x-only pubkey of the secp256k1 base point G
|
||||||
|
const UnspendableKey = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
|
||||||
|
|
||||||
|
func ParseTaprootDescriptor(desc string) (*TaprootDescriptor, error) {
|
||||||
|
desc = strings.ReplaceAll(desc, " ", "")
|
||||||
|
|
||||||
|
if !strings.HasPrefix(desc, "tr(") || !strings.HasSuffix(desc, ")") {
|
||||||
|
return nil, fmt.Errorf("invalid descriptor format")
|
||||||
|
}
|
||||||
|
|
||||||
|
content := desc[3 : len(desc)-1]
|
||||||
|
parts := strings.SplitN(content, ",", 2)
|
||||||
|
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return nil, fmt.Errorf("invalid descriptor format: missing script tree")
|
||||||
|
}
|
||||||
|
|
||||||
|
internalKey, err := parseKey(parts[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptTreeStr := parts[1]
|
||||||
|
if !strings.HasPrefix(scriptTreeStr, "{") || !strings.HasSuffix(scriptTreeStr, "}") {
|
||||||
|
return nil, fmt.Errorf("invalid script tree format")
|
||||||
|
}
|
||||||
|
scriptTreeStr = scriptTreeStr[1 : len(scriptTreeStr)-1]
|
||||||
|
|
||||||
|
scriptTree := []Expression{}
|
||||||
|
if scriptTreeStr != "" {
|
||||||
|
scriptParts, err := splitScriptTree(scriptTreeStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, scriptStr := range scriptParts {
|
||||||
|
leaf, err := parseExpression(scriptStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
scriptTree = append(scriptTree, leaf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TaprootDescriptor{
|
||||||
|
InternalKey: internalKey,
|
||||||
|
ScriptTree: scriptTree,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompileDescriptor compiles a TaprootDescriptor struct back into a descriptor string
|
||||||
|
func CompileDescriptor(desc TaprootDescriptor) string {
|
||||||
|
scriptParts := make([]string, len(desc.ScriptTree))
|
||||||
|
for i, leaf := range desc.ScriptTree {
|
||||||
|
scriptParts[i] = leaf.String()
|
||||||
|
}
|
||||||
|
scriptTree := strings.Join(scriptParts, ",")
|
||||||
|
return fmt.Sprintf("tr(%s,{%s})", desc.InternalKey.Hex, scriptTree)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseKey(keyStr string) (Key, error) {
|
||||||
|
decoded, err := hex.DecodeString(keyStr)
|
||||||
|
if err != nil {
|
||||||
|
return Key{}, fmt.Errorf("invalid key: not a valid hex string: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(decoded) {
|
||||||
|
case 32:
|
||||||
|
// x-only public key, this is correct for Taproot
|
||||||
|
return Key{Hex: keyStr}, nil
|
||||||
|
case 33:
|
||||||
|
// compressed public key, we need to remove the prefix byte
|
||||||
|
return Key{Hex: keyStr[2:]}, nil
|
||||||
|
default:
|
||||||
|
return Key{}, fmt.Errorf("invalid key length: expected 32 or 33 bytes, got %d", len(decoded))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func splitScriptTree(scriptTreeStr string) ([]string, error) {
|
||||||
|
var result []string
|
||||||
|
var current strings.Builder
|
||||||
|
depth := 0
|
||||||
|
|
||||||
|
for _, char := range scriptTreeStr {
|
||||||
|
switch char {
|
||||||
|
case '(':
|
||||||
|
depth++
|
||||||
|
current.WriteRune(char)
|
||||||
|
case ')':
|
||||||
|
depth--
|
||||||
|
current.WriteRune(char)
|
||||||
|
if depth == 0 {
|
||||||
|
result = append(result, current.String())
|
||||||
|
current.Reset()
|
||||||
|
}
|
||||||
|
case ',':
|
||||||
|
if depth == 0 {
|
||||||
|
if current.Len() > 0 {
|
||||||
|
result = append(result, current.String())
|
||||||
|
current.Reset()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
current.WriteRune(char)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
current.WriteRune(char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if current.Len() > 0 {
|
||||||
|
result = append(result, current.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if depth != 0 {
|
||||||
|
return nil, fmt.Errorf("mismatched parentheses in script tree")
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
350
common/descriptor/parser_test.go
Normal file
350
common/descriptor/parser_test.go
Normal file
@@ -0,0 +1,350 @@
|
|||||||
|
package descriptor_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ark-network/ark/common/descriptor"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseTaprootDescriptor(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
desc string
|
||||||
|
expected descriptor.TaprootDescriptor
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Basic Taproot",
|
||||||
|
desc: "tr(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,{pk(81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c)})",
|
||||||
|
expected: descriptor.TaprootDescriptor{
|
||||||
|
InternalKey: descriptor.Key{Hex: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"},
|
||||||
|
ScriptTree: []descriptor.Expression{
|
||||||
|
&descriptor.PK{
|
||||||
|
Key: descriptor.XOnlyKey{
|
||||||
|
descriptor.Key{
|
||||||
|
Hex: "81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "VTXO",
|
||||||
|
desc: "tr(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,{pk(81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c),and(pk(59bffef74a89f39715b9f6b8a83e53a60a458d45542f20e2e2f4f7dbffafc5f8),older(144))})",
|
||||||
|
expected: descriptor.TaprootDescriptor{
|
||||||
|
InternalKey: descriptor.Key{Hex: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"},
|
||||||
|
ScriptTree: []descriptor.Expression{
|
||||||
|
&descriptor.PK{
|
||||||
|
Key: descriptor.XOnlyKey{
|
||||||
|
descriptor.Key{
|
||||||
|
Hex: "81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&descriptor.And{
|
||||||
|
First: &descriptor.PK{
|
||||||
|
Key: descriptor.XOnlyKey{
|
||||||
|
descriptor.Key{
|
||||||
|
Hex: "59bffef74a89f39715b9f6b8a83e53a60a458d45542f20e2e2f4f7dbffafc5f8",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Second: &descriptor.Older{
|
||||||
|
Timeout: 144,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Boarding",
|
||||||
|
desc: "tr(0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0,{ and(pk(873079a0091c9b16abd1f8c508320b07f0d50144d09ccd792ce9c915dac60465), pk(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)), and(older(604672), pk(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)) })",
|
||||||
|
expected: descriptor.TaprootDescriptor{
|
||||||
|
InternalKey: descriptor.Key{Hex: "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"},
|
||||||
|
ScriptTree: []descriptor.Expression{
|
||||||
|
&descriptor.And{
|
||||||
|
First: &descriptor.PK{
|
||||||
|
Key: descriptor.XOnlyKey{
|
||||||
|
descriptor.Key{
|
||||||
|
Hex: "873079a0091c9b16abd1f8c508320b07f0d50144d09ccd792ce9c915dac60465",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Second: &descriptor.PK{
|
||||||
|
Key: descriptor.XOnlyKey{
|
||||||
|
descriptor.Key{
|
||||||
|
Hex: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&descriptor.And{
|
||||||
|
Second: &descriptor.PK{
|
||||||
|
Key: descriptor.XOnlyKey{
|
||||||
|
descriptor.Key{
|
||||||
|
Hex: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
First: &descriptor.Older{
|
||||||
|
Timeout: 604672,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid Key",
|
||||||
|
desc: "tr(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798G,{pk(81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c)})",
|
||||||
|
expected: descriptor.TaprootDescriptor{},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid Descriptor Format",
|
||||||
|
desc: "tr(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)",
|
||||||
|
expected: descriptor.TaprootDescriptor{},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid Descriptor Format - Missing Script Tree",
|
||||||
|
desc: "tr(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)",
|
||||||
|
expected: descriptor.TaprootDescriptor{},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid Empty Script Tree",
|
||||||
|
desc: "tr(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,{})",
|
||||||
|
expected: descriptor.TaprootDescriptor{
|
||||||
|
InternalKey: descriptor.Key{Hex: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"},
|
||||||
|
ScriptTree: []descriptor.Expression{},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := descriptor.ParseTaprootDescriptor(tt.desc)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
require.Equal(t, tt.wantErr, err != nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.Equal(t, tt.expected, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompileDescriptor(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
desc descriptor.TaprootDescriptor
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Basic Taproot",
|
||||||
|
desc: descriptor.TaprootDescriptor{
|
||||||
|
InternalKey: descriptor.Key{Hex: descriptor.UnspendableKey},
|
||||||
|
ScriptTree: []descriptor.Expression{
|
||||||
|
&descriptor.PK{
|
||||||
|
Key: descriptor.XOnlyKey{
|
||||||
|
descriptor.Key{
|
||||||
|
Hex: "81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "tr(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,{pk(81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c)})",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "VTXO",
|
||||||
|
desc: descriptor.TaprootDescriptor{
|
||||||
|
InternalKey: descriptor.Key{Hex: descriptor.UnspendableKey},
|
||||||
|
ScriptTree: []descriptor.Expression{
|
||||||
|
&descriptor.PK{
|
||||||
|
Key: descriptor.XOnlyKey{
|
||||||
|
descriptor.Key{
|
||||||
|
Hex: "81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&descriptor.And{
|
||||||
|
First: &descriptor.PK{
|
||||||
|
Key: descriptor.XOnlyKey{
|
||||||
|
descriptor.Key{
|
||||||
|
Hex: "59bffef74a89f39715b9f6b8a83e53a60a458d45542f20e2e2f4f7dbffafc5f8",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Second: &descriptor.Older{
|
||||||
|
Timeout: 1024,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: "tr(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,{pk(81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c),and(pk(59bffef74a89f39715b9f6b8a83e53a60a458d45542f20e2e2f4f7dbffafc5f8),older(1024))})",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := descriptor.CompileDescriptor(tt.desc)
|
||||||
|
require.Equal(t, tt.expected, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParsePk(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
policy string
|
||||||
|
expectedScript string
|
||||||
|
expected descriptor.PK
|
||||||
|
verify bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
policy: "pk(81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c)",
|
||||||
|
expectedScript: "2081e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952cac",
|
||||||
|
verify: false,
|
||||||
|
expected: descriptor.PK{
|
||||||
|
Key: descriptor.XOnlyKey{
|
||||||
|
descriptor.Key{
|
||||||
|
Hex: "81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
policy: "pk(81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c)",
|
||||||
|
expectedScript: "2081e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952cad",
|
||||||
|
verify: true,
|
||||||
|
expected: descriptor.PK{
|
||||||
|
Key: descriptor.XOnlyKey{
|
||||||
|
descriptor.Key{
|
||||||
|
Hex: "81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
var parsed descriptor.PK
|
||||||
|
err := parsed.Parse(test.policy)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, test.expected, parsed)
|
||||||
|
|
||||||
|
script, err := parsed.Script(test.verify)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, test.expectedScript, script)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseOlder(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
policy string
|
||||||
|
expectedScript string
|
||||||
|
expected descriptor.Older
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
policy: "older(512)",
|
||||||
|
expectedScript: "03010040b275",
|
||||||
|
expected: descriptor.Older{
|
||||||
|
Timeout: uint(512),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
policy: "older(1024)",
|
||||||
|
expectedScript: "03020040b275",
|
||||||
|
expected: descriptor.Older{
|
||||||
|
Timeout: uint(1024),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
var parsed descriptor.Older
|
||||||
|
err := parsed.Parse(test.policy)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, test.expected, parsed)
|
||||||
|
|
||||||
|
script, err := parsed.Script(false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, test.expectedScript, script)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseAnd(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
policy string
|
||||||
|
expectedScript string
|
||||||
|
expected descriptor.And
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
policy: "and(pk(81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c), older(512))",
|
||||||
|
expectedScript: "2081e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952cad03010040b275",
|
||||||
|
expected: descriptor.And{
|
||||||
|
First: &descriptor.PK{
|
||||||
|
Key: descriptor.XOnlyKey{
|
||||||
|
descriptor.Key{
|
||||||
|
Hex: "81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Second: &descriptor.Older{
|
||||||
|
Timeout: 512,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
policy: "and(older(512), pk(81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c))",
|
||||||
|
expectedScript: "03010040b2752081e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952cac",
|
||||||
|
expected: descriptor.And{
|
||||||
|
Second: &descriptor.PK{
|
||||||
|
Key: descriptor.XOnlyKey{
|
||||||
|
descriptor.Key{
|
||||||
|
Hex: "81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
First: &descriptor.Older{
|
||||||
|
Timeout: 512,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
policy: "and(pk(81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c), pk(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798))",
|
||||||
|
expectedScript: "2081e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952cad2079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ac",
|
||||||
|
expected: descriptor.And{
|
||||||
|
First: &descriptor.PK{
|
||||||
|
Key: descriptor.XOnlyKey{
|
||||||
|
descriptor.Key{
|
||||||
|
Hex: "81e0351fc94c3ba05f8d68354ff44711b02223f2b32fb7f3ef3a99a90af7952c",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Second: &descriptor.PK{
|
||||||
|
Key: descriptor.XOnlyKey{
|
||||||
|
descriptor.Key{
|
||||||
|
Hex: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
var parsed descriptor.And
|
||||||
|
err := parsed.Parse(test.policy)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, test.expected, parsed)
|
||||||
|
|
||||||
|
script, err := parsed.Script(false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, test.expectedScript, script)
|
||||||
|
}
|
||||||
|
}
|
||||||
10
common/descriptor/types.go
Normal file
10
common/descriptor/types.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package descriptor
|
||||||
|
|
||||||
|
type Key struct {
|
||||||
|
Hex string
|
||||||
|
}
|
||||||
|
|
||||||
|
type TaprootDescriptor struct {
|
||||||
|
InternalKey Key
|
||||||
|
ScriptTree []Expression
|
||||||
|
}
|
||||||
@@ -35,7 +35,7 @@ func EncodeAddress(
|
|||||||
|
|
||||||
func DecodeAddress(
|
func DecodeAddress(
|
||||||
addr string,
|
addr string,
|
||||||
) (hrp string, userKey *secp256k1.PublicKey, aspKey *secp256k1.PublicKey, err error) {
|
) (hrp string, userKey, aspKey *secp256k1.PublicKey, err error) {
|
||||||
prefix, buf, err := bech32.DecodeNoLimit(addr)
|
prefix, buf, err := bech32.DecodeNoLimit(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
52
common/tree/descriptor.go
Normal file
52
common/tree/descriptor.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package tree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
|
||||||
|
"github.com/ark-network/ark/common/descriptor"
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
|
"github.com/vulpemventures/go-elements/taproot"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ComputeOutputScript(desc descriptor.TaprootDescriptor) ([]byte, error) {
|
||||||
|
leaves := make([]taproot.TapElementsLeaf, 0)
|
||||||
|
|
||||||
|
for _, l := range desc.ScriptTree {
|
||||||
|
script, err := l.Script(false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptBytes, err := hex.DecodeString(script)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
leaves = append(leaves, taproot.NewBaseTapElementsLeaf(scriptBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
taprootTree := taproot.AssembleTaprootScriptTree(
|
||||||
|
leaves...,
|
||||||
|
)
|
||||||
|
|
||||||
|
root := taprootTree.RootNode.TapHash()
|
||||||
|
|
||||||
|
internalKey, err := hex.DecodeString(desc.InternalKey.Hex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
internalKeyParsed, err := schnorr.ParsePubKey(internalKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
taprootKey := taproot.ComputeTaprootOutputKey(internalKeyParsed, root[:])
|
||||||
|
|
||||||
|
outputScript, err := taprootOutputScript(taprootKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputScript, nil
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package tree
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ark-network/ark/common"
|
"github.com/ark-network/ark/common"
|
||||||
@@ -62,7 +63,7 @@ func DecodeClosure(script []byte) (Closure, error) {
|
|||||||
return closure, nil
|
return closure, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("invalid closure script")
|
return nil, fmt.Errorf("invalid closure script %s", hex.EncodeToString(script))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *ForfeitClosure) Leaf() (*taproot.TapElementsLeaf, error) {
|
func (f *ForfeitClosure) Leaf() (*taproot.TapElementsLeaf, error) {
|
||||||
|
|||||||
@@ -12,21 +12,18 @@ services:
|
|||||||
- ARK_ROUND_LIFETIME=512
|
- ARK_ROUND_LIFETIME=512
|
||||||
- ARK_TX_BUILDER_TYPE=covenantless
|
- ARK_TX_BUILDER_TYPE=covenantless
|
||||||
- ARK_MIN_RELAY_FEE=200
|
- ARK_MIN_RELAY_FEE=200
|
||||||
- ARK_NEUTRINO_PEER=bitcoin:18444
|
|
||||||
- ARK_ESPLORA_URL=http://chopsticks:3000
|
- ARK_ESPLORA_URL=http://chopsticks:3000
|
||||||
|
- ARK_BITCOIND_RPC_USER=admin1
|
||||||
|
- ARK_BITCOIND_RPC_PASS=123
|
||||||
|
- ARK_BITCOIND_RPC_HOST=bitcoin:18443
|
||||||
- ARK_NO_TLS=true
|
- ARK_NO_TLS=true
|
||||||
- ARK_NO_MACAROONS=true
|
- ARK_NO_MACAROONS=true
|
||||||
|
- ARK_DATADIR=/app/data
|
||||||
ports:
|
ports:
|
||||||
- "7070:7070"
|
- "7070:7070"
|
||||||
volumes:
|
volumes:
|
||||||
- clarkd:/app/data
|
- type: tmpfs
|
||||||
- clark:/app/wallet-data
|
target: /app/data
|
||||||
|
|
||||||
volumes:
|
|
||||||
clarkd:
|
|
||||||
external: false
|
|
||||||
clark:
|
|
||||||
external: false
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -12,11 +12,12 @@ services:
|
|||||||
- OCEAN_NETWORK=regtest
|
- OCEAN_NETWORK=regtest
|
||||||
- OCEAN_UTXO_EXPIRY_DURATION_IN_SECONDS=60
|
- OCEAN_UTXO_EXPIRY_DURATION_IN_SECONDS=60
|
||||||
- OCEAN_DB_TYPE=badger
|
- OCEAN_DB_TYPE=badger
|
||||||
|
- OCEAN_DATADIR=/app/data
|
||||||
ports:
|
ports:
|
||||||
- "18000:18000"
|
- "18000:18000"
|
||||||
volumes:
|
volumes:
|
||||||
- oceand:/app/data/oceand
|
- type: tmpfs
|
||||||
- ocean:/app/data/ocean
|
target: /app/data
|
||||||
arkd:
|
arkd:
|
||||||
container_name: arkd
|
container_name: arkd
|
||||||
build:
|
build:
|
||||||
@@ -36,21 +37,12 @@ services:
|
|||||||
- ARK_PORT=6060
|
- ARK_PORT=6060
|
||||||
- ARK_NO_TLS=true
|
- ARK_NO_TLS=true
|
||||||
- ARK_NO_MACAROONS=true
|
- ARK_NO_MACAROONS=true
|
||||||
|
- ARK_DATADIR=/app/data
|
||||||
ports:
|
ports:
|
||||||
- "6060:6060"
|
- "6060:6060"
|
||||||
volumes:
|
volumes:
|
||||||
- arkd:/app/data
|
- type: tmpfs
|
||||||
- ark:/app/wallet-data
|
target: /app/data
|
||||||
|
|
||||||
volumes:
|
|
||||||
oceand:
|
|
||||||
external: false
|
|
||||||
ocean:
|
|
||||||
external: false
|
|
||||||
arkd:
|
|
||||||
external: false
|
|
||||||
ark:
|
|
||||||
external: false
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ type ArkClient interface {
|
|||||||
Unlock(ctx context.Context, password string) error
|
Unlock(ctx context.Context, password string) error
|
||||||
Lock(ctx context.Context, password string) error
|
Lock(ctx context.Context, password string) error
|
||||||
Balance(ctx context.Context, computeExpiryDetails bool) (*Balance, error)
|
Balance(ctx context.Context, computeExpiryDetails bool) (*Balance, error)
|
||||||
Onboard(ctx context.Context, amount uint64) (string, error)
|
|
||||||
Receive(ctx context.Context) (string, string, error)
|
Receive(ctx context.Context) (string, string, error)
|
||||||
SendOnChain(ctx context.Context, receivers []Receiver) (string, error)
|
SendOnChain(ctx context.Context, receivers []Receiver) (string, error)
|
||||||
SendOffChain(
|
SendOffChain(
|
||||||
@@ -26,7 +25,7 @@ type ArkClient interface {
|
|||||||
ctx context.Context, addr string, amount uint64, withExpiryCoinselect bool,
|
ctx context.Context, addr string, amount uint64, withExpiryCoinselect bool,
|
||||||
) (string, error)
|
) (string, error)
|
||||||
SendAsync(ctx context.Context, withExpiryCoinselect bool, receivers []Receiver) (string, error)
|
SendAsync(ctx context.Context, withExpiryCoinselect bool, receivers []Receiver) (string, error)
|
||||||
ClaimAsync(ctx context.Context) (string, error)
|
Claim(ctx context.Context) (string, error)
|
||||||
ListVtxos(ctx context.Context) ([]client.Vtxo, []client.Vtxo, error)
|
ListVtxos(ctx context.Context) ([]client.Vtxo, []client.Vtxo, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,14 +92,15 @@ func (a *arkClient) InitWithWallet(
|
|||||||
}
|
}
|
||||||
|
|
||||||
storeData := store.StoreData{
|
storeData := store.StoreData{
|
||||||
AspUrl: args.AspUrl,
|
AspUrl: args.AspUrl,
|
||||||
AspPubkey: aspPubkey,
|
AspPubkey: aspPubkey,
|
||||||
WalletType: args.Wallet.GetType(),
|
WalletType: args.Wallet.GetType(),
|
||||||
ClientType: args.ClientType,
|
ClientType: args.ClientType,
|
||||||
Network: network,
|
Network: network,
|
||||||
RoundLifetime: info.RoundLifetime,
|
RoundLifetime: info.RoundLifetime,
|
||||||
UnilateralExitDelay: info.UnilateralExitDelay,
|
UnilateralExitDelay: info.UnilateralExitDelay,
|
||||||
MinRelayFee: uint64(info.MinRelayFee),
|
MinRelayFee: uint64(info.MinRelayFee),
|
||||||
|
BoardingDescriptorTemplate: info.BoardingDescriptorTemplate,
|
||||||
}
|
}
|
||||||
if err := a.store.AddData(ctx, storeData); err != nil {
|
if err := a.store.AddData(ctx, storeData); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -155,14 +156,15 @@ func (a *arkClient) Init(
|
|||||||
}
|
}
|
||||||
|
|
||||||
storeData := store.StoreData{
|
storeData := store.StoreData{
|
||||||
AspUrl: args.AspUrl,
|
AspUrl: args.AspUrl,
|
||||||
AspPubkey: aspPubkey,
|
AspPubkey: aspPubkey,
|
||||||
WalletType: args.WalletType,
|
WalletType: args.WalletType,
|
||||||
ClientType: args.ClientType,
|
ClientType: args.ClientType,
|
||||||
Network: network,
|
Network: network,
|
||||||
RoundLifetime: info.RoundLifetime,
|
RoundLifetime: info.RoundLifetime,
|
||||||
UnilateralExitDelay: info.UnilateralExitDelay,
|
UnilateralExitDelay: info.UnilateralExitDelay,
|
||||||
MinRelayFee: uint64(info.MinRelayFee),
|
MinRelayFee: uint64(info.MinRelayFee),
|
||||||
|
BoardingDescriptorTemplate: info.BoardingDescriptorTemplate,
|
||||||
}
|
}
|
||||||
walletSvc, err := getWallet(a.store, &storeData, supportedWallets)
|
walletSvc, err := getWallet(a.store, &storeData, supportedWallets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -201,12 +203,12 @@ func (a *arkClient) IsLocked(ctx context.Context) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *arkClient) Receive(ctx context.Context) (string, string, error) {
|
func (a *arkClient) Receive(ctx context.Context) (string, string, error) {
|
||||||
offchainAddr, onchainAddr, err := a.wallet.NewAddress(ctx, false)
|
offchainAddr, boardingAddr, err := a.wallet.NewAddress(ctx, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return offchainAddr, onchainAddr, nil
|
return offchainAddr, boardingAddr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *arkClient) ListVtxos(
|
func (a *arkClient) ListVtxos(
|
||||||
@@ -232,14 +234,11 @@ func (a *arkClient) ListVtxos(
|
|||||||
func (a *arkClient) ping(
|
func (a *arkClient) ping(
|
||||||
ctx context.Context, paymentID string,
|
ctx context.Context, paymentID string,
|
||||||
) func() {
|
) func() {
|
||||||
_, err := a.client.Ping(ctx, paymentID)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ticker := time.NewTicker(5 * time.Second)
|
ticker := time.NewTicker(5 * time.Second)
|
||||||
|
|
||||||
go func(t *time.Ticker) {
|
go func(t *time.Ticker) {
|
||||||
|
// nolint
|
||||||
|
a.client.Ping(ctx, paymentID)
|
||||||
for range t.C {
|
for range t.C {
|
||||||
// nolint
|
// nolint
|
||||||
a.client.Ping(ctx, paymentID)
|
a.client.Ping(ctx, paymentID)
|
||||||
|
|||||||
@@ -23,11 +23,8 @@ type ASPClient interface {
|
|||||||
ListVtxos(ctx context.Context, addr string) ([]Vtxo, []Vtxo, error)
|
ListVtxos(ctx context.Context, addr string) ([]Vtxo, []Vtxo, error)
|
||||||
GetRound(ctx context.Context, txID string) (*Round, error)
|
GetRound(ctx context.Context, txID string) (*Round, error)
|
||||||
GetRoundByID(ctx context.Context, roundID string) (*Round, error)
|
GetRoundByID(ctx context.Context, roundID string) (*Round, error)
|
||||||
Onboard(
|
|
||||||
ctx context.Context, tx, userPubkey string, congestionTree tree.CongestionTree,
|
|
||||||
) error
|
|
||||||
RegisterPayment(
|
RegisterPayment(
|
||||||
ctx context.Context, inputs []VtxoKey, ephemeralPublicKey string,
|
ctx context.Context, inputs []Input, ephemeralKey string,
|
||||||
) (string, error)
|
) (string, error)
|
||||||
ClaimPayment(
|
ClaimPayment(
|
||||||
ctx context.Context, paymentID string, outputs []Output,
|
ctx context.Context, paymentID string, outputs []Output,
|
||||||
@@ -37,7 +34,7 @@ type ASPClient interface {
|
|||||||
) (<-chan RoundEventChannel, error)
|
) (<-chan RoundEventChannel, error)
|
||||||
Ping(ctx context.Context, paymentID string) (RoundEvent, error)
|
Ping(ctx context.Context, paymentID string) (RoundEvent, error)
|
||||||
FinalizePayment(
|
FinalizePayment(
|
||||||
ctx context.Context, signedForfeitTxs []string,
|
ctx context.Context, signedForfeitTxs []string, signedRoundTx string,
|
||||||
) error
|
) error
|
||||||
CreatePayment(
|
CreatePayment(
|
||||||
ctx context.Context, inputs []VtxoKey, outputs []Output,
|
ctx context.Context, inputs []VtxoKey, outputs []Output,
|
||||||
@@ -45,6 +42,7 @@ type ASPClient interface {
|
|||||||
CompletePayment(
|
CompletePayment(
|
||||||
ctx context.Context, signedRedeemTx string, signedUnconditionalForfeitTxs []string,
|
ctx context.Context, signedRedeemTx string, signedUnconditionalForfeitTxs []string,
|
||||||
) error
|
) error
|
||||||
|
GetBoardingAddress(ctx context.Context, userPubkey string) (string, error)
|
||||||
SendTreeNonces(
|
SendTreeNonces(
|
||||||
ctx context.Context, roundID, cosignerPubkey string, nonces bitcointree.TreeNonces,
|
ctx context.Context, roundID, cosignerPubkey string, nonces bitcointree.TreeNonces,
|
||||||
) error
|
) error
|
||||||
@@ -55,12 +53,13 @@ type ASPClient interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Info struct {
|
type Info struct {
|
||||||
Pubkey string
|
Pubkey string
|
||||||
RoundLifetime int64
|
RoundLifetime int64
|
||||||
UnilateralExitDelay int64
|
UnilateralExitDelay int64
|
||||||
RoundInterval int64
|
RoundInterval int64
|
||||||
Network string
|
Network string
|
||||||
MinRelayFee int64
|
MinRelayFee int64
|
||||||
|
BoardingDescriptorTemplate string
|
||||||
}
|
}
|
||||||
|
|
||||||
type RoundEventChannel struct {
|
type RoundEventChannel struct {
|
||||||
@@ -68,11 +67,38 @@ type RoundEventChannel struct {
|
|||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Input interface {
|
||||||
|
GetTxID() string
|
||||||
|
GetVOut() uint32
|
||||||
|
GetDescriptor() string
|
||||||
|
}
|
||||||
|
|
||||||
type VtxoKey struct {
|
type VtxoKey struct {
|
||||||
Txid string
|
Txid string
|
||||||
VOut uint32
|
VOut uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (k VtxoKey) GetTxID() string {
|
||||||
|
return k.Txid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k VtxoKey) GetVOut() uint32 {
|
||||||
|
return k.VOut
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k VtxoKey) GetDescriptor() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type BoardingInput struct {
|
||||||
|
VtxoKey
|
||||||
|
Descriptor string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k BoardingInput) GetDescriptor() string {
|
||||||
|
return k.Descriptor
|
||||||
|
}
|
||||||
|
|
||||||
type Vtxo struct {
|
type Vtxo struct {
|
||||||
VtxoKey
|
VtxoKey
|
||||||
Amount uint64
|
Amount uint64
|
||||||
|
|||||||
@@ -97,12 +97,13 @@ func (a *grpcClient) GetInfo(ctx context.Context) (*client.Info, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &client.Info{
|
return &client.Info{
|
||||||
Pubkey: resp.GetPubkey(),
|
Pubkey: resp.GetPubkey(),
|
||||||
RoundLifetime: resp.GetRoundLifetime(),
|
RoundLifetime: resp.GetRoundLifetime(),
|
||||||
UnilateralExitDelay: resp.GetUnilateralExitDelay(),
|
UnilateralExitDelay: resp.GetUnilateralExitDelay(),
|
||||||
RoundInterval: resp.GetRoundInterval(),
|
RoundInterval: resp.GetRoundInterval(),
|
||||||
Network: resp.GetNetwork(),
|
Network: resp.GetNetwork(),
|
||||||
MinRelayFee: resp.GetMinRelayFee(),
|
MinRelayFee: resp.GetMinRelayFee(),
|
||||||
|
BoardingDescriptorTemplate: resp.GetBoardingDescriptorTemplate(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,20 +144,8 @@ func (a *grpcClient) GetRound(
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *grpcClient) Onboard(
|
|
||||||
ctx context.Context, tx, userPubkey string, congestionTree tree.CongestionTree,
|
|
||||||
) error {
|
|
||||||
req := &arkv1.OnboardRequest{
|
|
||||||
BoardingTx: tx,
|
|
||||||
UserPubkey: userPubkey,
|
|
||||||
CongestionTree: treeToProto(congestionTree).parse(),
|
|
||||||
}
|
|
||||||
_, err := a.svc.Onboard(ctx, req)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *grpcClient) RegisterPayment(
|
func (a *grpcClient) RegisterPayment(
|
||||||
ctx context.Context, inputs []client.VtxoKey, ephemeralPublicKey string,
|
ctx context.Context, inputs []client.Input, ephemeralPublicKey string,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
req := &arkv1.RegisterPaymentRequest{
|
req := &arkv1.RegisterPaymentRequest{
|
||||||
Inputs: ins(inputs).toProto(),
|
Inputs: ins(inputs).toProto(),
|
||||||
@@ -198,11 +187,16 @@ func (a *grpcClient) Ping(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *grpcClient) FinalizePayment(
|
func (a *grpcClient) FinalizePayment(
|
||||||
ctx context.Context, signedForfeitTxs []string,
|
ctx context.Context, signedForfeitTxs []string, signedRoundTx string,
|
||||||
) error {
|
) error {
|
||||||
req := &arkv1.FinalizePaymentRequest{
|
req := &arkv1.FinalizePaymentRequest{
|
||||||
SignedForfeitTxs: signedForfeitTxs,
|
SignedForfeitTxs: signedForfeitTxs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(signedRoundTx) > 0 {
|
||||||
|
req.SignedRoundTx = &signedRoundTx
|
||||||
|
}
|
||||||
|
|
||||||
_, err := a.svc.FinalizePayment(ctx, req)
|
_, err := a.svc.FinalizePayment(ctx, req)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -210,8 +204,13 @@ func (a *grpcClient) FinalizePayment(
|
|||||||
func (a *grpcClient) CreatePayment(
|
func (a *grpcClient) CreatePayment(
|
||||||
ctx context.Context, inputs []client.VtxoKey, outputs []client.Output,
|
ctx context.Context, inputs []client.VtxoKey, outputs []client.Output,
|
||||||
) (string, []string, error) {
|
) (string, []string, error) {
|
||||||
|
insCast := make([]client.Input, 0, len(inputs))
|
||||||
|
for _, in := range inputs {
|
||||||
|
insCast = append(insCast, in)
|
||||||
|
}
|
||||||
|
|
||||||
req := &arkv1.CreatePaymentRequest{
|
req := &arkv1.CreatePaymentRequest{
|
||||||
Inputs: ins(inputs).toProto(),
|
Inputs: ins(insCast).toProto(),
|
||||||
Outputs: outs(outputs).toProto(),
|
Outputs: outs(outputs).toProto(),
|
||||||
}
|
}
|
||||||
resp, err := a.svc.CreatePayment(ctx, req)
|
resp, err := a.svc.CreatePayment(ctx, req)
|
||||||
@@ -260,6 +259,19 @@ func (a *grpcClient) GetRoundByID(
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *grpcClient) GetBoardingAddress(
|
||||||
|
ctx context.Context, userPubkey string,
|
||||||
|
) (string, error) {
|
||||||
|
req := &arkv1.GetBoardingAddressRequest{
|
||||||
|
Pubkey: userPubkey,
|
||||||
|
}
|
||||||
|
resp, err := a.svc.GetBoardingAddress(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return resp.GetAddress(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *grpcClient) SendTreeNonces(
|
func (a *grpcClient) SendTreeNonces(
|
||||||
ctx context.Context, roundID, cosignerPubkey string, nonces bitcointree.TreeNonces,
|
ctx context.Context, roundID, cosignerPubkey string, nonces bitcointree.TreeNonces,
|
||||||
) error {
|
) error {
|
||||||
@@ -418,8 +430,8 @@ func (v vtxo) toVtxo() client.Vtxo {
|
|||||||
}
|
}
|
||||||
return client.Vtxo{
|
return client.Vtxo{
|
||||||
VtxoKey: client.VtxoKey{
|
VtxoKey: client.VtxoKey{
|
||||||
Txid: v.GetOutpoint().GetTxid(),
|
Txid: v.GetOutpoint().GetVtxoInput().GetTxid(),
|
||||||
VOut: v.GetOutpoint().GetVout(),
|
VOut: v.GetOutpoint().GetVtxoInput().GetVout(),
|
||||||
},
|
},
|
||||||
Amount: v.GetReceiver().GetAmount(),
|
Amount: v.GetReceiver().GetAmount(),
|
||||||
RoundTxid: v.GetPoolTxid(),
|
RoundTxid: v.GetPoolTxid(),
|
||||||
@@ -441,21 +453,35 @@ func (v vtxos) toVtxos() []client.Vtxo {
|
|||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
type input client.VtxoKey
|
func toProtoInput(i client.Input) *arkv1.Input {
|
||||||
|
if len(i.GetDescriptor()) > 0 {
|
||||||
|
return &arkv1.Input{
|
||||||
|
Input: &arkv1.Input_BoardingInput{
|
||||||
|
BoardingInput: &arkv1.BoardingInput{
|
||||||
|
Txid: i.GetTxID(),
|
||||||
|
Vout: i.GetVOut(),
|
||||||
|
Descriptor_: i.GetDescriptor(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (i input) toProto() *arkv1.Input {
|
|
||||||
return &arkv1.Input{
|
return &arkv1.Input{
|
||||||
Txid: i.Txid,
|
Input: &arkv1.Input_VtxoInput{
|
||||||
Vout: i.VOut,
|
VtxoInput: &arkv1.VtxoInput{
|
||||||
|
Txid: i.GetTxID(),
|
||||||
|
Vout: i.GetVOut(),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ins []client.VtxoKey
|
type ins []client.Input
|
||||||
|
|
||||||
func (i ins) toProto() []*arkv1.Input {
|
func (i ins) toProto() []*arkv1.Input {
|
||||||
list := make([]*arkv1.Input, 0, len(i))
|
list := make([]*arkv1.Input, 0, len(i))
|
||||||
for _, ii := range i {
|
for _, ii := range i {
|
||||||
list = append(list, input(ii).toProto())
|
list = append(list, toProtoInput(ii))
|
||||||
}
|
}
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
@@ -496,27 +522,3 @@ func (t treeFromProto) parse() tree.CongestionTree {
|
|||||||
|
|
||||||
return levels
|
return levels
|
||||||
}
|
}
|
||||||
|
|
||||||
type treeToProto tree.CongestionTree
|
|
||||||
|
|
||||||
func (t treeToProto) parse() *arkv1.Tree {
|
|
||||||
levels := make([]*arkv1.TreeLevel, 0, len(t))
|
|
||||||
for _, level := range t {
|
|
||||||
levelProto := &arkv1.TreeLevel{
|
|
||||||
Nodes: make([]*arkv1.Node, 0, len(level)),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, node := range level {
|
|
||||||
levelProto.Nodes = append(levelProto.Nodes, &arkv1.Node{
|
|
||||||
Txid: node.Txid,
|
|
||||||
Tx: node.Tx,
|
|
||||||
ParentTxid: node.ParentTxid,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
levels = append(levels, levelProto)
|
|
||||||
}
|
|
||||||
return &arkv1.Tree{
|
|
||||||
Levels: levels,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -114,12 +114,13 @@ func (a *restClient) GetInfo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &client.Info{
|
return &client.Info{
|
||||||
Pubkey: resp.Payload.Pubkey,
|
Pubkey: resp.Payload.Pubkey,
|
||||||
RoundLifetime: int64(roundLifetime),
|
RoundLifetime: int64(roundLifetime),
|
||||||
UnilateralExitDelay: int64(unilateralExitDelay),
|
UnilateralExitDelay: int64(unilateralExitDelay),
|
||||||
RoundInterval: int64(roundInterval),
|
RoundInterval: int64(roundInterval),
|
||||||
Network: resp.Payload.Network,
|
Network: resp.Payload.Network,
|
||||||
MinRelayFee: int64(minRelayFee),
|
MinRelayFee: int64(minRelayFee),
|
||||||
|
BoardingDescriptorTemplate: resp.Payload.BoardingDescriptorTemplate,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,8 +160,8 @@ func (a *restClient) ListVtxos(
|
|||||||
|
|
||||||
spendableVtxos = append(spendableVtxos, client.Vtxo{
|
spendableVtxos = append(spendableVtxos, client.Vtxo{
|
||||||
VtxoKey: client.VtxoKey{
|
VtxoKey: client.VtxoKey{
|
||||||
Txid: v.Outpoint.Txid,
|
Txid: v.Outpoint.VtxoInput.Txid,
|
||||||
VOut: uint32(v.Outpoint.Vout),
|
VOut: uint32(v.Outpoint.VtxoInput.Vout),
|
||||||
},
|
},
|
||||||
Amount: uint64(amount),
|
Amount: uint64(amount),
|
||||||
RoundTxid: v.PoolTxid,
|
RoundTxid: v.PoolTxid,
|
||||||
@@ -191,8 +192,8 @@ func (a *restClient) ListVtxos(
|
|||||||
|
|
||||||
spentVtxos = append(spentVtxos, client.Vtxo{
|
spentVtxos = append(spentVtxos, client.Vtxo{
|
||||||
VtxoKey: client.VtxoKey{
|
VtxoKey: client.VtxoKey{
|
||||||
Txid: v.Outpoint.Txid,
|
Txid: v.Outpoint.VtxoInput.Txid,
|
||||||
VOut: uint32(v.Outpoint.Vout),
|
VOut: uint32(v.Outpoint.VtxoInput.Vout),
|
||||||
},
|
},
|
||||||
Amount: uint64(amount),
|
Amount: uint64(amount),
|
||||||
RoundTxid: v.PoolTxid,
|
RoundTxid: v.PoolTxid,
|
||||||
@@ -243,29 +244,31 @@ func (a *restClient) GetRound(
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *restClient) Onboard(
|
|
||||||
ctx context.Context, tx, userPubkey string, congestionTree tree.CongestionTree,
|
|
||||||
) error {
|
|
||||||
body := models.V1OnboardRequest{
|
|
||||||
BoardingTx: tx,
|
|
||||||
CongestionTree: treeToProto(congestionTree).parse(),
|
|
||||||
UserPubkey: userPubkey,
|
|
||||||
}
|
|
||||||
_, err := a.svc.ArkServiceOnboard(
|
|
||||||
ark_service.NewArkServiceOnboardParams().WithBody(&body),
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *restClient) RegisterPayment(
|
func (a *restClient) RegisterPayment(
|
||||||
ctx context.Context, inputs []client.VtxoKey, ephemeralPublicKey string,
|
ctx context.Context, inputs []client.Input, ephemeralPublicKey string,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
ins := make([]*models.V1Input, 0, len(inputs))
|
ins := make([]*models.V1Input, 0, len(inputs))
|
||||||
for _, i := range inputs {
|
for _, i := range inputs {
|
||||||
ins = append(ins, &models.V1Input{
|
var input *models.V1Input
|
||||||
Txid: i.Txid,
|
|
||||||
Vout: int64(i.VOut),
|
if len(i.GetDescriptor()) > 0 {
|
||||||
})
|
input = &models.V1Input{
|
||||||
|
BoardingInput: &models.V1BoardingInput{
|
||||||
|
Txid: i.GetTxID(),
|
||||||
|
Vout: int64(i.GetVOut()),
|
||||||
|
Descriptor: i.GetDescriptor(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
input = &models.V1Input{
|
||||||
|
VtxoInput: &models.V1VtxoInput{
|
||||||
|
Txid: i.GetTxID(),
|
||||||
|
Vout: int64(i.GetVOut()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ins = append(ins, input)
|
||||||
}
|
}
|
||||||
body := &models.V1RegisterPaymentRequest{
|
body := &models.V1RegisterPaymentRequest{
|
||||||
Inputs: ins,
|
Inputs: ins,
|
||||||
@@ -377,13 +380,11 @@ func (a *restClient) Ping(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *restClient) FinalizePayment(
|
func (a *restClient) FinalizePayment(
|
||||||
ctx context.Context, signedForfeitTxs []string,
|
ctx context.Context, signedForfeitTxs []string, signedRoundTx string,
|
||||||
) error {
|
) error {
|
||||||
req := &arkv1.FinalizePaymentRequest{
|
|
||||||
SignedForfeitTxs: signedForfeitTxs,
|
|
||||||
}
|
|
||||||
body := models.V1FinalizePaymentRequest{
|
body := models.V1FinalizePaymentRequest{
|
||||||
SignedForfeitTxs: req.GetSignedForfeitTxs(),
|
SignedForfeitTxs: signedForfeitTxs,
|
||||||
|
SignedRoundTx: signedRoundTx,
|
||||||
}
|
}
|
||||||
_, err := a.svc.ArkServiceFinalizePayment(
|
_, err := a.svc.ArkServiceFinalizePayment(
|
||||||
ark_service.NewArkServiceFinalizePaymentParams().WithBody(&body),
|
ark_service.NewArkServiceFinalizePaymentParams().WithBody(&body),
|
||||||
@@ -396,9 +397,15 @@ func (a *restClient) CreatePayment(
|
|||||||
) (string, []string, error) {
|
) (string, []string, error) {
|
||||||
ins := make([]*models.V1Input, 0, len(inputs))
|
ins := make([]*models.V1Input, 0, len(inputs))
|
||||||
for _, i := range inputs {
|
for _, i := range inputs {
|
||||||
|
if len(i.GetDescriptor()) > 0 {
|
||||||
|
return "", nil, fmt.Errorf("boarding inputs are not allowed in create payment")
|
||||||
|
}
|
||||||
|
|
||||||
ins = append(ins, &models.V1Input{
|
ins = append(ins, &models.V1Input{
|
||||||
Txid: i.Txid,
|
VtxoInput: &models.V1VtxoInput{
|
||||||
Vout: int64(i.VOut),
|
Txid: i.Txid,
|
||||||
|
Vout: int64(i.VOut),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
outs := make([]*models.V1Output, 0, len(outputs))
|
outs := make([]*models.V1Output, 0, len(outputs))
|
||||||
@@ -477,6 +484,23 @@ func (a *restClient) GetRoundByID(
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *restClient) GetBoardingAddress(
|
||||||
|
ctx context.Context, pubkey string,
|
||||||
|
) (string, error) {
|
||||||
|
body := models.V1GetBoardingAddressRequest{
|
||||||
|
Pubkey: pubkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := a.svc.ArkServiceGetBoardingAddress(
|
||||||
|
ark_service.NewArkServiceGetBoardingAddressParams().WithBody(&body),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "",
|
||||||
|
err
|
||||||
|
}
|
||||||
|
return resp.Payload.Address, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *restClient) SendTreeNonces(
|
func (a *restClient) SendTreeNonces(
|
||||||
ctx context.Context, roundID, cosignerPubkey string, nonces bitcointree.TreeNonces,
|
ctx context.Context, roundID, cosignerPubkey string, nonces bitcointree.TreeNonces,
|
||||||
) error {
|
) error {
|
||||||
@@ -604,25 +628,3 @@ func (t treeFromProto) parse() tree.CongestionTree {
|
|||||||
|
|
||||||
return congestionTree
|
return congestionTree
|
||||||
}
|
}
|
||||||
|
|
||||||
type treeToProto tree.CongestionTree
|
|
||||||
|
|
||||||
func (t treeToProto) parse() *models.V1Tree {
|
|
||||||
levels := make([]*models.V1TreeLevel, 0, len(t))
|
|
||||||
for _, level := range t {
|
|
||||||
nodes := make([]*models.V1Node, 0, len(level))
|
|
||||||
for _, n := range level {
|
|
||||||
nodes = append(nodes, &models.V1Node{
|
|
||||||
Txid: n.Txid,
|
|
||||||
Tx: n.Tx,
|
|
||||||
ParentTxid: n.ParentTxid,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
levels = append(levels, &models.V1TreeLevel{
|
|
||||||
Nodes: nodes,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return &models.V1Tree{
|
|
||||||
Levels: levels,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ type ClientService interface {
|
|||||||
|
|
||||||
ArkServiceFinalizePayment(params *ArkServiceFinalizePaymentParams, opts ...ClientOption) (*ArkServiceFinalizePaymentOK, error)
|
ArkServiceFinalizePayment(params *ArkServiceFinalizePaymentParams, opts ...ClientOption) (*ArkServiceFinalizePaymentOK, error)
|
||||||
|
|
||||||
|
ArkServiceGetBoardingAddress(params *ArkServiceGetBoardingAddressParams, opts ...ClientOption) (*ArkServiceGetBoardingAddressOK, error)
|
||||||
|
|
||||||
ArkServiceGetEventStream(params *ArkServiceGetEventStreamParams, opts ...ClientOption) (*ArkServiceGetEventStreamOK, error)
|
ArkServiceGetEventStream(params *ArkServiceGetEventStreamParams, opts ...ClientOption) (*ArkServiceGetEventStreamOK, error)
|
||||||
|
|
||||||
ArkServiceGetInfo(params *ArkServiceGetInfoParams, opts ...ClientOption) (*ArkServiceGetInfoOK, error)
|
ArkServiceGetInfo(params *ArkServiceGetInfoParams, opts ...ClientOption) (*ArkServiceGetInfoOK, error)
|
||||||
@@ -72,8 +74,6 @@ type ClientService interface {
|
|||||||
|
|
||||||
ArkServiceListVtxos(params *ArkServiceListVtxosParams, opts ...ClientOption) (*ArkServiceListVtxosOK, error)
|
ArkServiceListVtxos(params *ArkServiceListVtxosParams, opts ...ClientOption) (*ArkServiceListVtxosOK, error)
|
||||||
|
|
||||||
ArkServiceOnboard(params *ArkServiceOnboardParams, opts ...ClientOption) (*ArkServiceOnboardOK, error)
|
|
||||||
|
|
||||||
ArkServicePing(params *ArkServicePingParams, opts ...ClientOption) (*ArkServicePingOK, error)
|
ArkServicePing(params *ArkServicePingParams, opts ...ClientOption) (*ArkServicePingOK, error)
|
||||||
|
|
||||||
ArkServiceRegisterPayment(params *ArkServiceRegisterPaymentParams, opts ...ClientOption) (*ArkServiceRegisterPaymentOK, error)
|
ArkServiceRegisterPayment(params *ArkServiceRegisterPaymentParams, opts ...ClientOption) (*ArkServiceRegisterPaymentOK, error)
|
||||||
@@ -233,6 +233,43 @@ func (a *Client) ArkServiceFinalizePayment(params *ArkServiceFinalizePaymentPara
|
|||||||
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
|
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
ArkServiceGetBoardingAddress ark service get boarding address API
|
||||||
|
*/
|
||||||
|
func (a *Client) ArkServiceGetBoardingAddress(params *ArkServiceGetBoardingAddressParams, opts ...ClientOption) (*ArkServiceGetBoardingAddressOK, error) {
|
||||||
|
// TODO: Validate the params before sending
|
||||||
|
if params == nil {
|
||||||
|
params = NewArkServiceGetBoardingAddressParams()
|
||||||
|
}
|
||||||
|
op := &runtime.ClientOperation{
|
||||||
|
ID: "ArkService_GetBoardingAddress",
|
||||||
|
Method: "POST",
|
||||||
|
PathPattern: "/v1/boarding",
|
||||||
|
ProducesMediaTypes: []string{"application/json"},
|
||||||
|
ConsumesMediaTypes: []string{"application/json"},
|
||||||
|
Schemes: []string{"http"},
|
||||||
|
Params: params,
|
||||||
|
Reader: &ArkServiceGetBoardingAddressReader{formats: a.formats},
|
||||||
|
Context: params.Context,
|
||||||
|
Client: params.HTTPClient,
|
||||||
|
}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(op)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := a.transport.Submit(op)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
success, ok := result.(*ArkServiceGetBoardingAddressOK)
|
||||||
|
if ok {
|
||||||
|
return success, nil
|
||||||
|
}
|
||||||
|
// unexpected success response
|
||||||
|
unexpectedSuccess := result.(*ArkServiceGetBoardingAddressDefault)
|
||||||
|
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
ArkServiceGetEventStream ark service get event stream API
|
ArkServiceGetEventStream ark service get event stream API
|
||||||
*/
|
*/
|
||||||
@@ -418,43 +455,6 @@ func (a *Client) ArkServiceListVtxos(params *ArkServiceListVtxosParams, opts ...
|
|||||||
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
|
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
ArkServiceOnboard ark service onboard API
|
|
||||||
*/
|
|
||||||
func (a *Client) ArkServiceOnboard(params *ArkServiceOnboardParams, opts ...ClientOption) (*ArkServiceOnboardOK, error) {
|
|
||||||
// TODO: Validate the params before sending
|
|
||||||
if params == nil {
|
|
||||||
params = NewArkServiceOnboardParams()
|
|
||||||
}
|
|
||||||
op := &runtime.ClientOperation{
|
|
||||||
ID: "ArkService_Onboard",
|
|
||||||
Method: "POST",
|
|
||||||
PathPattern: "/v1/onboard",
|
|
||||||
ProducesMediaTypes: []string{"application/json"},
|
|
||||||
ConsumesMediaTypes: []string{"application/json"},
|
|
||||||
Schemes: []string{"http"},
|
|
||||||
Params: params,
|
|
||||||
Reader: &ArkServiceOnboardReader{formats: a.formats},
|
|
||||||
Context: params.Context,
|
|
||||||
Client: params.HTTPClient,
|
|
||||||
}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(op)
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := a.transport.Submit(op)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
success, ok := result.(*ArkServiceOnboardOK)
|
|
||||||
if ok {
|
|
||||||
return success, nil
|
|
||||||
}
|
|
||||||
// unexpected success response
|
|
||||||
unexpectedSuccess := result.(*ArkServiceOnboardDefault)
|
|
||||||
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
ArkServicePing ark service ping API
|
ArkServicePing ark service ping API
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
// Code generated by go-swagger; DO NOT EDIT.
|
||||||
|
|
||||||
|
package ark_service
|
||||||
|
|
||||||
|
// This file was generated by the swagger tool.
|
||||||
|
// Editing this file might prove futile when you re-run the swagger generate command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-openapi/errors"
|
||||||
|
"github.com/go-openapi/runtime"
|
||||||
|
cr "github.com/go-openapi/runtime/client"
|
||||||
|
"github.com/go-openapi/strfmt"
|
||||||
|
|
||||||
|
"github.com/ark-network/ark/pkg/client-sdk/client/rest/service/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewArkServiceGetBoardingAddressParams creates a new ArkServiceGetBoardingAddressParams object,
|
||||||
|
// with the default timeout for this client.
|
||||||
|
//
|
||||||
|
// Default values are not hydrated, since defaults are normally applied by the API server side.
|
||||||
|
//
|
||||||
|
// To enforce default values in parameter, use SetDefaults or WithDefaults.
|
||||||
|
func NewArkServiceGetBoardingAddressParams() *ArkServiceGetBoardingAddressParams {
|
||||||
|
return &ArkServiceGetBoardingAddressParams{
|
||||||
|
timeout: cr.DefaultTimeout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewArkServiceGetBoardingAddressParamsWithTimeout creates a new ArkServiceGetBoardingAddressParams object
|
||||||
|
// with the ability to set a timeout on a request.
|
||||||
|
func NewArkServiceGetBoardingAddressParamsWithTimeout(timeout time.Duration) *ArkServiceGetBoardingAddressParams {
|
||||||
|
return &ArkServiceGetBoardingAddressParams{
|
||||||
|
timeout: timeout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewArkServiceGetBoardingAddressParamsWithContext creates a new ArkServiceGetBoardingAddressParams object
|
||||||
|
// with the ability to set a context for a request.
|
||||||
|
func NewArkServiceGetBoardingAddressParamsWithContext(ctx context.Context) *ArkServiceGetBoardingAddressParams {
|
||||||
|
return &ArkServiceGetBoardingAddressParams{
|
||||||
|
Context: ctx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewArkServiceGetBoardingAddressParamsWithHTTPClient creates a new ArkServiceGetBoardingAddressParams object
|
||||||
|
// with the ability to set a custom HTTPClient for a request.
|
||||||
|
func NewArkServiceGetBoardingAddressParamsWithHTTPClient(client *http.Client) *ArkServiceGetBoardingAddressParams {
|
||||||
|
return &ArkServiceGetBoardingAddressParams{
|
||||||
|
HTTPClient: client,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
ArkServiceGetBoardingAddressParams contains all the parameters to send to the API endpoint
|
||||||
|
|
||||||
|
for the ark service get boarding address operation.
|
||||||
|
|
||||||
|
Typically these are written to a http.Request.
|
||||||
|
*/
|
||||||
|
type ArkServiceGetBoardingAddressParams struct {
|
||||||
|
|
||||||
|
// Body.
|
||||||
|
Body *models.V1GetBoardingAddressRequest
|
||||||
|
|
||||||
|
timeout time.Duration
|
||||||
|
Context context.Context
|
||||||
|
HTTPClient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDefaults hydrates default values in the ark service get boarding address params (not the query body).
|
||||||
|
//
|
||||||
|
// All values with no default are reset to their zero value.
|
||||||
|
func (o *ArkServiceGetBoardingAddressParams) WithDefaults() *ArkServiceGetBoardingAddressParams {
|
||||||
|
o.SetDefaults()
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaults hydrates default values in the ark service get boarding address params (not the query body).
|
||||||
|
//
|
||||||
|
// All values with no default are reset to their zero value.
|
||||||
|
func (o *ArkServiceGetBoardingAddressParams) SetDefaults() {
|
||||||
|
// no default values defined for this parameter
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTimeout adds the timeout to the ark service get boarding address params
|
||||||
|
func (o *ArkServiceGetBoardingAddressParams) WithTimeout(timeout time.Duration) *ArkServiceGetBoardingAddressParams {
|
||||||
|
o.SetTimeout(timeout)
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTimeout adds the timeout to the ark service get boarding address params
|
||||||
|
func (o *ArkServiceGetBoardingAddressParams) SetTimeout(timeout time.Duration) {
|
||||||
|
o.timeout = timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithContext adds the context to the ark service get boarding address params
|
||||||
|
func (o *ArkServiceGetBoardingAddressParams) WithContext(ctx context.Context) *ArkServiceGetBoardingAddressParams {
|
||||||
|
o.SetContext(ctx)
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetContext adds the context to the ark service get boarding address params
|
||||||
|
func (o *ArkServiceGetBoardingAddressParams) SetContext(ctx context.Context) {
|
||||||
|
o.Context = ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithHTTPClient adds the HTTPClient to the ark service get boarding address params
|
||||||
|
func (o *ArkServiceGetBoardingAddressParams) WithHTTPClient(client *http.Client) *ArkServiceGetBoardingAddressParams {
|
||||||
|
o.SetHTTPClient(client)
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHTTPClient adds the HTTPClient to the ark service get boarding address params
|
||||||
|
func (o *ArkServiceGetBoardingAddressParams) SetHTTPClient(client *http.Client) {
|
||||||
|
o.HTTPClient = client
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithBody adds the body to the ark service get boarding address params
|
||||||
|
func (o *ArkServiceGetBoardingAddressParams) WithBody(body *models.V1GetBoardingAddressRequest) *ArkServiceGetBoardingAddressParams {
|
||||||
|
o.SetBody(body)
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBody adds the body to the ark service get boarding address params
|
||||||
|
func (o *ArkServiceGetBoardingAddressParams) SetBody(body *models.V1GetBoardingAddressRequest) {
|
||||||
|
o.Body = body
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteToRequest writes these params to a swagger request
|
||||||
|
func (o *ArkServiceGetBoardingAddressParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
|
||||||
|
|
||||||
|
if err := r.SetTimeout(o.timeout); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var res []error
|
||||||
|
if o.Body != nil {
|
||||||
|
if err := r.SetBodyParam(o.Body); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(res) > 0 {
|
||||||
|
return errors.CompositeValidationError(res...)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
// Code generated by go-swagger; DO NOT EDIT.
|
||||||
|
|
||||||
|
package ark_service
|
||||||
|
|
||||||
|
// This file was generated by the swagger tool.
|
||||||
|
// Editing this file might prove futile when you re-run the swagger generate command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/go-openapi/runtime"
|
||||||
|
"github.com/go-openapi/strfmt"
|
||||||
|
|
||||||
|
"github.com/ark-network/ark/pkg/client-sdk/client/rest/service/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ArkServiceGetBoardingAddressReader is a Reader for the ArkServiceGetBoardingAddress structure.
|
||||||
|
type ArkServiceGetBoardingAddressReader struct {
|
||||||
|
formats strfmt.Registry
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadResponse reads a server response into the received o.
|
||||||
|
func (o *ArkServiceGetBoardingAddressReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
|
||||||
|
switch response.Code() {
|
||||||
|
case 200:
|
||||||
|
result := NewArkServiceGetBoardingAddressOK()
|
||||||
|
if err := result.readResponse(response, consumer, o.formats); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
default:
|
||||||
|
result := NewArkServiceGetBoardingAddressDefault(response.Code())
|
||||||
|
if err := result.readResponse(response, consumer, o.formats); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if response.Code()/100 == 2 {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
return nil, result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewArkServiceGetBoardingAddressOK creates a ArkServiceGetBoardingAddressOK with default headers values
|
||||||
|
func NewArkServiceGetBoardingAddressOK() *ArkServiceGetBoardingAddressOK {
|
||||||
|
return &ArkServiceGetBoardingAddressOK{}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
ArkServiceGetBoardingAddressOK describes a response with status code 200, with default header values.
|
||||||
|
|
||||||
|
A successful response.
|
||||||
|
*/
|
||||||
|
type ArkServiceGetBoardingAddressOK struct {
|
||||||
|
Payload *models.V1GetBoardingAddressResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSuccess returns true when this ark service get boarding address o k response has a 2xx status code
|
||||||
|
func (o *ArkServiceGetBoardingAddressOK) IsSuccess() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRedirect returns true when this ark service get boarding address o k response has a 3xx status code
|
||||||
|
func (o *ArkServiceGetBoardingAddressOK) IsRedirect() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsClientError returns true when this ark service get boarding address o k response has a 4xx status code
|
||||||
|
func (o *ArkServiceGetBoardingAddressOK) IsClientError() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsServerError returns true when this ark service get boarding address o k response has a 5xx status code
|
||||||
|
func (o *ArkServiceGetBoardingAddressOK) IsServerError() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsCode returns true when this ark service get boarding address o k response a status code equal to that given
|
||||||
|
func (o *ArkServiceGetBoardingAddressOK) IsCode(code int) bool {
|
||||||
|
return code == 200
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code gets the status code for the ark service get boarding address o k response
|
||||||
|
func (o *ArkServiceGetBoardingAddressOK) Code() int {
|
||||||
|
return 200
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ArkServiceGetBoardingAddressOK) Error() string {
|
||||||
|
payload, _ := json.Marshal(o.Payload)
|
||||||
|
return fmt.Sprintf("[POST /v1/boarding][%d] arkServiceGetBoardingAddressOK %s", 200, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ArkServiceGetBoardingAddressOK) String() string {
|
||||||
|
payload, _ := json.Marshal(o.Payload)
|
||||||
|
return fmt.Sprintf("[POST /v1/boarding][%d] arkServiceGetBoardingAddressOK %s", 200, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ArkServiceGetBoardingAddressOK) GetPayload() *models.V1GetBoardingAddressResponse {
|
||||||
|
return o.Payload
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ArkServiceGetBoardingAddressOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
|
||||||
|
|
||||||
|
o.Payload = new(models.V1GetBoardingAddressResponse)
|
||||||
|
|
||||||
|
// response payload
|
||||||
|
if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewArkServiceGetBoardingAddressDefault creates a ArkServiceGetBoardingAddressDefault with default headers values
|
||||||
|
func NewArkServiceGetBoardingAddressDefault(code int) *ArkServiceGetBoardingAddressDefault {
|
||||||
|
return &ArkServiceGetBoardingAddressDefault{
|
||||||
|
_statusCode: code,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
ArkServiceGetBoardingAddressDefault describes a response with status code -1, with default header values.
|
||||||
|
|
||||||
|
An unexpected error response.
|
||||||
|
*/
|
||||||
|
type ArkServiceGetBoardingAddressDefault struct {
|
||||||
|
_statusCode int
|
||||||
|
|
||||||
|
Payload *models.RPCStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSuccess returns true when this ark service get boarding address default response has a 2xx status code
|
||||||
|
func (o *ArkServiceGetBoardingAddressDefault) IsSuccess() bool {
|
||||||
|
return o._statusCode/100 == 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsRedirect returns true when this ark service get boarding address default response has a 3xx status code
|
||||||
|
func (o *ArkServiceGetBoardingAddressDefault) IsRedirect() bool {
|
||||||
|
return o._statusCode/100 == 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsClientError returns true when this ark service get boarding address default response has a 4xx status code
|
||||||
|
func (o *ArkServiceGetBoardingAddressDefault) IsClientError() bool {
|
||||||
|
return o._statusCode/100 == 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsServerError returns true when this ark service get boarding address default response has a 5xx status code
|
||||||
|
func (o *ArkServiceGetBoardingAddressDefault) IsServerError() bool {
|
||||||
|
return o._statusCode/100 == 5
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsCode returns true when this ark service get boarding address default response a status code equal to that given
|
||||||
|
func (o *ArkServiceGetBoardingAddressDefault) IsCode(code int) bool {
|
||||||
|
return o._statusCode == code
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code gets the status code for the ark service get boarding address default response
|
||||||
|
func (o *ArkServiceGetBoardingAddressDefault) Code() int {
|
||||||
|
return o._statusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ArkServiceGetBoardingAddressDefault) Error() string {
|
||||||
|
payload, _ := json.Marshal(o.Payload)
|
||||||
|
return fmt.Sprintf("[POST /v1/boarding][%d] ArkService_GetBoardingAddress default %s", o._statusCode, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ArkServiceGetBoardingAddressDefault) String() string {
|
||||||
|
payload, _ := json.Marshal(o.Payload)
|
||||||
|
return fmt.Sprintf("[POST /v1/boarding][%d] ArkService_GetBoardingAddress default %s", o._statusCode, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ArkServiceGetBoardingAddressDefault) GetPayload() *models.RPCStatus {
|
||||||
|
return o.Payload
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *ArkServiceGetBoardingAddressDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
|
||||||
|
|
||||||
|
o.Payload = new(models.RPCStatus)
|
||||||
|
|
||||||
|
// response payload
|
||||||
|
if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
// Code generated by go-swagger; DO NOT EDIT.
|
|
||||||
|
|
||||||
package ark_service
|
|
||||||
|
|
||||||
// This file was generated by the swagger tool.
|
|
||||||
// Editing this file might prove futile when you re-run the swagger generate command
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-openapi/errors"
|
|
||||||
"github.com/go-openapi/runtime"
|
|
||||||
cr "github.com/go-openapi/runtime/client"
|
|
||||||
"github.com/go-openapi/strfmt"
|
|
||||||
|
|
||||||
"github.com/ark-network/ark/pkg/client-sdk/client/rest/service/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewArkServiceOnboardParams creates a new ArkServiceOnboardParams object,
|
|
||||||
// with the default timeout for this client.
|
|
||||||
//
|
|
||||||
// Default values are not hydrated, since defaults are normally applied by the API server side.
|
|
||||||
//
|
|
||||||
// To enforce default values in parameter, use SetDefaults or WithDefaults.
|
|
||||||
func NewArkServiceOnboardParams() *ArkServiceOnboardParams {
|
|
||||||
return &ArkServiceOnboardParams{
|
|
||||||
timeout: cr.DefaultTimeout,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewArkServiceOnboardParamsWithTimeout creates a new ArkServiceOnboardParams object
|
|
||||||
// with the ability to set a timeout on a request.
|
|
||||||
func NewArkServiceOnboardParamsWithTimeout(timeout time.Duration) *ArkServiceOnboardParams {
|
|
||||||
return &ArkServiceOnboardParams{
|
|
||||||
timeout: timeout,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewArkServiceOnboardParamsWithContext creates a new ArkServiceOnboardParams object
|
|
||||||
// with the ability to set a context for a request.
|
|
||||||
func NewArkServiceOnboardParamsWithContext(ctx context.Context) *ArkServiceOnboardParams {
|
|
||||||
return &ArkServiceOnboardParams{
|
|
||||||
Context: ctx,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewArkServiceOnboardParamsWithHTTPClient creates a new ArkServiceOnboardParams object
|
|
||||||
// with the ability to set a custom HTTPClient for a request.
|
|
||||||
func NewArkServiceOnboardParamsWithHTTPClient(client *http.Client) *ArkServiceOnboardParams {
|
|
||||||
return &ArkServiceOnboardParams{
|
|
||||||
HTTPClient: client,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
ArkServiceOnboardParams contains all the parameters to send to the API endpoint
|
|
||||||
|
|
||||||
for the ark service onboard operation.
|
|
||||||
|
|
||||||
Typically these are written to a http.Request.
|
|
||||||
*/
|
|
||||||
type ArkServiceOnboardParams struct {
|
|
||||||
|
|
||||||
// Body.
|
|
||||||
Body *models.V1OnboardRequest
|
|
||||||
|
|
||||||
timeout time.Duration
|
|
||||||
Context context.Context
|
|
||||||
HTTPClient *http.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDefaults hydrates default values in the ark service onboard params (not the query body).
|
|
||||||
//
|
|
||||||
// All values with no default are reset to their zero value.
|
|
||||||
func (o *ArkServiceOnboardParams) WithDefaults() *ArkServiceOnboardParams {
|
|
||||||
o.SetDefaults()
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDefaults hydrates default values in the ark service onboard params (not the query body).
|
|
||||||
//
|
|
||||||
// All values with no default are reset to their zero value.
|
|
||||||
func (o *ArkServiceOnboardParams) SetDefaults() {
|
|
||||||
// no default values defined for this parameter
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithTimeout adds the timeout to the ark service onboard params
|
|
||||||
func (o *ArkServiceOnboardParams) WithTimeout(timeout time.Duration) *ArkServiceOnboardParams {
|
|
||||||
o.SetTimeout(timeout)
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTimeout adds the timeout to the ark service onboard params
|
|
||||||
func (o *ArkServiceOnboardParams) SetTimeout(timeout time.Duration) {
|
|
||||||
o.timeout = timeout
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithContext adds the context to the ark service onboard params
|
|
||||||
func (o *ArkServiceOnboardParams) WithContext(ctx context.Context) *ArkServiceOnboardParams {
|
|
||||||
o.SetContext(ctx)
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetContext adds the context to the ark service onboard params
|
|
||||||
func (o *ArkServiceOnboardParams) SetContext(ctx context.Context) {
|
|
||||||
o.Context = ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithHTTPClient adds the HTTPClient to the ark service onboard params
|
|
||||||
func (o *ArkServiceOnboardParams) WithHTTPClient(client *http.Client) *ArkServiceOnboardParams {
|
|
||||||
o.SetHTTPClient(client)
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHTTPClient adds the HTTPClient to the ark service onboard params
|
|
||||||
func (o *ArkServiceOnboardParams) SetHTTPClient(client *http.Client) {
|
|
||||||
o.HTTPClient = client
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithBody adds the body to the ark service onboard params
|
|
||||||
func (o *ArkServiceOnboardParams) WithBody(body *models.V1OnboardRequest) *ArkServiceOnboardParams {
|
|
||||||
o.SetBody(body)
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetBody adds the body to the ark service onboard params
|
|
||||||
func (o *ArkServiceOnboardParams) SetBody(body *models.V1OnboardRequest) {
|
|
||||||
o.Body = body
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteToRequest writes these params to a swagger request
|
|
||||||
func (o *ArkServiceOnboardParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
|
|
||||||
|
|
||||||
if err := r.SetTimeout(o.timeout); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var res []error
|
|
||||||
if o.Body != nil {
|
|
||||||
if err := r.SetBodyParam(o.Body); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(res) > 0 {
|
|
||||||
return errors.CompositeValidationError(res...)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
// Code generated by go-swagger; DO NOT EDIT.
|
|
||||||
|
|
||||||
package ark_service
|
|
||||||
|
|
||||||
// This file was generated by the swagger tool.
|
|
||||||
// Editing this file might prove futile when you re-run the swagger generate command
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/go-openapi/runtime"
|
|
||||||
"github.com/go-openapi/strfmt"
|
|
||||||
|
|
||||||
"github.com/ark-network/ark/pkg/client-sdk/client/rest/service/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ArkServiceOnboardReader is a Reader for the ArkServiceOnboard structure.
|
|
||||||
type ArkServiceOnboardReader struct {
|
|
||||||
formats strfmt.Registry
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadResponse reads a server response into the received o.
|
|
||||||
func (o *ArkServiceOnboardReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
|
|
||||||
switch response.Code() {
|
|
||||||
case 200:
|
|
||||||
result := NewArkServiceOnboardOK()
|
|
||||||
if err := result.readResponse(response, consumer, o.formats); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
default:
|
|
||||||
result := NewArkServiceOnboardDefault(response.Code())
|
|
||||||
if err := result.readResponse(response, consumer, o.formats); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if response.Code()/100 == 2 {
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
return nil, result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewArkServiceOnboardOK creates a ArkServiceOnboardOK with default headers values
|
|
||||||
func NewArkServiceOnboardOK() *ArkServiceOnboardOK {
|
|
||||||
return &ArkServiceOnboardOK{}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
ArkServiceOnboardOK describes a response with status code 200, with default header values.
|
|
||||||
|
|
||||||
A successful response.
|
|
||||||
*/
|
|
||||||
type ArkServiceOnboardOK struct {
|
|
||||||
Payload models.V1OnboardResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSuccess returns true when this ark service onboard o k response has a 2xx status code
|
|
||||||
func (o *ArkServiceOnboardOK) IsSuccess() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRedirect returns true when this ark service onboard o k response has a 3xx status code
|
|
||||||
func (o *ArkServiceOnboardOK) IsRedirect() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsClientError returns true when this ark service onboard o k response has a 4xx status code
|
|
||||||
func (o *ArkServiceOnboardOK) IsClientError() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsServerError returns true when this ark service onboard o k response has a 5xx status code
|
|
||||||
func (o *ArkServiceOnboardOK) IsServerError() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsCode returns true when this ark service onboard o k response a status code equal to that given
|
|
||||||
func (o *ArkServiceOnboardOK) IsCode(code int) bool {
|
|
||||||
return code == 200
|
|
||||||
}
|
|
||||||
|
|
||||||
// Code gets the status code for the ark service onboard o k response
|
|
||||||
func (o *ArkServiceOnboardOK) Code() int {
|
|
||||||
return 200
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ArkServiceOnboardOK) Error() string {
|
|
||||||
payload, _ := json.Marshal(o.Payload)
|
|
||||||
return fmt.Sprintf("[POST /v1/onboard][%d] arkServiceOnboardOK %s", 200, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ArkServiceOnboardOK) String() string {
|
|
||||||
payload, _ := json.Marshal(o.Payload)
|
|
||||||
return fmt.Sprintf("[POST /v1/onboard][%d] arkServiceOnboardOK %s", 200, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ArkServiceOnboardOK) GetPayload() models.V1OnboardResponse {
|
|
||||||
return o.Payload
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ArkServiceOnboardOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
|
|
||||||
|
|
||||||
// response payload
|
|
||||||
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewArkServiceOnboardDefault creates a ArkServiceOnboardDefault with default headers values
|
|
||||||
func NewArkServiceOnboardDefault(code int) *ArkServiceOnboardDefault {
|
|
||||||
return &ArkServiceOnboardDefault{
|
|
||||||
_statusCode: code,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
ArkServiceOnboardDefault describes a response with status code -1, with default header values.
|
|
||||||
|
|
||||||
An unexpected error response.
|
|
||||||
*/
|
|
||||||
type ArkServiceOnboardDefault struct {
|
|
||||||
_statusCode int
|
|
||||||
|
|
||||||
Payload *models.RPCStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSuccess returns true when this ark service onboard default response has a 2xx status code
|
|
||||||
func (o *ArkServiceOnboardDefault) IsSuccess() bool {
|
|
||||||
return o._statusCode/100 == 2
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRedirect returns true when this ark service onboard default response has a 3xx status code
|
|
||||||
func (o *ArkServiceOnboardDefault) IsRedirect() bool {
|
|
||||||
return o._statusCode/100 == 3
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsClientError returns true when this ark service onboard default response has a 4xx status code
|
|
||||||
func (o *ArkServiceOnboardDefault) IsClientError() bool {
|
|
||||||
return o._statusCode/100 == 4
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsServerError returns true when this ark service onboard default response has a 5xx status code
|
|
||||||
func (o *ArkServiceOnboardDefault) IsServerError() bool {
|
|
||||||
return o._statusCode/100 == 5
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsCode returns true when this ark service onboard default response a status code equal to that given
|
|
||||||
func (o *ArkServiceOnboardDefault) IsCode(code int) bool {
|
|
||||||
return o._statusCode == code
|
|
||||||
}
|
|
||||||
|
|
||||||
// Code gets the status code for the ark service onboard default response
|
|
||||||
func (o *ArkServiceOnboardDefault) Code() int {
|
|
||||||
return o._statusCode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ArkServiceOnboardDefault) Error() string {
|
|
||||||
payload, _ := json.Marshal(o.Payload)
|
|
||||||
return fmt.Sprintf("[POST /v1/onboard][%d] ArkService_Onboard default %s", o._statusCode, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ArkServiceOnboardDefault) String() string {
|
|
||||||
payload, _ := json.Marshal(o.Payload)
|
|
||||||
return fmt.Sprintf("[POST /v1/onboard][%d] ArkService_Onboard default %s", o._statusCode, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ArkServiceOnboardDefault) GetPayload() *models.RPCStatus {
|
|
||||||
return o.Payload
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ArkServiceOnboardDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
|
|
||||||
|
|
||||||
o.Payload = new(models.RPCStatus)
|
|
||||||
|
|
||||||
// response payload
|
|
||||||
if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
// Code generated by go-swagger; DO NOT EDIT.
|
|
||||||
|
|
||||||
package ark_service
|
|
||||||
|
|
||||||
// This file was generated by the swagger tool.
|
|
||||||
// Editing this file might prove futile when you re-run the swagger generate command
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-openapi/errors"
|
|
||||||
"github.com/go-openapi/runtime"
|
|
||||||
cr "github.com/go-openapi/runtime/client"
|
|
||||||
"github.com/go-openapi/strfmt"
|
|
||||||
|
|
||||||
"github.com/ark-network/ark/pkg/client-sdk/client/rest/service/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewArkServiceTrustedOnboardingParams creates a new ArkServiceTrustedOnboardingParams object,
|
|
||||||
// with the default timeout for this client.
|
|
||||||
//
|
|
||||||
// Default values are not hydrated, since defaults are normally applied by the API server side.
|
|
||||||
//
|
|
||||||
// To enforce default values in parameter, use SetDefaults or WithDefaults.
|
|
||||||
func NewArkServiceTrustedOnboardingParams() *ArkServiceTrustedOnboardingParams {
|
|
||||||
return &ArkServiceTrustedOnboardingParams{
|
|
||||||
timeout: cr.DefaultTimeout,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewArkServiceTrustedOnboardingParamsWithTimeout creates a new ArkServiceTrustedOnboardingParams object
|
|
||||||
// with the ability to set a timeout on a request.
|
|
||||||
func NewArkServiceTrustedOnboardingParamsWithTimeout(timeout time.Duration) *ArkServiceTrustedOnboardingParams {
|
|
||||||
return &ArkServiceTrustedOnboardingParams{
|
|
||||||
timeout: timeout,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewArkServiceTrustedOnboardingParamsWithContext creates a new ArkServiceTrustedOnboardingParams object
|
|
||||||
// with the ability to set a context for a request.
|
|
||||||
func NewArkServiceTrustedOnboardingParamsWithContext(ctx context.Context) *ArkServiceTrustedOnboardingParams {
|
|
||||||
return &ArkServiceTrustedOnboardingParams{
|
|
||||||
Context: ctx,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewArkServiceTrustedOnboardingParamsWithHTTPClient creates a new ArkServiceTrustedOnboardingParams object
|
|
||||||
// with the ability to set a custom HTTPClient for a request.
|
|
||||||
func NewArkServiceTrustedOnboardingParamsWithHTTPClient(client *http.Client) *ArkServiceTrustedOnboardingParams {
|
|
||||||
return &ArkServiceTrustedOnboardingParams{
|
|
||||||
HTTPClient: client,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
ArkServiceTrustedOnboardingParams contains all the parameters to send to the API endpoint
|
|
||||||
|
|
||||||
for the ark service trusted onboarding operation.
|
|
||||||
|
|
||||||
Typically these are written to a http.Request.
|
|
||||||
*/
|
|
||||||
type ArkServiceTrustedOnboardingParams struct {
|
|
||||||
|
|
||||||
// Body.
|
|
||||||
Body *models.V1TrustedOnboardingRequest
|
|
||||||
|
|
||||||
timeout time.Duration
|
|
||||||
Context context.Context
|
|
||||||
HTTPClient *http.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDefaults hydrates default values in the ark service trusted onboarding params (not the query body).
|
|
||||||
//
|
|
||||||
// All values with no default are reset to their zero value.
|
|
||||||
func (o *ArkServiceTrustedOnboardingParams) WithDefaults() *ArkServiceTrustedOnboardingParams {
|
|
||||||
o.SetDefaults()
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDefaults hydrates default values in the ark service trusted onboarding params (not the query body).
|
|
||||||
//
|
|
||||||
// All values with no default are reset to their zero value.
|
|
||||||
func (o *ArkServiceTrustedOnboardingParams) SetDefaults() {
|
|
||||||
// no default values defined for this parameter
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithTimeout adds the timeout to the ark service trusted onboarding params
|
|
||||||
func (o *ArkServiceTrustedOnboardingParams) WithTimeout(timeout time.Duration) *ArkServiceTrustedOnboardingParams {
|
|
||||||
o.SetTimeout(timeout)
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTimeout adds the timeout to the ark service trusted onboarding params
|
|
||||||
func (o *ArkServiceTrustedOnboardingParams) SetTimeout(timeout time.Duration) {
|
|
||||||
o.timeout = timeout
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithContext adds the context to the ark service trusted onboarding params
|
|
||||||
func (o *ArkServiceTrustedOnboardingParams) WithContext(ctx context.Context) *ArkServiceTrustedOnboardingParams {
|
|
||||||
o.SetContext(ctx)
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetContext adds the context to the ark service trusted onboarding params
|
|
||||||
func (o *ArkServiceTrustedOnboardingParams) SetContext(ctx context.Context) {
|
|
||||||
o.Context = ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithHTTPClient adds the HTTPClient to the ark service trusted onboarding params
|
|
||||||
func (o *ArkServiceTrustedOnboardingParams) WithHTTPClient(client *http.Client) *ArkServiceTrustedOnboardingParams {
|
|
||||||
o.SetHTTPClient(client)
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHTTPClient adds the HTTPClient to the ark service trusted onboarding params
|
|
||||||
func (o *ArkServiceTrustedOnboardingParams) SetHTTPClient(client *http.Client) {
|
|
||||||
o.HTTPClient = client
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithBody adds the body to the ark service trusted onboarding params
|
|
||||||
func (o *ArkServiceTrustedOnboardingParams) WithBody(body *models.V1TrustedOnboardingRequest) *ArkServiceTrustedOnboardingParams {
|
|
||||||
o.SetBody(body)
|
|
||||||
return o
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetBody adds the body to the ark service trusted onboarding params
|
|
||||||
func (o *ArkServiceTrustedOnboardingParams) SetBody(body *models.V1TrustedOnboardingRequest) {
|
|
||||||
o.Body = body
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteToRequest writes these params to a swagger request
|
|
||||||
func (o *ArkServiceTrustedOnboardingParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
|
|
||||||
|
|
||||||
if err := r.SetTimeout(o.timeout); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var res []error
|
|
||||||
if o.Body != nil {
|
|
||||||
if err := r.SetBodyParam(o.Body); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(res) > 0 {
|
|
||||||
return errors.CompositeValidationError(res...)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
// Code generated by go-swagger; DO NOT EDIT.
|
|
||||||
|
|
||||||
package ark_service
|
|
||||||
|
|
||||||
// This file was generated by the swagger tool.
|
|
||||||
// Editing this file might prove futile when you re-run the swagger generate command
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/go-openapi/runtime"
|
|
||||||
"github.com/go-openapi/strfmt"
|
|
||||||
|
|
||||||
"github.com/ark-network/ark/pkg/client-sdk/client/rest/service/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ArkServiceTrustedOnboardingReader is a Reader for the ArkServiceTrustedOnboarding structure.
|
|
||||||
type ArkServiceTrustedOnboardingReader struct {
|
|
||||||
formats strfmt.Registry
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadResponse reads a server response into the received o.
|
|
||||||
func (o *ArkServiceTrustedOnboardingReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
|
|
||||||
switch response.Code() {
|
|
||||||
case 200:
|
|
||||||
result := NewArkServiceTrustedOnboardingOK()
|
|
||||||
if err := result.readResponse(response, consumer, o.formats); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
default:
|
|
||||||
result := NewArkServiceTrustedOnboardingDefault(response.Code())
|
|
||||||
if err := result.readResponse(response, consumer, o.formats); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if response.Code()/100 == 2 {
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
return nil, result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewArkServiceTrustedOnboardingOK creates a ArkServiceTrustedOnboardingOK with default headers values
|
|
||||||
func NewArkServiceTrustedOnboardingOK() *ArkServiceTrustedOnboardingOK {
|
|
||||||
return &ArkServiceTrustedOnboardingOK{}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
ArkServiceTrustedOnboardingOK describes a response with status code 200, with default header values.
|
|
||||||
|
|
||||||
A successful response.
|
|
||||||
*/
|
|
||||||
type ArkServiceTrustedOnboardingOK struct {
|
|
||||||
Payload *models.V1TrustedOnboardingResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSuccess returns true when this ark service trusted onboarding o k response has a 2xx status code
|
|
||||||
func (o *ArkServiceTrustedOnboardingOK) IsSuccess() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRedirect returns true when this ark service trusted onboarding o k response has a 3xx status code
|
|
||||||
func (o *ArkServiceTrustedOnboardingOK) IsRedirect() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsClientError returns true when this ark service trusted onboarding o k response has a 4xx status code
|
|
||||||
func (o *ArkServiceTrustedOnboardingOK) IsClientError() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsServerError returns true when this ark service trusted onboarding o k response has a 5xx status code
|
|
||||||
func (o *ArkServiceTrustedOnboardingOK) IsServerError() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsCode returns true when this ark service trusted onboarding o k response a status code equal to that given
|
|
||||||
func (o *ArkServiceTrustedOnboardingOK) IsCode(code int) bool {
|
|
||||||
return code == 200
|
|
||||||
}
|
|
||||||
|
|
||||||
// Code gets the status code for the ark service trusted onboarding o k response
|
|
||||||
func (o *ArkServiceTrustedOnboardingOK) Code() int {
|
|
||||||
return 200
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ArkServiceTrustedOnboardingOK) Error() string {
|
|
||||||
payload, _ := json.Marshal(o.Payload)
|
|
||||||
return fmt.Sprintf("[POST /v1/onboard/address][%d] arkServiceTrustedOnboardingOK %s", 200, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ArkServiceTrustedOnboardingOK) String() string {
|
|
||||||
payload, _ := json.Marshal(o.Payload)
|
|
||||||
return fmt.Sprintf("[POST /v1/onboard/address][%d] arkServiceTrustedOnboardingOK %s", 200, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ArkServiceTrustedOnboardingOK) GetPayload() *models.V1TrustedOnboardingResponse {
|
|
||||||
return o.Payload
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ArkServiceTrustedOnboardingOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
|
|
||||||
|
|
||||||
o.Payload = new(models.V1TrustedOnboardingResponse)
|
|
||||||
|
|
||||||
// response payload
|
|
||||||
if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewArkServiceTrustedOnboardingDefault creates a ArkServiceTrustedOnboardingDefault with default headers values
|
|
||||||
func NewArkServiceTrustedOnboardingDefault(code int) *ArkServiceTrustedOnboardingDefault {
|
|
||||||
return &ArkServiceTrustedOnboardingDefault{
|
|
||||||
_statusCode: code,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
ArkServiceTrustedOnboardingDefault describes a response with status code -1, with default header values.
|
|
||||||
|
|
||||||
An unexpected error response.
|
|
||||||
*/
|
|
||||||
type ArkServiceTrustedOnboardingDefault struct {
|
|
||||||
_statusCode int
|
|
||||||
|
|
||||||
Payload *models.RPCStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSuccess returns true when this ark service trusted onboarding default response has a 2xx status code
|
|
||||||
func (o *ArkServiceTrustedOnboardingDefault) IsSuccess() bool {
|
|
||||||
return o._statusCode/100 == 2
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsRedirect returns true when this ark service trusted onboarding default response has a 3xx status code
|
|
||||||
func (o *ArkServiceTrustedOnboardingDefault) IsRedirect() bool {
|
|
||||||
return o._statusCode/100 == 3
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsClientError returns true when this ark service trusted onboarding default response has a 4xx status code
|
|
||||||
func (o *ArkServiceTrustedOnboardingDefault) IsClientError() bool {
|
|
||||||
return o._statusCode/100 == 4
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsServerError returns true when this ark service trusted onboarding default response has a 5xx status code
|
|
||||||
func (o *ArkServiceTrustedOnboardingDefault) IsServerError() bool {
|
|
||||||
return o._statusCode/100 == 5
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsCode returns true when this ark service trusted onboarding default response a status code equal to that given
|
|
||||||
func (o *ArkServiceTrustedOnboardingDefault) IsCode(code int) bool {
|
|
||||||
return o._statusCode == code
|
|
||||||
}
|
|
||||||
|
|
||||||
// Code gets the status code for the ark service trusted onboarding default response
|
|
||||||
func (o *ArkServiceTrustedOnboardingDefault) Code() int {
|
|
||||||
return o._statusCode
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ArkServiceTrustedOnboardingDefault) Error() string {
|
|
||||||
payload, _ := json.Marshal(o.Payload)
|
|
||||||
return fmt.Sprintf("[POST /v1/onboard/address][%d] ArkService_TrustedOnboarding default %s", o._statusCode, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ArkServiceTrustedOnboardingDefault) String() string {
|
|
||||||
payload, _ := json.Marshal(o.Payload)
|
|
||||||
return fmt.Sprintf("[POST /v1/onboard/address][%d] ArkService_TrustedOnboarding default %s", o._statusCode, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ArkServiceTrustedOnboardingDefault) GetPayload() *models.RPCStatus {
|
|
||||||
return o.Payload
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *ArkServiceTrustedOnboardingDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
|
|
||||||
|
|
||||||
o.Payload = new(models.RPCStatus)
|
|
||||||
|
|
||||||
// response payload
|
|
||||||
if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// Code generated by go-swagger; DO NOT EDIT.
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
// This file was generated by the swagger tool.
|
||||||
|
// Editing this file might prove futile when you re-run the swagger generate command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/go-openapi/strfmt"
|
||||||
|
"github.com/go-openapi/swag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// V1BoardingInput v1 boarding input
|
||||||
|
//
|
||||||
|
// swagger:model v1BoardingInput
|
||||||
|
type V1BoardingInput struct {
|
||||||
|
|
||||||
|
// descriptor
|
||||||
|
Descriptor string `json:"descriptor,omitempty"`
|
||||||
|
|
||||||
|
// txid
|
||||||
|
Txid string `json:"txid,omitempty"`
|
||||||
|
|
||||||
|
// vout
|
||||||
|
Vout int64 `json:"vout,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates this v1 boarding input
|
||||||
|
func (m *V1BoardingInput) Validate(formats strfmt.Registry) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextValidate validates this v1 boarding input based on context it is used
|
||||||
|
func (m *V1BoardingInput) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalBinary interface implementation
|
||||||
|
func (m *V1BoardingInput) MarshalBinary() ([]byte, error) {
|
||||||
|
if m == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return swag.WriteJSON(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalBinary interface implementation
|
||||||
|
func (m *V1BoardingInput) UnmarshalBinary(b []byte) error {
|
||||||
|
var res V1BoardingInput
|
||||||
|
if err := swag.ReadJSON(b, &res); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*m = res
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -19,6 +19,9 @@ type V1FinalizePaymentRequest struct {
|
|||||||
|
|
||||||
// Forfeit txs signed by the user.
|
// Forfeit txs signed by the user.
|
||||||
SignedForfeitTxs []string `json:"signedForfeitTxs"`
|
SignedForfeitTxs []string `json:"signedForfeitTxs"`
|
||||||
|
|
||||||
|
// If payment has reverse boarding, the user must sign the associated inputs.
|
||||||
|
SignedRoundTx string `json:"signedRoundTx,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates this v1 finalize payment request
|
// Validate validates this v1 finalize payment request
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
// Code generated by go-swagger; DO NOT EDIT.
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
// This file was generated by the swagger tool.
|
||||||
|
// Editing this file might prove futile when you re-run the swagger generate command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/go-openapi/strfmt"
|
||||||
|
"github.com/go-openapi/swag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// V1GetBoardingAddressRequest v1 get boarding address request
|
||||||
|
//
|
||||||
|
// swagger:model v1GetBoardingAddressRequest
|
||||||
|
type V1GetBoardingAddressRequest struct {
|
||||||
|
|
||||||
|
// pubkey
|
||||||
|
Pubkey string `json:"pubkey,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates this v1 get boarding address request
|
||||||
|
func (m *V1GetBoardingAddressRequest) Validate(formats strfmt.Registry) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextValidate validates this v1 get boarding address request based on context it is used
|
||||||
|
func (m *V1GetBoardingAddressRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalBinary interface implementation
|
||||||
|
func (m *V1GetBoardingAddressRequest) MarshalBinary() ([]byte, error) {
|
||||||
|
if m == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return swag.WriteJSON(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalBinary interface implementation
|
||||||
|
func (m *V1GetBoardingAddressRequest) UnmarshalBinary(b []byte) error {
|
||||||
|
var res V1GetBoardingAddressRequest
|
||||||
|
if err := swag.ReadJSON(b, &res); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*m = res
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
// Code generated by go-swagger; DO NOT EDIT.
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
// This file was generated by the swagger tool.
|
||||||
|
// Editing this file might prove futile when you re-run the swagger generate command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/go-openapi/strfmt"
|
||||||
|
"github.com/go-openapi/swag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// V1GetBoardingAddressResponse v1 get boarding address response
|
||||||
|
//
|
||||||
|
// swagger:model v1GetBoardingAddressResponse
|
||||||
|
type V1GetBoardingAddressResponse struct {
|
||||||
|
|
||||||
|
// address
|
||||||
|
Address string `json:"address,omitempty"`
|
||||||
|
|
||||||
|
// descriptor
|
||||||
|
Descriptor string `json:"descriptor,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates this v1 get boarding address response
|
||||||
|
func (m *V1GetBoardingAddressResponse) Validate(formats strfmt.Registry) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextValidate validates this v1 get boarding address response based on context it is used
|
||||||
|
func (m *V1GetBoardingAddressResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalBinary interface implementation
|
||||||
|
func (m *V1GetBoardingAddressResponse) MarshalBinary() ([]byte, error) {
|
||||||
|
if m == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return swag.WriteJSON(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalBinary interface implementation
|
||||||
|
func (m *V1GetBoardingAddressResponse) UnmarshalBinary(b []byte) error {
|
||||||
|
var res V1GetBoardingAddressResponse
|
||||||
|
if err := swag.ReadJSON(b, &res); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*m = res
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -17,6 +17,9 @@ import (
|
|||||||
// swagger:model v1GetInfoResponse
|
// swagger:model v1GetInfoResponse
|
||||||
type V1GetInfoResponse struct {
|
type V1GetInfoResponse struct {
|
||||||
|
|
||||||
|
// boarding descriptor template
|
||||||
|
BoardingDescriptorTemplate string `json:"boardingDescriptorTemplate,omitempty"`
|
||||||
|
|
||||||
// min relay fee
|
// min relay fee
|
||||||
MinRelayFee string `json:"minRelayFee,omitempty"`
|
MinRelayFee string `json:"minRelayFee,omitempty"`
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ package models
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/go-openapi/errors"
|
||||||
"github.com/go-openapi/strfmt"
|
"github.com/go-openapi/strfmt"
|
||||||
"github.com/go-openapi/swag"
|
"github.com/go-openapi/swag"
|
||||||
)
|
)
|
||||||
@@ -17,20 +18,126 @@ import (
|
|||||||
// swagger:model v1Input
|
// swagger:model v1Input
|
||||||
type V1Input struct {
|
type V1Input struct {
|
||||||
|
|
||||||
// txid
|
// boarding input
|
||||||
Txid string `json:"txid,omitempty"`
|
BoardingInput *V1BoardingInput `json:"boardingInput,omitempty"`
|
||||||
|
|
||||||
// vout
|
// vtxo input
|
||||||
Vout int64 `json:"vout,omitempty"`
|
VtxoInput *V1VtxoInput `json:"vtxoInput,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates this v1 input
|
// Validate validates this v1 input
|
||||||
func (m *V1Input) Validate(formats strfmt.Registry) error {
|
func (m *V1Input) Validate(formats strfmt.Registry) error {
|
||||||
|
var res []error
|
||||||
|
|
||||||
|
if err := m.validateBoardingInput(formats); err != nil {
|
||||||
|
res = append(res, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.validateVtxoInput(formats); err != nil {
|
||||||
|
res = append(res, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(res) > 0 {
|
||||||
|
return errors.CompositeValidationError(res...)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContextValidate validates this v1 input based on context it is used
|
func (m *V1Input) validateBoardingInput(formats strfmt.Registry) error {
|
||||||
|
if swag.IsZero(m.BoardingInput) { // not required
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.BoardingInput != nil {
|
||||||
|
if err := m.BoardingInput.Validate(formats); err != nil {
|
||||||
|
if ve, ok := err.(*errors.Validation); ok {
|
||||||
|
return ve.ValidateName("boardingInput")
|
||||||
|
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||||
|
return ce.ValidateName("boardingInput")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *V1Input) validateVtxoInput(formats strfmt.Registry) error {
|
||||||
|
if swag.IsZero(m.VtxoInput) { // not required
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.VtxoInput != nil {
|
||||||
|
if err := m.VtxoInput.Validate(formats); err != nil {
|
||||||
|
if ve, ok := err.(*errors.Validation); ok {
|
||||||
|
return ve.ValidateName("vtxoInput")
|
||||||
|
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||||
|
return ce.ValidateName("vtxoInput")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextValidate validate this v1 input based on the context it is used
|
||||||
func (m *V1Input) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
func (m *V1Input) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||||
|
var res []error
|
||||||
|
|
||||||
|
if err := m.contextValidateBoardingInput(ctx, formats); err != nil {
|
||||||
|
res = append(res, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.contextValidateVtxoInput(ctx, formats); err != nil {
|
||||||
|
res = append(res, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(res) > 0 {
|
||||||
|
return errors.CompositeValidationError(res...)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *V1Input) contextValidateBoardingInput(ctx context.Context, formats strfmt.Registry) error {
|
||||||
|
|
||||||
|
if m.BoardingInput != nil {
|
||||||
|
|
||||||
|
if swag.IsZero(m.BoardingInput) { // not required
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.BoardingInput.ContextValidate(ctx, formats); err != nil {
|
||||||
|
if ve, ok := err.(*errors.Validation); ok {
|
||||||
|
return ve.ValidateName("boardingInput")
|
||||||
|
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||||
|
return ce.ValidateName("boardingInput")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *V1Input) contextValidateVtxoInput(ctx context.Context, formats strfmt.Registry) error {
|
||||||
|
|
||||||
|
if m.VtxoInput != nil {
|
||||||
|
|
||||||
|
if swag.IsZero(m.VtxoInput) { // not required
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.VtxoInput.ContextValidate(ctx, formats); err != nil {
|
||||||
|
if ve, ok := err.(*errors.Validation); ok {
|
||||||
|
return ve.ValidateName("vtxoInput")
|
||||||
|
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||||
|
return ce.ValidateName("vtxoInput")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,115 +0,0 @@
|
|||||||
// Code generated by go-swagger; DO NOT EDIT.
|
|
||||||
|
|
||||||
package models
|
|
||||||
|
|
||||||
// This file was generated by the swagger tool.
|
|
||||||
// Editing this file might prove futile when you re-run the swagger generate command
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/go-openapi/errors"
|
|
||||||
"github.com/go-openapi/strfmt"
|
|
||||||
"github.com/go-openapi/swag"
|
|
||||||
)
|
|
||||||
|
|
||||||
// V1OnboardRequest v1 onboard request
|
|
||||||
//
|
|
||||||
// swagger:model v1OnboardRequest
|
|
||||||
type V1OnboardRequest struct {
|
|
||||||
|
|
||||||
// boarding tx
|
|
||||||
BoardingTx string `json:"boardingTx,omitempty"`
|
|
||||||
|
|
||||||
// congestion tree
|
|
||||||
CongestionTree *V1Tree `json:"congestionTree,omitempty"`
|
|
||||||
|
|
||||||
// user pubkey
|
|
||||||
UserPubkey string `json:"userPubkey,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate validates this v1 onboard request
|
|
||||||
func (m *V1OnboardRequest) Validate(formats strfmt.Registry) error {
|
|
||||||
var res []error
|
|
||||||
|
|
||||||
if err := m.validateCongestionTree(formats); err != nil {
|
|
||||||
res = append(res, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(res) > 0 {
|
|
||||||
return errors.CompositeValidationError(res...)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *V1OnboardRequest) validateCongestionTree(formats strfmt.Registry) error {
|
|
||||||
if swag.IsZero(m.CongestionTree) { // not required
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.CongestionTree != nil {
|
|
||||||
if err := m.CongestionTree.Validate(formats); err != nil {
|
|
||||||
if ve, ok := err.(*errors.Validation); ok {
|
|
||||||
return ve.ValidateName("congestionTree")
|
|
||||||
} else if ce, ok := err.(*errors.CompositeError); ok {
|
|
||||||
return ce.ValidateName("congestionTree")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContextValidate validate this v1 onboard request based on the context it is used
|
|
||||||
func (m *V1OnboardRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
|
||||||
var res []error
|
|
||||||
|
|
||||||
if err := m.contextValidateCongestionTree(ctx, formats); err != nil {
|
|
||||||
res = append(res, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(res) > 0 {
|
|
||||||
return errors.CompositeValidationError(res...)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *V1OnboardRequest) contextValidateCongestionTree(ctx context.Context, formats strfmt.Registry) error {
|
|
||||||
|
|
||||||
if m.CongestionTree != nil {
|
|
||||||
|
|
||||||
if swag.IsZero(m.CongestionTree) { // not required
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.CongestionTree.ContextValidate(ctx, formats); err != nil {
|
|
||||||
if ve, ok := err.(*errors.Validation); ok {
|
|
||||||
return ve.ValidateName("congestionTree")
|
|
||||||
} else if ce, ok := err.(*errors.CompositeError); ok {
|
|
||||||
return ce.ValidateName("congestionTree")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalBinary interface implementation
|
|
||||||
func (m *V1OnboardRequest) MarshalBinary() ([]byte, error) {
|
|
||||||
if m == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return swag.WriteJSON(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalBinary interface implementation
|
|
||||||
func (m *V1OnboardRequest) UnmarshalBinary(b []byte) error {
|
|
||||||
var res V1OnboardRequest
|
|
||||||
if err := swag.ReadJSON(b, &res); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*m = res
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
// Code generated by go-swagger; DO NOT EDIT.
|
|
||||||
|
|
||||||
package models
|
|
||||||
|
|
||||||
// This file was generated by the swagger tool.
|
|
||||||
// Editing this file might prove futile when you re-run the swagger generate command
|
|
||||||
|
|
||||||
// V1OnboardResponse v1 onboard response
|
|
||||||
//
|
|
||||||
// swagger:model v1OnboardResponse
|
|
||||||
type V1OnboardResponse interface{}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
// Code generated by go-swagger; DO NOT EDIT.
|
|
||||||
|
|
||||||
package models
|
|
||||||
|
|
||||||
// This file was generated by the swagger tool.
|
|
||||||
// Editing this file might prove futile when you re-run the swagger generate command
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/go-openapi/strfmt"
|
|
||||||
"github.com/go-openapi/swag"
|
|
||||||
)
|
|
||||||
|
|
||||||
// V1TrustedOnboardingRequest v1 trusted onboarding request
|
|
||||||
//
|
|
||||||
// swagger:model v1TrustedOnboardingRequest
|
|
||||||
type V1TrustedOnboardingRequest struct {
|
|
||||||
|
|
||||||
// user pubkey
|
|
||||||
UserPubkey string `json:"userPubkey,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate validates this v1 trusted onboarding request
|
|
||||||
func (m *V1TrustedOnboardingRequest) Validate(formats strfmt.Registry) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContextValidate validates this v1 trusted onboarding request based on context it is used
|
|
||||||
func (m *V1TrustedOnboardingRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalBinary interface implementation
|
|
||||||
func (m *V1TrustedOnboardingRequest) MarshalBinary() ([]byte, error) {
|
|
||||||
if m == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return swag.WriteJSON(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalBinary interface implementation
|
|
||||||
func (m *V1TrustedOnboardingRequest) UnmarshalBinary(b []byte) error {
|
|
||||||
var res V1TrustedOnboardingRequest
|
|
||||||
if err := swag.ReadJSON(b, &res); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*m = res
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
// Code generated by go-swagger; DO NOT EDIT.
|
|
||||||
|
|
||||||
package models
|
|
||||||
|
|
||||||
// This file was generated by the swagger tool.
|
|
||||||
// Editing this file might prove futile when you re-run the swagger generate command
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/go-openapi/strfmt"
|
|
||||||
"github.com/go-openapi/swag"
|
|
||||||
)
|
|
||||||
|
|
||||||
// V1TrustedOnboardingResponse v1 trusted onboarding response
|
|
||||||
//
|
|
||||||
// swagger:model v1TrustedOnboardingResponse
|
|
||||||
type V1TrustedOnboardingResponse struct {
|
|
||||||
|
|
||||||
// address
|
|
||||||
Address string `json:"address,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate validates this v1 trusted onboarding response
|
|
||||||
func (m *V1TrustedOnboardingResponse) Validate(formats strfmt.Registry) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContextValidate validates this v1 trusted onboarding response based on context it is used
|
|
||||||
func (m *V1TrustedOnboardingResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalBinary interface implementation
|
|
||||||
func (m *V1TrustedOnboardingResponse) MarshalBinary() ([]byte, error) {
|
|
||||||
if m == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return swag.WriteJSON(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalBinary interface implementation
|
|
||||||
func (m *V1TrustedOnboardingResponse) UnmarshalBinary(b []byte) error {
|
|
||||||
var res V1TrustedOnboardingResponse
|
|
||||||
if err := swag.ReadJSON(b, &res); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*m = res
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
53
pkg/client-sdk/client/rest/service/models/v1_vtxo_input.go
Normal file
53
pkg/client-sdk/client/rest/service/models/v1_vtxo_input.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// Code generated by go-swagger; DO NOT EDIT.
|
||||||
|
|
||||||
|
package models
|
||||||
|
|
||||||
|
// This file was generated by the swagger tool.
|
||||||
|
// Editing this file might prove futile when you re-run the swagger generate command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/go-openapi/strfmt"
|
||||||
|
"github.com/go-openapi/swag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// V1VtxoInput v1 vtxo input
|
||||||
|
//
|
||||||
|
// swagger:model v1VtxoInput
|
||||||
|
type V1VtxoInput struct {
|
||||||
|
|
||||||
|
// txid
|
||||||
|
Txid string `json:"txid,omitempty"`
|
||||||
|
|
||||||
|
// vout
|
||||||
|
Vout int64 `json:"vout,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates this v1 vtxo input
|
||||||
|
func (m *V1VtxoInput) Validate(formats strfmt.Registry) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextValidate validates this v1 vtxo input based on context it is used
|
||||||
|
func (m *V1VtxoInput) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalBinary interface implementation
|
||||||
|
func (m *V1VtxoInput) MarshalBinary() ([]byte, error) {
|
||||||
|
if m == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return swag.WriteJSON(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalBinary interface implementation
|
||||||
|
func (m *V1VtxoInput) UnmarshalBinary(b []byte) error {
|
||||||
|
var res V1VtxoInput
|
||||||
|
if err := swag.ReadJSON(b, &res); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*m = res
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ark-network/ark/common"
|
"github.com/ark-network/ark/common"
|
||||||
|
"github.com/ark-network/ark/common/descriptor"
|
||||||
"github.com/ark-network/ark/common/tree"
|
"github.com/ark-network/ark/common/tree"
|
||||||
"github.com/ark-network/ark/pkg/client-sdk/client"
|
"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/explorer"
|
||||||
@@ -23,12 +24,7 @@ import (
|
|||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/vulpemventures/go-elements/address"
|
"github.com/vulpemventures/go-elements/address"
|
||||||
"github.com/vulpemventures/go-elements/elementsutil"
|
|
||||||
"github.com/vulpemventures/go-elements/network"
|
|
||||||
"github.com/vulpemventures/go-elements/payment"
|
|
||||||
"github.com/vulpemventures/go-elements/psetv2"
|
"github.com/vulpemventures/go-elements/psetv2"
|
||||||
"github.com/vulpemventures/go-elements/taproot"
|
|
||||||
"github.com/vulpemventures/go-elements/transaction"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type liquidReceiver struct {
|
type liquidReceiver struct {
|
||||||
@@ -139,92 +135,24 @@ func LoadCovenantClientWithWallet(
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantArkClient) Onboard(
|
|
||||||
ctx context.Context, amount uint64,
|
|
||||||
) (string, error) {
|
|
||||||
if amount <= 0 {
|
|
||||||
return "", fmt.Errorf("invalid amount to onboard %d", amount)
|
|
||||||
}
|
|
||||||
|
|
||||||
offchainAddr, _, err := a.wallet.NewAddress(ctx, false)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
net := utils.ToElementsNetwork(a.Network)
|
|
||||||
_, userPubkey, aspPubkey, _ := common.DecodeAddress(offchainAddr)
|
|
||||||
userPubkeyStr := hex.EncodeToString(userPubkey.SerializeCompressed())
|
|
||||||
congestionTreeLeaf := tree.Receiver{
|
|
||||||
Pubkey: userPubkeyStr,
|
|
||||||
Amount: amount,
|
|
||||||
}
|
|
||||||
|
|
||||||
treeFactoryFn, sharedOutputScript, sharedOutputAmount, err := tree.CraftCongestionTree(
|
|
||||||
net.AssetID,
|
|
||||||
aspPubkey,
|
|
||||||
[]tree.Receiver{congestionTreeLeaf},
|
|
||||||
a.MinRelayFee,
|
|
||||||
a.RoundLifetime,
|
|
||||||
a.UnilateralExitDelay,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
pay, err := payment.FromScript(sharedOutputScript, &net, nil)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
addr, err := pay.TaprootAddress()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
onchainReceiver := NewLiquidReceiver(addr, sharedOutputAmount)
|
|
||||||
|
|
||||||
pset, err := a.sendOnchain(ctx, []Receiver{onchainReceiver})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
ptx, _ := psetv2.NewPsetFromBase64(pset)
|
|
||||||
utx, _ := ptx.UnsignedTx()
|
|
||||||
txid := utx.TxHash().String()
|
|
||||||
|
|
||||||
congestionTree, err := treeFactoryFn(psetv2.InputArgs{
|
|
||||||
Txid: txid,
|
|
||||||
TxIndex: 0,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := a.client.Onboard(
|
|
||||||
ctx, pset, userPubkeyStr, congestionTree,
|
|
||||||
); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return txid, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *covenantArkClient) Balance(
|
func (a *covenantArkClient) Balance(
|
||||||
ctx context.Context, computeVtxoExpiration bool,
|
ctx context.Context, computeVtxoExpiration bool,
|
||||||
) (*Balance, error) {
|
) (*Balance, error) {
|
||||||
offchainAddrs, onchainAddrs, redeemAddrs, err := a.wallet.GetAddresses(ctx)
|
offchainAddrs, boardingAddrs, redeemAddrs, err := a.wallet.GetAddresses(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const nbWorkers = 3
|
||||||
wg := &sync.WaitGroup{}
|
wg := &sync.WaitGroup{}
|
||||||
wg.Add(3 * len(offchainAddrs))
|
wg.Add(nbWorkers * len(offchainAddrs))
|
||||||
|
|
||||||
chRes := make(chan balanceRes, 3)
|
chRes := make(chan balanceRes, nbWorkers*len(offchainAddrs))
|
||||||
for i := range offchainAddrs {
|
for i := range offchainAddrs {
|
||||||
offchainAddr := offchainAddrs[i]
|
offchainAddr := offchainAddrs[i]
|
||||||
onchainAddr := onchainAddrs[i]
|
boardingAddr := boardingAddrs[i]
|
||||||
redeemAddr := redeemAddrs[i]
|
redeemAddr := redeemAddrs[i]
|
||||||
|
|
||||||
go func(addr string) {
|
go func(addr string) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
balance, amountByExpiration, err := a.getOffchainBalance(
|
balance, amountByExpiration, err := a.getOffchainBalance(
|
||||||
@@ -241,17 +169,7 @@ func (a *covenantArkClient) Balance(
|
|||||||
}
|
}
|
||||||
}(offchainAddr)
|
}(offchainAddr)
|
||||||
|
|
||||||
go func(addr string) {
|
getDelayedBalance := func(addr string) {
|
||||||
defer wg.Done()
|
|
||||||
balance, err := a.explorer.GetBalance(addr)
|
|
||||||
if err != nil {
|
|
||||||
chRes <- balanceRes{err: err}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
chRes <- balanceRes{onchainSpendableBalance: balance}
|
|
||||||
}(onchainAddr)
|
|
||||||
|
|
||||||
go func(addr string) {
|
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
spendableBalance, lockedBalance, err := a.explorer.GetRedeemedVtxosBalance(
|
spendableBalance, lockedBalance, err := a.explorer.GetRedeemedVtxosBalance(
|
||||||
@@ -267,7 +185,10 @@ func (a *covenantArkClient) Balance(
|
|||||||
onchainLockedBalance: lockedBalance,
|
onchainLockedBalance: lockedBalance,
|
||||||
err: err,
|
err: err,
|
||||||
}
|
}
|
||||||
}(redeemAddr)
|
}
|
||||||
|
|
||||||
|
go getDelayedBalance(boardingAddr)
|
||||||
|
go getDelayedBalance(redeemAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@@ -317,7 +238,7 @@ func (a *covenantArkClient) Balance(
|
|||||||
}
|
}
|
||||||
|
|
||||||
count++
|
count++
|
||||||
if count == 3 {
|
if count == nbWorkers {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -519,7 +440,7 @@ func (a *covenantArkClient) CollaborativeRedeem(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
inputs := make([]client.VtxoKey, 0, len(selectedCoins))
|
inputs := make([]client.Input, 0, len(selectedCoins))
|
||||||
|
|
||||||
for _, coin := range selectedCoins {
|
for _, coin := range selectedCoins {
|
||||||
inputs = append(inputs, client.VtxoKey{
|
inputs = append(inputs, client.VtxoKey{
|
||||||
@@ -538,7 +459,7 @@ func (a *covenantArkClient) CollaborativeRedeem(
|
|||||||
}
|
}
|
||||||
|
|
||||||
poolTxID, err := a.handleRoundStream(
|
poolTxID, err := a.handleRoundStream(
|
||||||
ctx, paymentID, selectedCoins, receivers,
|
ctx, paymentID, selectedCoins, false, receivers,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -554,8 +475,85 @@ func (a *covenantArkClient) SendAsync(
|
|||||||
return "", fmt.Errorf("not implemented")
|
return "", fmt.Errorf("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantArkClient) ClaimAsync(ctx context.Context) (string, error) {
|
func (a *covenantArkClient) Claim(ctx context.Context) (string, error) {
|
||||||
return "", fmt.Errorf("not implemented")
|
myselfOffchain, _, err := a.wallet.NewAddress(ctx, false)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, mypubkey, _, err := common.DecodeAddress(myselfOffchain)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
boardingUtxos, err := a.getClaimableBoardingUtxos(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var pendingBalance uint64
|
||||||
|
for _, vtxo := range boardingUtxos {
|
||||||
|
pendingBalance += vtxo.Amount
|
||||||
|
}
|
||||||
|
if pendingBalance == 0 {
|
||||||
|
return "", fmt.Errorf("no funds to claim")
|
||||||
|
}
|
||||||
|
|
||||||
|
receiver := client.Output{
|
||||||
|
Address: myselfOffchain,
|
||||||
|
Amount: pendingBalance,
|
||||||
|
}
|
||||||
|
|
||||||
|
desc := strings.ReplaceAll(a.BoardingDescriptorTemplate, "USER", hex.EncodeToString(schnorr.SerializePubKey(mypubkey)))
|
||||||
|
|
||||||
|
return a.selfTransferAllPendingPayments(ctx, boardingUtxos, receiver, desc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *covenantArkClient) getClaimableBoardingUtxos(ctx context.Context) ([]explorer.Utxo, error) {
|
||||||
|
offchainAddrs, boardingAddrs, _, err := a.wallet.GetAddresses(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
claimable := make([]explorer.Utxo, 0)
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
_, myPubkey, _, err := common.DecodeAddress(offchainAddrs[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(myPubkey))
|
||||||
|
descriptorStr := strings.ReplaceAll(
|
||||||
|
a.BoardingDescriptorTemplate, "USER", myPubkeyStr,
|
||||||
|
)
|
||||||
|
desc, err := descriptor.ParseTaprootDescriptor(descriptorStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range boardingAddrs {
|
||||||
|
boardingUtxos, err := a.explorer.GetUtxos(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, utxo := range boardingUtxos {
|
||||||
|
u := utxo.ToUtxo(boardingTimeout)
|
||||||
|
|
||||||
|
if u.SpendableAt.Before(now) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
claimable = append(claimable, u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return claimable, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantArkClient) sendOnchain(
|
func (a *covenantArkClient) sendOnchain(
|
||||||
@@ -599,14 +597,14 @@ func (a *covenantArkClient) sendOnchain(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
utxos, delayedUtxos, change, err := a.coinSelectOnchain(
|
utxos, change, err := a.coinSelectOnchain(
|
||||||
ctx, targetAmount, nil,
|
ctx, targetAmount, nil,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := a.addInputs(ctx, updater, utxos, delayedUtxos, net); err != nil {
|
if err := a.addInputs(ctx, updater, utxos); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -649,14 +647,14 @@ func (a *covenantArkClient) sendOnchain(
|
|||||||
updater.Pset.Outputs = updater.Pset.Outputs[:len(updater.Pset.Outputs)-1]
|
updater.Pset.Outputs = updater.Pset.Outputs[:len(updater.Pset.Outputs)-1]
|
||||||
}
|
}
|
||||||
// reselect the difference
|
// reselect the difference
|
||||||
selected, delayedSelected, newChange, err := a.coinSelectOnchain(
|
selected, newChange, err := a.coinSelectOnchain(
|
||||||
ctx, feeAmount-change, append(utxos, delayedUtxos...),
|
ctx, feeAmount-change, utxos,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := a.addInputs(ctx, updater, selected, delayedSelected, net); err != nil {
|
if err := a.addInputs(ctx, updater, selected); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -787,7 +785,7 @@ func (a *covenantArkClient) sendOffchain(
|
|||||||
receiversOutput = append(receiversOutput, changeReceiver)
|
receiversOutput = append(receiversOutput, changeReceiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
inputs := make([]client.VtxoKey, 0, len(selectedCoins))
|
inputs := make([]client.Input, 0, len(selectedCoins))
|
||||||
for _, coin := range selectedCoins {
|
for _, coin := range selectedCoins {
|
||||||
inputs = append(inputs, client.VtxoKey{
|
inputs = append(inputs, client.VtxoKey{
|
||||||
Txid: coin.Txid,
|
Txid: coin.Txid,
|
||||||
@@ -811,7 +809,7 @@ func (a *covenantArkClient) sendOffchain(
|
|||||||
log.Infof("payment registered with id: %s", paymentID)
|
log.Infof("payment registered with id: %s", paymentID)
|
||||||
|
|
||||||
poolTxID, err := a.handleRoundStream(
|
poolTxID, err := a.handleRoundStream(
|
||||||
ctx, paymentID, selectedCoins, receiversOutput,
|
ctx, paymentID, selectedCoins, false, receiversOutput,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -821,126 +819,60 @@ func (a *covenantArkClient) sendOffchain(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantArkClient) addInputs(
|
func (a *covenantArkClient) addInputs(
|
||||||
ctx context.Context, updater *psetv2.Updater, utxos, delayedUtxos []explorer.Utxo, net network.Network,
|
ctx context.Context,
|
||||||
|
updater *psetv2.Updater,
|
||||||
|
utxos []explorer.Utxo,
|
||||||
) error {
|
) error {
|
||||||
offchainAddr, onchainAddr, err := a.wallet.NewAddress(ctx, false)
|
// TODO works only with single-key wallet
|
||||||
|
offchain, _, err := a.wallet.NewAddress(ctx, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, userPubkey, aspPubkey, _ := common.DecodeAddress(offchainAddr)
|
_, userPubkey, aspPubkey, err := common.DecodeAddress(offchain)
|
||||||
|
|
||||||
changeScript, err := address.ToOutputScript(onchainAddr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, utxo := range utxos {
|
for _, utxo := range utxos {
|
||||||
|
sequence, err := utxo.Sequence()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err := updater.AddInputs([]psetv2.InputArgs{
|
if err := updater.AddInputs([]psetv2.InputArgs{
|
||||||
{
|
{
|
||||||
Txid: utxo.Txid,
|
Txid: utxo.Txid,
|
||||||
TxIndex: utxo.Vout,
|
TxIndex: utxo.Vout,
|
||||||
|
Sequence: sequence,
|
||||||
},
|
},
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
assetID, err := elementsutil.AssetHashToBytes(utxo.Asset)
|
_, leafProof, _, _, err := tree.ComputeVtxoTaprootScript(
|
||||||
if err != nil {
|
userPubkey, aspPubkey, utxo.Delay, utils.ToElementsNetwork(a.Network),
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
value, err := elementsutil.ValueToBytes(utxo.Amount)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
witnessUtxo := transaction.TxOutput{
|
|
||||||
Asset: assetID,
|
|
||||||
Value: value,
|
|
||||||
Script: changeScript,
|
|
||||||
Nonce: []byte{0x00},
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := updater.AddInWitnessUtxo(
|
|
||||||
len(updater.Pset.Inputs)-1, &witnessUtxo,
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(delayedUtxos) > 0 {
|
|
||||||
_, leafProof, script, _, err := tree.ComputeVtxoTaprootScript(
|
|
||||||
userPubkey, aspPubkey, uint(a.UnilateralExitDelay), net,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, utxo := range delayedUtxos {
|
inputIndex := len(updater.Pset.Inputs) - 1
|
||||||
if err := a.addVtxoInput(
|
|
||||||
updater,
|
|
||||||
psetv2.InputArgs{
|
|
||||||
Txid: utxo.Txid,
|
|
||||||
TxIndex: utxo.Vout,
|
|
||||||
},
|
|
||||||
uint(a.UnilateralExitDelay),
|
|
||||||
leafProof,
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
assetID, err := elementsutil.AssetHashToBytes(utxo.Asset)
|
if err := updater.AddInTapLeafScript(inputIndex, psetv2.NewTapLeafScript(*leafProof, tree.UnspendableKey())); err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
value, err := elementsutil.ValueToBytes(utxo.Amount)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
witnessUtxo := transaction.NewTxOutput(assetID, value, script)
|
|
||||||
|
|
||||||
if err := updater.AddInWitnessUtxo(
|
|
||||||
len(updater.Pset.Inputs)-1, witnessUtxo,
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantArkClient) addVtxoInput(
|
|
||||||
updater *psetv2.Updater, inputArgs psetv2.InputArgs, exitDelay uint,
|
|
||||||
tapLeafProof *taproot.TapscriptElementsProof,
|
|
||||||
) error {
|
|
||||||
sequence, err := common.BIP68EncodeAsNumber(exitDelay)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
nextInputIndex := len(updater.Pset.Inputs)
|
|
||||||
if err := updater.AddInputs([]psetv2.InputArgs{inputArgs}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
updater.Pset.Inputs[nextInputIndex].Sequence = sequence
|
|
||||||
|
|
||||||
return updater.AddInTapLeafScript(
|
|
||||||
nextInputIndex,
|
|
||||||
psetv2.NewTapLeafScript(
|
|
||||||
*tapLeafProof,
|
|
||||||
tree.UnspendableKey(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *covenantArkClient) handleRoundStream(
|
func (a *covenantArkClient) handleRoundStream(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
paymentID string, vtxosToSign []client.Vtxo, receivers []client.Output,
|
paymentID string,
|
||||||
|
vtxosToSign []client.Vtxo,
|
||||||
|
mustSignRoundTx bool,
|
||||||
|
receivers []client.Output,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
eventsCh, err := a.client.GetEventStream(ctx, paymentID)
|
eventsCh, err := a.client.GetEventStream(ctx, paymentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -972,20 +904,20 @@ func (a *covenantArkClient) handleRoundStream(
|
|||||||
pingStop()
|
pingStop()
|
||||||
log.Info("a round finalization started")
|
log.Info("a round finalization started")
|
||||||
|
|
||||||
signedForfeitTxs, err := a.handleRoundFinalization(
|
signedForfeitTxs, signedRoundTx, err := a.handleRoundFinalization(
|
||||||
ctx, event.(client.RoundFinalizationEvent), vtxosToSign, receivers,
|
ctx, event.(client.RoundFinalizationEvent), vtxosToSign, mustSignRoundTx, receivers,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(signedForfeitTxs) <= 0 {
|
if len(signedForfeitTxs) <= 0 && len(vtxosToSign) > 0 {
|
||||||
log.Info("no forfeit txs to sign, waiting for the next round")
|
log.Info("no forfeit txs to sign, waiting for the next round")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("finalizing payment... ")
|
log.Info("finalizing payment... ")
|
||||||
if err := a.client.FinalizePayment(ctx, signedForfeitTxs); err != nil {
|
if err := a.client.FinalizePayment(ctx, signedForfeitTxs, signedRoundTx); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -998,15 +930,29 @@ func (a *covenantArkClient) handleRoundStream(
|
|||||||
|
|
||||||
func (a *covenantArkClient) handleRoundFinalization(
|
func (a *covenantArkClient) handleRoundFinalization(
|
||||||
ctx context.Context, event client.RoundFinalizationEvent,
|
ctx context.Context, event client.RoundFinalizationEvent,
|
||||||
vtxos []client.Vtxo, receivers []client.Output,
|
vtxos []client.Vtxo, mustSignRoundTx bool, receivers []client.Output,
|
||||||
) ([]string, error) {
|
) (signedForfeits []string, signedRoundTx string, err error) {
|
||||||
if err := a.validateCongestionTree(event, receivers); err != nil {
|
if err = a.validateCongestionTree(event, receivers); err != nil {
|
||||||
return nil, fmt.Errorf("failed to verify congestion tree: %s", err)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return a.loopAndSign(
|
if len(vtxos) > 0 {
|
||||||
ctx, event.ForfeitTxs, vtxos, event.Connectors,
|
signedForfeits, err = a.loopAndSign(
|
||||||
)
|
ctx, event.ForfeitTxs, vtxos, event.Connectors,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if mustSignRoundTx {
|
||||||
|
signedRoundTx, err = a.wallet.SignTransaction(ctx, a.explorer, event.Tx)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantArkClient) validateCongestionTree(
|
func (a *covenantArkClient) validateCongestionTree(
|
||||||
@@ -1197,23 +1143,49 @@ func (a *covenantArkClient) signForfeitTx(
|
|||||||
|
|
||||||
func (a *covenantArkClient) coinSelectOnchain(
|
func (a *covenantArkClient) coinSelectOnchain(
|
||||||
ctx context.Context, targetAmount uint64, exclude []explorer.Utxo,
|
ctx context.Context, targetAmount uint64, exclude []explorer.Utxo,
|
||||||
) ([]explorer.Utxo, []explorer.Utxo, uint64, error) {
|
) ([]explorer.Utxo, uint64, error) {
|
||||||
offchainAddrs, onchainAddrs, _, err := a.wallet.GetAddresses(ctx)
|
offchainAddrs, boardingAddrs, redemptionAddrs, err := a.wallet.GetAddresses(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
net := utils.ToElementsNetwork(a.Network)
|
|
||||||
|
_, myPubkey, _, err := common.DecodeAddress(offchainAddrs[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(myPubkey))
|
||||||
|
descriptorStr := strings.ReplaceAll(
|
||||||
|
a.BoardingDescriptorTemplate, "USER", myPubkeyStr,
|
||||||
|
)
|
||||||
|
desc, err := descriptor.ParseTaprootDescriptor(descriptorStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
fetchedUtxos := make([]explorer.Utxo, 0)
|
fetchedUtxos := make([]explorer.Utxo, 0)
|
||||||
for _, onchainAddr := range onchainAddrs {
|
for _, addr := range boardingAddrs {
|
||||||
utxos, err := a.explorer.GetUtxos(onchainAddr)
|
utxos, err := a.explorer.GetUtxos(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, err
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, utxo := range utxos {
|
||||||
|
u := utxo.ToUtxo(boardingTimeout)
|
||||||
|
if u.SpendableAt.Before(now) {
|
||||||
|
fetchedUtxos = append(fetchedUtxos, u)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fetchedUtxos = append(fetchedUtxos, utxos...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
utxos := make([]explorer.Utxo, 0)
|
selected := make([]explorer.Utxo, 0)
|
||||||
selectedAmount := uint64(0)
|
selectedAmount := uint64(0)
|
||||||
for _, utxo := range fetchedUtxos {
|
for _, utxo := range fetchedUtxos {
|
||||||
if selectedAmount >= targetAmount {
|
if selectedAmount >= targetAmount {
|
||||||
@@ -1226,61 +1198,51 @@ func (a *covenantArkClient) coinSelectOnchain(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
utxos = append(utxos, utxo)
|
selected = append(selected, utxo)
|
||||||
selectedAmount += utxo.Amount
|
selectedAmount += utxo.Amount
|
||||||
}
|
}
|
||||||
|
|
||||||
if selectedAmount >= targetAmount {
|
if selectedAmount >= targetAmount {
|
||||||
return utxos, nil, selectedAmount - targetAmount, nil
|
return selected, selectedAmount - targetAmount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchedUtxos = make([]explorer.Utxo, 0)
|
fetchedUtxos = make([]explorer.Utxo, 0)
|
||||||
for _, offchainAddr := range offchainAddrs {
|
for _, addr := range redemptionAddrs {
|
||||||
_, userPubkey, aspPubkey, _ := common.DecodeAddress(offchainAddr)
|
utxos, err := a.explorer.GetUtxos(addr)
|
||||||
_, _, _, addr, err := tree.ComputeVtxoTaprootScript(
|
|
||||||
userPubkey, aspPubkey, uint(a.UnilateralExitDelay), net,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
utxos, err = a.explorer.GetUtxos(addr)
|
for _, utxo := range utxos {
|
||||||
if err != nil {
|
u := utxo.ToUtxo(uint(a.UnilateralExitDelay))
|
||||||
return nil, nil, 0, err
|
if u.SpendableAt.Before(now) {
|
||||||
|
fetchedUtxos = append(fetchedUtxos, u)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fetchedUtxos = append(fetchedUtxos, utxos...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delayedUtxos := make([]explorer.Utxo, 0)
|
|
||||||
for _, utxo := range fetchedUtxos {
|
for _, utxo := range fetchedUtxos {
|
||||||
if selectedAmount >= targetAmount {
|
if selectedAmount >= targetAmount {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
availableAt := time.Unix(utxo.Status.Blocktime, 0).Add(
|
|
||||||
time.Duration(a.UnilateralExitDelay) * time.Second,
|
|
||||||
)
|
|
||||||
if availableAt.After(time.Now()) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, excluded := range exclude {
|
for _, excluded := range exclude {
|
||||||
if utxo.Txid == excluded.Txid && utxo.Vout == excluded.Vout {
|
if utxo.Txid == excluded.Txid && utxo.Vout == excluded.Vout {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delayedUtxos = append(delayedUtxos, utxo)
|
selected = append(selected, utxo)
|
||||||
selectedAmount += utxo.Amount
|
selectedAmount += utxo.Amount
|
||||||
}
|
}
|
||||||
|
|
||||||
if selectedAmount < targetAmount {
|
if selectedAmount < targetAmount {
|
||||||
return nil, nil, 0, fmt.Errorf(
|
return nil, 0, fmt.Errorf(
|
||||||
"not enough funds to cover amount %d", targetAmount,
|
"not enough funds to cover amount %d", targetAmount,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return utxos, delayedUtxos, selectedAmount - targetAmount, nil
|
return selected, selectedAmount - targetAmount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantArkClient) getRedeemBranches(
|
func (a *covenantArkClient) getRedeemBranches(
|
||||||
@@ -1373,3 +1335,39 @@ func (a *covenantArkClient) getVtxos(
|
|||||||
|
|
||||||
return vtxos, nil
|
return vtxos, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *covenantArkClient) selfTransferAllPendingPayments(
|
||||||
|
ctx context.Context, boardingUtxo []explorer.Utxo, myself client.Output, boardingDescriptor string,
|
||||||
|
) (string, error) {
|
||||||
|
inputs := make([]client.Input, 0, len(boardingUtxo))
|
||||||
|
|
||||||
|
for _, utxo := range boardingUtxo {
|
||||||
|
inputs = append(inputs, client.BoardingInput{
|
||||||
|
VtxoKey: client.VtxoKey{
|
||||||
|
Txid: utxo.Txid,
|
||||||
|
VOut: utxo.Vout,
|
||||||
|
},
|
||||||
|
Descriptor: boardingDescriptor,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
outputs := []client.Output{myself}
|
||||||
|
|
||||||
|
paymentID, err := a.client.RegisterPayment(ctx, inputs, "") // ephemeralPublicKey is not required for covenant
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.client.ClaimPayment(ctx, paymentID, outputs); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
roundTxid, err := a.handleRoundStream(
|
||||||
|
ctx, paymentID, make([]client.Vtxo, 0), len(boardingUtxo) > 0, outputs,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return roundTxid, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/ark-network/ark/common"
|
"github.com/ark-network/ark/common"
|
||||||
"github.com/ark-network/ark/common/bitcointree"
|
"github.com/ark-network/ark/common/bitcointree"
|
||||||
|
"github.com/ark-network/ark/common/descriptor"
|
||||||
"github.com/ark-network/ark/common/tree"
|
"github.com/ark-network/ark/common/tree"
|
||||||
"github.com/ark-network/ark/pkg/client-sdk/client"
|
"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/explorer"
|
||||||
@@ -22,7 +23,6 @@ import (
|
|||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"github.com/btcsuite/btcd/btcutil"
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
"github.com/btcsuite/btcd/btcutil/psbt"
|
"github.com/btcsuite/btcd/btcutil/psbt"
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
@@ -139,176 +139,24 @@ func LoadCovenantlessClientWithWallet(
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantlessArkClient) Onboard(
|
|
||||||
ctx context.Context, amount uint64,
|
|
||||||
) (string, error) {
|
|
||||||
if amount <= 0 {
|
|
||||||
return "", fmt.Errorf("invalid amount to onboard %d", amount)
|
|
||||||
}
|
|
||||||
|
|
||||||
offchainAddr, _, err := a.wallet.NewAddress(ctx, false)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, userPubkey, aspPubkey, _ := common.DecodeAddress(offchainAddr)
|
|
||||||
userPubkeyStr := hex.EncodeToString(userPubkey.SerializeCompressed())
|
|
||||||
|
|
||||||
congestionTreeLeaf := bitcointree.Receiver{
|
|
||||||
Pubkey: userPubkeyStr,
|
|
||||||
Amount: amount,
|
|
||||||
}
|
|
||||||
|
|
||||||
leaves := []bitcointree.Receiver{congestionTreeLeaf}
|
|
||||||
|
|
||||||
ephemeralKey, err := secp256k1.GeneratePrivateKey()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
cosigners := []*secp256k1.PublicKey{ephemeralKey.PubKey()} // TODO asp as cosigner
|
|
||||||
|
|
||||||
sharedOutputScript, sharedOutputAmount, err := bitcointree.CraftSharedOutput(
|
|
||||||
cosigners,
|
|
||||||
aspPubkey,
|
|
||||||
leaves,
|
|
||||||
a.MinRelayFee,
|
|
||||||
a.RoundLifetime,
|
|
||||||
a.UnilateralExitDelay,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
netParams := utils.ToBitcoinNetwork(a.Network)
|
|
||||||
|
|
||||||
address, err := btcutil.NewAddressTaproot(sharedOutputScript[2:], &netParams)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
onchainReceiver := NewBitcoinReceiver(
|
|
||||||
address.EncodeAddress(), uint64(sharedOutputAmount),
|
|
||||||
)
|
|
||||||
|
|
||||||
partialTx, err := a.sendOnchain(ctx, []Receiver{onchainReceiver})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
ptx, err := psbt.NewFromRawBytes(strings.NewReader(partialTx), true)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
txid := ptx.UnsignedTx.TxHash().String()
|
|
||||||
|
|
||||||
congestionTree, err := bitcointree.CraftCongestionTree(
|
|
||||||
&wire.OutPoint{
|
|
||||||
Hash: ptx.UnsignedTx.TxHash(),
|
|
||||||
Index: 0,
|
|
||||||
},
|
|
||||||
cosigners,
|
|
||||||
aspPubkey,
|
|
||||||
leaves,
|
|
||||||
a.MinRelayFee,
|
|
||||||
a.RoundLifetime,
|
|
||||||
a.UnilateralExitDelay,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
sweepClosure := bitcointree.CSVSigClosure{
|
|
||||||
Pubkey: aspPubkey,
|
|
||||||
Seconds: uint(a.RoundLifetime),
|
|
||||||
}
|
|
||||||
|
|
||||||
sweepTapLeaf, err := sweepClosure.Leaf()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
sweepTapTree := txscript.AssembleTaprootScriptTree(*sweepTapLeaf)
|
|
||||||
root := sweepTapTree.RootNode.TapHash()
|
|
||||||
|
|
||||||
signer := bitcointree.NewTreeSignerSession(
|
|
||||||
ephemeralKey,
|
|
||||||
congestionTree,
|
|
||||||
int64(a.MinRelayFee),
|
|
||||||
root.CloneBytes(),
|
|
||||||
)
|
|
||||||
|
|
||||||
nonces, err := signer.GetNonces() // TODO send nonces to ASP
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
coordinator, err := bitcointree.NewTreeCoordinatorSession(
|
|
||||||
congestionTree,
|
|
||||||
int64(a.MinRelayFee),
|
|
||||||
root.CloneBytes(),
|
|
||||||
cosigners,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := coordinator.AddNonce(ephemeralKey.PubKey(), nonces); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
aggregatedNonces, err := coordinator.AggregateNonces()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := signer.SetKeys(cosigners); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := signer.SetAggregatedNonces(aggregatedNonces); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
sigs, err := signer.Sign()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := coordinator.AddSig(ephemeralKey.PubKey(), sigs); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
signedTree, err := coordinator.SignTree()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := a.client.Onboard(
|
|
||||||
ctx, partialTx, userPubkeyStr, signedTree,
|
|
||||||
); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return txid, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *covenantlessArkClient) Balance(
|
func (a *covenantlessArkClient) Balance(
|
||||||
ctx context.Context, computeVtxoExpiration bool,
|
ctx context.Context, computeVtxoExpiration bool,
|
||||||
) (*Balance, error) {
|
) (*Balance, error) {
|
||||||
offchainAddrs, onchainAddrs, redeemAddrs, err := a.wallet.GetAddresses(ctx)
|
offchainAddrs, boardingAddrs, redeemAddrs, err := a.wallet.GetAddresses(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const nbWorkers = 3
|
||||||
wg := &sync.WaitGroup{}
|
wg := &sync.WaitGroup{}
|
||||||
wg.Add(3 * len(offchainAddrs))
|
wg.Add(nbWorkers * len(offchainAddrs))
|
||||||
|
|
||||||
chRes := make(chan balanceRes, 3)
|
chRes := make(chan balanceRes, nbWorkers*len(offchainAddrs))
|
||||||
for i := range offchainAddrs {
|
for i := range offchainAddrs {
|
||||||
offchainAddr := offchainAddrs[i]
|
offchainAddr := offchainAddrs[i]
|
||||||
onchainAddr := onchainAddrs[i]
|
boardingAddr := boardingAddrs[i]
|
||||||
redeemAddr := redeemAddrs[i]
|
redeemAddr := redeemAddrs[i]
|
||||||
|
|
||||||
go func(addr string) {
|
go func(addr string) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
balance, amountByExpiration, err := a.getOffchainBalance(
|
balance, amountByExpiration, err := a.getOffchainBalance(
|
||||||
@@ -325,17 +173,7 @@ func (a *covenantlessArkClient) Balance(
|
|||||||
}
|
}
|
||||||
}(offchainAddr)
|
}(offchainAddr)
|
||||||
|
|
||||||
go func(addr string) {
|
getDelayedBalance := func(addr string) {
|
||||||
defer wg.Done()
|
|
||||||
balance, err := a.explorer.GetBalance(addr)
|
|
||||||
if err != nil {
|
|
||||||
chRes <- balanceRes{err: err}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
chRes <- balanceRes{onchainSpendableBalance: balance}
|
|
||||||
}(onchainAddr)
|
|
||||||
|
|
||||||
go func(addr string) {
|
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
spendableBalance, lockedBalance, err := a.explorer.GetRedeemedVtxosBalance(
|
spendableBalance, lockedBalance, err := a.explorer.GetRedeemedVtxosBalance(
|
||||||
@@ -351,7 +189,10 @@ func (a *covenantlessArkClient) Balance(
|
|||||||
onchainLockedBalance: lockedBalance,
|
onchainLockedBalance: lockedBalance,
|
||||||
err: err,
|
err: err,
|
||||||
}
|
}
|
||||||
}(redeemAddr)
|
}
|
||||||
|
|
||||||
|
go getDelayedBalance(boardingAddr)
|
||||||
|
go getDelayedBalance(redeemAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@@ -401,7 +242,7 @@ func (a *covenantlessArkClient) Balance(
|
|||||||
}
|
}
|
||||||
|
|
||||||
count++
|
count++
|
||||||
if count == 3 {
|
if count == nbWorkers {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -603,7 +444,7 @@ func (a *covenantlessArkClient) CollaborativeRedeem(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
inputs := make([]client.VtxoKey, 0, len(selectedCoins))
|
inputs := make([]client.Input, 0, len(selectedCoins))
|
||||||
|
|
||||||
for _, coin := range selectedCoins {
|
for _, coin := range selectedCoins {
|
||||||
inputs = append(inputs, client.VtxoKey{
|
inputs = append(inputs, client.VtxoKey{
|
||||||
@@ -631,7 +472,7 @@ func (a *covenantlessArkClient) CollaborativeRedeem(
|
|||||||
}
|
}
|
||||||
|
|
||||||
poolTxID, err := a.handleRoundStream(
|
poolTxID, err := a.handleRoundStream(
|
||||||
ctx, paymentID, selectedCoins, receivers, roundEphemeralKey,
|
ctx, paymentID, selectedCoins, false, receivers, roundEphemeralKey,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -751,9 +592,7 @@ func (a *covenantlessArkClient) SendAsync(
|
|||||||
return signedRedeemTx, nil
|
return signedRedeemTx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantlessArkClient) ClaimAsync(
|
func (a *covenantlessArkClient) Claim(ctx context.Context) (string, error) {
|
||||||
ctx context.Context,
|
|
||||||
) (string, error) {
|
|
||||||
myselfOffchain, _, err := a.wallet.NewAddress(ctx, false)
|
myselfOffchain, _, err := a.wallet.NewAddress(ctx, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -764,10 +603,23 @@ func (a *covenantlessArkClient) ClaimAsync(
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, mypubkey, _, err := common.DecodeAddress(myselfOffchain)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
boardingUtxos, err := a.getClaimableBoardingUtxos(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
var pendingBalance uint64
|
var pendingBalance uint64
|
||||||
for _, vtxo := range pendingVtxos {
|
for _, vtxo := range pendingVtxos {
|
||||||
pendingBalance += vtxo.Amount
|
pendingBalance += vtxo.Amount
|
||||||
}
|
}
|
||||||
|
for _, vtxo := range boardingUtxos {
|
||||||
|
pendingBalance += vtxo.Amount
|
||||||
|
}
|
||||||
if pendingBalance == 0 {
|
if pendingBalance == 0 {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
@@ -776,7 +628,9 @@ func (a *covenantlessArkClient) ClaimAsync(
|
|||||||
Address: myselfOffchain,
|
Address: myselfOffchain,
|
||||||
Amount: pendingBalance,
|
Amount: pendingBalance,
|
||||||
}
|
}
|
||||||
return a.selfTransferAllPendingPayments(ctx, pendingVtxos, receiver)
|
|
||||||
|
desc := strings.ReplaceAll(a.BoardingDescriptorTemplate, "USER", hex.EncodeToString(schnorr.SerializePubKey(mypubkey)))
|
||||||
|
return a.selfTransferAllPendingPayments(ctx, pendingVtxos, boardingUtxos, receiver, desc)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantlessArkClient) sendOnchain(
|
func (a *covenantlessArkClient) sendOnchain(
|
||||||
@@ -822,14 +676,14 @@ func (a *covenantlessArkClient) sendOnchain(
|
|||||||
updater.Upsbt.Outputs = append(updater.Upsbt.Outputs, psbt.POutput{})
|
updater.Upsbt.Outputs = append(updater.Upsbt.Outputs, psbt.POutput{})
|
||||||
}
|
}
|
||||||
|
|
||||||
utxos, delayedUtxos, change, err := a.coinSelectOnchain(
|
utxos, change, err := a.coinSelectOnchain(
|
||||||
ctx, targetAmount, nil,
|
ctx, targetAmount, nil,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := a.addInputs(ctx, updater, utxos, delayedUtxos, &netParams); err != nil {
|
if err := a.addInputs(ctx, updater, utxos); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -869,14 +723,14 @@ func (a *covenantlessArkClient) sendOnchain(
|
|||||||
updater.Upsbt.UnsignedTx.TxOut = updater.Upsbt.UnsignedTx.TxOut[:len(updater.Upsbt.UnsignedTx.TxOut)-1]
|
updater.Upsbt.UnsignedTx.TxOut = updater.Upsbt.UnsignedTx.TxOut[:len(updater.Upsbt.UnsignedTx.TxOut)-1]
|
||||||
}
|
}
|
||||||
// reselect the difference
|
// reselect the difference
|
||||||
selected, delayedSelected, newChange, err := a.coinSelectOnchain(
|
selected, newChange, err := a.coinSelectOnchain(
|
||||||
ctx, feeAmount-change, append(utxos, delayedUtxos...),
|
ctx, feeAmount-change, utxos,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := a.addInputs(ctx, updater, selected, delayedSelected, &netParams); err != nil {
|
if err := a.addInputs(ctx, updater, selected); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -995,7 +849,7 @@ func (a *covenantlessArkClient) sendOffchain(
|
|||||||
receiversOutput = append(receiversOutput, changeReceiver)
|
receiversOutput = append(receiversOutput, changeReceiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
inputs := make([]client.VtxoKey, 0, len(selectedCoins))
|
inputs := make([]client.Input, 0, len(selectedCoins))
|
||||||
for _, coin := range selectedCoins {
|
for _, coin := range selectedCoins {
|
||||||
inputs = append(inputs, client.VtxoKey{
|
inputs = append(inputs, client.VtxoKey{
|
||||||
Txid: coin.Txid,
|
Txid: coin.Txid,
|
||||||
@@ -1024,7 +878,7 @@ func (a *covenantlessArkClient) sendOffchain(
|
|||||||
log.Infof("payment registered with id: %s", paymentID)
|
log.Infof("payment registered with id: %s", paymentID)
|
||||||
|
|
||||||
poolTxID, err := a.handleRoundStream(
|
poolTxID, err := a.handleRoundStream(
|
||||||
ctx, paymentID, selectedCoins, receiversOutput, roundEphemeralKey,
|
ctx, paymentID, selectedCoins, false, receiversOutput, roundEphemeralKey,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -1034,16 +888,17 @@ func (a *covenantlessArkClient) sendOffchain(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantlessArkClient) addInputs(
|
func (a *covenantlessArkClient) addInputs(
|
||||||
ctx context.Context, updater *psbt.Updater,
|
ctx context.Context,
|
||||||
utxos, delayedUtxos []explorer.Utxo, net *chaincfg.Params,
|
updater *psbt.Updater,
|
||||||
|
utxos []explorer.Utxo,
|
||||||
) error {
|
) error {
|
||||||
offchainAddr, onchainAddr, err := a.wallet.NewAddress(ctx, false)
|
// TODO works only with single-key wallet
|
||||||
|
offchain, _, err := a.wallet.NewAddress(ctx, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
addr, _ := btcutil.DecodeAddress(onchainAddr, net)
|
|
||||||
|
|
||||||
changeScript, err := txscript.PayToAddrScript(addr)
|
_, userPubkey, aspPubkey, err := common.DecodeAddress(offchain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1054,107 +909,41 @@ func (a *covenantlessArkClient) addInputs(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sequence, err := utxo.Sequence()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
updater.Upsbt.UnsignedTx.AddTxIn(&wire.TxIn{
|
updater.Upsbt.UnsignedTx.AddTxIn(&wire.TxIn{
|
||||||
PreviousOutPoint: wire.OutPoint{
|
PreviousOutPoint: wire.OutPoint{
|
||||||
Hash: *previousHash,
|
Hash: *previousHash,
|
||||||
Index: utxo.Vout,
|
Index: utxo.Vout,
|
||||||
},
|
},
|
||||||
|
Sequence: sequence,
|
||||||
})
|
})
|
||||||
|
|
||||||
updater.Upsbt.Inputs = append(updater.Upsbt.Inputs, psbt.PInput{})
|
_, leafProof, err := bitcointree.ComputeVtxoTaprootScript(
|
||||||
|
userPubkey, aspPubkey, utxo.Delay,
|
||||||
if err := updater.AddInWitnessUtxo(
|
|
||||||
&wire.TxOut{
|
|
||||||
Value: int64(utxo.Amount),
|
|
||||||
PkScript: changeScript,
|
|
||||||
},
|
|
||||||
len(updater.Upsbt.UnsignedTx.TxIn)-1,
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(delayedUtxos) > 0 {
|
|
||||||
_, userPubkey, aspPubkey, _ := common.DecodeAddress(offchainAddr)
|
|
||||||
|
|
||||||
vtxoTapKey, leafProof, err := bitcointree.ComputeVtxoTaprootScript(
|
|
||||||
userPubkey, aspPubkey, uint(a.UnilateralExitDelay),
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
p2tr, err := btcutil.NewAddressTaproot(schnorr.SerializePubKey(vtxoTapKey), net)
|
controlBlock := leafProof.ToControlBlock(bitcointree.UnspendableKey())
|
||||||
|
controlBlockBytes, err := controlBlock.ToBytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
script, err := txscript.PayToAddrScript(p2tr)
|
updater.Upsbt.Inputs = append(updater.Upsbt.Inputs, psbt.PInput{
|
||||||
if err != nil {
|
TaprootLeafScript: []*psbt.TaprootTapLeafScript{
|
||||||
return err
|
{
|
||||||
}
|
ControlBlock: controlBlockBytes,
|
||||||
|
Script: leafProof.Script,
|
||||||
for _, utxo := range delayedUtxos {
|
LeafVersion: leafProof.LeafVersion,
|
||||||
previousHash, err := chainhash.NewHashFromStr(utxo.Txid)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := a.addVtxoInput(
|
|
||||||
updater,
|
|
||||||
&wire.OutPoint{
|
|
||||||
Hash: *previousHash,
|
|
||||||
Index: utxo.Vout,
|
|
||||||
},
|
},
|
||||||
uint(a.UnilateralExitDelay),
|
},
|
||||||
leafProof,
|
})
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := updater.AddInWitnessUtxo(
|
|
||||||
&wire.TxOut{
|
|
||||||
Value: int64(utxo.Amount),
|
|
||||||
PkScript: script,
|
|
||||||
},
|
|
||||||
len(updater.Upsbt.Inputs)-1,
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *covenantlessArkClient) addVtxoInput(
|
|
||||||
updater *psbt.Updater, inputArgs *wire.OutPoint, exitDelay uint,
|
|
||||||
tapLeafProof *txscript.TapscriptProof,
|
|
||||||
) error {
|
|
||||||
sequence, err := common.BIP68EncodeAsNumber(exitDelay)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
nextInputIndex := len(updater.Upsbt.Inputs)
|
|
||||||
updater.Upsbt.UnsignedTx.AddTxIn(&wire.TxIn{
|
|
||||||
PreviousOutPoint: *inputArgs,
|
|
||||||
Sequence: sequence,
|
|
||||||
})
|
|
||||||
updater.Upsbt.Inputs = append(updater.Upsbt.Inputs, psbt.PInput{})
|
|
||||||
|
|
||||||
controlBlock := tapLeafProof.ToControlBlock(bitcointree.UnspendableKey())
|
|
||||||
controlBlockBytes, err := controlBlock.ToBytes()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
updater.Upsbt.Inputs[nextInputIndex].TaprootLeafScript = []*psbt.TaprootTapLeafScript{
|
|
||||||
{
|
|
||||||
ControlBlock: controlBlockBytes,
|
|
||||||
Script: tapLeafProof.Script,
|
|
||||||
LeafVersion: tapLeafProof.LeafVersion,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -1164,6 +953,7 @@ func (a *covenantlessArkClient) handleRoundStream(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
paymentID string,
|
paymentID string,
|
||||||
vtxosToSign []client.Vtxo,
|
vtxosToSign []client.Vtxo,
|
||||||
|
mustSignRoundTx bool,
|
||||||
receivers []client.Output,
|
receivers []client.Output,
|
||||||
roundEphemeralKey *secp256k1.PrivateKey,
|
roundEphemeralKey *secp256k1.PrivateKey,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
@@ -1218,20 +1008,20 @@ func (a *covenantlessArkClient) handleRoundStream(
|
|||||||
pingStop()
|
pingStop()
|
||||||
log.Info("a round finalization started")
|
log.Info("a round finalization started")
|
||||||
|
|
||||||
signedForfeitTxs, err := a.handleRoundFinalization(
|
signedForfeitTxs, signedRoundTx, err := a.handleRoundFinalization(
|
||||||
ctx, event.(client.RoundFinalizationEvent), vtxosToSign, receivers,
|
ctx, event.(client.RoundFinalizationEvent), vtxosToSign, mustSignRoundTx, receivers,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(signedForfeitTxs) <= 0 {
|
if len(signedForfeitTxs) <= 0 && len(vtxosToSign) > 0 {
|
||||||
log.Info("no forfeit txs to sign, waiting for the next round")
|
log.Info("no forfeit txs to sign, waiting for the next round")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("finalizing payment... ")
|
log.Info("finalizing payment... ")
|
||||||
if err := a.client.FinalizePayment(ctx, signedForfeitTxs); err != nil {
|
if err := a.client.FinalizePayment(ctx, signedForfeitTxs, signedRoundTx); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1311,15 +1101,29 @@ func (a *covenantlessArkClient) handleRoundSigningNoncesGenerated(
|
|||||||
|
|
||||||
func (a *covenantlessArkClient) handleRoundFinalization(
|
func (a *covenantlessArkClient) handleRoundFinalization(
|
||||||
ctx context.Context, event client.RoundFinalizationEvent,
|
ctx context.Context, event client.RoundFinalizationEvent,
|
||||||
vtxos []client.Vtxo, receivers []client.Output,
|
vtxos []client.Vtxo, mustSignRoundTx bool, receivers []client.Output,
|
||||||
) ([]string, error) {
|
) (signedForfeits []string, signedRoundTx string, err error) {
|
||||||
if err := a.validateCongestionTree(event, receivers); err != nil {
|
if err := a.validateCongestionTree(event, receivers); err != nil {
|
||||||
return nil, fmt.Errorf("failed to verify congestion tree: %s", err)
|
return nil, "", fmt.Errorf("failed to verify congestion tree: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return a.loopAndSign(
|
if len(vtxos) > 0 {
|
||||||
ctx, event.ForfeitTxs, vtxos, event.Connectors,
|
signedForfeits, err = a.loopAndSign(
|
||||||
)
|
ctx, event.ForfeitTxs, vtxos, event.Connectors,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if mustSignRoundTx {
|
||||||
|
signedRoundTx, err = a.wallet.SignTransaction(ctx, a.explorer, event.Tx)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantlessArkClient) validateCongestionTree(
|
func (a *covenantlessArkClient) validateCongestionTree(
|
||||||
@@ -1516,24 +1320,49 @@ func (a *covenantlessArkClient) loopAndSign(
|
|||||||
|
|
||||||
func (a *covenantlessArkClient) coinSelectOnchain(
|
func (a *covenantlessArkClient) coinSelectOnchain(
|
||||||
ctx context.Context, targetAmount uint64, exclude []explorer.Utxo,
|
ctx context.Context, targetAmount uint64, exclude []explorer.Utxo,
|
||||||
) ([]explorer.Utxo, []explorer.Utxo, uint64, error) {
|
) ([]explorer.Utxo, uint64, error) {
|
||||||
offchainAddrs, onchainAddrs, _, err := a.wallet.GetAddresses(ctx)
|
offchainAddrs, boardingAddrs, redemptionAddrs, err := a.wallet.GetAddresses(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
net := utils.ToBitcoinNetwork(a.Network)
|
_, myPubkey, _, err := common.DecodeAddress(offchainAddrs[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(myPubkey))
|
||||||
|
descriptorStr := strings.ReplaceAll(
|
||||||
|
a.BoardingDescriptorTemplate, "USER", myPubkeyStr,
|
||||||
|
)
|
||||||
|
desc, err := descriptor.ParseTaprootDescriptor(descriptorStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
fetchedUtxos := make([]explorer.Utxo, 0)
|
fetchedUtxos := make([]explorer.Utxo, 0)
|
||||||
for _, onchainAddr := range onchainAddrs {
|
for _, addr := range boardingAddrs {
|
||||||
utxos, err := a.explorer.GetUtxos(onchainAddr)
|
utxos, err := a.explorer.GetUtxos(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, err
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, utxo := range utxos {
|
||||||
|
u := utxo.ToUtxo(boardingTimeout)
|
||||||
|
if u.SpendableAt.Before(now) {
|
||||||
|
fetchedUtxos = append(fetchedUtxos, u)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fetchedUtxos = append(fetchedUtxos, utxos...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
utxos := make([]explorer.Utxo, 0)
|
selected := make([]explorer.Utxo, 0)
|
||||||
selectedAmount := uint64(0)
|
selectedAmount := uint64(0)
|
||||||
for _, utxo := range fetchedUtxos {
|
for _, utxo := range fetchedUtxos {
|
||||||
if selectedAmount >= targetAmount {
|
if selectedAmount >= targetAmount {
|
||||||
@@ -1546,70 +1375,51 @@ func (a *covenantlessArkClient) coinSelectOnchain(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
utxos = append(utxos, utxo)
|
selected = append(selected, utxo)
|
||||||
selectedAmount += utxo.Amount
|
selectedAmount += utxo.Amount
|
||||||
}
|
}
|
||||||
|
|
||||||
if selectedAmount >= targetAmount {
|
if selectedAmount >= targetAmount {
|
||||||
return utxos, nil, selectedAmount - targetAmount, nil
|
return selected, selectedAmount - targetAmount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchedUtxos = make([]explorer.Utxo, 0)
|
fetchedUtxos = make([]explorer.Utxo, 0)
|
||||||
for _, offchainAddr := range offchainAddrs {
|
for _, addr := range redemptionAddrs {
|
||||||
_, userPubkey, aspPubkey, _ := common.DecodeAddress(offchainAddr)
|
utxos, err := a.explorer.GetUtxos(addr)
|
||||||
|
|
||||||
vtxoTapKey, _, err := bitcointree.ComputeVtxoTaprootScript(
|
|
||||||
userPubkey, aspPubkey, uint(a.UnilateralExitDelay),
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, err
|
return nil, 0, err
|
||||||
}
|
|
||||||
p2tr, err := btcutil.NewAddressTaproot(
|
|
||||||
schnorr.SerializePubKey(vtxoTapKey), &net,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, 0, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := p2tr.EncodeAddress()
|
for _, utxo := range utxos {
|
||||||
|
u := utxo.ToUtxo(uint(a.UnilateralExitDelay))
|
||||||
utxos, err = a.explorer.GetUtxos(addr)
|
if u.SpendableAt.Before(now) {
|
||||||
if err != nil {
|
fetchedUtxos = append(fetchedUtxos, u)
|
||||||
return nil, nil, 0, err
|
}
|
||||||
}
|
}
|
||||||
fetchedUtxos = append(fetchedUtxos, utxos...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delayedUtxos := make([]explorer.Utxo, 0)
|
|
||||||
for _, utxo := range fetchedUtxos {
|
for _, utxo := range fetchedUtxos {
|
||||||
if selectedAmount >= targetAmount {
|
if selectedAmount >= targetAmount {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
availableAt := time.Unix(utxo.Status.Blocktime, 0).Add(
|
|
||||||
time.Duration(a.UnilateralExitDelay) * time.Second,
|
|
||||||
)
|
|
||||||
if availableAt.After(time.Now()) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, excluded := range exclude {
|
for _, excluded := range exclude {
|
||||||
if utxo.Txid == excluded.Txid && utxo.Vout == excluded.Vout {
|
if utxo.Txid == excluded.Txid && utxo.Vout == excluded.Vout {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delayedUtxos = append(delayedUtxos, utxo)
|
selected = append(selected, utxo)
|
||||||
selectedAmount += utxo.Amount
|
selectedAmount += utxo.Amount
|
||||||
}
|
}
|
||||||
|
|
||||||
if selectedAmount < targetAmount {
|
if selectedAmount < targetAmount {
|
||||||
return nil, nil, 0, fmt.Errorf(
|
return nil, 0, fmt.Errorf(
|
||||||
"not enough funds to cover amount %d", targetAmount,
|
"not enough funds to cover amount %d", targetAmount,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return utxos, delayedUtxos, selectedAmount - targetAmount, nil
|
return selected, selectedAmount - targetAmount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantlessArkClient) getRedeemBranches(
|
func (a *covenantlessArkClient) getRedeemBranches(
|
||||||
@@ -1673,6 +1483,53 @@ func (a *covenantlessArkClient) getOffchainBalance(
|
|||||||
return balance, amountByExpiration, nil
|
return balance, amountByExpiration, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *covenantlessArkClient) getClaimableBoardingUtxos(ctx context.Context) ([]explorer.Utxo, error) {
|
||||||
|
offchainAddrs, boardingAddrs, _, err := a.wallet.GetAddresses(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, myPubkey, _, err := common.DecodeAddress(offchainAddrs[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(myPubkey))
|
||||||
|
descriptorStr := strings.ReplaceAll(
|
||||||
|
a.BoardingDescriptorTemplate, "USER", myPubkeyStr,
|
||||||
|
)
|
||||||
|
desc, err := descriptor.ParseTaprootDescriptor(descriptorStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
claimable := make([]explorer.Utxo, 0)
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
for _, addr := range boardingAddrs {
|
||||||
|
boardingUtxos, err := a.explorer.GetUtxos(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, utxo := range boardingUtxos {
|
||||||
|
u := utxo.ToUtxo(boardingTimeout)
|
||||||
|
if u.SpendableAt.Before(now) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
claimable = append(claimable, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return claimable, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *covenantlessArkClient) getVtxos(
|
func (a *covenantlessArkClient) getVtxos(
|
||||||
ctx context.Context, addr string, computeVtxoExpiration bool,
|
ctx context.Context, addr string, computeVtxoExpiration bool,
|
||||||
) ([]client.Vtxo, []client.Vtxo, error) {
|
) ([]client.Vtxo, []client.Vtxo, error) {
|
||||||
@@ -1718,14 +1575,25 @@ func (a *covenantlessArkClient) getVtxos(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *covenantlessArkClient) selfTransferAllPendingPayments(
|
func (a *covenantlessArkClient) selfTransferAllPendingPayments(
|
||||||
ctx context.Context, pendingVtxos []client.Vtxo, myself client.Output,
|
ctx context.Context, pendingVtxos []client.Vtxo, boardingUtxo []explorer.Utxo, myself client.Output, boardingDescriptor string,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
inputs := make([]client.VtxoKey, 0, len(pendingVtxos))
|
inputs := make([]client.Input, 0, len(pendingVtxos)+len(boardingUtxo))
|
||||||
|
|
||||||
for _, coin := range pendingVtxos {
|
for _, coin := range pendingVtxos {
|
||||||
inputs = append(inputs, coin.VtxoKey)
|
inputs = append(inputs, coin.VtxoKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, utxo := range boardingUtxo {
|
||||||
|
fmt.Println(utxo)
|
||||||
|
fmt.Println(boardingDescriptor)
|
||||||
|
inputs = append(inputs, client.BoardingInput{
|
||||||
|
VtxoKey: client.VtxoKey{
|
||||||
|
Txid: utxo.Txid,
|
||||||
|
VOut: utxo.Vout,
|
||||||
|
},
|
||||||
|
Descriptor: boardingDescriptor,
|
||||||
|
})
|
||||||
|
}
|
||||||
outputs := []client.Output{myself}
|
outputs := []client.Output{myself}
|
||||||
|
|
||||||
roundEphemeralKey, err := secp256k1.GeneratePrivateKey()
|
roundEphemeralKey, err := secp256k1.GeneratePrivateKey()
|
||||||
@@ -1747,7 +1615,7 @@ func (a *covenantlessArkClient) selfTransferAllPendingPayments(
|
|||||||
}
|
}
|
||||||
|
|
||||||
roundTxid, err := a.handleRoundStream(
|
roundTxid, err := a.handleRoundStream(
|
||||||
ctx, paymentID, pendingVtxos, outputs, roundEphemeralKey,
|
ctx, paymentID, pendingVtxos, len(boardingUtxo) > 0, outputs, roundEphemeralKey,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|||||||
@@ -38,31 +38,27 @@ func main() {
|
|||||||
defer aliceArkClient.Lock(ctx, password)
|
defer aliceArkClient.Lock(ctx, password)
|
||||||
|
|
||||||
log.Info("alice is acquiring onchain funds...")
|
log.Info("alice is acquiring onchain funds...")
|
||||||
_, aliceOnchainAddr, err := aliceArkClient.Receive(ctx)
|
_, aliceBoardingAddr, err := aliceArkClient.Receive(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := runCommand("nigiri", "faucet", "--liquid", aliceOnchainAddr); err != nil {
|
if _, err := runCommand("nigiri", "faucet", "--liquid", aliceBoardingAddr); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
|
|
||||||
onboardAmount := uint64(20000)
|
onboardAmount := uint64(1_0000_0000) // 1 BTC
|
||||||
log.Infof("alice is onboarding with %d sats offchain...", onboardAmount)
|
log.Infof("alice is onboarding with %d sats offchain...", onboardAmount)
|
||||||
txid, err := aliceArkClient.Onboard(ctx, onboardAmount)
|
|
||||||
|
log.Infof("alice claiming onboarding funds...")
|
||||||
|
txid, err := aliceArkClient.Claim(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := generateBlock(); err != nil {
|
log.Infof("onboarding completed in round tx: %s", txid)
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
|
|
||||||
log.Infof("alice onboarded with tx: %s", txid)
|
|
||||||
|
|
||||||
aliceBalance, err := aliceArkClient.Balance(ctx, false)
|
aliceBalance, err := aliceArkClient.Balance(ctx, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -38,31 +38,19 @@ func main() {
|
|||||||
defer aliceArkClient.Lock(ctx, password)
|
defer aliceArkClient.Lock(ctx, password)
|
||||||
|
|
||||||
log.Info("alice is acquiring onchain funds...")
|
log.Info("alice is acquiring onchain funds...")
|
||||||
_, aliceOnchainAddr, err := aliceArkClient.Receive(ctx)
|
_, boardingAddress, err := aliceArkClient.Receive(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := runCommand("nigiri", "faucet", aliceOnchainAddr); err != nil {
|
if _, err := runCommand("nigiri", "faucet", boardingAddress); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
|
|
||||||
onboardAmount := uint64(20000)
|
onboardAmount := uint64(1_0000_0000) // 1 BTC
|
||||||
log.Infof("alice is onboarding with %d sats offchain...", onboardAmount)
|
log.Infof("alice is onboarding with %d sats offchain...", onboardAmount)
|
||||||
txid, err := aliceArkClient.Onboard(ctx, onboardAmount)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := generateBlock(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
|
|
||||||
log.Infof("alice onboarded with tx: %s", txid)
|
|
||||||
|
|
||||||
aliceBalance, err := aliceArkClient.Balance(ctx, false)
|
aliceBalance, err := aliceArkClient.Balance(ctx, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -72,6 +60,14 @@ func main() {
|
|||||||
log.Infof("alice onchain balance: %d", aliceBalance.OnchainBalance.SpendableAmount)
|
log.Infof("alice onchain balance: %d", aliceBalance.OnchainBalance.SpendableAmount)
|
||||||
log.Infof("alice offchain balance: %d", aliceBalance.OffchainBalance.Total)
|
log.Infof("alice offchain balance: %d", aliceBalance.OffchainBalance.Total)
|
||||||
|
|
||||||
|
log.Infof("alice claiming onboarding funds...")
|
||||||
|
txid, err := aliceArkClient.Claim(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("alice claimed onboarding funds in round %s", txid)
|
||||||
|
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
log.Info("bob is setting up his ark wallet...")
|
log.Info("bob is setting up his ark wallet...")
|
||||||
bobArkClient, err := setupArkClient()
|
bobArkClient, err := setupArkClient()
|
||||||
@@ -137,7 +133,7 @@ func main() {
|
|||||||
|
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
log.Info("bob is claiming the incoming payment...")
|
log.Info("bob is claiming the incoming payment...")
|
||||||
roundTxid, err := bobArkClient.ClaimAsync(ctx)
|
roundTxid, err := bobArkClient.Claim(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,35 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Utxo struct {
|
type Utxo struct {
|
||||||
|
Txid string
|
||||||
|
Vout uint32
|
||||||
|
Amount uint64
|
||||||
|
Asset string // liquid only
|
||||||
|
Delay uint
|
||||||
|
SpendableAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *Utxo) Sequence() (uint32, error) {
|
||||||
|
return common.BIP68EncodeAsNumber(u.Delay)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUtxo(explorerUtxo ExplorerUtxo, delay uint) Utxo {
|
||||||
|
utxoTime := explorerUtxo.Status.Blocktime
|
||||||
|
if utxoTime == 0 {
|
||||||
|
utxoTime = time.Now().Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
return Utxo{
|
||||||
|
Txid: explorerUtxo.Txid,
|
||||||
|
Vout: explorerUtxo.Vout,
|
||||||
|
Amount: explorerUtxo.Amount,
|
||||||
|
Asset: explorerUtxo.Asset,
|
||||||
|
Delay: delay,
|
||||||
|
SpendableAt: time.Unix(utxoTime, 0).Add(time.Duration(delay) * time.Second),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExplorerUtxo struct {
|
||||||
Txid string `json:"txid"`
|
Txid string `json:"txid"`
|
||||||
Vout uint32 `json:"vout"`
|
Vout uint32 `json:"vout"`
|
||||||
Amount uint64 `json:"value"`
|
Amount uint64 `json:"value"`
|
||||||
@@ -35,10 +64,14 @@ type Utxo struct {
|
|||||||
} `json:"status"`
|
} `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e ExplorerUtxo) ToUtxo(delay uint) Utxo {
|
||||||
|
return newUtxo(e, delay)
|
||||||
|
}
|
||||||
|
|
||||||
type Explorer interface {
|
type Explorer interface {
|
||||||
GetTxHex(txid string) (string, error)
|
GetTxHex(txid string) (string, error)
|
||||||
Broadcast(txHex string) (string, error)
|
Broadcast(txHex string) (string, error)
|
||||||
GetUtxos(addr string) ([]Utxo, error)
|
GetUtxos(addr string) ([]ExplorerUtxo, error)
|
||||||
GetBalance(addr string) (uint64, error)
|
GetBalance(addr string) (uint64, error)
|
||||||
GetRedeemedVtxosBalance(
|
GetRedeemedVtxosBalance(
|
||||||
addr string, unilateralExitDelay int64,
|
addr string, unilateralExitDelay int64,
|
||||||
@@ -143,7 +176,7 @@ func (e *explorerSvc) Broadcast(txStr string) (string, error) {
|
|||||||
return txid, nil
|
return txid, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *explorerSvc) GetUtxos(addr string) ([]Utxo, error) {
|
func (e *explorerSvc) GetUtxos(addr string) ([]ExplorerUtxo, error) {
|
||||||
resp, err := http.Get(fmt.Sprintf("%s/address/%s/utxo", e.baseUrl, addr))
|
resp, err := http.Get(fmt.Sprintf("%s/address/%s/utxo", e.baseUrl, addr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -157,7 +190,7 @@ func (e *explorerSvc) GetUtxos(addr string) ([]Utxo, error) {
|
|||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return nil, fmt.Errorf(string(body))
|
return nil, fmt.Errorf(string(body))
|
||||||
}
|
}
|
||||||
payload := []Utxo{}
|
payload := []ExplorerUtxo{}
|
||||||
if err := json.Unmarshal(body, &payload); err != nil {
|
if err := json.Unmarshal(body, &payload); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ module github.com/ark-network/ark/pkg/client-sdk
|
|||||||
|
|
||||||
go 1.22.6
|
go 1.22.6
|
||||||
|
|
||||||
|
replace github.com/ark-network/ark/common => ../../common
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/ark-network/ark/api-spec v0.0.0-20240815203029-edc4534dfc87
|
github.com/ark-network/ark/api-spec v0.0.0-20240815203029-edc4534dfc87
|
||||||
github.com/ark-network/ark/common v0.0.0-20240815203029-edc4534dfc87
|
github.com/ark-network/ark/common v0.0.0-20240815203029-edc4534dfc87
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
|
||||||
github.com/ark-network/ark/api-spec v0.0.0-20240815203029-edc4534dfc87 h1:VBY4KqHqxE4q6NnmvEZTLvLZoNA0Q6NhMhjBs1hzy9Y=
|
github.com/ark-network/ark/api-spec v0.0.0-20240815203029-edc4534dfc87 h1:VBY4KqHqxE4q6NnmvEZTLvLZoNA0Q6NhMhjBs1hzy9Y=
|
||||||
github.com/ark-network/ark/api-spec v0.0.0-20240815203029-edc4534dfc87/go.mod h1:m5H86Dx+k8cQjLXeYL1MV+h3x/XnhJCXJP/PL3KgZqY=
|
github.com/ark-network/ark/api-spec v0.0.0-20240815203029-edc4534dfc87/go.mod h1:m5H86Dx+k8cQjLXeYL1MV+h3x/XnhJCXJP/PL3KgZqY=
|
||||||
github.com/ark-network/ark/common v0.0.0-20240815203029-edc4534dfc87 h1:TIv00zlpxLKmY2LjFAIMF8RxNtn9rFqQsv73Lwoj2ds=
|
|
||||||
github.com/ark-network/ark/common v0.0.0-20240815203029-edc4534dfc87/go.mod h1:aYAGDfoeBLofnZt9n85wusFyCkrS7hvwdo5TynBlkuY=
|
|
||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||||
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
|
||||||
|
|||||||
@@ -21,14 +21,15 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type storeData struct {
|
type storeData struct {
|
||||||
AspUrl string `json:"asp_url"`
|
AspUrl string `json:"asp_url"`
|
||||||
AspPubkey string `json:"asp_pubkey"`
|
AspPubkey string `json:"asp_pubkey"`
|
||||||
WalletType string `json:"wallet_type"`
|
WalletType string `json:"wallet_type"`
|
||||||
ClientType string `json:"client_type"`
|
ClientType string `json:"client_type"`
|
||||||
Network string `json:"network"`
|
Network string `json:"network"`
|
||||||
RoundLifetime string `json:"round_lifetime"`
|
RoundLifetime string `json:"round_lifetime"`
|
||||||
UnilateralExitDelay string `json:"unilateral_exit_delay"`
|
UnilateralExitDelay string `json:"unilateral_exit_delay"`
|
||||||
MinRelayFee string `json:"min_relay_fee"`
|
MinRelayFee string `json:"min_relay_fee"`
|
||||||
|
BoardingDescriptorTemplate string `json:"boarding_descriptor_template"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d storeData) isEmpty() bool {
|
func (d storeData) isEmpty() bool {
|
||||||
@@ -43,27 +44,29 @@ func (d storeData) decode() store.StoreData {
|
|||||||
buf, _ := hex.DecodeString(d.AspPubkey)
|
buf, _ := hex.DecodeString(d.AspPubkey)
|
||||||
aspPubkey, _ := secp256k1.ParsePubKey(buf)
|
aspPubkey, _ := secp256k1.ParsePubKey(buf)
|
||||||
return store.StoreData{
|
return store.StoreData{
|
||||||
AspUrl: d.AspUrl,
|
AspUrl: d.AspUrl,
|
||||||
AspPubkey: aspPubkey,
|
AspPubkey: aspPubkey,
|
||||||
WalletType: d.WalletType,
|
WalletType: d.WalletType,
|
||||||
ClientType: d.ClientType,
|
ClientType: d.ClientType,
|
||||||
Network: network,
|
Network: network,
|
||||||
RoundLifetime: int64(roundLifetime),
|
RoundLifetime: int64(roundLifetime),
|
||||||
UnilateralExitDelay: int64(unilateralExitDelay),
|
UnilateralExitDelay: int64(unilateralExitDelay),
|
||||||
MinRelayFee: uint64(minRelayFee),
|
MinRelayFee: uint64(minRelayFee),
|
||||||
|
BoardingDescriptorTemplate: d.BoardingDescriptorTemplate,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d storeData) asMap() map[string]string {
|
func (d storeData) asMap() map[string]string {
|
||||||
return map[string]string{
|
return map[string]string{
|
||||||
"asp_url": d.AspUrl,
|
"asp_url": d.AspUrl,
|
||||||
"asp_pubkey": d.AspPubkey,
|
"asp_pubkey": d.AspPubkey,
|
||||||
"wallet_type": d.WalletType,
|
"wallet_type": d.WalletType,
|
||||||
"client_type": d.ClientType,
|
"client_type": d.ClientType,
|
||||||
"network": d.Network,
|
"network": d.Network,
|
||||||
"round_lifetime": d.RoundLifetime,
|
"round_lifetime": d.RoundLifetime,
|
||||||
"unilateral_exit_delay": d.UnilateralExitDelay,
|
"unilateral_exit_delay": d.UnilateralExitDelay,
|
||||||
"min_relay_fee": d.MinRelayFee,
|
"min_relay_fee": d.MinRelayFee,
|
||||||
|
"boarding_descriptor_template": d.BoardingDescriptorTemplate,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,14 +103,15 @@ func (s *Store) GetDatadir() string {
|
|||||||
|
|
||||||
func (s *Store) AddData(ctx context.Context, data store.StoreData) error {
|
func (s *Store) AddData(ctx context.Context, data store.StoreData) error {
|
||||||
sd := &storeData{
|
sd := &storeData{
|
||||||
AspUrl: data.AspUrl,
|
AspUrl: data.AspUrl,
|
||||||
AspPubkey: hex.EncodeToString(data.AspPubkey.SerializeCompressed()),
|
AspPubkey: hex.EncodeToString(data.AspPubkey.SerializeCompressed()),
|
||||||
WalletType: data.WalletType,
|
WalletType: data.WalletType,
|
||||||
ClientType: data.ClientType,
|
ClientType: data.ClientType,
|
||||||
Network: data.Network.Name,
|
Network: data.Network.Name,
|
||||||
RoundLifetime: fmt.Sprintf("%d", data.RoundLifetime),
|
RoundLifetime: fmt.Sprintf("%d", data.RoundLifetime),
|
||||||
UnilateralExitDelay: fmt.Sprintf("%d", data.UnilateralExitDelay),
|
UnilateralExitDelay: fmt.Sprintf("%d", data.UnilateralExitDelay),
|
||||||
MinRelayFee: fmt.Sprintf("%d", data.MinRelayFee),
|
MinRelayFee: fmt.Sprintf("%d", data.MinRelayFee),
|
||||||
|
BoardingDescriptorTemplate: data.BoardingDescriptorTemplate,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.write(sd); err != nil {
|
if err := s.write(sd); err != nil {
|
||||||
|
|||||||
@@ -13,14 +13,15 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type StoreData struct {
|
type StoreData struct {
|
||||||
AspUrl string
|
AspUrl string
|
||||||
AspPubkey *secp256k1.PublicKey
|
AspPubkey *secp256k1.PublicKey
|
||||||
WalletType string
|
WalletType string
|
||||||
ClientType string
|
ClientType string
|
||||||
Network common.Network
|
Network common.Network
|
||||||
RoundLifetime int64
|
RoundLifetime int64
|
||||||
UnilateralExitDelay int64
|
UnilateralExitDelay int64
|
||||||
MinRelayFee uint64
|
MinRelayFee uint64
|
||||||
|
BoardingDescriptorTemplate string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConfigStore interface {
|
type ConfigStore interface {
|
||||||
|
|||||||
@@ -18,14 +18,15 @@ func TestStore(t *testing.T) {
|
|||||||
key, _ := btcec.NewPrivateKey()
|
key, _ := btcec.NewPrivateKey()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
testStoreData := store.StoreData{
|
testStoreData := store.StoreData{
|
||||||
AspUrl: "localhost:7070",
|
AspUrl: "localhost:7070",
|
||||||
AspPubkey: key.PubKey(),
|
AspPubkey: key.PubKey(),
|
||||||
WalletType: wallet.SingleKeyWallet,
|
WalletType: wallet.SingleKeyWallet,
|
||||||
ClientType: client.GrpcClient,
|
ClientType: client.GrpcClient,
|
||||||
Network: common.LiquidRegTest,
|
Network: common.LiquidRegTest,
|
||||||
RoundLifetime: 512,
|
RoundLifetime: 512,
|
||||||
UnilateralExitDelay: 512,
|
UnilateralExitDelay: 512,
|
||||||
MinRelayFee: 300,
|
MinRelayFee: 300,
|
||||||
|
BoardingDescriptorTemplate: "tr(0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0,{ and(pk(873079a0091c9b16abd1f8c508320b07f0d50144d09ccd792ce9c915dac60465), pk(USER)), and(older(604672), pk(USER)) })",
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ import (
|
|||||||
|
|
||||||
"github.com/ark-network/ark/common"
|
"github.com/ark-network/ark/common"
|
||||||
"github.com/ark-network/ark/common/bitcointree"
|
"github.com/ark-network/ark/common/bitcointree"
|
||||||
|
"github.com/ark-network/ark/common/descriptor"
|
||||||
"github.com/ark-network/ark/pkg/client-sdk/explorer"
|
"github.com/ark-network/ark/pkg/client-sdk/explorer"
|
||||||
"github.com/ark-network/ark/pkg/client-sdk/internal/utils"
|
"github.com/ark-network/ark/pkg/client-sdk/internal/utils"
|
||||||
"github.com/ark-network/ark/pkg/client-sdk/store"
|
"github.com/ark-network/ark/pkg/client-sdk/store"
|
||||||
"github.com/ark-network/ark/pkg/client-sdk/wallet"
|
"github.com/ark-network/ark/pkg/client-sdk/wallet"
|
||||||
walletstore "github.com/ark-network/ark/pkg/client-sdk/wallet/singlekey/store"
|
walletstore "github.com/ark-network/ark/pkg/client-sdk/wallet/singlekey/store"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
|
||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"github.com/btcsuite/btcd/btcutil"
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
"github.com/btcsuite/btcd/btcutil/psbt"
|
"github.com/btcsuite/btcd/btcutil/psbt"
|
||||||
@@ -41,42 +41,42 @@ func NewBitcoinWallet(
|
|||||||
func (w *bitcoinWallet) GetAddresses(
|
func (w *bitcoinWallet) GetAddresses(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) ([]string, []string, []string, error) {
|
) ([]string, []string, []string, error) {
|
||||||
offchainAddr, onchainAddr, redemptionAddr, err := w.getAddress(ctx)
|
offchainAddr, boardingAddr, redemptionAddr, err := w.getAddress(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
offchainAddrs := []string{offchainAddr}
|
offchainAddrs := []string{offchainAddr}
|
||||||
onchainAddrs := []string{onchainAddr}
|
boardingAddrs := []string{boardingAddr}
|
||||||
redemptionAddrs := []string{redemptionAddr}
|
redemptionAddrs := []string{redemptionAddr}
|
||||||
return offchainAddrs, onchainAddrs, redemptionAddrs, nil
|
return offchainAddrs, boardingAddrs, redemptionAddrs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *bitcoinWallet) NewAddress(
|
func (w *bitcoinWallet) NewAddress(
|
||||||
ctx context.Context, _ bool,
|
ctx context.Context, _ bool,
|
||||||
) (string, string, error) {
|
) (string, string, error) {
|
||||||
offchainAddr, onchainAddr, _, err := w.getAddress(ctx)
|
offchainAddr, boardingAddr, _, err := w.getAddress(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
return offchainAddr, onchainAddr, nil
|
return offchainAddr, boardingAddr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *bitcoinWallet) NewAddresses(
|
func (w *bitcoinWallet) NewAddresses(
|
||||||
ctx context.Context, _ bool, num int,
|
ctx context.Context, _ bool, num int,
|
||||||
) ([]string, []string, error) {
|
) ([]string, []string, error) {
|
||||||
offchainAddr, onchainAddr, _, err := w.getAddress(ctx)
|
offchainAddr, boardingAddr, _, err := w.getAddress(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
offchainAddrs := make([]string, 0, num)
|
offchainAddrs := make([]string, 0, num)
|
||||||
onchainAddrs := make([]string, 0, num)
|
boardingAddrs := make([]string, 0, num)
|
||||||
for i := 0; i < num; i++ {
|
for i := 0; i < num; i++ {
|
||||||
offchainAddrs = append(offchainAddrs, offchainAddr)
|
offchainAddrs = append(offchainAddrs, offchainAddr)
|
||||||
onchainAddrs = append(onchainAddrs, onchainAddr)
|
boardingAddrs = append(boardingAddrs, boardingAddr)
|
||||||
}
|
}
|
||||||
return offchainAddrs, onchainAddrs, nil
|
return offchainAddrs, boardingAddrs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *bitcoinWallet) SignTransaction(
|
func (s *bitcoinWallet) SignTransaction(
|
||||||
@@ -92,11 +92,6 @@ func (s *bitcoinWallet) SignTransaction(
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := s.configStore.GetData(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, input := range updater.Upsbt.UnsignedTx.TxIn {
|
for i, input := range updater.Upsbt.UnsignedTx.TxIn {
|
||||||
if updater.Upsbt.Inputs[i].WitnessUtxo != nil {
|
if updater.Upsbt.Inputs[i].WitnessUtxo != nil {
|
||||||
continue
|
continue
|
||||||
@@ -122,28 +117,11 @@ func (s *bitcoinWallet) SignTransaction(
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
sighashType := txscript.SigHashAll
|
if err := updater.AddInSighashType(txscript.SigHashDefault, i); err != nil {
|
||||||
|
|
||||||
if utxo.PkScript[0] == txscript.OP_1 {
|
|
||||||
sighashType = txscript.SigHashDefault
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := updater.AddInSighashType(sighashType, i); err != nil {
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, onchainAddr, _, err := s.getAddress(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
net := utils.ToBitcoinNetwork(data.Network)
|
|
||||||
addr, _ := btcutil.DecodeAddress(onchainAddr, &net)
|
|
||||||
onchainWalletScript, err := txscript.PayToAddrScript(addr)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
prevouts := make(map[wire.OutPoint]*wire.TxOut)
|
prevouts := make(map[wire.OutPoint]*wire.TxOut)
|
||||||
|
|
||||||
for i, input := range updater.Upsbt.Inputs {
|
for i, input := range updater.Upsbt.Inputs {
|
||||||
@@ -158,36 +136,6 @@ func (s *bitcoinWallet) SignTransaction(
|
|||||||
txsighashes := txscript.NewTxSigHashes(updater.Upsbt.UnsignedTx, prevoutFetcher)
|
txsighashes := txscript.NewTxSigHashes(updater.Upsbt.UnsignedTx, prevoutFetcher)
|
||||||
|
|
||||||
for i, input := range ptx.Inputs {
|
for i, input := range ptx.Inputs {
|
||||||
if bytes.Equal(input.WitnessUtxo.PkScript, onchainWalletScript) {
|
|
||||||
if err := updater.AddInSighashType(txscript.SigHashAll, i); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
preimage, err := txscript.CalcWitnessSigHash(
|
|
||||||
input.WitnessUtxo.PkScript,
|
|
||||||
txsighashes,
|
|
||||||
txscript.SigHashAll,
|
|
||||||
updater.Upsbt.UnsignedTx,
|
|
||||||
i,
|
|
||||||
int64(input.WitnessUtxo.Value),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
sig := ecdsa.Sign(s.privateKey, preimage)
|
|
||||||
signatureWithSighashType := append(sig.Serialize(), byte(txscript.SigHashAll))
|
|
||||||
|
|
||||||
updater.Upsbt.Inputs[i].PartialSigs = []*psbt.PartialSig{
|
|
||||||
{
|
|
||||||
PubKey: s.walletData.Pubkey.SerializeCompressed(),
|
|
||||||
Signature: signatureWithSighashType,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(input.TaprootLeafScript) > 0 {
|
if len(input.TaprootLeafScript) > 0 {
|
||||||
pubkey := s.walletData.Pubkey
|
pubkey := s.walletData.Pubkey
|
||||||
for _, leaf := range input.TaprootLeafScript {
|
for _, leaf := range input.TaprootLeafScript {
|
||||||
@@ -269,11 +217,6 @@ func (w *bitcoinWallet) getAddress(
|
|||||||
|
|
||||||
netParams := utils.ToBitcoinNetwork(data.Network)
|
netParams := utils.ToBitcoinNetwork(data.Network)
|
||||||
|
|
||||||
onchainAddr, err := btcutil.NewAddressWitnessPubKeyHash(btcutil.Hash160(w.walletData.Pubkey.SerializeCompressed()), &netParams)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
vtxoTapKey, _, err := bitcointree.ComputeVtxoTaprootScript(
|
vtxoTapKey, _, err := bitcointree.ComputeVtxoTaprootScript(
|
||||||
w.walletData.Pubkey, data.AspPubkey, uint(data.UnilateralExitDelay),
|
w.walletData.Pubkey, data.AspPubkey, uint(data.UnilateralExitDelay),
|
||||||
)
|
)
|
||||||
@@ -289,5 +232,35 @@ func (w *bitcoinWallet) getAddress(
|
|||||||
return "", "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return offchainAddr, onchainAddr.EncodeAddress(), redemptionAddr.EncodeAddress(), nil
|
myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(w.walletData.Pubkey))
|
||||||
|
descriptorStr := strings.ReplaceAll(
|
||||||
|
data.BoardingDescriptorTemplate, "USER", myPubkeyStr,
|
||||||
|
)
|
||||||
|
|
||||||
|
desc, err := descriptor.ParseTaprootDescriptor(descriptorStr)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
boardingTapKey, _, err := bitcointree.ComputeVtxoTaprootScript(
|
||||||
|
w.walletData.Pubkey, data.AspPubkey, boardingTimeout,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
boardingAddr, err := btcutil.NewAddressTaproot(
|
||||||
|
schnorr.SerializePubKey(boardingTapKey),
|
||||||
|
&netParams,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return offchainAddr, boardingAddr.EncodeAddress(), redemptionAddr.EncodeAddress(), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,20 +3,21 @@ package singlekeywallet
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/ark-network/ark/common"
|
"github.com/ark-network/ark/common"
|
||||||
|
"github.com/ark-network/ark/common/descriptor"
|
||||||
"github.com/ark-network/ark/common/tree"
|
"github.com/ark-network/ark/common/tree"
|
||||||
"github.com/ark-network/ark/pkg/client-sdk/explorer"
|
"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/internal/utils"
|
||||||
"github.com/ark-network/ark/pkg/client-sdk/store"
|
"github.com/ark-network/ark/pkg/client-sdk/store"
|
||||||
"github.com/ark-network/ark/pkg/client-sdk/wallet"
|
"github.com/ark-network/ark/pkg/client-sdk/wallet"
|
||||||
walletstore "github.com/ark-network/ark/pkg/client-sdk/wallet/singlekey/store"
|
walletstore "github.com/ark-network/ark/pkg/client-sdk/wallet/singlekey/store"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
|
|
||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/vulpemventures/go-elements/payment"
|
|
||||||
"github.com/vulpemventures/go-elements/psetv2"
|
"github.com/vulpemventures/go-elements/psetv2"
|
||||||
"github.com/vulpemventures/go-elements/transaction"
|
"github.com/vulpemventures/go-elements/transaction"
|
||||||
)
|
)
|
||||||
@@ -40,42 +41,42 @@ func NewLiquidWallet(
|
|||||||
func (w *liquidWallet) GetAddresses(
|
func (w *liquidWallet) GetAddresses(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) ([]string, []string, []string, error) {
|
) ([]string, []string, []string, error) {
|
||||||
offchainAddr, onchainAddr, redemptionAddr, err := w.getAddress(ctx)
|
offchainAddr, boardingAddr, redemptionAddr, err := w.getAddress(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
offchainAddrs := []string{offchainAddr}
|
offchainAddrs := []string{offchainAddr}
|
||||||
onchainAddrs := []string{onchainAddr}
|
boardingAddrs := []string{boardingAddr}
|
||||||
redemptionAddrs := []string{redemptionAddr}
|
redemptionAddrs := []string{redemptionAddr}
|
||||||
return offchainAddrs, onchainAddrs, redemptionAddrs, nil
|
return offchainAddrs, boardingAddrs, redemptionAddrs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *liquidWallet) NewAddress(
|
func (w *liquidWallet) NewAddress(
|
||||||
ctx context.Context, _ bool,
|
ctx context.Context, _ bool,
|
||||||
) (string, string, error) {
|
) (string, string, error) {
|
||||||
offchainAddr, onchainAddr, _, err := w.getAddress(ctx)
|
offchainAddr, boardingAddr, _, err := w.getAddress(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
return offchainAddr, onchainAddr, nil
|
return offchainAddr, boardingAddr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *liquidWallet) NewAddresses(
|
func (w *liquidWallet) NewAddresses(
|
||||||
ctx context.Context, _ bool, num int,
|
ctx context.Context, _ bool, num int,
|
||||||
) ([]string, []string, error) {
|
) ([]string, []string, error) {
|
||||||
offchainAddr, onchainAddr, _, err := w.getAddress(ctx)
|
offchainAddr, boardingAddr, _, err := w.getAddress(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
offchainAddrs := make([]string, 0, num)
|
offchainAddrs := make([]string, 0, num)
|
||||||
onchainAddrs := make([]string, 0, num)
|
boardingAddrs := make([]string, 0, num)
|
||||||
for i := 0; i < num; i++ {
|
for i := 0; i < num; i++ {
|
||||||
offchainAddrs = append(offchainAddrs, offchainAddr)
|
offchainAddrs = append(offchainAddrs, offchainAddr)
|
||||||
onchainAddrs = append(onchainAddrs, onchainAddr)
|
boardingAddrs = append(boardingAddrs, boardingAddr)
|
||||||
}
|
}
|
||||||
return offchainAddrs, onchainAddrs, nil
|
return offchainAddrs, boardingAddrs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *liquidWallet) SignTransaction(
|
func (s *liquidWallet) SignTransaction(
|
||||||
@@ -114,13 +115,7 @@ func (s *liquidWallet) SignTransaction(
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
sighashType := txscript.SigHashAll
|
if err := updater.AddInSighashType(i, txscript.SigHashDefault); err != nil {
|
||||||
|
|
||||||
if utxo.Script[0] == txscript.OP_1 {
|
|
||||||
sighashType = txscript.SigHashDefault
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := updater.AddInSighashType(i, sighashType); err != nil {
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,8 +130,6 @@ func (s *liquidWallet) SignTransaction(
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
liquidNet := utils.ToElementsNetwork(storeData.Network)
|
liquidNet := utils.ToElementsNetwork(storeData.Network)
|
||||||
p2wpkh := payment.FromPublicKey(s.walletData.Pubkey, &liquidNet, nil)
|
|
||||||
onchainWalletScript := p2wpkh.WitnessScript
|
|
||||||
|
|
||||||
utx, err := pset.UnsignedTx()
|
utx, err := pset.UnsignedTx()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -156,33 +149,6 @@ func (s *liquidWallet) SignTransaction(
|
|||||||
serializedPubKey := s.walletData.Pubkey.SerializeCompressed()
|
serializedPubKey := s.walletData.Pubkey.SerializeCompressed()
|
||||||
|
|
||||||
for i, input := range pset.Inputs {
|
for i, input := range pset.Inputs {
|
||||||
prevout := input.GetUtxo()
|
|
||||||
|
|
||||||
if bytes.Equal(prevout.Script, onchainWalletScript) {
|
|
||||||
p, err := payment.FromScript(prevout.Script, &liquidNet, nil)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
preimage := utx.HashForWitnessV0(
|
|
||||||
i, p.Script, prevout.Value, txscript.SigHashAll,
|
|
||||||
)
|
|
||||||
|
|
||||||
sig := ecdsa.Sign(s.privateKey, preimage[:])
|
|
||||||
|
|
||||||
signatureWithSighashType := append(
|
|
||||||
sig.Serialize(), byte(txscript.SigHashAll),
|
|
||||||
)
|
|
||||||
|
|
||||||
err = signer.SignInput(
|
|
||||||
i, signatureWithSighashType, serializedPubKey, nil, nil,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(input.TapLeafScript) > 0 {
|
if len(input.TapLeafScript) > 0 {
|
||||||
genesis, err := chainhash.NewHashFromStr(liquidNet.GenesisBlockHash)
|
genesis, err := chainhash.NewHashFromStr(liquidNet.GenesisBlockHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -276,12 +242,6 @@ func (w *liquidWallet) getAddress(
|
|||||||
|
|
||||||
liquidNet := utils.ToElementsNetwork(data.Network)
|
liquidNet := utils.ToElementsNetwork(data.Network)
|
||||||
|
|
||||||
p2wpkh := payment.FromPublicKey(w.walletData.Pubkey, &liquidNet, nil)
|
|
||||||
onchainAddr, err := p2wpkh.WitnessPubKeyHash()
|
|
||||||
if err != nil {
|
|
||||||
return "", "", "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, _, redemptionAddr, err := tree.ComputeVtxoTaprootScript(
|
_, _, _, redemptionAddr, err := tree.ComputeVtxoTaprootScript(
|
||||||
w.walletData.Pubkey, data.AspPubkey, uint(data.UnilateralExitDelay), liquidNet,
|
w.walletData.Pubkey, data.AspPubkey, uint(data.UnilateralExitDelay), liquidNet,
|
||||||
)
|
)
|
||||||
@@ -289,5 +249,27 @@ func (w *liquidWallet) getAddress(
|
|||||||
return "", "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return offchainAddr, onchainAddr, redemptionAddr, nil
|
myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(w.walletData.Pubkey))
|
||||||
|
descriptorStr := strings.ReplaceAll(
|
||||||
|
data.BoardingDescriptorTemplate, "USER", myPubkeyStr,
|
||||||
|
)
|
||||||
|
|
||||||
|
desc, err := descriptor.ParseTaprootDescriptor(descriptorStr)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, _, boardingAddr, err := tree.ComputeVtxoTaprootScript(
|
||||||
|
w.walletData.Pubkey, data.AspPubkey, boardingTimeout, liquidNet,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return offchainAddr, boardingAddr, redemptionAddr, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ type WalletService interface {
|
|||||||
IsLocked() bool
|
IsLocked() bool
|
||||||
GetAddresses(
|
GetAddresses(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) (offchainAddresses, onchainAddresses, redemptionAddresses []string, err error)
|
) (offchainAddresses, boardingAddresses, redemptionAddresses []string, err error)
|
||||||
NewAddress(
|
NewAddress(
|
||||||
ctx context.Context, change bool,
|
ctx context.Context, change bool,
|
||||||
) (offchainAddr, onchainAddr string, err error)
|
) (offchainAddr, onchainAddr string, err error)
|
||||||
@@ -29,5 +29,5 @@ type WalletService interface {
|
|||||||
) (offchainAddresses, onchainAddresses []string, err error)
|
) (offchainAddresses, onchainAddresses []string, err error)
|
||||||
SignTransaction(
|
SignTransaction(
|
||||||
ctx context.Context, explorerSvc explorer.Explorer, tx string,
|
ctx context.Context, explorerSvc explorer.Explorer, tx string,
|
||||||
) (singedTx string, err error)
|
) (signedTx string, err error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,14 +21,15 @@ func TestWallet(t *testing.T) {
|
|||||||
key, _ := btcec.NewPrivateKey()
|
key, _ := btcec.NewPrivateKey()
|
||||||
password := "password"
|
password := "password"
|
||||||
testStoreData := store.StoreData{
|
testStoreData := store.StoreData{
|
||||||
AspUrl: "localhost:7070",
|
AspUrl: "localhost:7070",
|
||||||
AspPubkey: key.PubKey(),
|
AspPubkey: key.PubKey(),
|
||||||
WalletType: wallet.SingleKeyWallet,
|
WalletType: wallet.SingleKeyWallet,
|
||||||
ClientType: client.GrpcClient,
|
ClientType: client.GrpcClient,
|
||||||
Network: common.LiquidRegTest,
|
Network: common.LiquidRegTest,
|
||||||
RoundLifetime: 512,
|
RoundLifetime: 512,
|
||||||
UnilateralExitDelay: 512,
|
UnilateralExitDelay: 512,
|
||||||
MinRelayFee: 300,
|
MinRelayFee: 300,
|
||||||
|
BoardingDescriptorTemplate: "tr(0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0,{ and(pk(873079a0091c9b16abd1f8c508320b07f0d50144d09ccd792ce9c915dac60465), pk(USER)), and(older(604672), pk(USER)) })",
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ func init() {
|
|||||||
js.Global().Set("lock", LockWrapper())
|
js.Global().Set("lock", LockWrapper())
|
||||||
js.Global().Set("locked", IsLockedWrapper())
|
js.Global().Set("locked", IsLockedWrapper())
|
||||||
js.Global().Set("balance", BalanceWrapper())
|
js.Global().Set("balance", BalanceWrapper())
|
||||||
js.Global().Set("onboard", OnboardWrapper())
|
|
||||||
js.Global().Set("receive", ReceiveWrapper())
|
js.Global().Set("receive", ReceiveWrapper())
|
||||||
js.Global().Set("sendOnChain", SendOnChainWrapper())
|
js.Global().Set("sendOnChain", SendOnChainWrapper())
|
||||||
js.Global().Set("sendOffChain", SendOffChainWrapper())
|
js.Global().Set("sendOffChain", SendOffChainWrapper())
|
||||||
|
|||||||
@@ -140,33 +140,18 @@ func BalanceWrapper() js.Func {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func OnboardWrapper() js.Func {
|
|
||||||
return JSPromise(func(args []js.Value) (interface{}, error) {
|
|
||||||
if len(args) != 1 {
|
|
||||||
return nil, errors.New("invalid number of args")
|
|
||||||
}
|
|
||||||
amount := uint64(args[0].Int())
|
|
||||||
|
|
||||||
txID, err := arkSdkClient.Onboard(context.Background(), amount)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return js.ValueOf(txID), nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReceiveWrapper() js.Func {
|
func ReceiveWrapper() js.Func {
|
||||||
return JSPromise(func(args []js.Value) (interface{}, error) {
|
return JSPromise(func(args []js.Value) (interface{}, error) {
|
||||||
if arkSdkClient == nil {
|
if arkSdkClient == nil {
|
||||||
return nil, errors.New("ARK SDK client is not initialized")
|
return nil, errors.New("ARK SDK client is not initialized")
|
||||||
}
|
}
|
||||||
offchainAddr, onchainAddr, err := arkSdkClient.Receive(context.Background())
|
offchainAddr, boardingAddr, err := arkSdkClient.Receive(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
result := map[string]interface{}{
|
result := map[string]interface{}{
|
||||||
"offchainAddr": offchainAddr,
|
"offchainAddr": offchainAddr,
|
||||||
"onchainAddr": onchainAddr,
|
"boardingAddr": boardingAddr,
|
||||||
}
|
}
|
||||||
return js.ValueOf(result), nil
|
return js.ValueOf(result), nil
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ func mainAction(_ *cli.Context) error {
|
|||||||
BitcoindRpcUser: cfg.BitcoindRpcUser,
|
BitcoindRpcUser: cfg.BitcoindRpcUser,
|
||||||
BitcoindRpcPass: cfg.BitcoindRpcPass,
|
BitcoindRpcPass: cfg.BitcoindRpcPass,
|
||||||
BitcoindRpcHost: cfg.BitcoindRpcHost,
|
BitcoindRpcHost: cfg.BitcoindRpcHost,
|
||||||
|
BoardingExitDelay: cfg.BoardingExitDelay,
|
||||||
}
|
}
|
||||||
svc, err := grpcservice.NewService(svcConfig, appConfig)
|
svc, err := grpcservice.NewService(svcConfig, appConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ module github.com/ark-network/ark/server
|
|||||||
|
|
||||||
go 1.22.6
|
go 1.22.6
|
||||||
|
|
||||||
|
replace github.com/ark-network/ark/common => ../common
|
||||||
|
|
||||||
replace github.com/btcsuite/btcd/btcec/v2 => github.com/btcsuite/btcd/btcec/v2 v2.3.3
|
replace github.com/btcsuite/btcd/btcec/v2 => github.com/btcsuite/btcd/btcec/v2 v2.3.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBA
|
|||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
github.com/ark-network/ark/api-spec v0.0.0-20240812233307-18e343b31899 h1:PJL9Pam042F790x3mMovaIIkgeKIVaWm1aFOyH0k4PY=
|
github.com/ark-network/ark/api-spec v0.0.0-20240812233307-18e343b31899 h1:PJL9Pam042F790x3mMovaIIkgeKIVaWm1aFOyH0k4PY=
|
||||||
github.com/ark-network/ark/api-spec v0.0.0-20240812233307-18e343b31899/go.mod h1:0B5seq/gzuGL8OZGUaO12yj73ZJKAde8L+nmLQAZ7IA=
|
github.com/ark-network/ark/api-spec v0.0.0-20240812233307-18e343b31899/go.mod h1:0B5seq/gzuGL8OZGUaO12yj73ZJKAde8L+nmLQAZ7IA=
|
||||||
github.com/ark-network/ark/common v0.0.0-20240812233307-18e343b31899 h1:PxcHv+KaBdfrZCHoNYSUiCdI2wNIZ3Oxx8ZUewcEesg=
|
|
||||||
github.com/ark-network/ark/common v0.0.0-20240812233307-18e343b31899/go.mod h1:8DYeb06Dl8onmrV09xfsdDMGv5HoVtWoKhLBLXOYHew=
|
|
||||||
github.com/ark-network/ark/server/pkg/kvdb v0.0.0-20240812233307-18e343b31899 h1:pUtYz5kx/hvm8EiF4aj1BNdLFKMGnV3g4s1lQST9jFg=
|
github.com/ark-network/ark/server/pkg/kvdb v0.0.0-20240812233307-18e343b31899 h1:pUtYz5kx/hvm8EiF4aj1BNdLFKMGnV3g4s1lQST9jFg=
|
||||||
github.com/ark-network/ark/server/pkg/kvdb v0.0.0-20240812233307-18e343b31899/go.mod h1:8DXpdLoJXeIPh3JPd6AoANVTf7Rw63QLL8l+OJkNBlU=
|
github.com/ark-network/ark/server/pkg/kvdb v0.0.0-20240812233307-18e343b31899/go.mod h1:8DXpdLoJXeIPh3JPd6AoANVTf7Rw63QLL8l+OJkNBlU=
|
||||||
github.com/ark-network/ark/server/pkg/macaroons v0.0.0-20240812233307-18e343b31899 h1:Vlc9pbGToiqeBbAx3q4Wovg/DK6DJXAE2k5YAQiqza4=
|
github.com/ark-network/ark/server/pkg/macaroons v0.0.0-20240812233307-18e343b31899 h1:Vlc9pbGToiqeBbAx3q4Wovg/DK6DJXAE2k5YAQiqza4=
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ type Config struct {
|
|||||||
MinRelayFee uint64
|
MinRelayFee uint64
|
||||||
RoundLifetime int64
|
RoundLifetime int64
|
||||||
UnilateralExitDelay int64
|
UnilateralExitDelay int64
|
||||||
|
BoardingExitDelay int64
|
||||||
|
|
||||||
EsploraURL string
|
EsploraURL string
|
||||||
NeutrinoPeer string
|
NeutrinoPeer string
|
||||||
@@ -126,6 +127,12 @@ func (c *Config) Validate() error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.BoardingExitDelay < minAllowedSequence {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"invalid boarding exit delay, must at least %d", minAllowedSequence,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if c.RoundLifetime%minAllowedSequence != 0 {
|
if c.RoundLifetime%minAllowedSequence != 0 {
|
||||||
c.RoundLifetime -= c.RoundLifetime % minAllowedSequence
|
c.RoundLifetime -= c.RoundLifetime % minAllowedSequence
|
||||||
log.Infof(
|
log.Infof(
|
||||||
@@ -142,6 +149,14 @@ func (c *Config) Validate() error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.BoardingExitDelay%minAllowedSequence != 0 {
|
||||||
|
c.BoardingExitDelay -= c.BoardingExitDelay % minAllowedSequence
|
||||||
|
log.Infof(
|
||||||
|
"boarding exit delay must be a multiple of %d, rounded to %d",
|
||||||
|
minAllowedSequence, c.BoardingExitDelay,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if err := c.repoManager(); err != nil {
|
if err := c.repoManager(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -275,11 +290,11 @@ func (c *Config) txBuilderService() error {
|
|||||||
switch c.TxBuilderType {
|
switch c.TxBuilderType {
|
||||||
case "covenant":
|
case "covenant":
|
||||||
svc = txbuilder.NewTxBuilder(
|
svc = txbuilder.NewTxBuilder(
|
||||||
c.wallet, c.Network, c.RoundLifetime, c.UnilateralExitDelay,
|
c.wallet, c.Network, c.RoundLifetime, c.UnilateralExitDelay, c.BoardingExitDelay,
|
||||||
)
|
)
|
||||||
case "covenantless":
|
case "covenantless":
|
||||||
svc = cltxbuilder.NewTxBuilder(
|
svc = cltxbuilder.NewTxBuilder(
|
||||||
c.wallet, c.Network, c.RoundLifetime, c.UnilateralExitDelay,
|
c.wallet, c.Network, c.RoundLifetime, c.UnilateralExitDelay, c.BoardingExitDelay,
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("unknown tx builder type")
|
err = fmt.Errorf("unknown tx builder type")
|
||||||
@@ -323,7 +338,7 @@ func (c *Config) schedulerService() error {
|
|||||||
func (c *Config) appService() error {
|
func (c *Config) appService() error {
|
||||||
if common.IsLiquid(c.Network) {
|
if common.IsLiquid(c.Network) {
|
||||||
svc, err := application.NewCovenantService(
|
svc, err := application.NewCovenantService(
|
||||||
c.Network, c.RoundInterval, c.RoundLifetime, c.UnilateralExitDelay,
|
c.Network, c.RoundInterval, c.RoundLifetime, c.UnilateralExitDelay, c.BoardingExitDelay,
|
||||||
c.MinRelayFee, c.wallet, c.repo, c.txBuilder, c.scanner, c.scheduler,
|
c.MinRelayFee, c.wallet, c.repo, c.txBuilder, c.scanner, c.scheduler,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -335,7 +350,7 @@ func (c *Config) appService() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
svc, err := application.NewCovenantlessService(
|
svc, err := application.NewCovenantlessService(
|
||||||
c.Network, c.RoundInterval, c.RoundLifetime, c.UnilateralExitDelay,
|
c.Network, c.RoundInterval, c.RoundLifetime, c.UnilateralExitDelay, c.BoardingExitDelay,
|
||||||
c.MinRelayFee, c.wallet, c.repo, c.txBuilder, c.scanner, c.scheduler,
|
c.MinRelayFee, c.wallet, c.repo, c.txBuilder, c.scanner, c.scheduler,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ type Config struct {
|
|||||||
MinRelayFee uint64
|
MinRelayFee uint64
|
||||||
RoundLifetime int64
|
RoundLifetime int64
|
||||||
UnilateralExitDelay int64
|
UnilateralExitDelay int64
|
||||||
|
BoardingExitDelay int64
|
||||||
EsploraURL string
|
EsploraURL string
|
||||||
NeutrinoPeer string
|
NeutrinoPeer string
|
||||||
BitcoindRpcUser string
|
BitcoindRpcUser string
|
||||||
@@ -54,6 +55,7 @@ var (
|
|||||||
MinRelayFee = "MIN_RELAY_FEE"
|
MinRelayFee = "MIN_RELAY_FEE"
|
||||||
RoundLifetime = "ROUND_LIFETIME"
|
RoundLifetime = "ROUND_LIFETIME"
|
||||||
UnilateralExitDelay = "UNILATERAL_EXIT_DELAY"
|
UnilateralExitDelay = "UNILATERAL_EXIT_DELAY"
|
||||||
|
BoardingExitDelay = "BOARDING_EXIT_DELAY"
|
||||||
EsploraURL = "ESPLORA_URL"
|
EsploraURL = "ESPLORA_URL"
|
||||||
NeutrinoPeer = "NEUTRINO_PEER"
|
NeutrinoPeer = "NEUTRINO_PEER"
|
||||||
BitcoindRpcUser = "BITCOIND_RPC_USER"
|
BitcoindRpcUser = "BITCOIND_RPC_USER"
|
||||||
@@ -79,6 +81,7 @@ var (
|
|||||||
defaultMinRelayFee = 30 // 0.1 sat/vbyte on Liquid
|
defaultMinRelayFee = 30 // 0.1 sat/vbyte on Liquid
|
||||||
defaultRoundLifetime = 604672
|
defaultRoundLifetime = 604672
|
||||||
defaultUnilateralExitDelay = 1024
|
defaultUnilateralExitDelay = 1024
|
||||||
|
defaultBoardingExitDelay = 604672
|
||||||
defaultNoMacaroons = false
|
defaultNoMacaroons = false
|
||||||
defaultNoTLS = false
|
defaultNoTLS = false
|
||||||
)
|
)
|
||||||
@@ -104,6 +107,7 @@ func LoadConfig() (*Config, error) {
|
|||||||
viper.SetDefault(UnilateralExitDelay, defaultUnilateralExitDelay)
|
viper.SetDefault(UnilateralExitDelay, defaultUnilateralExitDelay)
|
||||||
viper.SetDefault(BlockchainScannerType, defaultBlockchainScannerType)
|
viper.SetDefault(BlockchainScannerType, defaultBlockchainScannerType)
|
||||||
viper.SetDefault(NoMacaroons, defaultNoMacaroons)
|
viper.SetDefault(NoMacaroons, defaultNoMacaroons)
|
||||||
|
viper.SetDefault(BoardingExitDelay, defaultBoardingExitDelay)
|
||||||
|
|
||||||
net, err := getNetwork()
|
net, err := getNetwork()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -132,6 +136,7 @@ func LoadConfig() (*Config, error) {
|
|||||||
MinRelayFee: viper.GetUint64(MinRelayFee),
|
MinRelayFee: viper.GetUint64(MinRelayFee),
|
||||||
RoundLifetime: viper.GetInt64(RoundLifetime),
|
RoundLifetime: viper.GetInt64(RoundLifetime),
|
||||||
UnilateralExitDelay: viper.GetInt64(UnilateralExitDelay),
|
UnilateralExitDelay: viper.GetInt64(UnilateralExitDelay),
|
||||||
|
BoardingExitDelay: viper.GetInt64(BoardingExitDelay),
|
||||||
EsploraURL: viper.GetString(EsploraURL),
|
EsploraURL: viper.GetString(EsploraURL),
|
||||||
NeutrinoPeer: viper.GetString(NeutrinoPeer),
|
NeutrinoPeer: viper.GetString(NeutrinoPeer),
|
||||||
BitcoindRpcUser: viper.GetString(BitcoindRpcUser),
|
BitcoindRpcUser: viper.GetString(BitcoindRpcUser),
|
||||||
|
|||||||
@@ -10,14 +10,18 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ark-network/ark/common"
|
"github.com/ark-network/ark/common"
|
||||||
|
"github.com/ark-network/ark/common/descriptor"
|
||||||
"github.com/ark-network/ark/common/tree"
|
"github.com/ark-network/ark/common/tree"
|
||||||
"github.com/ark-network/ark/server/internal/core/domain"
|
"github.com/ark-network/ark/server/internal/core/domain"
|
||||||
"github.com/ark-network/ark/server/internal/core/ports"
|
"github.com/ark-network/ark/server/internal/core/ports"
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"github.com/btcsuite/btcd/btcutil/psbt"
|
"github.com/btcsuite/btcd/btcutil/psbt"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/vulpemventures/go-elements/elementsutil"
|
||||||
"github.com/vulpemventures/go-elements/psetv2"
|
"github.com/vulpemventures/go-elements/psetv2"
|
||||||
|
"github.com/vulpemventures/go-elements/transaction"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -30,6 +34,7 @@ type covenantService struct {
|
|||||||
roundLifetime int64
|
roundLifetime int64
|
||||||
roundInterval int64
|
roundInterval int64
|
||||||
unilateralExitDelay int64
|
unilateralExitDelay int64
|
||||||
|
boardingExitDelay int64
|
||||||
minRelayFee uint64
|
minRelayFee uint64
|
||||||
|
|
||||||
wallet ports.WalletService
|
wallet ports.WalletService
|
||||||
@@ -41,23 +46,22 @@ type covenantService struct {
|
|||||||
paymentRequests *paymentsMap
|
paymentRequests *paymentsMap
|
||||||
forfeitTxs *forfeitTxsMap
|
forfeitTxs *forfeitTxsMap
|
||||||
|
|
||||||
eventsCh chan domain.RoundEvent
|
eventsCh chan domain.RoundEvent
|
||||||
onboardingCh chan onboarding
|
|
||||||
|
|
||||||
lastEvent domain.RoundEvent
|
currentRoundLock sync.Mutex
|
||||||
currentRound *domain.Round
|
currentRound *domain.Round
|
||||||
|
lastEvent domain.RoundEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCovenantService(
|
func NewCovenantService(
|
||||||
network common.Network,
|
network common.Network,
|
||||||
roundInterval, roundLifetime, unilateralExitDelay int64, minRelayFee uint64,
|
roundInterval, roundLifetime, unilateralExitDelay, boardingExitDelay int64, minRelayFee uint64,
|
||||||
walletSvc ports.WalletService, repoManager ports.RepoManager,
|
walletSvc ports.WalletService, repoManager ports.RepoManager,
|
||||||
builder ports.TxBuilder, scanner ports.BlockchainScanner,
|
builder ports.TxBuilder, scanner ports.BlockchainScanner,
|
||||||
scheduler ports.SchedulerService,
|
scheduler ports.SchedulerService,
|
||||||
) (Service, error) {
|
) (Service, error) {
|
||||||
eventsCh := make(chan domain.RoundEvent)
|
eventsCh := make(chan domain.RoundEvent)
|
||||||
onboardingCh := make(chan onboarding)
|
paymentRequests := newPaymentsMap()
|
||||||
paymentRequests := newPaymentsMap(nil)
|
|
||||||
|
|
||||||
forfeitTxs := newForfeitTxsMap(builder)
|
forfeitTxs := newForfeitTxsMap(builder)
|
||||||
pubkey, err := walletSvc.GetPubkey(context.Background())
|
pubkey, err := walletSvc.GetPubkey(context.Background())
|
||||||
@@ -69,9 +73,9 @@ func NewCovenantService(
|
|||||||
|
|
||||||
svc := &covenantService{
|
svc := &covenantService{
|
||||||
network, pubkey,
|
network, pubkey,
|
||||||
roundLifetime, roundInterval, unilateralExitDelay, minRelayFee,
|
roundLifetime, roundInterval, unilateralExitDelay, boardingExitDelay, minRelayFee,
|
||||||
walletSvc, repoManager, builder, scanner, sweeper,
|
walletSvc, repoManager, builder, scanner, sweeper,
|
||||||
paymentRequests, forfeitTxs, eventsCh, onboardingCh, nil, nil,
|
paymentRequests, forfeitTxs, eventsCh, sync.Mutex{}, nil, nil,
|
||||||
}
|
}
|
||||||
repoManager.RegisterEventsHandler(
|
repoManager.RegisterEventsHandler(
|
||||||
func(round *domain.Round) {
|
func(round *domain.Round) {
|
||||||
@@ -88,7 +92,6 @@ func NewCovenantService(
|
|||||||
return nil, fmt.Errorf("failed to restore watching vtxos: %s", err)
|
return nil, fmt.Errorf("failed to restore watching vtxos: %s", err)
|
||||||
}
|
}
|
||||||
go svc.listenToScannerNotifications()
|
go svc.listenToScannerNotifications()
|
||||||
go svc.listenToOnboarding()
|
|
||||||
return svc, nil
|
return svc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,30 +119,164 @@ func (s *covenantService) Stop() {
|
|||||||
s.repoManager.Close()
|
s.repoManager.Close()
|
||||||
log.Debug("closed connection to db")
|
log.Debug("closed connection to db")
|
||||||
close(s.eventsCh)
|
close(s.eventsCh)
|
||||||
close(s.onboardingCh)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *covenantService) SpendVtxos(ctx context.Context, inputs []domain.VtxoKey) (string, error) {
|
func (s *covenantService) GetBoardingAddress(ctx context.Context, userPubkey *secp256k1.PublicKey) (string, error) {
|
||||||
vtxos, err := s.repoManager.Vtxos().GetVtxos(ctx, inputs)
|
addr, _, err := s.builder.GetBoardingScript(userPubkey, s.pubkey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
for _, v := range vtxos {
|
return addr, nil
|
||||||
if v.Spent {
|
}
|
||||||
return "", fmt.Errorf("input %s:%d already spent", v.Txid, v.VOut)
|
|
||||||
|
func (s *covenantService) SpendVtxos(ctx context.Context, inputs []Input) (string, error) {
|
||||||
|
vtxosInputs := make([]domain.VtxoKey, 0)
|
||||||
|
boardingInputs := make([]Input, 0)
|
||||||
|
|
||||||
|
for _, in := range inputs {
|
||||||
|
if in.IsVtxo() {
|
||||||
|
vtxosInputs = append(vtxosInputs, in.VtxoKey())
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
boardingInputs = append(boardingInputs, in)
|
||||||
|
}
|
||||||
|
|
||||||
|
vtxos := make([]domain.Vtxo, 0)
|
||||||
|
|
||||||
|
if len(vtxosInputs) > 0 {
|
||||||
|
var err error
|
||||||
|
vtxos, err = s.repoManager.Vtxos().GetVtxos(ctx, vtxosInputs)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
for _, v := range vtxos {
|
||||||
|
if v.Spent {
|
||||||
|
return "", fmt.Errorf("input %s:%d already spent", v.Txid, v.VOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Redeemed {
|
||||||
|
return "", fmt.Errorf("input %s:%d already redeemed", v.Txid, v.VOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Spent {
|
||||||
|
return "", fmt.Errorf("input %s:%d already spent", v.Txid, v.VOut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boardingTxs := make(map[string]string, 0) // txid -> txhex
|
||||||
|
now := time.Now().Unix()
|
||||||
|
|
||||||
|
for _, in := range boardingInputs {
|
||||||
|
if _, ok := boardingTxs[in.Txid]; !ok {
|
||||||
|
txhex, err := s.wallet.GetTransaction(ctx, in.Txid)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get tx %s: %s", in.Txid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmed, blocktime, err := s.wallet.IsTransactionConfirmed(ctx, in.Txid)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to check tx %s: %s", in.Txid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !confirmed {
|
||||||
|
return "", fmt.Errorf("tx %s not confirmed", in.Txid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if blocktime+int64(s.boardingExitDelay) < now {
|
||||||
|
return "", fmt.Errorf("tx %s expired", in.Txid)
|
||||||
|
}
|
||||||
|
|
||||||
|
boardingTxs[in.Txid] = txhex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
utxos := make([]ports.BoardingInput, 0, len(boardingInputs))
|
||||||
|
|
||||||
|
for _, in := range boardingInputs {
|
||||||
|
desc, err := in.GetDescriptor()
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Debugf("failed to parse boarding input descriptor")
|
||||||
|
return "", fmt.Errorf("failed to parse descriptor %s for input %s:%d", in.Descriptor, in.Txid, in.Index)
|
||||||
|
}
|
||||||
|
input, err := s.newBoardingInput(boardingTxs[in.Txid], in.Index, *desc)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Debugf("failed to create boarding input")
|
||||||
|
return "", fmt.Errorf("input %s:%d is not a valid boarding input", in.Txid, in.Index)
|
||||||
|
}
|
||||||
|
|
||||||
|
utxos = append(utxos, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
payment, err := domain.NewPayment(vtxos)
|
payment, err := domain.NewPayment(vtxos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if err := s.paymentRequests.push(*payment); err != nil {
|
if err := s.paymentRequests.push(*payment, utxos); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return payment.Id, nil
|
return payment.Id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *covenantService) newBoardingInput(
|
||||||
|
txhex string, vout uint32, desc descriptor.TaprootDescriptor,
|
||||||
|
) (ports.BoardingInput, error) {
|
||||||
|
tx, err := transaction.NewTxFromHex(txhex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse tx: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tx.Outputs) <= int(vout) {
|
||||||
|
return nil, fmt.Errorf("output not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
out := tx.Outputs[vout]
|
||||||
|
script := out.Script
|
||||||
|
|
||||||
|
if len(out.RangeProof) > 0 || len(out.SurjectionProof) > 0 {
|
||||||
|
return nil, fmt.Errorf("output is confidential")
|
||||||
|
}
|
||||||
|
|
||||||
|
scriptFromDescriptor, err := tree.ComputeOutputScript(desc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to compute output script: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(script, scriptFromDescriptor) {
|
||||||
|
return nil, fmt.Errorf("descriptor does not match script in transaction output")
|
||||||
|
}
|
||||||
|
|
||||||
|
pubkey, timeout, err := descriptor.ParseBoardingDescriptor(desc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse boarding descriptor: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if timeout != uint(s.boardingExitDelay) {
|
||||||
|
return nil, fmt.Errorf("invalid boarding descriptor, timeout mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, expectedScript, err := s.builder.GetBoardingScript(pubkey, s.pubkey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to compute boarding script: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(script, expectedScript) {
|
||||||
|
return nil, fmt.Errorf("output script mismatch expected script")
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := elementsutil.ValueFromBytes(out.Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse value: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &boardingInput{
|
||||||
|
txId: tx.TxHash(),
|
||||||
|
vout: vout,
|
||||||
|
boardingPubKey: pubkey,
|
||||||
|
amount: value,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *covenantService) ClaimVtxos(ctx context.Context, creds string, receivers []domain.Receiver) error {
|
func (s *covenantService) ClaimVtxos(ctx context.Context, creds string, receivers []domain.Receiver) error {
|
||||||
// Check credentials
|
// Check credentials
|
||||||
payment, ok := s.paymentRequests.view(creds)
|
payment, ok := s.paymentRequests.view(creds)
|
||||||
@@ -178,6 +315,19 @@ func (s *covenantService) SignVtxos(ctx context.Context, forfeitTxs []string) er
|
|||||||
return s.forfeitTxs.sign(forfeitTxs)
|
return s.forfeitTxs.sign(forfeitTxs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *covenantService) SignRoundTx(ctx context.Context, signedRoundTx string) error {
|
||||||
|
s.currentRoundLock.Lock()
|
||||||
|
defer s.currentRoundLock.Unlock()
|
||||||
|
|
||||||
|
combined, err := s.builder.VerifyAndCombinePartialTx(s.currentRound.UnsignedTx, signedRoundTx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.currentRound.UnsignedTx = combined
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *covenantService) ListVtxos(ctx context.Context, pubkey *secp256k1.PublicKey) ([]domain.Vtxo, []domain.Vtxo, error) {
|
func (s *covenantService) ListVtxos(ctx context.Context, pubkey *secp256k1.PublicKey) ([]domain.Vtxo, []domain.Vtxo, error) {
|
||||||
pk := hex.EncodeToString(pubkey.SerializeCompressed())
|
pk := hex.EncodeToString(pubkey.SerializeCompressed())
|
||||||
return s.repoManager.Vtxos().GetAllVtxos(ctx, pk)
|
return s.repoManager.Vtxos().GetAllVtxos(ctx, pk)
|
||||||
@@ -209,50 +359,17 @@ func (s *covenantService) GetInfo(ctx context.Context) (*ServiceInfo, error) {
|
|||||||
RoundInterval: s.roundInterval,
|
RoundInterval: s.roundInterval,
|
||||||
Network: s.network.Name,
|
Network: s.network.Name,
|
||||||
MinRelayFee: int64(s.minRelayFee),
|
MinRelayFee: int64(s.minRelayFee),
|
||||||
|
BoardingDescriptorTemplate: fmt.Sprintf(
|
||||||
|
descriptor.BoardingDescriptorTemplate,
|
||||||
|
hex.EncodeToString(tree.UnspendableKey().SerializeCompressed()),
|
||||||
|
hex.EncodeToString(schnorr.SerializePubKey(s.pubkey)),
|
||||||
|
"USER",
|
||||||
|
s.boardingExitDelay,
|
||||||
|
"USER",
|
||||||
|
),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *covenantService) Onboard(
|
|
||||||
ctx context.Context, boardingTx string,
|
|
||||||
congestionTree tree.CongestionTree, userPubkey *secp256k1.PublicKey,
|
|
||||||
) error {
|
|
||||||
ptx, err := psetv2.NewPsetFromBase64(boardingTx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to parse boarding tx: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tree.ValidateCongestionTree(
|
|
||||||
congestionTree, boardingTx, s.pubkey, s.roundLifetime,
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
extracted, err := psetv2.Extract(ptx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to extract boarding tx: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
boardingTxHex, err := extracted.ToHex()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to convert boarding tx to hex: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
txid, err := s.wallet.BroadcastTransaction(ctx, boardingTxHex)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to broadcast boarding tx: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("broadcasted boarding tx %s", txid)
|
|
||||||
|
|
||||||
s.onboardingCh <- onboarding{
|
|
||||||
tx: boardingTx,
|
|
||||||
congestionTree: congestionTree,
|
|
||||||
userPubkey: userPubkey,
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *covenantService) RegisterCosignerPubkey(ctx context.Context, paymentId string, _ string) error {
|
func (s *covenantService) RegisterCosignerPubkey(ctx context.Context, paymentId string, _ string) error {
|
||||||
// if the user sends an ephemeral pubkey, something is going wrong client-side
|
// if the user sends an ephemeral pubkey, something is going wrong client-side
|
||||||
// we should delete the associated payment
|
// we should delete the associated payment
|
||||||
@@ -329,7 +446,7 @@ func (s *covenantService) startFinalization() {
|
|||||||
if num > paymentsThreshold {
|
if num > paymentsThreshold {
|
||||||
num = paymentsThreshold
|
num = paymentsThreshold
|
||||||
}
|
}
|
||||||
payments, _ := s.paymentRequests.pop(num)
|
payments, boardingInputs, _ := s.paymentRequests.pop(num)
|
||||||
if _, err := round.RegisterPayments(payments); err != nil {
|
if _, err := round.RegisterPayments(payments); err != nil {
|
||||||
round.Fail(fmt.Errorf("failed to register payments: %s", err))
|
round.Fail(fmt.Errorf("failed to register payments: %s", err))
|
||||||
log.WithError(err).Warn("failed to register payments")
|
log.WithError(err).Warn("failed to register payments")
|
||||||
@@ -343,7 +460,7 @@ func (s *covenantService) startFinalization() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
unsignedPoolTx, tree, connectorAddress, err := s.builder.BuildPoolTx(s.pubkey, payments, s.minRelayFee, sweptRounds)
|
unsignedPoolTx, tree, connectorAddress, err := s.builder.BuildPoolTx(s.pubkey, payments, boardingInputs, s.minRelayFee, sweptRounds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
round.Fail(fmt.Errorf("failed to create pool tx: %s", err))
|
round.Fail(fmt.Errorf("failed to create pool tx: %s", err))
|
||||||
log.WithError(err).Warn("failed to create pool tx")
|
log.WithError(err).Warn("failed to create pool tx")
|
||||||
@@ -351,16 +468,26 @@ func (s *covenantService) startFinalization() {
|
|||||||
}
|
}
|
||||||
log.Debugf("pool tx created for round %s", round.Id)
|
log.Debugf("pool tx created for round %s", round.Id)
|
||||||
|
|
||||||
// TODO BTC make the senders sign the tree
|
needForfeits := false
|
||||||
|
for _, pay := range payments {
|
||||||
connectors, forfeitTxs, err := s.builder.BuildForfeitTxs(s.pubkey, unsignedPoolTx, payments, s.minRelayFee)
|
if len(pay.Inputs) > 0 {
|
||||||
if err != nil {
|
needForfeits = true
|
||||||
round.Fail(fmt.Errorf("failed to create connectors and forfeit txs: %s", err))
|
break
|
||||||
log.WithError(err).Warn("failed to create connectors and forfeit txs")
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("forfeit transactions created for round %s", round.Id)
|
var forfeitTxs, connectors []string
|
||||||
|
|
||||||
|
if needForfeits {
|
||||||
|
connectors, forfeitTxs, err = s.builder.BuildForfeitTxs(s.pubkey, unsignedPoolTx, payments, s.minRelayFee)
|
||||||
|
if err != nil {
|
||||||
|
round.Fail(fmt.Errorf("failed to create connectors and forfeit txs: %s", err))
|
||||||
|
log.WithError(err).Warn("failed to create connectors and forfeit txs")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("forfeit transactions created for round %s", round.Id)
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := round.StartFinalization(
|
if _, err := round.StartFinalization(
|
||||||
connectorAddress, connectors, tree, unsignedPoolTx,
|
connectorAddress, connectors, tree, unsignedPoolTx,
|
||||||
@@ -401,71 +528,63 @@ func (s *covenantService) finalizeRound() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("signing round transaction %s\n", round.Id)
|
log.Debugf("signing round transaction %s\n", round.Id)
|
||||||
signedPoolTx, err := s.wallet.SignTransaction(ctx, round.UnsignedTx, true)
|
|
||||||
|
boardingInputs := make([]int, 0)
|
||||||
|
roundTx, err := psetv2.NewPsetFromBase64(round.UnsignedTx)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("failed to parse round tx: %s", round.UnsignedTx)
|
||||||
|
changes = round.Fail(fmt.Errorf("failed to parse round tx: %s", err))
|
||||||
|
log.WithError(err).Warn("failed to parse round tx")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, in := range roundTx.Inputs {
|
||||||
|
if len(in.TapLeafScript) > 0 {
|
||||||
|
if len(in.TapScriptSig) == 0 {
|
||||||
|
err = fmt.Errorf("missing tapscript spend sig for input %d", i)
|
||||||
|
changes = round.Fail(err)
|
||||||
|
log.WithError(err).Warn("missing boarding sig")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
boardingInputs = append(boardingInputs, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signedRoundTx := round.UnsignedTx
|
||||||
|
|
||||||
|
if len(boardingInputs) > 0 {
|
||||||
|
signedRoundTx, err = s.wallet.SignTransactionTapscript(ctx, signedRoundTx, boardingInputs)
|
||||||
|
if err != nil {
|
||||||
|
changes = round.Fail(fmt.Errorf("failed to sign round tx: %s", err))
|
||||||
|
log.WithError(err).Warn("failed to sign round tx")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signedRoundTx, err = s.wallet.SignTransaction(ctx, signedRoundTx, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
changes = round.Fail(fmt.Errorf("failed to sign round tx: %s", err))
|
changes = round.Fail(fmt.Errorf("failed to sign round tx: %s", err))
|
||||||
log.WithError(err).Warn("failed to sign round tx")
|
log.WithError(err).Warn("failed to sign round tx")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
txid, err := s.wallet.BroadcastTransaction(ctx, signedPoolTx)
|
txid, err := s.wallet.BroadcastTransaction(ctx, signedRoundTx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Debugf("failed to broadcast round tx: %s", signedRoundTx)
|
||||||
changes = round.Fail(fmt.Errorf("failed to broadcast pool tx: %s", err))
|
changes = round.Fail(fmt.Errorf("failed to broadcast pool tx: %s", err))
|
||||||
log.WithError(err).Warn("failed to broadcast pool tx")
|
log.WithError(err).Warn("failed to broadcast pool tx")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
changes, _ = round.EndFinalization(forfeitTxs, txid)
|
changes, err = round.EndFinalization(forfeitTxs, txid)
|
||||||
|
if err != nil {
|
||||||
log.Debugf("finalized round %s with pool tx %s", round.Id, round.Txid)
|
changes = round.Fail(fmt.Errorf("failed to finalize round: %s", err))
|
||||||
}
|
log.WithError(err).Warn("failed to finalize round")
|
||||||
|
|
||||||
func (s *covenantService) listenToOnboarding() {
|
|
||||||
for onboarding := range s.onboardingCh {
|
|
||||||
go s.handleOnboarding(onboarding)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *covenantService) handleOnboarding(onboarding onboarding) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
ptx, _ := psetv2.NewPsetFromBase64(onboarding.tx)
|
|
||||||
utx, _ := psetv2.Extract(ptx)
|
|
||||||
txid := utx.TxHash().String()
|
|
||||||
|
|
||||||
// wait for the tx to be confirmed with a timeout
|
|
||||||
timeout := time.NewTimer(5 * time.Minute)
|
|
||||||
defer timeout.Stop()
|
|
||||||
|
|
||||||
isConfirmed := false
|
|
||||||
|
|
||||||
for !isConfirmed {
|
|
||||||
select {
|
|
||||||
case <-timeout.C:
|
|
||||||
log.WithError(fmt.Errorf("operation timed out")).Warnf("failed to get confirmation for boarding tx %s", txid)
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
var err error
|
|
||||||
isConfirmed, _, err = s.wallet.IsTransactionConfirmed(ctx, txid)
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Warn("failed to check tx confirmation")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil || !isConfirmed {
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pubkey := hex.EncodeToString(onboarding.userPubkey.SerializeCompressed())
|
|
||||||
payments := getPaymentsFromOnboardingLiquid(onboarding.congestionTree, pubkey)
|
|
||||||
round := domain.NewFinalizedRound(
|
|
||||||
dustAmount, pubkey, txid, onboarding.tx, onboarding.congestionTree, payments,
|
|
||||||
)
|
|
||||||
if err := s.saveEvents(ctx, round.Id, round.Events()); err != nil {
|
|
||||||
log.WithError(err).Warn("failed to store new round events")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debugf("finalized round %s with pool tx %s", round.Id, round.Txid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *covenantService) listenToScannerNotifications() {
|
func (s *covenantService) listenToScannerNotifications() {
|
||||||
@@ -867,23 +986,6 @@ func (s *covenantService) saveEvents(
|
|||||||
return s.repoManager.Rounds().AddOrUpdateRound(ctx, *round)
|
return s.repoManager.Rounds().AddOrUpdateRound(ctx, *round)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPaymentsFromOnboardingLiquid(
|
|
||||||
congestionTree tree.CongestionTree, userKey string,
|
|
||||||
) []domain.Payment {
|
|
||||||
leaves := congestionTree.Leaves()
|
|
||||||
receivers := make([]domain.Receiver, 0, len(leaves))
|
|
||||||
for _, node := range leaves {
|
|
||||||
ptx, _ := psetv2.NewPsetFromBase64(node.Tx)
|
|
||||||
receiver := domain.Receiver{
|
|
||||||
Pubkey: userKey,
|
|
||||||
Amount: ptx.Outputs[0].Value,
|
|
||||||
}
|
|
||||||
receivers = append(receivers, receiver)
|
|
||||||
}
|
|
||||||
payment := domain.NewPaymentUnsafe(nil, receivers)
|
|
||||||
return []domain.Payment{*payment}
|
|
||||||
}
|
|
||||||
|
|
||||||
func findForfeitTxLiquid(
|
func findForfeitTxLiquid(
|
||||||
forfeits []string, connectorTxid string, connectorVout uint32, vtxoTxid string,
|
forfeits []string, connectorTxid string, connectorVout uint32, vtxoTxid string,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
|
|||||||
@@ -11,12 +11,15 @@ import (
|
|||||||
|
|
||||||
"github.com/ark-network/ark/common"
|
"github.com/ark-network/ark/common"
|
||||||
"github.com/ark-network/ark/common/bitcointree"
|
"github.com/ark-network/ark/common/bitcointree"
|
||||||
|
"github.com/ark-network/ark/common/descriptor"
|
||||||
"github.com/ark-network/ark/common/tree"
|
"github.com/ark-network/ark/common/tree"
|
||||||
"github.com/ark-network/ark/server/internal/core/domain"
|
"github.com/ark-network/ark/server/internal/core/domain"
|
||||||
"github.com/ark-network/ark/server/internal/core/ports"
|
"github.com/ark-network/ark/server/internal/core/ports"
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"github.com/btcsuite/btcd/btcutil/psbt"
|
"github.com/btcsuite/btcd/btcutil/psbt"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@@ -27,6 +30,7 @@ type covenantlessService struct {
|
|||||||
roundLifetime int64
|
roundLifetime int64
|
||||||
roundInterval int64
|
roundInterval int64
|
||||||
unilateralExitDelay int64
|
unilateralExitDelay int64
|
||||||
|
boardingExitDelay int64
|
||||||
minRelayFee uint64
|
minRelayFee uint64
|
||||||
|
|
||||||
wallet ports.WalletService
|
wallet ports.WalletService
|
||||||
@@ -38,11 +42,11 @@ type covenantlessService struct {
|
|||||||
paymentRequests *paymentsMap
|
paymentRequests *paymentsMap
|
||||||
forfeitTxs *forfeitTxsMap
|
forfeitTxs *forfeitTxsMap
|
||||||
|
|
||||||
eventsCh chan domain.RoundEvent
|
eventsCh chan domain.RoundEvent
|
||||||
onboardingCh chan onboarding
|
|
||||||
|
|
||||||
// cached data for the current round
|
// cached data for the current round
|
||||||
lastEvent domain.RoundEvent
|
lastEvent domain.RoundEvent
|
||||||
|
currentRoundLock sync.Mutex
|
||||||
currentRound *domain.Round
|
currentRound *domain.Round
|
||||||
treeSigningSessions map[string]*musigSigningSession
|
treeSigningSessions map[string]*musigSigningSession
|
||||||
asyncPaymentsCache map[domain.VtxoKey]struct {
|
asyncPaymentsCache map[domain.VtxoKey]struct {
|
||||||
@@ -53,14 +57,13 @@ type covenantlessService struct {
|
|||||||
|
|
||||||
func NewCovenantlessService(
|
func NewCovenantlessService(
|
||||||
network common.Network,
|
network common.Network,
|
||||||
roundInterval, roundLifetime, unilateralExitDelay int64, minRelayFee uint64,
|
roundInterval, roundLifetime, unilateralExitDelay, boardingExitDelay int64, minRelayFee uint64,
|
||||||
walletSvc ports.WalletService, repoManager ports.RepoManager,
|
walletSvc ports.WalletService, repoManager ports.RepoManager,
|
||||||
builder ports.TxBuilder, scanner ports.BlockchainScanner,
|
builder ports.TxBuilder, scanner ports.BlockchainScanner,
|
||||||
scheduler ports.SchedulerService,
|
scheduler ports.SchedulerService,
|
||||||
) (Service, error) {
|
) (Service, error) {
|
||||||
eventsCh := make(chan domain.RoundEvent)
|
eventsCh := make(chan domain.RoundEvent)
|
||||||
onboardingCh := make(chan onboarding)
|
paymentRequests := newPaymentsMap()
|
||||||
paymentRequests := newPaymentsMap(nil)
|
|
||||||
|
|
||||||
forfeitTxs := newForfeitTxsMap(builder)
|
forfeitTxs := newForfeitTxsMap(builder)
|
||||||
pubkey, err := walletSvc.GetPubkey(context.Background())
|
pubkey, err := walletSvc.GetPubkey(context.Background())
|
||||||
@@ -89,9 +92,10 @@ func NewCovenantlessService(
|
|||||||
paymentRequests: paymentRequests,
|
paymentRequests: paymentRequests,
|
||||||
forfeitTxs: forfeitTxs,
|
forfeitTxs: forfeitTxs,
|
||||||
eventsCh: eventsCh,
|
eventsCh: eventsCh,
|
||||||
onboardingCh: onboardingCh,
|
currentRoundLock: sync.Mutex{},
|
||||||
asyncPaymentsCache: asyncPaymentsCache,
|
asyncPaymentsCache: asyncPaymentsCache,
|
||||||
treeSigningSessions: make(map[string]*musigSigningSession),
|
treeSigningSessions: make(map[string]*musigSigningSession),
|
||||||
|
boardingExitDelay: boardingExitDelay,
|
||||||
}
|
}
|
||||||
|
|
||||||
repoManager.RegisterEventsHandler(
|
repoManager.RegisterEventsHandler(
|
||||||
@@ -109,7 +113,6 @@ func NewCovenantlessService(
|
|||||||
return nil, fmt.Errorf("failed to restore watching vtxos: %s", err)
|
return nil, fmt.Errorf("failed to restore watching vtxos: %s", err)
|
||||||
}
|
}
|
||||||
go svc.listenToScannerNotifications()
|
go svc.listenToScannerNotifications()
|
||||||
go svc.listenToOnboarding()
|
|
||||||
return svc, nil
|
return svc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +140,6 @@ func (s *covenantlessService) Stop() {
|
|||||||
s.repoManager.Close()
|
s.repoManager.Close()
|
||||||
log.Debug("closed connection to db")
|
log.Debug("closed connection to db")
|
||||||
close(s.eventsCh)
|
close(s.eventsCh)
|
||||||
close(s.onboardingCh)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *covenantlessService) CompleteAsyncPayment(
|
func (s *covenantlessService) CompleteAsyncPayment(
|
||||||
@@ -242,27 +244,147 @@ func (s *covenantlessService) CreateAsyncPayment(
|
|||||||
return res.RedeemTx, res.UnconditionalForfeitTxs, nil
|
return res.RedeemTx, res.UnconditionalForfeitTxs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *covenantlessService) SpendVtxos(ctx context.Context, inputs []domain.VtxoKey) (string, error) {
|
func (s *covenantlessService) SpendVtxos(ctx context.Context, inputs []Input) (string, error) {
|
||||||
vtxos, err := s.repoManager.Vtxos().GetVtxos(ctx, inputs)
|
vtxosInputs := make([]domain.VtxoKey, 0)
|
||||||
if err != nil {
|
boardingInputs := make([]Input, 0)
|
||||||
return "", err
|
|
||||||
}
|
for _, input := range inputs {
|
||||||
for _, v := range vtxos {
|
if input.IsVtxo() {
|
||||||
if v.Spent {
|
vtxosInputs = append(vtxosInputs, input.VtxoKey())
|
||||||
return "", fmt.Errorf("input %s:%d already spent", v.Txid, v.VOut)
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boardingInputs = append(boardingInputs, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
vtxos := make([]domain.Vtxo, 0)
|
||||||
|
if len(vtxosInputs) > 0 {
|
||||||
|
var err error
|
||||||
|
vtxos, err = s.repoManager.Vtxos().GetVtxos(ctx, vtxosInputs)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
for _, v := range vtxos {
|
||||||
|
if v.Spent {
|
||||||
|
return "", fmt.Errorf("input %s:%d already spent", v.Txid, v.VOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Redeemed {
|
||||||
|
return "", fmt.Errorf("input %s:%d already redeemed", v.Txid, v.VOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Spent {
|
||||||
|
return "", fmt.Errorf("input %s:%d already spent", v.Txid, v.VOut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boardingTxs := make(map[string]string, 0) // txid -> txhex
|
||||||
|
now := time.Now().Unix()
|
||||||
|
|
||||||
|
for _, in := range boardingInputs {
|
||||||
|
if _, ok := boardingTxs[in.Txid]; !ok {
|
||||||
|
// check if the tx exists and is confirmed
|
||||||
|
txhex, err := s.wallet.GetTransaction(ctx, in.Txid)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to get tx %s: %s", in.Txid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmed, blocktime, err := s.wallet.IsTransactionConfirmed(ctx, in.Txid)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to check tx %s: %s", in.Txid, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !confirmed {
|
||||||
|
return "", fmt.Errorf("tx %s not confirmed", in.Txid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the exit path is available, forbid registering the boarding utxo
|
||||||
|
if blocktime+int64(s.boardingExitDelay) < now {
|
||||||
|
return "", fmt.Errorf("tx %s expired", in.Txid)
|
||||||
|
}
|
||||||
|
|
||||||
|
boardingTxs[in.Txid] = txhex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
utxos := make([]ports.BoardingInput, 0, len(boardingInputs))
|
||||||
|
|
||||||
|
for _, in := range boardingInputs {
|
||||||
|
desc, err := in.GetDescriptor()
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Debugf("failed to parse boarding input descriptor")
|
||||||
|
return "", fmt.Errorf("failed to parse descriptor %s for input %s:%d", in.Descriptor, in.Txid, in.Index)
|
||||||
|
}
|
||||||
|
|
||||||
|
input, err := s.newBoardingInput(boardingTxs[in.Txid], in.Index, *desc)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Debugf("failed to create boarding input")
|
||||||
|
return "", fmt.Errorf("input %s:%d is not a valid boarding input", in.Txid, in.Index)
|
||||||
|
}
|
||||||
|
|
||||||
|
utxos = append(utxos, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
payment, err := domain.NewPayment(vtxos)
|
payment, err := domain.NewPayment(vtxos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if err := s.paymentRequests.push(*payment); err != nil {
|
if err := s.paymentRequests.push(*payment, utxos); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return payment.Id, nil
|
return payment.Id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *covenantlessService) newBoardingInput(txhex string, vout uint32, desc descriptor.TaprootDescriptor) (ports.BoardingInput, error) {
|
||||||
|
var tx wire.MsgTx
|
||||||
|
|
||||||
|
if err := tx.Deserialize(hex.NewDecoder(strings.NewReader(txhex))); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deserialize tx: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tx.TxOut) <= int(vout) {
|
||||||
|
return nil, fmt.Errorf("output not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
out := tx.TxOut[vout]
|
||||||
|
script := out.PkScript
|
||||||
|
|
||||||
|
scriptFromDescriptor, err := bitcointree.ComputeOutputScript(desc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to compute output script: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(script, scriptFromDescriptor) {
|
||||||
|
return nil, fmt.Errorf("descriptor does not match script in transaction output")
|
||||||
|
}
|
||||||
|
|
||||||
|
pubkey, timeout, err := descriptor.ParseBoardingDescriptor(desc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse boarding descriptor: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if timeout != uint(s.boardingExitDelay) {
|
||||||
|
return nil, fmt.Errorf("invalid boarding descriptor, timeout mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, expectedScript, err := s.builder.GetBoardingScript(pubkey, s.pubkey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get boarding script: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(script, expectedScript) {
|
||||||
|
return nil, fmt.Errorf("invalid boarding input output script")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &boardingInput{
|
||||||
|
txId: tx.TxHash(),
|
||||||
|
vout: vout,
|
||||||
|
boardingPubKey: pubkey,
|
||||||
|
amount: uint64(out.Value),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *covenantlessService) ClaimVtxos(ctx context.Context, creds string, receivers []domain.Receiver) error {
|
func (s *covenantlessService) ClaimVtxos(ctx context.Context, creds string, receivers []domain.Receiver) error {
|
||||||
// Check credentials
|
// Check credentials
|
||||||
payment, ok := s.paymentRequests.view(creds)
|
payment, ok := s.paymentRequests.view(creds)
|
||||||
@@ -293,6 +415,19 @@ func (s *covenantlessService) SignVtxos(ctx context.Context, forfeitTxs []string
|
|||||||
return s.forfeitTxs.sign(forfeitTxs)
|
return s.forfeitTxs.sign(forfeitTxs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *covenantlessService) SignRoundTx(ctx context.Context, signedRoundTx string) error {
|
||||||
|
s.currentRoundLock.Lock()
|
||||||
|
defer s.currentRoundLock.Unlock()
|
||||||
|
|
||||||
|
combined, err := s.builder.VerifyAndCombinePartialTx(s.currentRound.UnsignedTx, signedRoundTx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to verify and combine partial tx: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.currentRound.UnsignedTx = combined
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *covenantlessService) ListVtxos(ctx context.Context, pubkey *secp256k1.PublicKey) ([]domain.Vtxo, []domain.Vtxo, error) {
|
func (s *covenantlessService) ListVtxos(ctx context.Context, pubkey *secp256k1.PublicKey) ([]domain.Vtxo, []domain.Vtxo, error) {
|
||||||
pk := hex.EncodeToString(pubkey.SerializeCompressed())
|
pk := hex.EncodeToString(pubkey.SerializeCompressed())
|
||||||
return s.repoManager.Vtxos().GetAllVtxos(ctx, pk)
|
return s.repoManager.Vtxos().GetAllVtxos(ctx, pk)
|
||||||
@@ -324,50 +459,26 @@ func (s *covenantlessService) GetInfo(ctx context.Context) (*ServiceInfo, error)
|
|||||||
RoundInterval: s.roundInterval,
|
RoundInterval: s.roundInterval,
|
||||||
Network: s.network.Name,
|
Network: s.network.Name,
|
||||||
MinRelayFee: int64(s.minRelayFee),
|
MinRelayFee: int64(s.minRelayFee),
|
||||||
|
BoardingDescriptorTemplate: fmt.Sprintf(
|
||||||
|
descriptor.BoardingDescriptorTemplate,
|
||||||
|
hex.EncodeToString(bitcointree.UnspendableKey().SerializeCompressed()),
|
||||||
|
hex.EncodeToString(schnorr.SerializePubKey(s.pubkey)),
|
||||||
|
"USER",
|
||||||
|
s.boardingExitDelay,
|
||||||
|
"USER",
|
||||||
|
),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO clArk changes the onboard flow (2 rounds ?)
|
func (s *covenantlessService) GetBoardingAddress(
|
||||||
func (s *covenantlessService) Onboard(
|
ctx context.Context, userPubkey *secp256k1.PublicKey,
|
||||||
ctx context.Context, boardingTx string,
|
) (string, error) {
|
||||||
congestionTree tree.CongestionTree, userPubkey *secp256k1.PublicKey,
|
addr, _, err := s.builder.GetBoardingScript(userPubkey, s.pubkey)
|
||||||
) error {
|
|
||||||
ptx, err := psbt.NewFromRawBytes(strings.NewReader(boardingTx), true)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to parse boarding tx: %s", err)
|
return "", fmt.Errorf("failed to compute boarding script: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := bitcointree.ValidateCongestionTree(
|
return addr, nil
|
||||||
congestionTree, boardingTx, s.pubkey, s.roundLifetime, int64(s.minRelayFee),
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
extracted, err := psbt.Extract(ptx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to extract boarding tx: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var serialized bytes.Buffer
|
|
||||||
|
|
||||||
if err := extracted.Serialize(&serialized); err != nil {
|
|
||||||
return fmt.Errorf("failed to serialize boarding tx: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
txid, err := s.wallet.BroadcastTransaction(ctx, hex.EncodeToString(serialized.Bytes()))
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to broadcast boarding tx: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("broadcasted boarding tx %s", txid)
|
|
||||||
|
|
||||||
s.onboardingCh <- onboarding{
|
|
||||||
tx: boardingTx,
|
|
||||||
congestionTree: congestionTree,
|
|
||||||
userPubkey: userPubkey,
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *covenantlessService) RegisterCosignerPubkey(ctx context.Context, paymentId string, pubkey string) error {
|
func (s *covenantlessService) RegisterCosignerPubkey(ctx context.Context, paymentId string, pubkey string) error {
|
||||||
@@ -504,7 +615,7 @@ func (s *covenantlessService) startFinalization() {
|
|||||||
if num > paymentsThreshold {
|
if num > paymentsThreshold {
|
||||||
num = paymentsThreshold
|
num = paymentsThreshold
|
||||||
}
|
}
|
||||||
payments, cosigners := s.paymentRequests.pop(num)
|
payments, boardingInputs, cosigners := s.paymentRequests.pop(num)
|
||||||
if len(payments) > len(cosigners) {
|
if len(payments) > len(cosigners) {
|
||||||
err := fmt.Errorf("missing ephemeral key for payments")
|
err := fmt.Errorf("missing ephemeral key for payments")
|
||||||
round.Fail(fmt.Errorf("round aborted: %s", err))
|
round.Fail(fmt.Errorf("round aborted: %s", err))
|
||||||
@@ -534,7 +645,7 @@ func (s *covenantlessService) startFinalization() {
|
|||||||
|
|
||||||
cosigners = append(cosigners, ephemeralKey.PubKey())
|
cosigners = append(cosigners, ephemeralKey.PubKey())
|
||||||
|
|
||||||
unsignedPoolTx, tree, connectorAddress, err := s.builder.BuildPoolTx(s.pubkey, payments, s.minRelayFee, sweptRounds, cosigners...)
|
unsignedPoolTx, tree, connectorAddress, err := s.builder.BuildPoolTx(s.pubkey, payments, boardingInputs, s.minRelayFee, sweptRounds, cosigners...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
round.Fail(fmt.Errorf("failed to create pool tx: %s", err))
|
round.Fail(fmt.Errorf("failed to create pool tx: %s", err))
|
||||||
log.WithError(err).Warn("failed to create pool tx")
|
log.WithError(err).Warn("failed to create pool tx")
|
||||||
@@ -683,14 +794,25 @@ func (s *covenantlessService) startFinalization() {
|
|||||||
tree = signedTree
|
tree = signedTree
|
||||||
}
|
}
|
||||||
|
|
||||||
connectors, forfeitTxs, err := s.builder.BuildForfeitTxs(s.pubkey, unsignedPoolTx, payments, s.minRelayFee)
|
needForfeits := false
|
||||||
if err != nil {
|
for _, pay := range payments {
|
||||||
round.Fail(fmt.Errorf("failed to create connectors and forfeit txs: %s", err))
|
if len(pay.Inputs) > 0 {
|
||||||
log.WithError(err).Warn("failed to create connectors and forfeit txs")
|
needForfeits = true
|
||||||
return
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("forfeit transactions created for round %s", round.Id)
|
var forfeitTxs, connectors []string
|
||||||
|
|
||||||
|
if needForfeits {
|
||||||
|
connectors, forfeitTxs, err = s.builder.BuildForfeitTxs(s.pubkey, unsignedPoolTx, payments, s.minRelayFee)
|
||||||
|
if err != nil {
|
||||||
|
round.Fail(fmt.Errorf("failed to create connectors and forfeit txs: %s", err))
|
||||||
|
log.WithError(err).Warn("failed to create connectors and forfeit txs")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debugf("forfeit transactions created for round %s", round.Id)
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := round.StartFinalization(
|
if _, err := round.StartFinalization(
|
||||||
connectorAddress, connectors, tree, unsignedPoolTx,
|
connectorAddress, connectors, tree, unsignedPoolTx,
|
||||||
@@ -763,73 +885,61 @@ func (s *covenantlessService) finalizeRound() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("signing round transaction %s\n", round.Id)
|
log.Debugf("signing round transaction %s\n", round.Id)
|
||||||
signedPoolTx, err := s.wallet.SignTransaction(ctx, round.UnsignedTx, true)
|
|
||||||
|
boardingInputs := make([]int, 0)
|
||||||
|
roundTx, err := psbt.NewFromRawBytes(strings.NewReader(round.UnsignedTx), true)
|
||||||
|
if err != nil {
|
||||||
|
changes = round.Fail(fmt.Errorf("failed to parse round tx: %s", err))
|
||||||
|
log.WithError(err).Warn("failed to parse round tx")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, in := range roundTx.Inputs {
|
||||||
|
if len(in.TaprootLeafScript) > 0 {
|
||||||
|
if len(in.TaprootScriptSpendSig) == 0 {
|
||||||
|
err = fmt.Errorf("missing tapscript spend sig for input %d", i)
|
||||||
|
changes = round.Fail(err)
|
||||||
|
log.WithError(err).Warn("missing boarding sig")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
boardingInputs = append(boardingInputs, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signedRoundTx := round.UnsignedTx
|
||||||
|
|
||||||
|
if len(boardingInputs) > 0 {
|
||||||
|
signedRoundTx, err = s.wallet.SignTransactionTapscript(ctx, signedRoundTx, boardingInputs)
|
||||||
|
if err != nil {
|
||||||
|
changes = round.Fail(fmt.Errorf("failed to sign round tx: %s", err))
|
||||||
|
log.WithError(err).Warn("failed to sign round tx")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
signedRoundTx, err = s.wallet.SignTransaction(ctx, signedRoundTx, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
changes = round.Fail(fmt.Errorf("failed to sign round tx: %s", err))
|
changes = round.Fail(fmt.Errorf("failed to sign round tx: %s", err))
|
||||||
log.WithError(err).Warn("failed to sign round tx")
|
log.WithError(err).Warn("failed to sign round tx")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
txid, err := s.wallet.BroadcastTransaction(ctx, signedPoolTx)
|
txid, err := s.wallet.BroadcastTransaction(ctx, signedRoundTx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
changes = round.Fail(fmt.Errorf("failed to broadcast pool tx: %s", err))
|
changes = round.Fail(fmt.Errorf("failed to broadcast pool tx: %s", err))
|
||||||
log.WithError(err).Warn("failed to broadcast pool tx")
|
log.WithError(err).Warn("failed to broadcast pool tx")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
changes, _ = round.EndFinalization(forfeitTxs, txid)
|
changes, err = round.EndFinalization(forfeitTxs, txid)
|
||||||
|
if err != nil {
|
||||||
log.Debugf("finalized round %s with pool tx %s", round.Id, round.Txid)
|
changes = round.Fail(fmt.Errorf("failed to finalize round: %s", err))
|
||||||
}
|
log.WithError(err).Warn("failed to finalize round")
|
||||||
|
|
||||||
func (s *covenantlessService) listenToOnboarding() {
|
|
||||||
for onboarding := range s.onboardingCh {
|
|
||||||
go s.handleOnboarding(onboarding)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *covenantlessService) handleOnboarding(onboarding onboarding) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
ptx, _ := psbt.NewFromRawBytes(strings.NewReader(onboarding.tx), true)
|
|
||||||
txid := ptx.UnsignedTx.TxHash().String()
|
|
||||||
|
|
||||||
// wait for the tx to be confirmed with a timeout
|
|
||||||
timeout := time.NewTimer(15 * time.Minute)
|
|
||||||
defer timeout.Stop()
|
|
||||||
|
|
||||||
isConfirmed := false
|
|
||||||
|
|
||||||
for !isConfirmed {
|
|
||||||
select {
|
|
||||||
case <-timeout.C:
|
|
||||||
log.WithError(fmt.Errorf("operation timed out")).Warnf("failed to get confirmation for boarding tx %s", txid)
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
var err error
|
|
||||||
isConfirmed, _, err = s.wallet.IsTransactionConfirmed(ctx, txid)
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Warn("failed to check tx confirmation")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil || !isConfirmed {
|
|
||||||
log.Debugf("waiting for boarding tx %s to be confirmed", txid)
|
|
||||||
time.Sleep(5 * time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("boarding tx %s confirmed", txid)
|
|
||||||
|
|
||||||
pubkey := hex.EncodeToString(onboarding.userPubkey.SerializeCompressed())
|
|
||||||
payments := getPaymentsFromOnboardingBitcoin(onboarding.congestionTree, pubkey)
|
|
||||||
round := domain.NewFinalizedRound(
|
|
||||||
dustAmount, pubkey, txid, onboarding.tx, onboarding.congestionTree, payments,
|
|
||||||
)
|
|
||||||
if err := s.saveEvents(ctx, round.Id, round.Events()); err != nil {
|
|
||||||
log.WithError(err).Warn("failed to store new round events")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debugf("finalized round %s with pool tx %s", round.Id, round.Txid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *covenantlessService) listenToScannerNotifications() {
|
func (s *covenantlessService) listenToScannerNotifications() {
|
||||||
@@ -1232,24 +1342,6 @@ func (s *covenantlessService) saveEvents(
|
|||||||
return s.repoManager.Rounds().AddOrUpdateRound(ctx, *round)
|
return s.repoManager.Rounds().AddOrUpdateRound(ctx, *round)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPaymentsFromOnboardingBitcoin(
|
|
||||||
congestionTree tree.CongestionTree, userKey string,
|
|
||||||
) []domain.Payment {
|
|
||||||
leaves := congestionTree.Leaves()
|
|
||||||
receivers := make([]domain.Receiver, 0, len(leaves))
|
|
||||||
for _, node := range leaves {
|
|
||||||
ptx, _ := psbt.NewFromRawBytes(strings.NewReader(node.Tx), true)
|
|
||||||
|
|
||||||
receiver := domain.Receiver{
|
|
||||||
Pubkey: userKey,
|
|
||||||
Amount: uint64(ptx.UnsignedTx.TxOut[0].Value),
|
|
||||||
}
|
|
||||||
receivers = append(receivers, receiver)
|
|
||||||
}
|
|
||||||
payment := domain.NewPaymentUnsafe(nil, receivers)
|
|
||||||
return []domain.Payment{*payment}
|
|
||||||
}
|
|
||||||
|
|
||||||
func findForfeitTxBitcoin(
|
func findForfeitTxBitcoin(
|
||||||
forfeits []string, connectorTxid string, connectorVout uint32, vtxoTxid string,
|
forfeits []string, connectorTxid string, connectorVout uint32, vtxoTxid string,
|
||||||
) (string, error) {
|
) (string, error) {
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ package application
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/ark-network/ark/common/tree"
|
"github.com/ark-network/ark/common/descriptor"
|
||||||
"github.com/ark-network/ark/server/internal/core/domain"
|
"github.com/ark-network/ark/server/internal/core/domain"
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
)
|
)
|
||||||
@@ -16,9 +17,10 @@ var (
|
|||||||
type Service interface {
|
type Service interface {
|
||||||
Start() error
|
Start() error
|
||||||
Stop()
|
Stop()
|
||||||
SpendVtxos(ctx context.Context, inputs []domain.VtxoKey) (string, error)
|
SpendVtxos(ctx context.Context, inputs []Input) (string, error)
|
||||||
ClaimVtxos(ctx context.Context, creds string, receivers []domain.Receiver) error
|
ClaimVtxos(ctx context.Context, creds string, receivers []domain.Receiver) error
|
||||||
SignVtxos(ctx context.Context, forfeitTxs []string) error
|
SignVtxos(ctx context.Context, forfeitTxs []string) error
|
||||||
|
SignRoundTx(ctx context.Context, roundTx string) error
|
||||||
GetRoundByTxid(ctx context.Context, poolTxid string) (*domain.Round, error)
|
GetRoundByTxid(ctx context.Context, poolTxid string) (*domain.Round, error)
|
||||||
GetRoundById(ctx context.Context, id string) (*domain.Round, error)
|
GetRoundById(ctx context.Context, id string) (*domain.Round, error)
|
||||||
GetCurrentRound(ctx context.Context) (*domain.Round, error)
|
GetCurrentRound(ctx context.Context) (*domain.Round, error)
|
||||||
@@ -30,10 +32,6 @@ type Service interface {
|
|||||||
ctx context.Context, pubkey *secp256k1.PublicKey,
|
ctx context.Context, pubkey *secp256k1.PublicKey,
|
||||||
) (spendableVtxos, spentVtxos []domain.Vtxo, err error)
|
) (spendableVtxos, spentVtxos []domain.Vtxo, err error)
|
||||||
GetInfo(ctx context.Context) (*ServiceInfo, error)
|
GetInfo(ctx context.Context) (*ServiceInfo, error)
|
||||||
Onboard(
|
|
||||||
ctx context.Context, boardingTx string,
|
|
||||||
congestionTree tree.CongestionTree, userPubkey *secp256k1.PublicKey,
|
|
||||||
) error
|
|
||||||
// Async payments
|
// Async payments
|
||||||
CreateAsyncPayment(
|
CreateAsyncPayment(
|
||||||
ctx context.Context, inputs []domain.VtxoKey, receivers []domain.Receiver,
|
ctx context.Context, inputs []domain.VtxoKey, receivers []domain.Receiver,
|
||||||
@@ -41,6 +39,7 @@ type Service interface {
|
|||||||
CompleteAsyncPayment(
|
CompleteAsyncPayment(
|
||||||
ctx context.Context, redeemTx string, unconditionalForfeitTxs []string,
|
ctx context.Context, redeemTx string, unconditionalForfeitTxs []string,
|
||||||
) error
|
) error
|
||||||
|
GetBoardingAddress(ctx context.Context, userPubkey *secp256k1.PublicKey) (string, error)
|
||||||
// Tree signing methods
|
// Tree signing methods
|
||||||
RegisterCosignerPubkey(ctx context.Context, paymentId string, ephemeralPublicKey string) error
|
RegisterCosignerPubkey(ctx context.Context, paymentId string, ephemeralPublicKey string) error
|
||||||
RegisterCosignerNonces(
|
RegisterCosignerNonces(
|
||||||
@@ -54,12 +53,13 @@ type Service interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ServiceInfo struct {
|
type ServiceInfo struct {
|
||||||
PubKey string
|
PubKey string
|
||||||
RoundLifetime int64
|
RoundLifetime int64
|
||||||
UnilateralExitDelay int64
|
UnilateralExitDelay int64
|
||||||
RoundInterval int64
|
RoundInterval int64
|
||||||
Network string
|
Network string
|
||||||
MinRelayFee int64
|
MinRelayFee int64
|
||||||
|
BoardingDescriptorTemplate string
|
||||||
}
|
}
|
||||||
|
|
||||||
type WalletStatus struct {
|
type WalletStatus struct {
|
||||||
@@ -68,10 +68,28 @@ type WalletStatus struct {
|
|||||||
IsSynced bool
|
IsSynced bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type onboarding struct {
|
type Input struct {
|
||||||
tx string
|
Txid string
|
||||||
congestionTree tree.CongestionTree
|
Index uint32
|
||||||
userPubkey *secp256k1.PublicKey
|
Descriptor string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Input) IsVtxo() bool {
|
||||||
|
return len(i.Descriptor) <= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Input) VtxoKey() domain.VtxoKey {
|
||||||
|
return domain.VtxoKey{
|
||||||
|
Txid: i.Txid,
|
||||||
|
VOut: i.Index,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Input) GetDescriptor() (*descriptor.TaprootDescriptor, error) {
|
||||||
|
if i.IsVtxo() {
|
||||||
|
return nil, fmt.Errorf("input is not a boarding input")
|
||||||
|
}
|
||||||
|
return descriptor.ParseTaprootDescriptor(i.Descriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
type txOutpoint struct {
|
type txOutpoint struct {
|
||||||
|
|||||||
@@ -10,14 +10,16 @@ import (
|
|||||||
"github.com/ark-network/ark/common/tree"
|
"github.com/ark-network/ark/common/tree"
|
||||||
"github.com/ark-network/ark/server/internal/core/domain"
|
"github.com/ark-network/ark/server/internal/core/domain"
|
||||||
"github.com/ark-network/ark/server/internal/core/ports"
|
"github.com/ark-network/ark/server/internal/core/ports"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type timedPayment struct {
|
type timedPayment struct {
|
||||||
domain.Payment
|
domain.Payment
|
||||||
timestamp time.Time
|
boardingInputs []ports.BoardingInput
|
||||||
pingTimestamp time.Time
|
timestamp time.Time
|
||||||
|
pingTimestamp time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type paymentsMap struct {
|
type paymentsMap struct {
|
||||||
@@ -26,11 +28,8 @@ type paymentsMap struct {
|
|||||||
ephemeralKeys map[string]*secp256k1.PublicKey
|
ephemeralKeys map[string]*secp256k1.PublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPaymentsMap(payments []domain.Payment) *paymentsMap {
|
func newPaymentsMap() *paymentsMap {
|
||||||
paymentsById := make(map[string]*timedPayment)
|
paymentsById := make(map[string]*timedPayment)
|
||||||
for _, p := range payments {
|
|
||||||
paymentsById[p.Id] = &timedPayment{p, time.Now(), time.Time{}}
|
|
||||||
}
|
|
||||||
lock := &sync.RWMutex{}
|
lock := &sync.RWMutex{}
|
||||||
return &paymentsMap{lock, paymentsById, make(map[string]*secp256k1.PublicKey)}
|
return &paymentsMap{lock, paymentsById, make(map[string]*secp256k1.PublicKey)}
|
||||||
}
|
}
|
||||||
@@ -60,7 +59,7 @@ func (m *paymentsMap) delete(id string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *paymentsMap) push(payment domain.Payment) error {
|
func (m *paymentsMap) push(payment domain.Payment, boardingInputs []ports.BoardingInput) error {
|
||||||
m.lock.Lock()
|
m.lock.Lock()
|
||||||
defer m.lock.Unlock()
|
defer m.lock.Unlock()
|
||||||
|
|
||||||
@@ -68,7 +67,7 @@ func (m *paymentsMap) push(payment domain.Payment) error {
|
|||||||
return fmt.Errorf("duplicated inputs")
|
return fmt.Errorf("duplicated inputs")
|
||||||
}
|
}
|
||||||
|
|
||||||
m.payments[payment.Id] = &timedPayment{payment, time.Now(), time.Time{}}
|
m.payments[payment.Id] = &timedPayment{payment, boardingInputs, time.Now(), time.Time{}}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +83,7 @@ func (m *paymentsMap) pushEphemeralKey(paymentId string, pubkey *secp256k1.Publi
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *paymentsMap) pop(num int64) ([]domain.Payment, []*secp256k1.PublicKey) {
|
func (m *paymentsMap) pop(num int64) ([]domain.Payment, []ports.BoardingInput, []*secp256k1.PublicKey) {
|
||||||
m.lock.Lock()
|
m.lock.Lock()
|
||||||
defer m.lock.Unlock()
|
defer m.lock.Unlock()
|
||||||
|
|
||||||
@@ -109,8 +108,10 @@ func (m *paymentsMap) pop(num int64) ([]domain.Payment, []*secp256k1.PublicKey)
|
|||||||
}
|
}
|
||||||
|
|
||||||
payments := make([]domain.Payment, 0, num)
|
payments := make([]domain.Payment, 0, num)
|
||||||
|
boardingInputs := make([]ports.BoardingInput, 0)
|
||||||
cosigners := make([]*secp256k1.PublicKey, 0, num)
|
cosigners := make([]*secp256k1.PublicKey, 0, num)
|
||||||
for _, p := range paymentsByTime[:num] {
|
for _, p := range paymentsByTime[:num] {
|
||||||
|
boardingInputs = append(boardingInputs, p.boardingInputs...)
|
||||||
payments = append(payments, p.Payment)
|
payments = append(payments, p.Payment)
|
||||||
if pubkey, ok := m.ephemeralKeys[p.Payment.Id]; ok {
|
if pubkey, ok := m.ephemeralKeys[p.Payment.Id]; ok {
|
||||||
cosigners = append(cosigners, pubkey)
|
cosigners = append(cosigners, pubkey)
|
||||||
@@ -118,7 +119,7 @@ func (m *paymentsMap) pop(num int64) ([]domain.Payment, []*secp256k1.PublicKey)
|
|||||||
}
|
}
|
||||||
delete(m.payments, p.Id)
|
delete(m.payments, p.Id)
|
||||||
}
|
}
|
||||||
return payments, cosigners
|
return payments, boardingInputs, cosigners
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *paymentsMap) update(payment domain.Payment) error {
|
func (m *paymentsMap) update(payment domain.Payment) error {
|
||||||
@@ -312,3 +313,26 @@ func getSpentVtxos(payments map[string]domain.Payment) []domain.VtxoKey {
|
|||||||
}
|
}
|
||||||
return vtxos
|
return vtxos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type boardingInput struct {
|
||||||
|
txId chainhash.Hash
|
||||||
|
vout uint32
|
||||||
|
boardingPubKey *secp256k1.PublicKey
|
||||||
|
amount uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b boardingInput) GetHash() chainhash.Hash {
|
||||||
|
return b.txId
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b boardingInput) GetIndex() uint32 {
|
||||||
|
return b.vout
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b boardingInput) GetAmount() uint64 {
|
||||||
|
return b.amount
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b boardingInput) GetBoardingPubkey() *secp256k1.PublicKey {
|
||||||
|
return b.boardingPubKey
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,14 +28,6 @@ func NewPayment(inputs []Vtxo) (*Payment, error) {
|
|||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPaymentUnsafe(inputs []Vtxo, receivers []Receiver) *Payment {
|
|
||||||
return &Payment{
|
|
||||||
Id: uuid.New().String(),
|
|
||||||
Inputs: inputs,
|
|
||||||
Receivers: receivers,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Payment) AddReceivers(receivers []Receiver) (err error) {
|
func (p *Payment) AddReceivers(receivers []Receiver) (err error) {
|
||||||
if p.Receivers == nil {
|
if p.Receivers == nil {
|
||||||
p.Receivers = make([]Receiver, 0)
|
p.Receivers = make([]Receiver, 0)
|
||||||
@@ -70,18 +62,13 @@ func (p Payment) validate(ignoreOuts bool) error {
|
|||||||
if len(p.Id) <= 0 {
|
if len(p.Id) <= 0 {
|
||||||
return fmt.Errorf("missing id")
|
return fmt.Errorf("missing id")
|
||||||
}
|
}
|
||||||
if len(p.Inputs) <= 0 {
|
|
||||||
return fmt.Errorf("missing inputs")
|
|
||||||
}
|
|
||||||
if ignoreOuts {
|
if ignoreOuts {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(p.Receivers) <= 0 {
|
if len(p.Receivers) <= 0 {
|
||||||
return fmt.Errorf("missing outputs")
|
return fmt.Errorf("missing outputs")
|
||||||
}
|
}
|
||||||
// Check that input and output and output amounts match.
|
|
||||||
inAmount := p.TotalInputAmount()
|
|
||||||
outAmount := uint64(0)
|
|
||||||
for _, r := range p.Receivers {
|
for _, r := range p.Receivers {
|
||||||
if len(r.OnchainAddress) <= 0 && len(r.Pubkey) <= 0 {
|
if len(r.OnchainAddress) <= 0 && len(r.Pubkey) <= 0 {
|
||||||
return fmt.Errorf("missing receiver destination")
|
return fmt.Errorf("missing receiver destination")
|
||||||
@@ -89,10 +76,6 @@ func (p Payment) validate(ignoreOuts bool) error {
|
|||||||
if r.Amount < dustAmount {
|
if r.Amount < dustAmount {
|
||||||
return fmt.Errorf("receiver amount must be greater than dust")
|
return fmt.Errorf("receiver amount must be greater than dust")
|
||||||
}
|
}
|
||||||
outAmount += r.Amount
|
|
||||||
}
|
|
||||||
if inAmount != outAmount {
|
|
||||||
return fmt.Errorf("input and output amounts mismatch")
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ var inputs = []domain.Vtxo{
|
|||||||
|
|
||||||
func TestPayment(t *testing.T) {
|
func TestPayment(t *testing.T) {
|
||||||
t.Run("new_payment", func(t *testing.T) {
|
t.Run("new_payment", func(t *testing.T) {
|
||||||
t.Run("vaild", func(t *testing.T) {
|
t.Run("valid", func(t *testing.T) {
|
||||||
payment, err := domain.NewPayment(inputs)
|
payment, err := domain.NewPayment(inputs)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, payment)
|
require.NotNil(t, payment)
|
||||||
@@ -30,24 +30,6 @@ func TestPayment(t *testing.T) {
|
|||||||
require.Exactly(t, inputs, payment.Inputs)
|
require.Exactly(t, inputs, payment.Inputs)
|
||||||
require.Empty(t, payment.Receivers)
|
require.Empty(t, payment.Receivers)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("invaild", func(t *testing.T) {
|
|
||||||
fixtures := []struct {
|
|
||||||
inputs []domain.Vtxo
|
|
||||||
expectedErr string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
inputs: nil,
|
|
||||||
expectedErr: "missing inputs",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, f := range fixtures {
|
|
||||||
payment, err := domain.NewPayment(f.inputs)
|
|
||||||
require.EqualError(t, err, f.expectedErr)
|
|
||||||
require.Nil(t, payment)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("add_receivers", func(t *testing.T) {
|
t.Run("add_receivers", func(t *testing.T) {
|
||||||
@@ -87,15 +69,6 @@ func TestPayment(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectedErr: "receiver amount must be greater than dust",
|
expectedErr: "receiver amount must be greater than dust",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
receivers: []domain.Receiver{
|
|
||||||
{
|
|
||||||
Pubkey: "030000000000000000000000000000000000000000000000000000000000000001",
|
|
||||||
Amount: 600,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectedErr: "input and output amounts mismatch",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
payment, err := domain.NewPayment(inputs)
|
payment, err := domain.NewPayment(inputs)
|
||||||
|
|||||||
@@ -60,39 +60,6 @@ func NewRound(dustAmount uint64) *Round {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFinalizedRound(
|
|
||||||
dustAmount uint64, userKey, poolTxid, poolTx string,
|
|
||||||
congestionTree tree.CongestionTree, payments []Payment,
|
|
||||||
) *Round {
|
|
||||||
r := NewRound(dustAmount)
|
|
||||||
events := []RoundEvent{
|
|
||||||
RoundStarted{
|
|
||||||
Id: r.Id,
|
|
||||||
Timestamp: time.Now().Unix(),
|
|
||||||
},
|
|
||||||
PaymentsRegistered{
|
|
||||||
Id: r.Id,
|
|
||||||
Payments: payments,
|
|
||||||
},
|
|
||||||
RoundFinalizationStarted{
|
|
||||||
Id: r.Id,
|
|
||||||
CongestionTree: congestionTree,
|
|
||||||
PoolTx: poolTx,
|
|
||||||
},
|
|
||||||
RoundFinalized{
|
|
||||||
Id: r.Id,
|
|
||||||
Txid: poolTxid,
|
|
||||||
Timestamp: time.Now().Unix(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, event := range events {
|
|
||||||
r.raise(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRoundFromEvents(events []RoundEvent) *Round {
|
func NewRoundFromEvents(events []RoundEvent) *Round {
|
||||||
r := &Round{}
|
r := &Round{}
|
||||||
|
|
||||||
@@ -205,7 +172,11 @@ func (r *Round) StartFinalization(connectorAddress string, connectors []string,
|
|||||||
|
|
||||||
func (r *Round) EndFinalization(forfeitTxs []string, txid string) ([]RoundEvent, error) {
|
func (r *Round) EndFinalization(forfeitTxs []string, txid string) ([]RoundEvent, error) {
|
||||||
if len(forfeitTxs) <= 0 {
|
if len(forfeitTxs) <= 0 {
|
||||||
return nil, fmt.Errorf("missing list of signed forfeit txs")
|
for _, p := range r.Payments {
|
||||||
|
if len(p.Inputs) > 0 {
|
||||||
|
return nil, fmt.Errorf("missing list of signed forfeit txs")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(txid) <= 0 {
|
if len(txid) <= 0 {
|
||||||
return nil, fmt.Errorf("missing pool txid")
|
return nil, fmt.Errorf("missing pool txid")
|
||||||
@@ -216,6 +187,10 @@ func (r *Round) EndFinalization(forfeitTxs []string, txid string) ([]RoundEvent,
|
|||||||
if r.Stage.Ended {
|
if r.Stage.Ended {
|
||||||
return nil, fmt.Errorf("round already finalized")
|
return nil, fmt.Errorf("round already finalized")
|
||||||
}
|
}
|
||||||
|
if forfeitTxs == nil {
|
||||||
|
forfeitTxs = make([]string, 0)
|
||||||
|
}
|
||||||
|
|
||||||
event := RoundFinalized{
|
event := RoundFinalized{
|
||||||
Id: r.Id,
|
Id: r.Id,
|
||||||
Txid: txid,
|
Txid: txid,
|
||||||
|
|||||||
@@ -449,6 +449,7 @@ func testEndFinalization(t *testing.T) {
|
|||||||
Stage: domain.Stage{
|
Stage: domain.Stage{
|
||||||
Code: domain.FinalizationStage,
|
Code: domain.FinalizationStage,
|
||||||
},
|
},
|
||||||
|
Payments: paymentsById,
|
||||||
},
|
},
|
||||||
forfeitTxs: nil,
|
forfeitTxs: nil,
|
||||||
txid: txid,
|
txid: txid,
|
||||||
|
|||||||
@@ -16,9 +16,16 @@ type SweepInput interface {
|
|||||||
GetInternalKey() *secp256k1.PublicKey
|
GetInternalKey() *secp256k1.PublicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BoardingInput interface {
|
||||||
|
GetAmount() uint64
|
||||||
|
GetIndex() uint32
|
||||||
|
GetHash() chainhash.Hash
|
||||||
|
GetBoardingPubkey() *secp256k1.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
type TxBuilder interface {
|
type TxBuilder interface {
|
||||||
BuildPoolTx(
|
BuildPoolTx(
|
||||||
aspPubkey *secp256k1.PublicKey, payments []domain.Payment, minRelayFee uint64, sweptRounds []domain.Round,
|
aspPubkey *secp256k1.PublicKey, payments []domain.Payment, boardingInputs []BoardingInput, minRelayFee uint64, sweptRounds []domain.Round,
|
||||||
cosigners ...*secp256k1.PublicKey,
|
cosigners ...*secp256k1.PublicKey,
|
||||||
) (poolTx string, congestionTree tree.CongestionTree, connectorAddress string, err error)
|
) (poolTx string, congestionTree tree.CongestionTree, connectorAddress string, err error)
|
||||||
BuildForfeitTxs(aspPubkey *secp256k1.PublicKey, poolTx string, payments []domain.Payment, minRelayFee uint64) (connectors []string, forfeitTxs []string, err error)
|
BuildForfeitTxs(aspPubkey *secp256k1.PublicKey, poolTx string, payments []domain.Payment, minRelayFee uint64) (connectors []string, forfeitTxs []string, err error)
|
||||||
@@ -33,4 +40,6 @@ type TxBuilder interface {
|
|||||||
vtxosToSpend []domain.Vtxo,
|
vtxosToSpend []domain.Vtxo,
|
||||||
aspPubKey *secp256k1.PublicKey, receivers []domain.Receiver, minRelayFee uint64,
|
aspPubKey *secp256k1.PublicKey, receivers []domain.Receiver, minRelayFee uint64,
|
||||||
) (*domain.AsyncPaymentTxs, error)
|
) (*domain.AsyncPaymentTxs, error)
|
||||||
|
GetBoardingScript(userPubkey, aspPubkey *secp256k1.PublicKey) (addr string, script []byte, err error)
|
||||||
|
VerifyAndCombinePartialTx(dest string, src string) (string, error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ type WalletService interface {
|
|||||||
MainAccountBalance(ctx context.Context) (uint64, uint64, error)
|
MainAccountBalance(ctx context.Context) (uint64, uint64, error)
|
||||||
ConnectorsAccountBalance(ctx context.Context) (uint64, uint64, error)
|
ConnectorsAccountBalance(ctx context.Context) (uint64, uint64, error)
|
||||||
LockConnectorUtxos(ctx context.Context, utxos []TxOutpoint) error
|
LockConnectorUtxos(ctx context.Context, utxos []TxOutpoint) error
|
||||||
|
GetTransaction(ctx context.Context, txid string) (string, error)
|
||||||
Close()
|
Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ func deserializeEvent(buf []byte) (domain.RoundEvent, error) {
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
var event = domain.RoundFinalizationStarted{}
|
var event = domain.RoundFinalizationStarted{}
|
||||||
if err := json.Unmarshal(buf, &event); err == nil && len(event.Connectors) > 0 {
|
if err := json.Unmarshal(buf, &event); err == nil && len(event.PoolTx) > 0 {
|
||||||
return event, nil
|
return event, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,12 +11,15 @@ import (
|
|||||||
"github.com/ark-network/ark/server/internal/core/ports"
|
"github.com/ark-network/ark/server/internal/core/ports"
|
||||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
"github.com/vulpemventures/go-elements/address"
|
"github.com/vulpemventures/go-elements/address"
|
||||||
|
"github.com/vulpemventures/go-elements/elementsutil"
|
||||||
"github.com/vulpemventures/go-elements/network"
|
"github.com/vulpemventures/go-elements/network"
|
||||||
"github.com/vulpemventures/go-elements/payment"
|
"github.com/vulpemventures/go-elements/payment"
|
||||||
"github.com/vulpemventures/go-elements/psetv2"
|
"github.com/vulpemventures/go-elements/psetv2"
|
||||||
"github.com/vulpemventures/go-elements/taproot"
|
"github.com/vulpemventures/go-elements/taproot"
|
||||||
|
"github.com/vulpemventures/go-elements/transaction"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -25,10 +28,11 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type txBuilder struct {
|
type txBuilder struct {
|
||||||
wallet ports.WalletService
|
wallet ports.WalletService
|
||||||
net common.Network
|
net common.Network
|
||||||
roundLifetime int64 // in seconds
|
roundLifetime int64 // in seconds
|
||||||
exitDelay int64 // in seconds
|
exitDelay int64 // in seconds
|
||||||
|
boardingExitDelay int64 // in seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTxBuilder(
|
func NewTxBuilder(
|
||||||
@@ -36,8 +40,18 @@ func NewTxBuilder(
|
|||||||
net common.Network,
|
net common.Network,
|
||||||
roundLifetime int64,
|
roundLifetime int64,
|
||||||
exitDelay int64,
|
exitDelay int64,
|
||||||
|
boardingExitDelay int64,
|
||||||
) ports.TxBuilder {
|
) ports.TxBuilder {
|
||||||
return &txBuilder{wallet, net, roundLifetime, exitDelay}
|
return &txBuilder{wallet, net, roundLifetime, exitDelay, boardingExitDelay}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *txBuilder) GetBoardingScript(owner, asp *secp256k1.PublicKey) (string, []byte, error) {
|
||||||
|
addr, script, _, err := b.getBoardingTaproot(owner, asp)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr, script, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *txBuilder) GetVtxoScript(userPubkey, aspPubkey *secp256k1.PublicKey) ([]byte, error) {
|
func (b *txBuilder) GetVtxoScript(userPubkey, aspPubkey *secp256k1.PublicKey) ([]byte, error) {
|
||||||
@@ -112,7 +126,11 @@ func (b *txBuilder) BuildForfeitTxs(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *txBuilder) BuildPoolTx(
|
func (b *txBuilder) BuildPoolTx(
|
||||||
aspPubkey *secp256k1.PublicKey, payments []domain.Payment, minRelayFee uint64, sweptRounds []domain.Round,
|
aspPubkey *secp256k1.PublicKey,
|
||||||
|
payments []domain.Payment,
|
||||||
|
boardingInputs []ports.BoardingInput,
|
||||||
|
minRelayFee uint64,
|
||||||
|
sweptRounds []domain.Round,
|
||||||
_ ...*secp256k1.PublicKey, // cosigners are not used in the covenant
|
_ ...*secp256k1.PublicKey, // cosigners are not used in the covenant
|
||||||
) (poolTx string, congestionTree tree.CongestionTree, connectorAddress string, err error) {
|
) (poolTx string, congestionTree tree.CongestionTree, connectorAddress string, err error) {
|
||||||
// The creation of the tree and the pool tx are tightly coupled:
|
// The creation of the tree and the pool tx are tightly coupled:
|
||||||
@@ -146,7 +164,7 @@ func (b *txBuilder) BuildPoolTx(
|
|||||||
}
|
}
|
||||||
|
|
||||||
ptx, err := b.createPoolTx(
|
ptx, err := b.createPoolTx(
|
||||||
sharedOutputAmount, sharedOutputScript, payments, aspPubkey, connectorAddress, minRelayFee, sweptRounds,
|
sharedOutputAmount, sharedOutputScript, payments, boardingInputs, aspPubkey, connectorAddress, minRelayFee, sweptRounds,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@@ -197,9 +215,19 @@ func (b *txBuilder) GetSweepInput(parentblocktime int64, node tree.Node) (expira
|
|||||||
|
|
||||||
expirationTime := parentblocktime + lifetime
|
expirationTime := parentblocktime + lifetime
|
||||||
|
|
||||||
amount := uint64(0)
|
txhex, err := b.wallet.GetTransaction(context.Background(), txid)
|
||||||
for _, out := range pset.Outputs {
|
if err != nil {
|
||||||
amount += out.Value
|
return -1, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := transaction.NewTxFromHex(txhex)
|
||||||
|
if err != nil {
|
||||||
|
return -1, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
inputValue, err := elementsutil.ValueFromBytes(tx.Outputs[index].Value)
|
||||||
|
if err != nil {
|
||||||
|
return -1, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sweepInput = &sweepLiquidInput{
|
sweepInput = &sweepLiquidInput{
|
||||||
@@ -208,7 +236,7 @@ func (b *txBuilder) GetSweepInput(parentblocktime int64, node tree.Node) (expira
|
|||||||
TxIndex: index,
|
TxIndex: index,
|
||||||
},
|
},
|
||||||
sweepLeaf: sweepLeaf,
|
sweepLeaf: sweepLeaf,
|
||||||
amount: amount,
|
amount: inputValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
return expirationTime, sweepInput, nil
|
return expirationTime, sweepInput, nil
|
||||||
@@ -362,8 +390,11 @@ func (b *txBuilder) getLeafScriptAndTree(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *txBuilder) createPoolTx(
|
func (b *txBuilder) createPoolTx(
|
||||||
sharedOutputAmount uint64, sharedOutputScript []byte,
|
sharedOutputAmount uint64,
|
||||||
payments []domain.Payment, aspPubKey *secp256k1.PublicKey, connectorAddress string, minRelayFee uint64,
|
sharedOutputScript []byte,
|
||||||
|
payments []domain.Payment,
|
||||||
|
boardingInputs []ports.BoardingInput,
|
||||||
|
aspPubKey *secp256k1.PublicKey, connectorAddress string, minRelayFee uint64,
|
||||||
sweptRounds []domain.Round,
|
sweptRounds []domain.Round,
|
||||||
) (*psetv2.Pset, error) {
|
) (*psetv2.Pset, error) {
|
||||||
aspScript, err := p2wpkhScript(aspPubKey, b.onchainNetwork())
|
aspScript, err := p2wpkhScript(aspPubKey, b.onchainNetwork())
|
||||||
@@ -396,11 +427,13 @@ func (b *txBuilder) createPoolTx(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
outputs = append(outputs, psetv2.OutputArgs{
|
if connectorsAmount > 0 {
|
||||||
Asset: b.onchainNetwork().AssetID,
|
outputs = append(outputs, psetv2.OutputArgs{
|
||||||
Amount: connectorsAmount,
|
Asset: b.onchainNetwork().AssetID,
|
||||||
Script: connectorScript,
|
Amount: connectorsAmount,
|
||||||
})
|
Script: connectorScript,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
for _, receiver := range receivers {
|
for _, receiver := range receivers {
|
||||||
targetAmount += receiver.Amount
|
targetAmount += receiver.Amount
|
||||||
@@ -417,6 +450,9 @@ func (b *txBuilder) createPoolTx(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, in := range boardingInputs {
|
||||||
|
targetAmount -= in.GetAmount()
|
||||||
|
}
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
utxos, change, err := b.selectUtxos(ctx, sweptRounds, targetAmount)
|
utxos, change, err := b.selectUtxos(ctx, sweptRounds, targetAmount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -447,6 +483,48 @@ func (b *txBuilder) createPoolTx(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, in := range boardingInputs {
|
||||||
|
if err := updater.AddInputs(
|
||||||
|
[]psetv2.InputArgs{
|
||||||
|
{
|
||||||
|
Txid: in.GetHash().String(),
|
||||||
|
TxIndex: in.GetIndex(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
index := len(ptx.Inputs) - 1
|
||||||
|
|
||||||
|
assetBytes, err := elementsutil.AssetHashToBytes(b.onchainNetwork().AssetID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to convert asset to bytes: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
valueBytes, err := elementsutil.ValueToBytes(in.GetAmount())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to convert value to bytes: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, script, tapLeafProof, err := b.getBoardingTaproot(in.GetBoardingPubkey(), aspPubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := updater.AddInWitnessUtxo(index, transaction.NewTxOutput(assetBytes, valueBytes, script)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := updater.AddInTapLeafScript(index, psetv2.NewTapLeafScript(*tapLeafProof, tree.UnspendableKey())); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := updater.AddInSighashType(index, txscript.SigHashDefault); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := addInputs(updater, utxos); err != nil {
|
if err := addInputs(updater, utxos); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -471,14 +549,23 @@ func (b *txBuilder) createPoolTx(
|
|||||||
if feeAmount == change {
|
if feeAmount == change {
|
||||||
// fees = change, remove change output
|
// fees = change, remove change output
|
||||||
ptx.Outputs = ptx.Outputs[:len(ptx.Outputs)-1]
|
ptx.Outputs = ptx.Outputs[:len(ptx.Outputs)-1]
|
||||||
|
ptx.Global.OutputCount--
|
||||||
|
feeAmount += change
|
||||||
} else if feeAmount < change {
|
} else if feeAmount < change {
|
||||||
// change covers the fees, reduce change amount
|
// change covers the fees, reduce change amount
|
||||||
ptx.Outputs[len(ptx.Outputs)-1].Value = change - feeAmount
|
if change-feeAmount < dustLimit {
|
||||||
|
ptx.Outputs = ptx.Outputs[:len(ptx.Outputs)-1]
|
||||||
|
ptx.Global.OutputCount--
|
||||||
|
feeAmount += change
|
||||||
|
} else {
|
||||||
|
ptx.Outputs[len(ptx.Outputs)-1].Value = change - feeAmount
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// change is not enough to cover fees, re-select utxos
|
// change is not enough to cover fees, re-select utxos
|
||||||
if change > 0 {
|
if change > 0 {
|
||||||
// remove change output if present
|
// remove change output if present
|
||||||
ptx.Outputs = ptx.Outputs[:len(ptx.Outputs)-1]
|
ptx.Outputs = ptx.Outputs[:len(ptx.Outputs)-1]
|
||||||
|
ptx.Global.OutputCount--
|
||||||
}
|
}
|
||||||
newUtxos, change, err := b.selectUtxos(ctx, sweptRounds, feeAmount-change)
|
newUtxos, change, err := b.selectUtxos(ctx, sweptRounds, feeAmount-change)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -486,14 +573,18 @@ func (b *txBuilder) createPoolTx(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if change > 0 {
|
if change > 0 {
|
||||||
if err := updater.AddOutputs([]psetv2.OutputArgs{
|
if change < dustLimit {
|
||||||
{
|
feeAmount += change
|
||||||
Asset: b.onchainNetwork().AssetID,
|
} else {
|
||||||
Amount: change,
|
if err := updater.AddOutputs([]psetv2.OutputArgs{
|
||||||
Script: aspScript,
|
{
|
||||||
},
|
Asset: b.onchainNetwork().AssetID,
|
||||||
}); err != nil {
|
Amount: change,
|
||||||
return nil, err
|
Script: aspScript,
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,6 +632,77 @@ func (b *txBuilder) createPoolTx(
|
|||||||
return ptx, nil
|
return ptx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This method aims to verify and add partial signature from boarding input
|
||||||
|
func (b *txBuilder) VerifyAndCombinePartialTx(dest string, src string) (string, error) {
|
||||||
|
roundPset, err := psetv2.NewPsetFromBase64(dest)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
sourcePset, err := psetv2.NewPsetFromBase64(src)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
roundUtx, err := roundPset.UnsignedTx()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceUtx, err := sourcePset.UnsignedTx()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if roundUtx.TxHash().String() != sourceUtx.TxHash().String() {
|
||||||
|
return "", fmt.Errorf("txid mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
roundSigner, err := psetv2.NewSigner(roundPset)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, input := range sourcePset.Inputs {
|
||||||
|
if len(input.TapScriptSig) == 0 || len(input.TapLeafScript) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
partialSig := input.TapScriptSig[0]
|
||||||
|
|
||||||
|
leafHash, err := chainhash.NewHash(partialSig.LeafHash)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
preimage, err := b.getTaprootPreimage(src, i, leafHash)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
sig, err := schnorr.ParseSignature(partialSig.Signature)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
pubkey, err := schnorr.ParsePubKey(partialSig.PubKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sig.Verify(preimage, pubkey) {
|
||||||
|
return "", fmt.Errorf("invalid signature")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := roundSigner.SignTaprootInputTapscriptSig(i, partialSig); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return roundSigner.Pset.ToBase64()
|
||||||
|
}
|
||||||
|
|
||||||
func (b *txBuilder) createConnectors(
|
func (b *txBuilder) createConnectors(
|
||||||
poolTx string, payments []domain.Payment, connectorAddress string, minRelayFee uint64,
|
poolTx string, payments []domain.Payment, connectorAddress string, minRelayFee uint64,
|
||||||
) ([]*psetv2.Pset, error) {
|
) ([]*psetv2.Pset, error) {
|
||||||
@@ -733,6 +895,52 @@ func (b *txBuilder) onchainNetwork() *network.Network {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *txBuilder) getBoardingTaproot(owner, asp *secp256k1.PublicKey) (string, []byte, *taproot.TapscriptElementsProof, error) {
|
||||||
|
multisigClosure := tree.ForfeitClosure{
|
||||||
|
Pubkey: owner,
|
||||||
|
AspPubkey: asp,
|
||||||
|
}
|
||||||
|
|
||||||
|
csvClosure := tree.CSVSigClosure{
|
||||||
|
Pubkey: owner,
|
||||||
|
Seconds: uint(b.boardingExitDelay),
|
||||||
|
}
|
||||||
|
|
||||||
|
multisigLeaf, err := multisigClosure.Leaf()
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
csvLeaf, err := csvClosure.Leaf()
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tapTree := taproot.AssembleTaprootScriptTree(*multisigLeaf, *csvLeaf)
|
||||||
|
root := tapTree.RootNode.TapHash()
|
||||||
|
tapKey := taproot.ComputeTaprootOutputKey(tree.UnspendableKey(), root[:])
|
||||||
|
|
||||||
|
p2tr, err := payment.FromTweakedKey(tapKey, b.onchainNetwork(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, err := p2tr.TaprootAddress()
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tapLeaf, err := multisigClosure.Leaf()
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
leafProofIndex := tapTree.LeafProofIndex[tapLeaf.TapHash()]
|
||||||
|
leafProof := tapTree.LeafMerkleProofs[leafProofIndex]
|
||||||
|
|
||||||
|
return addr, p2tr.Script, &leafProof, nil
|
||||||
|
}
|
||||||
|
|
||||||
func extractSweepLeaf(input psetv2.Input) (sweepLeaf *psetv2.TapLeafScript, lifetime int64, err error) {
|
func extractSweepLeaf(input psetv2.Input) (sweepLeaf *psetv2.TapLeafScript, lifetime int64, err error) {
|
||||||
for _, leaf := range input.TapLeafScript {
|
for _, leaf := range input.TapLeafScript {
|
||||||
closure := &tree.CSVSigClosure{}
|
closure := &tree.CSVSigClosure{}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ const (
|
|||||||
minRelayFee = uint64(30)
|
minRelayFee = uint64(30)
|
||||||
roundLifetime = int64(1209344)
|
roundLifetime = int64(1209344)
|
||||||
unilateralExitDelay = int64(512)
|
unilateralExitDelay = int64(512)
|
||||||
|
boardingExitDelay = int64(512)
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -49,7 +50,7 @@ func TestMain(m *testing.M) {
|
|||||||
|
|
||||||
func TestBuildPoolTx(t *testing.T) {
|
func TestBuildPoolTx(t *testing.T) {
|
||||||
builder := txbuilder.NewTxBuilder(
|
builder := txbuilder.NewTxBuilder(
|
||||||
wallet, common.Liquid, roundLifetime, unilateralExitDelay,
|
wallet, common.Liquid, roundLifetime, unilateralExitDelay, boardingExitDelay,
|
||||||
)
|
)
|
||||||
|
|
||||||
fixtures, err := parsePoolTxFixtures()
|
fixtures, err := parsePoolTxFixtures()
|
||||||
@@ -60,7 +61,7 @@ func TestBuildPoolTx(t *testing.T) {
|
|||||||
t.Run("valid", func(t *testing.T) {
|
t.Run("valid", func(t *testing.T) {
|
||||||
for _, f := range fixtures.Valid {
|
for _, f := range fixtures.Valid {
|
||||||
poolTx, congestionTree, connAddr, err := builder.BuildPoolTx(
|
poolTx, congestionTree, connAddr, err := builder.BuildPoolTx(
|
||||||
pubkey, f.Payments, minRelayFee, []domain.Round{},
|
pubkey, f.Payments, []ports.BoardingInput{}, minRelayFee, []domain.Round{},
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEmpty(t, poolTx)
|
require.NotEmpty(t, poolTx)
|
||||||
@@ -81,7 +82,7 @@ func TestBuildPoolTx(t *testing.T) {
|
|||||||
t.Run("invalid", func(t *testing.T) {
|
t.Run("invalid", func(t *testing.T) {
|
||||||
for _, f := range fixtures.Invalid {
|
for _, f := range fixtures.Invalid {
|
||||||
poolTx, congestionTree, connAddr, err := builder.BuildPoolTx(
|
poolTx, congestionTree, connAddr, err := builder.BuildPoolTx(
|
||||||
pubkey, f.Payments, minRelayFee, []domain.Round{},
|
pubkey, f.Payments, []ports.BoardingInput{}, minRelayFee, []domain.Round{},
|
||||||
)
|
)
|
||||||
require.EqualError(t, err, f.ExpectedErr)
|
require.EqualError(t, err, f.ExpectedErr)
|
||||||
require.Empty(t, poolTx)
|
require.Empty(t, poolTx)
|
||||||
@@ -94,7 +95,7 @@ func TestBuildPoolTx(t *testing.T) {
|
|||||||
|
|
||||||
func TestBuildForfeitTxs(t *testing.T) {
|
func TestBuildForfeitTxs(t *testing.T) {
|
||||||
builder := txbuilder.NewTxBuilder(
|
builder := txbuilder.NewTxBuilder(
|
||||||
wallet, common.Liquid, 1209344, unilateralExitDelay,
|
wallet, common.Liquid, 1209344, unilateralExitDelay, boardingExitDelay,
|
||||||
)
|
)
|
||||||
|
|
||||||
fixtures, err := parseForfeitTxsFixtures()
|
fixtures, err := parseForfeitTxsFixtures()
|
||||||
|
|||||||
@@ -229,6 +229,16 @@ func (m *mockedWallet) MainAccountBalance(ctx context.Context) (uint64, uint64,
|
|||||||
return res, res2, args.Error(2)
|
return res, res2, args.Error(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockedWallet) GetTransaction(ctx context.Context, txid string) (string, error) {
|
||||||
|
args := m.Called(ctx, txid)
|
||||||
|
|
||||||
|
var res string
|
||||||
|
if a := args.Get(0); a != nil {
|
||||||
|
res = a.(string)
|
||||||
|
}
|
||||||
|
return res, args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
type mockedInput struct {
|
type mockedInput struct {
|
||||||
mock.Mock
|
mock.Mock
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user