From 42d5cabc4ba36333d43a52f58e8c31868af84806 Mon Sep 17 00:00:00 2001 From: Jad Wahab <15110087+jadwahab@users.noreply.github.com> Date: Sun, 9 Apr 2023 14:31:39 +0300 Subject: [PATCH] refactor!: use 1 dummy with bids --- errors.go | 3 +- ord/2dummies.go | 321 +++++++++++++++++++++++++++++++++++++++++++ ord/2dummies_test.go | 128 +++++++++++++++++ ord/bid.go | 133 ++++++++---------- ord/bid_test.go | 61 ++++---- ord/list2dummies.go | 88 ------------ ord/list_test.go | 2 + unlocker.go | 2 + 8 files changed, 539 insertions(+), 199 deletions(-) create mode 100644 ord/2dummies.go create mode 100644 ord/2dummies_test.go delete mode 100644 ord/list2dummies.go diff --git a/errors.go b/errors.go index ed077c2b..2caa8dc7 100644 --- a/errors.go +++ b/errors.go @@ -71,7 +71,6 @@ var ( ErrInsufficientUTXOValue = errors.New("need at least 1 utxos which is > ordinal price") ErrUTXOInputMismatch = errors.New("utxo and input mismatch") ErrInvalidSellOffer = errors.New("invalid sell offer (partially signed tx)") - ErrOrdinalOutputNoExist = errors.New("ordinal output expected in index 2 doesn't exist") - ErrOrdinalInputNoExist = errors.New("ordinal input expected in index 2 doesn't exist") ErrEmptyScripts = errors.New("at least one of needed scripts is empty") + ErrInsufficientFees = errors.New("fee paid not enough with new locking script") ) diff --git a/ord/2dummies.go b/ord/2dummies.go new file mode 100644 index 00000000..b5110ee4 --- /dev/null +++ b/ord/2dummies.go @@ -0,0 +1,321 @@ +package ord + +import ( + "bytes" + "context" + "encoding/hex" + "fmt" + + "github.com/libsv/go-bt/v2" + "github.com/libsv/go-bt/v2/bscript" + "github.com/libsv/go-bt/v2/sighash" + "github.com/pkg/errors" +) + +// TODO: are 2 dummies useful or to be removed? + +// AcceptOrdinalSaleListing2Dummies accepts a partially signed Bitcoin +// transaction offer to sell an ordinal. When accepting the offer, +// you will need to provide at least 3 UTXOs - with the first 2 +// being dummy utxos that will just pass through, and the rest with +// the required payment and tx fees. +func AcceptOrdinalSaleListing2Dummies(ctx context.Context, vla *ValidateListingArgs, + asoa *AcceptListingArgs) (*bt.Tx, error) { + + if valid := vla.Validate(asoa.PSTx); !valid { + return nil, bt.ErrInvalidSellOffer + } + sellerOrdinalInput := asoa.PSTx.Inputs[0] + sellerOutput := asoa.PSTx.Outputs[0] + + if len(asoa.UTXOs) < 3 { + return nil, bt.ErrInsufficientUTXOs + } + + tx := bt.NewTx() + + // add dummy inputs + err := tx.FromUTXOs(asoa.UTXOs[0], asoa.UTXOs[1]) + if err != nil { + return nil, fmt.Errorf(`failed to add inputs: %w`, err) + } + + tx.Inputs = append(tx.Inputs, sellerOrdinalInput) + + // add payment input(s) + err = tx.FromUTXOs(asoa.UTXOs[2:]...) + if err != nil { + return nil, fmt.Errorf(`failed to add inputs: %w`, err) + } + + // add dummy output to passthrough dummy inputs + tx.AddOutput(&bt.Output{ + LockingScript: asoa.DummyOutputScript, + Satoshis: asoa.UTXOs[0].Satoshis + asoa.UTXOs[1].Satoshis, + }) + + // add ordinal receive output + tx.AddOutput(&bt.Output{ + LockingScript: asoa.BuyerReceiveOrdinalScript, + Satoshis: 1, + }) + + tx.AddOutput(sellerOutput) + + err = tx.Change(asoa.ChangeScript, asoa.FQ) + if err != nil { + return nil, err + } + + //nolint:dupl // TODO: are 2 dummies useful or to be removed? + for i, u := range asoa.UTXOs { + // skip 3rd input (ordinals input) + j := i + if i >= 2 { + j++ + } + + if tx.Inputs[j] == nil { + return nil, fmt.Errorf("input expected at index %d doesn't exist", j) + } + if !(bytes.Equal(u.TxID, tx.Inputs[j].PreviousTxID())) { + return nil, bt.ErrUTXOInputMismatch + } + if *u.Unlocker == nil { + return nil, fmt.Errorf("UTXO unlocker at index %d not found", i) + } + err = tx.FillInput(ctx, *u.Unlocker, bt.UnlockerParams{InputIdx: uint32(j)}) + if err != nil { + return nil, err + } + } + + return tx, nil +} + +// MakeBid2DArgs contains the arguments +// needed to make a bid to buy an +// ordinal. +type MakeBid2DArgs struct { + BidAmount uint64 + OrdinalTxID string + OrdinalVOut uint32 + BidderUTXOs []*bt.UTXO + BuyerReceiveOrdinalScript *bscript.Script + DummyOutputScript *bscript.Script + ChangeScript *bscript.Script + FQ *bt.FeeQuote +} + +// MakeBidToBuy1SatOrdinal makes a bid offer to buy a 1 sat ordinal +// at a specific price - this tx will be partially signed and will +// need to be completed by the seller if they accept the bid. Multiple +// people can make different bids and the seller will need to choose +// only one to go through and broadcast to the node network. +// +// Note: this function is meant for ordinals in 1 satoshi outputs instead +// of ordinal ranges in 1 output (>1 satoshi outputs). +func MakeBidToBuy1SatOrdinal2Dummies(ctx context.Context, mba *MakeBid2DArgs) (*bt.Tx, error) { + if len(mba.BidderUTXOs) < 3 { + return nil, bt.ErrInsufficientUTXOs + } + + tx := bt.NewTx() + + // add dummy inputs + err := tx.FromUTXOs(mba.BidderUTXOs[0], mba.BidderUTXOs[1]) + if err != nil { + return nil, fmt.Errorf(`failed to add inputs: %w`, err) + } + + OrdinalTxIDBytes, err := hex.DecodeString(mba.OrdinalTxID) + if err != nil { + return nil, err + } + emptyOrdInput := &bt.Input{ + PreviousTxOutIndex: mba.OrdinalVOut, + PreviousTxScript: func() *bscript.Script { + //nolint:lll // add dummy ordinal PreviousTxScript + // so that the change function can estimate + // UnlockingScript sizes + s, _ := bscript.NewFromHexString("76a914c25e9a2b70ec83d7b4fbd0f36f00a86723a48e6b88ac0063036f72645118746578742f706c61696e3b636861727365743d7574662d38000d48656c6c6f2c20776f726c642168") // hello world (text/plain) test inscription + return s + }(), + } + err = emptyOrdInput.PreviousTxIDAdd(OrdinalTxIDBytes) + if err != nil { + return nil, fmt.Errorf(`failed to add ordinal input: %w`, err) + } + tx.Inputs = append(tx.Inputs, emptyOrdInput) + + // add payment input(s) + err = tx.FromUTXOs(mba.BidderUTXOs[2:]...) + if err != nil { + return nil, fmt.Errorf(`failed to add inputs: %w`, err) + } + + // add dummy output to passthrough dummy inputs + tx.AddOutput(&bt.Output{ + LockingScript: mba.DummyOutputScript, + Satoshis: mba.BidderUTXOs[0].Satoshis + mba.BidderUTXOs[1].Satoshis, + }) + + // add ordinal receive output + tx.AddOutput(&bt.Output{ + LockingScript: mba.BuyerReceiveOrdinalScript, + Satoshis: 1, + }) + + tx.AddOutput(&bt.Output{ + Satoshis: mba.BidAmount, + LockingScript: func() *bscript.Script { // add dummy p2pkh script to calc fees accurately + s, _ := bscript.NewP2PKHFromAddress("1FunnyJoke111111111111111112AVXh5") + return s + }(), + }) + + err = tx.Change(mba.ChangeScript, mba.FQ) + if err != nil { + return nil, err + } + + //nolint: dupl // TODO: are 2 dummies useful or to be removed? + for i, u := range mba.BidderUTXOs { + // skip 3rd input (ordinals input) + j := i + if i >= 2 { + j++ + } + + if tx.Inputs[j] == nil { + return nil, fmt.Errorf("input expected at index %d doesn't exist", j) + } + if !(bytes.Equal(u.TxID, tx.Inputs[j].PreviousTxID())) { + return nil, bt.ErrUTXOInputMismatch + } + if *u.Unlocker == nil { + return nil, fmt.Errorf("UTXO unlocker at index %d not found", i) + } + err = tx.FillInput(ctx, *u.Unlocker, bt.UnlockerParams{ + InputIdx: uint32(j), + SigHashFlags: sighash.SingleForkID, + }) + if err != nil { + return nil, err + } + } + + return tx, nil +} + +// ValidateBid2DArgs are the arguments needed to +// validate a specific bid to buy an ordinal. +// +// Note: index 2 should be the listed ordinal input. +type ValidateBid2DArgs struct { + PreviousUTXOs []*bt.UTXO // index 2 should be the listed ordinal input + BidAmount uint64 + ExpectedFQ *bt.FeeQuote +} + +// Validate a bid to buy an ordinal +// given specific validation parameters. +func (vba *ValidateBid2DArgs) Validate(pstx *bt.Tx) bool { + if pstx.InputCount() < 4 { + return false + } + if pstx.OutputCount() < 4 { + return false + } + + // check previous utxos match inputs + if len(vba.PreviousUTXOs) != pstx.InputCount() { + return false + } + for i := range vba.PreviousUTXOs { + if !bytes.Equal(pstx.Inputs[i].PreviousTxID(), vba.PreviousUTXOs[i].TxID) { + return false + } + if uint64(pstx.Inputs[i].PreviousTxOutIndex) != uint64(vba.PreviousUTXOs[i].Vout) { + return false + } + } + + // check passthrough dummy inputs and output to avoid + // mismatching and losing the ordinal to another output + if (vba.PreviousUTXOs[0].Satoshis + vba.PreviousUTXOs[1].Satoshis) != pstx.Outputs[0].Satoshis { + return false + } + + // check lou (ListedOrdinalUTXO) matches supplied pstx input index 2 + pstxOrdinalInput := pstx.Inputs[2] + if !bytes.Equal(pstxOrdinalInput.PreviousTxID(), vba.PreviousUTXOs[2].TxID) { + return false + } + if uint64(pstxOrdinalInput.PreviousTxOutIndex) != uint64(vba.PreviousUTXOs[2].Vout) { + return false + } + + // check enough fees paid + pstx.Outputs[2].Satoshis = vba.BidAmount + enough, err := pstx.IsFeePaidEnough(vba.ExpectedFQ) + if err != nil || !enough { + return false + } + + // TODO: check signatures valid + + return true +} + +// AcceptBid2DArgs contains the arguments +// needed to accept a bid to buy an +// ordinal. +type AcceptBid2DArgs struct { + PSTx *bt.Tx + SellerReceiveOrdinalScript *bscript.Script + OrdinalUnlocker bt.Unlocker + ExtraUTXOs []*bt.UTXO +} + +// AcceptBidToBuy1SatOrdinal2Dummies creates a PBST (Partially Signed Bitcoin +// Transaction) that offers a specific ordinal UTXO for sale at a +// specific price. +func AcceptBidToBuy1SatOrdinal2Dummies(ctx context.Context, vba *ValidateBid2DArgs, + aba *AcceptBid2DArgs) (*bt.Tx, error) { + + if valid := vba.Validate(aba.PSTx); !valid { + return nil, bt.ErrInvalidSellOffer + } + + if !aba.SellerReceiveOrdinalScript.IsP2PKH() { + // TODO: if a script different to/bigger than p2pkh is used to + // receive the ordinal, then the seller may need to add extra + // utxos `aba.ExtraUTXOs` to cover the extra bytes since the + // bidder only accounted for p2pkh script when calculating their + // change. + return nil, errors.New("only receive to p2pkh supported for now") + } + + tx, err := bt.NewTxFromBytes(aba.PSTx.Bytes()) + if err != nil { + return nil, err + } + + if tx.Outputs[2] == nil { + return nil, errors.New("ordinal output expected in index 2 doesn't exist") + } + tx.Outputs[2].LockingScript = aba.SellerReceiveOrdinalScript + + if tx.Inputs[2] == nil { + return nil, errors.New("ordinal input expected in index 2 doesn't exist") + } + tx.Inputs[2].PreviousTxScript = vba.PreviousUTXOs[2].LockingScript + tx.Inputs[2].PreviousTxSatoshis = vba.PreviousUTXOs[2].Satoshis + err = tx.FillInput(ctx, aba.OrdinalUnlocker, bt.UnlockerParams{InputIdx: 2}) + if err != nil { + return nil, err + } + + return tx, nil +} diff --git a/ord/2dummies_test.go b/ord/2dummies_test.go new file mode 100644 index 00000000..7c2172b8 --- /dev/null +++ b/ord/2dummies_test.go @@ -0,0 +1,128 @@ +package ord_test + +import ( + "context" + "encoding/hex" + "testing" + + "github.com/libsv/go-bk/wif" + "github.com/libsv/go-bt/v2" + "github.com/libsv/go-bt/v2/bscript" + "github.com/libsv/go-bt/v2/ord" + "github.com/libsv/go-bt/v2/unlocker" + "github.com/stretchr/testify/assert" +) + +func TestBidToBuyPSBT2DNoErrors(t *testing.T) { + fundingWif, _ := wif.DecodeWIF("L5W2nyKUCsDStVUBwZj2Q3Ph5vcae4bgdzprZDYqDpvZA8AFguFH") // 19NfKd8aTwvb5ngfP29RxgfQzZt8KAYtQo + fundingAddr, _ := bscript.NewAddressFromPublicKeyString(hex.EncodeToString(fundingWif.SerialisePubKey()), true) + fundingScript, _ := bscript.NewP2PKHFromAddress(fundingAddr.AddressString) + fundingUnlockerGetter := unlocker.Getter{PrivateKey: fundingWif.PrivKey} + fundingUnlocker, _ := fundingUnlockerGetter.Unlocker(context.Background(), fundingScript) + + bidAmount := 250 + + us := []*bt.UTXO{ + { + TxID: func() []byte { + t, _ := hex.DecodeString("411084d83d4f380cfc331ed849946bd7f354ca17138dbd723a6420ec9f5f4bd1") + return t + }(), + Vout: uint32(0), + LockingScript: fundingScript, + Satoshis: 20, + Unlocker: &fundingUnlocker, + }, + { + TxID: func() []byte { + t, _ := hex.DecodeString("411084d83d4f380cfc331ed849946bd7f354ca17138dbd723a6420ec9f5f4bd1") + return t + }(), + Vout: uint32(1), + LockingScript: fundingScript, + Satoshis: 20, + Unlocker: &fundingUnlocker, + }, + { + TxID: func() []byte { + t, _ := hex.DecodeString("4d815adc39a740810cb438eb285f6e08ae3957fdc4e4806399babfa806dfc456") + return t + }(), + Vout: uint32(0), + LockingScript: fundingScript, + Satoshis: 100000000, + Unlocker: &fundingUnlocker, + }, + } + + ordWif, _ := wif.DecodeWIF("KwQq67d4Jds3wxs3kQHB8PPwaoaBQfNKkzAacZeMesb7zXojVYpj") // 1HebepswCi6huw1KJ7LvkrgemAV63TyVUs + ordPrefixAddr, _ := bscript.NewAddressFromPublicKeyString(hex.EncodeToString(ordWif.SerialisePubKey()), true) + ordPrefixScript, _ := bscript.NewP2PKHFromAddress(ordPrefixAddr.AddressString) + ordUnlockerGetter := unlocker.Getter{PrivateKey: ordWif.PrivKey} + ordUnlocker, _ := ordUnlockerGetter.Unlocker(context.Background(), ordPrefixScript) + + ordUTXO := &bt.UTXO{ + TxID: func() []byte { + t, _ := hex.DecodeString("e17d7856c375640427943395d2341b6ed75f73afc8b22bb3681987278978a584") + return t + }(), + Vout: uint32(81), + LockingScript: func() *bscript.Script { + s, _ := bscript.NewFromHexString("76a914b69e544cbf33c4eabdd5cf8792cd4e53f5ed6d1788ac") + return s + }(), + Satoshis: 1, + } + + pstx, CreateBidError := ord.MakeBidToBuy1SatOrdinal2Dummies(context.Background(), &ord.MakeBid2DArgs{ + BidAmount: uint64(bidAmount), + OrdinalTxID: ordUTXO.TxIDStr(), + OrdinalVOut: ordUTXO.Vout, + BidderUTXOs: us, + BuyerReceiveOrdinalScript: func() *bscript.Script { + s, _ := bscript.NewP2PKHFromAddress("12R2qFEoUtWwwVecgrkxwMZNnMq6GB8pQW") // L3kLQ9rpDBLgbh3GfPSbXDGwxgmK2Dcb6Qrp4JZRRcne8FMDZWDc + return s + }(), + DummyOutputScript: func() *bscript.Script { + s, _ := bscript.NewP2PKHFromAddress("19NfKd8aTwvb5ngfP29RxgfQzZt8KAYtQo") // L1JWiLZtCkkqin41XtQ2Jxo1XGxj1R4ydT2zmxPiaeQfuyUK631D + return s + }(), + ChangeScript: func() *bscript.Script { + s, _ := bscript.NewP2PKHFromAddress("19NfKd8aTwvb5ngfP29RxgfQzZt8KAYtQo") // L1JWiLZtCkkqin41XtQ2Jxo1XGxj1R4ydT2zmxPiaeQfuyUK631D + return s + }(), + FQ: bt.NewFeeQuote(), + }) + + t.Run("no errors creating bid to buy ordinal", func(t *testing.T) { + assert.NoError(t, CreateBidError) + }) + + t.Run("validate PSBT bid to buy ordinal", func(t *testing.T) { + vba := &ord.ValidateBid2DArgs{ + BidAmount: uint64(bidAmount), + ExpectedFQ: bt.NewFeeQuote(), + // insert ordinal utxo at index 2 + PreviousUTXOs: append(us[:2], append([]*bt.UTXO{ordUTXO}, us[2:]...)...), + } + assert.True(t, vba.Validate(pstx)) + }) + + t.Run("no errors when accepting bid", func(t *testing.T) { + _, err := ord.AcceptBidToBuy1SatOrdinal2Dummies(context.Background(), &ord.ValidateBid2DArgs{ + BidAmount: uint64(bidAmount), + ExpectedFQ: bt.NewFeeQuote(), + PreviousUTXOs: append(us[:2], append([]*bt.UTXO{ordUTXO}, us[2:]...)...), + }, + &ord.AcceptBid2DArgs{ + PSTx: pstx, + SellerReceiveOrdinalScript: func() *bscript.Script { + s, _ := bscript.NewP2PKHFromAddress("1C3V9TTJefP8Hft96sVf54mQyDJh8Ze4w4") // L1JWiLZtCkkqin41XtQ2Jxo1XGxj1R4ydT2zmxPiaeQfuyUK631D + return s + }(), + OrdinalUnlocker: ordUnlocker, + }) + + assert.NoError(t, err) + }) +} diff --git a/ord/bid.go b/ord/bid.go index 867ec50b..0ef5f9b9 100644 --- a/ord/bid.go +++ b/ord/bid.go @@ -9,7 +9,6 @@ import ( "github.com/libsv/go-bt/v2" "github.com/libsv/go-bt/v2/bscript" "github.com/libsv/go-bt/v2/sighash" - "github.com/pkg/errors" ) // MakeBidArgs contains the arguments @@ -35,14 +34,28 @@ type MakeBidArgs struct { // Note: this function is meant for ordinals in 1 satoshi outputs instead // of ordinal ranges in 1 output (>1 satoshi outputs). func MakeBidToBuy1SatOrdinal(ctx context.Context, mba *MakeBidArgs) (*bt.Tx, error) { - if len(mba.BidderUTXOs) < 3 { + if len(mba.BidderUTXOs) < 2 { return nil, bt.ErrInsufficientUTXOs } + // check at least 1 utxo is larger than the listed ordinal price + validUTXOFound := false + for i, u := range mba.BidderUTXOs { + if u.Satoshis > mba.BidAmount { + // Move the UTXO at index i to the beginning + mba.BidderUTXOs = append([]*bt.UTXO{u}, append(mba.BidderUTXOs[:i], mba.BidderUTXOs[i+1:]...)...) + validUTXOFound = true + break + } + } + if !validUTXOFound { + return nil, bt.ErrInsufficientUTXOValue + } + tx := bt.NewTx() // add dummy inputs - err := tx.FromUTXOs(mba.BidderUTXOs[0], mba.BidderUTXOs[1]) + err := tx.FromUTXOs(mba.BidderUTXOs[0]) if err != nil { return nil, fmt.Errorf(`failed to add inputs: %w`, err) } @@ -68,21 +81,15 @@ func MakeBidToBuy1SatOrdinal(ctx context.Context, mba *MakeBidArgs) (*bt.Tx, err tx.Inputs = append(tx.Inputs, emptyOrdInput) // add payment input(s) - err = tx.FromUTXOs(mba.BidderUTXOs[2:]...) + err = tx.FromUTXOs(mba.BidderUTXOs[1:]...) if err != nil { return nil, fmt.Errorf(`failed to add inputs: %w`, err) } - // add dummy output to passthrough dummy inputs + // add dummy output tx.AddOutput(&bt.Output{ LockingScript: mba.DummyOutputScript, - Satoshis: mba.BidderUTXOs[0].Satoshis + mba.BidderUTXOs[1].Satoshis, - }) - - // add ordinal receive output - tx.AddOutput(&bt.Output{ - LockingScript: mba.BuyerReceiveOrdinalScript, - Satoshis: 1, + Satoshis: mba.BidderUTXOs[0].Satoshis - mba.BidAmount, }) tx.AddOutput(&bt.Output{ @@ -93,15 +100,22 @@ func MakeBidToBuy1SatOrdinal(ctx context.Context, mba *MakeBidArgs) (*bt.Tx, err }(), }) + // add ordinal receive output + tx.AddOutput(&bt.Output{ + LockingScript: mba.BuyerReceiveOrdinalScript, + Satoshis: 1, + }) + err = tx.Change(mba.ChangeScript, mba.FQ) if err != nil { return nil, err } + //nolint: dupl // TODO: are 2 dummies useful or to be removed? for i, u := range mba.BidderUTXOs { - // skip 3rd input (ordinals input) + // skip 2nd input (ordinals input) j := i - if i >= 2 { + if i >= 1 { j++ } @@ -128,54 +142,36 @@ func MakeBidToBuy1SatOrdinal(ctx context.Context, mba *MakeBidArgs) (*bt.Tx, err // ValidateBidArgs are the arguments needed to // validate a specific bid to buy an ordinal. -// -// Note: index 2 should be the listed ordinal input. +// as they appear in the tx. type ValidateBidArgs struct { - PreviousUTXOs []*bt.UTXO // index 2 should be the listed ordinal input - BidAmount uint64 - ExpectedFQ *bt.FeeQuote + OrdinalUTXO *bt.UTXO + BidAmount uint64 + ExpectedFQ *bt.FeeQuote } // Validate a bid to buy an ordinal // given specific validation parameters. func (vba *ValidateBidArgs) Validate(pstx *bt.Tx) bool { - if pstx.InputCount() < 4 { + if pstx.InputCount() < 3 { return false } - if pstx.OutputCount() < 4 { + if pstx.OutputCount() < 3 { // technically should have 4 including change return false } - // check previous utxos match inputs - if len(vba.PreviousUTXOs) != pstx.InputCount() { + // check OrdinalUTXO matches supplied pstx input index 1 + pstxOrdinalInput := pstx.Inputs[1] + if !bytes.Equal(pstxOrdinalInput.PreviousTxID(), vba.OrdinalUTXO.TxID) { return false } - for i := range vba.PreviousUTXOs { - if !bytes.Equal(pstx.Inputs[i].PreviousTxID(), vba.PreviousUTXOs[i].TxID) { - return false - } - if uint64(pstx.Inputs[i].PreviousTxOutIndex) != uint64(vba.PreviousUTXOs[i].Vout) { - return false - } - } - - // check passthrough dummy inputs and output to avoid - // mismatching and losing the ordinal to another output - if (vba.PreviousUTXOs[0].Satoshis + vba.PreviousUTXOs[1].Satoshis) != pstx.Outputs[0].Satoshis { + if uint64(pstxOrdinalInput.PreviousTxOutIndex) != uint64(vba.OrdinalUTXO.Vout) { return false } - // check lou (ListedOrdinalUTXO) matches supplied pstx input index 2 - pstxOrdinalInput := pstx.Inputs[2] - if !bytes.Equal(pstxOrdinalInput.PreviousTxID(), vba.PreviousUTXOs[2].TxID) { - return false - } - if uint64(pstxOrdinalInput.PreviousTxOutIndex) != uint64(vba.PreviousUTXOs[2].Vout) { - return false - } + // set the value of the output for the bid amount + pstx.Outputs[1].Satoshis = vba.BidAmount - // check enough funds paid - pstx.Outputs[2].Satoshis = vba.BidAmount + // check enough fees paid enough, err := pstx.IsFeePaidEnough(vba.ExpectedFQ) if err != nil || !enough { return false @@ -187,48 +183,35 @@ func (vba *ValidateBidArgs) Validate(pstx *bt.Tx) bool { } // AcceptBidArgs contains the arguments -// needed to make an offer to sell an +// needed to accept a bid to buy an // ordinal. type AcceptBidArgs struct { - PSTx *bt.Tx - SellerReceiveOrdinalScript *bscript.Script - OrdinalUnlocker bt.Unlocker - ExtraUTXOs []*bt.UTXO + PSTx *bt.Tx + SellerReceiveScript *bscript.Script + OrdinalUnlocker bt.Unlocker } -// AcceptBidToBuy1SatOrdinal creates a PBST (Partially Signed Bitcoin -// Transaction) that offers a specific ordinal UTXO for sale at a -// specific price. +// AcceptBidToBuy1SatOrdinal accepts a partially signed Bitcoin +// transaction bid to buy an ordinal. +// func AcceptBidToBuy1SatOrdinal(ctx context.Context, vba *ValidateBidArgs, aba *AcceptBidArgs) (*bt.Tx, error) { if valid := vba.Validate(aba.PSTx); !valid { return nil, bt.ErrInvalidSellOffer } - if !aba.SellerReceiveOrdinalScript.IsP2PKH() { - // TODO: if a script different to/bigger than p2pkh is used to - // receive the ordinal, then the seller may need to add extra - // utxos `aba.ExtraUTXOs` to cover the extra bytes since the - // bidder only accounted for p2pkh script when calculating their - // change. - return nil, errors.New("only receive to p2pkh supported for now") - } - - tx, err := bt.NewTxFromBytes(aba.PSTx.Bytes()) - if err != nil { - return nil, err - } + tx := aba.PSTx.Clone() - if tx.Outputs[2] == nil { - return nil, bt.ErrOrdinalOutputNoExist + tx.Outputs[1].LockingScript = aba.SellerReceiveScript + // check if fees paid are still enough with new + // locking script + enough, err := tx.IsFeePaidEnough(vba.ExpectedFQ) + if err != nil || !enough { + return nil, bt.ErrInsufficientFees } - tx.Outputs[2].LockingScript = aba.SellerReceiveOrdinalScript - if tx.Inputs[2] == nil { - return nil, bt.ErrOrdinalInputNoExist - } - tx.Inputs[2].PreviousTxScript = vba.PreviousUTXOs[2].LockingScript - tx.Inputs[2].PreviousTxSatoshis = vba.PreviousUTXOs[2].Satoshis - err = tx.FillInput(ctx, aba.OrdinalUnlocker, bt.UnlockerParams{InputIdx: 2}) + tx.Inputs[1].PreviousTxScript = vba.OrdinalUTXO.LockingScript + tx.Inputs[1].PreviousTxSatoshis = vba.OrdinalUTXO.Satoshis + err = tx.FillInput(ctx, aba.OrdinalUnlocker, bt.UnlockerParams{InputIdx: 1}) if err != nil { return nil, err } diff --git a/ord/bid_test.go b/ord/bid_test.go index 9286803f..c8257351 100644 --- a/ord/bid_test.go +++ b/ord/bid_test.go @@ -3,6 +3,7 @@ package ord_test import ( "context" "encoding/hex" + "fmt" "testing" "github.com/libsv/go-bk/wif" @@ -14,61 +15,45 @@ import ( ) func TestBidToBuyPSBTNoErrors(t *testing.T) { - fundingWif, _ := wif.DecodeWIF("L5W2nyKUCsDStVUBwZj2Q3Ph5vcae4bgdzprZDYqDpvZA8AFguFH") // 19NfKd8aTwvb5ngfP29RxgfQzZt8KAYtQo + fundingWif, _ := wif.DecodeWIF("L42PyNwEKE4XRaa8PzPh7JZurSAWJmx49nbVfaXYuiQg3RCubwn7") // 1JijRHzVfub38S2hizxkxEcVKQwuCTZmxJ fundingAddr, _ := bscript.NewAddressFromPublicKeyString(hex.EncodeToString(fundingWif.SerialisePubKey()), true) fundingScript, _ := bscript.NewP2PKHFromAddress(fundingAddr.AddressString) fundingUnlockerGetter := unlocker.Getter{PrivateKey: fundingWif.PrivKey} fundingUnlocker, _ := fundingUnlockerGetter.Unlocker(context.Background(), fundingScript) - bidAmount := 250 + bidAmount := 500 us := []*bt.UTXO{ { TxID: func() []byte { - t, _ := hex.DecodeString("411084d83d4f380cfc331ed849946bd7f354ca17138dbd723a6420ec9f5f4bd1") + t, _ := hex.DecodeString("e3e0c0b46826ae1cd8932daf70b280d686104cdd5c685dbe6bed823e437f9040") return t }(), Vout: uint32(0), LockingScript: fundingScript, - Satoshis: 20, + Satoshis: 900, Unlocker: &fundingUnlocker, }, { TxID: func() []byte { - t, _ := hex.DecodeString("411084d83d4f380cfc331ed849946bd7f354ca17138dbd723a6420ec9f5f4bd1") - return t - }(), - Vout: uint32(1), - LockingScript: fundingScript, - Satoshis: 20, - Unlocker: &fundingUnlocker, - }, - { - TxID: func() []byte { - t, _ := hex.DecodeString("4d815adc39a740810cb438eb285f6e08ae3957fdc4e4806399babfa806dfc456") + t, _ := hex.DecodeString("44ab22c6996ce2dee4829fa171dd2543f16bd35b7373aa446b3060bdbf43b588") return t }(), Vout: uint32(0), LockingScript: fundingScript, - Satoshis: 100000000, + Satoshis: 500, Unlocker: &fundingUnlocker, }, } - ordWif, _ := wif.DecodeWIF("KwQq67d4Jds3wxs3kQHB8PPwaoaBQfNKkzAacZeMesb7zXojVYpj") // 1HebepswCi6huw1KJ7LvkrgemAV63TyVUs - ordPrefixAddr, _ := bscript.NewAddressFromPublicKeyString(hex.EncodeToString(ordWif.SerialisePubKey()), true) - ordPrefixScript, _ := bscript.NewP2PKHFromAddress(ordPrefixAddr.AddressString) - ordUnlockerGetter := unlocker.Getter{PrivateKey: ordWif.PrivKey} - ordUnlocker, _ := ordUnlockerGetter.Unlocker(context.Background(), ordPrefixScript) - ordUTXO := &bt.UTXO{ TxID: func() []byte { - t, _ := hex.DecodeString("e17d7856c375640427943395d2341b6ed75f73afc8b22bb3681987278978a584") + t, _ := hex.DecodeString("75e24ffd0161f094a5e419dba42684c69faeacbeb805a1d9afdb29f6f4ac81ad") return t }(), - Vout: uint32(81), + Vout: uint32(0), LockingScript: func() *bscript.Script { - s, _ := bscript.NewFromHexString("76a914b69e544cbf33c4eabdd5cf8792cd4e53f5ed6d1788ac") + s, _ := bscript.NewFromHexString("76a914fabef78de0d136d0f9b13f047312fc4df094da9c88ac0063036f72645109696d6167652f706e67004dcf0e89504e470d0a1a0a0000000d4948445200000180000001800806000000a4c7b5bf00000006624b474400ff00ff00ffa0bda79300000e8449444154789cedddfb93ddf55dc7f1b339bb9b4d36091b2021d484d2909f8ab6dafa030816158233b6462c6da509f710508142200912728329500a4941e496001129424ba1741cb5b5056d404b2df687d21116103b4d099799e5127673367b76cffa47bc985977de8fc7ef2fce59f6ec79e6fbcbe7d3680000000000000000000000000000000000000000000000001fb4aea97e034cad93ffe094c9643fd988e68da79e7a32fa0c7efa8f3e13bd81f787df4fe68d3d7b7e18bdffe5a79c1abdffd183a3c9bcf1f4d37b7c07143663aadf0000534300008a120080a20400a0280100284a00008a120080a20400a0280100284a00008a120080a20400a0280100284a00008a120080a29c053ec53ef9c9df8ece83efede98d5e7ff7ee07a27d57f8115a7dc179d1fe6f77ff5db49f9cccee33b8f892bf88f637dfbc3dda0f0c0c44fb95abce88f6e917c833fffe8cefa029e40900a0280100284a00008a120080a20400a0280100284a00008a120080a20400a0280100284a00008a120080a20400a0280100284a00008aea9eea37b060c182e840f6fefe39d1ebffe217ff1b9d47fed18f1e1bbdff8bfff29264def8d0877e2dda5f78d105d17ef3a6add1be6f565fb4efe9ed89f66f0f0d45fbb1f658b43f78f060b45f159ee77fdbadb747fbd75e7b2ddabfb77f63f4f7f3fcf33f8bfe7e972d5b16bdfe8c19cd64de78e9a5c129bd0fc11300405102005094000014250000450900405102005094000014250000450900405102005094000014250000450900405102005094000014159f457dc8218744e7699f78e2ef46afff918f2c8df64f3df983687fdd755f8ef6ed763bdadff6d7b746fb679ffd51f41938e9532745bfff6677762545dfccec3e8103ad03d1be199e07dfdd93fdfcdffbde77a3dfdf09279c18fdfed6afbb2a9937dae17d0ad7df90fdfd9dbafcd468fff2cb2f47fb1f3dfb1fd1fe8d37de887eff9e00008a120080a20400a0280100284a00008a120080a20400a0280100284a00008a120080a20400a0280100284a00008a120080a20400a0a8f83e80d4c0c040741ef9e74eff7cf4fa73e7ce8bf64b9766f711dc7fffbdd1beab2bfb15ce9f7f68b4df78f535d17ecbb6cdd1bebfbf3fda4f8c8f47fb76b87fe8eb0f47fb73ce392bda0f8f0c47fbf43e8b73ce3e37da2f5c7844b47feeb9ff8cf68f7c23fbfdeddbb76f4abf833d0100142500004509004051020050940000142500004509004051020050940000142500004509004051020050940000142500004509004051537e1f406ac9e225d17d022bfee4b4e8f5bffffd7f89f63d3d3dd17efdbaaba2fdb265cba2fdd51bb3d7ef9f1d9ee73f3111edbb7bbaa3fd783bbb0f20fd0bdcb5f3be68ff3fafbc12edaf5c7f45b41f0fef13b8ecb2b5d17ec7d7b647fb175ef8ef69fd1dea0900a0280100284a00008a120080a20400a0280100284a00008a120080a20400a0280100284a00008a120080a20400a0280100284a00008aca0e43ff7f203dcffdc8458ba2fdb6add746fb77de7927daefdc7957b4ef4c46d7293476dd736fb4bfe4d28ba3fd64237bff5d5dd971ee336664ff867af49b8f45fb15a77d26dab7c7c6a2fd9d77dc1ded878686a2fdcf7ffe7cb41f1d1d8df6d39d270080a20400a0280100284a00008a120080a20400a0280100284a00008a120080a20400a0280100284a00008a120080a20400a0280100286adadf07303e3e1eedd3f3c87b7a7aa3fd830f3e10ede7ce9b17edaffff20dd1fef0c31744fbd9b36747fbf787df8ff6e97d02ad562bdabff1e61bd17ef3a6add17ee3357f15edd76fb832da9f7fdeea681f5ee7d0e8edcdfe7ea73b4f00004509004051020050940000142500004509004051020050940000142500004509004051020050940000142500004509004051020050d4b4bf0fa06f665fb43ffcf0c3a3fda2458ba2fdecfefe68df6c36a37d7a1efa9967ad8cf6add1ec3cfdf4e70faf0368f487bfbff3579f1bedefdd797fb44fdf7f7a1fc7c4c444b43fe288ecefaf3a4f00004509004051020050940000142500004509004051020050940000142500004509004051020050940000142500004509004051020050d4b4bf0f60786424dacf9a353bdaefdfbf3fdab70e1c88f6dbb65e17ed3b9dec40fc6ddbae8df637dc787db41f1e1e8ef6139dec3cfa766b2cda3ff6ad27a2fd2bafbc1cedd3cfcf359bae8ef6b36767f711bcf7debbd1be3d96fdfea63b4f00004509004051020050940000142500004509004051020050940000142500004509004051020050940000142500004509004051020050d4b4bf0fa0afaf2fda8f1cc8ee1378e8a1af47fb175f7ca12bd96fde724d74a07f7f7f761e7b7777f611ea743ad1bed96c46fb59b36645fbce44f6fe3ff7f93f8df6b3c3fb2cde7e7b28daffe4b99f449fdf919191e8f3bbf6f22b9279637232bb0f63baf304005094000014250000450900405102005094000014250000450900405102005094000014250000450900405102005094000014250000454dfbfb00daed76b41f1acace43ef09cfc33ff6d85f8f0e24ffca8d3745af3f7ffea1d17ee1c285d1feadb7de8af61ffb8d8f45fbbd7b7f19ed7b67ce8cf6edb1ecf3fbdefef7a2fdfc81f9d17ed5995f8c3ebfe9fb6fb55ad17e46b3f6bf816bfff4008509004051020050940000142500004509004051020050940000142500004509004051020050940000142500004509004051020050d494df07b060c1c2e83cf1952b5745af7fe4a223a3fdc4c444b44fcf931f08cf736f369bd1fec5175f8cf61f3eeaa8687ff2f2df8ff6a9fb76ed8ef6938de8e3df38e28845d1fedd77df89f6cdeeecf3d31d7efede7cebcd687fc5da75d17efb8e9ba35fe0abafbeda15bd8190270080a20400a0280100284a00008a120080a20400a0280100284a00008a120080a20400a0280100284a00008a120080a20400a0280100282a3e8bfab0c30e8bcec33ee18413a3d74fcfa33f75f9a9d1fee31fffcd687fef7dbba27d4f774fb44f3f01b3fa6645fb894e769f425757f603743a9d689fea84f7494c66d709c4f7110c0f0f47fbad5bb645fbc1c1c168bfebde9dd1feb8e38e8ff64f3ef98368fffaebfba23f004f000045090040510200509400001425000045090040510200509400001425000045090040510200509400001425000045090040510200505477fa1f181a1a8acea37efae93dd181e4871e7a68326f2c5ebc24daefd8714bb4dfbefd6bd1fe9a4d1ba3fd2d376f8ff603870c44fba38efa70b46fb55ad17ed1914746fb919191687f707434da379bcd687fda675744fbf5eb3644fb2d5b3645fb73cf3d3fdab7db63d1fe99a7f744fbf43cff94270080a20400a0280100284a00008a120080a20400a0280100284a00008a120080a20400a0280100284a00008a120080a20400a0280100282abe0f203567cedc68bf7cf91f46fbd1d1ec3cf9f1898968bf65ebe668dfdbdb1bedd3fb10d6acc9ce63ef9a91fd1be4d16f3c16ed0f1e3c18ed57acf874b46f84a7c13fb0fbc1689f7e7eeeb9e7ae68df13befedcb9d9f7c7972ebd3cdadf7adb8e683fd53c0100142500004509004051020050940000142500004509004051020050940000142500004509004051020050940000142500004509004051f17d0073e6cc994cf6471f7d74f4fa4f3cf178b4df78f5a6689fbafcb2b5d17efb8e5ba27dab95dd8730d66e47fb7ff8ce3f46fb2ffcd9e9d13e3d8ffedb8f7f27daaf3aeb8bd1bed9dd8cf65de185046bd75e19ed376fc9fefefafa6645fbf43cff157f7c5ab4fff6138f47df9ffbf6bd16fd023d0100142500004509004051020050940000142500004509004051020050940000142500004509004051020050940000142500004509004051f17d00e979dc4b162f89f6fbf6ed8bf677dcf937d17efdba0dd17e71f8f3cf9b3b37da5ff4e76ba27d7a1efeca556744fbf6f878b4ef74a2e3d81b67acfc42b47ffc5b4f44fbcf9e9e9d473fd61e8bf6232323d17edbd6eba2fda64d5747fb836307a3fdde5fed8df693939d689ff20400509400001425000045090040510200509400001425000045090040510200509400001425000045090040510200509400001425000045754df51b58b278497420fbde5fed8d7e8663961e13bdfecc99339379e3908181683fda6a45fb471e7934dabffefaebd1fe2b5fb921da4f7426a27da7939dc7dedbdb1bedefbe6b67b41f1c1c8cf6377df5c6683fdeceee63d8fffefe68dfd5957d85fdf4a7ff15fd07962c392afbfedafbcb29fd0ef60400509400001425000045090040510200509400001425000045090040510200509400001425000045090040510200509400001425000045754ff51b88cff33f263bcfffbc73cf4fe68d471ffd66b44fedda757fb4bff0a20ba2fde464f4bfbff1c0ee07a3fdea35e745fbee66f62770fb6d7744fb3517ae8ef6fbf767e7e96fdb7a6db4bff6daadd1bea7a727da9f75e6d9d17e646438fa000f0e0e4ef99d2a094f00004509004051020050940000142500004509004051020050940000142500004509004051020050940000142500004509004051020050d4b43ecbfa83b07469769fc0a5977c297afdf43cf4871ffefb68bf61c355d1fe778e3f21da9f7dce99d1fe9fbffb4f53fa193ee5e4e5d1e7e7a69b6e8e5e7f4eff9c689fdea7b07edd8668dfe974a2fd96ad9ba3fdf3cfffacf477a0270080a20400a0280100284a00008a120080a20400a0280100284a00008a120080a20400a0280100284a00008a120080a20400a028010028aaf459d81f844ffcd627a2f3e02fbef8d2e8f5efbefbce683f77debc68df887efa46e3a97f7db2f467f0a44ffd5ef47f70ac3d16bd7e734633dadf72cb8e68bfe6c2d5d1befa79fe294f0000450900405102005094000014250000450900405102005094000014250000450900405102005094000014250000450900405102005054f754bf81e9ee40eb40b46fb55ad1fef6dbb3fb002ebb3cbb8fe0c73f7ed679ec811feef9b7e8ffdff1c71d1fdd27909ee7ffd24b83d1bebb3bbb8f808c270080a20400a0280100284a00008a120080a20400a0280100284a00008a120080a20400a0280100284a00008a120080a20400a028010028eaff0080a891cbdf3cd1d00000000049454e44ae426082686a223150755161374b36324d694b43747373534c4b79316b683536575755374d74555235035345540474797065036f72640a636f6c6c656374696f6e0c734d6f6e2053696e6e657273036170700a74616c656f6673687561076d6f6e5479706508637265617475726505617564696f44623a2f2f333139616138346165323332663838353735353631616366323234303266343561663261306537616465313232623536353166623936633033356161643531300573746174734c4c7b22737472656e677468223a312c22766974616c697479223a332c226167696c697479223a322c22696e74656c6c6967656e6365223a342c226c75636b223a342c22737069726974223a357d04677569642433656330663636302d643437632d313165642d396530662d3835333264323665613832380373696e057072696465066e617475726508776174636866756c0472616365056669656e64046e616d6517576174636866756c204669656e64206f66205072696465") return s }(), Satoshis: 1, @@ -100,23 +85,30 @@ func TestBidToBuyPSBTNoErrors(t *testing.T) { t.Run("validate PSBT bid to buy ordinal", func(t *testing.T) { vba := &ord.ValidateBidArgs{ - BidAmount: uint64(bidAmount), - ExpectedFQ: bt.NewFeeQuote(), - // insert ordinal utxo at index 2 - PreviousUTXOs: append(us[:2], append([]*bt.UTXO{ordUTXO}, us[2:]...)...), + BidAmount: uint64(bidAmount), + ExpectedFQ: bt.NewFeeQuote(), + OrdinalUTXO: ordUTXO, } assert.True(t, vba.Validate(pstx)) }) + fmt.Println(pstx.String()) + t.Run("no errors when accepting bid", func(t *testing.T) { - _, err := ord.AcceptBidToBuy1SatOrdinal(context.Background(), &ord.ValidateBidArgs{ - BidAmount: uint64(bidAmount), - ExpectedFQ: bt.NewFeeQuote(), - PreviousUTXOs: append(us[:2], append([]*bt.UTXO{ordUTXO}, us[2:]...)...), + ordWif, _ := wif.DecodeWIF("KwQq67d4Jds3wxs3kQHB8PPwaoaBQfNKkzAacZeMesb7zXojVYpj") // 1HebepswCi6huw1KJ7LvkrgemAV63TyVUs + ordPrefixAddr, _ := bscript.NewAddressFromPublicKeyString(hex.EncodeToString(ordWif.SerialisePubKey()), true) + ordPrefixScript, _ := bscript.NewP2PKHFromAddress(ordPrefixAddr.AddressString) + ordUnlockerGetter := unlocker.Getter{PrivateKey: ordWif.PrivKey} + ordUnlocker, _ := ordUnlockerGetter.Unlocker(context.Background(), ordPrefixScript) + + tx, err := ord.AcceptBidToBuy1SatOrdinal(context.Background(), &ord.ValidateBidArgs{ + BidAmount: uint64(bidAmount), + ExpectedFQ: bt.NewFeeQuote(), + OrdinalUTXO: ordUTXO, }, &ord.AcceptBidArgs{ PSTx: pstx, - SellerReceiveOrdinalScript: func() *bscript.Script { + SellerReceiveScript: func() *bscript.Script { s, _ := bscript.NewP2PKHFromAddress("1C3V9TTJefP8Hft96sVf54mQyDJh8Ze4w4") // L1JWiLZtCkkqin41XtQ2Jxo1XGxj1R4ydT2zmxPiaeQfuyUK631D return s }(), @@ -124,5 +116,6 @@ func TestBidToBuyPSBTNoErrors(t *testing.T) { }) assert.NoError(t, err) + fmt.Println(tx.String()) }) } diff --git a/ord/list2dummies.go b/ord/list2dummies.go deleted file mode 100644 index 7cd55b37..00000000 --- a/ord/list2dummies.go +++ /dev/null @@ -1,88 +0,0 @@ -package ord - -import ( - "bytes" - "context" - "fmt" - - "github.com/libsv/go-bt/v2" -) - -// AcceptOrdinalSaleListing2Dummies accepts a partially signed Bitcoin -// transaction offer to sell an ordinal. When accepting the offer, -// you will need to provide at least 3 UTXOs - with the first 2 -// being dummy utxos that will just pass through, and the rest with -// the required payment and tx fees. -func AcceptOrdinalSaleListing2Dummies(ctx context.Context, vla *ValidateListingArgs, - asoa *AcceptListingArgs) (*bt.Tx, error) { - - if valid := vla.Validate(asoa.PSTx); !valid { - return nil, bt.ErrInvalidSellOffer - } - sellerOrdinalInput := asoa.PSTx.Inputs[0] - sellerOutput := asoa.PSTx.Outputs[0] - - if len(asoa.UTXOs) < 3 { - return nil, bt.ErrInsufficientUTXOs - } - - tx := bt.NewTx() - - // add dummy inputs - err := tx.FromUTXOs(asoa.UTXOs[0], asoa.UTXOs[1]) - if err != nil { - return nil, fmt.Errorf(`failed to add inputs: %w`, err) - } - - tx.Inputs = append(tx.Inputs, sellerOrdinalInput) - - // add payment input(s) - err = tx.FromUTXOs(asoa.UTXOs[2:]...) - if err != nil { - return nil, fmt.Errorf(`failed to add inputs: %w`, err) - } - - // add dummy output to passthrough dummy inputs - tx.AddOutput(&bt.Output{ - LockingScript: asoa.DummyOutputScript, - Satoshis: asoa.UTXOs[0].Satoshis + asoa.UTXOs[1].Satoshis, - }) - - // add ordinal receive output - tx.AddOutput(&bt.Output{ - LockingScript: asoa.BuyerReceiveOrdinalScript, - Satoshis: 1, - }) - - tx.AddOutput(sellerOutput) - - err = tx.Change(asoa.ChangeScript, asoa.FQ) - if err != nil { - return nil, err - } - - //nolint:dupl // false positive - for i, u := range asoa.UTXOs { - // skip 3rd input (ordinals input) - j := i - if i >= 2 { - j++ - } - - if tx.Inputs[j] == nil { - return nil, fmt.Errorf("input expected at index %d doesn't exist", j) - } - if !(bytes.Equal(u.TxID, tx.Inputs[j].PreviousTxID())) { - return nil, bt.ErrUTXOInputMismatch - } - if *u.Unlocker == nil { - return nil, fmt.Errorf("UTXO unlocker at index %d not found", i) - } - err = tx.FillInput(ctx, *u.Unlocker, bt.UnlockerParams{InputIdx: uint32(j)}) - if err != nil { - return nil, err - } - } - - return tx, nil -} diff --git a/ord/list_test.go b/ord/list_test.go index 2614a01d..4b0e3245 100644 --- a/ord/list_test.go +++ b/ord/list_test.go @@ -100,6 +100,7 @@ func TestOfferToSellPSBTNoErrors(t *testing.T) { assert.NoError(t, err) }) + // TODO: are 2 dummies useful or to be removed? t.Run("no errors when accepting listing using 2 dummies", func(t *testing.T) { us = append([]*bt.UTXO{ { @@ -137,4 +138,5 @@ func TestOfferToSellPSBTNoErrors(t *testing.T) { }) assert.NoError(t, err) }) + // } diff --git a/unlocker.go b/unlocker.go index 8db59848..c69e010b 100644 --- a/unlocker.go +++ b/unlocker.go @@ -13,6 +13,8 @@ type UnlockerParams struct { InputIdx uint32 // SigHashFlags the be applied [DEFAULT ALL|FORKID] SigHashFlags sighash.Flag + // TODO: add previous tx script and sats here instead of in + // input (and potentially remove from input) - see issue #143 } // Unlocker interface to allow custom implementations of different unlocking mechanisms.