From 67b9239188ff0518e8660a588056bbbc276dd1d0 Mon Sep 17 00:00:00 2001 From: loic1 <17323063+loic1@users.noreply.github.com> Date: Tue, 13 Dec 2022 16:27:49 -0700 Subject: [PATCH] 114 - Adds destroy nfts transaction and associated burn command to cli (#169) * added destroy nfts transaction and associated burn command to cli * Fixed variadic argument Co-authored-by: Peter Siemens * Added fromBucketName argument Co-authored-by: Peter Siemens * Updated description Co-authored-by: Peter Siemens * Fixed comments, test description, command description, added burn command to list, added destroyNFTs transaction to generateEditionProject * Fixed argument * Fixed argument types, removed FreshmintError wrapping * Updated console log of burn command Co-authored-by: Loic Lesavre Co-authored-by: Peter Siemens --- .../transactions/destroy_nfts.template.cdc | 30 +++++++++++++ .../{transfer.test.ts => NFTContract.test.ts} | 42 ++++++++++++++++++- packages/core/contracts/NFTContract.ts | 17 ++++++++ .../core/generators/CommonNFTGenerator.ts | 16 +++++++ packages/freshmint/commands/burn.ts | 32 ++++++++++++++ packages/freshmint/flow/index.ts | 7 ++++ packages/freshmint/generate/index.ts | 16 +++++++ packages/freshmint/index.ts | 2 + 8 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 cadence/nfts/common/transactions/destroy_nfts.template.cdc rename packages/core/contracts/{transfer.test.ts => NFTContract.test.ts} (70%) create mode 100644 packages/freshmint/commands/burn.ts diff --git a/cadence/nfts/common/transactions/destroy_nfts.template.cdc b/cadence/nfts/common/transactions/destroy_nfts.template.cdc new file mode 100644 index 00000000..8eda4310 --- /dev/null +++ b/cadence/nfts/common/transactions/destroy_nfts.template.cdc @@ -0,0 +1,30 @@ +import NonFungibleToken from {{{ imports.NonFungibleToken }}} +import {{ contractName }} from {{{ contractAddress }}} + +/// This transaction withdraws multiple NFTs from the signer's collection and destroys them. +/// +transaction(ids: [UInt64], fromBucketName: String?) { + + /// A reference to the signer's {{ contractName }} collection. + /// + let collectionRef: &{{ contractName }}.Collection + + prepare(signer: AuthAccount) { + + // Derive the collection path from the bucket name + let collectionName = {{ contractName }}.makeCollectionName(bucketName: fromBucketName) + let collectionStoragePath = {{ contractName }}.getStoragePath(suffix: collectionName) + + self.collectionRef = signer.borrow<&{{ contractName }}.Collection>(from: collectionStoragePath) + ?? panic("failed to borrow collection") + } + + execute { + for id in ids { + // withdraw the NFT from the signers's collection + let nft <- self.collectionRef.withdraw(withdrawID: id) + + destroy nft + } + } +} diff --git a/packages/core/contracts/transfer.test.ts b/packages/core/contracts/NFTContract.test.ts similarity index 70% rename from packages/core/contracts/transfer.test.ts rename to packages/core/contracts/NFTContract.test.ts index 430c99e5..6bd22343 100644 --- a/packages/core/contracts/transfer.test.ts +++ b/packages/core/contracts/NFTContract.test.ts @@ -13,7 +13,7 @@ import { teardownEmulator, } from '../testHelpers'; -describe('Transfer NFTs', () => { +describe('Common NFT tests', () => { const contract = new StandardNFTContract({ name: 'StandardNFT_Transfer_Test', schema: getTestSchema(), @@ -115,4 +115,44 @@ describe('Transfer NFTs', () => { // The 3 NFTs should now be in the new account expect([onChainNFT1.id, onChainNFT2.id, onChainNFT3.id]).toEqual([nft1.id, nft2.id, nft3.id]); }); + + it('should destroy three NFTs', async () => { + // Create a new account and set up an NFT collection + const account = await createAccount(); + await client.send(contract.setupCollection(account.authorizer)); + + // Mint 3 new NFTs + const [nft1, nft2, nft3] = await client.send(contract.mintNFTs(nfts.generate(3))); + + // Destroy the 3 NFTs + await client.send(contract.destroyNFTs([nft1.id, nft2.id, nft3.id])); + + const onChainNFT1 = await client.query(contract.getNFT(account.address, nft1.id)); + const onChainNFT2 = await client.query(contract.getNFT(account.address, nft2.id)); + const onChainNFT3 = await client.query(contract.getNFT(account.address, nft3.id)); + + // The 3 NFTs should be null + expect([onChainNFT1, onChainNFT2, onChainNFT3]).toEqual([null, null, null]); + }); + + it('should destroy three NFTs from a custom bucket', async () => { + // Create a new account and set up an NFT collection + const account = await createAccount(); + await client.send(contract.setupCollection(account.authorizer)); + + const bucketName = 'foo'; + + // Mint 3 new NFTs + const [nft1, nft2, nft3] = await client.send(contract.mintNFTs(nfts.generate(3), bucketName)); + + // Destroy the 3 NFTs + await client.send(contract.destroyNFTs([nft1.id, nft2.id, nft3.id], bucketName)); + + const onChainNFT1 = await client.query(contract.getNFT(account.address, nft1.id)); + const onChainNFT2 = await client.query(contract.getNFT(account.address, nft2.id)); + const onChainNFT3 = await client.query(contract.getNFT(account.address, nft3.id)); + + // The 3 NFTs should be null + expect([onChainNFT1, onChainNFT2, onChainNFT3]).toEqual([null, null, null]); + }); }); diff --git a/packages/core/contracts/NFTContract.ts b/packages/core/contracts/NFTContract.ts index 9c469da6..a1c75fb6 100644 --- a/packages/core/contracts/NFTContract.ts +++ b/packages/core/contracts/NFTContract.ts @@ -140,6 +140,23 @@ export abstract class NFTContract { }, Transaction.VoidResult); } + destroyNFTs(ids: string[], fromBucket?: string): Transaction { + return new Transaction(({ imports }: FreshmintConfig) => { + const script = CommonNFTGenerator.destroyNFTs({ + imports, + contractName: this.name, + contractAddress: this.getAddress(), + }); + + return { + script, + args: [fcl.arg(ids, t.Array(t.UInt64)), fcl.arg(fromBucket, t.Optional(t.String))], + computeLimit: 9999, + signers: this.getSigners(), + }; + }, Transaction.VoidResult); + } + setupCollection(authorizer: TransactionAuthorizer): Transaction { return new Transaction(({ imports }: FreshmintConfig) => { const script = CommonNFTGenerator.setupCollection({ diff --git a/packages/core/generators/CommonNFTGenerator.ts b/packages/core/generators/CommonNFTGenerator.ts index 9596ec3e..d8908a42 100644 --- a/packages/core/generators/CommonNFTGenerator.ts +++ b/packages/core/generators/CommonNFTGenerator.ts @@ -78,6 +78,22 @@ export class CommonNFTGenerator extends TemplateGenerator { }); } + static destroyNFTs({ + imports, + contractName, + contractAddress, + }: { + imports: ContractImports; + contractName: string; + contractAddress: string; + }): string { + return this.generate(require('../../../cadence/nfts/common/transactions/destroy_nfts.template.cdc'), { + imports, + contractName, + contractAddress, + }); + } + static setupCollection({ imports, contractName, diff --git a/packages/freshmint/commands/burn.ts b/packages/freshmint/commands/burn.ts new file mode 100644 index 00000000..dac9ba22 --- /dev/null +++ b/packages/freshmint/commands/burn.ts @@ -0,0 +1,32 @@ +import { Command } from 'commander'; +import ora from 'ora'; +import chalk from 'chalk'; + +import { FlowGateway, FlowNetwork } from '../flow'; +import { loadConfig } from '../config'; + +export default new Command('burn') + .argument('', 'The IDs of NFTs to destroy (e.g. 3425 1235 4524 216661).') + .description('burn (i.e. destroy) one or more NFTs') + .option('-n, --network ', "Network to use. Either 'emulator', 'testnet' or 'mainnet'", 'emulator') + .action(destroyNFTs); + +async function destroyNFTs(ids: string[], { network }: { network: FlowNetwork }) { + const config = await loadConfig(); + + const flow = new FlowGateway(network, config.getContractAccount(network)); + + const spinner = ora(); + + console.log(chalk.gray('\n> flow transactions send ./cadence/transactions/destroy_nfts.cdc <...>\n')); + + spinner.start(`Destroying the NFTs...`); + + await flow.destroyNFTs(ids); + + if (ids.length == 1) { + spinner.succeed(`1 NFT was destroyed.`); + } else { + spinner.succeed(`${ids.length} NFTs were destroyed.`); + } +} diff --git a/packages/freshmint/flow/index.ts b/packages/freshmint/flow/index.ts index 9bcbe77a..c5266104 100644 --- a/packages/freshmint/flow/index.ts +++ b/packages/freshmint/flow/index.ts @@ -230,6 +230,13 @@ export class FlowGateway { return parseGetEditionResults(results); } + + async destroyNFTs(ids: string[]) { + return await this.cli.transaction('./cadence/transactions/destroy_nfts.cdc', this.signer, [ + { type: t.Array(t.UInt64), value: ids }, + { type: t.Optional(t.String), value: null }, // bucketName + ]); + } } function parseMintResults(txOutput: any): MintResult[] { diff --git a/packages/freshmint/generate/index.ts b/packages/freshmint/generate/index.ts index 4c92b59f..a3c0a9a9 100644 --- a/packages/freshmint/generate/index.ts +++ b/packages/freshmint/generate/index.ts @@ -99,6 +99,14 @@ async function generateStandardProject(dir: string, contract: ContractConfig, in await writeFile(path.resolve(dir, 'cadence/transactions/mint_with_claim_key.cdc'), mintWithClaimKeyTransaction); + const destroyNFTsTransaction = CommonNFTGenerator.destroyNFTs({ + imports: shiftedImports, + contractName: contract.name, + contractAddress, + }); + + await writeFile(path.resolve(dir, 'cadence/transactions/destroy_nfts.cdc'), destroyNFTsTransaction); + if (includeCSVFile) { await createNFTsCSVFile(dir); } @@ -160,6 +168,14 @@ async function generateEditionProject(dir: string, contract: ContractConfig, inc await writeFile(path.resolve(dir, 'cadence/transactions/mint_with_claim_key.cdc'), mintWithClaimKeyTransaction); + const destroyNFTsTransaction = CommonNFTGenerator.destroyNFTs({ + imports: shiftedImports, + contractName: contract.name, + contractAddress, + }); + + await writeFile(path.resolve(dir, 'cadence/transactions/destroy_nfts.cdc'), destroyNFTsTransaction); + if (includeCSVFile) { await createEditionsCSVFile(dir); } diff --git a/packages/freshmint/index.ts b/packages/freshmint/index.ts index e8673785..714fe3c4 100755 --- a/packages/freshmint/index.ts +++ b/packages/freshmint/index.ts @@ -11,6 +11,7 @@ import start from './commands/start'; import dev from './commands/dev'; import deploy from './commands/deploy'; import mint from './commands/mint'; +import burn from './commands/burn'; import startDrop from './commands/start-drop'; import stopDrop from './commands/stop-drop'; import gen from './commands/gen'; @@ -36,6 +37,7 @@ async function main() { addCommand(dev); addCommand(deploy); addCommand(mint); + addCommand(burn); addCommand(startDrop); addCommand(stopDrop); addCommand(gen);