55 "errors"
66 "fmt"
77 "net/url"
8- "slices"
98 "time"
109
1110 "github.com/btcsuite/btcd/blockchain"
@@ -684,6 +683,16 @@ func (f *AssetWallet) FundPacket(ctx context.Context,
684683 return nil , err
685684 }
686685
686+ for _ , vPkt := range pkt .VPackets {
687+ if err := tapsend .PrepareOutputAssets (ctx , vPkt ); err != nil {
688+ log .Errorf ("Error preparing output assets: %v, " +
689+ "packets: %v" , err , limitSpewer .Sdump (vPkt ))
690+
691+ return nil , fmt .Errorf ("unable to prepare outputs: %w" ,
692+ err )
693+ }
694+ }
695+
687696 success = true
688697 return pkt , nil
689698}
@@ -693,12 +702,6 @@ func (f *AssetWallet) FundPacket(ctx context.Context,
693702func (f * AssetWallet ) FundBurn (ctx context.Context ,
694703 fundDesc * tapsend.FundingDescriptor ) (* FundedVPacket , error ) {
695704
696- // Extract the asset ID and group key from the funding descriptor.
697- assetId , err := fundDesc .AssetSpecifier .UnwrapIdOrErr ()
698- if err != nil {
699- return nil , err
700- }
701-
702705 // We need to find a commitment that has enough assets to satisfy this
703706 // send request. We'll map the address to a set of constraints, so we
704707 // can use that to do Taproot asset coin selection.
@@ -733,11 +736,10 @@ func (f *AssetWallet) FundBurn(ctx context.Context,
733736 }
734737 }()
735738
736- activeAssets := fn .Filter (
737- selectedCommitments , func (c * AnchoredCommitment ) bool {
738- return c .Asset .ID () == assetId
739- },
740- )
739+ // Determine the highest asset version across all selected inputs. We'll
740+ // use this as a default for the output version; we'll refine per packet
741+ // later once inputs are assigned.
742+ activeAssets := selectedCommitments
741743
742744 maxVersion := asset .V0
743745 for _ , activeAsset := range activeAssets {
@@ -746,52 +748,23 @@ func (f *AssetWallet) FundBurn(ctx context.Context,
746748 }
747749 }
748750
749- // Now that we know what inputs we're going to spend, we know that by
750- // definition, we use the first input's info as the burn's PrevID. But
751- // to know which input will actually be assigned as the first input in
752- // the allocated virtual packet, we first apply the same sorting that
753- // the allocation code will also apply.
754- slices .SortFunc (activeAssets , func (a , b * AnchoredCommitment ) int {
755- return tapsend .AssetSortForInputs (* a .Asset , * b .Asset )
756- })
757- firstInput := activeAssets [0 ]
758- firstPrevID := asset.PrevID {
759- OutPoint : firstInput .AnchorPoint ,
760- ID : firstInput .Asset .ID (),
761- ScriptKey : asset .ToSerialized (
762- firstInput .Asset .ScriptKey .PubKey ,
763- ),
764- }
765- burnKey := asset .NewScriptKey (asset .DeriveBurnKey (firstPrevID ))
766- newInternalKey , err := f .cfg .KeyRing .DeriveNextKey (
767- ctx , asset .TaprootAssetsKeyFamily ,
768- )
769- if err != nil {
770- return nil , err
771- }
772-
773751 // We want both the burn output and the change to be in the same anchor
774752 // output, that's why we create the packet manually.
775753 vPkt := & tappsbt.VPacket {
776- Inputs : []* tappsbt.VInput {{
777- PrevID : asset.PrevID {
778- ID : assetId ,
779- },
780- }},
754+ // No inputs are set here; they'll be populated during funding.
781755 Outputs : []* tappsbt.VOutput {{
782756 Amount : fundDesc .Amount ,
783757 Type : tappsbt .TypeSimple ,
784758 Interactive : true ,
785759 AnchorOutputIndex : 0 ,
786760 AssetVersion : maxVersion ,
787- ScriptKey : burnKey ,
761+ // We'll set the burn script key per packet after
762+ // inputs are selected so we use a placeholder here.
763+ ScriptKey : asset .NUMSScriptKey ,
788764 }},
789765 ChainParams : f .cfg .ChainParams ,
790766 Version : tappsbt .V1 ,
791767 }
792- vPkt .Outputs [0 ].SetAnchorInternalKey (
793- newInternalKey , f .cfg .ChainParams .HDCoinType ,
794- )
795768
796769 // The virtual transaction is now ready to be further enriched with the
797770 // split commitment and other data.
@@ -803,11 +776,41 @@ func (f *AssetWallet) FundBurn(ctx context.Context,
803776 return nil , err
804777 }
805778
806- // We don't support burning by group key yet, so we only expect a single
807- // vPacket (which implies a single asset ID is involved).
808- if len (fundedPkt .VPackets ) != 1 {
809- return nil , fmt .Errorf ("expected a single vPacket, got %d" ,
810- len (fundedPkt .VPackets ))
779+ // Now that inputs are assigned, set the proper burn script key and
780+ // output versions per virtual packet, and build the outputs.
781+ for _ , pkt := range fundedPkt .VPackets {
782+ if len (pkt .Inputs ) == 0 {
783+ return nil , fmt .Errorf ("no inputs in funded burn +" +
784+ "packet" )
785+ }
786+
787+ // Determine the per-packet max asset version from its inputs.
788+ pktMaxVersion := asset .V0
789+ for _ , in := range pkt .Inputs {
790+ if in .Asset ().Version > pktMaxVersion {
791+ pktMaxVersion = in .Asset ().Version
792+ }
793+ }
794+
795+ // The burn output is the interactive, non-split output.
796+ for _ , out := range pkt .Outputs {
797+ if out .Type .IsSplitRoot () || ! out .Interactive {
798+ continue
799+ }
800+
801+ // Compute burn key from the first input's PrevID.
802+ firstPrevID := pkt .Inputs [0 ].PrevID
803+ burnKey := asset .NewScriptKey (
804+ asset .DeriveBurnKey (firstPrevID ),
805+ )
806+ out .ScriptKey = burnKey
807+ out .AssetVersion = pktMaxVersion
808+ }
809+
810+ if err := tapsend .PrepareOutputAssets (ctx , pkt ); err != nil {
811+ return nil , fmt .Errorf ("unable to prepare burn + " +
812+ "outputs: %w" , err )
813+ }
811814 }
812815
813816 // Don't release the coins we've selected, as so far we've been
0 commit comments