Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 121 additions & 23 deletions cmd/commands/assets.go
Original file line number Diff line number Diff line change
Expand Up @@ -1092,6 +1092,10 @@ var burnAssetsCommand = cli.Command{
Name: assetIDName,
Usage: "the asset ID to burn units from",
},
cli.StringFlag{
Name: assetGroupKeyName,
Usage: "the group key to burn units from",
},
cli.Uint64Flag{
Name: assetAmountName,
Usage: "the amount of units to burn/destroy",
Expand All @@ -1106,15 +1110,51 @@ var burnAssetsCommand = cli.Command{
Action: burnAssets,
}

// buildAssetSpecifier validates and constructs an AssetSpecifier from
// CLI context
func buildAssetSpecifier(ctx *cli.Context) (*taprpc.AssetSpecifier, error) {
assetIDHex := ctx.String(assetIDName)
groupKeyHex := ctx.String(assetGroupKeyName)

// At least one of asset ID or group key must be provided.
if assetIDHex == "" && groupKeyHex == "" {
return nil, fmt.Errorf("either asset ID or " +
"group key must be specified")
}

assetSpecifier := &taprpc.AssetSpecifier{}

// Parse asset ID if provided.
if assetIDHex != "" {
assetIDBytes, err := hex.DecodeString(assetIDHex)
if err != nil {
return nil, fmt.Errorf("invalid asset ID: %w", err)
}

assetSpecifier.Id = assetIDBytes
}

// Parse group key if provided.
if groupKeyHex != "" {
groupKeyBytes, err := hex.DecodeString(groupKeyHex)
if err != nil {
return nil, fmt.Errorf("invalid group key: %w", err)
}

assetSpecifier.GroupKey = groupKeyBytes
}

return assetSpecifier, nil
}

func burnAssets(ctx *cli.Context) error {
if ctx.NArg() != 0 || ctx.NumFlags() == 0 {
return cli.ShowSubcommandHelp(ctx)
}

assetIDHex := ctx.String(assetIDName)
assetIDBytes, err := hex.DecodeString(assetIDHex)
assetSpecifier, err := buildAssetSpecifier(ctx)
if err != nil {
return fmt.Errorf("invalid asset ID")
return fmt.Errorf("invalid asset specifier: %w", err)
}

burnAmount := ctx.Uint64(assetAmountName)
Expand All @@ -1127,42 +1167,100 @@ func burnAssets(ctx *cli.Context) error {
defer cleanUp()

if !ctx.Bool(burnOverrideConfirmationName) {
balance, err := client.ListBalances(
ctxc, &taprpc.ListBalancesRequest{
GroupBy: &taprpc.ListBalancesRequest_AssetId{
AssetId: true,
},
AssetFilter: assetIDBytes,
},
var (
assetSpecBytes []byte
assetSpecType string
currBalance uint64
)
if err != nil {
return fmt.Errorf("unable to list current asset "+
"balances: %w", err)

// When both asset ID and group key are provided,
// prioritize asset ID for balance checking since
// it's more specific.
switch {
case len(assetSpecifier.GetId()) > 0:
assetSpecBytes = assetSpecifier.GetId()
assetSpecType = "Asset ID"
idHex := hex.EncodeToString(assetSpecBytes)

// nolint: lll
balance, err := client.ListBalances(ctxc,
&taprpc.ListBalancesRequest{
GroupBy: &taprpc.ListBalancesRequest_AssetId{
AssetId: true,
},
AssetFilter: assetSpecBytes,
},
)
if err != nil {
return fmt.Errorf("unable to list current "+
"asset balances: %w", err)
}

assetBalance, ok := balance.AssetBalances[idHex]
if !ok {
return fmt.Errorf("couldn't fetch balance for "+
"asset ID %x", assetSpecBytes)
}

currBalance = assetBalance.Balance

case len(assetSpecifier.GetGroupKey()) > 0:
assetSpecBytes = assetSpecifier.GetGroupKey()
assetSpecType = "Group Key"
gkHex := hex.EncodeToString(assetSpecBytes)

// nolint: lll
balance, err := client.ListBalances(ctxc,
&taprpc.ListBalancesRequest{
GroupBy: &taprpc.ListBalancesRequest_GroupKey{
GroupKey: true,
},
AssetFilter: assetSpecBytes,
},
)
if err != nil {
return fmt.Errorf("unable to list current "+
"asset balances: %w", err)
}

groupBalance, ok := balance.AssetGroupBalances[gkHex]
if !ok {
return fmt.Errorf("couldn't fetch balance for "+
"group key %x", assetSpecBytes)
}

currBalance = groupBalance.Balance
}

assetBalance, ok := balance.AssetBalances[assetIDHex]
if !ok {
return fmt.Errorf("couldn't fetch balance for asset %x",
assetIDBytes)
// Build confirmation message.
var specifierInfo string
if len(assetSpecifier.GetId()) > 0 &&
len(assetSpecifier.GetGroupKey()) > 0 {

specifierInfo = fmt.Sprintf("Asset ID: "+
"%x\nGroup Key: %x", assetSpecifier.GetId(),
assetSpecifier.GetGroupKey())
} else {
specifierInfo = fmt.Sprintf("%s: %x", assetSpecType,
assetSpecBytes)
}

msg := fmt.Sprintf("Please confirm destructive action.\n"+
"Asset ID: %x\nCurrent available balance: %d\n"+
"%s\nCurrent available balance: %d\n"+
"Amount to burn: %d\n Are you sure you want to "+
"irreversibly burn (destroy, remove from circulation) "+
"the specified amount of assets?\nPlease answer 'yes' "+
"or 'no' and press enter: ", assetIDBytes,
assetBalance.Balance, burnAmount)
"or 'no' and press enter: ",
specifierInfo, currBalance, burnAmount,
)

if !promptForConfirmation(msg) {
return nil
}
}

resp, err := client.BurnAsset(ctxc, &taprpc.BurnAssetRequest{
Asset: &taprpc.BurnAssetRequest_AssetId{
AssetId: assetIDBytes,
},
AssetSpecifier: assetSpecifier,
AmountToBurn: burnAmount,
ConfirmationText: taprootassets.AssetBurnConfirmationText,
})
Expand Down
9 changes: 9 additions & 0 deletions docs/release-notes/release-notes-0.8.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@

## RPC Additions

- The `BurnAsset` RPC now supports a
[new `AssetSpecifier` field](https://github.com/lightninglabs/taproot-assets/pull/1812)
that allows the user to specify the asset to burn by ID or GroupKey.
The `asset` field is now deprecated.

## tapcli Additions

# Improvements
Expand All @@ -47,6 +52,10 @@

## tapcli Updates

- The `tapcli assets burn` command now has a
[new `--group_key` flag](https://github.com/lightninglabs/taproot-assets/pull/1812)
that allows users to burn assets by group key.

## Code Health

## Breaking Changes
Expand Down
Loading
Loading