1import { ApiPromise } from "@polkadot/api";2import { Bytes, Option, u32, Vec } from '@polkadot/types-codec';3import {4 RmrkTraitsNftAccountIdOrCollectionNftTuple as NftOwner, RmrkTraitsPartEquippableList as EquippableList,5 RmrkTraitsPartPartType as PartType, RmrkTraitsResourceBasicResource as BasicResource,6 RmrkTraitsResourceComposableResource as ComposableResource, RmrkTraitsResourceResourceInfo as ResourceInfo, RmrkTraitsResourceSlotResource as SlotResource, RmrkTraitsTheme as Theme7} from "@polkadot/types/lookup";8import { IKeyringPair } from "@polkadot/types/types";9import chai from 'chai';10import chaiAsPromised from 'chai-as-promised';11import '../../interfaces/augment-api';12import privateKey from "../../substrate/privateKey";13import { executeTransaction } from "../../substrate/substrate-api";14import {15 getBase,16 getCollection,17 getCollectionsCount,18 getEquippableList,19 getNft,20 getParts,21 getResources, 22 getResourcePriority, 23 getTheme,24 NftIdTuple25} from "./fetch";26import {27 extractRmrkCoreTxResult,28 extractRmrkEquipTxResult, isCollectionPropertyExists, isNftOwnedBy, isNftPropertyExists, isTxResultSuccess, makeNftOwner,29 findResourceById, getResourceById, checkResourceStatus30} from "./helpers";3132chai.use(chaiAsPromised);33const expect = chai.expect;3435export async function createCollection(36 api: ApiPromise,37 issuerUri: string,38 metadata: string,39 max: number | null,40 symbol: string41 ): Promise<number> {42 let collectionId = 0;4344 const oldCollectionCount = await getCollectionsCount(api);45 const maxOptional = max ? max.toString() : null;46 const ss58Format = (api.registry.getChainProperties())!.toJSON().ss58Format;47 const issuer = privateKey(issuerUri, Number(ss58Format));48 const tx = api.tx.rmrkCore.createCollection(metadata, maxOptional, symbol);49 const events = await executeTransaction(api, issuer, tx);5051 const collectionResult = extractRmrkCoreTxResult(52 events, 'CollectionCreated', (data) => {53 return parseInt(data[1].toString(), 10)54 }55 );56 expect(collectionResult.success, 'Error: unable to create a collection').to.be.true;5758 collectionId = collectionResult.successData!;5960 const newCollectionCount = await getCollectionsCount(api);61 const collectionOption = await getCollection(api, collectionId);6263 expect(newCollectionCount).to.be.equal(oldCollectionCount + 1, 'Error: NFT collection count should increase');64 expect(collectionOption.isSome, 'Error: unable to fetch created NFT collection').to.be.true;6566 const collection = collectionOption.unwrap();6768 expect(collection.metadata.toUtf8()).to.be.equal(metadata, "Error: Invalid NFT collection metadata");69 expect(collection.max.isSome).to.be.equal(max !== null, "Error: Invalid NFT collection max");7071 if (collection.max.isSome) {72 expect(collection.max.unwrap().toNumber()).to.be.equal(max, "Error: Invalid NFT collection max");73 }74 expect(collection.symbol.toUtf8()).to.be.equal(symbol, "Error: Invalid NFT collection's symbol");75 expect(collection.nftsCount.toNumber()).to.be.equal(0, "Error: NFT collection shoudn't have any tokens");76 expect(collection.issuer.toString()).to.be.equal(issuer.address, "Error: Invalid NFT collection issuer");7778 return collectionId;79}8081export async function changeIssuer(82 api: ApiPromise,83 issuerUri: string,84 collectionId: number,85 newIssuer: string86) {87 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;88 const alice = privateKey(issuerUri, Number(ss58Format));89 const bob = privateKey(newIssuer, Number(ss58Format));9091 92 93 94 95 96 9798 const tx = api.tx.rmrkCore.changeCollectionIssuer(collectionId, bob.address);99 const events = await executeTransaction(api, alice, tx);100 const changeIssuerResult = extractRmrkCoreTxResult(101 events, 'IssuerChanged', (data) => {102 return parseInt(data[2].toString(), 10);103 }104 );105 expect(changeIssuerResult.success, 'Error: Unable to change NFT collection issuer').to.be.true;106 expect(changeIssuerResult.successData!, 'Error: Invalid collection id after changing the issuer')107 .to.be.eq(collectionId);108109 await getCollection(api, collectionId).then((collectionOption) => {110 const collection = collectionOption.unwrap();111 expect(collection.issuer.toString())112 .to.be.deep.eq(bob.address, 'Error: Invalid NFT collection issuer');113 });114}115116export async function deleteCollection(117 api: ApiPromise,118 issuerUri: string,119 collectionId: string120): Promise<number> {121 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;122 const issuer = privateKey(issuerUri, Number(ss58Format));123 const tx = api.tx.rmrkCore.destroyCollection(collectionId);124 const events = await executeTransaction(api, issuer, tx);125126 const collectionTxResult = extractRmrkCoreTxResult(127 events,128 "CollectionDestroy",129 (data) => {130 return parseInt(data[1].toString(), 10);131 }132 );133 expect(collectionTxResult.success, 'Error: Unable to delete NFT collection').to.be.true;134135 const collection = await getCollection(136 api,137 parseInt(collectionId, 10)138 );139 expect(collection.isEmpty, 'Error: NFT collection should be deleted').to.be.true;140141 return 0;142}143144export async function negativeDeleteCollection(145 api: ApiPromise,146 issuerUri: string,147 collectionId: string148): Promise<number> {149 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;150 const issuer = privateKey(issuerUri, Number(ss58Format));151 const tx = api.tx.rmrkCore.destroyCollection(collectionId);152 await expect(executeTransaction(api, issuer, tx)).to.be.rejected;153154 return 0;155}156157export async function setNftProperty(158 api: ApiPromise,159 issuerUri: string,160 collectionId: number,161 nftId: number,162 key: string,163 value: string164) {165 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;166 const issuer = privateKey(issuerUri, Number(ss58Format));167 const nftIdOpt = api.createType('Option<u32>', nftId);168 const tx = api.tx.rmrkCore.setProperty(169 collectionId,170 nftIdOpt,171 key,172 value173 );174 const events = await executeTransaction(api, issuer, tx);175176 const propResult = extractRmrkCoreTxResult(177 events, 'PropertySet', (data) => {178 return {179 collectionId: parseInt(data[0].toString(), 10),180 nftId: data[1] as Option<u32>,181 key: data[2] as Bytes,182 value: data[3] as Bytes183 };184 }185 );186187 expect(propResult.success, 'Error: Unable to set NFT property').to.be.true;188 const eventData = propResult.successData!;189 const eventDescription = 'from set NFT property event';190191 expect(eventData.collectionId, 'Error: Invalid collection ID ' + eventDescription)192 .to.be.equal(collectionId);193194 expect(eventData.nftId.eq(nftIdOpt), 'Error: Invalid NFT ID ' + eventDescription)195 .to.be.true;196197 expect(eventData.key.eq(key), 'Error: Invalid property key ' + eventDescription)198 .to.be.true;199200 expect(eventData.value.eq(value), 'Error: Invalid property value ' + eventDescription)201 .to.be.true;202203 expect(204 await isNftPropertyExists(api, collectionId, nftId, key, value),205 'Error: NFT property is not found'206 ).to.be.true;207}208209export async function mintNft(210 api: ApiPromise,211 issuerUri: string,212 ownerUri: string | null,213 collectionId: number,214 metadata: string,215 recipientUri: string | null = null,216 royalty: number | null = null,217 transferable: boolean = true,218 resources: {basic?: any, composable?: any, slot?: any}[] | null = null219): Promise<number> {220 let nftId = 0;221 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;222 const issuer = privateKey(issuerUri, Number(ss58Format));223 const owner = ownerUri ? privateKey(ownerUri, Number(ss58Format)).address : null;224 const recipient = recipientUri ? privateKey(recipientUri, Number(ss58Format)).address : null;225 const royaltyOptional = royalty ? royalty.toString() : null;226227 const actualOwnerUri = ownerUri ? ownerUri : issuerUri;228 const actualOwnerAddress = ownerUri ? owner : issuer.address;229230 const collectionOpt = await getCollection(api, collectionId);231232 const tx = api.tx.rmrkCore.mintNft(233 owner,234 collectionId,235 recipient,236 royaltyOptional,237 metadata,238 transferable,239 resources,240 );241242 const events = await executeTransaction(api, issuer, tx);243 const nftResult = extractRmrkCoreTxResult(244 events, 'NftMinted', (data) => {245 return parseInt(data[2].toString(), 10);246 }247 );248249 expect(nftResult.success, 'Error: Unable to mint NFT').to.be.true;250251 const newCollectionNftsCount = (await getCollection(api, collectionId))252 .unwrap()253 .nftsCount254 .toNumber();255256 const oldCollectionNftsCount = collectionOpt257 .unwrap()258 .nftsCount.toNumber();259260 expect(newCollectionNftsCount, 'Error: NFTs count should increase')261 .to.be.equal(oldCollectionNftsCount + 1);262263 nftId = nftResult.successData!;264265 const nftOption = await getNft(api, collectionId, nftId);266267 expect(nftOption.isSome, 'Error: Unable to fetch created NFT').to.be.true;268269 const nft = nftOption.unwrap();270271 expect(nft.owner.isAccountId, 'Error: NFT owner should be some user').to.be.true;272 expect(nft.owner.asAccountId.toString()).to.be.equal(actualOwnerAddress, "Error: Invalid NFT owner");273274 const isOwnedInUniques = await isNftOwnedBy(api, actualOwnerUri, collectionId, nftId);275 expect(isOwnedInUniques, `Error: created NFT is not actually owned by ${ownerUri}`)276 .to.be.true;277278 if (recipient === null && royalty === null) {279 expect(nft.royalty.isNone, 'Error: Invalid NFT recipient')280 .to.be.true;281 } else {282 expect(nft.royalty.isSome, 'Error: NFT royalty not found')283 .to.be.true;284285 const nftRoyalty = nft.royalty.unwrap();286 expect(nftRoyalty.recipient.eq(recipient), 'Error: Invalid NFT recipient')287 .to.be.true;288289 expect(nftRoyalty.amount.eq(royalty), 'Error: Invalid NFT royalty')290 .to.be.true;291 }292293 expect(nft.metadata.toUtf8()).to.be.equal(metadata, "Error: Invalid NFT metadata");294 295 const nftResources = await getResources(api, collectionId, nftId);296 if (resources == null) {297 expect(nftResources, 'Error: Invalid NFT resources').to.be.empty;298 } else {299 expect(nftResources.length).to.be.equal(resources.length);300301 for (let i = 0; i < resources.length; i++) {302 let successFindingResource = false;303 const resource = resources[i];304 305 for (let j = 0; j < nftResources.length && !successFindingResource; j++) {306 const nftResourceData = nftResources[j].toHuman();307 expect(nftResourceData.hasOwnProperty('resource'), `Error: Corrupted resource data on resource #${i}`);308 const nftResource = nftResourceData.resource!;309 type NftResourceKey = keyof typeof nftResource;310311 let typedResource = null;312 let typedNftResource = null;313314 if (resource.basic && nftResource.hasOwnProperty("Basic")) {315 typedResource = resource.basic!;316 typedNftResource = nftResource["Basic" as NftResourceKey]!;317 } else if (resource.composable && nftResource.hasOwnProperty("Composable")) {318 typedResource = resource.composable!;319 typedNftResource = nftResource["Composable" as NftResourceKey]! as any;320 if (typedResource.parts != undefined && typedResource.parts.toString() != typedNftResource.parts.toString()321 || typedResource.base != typedNftResource.base && typedResource.base != undefined) {322 continue;323 }324 } else if (resource.slot && nftResource.hasOwnProperty("Slot")) {325 typedResource = resource.slot!;326 typedNftResource = nftResource["Slot" as NftResourceKey]! as any;327 if (typedResource.slot != typedNftResource.slot && typedResource.slot != undefined 328 || typedResource.base != typedNftResource.base && typedResource.base != undefined) {329 continue;330 }331 } else {332 continue;333 }334335 if (typedResource.src != typedNftResource.src 336 || typedResource.metadata != typedNftResource.metadata 337 || typedResource.thumb != typedNftResource.thumb338 || typedResource.license != typedNftResource.license339 ) {340 continue;341 }342343 344 expect(nftResourceData.pending, `Error: Resource #${i} is pending`).to.be.false;345 expect(nftResourceData.pendingRemoval, `Error: Resource #${i} is pending removal`).to.be.false;346347 348 nftResources.splice(j, 1);349 successFindingResource = true;350 };351352 expect(successFindingResource, `Error: Couldn't find resource #${i}'s counterpart among the returned`).to.be.true;353 }354 }355356 return nftId;357}358359export async function sendNft(360 api: ApiPromise,361 expectedStatus: "pending" | "sent",362 originalOwnerUri: string,363 collectionId: number,364 nftId: number,365 newOwner: string | NftIdTuple366) {367 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;368 const originalOwner = privateKey(originalOwnerUri, Number(ss58Format));369 const newOwnerObj = makeNftOwner(api, newOwner);370371 const nftBeforeSendingOpt = await getNft(api, collectionId, nftId);372373 const tx = api.tx.rmrkCore.send(collectionId, nftId, newOwnerObj);374 const events = await executeTransaction(api, originalOwner, tx);375376 const sendResult = extractRmrkCoreTxResult(events, "NFTSent", (data) => {377 return {378 dstOwner: data[1] as NftOwner,379 collectionId: parseInt(data[2].toString(), 10),380 nftId: parseInt(data[3].toString(), 10)381 };382 });383384 expect(sendResult.success, 'Error: Unable to send NFT').to.be.true;385 const sendData = sendResult.successData!;386387 expect(sendData.dstOwner.eq(newOwnerObj), 'Error: Invalid target user (from event data)')388 .to.be.true;389390 expect(sendData.collectionId)391 .to.be.equal(collectionId, 'Error: Invalid collection ID (from event data)');392393 expect(sendData.nftId).to.be.equal(nftId, 'Error: Invalid NFT ID (from event data)');394395 expect(nftBeforeSendingOpt.isSome, 'Error: Unable to fetch NFT before sending').to.be.true;396397 const nftBeforeSending = nftBeforeSendingOpt.unwrap();398399 const nftAfterSendingOpt = await getNft(api, collectionId, nftId);400401 expect(nftAfterSendingOpt.isSome, 'Error: Unable to fetch NFT after sending').to.be.true;402403 const nftAfterSending = nftAfterSendingOpt.unwrap();404405 const isOwnedByNewOwner = await isNftOwnedBy(api, newOwner, collectionId, nftId);406 const isPending = expectedStatus === "pending";407408 expect(409 isOwnedByNewOwner,410 `Error: The NFT should be owned by ${newOwner.toString()}`411 ).to.be.true;412413 expect(nftAfterSending.royalty.eq(nftBeforeSending.royalty), 'Error: Invalid NFT royalty after sending')414 .to.be.true;415416 expect(nftAfterSending.metadata.eq(nftBeforeSending.metadata), 'Error: Invalid NFT metadata after sending')417 .to.be.true;418419 expect(nftAfterSending.equipped.eq(nftBeforeSending.equipped), 'Error: Invalid NFT equipped status after sending')420 .to.be.true;421422 expect(nftAfterSending.pending.eq(isPending), 'Error: Invalid NFT pending state')423 .to.be.true;424}425426export async function acceptNft(427 api: ApiPromise,428 issuerUri: string,429 collectionId: number,430 nftId: number,431 newOwner: string | [number, number]432) {433 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;434 const issuer = privateKey(issuerUri, Number(ss58Format));435 const newOwnerObj = makeNftOwner(api, newOwner);436437 let nftBeforeOpt = await getNft(api, collectionId, nftId);438439 const tx = api.tx.rmrkCore.acceptNft(collectionId, nftId, newOwnerObj);440 const events = await executeTransaction(api, issuer, tx);441442 const acceptResult = extractRmrkCoreTxResult(events, "NFTAccepted", (data) => {443 return {444 recipient: data[1] as NftOwner,445 collectionId: parseInt(data[2].toString(), 10),446 nftId: parseInt(data[3].toString(), 10)447 };448 });449450 expect(acceptResult.success, 'Error: Unable to accept NFT').to.be.true;451 const acceptData = acceptResult.successData!;452453 expect(acceptData.recipient.eq(newOwnerObj), 'Error: Invalid NFT recipient (from event data)')454 .to.be.true;455456 expect(acceptData.collectionId)457 .to.be.equal(collectionId, 'Error: Invalid collection ID (from event data)');458459 expect(acceptData.nftId)460 .to.be.equal(nftId, 'Error: Invalid NFT ID (from event data)');461462 const nftBefore = nftBeforeOpt.unwrap();463464 const isPendingBeforeAccept = nftBefore.pending.isTrue;465466 const nftAfter = (await getNft(api, collectionId, nftId)).unwrap();467 const isPendingAfterAccept = nftAfter.pending.isTrue;468469 expect(isPendingBeforeAccept, 'Error: NFT should be pending to be accepted')470 .to.be.true;471472 expect(isPendingAfterAccept, 'Error: NFT should NOT be pending after accept')473 .to.be.false;474475 const isOwnedInUniques = await isNftOwnedBy(api, newOwner, collectionId, nftId);476 expect(isOwnedInUniques, `Error: created NFT is not actually owned by ${newOwner.toString()}`)477 .to.be.true;478}479480export async function rejectNft(481 api: ApiPromise,482 issuerUri: string,483 collectionId: number,484 nftId: number,485) {486 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;487 const issuer = privateKey(issuerUri, Number(ss58Format));488 let nftBeforeOpt = await getNft(api, collectionId, nftId);489490 const tx = api.tx.rmrkCore.rejectNft(collectionId, nftId);491 const events = await executeTransaction(api, issuer, tx);492 const rejectResult = extractRmrkCoreTxResult(events, "NFTRejected", (data) => {493 return {494 collectionId: parseInt(data[1].toString(), 10),495 nftId: parseInt(data[2].toString(), 10)496 };497 });498499 const rejectData = rejectResult.successData!;500501 expect(rejectData.collectionId)502 .to.be.equal(collectionId, 'Error: Invalid collection ID (from event data)');503504 expect(rejectData.nftId)505 .to.be.equal(nftId, 'Error: Invalid NFT ID (from event data)');506507 const nftBefore = nftBeforeOpt.unwrap();508509 const isPendingBeforeReject = nftBefore.pending.isTrue;510511 const nftAfter = await getNft(api, collectionId, nftId);512513 expect(isPendingBeforeReject, 'Error: NFT should be pending to be rejected')514 .to.be.true;515516 expect(nftAfter.isNone, 'Error: NFT should be burned after reject')517 .to.be.true;518}519520export async function createBase(521 api: ApiPromise,522 issuerUri: string,523 baseType: string,524 symbol: string,525 parts: object[]526): Promise<number> {527 let baseId = 0;528 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;529 const issuer = privateKey(issuerUri, Number(ss58Format));530531 const partTypes = api.createType("Vec<RmrkTraitsPartPartType>", parts) as Vec<PartType>;532533 const tx = api.tx.rmrkEquip.createBase(baseType, symbol, partTypes);534 const events = await executeTransaction(api, issuer, tx);535536 const baseResult = extractRmrkEquipTxResult(537 events, 'BaseCreated', (data) => {538 return parseInt(data[1].toString(), 10);539 }540 );541542 expect(baseResult.success, 'Error: Unable to create Base')543 .to.be.true;544545 baseId = baseResult.successData!;546 const baseOptional = await getBase(api, baseId);547548 expect(baseOptional.isSome, 'Error: Unable to fetch created Base')549 .to.be.true;550551 const base = baseOptional.unwrap();552 const baseParts = await getParts(api, baseId);553554 expect(base.issuer.toString()).to.be.equal(issuer.address, "Error: Invalid Base issuer");555 expect(base.baseType.toUtf8()).to.be.equal(baseType, "Error: Invalid Base type");556 expect(base.symbol.toUtf8()).to.be.equal(symbol, "Error: Invalid Base symbol");557 expect(partTypes.eq(baseParts), "Error: Received invalid base parts").to.be.true;558559 return baseId;560}561562export async function setResourcePriorities(563 api: ApiPromise,564 issuerUri: string,565 collectionId: number,566 nftId: number,567 priorities: number[]568) {569 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;570 const issuer = privateKey(issuerUri, Number(ss58Format));571572 const prioritiesVec = api.createType('Vec<u32>', priorities);573 const tx = api.tx.rmrkCore.setPriority(collectionId, nftId, prioritiesVec);574 const events = await executeTransaction(api, issuer, tx);575576 const prioResult = extractRmrkCoreTxResult(events, 'PrioritySet', (data) => {577 return {578 collectionId: parseInt(data[0].toString(), 10),579 nftId: parseInt(data[1].toString(), 10)580 };581 });582583 expect(prioResult.success, 'Error: Unable to set resource priorities').to.be.true;584 const eventData = prioResult.successData!;585586 expect(eventData.collectionId)587 .to.be.equal(collectionId, 'Error: Invalid collection ID (set priorities event data)');588589 expect(eventData.nftId).to.be.equal(nftId, 'Error: Invalid NFT ID (set priorities event data');590591 for (var i = 0; i < priorities.length; i++) {592 const resourceId = priorities[i];593594 const fetchedPrio = await getResourcePriority(api, collectionId, nftId, resourceId);595 expect(fetchedPrio).to.be.equal(i, 'Error: Invalid priorities are set');596 }597598}599600export async function setEquippableList(601 api: ApiPromise,602 issuerUri: string,603 baseId: number,604 slotId: number,605 equippableList: "All" | "Empty" | { 'Custom': number[] }606) {607 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;608 const issuer = privateKey(issuerUri, Number(ss58Format));609 const equippable = api.createType('RmrkTraitsPartEquippableList', equippableList) as EquippableList;610611 const tx = api.tx.rmrkEquip.equippable(baseId, slotId, equippable);612 const events = await executeTransaction(api, issuer, tx);613614 const equipListResult = extractRmrkEquipTxResult(events, 'EquippablesUpdated', (data) => {615 return {616 baseId: parseInt(data[0].toString(), 10),617 slotId: parseInt(data[1].toString(), 10)618 };619 });620621 expect(equipListResult.success, 'Error: unable to update equippable list').to.be.true;622 const updateEvent = equipListResult.successData!;623624 expect(updateEvent.baseId)625 .to.be.equal(baseId, 'Error: invalid base ID from update equippable event');626627 expect(updateEvent.slotId)628 .to.be.equal(slotId, 'Error: invalid base ID from update equippable event');629630 const fetchedEquippableList = await getEquippableList(api, baseId, slotId);631632 expect(fetchedEquippableList, 'Error: unable to fetch equippable list').to.be.not.null;633 if (fetchedEquippableList) {634 expect(fetchedEquippableList)635 .to.be.deep.equal(equippableList, 'Error: invalid equippable list was set');636 }637}638639export async function addTheme(640 api: ApiPromise,641 issuerUri: string,642 baseId: number,643 themeObj: object,644 filterKeys: string[] | null = null645) {646 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;647 const issuer = privateKey(issuerUri, Number(ss58Format));648 const theme = api.createType('RmrkTraitsTheme', themeObj) as Theme;649650 const tx = api.tx.rmrkEquip.themeAdd(baseId, theme);651 const events = await executeTransaction(api, issuer, tx);652653 expect(isTxResultSuccess(events), 'Error: Unable to add Theme').to.be.true;654655 const fetchedThemeOpt = await getTheme(api, baseId, theme.name.toUtf8(), null);656657 expect(fetchedThemeOpt.isSome, 'Error: Unable to fetch theme').to.be.true;658659 const fetchedTheme = fetchedThemeOpt.unwrap();660661 expect(theme.name.eq(fetchedTheme.name), 'Error: Invalid theme name').to.be.true;662663 for (var i = 0; i < theme.properties.length; i++) {664 const property = theme.properties[i];665 const propertyKey = property.key.toUtf8();666667 const propertyFoundCount = fetchedTheme.properties.filter(668 (fetchedProp) => property.key.eq(fetchedProp.key)669 ).length;670671 expect(propertyFoundCount > 1, `Error: Too many properties with key ${propertyKey} found`)672 .to.be.false;673674 if (filterKeys) {675 const isFiltered = fetchedTheme.properties.find(676 (fetchedProp) => fetchedProp.key.eq(property.key)677 ) === undefined;678679 if (isFiltered) {680 expect(propertyFoundCount === 0, `Error: Unexpected filtered key ${propertyKey}`)681 .to.be.true;682 continue;683 }684 }685686 expect(propertyFoundCount === 1, `Error: The property with key ${propertyKey} is not found`)687 .to.be.true;688 }689}690691export async function lockCollection(692 api: ApiPromise,693 issuerUri: string,694 collectionId: number,695 max: number = 0696) {697 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;698 const issuer = privateKey(issuerUri, Number(ss58Format));699 const tx = api.tx.rmrkCore.lockCollection(collectionId);700 const events = await executeTransaction(api, issuer, tx);701 const lockResult = extractRmrkCoreTxResult(702 events, 'CollectionLocked', (data) => {703 return parseInt(data[1].toString(), 10);704 }705 );706 expect(lockResult.success, 'Error: Unable to lock a collection').to.be.true;707 expect(lockResult.successData!, 'Error: Invalid collection was locked')708 .to.be.eq(collectionId);709710 await getCollection(api, collectionId).then((collectionOption) => {711 const collection = collectionOption.unwrap();712 expect(collection.max.unwrap().toNumber()).to.be.equal(max);713 });714}715716export async function setPropertyCollection(717 api: ApiPromise,718 issuerUri: string,719 collectionId: number,720 key: string,721 value: string722) {723 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;724 const alice = privateKey(issuerUri, Number(ss58Format));725726 const tx = api.tx.rmrkCore.setProperty(collectionId, null, key, value);727 const events = await executeTransaction(api, alice, tx);728 const propResult = extractRmrkCoreTxResult(729 events, 'PropertySet', (data) => {730 return {731 collectionId: parseInt(data[0].toString(), 10),732 nftId: data[1] as Option<u32>,733 key: data[2] as Bytes,734 value: data[3] as Bytes735 };736 }737 );738739 expect(propResult.success, 'Error: Unable to set collection property').to.be.true;740 const eventData = propResult.successData!;741 const eventDescription = 'from set collection property event';742743 expect(eventData.collectionId, 'Error: Invalid collection ID ' + eventDescription)744 .to.be.equal(collectionId);745746 expect(eventData.nftId.eq(null), 'Error: Unexpected NFT ID ' + eventDescription)747 .to.be.true;748749 expect(eventData.key.eq(key), 'Error: Invalid property key ' + eventDescription)750 .to.be.true;751752 expect(eventData.value.eq(value), 'Error: Invalid property value ' + eventDescription)753 .to.be.true;754755 expect(await isCollectionPropertyExists(api, collectionId, key, value))756 .to.be.true;757}758759export async function burnNft(760 api: ApiPromise,761 issuerUri: string,762 collectionId: number,763 nftId: number764) {765 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;766 const issuer = privateKey(issuerUri, Number(ss58Format));767 const maxBurns = 10;768 const tx = api.tx.rmrkCore.burnNft(collectionId, nftId, maxBurns);769 const events = await executeTransaction(api, issuer, tx);770 const burnResult = extractRmrkCoreTxResult(771 events, 'NFTBurned', (data) => {772 return parseInt(data[1].toString(), 10);773 }774 );775776 expect(burnResult.success, 'Error: Unable to burn an NFT').to.be.true;777 expect(burnResult.successData!, 'Error: Invalid NFT was burned')778 .to.be.eq(nftId);779780 const nftBurned = await getNft(api, collectionId, nftId);781 expect(nftBurned.isSome).to.be.false;782}783784export async function acceptNftResource(785 api: ApiPromise,786 issuerUri: string,787 collectionId: number,788 nftId: number,789 resourceId: number,790) {791 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;792 const issuer = privateKey(issuerUri, Number(ss58Format));793794 const tx = api.tx.rmrkCore.acceptResource(795 collectionId,796 nftId,797 resourceId,798 );799800 const events = await executeTransaction(api, issuer, tx);801 const acceptResult = extractRmrkCoreTxResult(802 events, 'ResourceAccepted', (data) => {803 return {804 nftId: parseInt(data[0].toString(), 10),805 resourceId: parseInt(data[1].toString(), 10)806 };807 }808 )809810 expect(acceptResult.success, 'Error: Unable to accept a resource').to.be.true;811 expect(acceptResult.successData!.nftId, 'Error: Invalid NFT ID while accepting a resource')812 .to.be.eq(nftId);813 expect(acceptResult.successData!.resourceId, 'Error: Invalid resource ID while accepting a resource')814 .to.be.eq(resourceId);815816 const resource = await getResourceById(api, collectionId, nftId, resourceId);817 checkResourceStatus(resource, "added");818}819820async function executeResourceCreation(821 api: ApiPromise,822 issuer: IKeyringPair,823 tx: any,824 collectionId: number,825 nftId: number,826 expectedStatus: "pending" | "added"827): Promise<ResourceInfo> {828 const events = await executeTransaction(api, issuer, tx);829830 const resourceResult = extractRmrkCoreTxResult(831 events, 'ResourceAdded', (data) => {832 return parseInt(data[1].toString(), 10);833 }834 );835 expect(resourceResult.success, 'Error: Unable to add resource').to.be.true;836 const resourceId = resourceResult.successData!;837838 const resource = await getResourceById(api, collectionId, nftId, resourceId);839 checkResourceStatus(resource, expectedStatus);840841 return resource;842}843844export async function addNftBasicResource(845 api: ApiPromise,846 issuerUri: string,847 expectedStatus: "pending" | "added",848 collectionId: number,849 nftId: number,850 src: string | null,851 metadata: string | null,852 license: string | null,853 thumb: string | null854): Promise<number> {855 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;856 const issuer = privateKey(issuerUri, Number(ss58Format));857858 const basicResource = api.createType('RmrkTraitsResourceBasicResource', {859 src: src,860 metadata: metadata,861 license: license,862 thumb: thumb863 }) as BasicResource;864865 const tx = api.tx.rmrkCore.addBasicResource(866 collectionId,867 nftId,868 basicResource869 );870871 const resource = await executeResourceCreation(api, issuer, tx, collectionId, nftId, expectedStatus);872873 874 875 876 877 878 879 const resourceJson = resource.resource.toHuman() as any;880881 expect(resourceJson.hasOwnProperty('Basic'), 'Error: Expected basic resource type')882 .to.be.true;883884 const recvBasicRes = resourceJson['Basic'];885886 expect(recvBasicRes.src, 'Error: Invalid basic resource src')887 .to.be.eq(src);888 expect(recvBasicRes.metadata, 'Error: basic first resource metadata')889 .to.be.eq(metadata);890 expect(recvBasicRes.license, 'Error: basic first resource license')891 .to.be.eq(license);892 expect(recvBasicRes.thumb, 'Error: basic first resource thumb')893 .to.be.eq(thumb);894895 return resource.id.toNumber();896}897898export async function addNftComposableResource(899 api: ApiPromise,900 issuerUri: string,901 expectedStatus: "pending" | "added",902 collectionId: number,903 nftId: number,904 parts: number[],905 baseId: number,906 src: string | null,907 metadata: string | null,908 license: string | null,909 thumb: string | null910): Promise<number> {911 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;912 const issuer = privateKey(issuerUri, Number(ss58Format));913914 const composableResource = api.createType('RmrkTraitsResourceComposableResource', {915 parts: parts, 916 base: baseId,917 src: src,918 metadata: metadata,919 license: license,920 thumb: thumb921 }) as ComposableResource;922923 const tx = api.tx.rmrkCore.addComposableResource(924 collectionId,925 nftId,926 composableResource927 );928929 const resource = await executeResourceCreation(api, issuer, tx, collectionId, nftId, expectedStatus);930931 932 933 934 935 936 937 const resourceJson = resource.resource.toHuman() as any;938939 expect(resourceJson.hasOwnProperty('Composable'), 'Error: Expected composable resource type')940 .to.be.true;941942 const recvComposableRes = resourceJson['Composable'];943944 expect(recvComposableRes.parts.toString(), 'Error: Invalid composable resource parts')945 .to.be.eq(parts.toString());946 expect(recvComposableRes.base, 'Error: Invalid composable resource base id')947 .to.be.eq(baseId.toString());948 expect(recvComposableRes.src, 'Error: Invalid composable resource src')949 .to.be.eq(src);950 expect(recvComposableRes.metadata, 'Error: Invalid composable resource metadata')951 .to.be.eq(metadata);952 expect(recvComposableRes.license, 'Error: Invalid composable resource license')953 .to.be.eq(license);954 expect(recvComposableRes.thumb, 'Error: Invalid composable resource thumb')955 .to.be.eq(thumb);956957 return resource.id.toNumber();958}959960export async function addNftSlotResource(961 api: ApiPromise,962 issuerUri: string,963 expectedStatus: "pending" | "added",964 collectionId: number,965 nftId: number,966 baseId: number,967 slotId: number,968 src: string | null,969 metadata: string | null,970 license: string | null,971 thumb: string | null972): Promise<number> {973 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;974 const issuer = privateKey(issuerUri, Number(ss58Format));975976 const slotResource = api.createType('RmrkTraitsResourceSlotResource', {977 base: baseId,978 src: src,979 metadata,980 slot: slotId,981 license: license,982 thumb: thumb983 }) as SlotResource;984985 const tx = api.tx.rmrkCore.addSlotResource(986 collectionId,987 nftId,988 slotResource989 );990991 const resource = await executeResourceCreation(api, issuer, tx, collectionId, nftId, expectedStatus);992993 994 995 996 997 998 999 const resourceJson = resource.resource.toHuman() as any;10001001 expect(resourceJson.hasOwnProperty('Slot'), 'Error: Expected slot resource type')1002 .to.be.true;10031004 const recvSlotRes = resourceJson['Slot'];10051006 expect(recvSlotRes.base, 'Error: Invalid slot resource base id')1007 .to.be.eq(baseId.toString());1008 expect(recvSlotRes.slot, 'Error: Invalid slot resource slot id')1009 .to.be.eq(slotId.toString());1010 expect(recvSlotRes.src, 'Error: Invalid slot resource src')1011 .to.be.eq(src);1012 expect(recvSlotRes.metadata, 'Error: Invalid slot resource metadata')1013 .to.be.eq(metadata);1014 expect(recvSlotRes.license, 'Error: Invalid slot resource license')1015 .to.be.eq(license);1016 expect(recvSlotRes.thumb, 'Error: Invalid slot resource thumb')1017 .to.be.eq(thumb);10181019 return resource.id.toNumber();1020}10211022export async function equipNft(1023 api: ApiPromise,1024 issuerUri: string,1025 item: NftIdTuple,1026 equipper: NftIdTuple,1027 resource: number,1028 base: number,1029 slot: number1030) {1031 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;1032 const issuer = privateKey(issuerUri, Number(ss58Format));1033 const tx = api.tx.rmrkEquip.equip(item, equipper, resource, base, slot);1034 const events = await executeTransaction(api, issuer, tx);1035 const equipResult = extractRmrkEquipTxResult(1036 events, 'SlotEquipped', (data) => {1037 return {1038 item_collection: parseInt(data[0].toString(), 10),1039 item_nft: parseInt(data[1].toString(), 10),1040 base_id: parseInt(data[2].toString(), 10),1041 slot_id: parseInt(data[3].toString(), 10),1042 };1043 }1044 )1045 expect(equipResult.success, 'Error: Unable to equip an item').to.be.true;1046 expect(equipResult.successData!.item_collection, 'Error: Invalid item collection id')1047 .to.be.eq(item[0]);1048 expect(equipResult.successData!.item_nft, 'Error: Invalid item NFT id')1049 .to.be.eq(item[1]);1050 expect(equipResult.successData!.base_id, 'Error: Invalid base id')1051 .to.be.eq(base);1052 expect(equipResult.successData!.slot_id, 'Error: Invalid slot id')1053 .to.be.eq(slot);1054}10551056export async function unequipNft(1057 api: ApiPromise,1058 issuerUri: string,1059 item: any,1060 equipper: any,1061 resource: number,1062 base: number,1063 slot: number1064) {1065 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;1066 const issuer = privateKey(issuerUri, Number(ss58Format));1067 const tx = api.tx.rmrkEquip.equip(item, equipper, resource, base, slot);1068 const events = await executeTransaction(api, issuer, tx);10691070 const unEquipResult = extractRmrkEquipTxResult(1071 events,1072 "SlotUnequipped",1073 (data) => {1074 return {1075 item_collection: parseInt(data[0].toString(), 10),1076 item_nft: parseInt(data[1].toString(), 10),1077 base_id: parseInt(data[2].toString(), 10),1078 slot_id: parseInt(data[3].toString(), 10),1079 };1080 }1081 );10821083 expect(unEquipResult.success, 'Error: Unable to unequip an item').to.be.true;1084 expect(unEquipResult.successData!.item_collection, 'Error: Invalid item collection id')1085 .to.be.eq(item[0]);1086 expect(unEquipResult.successData!.item_nft, 'Error: Invalid item NFT id')1087 .to.be.eq(item[1]);1088 expect(unEquipResult.successData!.base_id, 'Error: Invalid base id')1089 .to.be.eq(base);1090 expect(unEquipResult.successData!.slot_id, 'Error: Invalid slot id')1091 .to.be.eq(slot);1092}10931094export async function removeNftResource(1095 api: ApiPromise,1096 expectedStatus: "pending" | "removed",1097 issuerUri: string,1098 collectionId: number,1099 nftId: number,1100 resourceId: number1101) {1102 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;1103 const issuer = privateKey(issuerUri, Number(ss58Format));11041105 const tx = api.tx.rmrkCore.removeResource(collectionId, nftId, resourceId);1106 const events = await executeTransaction(api, issuer, tx);1107 const removeResult = extractRmrkCoreTxResult(1108 events, 'ResourceRemoval', (data) => {1109 return {1110 nftId: parseInt(data[0].toString(), 10),1111 resourceId: parseInt(data[1].toString(), 10)1112 };1113 }1114 )1115 expect(removeResult.success, 'Error: Unable to remove a resource').to.be.true;1116 expect(removeResult.successData!.nftId, 'Error: Invalid NFT Id while removing a resource')1117 .to.be.eq(nftId);1118 expect(removeResult.successData!.resourceId, 'Error: Invalid resource Id while removing a resource')1119 .to.be.eq(resourceId);11201121 let afterDeleting = await findResourceById(api, collectionId, nftId, resourceId);11221123 if (expectedStatus === 'pending') {1124 expect(afterDeleting).not.to.be.null;1125 expect(afterDeleting?.pendingRemoval.isTrue).to.be.equal(true);1126 } else {1127 expect(afterDeleting).to.be.null;1128 }1129}11301131export async function acceptResourceRemoval(1132 api: ApiPromise,1133 issuerUri: string,1134 collectionId: number,1135 nftId: number,1136 resourceId: number1137) {1138 const ss58Format = api.registry.getChainProperties()!.toJSON().ss58Format;1139 const issuer = privateKey(issuerUri, Number(ss58Format));11401141 const tx = api.tx.rmrkCore.acceptResourceRemoval(collectionId, nftId, resourceId);1142 const events = await executeTransaction(api, issuer, tx);1143 const acceptResult = extractRmrkCoreTxResult(1144 events, 'ResourceRemovalAccepted', (data) => {1145 return {1146 nftId: parseInt(data[0].toString(), 10),1147 resourceId: parseInt(data[1].toString(), 10)1148 };1149 }1150 )1151 expect(acceptResult.success, 'Error: Unable to accept a resource').to.be.true;1152 expect(acceptResult.successData!.nftId, 'Error: Invalid NFT Id while accepting a resource')1153 .to.be.eq(nftId);1154 expect(acceptResult.successData!.resourceId, 'Error: Invalid resource Id while accepting a resource')1155 .to.be.eq(resourceId);11561157 let afterDeleting = await findResourceById(api, collectionId, nftId, resourceId);1158 expect(afterDeleting, 'Error: resource deleting failed').to.be.null;1159}