@@ -22,6 +22,7 @@ import (
2222 "github.com/lightninglabs/taproot-assets/proof"
2323 "github.com/lightninglabs/taproot-assets/tapdb/sqlc"
2424 "github.com/lightninglabs/taproot-assets/tapfreighter"
25+ "github.com/lightninglabs/taproot-assets/tapgarden"
2526 "github.com/lightninglabs/taproot-assets/tappsbt"
2627 "github.com/lightninglabs/taproot-assets/tapsend"
2728 "github.com/lightningnetwork/lnd/clock"
@@ -1320,6 +1321,116 @@ func (a *AssetStore) FetchManagedUTXOs(ctx context.Context) (
13201321 return managedUtxos , nil
13211322}
13221323
1324+ // ListZeroValueAnchors returns the set of managed anchor UTXOs that only
1325+ // contain tombstone/burn commitments and therefore have zero effective asset
1326+ // value.
1327+ //
1328+ // NOTE: This implements the tapfreighter.ZeroValueAnchorLister interface.
1329+ // ListZeroValueAnchors implements both the tapfreighter and tapgarden lister
1330+ // flavors by returning tapfreighter.ZeroValueAnchor; tapgarden wraps it via a
1331+ // thin adapter in planter.
1332+ func (a * AssetStore ) ListZeroValueAnchors (ctx context.Context ) (
1333+ []* tapfreighter.ZeroValueAnchor , error ) {
1334+
1335+ managedUtxos , err := a .FetchManagedUTXOs (ctx )
1336+ if err != nil {
1337+ return nil , err
1338+ }
1339+
1340+ now := a .clock .Now ().UTC ()
1341+ anchors := make ([]* tapfreighter.ZeroValueAnchor , 0 )
1342+
1343+ for _ , utxo := range managedUtxos {
1344+ // Skip entries that are currently leased.
1345+ if len (utxo .LeaseOwner ) != 0 {
1346+ if utxo .LeaseExpiry .IsZero () || ! utxo .LeaseExpiry .Before (now ) {
1347+ continue
1348+ }
1349+ }
1350+
1351+ anchorPointBytes , err := encodeOutpoint (utxo .OutPoint )
1352+ if err != nil {
1353+ return nil , err
1354+ }
1355+
1356+ filter := QueryAssetFilters {
1357+ AnchorPoint : anchorPointBytes ,
1358+ Spent : sqlBool (false ),
1359+ Leased : sqlBool (false ),
1360+ Now : sql.NullTime {
1361+ Time : now ,
1362+ Valid : true ,
1363+ },
1364+ }
1365+
1366+ commitments , err := a .queryCommitments (ctx , filter )
1367+ var (
1368+ anchorCommitment * commitment.TapCommitment
1369+ assets []* asset.Asset
1370+ )
1371+
1372+ switch {
1373+ case errors .Is (err , tapfreighter .ErrMatchingAssetsNotFound ):
1374+ // No spendable assets anchored here, which is exactly the
1375+ // situation we want to sweep.
1376+ case err != nil :
1377+ return nil , err
1378+ default :
1379+ if len (commitments ) > 0 {
1380+ anchorCommitment = commitments [0 ].Commitment
1381+ assets = anchorCommitment .CommittedAssets ()
1382+ }
1383+ }
1384+
1385+ zeroValue := len (assets ) == 0
1386+ for _ , asset := range assets {
1387+ if asset .Amount > 0 && ! asset .IsBurn () {
1388+ zeroValue = false
1389+ break
1390+ }
1391+ }
1392+
1393+ if ! zeroValue {
1394+ continue
1395+ }
1396+
1397+ anchors = append (anchors , & tapfreighter.ZeroValueAnchor {
1398+ OutPoint : utxo .OutPoint ,
1399+ Value : utxo .OutputValue ,
1400+ InternalKey : utxo .InternalKey ,
1401+ Commitment : anchorCommitment ,
1402+ TaprootAssetRoot : append ([]byte (nil ), utxo .TaprootAssetRoot ... ),
1403+ MerkleRoot : append ([]byte (nil ), utxo .MerkleRoot ... ),
1404+ TapscriptSibling : append ([]byte (nil ), utxo .TapscriptSibling ... ),
1405+ })
1406+ }
1407+
1408+ return anchors , nil
1409+ }
1410+
1411+ // ListZeroValueAnchorsMint adapts the store to the tapgarden.MintAnchorLister
1412+ // by mapping to the tapfreighter variant.
1413+ func (a * AssetStore ) ListZeroValueAnchorsMint (ctx context.Context ) (
1414+ []* tapgarden.MintZeroValueAnchor , error ) {
1415+ anchors , err := a .ListZeroValueAnchors (ctx )
1416+ if err != nil {
1417+ return nil , err
1418+ }
1419+ out := make ([]* tapgarden.MintZeroValueAnchor , 0 , len (anchors ))
1420+ for _ , z := range anchors {
1421+ out = append (out , & tapgarden.MintZeroValueAnchor {
1422+ OutPoint : z .OutPoint ,
1423+ Value : z .Value ,
1424+ InternalKey : z .InternalKey ,
1425+ Commitment : z .Commitment ,
1426+ TaprootAssetRoot : append ([]byte (nil ), z .TaprootAssetRoot ... ),
1427+ MerkleRoot : append ([]byte (nil ), z .MerkleRoot ... ),
1428+ TapscriptSibling : append ([]byte (nil ), z .TapscriptSibling ... ),
1429+ })
1430+ }
1431+ return out , nil
1432+ }
1433+
13231434// FetchAssetProofsSizes fetches the sizes of the proofs in the db.
13241435func (a * AssetStore ) FetchAssetProofsSizes (
13251436 ctx context.Context ) ([]AssetProofSize , error ) {
@@ -2476,6 +2587,26 @@ func (a *AssetStore) LogPendingParcel(ctx context.Context,
24762587 }
24772588 }
24782589
2590+ for _ , zeroAnchor := range spend .ZeroValueAnchors {
2591+ anchorPointBytes , err := encodeOutpoint (zeroAnchor )
2592+ if err != nil {
2593+ return err
2594+ }
2595+
2596+ err = q .UpdateUTXOLease (ctx , UpdateUTXOLease {
2597+ LeaseOwner : finalLeaseOwner [:],
2598+ LeaseExpiry : sql.NullTime {
2599+ Time : finalLeaseExpiry .UTC (),
2600+ Valid : true ,
2601+ },
2602+ Outpoint : anchorPointBytes ,
2603+ })
2604+ if err != nil {
2605+ return fmt .Errorf ("unable to lease zero value " +
2606+ "anchor: %w" , err )
2607+ }
2608+ }
2609+
24792610 // Then the passive assets.
24802611 if len (spend .PassiveAssets ) > 0 {
24812612 if spend .PassiveAssetsAnchor == nil {
0 commit comments