diff --git a/.gitignore b/.gitignore index 14628ad..4a5c32c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules temp build +.env diff --git a/package-lock.json b/package-lock.json index d57d3d4..efe8519 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,9 @@ "": { "name": "OnChainMetadataTutorial", "version": "0.0.1", + "dependencies": { + "dotenv": "^16.4.5" + }, "devDependencies": { "@ton/blueprint": "^0.16.0", "@ton/core": "^0.56.0", @@ -2343,7 +2346,6 @@ "version": "16.4.5", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", - "dev": true, "engines": { "node": ">=12" }, diff --git a/package.json b/package.json index e05356e..97b2734 100644 --- a/package.json +++ b/package.json @@ -20,5 +20,8 @@ "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "typescript": "^5.3.3" + }, + "dependencies": { + "dotenv": "^16.4.5" } } diff --git a/scripts/deployNftCollection.ts b/scripts/deployNftCollection.ts index c04f2b0..b44392a 100644 --- a/scripts/deployNftCollection.ts +++ b/scripts/deployNftCollection.ts @@ -2,6 +2,8 @@ import { Address, toNano } from '@ton/core'; import { NftCollection } from '../wrappers/NftCollection'; import { compile, NetworkProvider } from '@ton/blueprint'; import { buildCollectionContentCell, setItemContentCell } from './nftContent/onChain'; +import * as dotenv from 'dotenv'; +dotenv.config(); const randomSeed= Math.floor(Math.random() * 10000); @@ -11,21 +13,20 @@ export async function run(provider: NetworkProvider) { ownerAddress: provider.sender().address!!, nextItemIndex: 0, collectionContent: buildCollectionContentCell({ - name: "OnChain collection", - description: "Collection of items with onChain metadata", - image: "https://raw.githubusercontent.com/Cosmodude/Nexton/main/Nexton_Logo.jpg" + name: process.env.COLLECTION_NAME!, + description: process.env.COLLECTION_DESCRIPTION!, + image: process.env.COLLECTION_IMAGE! }), nftItemCode: await compile("NftItem"), royaltyParams: { - royaltyFactor: Math.floor(Math.random() * 500), - royaltyBase: 1000, + royaltyFactor: parseInt(process.env.COLLECTION_ROYALTY_PERCENT!), + royaltyBase: 100, royaltyAddress: provider.sender().address as Address } }, await compile('NftCollection'))); console.log(provider.sender().address as Address) await nftCollection.sendDeploy(provider.sender(), toNano('0.05')); - console.log() await provider.waitForDeploy(nftCollection.address); const mint = await nftCollection.sendMintNft(provider.sender(),{ diff --git a/tests/NftCollection.spec.ts b/tests/NftCollection.spec.ts index 70cf79c..4dac7c3 100644 --- a/tests/NftCollection.spec.ts +++ b/tests/NftCollection.spec.ts @@ -1,23 +1,53 @@ -import { Blockchain, SandboxContract } from '@ton/sandbox'; -import { Cell, toNano } from '@ton/core'; +import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox'; +import { Address, Cell, toNano } from '@ton/core'; import { NftCollection } from '../wrappers/NftCollection'; +import { buildCollectionContentCell, setItemContentCell } from '../scripts/nftContent/onChain'; import '@ton/test-utils'; import { compile } from '@ton/blueprint'; +import { flattenTransaction } from '@ton/test-utils'; +import * as dotenv from 'dotenv'; +dotenv.config(); describe('NftCollection', () => { - let code: Cell; + let collectionCode: Cell; + let item: Cell; + let collectionContent: Cell; + let nftItemContent: Cell; beforeAll(async () => { - code = await compile('NftCollection'); + collectionCode = await compile('NftCollection'); + item = await compile('NftItem'); }); let blockchain: Blockchain; let nftCollection: SandboxContract; + let collectionOwner: SandboxContract; beforeEach(async () => { blockchain = await Blockchain.create(); + collectionOwner = await blockchain.treasury("ownerWallet"); + collectionContent = buildCollectionContentCell({ + name: process.env.COLLECTION_NAME!, + description: process.env.COLLECTION_DESCRIPTION!, + image: process.env.COLLECTION_IMAGE! + }); + nftItemContent = setItemContentCell({ + name: "OnChain", + description: "Holds onchain metadata", + image: "https://raw.githubusercontent.com/Cosmodude/Nexton/main/Nexton_Logo.jpg", + }); - nftCollection = blockchain.openContract(NftCollection.createFromConfig({}, code)); + nftCollection = blockchain.openContract(NftCollection.createFromConfig({ + ownerAddress: collectionOwner.address, + nextItemIndex: 0, + collectionContent: collectionContent, + nftItemCode: item, + royaltyParams: { + royaltyFactor: parseInt(process.env.COLLECTION_ROYALTY_PERCENT!), //Math.floor(Math.random() * 500), + royaltyBase: 100, //1000, + royaltyAddress: collectionOwner.getSender().address as Address + } + },collectionCode)); const deployer = await blockchain.treasury('deployer'); @@ -31,8 +61,77 @@ describe('NftCollection', () => { }); }); - it('should deploy', async () => { - // the check is done inside beforeEach - // blockchain and nftCollection are ready to use + it('should get collection data after collection has been deployed', async () => { + const collection_data = await nftCollection.getCollectionData(); + // check next_item_index + expect(collection_data).toHaveProperty("nextItemId", BigInt(0)); + // check collection content + expect(collection_data.collectionContent).toEqualCell(collectionContent); + // check owner address + expect(collection_data.ownerAddress.toString()).toBe(collectionOwner.address.toString()); + }); + + it('should get roylty params after collection has been deployed', async () => { + const royalty_params = await nftCollection.getRoyaltyParams(); + expect(royalty_params.royaltyFactor).toBe(BigInt(parseInt(process.env.COLLECTION_ROYALTY_PERCENT!))); + expect(royalty_params.royaltyBase).toBe(BigInt(100)); + expect(royalty_params.royaltyAddress.toString()).toBe(collectionOwner.address.toString()); + }); + + it ('should mint NFT item if requested by collection owner', async () => { + + const nftOwner = await blockchain.treasury('NewNFTOwner'); + const mintResult = await nftCollection.sendMintNft(collectionOwner.getSender(), { + value: toNano("0.02"), + queryId: Math.floor(Math.random() * 10000), + amount: toNano("0.014"), + itemIndex: 0, + itemOwnerAddress: nftOwner.getSender().address!!, + itemContent: nftItemContent + }); + // const arr = mintResult.transactions.map(tx => flattenTransaction(tx)); + // console.log(arr); + // check that tx to the collection address is successful + expect(mintResult.transactions).toHaveTransaction({ + to: nftCollection.address, + op: 1, + value: toNano("0.02"), + success: true + }) + // check that getItemAddressByIndex() returns nft item address + const nftItemAddr = await nftCollection.getItemAddressByIndex({ type: 'int', value: BigInt(0) }); + // check that tx to the nft item address is successful + expect(mintResult.transactions).toHaveTransaction({ + from: nftCollection.address, + to: nftItemAddr, + value: toNano("0.014"), + success: true + }) + // check that next item index has been incremented + const collection_data = await nftCollection.getCollectionData(); + expect(collection_data.nextItemId).toBe(BigInt(1)); }); + + it ('should return 401 error if mint item was requested by non-owner', async () => { + const nonOwnerWallet = await blockchain.treasury("non-owner"); + const nftOwner = await blockchain.treasury('NewNFTOwner'); + + const result = await nftCollection.sendMintNft(nonOwnerWallet.getSender(), { + value: toNano("0.02"), + queryId: Math.floor(Math.random() * 10000), + amount: toNano("0.014"), + itemIndex: 0, + itemOwnerAddress: nftOwner.getSender().address!!, + itemContent: nftItemContent + }); + // check that tx failed with 401 exit code + expect(result.transactions).toHaveTransaction({ + to: nftCollection.address, + value: toNano("0.02"), + exitCode: 401, + op: 1, + success: false + }); + }) + }); diff --git a/wrappers/NftCollection.ts b/wrappers/NftCollection.ts index 96f5815..5ed76fb 100644 --- a/wrappers/NftCollection.ts +++ b/wrappers/NftCollection.ts @@ -105,7 +105,7 @@ export class NftCollection implements Contract { .endCell() }) } - + // GETTERS async getCollectionData(provider: ContractProvider): Promise<{ nextItemId: bigint, ownerAddress: Address, @@ -130,4 +130,16 @@ export class NftCollection implements Contract { return itemAddress; } + async getRoyaltyParams(provider: ContractProvider) { + const royaltyParams = await provider.get("royalty_params", []); + const stack = royaltyParams.stack; + let numerator: bigint = stack.readBigNumber(); + let denominator: bigint = stack.readBigNumber(); + let destination: Address = stack.readAddress(); + return { + royaltyFactor: numerator, + royaltyBase: denominator, + royaltyAddress: destination, + } + } }