From a917e4649b496ffc601d66942ab4f267c51c613d Mon Sep 17 00:00:00 2001 From: gilert Date: Sat, 13 Jul 2024 19:14:15 +0200 Subject: [PATCH 1/6] test: add test for sendDeploy function --- tests/NftCollection.spec.ts | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/tests/NftCollection.spec.ts b/tests/NftCollection.spec.ts index 70cf79c..21d1d8f 100644 --- a/tests/NftCollection.spec.ts +++ b/tests/NftCollection.spec.ts @@ -1,23 +1,42 @@ -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'; describe('NftCollection', () => { let code: Cell; + let item: Cell; beforeAll(async () => { code = await compile('NftCollection'); + item = await compile('NftItem'); }); let blockchain: Blockchain; let nftCollection: SandboxContract; + let collectionOwner: SandboxContract; beforeEach(async () => { blockchain = await Blockchain.create(); - - nftCollection = blockchain.openContract(NftCollection.createFromConfig({}, code)); + collectionOwner = await blockchain.treasury("ownerWallet"); + + nftCollection = blockchain.openContract(NftCollection.createFromConfig({ + ownerAddress: collectionOwner.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" + }), + nftItemCode: item, + royaltyParams: { + royaltyFactor: Math.floor(Math.random() * 500), + royaltyBase: 1000, + royaltyAddress: collectionOwner.getSender().address as Address + } + }, code)); const deployer = await blockchain.treasury('deployer'); From aeb573ebf6db666479691cd16354d29571c13628 Mon Sep 17 00:00:00 2001 From: gilert Date: Sat, 13 Jul 2024 20:04:07 +0200 Subject: [PATCH 2/6] test: add tests for getCollectionData function --- tests/NftCollection.spec.ts | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/tests/NftCollection.spec.ts b/tests/NftCollection.spec.ts index 21d1d8f..457df00 100644 --- a/tests/NftCollection.spec.ts +++ b/tests/NftCollection.spec.ts @@ -6,11 +6,12 @@ import '@ton/test-utils'; import { compile } from '@ton/blueprint'; describe('NftCollection', () => { - let code: Cell; + let collectionCode: Cell; let item: Cell; + let collectionContent: Cell; beforeAll(async () => { - code = await compile('NftCollection'); + collectionCode = await compile('NftCollection'); item = await compile('NftItem'); }); @@ -21,22 +22,23 @@ describe('NftCollection', () => { beforeEach(async () => { blockchain = await Blockchain.create(); collectionOwner = await blockchain.treasury("ownerWallet"); + collectionContent = buildCollectionContentCell({ + name: "OnChain collection", + description: "Collection of items with onChain metadata", + image: "https://raw.githubusercontent.com/Cosmodude/Nexton/main/Nexton_Logo.jpg" + }); nftCollection = blockchain.openContract(NftCollection.createFromConfig({ ownerAddress: collectionOwner.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" - }), + collectionContent: collectionContent, nftItemCode: item, royaltyParams: { royaltyFactor: Math.floor(Math.random() * 500), royaltyBase: 1000, royaltyAddress: collectionOwner.getSender().address as Address } - }, code)); + },collectionCode)); const deployer = await blockchain.treasury('deployer'); @@ -50,8 +52,13 @@ 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 it\'s 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()); }); }); From 6ce4c9dcbfe0f3331f4a56d57092c5bba37affdf Mon Sep 17 00:00:00 2001 From: gilert Date: Sat, 13 Jul 2024 20:32:11 +0200 Subject: [PATCH 3/6] test: add tests for sendMintNft function --- tests/NftCollection.spec.ts | 66 ++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/tests/NftCollection.spec.ts b/tests/NftCollection.spec.ts index 457df00..363e3ee 100644 --- a/tests/NftCollection.spec.ts +++ b/tests/NftCollection.spec.ts @@ -4,11 +4,13 @@ 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'; describe('NftCollection', () => { let collectionCode: Cell; let item: Cell; let collectionContent: Cell; + let nftItemContent: Cell; beforeAll(async () => { collectionCode = await compile('NftCollection'); @@ -27,6 +29,11 @@ describe('NftCollection', () => { description: "Collection of items with onChain metadata", image: "https://raw.githubusercontent.com/Cosmodude/Nexton/main/Nexton_Logo.jpg" }); + nftItemContent = setItemContentCell({ + name: "OnChain", + description: "Holds onchain metadata", + image: "https://raw.githubusercontent.com/Cosmodude/Nexton/main/Nexton_Logo.jpg", + }); nftCollection = blockchain.openContract(NftCollection.createFromConfig({ ownerAddress: collectionOwner.address, @@ -52,7 +59,7 @@ describe('NftCollection', () => { }); }); - it('should get collection data after it\'s been deployed', async () => { + 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)); @@ -61,4 +68,61 @@ describe('NftCollection', () => { // check owner address expect(collection_data.ownerAddress.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 + }); + }) + }); From a8ce98ed5e6bbcb9e7e88eb31bbf6ffb0ada14a8 Mon Sep 17 00:00:00 2001 From: gilert Date: Sun, 21 Jul 2024 13:19:40 +0200 Subject: [PATCH 4/6] add getRoyaltyParams() and tests for it --- tests/NftCollection.spec.ts | 7 +++++++ wrappers/NftCollection.ts | 14 +++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/NftCollection.spec.ts b/tests/NftCollection.spec.ts index 363e3ee..059b40c 100644 --- a/tests/NftCollection.spec.ts +++ b/tests/NftCollection.spec.ts @@ -69,6 +69,13 @@ describe('NftCollection', () => { 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(); + console.log('royalties ', royalty_params); + expect(royalty_params.royaltyBase).toBe(BigInt(1000)); + 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'); 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, + } + } } From 55a0441d17523dd7ec9c07c45eacd0676a656e64 Mon Sep 17 00:00:00 2001 From: gilert Date: Sun, 21 Jul 2024 13:30:13 +0200 Subject: [PATCH 5/6] add dotenv and add .env in .gitignore --- .gitignore | 1 + package-lock.json | 4 +++- package.json | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) 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" } } From 8b6473ef7bedea30cb26b7826ad1ebe256f97694 Mon Sep 17 00:00:00 2001 From: gilert Date: Sun, 21 Jul 2024 14:07:35 +0200 Subject: [PATCH 6/6] replace hardcoded data for collection with data from .env --- scripts/deployNftCollection.ts | 13 +++++++------ tests/NftCollection.spec.ts | 16 +++++++++------- 2 files changed, 16 insertions(+), 13 deletions(-) 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 059b40c..4dac7c3 100644 --- a/tests/NftCollection.spec.ts +++ b/tests/NftCollection.spec.ts @@ -5,6 +5,8 @@ import { buildCollectionContentCell, setItemContentCell } from '../scripts/nftCo 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 collectionCode: Cell; @@ -25,9 +27,9 @@ describe('NftCollection', () => { blockchain = await Blockchain.create(); collectionOwner = await blockchain.treasury("ownerWallet"); 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! }); nftItemContent = setItemContentCell({ name: "OnChain", @@ -41,8 +43,8 @@ describe('NftCollection', () => { collectionContent: collectionContent, nftItemCode: item, royaltyParams: { - royaltyFactor: Math.floor(Math.random() * 500), - royaltyBase: 1000, + royaltyFactor: parseInt(process.env.COLLECTION_ROYALTY_PERCENT!), //Math.floor(Math.random() * 500), + royaltyBase: 100, //1000, royaltyAddress: collectionOwner.getSender().address as Address } },collectionCode)); @@ -71,8 +73,8 @@ describe('NftCollection', () => { it('should get roylty params after collection has been deployed', async () => { const royalty_params = await nftCollection.getRoyaltyParams(); - console.log('royalties ', royalty_params); - expect(royalty_params.royaltyBase).toBe(BigInt(1000)); + 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()); });