@@ -1320,6 +1320,93 @@ func (a *AssetStore) FetchManagedUTXOs(ctx context.Context) (
13201320 return managedUtxos , nil
13211321}
13221322
1323+ // ListZeroValueAnchors returns the set of managed anchor UTXOs that only
1324+ // contain tombstone/burn commitments and therefore have zero effective asset
1325+ // value.
1326+ //
1327+ // NOTE: This implements the tapfreighter.ZeroValueAnchorLister interface.
1328+ // ListZeroValueAnchors implements both the tapfreighter and tapgarden lister
1329+ // flavors by returning tapfreighter.ZeroValueAnchor; tapgarden wraps it via a
1330+ // thin adapter in planter.
1331+ func (a * AssetStore ) ListZeroValueAnchors (ctx context.Context ) (
1332+ []* tapfreighter.ZeroValueAnchor , error ) {
1333+
1334+ managedUtxos , err := a .FetchManagedUTXOs (ctx )
1335+ if err != nil {
1336+ return nil , err
1337+ }
1338+
1339+ now := a .clock .Now ().UTC ()
1340+ anchors := make ([]* tapfreighter.ZeroValueAnchor , 0 )
1341+
1342+ for _ , utxo := range managedUtxos {
1343+ // Skip entries that are currently leased.
1344+ if len (utxo .LeaseOwner ) != 0 {
1345+ if utxo .LeaseExpiry .IsZero () || ! utxo .LeaseExpiry .Before (now ) {
1346+ continue
1347+ }
1348+ }
1349+
1350+ anchorPointBytes , err := encodeOutpoint (utxo .OutPoint )
1351+ if err != nil {
1352+ return nil , err
1353+ }
1354+
1355+ filter := QueryAssetFilters {
1356+ AnchorPoint : anchorPointBytes ,
1357+ Spent : sqlBool (false ),
1358+ Leased : sqlBool (false ),
1359+ Now : sql.NullTime {
1360+ Time : now ,
1361+ Valid : true ,
1362+ },
1363+ }
1364+
1365+ commitments , err := a .queryCommitments (ctx , filter )
1366+ var (
1367+ anchorCommitment * commitment.TapCommitment
1368+ assets []* asset.Asset
1369+ )
1370+
1371+ switch {
1372+ case errors .Is (err , tapfreighter .ErrMatchingAssetsNotFound ):
1373+ // No spendable assets anchored here, which is exactly the
1374+ // situation we want to sweep.
1375+ case err != nil :
1376+ return nil , err
1377+ default :
1378+ if len (commitments ) > 0 {
1379+ anchorCommitment = commitments [0 ].Commitment
1380+ assets = anchorCommitment .CommittedAssets ()
1381+ }
1382+ }
1383+
1384+ zeroValue := len (assets ) == 0
1385+ for _ , asset := range assets {
1386+ if asset .Amount > 0 && ! asset .IsBurn () {
1387+ zeroValue = false
1388+ break
1389+ }
1390+ }
1391+
1392+ if ! zeroValue {
1393+ continue
1394+ }
1395+
1396+ anchors = append (anchors , & tapfreighter.ZeroValueAnchor {
1397+ OutPoint : utxo .OutPoint ,
1398+ Value : utxo .OutputValue ,
1399+ InternalKey : utxo .InternalKey ,
1400+ Commitment : anchorCommitment ,
1401+ TaprootAssetRoot : append ([]byte (nil ), utxo .TaprootAssetRoot ... ),
1402+ MerkleRoot : append ([]byte (nil ), utxo .MerkleRoot ... ),
1403+ TapscriptSibling : append ([]byte (nil ), utxo .TapscriptSibling ... ),
1404+ })
1405+ }
1406+
1407+ return anchors , nil
1408+ }
1409+
13231410// FetchAssetProofsSizes fetches the sizes of the proofs in the db.
13241411func (a * AssetStore ) FetchAssetProofsSizes (
13251412 ctx context.Context ) ([]AssetProofSize , error ) {
@@ -2476,6 +2563,26 @@ func (a *AssetStore) LogPendingParcel(ctx context.Context,
24762563 }
24772564 }
24782565
2566+ for _ , zeroAnchor := range spend .ZeroValueAnchors {
2567+ anchorPointBytes , err := encodeOutpoint (zeroAnchor )
2568+ if err != nil {
2569+ return err
2570+ }
2571+
2572+ err = q .UpdateUTXOLease (ctx , UpdateUTXOLease {
2573+ LeaseOwner : finalLeaseOwner [:],
2574+ LeaseExpiry : sql.NullTime {
2575+ Time : finalLeaseExpiry .UTC (),
2576+ Valid : true ,
2577+ },
2578+ Outpoint : anchorPointBytes ,
2579+ })
2580+ if err != nil {
2581+ return fmt .Errorf ("unable to lease zero value " +
2582+ "anchor: %w" , err )
2583+ }
2584+ }
2585+
24792586 // Then the passive assets.
24802587 if len (spend .PassiveAssets ) > 0 {
24812588 if spend .PassiveAssetsAnchor == nil {
0 commit comments