Created
October 27, 2025 22:28
-
-
Save ffranr/9b94850c4fc828a1a3e5170b1d1af199 to your computer and use it in GitHub Desktop.
burn via asset ID or asset group pub key
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // BurnAsset burns the given number of units of a given asset by sending them | |
| // to a provably un-spendable script key. Burning means irrevocably destroying | |
| // a certain number of assets, reducing the total supply of the asset. Because | |
| // burning is such a destructive and non-reversible operation, some specific | |
| // values need to be set in the request to avoid accidental burns. | |
| func (r *rpcServer) BurnAsset(ctx context.Context, | |
| in *taprpc.BurnAssetRequest) (*taprpc.BurnAssetResponse, error) { | |
| rpcsLog.Debug("Executing asset burn") | |
| if in.AmountToBurn == 0 { | |
| return nil, fmt.Errorf("amount to burn must be specified") | |
| } | |
| if in.ConfirmationText != AssetBurnConfirmationText { | |
| return nil, fmt.Errorf("invalid confirmation text, please " + | |
| "read API doc and confirm safety measure to avoid " + | |
| "accidental asset burns") | |
| } | |
| var ( | |
| assetSpec asset.Specifier | |
| err error | |
| ) | |
| // TODO(darioAnongba): Remove this switch once the deprecated asset | |
| // field is removed. Keep only the AssetSpecifier case. | |
| // Added in v0.8.0. | |
| switch { | |
| case in.Asset != nil: | |
| rpcsLog.Warnf("Using deprecated asset field, please use " + | |
| "asset_specifier instead") | |
| switch { | |
| case len(in.GetAssetId()) > 0: | |
| var assetIdBytes [32]byte | |
| copy(assetIdBytes[:], in.GetAssetId()) | |
| id := asset.ID(assetIdBytes) | |
| assetSpec = asset.NewSpecifierFromId(id) | |
| case len(in.GetAssetIdStr()) > 0: | |
| assetIDBytes, err := hex.DecodeString( | |
| in.GetAssetIdStr(), | |
| ) | |
| if err != nil { | |
| return nil, fmt.Errorf("decoding asset ID: %w", | |
| err) | |
| } | |
| var id asset.ID | |
| copy(id[:], assetIDBytes) | |
| assetSpec = asset.NewSpecifierFromId(id) | |
| default: | |
| return nil, fmt.Errorf("asset ID must be specified") | |
| } | |
| case in.AssetSpecifier != nil: | |
| assetID, groupKey, err := parseAssetSpecifier( | |
| in.AssetSpecifier.GetId(), | |
| "", | |
| in.AssetSpecifier.GetGroupKey(), | |
| "", | |
| ) | |
| if err != nil { | |
| return nil, fmt.Errorf("unable to parse asset "+ | |
| "specifier: %w", err) | |
| } | |
| assetSpec, err = asset.NewSpecifier( | |
| assetID, groupKey, nil, true, | |
| ) | |
| if err != nil { | |
| return nil, fmt.Errorf("unable to create asset "+ | |
| "specifier: %w", err) | |
| } | |
| default: | |
| return nil, fmt.Errorf("asset_specifier field unset") | |
| } | |
| // If both a group key and an asset ID are provided, ensure they | |
| // correspond. | |
| if assetSpec.HasId() && assetSpec.HasGroupPubKey() { | |
| assetID, err := assetSpec.ID().UnwrapOrErr( | |
| fmt.Errorf("code error: asset ID missing"), | |
| ) | |
| if err != nil { | |
| return nil, err | |
| } | |
| assetGroup, err := r.cfg.TapAddrBook.QueryAssetGroupByID( | |
| ctx, assetID, | |
| ) | |
| if err != nil { | |
| return nil, fmt.Errorf("querying asset group: %w", err) | |
| } | |
| // The group key retrieved from the database must match the key | |
| // provided in the request. | |
| groupPubKey, err := assetSpec.GroupKey().UnwrapOrErr( | |
| fmt.Errorf("code error: asset group key missing"), | |
| ) | |
| if err != nil { | |
| return nil, err | |
| } | |
| if !groupPubKey.IsEqual(&assetGroup.GroupPubKey) { | |
| return nil, fmt.Errorf("provided group key does " + | |
| "not match provided asset ID") | |
| } | |
| } | |
| rpcsLog.Infof("Burning asset (asset_specifier=%v, burn_amount=%d)", | |
| assetSpec, in.AmountToBurn) | |
| fundResp, err := r.cfg.AssetWallet.FundBurn( | |
| ctx, &tapsend.FundingDescriptor{ | |
| AssetSpecifier: assetSpec, | |
| Amount: in.AmountToBurn, | |
| }, | |
| ) | |
| if err != nil { | |
| return nil, fmt.Errorf("error funding burn: %w", err) | |
| } | |
| // Sign all virtual packets created for this burn | |
| // (may be more than one when burning by group key). | |
| for _, vPkt := range fundResp.VPackets { | |
| _, err = r.cfg.AssetWallet.SignVirtualPacket(ctx, vPkt) | |
| if err != nil { | |
| return nil, fmt.Errorf("error signing packet: %w", err) | |
| } | |
| } | |
| resp, err := r.cfg.ChainPorter.RequestShipment( | |
| tapfreighter.NewPreSignedParcel( | |
| fundResp.VPackets, fundResp.InputCommitments, in.Note, | |
| ), | |
| ) | |
| if err != nil { | |
| return nil, err | |
| } | |
| parcel, err := marshalOutboundParcel(resp) | |
| if err != nil { | |
| return nil, fmt.Errorf("error marshaling outbound parcel: %w", | |
| err) | |
| } | |
| var burnProofs []*taprpc.DecodedProof | |
| for _, tOut := range resp.Outputs { | |
| p, err := proof.Decode(tOut.ProofSuffix) | |
| if err != nil { | |
| return nil, fmt.Errorf("error decoding burn proof: %w", | |
| err) | |
| } | |
| if !p.Asset.IsBurn() { | |
| continue | |
| } | |
| burnProof, err := r.marshalProof(ctx, p, true, false) | |
| if err != nil { | |
| return nil, fmt.Errorf("error decoding burn proof: %w", | |
| err) | |
| } | |
| burnProofs = append(burnProofs, burnProof) | |
| } | |
| return &taprpc.BurnAssetResponse{ | |
| BurnTransfer: parcel, | |
| BurnProofs: burnProofs, | |
| }, nil | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment