Add CreatePayment and CompletePayment (#229)

Co-authored-by: Marco Argentieri <tiero@users.noreply.github.com>

* Add claim command

* Persist pending data in sqlite repo

* Remove debug log

* Return pending data at interface level

* Fix unlocking btc wallet after restart

* Lint & Fix whitelist permissions

* Fix send command for covenant

* Update client/covenantless/claim.go

Signed-off-by: Marco Argentieri <3596602+tiero@users.noreply.github.com>

* Fix

* Pay for min relay fee instead of estimating fees for redeem and unconf forfeit txs

* Add support for pending payments (coventanless)

* Fixes

* Fixes

* Improve verbosity

* Fix coin selection

* Fix

---------

Signed-off-by: Marco Argentieri <3596602+tiero@users.noreply.github.com>
Co-authored-by: louisinger <louis@vulpem.com>
Co-authored-by: Marco Argentieri <tiero@users.noreply.github.com>
Co-authored-by: Marco Argentieri <3596602+tiero@users.noreply.github.com>
This commit is contained in:
Pietralberto Mazza
2024-08-10 19:18:02 +02:00
committed by GitHub
parent 57ce08f239
commit 72a7f29bab
67 changed files with 4730 additions and 769 deletions

View File

@@ -25,6 +25,14 @@ const dust = 450
type covenantLiquidCLI struct{}
func (c *covenantLiquidCLI) SendAsync(ctx *cli.Context) error {
return fmt.Errorf("not implemented")
}
func (c *covenantLiquidCLI) ClaimAsync(ctx *cli.Context) error {
return fmt.Errorf("not implemented")
}
func (c *covenantLiquidCLI) Receive(ctx *cli.Context) error {
offchainAddr, onchainAddr, _, err := getAddress(ctx)
if err != nil {

View File

@@ -109,7 +109,7 @@ func getClient(addr string) (arkv1.ArkServiceClient, func(), error) {
if !strings.Contains(addr, ":") {
addr = fmt.Sprintf("%s:%d", addr, port)
}
conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(creds))
conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(creds))
if err != nil {
return nil, nil, err
}

View File

@@ -0,0 +1,98 @@
package covenantless
import (
"github.com/ark-network/ark-cli/utils"
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
"github.com/urfave/cli/v2"
)
func (c *clArkBitcoinCLI) ClaimAsync(ctx *cli.Context) error {
client, cancel, err := getClientFromState(ctx)
if err != nil {
return err
}
defer cancel()
myselfOffchain, _, _, err := getAddress(ctx)
if err != nil {
return err
}
vtxos, err := getVtxos(ctx, nil, client, myselfOffchain, false)
if err != nil {
return err
}
var pendingBalance uint64
var pendingVtxos []vtxo
for _, vtxo := range vtxos {
if vtxo.pending {
pendingBalance += vtxo.amount
pendingVtxos = append(pendingVtxos, vtxo)
}
}
if pendingBalance == 0 {
return nil
}
receiver := receiver{
To: myselfOffchain,
Amount: pendingBalance,
}
return selfTransferAllPendingPayments(
ctx, client, pendingVtxos, receiver,
)
}
func selfTransferAllPendingPayments(
ctx *cli.Context, client arkv1.ArkServiceClient,
pendingVtxos []vtxo, myself receiver,
) error {
inputs := make([]*arkv1.Input, 0, len(pendingVtxos))
for _, coin := range pendingVtxos {
inputs = append(inputs, &arkv1.Input{
Txid: coin.txid,
Vout: coin.vout,
})
}
receiversOutput := []*arkv1.Output{
{
Address: myself.To,
Amount: myself.Amount,
},
}
secKey, err := utils.PrivateKeyFromPassword(ctx)
if err != nil {
return err
}
registerResponse, err := client.RegisterPayment(
ctx.Context, &arkv1.RegisterPaymentRequest{Inputs: inputs},
)
if err != nil {
return err
}
_, err = client.ClaimPayment(ctx.Context, &arkv1.ClaimPaymentRequest{
Id: registerResponse.GetId(),
Outputs: []*arkv1.Output{{Address: myself.To, Amount: myself.Amount}},
})
if err != nil {
return err
}
poolTxID, err := handleRoundStream(
ctx, client, registerResponse.GetId(),
pendingVtxos, secKey, receiversOutput,
)
if err != nil {
return err
}
return utils.PrintJSON(map[string]interface{}{
"pool_txid": poolTxID,
})
}

View File

@@ -70,15 +70,19 @@ func New() interfaces.CLI {
return &clArkBitcoinCLI{}
}
func (c *clArkBitcoinCLI) Send(ctx *cli.Context) error {
return fmt.Errorf("not implemented")
}
type receiver struct {
To string `json:"to"`
Amount uint64 `json:"amount"`
}
func (r *receiver) isOnchain() bool {
_, err := btcutil.DecodeAddress(r.To, nil)
return err == nil
}
// func (r *receiver) isOnchain() bool {
// _, err := btcutil.DecodeAddress(r.To, nil)
// return err == nil
// }
func sendOnchain(ctx *cli.Context, receivers []receiver) (string, error) {
ptx, err := psbt.New(nil, nil, 2, 0, nil)

View File

@@ -27,6 +27,7 @@ type vtxo struct {
vout uint32
poolTxid string
expireAt *time.Time
pending bool
}
func getVtxos(
@@ -43,19 +44,20 @@ func getVtxos(
vtxos := make([]vtxo, 0, len(response.GetSpendableVtxos()))
for _, v := range response.GetSpendableVtxos() {
var expireAt *time.Time
if v.ExpireAt > 0 {
if v.GetExpireAt() > 0 {
t := time.Unix(v.ExpireAt, 0)
expireAt = &t
}
if v.Swept {
if v.GetSwept() {
continue
}
vtxos = append(vtxos, vtxo{
amount: v.Receiver.Amount,
txid: v.Outpoint.Txid,
vout: v.Outpoint.Vout,
poolTxid: v.PoolTxid,
amount: v.GetReceiver().GetAmount(),
txid: v.GetOutpoint().GetTxid(),
vout: v.GetOutpoint().GetVout(),
poolTxid: v.GetPoolTxid(),
expireAt: expireAt,
pending: v.GetPending(),
})
}
@@ -108,7 +110,7 @@ func getClient(addr string) (arkv1.ArkServiceClient, func(), error) {
if !strings.Contains(addr, ":") {
addr = fmt.Sprintf("%s:%d", addr, port)
}
conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(creds))
conn, err := grpc.NewClient(addr, grpc.WithTransportCredentials(creds))
if err != nil {
return nil, nil, err
}

View File

@@ -2,118 +2,63 @@ package covenantless
import (
"bytes"
"encoding/json"
"fmt"
"sort"
"strings"
"github.com/ark-network/ark-cli/utils"
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
"github.com/ark-network/ark/common"
"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/urfave/cli/v2"
)
func (c *clArkBitcoinCLI) Send(ctx *cli.Context) error {
if !ctx.IsSet("receivers") && !ctx.IsSet("to") && !ctx.IsSet("amount") {
return fmt.Errorf("missing destination, either use --to and --amount to send or --receivers to send to many")
}
receivers := ctx.String("receivers")
to := ctx.String("to")
func (c *clArkBitcoinCLI) SendAsync(ctx *cli.Context) error {
receiver := ctx.String("to")
amount := ctx.Uint64("amount")
var receiversJSON []receiver
if len(receivers) > 0 {
if err := json.Unmarshal([]byte(receivers), &receiversJSON); err != nil {
return fmt.Errorf("invalid receivers: %s", err)
}
} else {
receiversJSON = []receiver{
{
To: to,
Amount: amount,
},
}
}
if len(receiversJSON) <= 0 {
return fmt.Errorf("no receivers specified")
}
onchainReceivers := make([]receiver, 0)
offchainReceivers := make([]receiver, 0)
for _, receiver := range receiversJSON {
if receiver.isOnchain() {
onchainReceivers = append(onchainReceivers, receiver)
} else {
offchainReceivers = append(offchainReceivers, receiver)
}
}
explorer := utils.NewExplorer(ctx)
if len(onchainReceivers) > 0 {
pset, err := sendOnchain(ctx, onchainReceivers)
if err != nil {
return err
}
txid, err := explorer.Broadcast(pset)
if err != nil {
return err
}
return utils.PrintJSON(map[string]interface{}{
"txid": txid,
})
}
if len(offchainReceivers) > 0 {
if err := sendOffchain(ctx, offchainReceivers); err != nil {
return err
}
}
return nil
}
func sendOffchain(ctx *cli.Context, receivers []receiver) error {
withExpiryCoinselect := ctx.Bool("enable-expiry-coinselect")
if amount < dust {
return fmt.Errorf("invalid amount (%d), must be greater than dust %d", amount, dust)
}
if receiver == "" {
return fmt.Errorf("receiver address is required")
}
isOnchain, _, _, err := decodeReceiverAddress(receiver)
if err != nil {
return err
}
if isOnchain {
return fmt.Errorf("receiver address is onchain")
}
offchainAddr, _, _, err := getAddress(ctx)
if err != nil {
return err
}
_, _, aspPubKey, err := common.DecodeAddress(offchainAddr)
if err != nil {
return err
}
receiversOutput := make([]*arkv1.Output, 0)
sumOfReceivers := uint64(0)
for _, receiver := range receivers {
_, _, aspKey, err := common.DecodeAddress(receiver.To)
_, _, aspKey, err := common.DecodeAddress(receiver)
if err != nil {
return fmt.Errorf("invalid receiver address: %s", err)
}
if !bytes.Equal(
aspPubKey.SerializeCompressed(), aspKey.SerializeCompressed(),
) {
return fmt.Errorf("invalid receiver address '%s': must be associated with the connected service provider", receiver.To)
}
if receiver.Amount < dust {
return fmt.Errorf("invalid amount (%d), must be greater than dust %d", receiver.Amount, dust)
return fmt.Errorf("invalid receiver address '%s': must be associated with the connected service provider", receiver)
}
receiversOutput := make([]*arkv1.Output, 0)
sumOfReceivers := uint64(0)
receiversOutput = append(receiversOutput, &arkv1.Output{
Address: receiver.To,
Amount: uint64(receiver.Amount),
Address: receiver,
Amount: amount,
})
sumOfReceivers += receiver.Amount
}
sumOfReceivers += amount
client, close, err := getClientFromState(ctx)
if err != nil {
return err
@@ -148,37 +93,66 @@ func sendOffchain(ctx *cli.Context, receivers []receiver) error {
})
}
secKey, err := utils.PrivateKeyFromPassword(ctx)
if err != nil {
return err
}
registerResponse, err := client.RegisterPayment(
ctx.Context, &arkv1.RegisterPaymentRequest{Inputs: inputs},
)
if err != nil {
return err
}
_, err = client.ClaimPayment(ctx.Context, &arkv1.ClaimPaymentRequest{
Id: registerResponse.GetId(),
resp, err := client.CreatePayment(
ctx.Context, &arkv1.CreatePaymentRequest{
Inputs: inputs,
Outputs: receiversOutput,
})
if err != nil {
return err
}
poolTxID, err := handleRoundStream(
ctx, client, registerResponse.GetId(),
selectedCoins, secKey, receiversOutput,
)
// TODO verify the redeem tx signature
fmt.Println("payment created")
fmt.Println("signing redeem and forfeit txs...")
seckey, err := utils.PrivateKeyFromPassword(ctx)
if err != nil {
return err
}
return utils.PrintJSON(map[string]interface{}{
"pool_txid": poolTxID,
})
signedUnconditionalForfeitTxs := make([]string, 0, len(resp.UsignedUnconditionalForfeitTxs))
for _, tx := range resp.UsignedUnconditionalForfeitTxs {
forfeitPtx, err := psbt.NewFromRawBytes(strings.NewReader(tx), true)
if err != nil {
return err
}
if err := signPsbt(ctx, forfeitPtx, explorer, seckey); err != nil {
return err
}
signedForfeitTx, err := forfeitPtx.B64Encode()
if err != nil {
return err
}
signedUnconditionalForfeitTxs = append(signedUnconditionalForfeitTxs, signedForfeitTx)
}
redeemPtx, err := psbt.NewFromRawBytes(strings.NewReader(resp.SignedRedeemTx), true)
if err != nil {
return err
}
if err := signPsbt(ctx, redeemPtx, explorer, seckey); err != nil {
return err
}
signedRedeem, err := redeemPtx.B64Encode()
if err != nil {
return err
}
if _, err = client.CompletePayment(ctx.Context, &arkv1.CompletePaymentRequest{
SignedRedeemTx: signedRedeem,
SignedUnconditionalForfeitTxs: signedUnconditionalForfeitTxs,
}); err != nil {
return err
}
fmt.Println("payment completed")
return nil
}
func coinSelect(vtxos []vtxo, amount uint64, sortByExpirationTime bool) ([]vtxo, uint64, error) {
@@ -213,7 +187,7 @@ func coinSelect(vtxos []vtxo, amount uint64, sortByExpirationTime bool) ([]vtxo,
change := selectedAmount - amount
if change < dust {
if change > 0 && change < dust {
if len(notSelected) > 0 {
selected = append(selected, notSelected[0])
change += notSelected[0].amount

View File

@@ -132,7 +132,7 @@ func signPsbt(
switch c := closure.(type) {
case *bitcointree.CSVSigClosure:
sign = bytes.Equal(c.Pubkey.SerializeCompressed()[1:], pubkey.SerializeCompressed()[1:])
case *bitcointree.ForfeitClosure:
case *bitcointree.MultisigClosure:
sign = bytes.Equal(c.Pubkey.SerializeCompressed()[1:], pubkey.SerializeCompressed()[1:])
}

View File

@@ -14,7 +14,7 @@ func computeVtxoTaprootScript(
Seconds: exitDelay,
}
forfeitClosure := &bitcointree.ForfeitClosure{
forfeitClosure := &bitcointree.MultisigClosure{
Pubkey: userPubkey,
AspPubkey: aspPubkey,
}

View File

@@ -72,18 +72,22 @@ var (
Value: "",
Required: false,
}
AmountToRedeemFlag = cli.Uint64Flag{
Name: "amount",
Usage: "amount to redeem",
Value: 0,
Required: false,
}
ForceFlag = cli.BoolFlag{
Name: "force",
Usage: "force redemption without collaborate with the Ark service provider",
Value: false,
Required: false,
}
AsyncPaymentFlag = cli.BoolFlag{
Name: "async",
Usage: "use async payment protocol",
Value: false,
Required: false,
}
)

View File

@@ -8,5 +8,7 @@ type CLI interface {
Receive(ctx *cli.Context) error
Redeem(ctx *cli.Context) error
Send(ctx *cli.Context) error
ClaimAsync(ctx *cli.Context) error
SendAsync(ctx *cli.Context) error
Onboard(ctx *cli.Context) error
}

View File

@@ -92,14 +92,36 @@ var (
sendCommand = cli.Command{
Name: "send",
Usage: "Send your onchain or offchain funds to one or many receivers",
Action: func(ctx *cli.Context) error {
state, err := utils.GetState(ctx)
if err != nil {
return err
}
networkName := state[utils.NETWORK]
cli, err := getCLI(networkName)
if err != nil {
return err
}
if strings.Contains(networkName, "liquid") {
return cli.Send(ctx)
}
return cli.SendAsync(ctx)
},
Flags: []cli.Flag{&flags.ReceiversFlag, &flags.ToFlag, &flags.AmountFlag, &flags.PasswordFlag, &flags.EnableExpiryCoinselectFlag, &flags.AsyncPaymentFlag},
}
claimCommand = cli.Command{
Name: "claim",
Usage: "Join round to claim pending payments",
Action: func(ctx *cli.Context) error {
cli, err := getCLIFromState(ctx)
if err != nil {
return err
}
return cli.Send(ctx)
return cli.ClaimAsync(ctx)
},
Flags: []cli.Flag{&flags.ReceiversFlag, &flags.ToFlag, &flags.AmountFlag, &flags.PasswordFlag, &flags.EnableExpiryCoinselectFlag},
Flags: []cli.Flag{&flags.PasswordFlag},
}
receiveCommand = cli.Command{
@@ -144,6 +166,7 @@ func main() {
&receiveCommand,
&redeemCommand,
&sendCommand,
&claimCommand,
&onboardCommand,
)
app.Flags = []cli.Flag{

View File

@@ -84,7 +84,6 @@ func (e *explorer) GetFeeRate() (float64, error) {
fmt.Println("empty fee-estimates response, default to 2 sat/vbyte")
return 2, nil
}
fmt.Println("fee rate", response["1"])
return response["1"], nil
}

View File

@@ -155,7 +155,7 @@ func (l *leaf) getOutputs() ([]*wire.TxOut, error) {
return nil, err
}
forfeitClosure := &ForfeitClosure{
forfeitClosure := &MultisigClosure{
Pubkey: l.vtxoKey,
AspPubkey: l.aspKey,
}

View File

@@ -20,7 +20,7 @@ type CSVSigClosure struct {
Seconds uint
}
type ForfeitClosure struct {
type MultisigClosure struct {
Pubkey *secp256k1.PublicKey
AspPubkey *secp256k1.PublicKey
}
@@ -33,7 +33,7 @@ func DecodeClosure(script []byte) (Closure, error) {
return closure, nil
}
closure = &ForfeitClosure{}
closure = &MultisigClosure{}
if valid, err := closure.Decode(script); err == nil && valid {
return closure, nil
}
@@ -42,7 +42,7 @@ func DecodeClosure(script []byte) (Closure, error) {
}
func (f *ForfeitClosure) Leaf() (*txscript.TapLeaf, error) {
func (f *MultisigClosure) Leaf() (*txscript.TapLeaf, error) {
aspKeyBytes := schnorr.SerializePubKey(f.AspPubkey)
userKeyBytes := schnorr.SerializePubKey(f.Pubkey)
@@ -57,7 +57,7 @@ func (f *ForfeitClosure) Leaf() (*txscript.TapLeaf, error) {
return &tapLeaf, nil
}
func (f *ForfeitClosure) Decode(script []byte) (bool, error) {
func (f *MultisigClosure) Decode(script []byte) (bool, error) {
valid, aspPubKey, err := decodeChecksigScript(script)
if err != nil {
return false, err
@@ -149,7 +149,7 @@ func ComputeVtxoTaprootScript(
Seconds: exitDelay,
}
forfeitClosure := &ForfeitClosure{
forfeitClosure := &MultisigClosure{
Pubkey: userPubkey,
AspPubkey: aspPubkey,
}

View File

@@ -18,6 +18,9 @@ services:
- ARK_NO_MACAROONS=true
ports:
- "6000:6000"
volumes:
- clarkd:/app/data
- clark:/app/wallet-data
volumes:
clarkd:

View File

@@ -14,6 +14,9 @@ services:
- OCEAN_DB_TYPE=badger
ports:
- "18000:18000"
volumes:
- oceand:/app/data/oceand
- ocean:/app/data/ocean
arkd:
container_name: arkd
build:
@@ -35,6 +38,9 @@ services:
- ARK_NO_MACAROONS=true
ports:
- "8080:8080"
volumes:
- arkd:/app/data
- ark:/app/wallet-data
volumes:
oceand:

View File

@@ -23,6 +23,8 @@ type ArkClient interface {
CollaborativeRedeem(
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)
}
type Receiver interface {

View File

@@ -37,6 +37,12 @@ type ASPClient interface {
FinalizePayment(
ctx context.Context, signedForfeitTxs []string,
) error
CreatePayment(
ctx context.Context, inputs []VtxoKey, outputs []Output,
) (string, []string, error)
CompletePayment(
ctx context.Context, signedRedeemTx string, signedUnconditionalForfeitTxs []string,
) error
Close()
}
@@ -64,6 +70,9 @@ type Vtxo struct {
Amount uint64
RoundTxid string
ExpiresAt *time.Time
RedeemTx string
UnconditionalForfeitTxs []string
Pending bool
}
type Output struct {

View File

@@ -199,6 +199,31 @@ func (a *grpcClient) FinalizePayment(
return err
}
func (a *grpcClient) CreatePayment(
ctx context.Context, inputs []client.VtxoKey, outputs []client.Output,
) (string, []string, error) {
req := &arkv1.CreatePaymentRequest{
Inputs: ins(inputs).toProto(),
Outputs: outs(outputs).toProto(),
}
resp, err := a.svc.CreatePayment(ctx, req)
if err != nil {
return "", nil, err
}
return resp.SignedRedeemTx, resp.UsignedUnconditionalForfeitTxs, nil
}
func (a *grpcClient) CompletePayment(
ctx context.Context, redeemTx string, signedForfeitTxs []string,
) error {
req := &arkv1.CompletePaymentRequest{
SignedRedeemTx: redeemTx,
SignedUnconditionalForfeitTxs: signedForfeitTxs,
}
_, err := a.svc.CompletePayment(ctx, req)
return err
}
func (a *grpcClient) GetRoundByID(
ctx context.Context, roundID string,
) (*client.Round, error) {
@@ -284,6 +309,12 @@ func (v vtxo) toVtxo() client.Vtxo {
t := time.Unix(v.GetExpireAt(), 0)
expiresAt = &t
}
var redeemTx string
var uncondForfeitTxs []string
if v.GetPendingData() != nil {
redeemTx = v.GetPendingData().GetRedeemTx()
uncondForfeitTxs = v.GetPendingData().GetUnconditionalForfeitTxs()
}
return client.Vtxo{
VtxoKey: client.VtxoKey{
Txid: v.GetOutpoint().GetTxid(),
@@ -292,6 +323,9 @@ func (v vtxo) toVtxo() client.Vtxo {
Amount: v.GetReceiver().GetAmount(),
RoundTxid: v.GetPoolTxid(),
ExpiresAt: expiresAt,
Pending: v.GetPending(),
RedeemTx: redeemTx,
UnconditionalForfeitTxs: uncondForfeitTxs,
}
}

View File

@@ -180,6 +180,13 @@ func (a *restClient) ListVtxos(
return nil, nil, err
}
var redeemTx string
var uncondForfeitTxs []string
if v.PendingData != nil {
redeemTx = v.PendingData.RedeemTx
uncondForfeitTxs = v.PendingData.UnconditionalForfeitTxs
}
spendableVtxos = append(spendableVtxos, client.Vtxo{
VtxoKey: client.VtxoKey{
Txid: v.Outpoint.Txid,
@@ -188,6 +195,9 @@ func (a *restClient) ListVtxos(
Amount: uint64(amount),
RoundTxid: v.PoolTxid,
ExpiresAt: expiresAt,
Pending: v.Pending,
RedeemTx: redeemTx,
UnconditionalForfeitTxs: uncondForfeitTxs,
})
}
@@ -358,6 +368,53 @@ func (a *restClient) FinalizePayment(
return err
}
func (a *restClient) CreatePayment(
ctx context.Context, inputs []client.VtxoKey, outputs []client.Output,
) (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),
})
}
outs := make([]*models.V1Output, 0, len(outputs))
for _, o := range outputs {
outs = append(outs, &models.V1Output{
Address: o.Address,
Amount: strconv.Itoa(int(o.Amount)),
})
}
body := models.V1CreatePaymentRequest{
Inputs: ins,
Outputs: outs,
}
resp, err := a.svc.ArkServiceCreatePayment(
ark_service.NewArkServiceCreatePaymentParams().WithBody(&body),
)
if err != nil {
return "", nil, err
}
return resp.GetPayload().SignedRedeemTx, resp.GetPayload().UsignedUnconditionalForfeitTxs, nil
}
func (a *restClient) CompletePayment(
ctx context.Context, signedRedeemTx string, signedUnconditionalForfeitTxs []string,
) error {
req := &arkv1.CompletePaymentRequest{
SignedRedeemTx: signedRedeemTx,
SignedUnconditionalForfeitTxs: signedUnconditionalForfeitTxs,
}
body := models.V1CompletePaymentRequest{
SignedRedeemTx: req.GetSignedRedeemTx(),
SignedUnconditionalForfeitTxs: req.GetSignedUnconditionalForfeitTxs(),
}
_, err := a.svc.ArkServiceCompletePayment(
ark_service.NewArkServiceCompletePaymentParams().WithBody(&body),
)
return err
}
func (a *restClient) GetRoundByID(
ctx context.Context, roundID string,
) (*client.Round, error) {

View File

@@ -56,6 +56,10 @@ type ClientOption func(*runtime.ClientOperation)
type ClientService interface {
ArkServiceClaimPayment(params *ArkServiceClaimPaymentParams, opts ...ClientOption) (*ArkServiceClaimPaymentOK, error)
ArkServiceCompletePayment(params *ArkServiceCompletePaymentParams, opts ...ClientOption) (*ArkServiceCompletePaymentOK, error)
ArkServiceCreatePayment(params *ArkServiceCreatePaymentParams, opts ...ClientOption) (*ArkServiceCreatePaymentOK, error)
ArkServiceFinalizePayment(params *ArkServiceFinalizePaymentParams, opts ...ClientOption) (*ArkServiceFinalizePaymentOK, error)
ArkServiceGetEventStream(params *ArkServiceGetEventStreamParams, opts ...ClientOption) (*ArkServiceGetEventStreamOK, error)
@@ -114,6 +118,80 @@ func (a *Client) ArkServiceClaimPayment(params *ArkServiceClaimPaymentParams, op
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
ArkServiceCompletePayment ark service complete payment API
*/
func (a *Client) ArkServiceCompletePayment(params *ArkServiceCompletePaymentParams, opts ...ClientOption) (*ArkServiceCompletePaymentOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewArkServiceCompletePaymentParams()
}
op := &runtime.ClientOperation{
ID: "ArkService_CompletePayment",
Method: "POST",
PathPattern: "/v1/payment/complete",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &ArkServiceCompletePaymentReader{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.(*ArkServiceCompletePaymentOK)
if ok {
return success, nil
}
// unexpected success response
unexpectedSuccess := result.(*ArkServiceCompletePaymentDefault)
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
ArkServiceCreatePayment ark service create payment API
*/
func (a *Client) ArkServiceCreatePayment(params *ArkServiceCreatePaymentParams, opts ...ClientOption) (*ArkServiceCreatePaymentOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewArkServiceCreatePaymentParams()
}
op := &runtime.ClientOperation{
ID: "ArkService_CreatePayment",
Method: "POST",
PathPattern: "/v1/payment",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &ArkServiceCreatePaymentReader{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.(*ArkServiceCreatePaymentOK)
if ok {
return success, nil
}
// unexpected success response
unexpectedSuccess := result.(*ArkServiceCreatePaymentDefault)
return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code())
}
/*
ArkServiceFinalizePayment ark service finalize payment 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"
)
// NewArkServiceCompletePaymentParams creates a new ArkServiceCompletePaymentParams 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 NewArkServiceCompletePaymentParams() *ArkServiceCompletePaymentParams {
return &ArkServiceCompletePaymentParams{
timeout: cr.DefaultTimeout,
}
}
// NewArkServiceCompletePaymentParamsWithTimeout creates a new ArkServiceCompletePaymentParams object
// with the ability to set a timeout on a request.
func NewArkServiceCompletePaymentParamsWithTimeout(timeout time.Duration) *ArkServiceCompletePaymentParams {
return &ArkServiceCompletePaymentParams{
timeout: timeout,
}
}
// NewArkServiceCompletePaymentParamsWithContext creates a new ArkServiceCompletePaymentParams object
// with the ability to set a context for a request.
func NewArkServiceCompletePaymentParamsWithContext(ctx context.Context) *ArkServiceCompletePaymentParams {
return &ArkServiceCompletePaymentParams{
Context: ctx,
}
}
// NewArkServiceCompletePaymentParamsWithHTTPClient creates a new ArkServiceCompletePaymentParams object
// with the ability to set a custom HTTPClient for a request.
func NewArkServiceCompletePaymentParamsWithHTTPClient(client *http.Client) *ArkServiceCompletePaymentParams {
return &ArkServiceCompletePaymentParams{
HTTPClient: client,
}
}
/*
ArkServiceCompletePaymentParams contains all the parameters to send to the API endpoint
for the ark service complete payment operation.
Typically these are written to a http.Request.
*/
type ArkServiceCompletePaymentParams struct {
// Body.
Body *models.V1CompletePaymentRequest
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the ark service complete payment params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *ArkServiceCompletePaymentParams) WithDefaults() *ArkServiceCompletePaymentParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the ark service complete payment params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *ArkServiceCompletePaymentParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the ark service complete payment params
func (o *ArkServiceCompletePaymentParams) WithTimeout(timeout time.Duration) *ArkServiceCompletePaymentParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the ark service complete payment params
func (o *ArkServiceCompletePaymentParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the ark service complete payment params
func (o *ArkServiceCompletePaymentParams) WithContext(ctx context.Context) *ArkServiceCompletePaymentParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the ark service complete payment params
func (o *ArkServiceCompletePaymentParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the ark service complete payment params
func (o *ArkServiceCompletePaymentParams) WithHTTPClient(client *http.Client) *ArkServiceCompletePaymentParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the ark service complete payment params
func (o *ArkServiceCompletePaymentParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithBody adds the body to the ark service complete payment params
func (o *ArkServiceCompletePaymentParams) WithBody(body *models.V1CompletePaymentRequest) *ArkServiceCompletePaymentParams {
o.SetBody(body)
return o
}
// SetBody adds the body to the ark service complete payment params
func (o *ArkServiceCompletePaymentParams) SetBody(body *models.V1CompletePaymentRequest) {
o.Body = body
}
// WriteToRequest writes these params to a swagger request
func (o *ArkServiceCompletePaymentParams) 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,185 @@
// 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"
)
// ArkServiceCompletePaymentReader is a Reader for the ArkServiceCompletePayment structure.
type ArkServiceCompletePaymentReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *ArkServiceCompletePaymentReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewArkServiceCompletePaymentOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
default:
result := NewArkServiceCompletePaymentDefault(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
}
}
// NewArkServiceCompletePaymentOK creates a ArkServiceCompletePaymentOK with default headers values
func NewArkServiceCompletePaymentOK() *ArkServiceCompletePaymentOK {
return &ArkServiceCompletePaymentOK{}
}
/*
ArkServiceCompletePaymentOK describes a response with status code 200, with default header values.
A successful response.
*/
type ArkServiceCompletePaymentOK struct {
Payload models.V1CompletePaymentResponse
}
// IsSuccess returns true when this ark service complete payment o k response has a 2xx status code
func (o *ArkServiceCompletePaymentOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this ark service complete payment o k response has a 3xx status code
func (o *ArkServiceCompletePaymentOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this ark service complete payment o k response has a 4xx status code
func (o *ArkServiceCompletePaymentOK) IsClientError() bool {
return false
}
// IsServerError returns true when this ark service complete payment o k response has a 5xx status code
func (o *ArkServiceCompletePaymentOK) IsServerError() bool {
return false
}
// IsCode returns true when this ark service complete payment o k response a status code equal to that given
func (o *ArkServiceCompletePaymentOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the ark service complete payment o k response
func (o *ArkServiceCompletePaymentOK) Code() int {
return 200
}
func (o *ArkServiceCompletePaymentOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /v1/payment/complete][%d] arkServiceCompletePaymentOK %s", 200, payload)
}
func (o *ArkServiceCompletePaymentOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /v1/payment/complete][%d] arkServiceCompletePaymentOK %s", 200, payload)
}
func (o *ArkServiceCompletePaymentOK) GetPayload() models.V1CompletePaymentResponse {
return o.Payload
}
func (o *ArkServiceCompletePaymentOK) 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
}
// NewArkServiceCompletePaymentDefault creates a ArkServiceCompletePaymentDefault with default headers values
func NewArkServiceCompletePaymentDefault(code int) *ArkServiceCompletePaymentDefault {
return &ArkServiceCompletePaymentDefault{
_statusCode: code,
}
}
/*
ArkServiceCompletePaymentDefault describes a response with status code -1, with default header values.
An unexpected error response.
*/
type ArkServiceCompletePaymentDefault struct {
_statusCode int
Payload *models.RPCStatus
}
// IsSuccess returns true when this ark service complete payment default response has a 2xx status code
func (o *ArkServiceCompletePaymentDefault) IsSuccess() bool {
return o._statusCode/100 == 2
}
// IsRedirect returns true when this ark service complete payment default response has a 3xx status code
func (o *ArkServiceCompletePaymentDefault) IsRedirect() bool {
return o._statusCode/100 == 3
}
// IsClientError returns true when this ark service complete payment default response has a 4xx status code
func (o *ArkServiceCompletePaymentDefault) IsClientError() bool {
return o._statusCode/100 == 4
}
// IsServerError returns true when this ark service complete payment default response has a 5xx status code
func (o *ArkServiceCompletePaymentDefault) IsServerError() bool {
return o._statusCode/100 == 5
}
// IsCode returns true when this ark service complete payment default response a status code equal to that given
func (o *ArkServiceCompletePaymentDefault) IsCode(code int) bool {
return o._statusCode == code
}
// Code gets the status code for the ark service complete payment default response
func (o *ArkServiceCompletePaymentDefault) Code() int {
return o._statusCode
}
func (o *ArkServiceCompletePaymentDefault) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /v1/payment/complete][%d] ArkService_CompletePayment default %s", o._statusCode, payload)
}
func (o *ArkServiceCompletePaymentDefault) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /v1/payment/complete][%d] ArkService_CompletePayment default %s", o._statusCode, payload)
}
func (o *ArkServiceCompletePaymentDefault) GetPayload() *models.RPCStatus {
return o.Payload
}
func (o *ArkServiceCompletePaymentDefault) 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,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"
)
// NewArkServiceCreatePaymentParams creates a new ArkServiceCreatePaymentParams 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 NewArkServiceCreatePaymentParams() *ArkServiceCreatePaymentParams {
return &ArkServiceCreatePaymentParams{
timeout: cr.DefaultTimeout,
}
}
// NewArkServiceCreatePaymentParamsWithTimeout creates a new ArkServiceCreatePaymentParams object
// with the ability to set a timeout on a request.
func NewArkServiceCreatePaymentParamsWithTimeout(timeout time.Duration) *ArkServiceCreatePaymentParams {
return &ArkServiceCreatePaymentParams{
timeout: timeout,
}
}
// NewArkServiceCreatePaymentParamsWithContext creates a new ArkServiceCreatePaymentParams object
// with the ability to set a context for a request.
func NewArkServiceCreatePaymentParamsWithContext(ctx context.Context) *ArkServiceCreatePaymentParams {
return &ArkServiceCreatePaymentParams{
Context: ctx,
}
}
// NewArkServiceCreatePaymentParamsWithHTTPClient creates a new ArkServiceCreatePaymentParams object
// with the ability to set a custom HTTPClient for a request.
func NewArkServiceCreatePaymentParamsWithHTTPClient(client *http.Client) *ArkServiceCreatePaymentParams {
return &ArkServiceCreatePaymentParams{
HTTPClient: client,
}
}
/*
ArkServiceCreatePaymentParams contains all the parameters to send to the API endpoint
for the ark service create payment operation.
Typically these are written to a http.Request.
*/
type ArkServiceCreatePaymentParams struct {
// Body.
Body *models.V1CreatePaymentRequest
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the ark service create payment params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *ArkServiceCreatePaymentParams) WithDefaults() *ArkServiceCreatePaymentParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the ark service create payment params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *ArkServiceCreatePaymentParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the ark service create payment params
func (o *ArkServiceCreatePaymentParams) WithTimeout(timeout time.Duration) *ArkServiceCreatePaymentParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the ark service create payment params
func (o *ArkServiceCreatePaymentParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the ark service create payment params
func (o *ArkServiceCreatePaymentParams) WithContext(ctx context.Context) *ArkServiceCreatePaymentParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the ark service create payment params
func (o *ArkServiceCreatePaymentParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the ark service create payment params
func (o *ArkServiceCreatePaymentParams) WithHTTPClient(client *http.Client) *ArkServiceCreatePaymentParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the ark service create payment params
func (o *ArkServiceCreatePaymentParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithBody adds the body to the ark service create payment params
func (o *ArkServiceCreatePaymentParams) WithBody(body *models.V1CreatePaymentRequest) *ArkServiceCreatePaymentParams {
o.SetBody(body)
return o
}
// SetBody adds the body to the ark service create payment params
func (o *ArkServiceCreatePaymentParams) SetBody(body *models.V1CreatePaymentRequest) {
o.Body = body
}
// WriteToRequest writes these params to a swagger request
func (o *ArkServiceCreatePaymentParams) 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"
)
// ArkServiceCreatePaymentReader is a Reader for the ArkServiceCreatePayment structure.
type ArkServiceCreatePaymentReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *ArkServiceCreatePaymentReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewArkServiceCreatePaymentOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
default:
result := NewArkServiceCreatePaymentDefault(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
}
}
// NewArkServiceCreatePaymentOK creates a ArkServiceCreatePaymentOK with default headers values
func NewArkServiceCreatePaymentOK() *ArkServiceCreatePaymentOK {
return &ArkServiceCreatePaymentOK{}
}
/*
ArkServiceCreatePaymentOK describes a response with status code 200, with default header values.
A successful response.
*/
type ArkServiceCreatePaymentOK struct {
Payload *models.V1CreatePaymentResponse
}
// IsSuccess returns true when this ark service create payment o k response has a 2xx status code
func (o *ArkServiceCreatePaymentOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this ark service create payment o k response has a 3xx status code
func (o *ArkServiceCreatePaymentOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this ark service create payment o k response has a 4xx status code
func (o *ArkServiceCreatePaymentOK) IsClientError() bool {
return false
}
// IsServerError returns true when this ark service create payment o k response has a 5xx status code
func (o *ArkServiceCreatePaymentOK) IsServerError() bool {
return false
}
// IsCode returns true when this ark service create payment o k response a status code equal to that given
func (o *ArkServiceCreatePaymentOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the ark service create payment o k response
func (o *ArkServiceCreatePaymentOK) Code() int {
return 200
}
func (o *ArkServiceCreatePaymentOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /v1/payment][%d] arkServiceCreatePaymentOK %s", 200, payload)
}
func (o *ArkServiceCreatePaymentOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /v1/payment][%d] arkServiceCreatePaymentOK %s", 200, payload)
}
func (o *ArkServiceCreatePaymentOK) GetPayload() *models.V1CreatePaymentResponse {
return o.Payload
}
func (o *ArkServiceCreatePaymentOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
o.Payload = new(models.V1CreatePaymentResponse)
// response payload
if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}
// NewArkServiceCreatePaymentDefault creates a ArkServiceCreatePaymentDefault with default headers values
func NewArkServiceCreatePaymentDefault(code int) *ArkServiceCreatePaymentDefault {
return &ArkServiceCreatePaymentDefault{
_statusCode: code,
}
}
/*
ArkServiceCreatePaymentDefault describes a response with status code -1, with default header values.
An unexpected error response.
*/
type ArkServiceCreatePaymentDefault struct {
_statusCode int
Payload *models.RPCStatus
}
// IsSuccess returns true when this ark service create payment default response has a 2xx status code
func (o *ArkServiceCreatePaymentDefault) IsSuccess() bool {
return o._statusCode/100 == 2
}
// IsRedirect returns true when this ark service create payment default response has a 3xx status code
func (o *ArkServiceCreatePaymentDefault) IsRedirect() bool {
return o._statusCode/100 == 3
}
// IsClientError returns true when this ark service create payment default response has a 4xx status code
func (o *ArkServiceCreatePaymentDefault) IsClientError() bool {
return o._statusCode/100 == 4
}
// IsServerError returns true when this ark service create payment default response has a 5xx status code
func (o *ArkServiceCreatePaymentDefault) IsServerError() bool {
return o._statusCode/100 == 5
}
// IsCode returns true when this ark service create payment default response a status code equal to that given
func (o *ArkServiceCreatePaymentDefault) IsCode(code int) bool {
return o._statusCode == code
}
// Code gets the status code for the ark service create payment default response
func (o *ArkServiceCreatePaymentDefault) Code() int {
return o._statusCode
}
func (o *ArkServiceCreatePaymentDefault) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /v1/payment][%d] ArkService_CreatePayment default %s", o._statusCode, payload)
}
func (o *ArkServiceCreatePaymentDefault) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[POST /v1/payment][%d] ArkService_CreatePayment default %s", o._statusCode, payload)
}
func (o *ArkServiceCreatePaymentDefault) GetPayload() *models.RPCStatus {
return o.Payload
}
func (o *ArkServiceCreatePaymentDefault) 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,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"
)
// V1CompletePaymentRequest v1 complete payment request
//
// swagger:model v1CompletePaymentRequest
type V1CompletePaymentRequest struct {
// signed redeem tx
SignedRedeemTx string `json:"signedRedeemTx,omitempty"`
// signed unconditional forfeit txs
SignedUnconditionalForfeitTxs []string `json:"signedUnconditionalForfeitTxs"`
}
// Validate validates this v1 complete payment request
func (m *V1CompletePaymentRequest) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this v1 complete payment request based on context it is used
func (m *V1CompletePaymentRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *V1CompletePaymentRequest) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *V1CompletePaymentRequest) UnmarshalBinary(b []byte) error {
var res V1CompletePaymentRequest
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,11 @@
// 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
// V1CompletePaymentResponse v1 complete payment response
//
// swagger:model v1CompletePaymentResponse
type V1CompletePaymentResponse interface{}

View File

@@ -0,0 +1,183 @@
// 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"
"strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// V1CreatePaymentRequest v1 create payment request
//
// swagger:model v1CreatePaymentRequest
type V1CreatePaymentRequest struct {
// inputs
Inputs []*V1Input `json:"inputs"`
// outputs
Outputs []*V1Output `json:"outputs"`
}
// Validate validates this v1 create payment request
func (m *V1CreatePaymentRequest) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateInputs(formats); err != nil {
res = append(res, err)
}
if err := m.validateOutputs(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *V1CreatePaymentRequest) validateInputs(formats strfmt.Registry) error {
if swag.IsZero(m.Inputs) { // not required
return nil
}
for i := 0; i < len(m.Inputs); i++ {
if swag.IsZero(m.Inputs[i]) { // not required
continue
}
if m.Inputs[i] != nil {
if err := m.Inputs[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("inputs" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("inputs" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *V1CreatePaymentRequest) validateOutputs(formats strfmt.Registry) error {
if swag.IsZero(m.Outputs) { // not required
return nil
}
for i := 0; i < len(m.Outputs); i++ {
if swag.IsZero(m.Outputs[i]) { // not required
continue
}
if m.Outputs[i] != nil {
if err := m.Outputs[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("outputs" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("outputs" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// ContextValidate validate this v1 create payment request based on the context it is used
func (m *V1CreatePaymentRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateInputs(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateOutputs(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *V1CreatePaymentRequest) contextValidateInputs(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Inputs); i++ {
if m.Inputs[i] != nil {
if swag.IsZero(m.Inputs[i]) { // not required
return nil
}
if err := m.Inputs[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("inputs" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("inputs" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
func (m *V1CreatePaymentRequest) contextValidateOutputs(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Outputs); i++ {
if m.Outputs[i] != nil {
if swag.IsZero(m.Outputs[i]) { // not required
return nil
}
if err := m.Outputs[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("outputs" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("outputs" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *V1CreatePaymentRequest) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *V1CreatePaymentRequest) UnmarshalBinary(b []byte) error {
var res V1CreatePaymentRequest
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"
)
// V1CreatePaymentResponse v1 create payment response
//
// swagger:model v1CreatePaymentResponse
type V1CreatePaymentResponse struct {
// signed only by the ASP
SignedRedeemTx string `json:"signedRedeemTx,omitempty"`
// usigned unconditional forfeit txs
UsignedUnconditionalForfeitTxs []string `json:"usignedUnconditionalForfeitTxs"`
}
// Validate validates this v1 create payment response
func (m *V1CreatePaymentResponse) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this v1 create payment response based on context it is used
func (m *V1CreatePaymentResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *V1CreatePaymentResponse) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *V1CreatePaymentResponse) UnmarshalBinary(b []byte) error {
var res V1CreatePaymentResponse
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"
)
// V1PendingPayment v1 pending payment
//
// swagger:model v1PendingPayment
type V1PendingPayment struct {
// redeem tx
RedeemTx string `json:"redeemTx,omitempty"`
// unconditional forfeit txs
UnconditionalForfeitTxs []string `json:"unconditionalForfeitTxs"`
}
// Validate validates this v1 pending payment
func (m *V1PendingPayment) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this v1 pending payment based on context it is used
func (m *V1PendingPayment) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *V1PendingPayment) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *V1PendingPayment) UnmarshalBinary(b []byte) error {
var res V1PendingPayment
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -24,6 +24,12 @@ type V1Vtxo struct {
// outpoint
Outpoint *V1Input `json:"outpoint,omitempty"`
// pending
Pending bool `json:"pending,omitempty"`
// pending data
PendingData *V1PendingPayment `json:"pendingData,omitempty"`
// pool txid
PoolTxid string `json:"poolTxid,omitempty"`
@@ -48,6 +54,10 @@ func (m *V1Vtxo) Validate(formats strfmt.Registry) error {
res = append(res, err)
}
if err := m.validatePendingData(formats); err != nil {
res = append(res, err)
}
if err := m.validateReceiver(formats); err != nil {
res = append(res, err)
}
@@ -77,6 +87,25 @@ func (m *V1Vtxo) validateOutpoint(formats strfmt.Registry) error {
return nil
}
func (m *V1Vtxo) validatePendingData(formats strfmt.Registry) error {
if swag.IsZero(m.PendingData) { // not required
return nil
}
if m.PendingData != nil {
if err := m.PendingData.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("pendingData")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("pendingData")
}
return err
}
}
return nil
}
func (m *V1Vtxo) validateReceiver(formats strfmt.Registry) error {
if swag.IsZero(m.Receiver) { // not required
return nil
@@ -104,6 +133,10 @@ func (m *V1Vtxo) ContextValidate(ctx context.Context, formats strfmt.Registry) e
res = append(res, err)
}
if err := m.contextValidatePendingData(ctx, formats); err != nil {
res = append(res, err)
}
if err := m.contextValidateReceiver(ctx, formats); err != nil {
res = append(res, err)
}
@@ -135,6 +168,27 @@ func (m *V1Vtxo) contextValidateOutpoint(ctx context.Context, formats strfmt.Reg
return nil
}
func (m *V1Vtxo) contextValidatePendingData(ctx context.Context, formats strfmt.Registry) error {
if m.PendingData != nil {
if swag.IsZero(m.PendingData) { // not required
return nil
}
if err := m.PendingData.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("pendingData")
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("pendingData")
}
return err
}
}
return nil
}
func (m *V1Vtxo) contextValidateReceiver(ctx context.Context, formats strfmt.Registry) error {
if m.Receiver != nil {

View File

@@ -547,6 +547,17 @@ func (a *covenantArkClient) CollaborativeRedeem(
return poolTxID, nil
}
func (a *covenantArkClient) SendAsync(
ctx context.Context,
withExpiryCoinselect bool, receivers []Receiver,
) (string, error) {
return "", fmt.Errorf("not implemented")
}
func (a *covenantArkClient) ClaimAsync(ctx context.Context) (string, error) {
return "", fmt.Errorf("not implemented")
}
func (a *covenantArkClient) sendOnchain(
ctx context.Context, receivers []Receiver,
) (string, error) {

View File

@@ -478,7 +478,7 @@ func (a *covenantlessArkClient) UnilateralRedeem(ctx context.Context) error {
vtxos := make([]client.Vtxo, 0)
for _, offchainAddr := range offchainAddrs {
spendableVtxos, err := a.getVtxos(ctx, offchainAddr, false)
spendableVtxos, _, err := a.getVtxos(ctx, offchainAddr, false)
if err != nil {
return err
}
@@ -574,7 +574,7 @@ func (a *covenantlessArkClient) CollaborativeRedeem(
vtxos := make([]client.Vtxo, 0)
for _, offchainAddr := range offchainAddrs {
spendableVtxos, err := a.getVtxos(ctx, offchainAddr, withExpiryCoinselect)
spendableVtxos, _, err := a.getVtxos(ctx, offchainAddr, withExpiryCoinselect)
if err != nil {
return "", err
}
@@ -627,6 +627,145 @@ func (a *covenantlessArkClient) CollaborativeRedeem(
return poolTxID, nil
}
func (a *covenantlessArkClient) SendAsync(
ctx context.Context,
withExpiryCoinselect bool, receivers []Receiver,
) (string, error) {
if len(receivers) <= 0 {
return "", fmt.Errorf("missing receivers")
}
for _, receiver := range receivers {
isOnchain, _, _, err := utils.DecodeReceiverAddress(receiver.To())
if err != nil {
return "", err
}
if isOnchain {
return "", fmt.Errorf("all receiver addresses must be offchain addresses")
}
}
offchainAddrs, _, _, err := a.wallet.GetAddresses(ctx)
if err != nil {
return "", err
}
_, _, aspPubKey, err := common.DecodeAddress(offchainAddrs[0])
if err != nil {
return "", err
}
receiversOutput := make([]client.Output, 0)
sumOfReceivers := uint64(0)
for _, receiver := range receivers {
_, _, aspKey, err := common.DecodeAddress(receiver.To())
if err != nil {
return "", fmt.Errorf("invalid receiver address: %s", err)
}
if !bytes.Equal(
aspPubKey.SerializeCompressed(), aspKey.SerializeCompressed(),
) {
return "", fmt.Errorf("invalid receiver address '%s': must be associated with the connected service provider", receiver)
}
if receiver.Amount() < DUST {
return "", fmt.Errorf("invalid amount (%d), must be greater than dust %d", receiver.Amount(), DUST)
}
receiversOutput = append(receiversOutput, client.Output{
Address: receiver.To(),
Amount: receiver.Amount(),
})
sumOfReceivers += receiver.Amount()
}
vtxos, _, err := a.getVtxos(ctx, offchainAddrs[0], withExpiryCoinselect)
if err != nil {
return "", err
}
selectedCoins, changeAmount, err := utils.CoinSelect(
vtxos, sumOfReceivers, DUST, withExpiryCoinselect,
)
if err != nil {
return "", err
}
if changeAmount > 0 {
changeReceiver := client.Output{
Address: offchainAddrs[0],
Amount: changeAmount,
}
receiversOutput = append(receiversOutput, changeReceiver)
}
inputs := make([]client.VtxoKey, 0, len(selectedCoins))
for _, coin := range selectedCoins {
inputs = append(inputs, coin.VtxoKey)
}
redeemTx, unconditionalForfeitTxs, err := a.client.CreatePayment(
ctx, inputs, receiversOutput)
if err != nil {
return "", err
}
// TODO verify the redeem tx signature
signedUnconditionalForfeitTxs := make([]string, 0, len(unconditionalForfeitTxs))
for _, tx := range unconditionalForfeitTxs {
signedForfeitTx, err := a.wallet.SignTransaction(ctx, a.explorer, tx)
if err != nil {
return "", err
}
signedUnconditionalForfeitTxs = append(signedUnconditionalForfeitTxs, signedForfeitTx)
}
signedRedeemTx, err := a.wallet.SignTransaction(ctx, a.explorer, redeemTx)
if err != nil {
return "", err
}
if err = a.client.CompletePayment(
ctx, signedRedeemTx, signedUnconditionalForfeitTxs,
); err != nil {
return "", err
}
return signedRedeemTx, nil
}
func (a *covenantlessArkClient) ClaimAsync(
ctx context.Context,
) (string, error) {
myselfOffchain, _, err := a.wallet.NewAddress(ctx, false)
if err != nil {
return "", err
}
_, pendingVtxos, err := a.getVtxos(ctx, myselfOffchain, false)
if err != nil {
return "", err
}
var pendingBalance uint64
for _, vtxo := range pendingVtxos {
pendingBalance += vtxo.Amount
}
if pendingBalance == 0 {
return "", nil
}
receiver := client.Output{
Address: myselfOffchain,
Amount: pendingBalance,
}
return a.selfTransferAllPendingPayments(ctx, pendingVtxos, receiver)
}
func (a *covenantlessArkClient) sendOnchain(
ctx context.Context, receivers []Receiver,
) (string, error) {
@@ -817,7 +956,7 @@ func (a *covenantlessArkClient) sendOffchain(
vtxos := make([]client.Vtxo, 0)
for _, offchainAddr := range offchainAddrs {
spendableVtxos, err := a.getVtxos(ctx, offchainAddr, withExpiryCoinselect)
spendableVtxos, _, err := a.getVtxos(ctx, offchainAddr, withExpiryCoinselect)
if err != nil {
return "", err
}
@@ -1394,16 +1533,20 @@ func (a *covenantlessArkClient) getRedeemBranches(
return redeemBranches, nil
}
// TODO (@louisinger): return pending balance in dedicated map.
// Currently, the returned balance is calculated from both spendable and
// pending vtxos.
func (a *covenantlessArkClient) getOffchainBalance(
ctx context.Context, addr string, computeVtxoExpiration bool,
) (uint64, map[int64]uint64, error) {
amountByExpiration := make(map[int64]uint64, 0)
vtxos, err := a.getVtxos(ctx, addr, computeVtxoExpiration)
spendableVtxos, pendingVtxos, err := a.getVtxos(ctx, addr, computeVtxoExpiration)
if err != nil {
return 0, nil, err
}
var balance uint64
vtxos := append(spendableVtxos, pendingVtxos...)
for _, vtxo := range vtxos {
balance += vtxo.Amount
@@ -1423,28 +1566,38 @@ func (a *covenantlessArkClient) getOffchainBalance(
func (a *covenantlessArkClient) getVtxos(
ctx context.Context, addr string, computeVtxoExpiration bool,
) ([]client.Vtxo, error) {
) ([]client.Vtxo, []client.Vtxo, error) {
vtxos, _, err := a.client.ListVtxos(ctx, addr)
if err != nil {
return nil, err
return nil, nil, err
}
pendingVtxos := make([]client.Vtxo, 0)
spendableVtxos := make([]client.Vtxo, 0)
for _, vtxo := range vtxos {
if vtxo.Pending {
pendingVtxos = append(pendingVtxos, vtxo)
continue
}
spendableVtxos = append(spendableVtxos, vtxo)
}
if !computeVtxoExpiration {
return vtxos, nil
return spendableVtxos, pendingVtxos, nil
}
redeemBranches, err := a.getRedeemBranches(ctx, vtxos)
redeemBranches, err := a.getRedeemBranches(ctx, spendableVtxos)
if err != nil {
return nil, err
return nil, nil, err
}
for vtxoTxid, branch := range redeemBranches {
expiration, err := branch.ExpiresAt()
if err != nil {
return nil, err
return nil, nil, err
}
for i, vtxo := range vtxos {
for i, vtxo := range spendableVtxos {
if vtxo.Txid == vtxoTxid {
vtxos[i].ExpiresAt = expiration
break
@@ -1452,5 +1605,35 @@ func (a *covenantlessArkClient) getVtxos(
}
}
return vtxos, nil
return spendableVtxos, pendingVtxos, nil
}
func (a *covenantlessArkClient) selfTransferAllPendingPayments(
ctx context.Context, pendingVtxos []client.Vtxo, myself client.Output,
) (string, error) {
inputs := make([]client.VtxoKey, 0, len(pendingVtxos))
for _, coin := range pendingVtxos {
inputs = append(inputs, coin.VtxoKey)
}
outputs := []client.Output{myself}
paymentID, err := a.client.RegisterPayment(ctx, inputs)
if err != nil {
return "", err
}
if err := a.client.ClaimPayment(ctx, paymentID, outputs); err != nil {
return "", err
}
roundTxid, err := a.handleRoundStream(
ctx, paymentID, pendingVtxos, outputs,
)
if err != nil {
return "", err
}
return roundTxid, nil
}

View File

@@ -106,12 +106,11 @@ func main() {
fmt.Println("")
log.Infof("alice is sending %d sats to bob offchain...", amount)
txid, err = aliceArkClient.SendOffChain(ctx, false, receivers)
if err != nil {
if _, err = aliceArkClient.SendAsync(ctx, false, receivers); err != nil {
log.Fatal(err)
}
log.Infof("payment completed in round tx: %s", txid)
log.Info("payment completed out of round")
if err := generateBlock(); err != nil {
log.Fatal(err)
@@ -135,6 +134,15 @@ func main() {
log.Infof("bob onchain balance: %d", bobBalance.OnchainBalance.SpendableAmount)
log.Infof("bob offchain balance: %d", bobBalance.OffchainBalance.Total)
fmt.Println("")
log.Info("bob is claiming the incoming payment...")
roundTxid, err := bobArkClient.ClaimAsync(ctx)
if err != nil {
log.Fatal(err)
}
log.Infof("bob claimed the incoming payment in round %s", roundTxid)
}
func setupArkClient() (arksdk.ArkClient, error) {

View File

@@ -15,7 +15,6 @@ import (
"github.com/ark-network/ark/pkg/client-sdk/internal/utils"
"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/wire"
log "github.com/sirupsen/logrus"
"github.com/vulpemventures/go-elements/psetv2"
"github.com/vulpemventures/go-elements/transaction"
)
@@ -97,8 +96,7 @@ func (e *explorerSvc) GetFeeRate() (float64, error) {
}
if len(response) == 0 {
log.Debug("empty fee-estimates response, default to 2 sat/vbyte")
return 2, nil
return 1, nil
}
return response["1"], nil

View File

@@ -201,7 +201,7 @@ func (s *bitcoinWallet) SignTransaction(
switch c := closure.(type) {
case *bitcointree.CSVSigClosure:
sign = bytes.Equal(c.Pubkey.SerializeCompressed()[1:], pubkey.SerializeCompressed()[1:])
case *bitcointree.ForfeitClosure:
case *bitcointree.MultisigClosure:
sign = bytes.Equal(c.Pubkey.SerializeCompressed()[1:], pubkey.SerializeCompressed()[1:])
}

View File

@@ -33,13 +33,16 @@ lint:
## run: run in dev mode
run: clean
@echo "Running arkd in dev mode..."
@export ARK_WALLET_ADDR=localhost:18000; \
@export ARK_NEUTRINO_PEER=localhost:18444; \
export ARK_ROUND_INTERVAL=10; \
export ARK_LOG_LEVEL=5; \
export ARK_NETWORK=liquidregtest; \
export ARK_NETWORK=regtest; \
export ARK_PORT=8080; \
export ARK_NO_TLS=true; \
export ARK_NO_MACAROONS=true; \
export ARK_TX_BUILDER_TYPE=covenantless; \
export ARK_ESPLORA_URL=http://localhost:3000; \
export ARK_MIN_RELAY_FEE=200; \
go run ./cmd/arkd
## test: runs unit and component tests

View File

@@ -101,6 +101,38 @@
]
}
},
"/v1/payment": {
"post": {
"operationId": "ArkService_CreatePayment",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1CreatePaymentResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1CreatePaymentRequest"
}
}
],
"tags": [
"ArkService"
]
}
},
"/v1/payment/claim": {
"post": {
"operationId": "ArkService_ClaimPayment",
@@ -133,6 +165,38 @@
]
}
},
"/v1/payment/complete": {
"post": {
"operationId": "ArkService_CompletePayment",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1CompletePaymentResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1CompletePaymentRequest"
}
}
],
"tags": [
"ArkService"
]
}
},
"/v1/payment/finalize": {
"post": {
"operationId": "ArkService_FinalizePayment",
@@ -368,6 +432,57 @@
"v1ClaimPaymentResponse": {
"type": "object"
},
"v1CompletePaymentRequest": {
"type": "object",
"properties": {
"signedRedeemTx": {
"type": "string"
},
"signedUnconditionalForfeitTxs": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"v1CompletePaymentResponse": {
"type": "object"
},
"v1CreatePaymentRequest": {
"type": "object",
"properties": {
"inputs": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/v1Input"
}
},
"outputs": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/v1Output"
}
}
}
},
"v1CreatePaymentResponse": {
"type": "object",
"properties": {
"signedRedeemTx": {
"type": "string",
"title": "signed only by the ASP"
},
"usignedUnconditionalForfeitTxs": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"v1FinalizePaymentRequest": {
"type": "object",
"properties": {
@@ -517,6 +632,20 @@
}
}
},
"v1PendingPayment": {
"type": "object",
"properties": {
"redeemTx": {
"type": "string"
},
"unconditionalForfeitTxs": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"v1PingResponse": {
"type": "object",
"properties": {
@@ -696,6 +825,12 @@
},
"swept": {
"type": "boolean"
},
"pending": {
"type": "boolean"
},
"pendingData": {
"$ref": "#/definitions/v1PendingPayment"
}
}
}

View File

@@ -0,0 +1,820 @@
{
"swagger": "2.0",
"info": {
"title": "clark/v1/service.proto",
"version": "version not set"
},
"tags": [
{
"name": "ArkService"
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"paths": {
"/v1/events": {
"get": {
"operationId": "ArkService_GetEventStream",
"responses": {
"200": {
"description": "A successful response.(streaming responses)",
"schema": {
"type": "object",
"properties": {
"result": {
"$ref": "#/definitions/v1GetEventStreamResponse"
},
"error": {
"$ref": "#/definitions/rpcStatus"
}
},
"title": "Stream result of v1GetEventStreamResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"tags": [
"ArkService"
]
}
},
"/v1/info": {
"get": {
"operationId": "ArkService_GetInfo",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1GetInfoResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"tags": [
"ArkService"
]
}
},
"/v1/onboard": {
"post": {
"operationId": "ArkService_Onboard",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1OnboardResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1OnboardRequest"
}
}
],
"tags": [
"ArkService"
]
}
},
"/v1/payment/async": {
"post": {
"operationId": "ArkService_CreateAsyncPayment",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1CreateAsyncPaymentResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1CreateAsyncPaymentRequest"
}
}
],
"tags": [
"ArkService"
]
}
},
"/v1/payment/async/complete": {
"post": {
"operationId": "ArkService_CompleteAsyncPayment",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1CompleteAsyncPaymentResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1CompleteAsyncPaymentRequest"
}
}
],
"tags": [
"ArkService"
]
}
},
"/v1/payment/claim": {
"post": {
"operationId": "ArkService_ClaimPayment",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1ClaimPaymentResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1ClaimPaymentRequest"
}
}
],
"tags": [
"ArkService"
]
}
},
"/v1/payment/finalize": {
"post": {
"operationId": "ArkService_FinalizePayment",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1FinalizePaymentResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1FinalizePaymentRequest"
}
}
],
"tags": [
"ArkService"
]
}
},
"/v1/payment/register": {
"post": {
"operationId": "ArkService_RegisterPayment",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1RegisterPaymentResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1RegisterPaymentRequest"
}
}
],
"tags": [
"ArkService"
]
}
},
"/v1/ping/{paymentId}": {
"get": {
"operationId": "ArkService_Ping",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1PingResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "paymentId",
"in": "path",
"required": true,
"type": "string"
}
],
"tags": [
"ArkService"
]
}
},
"/v1/round/id/{id}": {
"get": {
"operationId": "ArkService_GetRoundById",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1GetRoundByIdResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"type": "string"
}
],
"tags": [
"ArkService"
]
}
},
"/v1/round/{txid}": {
"get": {
"summary": "TODO BTC: signTree rpc",
"operationId": "ArkService_GetRound",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1GetRoundResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "txid",
"in": "path",
"required": true,
"type": "string"
}
],
"tags": [
"ArkService"
]
}
},
"/v1/vtxos/{address}": {
"get": {
"operationId": "ArkService_ListVtxos",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1ListVtxosResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "address",
"in": "path",
"required": true,
"type": "string"
}
],
"tags": [
"ArkService"
]
}
}
},
"definitions": {
"protobufAny": {
"type": "object",
"properties": {
"@type": {
"type": "string"
}
},
"additionalProperties": {}
},
"rpcStatus": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
},
"details": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/protobufAny"
}
}
}
},
"v1ClaimPaymentRequest": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Mocks wabisabi's credentials."
},
"outputs": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/v1Output"
},
"description": "List of receivers for a registered payment."
}
}
},
"v1ClaimPaymentResponse": {
"type": "object"
},
"v1CompleteAsyncPaymentRequest": {
"type": "object",
"properties": {
"signedSenderTx": {
"type": "string"
},
"signedReceiverTx": {
"type": "string"
},
"signedUnconditionalForfeitTx": {
"type": "string"
}
}
},
"v1CompleteAsyncPaymentResponse": {
"type": "object",
"properties": {
"senderTx": {
"type": "string"
},
"receiverTx": {
"type": "string"
},
"unconditionalForfeitTx": {
"type": "string"
}
}
},
"v1CreateAsyncPaymentRequest": {
"type": "object",
"properties": {
"input": {
"$ref": "#/definitions/v1Input"
},
"receiverPubkey": {
"type": "string"
}
}
},
"v1CreateAsyncPaymentResponse": {
"type": "object",
"properties": {
"unsignedSenderTx": {
"type": "string"
},
"unsignedReceiverTx": {
"type": "string"
},
"usignedUnconditionalForfeitTx": {
"type": "string"
}
}
},
"v1FinalizePaymentRequest": {
"type": "object",
"properties": {
"signedForfeitTxs": {
"type": "array",
"items": {
"type": "string"
},
"description": "Forfeit txs signed by the user."
}
}
},
"v1FinalizePaymentResponse": {
"type": "object"
},
"v1GetEventStreamResponse": {
"type": "object",
"properties": {
"roundFinalization": {
"$ref": "#/definitions/v1RoundFinalizationEvent",
"title": "TODO: BTC add \"signTree\" event"
},
"roundFinalized": {
"$ref": "#/definitions/v1RoundFinalizedEvent"
},
"roundFailed": {
"$ref": "#/definitions/v1RoundFailed"
}
}
},
"v1GetInfoResponse": {
"type": "object",
"properties": {
"pubkey": {
"type": "string"
},
"roundLifetime": {
"type": "string",
"format": "int64"
},
"unilateralExitDelay": {
"type": "string",
"format": "int64"
},
"roundInterval": {
"type": "string",
"format": "int64"
},
"network": {
"type": "string"
},
"minRelayFee": {
"type": "string",
"format": "int64"
}
}
},
"v1GetRoundByIdResponse": {
"type": "object",
"properties": {
"round": {
"$ref": "#/definitions/v1Round"
}
}
},
"v1GetRoundResponse": {
"type": "object",
"properties": {
"round": {
"$ref": "#/definitions/v1Round"
}
}
},
"v1Input": {
"type": "object",
"properties": {
"txid": {
"type": "string"
},
"vout": {
"type": "integer",
"format": "int64"
}
}
},
"v1ListVtxosResponse": {
"type": "object",
"properties": {
"spendableVtxos": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/v1Vtxo"
}
},
"spentVtxos": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/v1Vtxo"
}
}
}
},
"v1Node": {
"type": "object",
"properties": {
"txid": {
"type": "string"
},
"tx": {
"type": "string"
},
"parentTxid": {
"type": "string"
}
}
},
"v1OnboardRequest": {
"type": "object",
"properties": {
"boardingTx": {
"type": "string"
},
"congestionTree": {
"$ref": "#/definitions/v1Tree"
},
"userPubkey": {
"type": "string"
}
}
},
"v1OnboardResponse": {
"type": "object"
},
"v1Output": {
"type": "object",
"properties": {
"address": {
"type": "string",
"description": "Either the offchain or onchain address."
},
"amount": {
"type": "string",
"format": "uint64",
"description": "Amount to send in satoshis."
}
}
},
"v1PingResponse": {
"type": "object",
"properties": {
"forfeitTxs": {
"type": "array",
"items": {
"type": "string"
}
},
"event": {
"$ref": "#/definitions/v1RoundFinalizationEvent"
}
}
},
"v1RegisterPaymentRequest": {
"type": "object",
"properties": {
"inputs": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/v1Input"
}
}
}
},
"v1RegisterPaymentResponse": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Mocks wabisabi's credentials."
}
}
},
"v1Round": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"start": {
"type": "string",
"format": "int64"
},
"end": {
"type": "string",
"format": "int64"
},
"poolTx": {
"type": "string"
},
"congestionTree": {
"$ref": "#/definitions/v1Tree"
},
"forfeitTxs": {
"type": "array",
"items": {
"type": "string"
}
},
"connectors": {
"type": "array",
"items": {
"type": "string"
}
},
"stage": {
"$ref": "#/definitions/v1RoundStage"
}
}
},
"v1RoundFailed": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"reason": {
"type": "string"
}
}
},
"v1RoundFinalizationEvent": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"poolTx": {
"type": "string"
},
"forfeitTxs": {
"type": "array",
"items": {
"type": "string"
}
},
"congestionTree": {
"$ref": "#/definitions/v1Tree"
},
"connectors": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"v1RoundFinalizedEvent": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"poolTxid": {
"type": "string"
}
}
},
"v1RoundStage": {
"type": "string",
"enum": [
"ROUND_STAGE_UNSPECIFIED",
"ROUND_STAGE_REGISTRATION",
"ROUND_STAGE_FINALIZATION",
"ROUND_STAGE_FINALIZED",
"ROUND_STAGE_FAILED"
],
"default": "ROUND_STAGE_UNSPECIFIED"
},
"v1Tree": {
"type": "object",
"properties": {
"levels": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/v1TreeLevel"
}
}
}
},
"v1TreeLevel": {
"type": "object",
"properties": {
"nodes": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/v1Node"
}
}
}
},
"v1Vtxo": {
"type": "object",
"properties": {
"outpoint": {
"$ref": "#/definitions/v1Input"
},
"receiver": {
"$ref": "#/definitions/v1Output"
},
"spent": {
"type": "boolean"
},
"poolTxid": {
"type": "string"
},
"spentBy": {
"type": "string"
},
"expireAt": {
"type": "string",
"format": "int64"
},
"swept": {
"type": "boolean"
}
}
}
}
}

View File

@@ -60,8 +60,35 @@ service ArkService {
body: "*"
};
}
rpc CreatePayment(CreatePaymentRequest) returns (CreatePaymentResponse) {
option (google.api.http) = {
post: "/v1/payment"
body: "*"
};
}
rpc CompletePayment(CompletePaymentRequest) returns (CompletePaymentResponse) {
option (google.api.http) = {
post: "/v1/payment/complete"
body: "*"
};
}
}
message CreatePaymentRequest {
repeated Input inputs = 1;
repeated Output outputs = 2;
}
message CreatePaymentResponse {
string signed_redeem_tx = 1; // signed only by the ASP
repeated string usigned_unconditional_forfeit_txs = 2;
}
message CompletePaymentRequest {
string signed_redeem_tx = 1;
repeated string signed_unconditional_forfeit_txs = 2;
}
message CompletePaymentResponse {}
message RegisterPaymentRequest {
repeated Input inputs = 1;
}
@@ -217,4 +244,11 @@ message Vtxo {
string spent_by = 5;
int64 expire_at = 6;
bool swept = 7;
bool pending = 8;
PendingPayment pending_data = 9;
}
message PendingPayment {
string redeem_tx = 1;
repeated string unconditional_forfeit_txs =2;
}

File diff suppressed because it is too large Load Diff

View File

@@ -378,6 +378,58 @@ func local_request_ArkService_Onboard_0(ctx context.Context, marshaler runtime.M
}
func request_ArkService_CreatePayment_0(ctx context.Context, marshaler runtime.Marshaler, client ArkServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CreatePaymentRequest
var metadata runtime.ServerMetadata
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.CreatePayment(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_ArkService_CreatePayment_0(ctx context.Context, marshaler runtime.Marshaler, server ArkServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CreatePaymentRequest
var metadata runtime.ServerMetadata
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.CreatePayment(ctx, &protoReq)
return msg, metadata, err
}
func request_ArkService_CompletePayment_0(ctx context.Context, marshaler runtime.Marshaler, client ArkServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CompletePaymentRequest
var metadata runtime.ServerMetadata
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.CompletePayment(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_ArkService_CompletePayment_0(ctx context.Context, marshaler runtime.Marshaler, server ArkServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq CompletePaymentRequest
var metadata runtime.ServerMetadata
if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.CompletePayment(ctx, &protoReq)
return msg, metadata, err
}
// RegisterArkServiceHandlerServer registers the http handlers for service ArkService to "mux".
// UnaryRPC :call ArkServiceServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
@@ -617,6 +669,56 @@ func RegisterArkServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux,
})
mux.Handle("POST", pattern_ArkService_CreatePayment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ark.v1.ArkService/CreatePayment", runtime.WithHTTPPathPattern("/v1/payment"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_ArkService_CreatePayment_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ArkService_CreatePayment_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_ArkService_CompletePayment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/ark.v1.ArkService/CompletePayment", runtime.WithHTTPPathPattern("/v1/payment/complete"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_ArkService_CompletePayment_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ArkService_CompletePayment_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
@@ -878,6 +980,50 @@ func RegisterArkServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux,
})
mux.Handle("POST", pattern_ArkService_CreatePayment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ark.v1.ArkService/CreatePayment", runtime.WithHTTPPathPattern("/v1/payment"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ArkService_CreatePayment_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ArkService_CreatePayment_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_ArkService_CompletePayment_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/ark.v1.ArkService/CompletePayment", runtime.WithHTTPPathPattern("/v1/payment/complete"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ArkService_CompletePayment_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ArkService_CompletePayment_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
@@ -901,6 +1047,10 @@ var (
pattern_ArkService_GetInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "info"}, ""))
pattern_ArkService_Onboard_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "onboard"}, ""))
pattern_ArkService_CreatePayment_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "payment"}, ""))
pattern_ArkService_CompletePayment_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "payment", "complete"}, ""))
)
var (
@@ -923,4 +1073,8 @@ var (
forward_ArkService_GetInfo_0 = runtime.ForwardResponseMessage
forward_ArkService_Onboard_0 = runtime.ForwardResponseMessage
forward_ArkService_CreatePayment_0 = runtime.ForwardResponseMessage
forward_ArkService_CompletePayment_0 = runtime.ForwardResponseMessage
)

View File

@@ -29,6 +29,8 @@ type ArkServiceClient interface {
ListVtxos(ctx context.Context, in *ListVtxosRequest, opts ...grpc.CallOption) (*ListVtxosResponse, error)
GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error)
Onboard(ctx context.Context, in *OnboardRequest, opts ...grpc.CallOption) (*OnboardResponse, error)
CreatePayment(ctx context.Context, in *CreatePaymentRequest, opts ...grpc.CallOption) (*CreatePaymentResponse, error)
CompletePayment(ctx context.Context, in *CompletePaymentRequest, opts ...grpc.CallOption) (*CompletePaymentResponse, error)
}
type arkServiceClient struct {
@@ -152,6 +154,24 @@ func (c *arkServiceClient) Onboard(ctx context.Context, in *OnboardRequest, opts
return out, nil
}
func (c *arkServiceClient) CreatePayment(ctx context.Context, in *CreatePaymentRequest, opts ...grpc.CallOption) (*CreatePaymentResponse, error) {
out := new(CreatePaymentResponse)
err := c.cc.Invoke(ctx, "/ark.v1.ArkService/CreatePayment", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *arkServiceClient) CompletePayment(ctx context.Context, in *CompletePaymentRequest, opts ...grpc.CallOption) (*CompletePaymentResponse, error) {
out := new(CompletePaymentResponse)
err := c.cc.Invoke(ctx, "/ark.v1.ArkService/CompletePayment", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// ArkServiceServer is the server API for ArkService service.
// All implementations should embed UnimplementedArkServiceServer
// for forward compatibility
@@ -167,6 +187,8 @@ type ArkServiceServer interface {
ListVtxos(context.Context, *ListVtxosRequest) (*ListVtxosResponse, error)
GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error)
Onboard(context.Context, *OnboardRequest) (*OnboardResponse, error)
CreatePayment(context.Context, *CreatePaymentRequest) (*CreatePaymentResponse, error)
CompletePayment(context.Context, *CompletePaymentRequest) (*CompletePaymentResponse, error)
}
// UnimplementedArkServiceServer should be embedded to have forward compatible implementations.
@@ -203,6 +225,12 @@ func (UnimplementedArkServiceServer) GetInfo(context.Context, *GetInfoRequest) (
func (UnimplementedArkServiceServer) Onboard(context.Context, *OnboardRequest) (*OnboardResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Onboard not implemented")
}
func (UnimplementedArkServiceServer) CreatePayment(context.Context, *CreatePaymentRequest) (*CreatePaymentResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreatePayment not implemented")
}
func (UnimplementedArkServiceServer) CompletePayment(context.Context, *CompletePaymentRequest) (*CompletePaymentResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CompletePayment not implemented")
}
// UnsafeArkServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to ArkServiceServer will
@@ -398,6 +426,42 @@ func _ArkService_Onboard_Handler(srv interface{}, ctx context.Context, dec func(
return interceptor(ctx, in, info, handler)
}
func _ArkService_CreatePayment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CreatePaymentRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ArkServiceServer).CreatePayment(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ark.v1.ArkService/CreatePayment",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ArkServiceServer).CreatePayment(ctx, req.(*CreatePaymentRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ArkService_CompletePayment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CompletePaymentRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ArkServiceServer).CompletePayment(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/ark.v1.ArkService/CompletePayment",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ArkServiceServer).CompletePayment(ctx, req.(*CompletePaymentRequest))
}
return interceptor(ctx, in, info, handler)
}
// ArkService_ServiceDesc is the grpc.ServiceDesc for ArkService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@@ -441,6 +505,14 @@ var ArkService_ServiceDesc = grpc.ServiceDesc{
MethodName: "Onboard",
Handler: _ArkService_Onboard_Handler,
},
{
MethodName: "CreatePayment",
Handler: _ArkService_CreatePayment_Handler,
},
{
MethodName: "CompletePayment",
Handler: _ArkService_CompletePayment_Handler,
},
},
Streams: []grpc.StreamDesc{
{

View File

@@ -220,7 +220,7 @@ func post[T any](url, body, key, macaroon, tlsCert string) (result T, err error)
req.Header.Add("X-Macaroon", macaroon)
}
client := &http.Client{
Timeout: 15 * time.Second,
Timeout: 30 * time.Second,
Transport: &http.Transport{
TLSClientConfig: tlsConfig,
},
@@ -267,7 +267,7 @@ func get[T any](url, key, macaroon, tlsCert string) (result T, err error) {
}
client := &http.Client{
Timeout: 15 * time.Second,
Timeout: 30 * time.Second,
Transport: &http.Transport{
TLSClientConfig: tlsConfig,
},
@@ -331,7 +331,7 @@ func getBalance(url, macaroon, tlsCert string) (*balance, error) {
req.Header.Add("X-Macaroon", macaroon)
}
client := &http.Client{
Timeout: 15 * time.Second,
Timeout: 30 * time.Second,
Transport: &http.Transport{
TLSClientConfig: tlsConfig,
},
@@ -384,7 +384,7 @@ func getStatus(url, tlsCert string) (*status, error) {
req.Header.Add("Content-Type", "application/json")
client := &http.Client{
Timeout: 15 * time.Second,
Timeout: 30 * time.Second,
Transport: &http.Transport{
TLSClientConfig: tlsConfig,
},

View File

@@ -45,6 +45,8 @@ type AdminService interface {
GetScheduledSweeps(ctx context.Context) ([]ScheduledSweep, error)
GetRoundDetails(ctx context.Context, roundId string) (*RoundDetails, error)
GetRounds(ctx context.Context, after int64, before int64) ([]string, error)
GetWalletAddress(ctx context.Context) (string, error)
GetWalletStatus(ctx context.Context) (*WalletStatus, error)
}
type adminService struct {
@@ -154,3 +156,24 @@ func (a *adminService) GetScheduledSweeps(ctx context.Context) ([]ScheduledSweep
return scheduledSweeps, nil
}
func (a *adminService) GetWalletAddress(ctx context.Context) (string, error) {
addresses, err := a.walletSvc.DeriveAddresses(ctx, 1)
if err != nil {
return "", err
}
return addresses[0], nil
}
func (a *adminService) GetWalletStatus(ctx context.Context) (*WalletStatus, error) {
status, err := a.walletSvc.Status(ctx)
if err != nil {
return nil, err
}
return &WalletStatus{
IsInitialized: status.IsInitialized(),
IsUnlocked: status.IsUnlocked(),
IsSynced: status.IsSynced(),
}, nil
}

View File

@@ -161,6 +161,14 @@ func (s *covenantService) UpdatePaymentStatus(_ context.Context, id string) ([]s
return nil, nil, nil
}
func (s *covenantService) CompleteAsyncPayment(ctx context.Context, redeemTx string, unconditionalForfeitTxs []string) error {
return fmt.Errorf("unimplemented")
}
func (s *covenantService) CreateAsyncPayment(ctx context.Context, inputs []domain.VtxoKey, receivers []domain.Receiver) (string, []string, error) {
return "", nil, fmt.Errorf("unimplemented")
}
func (s *covenantService) SignVtxos(ctx context.Context, forfeitTxs []string) error {
return s.forfeitTxs.sign(forfeitTxs)
}

View File

@@ -42,6 +42,11 @@ type covenantlessService struct {
onboardingCh chan onboarding
currentRound *domain.Round
asyncPaymentsCache map[domain.VtxoKey]struct {
receivers []domain.Receiver
expireAt int64
}
}
func NewCovenantlessService(
@@ -62,13 +67,30 @@ func NewCovenantlessService(
}
sweeper := newSweeper(walletSvc, repoManager, builder, scheduler)
asyncPaymentsCache := make(map[domain.VtxoKey]struct {
receivers []domain.Receiver
expireAt int64
})
svc := &covenantlessService{
network, pubkey,
roundLifetime, roundInterval, unilateralExitDelay, minRelayFee,
walletSvc, repoManager, builder, scanner, sweeper,
paymentRequests, forfeitTxs, eventsCh, onboardingCh, nil,
network: network,
pubkey: pubkey,
roundLifetime: roundLifetime,
roundInterval: roundInterval,
unilateralExitDelay: unilateralExitDelay,
minRelayFee: minRelayFee,
wallet: walletSvc,
repoManager: repoManager,
builder: builder,
scanner: scanner,
sweeper: sweeper,
paymentRequests: paymentRequests,
forfeitTxs: forfeitTxs,
eventsCh: eventsCh,
onboardingCh: onboardingCh,
asyncPaymentsCache: asyncPaymentsCache,
}
repoManager.RegisterEventsHandler(
func(round *domain.Round) {
go svc.propagateEvents(round)
@@ -115,6 +137,108 @@ func (s *covenantlessService) Stop() {
close(s.onboardingCh)
}
func (s *covenantlessService) CompleteAsyncPayment(
ctx context.Context, redeemTx string, unconditionalForfeitTxs []string,
) error {
// TODO check that the user signed both transactions
redeemPtx, err := psbt.NewFromRawBytes(strings.NewReader(redeemTx), true)
if err != nil {
return fmt.Errorf("failed to parse redeem tx: %s", err)
}
redeemTxid := redeemPtx.UnsignedTx.TxID()
spentVtxos := make([]domain.VtxoKey, 0, len(unconditionalForfeitTxs))
for _, in := range redeemPtx.UnsignedTx.TxIn {
spentVtxos = append(spentVtxos, domain.VtxoKey{
Txid: in.PreviousOutPoint.Hash.String(),
VOut: in.PreviousOutPoint.Index,
})
}
asyncPayData, ok := s.asyncPaymentsCache[spentVtxos[0]]
if !ok {
return fmt.Errorf("async payment not found")
}
vtxos := make([]domain.Vtxo, 0, len(asyncPayData.receivers))
for i, receiver := range asyncPayData.receivers {
vtxos = append(vtxos, domain.Vtxo{
VtxoKey: domain.VtxoKey{
Txid: redeemTxid,
VOut: uint32(i),
},
Receiver: receiver,
ExpireAt: asyncPayData.expireAt,
AsyncPayment: &domain.AsyncPaymentTxs{
RedeemTx: redeemTx,
UnconditionalForfeitTxs: unconditionalForfeitTxs,
},
})
}
if err := s.repoManager.Vtxos().AddVtxos(ctx, vtxos); err != nil {
return fmt.Errorf("failed to add vtxos: %s", err)
}
log.Infof("added %d vtxos", len(vtxos))
if err := s.repoManager.Vtxos().SpendVtxos(ctx, spentVtxos, redeemTxid); err != nil {
return fmt.Errorf("failed to spend vtxo: %s", err)
}
log.Infof("spent %d vtxos", len(spentVtxos))
delete(s.asyncPaymentsCache, spentVtxos[0])
return nil
}
func (s *covenantlessService) CreateAsyncPayment(
ctx context.Context, inputs []domain.VtxoKey, receivers []domain.Receiver,
) (string, []string, error) {
vtxos, err := s.repoManager.Vtxos().GetVtxos(ctx, inputs)
if err != nil {
return "", nil, err
}
if len(vtxos) <= 0 {
return "", nil, fmt.Errorf("vtxos not found")
}
expiration := vtxos[0].ExpireAt
for _, vtxo := range vtxos {
if vtxo.Spent {
return "", nil, fmt.Errorf("all vtxos must be unspent")
}
if vtxo.Redeemed {
return "", nil, fmt.Errorf("all vtxos must be redeemed")
}
if vtxo.Swept {
return "", nil, fmt.Errorf("all vtxos must be swept")
}
if vtxo.ExpireAt < expiration {
expiration = vtxo.ExpireAt
}
}
res, err := s.builder.BuildAsyncPaymentTransactions(
vtxos, s.pubkey, receivers, s.minRelayFee,
)
if err != nil {
return "", nil, fmt.Errorf("failed to build async payment txs: %s", err)
}
s.asyncPaymentsCache[inputs[0]] = struct {
receivers []domain.Receiver
expireAt int64
}{
receivers: receivers,
expireAt: expiration,
}
return res.RedeemTx, res.UnconditionalForfeitTxs, nil
}
func (s *covenantlessService) SpendVtxos(ctx context.Context, inputs []domain.VtxoKey) (string, error) {
vtxos, err := s.repoManager.Vtxos().GetVtxos(ctx, inputs)
if err != nil {

View File

@@ -149,6 +149,7 @@ func (s *sweeper) createTask(
for _, input := range inputs {
// sweepableVtxos related to the sweep input
sweepableVtxos := make([]domain.VtxoKey, 0)
fmt.Println("input", input.GetHash().String(), input.GetIndex())
// check if input is the vtxo itself
vtxos, _ := s.repoManager.Vtxos().GetVtxos(

View File

@@ -34,6 +34,13 @@ type Service interface {
ctx context.Context, boardingTx string,
congestionTree tree.CongestionTree, userPubkey *secp256k1.PublicKey,
) error
// Async payments
CreateAsyncPayment(
ctx context.Context, inputs []domain.VtxoKey, receivers []domain.Receiver,
) (string, []string, error)
CompleteAsyncPayment(
ctx context.Context, redeemTx string, unconditionalForfeitTxs []string,
) error
}
type ServiceInfo struct {

View File

@@ -131,9 +131,15 @@ type Vtxo struct {
VtxoKey
Receiver
PoolTx string
SpentBy string
SpentBy string // round txid or async redeem txid
Spent bool
Redeemed bool
Swept bool
ExpireAt int64
AsyncPayment *AsyncPaymentTxs // nil if not async vtxo
}
type AsyncPaymentTxs struct {
RedeemTx string // always signed by the ASP when created
UnconditionalForfeitTxs []string
}

View File

@@ -29,4 +29,8 @@ type TxBuilder interface {
FinalizeAndExtractForfeit(tx string) (txhex string, err error)
// FindLeaves returns all the leaves txs that are reachable from the given outpoint
FindLeaves(congestionTree tree.CongestionTree, fromtxid string, vout uint32) (leaves []tree.Node, err error)
BuildAsyncPaymentTransactions(
vtxosToSpend []domain.Vtxo,
aspPubKey *secp256k1.PublicKey, receivers []domain.Receiver, minRelayFee uint64,
) (*domain.AsyncPaymentTxs, error)
}

View File

@@ -28,7 +28,7 @@ type WalletService interface {
SelectUtxos(ctx context.Context, asset string, amount uint64) ([]TxInput, uint64, error)
BroadcastTransaction(ctx context.Context, txHex string) (string, error)
WaitForSync(ctx context.Context, txid string) error
EstimateFees(ctx context.Context, pset string) (uint64, error)
EstimateFees(ctx context.Context, psbt string) (uint64, error)
ListConnectorUtxos(ctx context.Context, connectorAddress string) ([]TxInput, error)
MainAccountBalance(ctx context.Context) (uint64, uint64, error)
ConnectorsAccountBalance(ctx context.Context) (uint64, uint64, error)

View File

@@ -41,8 +41,17 @@ CREATE TABLE IF NOT EXISTS tx (
FOREIGN KEY (round_id) REFERENCES round(id)
);
CREATE TABLE IF NOT EXISTS uncond_forfeit_tx (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tx TEXT NOT NULL,
vtxo_txid TEXT NOT NULL,
vtxo_vout INTEGER NOT NULL,
position INTEGER NOT NULL,
FOREIGN KEY (vtxo_txid, vtxo_vout) REFERENCES vtxo(txid, vout)
);
CREATE TABLE IF NOT EXISTS vtxo (
txid TEXT NOT NULL PRIMARY KEY,
txid TEXT NOT NULL,
vout INTEGER NOT NULL,
pubkey TEXT NOT NULL,
amount INTEGER NOT NULL,
@@ -53,6 +62,8 @@ CREATE TABLE IF NOT EXISTS vtxo (
swept BOOLEAN NOT NULL,
expire_at INTEGER NOT NULL,
payment_id TEXT,
redeem_tx TEXT,
PRIMARY KEY (txid, vout),
FOREIGN KEY (payment_id) REFERENCES payment(id)
);
@@ -75,3 +86,8 @@ CREATE VIEW payment_vtxo_vw AS SELECT vtxo.*
FROM payment
LEFT OUTER JOIN vtxo
ON payment.id=vtxo.payment_id;
CREATE VIEW uncond_forfeit_tx_vw AS SELECT uncond_forfeit_tx.*
FROM vtxo
LEFT OUTER JOIN uncond_forfeit_tx
ON vtxo.txid=uncond_forfeit_tx.vtxo_txid AND vtxo.vout=uncond_forfeit_tx.vtxo_vout;

View File

@@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.26.0
// sqlc v1.27.0
package queries

View File

@@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.26.0
// sqlc v1.27.0
package queries
@@ -32,6 +32,7 @@ type PaymentVtxoVw struct {
Swept sql.NullBool
ExpireAt sql.NullInt64
PaymentID sql.NullString
RedeemTx sql.NullString
}
type Receiver struct {
@@ -85,6 +86,22 @@ type Tx struct {
IsLeaf sql.NullBool
}
type UncondForfeitTx struct {
ID int64
Tx string
VtxoTxid string
VtxoVout int64
Position int64
}
type UncondForfeitTxVw struct {
ID sql.NullInt64
Tx sql.NullString
VtxoTxid sql.NullString
VtxoVout sql.NullInt64
Position sql.NullInt64
}
type Vtxo struct {
Txid string
Vout int64
@@ -97,4 +114,5 @@ type Vtxo struct {
Swept bool
ExpireAt int64
PaymentID sql.NullString
RedeemTx sql.NullString
}

View File

@@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.26.0
// sqlc v1.27.0
// source: query.sql
package queries
@@ -54,30 +54,45 @@ func (q *Queries) MarkVtxoAsSwept(ctx context.Context, arg MarkVtxoAsSweptParams
}
const selectNotRedeemedVtxos = `-- name: SelectNotRedeemedVtxos :many
SELECT txid, vout, pubkey, amount, pool_tx, spent_by, spent, redeemed, swept, expire_at, payment_id FROM vtxo WHERE redeemed = false
SELECT vtxo.txid, vtxo.vout, vtxo.pubkey, vtxo.amount, vtxo.pool_tx, vtxo.spent_by, vtxo.spent, vtxo.redeemed, vtxo.swept, vtxo.expire_at, vtxo.payment_id, vtxo.redeem_tx,
uncond_forfeit_tx_vw.id, uncond_forfeit_tx_vw.tx, uncond_forfeit_tx_vw.vtxo_txid, uncond_forfeit_tx_vw.vtxo_vout, uncond_forfeit_tx_vw.position
FROM vtxo
LEFT OUTER JOIN uncond_forfeit_tx_vw ON vtxo.txid=uncond_forfeit_tx_vw.vtxo_txid AND vtxo.vout=uncond_forfeit_tx_vw.vtxo_vout
WHERE redeemed = false
`
func (q *Queries) SelectNotRedeemedVtxos(ctx context.Context) ([]Vtxo, error) {
type SelectNotRedeemedVtxosRow struct {
Vtxo Vtxo
UncondForfeitTxVw UncondForfeitTxVw
}
func (q *Queries) SelectNotRedeemedVtxos(ctx context.Context) ([]SelectNotRedeemedVtxosRow, error) {
rows, err := q.db.QueryContext(ctx, selectNotRedeemedVtxos)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Vtxo
var items []SelectNotRedeemedVtxosRow
for rows.Next() {
var i Vtxo
var i SelectNotRedeemedVtxosRow
if err := rows.Scan(
&i.Txid,
&i.Vout,
&i.Pubkey,
&i.Amount,
&i.PoolTx,
&i.SpentBy,
&i.Spent,
&i.Redeemed,
&i.Swept,
&i.ExpireAt,
&i.PaymentID,
&i.Vtxo.Txid,
&i.Vtxo.Vout,
&i.Vtxo.Pubkey,
&i.Vtxo.Amount,
&i.Vtxo.PoolTx,
&i.Vtxo.SpentBy,
&i.Vtxo.Spent,
&i.Vtxo.Redeemed,
&i.Vtxo.Swept,
&i.Vtxo.ExpireAt,
&i.Vtxo.PaymentID,
&i.Vtxo.RedeemTx,
&i.UncondForfeitTxVw.ID,
&i.UncondForfeitTxVw.Tx,
&i.UncondForfeitTxVw.VtxoTxid,
&i.UncondForfeitTxVw.VtxoVout,
&i.UncondForfeitTxVw.Position,
); err != nil {
return nil, err
}
@@ -93,30 +108,45 @@ func (q *Queries) SelectNotRedeemedVtxos(ctx context.Context) ([]Vtxo, error) {
}
const selectNotRedeemedVtxosWithPubkey = `-- name: SelectNotRedeemedVtxosWithPubkey :many
SELECT txid, vout, pubkey, amount, pool_tx, spent_by, spent, redeemed, swept, expire_at, payment_id FROM vtxo WHERE redeemed = false AND pubkey = ?
SELECT vtxo.txid, vtxo.vout, vtxo.pubkey, vtxo.amount, vtxo.pool_tx, vtxo.spent_by, vtxo.spent, vtxo.redeemed, vtxo.swept, vtxo.expire_at, vtxo.payment_id, vtxo.redeem_tx,
uncond_forfeit_tx_vw.id, uncond_forfeit_tx_vw.tx, uncond_forfeit_tx_vw.vtxo_txid, uncond_forfeit_tx_vw.vtxo_vout, uncond_forfeit_tx_vw.position
FROM vtxo
LEFT OUTER JOIN uncond_forfeit_tx_vw ON vtxo.txid=uncond_forfeit_tx_vw.vtxo_txid AND vtxo.vout=uncond_forfeit_tx_vw.vtxo_vout
WHERE redeemed = false AND pubkey = ?
`
func (q *Queries) SelectNotRedeemedVtxosWithPubkey(ctx context.Context, pubkey string) ([]Vtxo, error) {
type SelectNotRedeemedVtxosWithPubkeyRow struct {
Vtxo Vtxo
UncondForfeitTxVw UncondForfeitTxVw
}
func (q *Queries) SelectNotRedeemedVtxosWithPubkey(ctx context.Context, pubkey string) ([]SelectNotRedeemedVtxosWithPubkeyRow, error) {
rows, err := q.db.QueryContext(ctx, selectNotRedeemedVtxosWithPubkey, pubkey)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Vtxo
var items []SelectNotRedeemedVtxosWithPubkeyRow
for rows.Next() {
var i Vtxo
var i SelectNotRedeemedVtxosWithPubkeyRow
if err := rows.Scan(
&i.Txid,
&i.Vout,
&i.Pubkey,
&i.Amount,
&i.PoolTx,
&i.SpentBy,
&i.Spent,
&i.Redeemed,
&i.Swept,
&i.ExpireAt,
&i.PaymentID,
&i.Vtxo.Txid,
&i.Vtxo.Vout,
&i.Vtxo.Pubkey,
&i.Vtxo.Amount,
&i.Vtxo.PoolTx,
&i.Vtxo.SpentBy,
&i.Vtxo.Spent,
&i.Vtxo.Redeemed,
&i.Vtxo.Swept,
&i.Vtxo.ExpireAt,
&i.Vtxo.PaymentID,
&i.Vtxo.RedeemTx,
&i.UncondForfeitTxVw.ID,
&i.UncondForfeitTxVw.Tx,
&i.UncondForfeitTxVw.VtxoTxid,
&i.UncondForfeitTxVw.VtxoVout,
&i.UncondForfeitTxVw.Position,
); err != nil {
return nil, err
}
@@ -195,7 +225,7 @@ SELECT round.id, round.starting_timestamp, round.ending_timestamp, round.ended,
round_payment_vw.id, round_payment_vw.round_id,
round_tx_vw.id, round_tx_vw.tx, round_tx_vw.round_id, round_tx_vw.type, round_tx_vw.position, round_tx_vw.txid, round_tx_vw.tree_level, round_tx_vw.parent_txid, round_tx_vw.is_leaf,
payment_receiver_vw.payment_id, payment_receiver_vw.pubkey, payment_receiver_vw.amount, payment_receiver_vw.onchain_address,
payment_vtxo_vw.txid, payment_vtxo_vw.vout, payment_vtxo_vw.pubkey, payment_vtxo_vw.amount, payment_vtxo_vw.pool_tx, payment_vtxo_vw.spent_by, payment_vtxo_vw.spent, payment_vtxo_vw.redeemed, payment_vtxo_vw.swept, payment_vtxo_vw.expire_at, payment_vtxo_vw.payment_id
payment_vtxo_vw.txid, payment_vtxo_vw.vout, payment_vtxo_vw.pubkey, payment_vtxo_vw.amount, payment_vtxo_vw.pool_tx, payment_vtxo_vw.spent_by, payment_vtxo_vw.spent, payment_vtxo_vw.redeemed, payment_vtxo_vw.swept, payment_vtxo_vw.expire_at, payment_vtxo_vw.payment_id, payment_vtxo_vw.redeem_tx
FROM round
LEFT OUTER JOIN round_payment_vw ON round.id=round_payment_vw.round_id
LEFT OUTER JOIN round_tx_vw ON round.id=round_tx_vw.round_id
@@ -260,6 +290,7 @@ func (q *Queries) SelectRoundWithRoundId(ctx context.Context, id string) ([]Sele
&i.PaymentVtxoVw.Swept,
&i.PaymentVtxoVw.ExpireAt,
&i.PaymentVtxoVw.PaymentID,
&i.PaymentVtxoVw.RedeemTx,
); err != nil {
return nil, err
}
@@ -279,7 +310,7 @@ SELECT round.id, round.starting_timestamp, round.ending_timestamp, round.ended,
round_payment_vw.id, round_payment_vw.round_id,
round_tx_vw.id, round_tx_vw.tx, round_tx_vw.round_id, round_tx_vw.type, round_tx_vw.position, round_tx_vw.txid, round_tx_vw.tree_level, round_tx_vw.parent_txid, round_tx_vw.is_leaf,
payment_receiver_vw.payment_id, payment_receiver_vw.pubkey, payment_receiver_vw.amount, payment_receiver_vw.onchain_address,
payment_vtxo_vw.txid, payment_vtxo_vw.vout, payment_vtxo_vw.pubkey, payment_vtxo_vw.amount, payment_vtxo_vw.pool_tx, payment_vtxo_vw.spent_by, payment_vtxo_vw.spent, payment_vtxo_vw.redeemed, payment_vtxo_vw.swept, payment_vtxo_vw.expire_at, payment_vtxo_vw.payment_id
payment_vtxo_vw.txid, payment_vtxo_vw.vout, payment_vtxo_vw.pubkey, payment_vtxo_vw.amount, payment_vtxo_vw.pool_tx, payment_vtxo_vw.spent_by, payment_vtxo_vw.spent, payment_vtxo_vw.redeemed, payment_vtxo_vw.swept, payment_vtxo_vw.expire_at, payment_vtxo_vw.payment_id, payment_vtxo_vw.redeem_tx
FROM round
LEFT OUTER JOIN round_payment_vw ON round.id=round_payment_vw.round_id
LEFT OUTER JOIN round_tx_vw ON round.id=round_tx_vw.round_id
@@ -344,6 +375,7 @@ func (q *Queries) SelectRoundWithRoundTxId(ctx context.Context, txid string) ([]
&i.PaymentVtxoVw.Swept,
&i.PaymentVtxoVw.ExpireAt,
&i.PaymentVtxoVw.PaymentID,
&i.PaymentVtxoVw.RedeemTx,
); err != nil {
return nil, err
}
@@ -363,7 +395,7 @@ SELECT round.id, round.starting_timestamp, round.ending_timestamp, round.ended,
round_payment_vw.id, round_payment_vw.round_id,
round_tx_vw.id, round_tx_vw.tx, round_tx_vw.round_id, round_tx_vw.type, round_tx_vw.position, round_tx_vw.txid, round_tx_vw.tree_level, round_tx_vw.parent_txid, round_tx_vw.is_leaf,
payment_receiver_vw.payment_id, payment_receiver_vw.pubkey, payment_receiver_vw.amount, payment_receiver_vw.onchain_address,
payment_vtxo_vw.txid, payment_vtxo_vw.vout, payment_vtxo_vw.pubkey, payment_vtxo_vw.amount, payment_vtxo_vw.pool_tx, payment_vtxo_vw.spent_by, payment_vtxo_vw.spent, payment_vtxo_vw.redeemed, payment_vtxo_vw.swept, payment_vtxo_vw.expire_at, payment_vtxo_vw.payment_id
payment_vtxo_vw.txid, payment_vtxo_vw.vout, payment_vtxo_vw.pubkey, payment_vtxo_vw.amount, payment_vtxo_vw.pool_tx, payment_vtxo_vw.spent_by, payment_vtxo_vw.spent, payment_vtxo_vw.redeemed, payment_vtxo_vw.swept, payment_vtxo_vw.expire_at, payment_vtxo_vw.payment_id, payment_vtxo_vw.redeem_tx
FROM round
LEFT OUTER JOIN round_payment_vw ON round.id=round_payment_vw.round_id
LEFT OUTER JOIN round_tx_vw ON round.id=round_tx_vw.round_id
@@ -428,6 +460,7 @@ func (q *Queries) SelectSweepableRounds(ctx context.Context) ([]SelectSweepableR
&i.PaymentVtxoVw.Swept,
&i.PaymentVtxoVw.ExpireAt,
&i.PaymentVtxoVw.PaymentID,
&i.PaymentVtxoVw.RedeemTx,
); err != nil {
return nil, err
}
@@ -443,30 +476,45 @@ func (q *Queries) SelectSweepableRounds(ctx context.Context) ([]SelectSweepableR
}
const selectSweepableVtxos = `-- name: SelectSweepableVtxos :many
SELECT txid, vout, pubkey, amount, pool_tx, spent_by, spent, redeemed, swept, expire_at, payment_id FROM vtxo WHERE redeemed = false AND swept = false
SELECT vtxo.txid, vtxo.vout, vtxo.pubkey, vtxo.amount, vtxo.pool_tx, vtxo.spent_by, vtxo.spent, vtxo.redeemed, vtxo.swept, vtxo.expire_at, vtxo.payment_id, vtxo.redeem_tx,
uncond_forfeit_tx_vw.id, uncond_forfeit_tx_vw.tx, uncond_forfeit_tx_vw.vtxo_txid, uncond_forfeit_tx_vw.vtxo_vout, uncond_forfeit_tx_vw.position
FROM vtxo
LEFT OUTER JOIN uncond_forfeit_tx_vw ON vtxo.txid=uncond_forfeit_tx_vw.vtxo_txid AND vtxo.vout=uncond_forfeit_tx_vw.vtxo_vout
WHERE redeemed = false AND swept = false
`
func (q *Queries) SelectSweepableVtxos(ctx context.Context) ([]Vtxo, error) {
type SelectSweepableVtxosRow struct {
Vtxo Vtxo
UncondForfeitTxVw UncondForfeitTxVw
}
func (q *Queries) SelectSweepableVtxos(ctx context.Context) ([]SelectSweepableVtxosRow, error) {
rows, err := q.db.QueryContext(ctx, selectSweepableVtxos)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Vtxo
var items []SelectSweepableVtxosRow
for rows.Next() {
var i Vtxo
var i SelectSweepableVtxosRow
if err := rows.Scan(
&i.Txid,
&i.Vout,
&i.Pubkey,
&i.Amount,
&i.PoolTx,
&i.SpentBy,
&i.Spent,
&i.Redeemed,
&i.Swept,
&i.ExpireAt,
&i.PaymentID,
&i.Vtxo.Txid,
&i.Vtxo.Vout,
&i.Vtxo.Pubkey,
&i.Vtxo.Amount,
&i.Vtxo.PoolTx,
&i.Vtxo.SpentBy,
&i.Vtxo.Spent,
&i.Vtxo.Redeemed,
&i.Vtxo.Swept,
&i.Vtxo.ExpireAt,
&i.Vtxo.PaymentID,
&i.Vtxo.RedeemTx,
&i.UncondForfeitTxVw.ID,
&i.UncondForfeitTxVw.Tx,
&i.UncondForfeitTxVw.VtxoTxid,
&i.UncondForfeitTxVw.VtxoVout,
&i.UncondForfeitTxVw.Position,
); err != nil {
return nil, err
}
@@ -486,7 +534,7 @@ SELECT round.id, round.starting_timestamp, round.ending_timestamp, round.ended,
round_payment_vw.id, round_payment_vw.round_id,
round_tx_vw.id, round_tx_vw.tx, round_tx_vw.round_id, round_tx_vw.type, round_tx_vw.position, round_tx_vw.txid, round_tx_vw.tree_level, round_tx_vw.parent_txid, round_tx_vw.is_leaf,
payment_receiver_vw.payment_id, payment_receiver_vw.pubkey, payment_receiver_vw.amount, payment_receiver_vw.onchain_address,
payment_vtxo_vw.txid, payment_vtxo_vw.vout, payment_vtxo_vw.pubkey, payment_vtxo_vw.amount, payment_vtxo_vw.pool_tx, payment_vtxo_vw.spent_by, payment_vtxo_vw.spent, payment_vtxo_vw.redeemed, payment_vtxo_vw.swept, payment_vtxo_vw.expire_at, payment_vtxo_vw.payment_id
payment_vtxo_vw.txid, payment_vtxo_vw.vout, payment_vtxo_vw.pubkey, payment_vtxo_vw.amount, payment_vtxo_vw.pool_tx, payment_vtxo_vw.spent_by, payment_vtxo_vw.spent, payment_vtxo_vw.redeemed, payment_vtxo_vw.swept, payment_vtxo_vw.expire_at, payment_vtxo_vw.payment_id, payment_vtxo_vw.redeem_tx
FROM round
LEFT OUTER JOIN round_payment_vw ON round.id=round_payment_vw.round_id
LEFT OUTER JOIN round_tx_vw ON round.id=round_tx_vw.round_id
@@ -551,6 +599,7 @@ func (q *Queries) SelectSweptRounds(ctx context.Context) ([]SelectSweptRoundsRow
&i.PaymentVtxoVw.Swept,
&i.PaymentVtxoVw.ExpireAt,
&i.PaymentVtxoVw.PaymentID,
&i.PaymentVtxoVw.RedeemTx,
); err != nil {
return nil, err
}
@@ -566,7 +615,11 @@ func (q *Queries) SelectSweptRounds(ctx context.Context) ([]SelectSweptRoundsRow
}
const selectVtxoByOutpoint = `-- name: SelectVtxoByOutpoint :one
SELECT txid, vout, pubkey, amount, pool_tx, spent_by, spent, redeemed, swept, expire_at, payment_id FROM vtxo WHERE txid = ? AND vout = ?
SELECT vtxo.txid, vtxo.vout, vtxo.pubkey, vtxo.amount, vtxo.pool_tx, vtxo.spent_by, vtxo.spent, vtxo.redeemed, vtxo.swept, vtxo.expire_at, vtxo.payment_id, vtxo.redeem_tx,
uncond_forfeit_tx_vw.id, uncond_forfeit_tx_vw.tx, uncond_forfeit_tx_vw.vtxo_txid, uncond_forfeit_tx_vw.vtxo_vout, uncond_forfeit_tx_vw.position
FROM vtxo
LEFT OUTER JOIN uncond_forfeit_tx_vw ON vtxo.txid=uncond_forfeit_tx_vw.vtxo_txid AND vtxo.vout=uncond_forfeit_tx_vw.vtxo_vout
WHERE txid = ? AND vout = ?
`
type SelectVtxoByOutpointParams struct {
@@ -574,50 +627,76 @@ type SelectVtxoByOutpointParams struct {
Vout int64
}
func (q *Queries) SelectVtxoByOutpoint(ctx context.Context, arg SelectVtxoByOutpointParams) (Vtxo, error) {
type SelectVtxoByOutpointRow struct {
Vtxo Vtxo
UncondForfeitTxVw UncondForfeitTxVw
}
func (q *Queries) SelectVtxoByOutpoint(ctx context.Context, arg SelectVtxoByOutpointParams) (SelectVtxoByOutpointRow, error) {
row := q.db.QueryRowContext(ctx, selectVtxoByOutpoint, arg.Txid, arg.Vout)
var i Vtxo
var i SelectVtxoByOutpointRow
err := row.Scan(
&i.Txid,
&i.Vout,
&i.Pubkey,
&i.Amount,
&i.PoolTx,
&i.SpentBy,
&i.Spent,
&i.Redeemed,
&i.Swept,
&i.ExpireAt,
&i.PaymentID,
&i.Vtxo.Txid,
&i.Vtxo.Vout,
&i.Vtxo.Pubkey,
&i.Vtxo.Amount,
&i.Vtxo.PoolTx,
&i.Vtxo.SpentBy,
&i.Vtxo.Spent,
&i.Vtxo.Redeemed,
&i.Vtxo.Swept,
&i.Vtxo.ExpireAt,
&i.Vtxo.PaymentID,
&i.Vtxo.RedeemTx,
&i.UncondForfeitTxVw.ID,
&i.UncondForfeitTxVw.Tx,
&i.UncondForfeitTxVw.VtxoTxid,
&i.UncondForfeitTxVw.VtxoVout,
&i.UncondForfeitTxVw.Position,
)
return i, err
}
const selectVtxosByPoolTxid = `-- name: SelectVtxosByPoolTxid :many
SELECT txid, vout, pubkey, amount, pool_tx, spent_by, spent, redeemed, swept, expire_at, payment_id FROM vtxo WHERE pool_tx = ?
SELECT vtxo.txid, vtxo.vout, vtxo.pubkey, vtxo.amount, vtxo.pool_tx, vtxo.spent_by, vtxo.spent, vtxo.redeemed, vtxo.swept, vtxo.expire_at, vtxo.payment_id, vtxo.redeem_tx,
uncond_forfeit_tx_vw.id, uncond_forfeit_tx_vw.tx, uncond_forfeit_tx_vw.vtxo_txid, uncond_forfeit_tx_vw.vtxo_vout, uncond_forfeit_tx_vw.position
FROM vtxo
LEFT OUTER JOIN uncond_forfeit_tx_vw ON vtxo.txid=uncond_forfeit_tx_vw.vtxo_txid AND vtxo.vout=uncond_forfeit_tx_vw.vtxo_vout
WHERE pool_tx = ?
`
func (q *Queries) SelectVtxosByPoolTxid(ctx context.Context, poolTx string) ([]Vtxo, error) {
type SelectVtxosByPoolTxidRow struct {
Vtxo Vtxo
UncondForfeitTxVw UncondForfeitTxVw
}
func (q *Queries) SelectVtxosByPoolTxid(ctx context.Context, poolTx string) ([]SelectVtxosByPoolTxidRow, error) {
rows, err := q.db.QueryContext(ctx, selectVtxosByPoolTxid, poolTx)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Vtxo
var items []SelectVtxosByPoolTxidRow
for rows.Next() {
var i Vtxo
var i SelectVtxosByPoolTxidRow
if err := rows.Scan(
&i.Txid,
&i.Vout,
&i.Pubkey,
&i.Amount,
&i.PoolTx,
&i.SpentBy,
&i.Spent,
&i.Redeemed,
&i.Swept,
&i.ExpireAt,
&i.PaymentID,
&i.Vtxo.Txid,
&i.Vtxo.Vout,
&i.Vtxo.Pubkey,
&i.Vtxo.Amount,
&i.Vtxo.PoolTx,
&i.Vtxo.SpentBy,
&i.Vtxo.Spent,
&i.Vtxo.Redeemed,
&i.Vtxo.Swept,
&i.Vtxo.ExpireAt,
&i.Vtxo.PaymentID,
&i.Vtxo.RedeemTx,
&i.UncondForfeitTxVw.ID,
&i.UncondForfeitTxVw.Tx,
&i.UncondForfeitTxVw.VtxoTxid,
&i.UncondForfeitTxVw.VtxoVout,
&i.UncondForfeitTxVw.Position,
); err != nil {
return nil, err
}
@@ -803,18 +882,44 @@ func (q *Queries) UpsertTransaction(ctx context.Context, arg UpsertTransactionPa
return err
}
const upsertUnconditionalForfeitTx = `-- name: UpsertUnconditionalForfeitTx :exec
INSERT INTO uncond_forfeit_tx (tx, vtxo_txid, vtxo_vout, position)
VALUES (?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET
tx = EXCLUDED.tx,
vtxo_txid = EXCLUDED.vtxo_txid,
vtxo_vout = EXCLUDED.vtxo_vout,
position = EXCLUDED.position
`
type UpsertUnconditionalForfeitTxParams struct {
Tx string
VtxoTxid string
VtxoVout int64
Position int64
}
func (q *Queries) UpsertUnconditionalForfeitTx(ctx context.Context, arg UpsertUnconditionalForfeitTxParams) error {
_, err := q.db.ExecContext(ctx, upsertUnconditionalForfeitTx,
arg.Tx,
arg.VtxoTxid,
arg.VtxoVout,
arg.Position,
)
return err
}
const upsertVtxo = `-- name: UpsertVtxo :exec
INSERT INTO vtxo (txid, vout, pubkey, amount, pool_tx, spent_by, spent, redeemed, swept, expire_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(txid) DO UPDATE SET
vout = excluded.vout,
pubkey = excluded.pubkey,
amount = excluded.amount,
pool_tx = excluded.pool_tx,
spent_by = excluded.spent_by,
spent = excluded.spent,
redeemed = excluded.redeemed,
swept = excluded.swept,
expire_at = excluded.expire_at
INSERT INTO vtxo (txid, vout, pubkey, amount, pool_tx, spent_by, spent, redeemed, swept, expire_at, redeem_tx)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(txid, vout) DO UPDATE SET
pubkey = EXCLUDED.pubkey,
amount = EXCLUDED.amount,
pool_tx = EXCLUDED.pool_tx,
spent_by = EXCLUDED.spent_by,
spent = EXCLUDED.spent,
redeemed = EXCLUDED.redeemed,
swept = EXCLUDED.swept,
expire_at = EXCLUDED.expire_at,
redeem_tx = EXCLUDED.redeem_tx
`
type UpsertVtxoParams struct {
@@ -828,6 +933,7 @@ type UpsertVtxoParams struct {
Redeemed bool
Swept bool
ExpireAt int64
RedeemTx sql.NullString
}
func (q *Queries) UpsertVtxo(ctx context.Context, arg UpsertVtxoParams) error {
@@ -842,6 +948,7 @@ func (q *Queries) UpsertVtxo(ctx context.Context, arg UpsertVtxoParams) error {
arg.Redeemed,
arg.Swept,
arg.ExpireAt,
arg.RedeemTx,
)
return err
}

View File

@@ -111,33 +111,61 @@ SELECT id FROM round WHERE starting_timestamp > ? AND starting_timestamp < ?;
-- name: SelectRoundIds :many
SELECT id FROM round;
-- name: UpsertUnconditionalForfeitTx :exec
INSERT INTO uncond_forfeit_tx (tx, vtxo_txid, vtxo_vout, position)
VALUES (?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET
tx = EXCLUDED.tx,
vtxo_txid = EXCLUDED.vtxo_txid,
vtxo_vout = EXCLUDED.vtxo_vout,
position = EXCLUDED.position;
-- name: UpsertVtxo :exec
INSERT INTO vtxo (txid, vout, pubkey, amount, pool_tx, spent_by, spent, redeemed, swept, expire_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(txid) DO UPDATE SET
vout = excluded.vout,
pubkey = excluded.pubkey,
amount = excluded.amount,
pool_tx = excluded.pool_tx,
spent_by = excluded.spent_by,
spent = excluded.spent,
redeemed = excluded.redeemed,
swept = excluded.swept,
expire_at = excluded.expire_at;
INSERT INTO vtxo (txid, vout, pubkey, amount, pool_tx, spent_by, spent, redeemed, swept, expire_at, redeem_tx)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(txid, vout) DO UPDATE SET
pubkey = EXCLUDED.pubkey,
amount = EXCLUDED.amount,
pool_tx = EXCLUDED.pool_tx,
spent_by = EXCLUDED.spent_by,
spent = EXCLUDED.spent,
redeemed = EXCLUDED.redeemed,
swept = EXCLUDED.swept,
expire_at = EXCLUDED.expire_at,
redeem_tx = EXCLUDED.redeem_tx;
-- name: SelectSweepableVtxos :many
SELECT * FROM vtxo WHERE redeemed = false AND swept = false;
SELECT sqlc.embed(vtxo),
sqlc.embed(uncond_forfeit_tx_vw)
FROM vtxo
LEFT OUTER JOIN uncond_forfeit_tx_vw ON vtxo.txid=uncond_forfeit_tx_vw.vtxo_txid AND vtxo.vout=uncond_forfeit_tx_vw.vtxo_vout
WHERE redeemed = false AND swept = false;
-- name: SelectNotRedeemedVtxos :many
SELECT * FROM vtxo WHERE redeemed = false;
SELECT sqlc.embed(vtxo),
sqlc.embed(uncond_forfeit_tx_vw)
FROM vtxo
LEFT OUTER JOIN uncond_forfeit_tx_vw ON vtxo.txid=uncond_forfeit_tx_vw.vtxo_txid AND vtxo.vout=uncond_forfeit_tx_vw.vtxo_vout
WHERE redeemed = false;
-- name: SelectNotRedeemedVtxosWithPubkey :many
SELECT * FROM vtxo WHERE redeemed = false AND pubkey = ?;
SELECT sqlc.embed(vtxo),
sqlc.embed(uncond_forfeit_tx_vw)
FROM vtxo
LEFT OUTER JOIN uncond_forfeit_tx_vw ON vtxo.txid=uncond_forfeit_tx_vw.vtxo_txid AND vtxo.vout=uncond_forfeit_tx_vw.vtxo_vout
WHERE redeemed = false AND pubkey = ?;
-- name: SelectVtxoByOutpoint :one
SELECT * FROM vtxo WHERE txid = ? AND vout = ?;
SELECT sqlc.embed(vtxo),
sqlc.embed(uncond_forfeit_tx_vw)
FROM vtxo
LEFT OUTER JOIN uncond_forfeit_tx_vw ON vtxo.txid=uncond_forfeit_tx_vw.vtxo_txid AND vtxo.vout=uncond_forfeit_tx_vw.vtxo_vout
WHERE txid = ? AND vout = ?;
-- name: SelectVtxosByPoolTxid :many
SELECT * FROM vtxo WHERE pool_tx = ?;
SELECT sqlc.embed(vtxo),
sqlc.embed(uncond_forfeit_tx_vw)
FROM vtxo
LEFT OUTER JOIN uncond_forfeit_tx_vw ON vtxo.txid=uncond_forfeit_tx_vw.vtxo_txid AND vtxo.vout=uncond_forfeit_tx_vw.vtxo_vout
WHERE pool_tx = ?;
-- name: MarkVtxoAsRedeemed :exec
UPDATE vtxo SET redeemed = true WHERE txid = ? AND vout = ?;

View File

@@ -35,10 +35,14 @@ func (v *vxtoRepository) Close() {
func (v *vxtoRepository) AddVtxos(ctx context.Context, vtxos []domain.Vtxo) error {
txBody := func(querierWithTx *queries.Queries) error {
for _, vtxo := range vtxos {
for i := range vtxos {
vtxo := vtxos[i]
var redeemTx string
if vtxo.AsyncPayment != nil {
redeemTx = vtxo.AsyncPayment.RedeemTx
}
if err := querierWithTx.UpsertVtxo(
ctx,
queries.UpsertVtxoParams{
ctx, queries.UpsertVtxoParams{
Txid: vtxo.Txid,
Vout: int64(vtxo.VOut),
Pubkey: vtxo.Pubkey,
@@ -49,10 +53,24 @@ func (v *vxtoRepository) AddVtxos(ctx context.Context, vtxos []domain.Vtxo) erro
Redeemed: vtxo.Redeemed,
Swept: vtxo.Swept,
ExpireAt: vtxo.ExpireAt,
RedeemTx: sql.NullString{String: redeemTx, Valid: true},
},
); err != nil {
return err
}
if vtxo.AsyncPayment != nil {
for i, tx := range vtxo.AsyncPayment.UnconditionalForfeitTxs {
if err := querierWithTx.UpsertUnconditionalForfeitTx(ctx, queries.UpsertUnconditionalForfeitTxParams{
Tx: tx,
VtxoTxid: vtxo.Txid,
VtxoVout: int64(vtxo.VOut),
Position: int64(i),
}); err != nil {
return err
}
}
}
}
return nil
@@ -62,28 +80,50 @@ func (v *vxtoRepository) AddVtxos(ctx context.Context, vtxos []domain.Vtxo) erro
}
func (v *vxtoRepository) GetAllSweepableVtxos(ctx context.Context) ([]domain.Vtxo, error) {
rows, err := v.querier.SelectSweepableVtxos(ctx)
res, err := v.querier.SelectSweepableVtxos(ctx)
if err != nil {
return nil, err
}
rows := make([]vtxoWithUnconditionalForfeitTxs, 0, len(res))
for _, row := range res {
rows = append(rows, vtxoWithUnconditionalForfeitTxs{
vtxo: row.Vtxo,
tx: row.UncondForfeitTxVw,
})
}
return readRows(rows)
}
func (v *vxtoRepository) GetAllVtxos(ctx context.Context, pubkey string) ([]domain.Vtxo, []domain.Vtxo, error) {
withPubkey := len(pubkey) > 0
var rows []queries.Vtxo
var err error
var rows []vtxoWithUnconditionalForfeitTxs
if withPubkey {
rows, err = v.querier.SelectNotRedeemedVtxosWithPubkey(ctx, pubkey)
} else {
rows, err = v.querier.SelectNotRedeemedVtxos(ctx)
}
res, err := v.querier.SelectNotRedeemedVtxosWithPubkey(ctx, pubkey)
if err != nil {
return nil, nil, err
}
rows = make([]vtxoWithUnconditionalForfeitTxs, 0, len(res))
for _, row := range res {
rows = append(rows, vtxoWithUnconditionalForfeitTxs{
vtxo: row.Vtxo,
tx: row.UncondForfeitTxVw,
})
}
} else {
res, err := v.querier.SelectNotRedeemedVtxos(ctx)
if err != nil {
return nil, nil, err
}
rows = make([]vtxoWithUnconditionalForfeitTxs, 0, len(res))
for _, row := range res {
rows = append(rows, vtxoWithUnconditionalForfeitTxs{
vtxo: row.Vtxo,
tx: row.UncondForfeitTxVw,
})
}
}
vtxos, err := readRows(rows)
if err != nil {
@@ -107,7 +147,7 @@ func (v *vxtoRepository) GetAllVtxos(ctx context.Context, pubkey string) ([]doma
func (v *vxtoRepository) GetVtxos(ctx context.Context, outpoints []domain.VtxoKey) ([]domain.Vtxo, error) {
vtxos := make([]domain.Vtxo, 0, len(outpoints))
for _, o := range outpoints {
vtxo, err := v.querier.SelectVtxoByOutpoint(
res, err := v.querier.SelectVtxoByOutpoint(
ctx,
queries.SelectVtxoByOutpointParams{
Txid: o.Txid,
@@ -118,7 +158,12 @@ func (v *vxtoRepository) GetVtxos(ctx context.Context, outpoints []domain.VtxoKe
return nil, err
}
result, err := readRows([]queries.Vtxo{vtxo})
result, err := readRows([]vtxoWithUnconditionalForfeitTxs{
{
vtxo: res.Vtxo,
tx: res.UncondForfeitTxVw,
},
})
if err != nil {
return nil, err
}
@@ -134,10 +179,17 @@ func (v *vxtoRepository) GetVtxos(ctx context.Context, outpoints []domain.VtxoKe
}
func (v *vxtoRepository) GetVtxosForRound(ctx context.Context, txid string) ([]domain.Vtxo, error) {
rows, err := v.querier.SelectVtxosByPoolTxid(ctx, txid)
res, err := v.querier.SelectVtxosByPoolTxid(ctx, txid)
if err != nil {
return nil, err
}
rows := make([]vtxoWithUnconditionalForfeitTxs, 0, len(res))
for _, row := range res {
rows = append(rows, vtxoWithUnconditionalForfeitTxs{
vtxo: row.Vtxo,
tx: row.UncondForfeitTxVw,
})
}
return readRows(rows)
}
@@ -224,7 +276,18 @@ func (v *vxtoRepository) UpdateExpireAt(ctx context.Context, vtxos []domain.Vtxo
return execTx(ctx, v.db, txBody)
}
func rowToVtxo(row queries.Vtxo) domain.Vtxo {
func rowToVtxo(row queries.Vtxo, uncondForfeitTxs []queries.UncondForfeitTxVw) domain.Vtxo {
var asyncPayment *domain.AsyncPaymentTxs
if row.RedeemTx.Valid && len(uncondForfeitTxs) > 0 {
txs := make([]string, len(uncondForfeitTxs))
for _, tx := range uncondForfeitTxs {
txs[tx.Position.Int64] = tx.Tx.String
}
asyncPayment = &domain.AsyncPaymentTxs{
RedeemTx: row.RedeemTx.String,
UnconditionalForfeitTxs: txs,
}
}
return domain.Vtxo{
VtxoKey: domain.VtxoKey{
Txid: row.Txid,
@@ -240,13 +303,42 @@ func rowToVtxo(row queries.Vtxo) domain.Vtxo {
Redeemed: row.Redeemed,
Swept: row.Swept,
ExpireAt: row.ExpireAt,
AsyncPayment: asyncPayment,
}
}
func readRows(rows []queries.Vtxo) ([]domain.Vtxo, error) {
type vtxoWithUnconditionalForfeitTxs struct {
vtxo queries.Vtxo
tx queries.UncondForfeitTxVw
}
func readRows(rows []vtxoWithUnconditionalForfeitTxs) ([]domain.Vtxo, error) {
uncondForfeitTxsMap := make(map[domain.VtxoKey][]queries.UncondForfeitTxVw)
for _, row := range rows {
if !row.vtxo.RedeemTx.Valid {
continue
}
vtxoKey := domain.VtxoKey{
Txid: row.vtxo.Txid,
VOut: uint32(row.vtxo.Vout),
}
if _, ok := uncondForfeitTxsMap[vtxoKey]; !ok {
uncondForfeitTxsMap[vtxoKey] = make([]queries.UncondForfeitTxVw, 0)
}
if row.tx.Tx.Valid {
uncondForfeitTxsMap[vtxoKey] = append(
uncondForfeitTxsMap[vtxoKey], row.tx,
)
}
}
vtxos := make([]domain.Vtxo, 0, len(rows))
for _, v := range rows {
vtxos = append(vtxos, rowToVtxo(v))
for _, row := range rows {
vtxoKey := domain.VtxoKey{
Txid: row.vtxo.Txid,
VOut: uint32(row.vtxo.Vout),
}
uncondForfeitTxs := uncondForfeitTxsMap[vtxoKey]
vtxos = append(vtxos, rowToVtxo(row.vtxo, uncondForfeitTxs))
}
return vtxos, nil

View File

@@ -316,6 +316,12 @@ func (b *txBuilder) FindLeaves(
return foundLeaves, nil
}
func (b *txBuilder) BuildAsyncPaymentTransactions(
_ []domain.Vtxo, _ *secp256k1.PublicKey, _ []domain.Receiver, _ uint64,
) (*domain.AsyncPaymentTxs, error) {
return nil, fmt.Errorf("not implemented")
}
func (b *txBuilder) getLeafScriptAndTree(
userPubkey, aspPubkey *secp256k1.PublicKey,
) ([]byte, *taproot.IndexedElementsTapScriptTree, error) {

View File

@@ -303,6 +303,171 @@ func (b *txBuilder) FindLeaves(congestionTree tree.CongestionTree, fromtxid stri
return foundLeaves, nil
}
// TODO add locktimes to txs
func (b *txBuilder) BuildAsyncPaymentTransactions(
vtxos []domain.Vtxo, aspPubKey *secp256k1.PublicKey,
receivers []domain.Receiver, minRelayFee uint64,
) (*domain.AsyncPaymentTxs, error) {
if len(vtxos) <= 0 {
return nil, fmt.Errorf("missing vtxos")
}
ins := make([]*wire.OutPoint, 0, len(vtxos))
outs := make([]*wire.TxOut, 0, len(receivers))
unconditionalForfeitTxs := make([]string, 0, len(vtxos))
for _, vtxo := range vtxos {
if vtxo.Spent {
return nil, fmt.Errorf("all vtxos must be unspent")
}
senderBytes, err := hex.DecodeString(vtxo.Pubkey)
if err != nil {
return nil, err
}
sender, err := secp256k1.ParsePubKey(senderBytes)
if err != nil {
return nil, err
}
aspScript, err := p2trScript(aspPubKey, b.onchainNetwork())
if err != nil {
return nil, err
}
vtxoTxID, err := chainhash.NewHashFromStr(vtxo.Txid)
if err != nil {
return nil, err
}
vtxoOutpoint := &wire.OutPoint{
Hash: *vtxoTxID,
Index: vtxo.VOut,
}
vtxoScript, vtxoTree, err := b.getLeafScriptAndTree(sender, aspPubKey)
if err != nil {
return nil, err
}
output := &wire.TxOut{
PkScript: aspScript,
Value: int64(vtxo.Amount - minRelayFee),
}
forfeitClosure := &bitcointree.MultisigClosure{
Pubkey: sender,
AspPubkey: aspPubKey,
}
forfeitLeaf, err := forfeitClosure.Leaf()
if err != nil {
return nil, err
}
leafProof := vtxoTree.LeafMerkleProofs[vtxoTree.LeafProofIndex[forfeitLeaf.TapHash()]]
ctrlBlock := leafProof.ToControlBlock(bitcointree.UnspendableKey())
ctrlBlockBytes, err := ctrlBlock.ToBytes()
if err != nil {
return nil, err
}
unconditionnalForfeitPtx, err := psbt.New(
[]*wire.OutPoint{vtxoOutpoint},
[]*wire.TxOut{output},
2,
0,
[]uint32{wire.MaxTxInSequenceNum},
)
if err != nil {
return nil, err
}
unconditionnalForfeitPtx.Inputs[0].WitnessUtxo = &wire.TxOut{
Value: int64(vtxo.Amount),
PkScript: vtxoScript,
}
unconditionnalForfeitPtx.Inputs[0].TaprootInternalKey = schnorr.SerializePubKey(bitcointree.UnspendableKey())
unconditionnalForfeitPtx.Inputs[0].TaprootLeafScript = []*psbt.TaprootTapLeafScript{
{
ControlBlock: ctrlBlockBytes,
Script: forfeitLeaf.Script,
LeafVersion: txscript.BaseLeafVersion,
},
}
forfeitTx, err := unconditionnalForfeitPtx.B64Encode()
if err != nil {
return nil, err
}
unconditionalForfeitTxs = append(unconditionalForfeitTxs, forfeitTx)
ins = append(ins, vtxoOutpoint)
}
for i, receiver := range receivers {
// TODO (@louisinger): Add revert policy (sender+ASP)
buf, err := hex.DecodeString(receiver.Pubkey)
if err != nil {
return nil, err
}
receiverPk, err := secp256k1.ParsePubKey(buf)
if err != nil {
return nil, err
}
newVtxoScript, _, err := b.getLeafScriptAndTree(receiverPk, aspPubKey)
if err != nil {
return nil, err
}
// Deduct the min relay fee from the very last receiver which is supposed
// to be the change in case it's not a send-all.
value := receiver.Amount
if i == len(receivers)-1 {
value -= minRelayFee
}
outs = append(outs, &wire.TxOut{
Value: int64(value),
PkScript: newVtxoScript,
})
}
redeemPtx, err := psbt.New(
ins, outs, 2, 0, []uint32{wire.MaxTxInSequenceNum},
)
if err != nil {
return nil, err
}
for i := range redeemPtx.Inputs {
unconditionnalForfeitPsbt, _ := psbt.NewFromRawBytes(
strings.NewReader(unconditionalForfeitTxs[i]), true,
)
redeemPtx.Inputs[i].WitnessUtxo = unconditionnalForfeitPsbt.Inputs[0].WitnessUtxo
redeemPtx.Inputs[i].TaprootInternalKey = unconditionnalForfeitPsbt.Inputs[0].TaprootInternalKey
redeemPtx.Inputs[i].TaprootLeafScript = unconditionnalForfeitPsbt.Inputs[0].TaprootLeafScript
}
redeemTx, err := redeemPtx.B64Encode()
if err != nil {
return nil, err
}
signedRedeemTx, err := b.wallet.SignTransactionTapscript(
context.Background(), redeemTx, nil,
)
if err != nil {
return nil, err
}
return &domain.AsyncPaymentTxs{
RedeemTx: signedRedeemTx,
UnconditionalForfeitTxs: unconditionalForfeitTxs,
}, nil
}
func (b *txBuilder) getLeafScriptAndTree(
userPubkey, aspPubkey *secp256k1.PublicKey,
) ([]byte, *txscript.IndexedTapScriptTree, error) {
@@ -316,7 +481,7 @@ func (b *txBuilder) getLeafScriptAndTree(
return nil, nil, err
}
forfeitClosure := &bitcointree.ForfeitClosure{
forfeitClosure := &bitcointree.MultisigClosure{
Pubkey: userPubkey,
AspPubkey: aspPubkey,
}
@@ -693,7 +858,9 @@ func (b *txBuilder) createConnectors(
func (b *txBuilder) createForfeitTxs(
aspPubkey *secp256k1.PublicKey, payments []domain.Payment, connectors []*psbt.Packet, minRelayFee uint64,
) ([]string, error) {
aspScript, err := p2wpkhScript(aspPubkey, b.onchainNetwork())
// TODO (@louisinger): are we sure about this change?
aspScript, err := p2trScript(aspPubkey, b.onchainNetwork())
// aspScript, err := p2wpkhScript(aspPubkey, b.onchainNetwork())
if err != nil {
return nil, err
}
@@ -719,7 +886,7 @@ func (b *txBuilder) createForfeitTxs(
var forfeitProof *txscript.TapscriptProof
for _, proof := range vtxoTaprootTree.LeafMerkleProofs {
isForfeit, err := (&bitcointree.ForfeitClosure{}).Decode(proof.Script)
isForfeit, err := (&bitcointree.MultisigClosure{}).Decode(proof.Script)
if !isForfeit || err != nil {
continue
}

View File

@@ -10,7 +10,7 @@ import (
"github.com/decred/dcrd/dcrec/secp256k1/v4"
)
func p2wpkhScript(publicKey *secp256k1.PublicKey, net *chaincfg.Params) ([]byte, error) {
func p2trScript(publicKey *secp256k1.PublicKey, net *chaincfg.Params) ([]byte, error) {
tapKey := txscript.ComputeTaprootKeyNoScript(publicKey)
payment, err := btcutil.NewAddressWitnessPubKeyHash(

View File

@@ -93,7 +93,7 @@ func (s *service) signPsbt(packet *psbt.Packet) ([]uint32, error) {
}
}
// TODO: @louisinger shall we delete this code?
// TODO (@louisinger): shall we delete this code?
// prevOutputFetcher := wallet.PsbtPrevOutputFetcher(packet)
// sigHashes := txscript.NewTxSigHashes(tx, prevOutputFetcher)

View File

@@ -177,6 +177,82 @@ func (s *service) Restore(_ context.Context, seed, password string) error {
}
func (s *service) Unlock(_ context.Context, password string) error {
if s.wallet == nil {
pwd := []byte(password)
opt := btcwallet.LoaderWithLocalWalletDB(s.cfg.Datadir, false, time.Minute)
config := btcwallet.Config{
LogDir: s.cfg.Datadir,
PrivatePass: pwd,
PublicPass: pwd,
Birthday: time.Now(),
RecoveryWindow: 0,
NetParams: s.cfg.chainParams(),
LoaderOptions: []btcwallet.LoaderOption{opt},
CoinSelectionStrategy: wallet.CoinSelectionLargest,
ChainSource: s.chainSource,
}
blockCache := blockcache.NewBlockCache(20 * 1024 * 1024)
wallet, err := btcwallet.New(config, blockCache)
if err != nil {
return fmt.Errorf("failed to setup wallet loader: %s", err)
}
if err := wallet.Start(); err != nil {
return fmt.Errorf("failed to start wallet: %s", err)
}
for {
if !wallet.InternalWallet().ChainSynced() {
log.Debug("waiting sync....")
time.Sleep(3 * time.Second)
continue
}
break
}
log.Debugf("chain synced")
addrs, err := wallet.ListAddresses(string(aspKeyAccount), false)
if err != nil {
return err
}
for info, addrs := range addrs {
if info.AccountName != string(aspKeyAccount) {
continue
}
for _, addr := range addrs {
if addr.Internal {
continue
}
splittedPath := strings.Split(addr.DerivationPath, "/")
last := splittedPath[len(splittedPath)-1]
if last == "0" {
decoded, err := btcutil.DecodeAddress(addr.Address, s.cfg.chainParams())
if err != nil {
return err
}
infos, err := wallet.AddressInfo(decoded)
if err != nil {
return err
}
managedPubkeyAddr, ok := infos.(waddrmgr.ManagedPubKeyAddress)
if !ok {
return fmt.Errorf("failed to cast address to managed pubkey address")
}
s.aspTaprootAddr = managedPubkeyAddr
break
}
}
}
s.wallet = wallet
return nil
}
return s.wallet.InternalWallet().Unlock([]byte(password), nil)
}

View File

@@ -40,6 +40,48 @@ func NewHandler(service application.Service) arkv1.ArkServiceServer {
return h
}
func (h *handler) CompletePayment(ctx context.Context, req *arkv1.CompletePaymentRequest) (*arkv1.CompletePaymentResponse, error) {
if req.GetSignedRedeemTx() == "" {
return nil, status.Error(codes.InvalidArgument, "missing signed redeem tx")
}
if len(req.GetSignedUnconditionalForfeitTxs()) <= 0 {
return nil, status.Error(codes.InvalidArgument, "missing signed unconditional forfeit txs")
}
if err := h.svc.CompleteAsyncPayment(
ctx, req.GetSignedRedeemTx(), req.GetSignedUnconditionalForfeitTxs(),
); err != nil {
return nil, err
}
return &arkv1.CompletePaymentResponse{}, nil
}
func (h *handler) CreatePayment(ctx context.Context, req *arkv1.CreatePaymentRequest) (*arkv1.CreatePaymentResponse, error) {
vtxosKeys, err := parseInputs(req.GetInputs())
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
receivers, err := parseReceivers(req.GetOutputs())
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
redeemTx, unconditionalForfeitTxs, err := h.svc.CreateAsyncPayment(
ctx, vtxosKeys, receivers,
)
if err != nil {
return nil, err
}
return &arkv1.CreatePaymentResponse{
SignedRedeemTx: redeemTx,
UsignedUnconditionalForfeitTxs: unconditionalForfeitTxs,
}, nil
}
func (h *handler) Onboard(ctx context.Context, req *arkv1.OnboardRequest) (*arkv1.OnboardResponse, error) {
if req.GetUserPubkey() == "" {
return nil, status.Error(codes.InvalidArgument, "missing user pubkey")
@@ -98,12 +140,9 @@ func (h *handler) Ping(ctx context.Context, req *arkv1.PingRequest) (*arkv1.Ping
}
func (h *handler) RegisterPayment(ctx context.Context, req *arkv1.RegisterPaymentRequest) (*arkv1.RegisterPaymentResponse, error) {
vtxosKeys := make([]domain.VtxoKey, 0, len(req.GetInputs()))
for _, input := range req.GetInputs() {
vtxosKeys = append(vtxosKeys, domain.VtxoKey{
Txid: input.GetTxid(),
VOut: input.GetVout(),
})
vtxosKeys, err := parseInputs(req.GetInputs())
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
id, err := h.svc.SpendVtxos(ctx, vtxosKeys)
@@ -350,6 +389,13 @@ func (v vtxoList) toProto(hrp string, aspKey *secp256k1.PublicKey) []*arkv1.Vtxo
key, _ := secp256k1.ParsePubKey(buf)
addr, _ = common.EncodeAddress(hrp, key, aspKey)
}
var pendingData *arkv1.PendingPayment
if vv.AsyncPayment != nil {
pendingData = &arkv1.PendingPayment{
RedeemTx: vv.AsyncPayment.RedeemTx,
UnconditionalForfeitTxs: vv.AsyncPayment.UnconditionalForfeitTxs,
}
}
list = append(list, &arkv1.Vtxo{
Outpoint: &arkv1.Input{
Txid: vv.Txid,
@@ -364,8 +410,11 @@ func (v vtxoList) toProto(hrp string, aspKey *secp256k1.PublicKey) []*arkv1.Vtxo
ExpireAt: vv.ExpireAt,
SpentBy: vv.SpentBy,
Swept: vv.Swept,
PendingData: pendingData,
Pending: pendingData != nil,
})
}
return list
}

View File

@@ -30,6 +30,21 @@ func parseAddress(addr string) (string, *secp256k1.PublicKey, *secp256k1.PublicK
return common.DecodeAddress(addr)
}
func parseInputs(ins []*arkv1.Input) ([]domain.VtxoKey, error) {
if len(ins) <= 0 {
return nil, fmt.Errorf("missing inputs")
}
vtxos := make([]domain.VtxoKey, 0, len(ins))
for _, input := range ins {
vtxos = append(vtxos, domain.VtxoKey{
Txid: input.GetTxid(),
VOut: input.GetVout(),
})
}
return vtxos, nil
}
func parseReceivers(outs []*arkv1.Output) ([]domain.Receiver, error) {
receivers := make([]domain.Receiver, 0, len(outs))
for _, out := range outs {

View File

@@ -149,6 +149,14 @@ func Whitelist() map[string][]bakery.Op {
Entity: EntityArk,
Action: "write",
}},
fmt.Sprintf("/%s/CreatePayment", arkv1.ArkService_ServiceDesc.ServiceName): {{
Entity: EntityArk,
Action: "write",
}},
fmt.Sprintf("/%s/CompletePayment", arkv1.ArkService_ServiceDesc.ServiceName): {{
Entity: EntityArk,
Action: "write",
}},
fmt.Sprintf("/%s/Check", grpchealth.Health_ServiceDesc.ServiceName): {{
Entity: EntityHealth,
Action: "read",

View File

@@ -109,6 +109,14 @@ func TestSendOffchain(t *testing.T) {
require.NoError(t, err)
require.NoError(t, json.Unmarshal([]byte(balanceStr), &balance))
require.NotZero(t, balance.Offchain.Total)
_, err = runClarkCommand("claim", "--password", utils.Password)
require.NoError(t, err)
balanceStr, err = runClarkCommand("balance")
require.NoError(t, err)
require.NoError(t, json.Unmarshal([]byte(balanceStr), &balance))
require.NotZero(t, balance.Offchain.Total)
}
func TestUnilateralExit(t *testing.T) {