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:
Louis Singer
2024-09-04 19:21:26 +02:00
committed by GitHub
parent 8cba9c9d42
commit 4da76ec88b
113 changed files with 5627 additions and 4430 deletions

View File

@@ -15,7 +15,6 @@ type ArkClient interface {
Unlock(ctx context.Context, password string) error
Lock(ctx context.Context, password string) error
Balance(ctx context.Context, computeExpiryDetails bool) (*Balance, error)
Onboard(ctx context.Context, amount uint64) (string, error)
Receive(ctx context.Context) (string, string, error)
SendOnChain(ctx context.Context, receivers []Receiver) (string, error)
SendOffChain(
@@ -26,7 +25,7 @@ type ArkClient interface {
ctx context.Context, addr string, amount uint64, withExpiryCoinselect bool,
) (string, error)
SendAsync(ctx context.Context, withExpiryCoinselect bool, receivers []Receiver) (string, error)
ClaimAsync(ctx context.Context) (string, error)
Claim(ctx context.Context) (string, error)
ListVtxos(ctx context.Context) ([]client.Vtxo, []client.Vtxo, error)
}

View File

@@ -92,14 +92,15 @@ func (a *arkClient) InitWithWallet(
}
storeData := store.StoreData{
AspUrl: args.AspUrl,
AspPubkey: aspPubkey,
WalletType: args.Wallet.GetType(),
ClientType: args.ClientType,
Network: network,
RoundLifetime: info.RoundLifetime,
UnilateralExitDelay: info.UnilateralExitDelay,
MinRelayFee: uint64(info.MinRelayFee),
AspUrl: args.AspUrl,
AspPubkey: aspPubkey,
WalletType: args.Wallet.GetType(),
ClientType: args.ClientType,
Network: network,
RoundLifetime: info.RoundLifetime,
UnilateralExitDelay: info.UnilateralExitDelay,
MinRelayFee: uint64(info.MinRelayFee),
BoardingDescriptorTemplate: info.BoardingDescriptorTemplate,
}
if err := a.store.AddData(ctx, storeData); err != nil {
return err
@@ -155,14 +156,15 @@ func (a *arkClient) Init(
}
storeData := store.StoreData{
AspUrl: args.AspUrl,
AspPubkey: aspPubkey,
WalletType: args.WalletType,
ClientType: args.ClientType,
Network: network,
RoundLifetime: info.RoundLifetime,
UnilateralExitDelay: info.UnilateralExitDelay,
MinRelayFee: uint64(info.MinRelayFee),
AspUrl: args.AspUrl,
AspPubkey: aspPubkey,
WalletType: args.WalletType,
ClientType: args.ClientType,
Network: network,
RoundLifetime: info.RoundLifetime,
UnilateralExitDelay: info.UnilateralExitDelay,
MinRelayFee: uint64(info.MinRelayFee),
BoardingDescriptorTemplate: info.BoardingDescriptorTemplate,
}
walletSvc, err := getWallet(a.store, &storeData, supportedWallets)
if err != nil {
@@ -201,12 +203,12 @@ func (a *arkClient) IsLocked(ctx context.Context) bool {
}
func (a *arkClient) Receive(ctx context.Context) (string, string, error) {
offchainAddr, onchainAddr, err := a.wallet.NewAddress(ctx, false)
offchainAddr, boardingAddr, err := a.wallet.NewAddress(ctx, false)
if err != nil {
return "", "", err
}
return offchainAddr, onchainAddr, nil
return offchainAddr, boardingAddr, nil
}
func (a *arkClient) ListVtxos(
@@ -232,14 +234,11 @@ func (a *arkClient) ListVtxos(
func (a *arkClient) ping(
ctx context.Context, paymentID string,
) func() {
_, err := a.client.Ping(ctx, paymentID)
if err != nil {
return nil
}
ticker := time.NewTicker(5 * time.Second)
go func(t *time.Ticker) {
// nolint
a.client.Ping(ctx, paymentID)
for range t.C {
// nolint
a.client.Ping(ctx, paymentID)

View File

@@ -23,11 +23,8 @@ type ASPClient interface {
ListVtxos(ctx context.Context, addr string) ([]Vtxo, []Vtxo, error)
GetRound(ctx context.Context, txID string) (*Round, error)
GetRoundByID(ctx context.Context, roundID string) (*Round, error)
Onboard(
ctx context.Context, tx, userPubkey string, congestionTree tree.CongestionTree,
) error
RegisterPayment(
ctx context.Context, inputs []VtxoKey, ephemeralPublicKey string,
ctx context.Context, inputs []Input, ephemeralKey string,
) (string, error)
ClaimPayment(
ctx context.Context, paymentID string, outputs []Output,
@@ -37,7 +34,7 @@ type ASPClient interface {
) (<-chan RoundEventChannel, error)
Ping(ctx context.Context, paymentID string) (RoundEvent, error)
FinalizePayment(
ctx context.Context, signedForfeitTxs []string,
ctx context.Context, signedForfeitTxs []string, signedRoundTx string,
) error
CreatePayment(
ctx context.Context, inputs []VtxoKey, outputs []Output,
@@ -45,6 +42,7 @@ type ASPClient interface {
CompletePayment(
ctx context.Context, signedRedeemTx string, signedUnconditionalForfeitTxs []string,
) error
GetBoardingAddress(ctx context.Context, userPubkey string) (string, error)
SendTreeNonces(
ctx context.Context, roundID, cosignerPubkey string, nonces bitcointree.TreeNonces,
) error
@@ -55,12 +53,13 @@ type ASPClient interface {
}
type Info struct {
Pubkey string
RoundLifetime int64
UnilateralExitDelay int64
RoundInterval int64
Network string
MinRelayFee int64
Pubkey string
RoundLifetime int64
UnilateralExitDelay int64
RoundInterval int64
Network string
MinRelayFee int64
BoardingDescriptorTemplate string
}
type RoundEventChannel struct {
@@ -68,11 +67,38 @@ type RoundEventChannel struct {
Err error
}
type Input interface {
GetTxID() string
GetVOut() uint32
GetDescriptor() string
}
type VtxoKey struct {
Txid string
VOut uint32
}
func (k VtxoKey) GetTxID() string {
return k.Txid
}
func (k VtxoKey) GetVOut() uint32 {
return k.VOut
}
func (k VtxoKey) GetDescriptor() string {
return ""
}
type BoardingInput struct {
VtxoKey
Descriptor string
}
func (k BoardingInput) GetDescriptor() string {
return k.Descriptor
}
type Vtxo struct {
VtxoKey
Amount uint64

View File

@@ -97,12 +97,13 @@ func (a *grpcClient) GetInfo(ctx context.Context) (*client.Info, error) {
return nil, err
}
return &client.Info{
Pubkey: resp.GetPubkey(),
RoundLifetime: resp.GetRoundLifetime(),
UnilateralExitDelay: resp.GetUnilateralExitDelay(),
RoundInterval: resp.GetRoundInterval(),
Network: resp.GetNetwork(),
MinRelayFee: resp.GetMinRelayFee(),
Pubkey: resp.GetPubkey(),
RoundLifetime: resp.GetRoundLifetime(),
UnilateralExitDelay: resp.GetUnilateralExitDelay(),
RoundInterval: resp.GetRoundInterval(),
Network: resp.GetNetwork(),
MinRelayFee: resp.GetMinRelayFee(),
BoardingDescriptorTemplate: resp.GetBoardingDescriptorTemplate(),
}, nil
}
@@ -143,20 +144,8 @@ func (a *grpcClient) GetRound(
}, nil
}
func (a *grpcClient) Onboard(
ctx context.Context, tx, userPubkey string, congestionTree tree.CongestionTree,
) error {
req := &arkv1.OnboardRequest{
BoardingTx: tx,
UserPubkey: userPubkey,
CongestionTree: treeToProto(congestionTree).parse(),
}
_, err := a.svc.Onboard(ctx, req)
return err
}
func (a *grpcClient) RegisterPayment(
ctx context.Context, inputs []client.VtxoKey, ephemeralPublicKey string,
ctx context.Context, inputs []client.Input, ephemeralPublicKey string,
) (string, error) {
req := &arkv1.RegisterPaymentRequest{
Inputs: ins(inputs).toProto(),
@@ -198,11 +187,16 @@ func (a *grpcClient) Ping(
}
func (a *grpcClient) FinalizePayment(
ctx context.Context, signedForfeitTxs []string,
ctx context.Context, signedForfeitTxs []string, signedRoundTx string,
) error {
req := &arkv1.FinalizePaymentRequest{
SignedForfeitTxs: signedForfeitTxs,
}
if len(signedRoundTx) > 0 {
req.SignedRoundTx = &signedRoundTx
}
_, err := a.svc.FinalizePayment(ctx, req)
return err
}
@@ -210,8 +204,13 @@ func (a *grpcClient) FinalizePayment(
func (a *grpcClient) CreatePayment(
ctx context.Context, inputs []client.VtxoKey, outputs []client.Output,
) (string, []string, error) {
insCast := make([]client.Input, 0, len(inputs))
for _, in := range inputs {
insCast = append(insCast, in)
}
req := &arkv1.CreatePaymentRequest{
Inputs: ins(inputs).toProto(),
Inputs: ins(insCast).toProto(),
Outputs: outs(outputs).toProto(),
}
resp, err := a.svc.CreatePayment(ctx, req)
@@ -260,6 +259,19 @@ func (a *grpcClient) GetRoundByID(
}, nil
}
func (a *grpcClient) GetBoardingAddress(
ctx context.Context, userPubkey string,
) (string, error) {
req := &arkv1.GetBoardingAddressRequest{
Pubkey: userPubkey,
}
resp, err := a.svc.GetBoardingAddress(ctx, req)
if err != nil {
return "", err
}
return resp.GetAddress(), nil
}
func (a *grpcClient) SendTreeNonces(
ctx context.Context, roundID, cosignerPubkey string, nonces bitcointree.TreeNonces,
) error {
@@ -418,8 +430,8 @@ func (v vtxo) toVtxo() client.Vtxo {
}
return client.Vtxo{
VtxoKey: client.VtxoKey{
Txid: v.GetOutpoint().GetTxid(),
VOut: v.GetOutpoint().GetVout(),
Txid: v.GetOutpoint().GetVtxoInput().GetTxid(),
VOut: v.GetOutpoint().GetVtxoInput().GetVout(),
},
Amount: v.GetReceiver().GetAmount(),
RoundTxid: v.GetPoolTxid(),
@@ -441,21 +453,35 @@ func (v vtxos) toVtxos() []client.Vtxo {
return list
}
type input client.VtxoKey
func toProtoInput(i client.Input) *arkv1.Input {
if len(i.GetDescriptor()) > 0 {
return &arkv1.Input{
Input: &arkv1.Input_BoardingInput{
BoardingInput: &arkv1.BoardingInput{
Txid: i.GetTxID(),
Vout: i.GetVOut(),
Descriptor_: i.GetDescriptor(),
},
},
}
}
func (i input) toProto() *arkv1.Input {
return &arkv1.Input{
Txid: i.Txid,
Vout: i.VOut,
Input: &arkv1.Input_VtxoInput{
VtxoInput: &arkv1.VtxoInput{
Txid: i.GetTxID(),
Vout: i.GetVOut(),
},
},
}
}
type ins []client.VtxoKey
type ins []client.Input
func (i ins) toProto() []*arkv1.Input {
list := make([]*arkv1.Input, 0, len(i))
for _, ii := range i {
list = append(list, input(ii).toProto())
list = append(list, toProtoInput(ii))
}
return list
}
@@ -496,27 +522,3 @@ func (t treeFromProto) parse() tree.CongestionTree {
return levels
}
type treeToProto tree.CongestionTree
func (t treeToProto) parse() *arkv1.Tree {
levels := make([]*arkv1.TreeLevel, 0, len(t))
for _, level := range t {
levelProto := &arkv1.TreeLevel{
Nodes: make([]*arkv1.Node, 0, len(level)),
}
for _, node := range level {
levelProto.Nodes = append(levelProto.Nodes, &arkv1.Node{
Txid: node.Txid,
Tx: node.Tx,
ParentTxid: node.ParentTxid,
})
}
levels = append(levels, levelProto)
}
return &arkv1.Tree{
Levels: levels,
}
}

View File

@@ -114,12 +114,13 @@ func (a *restClient) GetInfo(
}
return &client.Info{
Pubkey: resp.Payload.Pubkey,
RoundLifetime: int64(roundLifetime),
UnilateralExitDelay: int64(unilateralExitDelay),
RoundInterval: int64(roundInterval),
Network: resp.Payload.Network,
MinRelayFee: int64(minRelayFee),
Pubkey: resp.Payload.Pubkey,
RoundLifetime: int64(roundLifetime),
UnilateralExitDelay: int64(unilateralExitDelay),
RoundInterval: int64(roundInterval),
Network: resp.Payload.Network,
MinRelayFee: int64(minRelayFee),
BoardingDescriptorTemplate: resp.Payload.BoardingDescriptorTemplate,
}, nil
}
@@ -159,8 +160,8 @@ func (a *restClient) ListVtxos(
spendableVtxos = append(spendableVtxos, client.Vtxo{
VtxoKey: client.VtxoKey{
Txid: v.Outpoint.Txid,
VOut: uint32(v.Outpoint.Vout),
Txid: v.Outpoint.VtxoInput.Txid,
VOut: uint32(v.Outpoint.VtxoInput.Vout),
},
Amount: uint64(amount),
RoundTxid: v.PoolTxid,
@@ -191,8 +192,8 @@ func (a *restClient) ListVtxos(
spentVtxos = append(spentVtxos, client.Vtxo{
VtxoKey: client.VtxoKey{
Txid: v.Outpoint.Txid,
VOut: uint32(v.Outpoint.Vout),
Txid: v.Outpoint.VtxoInput.Txid,
VOut: uint32(v.Outpoint.VtxoInput.Vout),
},
Amount: uint64(amount),
RoundTxid: v.PoolTxid,
@@ -243,29 +244,31 @@ func (a *restClient) GetRound(
}, nil
}
func (a *restClient) Onboard(
ctx context.Context, tx, userPubkey string, congestionTree tree.CongestionTree,
) error {
body := models.V1OnboardRequest{
BoardingTx: tx,
CongestionTree: treeToProto(congestionTree).parse(),
UserPubkey: userPubkey,
}
_, err := a.svc.ArkServiceOnboard(
ark_service.NewArkServiceOnboardParams().WithBody(&body),
)
return err
}
func (a *restClient) RegisterPayment(
ctx context.Context, inputs []client.VtxoKey, ephemeralPublicKey string,
ctx context.Context, inputs []client.Input, ephemeralPublicKey string,
) (string, error) {
ins := make([]*models.V1Input, 0, len(inputs))
for _, i := range inputs {
ins = append(ins, &models.V1Input{
Txid: i.Txid,
Vout: int64(i.VOut),
})
var input *models.V1Input
if len(i.GetDescriptor()) > 0 {
input = &models.V1Input{
BoardingInput: &models.V1BoardingInput{
Txid: i.GetTxID(),
Vout: int64(i.GetVOut()),
Descriptor: i.GetDescriptor(),
},
}
} else {
input = &models.V1Input{
VtxoInput: &models.V1VtxoInput{
Txid: i.GetTxID(),
Vout: int64(i.GetVOut()),
},
}
}
ins = append(ins, input)
}
body := &models.V1RegisterPaymentRequest{
Inputs: ins,
@@ -377,13 +380,11 @@ func (a *restClient) Ping(
}
func (a *restClient) FinalizePayment(
ctx context.Context, signedForfeitTxs []string,
ctx context.Context, signedForfeitTxs []string, signedRoundTx string,
) error {
req := &arkv1.FinalizePaymentRequest{
SignedForfeitTxs: signedForfeitTxs,
}
body := models.V1FinalizePaymentRequest{
SignedForfeitTxs: req.GetSignedForfeitTxs(),
SignedForfeitTxs: signedForfeitTxs,
SignedRoundTx: signedRoundTx,
}
_, err := a.svc.ArkServiceFinalizePayment(
ark_service.NewArkServiceFinalizePaymentParams().WithBody(&body),
@@ -396,9 +397,15 @@ func (a *restClient) CreatePayment(
) (string, []string, error) {
ins := make([]*models.V1Input, 0, len(inputs))
for _, i := range inputs {
if len(i.GetDescriptor()) > 0 {
return "", nil, fmt.Errorf("boarding inputs are not allowed in create payment")
}
ins = append(ins, &models.V1Input{
Txid: i.Txid,
Vout: int64(i.VOut),
VtxoInput: &models.V1VtxoInput{
Txid: i.Txid,
Vout: int64(i.VOut),
},
})
}
outs := make([]*models.V1Output, 0, len(outputs))
@@ -477,6 +484,23 @@ func (a *restClient) GetRoundByID(
}, nil
}
func (a *restClient) GetBoardingAddress(
ctx context.Context, pubkey string,
) (string, error) {
body := models.V1GetBoardingAddressRequest{
Pubkey: pubkey,
}
resp, err := a.svc.ArkServiceGetBoardingAddress(
ark_service.NewArkServiceGetBoardingAddressParams().WithBody(&body),
)
if err != nil {
return "",
err
}
return resp.Payload.Address, nil
}
func (a *restClient) SendTreeNonces(
ctx context.Context, roundID, cosignerPubkey string, nonces bitcointree.TreeNonces,
) error {
@@ -604,25 +628,3 @@ func (t treeFromProto) parse() tree.CongestionTree {
return congestionTree
}
type treeToProto tree.CongestionTree
func (t treeToProto) parse() *models.V1Tree {
levels := make([]*models.V1TreeLevel, 0, len(t))
for _, level := range t {
nodes := make([]*models.V1Node, 0, len(level))
for _, n := range level {
nodes = append(nodes, &models.V1Node{
Txid: n.Txid,
Tx: n.Tx,
ParentTxid: n.ParentTxid,
})
}
levels = append(levels, &models.V1TreeLevel{
Nodes: nodes,
})
}
return &models.V1Tree{
Levels: levels,
}
}

View File

@@ -62,6 +62,8 @@ type ClientService interface {
ArkServiceFinalizePayment(params *ArkServiceFinalizePaymentParams, opts ...ClientOption) (*ArkServiceFinalizePaymentOK, error)
ArkServiceGetBoardingAddress(params *ArkServiceGetBoardingAddressParams, opts ...ClientOption) (*ArkServiceGetBoardingAddressOK, error)
ArkServiceGetEventStream(params *ArkServiceGetEventStreamParams, opts ...ClientOption) (*ArkServiceGetEventStreamOK, error)
ArkServiceGetInfo(params *ArkServiceGetInfoParams, opts ...ClientOption) (*ArkServiceGetInfoOK, error)
@@ -72,8 +74,6 @@ type ClientService interface {
ArkServiceListVtxos(params *ArkServiceListVtxosParams, opts ...ClientOption) (*ArkServiceListVtxosOK, error)
ArkServiceOnboard(params *ArkServiceOnboardParams, opts ...ClientOption) (*ArkServiceOnboardOK, error)
ArkServicePing(params *ArkServicePingParams, opts ...ClientOption) (*ArkServicePingOK, error)
ArkServiceRegisterPayment(params *ArkServiceRegisterPaymentParams, opts ...ClientOption) (*ArkServiceRegisterPaymentOK, error)
@@ -233,6 +233,43 @@ func (a *Client) ArkServiceFinalizePayment(params *ArkServiceFinalizePaymentPara
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
ArkServiceGetBoardingAddress ark service get boarding address API
*/
func (a *Client) ArkServiceGetBoardingAddress(params *ArkServiceGetBoardingAddressParams, opts ...ClientOption) (*ArkServiceGetBoardingAddressOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewArkServiceGetBoardingAddressParams()
}
op := &runtime.ClientOperation{
ID: "ArkService_GetBoardingAddress",
Method: "POST",
PathPattern: "/v1/boarding",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &ArkServiceGetBoardingAddressReader{formats: a.formats},
Context: params.Context,
Client: params.HTTPClient,
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
success, ok := result.(*ArkServiceGetBoardingAddressOK)
if ok {
return success, nil
}
// unexpected success response
unexpectedSuccess := result.(*ArkServiceGetBoardingAddressDefault)
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
ArkServiceGetEventStream ark service get event stream API
*/
@@ -418,43 +455,6 @@ func (a *Client) ArkServiceListVtxos(params *ArkServiceListVtxosParams, opts ...
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
ArkServiceOnboard ark service onboard API
*/
func (a *Client) ArkServiceOnboard(params *ArkServiceOnboardParams, opts ...ClientOption) (*ArkServiceOnboardOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewArkServiceOnboardParams()
}
op := &runtime.ClientOperation{
ID: "ArkService_Onboard",
Method: "POST",
PathPattern: "/v1/onboard",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &ArkServiceOnboardReader{formats: a.formats},
Context: params.Context,
Client: params.HTTPClient,
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
success, ok := result.(*ArkServiceOnboardOK)
if ok {
return success, nil
}
// unexpected success response
unexpectedSuccess := result.(*ArkServiceOnboardDefault)
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
ArkServicePing ark service ping API
*/

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -19,6 +19,9 @@ type V1FinalizePaymentRequest struct {
// Forfeit txs signed by the user.
SignedForfeitTxs []string `json:"signedForfeitTxs"`
// If payment has reverse boarding, the user must sign the associated inputs.
SignedRoundTx string `json:"signedRoundTx,omitempty"`
}
// Validate validates this v1 finalize payment request

View File

@@ -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
}

View 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"
)
// 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
}

View File

@@ -17,6 +17,9 @@ import (
// swagger:model v1GetInfoResponse
type V1GetInfoResponse struct {
// boarding descriptor template
BoardingDescriptorTemplate string `json:"boardingDescriptorTemplate,omitempty"`
// min relay fee
MinRelayFee string `json:"minRelayFee,omitempty"`

View File

@@ -8,6 +8,7 @@ package models
import (
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
@@ -17,20 +18,126 @@ import (
// swagger:model v1Input
type V1Input struct {
// txid
Txid string `json:"txid,omitempty"`
// boarding input
BoardingInput *V1BoardingInput `json:"boardingInput,omitempty"`
// vout
Vout int64 `json:"vout,omitempty"`
// vtxo input
VtxoInput *V1VtxoInput `json:"vtxoInput,omitempty"`
}
// Validate validates this v1 input
func (m *V1Input) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateBoardingInput(formats); err != nil {
res = append(res, err)
}
if err := m.validateVtxoInput(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// ContextValidate validates this v1 input based on context it is used
func (m *V1Input) validateBoardingInput(formats strfmt.Registry) error {
if swag.IsZero(m.BoardingInput) { // not required
return nil
}
if m.BoardingInput != nil {
if err := m.BoardingInput.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("boardingInput")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("boardingInput")
}
return err
}
}
return nil
}
func (m *V1Input) validateVtxoInput(formats strfmt.Registry) error {
if swag.IsZero(m.VtxoInput) { // not required
return nil
}
if m.VtxoInput != nil {
if err := m.VtxoInput.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("vtxoInput")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("vtxoInput")
}
return err
}
}
return nil
}
// ContextValidate validate this v1 input based on the context it is used
func (m *V1Input) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateBoardingInput(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateVtxoInput(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *V1Input) contextValidateBoardingInput(ctx context.Context, formats strfmt.Registry) error {
if m.BoardingInput != nil {
if swag.IsZero(m.BoardingInput) { // not required
return nil
}
if err := m.BoardingInput.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("boardingInput")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("boardingInput")
}
return err
}
}
return nil
}
func (m *V1Input) contextValidateVtxoInput(ctx context.Context, formats strfmt.Registry) error {
if m.VtxoInput != nil {
if swag.IsZero(m.VtxoInput) { // not required
return nil
}
if err := m.VtxoInput.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("vtxoInput")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("vtxoInput")
}
return err
}
}
return nil
}

View File

@@ -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
}

View File

@@ -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{}

View File

@@ -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
}

View File

@@ -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
}

View 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
}

View File

@@ -11,6 +11,7 @@ import (
"time"
"github.com/ark-network/ark/common"
"github.com/ark-network/ark/common/descriptor"
"github.com/ark-network/ark/common/tree"
"github.com/ark-network/ark/pkg/client-sdk/client"
"github.com/ark-network/ark/pkg/client-sdk/explorer"
@@ -23,12 +24,7 @@ import (
"github.com/decred/dcrd/dcrec/secp256k1/v4"
log "github.com/sirupsen/logrus"
"github.com/vulpemventures/go-elements/address"
"github.com/vulpemventures/go-elements/elementsutil"
"github.com/vulpemventures/go-elements/network"
"github.com/vulpemventures/go-elements/payment"
"github.com/vulpemventures/go-elements/psetv2"
"github.com/vulpemventures/go-elements/taproot"
"github.com/vulpemventures/go-elements/transaction"
)
type liquidReceiver struct {
@@ -139,92 +135,24 @@ func LoadCovenantClientWithWallet(
}, nil
}
func (a *covenantArkClient) Onboard(
ctx context.Context, amount uint64,
) (string, error) {
if amount <= 0 {
return "", fmt.Errorf("invalid amount to onboard %d", amount)
}
offchainAddr, _, err := a.wallet.NewAddress(ctx, false)
if err != nil {
return "", err
}
net := utils.ToElementsNetwork(a.Network)
_, userPubkey, aspPubkey, _ := common.DecodeAddress(offchainAddr)
userPubkeyStr := hex.EncodeToString(userPubkey.SerializeCompressed())
congestionTreeLeaf := tree.Receiver{
Pubkey: userPubkeyStr,
Amount: amount,
}
treeFactoryFn, sharedOutputScript, sharedOutputAmount, err := tree.CraftCongestionTree(
net.AssetID,
aspPubkey,
[]tree.Receiver{congestionTreeLeaf},
a.MinRelayFee,
a.RoundLifetime,
a.UnilateralExitDelay,
)
if err != nil {
return "", err
}
pay, err := payment.FromScript(sharedOutputScript, &net, nil)
if err != nil {
return "", err
}
addr, err := pay.TaprootAddress()
if err != nil {
return "", err
}
onchainReceiver := NewLiquidReceiver(addr, sharedOutputAmount)
pset, err := a.sendOnchain(ctx, []Receiver{onchainReceiver})
if err != nil {
return "", err
}
ptx, _ := psetv2.NewPsetFromBase64(pset)
utx, _ := ptx.UnsignedTx()
txid := utx.TxHash().String()
congestionTree, err := treeFactoryFn(psetv2.InputArgs{
Txid: txid,
TxIndex: 0,
})
if err != nil {
return "", err
}
if err := a.client.Onboard(
ctx, pset, userPubkeyStr, congestionTree,
); err != nil {
return "", err
}
return txid, nil
}
func (a *covenantArkClient) Balance(
ctx context.Context, computeVtxoExpiration bool,
) (*Balance, error) {
offchainAddrs, onchainAddrs, redeemAddrs, err := a.wallet.GetAddresses(ctx)
offchainAddrs, boardingAddrs, redeemAddrs, err := a.wallet.GetAddresses(ctx)
if err != nil {
return nil, err
}
const nbWorkers = 3
wg := &sync.WaitGroup{}
wg.Add(3 * len(offchainAddrs))
wg.Add(nbWorkers * len(offchainAddrs))
chRes := make(chan balanceRes, 3)
chRes := make(chan balanceRes, nbWorkers*len(offchainAddrs))
for i := range offchainAddrs {
offchainAddr := offchainAddrs[i]
onchainAddr := onchainAddrs[i]
boardingAddr := boardingAddrs[i]
redeemAddr := redeemAddrs[i]
go func(addr string) {
defer wg.Done()
balance, amountByExpiration, err := a.getOffchainBalance(
@@ -241,17 +169,7 @@ func (a *covenantArkClient) Balance(
}
}(offchainAddr)
go func(addr string) {
defer wg.Done()
balance, err := a.explorer.GetBalance(addr)
if err != nil {
chRes <- balanceRes{err: err}
return
}
chRes <- balanceRes{onchainSpendableBalance: balance}
}(onchainAddr)
go func(addr string) {
getDelayedBalance := func(addr string) {
defer wg.Done()
spendableBalance, lockedBalance, err := a.explorer.GetRedeemedVtxosBalance(
@@ -267,7 +185,10 @@ func (a *covenantArkClient) Balance(
onchainLockedBalance: lockedBalance,
err: err,
}
}(redeemAddr)
}
go getDelayedBalance(boardingAddr)
go getDelayedBalance(redeemAddr)
}
wg.Wait()
@@ -317,7 +238,7 @@ func (a *covenantArkClient) Balance(
}
count++
if count == 3 {
if count == nbWorkers {
break
}
}
@@ -519,7 +440,7 @@ func (a *covenantArkClient) CollaborativeRedeem(
})
}
inputs := make([]client.VtxoKey, 0, len(selectedCoins))
inputs := make([]client.Input, 0, len(selectedCoins))
for _, coin := range selectedCoins {
inputs = append(inputs, client.VtxoKey{
@@ -538,7 +459,7 @@ func (a *covenantArkClient) CollaborativeRedeem(
}
poolTxID, err := a.handleRoundStream(
ctx, paymentID, selectedCoins, receivers,
ctx, paymentID, selectedCoins, false, receivers,
)
if err != nil {
return "", err
@@ -554,8 +475,85 @@ func (a *covenantArkClient) SendAsync(
return "", fmt.Errorf("not implemented")
}
func (a *covenantArkClient) ClaimAsync(ctx context.Context) (string, error) {
return "", fmt.Errorf("not implemented")
func (a *covenantArkClient) Claim(ctx context.Context) (string, error) {
myselfOffchain, _, err := a.wallet.NewAddress(ctx, false)
if err != nil {
return "", err
}
_, mypubkey, _, err := common.DecodeAddress(myselfOffchain)
if err != nil {
return "", err
}
boardingUtxos, err := a.getClaimableBoardingUtxos(ctx)
if err != nil {
return "", err
}
var pendingBalance uint64
for _, vtxo := range boardingUtxos {
pendingBalance += vtxo.Amount
}
if pendingBalance == 0 {
return "", fmt.Errorf("no funds to claim")
}
receiver := client.Output{
Address: myselfOffchain,
Amount: pendingBalance,
}
desc := strings.ReplaceAll(a.BoardingDescriptorTemplate, "USER", hex.EncodeToString(schnorr.SerializePubKey(mypubkey)))
return a.selfTransferAllPendingPayments(ctx, boardingUtxos, receiver, desc)
}
func (a *covenantArkClient) getClaimableBoardingUtxos(ctx context.Context) ([]explorer.Utxo, error) {
offchainAddrs, boardingAddrs, _, err := a.wallet.GetAddresses(ctx)
if err != nil {
return nil, err
}
claimable := make([]explorer.Utxo, 0)
now := time.Now()
_, myPubkey, _, err := common.DecodeAddress(offchainAddrs[0])
if err != nil {
return nil, err
}
myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(myPubkey))
descriptorStr := strings.ReplaceAll(
a.BoardingDescriptorTemplate, "USER", myPubkeyStr,
)
desc, err := descriptor.ParseTaprootDescriptor(descriptorStr)
if err != nil {
return nil, err
}
_, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc)
if err != nil {
return nil, err
}
for _, addr := range boardingAddrs {
boardingUtxos, err := a.explorer.GetUtxos(addr)
if err != nil {
return nil, err
}
for _, utxo := range boardingUtxos {
u := utxo.ToUtxo(boardingTimeout)
if u.SpendableAt.Before(now) {
continue
}
claimable = append(claimable, u)
}
}
return claimable, nil
}
func (a *covenantArkClient) sendOnchain(
@@ -599,14 +597,14 @@ func (a *covenantArkClient) sendOnchain(
}
}
utxos, delayedUtxos, change, err := a.coinSelectOnchain(
utxos, change, err := a.coinSelectOnchain(
ctx, targetAmount, nil,
)
if err != nil {
return "", err
}
if err := a.addInputs(ctx, updater, utxos, delayedUtxos, net); err != nil {
if err := a.addInputs(ctx, updater, utxos); err != nil {
return "", err
}
@@ -649,14 +647,14 @@ func (a *covenantArkClient) sendOnchain(
updater.Pset.Outputs = updater.Pset.Outputs[:len(updater.Pset.Outputs)-1]
}
// reselect the difference
selected, delayedSelected, newChange, err := a.coinSelectOnchain(
ctx, feeAmount-change, append(utxos, delayedUtxos...),
selected, newChange, err := a.coinSelectOnchain(
ctx, feeAmount-change, utxos,
)
if err != nil {
return "", err
}
if err := a.addInputs(ctx, updater, selected, delayedSelected, net); err != nil {
if err := a.addInputs(ctx, updater, selected); err != nil {
return "", err
}
@@ -787,7 +785,7 @@ func (a *covenantArkClient) sendOffchain(
receiversOutput = append(receiversOutput, changeReceiver)
}
inputs := make([]client.VtxoKey, 0, len(selectedCoins))
inputs := make([]client.Input, 0, len(selectedCoins))
for _, coin := range selectedCoins {
inputs = append(inputs, client.VtxoKey{
Txid: coin.Txid,
@@ -811,7 +809,7 @@ func (a *covenantArkClient) sendOffchain(
log.Infof("payment registered with id: %s", paymentID)
poolTxID, err := a.handleRoundStream(
ctx, paymentID, selectedCoins, receiversOutput,
ctx, paymentID, selectedCoins, false, receiversOutput,
)
if err != nil {
return "", err
@@ -821,126 +819,60 @@ func (a *covenantArkClient) sendOffchain(
}
func (a *covenantArkClient) addInputs(
ctx context.Context, updater *psetv2.Updater, utxos, delayedUtxos []explorer.Utxo, net network.Network,
ctx context.Context,
updater *psetv2.Updater,
utxos []explorer.Utxo,
) error {
offchainAddr, onchainAddr, err := a.wallet.NewAddress(ctx, false)
// TODO works only with single-key wallet
offchain, _, err := a.wallet.NewAddress(ctx, false)
if err != nil {
return err
}
_, userPubkey, aspPubkey, _ := common.DecodeAddress(offchainAddr)
changeScript, err := address.ToOutputScript(onchainAddr)
_, userPubkey, aspPubkey, err := common.DecodeAddress(offchain)
if err != nil {
return err
}
for _, utxo := range utxos {
sequence, err := utxo.Sequence()
if err != nil {
return err
}
if err := updater.AddInputs([]psetv2.InputArgs{
{
Txid: utxo.Txid,
TxIndex: utxo.Vout,
Txid: utxo.Txid,
TxIndex: utxo.Vout,
Sequence: sequence,
},
}); err != nil {
return err
}
assetID, err := elementsutil.AssetHashToBytes(utxo.Asset)
if err != nil {
return err
}
value, err := elementsutil.ValueToBytes(utxo.Amount)
if err != nil {
return err
}
witnessUtxo := transaction.TxOutput{
Asset: assetID,
Value: value,
Script: changeScript,
Nonce: []byte{0x00},
}
if err := updater.AddInWitnessUtxo(
len(updater.Pset.Inputs)-1, &witnessUtxo,
); err != nil {
return err
}
}
if len(delayedUtxos) > 0 {
_, leafProof, script, _, err := tree.ComputeVtxoTaprootScript(
userPubkey, aspPubkey, uint(a.UnilateralExitDelay), net,
_, leafProof, _, _, err := tree.ComputeVtxoTaprootScript(
userPubkey, aspPubkey, utxo.Delay, utils.ToElementsNetwork(a.Network),
)
if err != nil {
return err
}
for _, utxo := range delayedUtxos {
if err := a.addVtxoInput(
updater,
psetv2.InputArgs{
Txid: utxo.Txid,
TxIndex: utxo.Vout,
},
uint(a.UnilateralExitDelay),
leafProof,
); err != nil {
return err
}
inputIndex := len(updater.Pset.Inputs) - 1
assetID, err := elementsutil.AssetHashToBytes(utxo.Asset)
if err != nil {
return err
}
value, err := elementsutil.ValueToBytes(utxo.Amount)
if err != nil {
return err
}
witnessUtxo := transaction.NewTxOutput(assetID, value, script)
if err := updater.AddInWitnessUtxo(
len(updater.Pset.Inputs)-1, witnessUtxo,
); err != nil {
return err
}
if err := updater.AddInTapLeafScript(inputIndex, psetv2.NewTapLeafScript(*leafProof, tree.UnspendableKey())); err != nil {
return err
}
}
return nil
}
func (a *covenantArkClient) addVtxoInput(
updater *psetv2.Updater, inputArgs psetv2.InputArgs, exitDelay uint,
tapLeafProof *taproot.TapscriptElementsProof,
) error {
sequence, err := common.BIP68EncodeAsNumber(exitDelay)
if err != nil {
return nil
}
nextInputIndex := len(updater.Pset.Inputs)
if err := updater.AddInputs([]psetv2.InputArgs{inputArgs}); err != nil {
return err
}
updater.Pset.Inputs[nextInputIndex].Sequence = sequence
return updater.AddInTapLeafScript(
nextInputIndex,
psetv2.NewTapLeafScript(
*tapLeafProof,
tree.UnspendableKey(),
),
)
}
func (a *covenantArkClient) handleRoundStream(
ctx context.Context,
paymentID string, vtxosToSign []client.Vtxo, receivers []client.Output,
paymentID string,
vtxosToSign []client.Vtxo,
mustSignRoundTx bool,
receivers []client.Output,
) (string, error) {
eventsCh, err := a.client.GetEventStream(ctx, paymentID)
if err != nil {
@@ -972,20 +904,20 @@ func (a *covenantArkClient) handleRoundStream(
pingStop()
log.Info("a round finalization started")
signedForfeitTxs, err := a.handleRoundFinalization(
ctx, event.(client.RoundFinalizationEvent), vtxosToSign, receivers,
signedForfeitTxs, signedRoundTx, err := a.handleRoundFinalization(
ctx, event.(client.RoundFinalizationEvent), vtxosToSign, mustSignRoundTx, receivers,
)
if err != nil {
return "", err
}
if len(signedForfeitTxs) <= 0 {
if len(signedForfeitTxs) <= 0 && len(vtxosToSign) > 0 {
log.Info("no forfeit txs to sign, waiting for the next round")
continue
}
log.Info("finalizing payment... ")
if err := a.client.FinalizePayment(ctx, signedForfeitTxs); err != nil {
if err := a.client.FinalizePayment(ctx, signedForfeitTxs, signedRoundTx); err != nil {
return "", err
}
@@ -998,15 +930,29 @@ func (a *covenantArkClient) handleRoundStream(
func (a *covenantArkClient) handleRoundFinalization(
ctx context.Context, event client.RoundFinalizationEvent,
vtxos []client.Vtxo, receivers []client.Output,
) ([]string, error) {
if err := a.validateCongestionTree(event, receivers); err != nil {
return nil, fmt.Errorf("failed to verify congestion tree: %s", err)
vtxos []client.Vtxo, mustSignRoundTx bool, receivers []client.Output,
) (signedForfeits []string, signedRoundTx string, err error) {
if err = a.validateCongestionTree(event, receivers); err != nil {
return
}
return a.loopAndSign(
ctx, event.ForfeitTxs, vtxos, event.Connectors,
)
if len(vtxos) > 0 {
signedForfeits, err = a.loopAndSign(
ctx, event.ForfeitTxs, vtxos, event.Connectors,
)
if err != nil {
return
}
}
if mustSignRoundTx {
signedRoundTx, err = a.wallet.SignTransaction(ctx, a.explorer, event.Tx)
if err != nil {
return
}
}
return
}
func (a *covenantArkClient) validateCongestionTree(
@@ -1197,23 +1143,49 @@ func (a *covenantArkClient) signForfeitTx(
func (a *covenantArkClient) coinSelectOnchain(
ctx context.Context, targetAmount uint64, exclude []explorer.Utxo,
) ([]explorer.Utxo, []explorer.Utxo, uint64, error) {
offchainAddrs, onchainAddrs, _, err := a.wallet.GetAddresses(ctx)
) ([]explorer.Utxo, uint64, error) {
offchainAddrs, boardingAddrs, redemptionAddrs, err := a.wallet.GetAddresses(ctx)
if err != nil {
return nil, nil, 0, err
return nil, 0, err
}
net := utils.ToElementsNetwork(a.Network)
_, myPubkey, _, err := common.DecodeAddress(offchainAddrs[0])
if err != nil {
return nil, 0, err
}
myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(myPubkey))
descriptorStr := strings.ReplaceAll(
a.BoardingDescriptorTemplate, "USER", myPubkeyStr,
)
desc, err := descriptor.ParseTaprootDescriptor(descriptorStr)
if err != nil {
return nil, 0, err
}
_, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc)
if err != nil {
return nil, 0, err
}
now := time.Now()
fetchedUtxos := make([]explorer.Utxo, 0)
for _, onchainAddr := range onchainAddrs {
utxos, err := a.explorer.GetUtxos(onchainAddr)
for _, addr := range boardingAddrs {
utxos, err := a.explorer.GetUtxos(addr)
if err != nil {
return nil, nil, 0, err
return nil, 0, err
}
for _, utxo := range utxos {
u := utxo.ToUtxo(boardingTimeout)
if u.SpendableAt.Before(now) {
fetchedUtxos = append(fetchedUtxos, u)
}
}
fetchedUtxos = append(fetchedUtxos, utxos...)
}
utxos := make([]explorer.Utxo, 0)
selected := make([]explorer.Utxo, 0)
selectedAmount := uint64(0)
for _, utxo := range fetchedUtxos {
if selectedAmount >= targetAmount {
@@ -1226,61 +1198,51 @@ func (a *covenantArkClient) coinSelectOnchain(
}
}
utxos = append(utxos, utxo)
selected = append(selected, utxo)
selectedAmount += utxo.Amount
}
if selectedAmount >= targetAmount {
return utxos, nil, selectedAmount - targetAmount, nil
return selected, selectedAmount - targetAmount, nil
}
fetchedUtxos = make([]explorer.Utxo, 0)
for _, offchainAddr := range offchainAddrs {
_, userPubkey, aspPubkey, _ := common.DecodeAddress(offchainAddr)
_, _, _, addr, err := tree.ComputeVtxoTaprootScript(
userPubkey, aspPubkey, uint(a.UnilateralExitDelay), net,
)
for _, addr := range redemptionAddrs {
utxos, err := a.explorer.GetUtxos(addr)
if err != nil {
return nil, nil, 0, err
return nil, 0, err
}
utxos, err = a.explorer.GetUtxos(addr)
if err != nil {
return nil, nil, 0, err
for _, utxo := range utxos {
u := utxo.ToUtxo(uint(a.UnilateralExitDelay))
if u.SpendableAt.Before(now) {
fetchedUtxos = append(fetchedUtxos, u)
}
}
fetchedUtxos = append(fetchedUtxos, utxos...)
}
delayedUtxos := make([]explorer.Utxo, 0)
for _, utxo := range fetchedUtxos {
if selectedAmount >= targetAmount {
break
}
availableAt := time.Unix(utxo.Status.Blocktime, 0).Add(
time.Duration(a.UnilateralExitDelay) * time.Second,
)
if availableAt.After(time.Now()) {
continue
}
for _, excluded := range exclude {
if utxo.Txid == excluded.Txid && utxo.Vout == excluded.Vout {
continue
}
}
delayedUtxos = append(delayedUtxos, utxo)
selected = append(selected, utxo)
selectedAmount += utxo.Amount
}
if selectedAmount < targetAmount {
return nil, nil, 0, fmt.Errorf(
return nil, 0, fmt.Errorf(
"not enough funds to cover amount %d", targetAmount,
)
}
return utxos, delayedUtxos, selectedAmount - targetAmount, nil
return selected, selectedAmount - targetAmount, nil
}
func (a *covenantArkClient) getRedeemBranches(
@@ -1373,3 +1335,39 @@ func (a *covenantArkClient) getVtxos(
return vtxos, nil
}
func (a *covenantArkClient) selfTransferAllPendingPayments(
ctx context.Context, boardingUtxo []explorer.Utxo, myself client.Output, boardingDescriptor string,
) (string, error) {
inputs := make([]client.Input, 0, len(boardingUtxo))
for _, utxo := range boardingUtxo {
inputs = append(inputs, client.BoardingInput{
VtxoKey: client.VtxoKey{
Txid: utxo.Txid,
VOut: utxo.Vout,
},
Descriptor: boardingDescriptor,
})
}
outputs := []client.Output{myself}
paymentID, err := a.client.RegisterPayment(ctx, inputs, "") // ephemeralPublicKey is not required for covenant
if err != nil {
return "", err
}
if err := a.client.ClaimPayment(ctx, paymentID, outputs); err != nil {
return "", err
}
roundTxid, err := a.handleRoundStream(
ctx, paymentID, make([]client.Vtxo, 0), len(boardingUtxo) > 0, outputs,
)
if err != nil {
return "", err
}
return roundTxid, nil
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/ark-network/ark/common"
"github.com/ark-network/ark/common/bitcointree"
"github.com/ark-network/ark/common/descriptor"
"github.com/ark-network/ark/common/tree"
"github.com/ark-network/ark/pkg/client-sdk/client"
"github.com/ark-network/ark/pkg/client-sdk/explorer"
@@ -22,7 +23,6 @@ import (
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
@@ -139,176 +139,24 @@ func LoadCovenantlessClientWithWallet(
}, nil
}
func (a *covenantlessArkClient) Onboard(
ctx context.Context, amount uint64,
) (string, error) {
if amount <= 0 {
return "", fmt.Errorf("invalid amount to onboard %d", amount)
}
offchainAddr, _, err := a.wallet.NewAddress(ctx, false)
if err != nil {
return "", err
}
_, userPubkey, aspPubkey, _ := common.DecodeAddress(offchainAddr)
userPubkeyStr := hex.EncodeToString(userPubkey.SerializeCompressed())
congestionTreeLeaf := bitcointree.Receiver{
Pubkey: userPubkeyStr,
Amount: amount,
}
leaves := []bitcointree.Receiver{congestionTreeLeaf}
ephemeralKey, err := secp256k1.GeneratePrivateKey()
if err != nil {
return "", err
}
cosigners := []*secp256k1.PublicKey{ephemeralKey.PubKey()} // TODO asp as cosigner
sharedOutputScript, sharedOutputAmount, err := bitcointree.CraftSharedOutput(
cosigners,
aspPubkey,
leaves,
a.MinRelayFee,
a.RoundLifetime,
a.UnilateralExitDelay,
)
if err != nil {
return "", err
}
netParams := utils.ToBitcoinNetwork(a.Network)
address, err := btcutil.NewAddressTaproot(sharedOutputScript[2:], &netParams)
if err != nil {
return "", err
}
onchainReceiver := NewBitcoinReceiver(
address.EncodeAddress(), uint64(sharedOutputAmount),
)
partialTx, err := a.sendOnchain(ctx, []Receiver{onchainReceiver})
if err != nil {
return "", err
}
ptx, err := psbt.NewFromRawBytes(strings.NewReader(partialTx), true)
if err != nil {
return "", err
}
txid := ptx.UnsignedTx.TxHash().String()
congestionTree, err := bitcointree.CraftCongestionTree(
&wire.OutPoint{
Hash: ptx.UnsignedTx.TxHash(),
Index: 0,
},
cosigners,
aspPubkey,
leaves,
a.MinRelayFee,
a.RoundLifetime,
a.UnilateralExitDelay,
)
if err != nil {
return "", err
}
sweepClosure := bitcointree.CSVSigClosure{
Pubkey: aspPubkey,
Seconds: uint(a.RoundLifetime),
}
sweepTapLeaf, err := sweepClosure.Leaf()
if err != nil {
return "", err
}
sweepTapTree := txscript.AssembleTaprootScriptTree(*sweepTapLeaf)
root := sweepTapTree.RootNode.TapHash()
signer := bitcointree.NewTreeSignerSession(
ephemeralKey,
congestionTree,
int64(a.MinRelayFee),
root.CloneBytes(),
)
nonces, err := signer.GetNonces() // TODO send nonces to ASP
if err != nil {
return "", err
}
coordinator, err := bitcointree.NewTreeCoordinatorSession(
congestionTree,
int64(a.MinRelayFee),
root.CloneBytes(),
cosigners,
)
if err != nil {
return "", err
}
if err := coordinator.AddNonce(ephemeralKey.PubKey(), nonces); err != nil {
return "", err
}
aggregatedNonces, err := coordinator.AggregateNonces()
if err != nil {
return "", err
}
if err := signer.SetKeys(cosigners); err != nil {
return "", err
}
if err := signer.SetAggregatedNonces(aggregatedNonces); err != nil {
return "", err
}
sigs, err := signer.Sign()
if err != nil {
return "", err
}
if err := coordinator.AddSig(ephemeralKey.PubKey(), sigs); err != nil {
return "", err
}
signedTree, err := coordinator.SignTree()
if err != nil {
return "", err
}
if err := a.client.Onboard(
ctx, partialTx, userPubkeyStr, signedTree,
); err != nil {
return "", err
}
return txid, nil
}
func (a *covenantlessArkClient) Balance(
ctx context.Context, computeVtxoExpiration bool,
) (*Balance, error) {
offchainAddrs, onchainAddrs, redeemAddrs, err := a.wallet.GetAddresses(ctx)
offchainAddrs, boardingAddrs, redeemAddrs, err := a.wallet.GetAddresses(ctx)
if err != nil {
return nil, err
}
const nbWorkers = 3
wg := &sync.WaitGroup{}
wg.Add(3 * len(offchainAddrs))
wg.Add(nbWorkers * len(offchainAddrs))
chRes := make(chan balanceRes, 3)
chRes := make(chan balanceRes, nbWorkers*len(offchainAddrs))
for i := range offchainAddrs {
offchainAddr := offchainAddrs[i]
onchainAddr := onchainAddrs[i]
boardingAddr := boardingAddrs[i]
redeemAddr := redeemAddrs[i]
go func(addr string) {
defer wg.Done()
balance, amountByExpiration, err := a.getOffchainBalance(
@@ -325,17 +173,7 @@ func (a *covenantlessArkClient) Balance(
}
}(offchainAddr)
go func(addr string) {
defer wg.Done()
balance, err := a.explorer.GetBalance(addr)
if err != nil {
chRes <- balanceRes{err: err}
return
}
chRes <- balanceRes{onchainSpendableBalance: balance}
}(onchainAddr)
go func(addr string) {
getDelayedBalance := func(addr string) {
defer wg.Done()
spendableBalance, lockedBalance, err := a.explorer.GetRedeemedVtxosBalance(
@@ -351,7 +189,10 @@ func (a *covenantlessArkClient) Balance(
onchainLockedBalance: lockedBalance,
err: err,
}
}(redeemAddr)
}
go getDelayedBalance(boardingAddr)
go getDelayedBalance(redeemAddr)
}
wg.Wait()
@@ -401,7 +242,7 @@ func (a *covenantlessArkClient) Balance(
}
count++
if count == 3 {
if count == nbWorkers {
break
}
}
@@ -603,7 +444,7 @@ func (a *covenantlessArkClient) CollaborativeRedeem(
})
}
inputs := make([]client.VtxoKey, 0, len(selectedCoins))
inputs := make([]client.Input, 0, len(selectedCoins))
for _, coin := range selectedCoins {
inputs = append(inputs, client.VtxoKey{
@@ -631,7 +472,7 @@ func (a *covenantlessArkClient) CollaborativeRedeem(
}
poolTxID, err := a.handleRoundStream(
ctx, paymentID, selectedCoins, receivers, roundEphemeralKey,
ctx, paymentID, selectedCoins, false, receivers, roundEphemeralKey,
)
if err != nil {
return "", err
@@ -751,9 +592,7 @@ func (a *covenantlessArkClient) SendAsync(
return signedRedeemTx, nil
}
func (a *covenantlessArkClient) ClaimAsync(
ctx context.Context,
) (string, error) {
func (a *covenantlessArkClient) Claim(ctx context.Context) (string, error) {
myselfOffchain, _, err := a.wallet.NewAddress(ctx, false)
if err != nil {
return "", err
@@ -764,10 +603,23 @@ func (a *covenantlessArkClient) ClaimAsync(
return "", err
}
_, mypubkey, _, err := common.DecodeAddress(myselfOffchain)
if err != nil {
return "", err
}
boardingUtxos, err := a.getClaimableBoardingUtxos(ctx)
if err != nil {
return "", err
}
var pendingBalance uint64
for _, vtxo := range pendingVtxos {
pendingBalance += vtxo.Amount
}
for _, vtxo := range boardingUtxos {
pendingBalance += vtxo.Amount
}
if pendingBalance == 0 {
return "", nil
}
@@ -776,7 +628,9 @@ func (a *covenantlessArkClient) ClaimAsync(
Address: myselfOffchain,
Amount: pendingBalance,
}
return a.selfTransferAllPendingPayments(ctx, pendingVtxos, receiver)
desc := strings.ReplaceAll(a.BoardingDescriptorTemplate, "USER", hex.EncodeToString(schnorr.SerializePubKey(mypubkey)))
return a.selfTransferAllPendingPayments(ctx, pendingVtxos, boardingUtxos, receiver, desc)
}
func (a *covenantlessArkClient) sendOnchain(
@@ -822,14 +676,14 @@ func (a *covenantlessArkClient) sendOnchain(
updater.Upsbt.Outputs = append(updater.Upsbt.Outputs, psbt.POutput{})
}
utxos, delayedUtxos, change, err := a.coinSelectOnchain(
utxos, change, err := a.coinSelectOnchain(
ctx, targetAmount, nil,
)
if err != nil {
return "", err
}
if err := a.addInputs(ctx, updater, utxos, delayedUtxos, &netParams); err != nil {
if err := a.addInputs(ctx, updater, utxos); err != nil {
return "", err
}
@@ -869,14 +723,14 @@ func (a *covenantlessArkClient) sendOnchain(
updater.Upsbt.UnsignedTx.TxOut = updater.Upsbt.UnsignedTx.TxOut[:len(updater.Upsbt.UnsignedTx.TxOut)-1]
}
// reselect the difference
selected, delayedSelected, newChange, err := a.coinSelectOnchain(
ctx, feeAmount-change, append(utxos, delayedUtxos...),
selected, newChange, err := a.coinSelectOnchain(
ctx, feeAmount-change, utxos,
)
if err != nil {
return "", err
}
if err := a.addInputs(ctx, updater, selected, delayedSelected, &netParams); err != nil {
if err := a.addInputs(ctx, updater, selected); err != nil {
return "", err
}
@@ -995,7 +849,7 @@ func (a *covenantlessArkClient) sendOffchain(
receiversOutput = append(receiversOutput, changeReceiver)
}
inputs := make([]client.VtxoKey, 0, len(selectedCoins))
inputs := make([]client.Input, 0, len(selectedCoins))
for _, coin := range selectedCoins {
inputs = append(inputs, client.VtxoKey{
Txid: coin.Txid,
@@ -1024,7 +878,7 @@ func (a *covenantlessArkClient) sendOffchain(
log.Infof("payment registered with id: %s", paymentID)
poolTxID, err := a.handleRoundStream(
ctx, paymentID, selectedCoins, receiversOutput, roundEphemeralKey,
ctx, paymentID, selectedCoins, false, receiversOutput, roundEphemeralKey,
)
if err != nil {
return "", err
@@ -1034,16 +888,17 @@ func (a *covenantlessArkClient) sendOffchain(
}
func (a *covenantlessArkClient) addInputs(
ctx context.Context, updater *psbt.Updater,
utxos, delayedUtxos []explorer.Utxo, net *chaincfg.Params,
ctx context.Context,
updater *psbt.Updater,
utxos []explorer.Utxo,
) error {
offchainAddr, onchainAddr, err := a.wallet.NewAddress(ctx, false)
// TODO works only with single-key wallet
offchain, _, err := a.wallet.NewAddress(ctx, false)
if err != nil {
return err
}
addr, _ := btcutil.DecodeAddress(onchainAddr, net)
changeScript, err := txscript.PayToAddrScript(addr)
_, userPubkey, aspPubkey, err := common.DecodeAddress(offchain)
if err != nil {
return err
}
@@ -1054,107 +909,41 @@ func (a *covenantlessArkClient) addInputs(
return err
}
sequence, err := utxo.Sequence()
if err != nil {
return err
}
updater.Upsbt.UnsignedTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: wire.OutPoint{
Hash: *previousHash,
Index: utxo.Vout,
},
Sequence: sequence,
})
updater.Upsbt.Inputs = append(updater.Upsbt.Inputs, psbt.PInput{})
if err := updater.AddInWitnessUtxo(
&wire.TxOut{
Value: int64(utxo.Amount),
PkScript: changeScript,
},
len(updater.Upsbt.UnsignedTx.TxIn)-1,
); err != nil {
return err
}
}
if len(delayedUtxos) > 0 {
_, userPubkey, aspPubkey, _ := common.DecodeAddress(offchainAddr)
vtxoTapKey, leafProof, err := bitcointree.ComputeVtxoTaprootScript(
userPubkey, aspPubkey, uint(a.UnilateralExitDelay),
_, leafProof, err := bitcointree.ComputeVtxoTaprootScript(
userPubkey, aspPubkey, utxo.Delay,
)
if err != nil {
return err
}
p2tr, err := btcutil.NewAddressTaproot(schnorr.SerializePubKey(vtxoTapKey), net)
controlBlock := leafProof.ToControlBlock(bitcointree.UnspendableKey())
controlBlockBytes, err := controlBlock.ToBytes()
if err != nil {
return err
}
script, err := txscript.PayToAddrScript(p2tr)
if err != nil {
return err
}
for _, utxo := range delayedUtxos {
previousHash, err := chainhash.NewHashFromStr(utxo.Txid)
if err != nil {
return err
}
if err := a.addVtxoInput(
updater,
&wire.OutPoint{
Hash: *previousHash,
Index: utxo.Vout,
updater.Upsbt.Inputs = append(updater.Upsbt.Inputs, psbt.PInput{
TaprootLeafScript: []*psbt.TaprootTapLeafScript{
{
ControlBlock: controlBlockBytes,
Script: leafProof.Script,
LeafVersion: leafProof.LeafVersion,
},
uint(a.UnilateralExitDelay),
leafProof,
); err != nil {
return err
}
if err := updater.AddInWitnessUtxo(
&wire.TxOut{
Value: int64(utxo.Amount),
PkScript: script,
},
len(updater.Upsbt.Inputs)-1,
); err != nil {
return err
}
}
}
return nil
}
func (a *covenantlessArkClient) addVtxoInput(
updater *psbt.Updater, inputArgs *wire.OutPoint, exitDelay uint,
tapLeafProof *txscript.TapscriptProof,
) error {
sequence, err := common.BIP68EncodeAsNumber(exitDelay)
if err != nil {
return nil
}
nextInputIndex := len(updater.Upsbt.Inputs)
updater.Upsbt.UnsignedTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: *inputArgs,
Sequence: sequence,
})
updater.Upsbt.Inputs = append(updater.Upsbt.Inputs, psbt.PInput{})
controlBlock := tapLeafProof.ToControlBlock(bitcointree.UnspendableKey())
controlBlockBytes, err := controlBlock.ToBytes()
if err != nil {
return err
}
updater.Upsbt.Inputs[nextInputIndex].TaprootLeafScript = []*psbt.TaprootTapLeafScript{
{
ControlBlock: controlBlockBytes,
Script: tapLeafProof.Script,
LeafVersion: tapLeafProof.LeafVersion,
},
},
})
}
return nil
@@ -1164,6 +953,7 @@ func (a *covenantlessArkClient) handleRoundStream(
ctx context.Context,
paymentID string,
vtxosToSign []client.Vtxo,
mustSignRoundTx bool,
receivers []client.Output,
roundEphemeralKey *secp256k1.PrivateKey,
) (string, error) {
@@ -1218,20 +1008,20 @@ func (a *covenantlessArkClient) handleRoundStream(
pingStop()
log.Info("a round finalization started")
signedForfeitTxs, err := a.handleRoundFinalization(
ctx, event.(client.RoundFinalizationEvent), vtxosToSign, receivers,
signedForfeitTxs, signedRoundTx, err := a.handleRoundFinalization(
ctx, event.(client.RoundFinalizationEvent), vtxosToSign, mustSignRoundTx, receivers,
)
if err != nil {
return "", err
}
if len(signedForfeitTxs) <= 0 {
if len(signedForfeitTxs) <= 0 && len(vtxosToSign) > 0 {
log.Info("no forfeit txs to sign, waiting for the next round")
continue
}
log.Info("finalizing payment... ")
if err := a.client.FinalizePayment(ctx, signedForfeitTxs); err != nil {
if err := a.client.FinalizePayment(ctx, signedForfeitTxs, signedRoundTx); err != nil {
return "", err
}
@@ -1311,15 +1101,29 @@ func (a *covenantlessArkClient) handleRoundSigningNoncesGenerated(
func (a *covenantlessArkClient) handleRoundFinalization(
ctx context.Context, event client.RoundFinalizationEvent,
vtxos []client.Vtxo, receivers []client.Output,
) ([]string, error) {
vtxos []client.Vtxo, mustSignRoundTx bool, receivers []client.Output,
) (signedForfeits []string, signedRoundTx string, err error) {
if err := a.validateCongestionTree(event, receivers); err != nil {
return nil, fmt.Errorf("failed to verify congestion tree: %s", err)
return nil, "", fmt.Errorf("failed to verify congestion tree: %s", err)
}
return a.loopAndSign(
ctx, event.ForfeitTxs, vtxos, event.Connectors,
)
if len(vtxos) > 0 {
signedForfeits, err = a.loopAndSign(
ctx, event.ForfeitTxs, vtxos, event.Connectors,
)
if err != nil {
return
}
}
if mustSignRoundTx {
signedRoundTx, err = a.wallet.SignTransaction(ctx, a.explorer, event.Tx)
if err != nil {
return
}
}
return
}
func (a *covenantlessArkClient) validateCongestionTree(
@@ -1516,24 +1320,49 @@ func (a *covenantlessArkClient) loopAndSign(
func (a *covenantlessArkClient) coinSelectOnchain(
ctx context.Context, targetAmount uint64, exclude []explorer.Utxo,
) ([]explorer.Utxo, []explorer.Utxo, uint64, error) {
offchainAddrs, onchainAddrs, _, err := a.wallet.GetAddresses(ctx)
) ([]explorer.Utxo, uint64, error) {
offchainAddrs, boardingAddrs, redemptionAddrs, err := a.wallet.GetAddresses(ctx)
if err != nil {
return nil, nil, 0, err
return nil, 0, err
}
net := utils.ToBitcoinNetwork(a.Network)
_, myPubkey, _, err := common.DecodeAddress(offchainAddrs[0])
if err != nil {
return nil, 0, err
}
myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(myPubkey))
descriptorStr := strings.ReplaceAll(
a.BoardingDescriptorTemplate, "USER", myPubkeyStr,
)
desc, err := descriptor.ParseTaprootDescriptor(descriptorStr)
if err != nil {
return nil, 0, err
}
_, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc)
if err != nil {
return nil, 0, err
}
now := time.Now()
fetchedUtxos := make([]explorer.Utxo, 0)
for _, onchainAddr := range onchainAddrs {
utxos, err := a.explorer.GetUtxos(onchainAddr)
for _, addr := range boardingAddrs {
utxos, err := a.explorer.GetUtxos(addr)
if err != nil {
return nil, nil, 0, err
return nil, 0, err
}
for _, utxo := range utxos {
u := utxo.ToUtxo(boardingTimeout)
if u.SpendableAt.Before(now) {
fetchedUtxos = append(fetchedUtxos, u)
}
}
fetchedUtxos = append(fetchedUtxos, utxos...)
}
utxos := make([]explorer.Utxo, 0)
selected := make([]explorer.Utxo, 0)
selectedAmount := uint64(0)
for _, utxo := range fetchedUtxos {
if selectedAmount >= targetAmount {
@@ -1546,70 +1375,51 @@ func (a *covenantlessArkClient) coinSelectOnchain(
}
}
utxos = append(utxos, utxo)
selected = append(selected, utxo)
selectedAmount += utxo.Amount
}
if selectedAmount >= targetAmount {
return utxos, nil, selectedAmount - targetAmount, nil
return selected, selectedAmount - targetAmount, nil
}
fetchedUtxos = make([]explorer.Utxo, 0)
for _, offchainAddr := range offchainAddrs {
_, userPubkey, aspPubkey, _ := common.DecodeAddress(offchainAddr)
vtxoTapKey, _, err := bitcointree.ComputeVtxoTaprootScript(
userPubkey, aspPubkey, uint(a.UnilateralExitDelay),
)
for _, addr := range redemptionAddrs {
utxos, err := a.explorer.GetUtxos(addr)
if err != nil {
return nil, nil, 0, err
}
p2tr, err := btcutil.NewAddressTaproot(
schnorr.SerializePubKey(vtxoTapKey), &net,
)
if err != nil {
return nil, nil, 0, err
return nil, 0, err
}
addr := p2tr.EncodeAddress()
utxos, err = a.explorer.GetUtxos(addr)
if err != nil {
return nil, nil, 0, err
for _, utxo := range utxos {
u := utxo.ToUtxo(uint(a.UnilateralExitDelay))
if u.SpendableAt.Before(now) {
fetchedUtxos = append(fetchedUtxos, u)
}
}
fetchedUtxos = append(fetchedUtxos, utxos...)
}
delayedUtxos := make([]explorer.Utxo, 0)
for _, utxo := range fetchedUtxos {
if selectedAmount >= targetAmount {
break
}
availableAt := time.Unix(utxo.Status.Blocktime, 0).Add(
time.Duration(a.UnilateralExitDelay) * time.Second,
)
if availableAt.After(time.Now()) {
continue
}
for _, excluded := range exclude {
if utxo.Txid == excluded.Txid && utxo.Vout == excluded.Vout {
continue
}
}
delayedUtxos = append(delayedUtxos, utxo)
selected = append(selected, utxo)
selectedAmount += utxo.Amount
}
if selectedAmount < targetAmount {
return nil, nil, 0, fmt.Errorf(
return nil, 0, fmt.Errorf(
"not enough funds to cover amount %d", targetAmount,
)
}
return utxos, delayedUtxos, selectedAmount - targetAmount, nil
return selected, selectedAmount - targetAmount, nil
}
func (a *covenantlessArkClient) getRedeemBranches(
@@ -1673,6 +1483,53 @@ func (a *covenantlessArkClient) getOffchainBalance(
return balance, amountByExpiration, nil
}
func (a *covenantlessArkClient) getClaimableBoardingUtxos(ctx context.Context) ([]explorer.Utxo, error) {
offchainAddrs, boardingAddrs, _, err := a.wallet.GetAddresses(ctx)
if err != nil {
return nil, err
}
_, myPubkey, _, err := common.DecodeAddress(offchainAddrs[0])
if err != nil {
return nil, err
}
myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(myPubkey))
descriptorStr := strings.ReplaceAll(
a.BoardingDescriptorTemplate, "USER", myPubkeyStr,
)
desc, err := descriptor.ParseTaprootDescriptor(descriptorStr)
if err != nil {
return nil, err
}
_, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc)
if err != nil {
return nil, err
}
claimable := make([]explorer.Utxo, 0)
now := time.Now()
for _, addr := range boardingAddrs {
boardingUtxos, err := a.explorer.GetUtxos(addr)
if err != nil {
return nil, err
}
for _, utxo := range boardingUtxos {
u := utxo.ToUtxo(boardingTimeout)
if u.SpendableAt.Before(now) {
continue
}
claimable = append(claimable, u)
}
}
return claimable, nil
}
func (a *covenantlessArkClient) getVtxos(
ctx context.Context, addr string, computeVtxoExpiration bool,
) ([]client.Vtxo, []client.Vtxo, error) {
@@ -1718,14 +1575,25 @@ func (a *covenantlessArkClient) getVtxos(
}
func (a *covenantlessArkClient) selfTransferAllPendingPayments(
ctx context.Context, pendingVtxos []client.Vtxo, myself client.Output,
ctx context.Context, pendingVtxos []client.Vtxo, boardingUtxo []explorer.Utxo, myself client.Output, boardingDescriptor string,
) (string, error) {
inputs := make([]client.VtxoKey, 0, len(pendingVtxos))
inputs := make([]client.Input, 0, len(pendingVtxos)+len(boardingUtxo))
for _, coin := range pendingVtxos {
inputs = append(inputs, coin.VtxoKey)
}
for _, utxo := range boardingUtxo {
fmt.Println(utxo)
fmt.Println(boardingDescriptor)
inputs = append(inputs, client.BoardingInput{
VtxoKey: client.VtxoKey{
Txid: utxo.Txid,
VOut: utxo.Vout,
},
Descriptor: boardingDescriptor,
})
}
outputs := []client.Output{myself}
roundEphemeralKey, err := secp256k1.GeneratePrivateKey()
@@ -1747,7 +1615,7 @@ func (a *covenantlessArkClient) selfTransferAllPendingPayments(
}
roundTxid, err := a.handleRoundStream(
ctx, paymentID, pendingVtxos, outputs, roundEphemeralKey,
ctx, paymentID, pendingVtxos, len(boardingUtxo) > 0, outputs, roundEphemeralKey,
)
if err != nil {
return "", err

View File

@@ -38,31 +38,27 @@ func main() {
defer aliceArkClient.Lock(ctx, password)
log.Info("alice is acquiring onchain funds...")
_, aliceOnchainAddr, err := aliceArkClient.Receive(ctx)
_, aliceBoardingAddr, err := aliceArkClient.Receive(ctx)
if err != nil {
log.Fatal(err)
}
if _, err := runCommand("nigiri", "faucet", "--liquid", aliceOnchainAddr); err != nil {
if _, err := runCommand("nigiri", "faucet", "--liquid", aliceBoardingAddr); err != nil {
log.Fatal(err)
}
time.Sleep(5 * time.Second)
onboardAmount := uint64(20000)
onboardAmount := uint64(1_0000_0000) // 1 BTC
log.Infof("alice is onboarding with %d sats offchain...", onboardAmount)
txid, err := aliceArkClient.Onboard(ctx, onboardAmount)
log.Infof("alice claiming onboarding funds...")
txid, err := aliceArkClient.Claim(ctx)
if err != nil {
log.Fatal(err)
}
if err := generateBlock(); err != nil {
log.Fatal(err)
}
time.Sleep(5 * time.Second)
log.Infof("alice onboarded with tx: %s", txid)
log.Infof("onboarding completed in round tx: %s", txid)
aliceBalance, err := aliceArkClient.Balance(ctx, false)
if err != nil {

View File

@@ -38,31 +38,19 @@ func main() {
defer aliceArkClient.Lock(ctx, password)
log.Info("alice is acquiring onchain funds...")
_, aliceOnchainAddr, err := aliceArkClient.Receive(ctx)
_, boardingAddress, err := aliceArkClient.Receive(ctx)
if err != nil {
log.Fatal(err)
}
if _, err := runCommand("nigiri", "faucet", aliceOnchainAddr); err != nil {
if _, err := runCommand("nigiri", "faucet", boardingAddress); err != nil {
log.Fatal(err)
}
time.Sleep(5 * time.Second)
onboardAmount := uint64(20000)
onboardAmount := uint64(1_0000_0000) // 1 BTC
log.Infof("alice is onboarding with %d sats offchain...", onboardAmount)
txid, err := aliceArkClient.Onboard(ctx, onboardAmount)
if err != nil {
log.Fatal(err)
}
if err := generateBlock(); err != nil {
log.Fatal(err)
}
time.Sleep(5 * time.Second)
log.Infof("alice onboarded with tx: %s", txid)
aliceBalance, err := aliceArkClient.Balance(ctx, false)
if err != nil {
@@ -72,6 +60,14 @@ func main() {
log.Infof("alice onchain balance: %d", aliceBalance.OnchainBalance.SpendableAmount)
log.Infof("alice offchain balance: %d", aliceBalance.OffchainBalance.Total)
log.Infof("alice claiming onboarding funds...")
txid, err := aliceArkClient.Claim(ctx)
if err != nil {
log.Fatal(err)
}
log.Infof("alice claimed onboarding funds in round %s", txid)
fmt.Println("")
log.Info("bob is setting up his ark wallet...")
bobArkClient, err := setupArkClient()
@@ -137,7 +133,7 @@ func main() {
fmt.Println("")
log.Info("bob is claiming the incoming payment...")
roundTxid, err := bobArkClient.ClaimAsync(ctx)
roundTxid, err := bobArkClient.Claim(ctx)
if err != nil {
log.Fatal(err)
}

View File

@@ -25,6 +25,35 @@ const (
)
type Utxo struct {
Txid string
Vout uint32
Amount uint64
Asset string // liquid only
Delay uint
SpendableAt time.Time
}
func (u *Utxo) Sequence() (uint32, error) {
return common.BIP68EncodeAsNumber(u.Delay)
}
func newUtxo(explorerUtxo ExplorerUtxo, delay uint) Utxo {
utxoTime := explorerUtxo.Status.Blocktime
if utxoTime == 0 {
utxoTime = time.Now().Unix()
}
return Utxo{
Txid: explorerUtxo.Txid,
Vout: explorerUtxo.Vout,
Amount: explorerUtxo.Amount,
Asset: explorerUtxo.Asset,
Delay: delay,
SpendableAt: time.Unix(utxoTime, 0).Add(time.Duration(delay) * time.Second),
}
}
type ExplorerUtxo struct {
Txid string `json:"txid"`
Vout uint32 `json:"vout"`
Amount uint64 `json:"value"`
@@ -35,10 +64,14 @@ type Utxo struct {
} `json:"status"`
}
func (e ExplorerUtxo) ToUtxo(delay uint) Utxo {
return newUtxo(e, delay)
}
type Explorer interface {
GetTxHex(txid string) (string, error)
Broadcast(txHex string) (string, error)
GetUtxos(addr string) ([]Utxo, error)
GetUtxos(addr string) ([]ExplorerUtxo, error)
GetBalance(addr string) (uint64, error)
GetRedeemedVtxosBalance(
addr string, unilateralExitDelay int64,
@@ -143,7 +176,7 @@ func (e *explorerSvc) Broadcast(txStr string) (string, error) {
return txid, nil
}
func (e *explorerSvc) GetUtxos(addr string) ([]Utxo, error) {
func (e *explorerSvc) GetUtxos(addr string) ([]ExplorerUtxo, error) {
resp, err := http.Get(fmt.Sprintf("%s/address/%s/utxo", e.baseUrl, addr))
if err != nil {
return nil, err
@@ -157,7 +190,7 @@ func (e *explorerSvc) GetUtxos(addr string) ([]Utxo, error) {
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf(string(body))
}
payload := []Utxo{}
payload := []ExplorerUtxo{}
if err := json.Unmarshal(body, &payload); err != nil {
return nil, err
}

View File

@@ -2,6 +2,8 @@ module github.com/ark-network/ark/pkg/client-sdk
go 1.22.6
replace github.com/ark-network/ark/common => ../../common
require (
github.com/ark-network/ark/api-spec v0.0.0-20240815203029-edc4534dfc87
github.com/ark-network/ark/common v0.0.0-20240815203029-edc4534dfc87

View File

@@ -1,8 +1,6 @@
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/ark-network/ark/api-spec v0.0.0-20240815203029-edc4534dfc87 h1:VBY4KqHqxE4q6NnmvEZTLvLZoNA0Q6NhMhjBs1hzy9Y=
github.com/ark-network/ark/api-spec v0.0.0-20240815203029-edc4534dfc87/go.mod h1:m5H86Dx+k8cQjLXeYL1MV+h3x/XnhJCXJP/PL3KgZqY=
github.com/ark-network/ark/common v0.0.0-20240815203029-edc4534dfc87 h1:TIv00zlpxLKmY2LjFAIMF8RxNtn9rFqQsv73Lwoj2ds=
github.com/ark-network/ark/common v0.0.0-20240815203029-edc4534dfc87/go.mod h1:aYAGDfoeBLofnZt9n85wusFyCkrS7hvwdo5TynBlkuY=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=

View File

@@ -21,14 +21,15 @@ const (
)
type storeData struct {
AspUrl string `json:"asp_url"`
AspPubkey string `json:"asp_pubkey"`
WalletType string `json:"wallet_type"`
ClientType string `json:"client_type"`
Network string `json:"network"`
RoundLifetime string `json:"round_lifetime"`
UnilateralExitDelay string `json:"unilateral_exit_delay"`
MinRelayFee string `json:"min_relay_fee"`
AspUrl string `json:"asp_url"`
AspPubkey string `json:"asp_pubkey"`
WalletType string `json:"wallet_type"`
ClientType string `json:"client_type"`
Network string `json:"network"`
RoundLifetime string `json:"round_lifetime"`
UnilateralExitDelay string `json:"unilateral_exit_delay"`
MinRelayFee string `json:"min_relay_fee"`
BoardingDescriptorTemplate string `json:"boarding_descriptor_template"`
}
func (d storeData) isEmpty() bool {
@@ -43,27 +44,29 @@ func (d storeData) decode() store.StoreData {
buf, _ := hex.DecodeString(d.AspPubkey)
aspPubkey, _ := secp256k1.ParsePubKey(buf)
return store.StoreData{
AspUrl: d.AspUrl,
AspPubkey: aspPubkey,
WalletType: d.WalletType,
ClientType: d.ClientType,
Network: network,
RoundLifetime: int64(roundLifetime),
UnilateralExitDelay: int64(unilateralExitDelay),
MinRelayFee: uint64(minRelayFee),
AspUrl: d.AspUrl,
AspPubkey: aspPubkey,
WalletType: d.WalletType,
ClientType: d.ClientType,
Network: network,
RoundLifetime: int64(roundLifetime),
UnilateralExitDelay: int64(unilateralExitDelay),
MinRelayFee: uint64(minRelayFee),
BoardingDescriptorTemplate: d.BoardingDescriptorTemplate,
}
}
func (d storeData) asMap() map[string]string {
return map[string]string{
"asp_url": d.AspUrl,
"asp_pubkey": d.AspPubkey,
"wallet_type": d.WalletType,
"client_type": d.ClientType,
"network": d.Network,
"round_lifetime": d.RoundLifetime,
"unilateral_exit_delay": d.UnilateralExitDelay,
"min_relay_fee": d.MinRelayFee,
"asp_url": d.AspUrl,
"asp_pubkey": d.AspPubkey,
"wallet_type": d.WalletType,
"client_type": d.ClientType,
"network": d.Network,
"round_lifetime": d.RoundLifetime,
"unilateral_exit_delay": d.UnilateralExitDelay,
"min_relay_fee": d.MinRelayFee,
"boarding_descriptor_template": d.BoardingDescriptorTemplate,
}
}
@@ -100,14 +103,15 @@ func (s *Store) GetDatadir() string {
func (s *Store) AddData(ctx context.Context, data store.StoreData) error {
sd := &storeData{
AspUrl: data.AspUrl,
AspPubkey: hex.EncodeToString(data.AspPubkey.SerializeCompressed()),
WalletType: data.WalletType,
ClientType: data.ClientType,
Network: data.Network.Name,
RoundLifetime: fmt.Sprintf("%d", data.RoundLifetime),
UnilateralExitDelay: fmt.Sprintf("%d", data.UnilateralExitDelay),
MinRelayFee: fmt.Sprintf("%d", data.MinRelayFee),
AspUrl: data.AspUrl,
AspPubkey: hex.EncodeToString(data.AspPubkey.SerializeCompressed()),
WalletType: data.WalletType,
ClientType: data.ClientType,
Network: data.Network.Name,
RoundLifetime: fmt.Sprintf("%d", data.RoundLifetime),
UnilateralExitDelay: fmt.Sprintf("%d", data.UnilateralExitDelay),
MinRelayFee: fmt.Sprintf("%d", data.MinRelayFee),
BoardingDescriptorTemplate: data.BoardingDescriptorTemplate,
}
if err := s.write(sd); err != nil {

View File

@@ -13,14 +13,15 @@ const (
)
type StoreData struct {
AspUrl string
AspPubkey *secp256k1.PublicKey
WalletType string
ClientType string
Network common.Network
RoundLifetime int64
UnilateralExitDelay int64
MinRelayFee uint64
AspUrl string
AspPubkey *secp256k1.PublicKey
WalletType string
ClientType string
Network common.Network
RoundLifetime int64
UnilateralExitDelay int64
MinRelayFee uint64
BoardingDescriptorTemplate string
}
type ConfigStore interface {

View File

@@ -18,14 +18,15 @@ func TestStore(t *testing.T) {
key, _ := btcec.NewPrivateKey()
ctx := context.Background()
testStoreData := store.StoreData{
AspUrl: "localhost:7070",
AspPubkey: key.PubKey(),
WalletType: wallet.SingleKeyWallet,
ClientType: client.GrpcClient,
Network: common.LiquidRegTest,
RoundLifetime: 512,
UnilateralExitDelay: 512,
MinRelayFee: 300,
AspUrl: "localhost:7070",
AspPubkey: key.PubKey(),
WalletType: wallet.SingleKeyWallet,
ClientType: client.GrpcClient,
Network: common.LiquidRegTest,
RoundLifetime: 512,
UnilateralExitDelay: 512,
MinRelayFee: 300,
BoardingDescriptorTemplate: "tr(0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0,{ and(pk(873079a0091c9b16abd1f8c508320b07f0d50144d09ccd792ce9c915dac60465), pk(USER)), and(older(604672), pk(USER)) })",
}
tests := []struct {

View File

@@ -9,12 +9,12 @@ import (
"github.com/ark-network/ark/common"
"github.com/ark-network/ark/common/bitcointree"
"github.com/ark-network/ark/common/descriptor"
"github.com/ark-network/ark/pkg/client-sdk/explorer"
"github.com/ark-network/ark/pkg/client-sdk/internal/utils"
"github.com/ark-network/ark/pkg/client-sdk/store"
"github.com/ark-network/ark/pkg/client-sdk/wallet"
walletstore "github.com/ark-network/ark/pkg/client-sdk/wallet/singlekey/store"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/psbt"
@@ -41,42 +41,42 @@ func NewBitcoinWallet(
func (w *bitcoinWallet) GetAddresses(
ctx context.Context,
) ([]string, []string, []string, error) {
offchainAddr, onchainAddr, redemptionAddr, err := w.getAddress(ctx)
offchainAddr, boardingAddr, redemptionAddr, err := w.getAddress(ctx)
if err != nil {
return nil, nil, nil, err
}
offchainAddrs := []string{offchainAddr}
onchainAddrs := []string{onchainAddr}
boardingAddrs := []string{boardingAddr}
redemptionAddrs := []string{redemptionAddr}
return offchainAddrs, onchainAddrs, redemptionAddrs, nil
return offchainAddrs, boardingAddrs, redemptionAddrs, nil
}
func (w *bitcoinWallet) NewAddress(
ctx context.Context, _ bool,
) (string, string, error) {
offchainAddr, onchainAddr, _, err := w.getAddress(ctx)
offchainAddr, boardingAddr, _, err := w.getAddress(ctx)
if err != nil {
return "", "", err
}
return offchainAddr, onchainAddr, nil
return offchainAddr, boardingAddr, nil
}
func (w *bitcoinWallet) NewAddresses(
ctx context.Context, _ bool, num int,
) ([]string, []string, error) {
offchainAddr, onchainAddr, _, err := w.getAddress(ctx)
offchainAddr, boardingAddr, _, err := w.getAddress(ctx)
if err != nil {
return nil, nil, err
}
offchainAddrs := make([]string, 0, num)
onchainAddrs := make([]string, 0, num)
boardingAddrs := make([]string, 0, num)
for i := 0; i < num; i++ {
offchainAddrs = append(offchainAddrs, offchainAddr)
onchainAddrs = append(onchainAddrs, onchainAddr)
boardingAddrs = append(boardingAddrs, boardingAddr)
}
return offchainAddrs, onchainAddrs, nil
return offchainAddrs, boardingAddrs, nil
}
func (s *bitcoinWallet) SignTransaction(
@@ -92,11 +92,6 @@ func (s *bitcoinWallet) SignTransaction(
return "", err
}
data, err := s.configStore.GetData(ctx)
if err != nil {
return "", err
}
for i, input := range updater.Upsbt.UnsignedTx.TxIn {
if updater.Upsbt.Inputs[i].WitnessUtxo != nil {
continue
@@ -122,28 +117,11 @@ func (s *bitcoinWallet) SignTransaction(
return "", err
}
sighashType := txscript.SigHashAll
if utxo.PkScript[0] == txscript.OP_1 {
sighashType = txscript.SigHashDefault
}
if err := updater.AddInSighashType(sighashType, i); err != nil {
if err := updater.AddInSighashType(txscript.SigHashDefault, i); err != nil {
return "", err
}
}
_, onchainAddr, _, err := s.getAddress(ctx)
if err != nil {
return "", err
}
net := utils.ToBitcoinNetwork(data.Network)
addr, _ := btcutil.DecodeAddress(onchainAddr, &net)
onchainWalletScript, err := txscript.PayToAddrScript(addr)
if err != nil {
return "", err
}
prevouts := make(map[wire.OutPoint]*wire.TxOut)
for i, input := range updater.Upsbt.Inputs {
@@ -158,36 +136,6 @@ func (s *bitcoinWallet) SignTransaction(
txsighashes := txscript.NewTxSigHashes(updater.Upsbt.UnsignedTx, prevoutFetcher)
for i, input := range ptx.Inputs {
if bytes.Equal(input.WitnessUtxo.PkScript, onchainWalletScript) {
if err := updater.AddInSighashType(txscript.SigHashAll, i); err != nil {
return "", err
}
preimage, err := txscript.CalcWitnessSigHash(
input.WitnessUtxo.PkScript,
txsighashes,
txscript.SigHashAll,
updater.Upsbt.UnsignedTx,
i,
int64(input.WitnessUtxo.Value),
)
if err != nil {
return "", err
}
sig := ecdsa.Sign(s.privateKey, preimage)
signatureWithSighashType := append(sig.Serialize(), byte(txscript.SigHashAll))
updater.Upsbt.Inputs[i].PartialSigs = []*psbt.PartialSig{
{
PubKey: s.walletData.Pubkey.SerializeCompressed(),
Signature: signatureWithSighashType,
},
}
continue
}
if len(input.TaprootLeafScript) > 0 {
pubkey := s.walletData.Pubkey
for _, leaf := range input.TaprootLeafScript {
@@ -269,11 +217,6 @@ func (w *bitcoinWallet) getAddress(
netParams := utils.ToBitcoinNetwork(data.Network)
onchainAddr, err := btcutil.NewAddressWitnessPubKeyHash(btcutil.Hash160(w.walletData.Pubkey.SerializeCompressed()), &netParams)
if err != nil {
return "", "", "", err
}
vtxoTapKey, _, err := bitcointree.ComputeVtxoTaprootScript(
w.walletData.Pubkey, data.AspPubkey, uint(data.UnilateralExitDelay),
)
@@ -289,5 +232,35 @@ func (w *bitcoinWallet) getAddress(
return "", "", "", err
}
return offchainAddr, onchainAddr.EncodeAddress(), redemptionAddr.EncodeAddress(), nil
myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(w.walletData.Pubkey))
descriptorStr := strings.ReplaceAll(
data.BoardingDescriptorTemplate, "USER", myPubkeyStr,
)
desc, err := descriptor.ParseTaprootDescriptor(descriptorStr)
if err != nil {
return "", "", "", err
}
_, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc)
if err != nil {
return "", "", "", err
}
boardingTapKey, _, err := bitcointree.ComputeVtxoTaprootScript(
w.walletData.Pubkey, data.AspPubkey, boardingTimeout,
)
if err != nil {
return "", "", "", err
}
boardingAddr, err := btcutil.NewAddressTaproot(
schnorr.SerializePubKey(boardingTapKey),
&netParams,
)
if err != nil {
return "", "", "", err
}
return offchainAddr, boardingAddr.EncodeAddress(), redemptionAddr.EncodeAddress(), nil
}

View File

@@ -3,20 +3,21 @@ package singlekeywallet
import (
"bytes"
"context"
"encoding/hex"
"fmt"
"strings"
"github.com/ark-network/ark/common"
"github.com/ark-network/ark/common/descriptor"
"github.com/ark-network/ark/common/tree"
"github.com/ark-network/ark/pkg/client-sdk/explorer"
"github.com/ark-network/ark/pkg/client-sdk/internal/utils"
"github.com/ark-network/ark/pkg/client-sdk/store"
"github.com/ark-network/ark/pkg/client-sdk/wallet"
walletstore "github.com/ark-network/ark/pkg/client-sdk/wallet/singlekey/store"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/vulpemventures/go-elements/payment"
"github.com/vulpemventures/go-elements/psetv2"
"github.com/vulpemventures/go-elements/transaction"
)
@@ -40,42 +41,42 @@ func NewLiquidWallet(
func (w *liquidWallet) GetAddresses(
ctx context.Context,
) ([]string, []string, []string, error) {
offchainAddr, onchainAddr, redemptionAddr, err := w.getAddress(ctx)
offchainAddr, boardingAddr, redemptionAddr, err := w.getAddress(ctx)
if err != nil {
return nil, nil, nil, err
}
offchainAddrs := []string{offchainAddr}
onchainAddrs := []string{onchainAddr}
boardingAddrs := []string{boardingAddr}
redemptionAddrs := []string{redemptionAddr}
return offchainAddrs, onchainAddrs, redemptionAddrs, nil
return offchainAddrs, boardingAddrs, redemptionAddrs, nil
}
func (w *liquidWallet) NewAddress(
ctx context.Context, _ bool,
) (string, string, error) {
offchainAddr, onchainAddr, _, err := w.getAddress(ctx)
offchainAddr, boardingAddr, _, err := w.getAddress(ctx)
if err != nil {
return "", "", err
}
return offchainAddr, onchainAddr, nil
return offchainAddr, boardingAddr, nil
}
func (w *liquidWallet) NewAddresses(
ctx context.Context, _ bool, num int,
) ([]string, []string, error) {
offchainAddr, onchainAddr, _, err := w.getAddress(ctx)
offchainAddr, boardingAddr, _, err := w.getAddress(ctx)
if err != nil {
return nil, nil, err
}
offchainAddrs := make([]string, 0, num)
onchainAddrs := make([]string, 0, num)
boardingAddrs := make([]string, 0, num)
for i := 0; i < num; i++ {
offchainAddrs = append(offchainAddrs, offchainAddr)
onchainAddrs = append(onchainAddrs, onchainAddr)
boardingAddrs = append(boardingAddrs, boardingAddr)
}
return offchainAddrs, onchainAddrs, nil
return offchainAddrs, boardingAddrs, nil
}
func (s *liquidWallet) SignTransaction(
@@ -114,13 +115,7 @@ func (s *liquidWallet) SignTransaction(
return "", err
}
sighashType := txscript.SigHashAll
if utxo.Script[0] == txscript.OP_1 {
sighashType = txscript.SigHashDefault
}
if err := updater.AddInSighashType(i, sighashType); err != nil {
if err := updater.AddInSighashType(i, txscript.SigHashDefault); err != nil {
return "", err
}
}
@@ -135,8 +130,6 @@ func (s *liquidWallet) SignTransaction(
return "", err
}
liquidNet := utils.ToElementsNetwork(storeData.Network)
p2wpkh := payment.FromPublicKey(s.walletData.Pubkey, &liquidNet, nil)
onchainWalletScript := p2wpkh.WitnessScript
utx, err := pset.UnsignedTx()
if err != nil {
@@ -156,33 +149,6 @@ func (s *liquidWallet) SignTransaction(
serializedPubKey := s.walletData.Pubkey.SerializeCompressed()
for i, input := range pset.Inputs {
prevout := input.GetUtxo()
if bytes.Equal(prevout.Script, onchainWalletScript) {
p, err := payment.FromScript(prevout.Script, &liquidNet, nil)
if err != nil {
return "", err
}
preimage := utx.HashForWitnessV0(
i, p.Script, prevout.Value, txscript.SigHashAll,
)
sig := ecdsa.Sign(s.privateKey, preimage[:])
signatureWithSighashType := append(
sig.Serialize(), byte(txscript.SigHashAll),
)
err = signer.SignInput(
i, signatureWithSighashType, serializedPubKey, nil, nil,
)
if err != nil {
return "", err
}
continue
}
if len(input.TapLeafScript) > 0 {
genesis, err := chainhash.NewHashFromStr(liquidNet.GenesisBlockHash)
if err != nil {
@@ -276,12 +242,6 @@ func (w *liquidWallet) getAddress(
liquidNet := utils.ToElementsNetwork(data.Network)
p2wpkh := payment.FromPublicKey(w.walletData.Pubkey, &liquidNet, nil)
onchainAddr, err := p2wpkh.WitnessPubKeyHash()
if err != nil {
return "", "", "", err
}
_, _, _, redemptionAddr, err := tree.ComputeVtxoTaprootScript(
w.walletData.Pubkey, data.AspPubkey, uint(data.UnilateralExitDelay), liquidNet,
)
@@ -289,5 +249,27 @@ func (w *liquidWallet) getAddress(
return "", "", "", err
}
return offchainAddr, onchainAddr, redemptionAddr, nil
myPubkeyStr := hex.EncodeToString(schnorr.SerializePubKey(w.walletData.Pubkey))
descriptorStr := strings.ReplaceAll(
data.BoardingDescriptorTemplate, "USER", myPubkeyStr,
)
desc, err := descriptor.ParseTaprootDescriptor(descriptorStr)
if err != nil {
return "", "", "", err
}
_, boardingTimeout, err := descriptor.ParseBoardingDescriptor(*desc)
if err != nil {
return "", "", "", err
}
_, _, _, boardingAddr, err := tree.ComputeVtxoTaprootScript(
w.walletData.Pubkey, data.AspPubkey, boardingTimeout, liquidNet,
)
if err != nil {
return "", "", "", err
}
return offchainAddr, boardingAddr, redemptionAddr, nil
}

View File

@@ -20,7 +20,7 @@ type WalletService interface {
IsLocked() bool
GetAddresses(
ctx context.Context,
) (offchainAddresses, onchainAddresses, redemptionAddresses []string, err error)
) (offchainAddresses, boardingAddresses, redemptionAddresses []string, err error)
NewAddress(
ctx context.Context, change bool,
) (offchainAddr, onchainAddr string, err error)
@@ -29,5 +29,5 @@ type WalletService interface {
) (offchainAddresses, onchainAddresses []string, err error)
SignTransaction(
ctx context.Context, explorerSvc explorer.Explorer, tx string,
) (singedTx string, err error)
) (signedTx string, err error)
}

View File

@@ -21,14 +21,15 @@ func TestWallet(t *testing.T) {
key, _ := btcec.NewPrivateKey()
password := "password"
testStoreData := store.StoreData{
AspUrl: "localhost:7070",
AspPubkey: key.PubKey(),
WalletType: wallet.SingleKeyWallet,
ClientType: client.GrpcClient,
Network: common.LiquidRegTest,
RoundLifetime: 512,
UnilateralExitDelay: 512,
MinRelayFee: 300,
AspUrl: "localhost:7070",
AspPubkey: key.PubKey(),
WalletType: wallet.SingleKeyWallet,
ClientType: client.GrpcClient,
Network: common.LiquidRegTest,
RoundLifetime: 512,
UnilateralExitDelay: 512,
MinRelayFee: 300,
BoardingDescriptorTemplate: "tr(0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0,{ and(pk(873079a0091c9b16abd1f8c508320b07f0d50144d09ccd792ce9c915dac60465), pk(USER)), and(older(604672), pk(USER)) })",
}
tests := []struct {
name string

View File

@@ -27,7 +27,6 @@ func init() {
js.Global().Set("lock", LockWrapper())
js.Global().Set("locked", IsLockedWrapper())
js.Global().Set("balance", BalanceWrapper())
js.Global().Set("onboard", OnboardWrapper())
js.Global().Set("receive", ReceiveWrapper())
js.Global().Set("sendOnChain", SendOnChainWrapper())
js.Global().Set("sendOffChain", SendOffChainWrapper())

View File

@@ -140,33 +140,18 @@ func BalanceWrapper() js.Func {
})
}
func OnboardWrapper() js.Func {
return JSPromise(func(args []js.Value) (interface{}, error) {
if len(args) != 1 {
return nil, errors.New("invalid number of args")
}
amount := uint64(args[0].Int())
txID, err := arkSdkClient.Onboard(context.Background(), amount)
if err != nil {
return nil, err
}
return js.ValueOf(txID), nil
})
}
func ReceiveWrapper() js.Func {
return JSPromise(func(args []js.Value) (interface{}, error) {
if arkSdkClient == nil {
return nil, errors.New("ARK SDK client is not initialized")
}
offchainAddr, onchainAddr, err := arkSdkClient.Receive(context.Background())
offchainAddr, boardingAddr, err := arkSdkClient.Receive(context.Background())
if err != nil {
return nil, err
}
result := map[string]interface{}{
"offchainAddr": offchainAddr,
"onchainAddr": onchainAddr,
"boardingAddr": boardingAddr,
}
return js.ValueOf(result), nil
})