diff --git a/sdk/account_allowance_approve_transaction.go b/sdk/account_allowance_approve_transaction.go index f9f7ce6d..b52d8b60 100644 --- a/sdk/account_allowance_approve_transaction.go +++ b/sdk/account_allowance_approve_transaction.go @@ -214,6 +214,27 @@ func (tx *AccountAllowanceApproveTransaction) _ApproveTokenNftAllowanceAllSerial return tx } +func (tx *AccountAllowanceApproveTransaction) _DeleteTokenNftAllowanceAllSerials(tokenID TokenID, ownerAccountID *AccountID, spenderAccount AccountID) *AccountAllowanceApproveTransaction { + for _, t := range tx.nftAllowances { + if t.TokenID.String() == tokenID.String() { + if t.SpenderAccountID.String() == spenderAccount.String() { + t.SerialNumbers = []int64{} + t.AllSerials = true + return tx + } + } + } + + tx.nftAllowances = append(tx.nftAllowances, &TokenNftAllowance{ + TokenID: &tokenID, + SpenderAccountID: &spenderAccount, + SerialNumbers: []int64{}, + AllSerials: false, + OwnerAccountID: ownerAccountID, + }) + return tx +} + // AddAllTokenNftApproval // Approve allowance of non-fungible token transfers for a spender. // Spender has access to all of the owner's NFT units of type tokenId (currently @@ -230,6 +251,13 @@ func (tx *AccountAllowanceApproveTransaction) ApproveTokenNftAllowanceAllSerials return tx._ApproveTokenNftAllowanceAllSerials(tokenID, &ownerAccountID, spenderAccount) } +// DeleteTokenNftAllowanceAllSerials +// Revokes an allowance that permits a spender to transfer all of the owner's non-fungible tokens (NFTs) of a specific type (tokenId). +// This action applies to both the NFTs currently owned by the owner and any future NFTs of the same type. +func (tx *AccountAllowanceApproveTransaction) DeleteTokenNftAllowanceAllSerials(tokenID TokenID, ownerAccountID AccountID, spenderAccount AccountID) *AccountAllowanceApproveTransaction { + return tx._DeleteTokenNftAllowanceAllSerials(tokenID, &ownerAccountID, spenderAccount) +} + // List of NFT allowance records func (tx *AccountAllowanceApproveTransaction) GetTokenNftAllowances() []*TokenNftAllowance { return tx.nftAllowances diff --git a/sdk/account_allowance_approve_transaction_unit_test.go b/sdk/account_allowance_approve_transaction_unit_test.go index 53b15975..e4aebd55 100644 --- a/sdk/account_allowance_approve_transaction_unit_test.go +++ b/sdk/account_allowance_approve_transaction_unit_test.go @@ -264,3 +264,41 @@ func TestUnitAllowanceApproveTransactionFromToBytes(t *testing.T) { assert.Equal(t, tx.buildProtoBody(), txFromBytes.(AccountAllowanceApproveTransaction).buildProtoBody()) } + +func TestUnitDeleteTokenNftAllowanceAllSerials(t *testing.T) { + t.Parallel() + + transactionID := TransactionIDGenerate(AccountID{Account: 324}) + + transaction, err := NewAccountAllowanceApproveTransaction(). + SetTransactionID(transactionID). + SetNodeAccountIDs(nodeAccountID). + DeleteTokenNftAllowanceAllSerials(tokenID2, owner, spenderAccountID1). + DeleteTokenNftAllowanceAllSerials(tokenID1, owner, spenderAccountID2). + Freeze() + require.NoError(t, err) + + data := transaction.build() + + switch d := data.Data.(type) { + case *services.TransactionBody_CryptoApproveAllowance: + require.Equal(t, d.CryptoApproveAllowance.NftAllowances, []*services.NftAllowance{ + { + TokenId: tokenID2._ToProtobuf(), + Owner: owner._ToProtobuf(), + Spender: spenderAccountID1._ToProtobuf(), + SerialNumbers: []int64{}, + ApprovedForAll: &wrapperspb.BoolValue{Value: false}, + DelegatingSpender: nil, + }, + { + TokenId: tokenID1._ToProtobuf(), + Owner: owner._ToProtobuf(), + Spender: spenderAccountID2._ToProtobuf(), + SerialNumbers: []int64{}, + ApprovedForAll: &wrapperspb.BoolValue{Value: false}, + DelegatingSpender: nil, + }, + }) + } +} diff --git a/sdk/token_nft_allowance_e2e_test.go b/sdk/token_nft_allowance_e2e_test.go index dc399a4a..0dcf30b1 100644 --- a/sdk/token_nft_allowance_e2e_test.go +++ b/sdk/token_nft_allowance_e2e_test.go @@ -301,3 +301,67 @@ func TestIntegrationAfterGivenAllowanceForAllSerialsCanGiveSingleSerialToOtherAc require.NoError(t, err) require.Equal(t, env.OperatorID, info2[0].AccountID) } + +func TestIntegrationCantSendIfTokenNftSerialsDeleted(t *testing.T) { + t.Parallel() + env := NewIntegrationTestEnv(t) + defer CloseIntegrationTestEnv(env, nil) + spenderKey, err := PrivateKeyGenerateEd25519() + require.NoError(t, err) + + spenderCreate, err := NewAccountCreateTransaction(). + SetKey(spenderKey.PublicKey()). + SetInitialBalance(NewHbar(2)). + Execute(env.Client) + require.NoError(t, err) + spenderAccountReceipt, err := spenderCreate.SetValidateStatus(true).GetReceipt(env.Client) + spenderAccountId := spenderAccountReceipt.AccountID + + receiverKey, err := PrivateKeyGenerateEd25519() + require.NoError(t, err) + receiverCreate, err := NewAccountCreateTransaction(). + SetKey(receiverKey.PublicKey()). + SetInitialBalance(NewHbar(2)). + Execute(env.Client) + require.NoError(t, err) + receiverAccountReceipt, err := receiverCreate.SetValidateStatus(true).GetReceipt(env.Client) + receiverAccountId := receiverAccountReceipt.AccountID + + tokenID, err := createNft(&env) + require.NoError(t, err) + + frozenTxn, err := NewTokenAssociateTransaction().SetTokenIDs(tokenID).SetAccountID(*spenderAccountId).FreezeWith(env.Client) + require.NoError(t, err) + _, err = frozenTxn.Sign(spenderKey).Execute(env.Client) + require.NoError(t, err) + + frozenTxn, err = NewTokenAssociateTransaction().SetTokenIDs(tokenID).SetAccountID(*receiverAccountId).FreezeWith(env.Client) + require.NoError(t, err) + _, err = frozenTxn.Sign(receiverKey).Execute(env.Client) + require.NoError(t, err) + + mint, err := NewTokenMintTransaction().SetTokenID(tokenID).SetMetadata([]byte{0x01}).SetMetadata([]byte{0x02}).Execute(env.Client) + require.NoError(t, err) + mintReceipt, err := mint.SetValidateStatus(true).GetReceipt(env.Client) + require.NoError(t, err) + serials := mintReceipt.SerialNumbers + + nft1 := NftID{TokenID: tokenID, SerialNumber: serials[0]} + approveTx, err := NewAccountAllowanceApproveTransaction().ApproveTokenNftAllowanceAllSerials(nft1.TokenID, env.OperatorID, *spenderAccountId).Execute(env.Client) + require.NoError(t, err) + _, err = approveTx.SetValidateStatus(true).GetReceipt(env.Client) + require.NoError(t, err) + + resp, err := NewAccountAllowanceApproveTransaction().DeleteTokenNftAllowanceAllSerials(nft1.TokenID, env.OperatorID, *spenderAccountId).Execute(env.Client) + require.NoError(t, err) + + onBehalfOfTxId := TransactionIDGenerate(*spenderAccountId) + + frozenTransfer, err := NewTransferTransaction().AddApprovedNftTransfer(nft1, env.OperatorID, *receiverAccountId, true).SetTransactionID(onBehalfOfTxId).FreezeWith(env.Client) + require.NoError(t, err) + + resp, err = frozenTransfer.Sign(spenderKey).Execute(env.Client) + _, err = resp.SetValidateStatus(true).GetReceipt(env.Client) + require.Error(t, err) + require.Equal(t, "exceptional receipt status: SPENDER_DOES_NOT_HAVE_ALLOWANCE", err.Error()) +}